diff --git a/Cargo.lock b/Cargo.lock index 8377f1c4..8727111e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "amplify" -version = "4.1.1" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f58ae011435caa061fb64894c7b05d615321011d35dd07b53b2843a4b61933" +checksum = "add3e79c9c3e33209e1676562e0fda882bc20701f52870ba162d9f046b7d8b0b" dependencies = [ "amplify_apfloat", "amplify_derive 3.0.1", diff --git a/Cargo.toml b/Cargo.toml index 11fedf1e..7c6fc667 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ edition = "2021" license = "Apache-2.0" [workspace.dependencies] -amplify = "4.0.2" +amplify = "4.2.0" strict_encoding = "2.5.0" commit_verify = "0.10.5" single_use_seals = "0.10.0" diff --git a/dbc/src/tapret/tapscript.rs b/dbc/src/tapret/tapscript.rs index 97fd526a..00d68836 100644 --- a/dbc/src/tapret/tapscript.rs +++ b/dbc/src/tapret/tapscript.rs @@ -118,7 +118,7 @@ impl CommitVerify for TapScript { #[cfg(test)] mod test { - use amplify::RawArray; + use amplify::ByteArray; use commit_verify::{Digest, Sha256}; use super::*; @@ -126,7 +126,7 @@ mod test { pub fn commitment() -> TapretCommitment { let msg = Sha256::digest("test data"); TapretCommitment { - mpc: mpc::Commitment::from_raw_array(msg), + mpc: mpc::Commitment::from_byte_array(msg), nonce: 8, } } diff --git a/dbc/src/tapret/xonlypk.rs b/dbc/src/tapret/xonlypk.rs index 5ac25b69..65cc05ae 100644 --- a/dbc/src/tapret/xonlypk.rs +++ b/dbc/src/tapret/xonlypk.rs @@ -19,9 +19,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use bc::{InternalPk, TapBranchHash, TapLeafHash, TapNodeHash, TapScript}; +use bc::{InternalPk, OutputPk, TapBranchHash, TapLeafHash, TapNodeHash, TapScript}; use commit_verify::{mpc, CommitVerify, ConvolveCommit, ConvolveCommitProof}; -use secp256k1::XOnlyPublicKey; use super::{Lnpbp12, TapretNodePartner, TapretPathProof, TapretProof}; use crate::tapret::tapscript::TapretCommitment; @@ -46,20 +45,20 @@ pub enum TapretKeyError { impl ConvolveCommitProof for TapretProof { type Suppl = TapretPathProof; - fn restore_original(&self, _: &XOnlyPublicKey) -> InternalPk { self.internal_pk } + fn restore_original(&self, _: &OutputPk) -> InternalPk { self.internal_pk } fn extract_supplement(&self) -> &Self::Suppl { &self.path_proof } } impl ConvolveCommit for InternalPk { - type Commitment = XOnlyPublicKey; + type Commitment = OutputPk; type CommitError = TapretKeyError; fn convolve_commit( &self, supplement: &TapretPathProof, msg: &mpc::Commitment, - ) -> Result<(XOnlyPublicKey, TapretProof), Self::CommitError> { + ) -> Result<(OutputPk, TapretProof), Self::CommitError> { let tapret_commitment = TapretCommitment::with(*msg, supplement.nonce); let script_commitment = TapScript::commit(&tapret_commitment); @@ -80,7 +79,7 @@ impl ConvolveCommit for InternalPk { TapLeafHash::with_tap_script(&script_commitment).into() }; - let output_key = self.to_output_key(Some(merkle_root)); + let (output_key, _) = self.to_output_pk(Some(merkle_root)); let proof = TapretProof { path_proof: supplement.clone(), @@ -117,7 +116,7 @@ mod test { let tapret_commitment = TapretCommitment::with(msg, path_proof.nonce); let script_commitment = TapScript::commit(&tapret_commitment); let script_leaf = TapLeafHash::with_tap_script(&script_commitment); - let real_key = internal_pk.to_output_key(Some(script_leaf)); + let (real_key, _) = internal_pk.to_output_pk(Some(script_leaf)); assert_eq!(outer_key, real_key); diff --git a/primitives/src/block.rs b/primitives/src/block.rs index a8375cf0..7529eb58 100644 --- a/primitives/src/block.rs +++ b/primitives/src/block.rs @@ -47,6 +47,8 @@ impl FromHex for BlockHash { } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[derive(StrictType, StrictEncode, StrictDecode, StrictDumb)] +#[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), diff --git a/primitives/src/consensus.rs b/primitives/src/coding.rs similarity index 74% rename from primitives/src/consensus.rs rename to primitives/src/coding.rs index 9052cf5f..e77f2f59 100644 --- a/primitives/src/consensus.rs +++ b/primitives/src/coding.rs @@ -19,24 +19,111 @@ // 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::{confinement, IoError, RawArray, Wrapper}; +use amplify::hex::{self, FromHex, ToHex}; +use amplify::{confinement, ByteArray, Bytes32, IoError, Wrapper}; use crate::{ - LockTime, Outpoint, Sats, ScriptBytes, ScriptPubkey, SeqNo, SigScript, Tx, TxIn, TxOut, TxVer, - Txid, VarInt, Vout, Witness, + ControlBlock, InternalPk, InvalidLeafVer, LeafVer, LockTime, Outpoint, Parity, RedeemScript, + Sats, ScriptBytes, ScriptPubkey, SeqNo, SigScript, TapBranchHash, TapMerklePath, TapScript, Tx, + TxIn, TxOut, TxVer, Txid, Vout, Witness, WitnessScript, LIB_NAME_BITCOIN, }; pub type VarIntArray = Confined, 0, U32>; -pub trait VarIntSize { - fn var_int_size(&self) -> VarInt; +/// A variable-length unsigned integer. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Default)] +#[derive(StrictType, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_BITCOIN)] +pub struct VarInt(pub u64); + +#[allow(clippy::len_without_is_empty)] // VarInt has no concept of 'is_empty'. +impl VarInt { + pub const fn new(u: u64) -> Self { VarInt(u) } + + pub fn with(u: impl Into) -> Self { VarInt(u.into() as u64) } + + /// Gets the length of this VarInt when encoded. + /// + /// Returns 1 for 0..=0xFC, 3 for 0xFD..=(2^16-1), 5 for 0x10000..=(2^32-1), + /// and 9 otherwise. + #[inline] + pub const fn len(&self) -> usize { + match self.0 { + 0..=0xFC => 1, + 0xFD..=0xFFFF => 3, + 0x10000..=0xFFFFFFFF => 5, + _ => 9, + } + } + + pub const fn to_u64(&self) -> u64 { self.0 } + pub const fn into_u64(self) -> u64 { self.0 } + pub fn to_usize(&self) -> usize { + usize::try_from(self.0).expect("transaction too large for a non-64 bit platform") + } + pub fn into_usize(self) -> usize { self.to_usize() } } -impl VarIntSize for VarIntArray { - fn var_int_size(&self) -> VarInt { VarInt::with(self.len()) } +impl + Copy> PartialEq for VarInt { + fn eq(&self, other: &U) -> bool { self.0.eq(&(*other).into()) } +} + +pub trait LenVarInt { + fn len_var_int(&self) -> VarInt; +} + +impl LenVarInt for VarIntArray { + fn len_var_int(&self) -> VarInt { VarInt::with(self.len()) } +} + +#[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)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", transparent) +)] +pub struct ByteStr(VarIntArray); + +impl AsRef<[u8]> for ByteStr { + fn as_ref(&self) -> &[u8] { self.0.as_slice() } +} + +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) } + + pub fn into_vec(self) -> Vec { self.0.into_inner() } } #[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)] @@ -46,7 +133,9 @@ pub enum ConsensusDecodeError { #[from(io::Error)] Io(IoError), + #[display(inner)] #[from] + #[from(InvalidLeafVer)] #[from(confinement::Error)] Data(ConsensusDataError), } @@ -60,6 +149,20 @@ pub enum ConsensusDataError { /// not a minimally-encoded variable integer. NonMinimalVarInt, + /// invalid BIP340 (x-only) pubkey data. + InvalidXonlyPubkey(Bytes32), + + /// taproot Merkle path length exceeds BIP-341 consensus limit of 128 + /// elements. + LongTapMerklePath, + + /// Merkle path in the `PSBT_IN_TAP_TREE` is not encoded correctly. + InvalidTapMerklePath, + + #[from] + #[display(inner)] + InvalidLeafVer(InvalidLeafVer), + #[from] #[display(inner)] Confined(confinement::Error), @@ -223,7 +326,7 @@ impl ConsensusDecode for Outpoint { impl ConsensusEncode for Txid { fn consensus_encode(&self, writer: &mut impl Write) -> Result { - writer.write_all(&self.to_raw_array())?; + writer.write_all(&self.to_byte_array())?; Ok(32) } } @@ -294,6 +397,42 @@ impl ConsensusDecode for ScriptPubkey { } } +impl ConsensusEncode for WitnessScript { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + self.as_script_bytes().consensus_encode(writer) + } +} + +impl ConsensusDecode for WitnessScript { + fn consensus_decode(reader: &mut impl Read) -> Result { + ScriptBytes::consensus_decode(reader).map(Self::from_inner) + } +} + +impl ConsensusEncode for RedeemScript { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + self.as_script_bytes().consensus_encode(writer) + } +} + +impl ConsensusDecode for RedeemScript { + fn consensus_decode(reader: &mut impl Read) -> Result { + ScriptBytes::consensus_decode(reader).map(Self::from_inner) + } +} + +impl ConsensusEncode for TapScript { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + self.as_script_bytes().consensus_encode(writer) + } +} + +impl ConsensusDecode for TapScript { + fn consensus_decode(reader: &mut impl Read) -> Result { + ScriptBytes::consensus_decode(reader).map(Self::from_inner) + } +} + impl ConsensusEncode for SigScript { fn consensus_encode(&self, writer: &mut impl Write) -> Result { self.as_script_bytes().consensus_encode(writer) @@ -318,6 +457,81 @@ impl ConsensusDecode for Witness { } } +impl ConsensusEncode for InternalPk { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + writer.write_all(&self.to_byte_array())?; + Ok(32) + } +} + +impl ConsensusEncode for TapBranchHash { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + writer.write_all(&self.to_byte_array())?; + Ok(32) + } +} + +impl ConsensusDecode for TapBranchHash { + fn consensus_decode(reader: &mut impl Read) -> Result { + let mut buf = [0u8; 32]; + reader.read_exact(&mut buf)?; + Ok(TapBranchHash::from_byte_array(buf)) + } +} + +impl ConsensusDecode for InternalPk { + fn consensus_decode(reader: &mut impl Read) -> Result { + let mut buf = [0u8; 32]; + reader.read_exact(&mut buf)?; + InternalPk::from_byte_array(buf) + .map_err(|_| ConsensusDataError::InvalidXonlyPubkey(buf.into()).into()) + } +} + +impl ConsensusEncode for ControlBlock { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + let mut counter = 1; + + let first_byte = + self.leaf_version.to_consensus_u8() & self.output_key_parity.to_consensus_u8(); + first_byte.consensus_encode(writer)?; + + counter += self.internal_pk.consensus_encode(writer)?; + for step in &self.merkle_branch { + counter += step.consensus_encode(writer)?; + } + + Ok(counter) + } +} + +impl ConsensusDecode for ControlBlock { + fn consensus_decode(reader: &mut impl Read) -> Result { + let first_byte = u8::consensus_decode(reader)?; + let leaf_version = LeafVer::from_consensus_u8(first_byte & 0xFE)?; + let output_key_parity = Parity::from_consensus_u8(first_byte & 0x01).expect("binary value"); + + let internal_key = InternalPk::consensus_decode(reader)?; + + let mut buf = vec![]; + reader.read_to_end(&mut buf)?; + let mut iter = buf.chunks_exact(32); + let merkle_branch = iter.by_ref().map(TapBranchHash::from_slice_unsafe); + let merkle_branch = TapMerklePath::try_from_iter(merkle_branch) + .map_err(|_| ConsensusDataError::LongTapMerklePath)?; + if !iter.remainder().is_empty() { + return Err(ConsensusDataError::InvalidTapMerklePath.into()); + } + + Ok(ControlBlock { + leaf_version, + output_key_parity, + internal_pk: internal_key, + merkle_branch, + }) + } +} + impl ConsensusEncode for Sats { fn consensus_encode(&self, writer: &mut impl Write) -> Result { self.0.consensus_encode(writer) @@ -389,9 +603,21 @@ impl ConsensusDecode for VarInt { } } +impl ConsensusEncode for ByteStr { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + self.0.consensus_encode(writer) + } +} + +impl ConsensusDecode for ByteStr { + fn consensus_decode(reader: &mut impl Read) -> Result { + VarIntArray::consensus_decode(reader).map(Self::from_inner) + } +} + impl ConsensusEncode for VarIntArray { fn consensus_encode(&self, writer: &mut impl Write) -> Result { - let mut counter = self.var_int_size().consensus_encode(writer)?; + let mut counter = self.len_var_int().consensus_encode(writer)?; for item in self { counter += item.consensus_encode(writer)?; } diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index a5c3af32..9bb67a66 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -19,6 +19,14 @@ // 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 1.0: +// TODO: Complete block data type implementation +// TODO: Complete OpCode enumeration +// TODO: Do a no-std feature + // Coding conventions #![deny( non_upper_case_globals, @@ -33,6 +41,7 @@ #[macro_use] extern crate amplify; +// TODO: Make strict encoding optional dependency #[macro_use] extern crate strict_encoding; #[macro_use] @@ -51,25 +60,32 @@ mod script; mod segwit; mod taproot; mod tx; +mod sigtypes; mod util; mod weights; #[cfg(feature = "stl")] pub mod stl; -mod consensus; +mod coding; pub use block::{BlockHash, BlockHeader}; -pub use consensus::{ - ConsensusDataError, ConsensusDecode, ConsensusDecodeError, ConsensusEncode, VarIntArray, - VarIntSize, +pub use coding::{ + ByteStr, ConsensusDataError, ConsensusDecode, ConsensusDecodeError, ConsensusEncode, LenVarInt, + VarInt, VarIntArray, +}; +pub use script::{OpCode, RedeemScript, ScriptBytes, ScriptPubkey, SigScript}; +pub use segwit::{SegwitError, Witness, WitnessProgram, WitnessScript, WitnessVer, Wtxid}; +pub use sigtypes::{Bip340Sig, LegacySig, SigError, SighashFlag, SighashType}; +pub use taproot::{ + ControlBlock, FutureLeafVer, InternalPk, IntoTapHash, InvalidLeafVer, InvalidParityValue, + InvalidPubkey, LeafScript, LeafVer, OutputPk, Parity, TapBranchHash, TapCode, TapLeafHash, + TapMerklePath, TapNodeHash, TapScript, TaprootPk, TAPROOT_ANNEX_PREFIX, TAPROOT_LEAF_MASK, + TAPROOT_LEAF_TAPSCRIPT, }; -pub use script::{OpCode, ScriptBytes, ScriptPubkey, SigScript}; -pub use segwit::*; -pub use taproot::*; pub use tx::{ LockTime, Outpoint, OutpointParseError, Sats, SeqNo, Tx, TxIn, TxOut, TxParseError, TxVer, - Txid, Vout, Witness, Wtxid, LOCKTIME_THRESHOLD, + Txid, Vout, LOCKTIME_THRESHOLD, }; -pub use util::{Chain, ChainParseError, NonStandardValue, VarInt}; +pub use util::{Chain, ChainParseError, NonStandardValue}; pub use weights::{VBytes, Weight, WeightUnits}; pub const LIB_NAME_BITCOIN: &str = "Bitcoin"; diff --git a/primitives/src/script.rs b/primitives/src/script.rs index b7d25f6f..091c12c4 100644 --- a/primitives/src/script.rs +++ b/primitives/src/script.rs @@ -22,10 +22,10 @@ use std::fmt::{Formatter, LowerHex, UpperHex}; use amplify::confinement::Confined; -use amplify::hex::{Error, FromHex, ToHex}; +use amplify::hex::{self, FromHex, ToHex}; use crate::opcodes::*; -use crate::{VarIntArray, LIB_NAME_BITCOIN}; +use crate::{VarInt, VarIntArray, LIB_NAME_BITCOIN}; #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] @@ -125,10 +125,10 @@ pub struct SigScript( ); impl FromHex for SigScript { - fn from_hex(s: &str) -> Result { ScriptBytes::from_hex(s).map(Self) } + fn from_hex(s: &str) -> Result { ScriptBytes::from_hex(s).map(Self) } - fn from_byte_iter(_: I) -> Result - where I: Iterator> + ExactSizeIterator + DoubleEndedIterator { + fn from_byte_iter(_: I) -> Result + where I: Iterator> + ExactSizeIterator + DoubleEndedIterator { unreachable!() } } @@ -215,10 +215,48 @@ impl ScriptPubkey { } impl FromHex for ScriptPubkey { - fn from_hex(s: &str) -> Result { ScriptBytes::from_hex(s).map(Self) } + fn from_hex(s: &str) -> Result { ScriptBytes::from_hex(s).map(Self) } - fn from_byte_iter(_: I) -> Result - where I: Iterator> + ExactSizeIterator + DoubleEndedIterator { + 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)] +#[derive(StrictType, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_BITCOIN)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", transparent) +)] +pub struct RedeemScript( + #[from] + #[from(Vec)] + ScriptBytes, +); + +impl RedeemScript { + pub fn new() -> Self { Self::default() } + + pub fn with_capacity(capacity: usize) -> Self { + Self(ScriptBytes::from(Confined::with_capacity(capacity))) + } + + /// 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!() } } @@ -247,9 +285,9 @@ impl UpperHex for ScriptBytes { } 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 { + 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!() } } @@ -290,10 +328,10 @@ impl ScriptBytes { } #[inline] - fn push(&mut self, data: u8) { self.0.push(data).expect("script exceeds 4GB") } + pub(crate) fn push(&mut self, data: u8) { self.0.push(data).expect("script exceeds 4GB") } #[inline] - fn extend(&mut self, data: &[u8]) { + pub(crate) fn extend(&mut self, data: &[u8]) { self.0 .extend(data.iter().copied()) .expect("script exceeds 4GB") @@ -311,6 +349,8 @@ impl ScriptBytes { } } + pub fn len_var_int(&self) -> VarInt { VarInt(self.len() as u64) } + pub fn into_vec(self) -> Vec { self.0.into_inner() } pub(crate) fn as_var_int_array(&self) -> &VarIntArray { &self.0 } diff --git a/primitives/src/segwit.rs b/primitives/src/segwit.rs index 464e3b6c..8b462a1f 100644 --- a/primitives/src/segwit.rs +++ b/primitives/src/segwit.rs @@ -19,8 +19,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::vec; + +use amplify::confinement::Confined; +use amplify::hex::{self, FromHex}; +use amplify::Bytes32StrRev; + use crate::opcodes::*; -use crate::{OpCode, ScriptBytes, ScriptPubkey}; +use crate::{OpCode, ScriptBytes, ScriptPubkey, VarIntArray, LIB_NAME_BITCOIN}; #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display, Error)] #[display(doc_comments)] @@ -43,9 +49,12 @@ pub enum SegwitError { /// First byte of `scriptPubkey` in transaction output for transactions starting /// with 0 and 0x51-0x60 (inclusive). #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display)] +#[derive(StrictType, StrictEncode, StrictDecode, StrictDumb)] +#[strict_type(lib = LIB_NAME_BITCOIN, tags = repr, into_u8, try_from_u8)] #[repr(u8)] pub enum WitnessVer { /// Initial version of witness program. Used for P2WPKH and P2WPK outputs + #[strict_type(dumb)] #[display("segwit0")] V0 = OP_PUSHBYTES_0, @@ -204,19 +213,21 @@ 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()})] pub struct WitnessProgram { /// The witness program version. version: WitnessVer, /// The witness program. (Between 2 and 40 bytes) - program: Vec, + program: Confined, 2, 40>, } impl WitnessProgram { /// Creates a new witness program. pub fn new(version: WitnessVer, program: Vec) -> Result { - if program.len() < 2 || program.len() > 40 { - return Err(SegwitError::InvalidWitnessProgramLength(program.len())); - } + let len = program.len(); + let program = Confined::try_from(program) + .map_err(|_| SegwitError::InvalidWitnessProgramLength(len))?; // Specific segwit v0 check. These addresses can never spend funds sent // to them. @@ -289,3 +300,126 @@ impl ScriptPubkey { && script_len - 2 == push_opbyte as usize } } + +#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Default)] +#[wrapper(Deref, Index, RangeOps, BorrowSlice, LowerHex, UpperHex)] +#[wrapper_mut(DerefMut, IndexMut, RangeMut, BorrowSliceMut)] +#[derive(StrictType, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_BITCOIN)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", transparent) +)] +pub struct WitnessScript( + #[from] + #[from(Vec)] + ScriptBytes, +); + +impl WitnessScript { + pub fn new() -> Self { Self::default() } + + pub fn with_capacity(capacity: usize) -> Self { + Self(ScriptBytes::from(Confined::with_capacity(capacity))) + } + + /// 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 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)] +#[derive(CommitEncode)] +#[commit_encode(strategy = strict)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", transparent) +)] +#[wrapper(BorrowSlice, Index, RangeOps, Debug, LowerHex, UpperHex, 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)] +#[strict_type(lib = LIB_NAME_BITCOIN)] +pub struct Witness(VarIntArray>); + +impl IntoIterator for Witness { + type Item = VarIntArray; + type IntoIter = vec::IntoIter>; + + fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } +} + +impl Witness { + pub fn new() -> Self { default!() } + + pub fn elements(&self) -> impl Iterator { + self.0.iter().map(|el| el.as_slice()) + } + + pub fn from_consensus_stack(witness: impl IntoIterator>) -> Witness { + let iter = witness.into_iter().map(|vec| { + VarIntArray::try_from(vec).expect("witness stack element length exceeds 2^64 bytes") + }); + let stack = + VarIntArray::try_from_iter(iter).expect("witness stack size exceeds 2^64 bytes"); + Witness(stack) + } + + pub(crate) fn as_var_int_array(&self) -> &VarIntArray> { &self.0 } +} + +#[cfg(feature = "serde")] +mod _serde { + use serde::{Deserialize, Serialize}; + use serde_crate::ser::SerializeSeq; + use serde_crate::{Deserializer, Serializer}; + + use super::*; + use crate::ScriptBytes; + + impl Serialize for Witness { + fn serialize(&self, serializer: S) -> Result + where S: Serializer { + let mut ser = serializer.serialize_seq(Some(self.len()))?; + for el in &self.0 { + ser.serialize_element(&ScriptBytes::from(el.to_inner()))?; + } + ser.end() + } + } + + impl<'de> Deserialize<'de> for Witness { + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> { + let data = Vec::::deserialize(deserializer)?; + Ok(Witness::from_consensus_stack(data.into_iter().map(ScriptBytes::into_vec))) + } + } +} diff --git a/primitives/src/sigtypes.rs b/primitives/src/sigtypes.rs new file mode 100644 index 00000000..21d09726 --- /dev/null +++ b/primitives/src/sigtypes.rs @@ -0,0 +1,390 @@ +// Bitcoin protocol primitives 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::iter; + +use secp256k1::{ecdsa, schnorr}; + +use crate::{NonStandardValue, LIB_NAME_BITCOIN}; + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Default)] +#[derive(StrictType, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_BITCOIN, tags = repr, into_u8, try_from_u8)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +#[repr(u8)] +pub enum SighashFlag { + /// 0x1: Sign all outputs. + #[default] + All = 0x01, + /// 0x2: Sign no outputs --- anyone can choose the destination. + None = 0x02, + /// 0x3: Sign the output whose index matches this input's index. If none + /// exists, sign the hash + /// `0000000000000000000000000000000000000000000000000000000000000001`. + /// (This rule is probably an unintentional C++ism, but it's consensus so we + /// have to follow it.) + Single = 0x03, +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Default)] +#[derive(StrictType, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_BITCOIN)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +pub struct SighashType { + pub flag: SighashFlag, + pub anyone_can_pay: bool, +} + +impl SighashType { + pub const fn all() -> Self { + SighashType { + flag: SighashFlag::All, + anyone_can_pay: false, + } + } + pub const fn none() -> Self { + SighashType { + flag: SighashFlag::None, + anyone_can_pay: false, + } + } + pub const fn single() -> Self { + SighashType { + flag: SighashFlag::Single, + anyone_can_pay: false, + } + } + + pub const fn all_anyone_can_pay() -> Self { + SighashType { + flag: SighashFlag::All, + anyone_can_pay: true, + } + } + pub const fn none_anyone_can_pay() -> Self { + SighashType { + flag: SighashFlag::None, + anyone_can_pay: true, + } + } + pub const fn single_anyone_can_pay() -> Self { + SighashType { + flag: SighashFlag::Single, + anyone_can_pay: true, + } + } + + /// Creates a [`SighashType`] from a raw `u32`. + /// + /// **Note**: this replicates consensus behaviour, for current standardness + /// rules correctness you probably want [`Self::from_standard_u32`]. + /// + /// This might cause unexpected behavior because it does not roundtrip. That + /// is, `LegacySighashType::from_consensus(n) as u32 != n` for + /// non-standard values of `n`. While verifying signatures, the user + /// should retain the `n` and use it compute the signature hash message. + pub fn from_consensus_u32(n: u32) -> SighashType { + // In Bitcoin Core, the SignatureHash function will mask the (int32) value with + // 0x1f to (apparently) deactivate ACP when checking for SINGLE and NONE bits. + // We however want to be matching also against on ACP-masked ALL, SINGLE, and + // NONE. So here we re-activate ACP. + let mask = 0x1f | 0x80; + let (flag, anyone_can_pay) = match n & mask { + // "real" sighashes + 0x01 => (SighashFlag::All, false), + 0x02 => (SighashFlag::None, false), + 0x03 => (SighashFlag::Single, false), + 0x81 => (SighashFlag::All, true), + 0x82 => (SighashFlag::None, true), + 0x83 => (SighashFlag::Single, true), + // catchalls + x if x & 0x80 == 0x80 => (SighashFlag::All, true), + _ => (SighashFlag::All, false), + }; + SighashType { + flag, + anyone_can_pay, + } + } + + /// Creates a [`SighashType`] from a raw `u32`. + /// + /// # Errors + /// + /// If `n` is a non-standard sighash value. + pub fn from_standard_u32(n: u32) -> Result> { + let (flag, anyone_can_pay) = match n { + // Standard sighashes, see https://github.com/bitcoin/bitcoin/blob/b805dbb0b9c90dadef0424e5b3bf86ac308e103e/src/script/interpreter.cpp#L189-L198 + 0x01 => (SighashFlag::All, false), + 0x02 => (SighashFlag::None, false), + 0x03 => (SighashFlag::Single, false), + 0x81 => (SighashFlag::All, true), + 0x82 => (SighashFlag::None, true), + 0x83 => (SighashFlag::Single, true), + non_standard => return Err(NonStandardValue::with(non_standard, "SighashType")), + }; + Ok(SighashType { + flag, + anyone_can_pay, + }) + } + + /// Converts [`SighashType`] to a `u32` sighash flag. + /// + /// The returned value is guaranteed to be a valid according to standardness + /// rules. + #[inline] + pub const fn into_consensus_u32(self) -> u32 { self.into_consensus_u8() as u32 } + + /// Converts [`SighashType`] to a `u32` sighash flag. + /// + /// The returned value is guaranteed to be a valid according to standardness + /// rules. + #[inline] + pub const fn to_consensus_u32(&self) -> u32 { self.into_consensus_u32() } + + pub const fn into_consensus_u8(self) -> u8 { + let flag = self.flag as u8; + let mask = (self.anyone_can_pay as u8) << 7; + flag | mask + } + + pub const fn to_consensus_u8(self) -> u8 { + let flag = self.flag as u8; + let mask = (self.anyone_can_pay as u8) << 7; + flag | mask + } +} + +/// An ECDSA signature-related error. +#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)] +#[display(doc_comments)] +pub enum SigError { + /// Non-standard sighash type. + #[display(inner)] + #[from] + SighashType(NonStandardValue), + + /// empty signature. + EmptySignature, + + /// invalid signature DER encoding. + DerEncoding, + + /// invalid BIP340 signature length ({0}). + Bip340Encoding(usize), + + /// invalid BIP340 signature. + InvalidSignature, +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +#[derive(StrictType)] +#[strict_type(lib = LIB_NAME_BITCOIN)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +pub struct LegacySig { + /// The underlying ECDSA Signature + pub sig: ecdsa::Signature, + /// The corresponding hash type + pub sighash_type: SighashType, +} + +impl LegacySig { + /// Constructs an ECDSA bitcoin signature for [`SighashType::All`]. + pub fn sighash_all(sig: ecdsa::Signature) -> LegacySig { + LegacySig { + sig, + sighash_type: SighashType::all(), + } + } + + /// Deserializes from slice following the standardness rules for + /// [`SighashType`]. + pub fn from_bytes(bytes: &[u8]) -> Result { + let (hash_ty, sig) = bytes.split_last().ok_or(SigError::EmptySignature)?; + let sighash_type = SighashType::from_standard_u32(*hash_ty as u32)?; + let sig = ecdsa::Signature::from_der(sig).map_err(|_| SigError::DerEncoding)?; + Ok(LegacySig { sig, sighash_type }) + } + + /// Serializes an Legacy signature (inner secp256k1 signature in DER format) + /// into `Vec`. + // TODO: add support to serialize to a writer to SerializedSig + pub fn to_vec(self) -> Vec { + self.sig + .serialize_der() + .iter() + .copied() + .chain(iter::once(self.sighash_type.into_consensus_u8())) + .collect() + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +#[derive(StrictType)] +#[strict_type(lib = LIB_NAME_BITCOIN)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +pub struct Bip340Sig { + /// The underlying ECDSA Signature + pub sig: schnorr::Signature, + /// The corresponding hash type + pub sighash_type: Option, +} + +impl Bip340Sig { + /// Constructs an ECDSA bitcoin signature for [`SighashType::All`]. + pub fn sighash_default(sig: schnorr::Signature) -> Self { + Bip340Sig { + sig, + sighash_type: None, + } + } + + /// Deserializes from slice following the standardness rules for + /// [`SighashType`]. + pub fn from_bytes(bytes: &[u8]) -> Result { + let (hash_ty, sig) = match bytes.len() { + 0 => return Err(SigError::EmptySignature), + 64 => (None, bytes), + 65 => (Some(bytes[64] as u32), &bytes[..64]), + invalid => return Err(SigError::Bip340Encoding(invalid)), + }; + let sighash_type = hash_ty.map(SighashType::from_standard_u32).transpose()?; + let sig = schnorr::Signature::from_slice(sig).map_err(|_| SigError::InvalidSignature)?; + Ok(Bip340Sig { sig, sighash_type }) + } + + /// Serializes an ECDSA signature (inner secp256k1 signature in DER format) + /// into `Vec`. + // TODO: add support to serialize to a writer to SerializedSig + pub fn to_vec(self) -> Vec { + let mut ser = Vec::::with_capacity(65); + ser.extend_from_slice(&self.sig[..]); + if let Some(sighash_type) = self.sighash_type { + ser.push(sighash_type.into_consensus_u8()) + } + ser + } +} + +mod _strict_encode { + use std::io; + + use amplify::confinement::TinyBlob; + use amplify::hex::FromHex; + use amplify::Bytes64; + use strict_encoding::{ + DecodeError, ReadStruct, StrictDecode, StrictDumb, StrictEncode, TypedRead, TypedWrite, + WriteStruct, + }; + + use super::*; + + impl StrictDumb for LegacySig { + fn strict_dumb() -> Self { + Self { + sig: ecdsa::Signature::from_der(&Vec::::from_hex( + "304402206fa6c164fb89906e2e1d291cc5461ceadf0f115c6b71e58f87482c94d512c3630220\ + 0ab641f3ece1d77f13ad2d8910cb7abd5a9b85f0f9036317dbb1470f22e7714c").unwrap() + ).expect("hardcoded signature"), + sighash_type: default!(), + } + } + } + + impl StrictEncode for LegacySig { + fn strict_encode(&self, writer: W) -> io::Result { + writer.write_struct::(|w| { + Ok(w.write_field( + fname!("sig"), + &TinyBlob::try_from(self.sig.serialize_der().to_vec()) + .expect("invalid signature"), + )? + .write_field(fname!("sighash_type"), &self.sighash_type)? + .complete()) + }) + } + } + + impl StrictDecode for LegacySig { + fn strict_decode(reader: &mut impl TypedRead) -> Result { + reader.read_struct(|r| { + let bytes: TinyBlob = r.read_field(fname!("sig"))?; + let sig = ecdsa::Signature::from_der(bytes.as_slice()).map_err(|_| { + DecodeError::DataIntegrityError(s!("invalid signature DER encoding")) + })?; + let sighash_type = r.read_field(fname!("sighash_type"))?; + Ok(Self { sig, sighash_type }) + }) + } + } + + impl StrictDumb for Bip340Sig { + fn strict_dumb() -> Self { + Bip340Sig::from_bytes(&Vec::::from_hex( + "a12b3f4c224619d7834f0bad0a598b79111ba08146ae1205f3e6220a132aef0ed8290379624db643\ + e6b861d8dcd37b406a11f91a51bf5a6cdf9b3c9b772f67c301" + ).unwrap()) + .expect("hardcoded signature") + } + } + + impl StrictEncode for Bip340Sig { + fn strict_encode(&self, writer: W) -> io::Result { + writer.write_struct::(|w| { + Ok(w.write_field(fname!("sig"), &Bytes64::from(*self.sig.as_ref()))? + .write_field(fname!("sighash_type"), &self.sighash_type)? + .complete()) + }) + } + } + + impl StrictDecode for Bip340Sig { + fn strict_decode(reader: &mut impl TypedRead) -> Result { + reader.read_struct(|r| { + let bytes: Bytes64 = r.read_field(fname!("sig"))?; + let sig = schnorr::Signature::from_slice(bytes.as_slice()).map_err(|_| { + DecodeError::DataIntegrityError(format!( + "invalid signature BIP340 encoding '{bytes:x}'" + )) + })?; + let sighash_type = r.read_field(fname!("sighash_type"))?; + Ok(Self { sig, sighash_type }) + }) + } + } +} diff --git a/primitives/src/stl.rs b/primitives/src/stl.rs index addee42b..2fcf5d41 100644 --- a/primitives/src/stl.rs +++ b/primitives/src/stl.rs @@ -22,26 +22,85 @@ use strict_types::{CompileError, LibBuilder, TypeLib}; -use crate::{Tx, LIB_NAME_BITCOIN}; +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, +}; +#[deprecated(since = "0.10.8", note = "use LIB_ID_BP_TX instead")] pub const LIB_ID_BITCOIN: &str = "urn:ubideco:stl:6GgF7biXPVNcus2FfQj2pQuRzau11rXApMQLfCZhojgi#money-pardon-parody"; +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"; + +#[deprecated(since = "0.10.8", note = "use _bp_tx_stl instead")] +fn _bitcoin_stl() -> Result { _bp_tx_stl() } -fn _bitcoin_stl() -> Result { +fn _bp_tx_stl() -> Result { LibBuilder::new(libname!(LIB_NAME_BITCOIN), None) .transpile::() .compile() } -pub fn bitcoin_stl() -> TypeLib { _bitcoin_stl().expect("invalid strict type Bitcoin library") } +fn _bp_consensus_stl() -> Result { + LibBuilder::new(libname!(LIB_NAME_BITCOIN), tiny_bset! { + strict_types::stl::std_stl().to_dependency(), + }) + .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() + .transpile::() + .compile() +} + +#[deprecated(since = "0.10.8", note = "use bp_tx_stl instead")] +pub fn bitcoin_stl() -> TypeLib { bp_tx_stl() } + +pub fn bp_tx_stl() -> TypeLib { + _bp_tx_stl().expect("invalid strict type Bitcoin transaction library") +} + +pub fn bp_consensus_stl() -> TypeLib { + _bp_consensus_stl().expect("invalid strict type Bitcoin consensus library") +} #[cfg(test)] mod test { use super::*; #[test] - fn lib_id() { - let lib = bitcoin_stl(); - assert_eq!(lib.id().to_string(), LIB_ID_BITCOIN); + fn lib_id_tx() { + let lib = bp_tx_stl(); + assert_eq!(lib.id().to_string(), LIB_ID_BP_TX); + } + + #[test] + fn lib_id_consensus() { + let lib = bp_consensus_stl(); + assert_eq!(lib.id().to_string(), LIB_ID_BP_CONSENSUS); } } diff --git a/primitives/src/taproot.rs b/primitives/src/taproot.rs index b4dfc785..26c9552f 100644 --- a/primitives/src/taproot.rs +++ b/primitives/src/taproot.rs @@ -23,12 +23,13 @@ use std::borrow::Borrow; use std::fmt::{self, Formatter, LowerHex, UpperHex}; -use std::{cmp, io}; +use std::ops::BitXor; +use std::{cmp, io, slice, vec}; -use amplify::confinement::{Confined, TinyVec, U32}; -use amplify::{Bytes32, Wrapper}; +use amplify::confinement::{Confined, U32}; +use amplify::{confinement, Bytes32, Wrapper}; use commit_verify::{DigestExt, Sha256}; -use secp256k1::{Scalar, XOnlyPublicKey}; +use secp256k1::{PublicKey, Scalar, XOnlyPublicKey}; use strict_encoding::{ DecodeError, ReadTuple, StrictDecode, StrictEncode, StrictProduct, StrictTuple, StrictType, TypeName, TypedRead, TypedWrite, WriteTuple, @@ -38,30 +39,98 @@ use crate::opcodes::*; use crate::{ScriptBytes, ScriptPubkey, WitnessVer, LIB_NAME_BITCOIN}; /// The SHA-256 midstate value for the TapLeaf hash. -pub const MIDSTATE_TAPLEAF: [u8; 7] = *b"TapLeaf"; +const MIDSTATE_TAPLEAF: [u8; 7] = *b"TapLeaf"; // 9ce0e4e67c116c3938b3caf2c30f5089d3f3936c47636e607db33eeaddc6f0c9 /// The SHA-256 midstate value for the TapBranch hash. -pub const MIDSTATE_TAPBRANCH: [u8; 9] = *b"TapBranch"; +const MIDSTATE_TAPBRANCH: [u8; 9] = *b"TapBranch"; // 23a865a9b8a40da7977c1e04c49e246fb5be13769d24c9b7b583b5d4a8d226d2 /// The SHA-256 midstate value for the TapTweak hash. -pub const MIDSTATE_TAPTWEAK: [u8; 8] = *b"TapTweak"; +const MIDSTATE_TAPTWEAK: [u8; 8] = *b"TapTweak"; // d129a2f3701c655d6583b6c3b941972795f4e23294fd54f4a2ae8d8547ca590b /// The SHA-256 midstate value for the TapSig hash. -pub const MIDSTATE_TAPSIGHASH: [u8; 10] = *b"TapSighash"; +#[warn(dead_code)] +const MIDSTATE_TAPSIGHASH: [u8; 10] = *b"TapSighash"; // f504a425d7f8783b1363868ae3e556586eee945dbc7888dd02a6e2c31873fe9f #[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)] #[display("invalid public key")] -pub struct InvalidPubkey; +pub struct InvalidPubkey(pub Bytes32); +impl From for DecodeError { + fn from(e: InvalidPubkey) -> Self { + DecodeError::DataIntegrityError(format!("invalid x-only public key value '{:x}'", e.0)) + } +} + +macro_rules! dumb_key { + () => { + Self(XOnlyPublicKey::from_slice(&[1u8; 32]).unwrap()) + }; +} + +/// Generic taproot x-only (BIP-340) public key - a wrapper around +/// [`XOnlyPublicKey`] providing APIs compatible with the rest of the library. +/// Should be used everywhere when [`InternalPk`] and [`OutputPk`] do not apply: +/// as an output of BIP32 key derivation functions, inside tapscripts/ +/// leafscripts etc. #[derive(Wrapper, WrapperMut, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)] #[wrapper(Deref, LowerHex, Display, FromStr)] #[wrapper_mut(DerefMut)] #[derive(StrictType, StrictDumb)] -#[strict_type(lib = LIB_NAME_BITCOIN, dumb = { Self(XOnlyPublicKey::from_slice(&[1u8; 32]).unwrap()) })] +#[strict_type(lib = LIB_NAME_BITCOIN, dumb = dumb_key!())] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", transparent) +)] +pub struct TaprootPk(XOnlyPublicKey); + +impl TaprootPk { + pub fn from_byte_array(data: [u8; 32]) -> Result { + XOnlyPublicKey::from_slice(data.as_ref()) + .map(Self) + .map_err(|_| InvalidPubkey(data.into())) + } + + 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 [u8; 32] { + fn from(pk: TaprootPk) -> [u8; 32] { pk.to_byte_array() } +} + +impl StrictEncode for TaprootPk { + fn strict_encode(&self, writer: W) -> io::Result { + let bytes = Bytes32::from(self.0.serialize()); + writer.write_newtype::(&bytes) + } +} + +impl StrictDecode for TaprootPk { + 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()) + }) + } +} + +/// 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!())] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), @@ -70,15 +139,24 @@ pub struct InvalidPubkey; pub struct InternalPk(XOnlyPublicKey); impl InternalPk { + #[inline] + pub fn from_unchecked(pk: TaprootPk) -> Self { Self(pk.0) } + pub fn from_byte_array(data: [u8; 32]) -> Result { - XOnlyPublicKey::from_slice(data.as_ref()) + XOnlyPublicKey::from_slice(&data) .map(Self) - .map_err(|_| InvalidPubkey) + .map_err(|_| InvalidPubkey(data.into())) } pub fn to_byte_array(&self) -> [u8; 32] { self.0.serialize() } + #[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 + } + + pub fn to_output_pk(&self, merkle_root: Option) -> (OutputPk, Parity) { let mut engine = Sha256::from_tag(MIDSTATE_TAPTWEAK); // always hash the key engine.input_raw(&self.0.serialize()); @@ -97,10 +175,14 @@ impl InternalPk { tweaked_parity, tweak )); - output_key + (OutputPk(output_key), tweaked_parity.into()) } } +impl From for [u8; 32] { + fn from(pk: InternalPk) -> [u8; 32] { pk.to_byte_array() } +} + impl StrictEncode for InternalPk { fn strict_encode(&self, writer: W) -> io::Result { let bytes = Bytes32::from(self.0.serialize()); @@ -114,11 +196,57 @@ impl StrictDecode for InternalPk { let bytes: Bytes32 = r.read_field()?; XOnlyPublicKey::from_slice(bytes.as_slice()) .map(Self) - .map_err(|_| { - DecodeError::DataIntegrityError(format!( - "invalid x-only public key value '{bytes:x}'" - )) - }) + .map_err(|_| InvalidPubkey(bytes).into()) + }) + } +} + +/// Output taproot key - an [`InternalPk`] tweaked with merkle root of the +/// script tree - or its own hash. Used only inside addresses and raw 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!())] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", transparent) +)] +pub struct OutputPk(XOnlyPublicKey); + +impl OutputPk { + #[inline] + pub fn from_unchecked(pk: TaprootPk) -> Self { Self(pk.0) } + + pub fn from_byte_array(data: [u8; 32]) -> Result { + XOnlyPublicKey::from_slice(&data) + .map(Self) + .map_err(|_| InvalidPubkey(data.into())) + } + + pub fn to_byte_array(&self) -> [u8; 32] { self.0.serialize() } +} + +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()) }) } } @@ -129,6 +257,8 @@ pub trait IntoTapHash { #[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)] #[wrapper(Index, RangeOps, BorrowSlice, Hex, Display, FromStr)] +#[derive(StrictType, StrictEncode, StrictDecode, StrictDumb)] +#[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), @@ -143,7 +273,7 @@ pub struct TapLeafHash( impl TapLeafHash { pub fn with_leaf_script(leaf_script: &LeafScript) -> Self { let mut engine = Sha256::from_tag(MIDSTATE_TAPLEAF); - engine.input_raw(&[leaf_script.version.to_consensus()]); + engine.input_raw(&[leaf_script.version.to_consensus_u8()]); engine.input_with_len::(leaf_script.script.as_slice()); Self(engine.finish().into()) } @@ -162,6 +292,8 @@ impl IntoTapHash for TapLeafHash { #[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)] #[wrapper(Index, RangeOps, BorrowSlice, Hex, Display, FromStr)] +#[derive(StrictType, StrictEncode, StrictDecode, StrictDumb)] +#[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), @@ -207,14 +339,51 @@ impl IntoTapHash for TapNodeHash { fn into_tap_hash(self) -> TapNodeHash { self } } -#[derive(Wrapper, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)] +#[derive(Wrapper, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Default)] #[wrapper(Deref)] +#[derive(StrictType, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate", transparent) )] -pub struct TapMerklePath(TinyVec); +pub struct TapMerklePath(Confined, 0, 128>); + +impl IntoIterator for TapMerklePath { + type Item = TapBranchHash; + type IntoIter = vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } +} + +impl<'a> IntoIterator for &'a TapMerklePath { + type Item = &'a TapBranchHash; + type IntoIter = slice::Iter<'a, TapBranchHash>; + + fn into_iter(self) -> Self::IntoIter { self.0.iter() } +} + +impl TapMerklePath { + /// Tries to construct a confinement over a collection. Fails if the number + /// of items in the collection exceeds one of the confinement bounds. + // We can't use `impl TryFrom` due to the conflict with core library blanked + // implementation + #[inline] + pub fn try_from(path: Vec) -> Result { + Confined::try_from(path).map(Self::from_inner) + } + + /// Tries to construct a confinement with a collection of elements taken + /// from an iterator. Fails if the number of items in the collection + /// exceeds one of the confinement bounds. + #[inline] + pub fn try_from_iter>( + iter: I, + ) -> Result { + Confined::try_from_iter(iter).map(Self::from_inner) + } +} /// Taproot annex prefix. pub const TAPROOT_ANNEX_PREFIX: u8 = 0x50; @@ -258,27 +427,33 @@ impl StrictTuple for LeafVer { } impl StrictEncode for LeafVer { fn strict_encode(&self, writer: W) -> std::io::Result { - writer.write_tuple::(|w| Ok(w.write_field(&self.to_consensus())?.complete())) + writer.write_tuple::(|w| Ok(w.write_field(&self.to_consensus_u8())?.complete())) } } impl StrictDecode for LeafVer { fn strict_decode(reader: &mut impl TypedRead) -> Result { reader.read_tuple(|r| { let version = r.read_field()?; - Self::from_consensus(version) + Self::from_consensus_u8(version) .map_err(|err| DecodeError::DataIntegrityError(err.to_string())) }) } } impl LeafVer { + #[doc(hidden)] + #[deprecated(since = "0.10.9", note = "use from_consensus_u8")] + pub fn from_consensus(version: u8) -> Result { + Self::from_consensus_u8(version) + } + /// Creates a [`LeafVer`] from consensus byte representation. /// /// # Errors /// /// - If the last bit of the `version` is odd. /// - If the `version` is 0x50 ([`TAPROOT_ANNEX_PREFIX`]). - pub fn from_consensus(version: u8) -> Result { + pub fn from_consensus_u8(version: u8) -> Result { match version { TAPROOT_LEAF_TAPSCRIPT => Ok(LeafVer::TapScript), TAPROOT_ANNEX_PREFIX => Err(InvalidLeafVer(TAPROOT_ANNEX_PREFIX)), @@ -286,8 +461,12 @@ impl LeafVer { } } + #[doc(hidden)] + #[deprecated(since = "0.10.9", note = "use to_consensus_u8")] + pub fn to_consensus(self) -> u8 { self.to_consensus_u8() } + /// Returns the consensus representation of this [`LeafVer`]. - pub fn to_consensus(self) -> u8 { + pub fn to_consensus_u8(self) -> u8 { match self { LeafVer::TapScript => TAPROOT_LEAF_TAPSCRIPT, LeafVer::Future(version) => version.to_consensus(), @@ -296,11 +475,11 @@ impl LeafVer { } impl LowerHex for LeafVer { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { LowerHex::fmt(&self.to_consensus(), f) } + fn fmt(&self, f: &mut Formatter) -> fmt::Result { LowerHex::fmt(&self.to_consensus_u8(), f) } } impl UpperHex for LeafVer { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { UpperHex::fmt(&self.to_consensus(), f) } + fn fmt(&self, f: &mut Formatter) -> fmt::Result { UpperHex::fmt(&self.to_consensus_u8(), f) } } /// Inner type representing future (non-tapscript) leaf versions. See @@ -312,7 +491,11 @@ impl UpperHex for LeafVer { #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN, dumb = { Self(0x51) })] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", transparent) +)] pub struct FutureLeafVer(u8); impl FutureLeafVer { @@ -364,7 +547,18 @@ impl From for LeafScript { } impl LeafScript { + #[inline] + pub fn new(version: LeafVer, script: ScriptBytes) -> Self { LeafScript { version, script } } + #[inline] + pub fn with_bytes(version: LeafVer, script: Vec) -> Result { + Ok(LeafScript { + version, + script: ScriptBytes::from(script), + }) + } + #[inline] pub fn from_tap_script(tap_script: TapScript) -> Self { Self::from(tap_script) } + #[inline] pub fn tap_leaf_hash(&self) -> TapLeafHash { TapLeafHash::with_leaf_script(self) } } @@ -421,28 +615,28 @@ impl TapScript { } /// Adds a single opcode to the script. - pub fn push_opcode(&mut self, op_code: TapCode) { - self.0.push(op_code as u8).expect("script exceeds 4GB"); - } + pub fn push_opcode(&mut self, op_code: TapCode) { self.0.push(op_code as u8); } + + pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 } } impl ScriptPubkey { pub fn p2tr(internal_key: InternalPk, merkle_root: Option) -> Self { - let output_key = internal_key.to_output_key(merkle_root); + let (output_key, _) = internal_key.to_output_pk(merkle_root); Self::p2tr_tweaked(output_key) } pub fn p2tr_key_only(internal_key: InternalPk) -> Self { - let output_key = internal_key.to_output_key(None::); + let (output_key, _) = internal_key.to_output_pk(None::); Self::p2tr_tweaked(output_key) } pub fn p2tr_scripted(internal_key: InternalPk, merkle_root: impl IntoTapHash) -> Self { - let output_key = internal_key.to_output_key(Some(merkle_root)); + let (output_key, _) = internal_key.to_output_pk(Some(merkle_root)); Self::p2tr_tweaked(output_key) } - pub fn p2tr_tweaked(output_key: XOnlyPublicKey) -> Self { + pub fn p2tr_tweaked(output_key: OutputPk) -> Self { // output key is 32 bytes long, so it's safe to use // `new_witness_program_unchecked` (Segwitv1) Self::with_witness_program_unchecked(WitnessVer::V1, &output_key.serialize()) @@ -452,3 +646,107 @@ impl ScriptPubkey { self.len() == 34 && self[0] == WitnessVer::V1.op_code() as u8 && self[1] == OP_PUSHBYTES_32 } } + +/// invalid parity value {0} - must be 0 or 1 +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display, Error)] +#[display(doc_comments)] +pub struct InvalidParityValue(pub u8); + +/// Represents the parity passed between FFI function calls. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display)] +#[display(lowercase)] +#[derive(StrictType, StrictEncode, StrictDecode, StrictDumb)] +#[strict_type(lib = LIB_NAME_BITCOIN, tags = repr, into_u8, try_from_u8)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +#[repr(u8)] +pub enum Parity { + /// Even parity. + #[strict_type(dumb)] + Even = 0, + /// Odd parity. + Odd = 1, +} + +impl From for Parity { + fn from(parity: secp256k1::Parity) -> Self { + match parity { + secp256k1::Parity::Even => Parity::Even, + secp256k1::Parity::Odd => Parity::Odd, + } + } +} + +impl Parity { + /// Converts parity into an integer (byte) value. + /// + /// This returns `0` for even parity and `1` for odd parity. + pub fn to_consensus_u8(self) -> u8 { self as u8 } + + /// Constructs a [`Parity`] from a byte. + /// + /// The only allowed values are `0` meaning even parity and `1` meaning odd. + /// Other values result in error being returned. + pub fn from_consensus_u8(parity: u8) -> Result { + match parity { + 0 => Ok(Parity::Even), + 1 => Ok(Parity::Odd), + invalid => Err(InvalidParityValue(invalid)), + } + } +} + +/// Returns even parity if the operands are equal, odd otherwise. +impl BitXor for Parity { + type Output = Parity; + + fn bitxor(self, rhs: Parity) -> Self::Output { + // This works because Parity has only two values (i.e. only 1 bit of + // information). + if self == rhs { + Parity::Even // 1^1==0 and 0^0==0 + } else { + Parity::Odd // 1^0==1 and 0^1==1 + } + } +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +#[derive(StrictType, StrictEncode, StrictDecode, StrictDumb)] +#[strict_type(lib = LIB_NAME_BITCOIN)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +pub struct ControlBlock { + /// The tapleaf version. + pub leaf_version: LeafVer, + /// The parity of the output key (NOT THE INTERNAL KEY WHICH IS ALWAYS + /// XONLY). + pub output_key_parity: Parity, + /// The internal key. + pub internal_pk: InternalPk, + /// The merkle proof of a script associated with this leaf. + pub merkle_branch: TapMerklePath, +} + +impl ControlBlock { + #[inline] + pub fn with( + leaf_version: LeafVer, + internal_pk: InternalPk, + output_key_parity: Parity, + merkle_branch: TapMerklePath, + ) -> Self { + ControlBlock { + leaf_version, + output_key_parity, + internal_pk, + merkle_branch, + } + } +} diff --git a/primitives/src/tx.rs b/primitives/src/tx.rs index dc457ff4..c550cc38 100644 --- a/primitives/src/tx.rs +++ b/primitives/src/tx.rs @@ -25,16 +25,14 @@ use std::fmt::{self, Debug, Display, Formatter, LowerHex}; use std::iter::Sum; use std::num::ParseIntError; use std::str::FromStr; -use std::vec; use amplify::hex::{self, FromHex, ToHex}; -use amplify::{Bytes32StrRev, RawArray, Wrapper}; +use amplify::{ByteArray, Bytes32StrRev, Wrapper}; use commit_verify::{DigestExt, Sha256}; -use super::{VarIntArray, LIB_NAME_BITCOIN}; use crate::{ ConsensusDecode, ConsensusDecodeError, ConsensusEncode, NonStandardValue, ScriptPubkey, - SigScript, + SigScript, VarIntArray, Witness, Wtxid, LIB_NAME_BITCOIN, }; #[derive(Wrapper, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, From)] @@ -59,7 +57,7 @@ impl Txid { #[inline] pub const fn coinbase() -> Self { Self(Bytes32StrRev::zero()) } #[inline] - pub fn is_coinbase(&self) -> bool { self.to_raw_array() == [0u8; 32] } + pub fn is_coinbase(&self) -> bool { self.to_byte_array() == [0u8; 32] } } impl FromHex for Txid { @@ -69,30 +67,6 @@ impl FromHex for Txid { } } -#[derive(Wrapper, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, From)] -#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] -#[strict_type(lib = LIB_NAME_BITCOIN)] -#[derive(CommitEncode)] -#[commit_encode(strategy = strict)] -#[cfg_attr( - feature = "serde", - derive(Serialize, Deserialize), - serde(crate = "serde_crate", transparent) -)] -#[wrapper(BorrowSlice, Index, RangeOps, Debug, LowerHex, UpperHex, 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(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display, From)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] @@ -206,67 +180,6 @@ impl SeqNo { pub const fn to_consensus_u32(&self) -> u32 { self.0 } } -#[derive(Wrapper, Clone, Eq, PartialEq, Hash, Debug, From, Default)] -#[wrapper(Deref, Index, RangeOps)] -#[derive(StrictType, StrictEncode, StrictDecode)] -#[strict_type(lib = LIB_NAME_BITCOIN)] -pub struct Witness(VarIntArray>); - -impl IntoIterator for Witness { - type Item = VarIntArray; - type IntoIter = vec::IntoIter>; - - fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } -} - -impl Witness { - pub fn new() -> Self { default!() } - - pub fn elements(&self) -> impl Iterator { - self.0.iter().map(|el| el.as_slice()) - } - - pub fn from_consensus_stack(witness: impl IntoIterator>) -> Witness { - let iter = witness.into_iter().map(|vec| { - VarIntArray::try_from(vec).expect("witness stack element length exceeds 2^64 bytes") - }); - let stack = - VarIntArray::try_from_iter(iter).expect("witness stack size exceeds 2^64 bytes"); - Witness(stack) - } - - pub(crate) fn as_var_int_array(&self) -> &VarIntArray> { &self.0 } -} - -#[cfg(feature = "serde")] -mod _serde { - use serde::{Deserialize, Serialize}; - use serde_crate::ser::SerializeSeq; - use serde_crate::{Deserializer, Serializer}; - - use super::*; - use crate::ScriptBytes; - - impl Serialize for Witness { - fn serialize(&self, serializer: S) -> Result - where S: Serializer { - let mut ser = serializer.serialize_seq(Some(self.len()))?; - for el in &self.0 { - ser.serialize_element(&ScriptBytes::from(el.to_inner()))?; - } - ser.end() - } - } - - impl<'de> Deserialize<'de> for Witness { - fn deserialize(deserializer: D) -> Result - where D: Deserializer<'de> { - let data = Vec::::deserialize(deserializer)?; - Ok(Witness::from_consensus_stack(data.into_iter().map(ScriptBytes::into_vec))) - } - } -} - #[derive(Clone, Eq, PartialEq, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] @@ -616,7 +529,7 @@ impl Tx { /// /// This gives a way to identify a transaction that is "the same" as /// another in the sense of having same inputs and outputs. - pub fn ntxid(&self) -> [u8; 32] { self.to_unsigned_tx().txid().to_raw_array() } + pub fn ntxid(&self) -> [u8; 32] { self.to_unsigned_tx().txid().to_byte_array() } /// Computes the [`Txid`]. /// @@ -640,7 +553,7 @@ impl Tx { .expect("engines don't error"); let mut double = Sha256::default(); double.input_raw(&enc.finish()); - Txid::from_raw_array(double.finish()) + Txid::from_byte_array(double.finish()) } /// Computes the segwit version of the transaction id. @@ -655,7 +568,7 @@ impl Tx { .expect("engines don't error"); let mut double = Sha256::default(); double.input_raw(&enc.finish()); - Wtxid::from_raw_array(double.finish()) + Wtxid::from_byte_array(double.finish()) } } diff --git a/primitives/src/util.rs b/primitives/src/util.rs index cef8139c..d7c70642 100644 --- a/primitives/src/util.rs +++ b/primitives/src/util.rs @@ -92,39 +92,3 @@ impl Chain { } } } - -/// A variable-length unsigned integer. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub struct VarInt(pub u64); - -#[allow(clippy::len_without_is_empty)] // VarInt has no concept of 'is_empty'. -impl VarInt { - pub const fn new(u: u64) -> Self { VarInt(u) } - - pub fn with(u: impl Into) -> Self { VarInt(u.into() as u64) } - - /// Gets the length of this VarInt when encoded. - /// - /// Returns 1 for 0..=0xFC, 3 for 0xFD..=(2^16-1), 5 for 0x10000..=(2^32-1), - /// and 9 otherwise. - #[inline] - pub const fn len(&self) -> usize { - match self.0 { - 0..=0xFC => 1, - 0xFD..=0xFFFF => 3, - 0x10000..=0xFFFFFFFF => 5, - _ => 9, - } - } - - pub const fn to_u64(&self) -> u64 { self.0 } - pub const fn into_u64(self) -> u64 { self.0 } - pub fn to_usize(&self) -> usize { - usize::try_from(self.0).expect("transaction too large for a non-64 bit platform") - } - pub fn into_usize(self) -> usize { self.to_usize() } -} - -impl + Copy> PartialEq for VarInt { - fn eq(&self, other: &U) -> bool { self.0.eq(&(*other).into()) } -} diff --git a/primitives/src/weights.rs b/primitives/src/weights.rs index e9436e78..2a45f8d1 100644 --- a/primitives/src/weights.rs +++ b/primitives/src/weights.rs @@ -22,9 +22,11 @@ use std::iter::Sum; use std::ops::{Add, AddAssign}; -use crate::{ScriptPubkey, SigScript, Tx, TxIn, TxOut, VarIntSize, Witness}; +use crate::{LenVarInt, ScriptPubkey, SigScript, Tx, TxIn, TxOut, Witness, LIB_NAME_BITCOIN}; #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)] +#[derive(StrictType, StrictEncode, StrictDecode, StrictDumb)] +#[strict_type(lib = LIB_NAME_BITCOIN)] #[display("{0} vbytes")] pub struct VBytes(u32); @@ -47,6 +49,8 @@ impl VBytes { } #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)] +#[derive(StrictType, StrictEncode, StrictDecode, StrictDumb)] +#[strict_type(lib = LIB_NAME_BITCOIN)] #[display("{0} WU")] pub struct WeightUnits(u32); @@ -84,8 +88,8 @@ pub trait Weight { impl Weight for Tx { fn weight_units(&self) -> WeightUnits { let bytes = 4 // version - + self.inputs.var_int_size().len() - + self.outputs.var_int_size().len() + + self.inputs.len_var_int().len() + + self.outputs.len_var_int().len() + 4; // lock time let mut weight = WeightUnits::no_discount(bytes) + @@ -122,22 +126,22 @@ impl Weight for TxOut { impl Weight for ScriptPubkey { fn weight_units(&self) -> WeightUnits { - WeightUnits::no_discount(self.var_int_size().len() + self.len()) + WeightUnits::no_discount(self.len_var_int().len() + self.len()) } } impl Weight for SigScript { fn weight_units(&self) -> WeightUnits { - WeightUnits::no_discount(self.var_int_size().len() + self.len()) + WeightUnits::no_discount(self.len_var_int().len() + self.len()) } } impl Weight for Witness { fn weight_units(&self) -> WeightUnits { WeightUnits::witness_discount( - self.var_int_size().len() + + self.len_var_int().len() + self.iter() - .map(|item| item.var_int_size().len() + item.len()) + .map(|item| item.len_var_int().len() + item.len()) .sum::(), ) } diff --git a/src/bin/bpcore-stl.rs b/src/bin/bpcore-stl.rs index 6340bca1..483c26d1 100644 --- a/src/bin/bpcore-stl.rs +++ b/src/bin/bpcore-stl.rs @@ -19,19 +19,36 @@ // See the License for the specific language governing permissions and // limitations under the License. +use strict_encoding::libname; use strict_types::parse_args; fn main() { let (format, dir) = parse_args(); - bc::stl::bitcoin_stl() + let mut lib = bc::stl::bp_tx_stl(); + lib.name = libname!("Tx"); + lib.serialize( + format, + dir.as_ref(), + "0.1.0", + Some( + " + Description: Bitcoin transaction library + Author: Dr Maxim Orlovsky + Copyright (C) 2023 LNP/BP Standards Association. All rights reserved. + License: Apache-2.0", + ), + ) + .expect("unable to write to the file"); + + bc::stl::bp_consensus_stl() .serialize( format, dir.as_ref(), "0.1.0", Some( " - Description: Consensus layer for bitcoin protocol + Description: Consensus library for bitcoin protocol Author: Dr Maxim Orlovsky Copyright (C) 2023 LNP/BP Standards Association. All rights reserved. License: Apache-2.0", diff --git a/src/stl.rs b/src/stl.rs index ddafba9e..005f26b9 100644 --- a/src/stl.rs +++ b/src/stl.rs @@ -35,7 +35,7 @@ pub const LIB_ID_BPCORE: &str = fn _bp_core_stl() -> Result { LibBuilder::new(libname!(LIB_NAME_BPCORE), tiny_bset! { strict_types::stl::std_stl().to_dependency(), - bc::stl::bitcoin_stl().to_dependency(), + bc::stl::bp_tx_stl().to_dependency(), commit_verify::stl::commit_verify_stl().to_dependency() }) .transpile::() diff --git a/stl/Bitcoin@0.1.0.sta b/stl/Bitcoin@0.1.0.sta index c49fed3f..2c152aa6 100644 --- a/stl/Bitcoin@0.1.0.sta +++ b/stl/Bitcoin@0.1.0.sta @@ -1,26 +1,64 @@ -----BEGIN STRICT TYPE LIB----- -Id: urn:ubideco:stl:6GgF7biXPVNcus2FfQj2pQuRzau11rXApMQLfCZhojgi +Id: urn:ubideco:stl:D42LxJBQokrGJzvoSV3E1HoriGgLzPcxuL61JymwjEqV Name: Bitcoin -Dependencies: ~ +Dependencies: + urn:ubideco:stl:9KALDYR8Nyjq4FdMW6kYoL7vdkWnqPqNuFnmE9qHpNjZ -B0JpdGNvaW4AAA4ACExvY2tUaW1lBQEAAAQIT3V0cG9pbnQGAgR0eGlkAaOCQvPL -19HQoRLajeFgL1bU+G8OxMR2xcBoWUxLBGVWBHZvdXQBIeM+Q8WqXPIpJ1OjOMFn -7TtjnE3Zzr2pjzRpF7rJQ3UEU2F0cwUBAAAIC1NjcmlwdEJ5dGVzBQEACAAAQAAA -AAAAAAAA/////wAAAAAMU2NyaXB0UHVia2V5BQEBJav1uRIUF7qjOdRfexV1p3FL -4Xp1GF3QMTV61Mkt6YYFU2VxTm8FAQAABAlTaWdTY3JpcHQFAQElq/W5EhQXuqM5 -1F97FXWncUvhenUYXdAxNXrUyS3phgJUeAYEB3ZlcnNpb24BqHzGeWRn5VfXROXJ -YjrgDMuV2RoNLwPfdMB1Ek++x54GaW5wdXRzAAgBGUctF9hiB6cPITGpWKFopW4q -1lM/nGZMGXfaXPn+YoYAAAAAAAAAAP////8AAAAAB291dHB1dHMACAGQO2RweYSP -GyZTKuTOxqaJRKBTWLjwgcsms7v4LZ478wAAAAAAAAAA/////wAAAAAIbG9ja1Rp -bWUBNdodFTkgbrvd3KTDPYcx6vKbp9p03z3IgiAcTha1uRwEVHhJbgYECnByZXZP -dXRwdXQB6GpAzVwl+b3ihP9ppREyp0ErIVW9DlYSwb/jhJVgxQYJc2lnU2NyaXB0 -ATh1BLFLfA5GbUeeF0d9JHQkf/gDZOw9S6r3OiD3QXRrCHNlcXVlbmNlAQEZbYUp -yPbaRE0VTo2cjL2eD42JAlo7sJEhxj1SsB6HB3dpdG5lc3MBc3dDcDaTIEnNKJUI -qnF844KdskUuVehO1PwuPM0uAJ0FVHhPdXQGAgV2YWx1ZQGX9dcGQq4qM5IlO6hO -Hc7Ek+O/PtNxsYdzhjVOzIiCawxzY3JpcHRQdWJrZXkBvvwe/GalGf1kUmo6E4dA -5/EQLQu+/zrBRk7z90B2dFwFVHhWZXIFAQAARARUeGlkBQEABwAAQCAABFZvdXQF +B0JpdGNvaW4Be4SAPJ764hElp3wsObxw0v3o+UOuDf2c9OaC7cdmynADU3RkAQNT +dGQBAGGGItF7rvBmAt/ndcmA4LNrbrroCQ2AdfdRO+xLk/ZNBEJvb2wsAAlCaXAz +NDBTaWcGAgNzaWcABwAAQEAADHNpZ2hhc2hfdHlwZQAEAgAEbm9uZQAAAAEEc29t +ZQAFAQHbF2Q+dwkTJ+gEmSSB1dxgFSMIlVM55Yszn9RMT3JIKglCbG9ja0hhc2gF +AQAHAABAIAALQmxvY2tIZWFkZXIGBgd2ZXJzaW9uAABEDXByZXZCbG9ja0hhc2gB +136XKN8Qx+HJT3+gvLwHRRJSZMs+SsX4k0LE/8jKtLcKbWVya2xlUm9vdAAHAABA +IAAEdGltZQAABARiaXRzAAAEBW5vbmNlAAAEB0J5dGVTdHIFAQAIAABAAAAAAAAA +AAD/////AAAAAAVDaGFpbgMEB2JpdGNvaW4AB3JlZ3Rlc3SACHRlc3RuZXQzgwZz +aWduZXSEDENvbnRyb2xCbG9jawYEC2xlYWZWZXJzaW9uAbYzCakYv7aSDW7IWKQk +hyNGWmk/ckMHv/8d1zpzgU7JD291dHB1dEtleVBhcml0eQGPyiYjolVEI5WPpnqn +bUNqFd5k6xCz9PmZFCIvlRFmiAppbnRlcm5hbFBrAd/4ADyB/kf8VCOx0sbiDw3f +qma9zPN9dBPJH2XaADyIDG1lcmtsZUJyYW5jaAHuAx5mUVBotnQZXFF39AqDxABS +W8GkMBQYAGlPvfBzpA1GdXR1cmVMZWFmVmVyBQEAAAEKSW50ZXJuYWxQawUBAAcA +AEAgAApMZWFmU2NyaXB0BgIHdmVyc2lvbgG2MwmpGL+2kg1uyFikJIcjRlppP3JD +B7//Hdc6c4FOyQZzY3JpcHQBJav1uRIUF7qjOdRfexV1p3FL4Xp1GF3QMTV61Mkt +6YYHTGVhZlZlcgUBAAABCUxlZ2FjeVNpZwYCA3NpZwAIAABAAAAAAAAAAAD/AAAA +AAAAAAxzaWdoYXNoX3R5cGUB2xdkPncJEyfoBJkkgdXcYBUjCJVTOeWLM5/UTE9y +SCoITG9ja1RpbWUFAQAABAZPcENvZGUDEgpwdXNoQnl0ZXMwAAtwdXNoQnl0ZXMz +MiAJcHVzaERhdGExTAlwdXNoRGF0YTJNCXB1c2hEYXRhNE4IcmVzZXJ2ZWRQCHB1 +c2hOdW0xUQZyZXR1cm5qA2R1cHYFZXF1YWyHC2VxdWFsVmVyaWZ5iAlyaXBlbWQx +NjCmBHNoYTGnBnNoYTI1NqgHaGFzaDE2MKkHaGFzaDI1NqoIY2hlY2tTaWesDmNo +ZWNrU2lnVmVyaWZ5rQhPdXRwb2ludAYCBHR4aWQBo4JC88vX0dChEtqN4WAvVtT4 +bw7ExHbFwGhZTEsEZVYEdm91dAEh4z5Dxapc8iknU6M4wWftO2OcTdnOvamPNGkX +uslDdQhPdXRwdXRQawUBAAcAAEAgAAZQYXJpdHkDAgRldmVuAANvZGQBDFJlZGVl +bVNjcmlwdAUBASWr9bkSFBe6oznUX3sVdadxS+F6dRhd0DE1etTJLemGBFNhdHMF +AQAACAtTY3JpcHRCeXRlcwUBAAgAAEAAAAAAAAAAAP////8AAAAADFNjcmlwdFB1 +YmtleQUBASWr9bkSFBe6oznUX3sVdadxS+F6dRhd0DE1etTJLemGBVNlcU5vBQEA +AAQJU2lnU2NyaXB0BQEBJav1uRIUF7qjOdRfexV1p3FL4Xp1GF3QMTV61Mkt6YYL +U2lnaGFzaEZsYWcDAwNhbGwBBG5vbmUCBnNpbmdsZQMLU2lnaGFzaFR5cGUGAgRm +bGFnAf8+an/Ix4VDTvyO53SzALGb7Jhxopoe8QCL7xDrKNU0DGFueW9uZUNhblBh +eQJ7hIA8nvriESWnfCw5vHDS/ej5Q64N/Zz05oLtx2bKcGGGItF7rvBmAt/ndcmA +4LNrbrroCQ2AdfdRO+xLk/ZNDVRhcEJyYW5jaEhhc2gFAQAHAABAIAAHVGFwQ29k +ZQMGC3B1c2hCeXRlczMyIAlwdXNoRGF0YTFMCXB1c2hEYXRhMk0JcHVzaERhdGE0 +TghyZXNlcnZlZFAGcmV0dXJuagtUYXBMZWFmSGFzaAUBAAcAAEAgAA1UYXBNZXJr +bGVQYXRoBQEACAGv68Wd2P1QRx2YXcJBKC6rqYxIivJkY8F2flEGIsKS2AAAAAAA +AAAAgAAAAAAAAAALVGFwTm9kZUhhc2gFAQAHAABAIAAJVGFwU2NyaXB0BQEBJav1 +uRIUF7qjOdRfexV1p3FL4Xp1GF3QMTV61Mkt6YYCVHgGBAd2ZXJzaW9uAah8xnlk +Z+VX10TlyWI64AzLldkaDS8D33TAdRJPvseeBmlucHV0cwAIARlHLRfYYgenDyEx +qVihaKVuKtZTP5xmTBl32lz5/mKGAAAAAAAAAAD/////AAAAAAdvdXRwdXRzAAgB +kDtkcHmEjxsmUyrkzsamiUSgU1i48IHLJrO7+C2eO/MAAAAAAAAAAP////8AAAAA +CGxvY2tUaW1lATXaHRU5IG673dykwz2HMerym6fadN89yIIgHE4WtbkcBFR4SW4G +BApwcmV2T3V0cHV0AehqQM1cJfm94oT/aaURMqdBKyFVvQ5WEsG/44SVYMUGCXNp +Z1NjcmlwdAE4dQSxS3wORm1HnhdHfSR0JH/4A2TsPUuq9zog90F0awhzZXF1ZW5j +ZQEBGW2FKcj22kRNFU6NnIy9ng+NiQJaO7CRIcY9UrAehwd3aXRuZXNzAXN3Q3A2 +kyBJzSiVCKpxfOOCnbJFLlXoTtT8LjzNLgCdBVR4T3V0BgIFdmFsdWUBl/XXBkKu +KjOSJTuoTh3OxJPjvz7TcbGHc4Y1TsyIgmsMc2NyaXB0UHVia2V5Ab78HvxmpRn9 +ZFJqOhOHQOfxEC0Lvv86wUZO8/dAdnRcBVR4VmVyBQEAAEQEVHhpZAUBAAcAAEAg +AAZWQnl0ZXMFAQAABAZWYXJJbnQFAQAACARWb3V0BQEAAAQLV2VpZ2h0VW5pdHMF AQAABAdXaXRuZXNzBQEACAAIAABAAAAAAAAAAAD/////AAAAAAAAAAAAAAAA//// -/wAAAAA= +/wAAAAAOV2l0bmVzc1Byb2dyYW0GAgd2ZXJzaW9uAdHs2nZn5ELtTRJppmcDNuX+ +9DevXs4raa66DEZWxPqtB3Byb2dyYW0ACAAAQAIAAAAAAAAAKAAAAAAAAAANV2l0 +bmVzc1NjcmlwdAUBASWr9bkSFBe6oznUX3sVdadxS+F6dRhd0DE1etTJLemGCldp +dG5lc3NWZXIDEQJ2MAACdjFRAnYyUgJ2M1MCdjRUAnY1VQJ2NlYCdjdXAnY4WAJ2 +OVkDdjEwWgN2MTFbA3YxMlwDdjEzXQN2MTReA3YxNV8DdjE2YAVXdHhpZAUBAAcA +AEAgAA== -----END STRICT TYPE LIB----- diff --git a/stl/Bitcoin@0.1.0.stl b/stl/Bitcoin@0.1.0.stl index c6c7e8d6..4c105541 100644 Binary files a/stl/Bitcoin@0.1.0.stl and b/stl/Bitcoin@0.1.0.stl differ diff --git a/stl/Bitcoin@0.1.0.sty b/stl/Bitcoin@0.1.0.sty index deed2a6e..984f907c 100644 --- a/stl/Bitcoin@0.1.0.sty +++ b/stl/Bitcoin@0.1.0.sty @@ -1,8 +1,8 @@ {- - Id: urn:ubideco:stl:6GgF7biXPVNcus2FfQj2pQuRzau11rXApMQLfCZhojgi#money-pardon-parody + Id: urn:ubideco:stl:D42LxJBQokrGJzvoSV3E1HoriGgLzPcxuL61JymwjEqV#arena-complex-husband Name: Bitcoin Version: 0.1.0 - Description: Consensus layer for bitcoin protocol + Description: Consensus library for bitcoin protocol Author: Dr Maxim Orlovsky Copyright (C) 2023 LNP/BP Standards Association. All rights reserved. License: Apache-2.0 @@ -10,12 +10,61 @@ typelib Bitcoin --- no dependencies +import urn:ubideco:stl:9KALDYR8Nyjq4FdMW6kYoL7vdkWnqPqNuFnmE9qHpNjZ#justice-rocket-type as Std +-- Imports: +-- Bool := urn:ubideco:semid:7ZhBHGSJm9ixmm8Z9vCX7i5Ga7j5xrW8t11nsb1Cgpnx#laser-madam-maxwell + + +-- urn:ubideco:semid:8NDnad1DezNcQSjq85QMxnDhsBbLwxLCFUfMHEJpnFnH#politic-cactus-salon +data Bip340Sig :: sig [Byte ^ 64], sighash_type SighashType? +-- urn:ubideco:semid:FWCcWgBQCGQw8FX5Z4P3etje6e92qWpVPgXBonNrTM7C#canada-sharp-traffic +data BlockHash :: [Byte ^ 32] +-- urn:ubideco:semid:HHzmwexZQY4G1YKWB6mWwk7rJW8KKb1CaeuRw8xHV1tm#rose-apropos-join +data BlockHeader :: version I32 + , prevBlockHash BlockHash + , merkleRoot [Byte ^ 32] + , time U32 + , bits U32 + , nonce U32 +-- urn:ubideco:semid:EDr6wurh4X1tMNGqA7mRkyrAY6ngPGyRSEufTGSUtySJ#kinetic-avenue-escort +data ByteStr :: [Byte ^ ..0xffffffff] +-- urn:ubideco:semid:6aRP3odHaTGySvSWHjreC8HsbX5ss9LxkQqwcjaoxhpv#aspirin-brown-alpine +data Chain :: bitcoin:0 | regtest:128 | testnet3:131 | signet:132 + +-- urn:ubideco:semid:BPzqzv3DN65MTwzbTXJbHFyiKeYvmX1VExcqvk5FUb5c#nuclear-coral-gilbert +data ControlBlock :: leafVersion LeafVer + , outputKeyParity Parity + , internalPk InternalPk + , merkleBranch TapMerklePath +-- urn:ubideco:semid:CvDS9EgqtBkWLvADynNeR7VGwVAy14EXViKnLaBkqtac#student-formula-circus +data FutureLeafVer :: U8 +-- urn:ubideco:semid:G5HFVaWwWNYSzqk548JgGZ8WKy6dQ2ftVgkJvHjgRudZ#horse-major-vienna +data InternalPk :: [Byte ^ 32] +-- urn:ubideco:semid:Birr99aCTGqzwnuBou79KNhyLLBTNQvoTAkTbMu7C4y5#anvil-arctic-cloud +data LeafScript :: version LeafVer, script ScriptBytes +-- urn:ubideco:semid:DGELfUvcU62GNQRo7HaMbKDzYQwdYRMW3b91JHd4d3WY#tunnel-lagoon-cowboy +data LeafVer :: U8 +-- urn:ubideco:semid:89ux18kFuT9nT6LEWnvkpFBiWR9GJwBYVVBqFUPvrTuj#gondola-middle-style +data LegacySig :: sig [Byte ^ ..0xff], sighash_type SighashType -- urn:ubideco:semid:4dDWWU4afiPN3q4AgCMuFRFhL4UDta2u5SrqrBzPvjby#tokyo-inch-program data LockTime :: 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 + | dup:118 | equal:135 | equalVerify:136 | ripemd160:166 + | sha1:167 | sha256:168 | hash160:169 | hash256:170 + | checkSig:172 | checkSigVerify:173 + -- urn:ubideco:semid:FWt2MSo8A4nsYgYbuBqMRNLiKgtzvLBgUn774iKzTcuf#pocket-pegasus-frank data Outpoint :: txid Txid, vout Vout +-- urn:ubideco:semid:BA7caGkXGi42re8gf15M9awB9BwFrnHzxHpB9gMUpDQK#regular-vision-origin +data OutputPk :: [Byte ^ 32] +-- urn:ubideco:semid:AgJ5n58hrH761B4MV7giZ1FhMipaDrUmnFYCLno74HDy#method-editor-echo +data Parity :: even:0 | odd:1 + +-- urn:ubideco:semid:85m9Qv56neQKaqiPSZ8G8NPWz5DDeJeeXhBwnUpcZGug#delta-jumbo-clone +data RedeemScript :: ScriptBytes -- urn:ubideco:semid:BEBz6h7AGjYSDRCxVHnjYkkkxzBsjN3EvyNiD4ZrzmRL#pyramid-spray-star data Sats :: U64 -- urn:ubideco:semid:3Y4AgjkFbDusgo3YqRDWv9BznDeAJEUDEPeEq1mpSkAR#maestro-source-jackson @@ -26,6 +75,25 @@ data ScriptPubkey :: ScriptBytes data SeqNo :: U32 -- urn:ubideco:semid:2gTMqAC393rBSGtBhDn8sJq3F3HtDosbqKDQTw9bHFyT#prelude-analyze-think data SigScript :: ScriptBytes +-- urn:ubideco:semid:JBNAWau3wDWa66fxnSyRJLkcUXSovZLxQJxERoUMNCbD#prefix-pioneer-remark +data SighashFlag :: all:1 | none:2 | single:3 + +-- urn:ubideco:semid:6mjKj9hdk5HzS8XR6SwsyAvzzaEfXmuXqmLuWq2EfayQ#balsa-equator-ground +data SighashType :: flag SighashFlag, anyoneCanPay Std.Bool {- urn:ubideco:semid:7ZhBHGSJm9ixmm8Z9vCX7i5Ga7j5xrW8t11nsb1Cgpnx#laser-madam-maxwell -} +-- urn:ubideco:semid:CqisAtFLQku77z3HbB1rBe9EjuctxLqcojV8p4THe3Uj#europe-welcome-phone +data TapBranchHash :: [Byte ^ 32] +-- urn:ubideco:semid:5WjBwSuKQwoTq6eXRtXwFrAmcPP1vLpBJ94AWpUgEsPe#weekend-boris-import +data TapCode :: pushBytes32:32 | pushData1:76 | pushData2:77 | pushData4:78 + | reserved:80 | return:106 + +-- urn:ubideco:semid:FHWiTycy1JvN5CvVJ3ibyAhtXfr21t3BDNuLFnqYPQ2j#middle-jazz-basket +data TapLeafHash :: [Byte ^ 32] +-- urn:ubideco:semid:2Cy61crHEVHKfCSix5WhmS9ms6J4FFE9wCC6pLsyondh#dublin-chemist-version +data TapMerklePath :: [TapBranchHash ^ ..0x80] +-- urn:ubideco:semid:4M8xyvABKKKJseN6Pme5eKrAuusMNMXoY5s6ifsEcreC#crash-culture-jamaica +data TapNodeHash :: [Byte ^ 32] +-- urn:ubideco:semid:71AxyLFsoRG6hJ1c11gxad65nEbWfzkQBjWCPPrgCyjX#telecom-quest-helium +data TapScript :: ScriptBytes -- urn:ubideco:semid:DynChojW1sfr8VjSoZbmReHhZoU8u9KCiuwijgEGdToe#milk-gloria-prize data Tx :: version TxVer , inputs [TxIn ^ ..0xffffffff] @@ -42,8 +110,27 @@ data TxOut :: value Sats, scriptPubkey ScriptPubkey data TxVer :: I32 -- urn:ubideco:semid:C1GfCrG7AXu2sFhRBspd7KpJK2YgyTkVy6pty5rZynRs#cowboy-diego-betty data Txid :: [Byte ^ 32] +-- urn:ubideco:semid:8ALgmuRBL8YRNWmcFoPdBFCsZwCvDjupXgdq9DjFVnkV#waiter-salad-casino +data VBytes :: U32 +-- urn:ubideco:semid:AZqcQMyFBmixEkiDtAqGxB4nM8i9hmxV3mk4vUgNAkWT#conan-avalon-food +data VarInt :: U64 -- urn:ubideco:semid:3HHRtSJW5fnGkdVW1EVDH7B97Y79WhwvKyyfsaBkuQkk#chrome-robin-gallop data Vout :: U32 +-- urn:ubideco:semid:EcY6NU6BVRVkgCFwfWmSkbVKpLmWWVfDwBRRiSr6FJUC#bridge-version-voyage +data WeightUnits :: U32 -- urn:ubideco:semid:8mjN2CZj3Nhn2HjnKqTmEcN5vmyb3UJK8HSFW1uE3W2p#warning-saddle-period data Witness :: [[Byte ^ ..0xffffffff] ^ ..0xffffffff] +-- urn:ubideco:semid:FK7h8D8X6BTAuh8q4NdkdAGgF9vwAZRyYcUpFzR3fcDG#scholar-juice-titanic +data WitnessProgram :: version WitnessVer, program [Byte ^ 2..0x28] +-- urn:ubideco:semid:Bd2bXQc2D6YmMFpupKnFGvjBiZNcGKrhaxS9omUUedcS#boris-harbor-equal +data WitnessScript :: ScriptBytes +-- urn:ubideco:semid:F8Tgd8G4vQdBtiF8nnC46tANuaBcp9jnkaSeYxjREwEU#richard-average-unit +data WitnessVer :: v0:0 | v1:81 | v2:82 | v3:83 + | v4:84 | v5:85 | v6:86 | v7:87 + | v8:88 | v9:89 | v10:90 | v11:91 + | v12:92 | v13:93 | v14:94 | v15:95 + | v16:96 + +-- urn:ubideco:semid:HZbMnxQ1p2duNgYuYrQn9xtHBwSom63zWtRwy6pmBGfU#sushi-polygon-circus +data Wtxid :: [Byte ^ 32] diff --git a/stl/Tx@0.1.0.sta b/stl/Tx@0.1.0.sta new file mode 100644 index 00000000..167f7b48 --- /dev/null +++ b/stl/Tx@0.1.0.sta @@ -0,0 +1,26 @@ +-----BEGIN STRICT TYPE LIB----- +Id: urn:ubideco:stl:5PUqZnqASPU3zKASh2pTHYmfcV4pZfvt4UU8FxPsRt6S +Name: Tx +Dependencies: ~ + +AlR4AAAOAAhMb2NrVGltZQUBAAAECE91dHBvaW50BgIEdHhpZAGjgkLzy9fR0KES +2o3hYC9W1PhvDsTEdsXAaFlMSwRlVgR2b3V0ASHjPkPFqlzyKSdTozjBZ+07Y5xN +2c69qY80aRe6yUN1BFNhdHMFAQAACAtTY3JpcHRCeXRlcwUBAAgAAEAAAAAAAAAA +AP////8AAAAADFNjcmlwdFB1YmtleQUBASWr9bkSFBe6oznUX3sVdadxS+F6dRhd +0DE1etTJLemGBVNlcU5vBQEAAAQJU2lnU2NyaXB0BQEBJav1uRIUF7qjOdRfexV1 +p3FL4Xp1GF3QMTV61Mkt6YYCVHgGBAd2ZXJzaW9uAah8xnlkZ+VX10TlyWI64AzL +ldkaDS8D33TAdRJPvseeBmlucHV0cwAIARlHLRfYYgenDyExqVihaKVuKtZTP5xm +TBl32lz5/mKGAAAAAAAAAAD/////AAAAAAdvdXRwdXRzAAgBkDtkcHmEjxsmUyrk +zsamiUSgU1i48IHLJrO7+C2eO/MAAAAAAAAAAP////8AAAAACGxvY2tUaW1lATXa +HRU5IG673dykwz2HMerym6fadN89yIIgHE4WtbkcBFR4SW4GBApwcmV2T3V0cHV0 +AehqQM1cJfm94oT/aaURMqdBKyFVvQ5WEsG/44SVYMUGCXNpZ1NjcmlwdAE4dQSx +S3wORm1HnhdHfSR0JH/4A2TsPUuq9zog90F0awhzZXF1ZW5jZQEBGW2FKcj22kRN +FU6NnIy9ng+NiQJaO7CRIcY9UrAehwd3aXRuZXNzAXN3Q3A2kyBJzSiVCKpxfOOC +nbJFLlXoTtT8LjzNLgCdBVR4T3V0BgIFdmFsdWUBl/XXBkKuKjOSJTuoTh3OxJPj +vz7TcbGHc4Y1TsyIgmsMc2NyaXB0UHVia2V5Ab78HvxmpRn9ZFJqOhOHQOfxEC0L +vv86wUZO8/dAdnRcBVR4VmVyBQEAAEQEVHhpZAUBAAcAAEAgAARWb3V0BQEAAAQH +V2l0bmVzcwUBAAgACAAAQAAAAAAAAAAA/////wAAAAAAAAAAAAAAAP////8AAAAA + + +-----END STRICT TYPE LIB----- + diff --git a/stl/Tx@0.1.0.stl b/stl/Tx@0.1.0.stl new file mode 100644 index 00000000..79ac39a8 Binary files /dev/null and b/stl/Tx@0.1.0.stl differ diff --git a/stl/Tx@0.1.0.sty b/stl/Tx@0.1.0.sty new file mode 100644 index 00000000..b5859e11 --- /dev/null +++ b/stl/Tx@0.1.0.sty @@ -0,0 +1,49 @@ +{- + Id: urn:ubideco:stl:5PUqZnqASPU3zKASh2pTHYmfcV4pZfvt4UU8FxPsRt6S#winter-horizon-binary + Name: Tx + Version: 0.1.0 + Description: Bitcoin transaction library + Author: Dr Maxim Orlovsky + Copyright (C) 2023 LNP/BP Standards Association. All rights reserved. + License: Apache-2.0 +-} + +typelib Tx + +-- no dependencies + +-- urn:ubideco:semid:4dDWWU4afiPN3q4AgCMuFRFhL4UDta2u5SrqrBzPvjby#tokyo-inch-program +data LockTime :: U32 +-- urn:ubideco:semid:FWt2MSo8A4nsYgYbuBqMRNLiKgtzvLBgUn774iKzTcuf#pocket-pegasus-frank +data Outpoint :: txid Txid, vout Vout +-- urn:ubideco:semid:BEBz6h7AGjYSDRCxVHnjYkkkxzBsjN3EvyNiD4ZrzmRL#pyramid-spray-star +data Sats :: U64 +-- urn:ubideco:semid:3Y4AgjkFbDusgo3YqRDWv9BznDeAJEUDEPeEq1mpSkAR#maestro-source-jackson +data ScriptBytes :: [Byte ^ ..0xffffffff] +-- urn:ubideco:semid:2ZAYpWKB2BQxeXXjpQDpYGZ7eXFM9qQxN9TcdTiQqeB8#bingo-maestro-silk +data ScriptPubkey :: ScriptBytes +-- urn:ubideco:semid:5HtymNhYBhjqPkLLw9QVWZ62cLm57cZxgQTDUBBXtmL#rhino-time-rodent +data SeqNo :: U32 +-- urn:ubideco:semid:2gTMqAC393rBSGtBhDn8sJq3F3HtDosbqKDQTw9bHFyT#prelude-analyze-think +data SigScript :: ScriptBytes +-- urn:ubideco:semid:DynChojW1sfr8VjSoZbmReHhZoU8u9KCiuwijgEGdToe#milk-gloria-prize +data Tx :: version TxVer + , inputs [TxIn ^ ..0xffffffff] + , outputs [TxOut ^ ..0xffffffff] + , lockTime LockTime +-- urn:ubideco:semid:9Nf4Vvt3im8tFQSGzPWKfjfhsrkB8bf2XsLWfzywiFSv#antenna-crater-planet +data TxIn :: prevOutput Outpoint + , sigScript SigScript + , sequence SeqNo + , witness Witness +-- urn:ubideco:semid:HutVbeKmYYrNun96Pi4T7YfYww7SeWxRFPZGDiZwoGZV#design-jacket-spirit +data TxOut :: value Sats, scriptPubkey ScriptPubkey +-- urn:ubideco:semid:CLhr1zatQBSkCz9SiVrNoKB5igCZfF3hqRizfrviM6NR#english-natasha-virus +data TxVer :: I32 +-- urn:ubideco:semid:C1GfCrG7AXu2sFhRBspd7KpJK2YgyTkVy6pty5rZynRs#cowboy-diego-betty +data Txid :: [Byte ^ 32] +-- urn:ubideco:semid:3HHRtSJW5fnGkdVW1EVDH7B97Y79WhwvKyyfsaBkuQkk#chrome-robin-gallop +data Vout :: U32 +-- urn:ubideco:semid:8mjN2CZj3Nhn2HjnKqTmEcN5vmyb3UJK8HSFW1uE3W2p#warning-saddle-period +data Witness :: [[Byte ^ ..0xffffffff] ^ ..0xffffffff] +