diff --git a/Cargo.toml b/Cargo.toml index af3c0e07..182a431a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ single_use_seals = "0.11.0-beta.6" bp-consensus = { version = "0.11.0-beta.6", path = "consensus" } bp-dbc = { version = "0.11.0-beta.6", path = "./dbc" } bp-seals = { version = "0.11.0-beta.6", path = "./seals" } -secp256k1 = { version = "0.29.0", features = ["global-context"] } +secp256k1 = { version = "0.29.0", features = ["global-context", "rand-std"] } serde_crate = { package = "serde", version = "1", features = ["derive"] } [package] diff --git a/consensus/src/coding.rs b/consensus/src/coding.rs index 0744d561..c301ff48 100644 --- a/consensus/src/coding.rs +++ b/consensus/src/coding.rs @@ -25,10 +25,10 @@ use amplify::confinement::{Confined, MediumBlob, SmallBlob, TinyBlob, U32}; use amplify::{confinement, ByteArray, Bytes32, IoError, Wrapper}; use crate::{ - BlockHash, BlockHeader, BlockMerkleRoot, 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, + Annex, BlockHash, BlockHeader, BlockMerkleRoot, ControlBlock, InternalPk, InvalidLeafVer, + LeafVer, LockTime, Outpoint, Parity, RedeemScript, Sats, ScriptBytes, ScriptPubkey, SeqNo, + SigScript, Sighash, TapBranchHash, TapLeafHash, TapMerklePath, TapScript, Tx, TxIn, TxOut, + TxVer, Txid, Vout, Witness, WitnessScript, LIB_NAME_BITCOIN, TAPROOT_ANNEX_PREFIX, }; /// Bitcoin consensus allows arrays which length is encoded as VarInt to grow up @@ -36,9 +36,9 @@ use crate::{ /// block data structure to exceed 2^32 bytes (4GB), and any change to that rule /// will be a hardfork. So for practical reasons we are safe to restrict the /// maximum size here with just 32 bits. -pub type VarIntArray = Confined, 0, U32>; +pub type VarIntArray = Confined, MIN_LEN, U32>; -pub type VarIntBytes = Confined, 0, U32>; +pub type VarIntBytes = Confined, MIN_LEN, U32>; /// A variable-length unsigned integer. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Default)] @@ -82,7 +82,7 @@ pub trait LenVarInt { fn len_var_int(&self) -> VarInt; } -impl LenVarInt for VarIntArray { +impl LenVarInt for VarIntArray { fn len_var_int(&self) -> VarInt { VarInt::with(self.len()) } } @@ -190,6 +190,9 @@ pub enum ConsensusDataError { #[display(inner)] InvalidLeafVer(InvalidLeafVer), + /// invalid first annex byte `{0:#02x}`, which must be `0x50`. + WrongAnnexFirstByte(u8), + #[from] #[display(inner)] Confined(confinement::Error), @@ -459,7 +462,7 @@ impl ConsensusDecode for LockTime { impl ConsensusEncode for ScriptBytes { fn consensus_encode(&self, writer: &mut impl Write) -> Result { - self.as_var_int_array().consensus_encode(writer) + self.as_var_int_bytes().consensus_encode(writer) } } @@ -529,6 +532,22 @@ impl ConsensusDecode for SigScript { } } +impl ConsensusEncode for Annex { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + self.as_var_int_bytes().consensus_encode(writer) + } +} + +impl ConsensusDecode for Annex { + fn consensus_decode(reader: &mut impl Read) -> Result { + let bytes = VarIntBytes::<1>::consensus_decode(reader)?; + if bytes[0] != TAPROOT_ANNEX_PREFIX { + return Err(ConsensusDataError::WrongAnnexFirstByte(bytes[0]).into()); + } + Ok(Self::from(bytes)) + } +} + impl ConsensusEncode for Witness { fn consensus_encode(&self, writer: &mut impl Write) -> Result { self.as_var_int_array().consensus_encode(writer) @@ -628,6 +647,18 @@ impl ConsensusDecode for Sats { } } +impl ConsensusEncode for Sighash { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + self.to_byte_array().consensus_encode(writer) + } +} + +impl ConsensusEncode for TapLeafHash { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + self.to_byte_array().consensus_encode(writer) + } +} + impl ConsensusEncode for VarInt { fn consensus_encode(&self, writer: &mut impl Write) -> Result { match self.0 { @@ -699,7 +730,7 @@ impl ConsensusDecode for ByteStr { } } -impl ConsensusEncode for VarIntArray { +impl ConsensusEncode for VarIntArray { fn consensus_encode(&self, writer: &mut impl Write) -> Result { let mut counter = self.len_var_int().consensus_encode(writer)?; for item in self { @@ -709,14 +740,14 @@ impl ConsensusEncode for VarIntArray { } } -impl ConsensusDecode for VarIntArray { +impl ConsensusDecode for VarIntArray { fn consensus_decode(reader: &mut impl Read) -> Result { let len = VarInt::consensus_decode(reader)?; let mut arr = Vec::new(); for _ in 0..len.0 { arr.push(T::consensus_decode(reader)?); } - VarIntArray::try_from(arr).map_err(ConsensusDecodeError::from) + VarIntArray::::try_from(arr).map_err(ConsensusDecodeError::from) } } @@ -795,6 +826,20 @@ impl ConsensusDecode for u64 { } } +impl ConsensusEncode for Bytes32 { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + writer.write_all(&self.to_byte_array())?; + Ok(8) + } +} + +impl ConsensusEncode for [u8; 32] { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + writer.write_all(self)?; + Ok(8) + } +} + impl ConsensusDecode for [u8; 32] { fn consensus_decode(reader: &mut impl Read) -> Result { let mut buf = [0u8; 32]; diff --git a/consensus/src/lib.rs b/consensus/src/lib.rs index 9492205b..e48ab53e 100644 --- a/consensus/src/lib.rs +++ b/consensus/src/lib.rs @@ -65,6 +65,7 @@ mod weights; #[cfg(feature = "stl")] pub mod stl; mod coding; +mod sigcache; pub use block::{BlockHash, BlockHeader, BlockMerkleRoot}; pub use coding::{ @@ -76,12 +77,13 @@ pub use opcodes::OpCode; pub use pubkeys::{CompressedPk, InvalidPubkey, LegacyPk, PubkeyParseError, UncompressedPk}; pub use script::{RedeemScript, ScriptBytes, ScriptPubkey, SigScript}; pub use segwit::{SegwitError, Witness, WitnessProgram, WitnessScript, WitnessVer, Wtxid}; -pub use sigtypes::{Bip340Sig, LegacySig, SigError, SighashFlag, SighashType}; +pub use sigcache::{PrevoutMismatch, SighashCache, SighashError}; +pub use sigtypes::{Bip340Sig, LegacySig, ScriptCode, SigError, Sighash, SighashFlag, SighashType}; pub use taproot::{ - ControlBlock, FutureLeafVer, InternalPk, IntoTapHash, InvalidLeafVer, InvalidParityValue, - LeafScript, LeafVer, OutputPk, Parity, TapBranchHash, TapCode, TapLeafHash, TapMerklePath, - TapNodeHash, TapScript, XOnlyPk, MIDSTATE_TAPSIGHASH, TAPROOT_ANNEX_PREFIX, TAPROOT_LEAF_MASK, - TAPROOT_LEAF_TAPSCRIPT, + Annex, AnnexError, ControlBlock, FutureLeafVer, InternalKeypair, InternalPk, IntoTapHash, + InvalidLeafVer, InvalidParityValue, LeafScript, LeafVer, OutputPk, Parity, TapBranchHash, + TapCode, TapLeafHash, TapMerklePath, TapNodeHash, TapScript, TapSighash, XOnlyPk, + MIDSTATE_TAPSIGHASH, TAPROOT_ANNEX_PREFIX, TAPROOT_LEAF_MASK, TAPROOT_LEAF_TAPSCRIPT, }; pub use timelocks::{ InvalidTimelock, LockHeight, LockTime, LockTimestamp, SeqNo, TimelockParseError, diff --git a/consensus/src/script.rs b/consensus/src/script.rs index 4177d446..179f97a9 100644 --- a/consensus/src/script.rs +++ b/consensus/src/script.rs @@ -23,7 +23,7 @@ use amplify::confinement; use amplify::confinement::Confined; use crate::opcodes::*; -use crate::{ScriptHash, VarInt, VarIntArray, VarIntBytes, LIB_NAME_BITCOIN}; +use crate::{ScriptHash, VarInt, VarIntBytes, WitnessVer, LIB_NAME_BITCOIN}; #[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Default)] #[wrapper(Deref, AsSlice, Hex)] @@ -193,6 +193,29 @@ impl RedeemScript { Self(ScriptBytes::from_unsafe(script_bytes)) } + pub fn p2sh_wpkh(hash: impl Into<[u8; 20]>) -> Self { + Self::with_witness_program_unchecked(WitnessVer::V0, &hash.into()) + } + + pub fn p2sh_wsh(hash: impl Into<[u8; 32]>) -> Self { + Self::with_witness_program_unchecked(WitnessVer::V0, &hash.into()) + } + + fn with_witness_program_unchecked(ver: WitnessVer, prog: &[u8]) -> Self { + let mut script = Self::with_capacity(ScriptBytes::len_for_slice(prog.len()) + 2); + script.push_opcode(ver.op_code()); + script.push_slice(prog); + script + } + + pub fn is_p2sh_wpkh(&self) -> bool { + self.len() == 22 && self[0] == WitnessVer::V0.op_code() as u8 && self[1] == OP_PUSHBYTES_20 + } + + pub fn is_p2sh_wsh(&self) -> bool { + self.len() == 34 && self[0] == WitnessVer::V0.op_code() as u8 && self[1] == OP_PUSHBYTES_32 + } + /// Adds a single opcode to the script. #[inline] pub fn push_opcode(&mut self, op_code: OpCode) { self.0.push(op_code as u8); } @@ -285,7 +308,7 @@ impl ScriptBytes { pub fn into_vec(self) -> Vec { self.0.into_inner() } - pub(crate) fn as_var_int_array(&self) -> &VarIntArray { &self.0 } + pub(crate) fn as_var_int_bytes(&self) -> &VarIntBytes { &self.0 } } #[cfg(feature = "serde")] diff --git a/consensus/src/sigcache.rs b/consensus/src/sigcache.rs new file mode 100644 index 00000000..8d0a4e0b --- /dev/null +++ b/consensus/src/sigcache.rs @@ -0,0 +1,522 @@ +// Bitcoin protocol consensus library. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2024 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2019-2024 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::borrow::Borrow; + +use amplify::{Bytes32, IoError}; +use commit_verify::{Digest, DigestExt, Sha256}; + +use crate::{ + Annex, ConsensusEncode, Sats, ScriptCode, ScriptPubkey, SeqNo, SigScript, Sighash, SighashFlag, + SighashType, TapLeafHash, TapSighash, Tx as Transaction, TxIn, TxOut, Txid, VarIntArray, +}; + +/// Used for signature hash for invalid use of SIGHASH_SINGLE. +const UINT256_ONE: [u8; 32] = [ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)] +#[display( + "number of inputs ({inputs}) doesn't match to the number of provided prevouts ({prevouts}) \ + for signature hasher on tx {txid}." +)] +pub struct PrevoutMismatch { + txid: Txid, + inputs: usize, + prevouts: usize, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)] +#[display(doc_comments)] +pub enum SighashError { + /// invalid input index {index} in {txid} which has only {inputs} inputs. + InvalidInputIndex { + txid: Txid, + index: usize, + inputs: usize, + }, + + /// transaction {txid} input {index} uses SIGHASH_SINGLE, but the total + /// number of outputs is {outputs} and thus no signature can be produced. + NoSingleOutputMatch { + txid: Txid, + index: usize, + outputs: usize, + }, +} + +impl From for SighashError { + fn from(_: IoError) -> Self { unreachable!("in-memory I/O doesn't error in Rust") } +} + +/// Efficiently calculates signature hash message for legacy, segwit and taproot +/// inputs. +#[derive(Debug)] +pub struct SighashCache = TxOut, Tx: Borrow = Transaction> { + /// Access to transaction required for transaction introspection. + tx: Tx, + + prevouts: Vec, + + /// Common cache for taproot and segwit inputs, `None` for legacy inputs. + common_cache: Option, + + /// Cache for segwit v0 inputs (the result of another round of sha256 on + /// `common_cache`). + segwit_cache: Option, + + /// Cache for taproot v1 inputs. + taproot_cache: Option, +} + +/// Common values cached between segwit and taproot inputs. +#[derive(Copy, Clone, Debug)] +struct CommonCache { + prevouts: Bytes32, + sequences: Bytes32, + + /// In theory `outputs` could be an `Option` since `SIGHASH_NONE` and + /// `SIGHASH_SINGLE` do not need it, but since `SIGHASH_ALL` is by far + /// the most used variant we don't bother. + outputs: Bytes32, +} + +/// Values cached for segwit inputs, equivalent to [`CommonCache`] plus another +/// round of `sha256`. +#[derive(Copy, Clone, Debug)] +struct SegwitCache { + prevouts: Bytes32, + sequences: Bytes32, + outputs: Bytes32, +} + +/// Values cached for taproot inputs. +#[derive(Copy, Clone, Debug)] +struct TaprootCache { + amounts: Bytes32, + script_pubkeys: Bytes32, +} + +impl, Tx: Borrow> SighashCache { + /// Constructs a new `SighashCache` from an unsigned transaction. + /// + /// The sighash components are computed in a lazy manner when required. For + /// the generated sighashes to be valid, no fields in the transaction + /// may change except for script_sig and witness. + pub fn new(tx: Tx, prevouts: Vec) -> Result { + if tx.borrow().inputs.len() != prevouts.len() { + return Err(PrevoutMismatch { + txid: tx.borrow().txid(), + inputs: tx.borrow().inputs.len(), + prevouts: prevouts.len(), + }); + } + + Ok(SighashCache { + tx, + prevouts, + common_cache: None, + taproot_cache: None, + segwit_cache: None, + }) + } + + /// Computes the BIP341 sighash for any type with a fine-grained control + /// over annex and code separator. + pub fn tap_sighash_custom( + &mut self, + input_index: usize, + annex: Option, + leaf_hash_code_separator: Option<(TapLeafHash, u32)>, + sighash_type: Option, + ) -> Result { + let mut hasher = TapSighash::engine(); + + let SighashType { + flag: sighash_flag, + anyone_can_pay, + } = sighash_type.unwrap_or_default(); + + // epoch + 0u8.consensus_encode(&mut hasher)?; + + // * Control: + // hash_type (1). + match sighash_type { + None => 0u8.consensus_encode(&mut hasher)?, + Some(sighash_type) => sighash_type + .to_consensus_u8() + .consensus_encode(&mut hasher)?, + }; + + { + let tx = self.tx.borrow(); + // * Transaction Data: + // nVersion (4): the nVersion of the transaction. + tx.version.consensus_encode(&mut hasher)?; + + // nLockTime (4): the nLockTime of the transaction. + tx.lock_time.consensus_encode(&mut hasher)?; + } + + // If the hash_type & 0x80 does not equal SIGHASH_ANYONECANPAY: + // sha_prevouts (32): the SHA256 of the serialization of all input + // outpoints. sha_amounts (32): the SHA256 of the serialization of + // all spent output amounts. sha_scriptpubkeys (32): the SHA256 of + // the serialization of all spent output scriptPubKeys. + // sha_sequences (32): the SHA256 of the serialization of all + // input nSequence. + if !anyone_can_pay { + self.common_cache().prevouts.consensus_encode(&mut hasher)?; + self.taproot_cache().amounts.consensus_encode(&mut hasher)?; + self.taproot_cache() + .script_pubkeys + .consensus_encode(&mut hasher)?; + self.common_cache() + .sequences + .consensus_encode(&mut hasher)?; + } + + // If hash_type & 3 does not equal SIGHASH_NONE or SIGHASH_SINGLE: + // sha_outputs (32): the SHA256 of the serialization of all outputs in + // CTxOut format. + if sighash_flag != SighashFlag::None && sighash_flag != SighashFlag::Single { + self.common_cache().outputs.consensus_encode(&mut hasher)?; + } + + // * Data about this input: + // spend_type (1): equal to (ext_flag * 2) + annex_present, where annex_present + // is 0 if no annex is present, or 1 otherwise + let mut spend_type = 0u8; + if annex.is_some() { + spend_type |= 1u8; + } + if leaf_hash_code_separator.is_some() { + spend_type |= 2u8; + } + spend_type.consensus_encode(&mut hasher)?; + + let tx = self.tx.borrow(); + + // If hash_type & 0x80 equals SIGHASH_ANYONECANPAY: + // outpoint (36): the COutPoint of this input (32-byte hash + 4-byte + // little-endian). amount (8): value of the previous output spent + // by this input. scriptPubKey (35): scriptPubKey of the previous + // output spent by this input, serialized as script inside CTxOut. Its + // size is always 35 bytes. nSequence (4): nSequence of this input. + if anyone_can_pay { + let txin = tx + .inputs + .get(input_index) + .ok_or(SighashError::InvalidInputIndex { + txid: tx.txid(), + index: input_index, + inputs: tx.inputs.len(), + })?; + let previous_output = self.prevouts[input_index].borrow(); + txin.prev_output.consensus_encode(&mut hasher)?; + previous_output.value.consensus_encode(&mut hasher)?; + previous_output + .script_pubkey + .consensus_encode(&mut hasher)?; + txin.sequence.consensus_encode(&mut hasher)?; + } else { + (input_index as u32).consensus_encode(&mut hasher)?; + } + + // If an annex is present (the lowest bit of spend_type is set): + // sha_annex (32): the SHA256 of (compact_size(size of annex) || annex), + // where annex includes the mandatory 0x50 prefix. + if let Some(annex) = annex { + let mut enc = Sha256::default(); + annex.consensus_encode(&mut enc)?; + let hash = enc.finish(); + hash.consensus_encode(&mut hasher)?; + } + + // * Data about this output: + // If hash_type & 3 equals SIGHASH_SINGLE: + // sha_single_output (32): the SHA256 of the corresponding output in CTxOut + // format. + if sighash_flag == SighashFlag::Single { + let mut enc = Sha256::default(); + tx.outputs + .get(input_index) + .ok_or(SighashError::NoSingleOutputMatch { + txid: tx.txid(), + index: input_index, + outputs: tx.outputs.len(), + })? + .consensus_encode(&mut enc)?; + let hash = enc.finish(); + hash.consensus_encode(&mut hasher)?; + } + + // if (scriptpath): + // ss += TaggedHash("TapLeaf", bytes([leaf_ver]) + ser_string(script)) + // ss += bytes([0]) + // ss += struct.pack(", + ) -> Result { + self.tap_sighash_custom(input_index, None, None, sighash_type) + } + + /// Computes the BIP341 sighash for a script spend. + /// + /// Assumes the default `OP_CODESEPARATOR` position of `0xFFFFFFFF`. + pub fn tap_sighash_script( + &mut self, + input_index: usize, + leaf_hash: impl Into, + sighash_type: Option, + ) -> Result { + self.tap_sighash_custom( + input_index, + None, + Some((leaf_hash.into(), 0xFFFFFFFF)), + sighash_type, + ) + } + + /// Computes the BIP143 sighash for any flag type. + pub fn segwit_sighash( + &mut self, + input_index: usize, + script_code: &ScriptCode, + value: Sats, + sighash_type: SighashType, + ) -> Result { + let mut hasher = Sighash::engine(); + + let zero_hash = [0u8; 32]; + + let SighashType { + flag: sighash_flag, + anyone_can_pay, + } = sighash_type; + + self.tx.borrow().version.consensus_encode(&mut hasher)?; + + if !anyone_can_pay { + self.segwit_cache().prevouts.consensus_encode(&mut hasher)?; + } else { + zero_hash.consensus_encode(&mut hasher)?; + } + + if !anyone_can_pay && + sighash_flag != SighashFlag::Single && + sighash_flag != SighashFlag::None + { + self.segwit_cache() + .sequences + .consensus_encode(&mut hasher)?; + } else { + zero_hash.consensus_encode(&mut hasher)?; + } + + { + let tx = self.tx.borrow(); + let txin = tx + .inputs + .get(input_index) + .ok_or(SighashError::InvalidInputIndex { + txid: tx.txid(), + index: input_index, + inputs: tx.inputs.len(), + })?; + + txin.prev_output.consensus_encode(&mut hasher)?; + script_code.consensus_encode(&mut hasher)?; + value.consensus_encode(&mut hasher)?; + txin.sequence.consensus_encode(&mut hasher)?; + } + + if sighash_flag != SighashFlag::Single && sighash_flag != SighashFlag::None { + self.segwit_cache().outputs.consensus_encode(&mut hasher)?; + } else if sighash_flag == SighashFlag::Single && + input_index < self.tx.borrow().outputs.len() + { + let mut single_enc = Sighash::engine(); + self.tx.borrow().outputs[input_index].consensus_encode(&mut single_enc)?; + Sighash::from_engine(single_enc).consensus_encode(&mut hasher)?; + } else { + zero_hash.consensus_encode(&mut hasher)?; + } + + self.tx.borrow().lock_time.consensus_encode(&mut hasher)?; + sighash_type + .to_consensus_u32() + .consensus_encode(&mut hasher)?; + + Ok(Sighash::from_engine(hasher)) + } + + /// Computes the legacy sighash for any `sighash_type`. + pub fn legacy_sighash( + &self, + input_index: usize, + script_pubkey: &ScriptPubkey, + sighash_type: u32, + ) -> Result { + let tx_src = self.tx.borrow(); + let mut hasher = Sighash::engine(); + + if input_index >= tx_src.inputs.len() { + return Err(SighashError::InvalidInputIndex { + txid: tx_src.txid(), + index: input_index, + inputs: tx_src.inputs.len(), + }); + } + + let SighashType { + flag: sighash_flag, + anyone_can_pay, + } = SighashType::from_consensus_u32(sighash_type); + + if sighash_flag == SighashFlag::Single && input_index >= tx_src.outputs.len() { + return Ok(Sighash::from(UINT256_ONE)); + } + + // Build tx to sign + let mut tx = Transaction { + version: tx_src.version, + lock_time: tx_src.lock_time, + inputs: none!(), + outputs: none!(), + }; + + // Add all necessary inputs... + let sig_script = script_pubkey.as_script_bytes().clone().into(); + if anyone_can_pay { + tx.inputs = confined_vec![TxIn { + prev_output: tx_src.inputs[input_index].prev_output, + sig_script, + sequence: tx_src.inputs[input_index].sequence, + witness: none!(), + }]; + } else { + let inputs = tx_src.inputs.iter().enumerate().map(|(n, input)| TxIn { + prev_output: input.prev_output, + sig_script: if n == input_index { + sig_script.clone() + } else { + SigScript::new() + }, + sequence: if n != input_index && + (sighash_flag == SighashFlag::Single || sighash_flag == SighashFlag::None) + { + SeqNo::ZERO + } else { + input.sequence + }, + witness: none!(), + }); + tx.inputs = VarIntArray::from_iter_unsafe(inputs); + } + // ...then all outputs + tx.outputs = match sighash_flag { + SighashFlag::All => tx_src.outputs.clone(), + SighashFlag::Single => { + let outputs = tx_src.outputs.iter() + .take(input_index + 1) // sign all outputs up to and including this one, but erase + .enumerate() // all of them except for this one + .map(|(n, out)| if n == input_index { out.clone() } else { TxOut::default() }); + VarIntArray::from_iter_unsafe(outputs) + } + SighashFlag::None => none!(), + }; + // hash the result + tx.consensus_encode(&mut hasher)?; + sighash_type.consensus_encode(&mut hasher)?; + + Ok(Sighash::from_engine(hasher)) + } + + fn common_cache(&mut self) -> &CommonCache { + let tx = self.tx.borrow(); + self.common_cache.get_or_insert_with(|| { + let mut enc_prevouts = Sha256::default(); + let mut enc_sequences = Sha256::default(); + for txin in &tx.inputs { + txin.prev_output + .consensus_encode(&mut enc_prevouts) + .unwrap(); + txin.sequence.consensus_encode(&mut enc_sequences).unwrap(); + } + let mut enc_outputs = Sha256::default(); + for txout in &tx.outputs { + let _ = txout.consensus_encode(&mut enc_outputs); + } + CommonCache { + prevouts: enc_prevouts.finish().into(), + sequences: enc_sequences.finish().into(), + outputs: enc_outputs.finish().into(), + } + }) + } + + fn segwit_cache(&mut self) -> &SegwitCache { + let common_cache = *self.common_cache(); + self.segwit_cache.get_or_insert_with(|| SegwitCache { + prevouts: <[u8; 32]>::from(Sha256::digest(common_cache.prevouts)).into(), + sequences: <[u8; 32]>::from(Sha256::digest(common_cache.sequences)).into(), + outputs: <[u8; 32]>::from(Sha256::digest(common_cache.outputs)).into(), + }) + } + + fn taproot_cache(&mut self) -> &TaprootCache { + self.taproot_cache.get_or_insert_with(|| { + let mut enc_amounts = Sha256::default(); + let mut enc_script_pubkeys = Sha256::default(); + for prevout in &self.prevouts { + prevout + .borrow() + .value + .consensus_encode(&mut enc_amounts) + .unwrap(); + prevout + .borrow() + .script_pubkey + .consensus_encode(&mut enc_script_pubkeys) + .unwrap(); + } + TaprootCache { + amounts: enc_amounts.finish().into(), + script_pubkeys: enc_script_pubkeys.finish().into(), + } + }) + } +} diff --git a/consensus/src/sigtypes.rs b/consensus/src/sigtypes.rs index be6475b7..b40f8d58 100644 --- a/consensus/src/sigtypes.rs +++ b/consensus/src/sigtypes.rs @@ -19,13 +19,16 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::fmt::{self, Display, Formatter}; use std::iter; +use amplify::{ByteArray, Bytes32, Wrapper}; +use commit_verify::{DigestExt, Sha256}; use secp256k1::{ecdsa, schnorr}; -use crate::{NonStandardValue, LIB_NAME_BITCOIN}; +use crate::{NonStandardValue, ScriptBytes, ScriptPubkey, WitnessScript, LIB_NAME_BITCOIN}; -#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Default)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Display, Default)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN, tags = repr, into_u8, try_from_u8)] #[cfg_attr( @@ -33,6 +36,7 @@ use crate::{NonStandardValue, LIB_NAME_BITCOIN}; derive(Serialize, Deserialize), serde(crate = "serde_crate", rename_all = "camelCase") )] +#[display(uppercase)] #[repr(u8)] pub enum SighashFlag { /// 0x1: Sign all outputs. @@ -43,8 +47,8 @@ pub enum SighashFlag { /// 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.) + /// (This rule is probably an unintentional C++ism, but it's consensus, so + /// we have to follow it.) Single = 0x03, } @@ -182,6 +186,78 @@ impl SighashType { } } +impl Display for SighashType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Display::fmt(&self.flag, f)?; + if self.anyone_can_pay { + f.write_str(" | ANYONECANPAY")?; + } + Ok(()) + } +} + +#[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 Sighash( + #[from] + #[from([u8; 32])] + pub Bytes32, +); + +impl From for [u8; 32] { + fn from(value: Sighash) -> Self { value.0.into_inner() } +} + +impl From for secp256k1::Message { + fn from(sighash: Sighash) -> Self { secp256k1::Message::from_digest(sighash.to_byte_array()) } +} + +impl Sighash { + pub fn engine() -> Sha256 { Sha256::default() } + + pub fn from_engine(engine: Sha256) -> Self { + let mut engine2 = Sha256::default(); + engine2.input_raw(&engine.finish()); + Self(engine2.finish().into()) + } +} + +/// Type used for generating sighash in SegWit signing +#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)] +#[wrapper(Deref, AsSlice, Hex)] +#[wrapper_mut(DerefMut, AsSliceMut)] +pub struct ScriptCode(ScriptBytes); + +impl ScriptCode { + pub fn with_p2sh_wpkh(script_pubkey: &ScriptPubkey) -> Self { Self::with_p2wpkh(script_pubkey) } + + pub fn with_p2wpkh(script_pubkey: &ScriptPubkey) -> Self { + let mut pubkey_hash = [0u8; 20]; + pubkey_hash.copy_from_slice(&script_pubkey[2..22]); + let script_code = ScriptPubkey::p2pkh(pubkey_hash); + ScriptCode(script_code.into_inner()) + } + + pub fn with_p2sh_wsh(witness_script: &WitnessScript) -> Self { + Self::with_p2wsh(witness_script) + } + + pub fn with_p2wsh(witness_script: &WitnessScript) -> Self { + // TODO: Parse instructions and check for the presence of OP_CODESEPARATOR + ScriptCode(witness_script.to_inner()) + } + + #[inline] + pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 } +} + /// An ECDSA signature-related error. #[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)] #[display(doc_comments)] diff --git a/consensus/src/taproot.rs b/consensus/src/taproot.rs index 4a5887b5..31cfa4a2 100644 --- a/consensus/src/taproot.rs +++ b/consensus/src/taproot.rs @@ -29,9 +29,9 @@ use std::{cmp, io, slice, vec}; use amplify::confinement::Confined; use amplify::hex::FromHex; -use amplify::{confinement, Bytes32, Wrapper}; +use amplify::{confinement, ByteArray, Bytes32, Wrapper}; use commit_verify::{DigestExt, Sha256}; -use secp256k1::{PublicKey, Scalar, XOnlyPublicKey}; +use secp256k1::{Keypair, PublicKey, Scalar, XOnlyPublicKey}; use strict_encoding::{ DecodeError, ReadTuple, StrictDecode, StrictEncode, StrictProduct, StrictTuple, StrictType, TypeName, TypedRead, TypedWrite, WriteTuple, @@ -40,7 +40,7 @@ use strict_encoding::{ use crate::opcodes::*; use crate::{ CompressedPk, ConsensusEncode, InvalidPubkey, PubkeyParseError, ScriptBytes, ScriptPubkey, - WitnessVer, LIB_NAME_BITCOIN, + VarInt, VarIntBytes, WitnessVer, LIB_NAME_BITCOIN, }; /// The SHA-256 midstate value for the TapLeaf hash. @@ -138,6 +138,37 @@ impl FromStr for XOnlyPk { } } +/// Internal taproot public key, which can be present only in key fragment +/// inside taproot descriptors. +#[derive(Eq, PartialEq, From)] +pub struct InternalKeypair(#[from] Keypair); + +impl InternalKeypair { + pub fn to_output_keypair(&self, merkle_root: Option) -> (Keypair, Parity) { + let internal_pk = self.0.x_only_public_key().0; + let mut engine = Sha256::from_tag(MIDSTATE_TAPTWEAK); + // always hash the key + engine.input_raw(&internal_pk.serialize()); + if let Some(merkle_root) = merkle_root { + 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"); + let pair = self + .0 + .add_xonly_tweak(secp256k1::SECP256K1, &tweak) + .expect("hash collision"); + let (outpput_key, tweaked_parity) = pair.x_only_public_key(); + debug_assert!(internal_pk.tweak_add_check( + secp256k1::SECP256K1, + &outpput_key, + tweaked_parity, + tweak + )); + (pair, tweaked_parity.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)] @@ -165,6 +196,7 @@ impl InternalPk { XOnlyPk::from_byte_array(data).map(Self) } + #[inline] pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result> { XOnlyPk::from_bytes(bytes).map(Self) } @@ -172,13 +204,10 @@ impl InternalPk { #[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.0 - } + #[inline] + pub fn to_xonly_pk(&self) -> XOnlyPk { self.0 } - pub fn to_output_pk(&self, merkle_root: Option) -> (OutputPk, Parity) { + 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()); @@ -229,10 +258,15 @@ impl OutputPk { XOnlyPk::from_byte_array(data).map(Self) } + #[inline] pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result> { XOnlyPk::from_bytes(bytes).map(Self) } + #[inline] + pub fn to_xonly_pk(&self) -> XOnlyPk { self.0 } + + #[inline] pub fn to_script_pubkey(&self) -> ScriptPubkey { ScriptPubkey::p2tr_tweaked(*self) } #[inline] @@ -247,6 +281,37 @@ pub trait IntoTapHash { fn into_tap_hash(self) -> TapNodeHash; } +#[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 TapSighash( + #[from] + #[from([u8; 32])] + pub Bytes32, +); + +impl From for [u8; 32] { + fn from(value: TapSighash) -> Self { value.0.into_inner() } +} + +impl From for secp256k1::Message { + fn from(sighash: TapSighash) -> Self { + secp256k1::Message::from_digest(sighash.to_byte_array()) + } +} + +impl TapSighash { + pub fn engine() -> Sha256 { Sha256::from_tag(MIDSTATE_TAPSIGHASH) } + + pub fn from_engine(engine: Sha256) -> Self { Self(engine.finish().into()) } +} + #[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)] #[wrapper(Index, RangeOps, BorrowSlice, Hex, Display, FromStr)] #[derive(StrictType, StrictEncode, StrictDecode, StrictDumb)] @@ -641,18 +706,18 @@ impl TapScript { } impl ScriptPubkey { - pub fn p2tr(internal_key: InternalPk, merkle_root: Option) -> Self { + pub fn p2tr(internal_key: InternalPk, merkle_root: Option) -> Self { 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_pk(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_pk(Some(merkle_root)); + let (output_key, _) = internal_key.to_output_pk(Some(merkle_root.into_tap_hash())); Self::p2tr_tweaked(output_key) } @@ -770,3 +835,87 @@ impl ControlBlock { } } } + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display, Error, From)] +#[display(doc_comments)] +pub enum AnnexError { + /// invalid first annex byte `{0:#02x}`, which must be `0x50`. + WrongFirstByte(u8), + + #[from] + #[display(inner)] + Size(confinement::Error), +} + +/// The `Annex` struct enforces first byte to be `0x50`. +#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)] +#[wrapper(Deref, AsSlice, Hex)] +#[wrapper_mut(DerefMut, AsSliceMut)] +#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_BITCOIN, dumb = Self(confined_vec![0x50]))] +pub struct Annex(VarIntBytes<1>); + +impl TryFrom> for Annex { + type Error = confinement::Error; + fn try_from(script_bytes: Vec) -> Result { + Confined::try_from(script_bytes).map(Self) + } +} + +impl Annex { + /// Creates a new `Annex` struct checking the first byte is `0x50`. + /// Constructs script object assuming the script length is less than 4GB. + /// Panics otherwise. + #[inline] + pub fn new(annex_bytes: Vec) -> Result { + let annex = Confined::try_from(annex_bytes).map(Self)?; + if annex[0] != TAPROOT_ANNEX_PREFIX { + return Err(AnnexError::WrongFirstByte(annex[0])); + } + Ok(annex) + } + + pub fn len_var_int(&self) -> VarInt { VarInt(self.len() as u64) } + + pub fn into_vec(self) -> Vec { self.0.into_inner() } + + /// Returns the Annex bytes data (including first byte `0x50`). + pub fn as_slice(&self) -> &[u8] { self.0.as_slice() } + + pub(crate) fn as_var_int_bytes(&self) -> &VarIntBytes<1> { &self.0 } +} + +#[cfg(feature = "serde")] +mod _serde { + use amplify::hex::{FromHex, ToHex}; + use serde::{Deserialize, Serialize}; + use serde_crate::de::Error; + use serde_crate::{Deserializer, Serializer}; + + use super::*; + + impl Serialize for Annex { + fn serialize(&self, serializer: S) -> Result + where S: Serializer { + if serializer.is_human_readable() { + serializer.serialize_str(&self.to_hex()) + } else { + serializer.serialize_bytes(self.as_slice()) + } + } + } + + impl<'de> Deserialize<'de> for Annex { + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> { + if deserializer.is_human_readable() { + String::deserialize(deserializer).and_then(|string| { + Self::from_hex(&string).map_err(|_| D::Error::custom("wrong hex data")) + }) + } else { + let bytes = Vec::::deserialize(deserializer)?; + Self::new(bytes).map_err(|_| D::Error::custom("invalid annex data")) + } + } + } +} diff --git a/consensus/src/timelocks.rs b/consensus/src/timelocks.rs index 88b60a67..946dd512 100644 --- a/consensus/src/timelocks.rs +++ b/consensus/src/timelocks.rs @@ -386,6 +386,8 @@ impl FromStr for LockHeight { pub struct SeqNo(u32); impl SeqNo { + pub const ZERO: SeqNo = SeqNo(0); + #[inline] pub const fn from_consensus_u32(lock_time: u32) -> Self { SeqNo(lock_time) } diff --git a/consensus/src/tx.rs b/consensus/src/tx.rs index bf764320..36ed9523 100644 --- a/consensus/src/tx.rs +++ b/consensus/src/tx.rs @@ -360,8 +360,8 @@ impl Display for Sats { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { Display::fmt(&self.0, f) } } -#[derive(Clone, Eq, PartialEq, Hash, Debug)] -#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)] +#[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( feature = "serde", diff --git a/dbc/src/tapret/xonlypk.rs b/dbc/src/tapret/xonlypk.rs index 7e51fff9..ab8679e2 100644 --- a/dbc/src/tapret/xonlypk.rs +++ b/dbc/src/tapret/xonlypk.rs @@ -94,7 +94,7 @@ impl ConvolveCommit for InternalPk { mod test { use std::str::FromStr; - use bc::LeafScript; + use bc::{IntoTapHash, LeafScript}; use commit_verify::mpc::Commitment; use super::*; @@ -115,7 +115,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_pk(Some(script_leaf)); + let (real_key, _) = internal_pk.to_output_pk(Some(script_leaf.into_tap_hash())); assert_eq!(outer_key, real_key);