diff --git a/Cargo-recent.lock b/Cargo-recent.lock index f83c2120..54c72aa2 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -22,12 +22,11 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "base58ck" -version = "0.1.0" +version = "0.1.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +checksum = "365c0acd5b2e8dd0111a46c4faea83fb3cfb6e39a49a7c73a06e090db7b2eff0" dependencies = [ - "bitcoin-internals 0.3.0", - "bitcoin_hashes 0.14.0", + "bitcoin_hashes 0.14.101", ] [[package]] @@ -59,18 +58,18 @@ dependencies = [ [[package]] name = "bitcoin" -version = "0.32.6" +version = "0.32.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8929a18b8e33ea6b3c09297b687baaa71fb1b97353243a3f1029fad5c59c5b" +checksum = "bb0ce8bd5baaa0d303a19915a6d93afed161f528654e42da2a7a97d05c59499a" dependencies = [ "base58ck", "base64 0.21.7", "bech32", - "bitcoin-internals 0.3.0", + "bitcoin-consensus-encoding", "bitcoin-io", "bitcoin-units", - "bitcoin_hashes 0.14.0", - "hex-conservative 0.2.1", + "bitcoin_hashes 0.14.101", + "hex-conservative 0.2.2", "hex_lit", "secp256k1", "serde", @@ -78,33 +77,29 @@ dependencies = [ [[package]] name = "bitcoin-consensus-encoding" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2d6094e2a1ba3c93b5a596fe5a10d1a10c3c6e06785cde89f693a044c01aa40" -dependencies = [ - "bitcoin-internals 0.5.0", -] - -[[package]] -name = "bitcoin-internals" -version = "0.3.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" +checksum = "207311705279250ba465076a1bac4b1ac982855fff73fc5f67e22158ac58cdc9" dependencies = [ + "bitcoin-internals", + "hex-conservative 1.1.0", "serde", ] [[package]] name = "bitcoin-internals" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a30a22d1f112dde8e16be7b45c63645dc165cef254f835b3e1e9553e485cfa64" +checksum = "d573f4cf32996a8dce612e4348cece65a241f1882ed594047c9ba348e8869fa5" [[package]] name = "bitcoin-io" -version = "0.1.3" +version = "0.1.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" +checksum = "bb5de036369d1ac59d3c1819ebc4d850f89466f5401c571a285b6ed564a4cb78" +dependencies = [ + "bitcoin-consensus-encoding", +] [[package]] name = "bitcoin-private" @@ -114,33 +109,33 @@ checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57" [[package]] name = "bitcoin-units" -version = "0.1.2" +version = "0.1.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" +checksum = "9cb95693f371d089a4b5b6fc41c6f3ea6e01ee8c15388335dfac8ea685173b51" dependencies = [ - "bitcoin-internals 0.3.0", + "bitcoin-consensus-encoding", "serde", ] [[package]] name = "bitcoin_hashes" -version = "0.14.0" +version = "0.14.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" dependencies = [ "bitcoin-io", - "hex-conservative 0.2.1", + "hex-conservative 0.2.2", "serde", ] [[package]] name = "bitcoin_hashes" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f70c29ac06e7effa19682e91318deae86bdb46c4fd1bbd0f12fd196ff427ab0" +checksum = "a67800fcf7f3ca52f7796d4466948a424326b225950c79d1e76d0458760bb25d" dependencies = [ "bitcoin-consensus-encoding", - "bitcoin-internals 0.5.0", + "bitcoin-internals", "hex-conservative 1.1.0", "serde", ] @@ -230,8 +225,9 @@ dependencies = [ "bech32", "bincode", "bitcoin", - "bitcoin-internals 0.5.0", - "bitcoin_hashes 1.0.0", + "bitcoin-consensus-encoding", + "bitcoin-internals", + "bitcoin_hashes 1.1.0", "getrandom 0.2.16", "hex-conservative 1.1.0", "rand", @@ -313,9 +309,9 @@ dependencies = [ [[package]] name = "hex-conservative" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" dependencies = [ "arrayvec", ] @@ -512,7 +508,7 @@ version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "bitcoin_hashes 0.14.0", + "bitcoin_hashes 0.14.101", "rand", "secp256k1-sys", "serde", diff --git a/Cargo.toml b/Cargo.toml index 256c2c16..5850b505 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,9 +29,10 @@ base64 = ["bitcoin/base64"] [dependencies] bech32 = "0.11.0" -bitcoin = "0.32.2" -hashes = { package = "bitcoin_hashes", version = "1.0", features = [ "hex" ] } -internals = { package = "bitcoin-internals", version = "0.5" } +bitcoin = { version = "0.32.102", default-features = false, features = [ "encoding", "std" ] } +encoding = { package = "bitcoin-consensus-encoding", version = "1.1.0", default-features = false, features = [ "hex", "std" ] } +hashes = { package = "bitcoin_hashes", version = "1.1", features = [ "hex" ] } +internals = { package = "bitcoin-internals", version = "0.6" } secp256k1-zkp = { version = "0.11.0", features = ["global-context", "hashes"] } # Used for ContractHash::from_json_contract. @@ -73,6 +74,7 @@ match_bool = "allow" # Adds extra indentation and LOC. match_same_arms = "allow" # Collapses things that are conceptually unrelated to each other. must_use_candidate = "allow" # Useful for audit but many false positives. similar_names = "allow" # Too many (subjectively) false positives. +struct_field_names = "allow" # Dumb # Exhaustive list of pedantic clippy lints assigning_clones = "warn" bool_to_int_with_if = "warn" @@ -167,7 +169,6 @@ stable_sort_primitive = "warn" str_split_at_newline = "warn" string_add_assign = "warn" struct_excessive_bools = "warn" -struct_field_names = "warn" too_many_lines = "allow" # FIXME 14 triggers for this lint; probably most should be fixed transmute_ptr_to_ptr = "warn" trivially_copy_pass_by_ref = "warn" @@ -191,7 +192,6 @@ zero_sized_map_values = "warn" [package.metadata.rbmt.lint] allowed_duplicates = [ "bitcoin_hashes", - "bitcoin-internals", "hex-conservative", ] diff --git a/elementsd-tests/src/taproot.rs b/elementsd-tests/src/taproot.rs index 0bfdc0c9..364c7230 100644 --- a/elementsd-tests/src/taproot.rs +++ b/elementsd-tests/src/taproot.rs @@ -223,7 +223,7 @@ fn taproot_spend_test( hash_ty: sighash_ty, }; - tx.input[0].witness.script_witness = vec![schnorr_sig.to_vec()]; + tx.input[0].witness.script_witness.push(schnorr_sig.to_vec()); } else { // script spend // try spending using leaf1 @@ -250,11 +250,9 @@ fn taproot_spend_test( hash_ty: sighash_ty, }; - tx.input[0].witness.script_witness = vec![ - schnorr_sig.to_vec(), // witness - script_ver.0.into_bytes(), // leaf script - ctrl_block.serialize(), // control block - ]; + tx.input[0].witness.script_witness.push(schnorr_sig.to_vec()); // witness + tx.input[0].witness.script_witness.push(script_ver.0.into_bytes()); // leaf script + tx.input[0].witness.script_witness.push(ctrl_block.serialize()); // control block } let tx_hex = serialize_hex(&tx); diff --git a/examples/raw_blind.rs b/examples/raw_blind.rs index c45be283..18a0e974 100644 --- a/examples/raw_blind.rs +++ b/examples/raw_blind.rs @@ -305,15 +305,13 @@ fn main() { // Finalize(TODO in miniscript) pset.inputs_mut()[0].partial_sigs.clear(); - pset.inputs_mut()[0].final_script_witness = Some(vec![ - inp0_sig, - inp0_pk.to_bytes(), - ]); + let wit = pset.inputs_mut()[0].final_script_witness.insert(elements::Witness::new()); + wit.push(inp0_sig); + wit.push(inp0_pk.to_bytes()); pset.inputs_mut()[1].partial_sigs.clear(); - pset.inputs_mut()[1].final_script_witness = Some(vec![ - inp1_sig, - inp1_pk.to_bytes(), - ]); + let wit = pset.inputs_mut()[1].final_script_witness.insert(elements::Witness::new()); + wit.push(inp1_sig); + wit.push(inp1_pk.to_bytes()); assert_eq!(pset, deser_pset(&tests["finalized"])); // Extracted tx diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 14d0784b..5bbfee40 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -33,6 +33,13 @@ allowed_duplicates = [ "wasi", ] +[[bin]] +name = "decode_equiv_transaction" +path = "fuzz_targets/decode_equiv_transaction.rs" +test = false +doc = false +bench = false + [[bin]] name = "deserialize_block" path = "fuzz_targets/deserialize_block.rs" diff --git a/fuzz/fuzz_targets/decode_equiv_transaction.rs b/fuzz/fuzz_targets/decode_equiv_transaction.rs new file mode 100644 index 00000000..684a62b9 --- /dev/null +++ b/fuzz/fuzz_targets/decode_equiv_transaction.rs @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Fuzz test for equivalent decoding between the legacy and consensus-encoding decoding schemes. + +#![cfg_attr(fuzzing, no_main)] +#![cfg_attr(not(fuzzing), allow(unused))] +use libfuzzer_sys::fuzz_target; + +use elements::bitcoin::hex::DisplayHex as _; + +type Target = elements::Transaction; + +#[cfg(not(fuzzing))] +fn main() {} + +struct DisplayError<'e>(&'e dyn std::error::Error); + +impl core::fmt::Display for DisplayError<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "{}", self.0)?; + let mut e = self.0; + while let Some(source) = e.source() { + writeln!(f, "caused by {}", source)?; + e = source; + } + Ok(()) + } +} + +fn do_test(data: &[u8]) { + let old: Result = elements::encode::deserialize(data); + let new: Result = elements::encoding::decode_from_slice(data); + + match (old, new) { + (Err(_), Err(_)) => {}, + (Err(e), Ok(new)) => { + let e = DisplayError(&e); + panic!("Decodable trait failed with {e}; Decode parsed {:?}", new); + } + (Ok(old), Err(e)) => { + let e = DisplayError(&e); + panic!("Decode trait failed with {e}; Decodable parsed {:?}", old); + } + (Ok(old), Ok(new)) => { + assert_eq!( + old, new, + "Decodable (left) did not match Decode (right)", + ); + + let reser_old = elements::encode::serialize(&old); + let reser_new = elements::encoding::encode_to_vec(&new); + assert_eq!( + reser_old, reser_new, + "Encodable (left) did not match Encode (right)\nOld hex: {}\nNew hex: {}\nTransaction: {:?}", + reser_old.as_hex(), + reser_new.as_hex(), + old, + ); + } + } +} + +fuzz_target!(|data: &[u8]| { + do_test(data); +}); + +#[cfg(test)] +mod tests { + #[test] + fn duplicate_crash() { + let v = elements::hex::decode_to_vec("abcd"); + super::do_test(&v); + } +} diff --git a/rustfmt.toml b/rustfmt.toml index 09b6229f..e9b78f17 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -2,6 +2,9 @@ ignore = [ "/", "!/src/lib.rs", "!/src/confidential/*.rs", + "!/src/transaction/decoders.rs", + "!/src/transaction/encoders.rs", + "!/src/transaction/pegin_witness.rs" ] hard_tabs = false tab_spaces = 4 diff --git a/src/block.rs b/src/block.rs index f022f1ae..c2153b33 100644 --- a/src/block.rs +++ b/src/block.rs @@ -360,7 +360,6 @@ pub struct Block { /// Complete list of transaction in the block pub txdata: Vec, } -serde_struct_impl!(Block, header, txdata); impl_consensus_encoding!(Block, header, txdata); impl Block { diff --git a/src/confidential/asset.rs b/src/confidential/asset.rs index d419ebde..5636b4bf 100644 --- a/src/confidential/asset.rs +++ b/src/confidential/asset.rs @@ -10,7 +10,9 @@ use secp256k1_zkp::{self, Generator, Secp256k1, Signing, Tweak, ZERO_TWEAK}; #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use super::CommitmentEncoder; use crate::encode::{self, Decodable, Encodable}; +use crate::encoding; use crate::issuance::AssetId; type ExplicitInner = AssetId; @@ -18,6 +20,7 @@ type ConfInner = Generator; const EXPLICIT_LEN: usize = 32; const CONFIDENTIAL_LEN: usize = 33; +const CONFIDENTIAL_LEN_LESS_PREFIX: usize = CONFIDENTIAL_LEN - 1; const CONF_PREFIX_1: u8 = 0x0a; const CONF_PREFIX_2: u8 = 0x0b; @@ -344,3 +347,112 @@ impl<'de> Deserialize<'de> for BlindingFactor { } } } + +encoding::encoder_newtype_exact! { + /// Encoder for the [`Asset`] type. + #[derive(Clone, Debug)] + pub struct Encoder<'e>(CommitmentEncoder<'e>); +} + +impl encoding::Encode for Asset { + type Encoder<'e> = Encoder<'e>; + + fn encoder(&self) -> Self::Encoder<'_> { + Encoder::new(match *self { + Self::Null => CommitmentEncoder::Null(0), + Self::Explicit(ref id) => CommitmentEncoder::Explicit32(Some(1), id.as_byte_array()), + Self::Confidential(ref gen) => CommitmentEncoder::Explicit33(gen.serialize()), + }) + } +} + +decoder_state_machine! { + /// A decoder for the [`Asset`] type. + pub struct Decoder(enum DecoderInner { + Done(Asset), + Errored, + DecodePrefix { + decoder: encoding::ArrayDecoder<1>, + => transition_decode_prefix(prefix, ...) -> Result { + match prefix { + [0] => Ok(DecoderInner::Done(Asset::Null)), + [1] => { + Ok(DecoderInner::DecodeExplicit { decoder: encoding::ArrayDecoder::default() }) + }, + [prefix @ (CONF_PREFIX_1 | CONF_PREFIX_2)] => { + Ok(DecoderInner::DecodeConfidential { decoder: encoding::ArrayDecoder::default(), prefix }) + }, + [prefix] => Err(DecoderErrorInner::InvalidConfidentialPrefix { prefix }) + } + } + }, + DecodeExplicit { + decoder: encoding::ArrayDecoder + => transition_decode_explicit(bytes, ...) -> Result { + Ok(DecoderInner::Done(Asset::Explicit(AssetId::from_byte_array(bytes)))) + } + }, + DecodeConfidential { + decoder: encoding::ArrayDecoder, + prefix: u8 + => transition_decode_confidential(x_coord, ...) -> Result { + let mut bytes = [0; CONFIDENTIAL_LEN]; + bytes[0] = prefix; + bytes[1..].copy_from_slice(&x_coord); + let gen = ConfInner::from_slice(&bytes) + .map_err(DecoderErrorInner::InvalidCommitment)?; + Ok(DecoderInner::Done(Asset::Confidential(gen))) + } + }, + }); + + /// A decoder error for the [`Asset`] type. + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct DecoderError(enum DecoderErrorInner { + [macro-inserted decoder variants] + /// Confidential prefix was not one of the two allowable values. + InvalidConfidentialPrefix { + prefix: u8, + }, + /// Malformed confidential commitment. + InvalidCommitment(secp256k1_zkp::Error), + }); +} + +impl fmt::Display for DecoderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use DecoderErrorInner as Inner; + match self.0 { + Inner::DecodePrefix(_) => f.write_str("failed to decode prefix"), + Inner::DecodeExplicit(_) => f.write_str("failed to decode explicit value"), + Inner::DecodeConfidential(_) => f.write_str("failed to decode confidential value"), + Inner::InvalidConfidentialPrefix { prefix, .. } => { + write!( + f, + "confidential prefix 0x{:02x} was not one of 0, 1, 0x{:02x} or 0x{:02x}", + prefix, CONF_PREFIX_1, CONF_PREFIX_2, + ) + } + Inner::InvalidCommitment(_) => f.write_str("failed to parse confidential commitment"), + } + } +} + +impl std::error::Error for DecoderError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use DecoderErrorInner as Inner; + match self.0 { + Inner::DecodePrefix(ref e) => Some(e), + Inner::DecodeExplicit(ref e) => Some(e), + Inner::DecodeConfidential(ref e) => Some(e), + Inner::InvalidConfidentialPrefix { .. } => None, + Inner::InvalidCommitment(ref e) => Some(e), + } + } +} + +impl Default for Decoder { + fn default() -> Self { + Self(DecoderInner::DecodePrefix { decoder: encoding::ArrayDecoder::default() }) + } +} diff --git a/src/confidential/mod.rs b/src/confidential/mod.rs index b6dbe807..22fe9542 100644 --- a/src/confidential/mod.rs +++ b/src/confidential/mod.rs @@ -25,17 +25,120 @@ mod range_proof; mod surjection_proof; mod value; -use core::fmt; +use core::{fmt, slice}; use secp256k1_zkp; -pub use self::asset::{Asset, BlindingFactor as AssetBlindingFactor}; -pub use self::nonce::Nonce; -pub use self::range_proof::RangeProof; -pub use self::surjection_proof::SurjectionProof; -pub use self::value::{BlindingFactor as ValueBlindingFactor, Value}; -use crate::encode; +pub use self::asset::{ + Asset, BlindingFactor as AssetBlindingFactor, Decoder as AssetDecoder, + DecoderError as AssetDecoderError, Encoder as AssetEncoder, +}; +pub use self::nonce::{ + Decoder as NonceDecoder, DecoderError as NonceDecoderError, Encoder as NonceEncoder, Nonce, +}; +pub use self::range_proof::{ + Decoder as RangeProofDecoder, DecoderError as RangeProofDecoderError, + Encoder as RangeProofEncoder, RangeProof, +}; +pub use self::surjection_proof::{ + Decoder as SurjectionProofDecoder, DecoderError as SurjectionProofDecoderError, + Encoder as SurjectionProofEncoder, SurjectionProof, +}; +pub use self::value::{ + BlindingFactor as ValueBlindingFactor, Decoder as ValueDecoder, + DecoderError as ValueDecoderError, Encoder as ValueEncoder, Value, +}; use crate::issuance::AssetId; +use crate::{encode, encoding}; + +#[derive(Clone, Debug)] +enum CommitmentEncoder<'e> { + Null(u8), + Explicit8(Option, [u8; 8]), + Explicit32(Option, &'e [u8; 32]), + Explicit33([u8; 33]), +} + +impl encoding::Encoder for CommitmentEncoder<'_> { + fn current_chunk(&self) -> &[u8] { + match *self { + Self::Null(ref prefix) => slice::from_ref(prefix), + Self::Explicit8(ref prefix, ref arr) => prefix.as_ref().map_or(arr, slice::from_ref), + Self::Explicit32(ref prefix, arr) => prefix.as_ref().map_or(arr, slice::from_ref), + Self::Explicit33(ref arr) => arr, + } + } + + fn advance(&mut self) -> encoding::EncoderStatus { + match *self { + Self::Explicit8(ref mut prefix @ Some(_), _) + | Self::Explicit32(ref mut prefix @ Some(_), _) => { + *prefix = None; + encoding::EncoderStatus::HasMore + } + _ => encoding::EncoderStatus::Finished, + } + } +} + +impl encoding::ExactSizeEncoder for CommitmentEncoder<'_> { + fn len(&self) -> usize { + match *self { + Self::Null(_) => 1, + Self::Explicit8(Some(_), _) => 9, + Self::Explicit8(None, _) => 8, + Self::Explicit32(Some(_), _) => 33, + Self::Explicit32(None, _) => 32, + Self::Explicit33(_) => 33, + } + } +} + +/// Because the rust-secp256k1-zkp proof types have no `as_bytes()` method, we need +/// to serialize them to a byte vector before encoding them. +/// +/// This encoder accomplishes that -- this situation never happens in rust-bitcoin +/// so there is no "owned bytes encoder" shipped with bitcoin-consensus-encoding. +#[derive(Clone, Debug)] +struct PrefixedByteVecEncoder { + prefix_encoder: Option, + data: Vec, +} + +impl PrefixedByteVecEncoder { + pub fn new(data: Vec) -> Self { + Self { prefix_encoder: Some(encoding::CompactSizeEncoder::new(data.len())), data } + } +} + +impl encoding::Encoder for PrefixedByteVecEncoder { + fn current_chunk(&self) -> &[u8] { + if let Some(ref enc) = self.prefix_encoder { + return enc.current_chunk(); + } + &self.data + } + + fn advance(&mut self) -> encoding::EncoderStatus { + if let Some(ref mut enc) = self.prefix_encoder { + if enc.advance().has_finished() { + self.prefix_encoder = None; + if self.data.is_empty() { + return encoding::EncoderStatus::Finished; + } + } + encoding::EncoderStatus::HasMore + } else { + encoding::EncoderStatus::Finished + } + } +} + +impl encoding::ExactSizeEncoder for PrefixedByteVecEncoder { + fn len(&self) -> usize { + self.prefix_encoder.as_ref().map_or(0, encoding::CompactSizeEncoder::len) + self.data.len() + } +} /// Error decoding hexadecimal string into tweak-like value. #[derive(Debug, Clone, PartialEq, Eq)] @@ -96,6 +199,7 @@ mod tests { use super::*; use crate::encode::Encodable as _; + use crate::encoding; const VALUE_EXPLICIT: [u8; 9] = [1, 0, 0, 0, 0, 0, 0, 3, 232]; @@ -139,6 +243,15 @@ mod tests { 1, 1, 1, 1, ]; + #[test] + fn prefixed_byte_encoder() { + assert_eq!(encoding::drain_to_vec(&mut PrefixedByteVecEncoder::new(vec![])), [0]); + assert_eq!( + encoding::drain_to_vec(&mut PrefixedByteVecEncoder::new(vec![1, 2, 3])), + [3, 1, 2, 3] + ); + } + #[test] fn encode_length() { let val_encodings = [ @@ -158,6 +271,9 @@ mod tests { assert_eq!(v.consensus_encode(&mut x).unwrap(), v.encoded_length()); assert_eq!(x.len(), v.encoded_length()); assert_eq!(x, *enc); + + assert_eq!(encoding::encode_to_vec(v), *enc); + assert_eq!(encoding::decode_from_slice(enc), Ok(*v)); } let nonce_encodings = [ @@ -177,6 +293,9 @@ mod tests { assert_eq!(v.consensus_encode(&mut x).unwrap(), v.encoded_length()); assert_eq!(x.len(), v.encoded_length()); assert_eq!(x, *enc); + + assert_eq!(encoding::encode_to_vec(v), *enc); + assert_eq!(encoding::decode_from_slice(enc), Ok(*v)); } let asset_encodings = [ @@ -196,6 +315,9 @@ mod tests { assert_eq!(v.consensus_encode(&mut x).unwrap(), v.encoded_length()); assert_eq!(x.len(), v.encoded_length()); assert_eq!(x, *enc); + + assert_eq!(encoding::encode_to_vec(v), *enc); + assert_eq!(encoding::decode_from_slice(enc), Ok(*v)); } } @@ -207,6 +329,7 @@ mod tests { assert_eq!(x, Value::from_commitment(&commitment[..]).unwrap()); commitment[0] = 42; assert!(Value::from_commitment(&commitment[..]).is_err()); + assert_eq!(encoding::encode_to_vec(&x), VALUE_COMMITMENT1); let x = Asset::from_commitment(&ASSET_COMMITMENT1).unwrap(); let commitment = x.commitment().unwrap(); @@ -214,6 +337,7 @@ mod tests { assert_eq!(x, Asset::from_commitment(&commitment[..]).unwrap()); commitment[0] = 42; assert!(Asset::from_commitment(&commitment[..]).is_err()); + assert_eq!(encoding::encode_to_vec(&x), ASSET_COMMITMENT1); let x = Nonce::from_commitment(&NONCE_COMMITMENT1).unwrap(); let commitment = x.commitment().unwrap(); @@ -221,6 +345,7 @@ mod tests { assert_eq!(x, Nonce::from_commitment(&commitment[..]).unwrap()); commitment[0] = 42; assert!(Nonce::from_commitment(&commitment[..]).is_err()); + assert_eq!(encoding::encode_to_vec(&x), NONCE_COMMITMENT1); } #[cfg(feature = "serde")] diff --git a/src/confidential/nonce.rs b/src/confidential/nonce.rs index 0d9ca000..12310e0c 100644 --- a/src/confidential/nonce.rs +++ b/src/confidential/nonce.rs @@ -10,7 +10,9 @@ use secp256k1_zkp::{self, PublicKey, Secp256k1, SecretKey, Signing}; #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use super::CommitmentEncoder; use crate::encode::{self, Decodable, Encodable}; +use crate::encoding; use crate::hashes::sha256d; type ExplicitInner = [u8; 32]; @@ -18,6 +20,7 @@ type ConfInner = PublicKey; const EXPLICIT_LEN: usize = 32; const CONFIDENTIAL_LEN: usize = 33; +const CONFIDENTIAL_LEN_LESS_PREFIX: usize = CONFIDENTIAL_LEN - 1; const CONF_PREFIX_1: u8 = 0x02; const CONF_PREFIX_2: u8 = 0x03; @@ -247,3 +250,112 @@ impl<'de> Deserialize<'de> for Nonce { d.deserialize_seq(CommitVisitor) } } + +encoding::encoder_newtype_exact! { + /// Encoder for the [`Nonce`] type. + #[derive(Clone, Debug)] + pub struct Encoder<'e>(CommitmentEncoder<'e>); +} + +impl encoding::Encode for Nonce { + type Encoder<'e> = Encoder<'e>; + + fn encoder(&self) -> Self::Encoder<'_> { + Encoder::new(match *self { + Self::Null => CommitmentEncoder::Null(0), + Self::Explicit(ref id) => CommitmentEncoder::Explicit32(Some(1), id), + Self::Confidential(ref gen) => CommitmentEncoder::Explicit33(gen.serialize()), + }) + } +} + +decoder_state_machine! { + /// A decoder for the [`Nonce`] type. + pub struct Decoder(enum DecoderInner { + Done(Nonce), + Errored, + DecodePrefix { + decoder: encoding::ArrayDecoder<1>, + => transition_decode_prefix(prefix, ...) -> Result { + match prefix { + [0] => Ok(DecoderInner::Done(Nonce::Null)), + [1] => { + Ok(DecoderInner::DecodeExplicit { decoder: encoding::ArrayDecoder::default() }) + }, + [prefix @ (CONF_PREFIX_1 | CONF_PREFIX_2)] => { + Ok(DecoderInner::DecodeConfidential { decoder: encoding::ArrayDecoder::default(), prefix }) + }, + [prefix] => Err(DecoderErrorInner::InvalidConfidentialPrefix { prefix }) + } + } + }, + DecodeExplicit { + decoder: encoding::ArrayDecoder + => transition_decode_explicit(bytes, ...) -> Result { + Ok(DecoderInner::Done(Nonce::Explicit(bytes))) + } + }, + DecodeConfidential { + decoder: encoding::ArrayDecoder, + prefix: u8 + => transition_decode_confidential(x_coord, ...) -> Result { + let mut bytes = [0; CONFIDENTIAL_LEN]; + bytes[0] = prefix; + bytes[1..].copy_from_slice(&x_coord); + let gen = ConfInner::from_slice(&bytes) + .map_err(DecoderErrorInner::InvalidCommitment)?; + Ok(DecoderInner::Done(Nonce::Confidential(gen))) + } + }, + }); + + /// A decoder error for the [`Nonce`] type. + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct DecoderError(enum DecoderErrorInner { + [macro-inserted decoder variants] + /// Confidential prefix was not one of the two allowable values. + InvalidConfidentialPrefix { + prefix: u8, + }, + /// Malformed confidential commitment. + InvalidCommitment(bitcoin::secp256k1::Error), + }); +} + +impl fmt::Display for DecoderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use DecoderErrorInner as Inner; + match self.0 { + Inner::DecodePrefix(_) => f.write_str("failed to decode prefix"), + Inner::DecodeExplicit(_) => f.write_str("failed to decode explicit value"), + Inner::DecodeConfidential(_) => f.write_str("failed to decode confidential value"), + Inner::InvalidConfidentialPrefix { prefix, .. } => { + write!( + f, + "confidential prefix 0x{:02x} was not one of 0, 1, 0x{:02x} or 0x{:02x}", + prefix, CONF_PREFIX_1, CONF_PREFIX_2, + ) + } + Inner::InvalidCommitment(_) => f.write_str("failed to parse confidential commitment"), + } + } +} + +impl std::error::Error for DecoderError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use DecoderErrorInner as Inner; + match self.0 { + Inner::DecodePrefix(ref e) => Some(e), + Inner::DecodeExplicit(ref e) => Some(e), + Inner::DecodeConfidential(ref e) => Some(e), + Inner::InvalidConfidentialPrefix { .. } => None, + Inner::InvalidCommitment(ref e) => Some(e), + } + } +} + +impl Default for Decoder { + fn default() -> Self { + Self(DecoderInner::DecodePrefix { decoder: encoding::ArrayDecoder::default() }) + } +} diff --git a/src/confidential/range_proof.rs b/src/confidential/range_proof.rs index 89894333..441b8c4f 100644 --- a/src/confidential/range_proof.rs +++ b/src/confidential/range_proof.rs @@ -3,6 +3,7 @@ //! Range Proofs use core::convert::TryInto; +use core::fmt; use std::io; use secp256k1_zkp::rand::{CryptoRng, RngCore}; @@ -11,7 +12,7 @@ use secp256k1_zkp::{self, Generator, PedersenCommitment, Secp256k1, SecretKey, S use serde::{Deserializer, Serializer}; use crate::confidential::ValueBlindingFactor; -use crate::encode; +use crate::{encode, encoding}; /// A range proof, which represents a proof that a confidential value lies within /// some range (typically `[0, 2^64)`). @@ -190,3 +191,56 @@ impl<'de> serde::Deserialize<'de> for RangeProof { .map(|inner| Self { inner: inner.map(Box::new) }) } } + +encoding::encoder_newtype_exact! { + /// Encoder for the [`RangeProof`] type. + #[derive(Clone, Debug)] + pub struct Encoder<'e>(super::PrefixedByteVecEncoder); +} + +impl encoding::Encode for RangeProof { + type Encoder<'e> = Encoder<'e>; + + fn encoder(&self) -> Self::Encoder<'_> { + Encoder::new(super::PrefixedByteVecEncoder::new(self.to_vec())) + } +} + +decoder_newtype! { + /// Decoder for the [`RangeProof`] type. + #[derive(Default)] + pub struct Decoder(encoding::ByteVecDecoder); + + /// Decoder error for the [`RangeProof`] type. + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct DecoderError(enum DecoderErrorInner { + Decode(encoding::ByteVecDecoderError), + RangeProof(secp256k1_zkp::Error), + }); + + impl Decode for RangeProof { + fn convert_inner(v) -> Result<_, DecoderErrorInner> { + Self::Output::from_slice(&v).map_err(DecoderErrorInner::RangeProof) + } + } +} + +impl fmt::Display for DecoderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use DecoderErrorInner as Inner; + match self.0 { + Inner::Decode(..) => f.write_str("error decoding byte vector"), + Inner::RangeProof(..) => f.write_str("error decoding range proof"), + } + } +} + +impl std::error::Error for DecoderError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use DecoderErrorInner as Inner; + match self.0 { + Inner::Decode(ref e) => Some(e), + Inner::RangeProof(ref e) => Some(e), + } + } +} diff --git a/src/confidential/surjection_proof.rs b/src/confidential/surjection_proof.rs index 6fb6f8ea..9a9ceed5 100644 --- a/src/confidential/surjection_proof.rs +++ b/src/confidential/surjection_proof.rs @@ -2,6 +2,7 @@ //! Surjection Proofs +use core::fmt; use std::io; use secp256k1_zkp::rand::{CryptoRng, RngCore}; @@ -10,7 +11,7 @@ use secp256k1_zkp::{self, Generator, Secp256k1, Signing, Tweak, ZERO_TWEAK}; use serde::{Deserializer, Serializer}; use crate::confidential::{AssetBlindingFactor, AssetId}; -use crate::encode; +use crate::{encode, encoding}; /// A surjection proof, proving that an asset commitment commits to the same asset ID /// as a commitment from a given set. @@ -143,3 +144,56 @@ impl<'de> serde::Deserialize<'de> for SurjectionProof { .map(|inner| Self { inner: inner.map(Box::new) }) } } + +encoding::encoder_newtype_exact! { + /// Encoder for the [`SurjectionProof`] type. + #[derive(Clone, Debug)] + pub struct Encoder<'e>(super::PrefixedByteVecEncoder); +} + +impl encoding::Encode for SurjectionProof { + type Encoder<'e> = Encoder<'e>; + + fn encoder(&self) -> Self::Encoder<'_> { + Encoder::new(super::PrefixedByteVecEncoder::new(self.to_vec())) + } +} + +decoder_newtype! { + /// Decoder for the [`SurjectionProof`] type. + #[derive(Default)] + pub struct Decoder(encoding::ByteVecDecoder); + + /// Decoder error for the [`SurjectionProof`] type. + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct DecoderError(enum DecoderErrorInner { + Decode(encoding::ByteVecDecoderError), + SurjectionProof(secp256k1_zkp::Error), + }); + + impl Decode for SurjectionProof { + fn convert_inner(v) -> Result<_, DecoderErrorInner> { + Self::Output::from_slice(&v).map_err(DecoderErrorInner::SurjectionProof) + } + } +} + +impl fmt::Display for DecoderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use DecoderErrorInner as Inner; + match self.0 { + Inner::Decode(..) => f.write_str("error decoding byte vector"), + Inner::SurjectionProof(..) => f.write_str("error decoding surjection proof"), + } + } +} + +impl std::error::Error for DecoderError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use DecoderErrorInner as Inner; + match self.0 { + Inner::Decode(ref e) => Some(e), + Inner::SurjectionProof(ref e) => Some(e), + } + } +} diff --git a/src/confidential/value.rs b/src/confidential/value.rs index a8267148..930e20c3 100644 --- a/src/confidential/value.rs +++ b/src/confidential/value.rs @@ -14,8 +14,10 @@ use secp256k1_zkp::{ #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use super::CommitmentEncoder; use crate::confidential::AssetBlindingFactor; use crate::encode::{self, Decodable, Encodable}; +use crate::encoding; use crate::issuance::AssetId; type ExplicitInner = u64; @@ -23,6 +25,7 @@ type ConfInner = PedersenCommitment; const EXPLICIT_LEN: usize = 8; const CONFIDENTIAL_LEN: usize = 33; +const CONFIDENTIAL_LEN_LESS_PREFIX: usize = CONFIDENTIAL_LEN - 1; const CONF_PREFIX_1: u8 = 0x08; const CONF_PREFIX_2: u8 = 0x09; @@ -407,3 +410,112 @@ impl<'de> Deserialize<'de> for BlindingFactor { } } } + +encoding::encoder_newtype_exact! { + /// Encoder for the [`Asset`] type. + #[derive(Clone, Debug)] + pub struct Encoder<'e>(CommitmentEncoder<'e>); +} + +impl encoding::Encode for Value { + type Encoder<'e> = Encoder<'e>; + + fn encoder(&self) -> Self::Encoder<'_> { + Encoder::new(match *self { + Self::Null => CommitmentEncoder::Null(0), + Self::Explicit(ref id) => CommitmentEncoder::Explicit8(Some(1), id.to_be_bytes()), + Self::Confidential(ref gen) => CommitmentEncoder::Explicit33(gen.serialize()), + }) + } +} + +decoder_state_machine! { + /// A decoder for the [`Value`] type. + pub struct Decoder(enum DecoderInner { + Done(Value), + Errored, + DecodePrefix { + decoder: encoding::ArrayDecoder<1>, + => transition_decode_prefix(prefix, ...) -> Result { + match prefix { + [0] => Ok(DecoderInner::Done(Value::Null)), + [1] => { + Ok(DecoderInner::DecodeExplicit { decoder: encoding::ArrayDecoder::default() }) + }, + [prefix @ (CONF_PREFIX_1 | CONF_PREFIX_2)] => { + Ok(DecoderInner::DecodeConfidential { decoder: encoding::ArrayDecoder::default(), prefix }) + }, + [prefix] => Err(DecoderErrorInner::InvalidConfidentialPrefix { prefix }) + } + } + }, + DecodeExplicit { + decoder: encoding::ArrayDecoder + => transition_decode_explicit(bytes, ...) -> Result { + Ok(DecoderInner::Done(Value::Explicit(u64::from_be_bytes(bytes)))) + } + }, + DecodeConfidential { + decoder: encoding::ArrayDecoder, + prefix: u8 + => transition_decode_confidential(x_coord, ...) -> Result { + let mut bytes = [0; CONFIDENTIAL_LEN]; + bytes[0] = prefix; + bytes[1..].copy_from_slice(&x_coord); + let gen = ConfInner::from_slice(&bytes) + .map_err(DecoderErrorInner::InvalidCommitment)?; + Ok(DecoderInner::Done(Value::Confidential(gen))) + } + }, + }); + + /// A decoder error for the [`Value`] type. + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct DecoderError(enum DecoderErrorInner { + [macro-inserted decoder variants] + /// Confidential prefix was not one of the two allowable values. + InvalidConfidentialPrefix { + prefix: u8, + }, + /// Malformed confidential commitment. + InvalidCommitment(secp256k1_zkp::Error), + }); +} + +impl fmt::Display for DecoderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use DecoderErrorInner as Inner; + match self.0 { + Inner::DecodePrefix(_) => f.write_str("failed to decode prefix"), + Inner::DecodeExplicit(_) => f.write_str("failed to decode explicit value"), + Inner::DecodeConfidential(_) => f.write_str("failed to decode confidential value"), + Inner::InvalidConfidentialPrefix { prefix, .. } => { + write!( + f, + "confidential prefix 0x{:02x} was not one of 0, 1, 0x{:02x} or 0x{:02x}", + prefix, CONF_PREFIX_1, CONF_PREFIX_2, + ) + } + Inner::InvalidCommitment(_) => f.write_str("failed to parse confidential commitment"), + } + } +} + +impl std::error::Error for DecoderError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use DecoderErrorInner as Inner; + match self.0 { + Inner::DecodePrefix(ref e) => Some(e), + Inner::DecodeExplicit(ref e) => Some(e), + Inner::DecodeConfidential(ref e) => Some(e), + Inner::InvalidConfidentialPrefix { .. } => None, + Inner::InvalidCommitment(ref e) => Some(e), + } + } +} + +impl Default for Decoder { + fn default() -> Self { + Self(DecoderInner::DecodePrefix { decoder: encoding::ArrayDecoder::default() }) + } +} diff --git a/src/encode.rs b/src/encode.rs index a1204062..578e2d87 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -29,6 +29,39 @@ pub use bitcoin::{self, consensus::encode::MAX_VEC_SIZE}; use crate::taproot::TapLeafHash; +/// Adaptor to count bytes, used to implement Encodable/Decodable +/// in terms of the new Encode/Decode traits. +pub(crate) struct ByteCounter { + inner: W, + count: usize, +} + +impl ByteCounter { + pub(crate) fn new(inner: W) -> Self { + Self { inner, count: 0 } + } + + pub(crate) fn into_count(self) -> usize { + self.count + } +} + +impl io::Write for ByteCounter + where W: io::Write +{ + fn write(&mut self, buf: &[u8]) -> io::Result { + let res = self.inner.write(buf); + if let Ok(size) = res { + self.count += size; + } + res + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + /// Encoding error #[derive(Debug)] pub enum Error { @@ -63,6 +96,10 @@ pub enum Error { BadLockTime(crate::LockTime), /// `VarInt` was encoded in a non-minimal way. NonMinimalVarInt, + /// Error decoding a pegin witness. + PeginWitness(crate::PeginWitnessDecoderError), + /// Error decoding a script witness. + Witness(crate::WitnessDecoderError), } impl fmt::Display for Error { @@ -90,6 +127,8 @@ impl fmt::Display for Error { Error::HexVariableError(ref e) => write!(f, "Hex variable error: {}", e), Error::BadLockTime(ref lt) => write!(f, "Invalid locktime {}", lt), Error::NonMinimalVarInt => write!(f, "non-minimal varint"), + Self::PeginWitness(..) => f.write_str("error decoding pegin witness"), + Self::Witness(..) => f.write_str("error decoding script witness"), } } } @@ -98,6 +137,8 @@ impl error::Error for Error { fn cause(&self) -> Option<&dyn error::Error> { match *self { Error::Secp256k1zkp(ref e) => Some(e), + Self::PeginWitness(ref e) => Some(e), + Self::Witness(ref e) => Some(e), _ => None, } } @@ -240,6 +281,24 @@ impl Decodable for crate::locktime::Time { } } +impl Encodable for crate::Witness { + fn consensus_encode(&self, e: W) -> Result { + let mut counter = ByteCounter::new(e); + crate::encoding::encode_to_writer(self, &mut counter)?; + Ok(counter.into_count()) + } +} + +impl Decodable for crate::Witness { + fn consensus_decode(d: D) -> Result { + match crate::encoding::decode_from_read_unbuffered(d) { + Ok(wit) => Ok(wit), + Err(crate::encoding::ReadError::Io(e)) => Err(Error::Io(e)), + Err(crate::encoding::ReadError::Decode(e)) => Err(Error::Witness(e)), + } + } +} + /// A variable sized integer. pub struct VarInt(pub u64); impl Encodable for VarInt { diff --git a/src/internal_macros.rs b/src/internal_macros.rs index 3e2b7431..a26ff92c 100644 --- a/src/internal_macros.rs +++ b/src/internal_macros.rs @@ -527,3 +527,547 @@ macro_rules! hex_deserialize( macro_rules! hex_script( ($e:expr) => (crate::Script::from_hex_no_prefix($e).expect("hex decoding")) ); + +/// Generates a state machine decoder satisfying the [`crate::encoding::Decoder`] trait. +/// +/// This macro creates a decoder that processes input bytes through multiple states, +/// transitioning between states based on decoded data. It automatically generates +/// both the decoder struct and associated error types. +/// +/// # Syntax +/// +/// ```ignore +/// decoder_state_machine! { +/// /// Documentation for the decoder struct +/// pub struct DecoderName(enum InnerEnumName { +/// Done(TargetType), +/// Errored, +/// StateName { +/// decoder: DecoderType, +/// field1: FieldType1, +/// field2: FieldType2, +/// => transition_function_name(output, ...) -> Result { +/// // Transition logic that returns Ok(NextState) or Err(error) +/// } +/// }, +/// // ... more states +/// }); +/// +/// /// Documentation for the error struct +/// pub struct ErrorName(enum InnerErrorName { +/// [macro-inserted decoder variants] +/// CustomError1(ErrorType1), +/// CustomError2 { field: ErrorType2 }, +/// // ... custom error variants +/// }); +/// } +/// ``` +/// +/// # Generated Code +/// +/// The macro generates: +/// - A public decoder struct wrapping a private enum +/// - A private enum with states, Done, and Errored variants +/// - Transition functions for each state +/// - A public error struct wrapping a private error enum +/// - A private error enum with decoder errors and custom variants +/// - `Decoder` trait implementation with `push_bytes`, `end`, and `read_limit` methods +/// - `Decode` impl for the target type +/// +/// The macro does **not** generate a `fmt::Display` or `std::error::Error` impl +/// for `ErrorName`, so the user must implement these outside of the macro. +/// +/// The variants that the macro inserts into `ErrorName` have the same names +/// as the variants on `InnerEnumName`, and they contain a single value of +/// type `::Error`. +/// +/// # State Structure +/// +/// Each state must have: +/// - `decoder: SomeDecoderType` - The decoder for this state's data +/// - Optional additional fields to carry state between transitions +/// - A transition function that processes the decoder's output +/// +/// # Transition Functions +/// +/// Transition functions: +/// - Take the decoder's output as first parameter +/// - Take any additional state fields as subsequent parameters +/// - Return `Result` +/// - Are called automatically when the decoder completes +/// +/// # Error Handling +/// +/// The macro automatically: +/// - Creates error variants for each decoder type +/// - Maps decoder errors to the appropriate variant +/// +/// # Example Usage +/// +/// ```ignore +/// decoder_state_machine! { +/// /// Decodes a length-prefixed string +/// pub struct StringDecoder(enum StringDecoderInner { +/// Done(String), +/// Errored, +/// ReadLength { +/// decoder: encoding::ArrayDecoder<4> +/// => transition_read_length(length_bytes, ...) -> Result { +/// let length = u32::from_be_bytes(length_bytes) as usize; +/// if length > MAX_STRING_LENGTH { +/// return Err(StringDecoderErrorInner::StringTooLong { length }); +/// } +/// Ok(StringDecoderInner::ReadData { +/// decoder: encoding::VecDecoder::new(length), +/// expected_length: length +/// }) +/// } +/// }, +/// ReadData { +/// decoder: encoding::VecDecoder, +/// expected_length: usize +/// => transition_read_data(data, expected_length, ...) -> Result { +/// if data.len() != expected_length { +/// return Err(StringDecoderErrorInner::LengthMismatch); +/// } +/// let string = String::from_utf8(data) +/// .map_err(StringDecoderErrorInner::InvalidUtf8)?; +/// Ok(StringDecoderInner::Done(string)) +/// } +/// }, +/// }); +/// +/// /// Errors that can occur during string decoding +/// #[derive(Debug)] +/// pub struct StringDecoderError(enum StringDecoderErrorInner { +/// [macro-inserted decoder variants] +/// StringTooLong { length: usize }, +/// LengthMismatch, +/// InvalidUtf8(std::string::FromUtf8Error), +/// }); +/// } +/// ``` +macro_rules! decoder_state_machine { + ( + $(#[$($struct_attr:tt)*])* + pub struct $outer_ty:ident(enum $inner_ty:ident { + Done($target_ty:ty), + Errored, + $( + $variant:ident { + decoder: $decoder_ty:ty$(,)? + $(, $field:ident: $field_ty:ty)* + => $transition_fn:ident($output:ident, ...) -> Result { + $($transition_fn_inner:tt)* + } + }, + )* + }); + + $(#[$($error_struct_attr:tt)*])* + pub struct $error_ty:ident(enum $inner_error_ty:ident { + [macro-inserted decoder variants] + $($extra_variants:tt)* + }); + ) => { + $(#[$($struct_attr)*])* + pub struct $outer_ty($inner_ty); + + #[allow(clippy::large_enum_variant)] + enum $inner_ty { + $($variant { + decoder: $decoder_ty + $(, $field: $field_ty)* + },)* + Done($target_ty), + Errored, + } + + impl $inner_ty { + $( + #[inline] + #[allow(clippy::unnecessary_wraps)] // returns a Result even if it never returns Err + fn $transition_fn( + $output: <$decoder_ty as $crate::encoding::Decoder>::Output + $(, $field: $field_ty)* + ) -> Result { + $($transition_fn_inner)* + } + )* + } + + $(#[$($error_struct_attr)*])* + pub struct $error_ty($inner_error_ty); + + $(#[$($error_struct_attr)*])* + enum $inner_error_ty { + $( + $variant(<$decoder_ty as $crate::encoding::Decoder>::Error), + )* + $($extra_variants)* + } + + impl $crate::encoding::Decoder for $outer_ty { + type Output = $target_ty; + type Error = $error_ty; + + fn push_bytes( + &mut self, + bytes: &mut &[u8], + ) -> Result<$crate::encoding::DecoderStatus, Self::Error> { + use $inner_ty as Inner; + loop { + match core::mem::replace(&mut self.0, Inner::Errored) { + $( + Inner::$variant { mut decoder $(, $field)* } => { + if decoder + .push_bytes(bytes) + .map_err($inner_error_ty::$variant) + .map_err($error_ty)? + .needs_more() + { + self.0 = Inner::$variant { decoder $(, $field)* }; + return Ok($crate::encoding::DecoderStatus::NeedsMore); + } + + let output = decoder.end() + .map_err($inner_error_ty::$variant) + .map_err($error_ty)?; + self.0 = $inner_ty::$transition_fn(output $(, $field)*) + .map_err($error_ty)?; + } + )* + Inner::Done(out) => { + self.0 = Inner::Done(out); + return Ok($crate::encoding::DecoderStatus::Ready); + } + Inner::Errored => panic!("called push_bytes() on an errored decoder"), + + } + } + } + + fn end(self) -> Result { + use $inner_ty as Inner; + match self.0 { + $( + Inner::$variant { decoder, .. } => { + decoder.end() + .map_err($inner_error_ty::$variant) + .map_err($error_ty)?; + // This unreachable! can be hit by badly behaved decoders where push_bytes() + // returns DecoderStatus::NeedMore, but end() returns Ok. In general, end() + // should return Ok only if push_bytes has returned DecoderStatus::Ready. + unreachable!( + "end() succeeded on decoder for {} state, but we did not leave that state", + stringify!($variant), + ) + } + )* + Inner::Done(out) => Ok(out), + Inner::Errored => panic!("called end() on an errored decoder"), + } + } + + fn read_limit(&self) -> usize { + use $inner_ty as Inner; + match self.0 { + $( + Inner::$variant { ref decoder, .. } => decoder.read_limit(), + )* + Inner::Done(_) | Inner::Errored => 0, + } + } + } + + impl $crate::encoding::Decode for $target_ty { + type Decoder = $outer_ty; + } + }; +} + +/// Generates a simple decoder wrapper that converts output from an inner decoder. +/// +/// This macro creates a decoder that wraps an existing decoder and applies a conversion +/// function to transform the inner decoder's output into the target type. It's simpler +/// than `decoder_state_machine!` as it only wraps a single decoder without state transitions. +/// +/// # Syntax +/// +/// There are two invocation patterns: +/// +/// ## Direct Error Wrapping +/// ```ignore +/// decoder_newtype! { +/// /// Documentation for the decoder struct +/// pub struct DecoderName(InnerDecoderType); +/// +/// /// Documentation for the error struct +/// pub struct ErrorName(InnerErrorName); +/// const ERROR_DISPLAY = "output of the Display::fmt function"; +/// +/// impl Decode for TargetType { +/// fn convert_inner(output_var) -> Result<_, InnerErrorName> { +/// // Conversion logic -- returns `InnerErrorName` for errors! +/// } +/// } +/// } +/// ``` +/// +/// In this case `InnerErrorName` must be an already-existing error type. +/// +/// ## Error Enum Wrapping +/// ```ignore +/// decoder_newtype! { +/// /// Documentation for the decoder struct +/// pub struct DecoderName(InnerDecoderType); +/// +/// /// Documentation for the error struct +/// pub struct ErrorName(enum InnerErrorName { +/// // The first variant must be called Decode and hold the inner decoder type. +/// Decode(InnerDecoderErrorType), +/// // All other variants are free-form. +/// CustomError1(ErrorType1), +/// CustomError2 { field: ErrorType2 }, +/// // ... custom error variants +/// }); +/// +/// impl Decode for TargetType { +/// fn convert_inner(output_var) -> Result<_, ErrorName> { +/// // Conversion logic -- returns `ErrorName` for errors! +/// } +/// } +/// } +/// ``` +/// +/// In this case `InnerErrorName` will be generated as a private enum by the macro. +/// +/// # Generated Code +/// +/// The macro generates: +/// - A public decoder struct wrapping the inner decoder +/// - A public error struct wrapping a private error enum +/// - A private error enum with `Decode` variant and custom variants +/// - `Decoder` trait implementation with `push_bytes`, `end`, and `read_limit` methods +/// - `Decode` trait implementation for the target type +/// +/// **When using the enum pattern, the macro does not generate a `fmt::Display` or +/// `std::error::Error` impl for `ErrorName`. The user must implement these outside +/// of the macro.** When using the non-enum pattern, both `Display` and `Error` are +/// generated by the macro. +/// +/// # Parameters +/// +/// - `DecoderName` - The wrapper decoder struct name +/// - `InnerDecoderType` - The existing decoder type to wrap +/// - `ErrorName` - The error struct name +/// - `InnerErrorName` - The private error enum name +/// - `InnerDecoderErrorType` - The error type from the inner decoder +/// - `TargetType` - The final output type after conversion +/// - `convert_inner` - Function that converts inner output to target type +/// +/// # Conversion Function +/// +/// The conversion function: +/// - Takes the inner decoder's output as its parameter +/// - Returns `Result` for enum pattern, `Result` +/// for direct-wrapping pattern. +/// - Is called automatically when the inner decoder completes +/// - Can return custom errors defined in the error enum (enum pattern only) +/// +/// # Pattern Selection +/// +/// - Use **direct error wrapping** when conversion cannot fail or only needs to propagate the inner decoder's errors +/// - Use **error enum wrapping** when conversion can fail with custom error types +/// +/// # Error Handling +/// +/// The macro automatically: +/// - Creates a `Decode` variant containing the inner decoder's error +/// - Maps inner decoder errors to the `Decode` variant +/// - Allows custom error variants for conversion failures +/// +/// # Example Usage +/// +/// ## Direct Error Wrapping +/// ```ignore +/// decoder_newtype! { +/// /// Decoder for the [`TxOut`] type. +/// #[derive(Default)] +/// pub struct TxOutDecoder(Decoder4< crate::confidential::AssetDecoder, +/// crate::confidential::ValueDecoder, +/// crate::confidential::NonceDecoder, +/// crate::script::ScriptDecoder, +/// >); +/// +/// /// Decoder error for the [`TxOut`] type. +/// #[derive(Clone, PartialEq, Eq, Debug)] +/// pub struct TxOutDecoderError(Decoder4Error< +/// crate::confidential::AssetDecoderError, +/// crate::confidential::ValueDecoderError, +/// crate::confidential::NonceDecoderError, +/// crate::script::ScriptDecoderError, +/// >); +/// const ERROR_DISPLAY = "error decoding transaction output witness"; +/// +/// impl Decode for TxOut { +/// fn convert_inner(output) -> Result<_, TxOutDecoderErrorInner> { +/// let (asset, value, nonce, script_pubkey) = output; +/// Ok(TxOut { asset, value, nonce, script_pubkey, witness: TxOutWitness::empty() }) +/// } +/// } +/// } +/// ``` +/// +/// ## Error Enum Wrapping +/// ```ignore +/// decoder_newtype! { +/// /// Decoder for range proofs +/// #[derive(Default)] +/// pub struct Decoder(encoding::ByteVecDecoder); +/// +/// /// Decoder error for range proofs +/// #[derive(Clone, PartialEq, Eq, Debug)] +/// pub struct DecoderError(enum DecoderErrorInner { +/// Decode(encoding::ByteVecDecoderError), +/// RangeProof(secp256k1_zkp::Error), +/// }); +/// +/// impl Decode for RangeProof { +/// fn convert_inner(v) -> Result<_, DecoderError> { +/// RangeProof::from_slice(&v).map_err(DecoderError::RangeProof) +/// } +/// } +/// } +/// ``` +macro_rules! decoder_newtype { + // With directly wrapped inner error + ( + $(#[$($struct_attr:tt)*])* + pub struct $outer_ty:ident($inner_ty:ty); + + $(#[$($error_struct_attr:tt)*])* + pub struct $error_ty:ident($inner_ty_error:ty); + + const ERROR_DISPLAY = $error_display:expr; + + impl Decode for $target_ty:ty { + fn convert_inner($output:ident) -> Result<_, $error_inner1:ty> { + $($output_fn_inner:tt)* + } + } + ) => { + $(#[$($struct_attr)*])* + pub struct $outer_ty($inner_ty); + + $(#[$($error_struct_attr)*])* + pub struct $error_ty($inner_ty_error); + + impl core::fmt::Display for $error_ty { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str($error_display) + } + } + + impl std::error::Error for $error_ty { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.0) + } + } + + impl $crate::encoding::Decoder for $outer_ty { + type Output = $target_ty; + type Error = $error_ty; + + fn push_bytes( + &mut self, + bytes: &mut &[u8], + ) -> Result<$crate::encoding::DecoderStatus, Self::Error> { + self.0 + .push_bytes(bytes) + .map_err($error_ty) + } + + fn end(self) -> Result { + let $output = self.0 + .end() + .map_err($error_ty)?; + let converted = { + $($output_fn_inner)* + }; + converted.map_err($error_ty) + } + + fn read_limit(&self) -> usize { + self.0.read_limit() + } + } + + impl $crate::encoding::Decode for $target_ty{ + type Decoder = $outer_ty; + } + }; + // With inner error enum + ( + $(#[$($struct_attr:tt)*])* + pub struct $outer_ty:ident($inner_ty:ty); + + $(#[$($error_struct_attr:tt)*])* + pub struct $error_ty:ident(enum $inner_error_ty:ident { + Decode($inner_ty_error:ty), + $($extra_variants:tt)* + }); + + impl Decode for $target_ty:ty { + fn convert_inner($output:ident) -> Result<_, $error_inner1:ty> { + $($output_fn_inner:tt)* + } + } + ) => { + $(#[$($struct_attr)*])* + pub struct $outer_ty($inner_ty); + + $(#[$($error_struct_attr)*])* + pub struct $error_ty($inner_error_ty); + + $(#[$($error_struct_attr)*])* + enum $inner_error_ty { + Decode($inner_ty_error), + $($extra_variants)* + } + + impl $crate::encoding::Decoder for $outer_ty { + type Output = $target_ty; + type Error = $error_ty; + + fn push_bytes( + &mut self, + bytes: &mut &[u8], + ) -> Result<$crate::encoding::DecoderStatus, Self::Error> { + self.0 + .push_bytes(bytes) + .map_err($inner_error_ty::Decode) + .map_err($error_ty) + } + + fn end(self) -> Result { + let $output = self.0 + .end() + .map_err($inner_error_ty::Decode) + .map_err($error_ty)?; + let converted = { + $($output_fn_inner)* + }; + converted.map_err($error_ty) + } + + fn read_limit(&self) -> usize { + self.0.read_limit() + } + } + + impl $crate::encoding::Decode for $target_ty{ + type Decoder = $outer_ty; + } + }; +} diff --git a/src/lib.rs b/src/lib.rs index 18c92cea..68b2a1c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,8 @@ /// Re-export of bitcoin crate pub extern crate bitcoin; +/// Re-export of bitcoin-consensus-encoding crate +pub extern crate encoding; /// Re-export of `bitcoin_hashes` crate pub extern crate hashes; /// Re-export of hex crate @@ -67,8 +69,6 @@ mod parse; pub mod pset; pub mod schnorr; pub mod script; -#[cfg(feature = "serde")] -mod serde_utils; pub mod sighash; pub mod taproot; mod transaction; @@ -77,6 +77,8 @@ mod endian; pub mod genesis; // export everything at the top level so it can be used as `elements::Transaction` etc. +use internals::array_vec::ArrayVec; + pub use crate::address::{Address, AddressError, AddressParams}; pub use crate::blind::{ BlindError, ConfidentialTxOutError, CtLocation, CtLocationType, RangeProofMessage, @@ -93,6 +95,21 @@ pub use crate::schnorr::{SchnorrSig, SchnorrSigError}; pub use crate::script::Script; pub use crate::sighash::SchnorrSighashType; pub use crate::transaction::{ - AssetIssuance, EcdsaSighashType, OutPoint, PeginData, PegoutData, Sequence, Transaction, TxIn, - TxInWitness, TxOut, TxOutWitness, + AssetIssuance, AssetIssuanceDecoder, AssetIssuanceDecoderError, AssetIssuanceEncoder, + EcdsaSighashType, OutPoint, PeginData, PeginDataDecoder, PeginDataEncoder, PeginWitness, + PeginWitnessDecoder, PeginWitnessDecoderError, PeginWitnessEncoder, PegoutData, Sequence, + SequenceDecoder, SequenceDecoderError, SequenceEncoder, Transaction, TransactionDecoder, + TransactionDecoderError, TransactionEncoder, TxIn, TxInDecoder, TxInDecoderError, TxInEncoder, + TxInWitness, TxInWitnessDecoder, TxInWitnessDecoderError, TxInWitnessEncoder, TxOut, + TxOutDecoder, TxOutDecoderError, TxOutEncoder, TxOutWitness, TxOutWitnessDecoder, + TxOutWitnessDecoderError, TxOutWitnessEncoder, Witness, WitnessDecoder, WitnessDecoderError, + WitnessEncoder, }; + +// Encode a compact size to a slice without allocating +pub(crate) fn compact_size_encode(value: usize) -> ArrayVec { + use crate::encoding::Encoder as _; + + let encoder = encoding::CompactSizeEncoder::new(value); + ArrayVec::from_slice(encoder.current_chunk()) +} diff --git a/src/locktime.rs b/src/locktime.rs index 0fd8c043..40b58ae3 100644 --- a/src/locktime.rs +++ b/src/locktime.rs @@ -22,6 +22,7 @@ use std::cmp::{PartialOrd, Ordering}; use std::convert::TryFrom; use std::str::FromStr; use std::io::{Read, Write}; +use crate::encoding; use crate::error::ParseIntError; use crate::parse; @@ -66,7 +67,6 @@ pub const LOCK_TIME_THRESHOLD: u32 = 500_000_000; /// ``` #[allow(clippy::derive_ord_xor_partial_ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum LockTime { /// A block height lock time value. /// @@ -275,6 +275,36 @@ impl fmt::Display for LockTime { } } +encoding::encoder_newtype_exact! { + /// Encoder for the [`LockTime`] type. + pub struct LockTimeEncoder<'e>(encoding::ArrayEncoder<4>); +} + +impl encoding::Encode for LockTime { + type Encoder<'e> = LockTimeEncoder<'e>; + + fn encoder(&self) -> Self::Encoder<'_> { + LockTimeEncoder::new(encoding::ArrayEncoder::without_length_prefix(self.to_consensus_u32().to_le_bytes())) + } +} + +decoder_newtype! { + /// Decoder for the [`LockTime`] type. + #[derive(Default)] + pub struct LockTimeDecoder(encoding::ArrayDecoder<4>); + + /// Decoder error for the [`LockTime`] type. + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct LockTimeDecoderError(encoding::UnexpectedEofError); + const ERROR_DISPLAY = "failed to decode locktime"; + + impl Decode for LockTime { + fn convert_inner(bytes) -> Result<_, UnexpectedEofError> { + Ok(LockTime::from_consensus(u32::from_le_bytes(bytes))) + } + } +} + impl Encodable for LockTime { #[inline] fn consensus_encode(&self, w: W) -> Result { @@ -292,7 +322,6 @@ impl Decodable for LockTime { /// An absolute block height, guaranteed to always contain a valid height value. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Height(u32); impl Height { @@ -377,7 +406,6 @@ impl TryFrom for Height { /// `to_consensus_u32()`. Said another way, `Time(x)` means 'x seconds since epoch' _not_ '(x - /// threshold) seconds since epoch'. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Time(u32); impl Time { diff --git a/src/opcodes.rs b/src/opcodes.rs index fb8648c4..d9cfec5b 100644 --- a/src/opcodes.rs +++ b/src/opcodes.rs @@ -20,8 +20,6 @@ #![allow(non_camel_case_types)] -#[cfg(feature = "serde")] use serde; - use std::fmt; // Note: I am deliberately not implementing PartialOrd or Ord on the @@ -862,16 +860,6 @@ impl fmt::Display for All { } } -#[cfg(feature = "serde")] -impl serde::Serialize for All { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - /// Empty stack is also FALSE pub static OP_FALSE: All = all::OP_PUSHBYTES_0; /// Number 1 is also TRUE diff --git a/src/pset/map/global.rs b/src/pset/map/global.rs index 8051cc39..4ed267e3 100644 --- a/src/pset/map/global.rs +++ b/src/pset/map/global.rs @@ -58,7 +58,6 @@ const PSBT_ELEMENTS_GLOBAL_TX_MODIFIABLE: u8 = 0x01; /// Global transaction data #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TxData { /// Transaction version. Must be 2. pub version: u32, @@ -93,10 +92,8 @@ impl Default for TxData { /// A key-value map for global data. #[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Global { /// Global transaction data - #[cfg_attr(feature = "serde", serde(flatten))] pub tx_data: TxData, /// The version number of this PSET. Must be present. pub version: u32, @@ -109,16 +106,8 @@ pub struct Global { /// Elements tx modifiable flag pub elements_tx_modifiable_flag: Option, /// Other Proprietary fields - #[cfg_attr( - feature = "serde", - serde(with = "crate::serde_utils::btreemap_as_seq_byte_values") - )] pub proprietary: BTreeMap>, /// Unknown global key-value pairs. - #[cfg_attr( - feature = "serde", - serde(with = "crate::serde_utils::btreemap_as_seq_byte_values") - )] pub unknown: BTreeMap>, } diff --git a/src/pset/map/input.rs b/src/pset/map/input.rs index 53438a1a..1aa02d75 100644 --- a/src/pset/map/input.rs +++ b/src/pset/map/input.rs @@ -32,7 +32,7 @@ use crate::pset::raw; use crate::pset::serialize; use crate::pset::{self, error, Error}; use crate::{transaction::SighashTypeParseError, SchnorrSighashType}; -use crate::{AssetIssuance, BlockHash, EcdsaSighashType, RangeProof, Script, Transaction, TxIn, TxOut, Txid, SurjectionProof}; +use crate::{AssetIssuance, BlockHash, EcdsaSighashType, PeginWitness, RangeProof, Script, Transaction, TxIn, TxOut, Txid, SurjectionProof}; use bitcoin::bip32::KeySource; use bitcoin::{PublicKey, key::XOnlyPublicKey}; use secp256k1_zkp::{self, Tweak, ZERO_TWEAK}; @@ -172,7 +172,6 @@ const PSBT_ELEMENTS_IN_BLINDED_ISSUANCE: u8 = 0x15; /// A key-value map for an input of the corresponding index in the unsigned /// transaction. #[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Input { /// The non-witness transaction this input spends from. Should only be /// [`std::option::Option::Some`] for inputs which spend non-segwit outputs or @@ -184,10 +183,6 @@ pub struct Input { pub witness_utxo: Option, /// A map from public keys to their corresponding signature as would be /// pushed to the stack from a scriptSig or witness. - #[cfg_attr( - feature = "serde", - serde(with = "crate::serde_utils::btreemap_byte_values") - )] pub partial_sigs: BTreeMap>, /// The sighash type to be used for this input. Signatures for this input /// must use the sighash type. @@ -198,38 +193,21 @@ pub struct Input { pub witness_script: Option