diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index 4f1cae5..1a8caf0 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#9ad3bbdb9202bc46a6047b4a684f813298c2396a" +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#9ad3bbdb9202bc46a6047b4a684f813298c2396a" +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/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 } 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", ] diff --git a/src/binary_encoding.rs b/src/binary_encoding.rs new file mode 100644 index 0000000..6360197 --- /dev/null +++ b/src/binary_encoding.rs @@ -0,0 +1,358 @@ +//! 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. + 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, +} + +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), + /// 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::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 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_u64, 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 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; + 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]); + } + } + + 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_while_decoding_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_while_decoding_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 empty_slice() { + let mut decoder = KeyDecoder::new(); + let mut empty: &[u8] = &[]; + + let result = decoder.push_bytes(&mut empty).unwrap(); + assert!(result); + } + + #[test] + fn read_limit_in_keylength_state() { + let mut decoder = KeyDecoder::new(); + let mut bytes: &[u8] = &[0x03]; + let _ = decoder.push_bytes(&mut bytes); + + assert_eq!(decoder.read_limit(), 3); + } + + #[test] + fn read_limit_in_keytype_state() { + let mut decoder = KeyDecoder::new(); + let mut bytes: &[u8] = &[0x03]; + let _ = decoder.push_bytes(&mut bytes); + + 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); + } + + #[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); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 3173c0a..4c6c103 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,6 +39,7 @@ mod macros; mod serde_utils; mod sighash_type; +pub mod binary_encoding; pub mod raw; pub mod serialize; pub mod v0;