Strengthen the type of `taproot_control_block()`
The type returned by `Witness::taproot_control_block()` was just `&[u8]` which wasn't very nice since users then had to manually decode it which so far also required allocation. Thanks to previous improvements to `ControlBlock` it is now possible to return a `ControlBlock` type directly. To avoid expensive checks, this change adds a new type `SerializedXOnlyPublicKey` which is a wrapper around `[u8; 32]` that is used in `ControlBlock` if complete checking is undesirable. It is then used in the `ControlBlock` returned from `Witness::taproot_control_block`. Users can still conveniently validate the key using `to_validated` method. It then uses this type in the recently-added `P2TrSpend` type. As a side effect this checks more properties of `Witness` when calling unrelated methods on `Witness`. From correctness perspective this should be OK: a witness obtained from a verified source will be correct anyway and, if these checks were done by the caller, they can be removed. From performance perspective, if the `Witness` was obtained from a verified source (e.g. using Bitcoin Core RPC) these checks are wasted CPU time. But they shouldn't be too expensive, we already avoid `secp256k1` overhead and, given that they always succeed in such case, they should be easy to branch-predict.
This commit is contained in:
parent
e8a42d5851
commit
492073f288
|
@ -10,15 +10,15 @@ use io::{BufRead, Write};
|
||||||
use crate::consensus::encode::{self, Error, ReadExt, WriteExt, MAX_VEC_SIZE};
|
use crate::consensus::encode::{self, Error, ReadExt, WriteExt, MAX_VEC_SIZE};
|
||||||
use crate::consensus::{Decodable, Encodable};
|
use crate::consensus::{Decodable, Encodable};
|
||||||
use crate::crypto::ecdsa;
|
use crate::crypto::ecdsa;
|
||||||
|
use crate::crypto::key::SerializedXOnlyPublicKey;
|
||||||
use crate::prelude::Vec;
|
use crate::prelude::Vec;
|
||||||
#[cfg(doc)]
|
#[cfg(doc)]
|
||||||
use crate::script::ScriptExt as _;
|
use crate::script::ScriptExt as _;
|
||||||
use crate::taproot::{
|
use crate::taproot::{self, ControlBlock, LeafScript, TAPROOT_ANNEX_PREFIX, TaprootMerkleBranch};
|
||||||
self, ControlBlock, LeafScript, LeafVersion, TAPROOT_ANNEX_PREFIX, TAPROOT_CONTROL_BASE_SIZE,
|
|
||||||
TAPROOT_LEAF_MASK, TaprootMerkleBranch,
|
|
||||||
};
|
|
||||||
use crate::Script;
|
use crate::Script;
|
||||||
|
|
||||||
|
type BorrowedControlBlock<'a> = ControlBlock<&'a TaprootMerkleBranch, &'a SerializedXOnlyPublicKey>;
|
||||||
|
|
||||||
#[rustfmt::skip] // Keep public re-exports separate.
|
#[rustfmt::skip] // Keep public re-exports separate.
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use primitives::witness::{Iter, Witness};
|
pub use primitives::witness::{Iter, Witness};
|
||||||
|
@ -176,9 +176,8 @@ crate::internal_macros::define_extension_trait! {
|
||||||
/// version.
|
/// version.
|
||||||
fn taproot_leaf_script(&self) -> Option<LeafScript<&Script>> {
|
fn taproot_leaf_script(&self) -> Option<LeafScript<&Script>> {
|
||||||
match P2TrSpend::from_witness(self) {
|
match P2TrSpend::from_witness(self) {
|
||||||
Some(P2TrSpend::Script { leaf_script, control_block, .. }) if control_block.len() >= TAPROOT_CONTROL_BASE_SIZE => {
|
Some(P2TrSpend::Script { leaf_script, control_block, .. }) => {
|
||||||
let version = LeafVersion::from_consensus(control_block[0] & TAPROOT_LEAF_MASK).ok()?;
|
Some(LeafScript { version: control_block.leaf_version, script: leaf_script, })
|
||||||
Some(LeafScript { version, script: leaf_script, })
|
|
||||||
},
|
},
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
@ -191,7 +190,7 @@ crate::internal_macros::define_extension_trait! {
|
||||||
/// byte of the last element being equal to 0x50.
|
/// byte of the last element being equal to 0x50.
|
||||||
///
|
///
|
||||||
/// See [`Script::is_p2tr`] to check whether this is actually a Taproot witness.
|
/// See [`Script::is_p2tr`] to check whether this is actually a Taproot witness.
|
||||||
fn taproot_control_block(&self) -> Option<&[u8]> {
|
fn taproot_control_block(&self) -> Option<BorrowedControlBlock<'_>> {
|
||||||
match P2TrSpend::from_witness(self) {
|
match P2TrSpend::from_witness(self) {
|
||||||
Some(P2TrSpend::Script { control_block, .. }) => Some(control_block),
|
Some(P2TrSpend::Script { control_block, .. }) => Some(control_block),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
@ -236,7 +235,7 @@ enum P2TrSpend<'a> {
|
||||||
},
|
},
|
||||||
Script {
|
Script {
|
||||||
leaf_script: &'a Script,
|
leaf_script: &'a Script,
|
||||||
control_block: &'a [u8],
|
control_block: BorrowedControlBlock<'a>,
|
||||||
annex: Option<&'a [u8]>,
|
annex: Option<&'a [u8]>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -275,17 +274,21 @@ impl<'a> P2TrSpend<'a> {
|
||||||
// last one does NOT start with TAPROOT_ANNEX_PREFIX. This is handled in the catchall
|
// last one does NOT start with TAPROOT_ANNEX_PREFIX. This is handled in the catchall
|
||||||
// arm.
|
// arm.
|
||||||
3.. if witness.last().expect("len > 0").starts_with(&[TAPROOT_ANNEX_PREFIX]) => {
|
3.. if witness.last().expect("len > 0").starts_with(&[TAPROOT_ANNEX_PREFIX]) => {
|
||||||
|
let control_block = witness.get_back(1).expect("len > 1");
|
||||||
|
let control_block = BorrowedControlBlock::decode_borrowed(control_block).ok()?;
|
||||||
let spend = P2TrSpend::Script {
|
let spend = P2TrSpend::Script {
|
||||||
leaf_script: Script::from_bytes(witness.get_back(2).expect("len > 2")),
|
leaf_script: Script::from_bytes(witness.get_back(2).expect("len > 2")),
|
||||||
control_block: witness.get_back(1).expect("len > 1"),
|
control_block,
|
||||||
annex: witness.last(),
|
annex: witness.last(),
|
||||||
};
|
};
|
||||||
Some(spend)
|
Some(spend)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
let control_block = witness.last().expect("len > 0");
|
||||||
|
let control_block = BorrowedControlBlock::decode_borrowed(control_block).ok()?;
|
||||||
let spend = P2TrSpend::Script {
|
let spend = P2TrSpend::Script {
|
||||||
leaf_script: Script::from_bytes(witness.get_back(1).expect("len > 1")),
|
leaf_script: Script::from_bytes(witness.get_back(1).expect("len > 1")),
|
||||||
control_block: witness.last().expect("len > 0"),
|
control_block,
|
||||||
annex: None,
|
annex: None,
|
||||||
};
|
};
|
||||||
Some(spend)
|
Some(spend)
|
||||||
|
@ -324,6 +327,7 @@ mod test {
|
||||||
use crate::consensus::{deserialize, encode, serialize};
|
use crate::consensus::{deserialize, encode, serialize};
|
||||||
use crate::hex::DisplayHex;
|
use crate::hex::DisplayHex;
|
||||||
use crate::sighash::EcdsaSighashType;
|
use crate::sighash::EcdsaSighashType;
|
||||||
|
use crate::taproot::LeafVersion;
|
||||||
use crate::Transaction;
|
use crate::Transaction;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -383,7 +387,7 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn get_tapscript() {
|
fn get_tapscript() {
|
||||||
let tapscript = hex!("deadbeef");
|
let tapscript = hex!("deadbeef");
|
||||||
let control_block = hex!("02");
|
let control_block = hex!("c0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
||||||
// annex starting with 0x50 causes the branching logic.
|
// annex starting with 0x50 causes the branching logic.
|
||||||
let annex = hex!("50");
|
let annex = hex!("50");
|
||||||
|
|
||||||
|
@ -449,7 +453,8 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn get_control_block() {
|
fn get_control_block() {
|
||||||
let tapscript = hex!("deadbeef");
|
let tapscript = hex!("deadbeef");
|
||||||
let control_block = hex!("02");
|
let control_block = hex!("c0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
||||||
|
let expected_control_block = BorrowedControlBlock::decode_borrowed(&control_block).unwrap();
|
||||||
// annex starting with 0x50 causes the branching logic.
|
// annex starting with 0x50 causes the branching logic.
|
||||||
let annex = hex!("50");
|
let annex = hex!("50");
|
||||||
let signature = vec![0xff; 64];
|
let signature = vec![0xff; 64];
|
||||||
|
@ -468,15 +473,15 @@ mod test {
|
||||||
deserialize::<Witness>(&witness_serialized_key_spend_annex[..]).unwrap();
|
deserialize::<Witness>(&witness_serialized_key_spend_annex[..]).unwrap();
|
||||||
|
|
||||||
// With or without annex, the tapscript should be returned.
|
// With or without annex, the tapscript should be returned.
|
||||||
assert_eq!(witness.taproot_control_block(), Some(&control_block[..]));
|
assert_eq!(witness.taproot_control_block().unwrap(), expected_control_block);
|
||||||
assert_eq!(witness_annex.taproot_control_block(), Some(&control_block[..]));
|
assert_eq!(witness_annex.taproot_control_block().unwrap(), expected_control_block);
|
||||||
assert!(witness_key_spend_annex.taproot_control_block().is_none())
|
assert!(witness_key_spend_annex.taproot_control_block().is_none())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_annex() {
|
fn get_annex() {
|
||||||
let tapscript = hex!("deadbeef");
|
let tapscript = hex!("deadbeef");
|
||||||
let control_block = hex!("02");
|
let control_block = hex!("c0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
||||||
// annex starting with 0x50 causes the branching logic.
|
// annex starting with 0x50 causes the branching logic.
|
||||||
let annex = hex!("50");
|
let annex = hex!("50");
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ use crate::taproot::{TapNodeHash, TapTweakHash};
|
||||||
|
|
||||||
#[rustfmt::skip] // Keep public re-exports separate.
|
#[rustfmt::skip] // Keep public re-exports separate.
|
||||||
pub use secp256k1::{constants, Keypair, Parity, Secp256k1, Verification, XOnlyPublicKey};
|
pub use secp256k1::{constants, Keypair, Parity, Secp256k1, Verification, XOnlyPublicKey};
|
||||||
|
pub use serialized_x_only::SerializedXOnlyPublicKey;
|
||||||
|
|
||||||
#[cfg(feature = "rand-std")]
|
#[cfg(feature = "rand-std")]
|
||||||
pub use secp256k1::rand;
|
pub use secp256k1::rand;
|
||||||
|
@ -1208,6 +1209,63 @@ impl fmt::Display for InvalidWifCompressionFlagError {
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
impl std::error::Error for InvalidWifCompressionFlagError {}
|
impl std::error::Error for InvalidWifCompressionFlagError {}
|
||||||
|
|
||||||
|
mod serialized_x_only {
|
||||||
|
internals::transparent_newtype! {
|
||||||
|
/// An array of bytes that's semantically an x-only public but was **not** validated.
|
||||||
|
///
|
||||||
|
/// This can be useful when validation is not desired but semantics of the bytes should be
|
||||||
|
/// preserved. The validation can still happen using `to_validated()` method.
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
|
pub struct SerializedXOnlyPublicKey([u8; 32]);
|
||||||
|
|
||||||
|
impl SerializedXOnlyPublicKey {
|
||||||
|
pub(crate) fn from_bytes_ref(bytes: &_) -> Self;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SerializedXOnlyPublicKey {
|
||||||
|
/// Marks the supplied bytes as a serialized x-only public key.
|
||||||
|
pub const fn from_byte_array(bytes: [u8; 32]) -> Self {
|
||||||
|
Self(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the raw bytes.
|
||||||
|
pub const fn to_byte_array(self) -> [u8; 32] {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the raw bytes.
|
||||||
|
pub const fn as_byte_array(&self) -> &[u8; 32] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SerializedXOnlyPublicKey {
|
||||||
|
/// Returns `XOnlyPublicKey` if the bytes are valid.
|
||||||
|
pub fn to_validated(self) -> Result<XOnlyPublicKey, secp256k1::Error> {
|
||||||
|
XOnlyPublicKey::from_byte_array(self.as_byte_array())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[u8; 32]> for SerializedXOnlyPublicKey {
|
||||||
|
fn as_ref(&self) -> &[u8; 32] {
|
||||||
|
self.as_byte_array()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&SerializedXOnlyPublicKey> for SerializedXOnlyPublicKey {
|
||||||
|
fn from(borrowed: &SerializedXOnlyPublicKey) -> Self {
|
||||||
|
*borrowed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for SerializedXOnlyPublicKey {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(&self.as_byte_array().as_hex(), f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -22,7 +22,7 @@ use io::Write;
|
||||||
use secp256k1::{Scalar, Secp256k1};
|
use secp256k1::{Scalar, Secp256k1};
|
||||||
|
|
||||||
use crate::consensus::Encodable;
|
use crate::consensus::Encodable;
|
||||||
use crate::crypto::key::{TapTweak, TweakedPublicKey, UntweakedPublicKey, XOnlyPublicKey};
|
use crate::crypto::key::{SerializedXOnlyPublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey, XOnlyPublicKey};
|
||||||
use crate::prelude::{BTreeMap, BTreeSet, BinaryHeap, Vec};
|
use crate::prelude::{BTreeMap, BTreeSet, BinaryHeap, Vec};
|
||||||
use crate::{Script, ScriptBuf};
|
use crate::{Script, ScriptBuf};
|
||||||
|
|
||||||
|
@ -1141,13 +1141,13 @@ impl<'leaf> ScriptLeaf<'leaf> {
|
||||||
/// Control block data structure used in Tapscript satisfaction.
|
/// Control block data structure used in Tapscript satisfaction.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub struct ControlBlock<Branch = TaprootMerkleBranchBuf> where Branch: ?Sized {
|
pub struct ControlBlock<Branch = TaprootMerkleBranchBuf, Key = UntweakedPublicKey> where Branch: ?Sized {
|
||||||
/// The tapleaf version.
|
/// The tapleaf version.
|
||||||
pub leaf_version: LeafVersion,
|
pub leaf_version: LeafVersion,
|
||||||
/// The parity of the output key (NOT THE INTERNAL KEY WHICH IS ALWAYS XONLY).
|
/// The parity of the output key (NOT THE INTERNAL KEY WHICH IS ALWAYS XONLY).
|
||||||
pub output_key_parity: secp256k1::Parity,
|
pub output_key_parity: secp256k1::Parity,
|
||||||
/// The internal key.
|
/// The internal key.
|
||||||
pub internal_key: UntweakedPublicKey,
|
pub internal_key: Key,
|
||||||
/// The Merkle proof of a script associated with this leaf.
|
/// The Merkle proof of a script associated with this leaf.
|
||||||
pub merkle_branch: Branch,
|
pub merkle_branch: Branch,
|
||||||
}
|
}
|
||||||
|
@ -1166,6 +1166,24 @@ impl ControlBlock {
|
||||||
/// - [`TaprootError::InvalidInternalKey`] if internal key is invalid (first 32 bytes after the parity byte).
|
/// - [`TaprootError::InvalidInternalKey`] if internal key is invalid (first 32 bytes after the parity byte).
|
||||||
/// - [`TaprootError::InvalidMerkleTreeDepth`] if Merkle tree is too deep (more than 128 levels).
|
/// - [`TaprootError::InvalidMerkleTreeDepth`] if Merkle tree is too deep (more than 128 levels).
|
||||||
pub fn decode(sl: &[u8]) -> Result<ControlBlock, TaprootError> {
|
pub fn decode(sl: &[u8]) -> Result<ControlBlock, TaprootError> {
|
||||||
|
use alloc::borrow::ToOwned;
|
||||||
|
|
||||||
|
let ControlBlock {
|
||||||
|
leaf_version,
|
||||||
|
output_key_parity,
|
||||||
|
internal_key,
|
||||||
|
merkle_branch,
|
||||||
|
} = ControlBlock::<&TaprootMerkleBranch, &SerializedXOnlyPublicKey>::decode_borrowed(sl)?;
|
||||||
|
|
||||||
|
let internal_key = internal_key.to_validated().map_err(TaprootError::InvalidInternalKey)?;
|
||||||
|
let merkle_branch = merkle_branch.to_owned();
|
||||||
|
|
||||||
|
Ok(ControlBlock { leaf_version, output_key_parity, internal_key, merkle_branch })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B, K> ControlBlock<B, K> {
|
||||||
|
pub(crate) fn decode_borrowed<'a>(sl: &'a [u8]) -> Result<Self, TaprootError> where B: From<&'a TaprootMerkleBranch>, K: From<&'a SerializedXOnlyPublicKey> {
|
||||||
let (base, merkle_branch) = sl.split_first_chunk::<TAPROOT_CONTROL_BASE_SIZE>()
|
let (base, merkle_branch) = sl.split_first_chunk::<TAPROOT_CONTROL_BASE_SIZE>()
|
||||||
.ok_or(InvalidControlBlockSizeError(sl.len()))?;
|
.ok_or(InvalidControlBlockSizeError(sl.len()))?;
|
||||||
|
|
||||||
|
@ -1177,9 +1195,8 @@ impl ControlBlock {
|
||||||
};
|
};
|
||||||
|
|
||||||
let leaf_version = LeafVersion::from_consensus(first & TAPROOT_LEAF_MASK)?;
|
let leaf_version = LeafVersion::from_consensus(first & TAPROOT_LEAF_MASK)?;
|
||||||
let internal_key = UntweakedPublicKey::from_byte_array(internal_key)
|
let internal_key = SerializedXOnlyPublicKey::from_bytes_ref(internal_key).into();
|
||||||
.map_err(TaprootError::InvalidInternalKey)?;
|
let merkle_branch = TaprootMerkleBranch::decode(merkle_branch)?.into();
|
||||||
let merkle_branch = TaprootMerkleBranchBuf::decode(merkle_branch)?;
|
|
||||||
Ok(ControlBlock { leaf_version, output_key_parity, internal_key, merkle_branch })
|
Ok(ControlBlock { leaf_version, output_key_parity, internal_key, merkle_branch })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue