From 722d6b3c81bce5915e561793f37a98283b2a476f Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sat, 7 Oct 2023 13:42:00 +0200 Subject: [PATCH 01/14] consensus: make InvalidPubkey error generic over key length --- consensus/src/taproot.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/consensus/src/taproot.rs b/consensus/src/taproot.rs index 4fed9430..7a41bab2 100644 --- a/consensus/src/taproot.rs +++ b/consensus/src/taproot.rs @@ -27,7 +27,7 @@ use std::ops::BitXor; use std::{cmp, io, slice, vec}; use amplify::confinement::{Confined, U32}; -use amplify::{confinement, Bytes32, Wrapper}; +use amplify::{confinement, Array, Bytes32, Wrapper}; use commit_verify::{DigestExt, Sha256}; use secp256k1::{PublicKey, Scalar, XOnlyPublicKey}; use strict_encoding::{ @@ -57,10 +57,10 @@ const MIDSTATE_TAPSIGHASH: [u8; 10] = *b"TapSighash"; #[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)] #[display("invalid public key")] -pub struct InvalidPubkey(pub Bytes32); +pub struct InvalidPubkey(pub Array); -impl From for DecodeError { - fn from(e: InvalidPubkey) -> Self { +impl From> for DecodeError { + fn from(e: InvalidPubkey) -> Self { DecodeError::DataIntegrityError(format!("invalid x-only public key value '{:x}'", e.0)) } } @@ -89,7 +89,7 @@ macro_rules! dumb_key { pub struct TaprootPk(XOnlyPublicKey); 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())) @@ -142,7 +142,7 @@ impl InternalPk { #[inline] pub fn from_unchecked(pk: TaprootPk) -> Self { Self(pk.0) } - pub fn from_byte_array(data: [u8; 32]) -> Result { + pub fn from_byte_array(data: [u8; 32]) -> Result> { XOnlyPublicKey::from_slice(&data) .map(Self) .map_err(|_| InvalidPubkey(data.into())) @@ -220,7 +220,7 @@ impl OutputPk { #[inline] pub fn from_unchecked(pk: TaprootPk) -> Self { Self(pk.0) } - pub fn from_byte_array(data: [u8; 32]) -> Result { + pub fn from_byte_array(data: [u8; 32]) -> Result> { XOnlyPublicKey::from_slice(&data) .map(Self) .map_err(|_| InvalidPubkey(data.into())) From ac2e3be8f0512e6c53dcdaae1a7cd54258982226 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 8 Oct 2023 12:20:47 +0200 Subject: [PATCH 02/14] consensus: add hastypes from bp-std --- consensus/src/hashtypes.rs | 134 +++++++++++++++++++++++++++++++++++++ consensus/src/lib.rs | 2 + 2 files changed, 136 insertions(+) create mode 100644 consensus/src/hashtypes.rs diff --git a/consensus/src/hashtypes.rs b/consensus/src/hashtypes.rs new file mode 100644 index 00000000..ab2d5ed7 --- /dev/null +++ b/consensus/src/hashtypes.rs @@ -0,0 +1,134 @@ +// 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::fmt; +use std::fmt::{Debug, Formatter}; + +use amplify::hex::ToHex; +use amplify::{Array, Wrapper}; + +#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Display, From)] +#[wrapper(BorrowSlice, Index, RangeOps, FromStr, Hex)] +#[display(LowerHex)] +pub struct PubkeyHash( + #[from] + #[from([u8; 20])] + Array, +); + +impl AsRef<[u8; 20]> for PubkeyHash { + fn as_ref(&self) -> &[u8; 20] { self.0.as_inner() } +} + +impl AsRef<[u8]> for PubkeyHash { + fn as_ref(&self) -> &[u8] { self.0.as_ref() } +} + +impl From for [u8; 20] { + fn from(value: PubkeyHash) -> Self { value.0.into_inner() } +} + +impl Debug for PubkeyHash { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_tuple("PubkeyHash").field(&self.to_hex()).finish() + } +} + +#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Display, From)] +#[wrapper(BorrowSlice, Index, RangeOps, FromStr, Hex)] +#[display(LowerHex)] +pub struct ScriptHash( + #[from] + #[from([u8; 20])] + Array, +); + +impl AsRef<[u8; 20]> for ScriptHash { + fn as_ref(&self) -> &[u8; 20] { self.0.as_inner() } +} + +impl AsRef<[u8]> for ScriptHash { + fn as_ref(&self) -> &[u8] { self.0.as_ref() } +} + +impl From for [u8; 20] { + fn from(value: ScriptHash) -> Self { value.0.into_inner() } +} + +impl Debug for ScriptHash { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_tuple("ScriptHash").field(&self.to_hex()).finish() + } +} + +#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Display, From)] +#[wrapper(BorrowSlice, Index, RangeOps, FromStr, Hex)] +#[display(LowerHex)] +pub struct WPubkeyHash( + #[from] + #[from([u8; 20])] + Array, +); + +impl AsRef<[u8; 20]> for WPubkeyHash { + fn as_ref(&self) -> &[u8; 20] { self.0.as_inner() } +} + +impl AsRef<[u8]> for WPubkeyHash { + fn as_ref(&self) -> &[u8] { self.0.as_ref() } +} + +impl From for [u8; 20] { + fn from(value: WPubkeyHash) -> Self { value.0.into_inner() } +} + +impl Debug for WPubkeyHash { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_tuple("WPubkeyHash").field(&self.to_hex()).finish() + } +} + +#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Display, From)] +#[wrapper(BorrowSlice, Index, RangeOps, FromStr, Hex)] +#[display(LowerHex)] +pub struct WScriptHash( + #[from] + #[from([u8; 32])] + Array, +); + +impl AsRef<[u8; 32]> for WScriptHash { + fn as_ref(&self) -> &[u8; 32] { self.0.as_inner() } +} + +impl AsRef<[u8]> for WScriptHash { + fn as_ref(&self) -> &[u8] { self.0.as_ref() } +} + +impl From for [u8; 32] { + fn from(value: WScriptHash) -> Self { value.0.into_inner() } +} + +impl Debug for WScriptHash { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_tuple("WScriptHash").field(&self.to_hex()).finish() + } +} diff --git a/consensus/src/lib.rs b/consensus/src/lib.rs index cd298aea..e75cbada 100644 --- a/consensus/src/lib.rs +++ b/consensus/src/lib.rs @@ -62,6 +62,7 @@ mod script; mod segwit; mod taproot; mod tx; +mod hashtypes; mod sigtypes; mod util; mod weights; @@ -74,6 +75,7 @@ pub use coding::{ ByteStr, ConsensusDataError, ConsensusDecode, ConsensusDecodeError, ConsensusEncode, LenVarInt, VarInt, VarIntArray, }; +pub use hashtypes::{PubkeyHash, ScriptHash, WPubkeyHash, WScriptHash}; 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}; From 607b0dd2f0dff4c5d32cb53cd38471d48722c8b8 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 8 Oct 2023 20:31:05 +0200 Subject: [PATCH 03/14] consensus: remove as much boilerplate code using derives as possible --- Cargo.lock | 61 ++++++++++++++----- Cargo.toml | 6 +- consensus/src/block.rs | 10 +-- consensus/src/coding.rs | 24 +------- consensus/src/hashtypes.rs | 122 +++++++++++++------------------------ consensus/src/lib.rs | 6 +- consensus/src/script.rs | 108 +++++++++++++++----------------- consensus/src/segwit.rs | 23 +------ consensus/src/stl.rs | 12 ++-- consensus/src/taproot.rs | 91 ++++++++++++++------------- consensus/src/tx.rs | 9 +-- stl/Bitcoin@0.1.0.sta | 70 ++++++++++----------- stl/Bitcoin@0.1.0.stl | Bin 2596 -> 2723 bytes stl/Bitcoin@0.1.0.sty | 16 ++++- 14 files changed, 257 insertions(+), 301 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d60a344..1a8b740c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,14 +4,14 @@ version = 3 [[package]] name = "amplify" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a7164b219700bd9a4b8708bf4784b974d079c8c8b761b9f18a0c45ece214cb" +checksum = "213783efd41ed08b2861e86dd5aac633cf612f861e9222ace8b3621bd58af5c6" dependencies = [ "amplify_apfloat", - "amplify_derive", + "amplify_derive 3.0.1", "amplify_num", - "amplify_syn", + "amplify_syn 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "ascii", "serde", "serde_json", @@ -31,13 +31,24 @@ dependencies = [ "bitflags", ] +[[package]] +name = "amplify_derive" +version = "3.0.1" +source = "git+https://github.com/rust-amplify/amplify-derive?branch=wrapper#9ca1b8af3269a24a1aa75c158ce1e672aa56cdec" +dependencies = [ + "amplify_syn 2.0.1 (git+https://github.com/rust-amplify/amplify-derive?branch=wrapper)", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "amplify_derive" version = "4.0.0-alpha.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01c4835e964725149d7961ec5af2ca1302f6f68c8c738b4acb06185f596c3333" dependencies = [ - "amplify_syn", + "amplify_syn 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", "syn 1.0.109", @@ -63,6 +74,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "amplify_syn" +version = "2.0.1" +source = "git+https://github.com/rust-amplify/amplify-derive?branch=wrapper#9ca1b8af3269a24a1aa75c158ce1e672aa56cdec" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "arrayref" version = "0.3.7" @@ -236,11 +257,10 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "commit_encoding_derive" version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00033f14d67c4169d588f085ea2faeb7b610cf03a74d42ea09eeba31abef2047" +source = "git+https://github.com/LNP-BP/client_side_validation?branch=develop#23b7c189e2b5eed08f89d304381fb42c10e86c6e" dependencies = [ "amplify", - "amplify_syn", + "amplify_syn 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", "syn 1.0.109", @@ -248,13 +268,13 @@ dependencies = [ [[package]] name = "commit_verify" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caa8114b3ff20947176c8cbfd1e84e56649501eed4e33ba9205c70374b2615ae" +version = "0.10.6" +source = "git+https://github.com/LNP-BP/client_side_validation?branch=develop#23b7c189e2b5eed08f89d304381fb42c10e86c6e" dependencies = [ "amplify", "commit_encoding_derive", "rand", + "ripemd", "serde", "sha2", "strict_encoding", @@ -482,6 +502,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 +589,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 2.0.2", "itoa", "ryu", "serde", @@ -588,7 +617,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ae7f7cb6a68cfc99674a70a47ab790c6ede965107cd0823ed814b5e73b3bee2" dependencies = [ - "amplify_derive", + "amplify_derive 4.0.0-alpha.6", ] [[package]] @@ -608,7 +637,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f5adae55367464f5a229bfd539682c94f870b98a220be6e61dc43f85d612e7e" dependencies = [ - "amplify_syn", + "amplify_syn 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "heck", "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 0844a24a..0c142ecb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ edition = "2021" license = "Apache-2.0" [workspace.dependencies] -amplify = "4.2.0" +amplify = "4.4.0" strict_encoding = "2.5.0" strict_types = "1.6.2" commit_verify = "0.10.5" @@ -80,3 +80,7 @@ stl = ["strict_types", "strict_types/base64", "bp-consensus/stl", "commit_verify [package.metadata.docs.rs] features = [ "all" ] + +[patch.crates-io] +amplify_derive = { git = "https://github.com/rust-amplify/amplify-derive", branch = "wrapper" } +commit_verify = { git = "https://github.com/LNP-BP/client_side_validation", branch = "develop" } 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..dd2a4b95 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) } diff --git a/consensus/src/hashtypes.rs b/consensus/src/hashtypes.rs index ab2d5ed7..dd6518bb 100644 --- a/consensus/src/hashtypes.rs +++ b/consensus/src/hashtypes.rs @@ -19,116 +19,82 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::fmt; -use std::fmt::{Debug, Formatter}; - -use amplify::hex::ToHex; -use amplify::{Array, Wrapper}; - -#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Display, From)] -#[wrapper(BorrowSlice, Index, RangeOps, FromStr, Hex)] -#[display(LowerHex)] +use amplify::{Bytes20, Bytes32, Wrapper}; + +use crate::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])] - Array, + Bytes20, ); -impl AsRef<[u8; 20]> for PubkeyHash { - fn as_ref(&self) -> &[u8; 20] { self.0.as_inner() } -} - -impl AsRef<[u8]> for PubkeyHash { - fn as_ref(&self) -> &[u8] { self.0.as_ref() } -} - impl From for [u8; 20] { fn from(value: PubkeyHash) -> Self { value.0.into_inner() } } -impl Debug for PubkeyHash { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_tuple("PubkeyHash").field(&self.to_hex()).finish() - } -} - -#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Display, From)] -#[wrapper(BorrowSlice, Index, RangeOps, FromStr, Hex)] -#[display(LowerHex)] +#[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])] - Array, + Bytes20, ); -impl AsRef<[u8; 20]> for ScriptHash { - fn as_ref(&self) -> &[u8; 20] { self.0.as_inner() } -} - -impl AsRef<[u8]> for ScriptHash { - fn as_ref(&self) -> &[u8] { self.0.as_ref() } -} - impl From for [u8; 20] { fn from(value: ScriptHash) -> Self { value.0.into_inner() } } -impl Debug for ScriptHash { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_tuple("ScriptHash").field(&self.to_hex()).finish() - } -} - -#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Display, From)] -#[wrapper(BorrowSlice, Index, RangeOps, FromStr, Hex)] -#[display(LowerHex)] +#[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])] - Array, + Bytes20, ); -impl AsRef<[u8; 20]> for WPubkeyHash { - fn as_ref(&self) -> &[u8; 20] { self.0.as_inner() } -} - -impl AsRef<[u8]> for WPubkeyHash { - fn as_ref(&self) -> &[u8] { self.0.as_ref() } -} - impl From for [u8; 20] { fn from(value: WPubkeyHash) -> Self { value.0.into_inner() } } -impl Debug for WPubkeyHash { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_tuple("WPubkeyHash").field(&self.to_hex()).finish() - } -} - -#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Display, From)] -#[wrapper(BorrowSlice, Index, RangeOps, FromStr, Hex)] -#[display(LowerHex)] +#[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])] - Array, + Bytes32, ); -impl AsRef<[u8; 32]> for WScriptHash { - fn as_ref(&self) -> &[u8; 32] { self.0.as_inner() } -} - -impl AsRef<[u8]> for WScriptHash { - fn as_ref(&self) -> &[u8] { self.0.as_ref() } -} - impl From for [u8; 32] { fn from(value: WScriptHash) -> Self { value.0.into_inner() } } - -impl Debug for WScriptHash { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_tuple("WScriptHash").field(&self.to_hex()).finish() - } -} diff --git a/consensus/src/lib.rs b/consensus/src/lib.rs index e75cbada..2bb04020 100644 --- a/consensus/src/lib.rs +++ b/consensus/src/lib.rs @@ -81,9 +81,9 @@ pub use segwit::{SegwitError, Witness, WitnessProgram, WitnessScript, WitnessVer 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, - TAPROOT_LEAF_TAPSCRIPT, + InvalidPubkey, LeafScript, LeafVer, OutputPk, Parity, PubkeyParseError, TapBranchHash, TapCode, + TapLeafHash, TapMerklePath, TapNodeHash, TapScript, TaprootPk, TAPROOT_ANNEX_PREFIX, + TAPROOT_LEAF_MASK, TAPROOT_LEAF_TAPSCRIPT, }; pub use tx::{ LockTime, Outpoint, OutpointParseError, Sats, SeqNo, Tx, TxIn, TxOut, TxParseError, TxVer, 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..90fe506c 100644 --- a/consensus/src/segwit.rs +++ b/consensus/src/segwit.rs @@ -22,7 +22,6 @@ use std::vec; use amplify::confinement::Confined; -use amplify::hex::{self, FromHex}; use amplify::Bytes32StrRev; use crate::opcodes::*; @@ -302,8 +301,8 @@ impl ScriptPubkey { } #[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( @@ -330,15 +329,6 @@ impl WitnessScript { pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 } } -impl FromHex for WitnessScript { - 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, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, From)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] @@ -349,20 +339,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..17337cea 100644 --- a/consensus/src/stl.rs +++ b/consensus/src/stl.rs @@ -24,9 +24,9 @@ use strict_types::{CompileError, LibBuilder, TypeLib}; 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, + LegacySig, OpCode, OutputPk, PubkeyHash, RedeemScript, ScriptHash, TapCode, TapLeafHash, + TapNodeHash, TapScript, Tx, 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 +35,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:BXLU9v5ByfaCA9usMQZPf4azau1u1gcGaMPicp56ULZ1#nadia-canal-king"; #[deprecated(since = "0.10.8", note = "use _bp_tx_stl instead")] fn _bitcoin_stl() -> Result { _bp_tx_stl() } @@ -53,6 +53,10 @@ fn _bp_consensus_stl() -> Result { .transpile::() .transpile::() .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() .transpile::() .transpile::() .transpile::() diff --git a/consensus/src/taproot.rs b/consensus/src/taproot.rs index 7a41bab2..f85847af 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::{self, FromHex}; use amplify::{confinement, Array, Bytes32, Wrapper}; use commit_verify::{DigestExt, Sha256}; use secp256k1::{PublicKey, Scalar, XOnlyPublicKey}; @@ -55,6 +57,15 @@ const MIDSTATE_TAPTWEAK: [u8; 8] = *b"TapTweak"; const MIDSTATE_TAPSIGHASH: [u8; 10] = *b"TapSighash"; // f504a425d7f8783b1363868ae3e556586eee945dbc7888dd02a6e2c31873fe9f +#[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, Error)] #[display("invalid public key")] pub struct InvalidPubkey(pub Array); @@ -77,7 +88,7 @@ macro_rules! dumb_key { /// 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!())] @@ -124,36 +135,46 @@ impl StrictDecode for TaprootPk { } } +impl FromStr for TaprootPk { + 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(TaprootPk); impl InternalPk { #[inline] - pub fn from_unchecked(pk: TaprootPk) -> Self { Self(pk.0) } + pub fn from_unchecked(pk: TaprootPk) -> Self { Self(pk) } + #[inline] pub fn from_byte_array(data: [u8; 32]) -> Result> { - XOnlyPublicKey::from_slice(&data) - .map(Self) - .map_err(|_| InvalidPubkey(data.into())) + TaprootPk::from_byte_array(data).map(Self) } - pub fn to_byte_array(&self) -> [u8; 32] { self.0.serialize() } + #[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 +182,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 +196,7 @@ impl InternalPk { tweaked_parity, tweak )); - (OutputPk(output_key), tweaked_parity.into()) + (OutputPk(TaprootPk(output_key)), tweaked_parity.into()) } } @@ -194,9 +215,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 = TaprootPk::from_byte_array(bytes.to_byte_array())?; + Ok(InternalPk(pk)) }) } } @@ -207,50 +227,32 @@ 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(TaprootPk); impl OutputPk { #[inline] - pub fn from_unchecked(pk: TaprootPk) -> Self { Self(pk.0) } + pub fn from_unchecked(pk: TaprootPk) -> Self { Self(pk) } + #[inline] pub fn from_byte_array(data: [u8; 32]) -> Result> { - XOnlyPublicKey::from_slice(&data) - .map(Self) - .map_err(|_| InvalidPubkey(data.into())) + TaprootPk::from_byte_array(data).map(Self) } - pub fn to_byte_array(&self) -> [u8; 32] { self.0.serialize() } + #[inline] + pub fn to_byte_array(&self) -> [u8; 32] { self.0.to_byte_array() } } impl From for [u8; 32] { fn from(pk: OutputPk) -> [u8; 32] { pk.to_byte_array() } } -impl StrictEncode for OutputPk { - fn strict_encode(&self, writer: W) -> io::Result { - let bytes = Bytes32::from(self.0.serialize()); - writer.write_newtype::(&bytes) - } -} - -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()) - }) - } -} - pub trait IntoTapHash { fn into_tap_hash(self) -> TapNodeHash; } @@ -319,7 +321,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 +341,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 +598,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/tx.rs b/consensus/src/tx.rs index 22e98ce5..71bf7c7b 100644 --- a/consensus/src/tx.rs +++ b/consensus/src/tx.rs @@ -45,7 +45,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 +60,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)] diff --git a/stl/Bitcoin@0.1.0.sta b/stl/Bitcoin@0.1.0.sta index 2c152aa6..88df8d8a 100644 --- a/stl/Bitcoin@0.1.0.sta +++ b/stl/Bitcoin@0.1.0.sta @@ -1,11 +1,11 @@ -----BEGIN STRICT TYPE LIB----- -Id: urn:ubideco:stl:D42LxJBQokrGJzvoSV3E1HoriGgLzPcxuL61JymwjEqV +Id: urn:ubideco:stl:BXLU9v5ByfaCA9usMQZPf4azau1u1gcGaMPicp56ULZ1 Name: Bitcoin Dependencies: urn:ubideco:stl:9KALDYR8Nyjq4FdMW6kYoL7vdkWnqPqNuFnmE9qHpNjZ B0JpdGNvaW4Be4SAPJ764hElp3wsObxw0v3o+UOuDf2c9OaC7cdmynADU3RkAQNT -dGQBAGGGItF7rvBmAt/ndcmA4LNrbrroCQ2AdfdRO+xLk/ZNBEJvb2wsAAlCaXAz +dGQBAGGGItF7rvBmAt/ndcmA4LNrbrroCQ2AdfdRO+xLk/ZNBEJvb2wxAAlCaXAz NDBTaWcGAgNzaWcABwAAQEAADHNpZ2hhc2hfdHlwZQAEAgAEbm9uZQAAAAEEc29t ZQAFAQHbF2Q+dwkTJ+gEmSSB1dxgFSMIlVM55Yszn9RMT3JIKglCbG9ja0hhc2gF AQAHAABAIAALQmxvY2tIZWFkZXIGBgd2ZXJzaW9uAABEDXByZXZCbG9ja0hhc2gB @@ -27,38 +27,40 @@ 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== +uslDdQhPdXRwdXRQawUBAb/KFrvCPoxAevOKTOsWOLsldYdSgSH50t1qtCASSyXW +BlBhcml0eQMCBGV2ZW4AA29kZAEKUHVia2V5SGFzaAUBAAcAAEAUAAxSZWRlZW1T +Y3JpcHQFAQElq/W5EhQXuqM51F97FXWncUvhenUYXdAxNXrUyS3phgRTYXRzBQEA +AAgLU2NyaXB0Qnl0ZXMFAQAIAABAAAAAAAAAAAD/////AAAAAApTY3JpcHRIYXNo +BQEABwAAQBQADFNjcmlwdFB1YmtleQUBASWr9bkSFBe6oznUX3sVdadxS+F6dRhd +0DE1etTJLemGBVNlcU5vBQEAAAQJU2lnU2NyaXB0BQEBJav1uRIUF7qjOdRfexV1 +p3FL4Xp1GF3QMTV61Mkt6YYLU2lnaGFzaEZsYWcDAwNhbGwBBG5vbmUCBnNpbmds +ZQMLU2lnaGFzaFR5cGUGAgRmbGFnAf8+an/Ix4VDTvyO53SzALGb7Jhxopoe8QCL +7xDrKNU0DGFueW9uZUNhblBheQJ7hIA8nvriESWnfCw5vHDS/ej5Q64N/Zz05oLt +x2bKcGGGItF7rvBmAt/ndcmA4LNrbrroCQ2AdfdRO+xLk/ZNDVRhcEJyYW5jaEhh +c2gFAQAHAABAIAAHVGFwQ29kZQMGC3B1c2hCeXRlczMyIAlwdXNoRGF0YTFMCXB1 +c2hEYXRhMk0JcHVzaERhdGE0TghyZXNlcnZlZFAGcmV0dXJuagtUYXBMZWFmSGFz +aAUBAAcAAEAgAA1UYXBNZXJrbGVQYXRoBQEACAGv68Wd2P1QRx2YXcJBKC6rqYxI +ivJkY8F2flEGIsKS2AAAAAAAAAAAgAAAAAAAAAALVGFwTm9kZUhhc2gFAQAHAABA +IAAJVGFwU2NyaXB0BQEBJav1uRIUF7qjOdRfexV1p3FL4Xp1GF3QMTV61Mkt6YYJ +VGFwcm9vdFBrBQEABwAAQCAAAlR4BgQHdmVyc2lvbgGofMZ5ZGflV9dE5cliOuAM +y5XZGg0vA990wHUST77HngZpbnB1dHMACAEZRy0X2GIHpw8hMalYoWilbirWUz+c +ZkwZd9pc+f5ihgAAAAAAAAAA/////wAAAAAHb3V0cHV0cwAIAZA7ZHB5hI8bJlMq +5M7GpolEoFNYuPCByyazu/gtnjvzAAAAAAAAAAD/////AAAAAAhsb2NrVGltZQE1 +2h0VOSBuu93cpMM9hzHq8pun2nTfPciCIBxOFrW5HARUeEluBgQKcHJldk91dHB1 +dAHoakDNXCX5veKE/2mlETKnQSshVb0OVhLBv+OElWDFBglzaWdTY3JpcHQBOHUE +sUt8DkZtR54XR30kdCR/+ANk7D1Lqvc6IPdBdGsIc2VxdWVuY2UBARlthSnI9tpE +TRVOjZyMvZ4PjYkCWjuwkSHGPVKwHocHd2l0bmVzcwFzd0NwNpMgSc0olQiqcXzj +gp2yRS5V6E7U/C48zS4AnQVUeE91dAYCBXZhbHVlAZf11wZCriozkiU7qE4dzsST +478+03Gxh3OGNU7MiIJrDHNjcmlwdFB1YmtleQG+/B78ZqUZ/WRSajoTh0Dn8RAt +C77/OsFGTvP3QHZ0XAVUeFZlcgUBAABEBFR4aWQFAQAHAABAIAAGVkJ5dGVzBQEA +AAQGVmFySW50BQEAAAgEVm91dAUBAAAEC1dQdWJrZXlIYXNoBQEABwAAQBQAC1dT +Y3JpcHRIYXNoBQEABwAAQCAAC1dlaWdodFVuaXRzBQEAAAQHV2l0bmVzcwUBAAgA +CAAAQAAAAAAAAAAA/////wAAAAAAAAAAAAAAAP////8AAAAADldpdG5lc3NQcm9n +cmFtBgIHdmVyc2lvbgHR7Np2Z+RC7U0SaaZnAzbl/vQ3r17OK2muugxGVsT6rQdw +cm9ncmFtAAgAAEACAAAAAAAAACgAAAAAAAAADVdpdG5lc3NTY3JpcHQFAQElq/W5 +EhQXuqM51F97FXWncUvhenUYXdAxNXrUyS3phgpXaXRuZXNzVmVyAxECdjAAAnYx +UQJ2MlICdjNTAnY0VAJ2NVUCdjZWAnY3VwJ2OFgCdjlZA3YxMFoDdjExWwN2MTJc +A3YxM10DdjE0XgN2MTVfA3YxNmAFV3R4aWQFAQAHAABAIAA= -----END STRICT TYPE LIB----- diff --git a/stl/Bitcoin@0.1.0.stl b/stl/Bitcoin@0.1.0.stl index 4c1055410a78c1fdcc2bf23610fc087f72757654..3c8169a4352e444d427894189c118e0629bc8abc 100644 GIT binary patch delta 186 zcmZ1?vRHINB%|TRC{`9v#{H+nb|13qaj5#- Date: Sun, 8 Oct 2023 21:01:49 +0200 Subject: [PATCH 04/14] consensus: add pubkey types moved from bp-std --- consensus/src/lib.rs | 8 +- consensus/src/pubkeys.rs | 240 +++++++++++++++++++++++++++++++++++++++ consensus/src/stl.rs | 13 ++- consensus/src/taproot.rs | 66 +++++------ stl/Bitcoin@0.1.0.sta | 105 ++++++++--------- stl/Bitcoin@0.1.0.stl | Bin 2723 -> 2868 bytes stl/Bitcoin@0.1.0.sty | 16 ++- 7 files changed, 344 insertions(+), 104 deletions(-) create mode 100644 consensus/src/pubkeys.rs diff --git a/consensus/src/lib.rs b/consensus/src/lib.rs index 2bb04020..10b87641 100644 --- a/consensus/src/lib.rs +++ b/consensus/src/lib.rs @@ -59,6 +59,7 @@ pub extern crate secp256k1; mod block; pub mod opcodes; mod script; +mod pubkeys; mod segwit; mod taproot; mod tx; @@ -76,14 +77,15 @@ pub use coding::{ 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, PubkeyParseError, TapBranchHash, TapCode, - TapLeafHash, TapMerklePath, TapNodeHash, TapScript, TaprootPk, TAPROOT_ANNEX_PREFIX, - TAPROOT_LEAF_MASK, TAPROOT_LEAF_TAPSCRIPT, + LeafScript, LeafVer, OutputPk, Parity, TapBranchHash, TapCode, TapLeafHash, TapMerklePath, + TapNodeHash, TapScript, XOnlyPk, TAPROOT_ANNEX_PREFIX, TAPROOT_LEAF_MASK, + TAPROOT_LEAF_TAPSCRIPT, }; pub use tx::{ LockTime, Outpoint, OutpointParseError, Sats, SeqNo, Tx, TxIn, TxOut, TxParseError, TxVer, diff --git a/consensus/src/pubkeys.rs b/consensus/src/pubkeys.rs new file mode 100644 index 00000000..dcb7beb2 --- /dev/null +++ b/consensus/src/pubkeys.rs @@ -0,0 +1,240 @@ +// 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, Error)] +#[display("invalid public key")] +pub struct InvalidPubkey(pub 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(data.into())) + } + pub fn to_byte_array(&self) -> [u8; 33] { self.0.serialize() } +} + +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(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(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(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 len = bytes.len().min(65); + let mut data = [0u8; 65]; + data[..len].copy_from_slice(&bytes[..len]); + + let pubkey = PublicKey::from_slice(bytes).map_err(|_| InvalidPubkey(data.into()).into())?; + + Ok(match bytes.len() { + 33 => Self::compressed(pubkey), + 65 => Self::uncompressed(pubkey), + _ => { + return Err(InvalidPubkey(data.into())); + } + }) + } +} + +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(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/stl.rs b/consensus/src/stl.rs index 17337cea..9df9cde4 100644 --- a/consensus/src/stl.rs +++ b/consensus/src/stl.rs @@ -23,10 +23,10 @@ use strict_types::{CompileError, LibBuilder, TypeLib}; use crate::{ - Bip340Sig, BlockHeader, ByteStr, Chain, ControlBlock, FutureLeafVer, InternalPk, LeafScript, - LegacySig, OpCode, OutputPk, PubkeyHash, RedeemScript, ScriptHash, TapCode, TapLeafHash, - TapNodeHash, TapScript, Tx, VBytes, VarInt, WPubkeyHash, WScriptHash, WeightUnits, - WitnessProgram, WitnessScript, WitnessVer, Wtxid, LIB_NAME_BITCOIN, + Bip340Sig, BlockHeader, ByteStr, Chain, CompressedPk, ControlBlock, FutureLeafVer, InternalPk, + LeafScript, LegacyPk, LegacySig, 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 +35,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:BXLU9v5ByfaCA9usMQZPf4azau1u1gcGaMPicp56ULZ1#nadia-canal-king"; + "urn:ubideco:stl:3NDewC9zBEosrNM82npGstFcvPhjg9E9nLoQZrQq6kwe#remark-shelter-weekend"; #[deprecated(since = "0.10.8", note = "use _bp_tx_stl instead")] fn _bitcoin_stl() -> Result { _bp_tx_stl() } @@ -62,6 +62,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 f85847af..6a4a77bc 100644 --- a/consensus/src/taproot.rs +++ b/consensus/src/taproot.rs @@ -28,8 +28,8 @@ use std::str::FromStr; use std::{cmp, io, slice, vec}; use amplify::confinement::{Confined, U32}; -use amplify::hex::{self, FromHex}; -use amplify::{confinement, Array, Bytes32, Wrapper}; +use amplify::hex::FromHex; +use amplify::{confinement, Bytes32, Wrapper}; use commit_verify::{DigestExt, Sha256}; use secp256k1::{PublicKey, Scalar, XOnlyPublicKey}; use strict_encoding::{ @@ -38,7 +38,9 @@ use strict_encoding::{ }; use crate::opcodes::*; -use crate::{ScriptBytes, ScriptPubkey, WitnessVer, LIB_NAME_BITCOIN}; +use crate::{ + InvalidPubkey, PubkeyParseError, ScriptBytes, ScriptPubkey, WitnessVer, LIB_NAME_BITCOIN, +}; /// The SHA-256 midstate value for the TapLeaf hash. const MIDSTATE_TAPLEAF: [u8; 7] = *b"TapLeaf"; @@ -57,31 +59,12 @@ const MIDSTATE_TAPTWEAK: [u8; 8] = *b"TapTweak"; const MIDSTATE_TAPSIGHASH: [u8; 10] = *b"TapSighash"; // f504a425d7f8783b1363868ae3e556586eee945dbc7888dd02a6e2c31873fe9f -#[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, Error)] -#[display("invalid public key")] -pub struct InvalidPubkey(pub Array); - impl From> for DecodeError { fn from(e: InvalidPubkey) -> Self { DecodeError::DataIntegrityError(format!("invalid x-only public key value '{:x}'", e.0)) } } -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: @@ -91,15 +74,17 @@ macro_rules! dumb_key { #[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> { XOnlyPublicKey::from_slice(data.as_ref()) .map(Self) @@ -109,22 +94,22 @@ impl TaprootPk { pub fn to_byte_array(&self) -> [u8; 32] { self.0.serialize() } } -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()?; @@ -135,7 +120,7 @@ impl StrictDecode for TaprootPk { } } -impl FromStr for TaprootPk { +impl FromStr for XOnlyPk { type Err = PubkeyParseError<32>; fn from_str(s: &str) -> Result { @@ -157,15 +142,15 @@ impl FromStr for TaprootPk { derive(Serialize, Deserialize), serde(crate = "serde_crate", transparent) )] -pub struct InternalPk(TaprootPk); +pub struct InternalPk(XOnlyPk); impl InternalPk { #[inline] - pub fn from_unchecked(pk: TaprootPk) -> Self { Self(pk) } + pub fn from_unchecked(pk: XOnlyPk) -> Self { Self(pk) } #[inline] pub fn from_byte_array(data: [u8; 32]) -> Result> { - TaprootPk::from_byte_array(data).map(Self) + XOnlyPk::from_byte_array(data).map(Self) } #[inline] @@ -196,7 +181,7 @@ impl InternalPk { tweaked_parity, tweak )); - (OutputPk(TaprootPk(output_key)), tweaked_parity.into()) + (OutputPk(XOnlyPk(output_key)), tweaked_parity.into()) } } @@ -204,6 +189,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()); @@ -215,7 +201,7 @@ impl StrictDecode for InternalPk { fn strict_decode(reader: &mut impl TypedRead) -> Result { reader.read_tuple(|r| { let bytes: Bytes32 = r.read_field()?; - let pk = TaprootPk::from_byte_array(bytes.to_byte_array())?; + let pk = XOnlyPk::from_byte_array(bytes.to_byte_array())?; Ok(InternalPk(pk)) }) } @@ -234,15 +220,15 @@ impl StrictDecode for InternalPk { derive(Serialize, Deserialize), serde(crate = "serde_crate", transparent) )] -pub struct OutputPk(TaprootPk); +pub struct OutputPk(XOnlyPk); impl OutputPk { #[inline] - pub fn from_unchecked(pk: TaprootPk) -> Self { Self(pk) } + pub fn from_unchecked(pk: XOnlyPk) -> Self { Self(pk) } #[inline] pub fn from_byte_array(data: [u8; 32]) -> Result> { - TaprootPk::from_byte_array(data).map(Self) + XOnlyPk::from_byte_array(data).map(Self) } #[inline] diff --git a/stl/Bitcoin@0.1.0.sta b/stl/Bitcoin@0.1.0.sta index 88df8d8a..9e7cc16f 100644 --- a/stl/Bitcoin@0.1.0.sta +++ b/stl/Bitcoin@0.1.0.sta @@ -1,66 +1,69 @@ -----BEGIN STRICT TYPE LIB----- -Id: urn:ubideco:stl:BXLU9v5ByfaCA9usMQZPf4azau1u1gcGaMPicp56ULZ1 +Id: urn:ubideco:stl:3NDewC9zBEosrNM82npGstFcvPhjg9E9nLoQZrQq6kwe Name: Bitcoin Dependencies: urn:ubideco:stl:9KALDYR8Nyjq4FdMW6kYoL7vdkWnqPqNuFnmE9qHpNjZ B0JpdGNvaW4Be4SAPJ764hElp3wsObxw0v3o+UOuDf2c9OaC7cdmynADU3RkAQNT -dGQBAGGGItF7rvBmAt/ndcmA4LNrbrroCQ2AdfdRO+xLk/ZNBEJvb2wxAAlCaXAz +dGQBAGGGItF7rvBmAt/ndcmA4LNrbrroCQ2AdfdRO+xLk/ZNBEJvb2w0AAlCaXAz 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 -uslDdQhPdXRwdXRQawUBAb/KFrvCPoxAevOKTOsWOLsldYdSgSH50t1qtCASSyXW -BlBhcml0eQMCBGV2ZW4AA29kZAEKUHVia2V5SGFzaAUBAAcAAEAUAAxSZWRlZW1T -Y3JpcHQFAQElq/W5EhQXuqM51F97FXWncUvhenUYXdAxNXrUyS3phgRTYXRzBQEA -AAgLU2NyaXB0Qnl0ZXMFAQAIAABAAAAAAAAAAAD/////AAAAAApTY3JpcHRIYXNo -BQEABwAAQBQADFNjcmlwdFB1YmtleQUBASWr9bkSFBe6oznUX3sVdadxS+F6dRhd -0DE1etTJLemGBVNlcU5vBQEAAAQJU2lnU2NyaXB0BQEBJav1uRIUF7qjOdRfexV1 -p3FL4Xp1GF3QMTV61Mkt6YYLU2lnaGFzaEZsYWcDAwNhbGwBBG5vbmUCBnNpbmds -ZQMLU2lnaGFzaFR5cGUGAgRmbGFnAf8+an/Ix4VDTvyO53SzALGb7Jhxopoe8QCL -7xDrKNU0DGFueW9uZUNhblBheQJ7hIA8nvriESWnfCw5vHDS/ej5Q64N/Zz05oLt -x2bKcGGGItF7rvBmAt/ndcmA4LNrbrroCQ2AdfdRO+xLk/ZNDVRhcEJyYW5jaEhh -c2gFAQAHAABAIAAHVGFwQ29kZQMGC3B1c2hCeXRlczMyIAlwdXNoRGF0YTFMCXB1 -c2hEYXRhMk0JcHVzaERhdGE0TghyZXNlcnZlZFAGcmV0dXJuagtUYXBMZWFmSGFz -aAUBAAcAAEAgAA1UYXBNZXJrbGVQYXRoBQEACAGv68Wd2P1QRx2YXcJBKC6rqYxI -ivJkY8F2flEGIsKS2AAAAAAAAAAAgAAAAAAAAAALVGFwTm9kZUhhc2gFAQAHAABA -IAAJVGFwU2NyaXB0BQEBJav1uRIUF7qjOdRfexV1p3FL4Xp1GF3QMTV61Mkt6YYJ -VGFwcm9vdFBrBQEABwAAQCAAAlR4BgQHdmVyc2lvbgGofMZ5ZGflV9dE5cliOuAM -y5XZGg0vA990wHUST77HngZpbnB1dHMACAEZRy0X2GIHpw8hMalYoWilbirWUz+c -ZkwZd9pc+f5ihgAAAAAAAAAA/////wAAAAAHb3V0cHV0cwAIAZA7ZHB5hI8bJlMq -5M7GpolEoFNYuPCByyazu/gtnjvzAAAAAAAAAAD/////AAAAAAhsb2NrVGltZQE1 -2h0VOSBuu93cpMM9hzHq8pun2nTfPciCIBxOFrW5HARUeEluBgQKcHJldk91dHB1 -dAHoakDNXCX5veKE/2mlETKnQSshVb0OVhLBv+OElWDFBglzaWdTY3JpcHQBOHUE -sUt8DkZtR54XR30kdCR/+ANk7D1Lqvc6IPdBdGsIc2VxdWVuY2UBARlthSnI9tpE -TRVOjZyMvZ4PjYkCWjuwkSHGPVKwHocHd2l0bmVzcwFzd0NwNpMgSc0olQiqcXzj -gp2yRS5V6E7U/C48zS4AnQVUeE91dAYCBXZhbHVlAZf11wZCriozkiU7qE4dzsST -478+03Gxh3OGNU7MiIJrDHNjcmlwdFB1YmtleQG+/B78ZqUZ/WRSajoTh0Dn8RAt -C77/OsFGTvP3QHZ0XAVUeFZlcgUBAABEBFR4aWQFAQAHAABAIAAGVkJ5dGVzBQEA -AAQGVmFySW50BQEAAAgEVm91dAUBAAAEC1dQdWJrZXlIYXNoBQEABwAAQBQAC1dT -Y3JpcHRIYXNoBQEABwAAQCAAC1dlaWdodFVuaXRzBQEAAAQHV2l0bmVzcwUBAAgA -CAAAQAAAAAAAAAAA/////wAAAAAAAAAAAAAAAP////8AAAAADldpdG5lc3NQcm9n -cmFtBgIHdmVyc2lvbgHR7Np2Z+RC7U0SaaZnAzbl/vQ3r17OK2muugxGVsT6rQdw -cm9ncmFtAAgAAEACAAAAAAAAACgAAAAAAAAADVdpdG5lc3NTY3JpcHQFAQElq/W5 -EhQXuqM51F97FXWncUvhenUYXdAxNXrUyS3phgpXaXRuZXNzVmVyAxECdjAAAnYx -UQJ2MlICdjNTAnY0VAJ2NVUCdjZWAnY3VwJ2OFgCdjlZA3YxMFoDdjExWwN2MTJc -A3YxM10DdjE0XgN2MTVfA3YxNmAFV3R4aWQFAQAHAABAIAA= +aWduZXSEDENvbXByZXNzZWRQawUBAAcAAEAhAAxDb250cm9sQmxvY2sGBAtsZWFm +VmVyc2lvbgG2MwmpGL+2kg1uyFikJIcjRlppP3JDB7//Hdc6c4FOyQ9vdXRwdXRL +ZXlQYXJpdHkBj8omI6JVRCOVj6Z6p21DahXeZOsQs/T5mRQiL5URZogKaW50ZXJu +YWxQawHf+AA8gf5H/FQjsdLG4g8N36pmvczzfXQTyR9l2gA8iAxtZXJrbGVCcmFu +Y2gB7gMeZlFQaLZ0GVxRd/QKg8QAUlvBpDAUGABpT73wc6QNRnV0dXJlTGVhZlZl +cgUBAAABCkludGVybmFsUGsFAQAHAABAIAAKTGVhZlNjcmlwdAYCB3ZlcnNpb24B +tjMJqRi/tpINbshYpCSHI0ZaaT9yQwe//x3XOnOBTskGc2NyaXB0ASWr9bkSFBe6 +oznUX3sVdadxS+F6dRhd0DE1etTJLemGB0xlYWZWZXIFAQAAAQhMZWdhY3lQawYC +CmNvbXByZXNzZWQCe4SAPJ764hElp3wsObxw0v3o+UOuDf2c9OaC7cdmynBhhiLR +e67wZgLf53XJgOCza2666AkNgHX3UTvsS5P2TQZwdWJrZXkABwAAQCEACUxlZ2Fj +eVNpZwYCA3NpZwAIAABAAAAAAAAAAAD/AAAAAAAAAAxzaWdoYXNoX3R5cGUB2xdk +PncJEyfoBJkkgdXcYBUjCJVTOeWLM5/UTE9ySCoITG9ja1RpbWUFAQAABAZPcENv +ZGUDEgpwdXNoQnl0ZXMwAAtwdXNoQnl0ZXMzMiAJcHVzaERhdGExTAlwdXNoRGF0 +YTJNCXB1c2hEYXRhNE4IcmVzZXJ2ZWRQCHB1c2hOdW0xUQZyZXR1cm5qA2R1cHYF +ZXF1YWyHC2VxdWFsVmVyaWZ5iAlyaXBlbWQxNjCmBHNoYTGnBnNoYTI1NqgHaGFz +aDE2MKkHaGFzaDI1NqoIY2hlY2tTaWesDmNoZWNrU2lnVmVyaWZ5rQhPdXRwb2lu +dAYCBHR4aWQBo4JC88vX0dChEtqN4WAvVtT4bw7ExHbFwGhZTEsEZVYEdm91dAEh +4z5Dxapc8iknU6M4wWftO2OcTdnOvamPNGkXuslDdQhPdXRwdXRQawUBAfyipyq+ +kf7NgqixmJBjIsJOdqqqNfIk0XMFY6AYLohZBlBhcml0eQMCBGV2ZW4AA29kZAEK +UHVia2V5SGFzaAUBAAcAAEAUAAxSZWRlZW1TY3JpcHQFAQElq/W5EhQXuqM51F97 +FXWncUvhenUYXdAxNXrUyS3phgRTYXRzBQEAAAgLU2NyaXB0Qnl0ZXMFAQAIAABA +AAAAAAAAAAD/////AAAAAApTY3JpcHRIYXNoBQEABwAAQBQADFNjcmlwdFB1Ymtl +eQUBASWr9bkSFBe6oznUX3sVdadxS+F6dRhd0DE1etTJLemGBVNlcU5vBQEAAAQJ +U2lnU2NyaXB0BQEBJav1uRIUF7qjOdRfexV1p3FL4Xp1GF3QMTV61Mkt6YYLU2ln +aGFzaEZsYWcDAwNhbGwBBG5vbmUCBnNpbmdsZQMLU2lnaGFzaFR5cGUGAgRmbGFn +Af8+an/Ix4VDTvyO53SzALGb7Jhxopoe8QCL7xDrKNU0DGFueW9uZUNhblBheQJ7 +hIA8nvriESWnfCw5vHDS/ej5Q64N/Zz05oLtx2bKcGGGItF7rvBmAt/ndcmA4LNr +brroCQ2AdfdRO+xLk/ZNDVRhcEJyYW5jaEhhc2gFAQAHAABAIAAHVGFwQ29kZQMG +C3B1c2hCeXRlczMyIAlwdXNoRGF0YTFMCXB1c2hEYXRhMk0JcHVzaERhdGE0Tghy +ZXNlcnZlZFAGcmV0dXJuagtUYXBMZWFmSGFzaAUBAAcAAEAgAA1UYXBNZXJrbGVQ +YXRoBQEACAGv68Wd2P1QRx2YXcJBKC6rqYxIivJkY8F2flEGIsKS2AAAAAAAAAAA +gAAAAAAAAAALVGFwTm9kZUhhc2gFAQAHAABAIAAJVGFwU2NyaXB0BQEBJav1uRIU +F7qjOdRfexV1p3FL4Xp1GF3QMTV61Mkt6YYCVHgGBAd2ZXJzaW9uAah8xnlkZ+VX +10TlyWI64AzLldkaDS8D33TAdRJPvseeBmlucHV0cwAIARlHLRfYYgenDyExqVih +aKVuKtZTP5xmTBl32lz5/mKGAAAAAAAAAAD/////AAAAAAdvdXRwdXRzAAgBkDtk +cHmEjxsmUyrkzsamiUSgU1i48IHLJrO7+C2eO/MAAAAAAAAAAP////8AAAAACGxv +Y2tUaW1lATXaHRU5IG673dykwz2HMerym6fadN89yIIgHE4WtbkcBFR4SW4GBApw +cmV2T3V0cHV0AehqQM1cJfm94oT/aaURMqdBKyFVvQ5WEsG/44SVYMUGCXNpZ1Nj +cmlwdAE4dQSxS3wORm1HnhdHfSR0JH/4A2TsPUuq9zog90F0awhzZXF1ZW5jZQEB +GW2FKcj22kRNFU6NnIy9ng+NiQJaO7CRIcY9UrAehwd3aXRuZXNzAXN3Q3A2kyBJ +zSiVCKpxfOOCnbJFLlXoTtT8LjzNLgCdBVR4T3V0BgIFdmFsdWUBl/XXBkKuKjOS +JTuoTh3OxJPjvz7TcbGHc4Y1TsyIgmsMc2NyaXB0UHVia2V5Ab78HvxmpRn9ZFJq +OhOHQOfxEC0Lvv86wUZO8/dAdnRcBVR4VmVyBQEAAEQEVHhpZAUBAAcAAEAgAA5V +bmNvbXByZXNzZWRQawUBAAcAAEBBAAZWQnl0ZXMFAQAABAZWYXJJbnQFAQAACARW +b3V0BQEAAAQLV1B1YmtleUhhc2gFAQAHAABAFAALV1NjcmlwdEhhc2gFAQAHAABA +IAALV2VpZ2h0VW5pdHMFAQAABAdXaXRuZXNzBQEACAAIAABAAAAAAAAAAAD///// +AAAAAAAAAAAAAAAA/////wAAAAAOV2l0bmVzc1Byb2dyYW0GAgd2ZXJzaW9uAdHs +2nZn5ELtTRJppmcDNuX+9DevXs4raa66DEZWxPqtB3Byb2dyYW0ACAAAQAIAAAAA +AAAAKAAAAAAAAAANV2l0bmVzc1NjcmlwdAUBASWr9bkSFBe6oznUX3sVdadxS+F6 +dRhd0DE1etTJLemGCldpdG5lc3NWZXIDEQJ2MAACdjFRAnYyUgJ2M1MCdjRUAnY1 +VQJ2NlYCdjdXAnY4WAJ2OVkDdjEwWgN2MTFbA3YxMlwDdjEzXQN2MTReA3YxNV8D +djE2YAVXdHhpZAUBAAcAAEAgAAdYT25seVBrBQEABwAAQCAA -----END STRICT TYPE LIB----- diff --git a/stl/Bitcoin@0.1.0.stl b/stl/Bitcoin@0.1.0.stl index 3c8169a4352e444d427894189c118e0629bc8abc..6e7be1b36cdd20fa06bc1e5d11c584ceaf8fa6c2 100644 GIT binary patch delta 178 zcmZ21xZH$qO;v7Dy>50je0oiO! zT*>(`WlR$}6(+N=De$oslqO}TR>HL0XKG|r__Jua*1n1V&Ni*sIAcPx(jmXHRjW)t zsaz~(O*~oCDyb11fK#E2~=Tla&K>RP-_JN00#g7Kp+6Kng{d&v!)4s0Y1 Date: Sun, 8 Oct 2023 21:09:17 +0200 Subject: [PATCH 05/14] consensus: move timelock types from psbt --- Cargo.lock | 143 ++++++++++++++ Cargo.toml | 3 +- consensus/Cargo.toml | 5 +- consensus/src/lib.rs | 7 +- consensus/src/timelocks.rs | 373 +++++++++++++++++++++++++++++++++++++ consensus/src/tx.rs | 98 +--------- 6 files changed, 528 insertions(+), 101 deletions(-) create mode 100644 consensus/src/timelocks.rs diff --git a/Cargo.lock b/Cargo.lock index 1a8b740c..388ccfe2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,6 +84,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" @@ -177,6 +192,7 @@ name = "bp-consensus" version = "0.10.10" dependencies = [ "amplify", + "chrono", "commit_verify", "secp256k1", "serde", @@ -254,6 +270,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" @@ -287,6 +317,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" @@ -376,6 +412,29 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "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]] name = "indexmap" version = "1.9.3" @@ -402,6 +461,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" @@ -436,6 +504,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" @@ -829,6 +906,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 0c142ecb..6cec2a1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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/lib.rs b/consensus/src/lib.rs index 10b87641..5be4c4f1 100644 --- a/consensus/src/lib.rs +++ b/consensus/src/lib.rs @@ -65,6 +65,7 @@ mod taproot; mod tx; mod hashtypes; mod sigtypes; +mod timelocks; mod util; mod weights; #[cfg(feature = "stl")] @@ -87,9 +88,11 @@ pub use taproot::{ TapNodeHash, TapScript, XOnlyPk, TAPROOT_ANNEX_PREFIX, TAPROOT_LEAF_MASK, TAPROOT_LEAF_TAPSCRIPT, }; +pub use timelocks::{ + InvalidTimelock, LockHeight, LockTime, LockTimestamp, TimelockParseError, LOCKTIME_THRESHOLD, +}; pub use tx::{ - LockTime, Outpoint, OutpointParseError, Sats, SeqNo, Tx, TxIn, TxOut, TxParseError, TxVer, - Txid, Vout, LOCKTIME_THRESHOLD, + Outpoint, OutpointParseError, Sats, SeqNo, Tx, TxIn, TxOut, TxParseError, TxVer, Txid, Vout, }; pub use util::{Chain, ChainParseError, NonStandardValue}; pub use weights::{VBytes, Weight, WeightUnits}; diff --git a/consensus/src/timelocks.rs b/consensus/src/timelocks.rs new file mode 100644 index 00000000..05f81e39 --- /dev/null +++ b/consensus/src/timelocks.rs @@ -0,0 +1,373 @@ +// 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; + +/// 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, +} + +/// 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, 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)) + } + } +} diff --git a/consensus/src/tx.rs b/consensus/src/tx.rs index 71bf7c7b..c42e7789 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, SigScript, VarIntArray, Witness, Wtxid, LIB_NAME_BITCOIN, }; #[derive(Wrapper, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, From)] @@ -426,99 +425,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)] From 1963ee91794305e861b5209eac2a904cda73c6f2 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 8 Oct 2023 21:12:40 +0200 Subject: [PATCH 06/14] consensus: add TimeLockInterval --- consensus/src/timelocks.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/consensus/src/timelocks.rs b/consensus/src/timelocks.rs index 05f81e39..debe100f 100644 --- a/consensus/src/timelocks.rs +++ b/consensus/src/timelocks.rs @@ -371,3 +371,27 @@ impl FromStr for LockHeight { } } } + +/// Time lock interval describing both relative (OP_CHECKSEQUENCEVERIFY) and +/// absolute (OP_CHECKTIMELOCKVERIFY) timelocks. +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)] +#[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 + #[from] + Height(LockHeight), + + /// Describes number of 512-second intervals for the timelock + #[from] + Time(LockTimestamp), +} + +impl Default for TimeLockInterval { + fn default() -> Self { TimeLockInterval::Height(default!()) } +} From f9a61ab553e552ffb3afb320e3f0ab4d8b229f7c Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 8 Oct 2023 21:22:25 +0200 Subject: [PATCH 07/14] consensus: move nseq consensus-related methods from bp-std --- consensus/src/lib.rs | 10 ++--- consensus/src/segwit.rs | 4 +- consensus/src/stl.rs | 13 ++++-- consensus/src/taproot.rs | 3 +- consensus/src/timelocks.rs | 88 ++++++++++++++++++++++++++++--------- consensus/src/tx.rs | 20 +-------- stl/Bitcoin@0.1.0.sta | 88 +++++++++++++++++++------------------ stl/Bitcoin@0.1.0.stl | Bin 2868 -> 2948 bytes stl/Bitcoin@0.1.0.sty | 9 +++- 9 files changed, 138 insertions(+), 97 deletions(-) diff --git a/consensus/src/lib.rs b/consensus/src/lib.rs index 5be4c4f1..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: @@ -85,14 +82,15 @@ pub use sigtypes::{Bip340Sig, LegacySig, SigError, SighashFlag, SighashType}; pub use taproot::{ ControlBlock, FutureLeafVer, InternalPk, IntoTapHash, InvalidLeafVer, InvalidParityValue, LeafScript, LeafVer, OutputPk, Parity, TapBranchHash, TapCode, TapLeafHash, TapMerklePath, - TapNodeHash, TapScript, XOnlyPk, TAPROOT_ANNEX_PREFIX, TAPROOT_LEAF_MASK, + TapNodeHash, TapScript, XOnlyPk, MIDSTATE_TAPSIGHASH, TAPROOT_ANNEX_PREFIX, TAPROOT_LEAF_MASK, TAPROOT_LEAF_TAPSCRIPT, }; pub use timelocks::{ - InvalidTimelock, LockHeight, LockTime, LockTimestamp, TimelockParseError, LOCKTIME_THRESHOLD, + InvalidTimelock, LockHeight, LockTime, LockTimestamp, SeqNo, TimelockParseError, + LOCKTIME_THRESHOLD, SEQ_NO_CSV_DISABLE_MASK, SEQ_NO_CSV_TYPE_MASK, }; pub use tx::{ - Outpoint, OutpointParseError, Sats, SeqNo, Tx, TxIn, TxOut, TxParseError, TxVer, Txid, Vout, + 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/segwit.rs b/consensus/src/segwit.rs index 90fe506c..9df0da17 100644 --- a/consensus/src/segwit.rs +++ b/consensus/src/segwit.rs @@ -213,7 +213,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, @@ -222,6 +222,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(); diff --git a/consensus/src/stl.rs b/consensus/src/stl.rs index 9df9cde4..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, CompressedPk, ControlBlock, FutureLeafVer, InternalPk, - LeafScript, LegacyPk, LegacySig, OpCode, OutputPk, PubkeyHash, RedeemScript, ScriptHash, - TapCode, TapLeafHash, TapNodeHash, TapScript, Tx, UncompressedPk, VBytes, VarInt, WPubkeyHash, - WScriptHash, WeightUnits, WitnessProgram, WitnessScript, WitnessVer, Wtxid, LIB_NAME_BITCOIN, + 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:3NDewC9zBEosrNM82npGstFcvPhjg9E9nLoQZrQq6kwe#remark-shelter-weekend"; + "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() } @@ -75,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 6a4a77bc..7b2d46e7 100644 --- a/consensus/src/taproot.rs +++ b/consensus/src/taproot.rs @@ -55,8 +55,7 @@ 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 impl From> for DecodeError { diff --git a/consensus/src/timelocks.rs b/consensus/src/timelocks.rs index debe100f..6aba41ac 100644 --- a/consensus/src/timelocks.rs +++ b/consensus/src/timelocks.rs @@ -28,6 +28,25 @@ 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}")] @@ -54,22 +73,6 @@ pub enum TimelockParseError { NoRand, } -/// 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, Default)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] @@ -372,9 +375,52 @@ impl FromStr for LockHeight { } } +#[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, From)] +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN, tags = order)] #[cfg_attr( @@ -384,12 +430,12 @@ impl FromStr for LockHeight { )] pub enum TimeLockInterval { /// Describes number of blocks for the timelock - #[from] - Height(LockHeight), + #[display("height({0})")] + Height(u16), /// Describes number of 512-second intervals for the timelock - #[from] - Time(LockTimestamp), + #[display("time({0})")] + Time(u16), } impl Default for TimeLockInterval { diff --git a/consensus/src/tx.rs b/consensus/src/tx.rs index c42e7789..a85c4a2d 100644 --- a/consensus/src/tx.rs +++ b/consensus/src/tx.rs @@ -31,7 +31,7 @@ use commit_verify::{DigestExt, Sha256}; use crate::{ ConsensusDecode, ConsensusDecodeError, ConsensusEncode, LockTime, NonStandardValue, - ScriptPubkey, SigScript, VarIntArray, Witness, Wtxid, LIB_NAME_BITCOIN, + ScriptPubkey, SeqNo, SigScript, VarIntArray, Witness, Wtxid, LIB_NAME_BITCOIN, }; #[derive(Wrapper, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, From)] @@ -215,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)] diff --git a/stl/Bitcoin@0.1.0.sta b/stl/Bitcoin@0.1.0.sta index 9e7cc16f..6b4649f8 100644 --- a/stl/Bitcoin@0.1.0.sta +++ b/stl/Bitcoin@0.1.0.sta @@ -1,11 +1,11 @@ -----BEGIN STRICT TYPE LIB----- -Id: urn:ubideco:stl:3NDewC9zBEosrNM82npGstFcvPhjg9E9nLoQZrQq6kwe +Id: urn:ubideco:stl:4AXTqXq8jUDs244XbhvErdsG82Y8r9PiaPBPAmD5y9fQ Name: Bitcoin Dependencies: urn:ubideco:stl:9KALDYR8Nyjq4FdMW6kYoL7vdkWnqPqNuFnmE9qHpNjZ B0JpdGNvaW4Be4SAPJ764hElp3wsObxw0v3o+UOuDf2c9OaC7cdmynADU3RkAQNT -dGQBAGGGItF7rvBmAt/ndcmA4LNrbrroCQ2AdfdRO+xLk/ZNBEJvb2w0AAlCaXAz +dGQBAGGGItF7rvBmAt/ndcmA4LNrbrroCQ2AdfdRO+xLk/ZNBEJvb2w3AAlCaXAz NDBTaWcGAgNzaWcABwAAQEAADHNpZ2hhc2hfdHlwZQAEAgAEbm9uZQAAAAEEc29t ZQAFAQHbF2Q+dwkTJ+gEmSSB1dxgFSMIlVM55Yszn9RMT3JIKglCbG9ja0hhc2gF AQAHAABAIAALQmxvY2tIZWFkZXIGBgd2ZXJzaW9uAABEDXByZXZCbG9ja0hhc2gB @@ -23,47 +23,49 @@ oznUX3sVdadxS+F6dRhd0DE1etTJLemGB0xlYWZWZXIFAQAAAQhMZWdhY3lQawYC CmNvbXByZXNzZWQCe4SAPJ764hElp3wsObxw0v3o+UOuDf2c9OaC7cdmynBhhiLR e67wZgLf53XJgOCza2666AkNgHX3UTvsS5P2TQZwdWJrZXkABwAAQCEACUxlZ2Fj eVNpZwYCA3NpZwAIAABAAAAAAAAAAAD/AAAAAAAAAAxzaWdoYXNoX3R5cGUB2xdk -PncJEyfoBJkkgdXcYBUjCJVTOeWLM5/UTE9ySCoITG9ja1RpbWUFAQAABAZPcENv -ZGUDEgpwdXNoQnl0ZXMwAAtwdXNoQnl0ZXMzMiAJcHVzaERhdGExTAlwdXNoRGF0 -YTJNCXB1c2hEYXRhNE4IcmVzZXJ2ZWRQCHB1c2hOdW0xUQZyZXR1cm5qA2R1cHYF -ZXF1YWyHC2VxdWFsVmVyaWZ5iAlyaXBlbWQxNjCmBHNoYTGnBnNoYTI1NqgHaGFz -aDE2MKkHaGFzaDI1NqoIY2hlY2tTaWesDmNoZWNrU2lnVmVyaWZ5rQhPdXRwb2lu -dAYCBHR4aWQBo4JC88vX0dChEtqN4WAvVtT4bw7ExHbFwGhZTEsEZVYEdm91dAEh -4z5Dxapc8iknU6M4wWftO2OcTdnOvamPNGkXuslDdQhPdXRwdXRQawUBAfyipyq+ -kf7NgqixmJBjIsJOdqqqNfIk0XMFY6AYLohZBlBhcml0eQMCBGV2ZW4AA29kZAEK -UHVia2V5SGFzaAUBAAcAAEAUAAxSZWRlZW1TY3JpcHQFAQElq/W5EhQXuqM51F97 -FXWncUvhenUYXdAxNXrUyS3phgRTYXRzBQEAAAgLU2NyaXB0Qnl0ZXMFAQAIAABA -AAAAAAAAAAD/////AAAAAApTY3JpcHRIYXNoBQEABwAAQBQADFNjcmlwdFB1Ymtl -eQUBASWr9bkSFBe6oznUX3sVdadxS+F6dRhd0DE1etTJLemGBVNlcU5vBQEAAAQJ -U2lnU2NyaXB0BQEBJav1uRIUF7qjOdRfexV1p3FL4Xp1GF3QMTV61Mkt6YYLU2ln -aGFzaEZsYWcDAwNhbGwBBG5vbmUCBnNpbmdsZQMLU2lnaGFzaFR5cGUGAgRmbGFn -Af8+an/Ix4VDTvyO53SzALGb7Jhxopoe8QCL7xDrKNU0DGFueW9uZUNhblBheQJ7 -hIA8nvriESWnfCw5vHDS/ej5Q64N/Zz05oLtx2bKcGGGItF7rvBmAt/ndcmA4LNr -brroCQ2AdfdRO+xLk/ZNDVRhcEJyYW5jaEhhc2gFAQAHAABAIAAHVGFwQ29kZQMG -C3B1c2hCeXRlczMyIAlwdXNoRGF0YTFMCXB1c2hEYXRhMk0JcHVzaERhdGE0Tghy -ZXNlcnZlZFAGcmV0dXJuagtUYXBMZWFmSGFzaAUBAAcAAEAgAA1UYXBNZXJrbGVQ -YXRoBQEACAGv68Wd2P1QRx2YXcJBKC6rqYxIivJkY8F2flEGIsKS2AAAAAAAAAAA -gAAAAAAAAAALVGFwTm9kZUhhc2gFAQAHAABAIAAJVGFwU2NyaXB0BQEBJav1uRIU -F7qjOdRfexV1p3FL4Xp1GF3QMTV61Mkt6YYCVHgGBAd2ZXJzaW9uAah8xnlkZ+VX -10TlyWI64AzLldkaDS8D33TAdRJPvseeBmlucHV0cwAIARlHLRfYYgenDyExqVih -aKVuKtZTP5xmTBl32lz5/mKGAAAAAAAAAAD/////AAAAAAdvdXRwdXRzAAgBkDtk -cHmEjxsmUyrkzsamiUSgU1i48IHLJrO7+C2eO/MAAAAAAAAAAP////8AAAAACGxv -Y2tUaW1lATXaHRU5IG673dykwz2HMerym6fadN89yIIgHE4WtbkcBFR4SW4GBApw -cmV2T3V0cHV0AehqQM1cJfm94oT/aaURMqdBKyFVvQ5WEsG/44SVYMUGCXNpZ1Nj -cmlwdAE4dQSxS3wORm1HnhdHfSR0JH/4A2TsPUuq9zog90F0awhzZXF1ZW5jZQEB -GW2FKcj22kRNFU6NnIy9ng+NiQJaO7CRIcY9UrAehwd3aXRuZXNzAXN3Q3A2kyBJ -zSiVCKpxfOOCnbJFLlXoTtT8LjzNLgCdBVR4T3V0BgIFdmFsdWUBl/XXBkKuKjOS -JTuoTh3OxJPjvz7TcbGHc4Y1TsyIgmsMc2NyaXB0UHVia2V5Ab78HvxmpRn9ZFJq -OhOHQOfxEC0Lvv86wUZO8/dAdnRcBVR4VmVyBQEAAEQEVHhpZAUBAAcAAEAgAA5V -bmNvbXByZXNzZWRQawUBAAcAAEBBAAZWQnl0ZXMFAQAABAZWYXJJbnQFAQAACARW -b3V0BQEAAAQLV1B1YmtleUhhc2gFAQAHAABAFAALV1NjcmlwdEhhc2gFAQAHAABA -IAALV2VpZ2h0VW5pdHMFAQAABAdXaXRuZXNzBQEACAAIAABAAAAAAAAAAAD///// -AAAAAAAAAAAAAAAA/////wAAAAAOV2l0bmVzc1Byb2dyYW0GAgd2ZXJzaW9uAdHs -2nZn5ELtTRJppmcDNuX+9DevXs4raa66DEZWxPqtB3Byb2dyYW0ACAAAQAIAAAAA -AAAAKAAAAAAAAAANV2l0bmVzc1NjcmlwdAUBASWr9bkSFBe6oznUX3sVdadxS+F6 -dRhd0DE1etTJLemGCldpdG5lc3NWZXIDEQJ2MAACdjFRAnYyUgJ2M1MCdjRUAnY1 -VQJ2NlYCdjdXAnY4WAJ2OVkDdjEwWgN2MTFbA3YxMlwDdjEzXQN2MTReA3YxNV8D -djE2YAVXdHhpZAUBAAcAAEAgAAdYT25seVBrBQEABwAAQCAA +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 6e7be1b36cdd20fa06bc1e5d11c584ceaf8fa6c2..939ab1b35849240f41e0213d1bc5ab26872d6127 100644 GIT binary patch delta 113 zcmdlY)*?P3lF@u)R4}s+mrs6jwnu7adPWH=BLf2q2Z$e%nVSmc@xpk;C5gEOo3}AP rVb&D@$%4c^^GZ^S$`W%}m>AeHz$P$&G%+!-y$|4lF?*iR4_9qhfjWTc1UJ!>gJowPnb8qVxPqf0JN_Q#Q*>R diff --git a/stl/Bitcoin@0.1.0.sty b/stl/Bitcoin@0.1.0.sty index e1937717..2d93bf01 100644 --- a/stl/Bitcoin@0.1.0.sty +++ b/stl/Bitcoin@0.1.0.sty @@ -1,5 +1,5 @@ {- - Id: urn:ubideco:stl:3NDewC9zBEosrNM82npGstFcvPhjg9E9nLoQZrQq6kwe#remark-shelter-weekend + Id: urn:ubideco:stl:4AXTqXq8jUDs244XbhvErdsG82Y8r9PiaPBPAmD5y9fQ#cheese-provide-morph Name: Bitcoin Version: 0.1.0 Description: Consensus library for bitcoin protocol @@ -51,8 +51,12 @@ data LeafVer :: U8 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 @@ -102,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] From 43cc2326e801c1141833c5fbe4a4e38d0aeccd9a Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 8 Oct 2023 21:31:04 +0200 Subject: [PATCH 08/14] consensus: make inner bytes in hash types public to allow match patterns --- consensus/src/hashtypes.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/consensus/src/hashtypes.rs b/consensus/src/hashtypes.rs index dd6518bb..f66f20f1 100644 --- a/consensus/src/hashtypes.rs +++ b/consensus/src/hashtypes.rs @@ -35,7 +35,7 @@ use crate::LIB_NAME_BITCOIN; pub struct PubkeyHash( #[from] #[from([u8; 20])] - Bytes20, + pub Bytes20, ); impl From for [u8; 20] { @@ -54,7 +54,7 @@ impl From for [u8; 20] { pub struct ScriptHash( #[from] #[from([u8; 20])] - Bytes20, + pub Bytes20, ); impl From for [u8; 20] { @@ -73,7 +73,7 @@ impl From for [u8; 20] { pub struct WPubkeyHash( #[from] #[from([u8; 20])] - Bytes20, + pub Bytes20, ); impl From for [u8; 20] { @@ -92,7 +92,7 @@ impl From for [u8; 20] { pub struct WScriptHash( #[from] #[from([u8; 32])] - Bytes32, + pub Bytes32, ); impl From for [u8; 32] { From 83690753205fa294b9f3a2f41c87020505fef1f7 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 8 Oct 2023 21:39:46 +0200 Subject: [PATCH 09/14] consensus: add from_bytes constructors to key types --- consensus/src/pubkeys.rs | 40 ++++++++++++++++++++++------------------ consensus/src/taproot.rs | 25 +++++++++++++++++++++---- 2 files changed, 43 insertions(+), 22 deletions(-) diff --git a/consensus/src/pubkeys.rs b/consensus/src/pubkeys.rs index dcb7beb2..7353cd3a 100644 --- a/consensus/src/pubkeys.rs +++ b/consensus/src/pubkeys.rs @@ -41,9 +41,16 @@ pub enum PubkeyParseError { InvalidPubkey(InvalidPubkey), } -#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)] -#[display("invalid public key")] -pub struct InvalidPubkey(pub Bytes); +#[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)] @@ -63,9 +70,13 @@ impl CompressedPk { pub fn from_byte_array(data: [u8; 33]) -> Result> { PublicKey::from_slice(&data) .map(Self) - .map_err(|_| InvalidPubkey(data.into())) + .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 { @@ -81,7 +92,7 @@ impl StrictDecode for CompressedPk { let bytes: Bytes<33> = r.read_field()?; PublicKey::from_slice(bytes.as_slice()) .map(Self) - .map_err(|_| InvalidPubkey(bytes).into()) + .map_err(|_| InvalidPubkey::Specified(bytes).into()) }) } } @@ -114,7 +125,7 @@ impl UncompressedPk { pub fn from_byte_array(data: [u8; 65]) -> Result> { PublicKey::from_slice(&data) .map(Self) - .map_err(|_| InvalidPubkey(data.into())) + .map_err(|_| InvalidPubkey::Specified(data.into())) } pub fn to_byte_array(&self) -> [u8; 65] { self.0.serialize_uncompressed() } } @@ -132,7 +143,7 @@ impl StrictDecode for UncompressedPk { let bytes: Bytes<65> = r.read_field()?; PublicKey::from_slice(bytes.as_slice()) .map(Self) - .map_err(|_| InvalidPubkey(bytes).into()) + .map_err(|_| InvalidPubkey::Specified(bytes).into()) }) } } @@ -190,18 +201,11 @@ impl LegacyPk { pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result> { let bytes = bytes.as_ref(); - let len = bytes.len().min(65); - let mut data = [0u8; 65]; - data[..len].copy_from_slice(&bytes[..len]); - - let pubkey = PublicKey::from_slice(bytes).map_err(|_| InvalidPubkey(data.into()).into())?; - + let pubkey = PublicKey::from_slice(bytes)?; Ok(match bytes.len() { 33 => Self::compressed(pubkey), 65 => Self::uncompressed(pubkey), - _ => { - return Err(InvalidPubkey(data.into())); - } + _ => unreachable!(), }) } } @@ -222,8 +226,8 @@ impl StrictDecode for LegacyPk { 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(bytes))?; + let pubkey = PublicKey::from_slice(bytes.as_slice()) + .map_err(|_| InvalidPubkey::Specified(bytes))?; Ok(LegacyPk { compressed, pubkey }) }) } diff --git a/consensus/src/taproot.rs b/consensus/src/taproot.rs index 7b2d46e7..779b49f0 100644 --- a/consensus/src/taproot.rs +++ b/consensus/src/taproot.rs @@ -39,7 +39,8 @@ use strict_encoding::{ use crate::opcodes::*; use crate::{ - InvalidPubkey, PubkeyParseError, ScriptBytes, ScriptPubkey, WitnessVer, LIB_NAME_BITCOIN, + CompressedPk, InvalidPubkey, PubkeyParseError, ScriptBytes, ScriptPubkey, WitnessVer, + LIB_NAME_BITCOIN, }; /// The SHA-256 midstate value for the TapLeaf hash. @@ -60,7 +61,7 @@ pub const MIDSTATE_TAPSIGHASH: [u8; 10] = *b"TapSighash"; impl From> for DecodeError { fn from(e: InvalidPubkey) -> Self { - DecodeError::DataIntegrityError(format!("invalid x-only public key value '{:x}'", e.0)) + DecodeError::DataIntegrityError(format!("invalid x-only public key value '{e}'")) } } @@ -87,10 +88,18 @@ impl XOnlyPk { 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 XOnlyPk { @@ -114,7 +123,7 @@ impl StrictDecode for XOnlyPk { 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()) }) } } @@ -152,6 +161,10 @@ impl InternalPk { XOnlyPk::from_byte_array(data).map(Self) } + 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() } @@ -230,6 +243,10 @@ impl OutputPk { XOnlyPk::from_byte_array(data).map(Self) } + 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() } } From 33ba0bef688ad180d117046e2945bb0efe2097f2 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 8 Oct 2023 22:54:09 +0200 Subject: [PATCH 10/14] consensus: add script convertors --- consensus/src/hashtypes.rs | 65 +++++++++++++++++++++++++++++++++++++- consensus/src/pubkeys.rs | 7 ++++ consensus/src/segwit.rs | 13 ++++++-- consensus/src/taproot.rs | 2 ++ 4 files changed, 84 insertions(+), 3 deletions(-) diff --git a/consensus/src/hashtypes.rs b/consensus/src/hashtypes.rs index f66f20f1..0153c80c 100644 --- a/consensus/src/hashtypes.rs +++ b/consensus/src/hashtypes.rs @@ -20,8 +20,11 @@ // limitations under the License. use amplify::{Bytes20, Bytes32, Wrapper}; +use commit_verify::{DigestExt, Ripemd160, Sha256}; -use crate::LIB_NAME_BITCOIN; +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)] @@ -42,6 +45,36 @@ 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)] @@ -61,6 +94,16 @@ 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)] @@ -80,6 +123,16 @@ 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)] @@ -98,3 +151,13 @@ pub struct WScriptHash( 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/pubkeys.rs b/consensus/src/pubkeys.rs index 7353cd3a..3b55b541 100644 --- a/consensus/src/pubkeys.rs +++ b/consensus/src/pubkeys.rs @@ -208,6 +208,13 @@ impl LegacyPk { _ => 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 { diff --git a/consensus/src/segwit.rs b/consensus/src/segwit.rs index 9df0da17..2af2a3b7 100644 --- a/consensus/src/segwit.rs +++ b/consensus/src/segwit.rs @@ -22,10 +22,12 @@ use std::vec; use amplify::confinement::Confined; -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)] @@ -328,6 +330,13 @@ 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 to_redeem_script(&self) -> RedeemScript { + let script = ScriptPubkey::p2wsh(WScriptHash::from(self)); + RedeemScript::from_inner(script.into_inner()) + } + + pub fn to_script_pubkey(&self) -> ScriptPubkey { ScriptPubkey::p2wsh(WScriptHash::from(self)) } + pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 } } diff --git a/consensus/src/taproot.rs b/consensus/src/taproot.rs index 779b49f0..08371d7e 100644 --- a/consensus/src/taproot.rs +++ b/consensus/src/taproot.rs @@ -247,6 +247,8 @@ impl OutputPk { XOnlyPk::from_bytes(bytes).map(Self) } + pub fn to_script_pubkey(&self) -> ScriptPubkey { ScriptPubkey::p2tr_tweaked(*self) } + #[inline] pub fn to_byte_array(&self) -> [u8; 32] { self.0.to_byte_array() } } From 1e7d0f4272d9c08e99f427e4032cda1b5722bdfa Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 9 Oct 2023 17:19:15 +0200 Subject: [PATCH 11/14] co: remove codecov token --- .github/workflows/codecov.yml | 2 -- 1 file changed, 2 deletions(-) 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 }} From f9409def5b7dccce30454636125558c477c5385e Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 9 Oct 2023 17:19:33 +0200 Subject: [PATCH 12/14] ci: fail on clippy warnings --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b8979f05..48580889 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: From f3fd08046d37f2f8c41e65a637bfc9815372d71c Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 9 Oct 2023 17:20:23 +0200 Subject: [PATCH 13/14] chore: update dependencies --- Cargo.lock | 91 +++++++++++++++++------------------------------------- Cargo.toml | 14 +++------ 2 files changed, 33 insertions(+), 72 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 388ccfe2..cf59e8ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,14 +4,14 @@ version = 3 [[package]] name = "amplify" -version = "4.4.0" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213783efd41ed08b2861e86dd5aac633cf612f861e9222ace8b3621bd58af5c6" +checksum = "8629db306c0bbeb0a402e2918bdcf0026b5ddb24c46460f3bf5410b350d98710" dependencies = [ "amplify_apfloat", - "amplify_derive 3.0.1", + "amplify_derive", "amplify_num", - "amplify_syn 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "amplify_syn", "ascii", "serde", "serde_json", @@ -33,22 +33,11 @@ dependencies = [ [[package]] name = "amplify_derive" -version = "3.0.1" -source = "git+https://github.com/rust-amplify/amplify-derive?branch=wrapper#9ca1b8af3269a24a1aa75c158ce1e672aa56cdec" -dependencies = [ - "amplify_syn 2.0.1 (git+https://github.com/rust-amplify/amplify-derive?branch=wrapper)", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[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 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "amplify_syn", "proc-macro2", "quote", "syn 1.0.109", @@ -74,16 +63,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "amplify_syn" -version = "2.0.1" -source = "git+https://github.com/rust-amplify/amplify-derive?branch=wrapper#9ca1b8af3269a24a1aa75c158ce1e672aa56cdec" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "android-tzdata" version = "0.1.1" @@ -287,10 +266,11 @@ dependencies = [ [[package]] name = "commit_encoding_derive" version = "0.10.0" -source = "git+https://github.com/LNP-BP/client_side_validation?branch=develop#23b7c189e2b5eed08f89d304381fb42c10e86c6e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00033f14d67c4169d588f085ea2faeb7b610cf03a74d42ea09eeba31abef2047" dependencies = [ "amplify", - "amplify_syn 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "amplify_syn", "proc-macro2", "quote", "syn 1.0.109", @@ -299,7 +279,8 @@ dependencies = [ [[package]] name = "commit_verify" version = "0.10.6" -source = "git+https://github.com/LNP-BP/client_side_validation?branch=develop#23b7c189e2b5eed08f89d304381fb42c10e86c6e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d9d6e86f6cec8d4af19a0e418bac9cb266a6dc70660bcdcdac1e0fa924e0d1" dependencies = [ "amplify", "commit_encoding_derive", @@ -394,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" @@ -435,16 +410,6 @@ dependencies = [ "cc", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "2.0.2" @@ -452,7 +417,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", - "hashbrown 0.14.1", + "hashbrown", ] [[package]] @@ -533,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", ] @@ -670,7 +635,7 @@ version = "0.9.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" dependencies = [ - "indexmap 2.0.2", + "indexmap", "itoa", "ryu", "serde", @@ -690,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 4.0.0-alpha.6", + "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", @@ -710,11 +675,11 @@ 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 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "amplify_syn", "heck", "proc-macro2", "quote", @@ -723,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", ] @@ -815,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", diff --git a/Cargo.toml b/Cargo.toml index 6cec2a1d..5bb192d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,11 +22,11 @@ edition = "2021" license = "Apache-2.0" [workspace.dependencies] -amplify = "4.4.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" } @@ -81,7 +81,3 @@ stl = ["strict_types", "strict_types/base64", "bp-consensus/stl", "commit_verify [package.metadata.docs.rs] features = [ "all" ] - -[patch.crates-io] -amplify_derive = { git = "https://github.com/rust-amplify/amplify-derive", branch = "wrapper" } -commit_verify = { git = "https://github.com/LNP-BP/client_side_validation", branch = "develop" } From 48d79c9f210772dbd836aba258fa836169e8c36d Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 9 Oct 2023 17:42:41 +0200 Subject: [PATCH 14/14] chore: fix clippy warnings --- .github/workflows/lint.yml | 2 +- consensus/src/coding.rs | 44 +++++++++++++++++++------------------- consensus/src/segwit.rs | 3 +-- consensus/src/tx.rs | 3 ++- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 48580889..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 -D warnings + args: --workspace --all-features --all-targets -- -D warnings doc: runs-on: ubuntu-latest steps: diff --git a/consensus/src/coding.rs b/consensus/src/coding.rs index dd2a4b95..46b191ab 100644 --- a/consensus/src/coding.rs +++ b/consensus/src/coding.rs @@ -715,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] @@ -860,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/segwit.rs b/consensus/src/segwit.rs index 2af2a3b7..b4b134a9 100644 --- a/consensus/src/segwit.rs +++ b/consensus/src/segwit.rs @@ -297,8 +297,7 @@ 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 } diff --git a/consensus/src/tx.rs b/consensus/src/tx.rs index a85c4a2d..eb1a6fda 100644 --- a/consensus/src/tx.rs +++ b/consensus/src/tx.rs @@ -252,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) } @@ -443,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) } }