From 6dba3f8546c5ca0050ae6256279b89a41a32ae67 Mon Sep 17 00:00:00 2001 From: nymius <155548262+nymius@users.noreply.github.com> Date: Fri, 6 Feb 2026 13:52:15 -0300 Subject: [PATCH 01/11] build: add bitcoin-consensus-encoding crate from git url to get CompactSizeEncoder::encoded_size --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 74e0494..df80bb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ silent-payments = [] # Silent Payments support [dependencies] bitcoin = { version = "0.32.8", default-features = false } +encoding = { package = "bitcoin-consensus-encoding", git = "https://github.com/rust-bitcoin/rust-bitcoin", branch = "master", version = "1.0.0-rc.3", default-features = false, features = [ "alloc" ] } miniscript = { version = "12.2.0", default-features = false, optional = true } serde = { version = "1.0.195", default-features = false, features = ["derive", "alloc"], optional = true } From d61e32b552e9d0f7a4a894552a29845eb082c0ec Mon Sep 17 00:00:00 2001 From: nymius <155548262+nymius@users.noreply.github.com> Date: Fri, 6 Feb 2026 13:52:48 -0300 Subject: [PATCH 02/11] feat: implement binary encoding for raw::Key using consensus_encoding crate --- src/binary_encoding.rs | 433 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 2 files changed, 434 insertions(+) create mode 100644 src/binary_encoding.rs diff --git a/src/binary_encoding.rs b/src/binary_encoding.rs new file mode 100644 index 0000000..f5e5d02 --- /dev/null +++ b/src/binary_encoding.rs @@ -0,0 +1,433 @@ +//! Scrachtpad to implement binary serialization for PSBT using consensus_encoding crate +use encoding::{ + ByteVecDecoder, ByteVecDecoderError, BytesEncoder, CompactSizeDecoder, CompactSizeDecoderError, + CompactSizeEncoder, Decodable, Decoder, Encodable, Encoder3, +}; + +use crate::prelude::Vec; + +/// The key of a key-value PSBT pair, in its raw byte form. +/// +/// - ` := ` +/// +/// We do not carry the `keylen` around, we just create the `VarInt` length when serializing and +/// deserializing. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Key { + /// The `keytype` of this PSBT map key (`keytype`). + pub type_value: u8, + /// The `keydata` itself in raw byte form. + #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::hex_bytes"))] + pub key: Vec, +} + +encoding::encoder_newtype! { + /// The encoder for the [`Transaction`] type. + pub struct KeyEncoder<'e>( + Encoder3< + CompactSizeEncoder, + CompactSizeEncoder, + BytesEncoder<'e>, + > + ); +} + +impl Encodable for Key { + type Encoder<'a> + = KeyEncoder<'a> + where + Self: 'a; + + fn encoder(&self) -> Self::Encoder<'_> { + let encoded_size = CompactSizeEncoder::encoded_size(self.type_value as usize); + KeyEncoder::new(Encoder3::new( + CompactSizeEncoder::new(self.key.len() + encoded_size), + CompactSizeEncoder::new(self.type_value as usize), + BytesEncoder::without_length_prefix(&self.key), + )) + } +} + +/// The decoder for the [`Key`] type. +pub struct KeyDecoder { + state: KeyDecoderState, +} + +impl Default for KeyDecoder { + fn default() -> Self { + Self::new() + } +} + +impl KeyDecoder { + /// Constructs a new [`KeyDecoder`]. + pub const fn new() -> Self { + Self { state: KeyDecoderState::KeyLength(ByteVecDecoder::new()) } + } +} + +/// An error consensus decoding a PSBT [`Key`] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct KeyDecoderError(KeyDecoderErrorInner); + +#[derive(Debug, Clone, PartialEq, Eq)] +enum KeyDecoderErrorInner { + /// Error while decoding the key length. + Length(ByteVecDecoderError), + /// Error while decoding the key type. + Type(CompactSizeDecoderError), + /// The keytype is not listed in BIP 174. + InvalidType, + /// Attempt to call `end()` before the key was complete. Holds + /// a description of the current state. + EarlyEnd(&'static str), +} + +impl alloc::fmt::Display for KeyDecoderError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + use KeyDecoderErrorInner as E; + + match self.0 { + E::Length(ref e) => write!(f, "key decoder error: {}", e), + E::Type(ref e) => write!(f, "key decoder error: {}", e), + E::InvalidType => write!(f, "type is not defined in BIP 174"), + E::EarlyEnd(s) => write!(f, "early end of key (still decoding {})", s), + } + } +} +/// The state of the key decoder. +pub enum KeyDecoderState { + /// Decoding the key length. + KeyLength(ByteVecDecoder), + /// Decoding the key type. + KeyType(Vec, CompactSizeDecoder), + /// Done decoding the [`Key`]. + Done(Key), + /// When `end()`ing a sub-decoder, encountered an error which prevented us + /// from constructing the next sub-decoder. + Errored, +} + +impl Decodable for Key { + type Decoder = KeyDecoder; + fn decoder() -> Self::Decoder { + KeyDecoder::new() + } +} + +impl Decoder for KeyDecoder { + type Output = Key; + type Error = KeyDecoderError; + + #[inline] + fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result { + use {KeyDecoderError as E, KeyDecoderErrorInner as Inner, KeyDecoderState as State}; + + loop { + match &mut self.state { + State::KeyLength(decoder) => { + if decoder.push_bytes(bytes).map_err(|e| E(Inner::Length(e)))? { + return Ok(true); + } + } + State::KeyType(key_as_bytes, decoder) => { + let key_as_bytes_slice = &mut key_as_bytes.as_slice(); + if decoder.push_bytes(key_as_bytes_slice).map_err(|e| E(Inner::Type(e)))? { + return Ok(true); + } + *key_as_bytes = (*key_as_bytes_slice).to_vec(); + }, + State::Done(..) => return Ok(false), + State::Errored => panic!("call to push_bytes() after decoder errored"), + } + + match core::mem::replace(&mut self.state, State::Errored) { + State::KeyLength(decoder) => { + let type_and_data = decoder.end().map_err(|e| E(Inner::Length(e)))?; + + self.state = State::KeyType(type_and_data, CompactSizeDecoder::new()); + } + State::KeyType(type_and_data, decoder) => { + let key_type = decoder.end().map_err(|e| E(Inner::Type(e)))?; + + let is_valid = matches!(key_type, 0x00..=0x18 | 0xFB | 0xFC); + + if !is_valid { + return Err(E(Inner::InvalidType)); + } + + let key_type_u8 = u8::try_from(key_type).expect("already validated"); + + let data = (*type_and_data).to_vec(); + + self.state = State::Done(Key { + type_value: key_type_u8, + key: data, + }); + }, + State::Done(..) => { + return Ok(false); + }, + State::Errored => panic!("call to push_bytes() after decoder errored"), + } + } + } + + #[inline] + fn end(self) -> Result { + use {KeyDecoderError as E, KeyDecoderErrorInner as Inner, KeyDecoderState as State}; + + match self.state { + State::KeyLength(_) => Err(E(Inner::EarlyEnd("length"))), + State::KeyType(..) => Err(E(Inner::EarlyEnd("type"))), + State::Done(key) => Ok(key), + State::Errored => panic!("call to end() after decoder errored"), + } + } + + #[inline] + fn read_limit(&self) -> usize { + use KeyDecoderState as State; + + match &self.state { + State::KeyLength(decoder) => decoder.read_limit(), + State::KeyType(bytes, _) => bytes.len(), + State::Done(_) => 0, + // `read_limit` is not documented to panic or return an error, so we + // return a dummy value if the decoder is in an error state. + State::Errored => 0, + } + } +} + +#[cfg(test)] +mod tests { + use super::{ + Key, KeyDecoder, Vec, + }; + use encoding::{Decodable, Decoder, Encodable, Encoder}; + + fn encode_key(key: Key) -> Vec { + let mut key_encoder = key.encoder(); + let mut advance = true; + let mut encoded = vec![]; + + while advance { + for &byte in key_encoder.current_chunk() { + encoded.push(byte); + } + advance = key_encoder.advance(); + } + + encoded + } + + #[test] + fn roundtrip() { + let original = Key { type_value: 0x06, key: vec![0x01, 0x02, 0x03, 0x04] }; + let encoded = encode_key(original.clone()); + let mut key_decoder = Key::decoder(); + let result = key_decoder.push_bytes(&mut encoded.as_slice()); + + assert!(!result.unwrap()); + + let decoded = key_decoder.end().unwrap(); + + assert_eq!(original, decoded); + } + + mod encode { + use super::*; + + #[test] + fn keytype_only() { + let key = Key { type_value: 0x00, key: vec![] }; + let encoded = encode_key(key); + + assert_eq!(&encoded.as_slice(), &[0x01, 0x00]); + } + + #[test] + fn key_with_keydata() { + let key = Key { type_value: 0x02, key: vec![0x01, 0x02, 0x03] }; + let encoded = encode_key(key); + + assert_eq!(encoded, vec![0x04, 0x02, 0x01, 0x02, 0x03]); + } + + #[test] + fn empty_keydata_all_valid_keytypes() { + let valid_types: Vec = (0x00..=0x18).chain([0xFB, 0xFC]).collect(); + + for type_value in valid_types { + let key = Key { type_value, key: vec![] }; + let encoded = encode_key(key); + assert_eq!(encoded.len(), 2); + assert_eq!(encoded[0], 1); + assert_eq!(encoded[1], type_value); + } + } + } + + mod decode { + use super::*; + + #[test] + fn keytype_only() { + let bytes: Vec = vec![0x01, 0x00]; + let mut key_decoder = Key::decoder(); + let _ = key_decoder.push_bytes(&mut bytes.as_slice()); + let key = key_decoder.end().unwrap(); + + assert_eq!(key.type_value, 0x00); + assert!(key.key.is_empty()); + } + + #[test] + fn key_with_keydata() { + let bytes = vec![0x04, 0x02, 0x01, 0x02, 0x03]; + let mut key_decoder = Key::decoder(); + let _ = key_decoder.push_bytes(&mut bytes.as_slice()); + let key = key_decoder.end().unwrap(); + + assert_eq!(key.type_value, 0x02); + assert_eq!(key.key, vec![0x01, 0x02, 0x03]); + } + + #[test] + fn read_limit_after_length_state() { + let decoder = Key::decoder(); + assert!(decoder.read_limit() > 0); + } + + #[test] + fn early_end_length() { + let decoder = KeyDecoder::new(); + let result = decoder.end(); + + assert!(result.is_err()); + let err_str = format!("{}", result.unwrap_err()); + assert_eq!(err_str, "early end of key (still decoding length)"); + } + + #[test] + fn early_end_type() { + let mut decoder = KeyDecoder::new(); + let mut bytes: &[u8] = &[0x01]; + let _ = decoder.push_bytes(&mut bytes); + // Provide a partial, larger than 1 byte compact size unsigned integer + let mut bytes: &[u8] = &[0xfd]; + let needs_more = decoder.push_bytes(&mut bytes).unwrap(); + assert!(needs_more); // needs_more should be true, because 0xfd requires 3 bytes + + let result = decoder.end(); + assert!(result.is_err()); + let err_str = format!("{}", result.unwrap_err()); + assert_eq!(err_str, "early end of key (still decoding type)"); + } + + #[test] + fn push_bytes_incremental_push() { + let mut decoder = KeyDecoder::new(); + + let full_bytes = vec![0x04, 0x02, 0x01, 0x02, 0x03]; + + for byte in full_bytes { + let mut slice: &[u8] = &[byte]; + let needs_more = decoder.push_bytes(&mut slice).unwrap(); + + assert!(needs_more || slice.is_empty()); + } + + let key = decoder.end().unwrap(); + assert_eq!(key.type_value, 0x02); + assert_eq!(key.key, vec![0x01, 0x02, 0x03]); + } + + #[test] + fn read_limit_after_done_state() { + let mut decoder = KeyDecoder::new(); + let mut bytes: &[u8] = &[0x01, 0x00]; + let _ = decoder.push_bytes(&mut bytes); + + assert_eq!(decoder.read_limit(), 0); + } + + #[test] + fn push_bytes_display_invalid_type_error() { + let mut decoder = KeyDecoder::new(); + let mut bytes: &[u8] = &[0x01, 0x50]; // Invalid type + let result = decoder.push_bytes(&mut bytes); + + let err = result.unwrap_err(); + assert_eq!(format!("{}", err), "type is not defined in BIP 174"); + } + + #[test] + fn empty_slice() { + let mut decoder = KeyDecoder::new(); + let mut empty: &[u8] = &[]; + + let result = decoder.push_bytes(&mut empty).unwrap(); + assert!(result); + } + + #[test] + fn push_bytes_error_with_invalid_types() { + for type_value in 0x19u8..=0xFA { + if type_value == 0xFB || type_value == 0xFC { + continue; + } + let bytes = vec![0x01, type_value]; + let mut key_decoder = Key::decoder(); + let result = key_decoder.push_bytes(&mut bytes.as_slice()); + assert!(result.is_err(), "type is not defined in BIP 174"); + } + } + + #[test] + #[should_panic(expected = "call to push_bytes() after decoder errored")] + fn push_bytes_after_invalid_type_panics() { + let mut decoder = KeyDecoder::new(); + let mut bytes: &[u8] = &[0x01, 0x50]; + + let result = decoder.push_bytes(&mut bytes); + + assert!(result.is_err()); + + let mut more: &[u8] = &[0x00]; + let _ = decoder.push_bytes(&mut more); + } + + #[test] + #[should_panic(expected = "call to end() after decoder errored")] + fn end_after_invalid_type_panics() { + let mut decoder = KeyDecoder::new(); + let mut bytes: &[u8] = &[0x01, 0x50]; + + let _ = decoder.push_bytes(&mut bytes); + + let _ = decoder.end(); + } + + #[test] + fn read_limit_after_invalid_type() { + let mut decoder = KeyDecoder::new(); + let mut bytes: &[u8] = &[0x01, 0x50]; + + let _ = decoder.push_bytes(&mut bytes); + + assert_eq!(decoder.read_limit(), 0); + } + + #[test] + fn read_limit_in_keyasbytes_state() { + let mut decoder = KeyDecoder::new(); + let mut bytes: &[u8] = &[0x03]; + let _ = decoder.push_bytes(&mut bytes); + + assert_eq!(decoder.read_limit(), 3); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 3173c0a..c7fc7d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,6 +43,7 @@ pub mod raw; pub mod serialize; pub mod v0; pub mod v2; +pub mod binary_encoding; mod version; use bitcoin::io; From 9e89dce870239569ef7c8286f88d21360683b7db Mon Sep 17 00:00:00 2001 From: nymius <155548262+nymius@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:21:04 -0300 Subject: [PATCH 03/11] build: update lock files --- Cargo-minimal.lock | 36 +++++++++++++++++++++++++++++++----- Cargo-recent.lock | 36 +++++++++++++++++++++++++++++++----- bitcoind-tests/Cargo.lock | 36 +++++++++++++++++++++++++++++++----- 3 files changed, 93 insertions(+), 15 deletions(-) diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index 4f1cae5..c8153d0 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -20,7 +20,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" dependencies = [ - "bitcoin-internals", + "bitcoin-internals 0.3.0", "bitcoin_hashes 0.14.0", ] @@ -45,16 +45,24 @@ dependencies = [ "base58ck", "base64", "bech32", - "bitcoin-internals", + "bitcoin-internals 0.3.0", "bitcoin-io", "bitcoin-units", "bitcoin_hashes 0.14.0", - "hex-conservative", + "hex-conservative 0.2.2", "hex_lit", "secp256k1", "serde", ] +[[package]] +name = "bitcoin-consensus-encoding" +version = "1.0.0-rc.3" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?branch=master#a0c63076791095b35e20162edbea2bd90268527d" +dependencies = [ + "bitcoin-internals 0.5.0", +] + [[package]] name = "bitcoin-internals" version = "0.3.0" @@ -64,6 +72,14 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoin-internals" +version = "0.5.0" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?branch=master#a0c63076791095b35e20162edbea2bd90268527d" +dependencies = [ + "hex-conservative 0.3.0", +] + [[package]] name = "bitcoin-io" version = "0.1.1" @@ -82,7 +98,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d437fd727271c866d6fd5e71eb2c886437d4c97f80d89246be3189b1da4e58b" dependencies = [ - "bitcoin-internals", + "bitcoin-internals 0.3.0", "serde", ] @@ -102,7 +118,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" dependencies = [ "bitcoin-io", - "hex-conservative", + "hex-conservative 0.2.2", "serde", ] @@ -138,6 +154,15 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "hex-conservative" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afe881d0527571892c4034822e59bb10c6c991cce6abe8199b6f5cf10766f55" +dependencies = [ + "arrayvec", +] + [[package]] name = "hex_lit" version = "0.1.1" @@ -187,6 +212,7 @@ version = "0.2.0" dependencies = [ "anyhow", "bitcoin", + "bitcoin-consensus-encoding", "miniscript", "serde", "serde_json", diff --git a/Cargo-recent.lock b/Cargo-recent.lock index ca71305..12e34e1 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -20,7 +20,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" dependencies = [ - "bitcoin-internals", + "bitcoin-internals 0.3.0", "bitcoin_hashes", ] @@ -45,16 +45,24 @@ dependencies = [ "base58ck", "base64", "bech32", - "bitcoin-internals", + "bitcoin-internals 0.3.0", "bitcoin-io", "bitcoin-units", "bitcoin_hashes", - "hex-conservative", + "hex-conservative 0.2.2", "hex_lit", "secp256k1", "serde", ] +[[package]] +name = "bitcoin-consensus-encoding" +version = "1.0.0-rc.3" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?branch=master#a0c63076791095b35e20162edbea2bd90268527d" +dependencies = [ + "bitcoin-internals 0.5.0", +] + [[package]] name = "bitcoin-internals" version = "0.3.0" @@ -64,6 +72,14 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoin-internals" +version = "0.5.0" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?branch=master#a0c63076791095b35e20162edbea2bd90268527d" +dependencies = [ + "hex-conservative 0.3.1", +] + [[package]] name = "bitcoin-io" version = "0.1.2" @@ -76,7 +92,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" dependencies = [ - "bitcoin-internals", + "bitcoin-internals 0.3.0", "serde", ] @@ -87,7 +103,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" dependencies = [ "bitcoin-io", - "hex-conservative", + "hex-conservative 0.2.2", "serde", ] @@ -132,6 +148,15 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "hex-conservative" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b9348ee0d8d4e3a894946c1ab104d08a2e44ca13656613afada8905ea609b6" +dependencies = [ + "arrayvec", +] + [[package]] name = "hex_lit" version = "0.1.1" @@ -190,6 +215,7 @@ version = "0.2.0" dependencies = [ "anyhow", "bitcoin", + "bitcoin-consensus-encoding", "miniscript", "serde", "serde_json", diff --git a/bitcoind-tests/Cargo.lock b/bitcoind-tests/Cargo.lock index 0e97396..3b6b81b 100644 --- a/bitcoind-tests/Cargo.lock +++ b/bitcoind-tests/Cargo.lock @@ -26,7 +26,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" dependencies = [ - "bitcoin-internals", + "bitcoin-internals 0.3.0", "bitcoin_hashes", ] @@ -57,16 +57,24 @@ dependencies = [ "base58ck", "base64 0.21.7", "bech32", - "bitcoin-internals", + "bitcoin-internals 0.3.0", "bitcoin-io", "bitcoin-units", "bitcoin_hashes", - "hex-conservative", + "hex-conservative 0.2.2", "hex_lit", "secp256k1", "serde", ] +[[package]] +name = "bitcoin-consensus-encoding" +version = "1.0.0-rc.3" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?branch=master#a0c63076791095b35e20162edbea2bd90268527d" +dependencies = [ + "bitcoin-internals 0.5.0", +] + [[package]] name = "bitcoin-internals" version = "0.3.0" @@ -76,6 +84,14 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoin-internals" +version = "0.5.0" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?branch=master#a0c63076791095b35e20162edbea2bd90268527d" +dependencies = [ + "hex-conservative 0.3.1", +] + [[package]] name = "bitcoin-io" version = "0.1.4" @@ -88,7 +104,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" dependencies = [ - "bitcoin-internals", + "bitcoin-internals 0.3.0", "serde", ] @@ -99,7 +115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" dependencies = [ "bitcoin-io", - "hex-conservative", + "hex-conservative 0.2.2", "serde", ] @@ -283,6 +299,15 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "hex-conservative" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b9348ee0d8d4e3a894946c1ab104d08a2e44ca13656613afada8905ea609b6" +dependencies = [ + "arrayvec", +] + [[package]] name = "hex_lit" version = "0.1.1" @@ -410,6 +435,7 @@ name = "psbt-v2" version = "0.2.0" dependencies = [ "bitcoin", + "bitcoin-consensus-encoding", "miniscript", ] From 78c5aa212757f40e5d267235973b569a161033f8 Mon Sep 17 00:00:00 2001 From: nymius <155548262+nymius@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:24:26 -0300 Subject: [PATCH 04/11] style(binary_encoding): apply cargo +nightly fmt --- src/binary_encoding.rs | 28 +++++++++------------------- src/lib.rs | 2 +- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/binary_encoding.rs b/src/binary_encoding.rs index f5e5d02..f281d2d 100644 --- a/src/binary_encoding.rs +++ b/src/binary_encoding.rs @@ -55,16 +55,12 @@ pub struct KeyDecoder { } impl Default for KeyDecoder { - fn default() -> Self { - Self::new() - } + fn default() -> Self { Self::new() } } impl KeyDecoder { /// Constructs a new [`KeyDecoder`]. - pub const fn new() -> Self { - Self { state: KeyDecoderState::KeyLength(ByteVecDecoder::new()) } - } + pub const fn new() -> Self { Self { state: KeyDecoderState::KeyLength(ByteVecDecoder::new()) } } } /// An error consensus decoding a PSBT [`Key`] @@ -111,9 +107,7 @@ pub enum KeyDecoderState { impl Decodable for Key { type Decoder = KeyDecoder; - fn decoder() -> Self::Decoder { - KeyDecoder::new() - } + fn decoder() -> Self::Decoder { KeyDecoder::new() } } impl Decoder for KeyDecoder { @@ -137,7 +131,7 @@ impl Decoder for KeyDecoder { return Ok(true); } *key_as_bytes = (*key_as_bytes_slice).to_vec(); - }, + } State::Done(..) => return Ok(false), State::Errored => panic!("call to push_bytes() after decoder errored"), } @@ -161,14 +155,11 @@ impl Decoder for KeyDecoder { let data = (*type_and_data).to_vec(); - self.state = State::Done(Key { - type_value: key_type_u8, - key: data, - }); - }, + self.state = State::Done(Key { type_value: key_type_u8, key: data }); + } State::Done(..) => { return Ok(false); - }, + } State::Errored => panic!("call to push_bytes() after decoder errored"), } } @@ -203,11 +194,10 @@ impl Decoder for KeyDecoder { #[cfg(test)] mod tests { - use super::{ - Key, KeyDecoder, Vec, - }; use encoding::{Decodable, Decoder, Encodable, Encoder}; + use super::{Key, KeyDecoder, Vec}; + fn encode_key(key: Key) -> Vec { let mut key_encoder = key.encoder(); let mut advance = true; diff --git a/src/lib.rs b/src/lib.rs index c7fc7d0..4c6c103 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,11 +39,11 @@ mod macros; mod serde_utils; mod sighash_type; +pub mod binary_encoding; pub mod raw; pub mod serialize; pub mod v0; pub mod v2; -pub mod binary_encoding; mod version; use bitcoin::io; From c5ee43d3230484b8b204aabba5afe7f4b6bbc274 Mon Sep 17 00:00:00 2001 From: nymius <155548262+nymius@users.noreply.github.com> Date: Wed, 11 Feb 2026 21:11:18 -0300 Subject: [PATCH 05/11] refactor: change type_value type from u8 to u64 --- src/binary_encoding.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/binary_encoding.rs b/src/binary_encoding.rs index f281d2d..23cea8c 100644 --- a/src/binary_encoding.rs +++ b/src/binary_encoding.rs @@ -15,8 +15,8 @@ use crate::prelude::Vec; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Key { - /// The `keytype` of this PSBT map key (`keytype`). - pub type_value: u8, + /// The `keytype` of this PSBT map key. + pub type_value: u64, /// The `keydata` itself in raw byte form. #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::hex_bytes"))] pub key: Vec, @@ -151,11 +151,11 @@ impl Decoder for KeyDecoder { return Err(E(Inner::InvalidType)); } - let key_type_u8 = u8::try_from(key_type).expect("already validated"); + let key_type_u64 = u64::try_from(key_type).expect("the maximum encodable value in a compact size unsigned integer fits within u64"); let data = (*type_and_data).to_vec(); - self.state = State::Done(Key { type_value: key_type_u8, key: data }); + self.state = State::Done(Key { type_value: key_type_u64, key: data }); } State::Done(..) => { return Ok(false); From 99474024d624267e4c10a761845d6886da4b55f0 Mon Sep 17 00:00:00 2001 From: nymius <155548262+nymius@users.noreply.github.com> Date: Wed, 11 Feb 2026 21:17:01 -0300 Subject: [PATCH 06/11] fix: remove keytype checks BIP 174 states that all unknown keytypes should be ignored. Previously the code threw an error when the keytype wasn't defined in BIP 174 table. To avoid going against the specification this commit removes that check. --- src/binary_encoding.rs | 71 +----------------------------------------- 1 file changed, 1 insertion(+), 70 deletions(-) diff --git a/src/binary_encoding.rs b/src/binary_encoding.rs index 23cea8c..d261dd6 100644 --- a/src/binary_encoding.rs +++ b/src/binary_encoding.rs @@ -73,8 +73,6 @@ enum KeyDecoderErrorInner { Length(ByteVecDecoderError), /// Error while decoding the key type. Type(CompactSizeDecoderError), - /// The keytype is not listed in BIP 174. - InvalidType, /// Attempt to call `end()` before the key was complete. Holds /// a description of the current state. EarlyEnd(&'static str), @@ -87,11 +85,11 @@ impl alloc::fmt::Display for KeyDecoderError { match self.0 { E::Length(ref e) => write!(f, "key decoder error: {}", e), E::Type(ref e) => write!(f, "key decoder error: {}", e), - E::InvalidType => write!(f, "type is not defined in BIP 174"), E::EarlyEnd(s) => write!(f, "early end of key (still decoding {})", s), } } } + /// The state of the key decoder. pub enum KeyDecoderState { /// Decoding the key length. @@ -145,12 +143,6 @@ impl Decoder for KeyDecoder { State::KeyType(type_and_data, decoder) => { let key_type = decoder.end().map_err(|e| E(Inner::Type(e)))?; - let is_valid = matches!(key_type, 0x00..=0x18 | 0xFB | 0xFC); - - if !is_valid { - return Err(E(Inner::InvalidType)); - } - let key_type_u64 = u64::try_from(key_type).expect("the maximum encodable value in a compact size unsigned integer fits within u64"); let data = (*type_and_data).to_vec(); @@ -245,19 +237,6 @@ mod tests { assert_eq!(encoded, vec![0x04, 0x02, 0x01, 0x02, 0x03]); } - - #[test] - fn empty_keydata_all_valid_keytypes() { - let valid_types: Vec = (0x00..=0x18).chain([0xFB, 0xFC]).collect(); - - for type_value in valid_types { - let key = Key { type_value, key: vec![] }; - let encoded = encode_key(key); - assert_eq!(encoded.len(), 2); - assert_eq!(encoded[0], 1); - assert_eq!(encoded[1], type_value); - } - } } mod decode { @@ -344,16 +323,6 @@ mod tests { assert_eq!(decoder.read_limit(), 0); } - #[test] - fn push_bytes_display_invalid_type_error() { - let mut decoder = KeyDecoder::new(); - let mut bytes: &[u8] = &[0x01, 0x50]; // Invalid type - let result = decoder.push_bytes(&mut bytes); - - let err = result.unwrap_err(); - assert_eq!(format!("{}", err), "type is not defined in BIP 174"); - } - #[test] fn empty_slice() { let mut decoder = KeyDecoder::new(); @@ -363,44 +332,6 @@ mod tests { assert!(result); } - #[test] - fn push_bytes_error_with_invalid_types() { - for type_value in 0x19u8..=0xFA { - if type_value == 0xFB || type_value == 0xFC { - continue; - } - let bytes = vec![0x01, type_value]; - let mut key_decoder = Key::decoder(); - let result = key_decoder.push_bytes(&mut bytes.as_slice()); - assert!(result.is_err(), "type is not defined in BIP 174"); - } - } - - #[test] - #[should_panic(expected = "call to push_bytes() after decoder errored")] - fn push_bytes_after_invalid_type_panics() { - let mut decoder = KeyDecoder::new(); - let mut bytes: &[u8] = &[0x01, 0x50]; - - let result = decoder.push_bytes(&mut bytes); - - assert!(result.is_err()); - - let mut more: &[u8] = &[0x00]; - let _ = decoder.push_bytes(&mut more); - } - - #[test] - #[should_panic(expected = "call to end() after decoder errored")] - fn end_after_invalid_type_panics() { - let mut decoder = KeyDecoder::new(); - let mut bytes: &[u8] = &[0x01, 0x50]; - - let _ = decoder.push_bytes(&mut bytes); - - let _ = decoder.end(); - } - #[test] fn read_limit_after_invalid_type() { let mut decoder = KeyDecoder::new(); From 90f68c0ea6ea5c64931db307fc867f128beb5d3e Mon Sep 17 00:00:00 2001 From: nymius <155548262+nymius@users.noreply.github.com> Date: Wed, 11 Feb 2026 21:20:55 -0300 Subject: [PATCH 07/11] test: be explicit in test names --- src/binary_encoding.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/binary_encoding.rs b/src/binary_encoding.rs index d261dd6..b442110 100644 --- a/src/binary_encoding.rs +++ b/src/binary_encoding.rs @@ -271,7 +271,7 @@ mod tests { } #[test] - fn early_end_length() { + fn early_end_while_decoding_length() { let decoder = KeyDecoder::new(); let result = decoder.end(); @@ -281,7 +281,7 @@ mod tests { } #[test] - fn early_end_type() { + fn early_end_while_decoding_type() { let mut decoder = KeyDecoder::new(); let mut bytes: &[u8] = &[0x01]; let _ = decoder.push_bytes(&mut bytes); @@ -333,7 +333,7 @@ mod tests { } #[test] - fn read_limit_after_invalid_type() { + fn read_limit_in_keylength_state() { let mut decoder = KeyDecoder::new(); let mut bytes: &[u8] = &[0x01, 0x50]; @@ -343,7 +343,7 @@ mod tests { } #[test] - fn read_limit_in_keyasbytes_state() { + fn read_limit_in_keytype_state() { let mut decoder = KeyDecoder::new(); let mut bytes: &[u8] = &[0x03]; let _ = decoder.push_bytes(&mut bytes); From 870e2bcb6ab74383f37eccbf54b6f65d9198f42d Mon Sep 17 00:00:00 2001 From: nymius <155548262+nymius@users.noreply.github.com> Date: Wed, 11 Feb 2026 21:24:25 -0300 Subject: [PATCH 08/11] test: keep KeyDecoder in KeyLength state for read_limit_in_keylength_state test --- src/binary_encoding.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/binary_encoding.rs b/src/binary_encoding.rs index b442110..8fa6dc4 100644 --- a/src/binary_encoding.rs +++ b/src/binary_encoding.rs @@ -335,11 +335,10 @@ mod tests { #[test] fn read_limit_in_keylength_state() { let mut decoder = KeyDecoder::new(); - let mut bytes: &[u8] = &[0x01, 0x50]; - + let mut bytes: &[u8] = &[0x03]; let _ = decoder.push_bytes(&mut bytes); - assert_eq!(decoder.read_limit(), 0); + assert_eq!(decoder.read_limit(), 3); } #[test] From b60d0929b6e850b31e9103b246b94be9d8b9ef87 Mon Sep 17 00:00:00 2001 From: nymius <155548262+nymius@users.noreply.github.com> Date: Wed, 11 Feb 2026 21:25:43 -0300 Subject: [PATCH 09/11] test: keep KeyDecoder in KeyType state for read_limit_in_keytype_state test --- src/binary_encoding.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/binary_encoding.rs b/src/binary_encoding.rs index 8fa6dc4..d3aee28 100644 --- a/src/binary_encoding.rs +++ b/src/binary_encoding.rs @@ -347,7 +347,12 @@ mod tests { let mut bytes: &[u8] = &[0x03]; let _ = decoder.push_bytes(&mut bytes); - assert_eq!(decoder.read_limit(), 3); + let mut bytes: &[u8] = &[0xfd]; + let needs_more = decoder.push_bytes(&mut bytes).unwrap(); + assert!(needs_more); // needs_more should be true, because 0xfd requires 3 bytes + // (prefix byte + 2 data bytes) + + assert_eq!(decoder.read_limit(), 2); } } } From d12934ece3adfe8ed0f85f793409a4c0a43f5341 Mon Sep 17 00:00:00 2001 From: nymius <155548262+nymius@users.noreply.github.com> Date: Wed, 11 Feb 2026 21:26:28 -0300 Subject: [PATCH 10/11] test: rename `read_limit_after_done_state` test to `read_limit_in_done_state` --- src/binary_encoding.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/binary_encoding.rs b/src/binary_encoding.rs index d3aee28..6360197 100644 --- a/src/binary_encoding.rs +++ b/src/binary_encoding.rs @@ -314,15 +314,6 @@ mod tests { assert_eq!(key.key, vec![0x01, 0x02, 0x03]); } - #[test] - fn read_limit_after_done_state() { - let mut decoder = KeyDecoder::new(); - let mut bytes: &[u8] = &[0x01, 0x00]; - let _ = decoder.push_bytes(&mut bytes); - - assert_eq!(decoder.read_limit(), 0); - } - #[test] fn empty_slice() { let mut decoder = KeyDecoder::new(); @@ -354,5 +345,14 @@ mod tests { assert_eq!(decoder.read_limit(), 2); } + + #[test] + fn read_limit_in_done_state() { + let mut decoder = KeyDecoder::new(); + let mut bytes: &[u8] = &[0x01, 0x00]; + let _ = decoder.push_bytes(&mut bytes); + + assert_eq!(decoder.read_limit(), 0); + } } } From 13e01b31ee482e931dddcb14eadecc8fb7475c0c Mon Sep 17 00:00:00 2001 From: nymius <155548262+nymius@users.noreply.github.com> Date: Wed, 11 Feb 2026 21:27:56 -0300 Subject: [PATCH 11/11] build: update Cargo-minimal.lock with just update-lock-files --- Cargo-minimal.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index c8153d0..1a8caf0 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -58,7 +58,7 @@ dependencies = [ [[package]] name = "bitcoin-consensus-encoding" version = "1.0.0-rc.3" -source = "git+https://github.com/rust-bitcoin/rust-bitcoin?branch=master#a0c63076791095b35e20162edbea2bd90268527d" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?branch=master#9ad3bbdb9202bc46a6047b4a684f813298c2396a" dependencies = [ "bitcoin-internals 0.5.0", ] @@ -75,7 +75,7 @@ dependencies = [ [[package]] name = "bitcoin-internals" version = "0.5.0" -source = "git+https://github.com/rust-bitcoin/rust-bitcoin?branch=master#a0c63076791095b35e20162edbea2bd90268527d" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?branch=master#9ad3bbdb9202bc46a6047b4a684f813298c2396a" dependencies = [ "hex-conservative 0.3.0", ]