Skip to content

Commit 8e9f99b

Browse files
committed
Merge rust-bitcoin/rust-bitcoin#718: Converting LeafVersion into an enum
ef8a3a8 Introduce FutureLeafVersion (Dr Maxim Orlovsky) b028385 Improve docs in LeafVersion (Dr Maxim Orlovsky) 839c022 Make serde for LeafVersion to have byte representation (Dr Maxim Orlovsky) 67b8db0 Converting LeafVersion into an enum (Dr Maxim Orlovsky) 2405417 Use TAPROOT_ANNEX_PREFIX in sighash module (Dr Maxim Orlovsky) Pull request description: The original `LeafVersion` implementation was just a newtype around `u8`. I think that having enum explicitly listing consensus script implementation rules may be more beneficial in terms of both code readibility and future use of multiple script types, where `LeafVersion` may operate as a context object provided to `Script` to specify interpretation rules for particular op codes. ACKs for top commit: Kixunil: ACK ef8a3a8 sanket1729: crACK ef8a3a8. Waiting a day to let others complete review before merging. apoelstra: ACK ef8a3a8 Tree-SHA512: 3356d2b9b00cf904edfece26d26ffbc646ba74446cc23ec4b2b4026ed50861285802f077226e30ba8fed466f68f8e8556c729ce48cb38581b1d95a02a6fde9cf
2 parents 2ec9af3 + ef8a3a8 commit 8e9f99b

File tree

3 files changed

+119
-36
lines changed

3 files changed

+119
-36
lines changed

src/util/psbt/serialize.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ impl Serialize for (Script, LeafVersion) {
259259
fn serialize(&self) -> Vec<u8> {
260260
let mut buf = Vec::with_capacity(self.0.len() + 1);
261261
buf.extend(self.0.as_bytes());
262-
buf.push(self.1.as_u8());
262+
buf.push(self.1.into_consensus());
263263
buf
264264
}
265265
}
@@ -271,7 +271,7 @@ impl Deserialize for (Script, LeafVersion) {
271271
}
272272
// The last byte is LeafVersion.
273273
let script = Script::deserialize(&bytes[..bytes.len() - 1])?;
274-
let leaf_ver = LeafVersion::from_u8(bytes[bytes.len() - 1])
274+
let leaf_ver = LeafVersion::from_consensus(bytes[bytes.len() - 1])
275275
.map_err(|_| encode::Error::ParseFailed("invalid leaf version"))?;
276276
Ok((script, leaf_ver))
277277
}
@@ -307,7 +307,7 @@ impl Serialize for TapTree {
307307
// TaprootMerkleBranch can only have len atmost 128(TAPROOT_CONTROL_MAX_NODE_COUNT).
308308
// safe to cast from usize to u8
309309
buf.push(leaf_info.merkle_branch.as_inner().len() as u8);
310-
buf.push(leaf_info.ver.as_u8());
310+
buf.push(leaf_info.ver.into_consensus());
311311
leaf_info.script.consensus_encode(&mut buf).expect("Vecs dont err");
312312
}
313313
buf
@@ -329,7 +329,7 @@ impl Deserialize for TapTree {
329329
bytes_iter.nth(consumed - 1);
330330
}
331331

332-
let leaf_version = LeafVersion::from_u8(*version)
332+
let leaf_version = LeafVersion::from_consensus(*version)
333333
.map_err(|_| encode::Error::ParseFailed("Leaf Version Error"))?;
334334
builder = builder.add_leaf_with_ver(usize::from(*depth), script, leaf_version)
335335
.map_err(|_| encode::Error::ParseFailed("Tree not in DFS order"))?;

src/util/sighash.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use core::fmt;
2727
use core::ops::{Deref, DerefMut};
2828
use hashes::{sha256, sha256d, Hash};
2929
use io;
30-
use util::taproot::{TapLeafHash, TapSighashHash};
30+
use util::taproot::{TapLeafHash, TAPROOT_ANNEX_PREFIX, TapSighashHash};
3131
use SigHash;
3232
use {Script, Transaction, TxOut};
3333

