diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 0dafd287..20773195 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -49,5 +49,3 @@ jobs: files: ./coverage.lcov flags: rust fail_ci_if_error: true - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b8979f05..3f51bde0 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -39,7 +39,7 @@ jobs: name: Clippy with: command: clippy - args: --workspace --all-features --all-targets + args: --workspace --all-features --all-targets -- -D warnings doc: runs-on: ubuntu-latest steps: diff --git a/Cargo.lock b/Cargo.lock index 8d60a344..cf59e8ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "amplify" -version = "4.3.0" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a7164b219700bd9a4b8708bf4784b974d079c8c8b761b9f18a0c45ece214cb" +checksum = "8629db306c0bbeb0a402e2918bdcf0026b5ddb24c46460f3bf5410b350d98710" dependencies = [ "amplify_apfloat", "amplify_derive", @@ -33,9 +33,9 @@ dependencies = [ [[package]] name = "amplify_derive" -version = "4.0.0-alpha.6" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c4835e964725149d7961ec5af2ca1302f6f68c8c738b4acb06185f596c3333" +checksum = "759dcbfaf94d838367a86d493ec34ccc8aa6fe365cb7880d6bf89006de24d9c1" dependencies = [ "amplify_syn", "proc-macro2", @@ -63,6 +63,21 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "arrayref" version = "0.3.7" @@ -156,6 +171,7 @@ name = "bp-consensus" version = "0.10.10" dependencies = [ "amplify", + "chrono", "commit_verify", "secp256k1", "serde", @@ -233,6 +249,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + [[package]] name = "commit_encoding_derive" version = "0.10.0" @@ -248,13 +278,14 @@ dependencies = [ [[package]] name = "commit_verify" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caa8114b3ff20947176c8cbfd1e84e56649501eed4e33ba9205c70374b2615ae" +checksum = "91d9d6e86f6cec8d4af19a0e418bac9cb266a6dc70660bcdcdac1e0fa924e0d1" dependencies = [ "amplify", "commit_encoding_derive", "rand", + "ripemd", "serde", "sha2", "strict_encoding", @@ -267,6 +298,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + [[package]] name = "cpufeatures" version = "0.2.9" @@ -338,12 +375,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.1" @@ -357,13 +388,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] -name = "indexmap" -version = "1.9.3" +name = "iana-time-zone" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" dependencies = [ - "autocfg", - "hashbrown 0.12.3", + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", ] [[package]] @@ -373,7 +417,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", - "hashbrown 0.14.1", + "hashbrown", ] [[package]] @@ -382,6 +426,15 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -416,6 +469,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -436,9 +498,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1106fec09662ec6dd98ccac0f81cef56984d0b49f75c92d8cbad76e20c005c" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -482,6 +544,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest", +] + [[package]] name = "ryu" version = "1.0.15" @@ -560,11 +631,11 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.21" +version = "0.9.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c" +checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" dependencies = [ - "indexmap 1.9.3", + "indexmap", "itoa", "ryu", "serde", @@ -584,18 +655,18 @@ dependencies = [ [[package]] name = "single_use_seals" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae7f7cb6a68cfc99674a70a47ab790c6ede965107cd0823ed814b5e73b3bee2" +checksum = "ed7655b4b597fca10d2cf7579d3dfee1987a45342bdeecf90cab5affec1c7197" dependencies = [ "amplify_derive", ] [[package]] name = "strict_encoding" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5667c167479cd4abf9f4e02a86fcc45b0d59cd1dbef97f341ecd31b7bcd320" +checksum = "ab7b75b4af0aff9dd97b68df262bf0e807b7d007cc860fa217943f898a05a5ab" dependencies = [ "amplify", "half", @@ -604,9 +675,9 @@ dependencies = [ [[package]] name = "strict_encoding_derive" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5adae55367464f5a229bfd539682c94f870b98a220be6e61dc43f85d612e7e" +checksum = "37064ec285e2a633465eb525c8698eea51373dee889fe310e0d32df8343e7f4f" dependencies = [ "amplify_syn", "heck", @@ -617,15 +688,15 @@ dependencies = [ [[package]] name = "strict_types" -version = "1.6.2" +version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41eb7f3d2da6b413204be9605fd27de10a440875695246cf5e000eecf6d08ed" +checksum = "d10cc45e67d452cfe0d87d4714c3250190d97479af3502bbd823651bfe0f505f" dependencies = [ "amplify", "baid58", "base64", "half", - "indexmap 1.9.3", + "indexmap", "sha2", "strict_encoding", ] @@ -709,7 +780,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.0.2", + "indexmap", "serde", "serde_spanned", "toml_datetime", @@ -800,6 +871,72 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "winnow" version = "0.5.16" diff --git a/Cargo.toml b/Cargo.toml index 0844a24a..5bb192d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,11 +22,11 @@ edition = "2021" license = "Apache-2.0" [workspace.dependencies] -amplify = "4.2.0" -strict_encoding = "2.5.0" -strict_types = "1.6.2" -commit_verify = "0.10.5" -single_use_seals = "0.10.0" +amplify = "4.5.0" +strict_encoding = "2.6.1" +strict_types = "1.6.3" +commit_verify = "0.10.6" +single_use_seals = "0.10.1" bp-consensus = { version = "0.10.10", path = "consensus" } bp-dbc = { version = "0.10.10", path = "./dbc" } bp-seals = { version = "0.10.10", path = "./seals" } @@ -69,7 +69,8 @@ serde_crate = { workspace = true, optional = true } [features] default = [] -all = ["serde", "stl"] +all = ["chrono", "serde", "stl"] +chrono = ["bp-consensus/chrono"] serde = [ "serde_crate", "bp-consensus/serde", diff --git a/consensus/Cargo.toml b/consensus/Cargo.toml index f6b21d2b..305f3107 100644 --- a/consensus/Cargo.toml +++ b/consensus/Cargo.toml @@ -22,10 +22,11 @@ strict_types = { workspace = true, optional = true } commit_verify = { workspace = true } secp256k1 = { workspace = true } serde_crate = { workspace = true, optional = true } +chrono = { version = "0.4.31", optional = true } [features] -default = [] -all = ["stl", "serde"] +default = ["chrono"] +all = ["chrono", "stl", "serde"] stl = ["strict_types"] serde = [ "serde_crate", diff --git a/consensus/src/block.rs b/consensus/src/block.rs index b3a27274..5a611950 100644 --- a/consensus/src/block.rs +++ b/consensus/src/block.rs @@ -19,7 +19,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use amplify::hex::{self, FromHex}; use amplify::{Bytes32, Bytes32StrRev, Wrapper}; use crate::LIB_NAME_BITCOIN; @@ -32,20 +31,13 @@ use crate::LIB_NAME_BITCOIN; derive(Serialize, Deserialize), serde(crate = "serde_crate", transparent) )] -#[wrapper(BorrowSlice, Index, RangeOps, Debug, LowerHex, UpperHex, Display, FromStr)] +#[wrapper(BorrowSlice, Index, RangeOps, Debug, Hex, Display, FromStr)] pub struct BlockHash( #[from] #[from([u8; 32])] Bytes32StrRev, ); -impl FromHex for BlockHash { - fn from_byte_iter(iter: I) -> Result - where I: Iterator> + ExactSizeIterator + DoubleEndedIterator { - Bytes32StrRev::from_byte_iter(iter).map(Self) - } -} - #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[derive(StrictType, StrictEncode, StrictDecode, StrictDumb)] #[strict_type(lib = LIB_NAME_BITCOIN)] diff --git a/consensus/src/coding.rs b/consensus/src/coding.rs index 51439b54..46b191ab 100644 --- a/consensus/src/coding.rs +++ b/consensus/src/coding.rs @@ -19,11 +19,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::fmt::{Formatter, LowerHex, UpperHex}; use std::io::{self, Cursor, Read, Write}; use amplify::confinement::{Confined, U32}; -use amplify::hex::{self, FromHex, ToHex}; use amplify::{confinement, ByteArray, Bytes32, IoError, Wrapper}; use crate::{ @@ -83,7 +81,7 @@ impl LenVarInt for VarIntArray { #[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default, Debug, From)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] -#[wrapper(Deref, Index, RangeOps, BorrowSlice)] +#[wrapper(Deref, Index, RangeOps, BorrowSlice, Hex)] #[wrapper_mut(DerefMut, IndexMut, RangeMut, BorrowSliceMut)] #[cfg_attr( feature = "serde", @@ -100,26 +98,6 @@ impl From> for ByteStr { fn from(value: Vec) -> Self { Self(Confined::try_from(value).expect("u64 >= usize")) } } -impl LowerHex for ByteStr { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.0.as_inner().to_hex()) - } -} - -impl UpperHex for ByteStr { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.0.as_inner().to_hex().to_uppercase()) - } -} - -impl FromHex for ByteStr { - fn from_hex(s: &str) -> Result { Vec::::from_hex(s).map(Self::from) } - fn from_byte_iter(_: I) -> Result - where I: Iterator> + ExactSizeIterator + DoubleEndedIterator { - unreachable!() - } -} - impl ByteStr { pub fn len_var_int(&self) -> VarInt { VarInt(self.len() as u64) } @@ -737,10 +715,10 @@ mod tests { d: impl AsRef<[u8]>, ) -> Result { let mut cursor = Cursor::new(d.as_ref()); - Ok(T::consensus_decode(&mut cursor).map_err(|err| match err { + T::consensus_decode(&mut cursor).map_err(|err| match err { ConsensusDecodeError::Data(e) => e, ConsensusDecodeError::Io(_) => unreachable!(), - })?) + }) } #[test] @@ -882,44 +860,44 @@ mod tests { #[test] fn deserialize_int_test() { // u8 - assert_eq!(deserialize(&[58u8]).ok(), Some(58u8)); + assert_eq!(deserialize([58u8]).ok(), Some(58u8)); // u16 - assert_eq!(deserialize(&[0x01u8, 0x02]).ok(), Some(0x0201u16)); - assert_eq!(deserialize(&[0xABu8, 0xCD]).ok(), Some(0xCDABu16)); - assert_eq!(deserialize(&[0xA0u8, 0x0D]).ok(), Some(0xDA0u16)); - let failure16: Result = deserialize(&[1u8]); + assert_eq!(deserialize([0x01u8, 0x02]).ok(), Some(0x0201u16)); + assert_eq!(deserialize([0xABu8, 0xCD]).ok(), Some(0xCDABu16)); + assert_eq!(deserialize([0xA0u8, 0x0D]).ok(), Some(0xDA0u16)); + let failure16: Result = deserialize([1u8]); assert!(failure16.is_err()); // u32 - assert_eq!(deserialize(&[0xABu8, 0xCD, 0, 0]).ok(), Some(0xCDABu32)); - assert_eq!(deserialize(&[0xA0u8, 0x0D, 0xAB, 0xCD]).ok(), Some(0xCDAB0DA0u32)); + assert_eq!(deserialize([0xABu8, 0xCD, 0, 0]).ok(), Some(0xCDABu32)); + assert_eq!(deserialize([0xA0u8, 0x0D, 0xAB, 0xCD]).ok(), Some(0xCDAB0DA0u32)); - let failure32: Result = deserialize(&[1u8, 2, 3]); + let failure32: Result = deserialize([1u8, 2, 3]); assert!(failure32.is_err()); // i32 - assert_eq!(deserialize(&[0xABu8, 0xCD, 0, 0]).ok(), Some(0xCDABi32)); - assert_eq!(deserialize(&[0xA0u8, 0x0D, 0xAB, 0x2D]).ok(), Some(0x2DAB0DA0i32)); + assert_eq!(deserialize([0xABu8, 0xCD, 0, 0]).ok(), Some(0xCDABi32)); + assert_eq!(deserialize([0xA0u8, 0x0D, 0xAB, 0x2D]).ok(), Some(0x2DAB0DA0i32)); - assert_eq!(deserialize(&[0, 0, 0, 0]).ok(), Some(-0_i32)); - assert_eq!(deserialize(&[0, 0, 0, 0]).ok(), Some(0_i32)); + assert_eq!(deserialize([0, 0, 0, 0]).ok(), Some(-0_i32)); + assert_eq!(deserialize([0, 0, 0, 0]).ok(), Some(0_i32)); - assert_eq!(deserialize(&[0xFF, 0xFF, 0xFF, 0xFF]).ok(), Some(-1_i32)); - assert_eq!(deserialize(&[0xFE, 0xFF, 0xFF, 0xFF]).ok(), Some(-2_i32)); - assert_eq!(deserialize(&[0x01, 0xFF, 0xFF, 0xFF]).ok(), Some(-255_i32)); - assert_eq!(deserialize(&[0x02, 0xFF, 0xFF, 0xFF]).ok(), Some(-254_i32)); + assert_eq!(deserialize([0xFF, 0xFF, 0xFF, 0xFF]).ok(), Some(-1_i32)); + assert_eq!(deserialize([0xFE, 0xFF, 0xFF, 0xFF]).ok(), Some(-2_i32)); + assert_eq!(deserialize([0x01, 0xFF, 0xFF, 0xFF]).ok(), Some(-255_i32)); + assert_eq!(deserialize([0x02, 0xFF, 0xFF, 0xFF]).ok(), Some(-254_i32)); - let failurei32: Result = deserialize(&[1u8, 2, 3]); + let failurei32: Result = deserialize([1u8, 2, 3]); assert!(failurei32.is_err()); // u64 - assert_eq!(deserialize(&[0xABu8, 0xCD, 0, 0, 0, 0, 0, 0]).ok(), Some(0xCDABu64)); + assert_eq!(deserialize([0xABu8, 0xCD, 0, 0, 0, 0, 0, 0]).ok(), Some(0xCDABu64)); assert_eq!( - deserialize(&[0xA0u8, 0x0D, 0xAB, 0xCD, 0x99, 0, 0, 0x99]).ok(), + deserialize([0xA0u8, 0x0D, 0xAB, 0xCD, 0x99, 0, 0, 0x99]).ok(), Some(0x99000099CDAB0DA0u64) ); - let failure64: Result = deserialize(&[1u8, 2, 3, 4, 5, 6, 7]); + let failure64: Result = deserialize([1u8, 2, 3, 4, 5, 6, 7]); assert!(failure64.is_err()); } } diff --git a/consensus/src/hashtypes.rs b/consensus/src/hashtypes.rs new file mode 100644 index 00000000..0153c80c --- /dev/null +++ b/consensus/src/hashtypes.rs @@ -0,0 +1,163 @@ +// Bitcoin protocol consensus library. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use amplify::{Bytes20, Bytes32, Wrapper}; +use commit_verify::{DigestExt, Ripemd160, Sha256}; + +use crate::{ + CompressedPk, LegacyPk, RedeemScript, UncompressedPk, WitnessScript, LIB_NAME_BITCOIN, +}; + +#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)] +#[wrapper(Index, RangeOps, AsSlice, BorrowSlice, Hex, Display, FromStr)] +#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_BITCOIN)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", transparent) +)] +pub struct PubkeyHash( + #[from] + #[from([u8; 20])] + pub Bytes20, +); + +impl From for [u8; 20] { + fn from(value: PubkeyHash) -> Self { value.0.into_inner() } +} + +impl From for PubkeyHash { + fn from(pk: CompressedPk) -> Self { + let mut engine = Sha256::default(); + engine.input_raw(&pk.to_byte_array()); + let mut engine2 = Ripemd160::default(); + engine2.input_raw(&engine.finish()); + Self(engine2.finish().into()) + } +} + +impl From for PubkeyHash { + fn from(pk: UncompressedPk) -> Self { + let mut engine = Sha256::default(); + engine.input_raw(&pk.to_byte_array()); + let mut engine2 = Ripemd160::default(); + engine2.input_raw(&engine.finish()); + Self(engine2.finish().into()) + } +} + +impl From for PubkeyHash { + fn from(pk: LegacyPk) -> Self { + let mut engine = Sha256::default(); + engine.input_raw(&pk.to_vec()); + let mut engine2 = Ripemd160::default(); + engine2.input_raw(&engine.finish()); + Self(engine2.finish().into()) + } +} + +#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)] +#[wrapper(Index, RangeOps, AsSlice, BorrowSlice, Hex, Display, FromStr)] +#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_BITCOIN)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", transparent) +)] +pub struct ScriptHash( + #[from] + #[from([u8; 20])] + pub Bytes20, +); + +impl From for [u8; 20] { + fn from(value: ScriptHash) -> Self { value.0.into_inner() } +} + +impl From<&RedeemScript> for ScriptHash { + fn from(redeem_script: &RedeemScript) -> Self { + let mut engine = Sha256::default(); + engine.input_raw(redeem_script.as_slice()); + let mut engine2 = Ripemd160::default(); + engine2.input_raw(&engine.finish()); + Self(engine2.finish().into()) + } +} + +#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)] +#[wrapper(Index, RangeOps, AsSlice, BorrowSlice, Hex, Display, FromStr)] +#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_BITCOIN)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", transparent) +)] +pub struct WPubkeyHash( + #[from] + #[from([u8; 20])] + pub Bytes20, +); + +impl From for [u8; 20] { + fn from(value: WPubkeyHash) -> Self { value.0.into_inner() } +} + +impl From for WPubkeyHash { + fn from(pk: CompressedPk) -> Self { + let mut engine = Sha256::default(); + engine.input_raw(&pk.to_byte_array()); + let mut engine2 = Ripemd160::default(); + engine2.input_raw(&engine.finish()); + Self(engine2.finish().into()) + } +} + +#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)] +#[wrapper(Index, RangeOps, AsSlice, BorrowSlice, Hex, Display, FromStr)] +#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_BITCOIN)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", transparent) +)] +pub struct WScriptHash( + #[from] + #[from([u8; 32])] + pub Bytes32, +); + +impl From for [u8; 32] { + fn from(value: WScriptHash) -> Self { value.0.into_inner() } +} + +impl From<&WitnessScript> for WScriptHash { + fn from(witness_script: &WitnessScript) -> Self { + let mut engine = Sha256::default(); + engine.input_raw(witness_script.as_slice()); + let mut engine2 = Sha256::default(); + engine2.input_raw(&engine.finish()); + Self(engine2.finish().into()) + } +} diff --git a/consensus/src/lib.rs b/consensus/src/lib.rs index cd298aea..7d5fe62a 100644 --- a/consensus/src/lib.rs +++ b/consensus/src/lib.rs @@ -19,9 +19,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Version 0.10.10: -// TODO: Ensure all serde uses both string and binary version -// TODO: Move consensus-level timelocks and sequence locks from other libraries // Version 0.11.0: // TODO: Ensure script length control doesn't panic for data structures > 4GB // Version 1.0: @@ -59,10 +56,13 @@ pub extern crate secp256k1; mod block; pub mod opcodes; mod script; +mod pubkeys; mod segwit; mod taproot; mod tx; +mod hashtypes; mod sigtypes; +mod timelocks; mod util; mod weights; #[cfg(feature = "stl")] @@ -74,18 +74,23 @@ pub use coding::{ ByteStr, ConsensusDataError, ConsensusDecode, ConsensusDecodeError, ConsensusEncode, LenVarInt, VarInt, VarIntArray, }; +pub use hashtypes::{PubkeyHash, ScriptHash, WPubkeyHash, WScriptHash}; +pub use pubkeys::{CompressedPk, InvalidPubkey, LegacyPk, PubkeyParseError, UncompressedPk}; pub use script::{OpCode, RedeemScript, ScriptBytes, ScriptPubkey, SigScript}; pub use segwit::{SegwitError, Witness, WitnessProgram, WitnessScript, WitnessVer, Wtxid}; pub use sigtypes::{Bip340Sig, LegacySig, SigError, SighashFlag, SighashType}; pub use taproot::{ ControlBlock, FutureLeafVer, InternalPk, IntoTapHash, InvalidLeafVer, InvalidParityValue, - InvalidPubkey, LeafScript, LeafVer, OutputPk, Parity, TapBranchHash, TapCode, TapLeafHash, - TapMerklePath, TapNodeHash, TapScript, TaprootPk, TAPROOT_ANNEX_PREFIX, TAPROOT_LEAF_MASK, + LeafScript, LeafVer, OutputPk, Parity, TapBranchHash, TapCode, TapLeafHash, TapMerklePath, + TapNodeHash, TapScript, XOnlyPk, MIDSTATE_TAPSIGHASH, TAPROOT_ANNEX_PREFIX, TAPROOT_LEAF_MASK, TAPROOT_LEAF_TAPSCRIPT, }; +pub use timelocks::{ + InvalidTimelock, LockHeight, LockTime, LockTimestamp, SeqNo, TimelockParseError, + LOCKTIME_THRESHOLD, SEQ_NO_CSV_DISABLE_MASK, SEQ_NO_CSV_TYPE_MASK, +}; pub use tx::{ - LockTime, Outpoint, OutpointParseError, Sats, SeqNo, Tx, TxIn, TxOut, TxParseError, TxVer, - Txid, Vout, LOCKTIME_THRESHOLD, + Outpoint, OutpointParseError, Sats, Tx, TxIn, TxOut, TxParseError, TxVer, Txid, Vout, }; pub use util::{Chain, ChainParseError, NonStandardValue}; pub use weights::{VBytes, Weight, WeightUnits}; diff --git a/consensus/src/pubkeys.rs b/consensus/src/pubkeys.rs new file mode 100644 index 00000000..3b55b541 --- /dev/null +++ b/consensus/src/pubkeys.rs @@ -0,0 +1,251 @@ +// Bitcoin protocol consensus library. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::io; +use std::str::FromStr; + +use amplify::hex::FromHex; +use amplify::{hex, Bytes, Wrapper}; +use secp256k1::PublicKey; +use strict_encoding::{ + DecodeError, ReadStruct, ReadTuple, StrictDecode, StrictEncode, TypedRead, TypedWrite, + WriteStruct, +}; + +use crate::LIB_NAME_BITCOIN; + +#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)] +#[display(doc_comments)] +pub enum PubkeyParseError { + #[from] + Hex(hex::Error), + #[from] + InvalidPubkey(InvalidPubkey), +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, From, Error)] +pub enum InvalidPubkey { + #[from(secp256k1::Error)] + #[display("invalid public key")] + Unspecified, + + #[from] + #[display("invalid public key {0:x}")] + Specified(Bytes), +} + +#[derive(Wrapper, WrapperMut, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)] +#[wrapper(Deref, LowerHex, Display)] +#[wrapper_mut(DerefMut)] +#[derive(StrictType, StrictDumb)] +#[strict_type(lib = LIB_NAME_BITCOIN, dumb = Self::dumb())] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", transparent) +)] +pub struct CompressedPk(PublicKey); + +impl CompressedPk { + fn dumb() -> Self { Self(PublicKey::from_slice(&[2u8; 33]).unwrap()) } + + pub fn from_byte_array(data: [u8; 33]) -> Result> { + PublicKey::from_slice(&data) + .map(Self) + .map_err(|_| InvalidPubkey::Specified(data.into())) + } + pub fn to_byte_array(&self) -> [u8; 33] { self.0.serialize() } + + pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result> { + Ok(CompressedPk(PublicKey::from_slice(bytes.as_ref())?)) + } +} + +impl StrictEncode for CompressedPk { + fn strict_encode(&self, writer: W) -> io::Result { + let bytes = Bytes::<33>::from(self.0.serialize()); + writer.write_newtype::(&bytes) + } +} + +impl StrictDecode for CompressedPk { + fn strict_decode(reader: &mut impl TypedRead) -> Result { + reader.read_tuple(|r| { + let bytes: Bytes<33> = r.read_field()?; + PublicKey::from_slice(bytes.as_slice()) + .map(Self) + .map_err(|_| InvalidPubkey::Specified(bytes).into()) + }) + } +} + +impl FromStr for CompressedPk { + type Err = PubkeyParseError<33>; + + fn from_str(s: &str) -> Result { + let data = <[u8; 33]>::from_hex(s)?; + let pk = Self::from_byte_array(data)?; + Ok(pk) + } +} + +#[derive(Wrapper, WrapperMut, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)] +#[wrapper(Deref, LowerHex, Display)] +#[wrapper_mut(DerefMut)] +#[derive(StrictType, StrictDumb)] +#[strict_type(lib = LIB_NAME_BITCOIN, dumb = Self::dumb())] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", transparent) +)] +pub struct UncompressedPk(PublicKey); + +impl UncompressedPk { + fn dumb() -> Self { Self(PublicKey::from_slice(&[2u8; 33]).unwrap()) } + + pub fn from_byte_array(data: [u8; 65]) -> Result> { + PublicKey::from_slice(&data) + .map(Self) + .map_err(|_| InvalidPubkey::Specified(data.into())) + } + pub fn to_byte_array(&self) -> [u8; 65] { self.0.serialize_uncompressed() } +} + +impl StrictEncode for UncompressedPk { + fn strict_encode(&self, writer: W) -> io::Result { + let bytes = Bytes::<65>::from(self.0.serialize_uncompressed()); + writer.write_newtype::(&bytes) + } +} + +impl StrictDecode for UncompressedPk { + fn strict_decode(reader: &mut impl TypedRead) -> Result { + reader.read_tuple(|r| { + let bytes: Bytes<65> = r.read_field()?; + PublicKey::from_slice(bytes.as_slice()) + .map(Self) + .map_err(|_| InvalidPubkey::Specified(bytes).into()) + }) + } +} + +impl FromStr for UncompressedPk { + type Err = PubkeyParseError<65>; + + fn from_str(s: &str) -> Result { + let data = <[u8; 65]>::from_hex(s)?; + let pk = Self::from_byte_array(data)?; + Ok(pk) + } +} + +#[derive(Wrapper, WrapperMut, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] +#[wrapper(Deref, LowerHex, Display)] +#[wrapper_mut(DerefMut)] +#[derive(StrictType, StrictDumb)] +#[strict_type(lib = LIB_NAME_BITCOIN, dumb = Self::dumb())] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))] +pub struct LegacyPk { + pub compressed: bool, + #[wrap] + pub pubkey: PublicKey, +} + +impl From for LegacyPk { + fn from(pk: PublicKey) -> Self { LegacyPk::compressed(pk) } +} + +impl From for LegacyPk { + fn from(pk: CompressedPk) -> Self { LegacyPk::compressed(pk.0) } +} + +impl From for LegacyPk { + fn from(pk: UncompressedPk) -> Self { LegacyPk::uncompressed(pk.0) } +} + +impl LegacyPk { + fn dumb() -> Self { Self::compressed(PublicKey::from_slice(&[2u8; 33]).unwrap()) } + + pub const fn compressed(pubkey: PublicKey) -> Self { + LegacyPk { + compressed: true, + pubkey, + } + } + + pub const fn uncompressed(pubkey: PublicKey) -> Self { + LegacyPk { + compressed: false, + pubkey, + } + } + + pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result> { + let bytes = bytes.as_ref(); + let pubkey = PublicKey::from_slice(bytes)?; + Ok(match bytes.len() { + 33 => Self::compressed(pubkey), + 65 => Self::uncompressed(pubkey), + _ => unreachable!(), + }) + } + + pub fn to_vec(&self) -> Vec { + match self.compressed { + true => self.pubkey.serialize().to_vec(), + false => self.pubkey.serialize_uncompressed().to_vec(), + } + } +} + +impl StrictEncode for LegacyPk { + fn strict_encode(&self, writer: W) -> io::Result { + writer.write_struct::(|w| { + let bytes = Bytes::<33>::from(self.pubkey.serialize()); + Ok(w.write_field(fname!("compressed"), &self.compressed)? + .write_field(fname!("pubkey"), &bytes)? + .complete()) + }) + } +} + +impl StrictDecode for LegacyPk { + fn strict_decode(reader: &mut impl TypedRead) -> Result { + reader.read_struct(|r| { + let compressed = r.read_field(fname!("compressed"))?; + let bytes: Bytes<33> = r.read_field(fname!("pubkey"))?; + let pubkey = PublicKey::from_slice(bytes.as_slice()) + .map_err(|_| InvalidPubkey::Specified(bytes))?; + Ok(LegacyPk { compressed, pubkey }) + }) + } +} + +impl FromStr for LegacyPk { + type Err = PubkeyParseError<65>; + + fn from_str(s: &str) -> Result { + let data = Vec::::from_hex(s)?; + let pk = Self::from_bytes(data)?; + Ok(pk) + } +} diff --git a/consensus/src/script.rs b/consensus/src/script.rs index b96de673..7648d8fa 100644 --- a/consensus/src/script.rs +++ b/consensus/src/script.rs @@ -19,10 +19,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::fmt::{Formatter, LowerHex, UpperHex}; - use amplify::confinement::Confined; -use amplify::hex::{self, FromHex, ToHex}; +use commit_verify::{DigestExt, Ripemd160}; use crate::opcodes::*; use crate::{VarInt, VarIntArray, LIB_NAME_BITCOIN}; @@ -109,8 +107,8 @@ pub enum OpCode { } #[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Default)] -#[wrapper(Deref, Index, RangeOps, BorrowSlice, LowerHex, UpperHex)] -#[wrapper_mut(DerefMut, IndexMut, RangeMut, BorrowSliceMut)] +#[wrapper(Deref, AsSlice, Hex)] +#[wrapper_mut(DerefMut, AsSliceMut)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( @@ -124,23 +122,14 @@ pub struct SigScript( ScriptBytes, ); -impl FromHex for SigScript { - fn from_hex(s: &str) -> Result { ScriptBytes::from_hex(s).map(Self) } - - fn from_byte_iter(_: I) -> Result - where I: Iterator> + ExactSizeIterator + DoubleEndedIterator { - unreachable!() - } -} - impl SigScript { pub fn empty() -> Self { SigScript::default() } pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 } } #[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Default)] -#[wrapper(Deref, Index, RangeOps, BorrowSlice, LowerHex, UpperHex)] -#[wrapper_mut(DerefMut, IndexMut, RangeMut, BorrowSliceMut)] +#[wrapper(Deref, AsSlice, Hex)] +#[wrapper_mut(DerefMut, AsSliceMut)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( @@ -214,18 +203,9 @@ impl ScriptPubkey { pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 } } -impl FromHex for ScriptPubkey { - fn from_hex(s: &str) -> Result { ScriptBytes::from_hex(s).map(Self) } - - fn from_byte_iter(_: I) -> Result - where I: Iterator> + ExactSizeIterator + DoubleEndedIterator { - unreachable!() - } -} - #[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Default)] -#[wrapper(Deref, Index, RangeOps, BorrowSlice, LowerHex, UpperHex)] -#[wrapper_mut(DerefMut, IndexMut, RangeMut, BorrowSliceMut)] +#[wrapper(Deref, AsSlice, Hex)] +#[wrapper_mut(DerefMut, AsSliceMut)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( @@ -249,49 +229,26 @@ impl RedeemScript { /// Adds a single opcode to the script. pub fn push_opcode(&mut self, op_code: OpCode) { self.0.push(op_code as u8); } - pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 } -} - -impl FromHex for RedeemScript { - fn from_hex(s: &str) -> Result { ScriptBytes::from_hex(s).map(Self) } - - fn from_byte_iter(_: I) -> Result - where I: Iterator> + ExactSizeIterator + DoubleEndedIterator { - unreachable!() + pub fn to_script_pubkey(&self) -> ScriptPubkey { + let mut engine = Ripemd160::default(); + engine.input_raw(self.as_slice()); + ScriptPubkey::p2sh(engine.finish()) } + + pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 } } #[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default, Debug, From)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] -#[wrapper(Deref, Index, RangeOps, BorrowSlice)] -#[wrapper_mut(DerefMut, IndexMut, RangeMut, BorrowSliceMut)] +#[wrapper(Deref, AsSlice, Hex)] +#[wrapper_mut(DerefMut, AsSliceMut)] pub struct ScriptBytes(VarIntArray); impl From> for ScriptBytes { fn from(value: Vec) -> Self { Self(Confined::try_from(value).expect("u64 >= usize")) } } -impl LowerHex for ScriptBytes { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.0.as_inner().to_hex()) - } -} - -impl UpperHex for ScriptBytes { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.0.as_inner().to_hex().to_uppercase()) - } -} - -impl FromHex for ScriptBytes { - fn from_hex(s: &str) -> Result { Vec::::from_hex(s).map(Self::from) } - fn from_byte_iter(_: I) -> Result - where I: Iterator> + ExactSizeIterator + DoubleEndedIterator { - unreachable!() - } -} - impl ScriptBytes { /// Adds instructions to push some arbitrary data onto the stack. /// @@ -358,6 +315,7 @@ impl ScriptBytes { #[cfg(feature = "serde")] mod _serde { + use amplify::hex::{FromHex, ToHex}; use serde::{Deserialize, Serialize}; use serde_crate::de::Error; use serde_crate::{Deserializer, Serializer}; @@ -388,3 +346,37 @@ mod _serde { } } } + +#[cfg(test)] +mod test { + use amplify::hex::ToHex; + + use super::*; + + #[test] + fn script_index() { + let mut script = ScriptPubkey::op_return(&[0u8; 40]); + assert_eq!(script[0], OP_RETURN); + assert_eq!(&script[..2], &[OP_RETURN, OP_PUSHBYTES_40]); + assert_eq!(&script[40..], &[0u8, 0u8]); + assert_eq!(&script[2..4], &[0u8, 0u8]); + assert_eq!(&script[2..=3], &[0u8, 0u8]); + + script[0] = 0xFF; + script[..2].copy_from_slice(&[0xFF, 0xFF]); + script[40..].copy_from_slice(&[0xFF, 0xFF]); + script[2..4].copy_from_slice(&[0xFF, 0xFF]); + script[2..=3].copy_from_slice(&[0xFF, 0xFF]); + + assert_eq!(script[0], 0xFF); + assert_eq!(&script[..2], &[0xFF, 0xFF]); + assert_eq!(&script[40..], &[0xFF, 0xFF]); + assert_eq!(&script[2..4], &[0xFF, 0xFF]); + assert_eq!(&script[2..=3], &[0xFF, 0xFF]); + + assert_eq!( + &script.to_hex(), + "ffffffff000000000000000000000000000000000000000000000000000000000000000000000000ffff" + ); + } +} diff --git a/consensus/src/segwit.rs b/consensus/src/segwit.rs index 22fd6a1f..b4b134a9 100644 --- a/consensus/src/segwit.rs +++ b/consensus/src/segwit.rs @@ -22,11 +22,12 @@ use std::vec; use amplify::confinement::Confined; -use amplify::hex::{self, FromHex}; -use amplify::Bytes32StrRev; +use amplify::{Bytes32StrRev, Wrapper}; use crate::opcodes::*; -use crate::{OpCode, ScriptBytes, ScriptPubkey, VarIntArray, LIB_NAME_BITCOIN}; +use crate::{ + OpCode, RedeemScript, ScriptBytes, ScriptPubkey, VarIntArray, WScriptHash, LIB_NAME_BITCOIN, +}; #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display, Error)] #[display(doc_comments)] @@ -214,7 +215,7 @@ impl WitnessVer { /// Witness program as defined in BIP141. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(StrictType, StrictEncode, StrictDecode, StrictDumb)] -#[strict_type(lib = LIB_NAME_BITCOIN, dumb = {Self::new(strict_dumb!(), vec![0; 32]).unwrap()})] +#[strict_type(lib = LIB_NAME_BITCOIN, dumb = Self::dumb())] pub struct WitnessProgram { /// The witness program version. version: WitnessVer, @@ -223,6 +224,8 @@ pub struct WitnessProgram { } impl WitnessProgram { + fn dumb() -> Self { Self::new(strict_dumb!(), vec![0; 32]).unwrap() } + /// Creates a new witness program. pub fn new(version: WitnessVer, program: Vec) -> Result { let len = program.len(); @@ -294,16 +297,15 @@ impl ScriptPubkey { }; let push_opbyte = self[1]; // Second byte push opcode 2-40 bytes WitnessVer::from_op_code(ver_opcode).is_ok() - && push_opbyte >= OP_PUSHBYTES_2 - && push_opbyte <= OP_PUSHBYTES_40 + && (OP_PUSHBYTES_2..=OP_PUSHBYTES_40).contains(&push_opbyte) // Check that the rest of the script has the correct size && script_len - 2 == push_opbyte as usize } } #[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Default)] -#[wrapper(Deref, Index, RangeOps, BorrowSlice, LowerHex, UpperHex)] -#[wrapper_mut(DerefMut, IndexMut, RangeMut, BorrowSliceMut)] +#[wrapper(Deref, AsSlice, Hex)] +#[wrapper_mut(DerefMut, AsSliceMut)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( @@ -327,16 +329,14 @@ impl WitnessScript { /// Adds a single opcode to the script. pub fn push_opcode(&mut self, op_code: OpCode) { self.0.push(op_code as u8); } - pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 } -} + pub fn to_redeem_script(&self) -> RedeemScript { + let script = ScriptPubkey::p2wsh(WScriptHash::from(self)); + RedeemScript::from_inner(script.into_inner()) + } -impl FromHex for WitnessScript { - fn from_hex(s: &str) -> Result { ScriptBytes::from_hex(s).map(Self) } + pub fn to_script_pubkey(&self) -> ScriptPubkey { ScriptPubkey::p2wsh(WScriptHash::from(self)) } - fn from_byte_iter(_: I) -> Result - where I: Iterator> + ExactSizeIterator + DoubleEndedIterator { - unreachable!() - } + pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 } } #[derive(Wrapper, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, From)] @@ -349,20 +349,13 @@ impl FromHex for WitnessScript { derive(Serialize, Deserialize), serde(crate = "serde_crate", transparent) )] -#[wrapper(BorrowSlice, Index, RangeOps, Debug, LowerHex, UpperHex, Display, FromStr)] +#[wrapper(BorrowSlice, Index, RangeOps, Debug, Hex, Display, FromStr)] pub struct Wtxid( #[from] #[from([u8; 32])] Bytes32StrRev, ); -impl FromHex for Wtxid { - fn from_byte_iter(iter: I) -> Result - where I: Iterator> + ExactSizeIterator + DoubleEndedIterator { - Bytes32StrRev::from_byte_iter(iter).map(Self) - } -} - #[derive(Wrapper, Clone, Eq, PartialEq, Hash, Debug, From, Default)] #[wrapper(Deref, Index, RangeOps)] #[derive(StrictType, StrictEncode, StrictDecode)] diff --git a/consensus/src/stl.rs b/consensus/src/stl.rs index d7adf2ee..c5a9f94d 100644 --- a/consensus/src/stl.rs +++ b/consensus/src/stl.rs @@ -22,11 +22,13 @@ use strict_types::{CompileError, LibBuilder, TypeLib}; +use crate::timelocks::TimeLockInterval; use crate::{ - Bip340Sig, BlockHeader, ByteStr, Chain, ControlBlock, FutureLeafVer, InternalPk, LeafScript, - LegacySig, OpCode, OutputPk, RedeemScript, TapCode, TapLeafHash, TapNodeHash, TapScript, Tx, - VBytes, VarInt, WeightUnits, WitnessProgram, WitnessScript, WitnessVer, Wtxid, - LIB_NAME_BITCOIN, + Bip340Sig, BlockHeader, ByteStr, Chain, CompressedPk, ControlBlock, FutureLeafVer, InternalPk, + LeafScript, LegacyPk, LegacySig, LockHeight, LockTimestamp, OpCode, OutputPk, PubkeyHash, + RedeemScript, ScriptHash, TapCode, TapLeafHash, TapNodeHash, TapScript, Tx, UncompressedPk, + VBytes, VarInt, WPubkeyHash, WScriptHash, WeightUnits, WitnessProgram, WitnessScript, + WitnessVer, Wtxid, LIB_NAME_BITCOIN, }; #[deprecated(since = "0.10.8", note = "use LIB_ID_BP_TX instead")] @@ -35,7 +37,7 @@ pub const LIB_ID_BITCOIN: &str = pub const LIB_ID_BP_TX: &str = "urn:ubideco:stl:6GgF7biXPVNcus2FfQj2pQuRzau11rXApMQLfCZhojgi#money-pardon-parody"; pub const LIB_ID_BP_CONSENSUS: &str = - "urn:ubideco:stl:D42LxJBQokrGJzvoSV3E1HoriGgLzPcxuL61JymwjEqV#arena-complex-husband"; + "urn:ubideco:stl:4AXTqXq8jUDs244XbhvErdsG82Y8r9PiaPBPAmD5y9fQ#cheese-provide-morph"; #[deprecated(since = "0.10.8", note = "use _bp_tx_stl instead")] fn _bitcoin_stl() -> Result { _bp_tx_stl() } @@ -53,11 +55,18 @@ fn _bp_consensus_stl() -> Result { .transpile::() .transpile::() .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() .transpile::() .transpile::() .transpile::() .transpile::() .transpile::() + .transpile::() + .transpile::() + .transpile::() .transpile::() .transpile::() .transpile::() @@ -68,6 +77,9 @@ fn _bp_consensus_stl() -> Result { .transpile::() .transpile::() .transpile::() + .transpile::() + .transpile::() + .transpile::() .transpile::() .transpile::() .transpile::() diff --git a/consensus/src/taproot.rs b/consensus/src/taproot.rs index 4fed9430..08371d7e 100644 --- a/consensus/src/taproot.rs +++ b/consensus/src/taproot.rs @@ -24,9 +24,11 @@ use std::borrow::Borrow; use std::fmt::{self, Formatter, LowerHex, UpperHex}; use std::ops::BitXor; +use std::str::FromStr; use std::{cmp, io, slice, vec}; use amplify::confinement::{Confined, U32}; +use amplify::hex::FromHex; use amplify::{confinement, Bytes32, Wrapper}; use commit_verify::{DigestExt, Sha256}; use secp256k1::{PublicKey, Scalar, XOnlyPublicKey}; @@ -36,7 +38,10 @@ use strict_encoding::{ }; use crate::opcodes::*; -use crate::{ScriptBytes, ScriptPubkey, WitnessVer, LIB_NAME_BITCOIN}; +use crate::{ + CompressedPk, InvalidPubkey, PubkeyParseError, ScriptBytes, ScriptPubkey, WitnessVer, + LIB_NAME_BITCOIN, +}; /// The SHA-256 midstate value for the TapLeaf hash. const MIDSTATE_TAPLEAF: [u8; 7] = *b"TapLeaf"; @@ -51,109 +56,122 @@ const MIDSTATE_TAPTWEAK: [u8; 8] = *b"TapTweak"; // d129a2f3701c655d6583b6c3b941972795f4e23294fd54f4a2ae8d8547ca590b /// The SHA-256 midstate value for the TapSig hash. -#[warn(dead_code)] -const MIDSTATE_TAPSIGHASH: [u8; 10] = *b"TapSighash"; +pub const MIDSTATE_TAPSIGHASH: [u8; 10] = *b"TapSighash"; // f504a425d7f8783b1363868ae3e556586eee945dbc7888dd02a6e2c31873fe9f -#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)] -#[display("invalid public key")] -pub struct InvalidPubkey(pub Bytes32); - -impl From for DecodeError { - fn from(e: InvalidPubkey) -> Self { - DecodeError::DataIntegrityError(format!("invalid x-only public key value '{:x}'", e.0)) +impl From> for DecodeError { + fn from(e: InvalidPubkey) -> Self { + DecodeError::DataIntegrityError(format!("invalid x-only public key value '{e}'")) } } -macro_rules! dumb_key { - () => { - Self(XOnlyPublicKey::from_slice(&[1u8; 32]).unwrap()) - }; -} - /// Generic taproot x-only (BIP-340) public key - a wrapper around /// [`XOnlyPublicKey`] providing APIs compatible with the rest of the library. /// Should be used everywhere when [`InternalPk`] and [`OutputPk`] do not apply: /// as an output of BIP32 key derivation functions, inside tapscripts/ /// leafscripts etc. #[derive(Wrapper, WrapperMut, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)] -#[wrapper(Deref, LowerHex, Display, FromStr)] +#[wrapper(Deref, LowerHex, Display)] #[wrapper_mut(DerefMut)] #[derive(StrictType, StrictDumb)] -#[strict_type(lib = LIB_NAME_BITCOIN, dumb = dumb_key!())] +#[strict_type(lib = LIB_NAME_BITCOIN, dumb = Self::dumb())] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate", transparent) )] -pub struct TaprootPk(XOnlyPublicKey); +pub struct XOnlyPk(XOnlyPublicKey); + +impl XOnlyPk { + fn dumb() -> Self { Self(XOnlyPublicKey::from_slice(&[1u8; 32]).unwrap()) } -impl TaprootPk { - pub fn from_byte_array(data: [u8; 32]) -> Result { + pub fn from_byte_array(data: [u8; 32]) -> Result> { XOnlyPublicKey::from_slice(data.as_ref()) .map(Self) - .map_err(|_| InvalidPubkey(data.into())) + .map_err(|_| InvalidPubkey::Specified(data.into())) } pub fn to_byte_array(&self) -> [u8; 32] { self.0.serialize() } + + pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result> { + Ok(XOnlyPk(XOnlyPublicKey::from_slice(bytes.as_ref())?)) + } +} + +impl From for XOnlyPk { + fn from(pubkey: CompressedPk) -> Self { XOnlyPk(pubkey.x_only_public_key().0) } } -impl From for TaprootPk { - fn from(pubkey: PublicKey) -> Self { TaprootPk(pubkey.x_only_public_key().0) } +impl From for XOnlyPk { + fn from(pubkey: PublicKey) -> Self { XOnlyPk(pubkey.x_only_public_key().0) } } -impl From for [u8; 32] { - fn from(pk: TaprootPk) -> [u8; 32] { pk.to_byte_array() } +impl From for [u8; 32] { + fn from(pk: XOnlyPk) -> [u8; 32] { pk.to_byte_array() } } -impl StrictEncode for TaprootPk { +impl StrictEncode for XOnlyPk { fn strict_encode(&self, writer: W) -> io::Result { let bytes = Bytes32::from(self.0.serialize()); writer.write_newtype::(&bytes) } } -impl StrictDecode for TaprootPk { +impl StrictDecode for XOnlyPk { fn strict_decode(reader: &mut impl TypedRead) -> Result { reader.read_tuple(|r| { let bytes: Bytes32 = r.read_field()?; XOnlyPublicKey::from_slice(bytes.as_slice()) .map(Self) - .map_err(|_| InvalidPubkey(bytes).into()) + .map_err(|_| InvalidPubkey::Specified(bytes).into()) }) } } +impl FromStr for XOnlyPk { + type Err = PubkeyParseError<32>; + + fn from_str(s: &str) -> Result { + let data = <[u8; 32]>::from_hex(s)?; + let pk = Self::from_byte_array(data)?; + Ok(pk) + } +} + /// Internal taproot public key, which can be present only in key fragment /// inside taproot descriptors. #[derive(Wrapper, WrapperMut, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)] #[wrapper(Deref, LowerHex, Display, FromStr)] #[wrapper_mut(DerefMut)] #[derive(StrictType, StrictDumb)] -#[strict_type(lib = LIB_NAME_BITCOIN, dumb = dumb_key!())] +#[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate", transparent) )] -pub struct InternalPk(XOnlyPublicKey); +pub struct InternalPk(XOnlyPk); impl InternalPk { #[inline] - pub fn from_unchecked(pk: TaprootPk) -> Self { Self(pk.0) } + pub fn from_unchecked(pk: XOnlyPk) -> Self { Self(pk) } - pub fn from_byte_array(data: [u8; 32]) -> Result { - XOnlyPublicKey::from_slice(&data) - .map(Self) - .map_err(|_| InvalidPubkey(data.into())) + #[inline] + pub fn from_byte_array(data: [u8; 32]) -> Result> { + XOnlyPk::from_byte_array(data).map(Self) } - pub fn to_byte_array(&self) -> [u8; 32] { self.0.serialize() } + pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result> { + XOnlyPk::from_bytes(bytes).map(Self) + } + + #[inline] + pub fn to_byte_array(&self) -> [u8; 32] { self.0.to_byte_array() } #[deprecated(since = "0.10.9", note = "use to_output_pk")] pub fn to_output_key(&self, merkle_root: Option) -> XOnlyPublicKey { let (pk, _) = self.to_output_pk(merkle_root); - pk.0 + pk.0.0 } pub fn to_output_pk(&self, merkle_root: Option) -> (OutputPk, Parity) { @@ -161,7 +179,7 @@ impl InternalPk { // always hash the key engine.input_raw(&self.0.serialize()); if let Some(merkle_root) = merkle_root { - engine.input_raw(merkle_root.into_tap_hash().as_slice()); + engine.input_raw(merkle_root.into_tap_hash().as_ref()); } let tweak = Scalar::from_be_bytes(engine.finish()).expect("hash value greater than curve order"); @@ -175,7 +193,7 @@ impl InternalPk { tweaked_parity, tweak )); - (OutputPk(output_key), tweaked_parity.into()) + (OutputPk(XOnlyPk(output_key)), tweaked_parity.into()) } } @@ -183,6 +201,7 @@ impl From for [u8; 32] { fn from(pk: InternalPk) -> [u8; 32] { pk.to_byte_array() } } +// TODO: Remove custom implementation in v0.11 impl StrictEncode for InternalPk { fn strict_encode(&self, writer: W) -> io::Result { let bytes = Bytes32::from(self.0.serialize()); @@ -194,9 +213,8 @@ impl StrictDecode for InternalPk { fn strict_decode(reader: &mut impl TypedRead) -> Result { reader.read_tuple(|r| { let bytes: Bytes32 = r.read_field()?; - XOnlyPublicKey::from_slice(bytes.as_slice()) - .map(Self) - .map_err(|_| InvalidPubkey(bytes).into()) + let pk = XOnlyPk::from_byte_array(bytes.to_byte_array())?; + Ok(InternalPk(pk)) }) } } @@ -207,48 +225,36 @@ impl StrictDecode for InternalPk { #[derive(Wrapper, WrapperMut, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)] #[wrapper(Deref, LowerHex, Display, FromStr)] #[wrapper_mut(DerefMut)] -#[derive(StrictType, StrictDumb)] -#[strict_type(lib = LIB_NAME_BITCOIN, dumb = dumb_key!())] +#[derive(StrictType, StrictEncode, StrictDecode, StrictDumb)] +#[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate", transparent) )] -pub struct OutputPk(XOnlyPublicKey); +pub struct OutputPk(XOnlyPk); impl OutputPk { #[inline] - pub fn from_unchecked(pk: TaprootPk) -> Self { Self(pk.0) } + pub fn from_unchecked(pk: XOnlyPk) -> Self { Self(pk) } - pub fn from_byte_array(data: [u8; 32]) -> Result { - XOnlyPublicKey::from_slice(&data) - .map(Self) - .map_err(|_| InvalidPubkey(data.into())) + #[inline] + pub fn from_byte_array(data: [u8; 32]) -> Result> { + XOnlyPk::from_byte_array(data).map(Self) } - pub fn to_byte_array(&self) -> [u8; 32] { self.0.serialize() } -} + pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result> { + XOnlyPk::from_bytes(bytes).map(Self) + } -impl From for [u8; 32] { - fn from(pk: OutputPk) -> [u8; 32] { pk.to_byte_array() } -} + pub fn to_script_pubkey(&self) -> ScriptPubkey { ScriptPubkey::p2tr_tweaked(*self) } -impl StrictEncode for OutputPk { - fn strict_encode(&self, writer: W) -> io::Result { - let bytes = Bytes32::from(self.0.serialize()); - writer.write_newtype::(&bytes) - } + #[inline] + pub fn to_byte_array(&self) -> [u8; 32] { self.0.to_byte_array() } } -impl StrictDecode for OutputPk { - fn strict_decode(reader: &mut impl TypedRead) -> Result { - reader.read_tuple(|r| { - let bytes: Bytes32 = r.read_field()?; - XOnlyPublicKey::from_slice(bytes.as_slice()) - .map(Self) - .map_err(|_| InvalidPubkey(bytes).into()) - }) - } +impl From for [u8; 32] { + fn from(pk: OutputPk) -> [u8; 32] { pk.to_byte_array() } } pub trait IntoTapHash { @@ -319,7 +325,7 @@ impl IntoTapHash for TapBranchHash { } #[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)] -#[wrapper(Deref, Index, RangeOps, BorrowSlice, Hex, Display, FromStr)] +#[wrapper(Index, RangeOps, AsSlice, BorrowSlice, Hex, Display, FromStr)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( @@ -339,8 +345,9 @@ impl IntoTapHash for TapNodeHash { fn into_tap_hash(self) -> TapNodeHash { self } } -#[derive(Wrapper, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Default)] +#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Default)] #[wrapper(Deref)] +#[wrapper_mut(DerefMut)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( @@ -595,8 +602,8 @@ pub enum TapCode { } #[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Default)] -#[wrapper(Deref, Index, RangeOps, BorrowSlice, LowerHex, UpperHex)] -#[wrapper_mut(DerefMut, IndexMut, RangeMut, BorrowSliceMut)] +#[wrapper(Deref, AsSlice, Hex)] +#[wrapper_mut(DerefMut, AsSliceMut)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( diff --git a/consensus/src/timelocks.rs b/consensus/src/timelocks.rs new file mode 100644 index 00000000..6aba41ac --- /dev/null +++ b/consensus/src/timelocks.rs @@ -0,0 +1,443 @@ +// Bitcoin protocol consensus library. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::cmp::Ordering; +use std::fmt::{self, Display, Formatter}; +use std::num::ParseIntError; +use std::str::FromStr; + +use chrono::Utc; + +use crate::LIB_NAME_BITCOIN; + +/// The Threshold for deciding whether a lock time value is a height or a time +/// (see [Bitcoin Core]). +/// +/// `LockTime` values _below_ the threshold are interpreted as block heights, +/// values _above_ (or equal to) the threshold are interpreted as block times +/// (UNIX timestamp, seconds since epoch). +/// +/// Bitcoin is able to safely use this value because a block height greater than +/// 500,000,000 would never occur because it would represent a height in +/// approximately 9500 years. Conversely, block times under 500,000,000 will +/// never happen because they would represent times before 1986 which +/// are, for obvious reasons, not useful within the Bitcoin network. +/// +/// [Bitcoin Core]: https://github.com/bitcoin/bitcoin/blob/9ccaee1d5e2e4b79b0a7c29aadb41b97e4741332/src/script/script.h#L39 +pub const LOCKTIME_THRESHOLD: u32 = 500_000_000; + +pub const SEQ_NO_CSV_DISABLE_MASK: u32 = 0x80000000; +pub const SEQ_NO_CSV_TYPE_MASK: u32 = 0x00400000; + +/// Error constructing timelock from the provided value. +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Error)] +#[display("invalid timelock value {0}")] +pub struct InvalidTimelock(pub u32); + +#[derive(Debug, Clone, PartialEq, Eq, From, Display)] +#[display(doc_comments)] +pub enum TimelockParseError { + /// invalid number in time lock descriptor + #[from] + InvalidNumber(ParseIntError), + + /// block height `{0}` is too large for time lock + InvalidHeight(u32), + + /// timestamp `{0}` is too small for time lock + InvalidTimestamp(u32), + + /// time lock descriptor `{0}` is not recognized + InvalidDescriptor(String), + + /// use of randomly-generated RBF sequence numbers requires compilation + /// with `rand` feature + NoRand, +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] +#[derive(StrictType, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_BITCOIN)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", transparent) +)] +pub struct LockTime(u32); + +impl PartialOrd for LockTime { + fn partial_cmp(&self, other: &Self) -> Option { + if self.is_height_based() != other.is_height_based() { + None + } else { + Some(self.0.cmp(&other.0)) + } + } +} + +impl LockTime { + /// Zero time lock + pub const ZERO: Self = Self(0); + + /// Create zero time lock + #[inline] + #[deprecated(since = "0.10.8", note = "use LockTime::ZERO")] + pub const fn zero() -> Self { Self(0) } + + /// Creates absolute time lock with the given block height. + /// + /// Block height must be strictly less than `0x1DCD6500`, otherwise + /// `None` is returned. + #[inline] + pub const fn from_height(height: u32) -> Option { + if height < LOCKTIME_THRESHOLD { + Some(Self(height)) + } else { + None + } + } + + /// Creates absolute time lock with the given UNIX timestamp value. + /// + /// Timestamp value must be greater or equal to `0x1DCD6500`, otherwise + /// `None` is returned. + #[inline] + pub const fn from_unix_timestamp(timestamp: u32) -> Option { + if timestamp < LOCKTIME_THRESHOLD { + None + } else { + Some(Self(timestamp)) + } + } + + /// Converts into full u32 representation of `nLockTime` value as it is + /// serialized in bitcoin transaction. + #[inline] + pub const fn from_consensus_u32(lock_time: u32) -> Self { LockTime(lock_time) } + + #[inline] + pub const fn to_consensus_u32(&self) -> u32 { self.0 } + + #[inline] + pub const fn into_consensus_u32(self) -> u32 { self.0 } + + /// Checks if the absolute timelock provided by the `nLockTime` value + /// specifies height-based lock + #[inline] + pub const fn is_height_based(self) -> bool { self.0 < LOCKTIME_THRESHOLD } + + /// Checks if the absolute timelock provided by the `nLockTime` value + /// specifies time-based lock + #[inline] + pub const fn is_time_based(self) -> bool { !self.is_height_based() } +} + +/// Value for a transaction `nTimeLock` field which is guaranteed to represent a +/// UNIX timestamp which is always either 0 or a greater than or equal to +/// 500000000. +#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Hash, Debug, Default)] +#[derive(StrictType, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_BITCOIN)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", transparent) +)] +pub struct LockTimestamp(u32); + +impl From for u32 { + fn from(lock_timestamp: LockTimestamp) -> Self { lock_timestamp.into_consensus_u32() } +} + +impl From for LockTime { + fn from(lock: LockTimestamp) -> Self { LockTime::from_consensus_u32(lock.into_consensus_u32()) } +} + +impl TryFrom for LockTimestamp { + type Error = InvalidTimelock; + + fn try_from(value: u32) -> Result { Self::try_from_consensus_u32(value) } +} + +impl TryFrom for LockTimestamp { + type Error = InvalidTimelock; + + fn try_from(lock_time: LockTime) -> Result { + Self::try_from_lock_time(lock_time) + } +} + +impl LockTimestamp { + /// Create zero time lock + #[inline] + pub fn anytime() -> Self { Self(0) } + + #[cfg(feature = "chrono")] + /// Creates absolute time lock valid since the current timestamp. + pub fn since_now() -> Self { + let now = Utc::now(); + LockTimestamp::from_unix_timestamp(now.timestamp() as u32) + .expect("we are too far in the future") + } + + /// Creates absolute time lock with the given UNIX timestamp value. + /// + /// Timestamp value must be greater or equal to `0x1DCD6500`, otherwise + /// `None` is returned. + #[inline] + pub fn from_unix_timestamp(timestamp: u32) -> Option { + if timestamp < LOCKTIME_THRESHOLD { + None + } else { + Some(Self(timestamp)) + } + } + + #[inline] + pub const fn try_from_lock_time(lock_time: LockTime) -> Result { + Self::try_from_consensus_u32(lock_time.into_consensus_u32()) + } + + #[inline] + pub const fn try_from_consensus_u32(lock_time: u32) -> Result { + if !LockTime::from_consensus_u32(lock_time).is_time_based() { + return Err(InvalidTimelock(lock_time)); + } + Ok(Self(lock_time)) + } + + /// Converts into full u32 representation of `nLockTime` value as it is + /// serialized in bitcoin transaction. + #[inline] + pub const fn to_consensus_u32(&self) -> u32 { self.0 } + + /// Converts into full u32 representation of `nLockTime` value as it is + /// serialized in bitcoin transaction. + #[inline] + pub const fn into_consensus_u32(self) -> u32 { self.0 } + + /// Converts into [`LockTime`] representation. + #[inline] + pub fn into_lock_time(self) -> LockTime { self.into() } + + /// Converts into [`LockTime`] representation. + #[inline] + pub fn to_lock_time(self) -> LockTime { self.into_lock_time() } +} + +impl Display for LockTimestamp { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("time(")?; + Display::fmt(&self.0, f)?; + f.write_str(")") + } +} + +impl FromStr for LockTimestamp { + type Err = TimelockParseError; + + fn from_str(s: &str) -> Result { + let s = s.to_lowercase(); + if s == "0" || s == "none" { + Ok(LockTimestamp::anytime()) + } else if s.starts_with("time(") && s.ends_with(')') { + let no = s[5..].trim_end_matches(')').parse()?; + LockTimestamp::try_from(no).map_err(|_| TimelockParseError::InvalidTimestamp(no)) + } else { + Err(TimelockParseError::InvalidDescriptor(s)) + } + } +} + +/// Value for a transaction `nTimeLock` field which is guaranteed to represent a +/// block height number which is always less than 500000000. +#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Hash, Debug, Default)] +#[derive(StrictType, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_BITCOIN)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", transparent) +)] +pub struct LockHeight(u32); + +impl From for u32 { + fn from(lock_height: LockHeight) -> Self { lock_height.into_consensus_u32() } +} + +impl From for LockTime { + fn from(lock: LockHeight) -> Self { LockTime::from_consensus_u32(lock.into_consensus_u32()) } +} + +impl TryFrom for LockHeight { + type Error = InvalidTimelock; + + fn try_from(value: u32) -> Result { Self::try_from_consensus_u32(value) } +} + +impl TryFrom for LockHeight { + type Error = InvalidTimelock; + + fn try_from(lock_time: LockTime) -> Result { + Self::try_from_lock_time(lock_time) + } +} + +impl LockHeight { + /// Create zero time lock + #[inline] + pub fn anytime() -> Self { Self(0) } + + /// Creates absolute time lock with the given block height. + /// + /// Block height must be strictly less than `0x1DCD6500`, otherwise + /// `None` is returned. + #[inline] + pub fn from_height(height: u32) -> Option { + if height < LOCKTIME_THRESHOLD { + Some(Self(height)) + } else { + None + } + } + + #[inline] + pub const fn try_from_lock_time(lock_time: LockTime) -> Result { + Self::try_from_consensus_u32(lock_time.into_consensus_u32()) + } + + #[inline] + pub const fn try_from_consensus_u32(lock_time: u32) -> Result { + if !LockTime::from_consensus_u32(lock_time).is_height_based() { + return Err(InvalidTimelock(lock_time)); + } + Ok(Self(lock_time)) + } + + /// Converts into full u32 representation of `nLockTime` value as it is + /// serialized in bitcoin transaction. + #[inline] + pub const fn to_consensus_u32(&self) -> u32 { self.0 } + + /// Converts into full u32 representation of `nLockTime` value as it is + /// serialized in bitcoin transaction. + #[inline] + pub const fn into_consensus_u32(self) -> u32 { self.0 } + + /// Converts into [`LockTime`] representation. + #[inline] + pub fn to_lock_time(&self) -> LockTime { self.into_lock_time() } + + /// Converts into [`LockTime`] representation. + #[inline] + pub fn into_lock_time(self) -> LockTime { self.into() } +} + +impl Display for LockHeight { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("height(")?; + Display::fmt(&self.0, f)?; + f.write_str(")") + } +} + +impl FromStr for LockHeight { + type Err = TimelockParseError; + + fn from_str(s: &str) -> Result { + let s = s.to_lowercase(); + if s == "0" || s == "none" { + Ok(LockHeight::anytime()) + } else if s.starts_with("height(") && s.ends_with(')') { + let no = s[7..].trim_end_matches(')').parse()?; + LockHeight::try_from(no).map_err(|_| TimelockParseError::InvalidHeight(no)) + } else { + Err(TimelockParseError::InvalidDescriptor(s)) + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_BITCOIN)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", transparent) +)] +pub struct SeqNo(u32); + +impl SeqNo { + #[inline] + pub const fn from_consensus_u32(lock_time: u32) -> Self { SeqNo(lock_time) } + + #[inline] + pub const fn to_consensus_u32(&self) -> u32 { self.0 } + + /// Creates relative time lock measured in number of blocks (implies RBF). + #[inline] + pub const fn from_height(blocks: u16) -> SeqNo { SeqNo(blocks as u32) } + + /// Creates relative time lock measured in number of 512-second intervals + /// (implies RBF). + #[inline] + pub const fn from_intervals(intervals: u16) -> SeqNo { + SeqNo(intervals as u32 | SEQ_NO_CSV_TYPE_MASK) + } + + /// Gets structured relative time lock information from the `nSeq` value. + /// See [`TimeLockInterval`]. + pub const fn time_lock_interval(self) -> Option { + if self.0 & SEQ_NO_CSV_DISABLE_MASK != 0 { + None + } else if self.0 & SEQ_NO_CSV_TYPE_MASK != 0 { + Some(TimeLockInterval::Time((self.0 & 0xFFFF) as u16)) + } else { + Some(TimeLockInterval::Height((self.0 & 0xFFFF) as u16)) + } + } + + pub const fn is_timelock(self) -> bool { self.0 & SEQ_NO_CSV_DISABLE_MASK > 1 } +} + +/// Time lock interval describing both relative (OP_CHECKSEQUENCEVERIFY) and +/// absolute (OP_CHECKTIMELOCKVERIFY) timelocks. +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)] +#[derive(StrictType, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_BITCOIN, tags = order)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +pub enum TimeLockInterval { + /// Describes number of blocks for the timelock + #[display("height({0})")] + Height(u16), + + /// Describes number of 512-second intervals for the timelock + #[display("time({0})")] + Time(u16), +} + +impl Default for TimeLockInterval { + fn default() -> Self { TimeLockInterval::Height(default!()) } +} diff --git a/consensus/src/tx.rs b/consensus/src/tx.rs index 22e98ce5..eb1a6fda 100644 --- a/consensus/src/tx.rs +++ b/consensus/src/tx.rs @@ -20,7 +20,6 @@ // limitations under the License. use core::slice; -use std::cmp::Ordering; use std::fmt::{self, Debug, Display, Formatter, LowerHex}; use std::iter::Sum; use std::num::ParseIntError; @@ -31,8 +30,8 @@ use amplify::{ByteArray, Bytes32StrRev, Wrapper}; use commit_verify::{DigestExt, Sha256}; use crate::{ - ConsensusDecode, ConsensusDecodeError, ConsensusEncode, NonStandardValue, ScriptPubkey, - SigScript, VarIntArray, Witness, Wtxid, LIB_NAME_BITCOIN, + ConsensusDecode, ConsensusDecodeError, ConsensusEncode, LockTime, NonStandardValue, + ScriptPubkey, SeqNo, SigScript, VarIntArray, Witness, Wtxid, LIB_NAME_BITCOIN, }; #[derive(Wrapper, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, From)] @@ -45,7 +44,7 @@ use crate::{ derive(Serialize, Deserialize), serde(crate = "serde_crate", transparent) )] -#[wrapper(BorrowSlice, Index, RangeOps, Debug, LowerHex, UpperHex, Display, FromStr)] +#[wrapper(BorrowSlice, Index, RangeOps, Debug, Hex, Display, FromStr)] // all-zeros used in coinbase pub struct Txid( #[from] @@ -60,13 +59,6 @@ impl Txid { pub fn is_coinbase(&self) -> bool { self.to_byte_array() == [0u8; 32] } } -impl FromHex for Txid { - fn from_byte_iter(iter: I) -> Result - where I: Iterator> + ExactSizeIterator + DoubleEndedIterator { - Bytes32StrRev::from_byte_iter(iter).map(Self) - } -} - #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display, From)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] @@ -223,24 +215,6 @@ mod _serde_outpoint { } } -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] -#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] -#[strict_type(lib = LIB_NAME_BITCOIN)] -#[cfg_attr( - feature = "serde", - derive(Serialize, Deserialize), - serde(crate = "serde_crate", transparent) -)] -pub struct SeqNo(u32); - -impl SeqNo { - #[inline] - pub const fn from_consensus_u32(lock_time: u32) -> Self { SeqNo(lock_time) } - - #[inline] - pub const fn to_consensus_u32(&self) -> u32 { self.0 } -} - #[derive(Clone, Eq, PartialEq, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] @@ -278,6 +252,7 @@ pub struct Sats( impl Sats { pub const ZERO: Self = Sats(0); + #[allow(clippy::inconsistent_digit_grouping)] pub const BTC: Self = Sats(1_000_000_00); pub const fn from_btc(btc: u32) -> Self { Self(btc as u64 * Self::BTC.0) } @@ -433,99 +408,6 @@ impl TxVer { pub const fn to_consensus_i32(&self) -> i32 { self.0 } } -/// The Threshold for deciding whether a lock time value is a height or a time -/// (see [Bitcoin Core]). -/// -/// `LockTime` values _below_ the threshold are interpreted as block heights, -/// values _above_ (or equal to) the threshold are interpreted as block times -/// (UNIX timestamp, seconds since epoch). -/// -/// Bitcoin is able to safely use this value because a block height greater than -/// 500,000,000 would never occur because it would represent a height in -/// approximately 9500 years. Conversely, block times under 500,000,000 will -/// never happen because they would represent times before 1986 which -/// are, for obvious reasons, not useful within the Bitcoin network. -/// -/// [Bitcoin Core]: https://github.com/bitcoin/bitcoin/blob/9ccaee1d5e2e4b79b0a7c29aadb41b97e4741332/src/script/script.h#L39 -pub const LOCKTIME_THRESHOLD: u32 = 500_000_000; - -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] -#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] -#[strict_type(lib = LIB_NAME_BITCOIN)] -#[cfg_attr( - feature = "serde", - derive(Serialize, Deserialize), - serde(crate = "serde_crate", transparent) -)] -pub struct LockTime(u32); - -impl PartialOrd for LockTime { - fn partial_cmp(&self, other: &Self) -> Option { - if self.is_height_based() != other.is_height_based() { - None - } else { - Some(self.0.cmp(&other.0)) - } - } -} - -impl LockTime { - /// Zero time lock - pub const ZERO: Self = Self(0); - - /// Create zero time lock - #[inline] - #[deprecated(since = "0.10.8", note = "use LockTime::ZERO")] - pub const fn zero() -> Self { Self(0) } - - /// Creates absolute time lock with the given block height. - /// - /// Block height must be strictly less than `0x1DCD6500`, otherwise - /// `None` is returned. - #[inline] - pub const fn from_height(height: u32) -> Option { - if height < LOCKTIME_THRESHOLD { - Some(Self(height)) - } else { - None - } - } - - /// Creates absolute time lock with the given UNIX timestamp value. - /// - /// Timestamp value must be greater or equal to `0x1DCD6500`, otherwise - /// `None` is returned. - #[inline] - pub const fn from_unix_timestamp(timestamp: u32) -> Option { - if timestamp < LOCKTIME_THRESHOLD { - None - } else { - Some(Self(timestamp)) - } - } - - /// Converts into full u32 representation of `nLockTime` value as it is - /// serialized in bitcoin transaction. - #[inline] - pub const fn from_consensus_u32(lock_time: u32) -> Self { LockTime(lock_time) } - - #[inline] - pub const fn to_consensus_u32(&self) -> u32 { self.0 } - - #[inline] - pub const fn into_consensus_u32(self) -> u32 { self.0 } - - /// Checks if the absolute timelock provided by the `nLockTime` value - /// specifies height-based lock - #[inline] - pub const fn is_height_based(self) -> bool { self.0 < LOCKTIME_THRESHOLD } - - /// Checks if the absolute timelock provided by the `nLockTime` value - /// specifies time-based lock - #[inline] - pub const fn is_time_based(self) -> bool { !self.is_height_based() } -} - #[derive(Clone, Eq, PartialEq, Hash, Debug, Display)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] @@ -562,7 +444,7 @@ impl FromStr for Tx { fn from_str(s: &str) -> Result { let data = Vec::::from_hex(s)?; - Tx::consensus_deserialize(&data).map_err(TxParseError::from) + Tx::consensus_deserialize(data).map_err(TxParseError::from) } } diff --git a/stl/Bitcoin@0.1.0.sta b/stl/Bitcoin@0.1.0.sta index 2c152aa6..6b4649f8 100644 --- a/stl/Bitcoin@0.1.0.sta +++ b/stl/Bitcoin@0.1.0.sta @@ -1,64 +1,71 @@ -----BEGIN STRICT TYPE LIB----- -Id: urn:ubideco:stl:D42LxJBQokrGJzvoSV3E1HoriGgLzPcxuL61JymwjEqV +Id: urn:ubideco:stl:4AXTqXq8jUDs244XbhvErdsG82Y8r9PiaPBPAmD5y9fQ Name: Bitcoin Dependencies: urn:ubideco:stl:9KALDYR8Nyjq4FdMW6kYoL7vdkWnqPqNuFnmE9qHpNjZ B0JpdGNvaW4Be4SAPJ764hElp3wsObxw0v3o+UOuDf2c9OaC7cdmynADU3RkAQNT -dGQBAGGGItF7rvBmAt/ndcmA4LNrbrroCQ2AdfdRO+xLk/ZNBEJvb2wsAAlCaXAz +dGQBAGGGItF7rvBmAt/ndcmA4LNrbrroCQ2AdfdRO+xLk/ZNBEJvb2w3AAlCaXAz NDBTaWcGAgNzaWcABwAAQEAADHNpZ2hhc2hfdHlwZQAEAgAEbm9uZQAAAAEEc29t ZQAFAQHbF2Q+dwkTJ+gEmSSB1dxgFSMIlVM55Yszn9RMT3JIKglCbG9ja0hhc2gF AQAHAABAIAALQmxvY2tIZWFkZXIGBgd2ZXJzaW9uAABEDXByZXZCbG9ja0hhc2gB 136XKN8Qx+HJT3+gvLwHRRJSZMs+SsX4k0LE/8jKtLcKbWVya2xlUm9vdAAHAABA IAAEdGltZQAABARiaXRzAAAEBW5vbmNlAAAEB0J5dGVTdHIFAQAIAABAAAAAAAAA AAD/////AAAAAAVDaGFpbgMEB2JpdGNvaW4AB3JlZ3Rlc3SACHRlc3RuZXQzgwZz -aWduZXSEDENvbnRyb2xCbG9jawYEC2xlYWZWZXJzaW9uAbYzCakYv7aSDW7IWKQk -hyNGWmk/ckMHv/8d1zpzgU7JD291dHB1dEtleVBhcml0eQGPyiYjolVEI5WPpnqn -bUNqFd5k6xCz9PmZFCIvlRFmiAppbnRlcm5hbFBrAd/4ADyB/kf8VCOx0sbiDw3f -qma9zPN9dBPJH2XaADyIDG1lcmtsZUJyYW5jaAHuAx5mUVBotnQZXFF39AqDxABS -W8GkMBQYAGlPvfBzpA1GdXR1cmVMZWFmVmVyBQEAAAEKSW50ZXJuYWxQawUBAAcA -AEAgAApMZWFmU2NyaXB0BgIHdmVyc2lvbgG2MwmpGL+2kg1uyFikJIcjRlppP3JD -B7//Hdc6c4FOyQZzY3JpcHQBJav1uRIUF7qjOdRfexV1p3FL4Xp1GF3QMTV61Mkt -6YYHTGVhZlZlcgUBAAABCUxlZ2FjeVNpZwYCA3NpZwAIAABAAAAAAAAAAAD/AAAA -AAAAAAxzaWdoYXNoX3R5cGUB2xdkPncJEyfoBJkkgdXcYBUjCJVTOeWLM5/UTE9y -SCoITG9ja1RpbWUFAQAABAZPcENvZGUDEgpwdXNoQnl0ZXMwAAtwdXNoQnl0ZXMz -MiAJcHVzaERhdGExTAlwdXNoRGF0YTJNCXB1c2hEYXRhNE4IcmVzZXJ2ZWRQCHB1 -c2hOdW0xUQZyZXR1cm5qA2R1cHYFZXF1YWyHC2VxdWFsVmVyaWZ5iAlyaXBlbWQx -NjCmBHNoYTGnBnNoYTI1NqgHaGFzaDE2MKkHaGFzaDI1NqoIY2hlY2tTaWesDmNo -ZWNrU2lnVmVyaWZ5rQhPdXRwb2ludAYCBHR4aWQBo4JC88vX0dChEtqN4WAvVtT4 -bw7ExHbFwGhZTEsEZVYEdm91dAEh4z5Dxapc8iknU6M4wWftO2OcTdnOvamPNGkX -uslDdQhPdXRwdXRQawUBAAcAAEAgAAZQYXJpdHkDAgRldmVuAANvZGQBDFJlZGVl -bVNjcmlwdAUBASWr9bkSFBe6oznUX3sVdadxS+F6dRhd0DE1etTJLemGBFNhdHMF -AQAACAtTY3JpcHRCeXRlcwUBAAgAAEAAAAAAAAAAAP////8AAAAADFNjcmlwdFB1 -YmtleQUBASWr9bkSFBe6oznUX3sVdadxS+F6dRhd0DE1etTJLemGBVNlcU5vBQEA -AAQJU2lnU2NyaXB0BQEBJav1uRIUF7qjOdRfexV1p3FL4Xp1GF3QMTV61Mkt6YYL -U2lnaGFzaEZsYWcDAwNhbGwBBG5vbmUCBnNpbmdsZQMLU2lnaGFzaFR5cGUGAgRm -bGFnAf8+an/Ix4VDTvyO53SzALGb7Jhxopoe8QCL7xDrKNU0DGFueW9uZUNhblBh -eQJ7hIA8nvriESWnfCw5vHDS/ej5Q64N/Zz05oLtx2bKcGGGItF7rvBmAt/ndcmA -4LNrbrroCQ2AdfdRO+xLk/ZNDVRhcEJyYW5jaEhhc2gFAQAHAABAIAAHVGFwQ29k -ZQMGC3B1c2hCeXRlczMyIAlwdXNoRGF0YTFMCXB1c2hEYXRhMk0JcHVzaERhdGE0 -TghyZXNlcnZlZFAGcmV0dXJuagtUYXBMZWFmSGFzaAUBAAcAAEAgAA1UYXBNZXJr -bGVQYXRoBQEACAGv68Wd2P1QRx2YXcJBKC6rqYxIivJkY8F2flEGIsKS2AAAAAAA -AAAAgAAAAAAAAAALVGFwTm9kZUhhc2gFAQAHAABAIAAJVGFwU2NyaXB0BQEBJav1 -uRIUF7qjOdRfexV1p3FL4Xp1GF3QMTV61Mkt6YYCVHgGBAd2ZXJzaW9uAah8xnlk -Z+VX10TlyWI64AzLldkaDS8D33TAdRJPvseeBmlucHV0cwAIARlHLRfYYgenDyEx -qVihaKVuKtZTP5xmTBl32lz5/mKGAAAAAAAAAAD/////AAAAAAdvdXRwdXRzAAgB -kDtkcHmEjxsmUyrkzsamiUSgU1i48IHLJrO7+C2eO/MAAAAAAAAAAP////8AAAAA -CGxvY2tUaW1lATXaHRU5IG673dykwz2HMerym6fadN89yIIgHE4WtbkcBFR4SW4G -BApwcmV2T3V0cHV0AehqQM1cJfm94oT/aaURMqdBKyFVvQ5WEsG/44SVYMUGCXNp -Z1NjcmlwdAE4dQSxS3wORm1HnhdHfSR0JH/4A2TsPUuq9zog90F0awhzZXF1ZW5j -ZQEBGW2FKcj22kRNFU6NnIy9ng+NiQJaO7CRIcY9UrAehwd3aXRuZXNzAXN3Q3A2 -kyBJzSiVCKpxfOOCnbJFLlXoTtT8LjzNLgCdBVR4T3V0BgIFdmFsdWUBl/XXBkKu -KjOSJTuoTh3OxJPjvz7TcbGHc4Y1TsyIgmsMc2NyaXB0UHVia2V5Ab78HvxmpRn9 -ZFJqOhOHQOfxEC0Lvv86wUZO8/dAdnRcBVR4VmVyBQEAAEQEVHhpZAUBAAcAAEAg -AAZWQnl0ZXMFAQAABAZWYXJJbnQFAQAACARWb3V0BQEAAAQLV2VpZ2h0VW5pdHMF -AQAABAdXaXRuZXNzBQEACAAIAABAAAAAAAAAAAD/////AAAAAAAAAAAAAAAA//// -/wAAAAAOV2l0bmVzc1Byb2dyYW0GAgd2ZXJzaW9uAdHs2nZn5ELtTRJppmcDNuX+ -9DevXs4raa66DEZWxPqtB3Byb2dyYW0ACAAAQAIAAAAAAAAAKAAAAAAAAAANV2l0 -bmVzc1NjcmlwdAUBASWr9bkSFBe6oznUX3sVdadxS+F6dRhd0DE1etTJLemGCldp -dG5lc3NWZXIDEQJ2MAACdjFRAnYyUgJ2M1MCdjRUAnY1VQJ2NlYCdjdXAnY4WAJ2 -OVkDdjEwWgN2MTFbA3YxMlwDdjEzXQN2MTReA3YxNV8DdjE2YAVXdHhpZAUBAAcA -AEAgAA== +aWduZXSEDENvbXByZXNzZWRQawUBAAcAAEAhAAxDb250cm9sQmxvY2sGBAtsZWFm +VmVyc2lvbgG2MwmpGL+2kg1uyFikJIcjRlppP3JDB7//Hdc6c4FOyQ9vdXRwdXRL +ZXlQYXJpdHkBj8omI6JVRCOVj6Z6p21DahXeZOsQs/T5mRQiL5URZogKaW50ZXJu +YWxQawHf+AA8gf5H/FQjsdLG4g8N36pmvczzfXQTyR9l2gA8iAxtZXJrbGVCcmFu +Y2gB7gMeZlFQaLZ0GVxRd/QKg8QAUlvBpDAUGABpT73wc6QNRnV0dXJlTGVhZlZl +cgUBAAABCkludGVybmFsUGsFAQAHAABAIAAKTGVhZlNjcmlwdAYCB3ZlcnNpb24B +tjMJqRi/tpINbshYpCSHI0ZaaT9yQwe//x3XOnOBTskGc2NyaXB0ASWr9bkSFBe6 +oznUX3sVdadxS+F6dRhd0DE1etTJLemGB0xlYWZWZXIFAQAAAQhMZWdhY3lQawYC +CmNvbXByZXNzZWQCe4SAPJ764hElp3wsObxw0v3o+UOuDf2c9OaC7cdmynBhhiLR +e67wZgLf53XJgOCza2666AkNgHX3UTvsS5P2TQZwdWJrZXkABwAAQCEACUxlZ2Fj +eVNpZwYCA3NpZwAIAABAAAAAAAAAAAD/AAAAAAAAAAxzaWdoYXNoX3R5cGUB2xdk +PncJEyfoBJkkgdXcYBUjCJVTOeWLM5/UTE9ySCoKTG9ja0hlaWdodAUBAAAECExv +Y2tUaW1lBQEAAAQNTG9ja1RpbWVzdGFtcAUBAAAEBk9wQ29kZQMSCnB1c2hCeXRl +czAAC3B1c2hCeXRlczMyIAlwdXNoRGF0YTFMCXB1c2hEYXRhMk0JcHVzaERhdGE0 +TghyZXNlcnZlZFAIcHVzaE51bTFRBnJldHVybmoDZHVwdgVlcXVhbIcLZXF1YWxW +ZXJpZnmICXJpcGVtZDE2MKYEc2hhMacGc2hhMjU2qAdoYXNoMTYwqQdoYXNoMjU2 +qghjaGVja1NpZ6wOY2hlY2tTaWdWZXJpZnmtCE91dHBvaW50BgIEdHhpZAGjgkLz +y9fR0KES2o3hYC9W1PhvDsTEdsXAaFlMSwRlVgR2b3V0ASHjPkPFqlzyKSdTozjB +Z+07Y5xN2c69qY80aRe6yUN1CE91dHB1dFBrBQEB/KKnKr6R/s2CqLGYkGMiwk52 +qqo18iTRcwVjoBguiFkGUGFyaXR5AwIEZXZlbgADb2RkAQpQdWJrZXlIYXNoBQEA +BwAAQBQADFJlZGVlbVNjcmlwdAUBASWr9bkSFBe6oznUX3sVdadxS+F6dRhd0DE1 +etTJLemGBFNhdHMFAQAACAtTY3JpcHRCeXRlcwUBAAgAAEAAAAAAAAAAAP////8A +AAAAClNjcmlwdEhhc2gFAQAHAABAFAAMU2NyaXB0UHVia2V5BQEBJav1uRIUF7qj +OdRfexV1p3FL4Xp1GF3QMTV61Mkt6YYFU2VxTm8FAQAABAlTaWdTY3JpcHQFAQEl +q/W5EhQXuqM51F97FXWncUvhenUYXdAxNXrUyS3phgtTaWdoYXNoRmxhZwMDA2Fs +bAEEbm9uZQIGc2luZ2xlAwtTaWdoYXNoVHlwZQYCBGZsYWcB/z5qf8jHhUNO/I7n +dLMAsZvsmHGimh7xAIvvEOso1TQMYW55b25lQ2FuUGF5AnuEgDye+uIRJad8LDm8 +cNL96PlDrg39nPTmgu3HZspwYYYi0Xuu8GYC3+d1yYDgs2tuuugJDYB191E77EuT +9k0NVGFwQnJhbmNoSGFzaAUBAAcAAEAgAAdUYXBDb2RlAwYLcHVzaEJ5dGVzMzIg +CXB1c2hEYXRhMUwJcHVzaERhdGEyTQlwdXNoRGF0YTROCHJlc2VydmVkUAZyZXR1 +cm5qC1RhcExlYWZIYXNoBQEABwAAQCAADVRhcE1lcmtsZVBhdGgFAQAIAa/rxZ3Y +/VBHHZhdwkEoLqupjEiK8mRjwXZ+UQYiwpLYAAAAAAAAAACAAAAAAAAAAAtUYXBO +b2RlSGFzaAUBAAcAAEAgAAlUYXBTY3JpcHQFAQElq/W5EhQXuqM51F97FXWncUvh +enUYXdAxNXrUyS3phhBUaW1lTG9ja0ludGVydmFsBAIABmhlaWdodAAFAQAAAgEE +dGltZQAFAQAAAgJUeAYEB3ZlcnNpb24BqHzGeWRn5VfXROXJYjrgDMuV2RoNLwPf +dMB1Ek++x54GaW5wdXRzAAgBGUctF9hiB6cPITGpWKFopW4q1lM/nGZMGXfaXPn+ +YoYAAAAAAAAAAP////8AAAAAB291dHB1dHMACAGQO2RweYSPGyZTKuTOxqaJRKBT +WLjwgcsms7v4LZ478wAAAAAAAAAA/////wAAAAAIbG9ja1RpbWUBNdodFTkgbrvd +3KTDPYcx6vKbp9p03z3IgiAcTha1uRwEVHhJbgYECnByZXZPdXRwdXQB6GpAzVwl ++b3ihP9ppREyp0ErIVW9DlYSwb/jhJVgxQYJc2lnU2NyaXB0ATh1BLFLfA5GbUee +F0d9JHQkf/gDZOw9S6r3OiD3QXRrCHNlcXVlbmNlAQEZbYUpyPbaRE0VTo2cjL2e +D42JAlo7sJEhxj1SsB6HB3dpdG5lc3MBc3dDcDaTIEnNKJUIqnF844KdskUuVehO +1PwuPM0uAJ0FVHhPdXQGAgV2YWx1ZQGX9dcGQq4qM5IlO6hOHc7Ek+O/PtNxsYdz +hjVOzIiCawxzY3JpcHRQdWJrZXkBvvwe/GalGf1kUmo6E4dA5/EQLQu+/zrBRk7z +90B2dFwFVHhWZXIFAQAARARUeGlkBQEABwAAQCAADlVuY29tcHJlc3NlZFBrBQEA +BwAAQEEABlZCeXRlcwUBAAAEBlZhckludAUBAAAIBFZvdXQFAQAABAtXUHVia2V5 +SGFzaAUBAAcAAEAUAAtXU2NyaXB0SGFzaAUBAAcAAEAgAAtXZWlnaHRVbml0cwUB +AAAEB1dpdG5lc3MFAQAIAAgAAEAAAAAAAAAAAP////8AAAAAAAAAAAAAAAD///// +AAAAAA5XaXRuZXNzUHJvZ3JhbQYCB3ZlcnNpb24B0ezadmfkQu1NEmmmZwM25f70 +N69ezitprroMRlbE+q0HcHJvZ3JhbQAIAABAAgAAAAAAAAAoAAAAAAAAAA1XaXRu +ZXNzU2NyaXB0BQEBJav1uRIUF7qjOdRfexV1p3FL4Xp1GF3QMTV61Mkt6YYKV2l0 +bmVzc1ZlcgMRAnYwAAJ2MVECdjJSAnYzUwJ2NFQCdjVVAnY2VgJ2N1cCdjhYAnY5 +WQN2MTBaA3YxMVsDdjEyXAN2MTNdA3YxNF4DdjE1XwN2MTZgBVd0eGlkBQEABwAA +QCAAB1hPbmx5UGsFAQAHAABAIAA= -----END STRICT TYPE LIB----- diff --git a/stl/Bitcoin@0.1.0.stl b/stl/Bitcoin@0.1.0.stl index 4c105541..939ab1b3 100644 Binary files a/stl/Bitcoin@0.1.0.stl and b/stl/Bitcoin@0.1.0.stl differ diff --git a/stl/Bitcoin@0.1.0.sty b/stl/Bitcoin@0.1.0.sty index 984f907c..2d93bf01 100644 --- a/stl/Bitcoin@0.1.0.sty +++ b/stl/Bitcoin@0.1.0.sty @@ -1,5 +1,5 @@ {- - Id: urn:ubideco:stl:D42LxJBQokrGJzvoSV3E1HoriGgLzPcxuL61JymwjEqV#arena-complex-husband + Id: urn:ubideco:stl:4AXTqXq8jUDs244XbhvErdsG82Y8r9PiaPBPAmD5y9fQ#cheese-provide-morph Name: Bitcoin Version: 0.1.0 Description: Consensus library for bitcoin protocol @@ -32,6 +32,8 @@ data ByteStr :: [Byte ^ ..0xffffffff] -- urn:ubideco:semid:6aRP3odHaTGySvSWHjreC8HsbX5ss9LxkQqwcjaoxhpv#aspirin-brown-alpine data Chain :: bitcoin:0 | regtest:128 | testnet3:131 | signet:132 +-- urn:ubideco:semid:EoHBsHBYQiRvuKFEJvqwp8FiBPrpoFhkhQ9peLsFxrai#aspect-citrus-nerve +data CompressedPk :: [Byte ^ 33] -- urn:ubideco:semid:BPzqzv3DN65MTwzbTXJbHFyiKeYvmX1VExcqvk5FUb5c#nuclear-coral-gilbert data ControlBlock :: leafVersion LeafVer , outputKeyParity Parity @@ -45,10 +47,16 @@ data InternalPk :: [Byte ^ 32] data LeafScript :: version LeafVer, script ScriptBytes -- urn:ubideco:semid:DGELfUvcU62GNQRo7HaMbKDzYQwdYRMW3b91JHd4d3WY#tunnel-lagoon-cowboy data LeafVer :: U8 +-- urn:ubideco:semid:6oujgsJXTqoBmeQThDPW8cJuEG2SJJmbX9BRqsp9zS2L#student-avenue-economy +data LegacyPk :: compressed Std.Bool {- urn:ubideco:semid:7ZhBHGSJm9ixmm8Z9vCX7i5Ga7j5xrW8t11nsb1Cgpnx#laser-madam-maxwell -}, pubkey [Byte ^ 33] -- urn:ubideco:semid:89ux18kFuT9nT6LEWnvkpFBiWR9GJwBYVVBqFUPvrTuj#gondola-middle-style data LegacySig :: sig [Byte ^ ..0xff], sighash_type SighashType +-- urn:ubideco:semid:Gm9B1aVGcLG8MQ27bZrVg4mteg1AuKoFn1aFNkZ72Y2Y#greek-support-hawaii +data LockHeight :: U32 -- urn:ubideco:semid:4dDWWU4afiPN3q4AgCMuFRFhL4UDta2u5SrqrBzPvjby#tokyo-inch-program data LockTime :: U32 +-- urn:ubideco:semid:8h25q8Va3Sx3csGvq4reMPqG7w4QrEowpYxdMAwaErmF#compass-protein-barcode +data LockTimestamp :: U32 -- urn:ubideco:semid:F8WJfUNUgyVDSX6zXjrdi2pWBa54zLWorawtahJf33Hw#shampoo-rufus-tobacco data OpCode :: pushBytes0:0 | pushBytes32:32 | pushData1:76 | pushData2:77 | pushData4:78 | reserved:80 | pushNum1:81 | return:106 @@ -58,17 +66,21 @@ data OpCode :: pushBytes0:0 | pushBytes32:32 | pushData1:76 | pushData -- urn:ubideco:semid:FWt2MSo8A4nsYgYbuBqMRNLiKgtzvLBgUn774iKzTcuf#pocket-pegasus-frank data Outpoint :: txid Txid, vout Vout --- urn:ubideco:semid:BA7caGkXGi42re8gf15M9awB9BwFrnHzxHpB9gMUpDQK#regular-vision-origin -data OutputPk :: [Byte ^ 32] +-- urn:ubideco:semid:6ZEyjfF96enVZgwNdLkdjfqmAeMW5y939Fkf8VyDAaFZ#beach-twist-hotel +data OutputPk :: XOnlyPk -- urn:ubideco:semid:AgJ5n58hrH761B4MV7giZ1FhMipaDrUmnFYCLno74HDy#method-editor-echo data Parity :: even:0 | odd:1 +-- urn:ubideco:semid:ENHnWLRrbiPSgpSZMuQ1LaEwQeRjUMmB1bZstaT3F3hm#soviet-senator-wisdom +data PubkeyHash :: [Byte ^ 20] -- urn:ubideco:semid:85m9Qv56neQKaqiPSZ8G8NPWz5DDeJeeXhBwnUpcZGug#delta-jumbo-clone data RedeemScript :: ScriptBytes -- urn:ubideco:semid:BEBz6h7AGjYSDRCxVHnjYkkkxzBsjN3EvyNiD4ZrzmRL#pyramid-spray-star data Sats :: U64 -- urn:ubideco:semid:3Y4AgjkFbDusgo3YqRDWv9BznDeAJEUDEPeEq1mpSkAR#maestro-source-jackson data ScriptBytes :: [Byte ^ ..0xffffffff] +-- urn:ubideco:semid:7ExpFpLww6B4ADMMLsw5NtUfRzgJMupAFgYfme5yYMMr#samuel-miami-cave +data ScriptHash :: [Byte ^ 20] -- urn:ubideco:semid:2ZAYpWKB2BQxeXXjpQDpYGZ7eXFM9qQxN9TcdTiQqeB8#bingo-maestro-silk data ScriptPubkey :: ScriptBytes -- urn:ubideco:semid:5HtymNhYBhjqPkLLw9QVWZ62cLm57cZxgQTDUBBXtmL#rhino-time-rodent @@ -94,6 +106,9 @@ data TapMerklePath :: [TapBranchHash ^ ..0x80] data TapNodeHash :: [Byte ^ 32] -- urn:ubideco:semid:71AxyLFsoRG6hJ1c11gxad65nEbWfzkQBjWCPPrgCyjX#telecom-quest-helium data TapScript :: ScriptBytes +-- urn:ubideco:semid:EjaLfxk1qE5LgYWBnrg1tcYHAcmMnCXd2S1S3szJw666#texas-tommy-geneva +data TimeLockInterval :: height U16 + | time U16 -- urn:ubideco:semid:DynChojW1sfr8VjSoZbmReHhZoU8u9KCiuwijgEGdToe#milk-gloria-prize data Tx :: version TxVer , inputs [TxIn ^ ..0xffffffff] @@ -110,12 +125,18 @@ data TxOut :: value Sats, scriptPubkey ScriptPubkey data TxVer :: I32 -- urn:ubideco:semid:C1GfCrG7AXu2sFhRBspd7KpJK2YgyTkVy6pty5rZynRs#cowboy-diego-betty data Txid :: [Byte ^ 32] +-- urn:ubideco:semid:CWjhoHVM4EyLcLJthK7fhePqmUpVctdhjdabggJFkig1#yellow-jaguar-pastel +data UncompressedPk :: [Byte ^ 65] -- urn:ubideco:semid:8ALgmuRBL8YRNWmcFoPdBFCsZwCvDjupXgdq9DjFVnkV#waiter-salad-casino data VBytes :: U32 -- urn:ubideco:semid:AZqcQMyFBmixEkiDtAqGxB4nM8i9hmxV3mk4vUgNAkWT#conan-avalon-food data VarInt :: U64 -- urn:ubideco:semid:3HHRtSJW5fnGkdVW1EVDH7B97Y79WhwvKyyfsaBkuQkk#chrome-robin-gallop data Vout :: U32 +-- urn:ubideco:semid:FyeKxsZ9bQaZG8PDMFjvLxfFTmA2SqUc8TTbS4Wh4daR#plasma-august-marion +data WPubkeyHash :: [Byte ^ 20] +-- urn:ubideco:semid:4QBBHmPiEoFttFq6Tghc1QWDZUqLYnsNiE8gbsFK5hoU#vibrate-charm-spain +data WScriptHash :: [Byte ^ 32] -- urn:ubideco:semid:EcY6NU6BVRVkgCFwfWmSkbVKpLmWWVfDwBRRiSr6FJUC#bridge-version-voyage data WeightUnits :: U32 -- urn:ubideco:semid:8mjN2CZj3Nhn2HjnKqTmEcN5vmyb3UJK8HSFW1uE3W2p#warning-saddle-period @@ -133,4 +154,6 @@ data WitnessVer :: v0:0 | v1:81 | v2:82 | v3:83 -- urn:ubideco:semid:HZbMnxQ1p2duNgYuYrQn9xtHBwSom63zWtRwy6pmBGfU#sushi-polygon-circus data Wtxid :: [Byte ^ 32] +-- urn:ubideco:semid:J1BbH2Lx8P3yw9G244d92MMTP5jrkiaVzsr6FzRxpfur#evident-finance-promo +data XOnlyPk :: [Byte ^ 32]