@@ -229,13 +229,13 @@ impl<'s> ScriptPath<'s> {
229229
}
230230
/// Create a new ScriptPath structure using default leaf version value
231231
pub fn with_defaults(script: &'s Script) -> Self {
232-
Self::new(script, LeafVersion::default())
232+
Self::new(script, LeafVersion::TapScript)
233233
}
234234
/// Compute the leaf hash
235235
pub fn leaf_hash(&self) -> TapLeafHash {
236236
let mut enc = TapLeafHash::engine();
237237

238-
self.leaf_version.as_u8().consensus_encode(&mut enc).expect("Writing to hash enging should never fail");
238+
self.leaf_version.into_consensus().consensus_encode(&mut enc).expect("Writing to hash enging should never fail");
239239
self.script.consensus_encode(&mut enc).expect("Writing to hash enging should never fail");
240240

241241
TapLeafHash::from_engine(enc)
@@ -725,7 +725,7 @@ pub struct Annex<'a>(&'a [u8]);
725725
impl<'a> Annex<'a> {
726726
/// Creates a new `Annex` struct checking the first byte is `0x50`
727727
pub fn new(annex_bytes: &'a [u8]) -> Result<Self, Error> {
728-
if annex_bytes.first() == Some(&0x50) {
728+
if annex_bytes.first() == Some(&TAPROOT_ANNEX_PREFIX) {
729729
Ok(Annex(annex_bytes))
730730
} else {
731731
Err(Error::WrongAnnex)

src/util/taproot.rs

Lines changed: 111 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
//!
1616
//! This module provides support for taproot tagged hashes.
1717
//!
18+
1819
use prelude::*;
1920
use io;
2021
use secp256k1::{self, Secp256k1};
@@ -120,7 +121,7 @@ impl TapLeafHash {
120121
/// function to compute leaf hash from components
121122
pub fn from_script(script: &Script, ver: LeafVersion) -> TapLeafHash {
122123
let mut eng = TapLeafHash::engine();
123-
ver.as_u8()
124+
ver.into_consensus()
124125
.consensus_encode(&mut eng)
125126
.expect("engines don't error");
126127
script
@@ -142,6 +143,8 @@ pub const TAPROOT_LEAF_MASK: u8 = 0xfe;
142143
/// Tapscript leaf version
143144
// https://github.com/bitcoin/bitcoin/blob/e826b22da252e0599c61d21c98ff89f366b3120f/src/script/interpreter.h#L226
144145
pub const TAPROOT_LEAF_TAPSCRIPT: u8 = 0xc0;
146+
/// Taproot annex prefix
147+
pub const TAPROOT_ANNEX_PREFIX: u8 = 0x50;
145148
/// Tapscript control base size
146149
// https://github.com/bitcoin/bitcoin/blob/e826b22da252e0599c61d21c98ff89f366b3120f/src/script/interpreter.h#L227
147150
pub const TAPROOT_CONTROL_BASE_SIZE: usize = 33;
@@ -152,6 +155,7 @@ pub const TAPROOT_CONTROL_MAX_SIZE: usize =
152155

153156
// type alias for versioned tap script corresponding merkle proof
154157
type ScriptMerkleProofMap = BTreeMap<(Script, LeafVersion), BTreeSet<TaprootMerkleBranch>>;
158+
155159
/// Data structure for representing Taproot spending information.
156160
/// Taproot output corresponds to a combination of a
157161
/// single public key condition (known the internal key), and zero or more
@@ -216,7 +220,7 @@ impl TaprootSpendInfo {
216220
{
217221
let mut node_weights = BinaryHeap::<(Reverse<u64>, NodeInfo)>::new();
218222
for (p, leaf) in script_weights {
219-
node_weights.push((Reverse(p as u64), NodeInfo::new_leaf_with_ver(leaf, LeafVersion::default())));
223+
node_weights.push((Reverse(p as u64), NodeInfo::new_leaf_with_ver(leaf, LeafVersion::TapScript)));
220224
}
221225
if node_weights.is_empty() {
222226
return Err(TaprootBuilderError::IncompleteTree);
@@ -409,7 +413,7 @@ impl TaprootBuilder {
409413
/// See [`TaprootBuilder::add_leaf_with_ver`] for adding a leaf with specific version
410414
/// See [Uncyclopedia](https://en.wikipedia.org/wiki/Depth-first_search) for more details
411415
pub fn add_leaf(self, depth: usize, script: Script) -> Result<Self, TaprootBuilderError> {
412-
self.add_leaf_with_ver(depth, script, LeafVersion::default())
416+
self.add_leaf_with_ver(depth, script, LeafVersion::TapScript)
413417
}
414418

415419
/// Add a hidden/omitted node at a depth `depth` to the builder.
@@ -680,7 +684,7 @@ impl ControlBlock {
680684
return Err(TaprootError::InvalidControlBlockSize(sl.len()));
681685
}
682686
let output_key_parity = secp256k1::Parity::from((sl[0] & 1) as i32);
683-
let leaf_version = LeafVersion::from_u8(sl[0] & TAPROOT_LEAF_MASK)?;
687+
let leaf_version = LeafVersion::from_consensus(sl[0] & TAPROOT_LEAF_MASK)?;
684688
let internal_key = UntweakedPublicKey::from_slice(&sl[1..TAPROOT_CONTROL_BASE_SIZE])
685689
.map_err(TaprootError::InvalidInternalKey)?;
686690
let merkle_branch = TaprootMerkleBranch::from_slice(&sl[TAPROOT_CONTROL_BASE_SIZE..])?;
@@ -700,7 +704,7 @@ impl ControlBlock {
700704

701705
/// Serialize to a writer. Returns the number of bytes written
702706
pub fn encode<Write: io::Write>(&self, mut writer: Write) -> io::Result<usize> {
703-
let first_byte: u8 = i32::from(self.output_key_parity) as u8 | self.leaf_version.as_u8();
707+
let first_byte: u8 = i32::from(self.output_key_parity) as u8 | self.leaf_version.into_consensus();
704708
let mut bytes_written = 0;
705709
bytes_written += writer.write(&[first_byte])?;
706710
bytes_written += writer.write(&self.internal_key.serialize())?;
@@ -756,20 +760,54 @@ impl ControlBlock {
756760
}
757761
}
758762

763+
/// Inner type representing future (non-tapscript) leaf versions. See [`LeafVersion::Future`].
764+
///
765+
/// NB: NO PUBLIC CONSTRUCTOR!
766+
/// The only way to construct this is by converting u8 to LeafVersion and then extracting it.
767+
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
768+
pub struct FutureLeafVersion(u8);
769+
770+
impl FutureLeafVersion {
771+
pub(self) fn from_consensus(version: u8) -> Result<FutureLeafVersion, TaprootError> {
772+
match version {
773+
TAPROOT_LEAF_TAPSCRIPT => unreachable!("FutureLeafVersion::from_consensus should be never called for 0xC0 value"),
774+
TAPROOT_ANNEX_PREFIX => Err(TaprootError::InvalidTaprootLeafVersion(TAPROOT_ANNEX_PREFIX)),
775+
odd if odd & 0xFE != odd => Err(TaprootError::InvalidTaprootLeafVersion(odd)),
776+
even => Ok(FutureLeafVersion(even))
777+
}
778+
}
779+
780+
/// Get consensus representation of the future leaf version.
781+
#[inline]
782+
pub fn into_consensus(self) -> u8 {
783+
self.0
784+
}
785+
}
786+
787+
impl fmt::Display for FutureLeafVersion {
788+
#[inline]
789+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
790+
fmt::Display::fmt(&self.0, f)
791+
}
792+
}
793+
759794
/// The leaf version for tapleafs
760795
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
761-
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
762-
pub struct LeafVersion(u8);
796+
pub enum LeafVersion {
797+
/// BIP-342 tapscript
798+
TapScript,
763799

764-
impl Default for LeafVersion {
765-
fn default() -> Self {
766-
LeafVersion(TAPROOT_LEAF_TAPSCRIPT)
767-
}
800+
/// Future leaf version
801+
Future(FutureLeafVersion)
768802
}
769803

770804
impl LeafVersion {
771-
/// Obtain LeafVersion from u8, will error when last bit of ver is even or
772-
/// when ver is 0x50 (ANNEX_TAG)
805+
/// Obtain LeafVersion from consensus byte representation.
806+
///
807+
/// # Errors
808+
/// - If the last bit of the `version` is odd.
809+
/// - If the `version` is 0x50 ([`TAPROOT_ANNEX_PREFIX`]).
810+
/// - If the `version` is not a valid [`LeafVersion`] byte.
773811
// Text from BIP341:
774812
// In order to support some forms of static analysis that rely on
775813
// being able to identify script spends without access to the output being
@@ -779,23 +817,68 @@ impl LeafVersion {
779817
// or an opcode that is not valid as the first opcode).
780818
// The values that comply to this rule are the 32 even values between
781819
// 0xc0 and 0xfe and also 0x66, 0x7e, 0x80, 0x84, 0x96, 0x98, 0xba, 0xbc, 0xbe
782-
pub fn from_u8(ver: u8) -> Result<Self, TaprootError> {
783-
if ver & TAPROOT_LEAF_MASK == ver && ver != 0x50 {
784-
Ok(LeafVersion(ver))
785-
} else {
786-
Err(TaprootError::InvalidTaprootLeafVersion(ver))
820+
pub fn from_consensus(version: u8) -> Result<Self, TaprootError> {
821+
match version {
822+
TAPROOT_LEAF_TAPSCRIPT => Ok(LeafVersion::TapScript),
823+
TAPROOT_ANNEX_PREFIX => Err(TaprootError::InvalidTaprootLeafVersion(TAPROOT_ANNEX_PREFIX)),
824+
future => FutureLeafVersion::from_consensus(future).map(LeafVersion::Future),
787825
}
788826
}
789827

790-
/// Get the inner version from LeafVersion
791-
pub fn as_u8(&self) -> u8 {
792-
self.0
828+
/// Get consensus representation of the [`LeafVersion`].
829+
pub fn into_consensus(self) -> u8 {
830+
match self {
831+
LeafVersion::TapScript => TAPROOT_LEAF_TAPSCRIPT,
832+
LeafVersion::Future(version) => version.into_consensus(),
833+
}
793834
}
794835
}
795836

796-
impl Into<u8> for LeafVersion {
797-
fn into(self) -> u8 {
798-
self.0
837+
impl fmt::Display for LeafVersion {
838+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
839+
match (self, f.alternate()) {
840+
(LeafVersion::TapScript, false) => f.write_str("tapscript"),
841+
(LeafVersion::TapScript, true) => fmt::Display::fmt(&TAPROOT_LEAF_TAPSCRIPT, f),
842+
(LeafVersion::Future(version), false) => write!(f, "future_script_{:#02x}", version.0),
843+
(LeafVersion::Future(version), true) => fmt::Display::fmt(version, f),
844+
}
845+
}
846+
}
847+
848+
#[cfg(feature = "serde")]
849+
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
850+
impl ::serde::Serialize for LeafVersion {
851+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
852+
where
853+
S: ::serde::Serializer,
854+
{
855+
serializer.serialize_u8(self.into_consensus())
856+
}
857+
}
858+
859+
#[cfg(feature = "serde")]
860+
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
861+
impl<'de> ::serde::Deserialize<'de> for LeafVersion {
862+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: ::serde::Deserializer<'de> {
863+
struct U8Visitor;
864+
impl<'de> ::serde::de::Visitor<'de> for U8Visitor {
865+
type Value = LeafVersion;
866+
867+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
868+
formatter.write_str("a valid consensus-encoded taproot leaf version")
869+
}
870+
871+
fn visit_u8<E>(self, value: u8) -> Result<Self::Value, E>
872+
where
873+
E: ::serde::de::Error,
874+
{
875+
LeafVersion::from_consensus(value).map_err(|_| {
876+
E::invalid_value(::serde::de::Unexpected::Unsigned(value as u64), &"consensus-encoded leaf version as u8")
877+
})
878+
}
879+
}
880+
881+
deserializer.deserialize_u8(U8Visitor)
799882
}
800883
}
801884

@@ -1063,7 +1146,7 @@ mod test {
10631146
length,
10641147
tree_info
10651148
.script_map
1066-
.get(&(Script::from_hex(script).unwrap(), LeafVersion::default()))
1149+
.get(&(Script::from_hex(script).unwrap(), LeafVersion::TapScript))
10671150
.expect("Present Key")
10681151
.iter()
10691152
.next()
@@ -1078,7 +1161,7 @@ mod test {
10781161

10791162
// Try to create and verify a control block from each path
10801163
for (_weights, script) in script_weights {
1081-
let ver_script = (script, LeafVersion::default());
1164+
let ver_script = (script, LeafVersion::TapScript);
10821165
let ctrl_block = tree_info.control_block(&ver_script).unwrap();
10831166
assert!(ctrl_block.verify_taproot_commitment(&secp, &output_key, &ver_script.0))
10841167
}
@@ -1114,7 +1197,7 @@ mod test {
11141197
let output_key = tree_info.output_key();
11151198

11161199
for script in vec![a, b, c, d, e] {
1117-
let ver_script = (script, LeafVersion::default());
1200+
let ver_script = (script, LeafVersion::TapScript);
11181201
let ctrl_block = tree_info.control_block(&ver_script).unwrap();
11191202
assert!(ctrl_block.verify_taproot_commitment(&secp, &output_key, &ver_script.0))
11201203
}
@@ -1137,7 +1220,7 @@ mod test {
11371220
}
11381221
} else {
11391222
let script = Script::from_str(v["script"].as_str().unwrap()).unwrap();
1140-
let ver = LeafVersion::from_u8(v["leafVersion"].as_u64().unwrap() as u8).unwrap();
1223+
let ver = LeafVersion::from_consensus(v["leafVersion"].as_u64().unwrap() as u8).unwrap();
11411224
leaves.push((script.clone(), ver));
11421225
builder = builder.add_leaf_with_ver(depth, script, ver).unwrap();
11431226
}

0 commit comments

Comments
 (0)