From ad4788ee9889c0b997eb8fc52bc6601983d7e57b Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Sun, 28 Jun 2026 23:05:35 +0000 Subject: [PATCH 01/12] rustfmt src/lib.rs Copy the (nightly-only) rustfmt.toml from rust-bitcoin, and add an ignore block which scopes it specifically to src/lib.rs. As I add new files, I will add them to the format whitelist. Because I am going to be adding a ton of new exports to src/lib.rs, add that file for now. To make this work with jj-fix, run jj config edit --repo and add [fix.tools.cargo-fmt] command = ["/usr/bin/env", "rustfmt", "+nightly", "--edition", "2018"] patterns = [ "./src/lib.rs", "./src/confidential/range_proof.rs", "./src/confidential/surjection_proof.rs", "./src/transaction/decoders.rs", "./src/transaction/encoders.rs", "./src/transaction/pegin_witness.rs", ] --- rustfmt.toml | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 11 ++++---- 2 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 rustfmt.toml diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..db148418 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,80 @@ +ignore = [ + "/", + "!/src/lib.rs" +] +hard_tabs = false +tab_spaces = 4 +newline_style = "Auto" +indent_style = "Block" + +max_width = 100 # This is the number of characters. +# `use_small_heuristics` is ignored if the granular width config values are explicitly set. +use_small_heuristics = "Max" # "Max" == All granular width settings same as `max_width`. +# # Granular width configuration settings. These are percentages of `max_width`. +# fn_call_width = 60 +# attr_fn_like_width = 70 +# struct_lit_width = 18 +# struct_variant_width = 35 +# array_width = 60 +# chain_width = 60 +# single_line_if_else_max_width = 50 + +wrap_comments = false +format_code_in_doc_comments = false +comment_width = 100 # Default 80 +normalize_comments = false +normalize_doc_attributes = false +format_strings = false +format_macro_matchers = false +format_macro_bodies = true +hex_literal_case = "Preserve" +empty_item_single_line = true +struct_lit_single_line = true +fn_single_line = true # Default false +where_single_line = false +imports_indent = "Block" +imports_layout = "Mixed" +imports_granularity = "Module" # Default "Preserve" +group_imports = "StdExternalCrate" # Default "Preserve" +reorder_imports = true +reorder_modules = true +reorder_impl_items = false +type_punctuation_density = "Wide" +space_before_colon = false +space_after_colon = true +spaces_around_ranges = false +binop_separator = "Front" +remove_nested_parens = true +combine_control_expr = true +overflow_delimited_expr = false +struct_field_align_threshold = 0 +enum_discrim_align_threshold = 0 +match_arm_blocks = false # Default true +match_arm_leading_pipes = "Never" +force_multiline_blocks = false +fn_params_layout = "Tall" +brace_style = "SameLineWhere" +control_brace_style = "AlwaysSameLine" +trailing_semicolon = true +trailing_comma = "Vertical" +match_block_trailing_comma = false +blank_lines_upper_bound = 1 +blank_lines_lower_bound = 0 +edition = "2021" +style_edition = "2021" +inline_attribute_width = 0 +format_generated_files = true +merge_derives = true +use_try_shorthand = false +use_field_init_shorthand = false +force_explicit_abi = true +condense_wildcard_suffixes = false +color = "Auto" +unstable_features = false +disable_all_formatting = false +skip_children = false +show_parse_errors = true +error_on_line_overflow = false +error_on_unformatted = false +emit_mode = "Files" +make_backup = false diff --git a/src/lib.rs b/src/lib.rs index dfc51d3a..2fc14b6c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,11 +79,11 @@ pub mod genesis; // export everything at the top level so it can be used as `elements::Transaction` etc. pub use crate::address::{Address, AddressError, AddressParams}; pub use crate::blind::{ - BlindAssetProofs, BlindError, BlindValueProofs, ConfidentialTxOutError, RangeProofMessage, - SurjectionInput, TxOutError, TxOutSecrets, UnblindError, VerificationError, CtLocation, CtLocationType, + BlindAssetProofs, BlindError, BlindValueProofs, ConfidentialTxOutError, CtLocation, + CtLocationType, RangeProofMessage, SurjectionInput, TxOutError, TxOutSecrets, UnblindError, + VerificationError, }; -pub use crate::block::ExtData as BlockExtData; -pub use crate::block::{Block, BlockHeader, DynafedRoot}; +pub use crate::block::{Block, BlockHeader, DynafedRoot, ExtData as BlockExtData}; pub use crate::ext::{ReadExt, WriteExt}; pub use crate::fast_merkle_root::fast_merkle_root; pub use crate::hash_types::*; @@ -92,8 +92,7 @@ pub use crate::locktime::LockTime; pub use crate::schnorr::{SchnorrSig, SchnorrSigError}; pub use crate::script::Script; pub use crate::sighash::SchnorrSighashType; -pub use crate::transaction::Sequence; pub use crate::transaction::{ - AssetIssuance, EcdsaSighashType, OutPoint, PeginData, PegoutData, Transaction, TxIn, + AssetIssuance, EcdsaSighashType, OutPoint, PeginData, PegoutData, Sequence, Transaction, TxIn, TxInWitness, TxOut, TxOutWitness, }; From f3a0afe7f0d3c5f2e65300088293afb9245f4062 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 29 Jun 2026 13:20:25 +0000 Subject: [PATCH 02/12] fuzz: fix typo in generate-files.sh --- .github/workflows/cron-daily-fuzz.yml | 2 +- fuzz/Cargo.toml | 2 +- fuzz/generate-files.sh | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cron-daily-fuzz.yml b/.github/workflows/cron-daily-fuzz.yml index 7a4f4314..7f3af38e 100644 --- a/.github/workflows/cron-daily-fuzz.yml +++ b/.github/workflows/cron-daily-fuzz.yml @@ -1,5 +1,5 @@ ###### -## DO NOT EDIT THIS FILE DIRECTLY. It is generated by generate-fuzz.sh. +## DO NOT EDIT THIS FILE DIRECTLY. It is generated by generate-files.sh. ## Edit that script instead and re-run it. ###### name: Fuzz diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index bf452ae6..14d0784b 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -1,5 +1,5 @@ ###### -## DO NOT EDIT THIS FILE DIRECTLY. It is generated by generate-fuzz.sh. +## DO NOT EDIT THIS FILE DIRECTLY. It is generated by generate-files.sh. ## Edit that script instead and re-run it. ###### [package] diff --git a/fuzz/generate-files.sh b/fuzz/generate-files.sh index cbe54f11..dc19fb28 100755 --- a/fuzz/generate-files.sh +++ b/fuzz/generate-files.sh @@ -11,7 +11,7 @@ source "$REPO_DIR/fuzz/fuzz-util.sh" # 1. Generate fuzz/Cargo.toml cat > "$REPO_DIR/fuzz/Cargo.toml" < "$REPO_DIR/.github/workflows/cron-daily-fuzz.yml" < Date: Wed, 24 Jun 2026 15:29:06 +0000 Subject: [PATCH 03/12] remove Encodable/Decodable impls for PedersenCommitment/Generator/PublicKey These impls shouldn't really be in the public API; we don't ever consensus encode bare secp256k1-zkp objects. We only consensus-encode values and assets and nonces, which have their own dedicated types. Also, as a practical problem, we are going to remove our Encodable/Decodable trait in favor of the bitcoin-consensus-encoding Encode/Decode traits. Since we own neither the secp256k1-zkp objects nor the traits, we won't be able to retain these. --- src/confidential.rs | 58 ++++++++++----------------------------------- 1 file changed, 12 insertions(+), 46 deletions(-) diff --git a/src/confidential.rs b/src/confidential.rs index 2e0ed39a..4a91b960 100644 --- a/src/confidential.rs +++ b/src/confidential.rs @@ -141,18 +141,14 @@ impl Encodable for Value { 1u8.consensus_encode(&mut s)?; Ok(1 + u64::swap_bytes(n).consensus_encode(&mut s)?) } - Value::Confidential(commitment) => commitment.consensus_encode(&mut s), + Value::Confidential(commitment) => { + s.write_all(&commitment.serialize())?; + Ok(33) + } } } } -impl Encodable for PedersenCommitment { - fn consensus_encode(&self, mut e: W) -> Result { - e.write_all(&self.serialize())?; - Ok(33) - } -} - impl Decodable for Value { fn consensus_decode(mut d: D) -> Result { let prefix = u8::consensus_decode(&mut d)?; @@ -174,13 +170,6 @@ impl Decodable for Value { } } -impl Decodable for PedersenCommitment { - fn consensus_decode(d: D) -> Result { - let bytes = <[u8; 33]>::consensus_decode(d)?; - Ok(PedersenCommitment::from_slice(&bytes)?) - } -} - #[cfg(feature = "serde")] impl Serialize for Value { fn serialize(&self, s: S) -> Result { @@ -363,18 +352,14 @@ impl Encodable for Asset { 1u8.consensus_encode(&mut s)?; Ok(1 + n.consensus_encode(&mut s)?) } - Asset::Confidential(generator) => generator.consensus_encode(&mut s) + Asset::Confidential(generator) => { + s.write_all(&generator.serialize())?; + Ok(33) + } } } } -impl Encodable for Generator { - fn consensus_encode(&self, mut e: W) -> Result { - e.write_all(&self.serialize())?; - Ok(33) - } -} - impl Decodable for Asset { fn consensus_decode(mut d: D) -> Result { let prefix = u8::consensus_decode(&mut d)?; @@ -396,14 +381,6 @@ impl Decodable for Asset { } } -impl Decodable for Generator { - fn consensus_decode(d: D) -> Result { - let bytes = <[u8; 33]>::consensus_decode(d)?; - Ok(Generator::from_slice(&bytes)?) - } -} - - #[cfg(feature = "serde")] impl Serialize for Asset { fn serialize(&self, s: S) -> Result { @@ -615,18 +592,14 @@ impl Encodable for Nonce { 1u8.consensus_encode(&mut s)?; Ok(1 + n.consensus_encode(&mut s)?) } - Nonce::Confidential(commitment) => commitment.consensus_encode(&mut s), + Nonce::Confidential(commitment) => { + s.write_all(&commitment.serialize())?; + Ok(33) + } } } } -impl Encodable for PublicKey { - fn consensus_encode(&self, mut e: W) -> Result { - e.write_all(&self.serialize())?; - Ok(33) - } -} - impl Decodable for Nonce { fn consensus_decode(mut d: D) -> Result { let prefix = u8::consensus_decode(&mut d)?; @@ -648,13 +621,6 @@ impl Decodable for Nonce { } } -impl Decodable for PublicKey { - fn consensus_decode(d: D) -> Result { - let bytes = <[u8; 33]>::consensus_decode(d)?; - Ok(PublicKey::from_slice(&bytes)?) - } -} - #[cfg(feature = "serde")] impl Serialize for Nonce { fn serialize(&self, s: S) -> Result { From 9b949f4249f8b3b2cc07d4c22d20003be342adec Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 25 Jun 2026 14:06:58 +0000 Subject: [PATCH 04/12] rename confidential.rs to confidential/mod.rs --- src/{confidential.rs => confidential/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{confidential.rs => confidential/mod.rs} (100%) diff --git a/src/confidential.rs b/src/confidential/mod.rs similarity index 100% rename from src/confidential.rs rename to src/confidential/mod.rs From c2e7136a9e7dd26853725f893ec3f3a51942af40 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Tue, 30 Jun 2026 15:19:51 +0000 Subject: [PATCH 05/12] confidential: encapsulate RangeProof This commit (and the next) are a bit bigger than I'd expected. Essentially, throughout the codebase we use Option> to represent rangerproofs, and similar for surjection proofs (done in the next commit). Aside from being very noisy, this causes a multitude of problems: * we needed the aux traits `BlindValueProofs` and `BlindAssetProofs` to add exact-value methods * there was a type confusion throughout the PSET code where we did not distinguish between empty rangeproofs and unset rangeproofs; we were using None for both * poor encapsulation in general; not only using literal None to mean "empty rangeproof" but mucking around with Option::as_ref and boxing/unboxing makes the code hard to read * we had to impl our Encodable/Decodable traits directly on the secp-zkp types, which we will be unable to do once we move to the new traits This commit replaces the Option> with a new rust-elements RangeProof type. This fixes all the above issues. Also, FYI for new files I am quietly relicensing from CC0 to MIT+Apache. I am not doing this in this non-license-related PR to be sneaky; I was just creating new files so it was an opportunity to use a new license header. See https://github.com/rust-bitcoin/rust-bitcoin/issues/5849 for motivation. --- rustfmt.toml | 3 +- src/blind.rs | 79 +------------ src/confidential/mod.rs | 4 + src/confidential/range_proof.rs | 192 ++++++++++++++++++++++++++++++++ src/lib.rs | 6 +- src/pset/map/input.rs | 32 +++--- src/pset/map/output.rs | 18 +-- src/pset/mod.rs | 30 +++-- src/pset/serialize.rs | 14 +-- src/transaction.rs | 60 +++------- 10 files changed, 273 insertions(+), 165 deletions(-) create mode 100644 src/confidential/range_proof.rs diff --git a/rustfmt.toml b/rustfmt.toml index db148418..77aa0184 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,6 +1,7 @@ ignore = [ "/", - "!/src/lib.rs" + "!/src/lib.rs", + "!/src/confidential/range_proof.rs", ] hard_tabs = false tab_spaces = 4 diff --git a/src/blind.rs b/src/blind.rs index d8e26e6a..f57e9dfe 100644 --- a/src/blind.rs +++ b/src/blind.rs @@ -23,9 +23,9 @@ use secp256k1_zkp::{ rand::{CryptoRng, RngCore}, PedersenCommitment, SecretKey, Tag, Tweak, Verification, ZERO_TWEAK, }; -use secp256k1_zkp::{Generator, RangeProof, Secp256k1, Signing, SurjectionProof}; +use secp256k1_zkp::{Generator, Secp256k1, Signing, SurjectionProof}; -use crate::{AddressParams, Script, TxIn}; +use crate::{AddressParams, RangeProof, Script, TxIn}; use crate::{ confidential::{Asset, AssetBlindingFactor, Nonce, Value, ValueBlindingFactor}, @@ -657,7 +657,7 @@ impl TxOut { out_secrets.asset_bf, ); let exp_value = Value::Explicit(out_secrets.value); - let (out_value, nonce, range_proof) = exp_value.blind( + let (out_value, nonce, rangeproof) = exp_value.blind( secp, out_secrets.value_bf, receiver_blinding_pk, @@ -673,7 +673,7 @@ impl TxOut { script_pubkey: spk, witness: TxOutWitness { surjection_proof: Some(Box::new(surjection_proof)), - rangeproof: Some(Box::new(range_proof)), + rangeproof, }, }; Ok(txout) @@ -977,10 +977,10 @@ impl TxIn { let (comm, prf) = v.blind_with_shared_secret(secp, bf, blind_sk, &spk, &msg)?; if i == 0 { self.asset_issuance.amount = comm; - self.witness.amount_rangeproof = Some(Box::new(prf)); + self.witness.amount_rangeproof = prf; } else { self.asset_issuance.inflation_keys = comm; - self.witness.inflation_keys_rangeproof = Some(Box::new(prf)); + self.witness.inflation_keys_rangeproof = prf; } } Ok(()) @@ -1365,73 +1365,6 @@ impl From for BlindError { } } -/// A trait to create and verify explicit rangeproofs -pub trait BlindValueProofs: Sized { - /// Outputs a `[RangeProof]` that blinded value - /// corresponfs to unblinded explicit value - fn blind_value_proof( - rng: &mut R, - secp: &Secp256k1, - explicit_val: u64, - value_commit: PedersenCommitment, - asset_gen: Generator, - vbf: ValueBlindingFactor, - ) -> Result; - - /// Verify that the Rangeproof proves that commitment - /// is actually bound to the explicit value - fn blind_value_proof_verify( - &self, - secp: &Secp256k1, - explicit_val: u64, - asset_gen: Generator, - value_commit: PedersenCommitment, - ) -> bool; -} - -impl BlindValueProofs for RangeProof { - /// Outputs a [`RangeProof`] that blinded `value_commit` - /// corresponds to explicit value - fn blind_value_proof( - rng: &mut R, - secp: &Secp256k1, - explicit_val: u64, - value_commit: PedersenCommitment, - asset_gen: Generator, - vbf: ValueBlindingFactor, - ) -> Result { - RangeProof::new( - secp, - explicit_val, // min_value - value_commit, // value_commit - explicit_val, // value - vbf.into_inner(), // blinding factor - &[], // message - &[], // add commitment - SecretKey::new(rng), // nonce - -1, // exp - 0, // min bits - asset_gen, // additional gen - ) - } - - /// Verify that the Rangeproof proves that commitment - /// is actually bound to the explicit value - fn blind_value_proof_verify( - &self, - secp: &Secp256k1, - explicit_val: u64, - asset_gen: Generator, - value_commit: PedersenCommitment, - ) -> bool { - let r = self.verify(secp, value_commit, &[], asset_gen); - match r { - Ok(e) => e.start == explicit_val && e.end - 1 == explicit_val, - Err(..) => false, - } - } -} - /// A trait to create and verify explicit surjection proofs pub trait BlindAssetProofs: Sized { /// Outputs a `[SurjectionProof]` that blinded asset diff --git a/src/confidential/mod.rs b/src/confidential/mod.rs index 4a91b960..1765d734 100644 --- a/src/confidential/mod.rs +++ b/src/confidential/mod.rs @@ -17,6 +17,8 @@ //! Structures representing Pedersen commitments of various types //! +mod range_proof; + use crate::hashes::sha256d; use secp256k1_zkp::{self, CommitmentSecrets, Generator, PedersenCommitment, PublicKey, Secp256k1, SecretKey, Signing, Tweak, ZERO_TWEAK, @@ -31,6 +33,8 @@ use std::{fmt, io, ops::{AddAssign, Neg}, str}; use crate::encode::{self, Decodable, Encodable}; use crate::issuance::AssetId; +pub use self::range_proof::RangeProof; + /// A CT commitment to an amount #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] pub enum Value { diff --git a/src/confidential/range_proof.rs b/src/confidential/range_proof.rs new file mode 100644 index 00000000..e9a6c6ab --- /dev/null +++ b/src/confidential/range_proof.rs @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Range Proofs + +use core::convert::TryInto; +use std::io; + +use secp256k1_zkp::rand::{CryptoRng, RngCore}; +use secp256k1_zkp::{self, Generator, PedersenCommitment, Secp256k1, SecretKey, Signing, Tweak}; +#[cfg(feature = "serde")] +use serde::{Deserializer, Serializer}; + +use crate::confidential::ValueBlindingFactor; +use crate::encode; + +/// A range proof, which represents a proof that a confidential value lies within +/// some range (typically `[0, 2^64)`). +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct RangeProof { + inner: Option>, +} + +impl RangeProof { + /// No range proof. + pub const EMPTY: Self = Self { inner: None }; + + /// Constructs a new [`RangeProof`]. + #[allow(clippy::too_many_arguments)] + pub fn new( + secp: &Secp256k1, + min_value: u64, + commitment: PedersenCommitment, + value: u64, + commitment_blinding: Tweak, + message: &[u8], + additional_commitment: &[u8], + sk: SecretKey, + exp: i32, + min_bits: u8, + additional_generator: Generator, + ) -> Result { + secp256k1_zkp::RangeProof::new( + secp, + min_value, + commitment, + value, + commitment_blinding, + message, + additional_commitment, + sk, + exp, + min_bits, + additional_generator, + ) + .map(|inner| Self { inner: Some(Box::new(inner)) }) + } + + /// Parses a [`RangeProof`] from a byte slice (with no length prefix). + pub fn from_slice(sl: &[u8]) -> Result { + if sl.is_empty() { + Ok(Self { inner: None }) + } else { + secp256k1_zkp::RangeProof::from_slice(sl) + .map(|inner| Self { inner: Some(Box::new(inner)) }) + } + } + + /// Outputs a [`RangeProof`] proving that a commitment matches an exact value. + pub fn blind_value_proof( + rng: &mut R, + secp: &Secp256k1, + explicit_val: u64, + value_commit: PedersenCommitment, + asset_gen: Generator, + vbf: ValueBlindingFactor, + ) -> Result { + secp256k1_zkp::RangeProof::new( + secp, + explicit_val, // min_value + value_commit, // value_commit + explicit_val, // value + vbf.into_inner(), // blinding factor + &[], // message + &[], // add commitment + SecretKey::new(rng), // nonce + -1, // exp + 0, // min bits + asset_gen, // additional gen + ) + .map(|inner| Self { inner: Some(Box::new(inner)) }) + } + + /// Verifies a [`RangeProof`] proving that a commitment matches an exact value. + pub fn blind_value_proof_verify( + &self, + secp: &Secp256k1, + explicit_val: u64, + asset_gen: Generator, + value_commit: PedersenCommitment, + ) -> bool { + let Some(inner) = self.inner.as_deref() else { + return false; + }; + if explicit_val == u64::MAX { + // FIXME upstream will panic on this input; we should be able to validate + // proofs with this value. + return false; + } + + let Ok(range) = inner.verify(secp, value_commit, &[], asset_gen) else { + return false; + }; + range == (explicit_val..explicit_val + 1) + } + + /// The length of the range proof (zero if it is empty/absent). + pub fn len(&self) -> usize { self.inner.as_deref().map_or(0, secp256k1_zkp::RangeProof::len) } + + /// Whether the range proof is absent. + pub fn is_empty(&self) -> bool { self.inner.is_none() } + + /// Serializes the range proof as a byte vector. + pub fn to_vec(&self) -> Vec { + match self.inner.as_deref() { + Some(prf) => secp256k1_zkp::RangeProof::serialize(prf), + None => Vec::new(), + } + } + + /// Extracts the minimum value encoded in the range proof. + pub fn minimim_value(&self) -> Option { + // inefficient, consider implementing index on rangeproof + let prf = self.to_vec(); + let byte0 = prf.first()?; + + let has_nonzero_range = byte0 & 64 == 64; + let has_min = byte0 & 32 == 32; + + if !has_min { + None + } else if has_nonzero_range { + let bytes: [u8; 8] = prf.get(2..10)?.try_into().ok()?; + Some(u64::from_be_bytes(bytes)) + } else { + let bytes: [u8; 8] = prf.get(1..9)?.try_into().ok()?; + Some(u64::from_be_bytes(bytes)) + } + } + + /// Obtains a reference to the underlying secp256k1-zkp object. + pub fn as_ref(&self) -> Option<&secp256k1_zkp::RangeProof> { self.inner.as_deref() } +} + +impl crate::encode::Encodable for RangeProof { + fn consensus_encode(&self, e: W) -> Result { + self.to_vec().consensus_encode(e) + } +} + +impl crate::encode::Decodable for RangeProof { + fn consensus_decode(d: D) -> Result { + let v = Vec::::consensus_decode(d)?; + if v.is_empty() { + Ok(Self { inner: None }) + } else { + secp256k1_zkp::RangeProof::from_slice(&v) + .map(|inner| Self { inner: Some(Box::new(inner)) }) + .map_err(encode::Error::Secp256k1zkp) + } + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for RangeProof { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.inner.serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for RangeProof { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Option::::deserialize(deserializer) + .map(|inner| Self { inner: inner.map(Box::new) }) + } +} diff --git a/src/lib.rs b/src/lib.rs index 2fc14b6c..1b019567 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,11 +79,11 @@ pub mod genesis; // export everything at the top level so it can be used as `elements::Transaction` etc. pub use crate::address::{Address, AddressError, AddressParams}; pub use crate::blind::{ - BlindAssetProofs, BlindError, BlindValueProofs, ConfidentialTxOutError, CtLocation, - CtLocationType, RangeProofMessage, SurjectionInput, TxOutError, TxOutSecrets, UnblindError, - VerificationError, + BlindAssetProofs, BlindError, ConfidentialTxOutError, CtLocation, CtLocationType, + RangeProofMessage, SurjectionInput, TxOutError, TxOutSecrets, UnblindError, VerificationError, }; pub use crate::block::{Block, BlockHeader, DynafedRoot, ExtData as BlockExtData}; +pub use crate::confidential::RangeProof; pub use crate::ext::{ReadExt, WriteExt}; pub use crate::fast_merkle_root::fast_merkle_root; pub use crate::hash_types::*; diff --git a/src/pset/map/input.rs b/src/pset/map/input.rs index ae67f854..cd8ab629 100644 --- a/src/pset/map/input.rs +++ b/src/pset/map/input.rs @@ -32,10 +32,10 @@ use crate::pset::raw; use crate::pset::serialize; use crate::pset::{self, error, Error}; use crate::{transaction::SighashTypeParseError, SchnorrSighashType}; -use crate::{AssetIssuance, BlockHash, EcdsaSighashType, Script, Transaction, TxIn, TxOut, Txid}; +use crate::{AssetIssuance, BlockHash, EcdsaSighashType, RangeProof, Script, Transaction, TxIn, TxOut, Txid}; use bitcoin::bip32::KeySource; use bitcoin::{PublicKey, key::XOnlyPublicKey}; -use secp256k1_zkp::{self, RangeProof, SurjectionProof, Tweak, ZERO_TWEAK}; +use secp256k1_zkp::{self, SurjectionProof, Tweak, ZERO_TWEAK}; use crate::{OutPoint, Sequence}; @@ -262,9 +262,9 @@ pub struct Input { /// The issuance value commitment pub issuance_value_comm: Option, /// Issuance value rangeproof - pub issuance_value_rangeproof: Option>, + pub issuance_value_rangeproof: Option, /// Issuance keys rangeproof - pub issuance_keys_rangeproof: Option>, + pub issuance_keys_rangeproof: Option, /// Pegin Transaction. Should be a `bitcoin::Transaction` pub pegin_tx: Option, /// Pegin Transaction proof @@ -287,15 +287,15 @@ pub struct Input { /// Issuance asset entropy pub issuance_asset_entropy: Option<[u8; 32]>, /// input utxo rangeproof - pub in_utxo_rangeproof: Option>, + pub in_utxo_rangeproof: Option, /// Proof that blinded issuance matches the commitment - pub in_issuance_blind_value_proof: Option>, + pub in_issuance_blind_value_proof: Option, /// Proof that blinded inflation keys matches the corresponding commitment - pub in_issuance_blind_inflation_keys_proof: Option>, + pub in_issuance_blind_inflation_keys_proof: Option, /// The explicit amount of the input pub amount: Option, /// The blind value rangeproof - pub blind_value_proof: Option>, + pub blind_value_proof: Option, /// The input explicit asset pub asset: Option, /// The blind asset surjection proof @@ -540,8 +540,8 @@ impl Input { } // Witness - ret.issuance_keys_rangeproof = txin.witness.inflation_keys_rangeproof; - ret.issuance_value_rangeproof = txin.witness.amount_rangeproof; + ret.issuance_keys_rangeproof = Some(txin.witness.inflation_keys_rangeproof); + ret.issuance_value_rangeproof = Some(txin.witness.amount_rangeproof); } ret } @@ -746,10 +746,10 @@ impl Map for Input { impl_pset_prop_insert_pair!(self.issuance_value_comm <= | ); } PSBT_ELEMENTS_IN_ISSUANCE_VALUE_RANGEPROOF => { - impl_pset_prop_insert_pair!(self.issuance_value_rangeproof <= | >); + impl_pset_prop_insert_pair!(self.issuance_value_rangeproof <= | ); } PSBT_ELEMENTS_IN_ISSUANCE_KEYS_RANGEPROOF => { - impl_pset_prop_insert_pair!(self.issuance_keys_rangeproof <= | >); + impl_pset_prop_insert_pair!(self.issuance_keys_rangeproof <= | ); } PSBT_ELEMENTS_IN_PEG_IN_TX => { impl_pset_prop_insert_pair!(self.pegin_tx <= | ); @@ -783,19 +783,19 @@ impl Map for Input { impl_pset_prop_insert_pair!(self.issuance_asset_entropy <= | ); } PSBT_ELEMENTS_IN_UTXO_RANGEPROOF => { - impl_pset_prop_insert_pair!(self.in_utxo_rangeproof <= | >); + impl_pset_prop_insert_pair!(self.in_utxo_rangeproof <= | ); } PSBT_ELEMENTS_IN_ISSUANCE_BLIND_VALUE_PROOF => { - impl_pset_prop_insert_pair!(self.in_issuance_blind_value_proof <= | >); + impl_pset_prop_insert_pair!(self.in_issuance_blind_value_proof <= | ); } PSBT_ELEMENTS_IN_ISSUANCE_BLIND_INFLATION_KEYS_PROOF => { - impl_pset_prop_insert_pair!(self.in_issuance_blind_inflation_keys_proof <= | >); + impl_pset_prop_insert_pair!(self.in_issuance_blind_inflation_keys_proof <= | ); } PSBT_ELEMENTS_IN_EXPLICIT_VALUE => { impl_pset_prop_insert_pair!(self.amount <= | ); } PSBT_ELEMENTS_IN_VALUE_PROOF => { - impl_pset_prop_insert_pair!(self.blind_value_proof <= | >); + impl_pset_prop_insert_pair!(self.blind_value_proof <= | ); } PSBT_ELEMENTS_IN_EXPLICIT_ASSET => { impl_pset_prop_insert_pair!(self.asset <= | ); diff --git a/src/pset/map/output.rs b/src/pset/map/output.rs index 69e361c4..8b35288e 100644 --- a/src/pset/map/output.rs +++ b/src/pset/map/output.rs @@ -23,10 +23,10 @@ use crate::pset::map::Map; use crate::pset::raw; use crate::pset::Error; use crate::{confidential, pset}; -use crate::{encode, Script, TxOutWitness}; +use crate::{encode, RangeProof, Script, TxOutWitness}; use bitcoin::bip32::KeySource; use bitcoin::{PublicKey, key::XOnlyPublicKey}; -use secp256k1_zkp::{self, Generator, RangeProof, SurjectionProof}; +use secp256k1_zkp::{self, Generator, SurjectionProof}; use crate::issuance; @@ -116,7 +116,7 @@ pub struct Output { pub asset_comm: Option, // Proprietary key-value pairs for this output. /// Output value rangeproof - pub value_rangeproof: Option>, + pub value_rangeproof: Option, /// Output Asset surjection proof pub asset_surjection_proof: Option>, /// Blinding pubkey which is used in receiving address @@ -126,7 +126,7 @@ pub struct Output { /// The index of the input whose owner should blind this output pub blinder_index: Option, /// The blind value rangeproof - pub blind_value_proof: Option>, + pub blind_value_proof: Option, /// The blind asset surjection proof pub blind_asset_proof: Option>, /// Pset @@ -233,7 +233,7 @@ impl Output { }); } rv.script_pubkey = txout.script_pubkey; - rv.value_rangeproof = txout.witness.rangeproof; + rv.value_rangeproof = Some(txout.witness.rangeproof); rv.asset_surjection_proof = txout.witness.surjection_proof; rv } @@ -262,7 +262,9 @@ impl Output { script_pubkey: self.script_pubkey.clone(), witness: TxOutWitness { surjection_proof: self.asset_surjection_proof.clone(), - rangeproof: self.value_rangeproof.clone(), + rangeproof: self.value_rangeproof + .clone() + .unwrap_or(RangeProof::EMPTY), }, } } @@ -354,7 +356,7 @@ impl Map for Output { impl_pset_prop_insert_pair!(self.asset_comm <= | ); } PSBT_ELEMENTS_OUT_VALUE_RANGEPROOF => { - impl_pset_prop_insert_pair!(self.value_rangeproof <= | >); + impl_pset_prop_insert_pair!(self.value_rangeproof <= | ); } PSBT_ELEMENTS_OUT_ASSET_SURJECTION_PROOF => { impl_pset_prop_insert_pair!(self.asset_surjection_proof <= | >); @@ -369,7 +371,7 @@ impl Map for Output { impl_pset_prop_insert_pair!(self.blinder_index <= | ); } PSBT_ELEMENTS_OUT_BLIND_VALUE_PROOF => { - impl_pset_prop_insert_pair!(self.blind_value_proof <= | >); + impl_pset_prop_insert_pair!(self.blind_value_proof <= | ); } PSBT_ELEMENTS_OUT_BLIND_ASSET_PROOF => { impl_pset_prop_insert_pair!(self.blind_asset_proof <= | >); diff --git a/src/pset/mod.rs b/src/pset/mod.rs index a61a3610..f51e23ce 100644 --- a/src/pset/mod.rs +++ b/src/pset/mod.rs @@ -38,20 +38,20 @@ mod str; #[cfg(feature = "base64")] pub use self::str::ParseError; -use crate::blind::{BlindAssetProofs, BlindValueProofs}; +use crate::blind::BlindAssetProofs; use crate::confidential; use crate::encode::{self, Decodable, Encodable}; use crate::{ blind::RangeProofMessage, confidential::{AssetBlindingFactor, ValueBlindingFactor}, - TxOutSecrets, + RangeProof, TxOutSecrets, }; use crate::{ LockTime, OutPoint, Sequence, SurjectionInput, Transaction, TxIn, TxInWitness, TxOut, TxOutWitness, Txid, CtLocation, CtLocationType, }; use secp256k1_zkp::rand::{CryptoRng, RngCore}; -use secp256k1_zkp::{self, RangeProof, SecretKey, SurjectionProof}; +use secp256k1_zkp::{self, SecretKey, SurjectionProof}; pub use self::error::{Error, PsetBlindError, PsetHash}; use self::map::Map; @@ -300,8 +300,12 @@ impl PartiallySignedTransaction { sequence: psetin.sequence.unwrap_or(Sequence::MAX), asset_issuance: psetin.asset_issuance(), witness: TxInWitness { - amount_rangeproof: psetin.issuance_value_rangeproof.clone(), - inflation_keys_rangeproof: psetin.issuance_keys_rangeproof.clone(), + amount_rangeproof: psetin.issuance_value_rangeproof + .clone() + .unwrap_or(RangeProof::EMPTY), + inflation_keys_rangeproof: psetin.issuance_keys_rangeproof + .clone() + .unwrap_or(RangeProof::EMPTY), script_witness: psetin .final_script_witness .as_ref() @@ -336,7 +340,9 @@ impl PartiallySignedTransaction { script_pubkey: out.script_pubkey.clone(), witness: TxOutWitness { surjection_proof: out.asset_surjection_proof.clone(), - rangeproof: out.value_rangeproof.clone(), + rangeproof: out.value_rangeproof + .clone() + .unwrap_or(RangeProof::EMPTY), }, }; outputs.push(txout); @@ -518,7 +524,7 @@ impl PartiallySignedTransaction { // mutate the pset { - self.outputs[i].value_rangeproof = txout.witness.rangeproof; + self.outputs[i].value_rangeproof = Some(txout.witness.rangeproof); self.outputs[i].asset_surjection_proof = txout.witness.surjection_proof; self.outputs[i].amount_comm = txout.value.commitment(); self.outputs[i].asset_comm = txout.asset.commitment(); @@ -541,10 +547,10 @@ impl PartiallySignedTransaction { let value_comm = self.outputs[i] .amount_comm .expect("Blinding proof successful"); - self.outputs[i].blind_value_proof = Some(Box::new( + self.outputs[i].blind_value_proof = Some( RangeProof::blind_value_proof(rng, secp, value, value_comm, asset_gen, vbf) .map_err(|e| PsetBlindError::BlindingProofsCreationError(i, e))?, - )); + ); } // return blinding factors used let location = CtLocation{ input_index: i, ty: CtLocationType::Input}; @@ -670,7 +676,7 @@ impl PartiallySignedTransaction { // mutate the pset { - self.outputs[last_out_index].value_rangeproof = Some(Box::new(rangeproof)); + self.outputs[last_out_index].value_rangeproof = Some(rangeproof); self.outputs[last_out_index].asset_surjection_proof = Some(Box::new(surjection_proof)); self.outputs[last_out_index].amount_comm = value_commitment.commitment(); self.outputs[last_out_index].asset_comm = out_asset_commitment.commitment(); @@ -693,10 +699,10 @@ impl PartiallySignedTransaction { let value_comm = self.outputs[last_out_index] .amount_comm .expect("Blinding proof successful"); - self.outputs[last_out_index].blind_value_proof = Some(Box::new( + self.outputs[last_out_index].blind_value_proof = Some( RangeProof::blind_value_proof(rng, secp, value, value_comm, asset_gen, final_vbf) .map_err(|e| PsetBlindError::BlindingProofsCreationError(last_out_index, e))?, - )); + ); self.global.scalars.clear(); } diff --git a/src/pset/serialize.rs b/src/pset/serialize.rs index 5b152c04..6d247404 100644 --- a/src/pset/serialize.rs +++ b/src/pset/serialize.rs @@ -24,12 +24,12 @@ use crate::encode::{ self, deserialize, deserialize_partial, serialize, Decodable, Encodable, VarInt, }; use crate::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; -use crate::{AssetId, BlockHash, Script, Transaction, TxOut, Txid}; +use crate::{AssetId, BlockHash, RangeProof, Script, Transaction, TxOut, Txid}; use bitcoin; use bitcoin::bip32::{ChildNumber, Fingerprint, KeySource}; use bitcoin::{key::XOnlyPublicKey, PublicKey}; use internals::slice::SliceExt; -use secp256k1_zkp::{self, RangeProof, SurjectionProof, Tweak}; +use secp256k1_zkp::{self, SurjectionProof, Tweak}; use super::map::{PsbtSighashType, TapTree}; use crate::schnorr; @@ -282,17 +282,15 @@ impl Deserialize for confidential::Asset { } } -impl Serialize for Box { +impl Serialize for RangeProof { fn serialize(&self) -> Vec { - RangeProof::serialize(self) + self.to_vec() } } -impl Deserialize for Box { +impl Deserialize for RangeProof { fn deserialize(bytes: &[u8]) -> Result { - let prf = RangeProof::from_slice(bytes) - .map_err(|_| encode::Error::ParseFailed("Invalid Rangeproof"))?; - Ok(Box::new(prf)) + Self::from_slice(bytes).map_err(encode::Error::Secp256k1zkp) } } diff --git a/src/transaction.rs b/src/transaction.rs index 1ba454b7..822a571e 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -30,10 +30,8 @@ use crate::issuance::{AssetEntropy, AssetId}; use crate::opcodes; use crate::parse::impl_parse_str_through_int; use crate::script::Instruction; -use crate::{LockTime, Script, Txid, Wtxid}; -use secp256k1_zkp::{ - RangeProof, SurjectionProof, Tweak, ZERO_TWEAK, -}; +use crate::{LockTime, RangeProof, Script, Txid, Wtxid}; +use secp256k1_zkp::{SurjectionProof, Tweak, ZERO_TWEAK}; /// Description of an asset issuance in a transaction input #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] @@ -362,9 +360,9 @@ impl std::error::Error for RelativeLockTimeError { #[derive(Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] pub struct TxInWitness { /// Amount rangeproof - pub amount_rangeproof: Option>, + pub amount_rangeproof: RangeProof, /// Rangeproof for inflation keys - pub inflation_keys_rangeproof: Option>, + pub inflation_keys_rangeproof: RangeProof, /// Traditional script witness pub script_witness: Vec>, /// Pegin witness, basically the same thing @@ -377,8 +375,8 @@ impl TxInWitness { /// Create an empty input witness. pub fn empty() -> Self { TxInWitness { - amount_rangeproof: None, - inflation_keys_rangeproof: None, + amount_rangeproof: RangeProof::EMPTY, + inflation_keys_rangeproof: RangeProof::EMPTY, script_witness: Vec::new(), pegin_witness: Vec::new(), } @@ -386,8 +384,8 @@ impl TxInWitness { /// Whether this witness is null pub fn is_empty(&self) -> bool { - self.amount_rangeproof.is_none() && - self.inflation_keys_rangeproof.is_none() && + self.amount_rangeproof.is_empty() && + self.inflation_keys_rangeproof.is_empty() && self.script_witness.is_empty() && self.pegin_witness.is_empty() } @@ -645,10 +643,8 @@ pub struct TxOutWitness { // We Box it because surjection proof internally is an array [u8; N] that // allocates on stack even when the surjection proof is empty pub surjection_proof: Option>, - /// Rangeproof showing that the value commitment is legitimate - // We Box it because range proof internally is an array [u8; N] that - // allocates on stack even when the range proof is empty - pub rangeproof: Option>, + /// Rangeproof showing that the value commitment is legitimate. + pub rangeproof: RangeProof, } serde_struct_impl!(TxOutWitness, surjection_proof, rangeproof); impl_consensus_encoding!(TxOutWitness, surjection_proof, rangeproof); @@ -658,18 +654,18 @@ impl TxOutWitness { pub fn empty() -> Self { TxOutWitness { surjection_proof: None, - rangeproof: None, + rangeproof: RangeProof::EMPTY, } } /// Whether this witness is null pub fn is_empty(&self) -> bool { - self.surjection_proof.is_none() && self.rangeproof.is_none() + self.surjection_proof.is_none() && self.rangeproof.is_empty() } /// The rangeproof len if is present, otherwise 0 pub fn rangeproof_len(&self) -> usize { - self.rangeproof.as_ref().map_or(0, |prf| prf.len()) + self.rangeproof.len() } /// The surjection proof len if is present, otherwise 0 @@ -839,29 +835,7 @@ impl TxOut { confidential::Value::Null => min_value, confidential::Value::Explicit(n) => n, confidential::Value::Confidential(..) => { - match &self.witness.rangeproof { - None => min_value, - Some(prf) => { - // inefficient, consider implementing index on rangeproof - let prf = prf.serialize(); - debug_assert!(prf.len() > 10); - - let has_nonzero_range = prf[0] & 64 == 64; - let has_min = prf[0] & 32 == 32; - - if !has_min { - min_value - } else if has_nonzero_range { - bitcoin::consensus::deserialize::(&prf[2..10]) - .expect("any 8 bytes is a u64") - .swap_bytes() // min-value is BE - } else { - bitcoin::consensus::deserialize::(&prf[1..9]) - .expect("any 8 bytes is a u64") - .swap_bytes() // min-value is BE - } - } - } + self.witness.rangeproof.minimim_value().unwrap_or(min_value) } } } @@ -974,10 +948,8 @@ impl Transaction { 0 } ) + if witness_flag { - let amt_prf_len = input.witness.amount_rangeproof.as_ref() - .map_or(0, |x| x.len()); - let keys_prf_len = input.witness.inflation_keys_rangeproof.as_ref() - .map_or(0, |x| x.len()); + let amt_prf_len = input.witness.amount_rangeproof.len(); + let keys_prf_len = input.witness.inflation_keys_rangeproof.len(); VarInt(amt_prf_len as u64).size() + amt_prf_len + From c76a63a8b2df3491a683c3109ef89df35a3ea8b7 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 24 Jun 2026 15:40:13 +0000 Subject: [PATCH 06/12] confidential: encapsulate SurjectionProof All the justification from the previous commit message apply here. --- rustfmt.toml | 1 + src/blind.rs | 63 +----------- src/confidential/mod.rs | 2 + src/confidential/surjection_proof.rs | 145 +++++++++++++++++++++++++++ src/encode.rs | 56 +---------- src/lib.rs | 6 +- src/pset/map/input.rs | 8 +- src/pset/map/output.rs | 18 ++-- src/pset/mod.rs | 21 ++-- src/pset/serialize.rs | 14 ++- src/transaction.rs | 18 ++-- 11 files changed, 198 insertions(+), 154 deletions(-) create mode 100644 src/confidential/surjection_proof.rs diff --git a/rustfmt.toml b/rustfmt.toml index 77aa0184..13332480 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -2,6 +2,7 @@ ignore = [ "/", "!/src/lib.rs", "!/src/confidential/range_proof.rs", + "!/src/confidential/surjection_proof.rs", ] hard_tabs = false tab_spaces = 4 diff --git a/src/blind.rs b/src/blind.rs index f57e9dfe..65ae360e 100644 --- a/src/blind.rs +++ b/src/blind.rs @@ -23,9 +23,9 @@ use secp256k1_zkp::{ rand::{CryptoRng, RngCore}, PedersenCommitment, SecretKey, Tag, Tweak, Verification, ZERO_TWEAK, }; -use secp256k1_zkp::{Generator, Secp256k1, Signing, SurjectionProof}; +use secp256k1_zkp::{Generator, Secp256k1, Signing}; -use crate::{AddressParams, RangeProof, Script, TxIn}; +use crate::{AddressParams, RangeProof, Script, TxIn, SurjectionProof}; use crate::{ confidential::{Asset, AssetBlindingFactor, Nonce, Value, ValueBlindingFactor}, @@ -497,13 +497,7 @@ impl Asset { }) .collect::, _>>()?; - let surjection_proof = SurjectionProof::new( - secp, - rng, - asset.into_tag(), - asset_bf.into_inner(), - inputs.as_ref(), - )?; + let surjection_proof = SurjectionProof::new(secp, rng, asset, asset_bf, inputs)?; Ok((out_asset, surjection_proof)) } @@ -672,7 +666,7 @@ impl TxOut { nonce, script_pubkey: spk, witness: TxOutWitness { - surjection_proof: Some(Box::new(surjection_proof)), + surjection_proof, rangeproof, }, }; @@ -1365,55 +1359,6 @@ impl From for BlindError { } } -/// A trait to create and verify explicit surjection proofs -pub trait BlindAssetProofs: Sized { - /// Outputs a `[SurjectionProof]` that blinded asset - /// corresponfs to unblinded explicit asset - fn blind_asset_proof( - rng: &mut R, - secp: &Secp256k1, - asset: AssetId, - abf: AssetBlindingFactor, - ) -> Result; - - /// Verify that the Surjection proves that asset commitment - /// is actually bound to the explicit asset - fn blind_asset_proof_verify( - &self, - secp: &Secp256k1, - asset: AssetId, - asset_commit: Generator, - ) -> bool; -} - -impl BlindAssetProofs for SurjectionProof { - fn blind_asset_proof( - rng: &mut R, - secp: &Secp256k1, - asset: AssetId, - abf: AssetBlindingFactor, - ) -> Result { - let gen = Generator::new_unblinded(secp, asset.into_tag()); - SurjectionProof::new( - secp, - rng, - asset.into_tag(), - abf.into_inner(), - &[(gen, asset.into_tag(), ZERO_TWEAK)], - ) - } - - fn blind_asset_proof_verify( - &self, - secp: &Secp256k1, - asset: AssetId, - asset_commit: Generator, - ) -> bool { - let gen = Generator::new_unblinded(secp, asset.into_tag()); - self.verify(secp, asset_commit, &[gen]) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/confidential/mod.rs b/src/confidential/mod.rs index 1765d734..d85df050 100644 --- a/src/confidential/mod.rs +++ b/src/confidential/mod.rs @@ -18,6 +18,7 @@ //! mod range_proof; +mod surjection_proof; use crate::hashes::sha256d; use secp256k1_zkp::{self, CommitmentSecrets, Generator, PedersenCommitment, @@ -34,6 +35,7 @@ use crate::encode::{self, Decodable, Encodable}; use crate::issuance::AssetId; pub use self::range_proof::RangeProof; +pub use self::surjection_proof::SurjectionProof; /// A CT commitment to an amount #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] diff --git a/src/confidential/surjection_proof.rs b/src/confidential/surjection_proof.rs new file mode 100644 index 00000000..ad3d3e3f --- /dev/null +++ b/src/confidential/surjection_proof.rs @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Surjection Proofs + +use std::io; + +use secp256k1_zkp::rand::{CryptoRng, RngCore}; +use secp256k1_zkp::{self, Generator, Secp256k1, Signing, Tweak, ZERO_TWEAK}; +#[cfg(feature = "serde")] +use serde::{Deserializer, Serializer}; + +use crate::confidential::{AssetBlindingFactor, AssetId}; +use crate::encode; + +/// A surjection proof, proving that an asset commitment commits to the same asset ID +/// as a commitment from a given set. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct SurjectionProof { + inner: Option>, +} + +impl SurjectionProof { + /// No surjection proof. + pub const EMPTY: Self = Self { inner: None }; + + /// Constructs a new [`SurjectionProof`]. + pub fn new( + secp: &Secp256k1, + rng: &mut R, + asset: AssetId, + asset_bf: AssetBlindingFactor, + inputs: S, + ) -> Result + where + R: RngCore + CryptoRng, + C: Signing, + S: AsRef<[(Generator, secp256k1_zkp::Tag, Tweak)]>, + { + secp256k1_zkp::SurjectionProof::new( + secp, + rng, + asset.into_tag(), + asset_bf.into_inner(), + inputs.as_ref(), + ) + .map(|inner| Self { inner: Some(Box::new(inner)) }) + } + + /// Parses a [`SurjectionProof`] from a byte slice (with no length prefix). + pub fn from_slice(sl: &[u8]) -> Result { + if sl.is_empty() { + Ok(Self { inner: None }) + } else { + secp256k1_zkp::SurjectionProof::from_slice(sl) + .map(|inner| Self { inner: Some(Box::new(inner)) }) + } + } + + /// Serializes the surjection proof as a byte vector. + pub fn to_vec(&self) -> Vec { + match self.inner.as_deref() { + Some(prf) => secp256k1_zkp::SurjectionProof::serialize(prf), + None => Vec::new(), + } + } + + /// Outputs a [`SurjectionProof`] proving that an asset matches an exact asset ID. + pub fn blind_asset_proof( + rng: &mut R, + secp: &Secp256k1, + asset: AssetId, + abf: AssetBlindingFactor, + ) -> Result { + let gen = Generator::new_unblinded(secp, asset.into_tag()); + SurjectionProof::new(secp, rng, asset, abf, [(gen, asset.into_tag(), ZERO_TWEAK)]) + } + + /// Verifies a [`SurjectionProof`] proving that an asset matches an exact asset ID. + pub fn blind_asset_proof_verify( + &self, + secp: &Secp256k1, + asset: AssetId, + asset_commit: Generator, + ) -> bool { + let gen = Generator::new_unblinded(secp, asset.into_tag()); + match self.inner.as_deref() { + Some(inner) => inner.verify(secp, asset_commit, &[gen]), + None => false, + } + } + + /// The length of the range proof (zero if it is empty/absent). + pub fn len(&self) -> usize { + self.inner.as_deref().map_or(0, secp256k1_zkp::SurjectionProof::len) + } + + /// Whether the surjectionproof is absent. + pub fn is_empty(&self) -> bool { self.inner.is_none() } + + /// Obtains a reference to the underlying secp256k1-zkp object. + pub fn as_ref(&self) -> Option<&secp256k1_zkp::SurjectionProof> { self.inner.as_deref() } +} + +impl crate::encode::Encodable for SurjectionProof { + fn consensus_encode(&self, e: W) -> Result { + match self.inner.as_ref() { + Some(prf) => secp256k1_zkp::SurjectionProof::serialize(prf).consensus_encode(e), + None => <[u8]>::consensus_encode(&[], e), + } + } +} + +impl crate::encode::Decodable for SurjectionProof { + fn consensus_decode(d: D) -> Result { + let v = Vec::::consensus_decode(d)?; + if v.is_empty() { + Ok(Self { inner: None }) + } else { + secp256k1_zkp::SurjectionProof::from_slice(&v) + .map(|inner| Self { inner: Some(Box::new(inner)) }) + .map_err(encode::Error::Secp256k1zkp) + } + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for SurjectionProof { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.inner.serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for SurjectionProof { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Option::::deserialize(deserializer) + .map(|inner| Self { inner: inner.map(Box::new) }) + } +} diff --git a/src/encode.rs b/src/encode.rs index 068a3c94..a1204062 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -20,7 +20,7 @@ use std::{any, error, fmt, io, mem}; use bitcoin::ScriptBuf; use hex::{DecodeFixedLengthBytesError, DecodeVariableLengthBytesError}; -use secp256k1_zkp::{self, RangeProof, SurjectionProof, Tweak}; +use secp256k1_zkp::{self, Tweak}; use crate::hashes::{sha256, Hash}; use crate::pset; @@ -417,31 +417,6 @@ impl_array!(20); impl_array!(32); impl_array!(33); -macro_rules! impl_box_option { - ($type: ty) => { - impl Encodable for Option> { - #[inline] - fn consensus_encode(&self, e: W) -> Result { - match self { - None => Vec::::new().consensus_encode(e), - Some(v) => v.serialize().consensus_encode(e), - } - } - } - - impl Decodable for Option> { - #[inline] - fn consensus_decode(mut d: D) -> Result { - let v: Vec = Decodable::consensus_decode(&mut d)?; - if v.is_empty() { - Ok(None) - } else { - Ok(Some(Box::new(<$type>::from_slice(&v)?))) - } - } - } - }; -} // special implementations for elements only fields impl Encodable for Tweak { fn consensus_encode(&self, e: W) -> Result { @@ -455,32 +430,6 @@ impl Decodable for Tweak { } } -impl Encodable for RangeProof { - fn consensus_encode(&self, e: W) -> Result { - self.serialize().consensus_encode(e) - } -} - -impl Decodable for RangeProof { - fn consensus_decode(d: D) -> Result { - Ok(RangeProof::from_slice(&>::consensus_decode(d)?)?) - } -} - -impl Encodable for SurjectionProof { - fn consensus_encode(&self, e: W) -> Result { - self.serialize().consensus_encode(e) - } -} - -impl Decodable for SurjectionProof { - fn consensus_decode(d: D) -> Result { - Ok(SurjectionProof::from_slice(&>::consensus_decode( - d, - )?)?) - } -} - impl Encodable for sha256::Hash { fn consensus_encode(&self, s: S) -> Result { self.to_byte_array().consensus_encode(s) @@ -508,6 +457,3 @@ impl Decodable for TapLeafHash { )) } } - -impl_box_option!(RangeProof); -impl_box_option!(SurjectionProof); diff --git a/src/lib.rs b/src/lib.rs index 1b019567..18c92cea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,11 +79,11 @@ pub mod genesis; // export everything at the top level so it can be used as `elements::Transaction` etc. pub use crate::address::{Address, AddressError, AddressParams}; pub use crate::blind::{ - BlindAssetProofs, BlindError, ConfidentialTxOutError, CtLocation, CtLocationType, - RangeProofMessage, SurjectionInput, TxOutError, TxOutSecrets, UnblindError, VerificationError, + BlindError, ConfidentialTxOutError, CtLocation, CtLocationType, RangeProofMessage, + SurjectionInput, TxOutError, TxOutSecrets, UnblindError, VerificationError, }; pub use crate::block::{Block, BlockHeader, DynafedRoot, ExtData as BlockExtData}; -pub use crate::confidential::RangeProof; +pub use crate::confidential::{RangeProof, SurjectionProof}; pub use crate::ext::{ReadExt, WriteExt}; pub use crate::fast_merkle_root::fast_merkle_root; pub use crate::hash_types::*; diff --git a/src/pset/map/input.rs b/src/pset/map/input.rs index cd8ab629..ecd0af54 100644 --- a/src/pset/map/input.rs +++ b/src/pset/map/input.rs @@ -32,10 +32,10 @@ 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}; +use crate::{AssetIssuance, BlockHash, EcdsaSighashType, RangeProof, Script, Transaction, TxIn, TxOut, Txid, SurjectionProof}; use bitcoin::bip32::KeySource; use bitcoin::{PublicKey, key::XOnlyPublicKey}; -use secp256k1_zkp::{self, SurjectionProof, Tweak, ZERO_TWEAK}; +use secp256k1_zkp::{self, Tweak, ZERO_TWEAK}; use crate::{OutPoint, Sequence}; @@ -299,7 +299,7 @@ pub struct Input { /// The input explicit asset pub asset: Option, /// The blind asset surjection proof - pub blind_asset_proof: Option>, + pub blind_asset_proof: Option, /// Whether the issuance is blinded pub blinded_issuance: Option, /// Other fields @@ -801,7 +801,7 @@ impl Map for Input { impl_pset_prop_insert_pair!(self.asset <= | ); } PSBT_ELEMENTS_IN_ASSET_PROOF => { - impl_pset_prop_insert_pair!(self.blind_asset_proof <= | >); + impl_pset_prop_insert_pair!(self.blind_asset_proof <= | ); } PSBT_ELEMENTS_IN_BLINDED_ISSUANCE => { impl_pset_prop_insert_pair!(self.blinded_issuance <= | ); diff --git a/src/pset/map/output.rs b/src/pset/map/output.rs index 8b35288e..ef0be12c 100644 --- a/src/pset/map/output.rs +++ b/src/pset/map/output.rs @@ -23,10 +23,10 @@ use crate::pset::map::Map; use crate::pset::raw; use crate::pset::Error; use crate::{confidential, pset}; -use crate::{encode, RangeProof, Script, TxOutWitness}; +use crate::{encode, RangeProof, Script, TxOutWitness, SurjectionProof}; use bitcoin::bip32::KeySource; use bitcoin::{PublicKey, key::XOnlyPublicKey}; -use secp256k1_zkp::{self, Generator, SurjectionProof}; +use secp256k1_zkp::{self, Generator}; use crate::issuance; @@ -118,7 +118,7 @@ pub struct Output { /// Output value rangeproof pub value_rangeproof: Option, /// Output Asset surjection proof - pub asset_surjection_proof: Option>, + pub asset_surjection_proof: Option, /// Blinding pubkey which is used in receiving address pub blinding_key: Option, /// The ephermal pk sampled by sender @@ -128,7 +128,7 @@ pub struct Output { /// The blind value rangeproof pub blind_value_proof: Option, /// The blind asset surjection proof - pub blind_asset_proof: Option>, + pub blind_asset_proof: Option, /// Pset /// Other fields #[cfg_attr( @@ -234,7 +234,7 @@ impl Output { } rv.script_pubkey = txout.script_pubkey; rv.value_rangeproof = Some(txout.witness.rangeproof); - rv.asset_surjection_proof = txout.witness.surjection_proof; + rv.asset_surjection_proof = Some(txout.witness.surjection_proof); rv } @@ -261,7 +261,9 @@ impl Output { .unwrap_or_default(), script_pubkey: self.script_pubkey.clone(), witness: TxOutWitness { - surjection_proof: self.asset_surjection_proof.clone(), + surjection_proof: self.asset_surjection_proof + .clone() + .unwrap_or(SurjectionProof::EMPTY), rangeproof: self.value_rangeproof .clone() .unwrap_or(RangeProof::EMPTY), @@ -359,7 +361,7 @@ impl Map for Output { impl_pset_prop_insert_pair!(self.value_rangeproof <= | ); } PSBT_ELEMENTS_OUT_ASSET_SURJECTION_PROOF => { - impl_pset_prop_insert_pair!(self.asset_surjection_proof <= | >); + impl_pset_prop_insert_pair!(self.asset_surjection_proof <= | ); } PSBT_ELEMENTS_OUT_BLINDING_PUBKEY => { impl_pset_prop_insert_pair!(self.blinding_key <= | ); @@ -374,7 +376,7 @@ impl Map for Output { impl_pset_prop_insert_pair!(self.blind_value_proof <= | ); } PSBT_ELEMENTS_OUT_BLIND_ASSET_PROOF => { - impl_pset_prop_insert_pair!(self.blind_asset_proof <= | >); + impl_pset_prop_insert_pair!(self.blind_asset_proof <= | ); } _ => match self.proprietary.entry(prop_key) { Entry::Vacant(empty_key) => { diff --git a/src/pset/mod.rs b/src/pset/mod.rs index f51e23ce..aeb3cea7 100644 --- a/src/pset/mod.rs +++ b/src/pset/mod.rs @@ -38,20 +38,19 @@ mod str; #[cfg(feature = "base64")] pub use self::str::ParseError; -use crate::blind::BlindAssetProofs; use crate::confidential; use crate::encode::{self, Decodable, Encodable}; use crate::{ blind::RangeProofMessage, confidential::{AssetBlindingFactor, ValueBlindingFactor}, - RangeProof, TxOutSecrets, + RangeProof, SurjectionProof, TxOutSecrets, }; use crate::{ LockTime, OutPoint, Sequence, SurjectionInput, Transaction, TxIn, TxInWitness, TxOut, TxOutWitness, Txid, CtLocation, CtLocationType, }; use secp256k1_zkp::rand::{CryptoRng, RngCore}; -use secp256k1_zkp::{self, SecretKey, SurjectionProof}; +use secp256k1_zkp::{self, SecretKey}; pub use self::error::{Error, PsetBlindError, PsetHash}; use self::map::Map; @@ -339,7 +338,9 @@ impl PartiallySignedTransaction { .unwrap_or_default(), script_pubkey: out.script_pubkey.clone(), witness: TxOutWitness { - surjection_proof: out.asset_surjection_proof.clone(), + surjection_proof: out.asset_surjection_proof + .clone() + .unwrap_or(SurjectionProof::EMPTY), rangeproof: out.value_rangeproof .clone() .unwrap_or(RangeProof::EMPTY), @@ -525,7 +526,7 @@ impl PartiallySignedTransaction { // mutate the pset { self.outputs[i].value_rangeproof = Some(txout.witness.rangeproof); - self.outputs[i].asset_surjection_proof = txout.witness.surjection_proof; + self.outputs[i].asset_surjection_proof = Some(txout.witness.surjection_proof); self.outputs[i].amount_comm = txout.value.commitment(); self.outputs[i].asset_comm = txout.asset.commitment(); self.outputs[i].ecdh_pubkey = @@ -536,10 +537,10 @@ impl PartiallySignedTransaction { let asset_id = self.outputs[i] .asset .ok_or(PsetBlindError::MustHaveExplicitTxOut(i))?; - self.outputs[i].blind_asset_proof = Some(Box::new( + self.outputs[i].blind_asset_proof = Some( SurjectionProof::blind_asset_proof(rng, secp, asset_id, abf) .map_err(|e| PsetBlindError::BlindingProofsCreationError(i, e))?, - )); + ); let asset_gen = self.outputs[i] .asset_comm @@ -677,7 +678,7 @@ impl PartiallySignedTransaction { // mutate the pset { self.outputs[last_out_index].value_rangeproof = Some(rangeproof); - self.outputs[last_out_index].asset_surjection_proof = Some(Box::new(surjection_proof)); + self.outputs[last_out_index].asset_surjection_proof = Some(surjection_proof); self.outputs[last_out_index].amount_comm = value_commitment.commitment(); self.outputs[last_out_index].asset_comm = out_asset_commitment.commitment(); self.outputs[last_out_index].ecdh_pubkey = @@ -688,10 +689,10 @@ impl PartiallySignedTransaction { let asset_id = self.outputs[last_out_index] .asset .ok_or(PsetBlindError::MustHaveExplicitTxOut(last_out_index))?; - self.outputs[last_out_index].blind_asset_proof = Some(Box::new( + self.outputs[last_out_index].blind_asset_proof = Some( SurjectionProof::blind_asset_proof(rng, secp, asset_id, out_abf) .map_err(|e| PsetBlindError::BlindingProofsCreationError(last_out_index, e))?, - )); + ); let asset_gen = self.outputs[last_out_index] .asset_comm diff --git a/src/pset/serialize.rs b/src/pset/serialize.rs index 6d247404..16e3b8b1 100644 --- a/src/pset/serialize.rs +++ b/src/pset/serialize.rs @@ -24,12 +24,12 @@ use crate::encode::{ self, deserialize, deserialize_partial, serialize, Decodable, Encodable, VarInt, }; use crate::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; -use crate::{AssetId, BlockHash, RangeProof, Script, Transaction, TxOut, Txid}; +use crate::{AssetId, BlockHash, RangeProof, Script, SurjectionProof, Transaction, TxOut, Txid}; use bitcoin; use bitcoin::bip32::{ChildNumber, Fingerprint, KeySource}; use bitcoin::{key::XOnlyPublicKey, PublicKey}; use internals::slice::SliceExt; -use secp256k1_zkp::{self, SurjectionProof, Tweak}; +use secp256k1_zkp::{self, Tweak}; use super::map::{PsbtSighashType, TapTree}; use crate::schnorr; @@ -294,17 +294,15 @@ impl Deserialize for RangeProof { } } -impl Serialize for Box { +impl Serialize for SurjectionProof { fn serialize(&self) -> Vec { - SurjectionProof::serialize(self) + self.to_vec() } } -impl Deserialize for Box { +impl Deserialize for SurjectionProof { fn deserialize(bytes: &[u8]) -> Result { - let prf = SurjectionProof::from_slice(bytes) - .map_err(|_| encode::Error::ParseFailed("Invalid SurjectionProof"))?; - Ok(Box::new(prf)) + Self::from_slice(bytes).map_err(encode::Error::Secp256k1zkp) } } diff --git a/src/transaction.rs b/src/transaction.rs index 822a571e..694c43ac 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -30,8 +30,10 @@ use crate::issuance::{AssetEntropy, AssetId}; use crate::opcodes; use crate::parse::impl_parse_str_through_int; use crate::script::Instruction; -use crate::{LockTime, RangeProof, Script, Txid, Wtxid}; -use secp256k1_zkp::{SurjectionProof, Tweak, ZERO_TWEAK}; +use crate::{LockTime, RangeProof, Script, SurjectionProof, Txid, Wtxid}; +use secp256k1_zkp::{ + Tweak, ZERO_TWEAK, +}; /// Description of an asset issuance in a transaction input #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] @@ -642,8 +644,10 @@ pub struct TxOutWitness { /// Surjection proof showing that the asset commitment is legitimate // We Box it because surjection proof internally is an array [u8; N] that // allocates on stack even when the surjection proof is empty - pub surjection_proof: Option>, - /// Rangeproof showing that the value commitment is legitimate. + pub surjection_proof: SurjectionProof, + /// Rangeproof showing that the value commitment is legitimate + // We Box it because range proof internally is an array [u8; N] that + // allocates on stack even when the range proof is empty pub rangeproof: RangeProof, } serde_struct_impl!(TxOutWitness, surjection_proof, rangeproof); @@ -653,14 +657,14 @@ impl TxOutWitness { /// Create an empty output witness. pub fn empty() -> Self { TxOutWitness { - surjection_proof: None, + surjection_proof: SurjectionProof::EMPTY, rangeproof: RangeProof::EMPTY, } } /// Whether this witness is null pub fn is_empty(&self) -> bool { - self.surjection_proof.is_none() && self.rangeproof.is_empty() + self.surjection_proof.is_empty() && self.rangeproof.is_empty() } /// The rangeproof len if is present, otherwise 0 @@ -670,7 +674,7 @@ impl TxOutWitness { /// The surjection proof len if is present, otherwise 0 pub fn surjectionproof_len(&self) -> usize { - self.surjection_proof.as_ref().map_or(0, |prf| prf.len()) + self.surjection_proof.len() } } From 34be108f91663422a8d5c09373798b8868e37072 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 25 Jun 2026 12:25:13 +0000 Subject: [PATCH 07/12] confidential: improve unit tests by defining constants --- src/confidential/mod.rs | 142 ++++++++++++++++++++++------------------ 1 file changed, 80 insertions(+), 62 deletions(-) diff --git a/src/confidential/mod.rs b/src/confidential/mod.rs index d85df050..23510c17 100644 --- a/src/confidential/mod.rs +++ b/src/confidential/mod.rs @@ -1086,83 +1086,125 @@ mod tests { #[cfg(feature = "serde")] use bincode; + const VALUE_EXPLICIT: [u8; 9] = [ 1, 0, 0, 0, 0, 0, 0, 3, 232 ]; + + const VALUE_COMMITMENT1: [u8; 33] = [ + 0x08, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, + ]; + + const VALUE_COMMITMENT2: [u8; 33] = [ + 0x09, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, + ]; + + const NONCE_EXPLICIT: [u8; 33] = [ + 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]; + + const NONCE_COMMITMENT1: [u8; 33] = [ + 0x02, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, + ]; + + const NONCE_COMMITMENT2: [u8; 33] = [ + 0x03, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, + ]; + + const ASSET_EXPLICIT: [u8; 33] = [ + 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]; + + const ASSET_COMMITMENT1: [u8; 33] = [ + 0x0a, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, + ]; + + const ASSET_COMMITMENT2: [u8; 33] = [ + 0x0b, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, + ]; + #[test] fn encode_length() { + let val_encodings = [ + vec![0], + VALUE_EXPLICIT.to_vec(), + VALUE_COMMITMENT1.to_vec(), + VALUE_COMMITMENT2.to_vec(), + ]; let vals = [ Value::Null, Value::Explicit(1000), - Value::from_commitment(&[ - 0x08, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, - ]) - .unwrap(), + Value::from_commitment(&VALUE_COMMITMENT1).unwrap(), + Value::from_commitment(&VALUE_COMMITMENT2).unwrap(), ]; - for v in &vals[..] { + for (v, enc) in vals.iter().zip(val_encodings.iter()) { let mut x = vec![]; assert_eq!(v.consensus_encode(&mut x).unwrap(), v.encoded_length()); assert_eq!(x.len(), v.encoded_length()); + assert_eq!(x, *enc); } + let nonce_encodings = [ + vec![0], + NONCE_EXPLICIT.to_vec(), + NONCE_COMMITMENT1.to_vec(), + NONCE_COMMITMENT2.to_vec(), + ]; let nonces = [ Nonce::Null, Nonce::Explicit([0; 32]), - Nonce::from_commitment(&[ - 0x02, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, - ]) - .unwrap(), + Nonce::from_commitment(&NONCE_COMMITMENT1).unwrap(), + Nonce::from_commitment(&NONCE_COMMITMENT2).unwrap(), ]; - for v in &nonces[..] { + for (v, enc) in nonces.iter().zip(nonce_encodings.iter()) { let mut x = vec![]; assert_eq!(v.consensus_encode(&mut x).unwrap(), v.encoded_length()); assert_eq!(x.len(), v.encoded_length()); + assert_eq!(x, *enc); } + let asset_encodings = [ + vec![0], + ASSET_EXPLICIT.to_vec(), + ASSET_COMMITMENT1.to_vec(), + ASSET_COMMITMENT2.to_vec(), + ]; let assets = [ Asset::Null, Asset::Explicit(AssetId::from_byte_array([0; 32])), - Asset::from_commitment(&[ - 0x0a, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, - ]) - .unwrap(), + Asset::from_commitment(&ASSET_COMMITMENT1).unwrap(), + Asset::from_commitment(&ASSET_COMMITMENT2).unwrap(), ]; - for v in &assets[..] { + for (v, enc) in assets.iter().zip(asset_encodings.iter()) { let mut x = vec![]; assert_eq!(v.consensus_encode(&mut x).unwrap(), v.encoded_length()); assert_eq!(x.len(), v.encoded_length()); + assert_eq!(x, *enc); } } #[test] fn commitments() { - let x = Value::from_commitment(&[ - 0x08, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, - ]) - .unwrap(); + let x = Value::from_commitment(&VALUE_COMMITMENT1).unwrap(); let commitment = x.commitment().unwrap(); let mut commitment = commitment.serialize(); assert_eq!(x, Value::from_commitment(&commitment[..]).unwrap()); commitment[0] = 42; assert!(Value::from_commitment(&commitment[..]).is_err()); - let x = Asset::from_commitment(&[ - 0x0a, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, - ]) - .unwrap(); + let x = Asset::from_commitment(&ASSET_COMMITMENT1).unwrap(); let commitment = x.commitment().unwrap(); let mut commitment = commitment.serialize(); assert_eq!(x, Asset::from_commitment(&commitment[..]).unwrap()); commitment[0] = 42; assert!(Asset::from_commitment(&commitment[..]).is_err()); - let x = Nonce::from_commitment(&[ - 0x02, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, - ]) - .unwrap(); + let x = Nonce::from_commitment(&NONCE_COMMITMENT1).unwrap(); let commitment = x.commitment().unwrap(); let mut commitment = commitment.serialize(); assert_eq!(x, Nonce::from_commitment(&commitment[..]).unwrap()); @@ -1186,11 +1228,7 @@ mod tests { ] ); - let value = Value::from_commitment(&[ - 0x08, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - ]).unwrap(); + let value = Value::from_commitment(&VALUE_COMMITMENT1).unwrap(); assert_tokens( &value.readable(), &[ @@ -1207,13 +1245,7 @@ mod tests { &[ Token::Seq { len: Some(2) }, Token::U8(2), - Token::Bytes( - &[ - 8, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 - ] - ), + Token::Bytes(&VALUE_COMMITMENT1), Token::SeqEnd ] ); @@ -1264,11 +1296,7 @@ mod tests { ] ); - let asset = Asset::from_commitment(&[ - 0x0a, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - ]).unwrap(); + let asset = Asset::from_commitment(&ASSET_COMMITMENT1).unwrap(); assert_tokens( &asset.readable(), &[ @@ -1285,13 +1313,7 @@ mod tests { &[ Token::Seq { len: Some(2) }, Token::U8(2), - Token::Bytes( - &[ - 10, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 - ] - ), + Token::Bytes(&ASSET_COMMITMENT1), Token::SeqEnd ] ); @@ -1335,11 +1357,7 @@ mod tests { ] ); - let nonce = Nonce::from_commitment(&[ - 0x02, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - ]).unwrap(); + let nonce = Nonce::from_commitment(&NONCE_COMMITMENT1).unwrap(); assert_tokens( &nonce.readable(), &[ From 0321359c0af79d7b7e652c68472f2c42e2d3cffa Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 25 Jun 2026 14:10:14 +0000 Subject: [PATCH 08/12] confidential: move commitment types into their own modules The code for value, nonce, asset, have diverged in various ways. By splitting them into multiple files I hope to make diffing them easier. Code move only; the next commits will make chonges to bring the three files closer together. Also I am quietly relicensing this stuff from CC0 to MIT+Apache. I am not doing this to be sneaky; I was just creating new files so it was an opportunity to use a new license header. See https://github.com/rust-bitcoin/rust-bitcoin/issues/5849 --- src/confidential/asset.rs | 362 +++++++++++++ src/confidential/mod.rs | 1007 +------------------------------------ src/confidential/nonce.rs | 255 ++++++++++ src/confidential/value.rs | 416 +++++++++++++++ 4 files changed, 1044 insertions(+), 996 deletions(-) create mode 100644 src/confidential/asset.rs create mode 100644 src/confidential/nonce.rs create mode 100644 src/confidential/value.rs diff --git a/src/confidential/asset.rs b/src/confidential/asset.rs new file mode 100644 index 00000000..1b782f7a --- /dev/null +++ b/src/confidential/asset.rs @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Confiential Assets + +use core::{fmt, str}; +use std::io; + +use secp256k1_zkp::{self, Generator, Secp256k1, Signing, Tweak, ZERO_TWEAK}; +use secp256k1_zkp::rand::Rng; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use crate::encode::{self, Decodable, Encodable}; +use crate::issuance::AssetId; + +/// A CT commitment to an asset +#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum Asset { + /// No value + #[default] + Null, + /// Asset entropy is explicitly encoded + Explicit(AssetId), + /// Asset is committed + Confidential(Generator), +} + +impl Asset { + /// Create asset commitment. + pub fn new_confidential( + secp: &Secp256k1, + asset: AssetId, + bf: AssetBlindingFactor, + ) -> Self { + Asset::Confidential(Generator::new_blinded( + secp, + asset.into_tag(), + bf.into_inner(), + )) + } + + /// Serialized length, in bytes + pub fn encoded_length(&self) -> usize { + match *self { + Asset::Null => 1, + Asset::Explicit(..) => 33, + Asset::Confidential(..) => 33, + } + } + + /// Create from commitment. + pub fn from_commitment(bytes: &[u8]) -> Result { + Ok(Asset::Confidential(Generator::from_slice(bytes)?)) + } + + /// Check if the object is null. + pub fn is_null(&self) -> bool { + matches!(*self, Asset::Null) + } + + /// Check if the object is explicit. + pub fn is_explicit(&self) -> bool { + matches!(*self, Asset::Explicit(_)) + } + + /// Check if the object is confidential. + pub fn is_confidential(&self) -> bool { + matches!(*self, Asset::Confidential(_)) + } + + /// Returns the explicit inner value. + /// Returns [None] if [`Asset::is_explicit`] returns false. + pub fn explicit(&self) -> Option { + match *self { + Asset::Explicit(i) => Some(i), + _ => None, + } + } + + /// Returns the confidential commitment in case of a confidential value. + /// Returns [None] if [`Asset::is_confidential`] returns false. + pub fn commitment(&self) -> Option { + match *self { + Asset::Confidential(i) => Some(i), + _ => None, + } + } + + /// Internally used function for getting the generator from asset + /// Used in the amount verification check + /// Returns [`None`] is the asset is [`Asset::Null`] + /// Converts a explicit asset into a generator and returns the confidential + /// generator as is. + pub fn into_asset_gen ( + self, + secp: &Secp256k1, + ) -> Option { + match self { + // Only error is Null error which is dealt with later + // when we have more context information about it. + Asset::Null => None, + Asset::Explicit(x) => { + Some(Generator::new_unblinded(secp, x.into_tag())) + } + Asset::Confidential(gen) => Some(gen), + } + } +} + +impl From for Asset { + fn from(from: Generator) -> Self { + Asset::Confidential(from) + } +} + +impl fmt::Display for Asset { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Asset::Null => f.write_str("null"), + Asset::Explicit(n) => write!(f, "{}", n), + Asset::Confidential(generator) => write!(f, "{:02x}", generator), + } + } +} + +impl Encodable for Asset { + fn consensus_encode(&self, mut s: S) -> Result { + match *self { + Asset::Null => 0u8.consensus_encode(s), + Asset::Explicit(n) => { + 1u8.consensus_encode(&mut s)?; + Ok(1 + n.consensus_encode(&mut s)?) + } + Asset::Confidential(generator) => { + s.write_all(&generator.serialize())?; + Ok(33) + } + } + } +} + +impl Decodable for Asset { + fn consensus_decode(mut d: D) -> Result { + let prefix = u8::consensus_decode(&mut d)?; + + match prefix { + 0 => Ok(Asset::Null), + 1 => { + let explicit = Decodable::consensus_decode(&mut d)?; + Ok(Asset::Explicit(explicit)) + } + p if p == 0x0a || p == 0x0b => { + let mut comm = [0u8; 33]; + comm[0] = p; + d.read_exact(&mut comm[1..])?; + Ok(Asset::Confidential(Generator::from_slice(&comm[..])?)) + } + p => Err(encode::Error::InvalidConfidentialPrefix(p)), + } + } +} + +#[cfg(feature = "serde")] +impl Serialize for Asset { + fn serialize(&self, s: S) -> Result { + use serde::ser::SerializeSeq; + + let seq_len = match *self { + Asset::Null => 1, + Asset::Explicit(_) | Asset::Confidential(_) => 2 + }; + let mut seq = s.serialize_seq(Some(seq_len))?; + + match *self { + Asset::Null => seq.serialize_element(&0u8)?, + Asset::Explicit(n) => { + seq.serialize_element(&1u8)?; + seq.serialize_element(&n)?; + } + Asset::Confidential(commitment) => { + seq.serialize_element(&2u8)?; + seq.serialize_element(&commitment)?; + } + } + seq.end() + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Asset { + fn deserialize>(d: D) -> Result { + use serde::de::{Error, SeqAccess, Visitor}; + struct CommitVisitor; + + impl<'de> Visitor<'de> for CommitVisitor { + type Value = Asset; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("a committed value") + } + + fn visit_seq>(self, mut access: A) -> Result { + let prefix = access.next_element::()?; + match prefix { + Some(0) => Ok(Asset::Null), + Some(1) => { + match access.next_element()? { + Some(x) => Ok(Asset::Explicit(x)), + None => Err(A::Error::custom("missing explicit asset")), + } + } + Some(2) => { + match access.next_element()? { + Some(x) => Ok(Asset::Confidential(x)), + None => Err(A::Error::custom("missing generator")), + } + } + _ => Err(A::Error::custom("wrong or missing prefix")), + } + } + } + + d.deserialize_seq(CommitVisitor) + } +} + + +/// Blinding factor used for asset commitments. +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] +pub struct AssetBlindingFactor(pub(crate) Tweak); + +impl AssetBlindingFactor { + /// Generate random asset blinding factor. + pub fn new(rng: &mut R) -> Self { + AssetBlindingFactor(Tweak::new(rng)) + } + + /// Parse a blinding factor from a 64-character hex string. + #[deprecated(since = "0.27.0", note = "use s.parse() instead")] + pub fn from_hex(s: &str) -> Result { + s.parse() + } + + /// Create from bytes. + pub fn from_byte_array(bytes: [u8; 32]) -> Result { + Ok(AssetBlindingFactor(Tweak::from_inner(bytes)?)) + } + + /// Create from bytes. + pub fn from_slice(bytes: &[u8]) -> Result { + Ok(AssetBlindingFactor(Tweak::from_slice(bytes)?)) + } + + /// Returns the inner value. + pub fn into_inner(self) -> Tweak { + self.0 + } + + /// Get a unblinded/zero `AssetBlinding` factor + pub fn zero() -> Self { + AssetBlindingFactor(ZERO_TWEAK) + } +} + +impl core::borrow::Borrow<[u8]> for AssetBlindingFactor { + fn borrow(&self) -> &[u8] { &self.0[..] } +} + +hex::impl_fmt_traits! { + #[display_backward(true)] + impl fmt_traits for AssetBlindingFactor { + const LENGTH: usize = 32; + } +} + +impl str::FromStr for AssetBlindingFactor { + type Err = encode::Error; + + fn from_str(s: &str) -> Result { + let mut slice: [u8; 32] = hex::decode_to_array(s)?; + slice.reverse(); + + let inner = Tweak::from_inner(slice)?; + Ok(AssetBlindingFactor(inner)) + } +} + +#[cfg(feature = "serde")] +impl Serialize for AssetBlindingFactor { + fn serialize(&self, s: S) -> Result { + if s.is_human_readable() { + s.collect_str(&self) + } else { + s.serialize_bytes(&self.0[..]) + } + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for AssetBlindingFactor { + fn deserialize>(d: D) -> Result { + if d.is_human_readable() { + struct HexVisitor; + + impl ::serde::de::Visitor<'_> for HexVisitor { + type Value = AssetBlindingFactor; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + formatter.write_str("an ASCII hex string") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: ::serde::de::Error, + { + if let Ok(hex) = ::std::str::from_utf8(v) { + hex.parse().map_err(E::custom) + } else { + Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self)) + } + } + + fn visit_str(self, v: &str) -> Result + where + E: ::serde::de::Error, + { + v.parse().map_err(E::custom) + } + } + + d.deserialize_str(HexVisitor) + } else { + struct BytesVisitor; + + impl ::serde::de::Visitor<'_> for BytesVisitor { + type Value = AssetBlindingFactor; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + formatter.write_str("a bytestring") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: ::serde::de::Error, + { + use core::convert::TryFrom; + + match <[u8; 32]>::try_from(v) { + Ok(ret) => { + let inner = Tweak::from_inner(ret).map_err(E::custom)?; + Ok(AssetBlindingFactor(inner)) + } + Err(_) => Err(E::invalid_length(v.len(), &stringify!($len))), + } + } + } + + d.deserialize_bytes(BytesVisitor) + } + } +} + diff --git a/src/confidential/mod.rs b/src/confidential/mod.rs index 23510c17..c10a50e7 100644 --- a/src/confidential/mod.rs +++ b/src/confidential/mod.rs @@ -17,679 +17,24 @@ //! Structures representing Pedersen commitments of various types //! +mod asset; +mod nonce; mod range_proof; mod surjection_proof; +mod value; -use crate::hashes::sha256d; -use secp256k1_zkp::{self, CommitmentSecrets, Generator, PedersenCommitment, - PublicKey, Secp256k1, SecretKey, Signing, Tweak, ZERO_TWEAK, - compute_adaptive_blinding_factor, - rand::{CryptoRng, Rng, RngCore} -}; -#[cfg(feature = "serde")] -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use secp256k1_zkp; -use std::{fmt, io, ops::{AddAssign, Neg}, str}; +use core::fmt; -use crate::encode::{self, Decodable, Encodable}; +use crate::encode; use crate::issuance::AssetId; +pub use self::asset::{Asset, AssetBlindingFactor}; +pub use self::nonce::Nonce; pub use self::range_proof::RangeProof; pub use self::surjection_proof::SurjectionProof; - -/// A CT commitment to an amount -#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub enum Value { - /// No value - #[default] - Null, - /// Value is explicitly encoded - Explicit(u64), - /// Value is committed - Confidential(PedersenCommitment), -} - -impl Value { - /// Create value commitment. - pub fn new_confidential( - secp: &Secp256k1, - value: u64, - asset: Generator, - bf: ValueBlindingFactor, - ) -> Self { - Value::Confidential(PedersenCommitment::new(secp, value, bf.0, asset)) - } - - /// Create value commitment from assetID, asset blinding factor, - /// value and value blinding factor - pub fn new_confidential_from_assetid( - secp: &Secp256k1, - value: u64, - asset: AssetId, - v_bf: ValueBlindingFactor, - a_bf: AssetBlindingFactor, - ) -> Self { - let generator = Generator::new_blinded(secp, asset.into_tag(), a_bf.0); - let comm = PedersenCommitment::new(secp, value, v_bf.0, generator); - - Value::Confidential(comm) - } - - /// Serialized length, in bytes - pub fn encoded_length(&self) -> usize { - match *self { - Value::Null => 1, - Value::Explicit(..) => 9, - Value::Confidential(..) => 33, - } - } - - /// Create from commitment. - pub fn from_commitment(bytes: &[u8]) -> Result { - Ok(Value::Confidential(PedersenCommitment::from_slice(bytes)?)) - } - - /// Check if the object is null. - pub fn is_null(&self) -> bool { - matches!(*self, Value::Null) - } - - /// Check if the object is explicit. - pub fn is_explicit(&self) -> bool { - matches!(*self, Value::Explicit(_)) - } - - /// Check if the object is confidential. - pub fn is_confidential(&self) -> bool { - matches!(*self, Value::Confidential(_)) - } - - /// Returns the explicit inner value. - /// Returns [None] if [`Value::is_explicit`] returns false. - pub fn explicit(&self) -> Option { - match *self { - Value::Explicit(i) => Some(i), - _ => None, - } - } - - /// Returns the confidential commitment in case of a confidential value. - /// Returns [None] if [`Value::is_confidential`] returns false. - pub fn commitment(&self) -> Option { - match *self { - Value::Confidential(i) => Some(i), - _ => None, - } - } -} - -impl From for Value { - fn from(from: PedersenCommitment) -> Self { - Value::Confidential(from) - } -} - -impl fmt::Display for Value { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Value::Null => f.write_str("null"), - Value::Explicit(n) => write!(f, "{}", n), - Value::Confidential(commitment) => write!(f, "{:02x}", commitment), - } - } -} - -impl Encodable for Value { - fn consensus_encode(&self, mut s: S) -> Result { - match *self { - Value::Null => 0u8.consensus_encode(s), - Value::Explicit(n) => { - 1u8.consensus_encode(&mut s)?; - Ok(1 + u64::swap_bytes(n).consensus_encode(&mut s)?) - } - Value::Confidential(commitment) => { - s.write_all(&commitment.serialize())?; - Ok(33) - } - } - } -} - -impl Decodable for Value { - fn consensus_decode(mut d: D) -> Result { - let prefix = u8::consensus_decode(&mut d)?; - - match prefix { - 0 => Ok(Value::Null), - 1 => { - let explicit = u64::swap_bytes(Decodable::consensus_decode(&mut d)?); - Ok(Value::Explicit(explicit)) - } - p if p == 0x08 || p == 0x09 => { - let mut comm = [0u8; 33]; - comm[0] = p; - d.read_exact(&mut comm[1..])?; - Ok(Value::Confidential(PedersenCommitment::from_slice(&comm)?)) - } - p => Err(encode::Error::InvalidConfidentialPrefix(p)), - } - } -} - -#[cfg(feature = "serde")] -impl Serialize for Value { - fn serialize(&self, s: S) -> Result { - use serde::ser::SerializeSeq; - - let seq_len = match *self { - Value::Null => 1, - Value::Explicit(_) | Value::Confidential(_) => 2 - }; - let mut seq = s.serialize_seq(Some(seq_len))?; - - match *self { - Value::Null => seq.serialize_element(&0u8)?, - Value::Explicit(n) => { - seq.serialize_element(&1u8)?; - seq.serialize_element(&u64::swap_bytes(n))?; - } - Value::Confidential(commitment) => { - seq.serialize_element(&2u8)?; - seq.serialize_element(&commitment)?; - } - } - seq.end() - } -} - -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for Value { - fn deserialize>(d: D) -> Result { - use serde::de::{Error, SeqAccess, Visitor}; - struct CommitVisitor; - - impl<'de> Visitor<'de> for CommitVisitor { - type Value = Value; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("a committed value") - } - - fn visit_seq>(self, mut access: A) -> Result { - let prefix = access.next_element::()?; - match prefix { - Some(0) => Ok(Value::Null), - Some(1) => { - match access.next_element()? { - Some(x) => Ok(Value::Explicit(u64::swap_bytes(x))), - None => Err(A::Error::custom("missing explicit value")), - } - } - Some(2) => { - match access.next_element()? { - Some(x) => Ok(Value::Confidential(x)), - None => Err(A::Error::custom("missing pedersen commitment")), - } - } - _ => Err(A::Error::custom("wrong or missing prefix")), - } - } - } - - d.deserialize_seq(CommitVisitor) - } -} - -/// A CT commitment to an asset -#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub enum Asset { - /// No value - #[default] - Null, - /// Asset entropy is explicitly encoded - Explicit(AssetId), - /// Asset is committed - Confidential(Generator), -} - -impl Asset { - /// Create asset commitment. - pub fn new_confidential( - secp: &Secp256k1, - asset: AssetId, - bf: AssetBlindingFactor, - ) -> Self { - Asset::Confidential(Generator::new_blinded( - secp, - asset.into_tag(), - bf.into_inner(), - )) - } - - /// Serialized length, in bytes - pub fn encoded_length(&self) -> usize { - match *self { - Asset::Null => 1, - Asset::Explicit(..) => 33, - Asset::Confidential(..) => 33, - } - } - - /// Create from commitment. - pub fn from_commitment(bytes: &[u8]) -> Result { - Ok(Asset::Confidential(Generator::from_slice(bytes)?)) - } - - /// Check if the object is null. - pub fn is_null(&self) -> bool { - matches!(*self, Asset::Null) - } - - /// Check if the object is explicit. - pub fn is_explicit(&self) -> bool { - matches!(*self, Asset::Explicit(_)) - } - - /// Check if the object is confidential. - pub fn is_confidential(&self) -> bool { - matches!(*self, Asset::Confidential(_)) - } - - /// Returns the explicit inner value. - /// Returns [None] if [`Asset::is_explicit`] returns false. - pub fn explicit(&self) -> Option { - match *self { - Asset::Explicit(i) => Some(i), - _ => None, - } - } - - /// Returns the confidential commitment in case of a confidential value. - /// Returns [None] if [`Asset::is_confidential`] returns false. - pub fn commitment(&self) -> Option { - match *self { - Asset::Confidential(i) => Some(i), - _ => None, - } - } - - /// Internally used function for getting the generator from asset - /// Used in the amount verification check - /// Returns [`None`] is the asset is [`Asset::Null`] - /// Converts a explicit asset into a generator and returns the confidential - /// generator as is. - pub fn into_asset_gen ( - self, - secp: &Secp256k1, - ) -> Option { - match self { - // Only error is Null error which is dealt with later - // when we have more context information about it. - Asset::Null => None, - Asset::Explicit(x) => { - Some(Generator::new_unblinded(secp, x.into_tag())) - } - Asset::Confidential(gen) => Some(gen), - } - } -} - -impl From for Asset { - fn from(from: Generator) -> Self { - Asset::Confidential(from) - } -} - -impl fmt::Display for Asset { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Asset::Null => f.write_str("null"), - Asset::Explicit(n) => write!(f, "{}", n), - Asset::Confidential(generator) => write!(f, "{:02x}", generator), - } - } -} - -impl Encodable for Asset { - fn consensus_encode(&self, mut s: S) -> Result { - match *self { - Asset::Null => 0u8.consensus_encode(s), - Asset::Explicit(n) => { - 1u8.consensus_encode(&mut s)?; - Ok(1 + n.consensus_encode(&mut s)?) - } - Asset::Confidential(generator) => { - s.write_all(&generator.serialize())?; - Ok(33) - } - } - } -} - -impl Decodable for Asset { - fn consensus_decode(mut d: D) -> Result { - let prefix = u8::consensus_decode(&mut d)?; - - match prefix { - 0 => Ok(Asset::Null), - 1 => { - let explicit = Decodable::consensus_decode(&mut d)?; - Ok(Asset::Explicit(explicit)) - } - p if p == 0x0a || p == 0x0b => { - let mut comm = [0u8; 33]; - comm[0] = p; - d.read_exact(&mut comm[1..])?; - Ok(Asset::Confidential(Generator::from_slice(&comm[..])?)) - } - p => Err(encode::Error::InvalidConfidentialPrefix(p)), - } - } -} - -#[cfg(feature = "serde")] -impl Serialize for Asset { - fn serialize(&self, s: S) -> Result { - use serde::ser::SerializeSeq; - - let seq_len = match *self { - Asset::Null => 1, - Asset::Explicit(_) | Asset::Confidential(_) => 2 - }; - let mut seq = s.serialize_seq(Some(seq_len))?; - - match *self { - Asset::Null => seq.serialize_element(&0u8)?, - Asset::Explicit(n) => { - seq.serialize_element(&1u8)?; - seq.serialize_element(&n)?; - } - Asset::Confidential(commitment) => { - seq.serialize_element(&2u8)?; - seq.serialize_element(&commitment)?; - } - } - seq.end() - } -} - -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for Asset { - fn deserialize>(d: D) -> Result { - use serde::de::{Error, SeqAccess, Visitor}; - struct CommitVisitor; - - impl<'de> Visitor<'de> for CommitVisitor { - type Value = Asset; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("a committed value") - } - - fn visit_seq>(self, mut access: A) -> Result { - let prefix = access.next_element::()?; - match prefix { - Some(0) => Ok(Asset::Null), - Some(1) => { - match access.next_element()? { - Some(x) => Ok(Asset::Explicit(x)), - None => Err(A::Error::custom("missing explicit asset")), - } - } - Some(2) => { - match access.next_element()? { - Some(x) => Ok(Asset::Confidential(x)), - None => Err(A::Error::custom("missing generator")), - } - } - _ => Err(A::Error::custom("wrong or missing prefix")), - } - } - } - - d.deserialize_seq(CommitVisitor) - } -} - -/// A CT commitment to an output nonce (i.e. a public key) -#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub enum Nonce { - /// No value - #[default] - Null, - /// There should be no such thing as an "explicit nonce", but Elements will deserialize - /// such a thing (and insists that its size be 32 bytes). So we stick a 32-byte type here - /// that implements all the traits we need. - Explicit([u8; 32]), - /// Nonce is committed - Confidential(PublicKey), -} - -impl Nonce { - /// Create nonce commitment. - pub fn new_confidential( - rng: &mut R, - secp: &Secp256k1, - receiver_blinding_pk: &PublicKey, - ) -> (Self, SecretKey) { - let ephemeral_sk = SecretKey::new(rng); - Self::with_ephemeral_sk(secp, ephemeral_sk, receiver_blinding_pk) - } - - /// Similar to [`Nonce::new_confidential`], but with a given `ephemeral_sk` - /// instead of sampling it from rng. - pub fn with_ephemeral_sk( - secp: &Secp256k1, - ephemeral_sk: SecretKey, - receiver_blinding_pk: &PublicKey - ) -> (Self, SecretKey) { - let sender_pk = PublicKey::from_secret_key(secp, &ephemeral_sk); - let shared_secret = Self::make_shared_secret(receiver_blinding_pk, &ephemeral_sk); - (Nonce::Confidential(sender_pk), shared_secret) - } - - /// Calculate the shared secret. - pub fn shared_secret(&self, receiver_blinding_sk: &SecretKey) -> Option { - match self { - Nonce::Confidential(sender_pk) => { - Some(Self::make_shared_secret(sender_pk, receiver_blinding_sk)) - } - _ => None, - } - } - - /// Create the shared secret. - fn make_shared_secret(pk: &PublicKey, sk: &SecretKey) -> SecretKey { - let xy = secp256k1_zkp::ecdh::shared_secret_point(pk, sk); - let shared_secret = { - // Yes, what follows is the compressed representation of a Bitcoin public key. - // However, this is more by accident then by design, see here: https://github.com/rust-bitcoin/rust-secp256k1/pull/255#issuecomment-744146282 - - let mut dh_secret = [0u8; 33]; - dh_secret[0] = if xy.last().unwrap() % 2 == 0 { - 0x02 - } else { - 0x03 - }; - dh_secret[1..].copy_from_slice(&xy[0..32]); - - sha256d::Hash::hash(&dh_secret).to_byte_array() - }; - - SecretKey::from_slice(&shared_secret[..32]).expect("always has exactly 32 bytes") - } - - /// Serialized length, in bytes - pub fn encoded_length(&self) -> usize { - match *self { - Nonce::Null => 1, - Nonce::Explicit(..) => 33, - Nonce::Confidential(..) => 33, - } - } - - /// Create from commitment. - pub fn from_commitment(bytes: &[u8]) -> Result { - Ok(Nonce::Confidential( - PublicKey::from_slice(bytes).map_err(secp256k1_zkp::Error::Upstream)?, - )) - } - - /// Check if the object is null. - pub fn is_null(&self) -> bool { - matches!(*self, Nonce::Null) - } - - /// Check if the object is explicit. - pub fn is_explicit(&self) -> bool { - matches!(*self, Nonce::Explicit(_)) - } - - /// Check if the object is confidential. - pub fn is_confidential(&self) -> bool { - matches!(*self, Nonce::Confidential(_)) - } - - /// Returns the explicit inner value. - /// Returns [None] if [`Nonce::is_explicit`] returns false. - pub fn explicit(&self) -> Option<[u8; 32]> { - match *self { - Nonce::Explicit(i) => Some(i), - _ => None, - } - } - - /// Returns the confidential commitment in case of a confidential value. - /// Returns [None] if [`Nonce::is_confidential`] returns false. - pub fn commitment(&self) -> Option { - match *self { - Nonce::Confidential(i) => Some(i), - _ => None, - } - } -} - -impl From for Nonce { - fn from(from: PublicKey) -> Self { - Nonce::Confidential(from) - } -} - -impl fmt::Display for Nonce { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Nonce::Null => f.write_str("null"), - Nonce::Explicit(n) => { - for b in &n { - write!(f, "{:02x}", b)?; - } - Ok(()) - } - Nonce::Confidential(pk) => write!(f, "{:02x}", pk), - } - } -} - -impl Encodable for Nonce { - fn consensus_encode(&self, mut s: S) -> Result { - match *self { - Nonce::Null => 0u8.consensus_encode(s), - Nonce::Explicit(n) => { - 1u8.consensus_encode(&mut s)?; - Ok(1 + n.consensus_encode(&mut s)?) - } - Nonce::Confidential(commitment) => { - s.write_all(&commitment.serialize())?; - Ok(33) - } - } - } -} - -impl Decodable for Nonce { - fn consensus_decode(mut d: D) -> Result { - let prefix = u8::consensus_decode(&mut d)?; - - match prefix { - 0 => Ok(Nonce::Null), - 1 => { - let explicit = Decodable::consensus_decode(&mut d)?; - Ok(Nonce::Explicit(explicit)) - } - p if p == 0x02 || p == 0x03 => { - let mut comm = [0u8; 33]; - comm[0] = p; - d.read_exact(&mut comm[1..])?; - Ok(Nonce::Confidential(PublicKey::from_slice(&comm)?)) - } - p => Err(encode::Error::InvalidConfidentialPrefix(p)), - } - } -} - -#[cfg(feature = "serde")] -impl Serialize for Nonce { - fn serialize(&self, s: S) -> Result { - use serde::ser::SerializeSeq; - - let seq_len = match *self { - Nonce::Null => 1, - Nonce::Explicit(_) | Nonce::Confidential(_) => 2 - }; - let mut seq = s.serialize_seq(Some(seq_len))?; - - match *self { - Nonce::Null => seq.serialize_element(&0u8)?, - Nonce::Explicit(n) => { - seq.serialize_element(&1u8)?; - seq.serialize_element(&n)?; - } - Nonce::Confidential(commitment) => { - seq.serialize_element(&2u8)?; - seq.serialize_element(&commitment)?; - } - } - seq.end() - } -} - -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for Nonce { - fn deserialize>(d: D) -> Result { - use serde::de::{Error, SeqAccess, Visitor}; - struct CommitVisitor; - - impl<'de> Visitor<'de> for CommitVisitor { - type Value = Nonce; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("a committed value") - } - - fn visit_seq>(self, mut access: A) -> Result { - let prefix = access.next_element::()?; - match prefix { - Some(0) => Ok(Nonce::Null), - Some(1) => { - match access.next_element()? { - Some(x) => Ok(Nonce::Explicit(x)), - None => Err(A::Error::custom("missing explicit nonce")), - } - } - Some(2) => { - match access.next_element()? { - Some(x) => Ok(Nonce::Confidential(x)), - None => Err(A::Error::custom("missing nonce")), - } - } - _ => Err(A::Error::custom("wrong or missing prefix")) - } - } - } - - d.deserialize_seq(CommitVisitor) - } -} +pub use self::value::{Value, ValueBlindingFactor}; /// Error decoding hexadecimal string into tweak-like value. #[derive(Debug, Clone, PartialEq, Eq)] @@ -744,342 +89,12 @@ impl std::error::Error for TweakHexDecodeError { } } } - -/// Blinding factor used for asset commitments. -#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub struct AssetBlindingFactor(pub(crate) Tweak); - -impl AssetBlindingFactor { - /// Generate random asset blinding factor. - pub fn new(rng: &mut R) -> Self { - AssetBlindingFactor(Tweak::new(rng)) - } - - /// Parse a blinding factor from a 64-character hex string. - #[deprecated(since = "0.27.0", note = "use s.parse() instead")] - pub fn from_hex(s: &str) -> Result { - s.parse() - } - - /// Create from bytes. - pub fn from_byte_array(bytes: [u8; 32]) -> Result { - Ok(AssetBlindingFactor(Tweak::from_inner(bytes)?)) - } - - /// Create from bytes. - pub fn from_slice(bytes: &[u8]) -> Result { - Ok(AssetBlindingFactor(Tweak::from_slice(bytes)?)) - } - - /// Returns the inner value. - pub fn into_inner(self) -> Tweak { - self.0 - } - - /// Get a unblinded/zero `AssetBlinding` factor - pub fn zero() -> Self { - AssetBlindingFactor(ZERO_TWEAK) - } -} - -impl core::borrow::Borrow<[u8]> for AssetBlindingFactor { - fn borrow(&self) -> &[u8] { &self.0[..] } -} - -hex::impl_fmt_traits! { - #[display_backward(true)] - impl fmt_traits for AssetBlindingFactor { - const LENGTH: usize = 32; - } -} - -impl str::FromStr for AssetBlindingFactor { - type Err = encode::Error; - - fn from_str(s: &str) -> Result { - let mut slice: [u8; 32] = hex::decode_to_array(s)?; - slice.reverse(); - - let inner = Tweak::from_inner(slice)?; - Ok(AssetBlindingFactor(inner)) - } -} - -#[cfg(feature = "serde")] -impl Serialize for AssetBlindingFactor { - fn serialize(&self, s: S) -> Result { - if s.is_human_readable() { - s.collect_str(&self) - } else { - s.serialize_bytes(&self.0[..]) - } - } -} - -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for AssetBlindingFactor { - fn deserialize>(d: D) -> Result { - if d.is_human_readable() { - struct HexVisitor; - - impl ::serde::de::Visitor<'_> for HexVisitor { - type Value = AssetBlindingFactor; - - fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - formatter.write_str("an ASCII hex string") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: ::serde::de::Error, - { - if let Ok(hex) = ::std::str::from_utf8(v) { - hex.parse().map_err(E::custom) - } else { - Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self)) - } - } - - fn visit_str(self, v: &str) -> Result - where - E: ::serde::de::Error, - { - v.parse().map_err(E::custom) - } - } - - d.deserialize_str(HexVisitor) - } else { - struct BytesVisitor; - - impl ::serde::de::Visitor<'_> for BytesVisitor { - type Value = AssetBlindingFactor; - - fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - formatter.write_str("a bytestring") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: ::serde::de::Error, - { - use core::convert::TryFrom; - - match <[u8; 32]>::try_from(v) { - Ok(ret) => { - let inner = Tweak::from_inner(ret).map_err(E::custom)?; - Ok(AssetBlindingFactor(inner)) - } - Err(_) => Err(E::invalid_length(v.len(), &stringify!($len))), - } - } - } - - d.deserialize_bytes(BytesVisitor) - } - } -} - -/// Blinding factor used for value commitments. -#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub struct ValueBlindingFactor(pub(crate) Tweak); - -impl ValueBlindingFactor { - /// Generate random value blinding factor. - pub fn new(rng: &mut R) -> Self { - ValueBlindingFactor(Tweak::new(rng)) - } - - /// Parse a blinding factor from a 64-character hex string. - #[deprecated(since = "0.27.0", note = "use s.parse() instead")] - pub fn from_hex(s: &str) -> Result { - s.parse() - } - - /// Create the value blinding factor of the last output of a transaction. - pub fn last( - secp: &Secp256k1, - value: u64, - abf: AssetBlindingFactor, - inputs: &[(u64, AssetBlindingFactor, ValueBlindingFactor)], - outputs: &[(u64, AssetBlindingFactor, ValueBlindingFactor)], - ) -> Self { - let set_a = inputs - .iter() - .map(|(value, abf, vbf)| CommitmentSecrets { - value: *value, - value_blinding_factor: vbf.0, - generator_blinding_factor: abf.into_inner(), - }) - .collect::>(); - let set_b = outputs - .iter() - .map(|(value, abf, vbf)| CommitmentSecrets { - value: *value, - value_blinding_factor: vbf.0, - generator_blinding_factor: abf.into_inner(), - }) - .collect::>(); - - ValueBlindingFactor(compute_adaptive_blinding_factor( - secp, value, abf.0, &set_a, &set_b, - )) - } - - /// Create from bytes. - pub fn from_slice(bytes: &[u8]) -> Result { - Ok(ValueBlindingFactor(Tweak::from_slice(bytes)?)) - } - - /// Returns the inner value. - pub fn into_inner(self) -> Tweak { - self.0 - } - - /// Get a unblinded/zero `AssetBlinding` factor - pub fn zero() -> Self { - ValueBlindingFactor(ZERO_TWEAK) - } -} - -impl AddAssign for ValueBlindingFactor { - fn add_assign(&mut self, other: Self) { - if self.0.as_ref() == &[0u8; 32] { - *self = other; - } else if other.0.as_ref() == &[0u8; 32] { - // nothing to do - } else { - // Since libsecp does not expose low level APIs - // for scalar arethematic, we need to abuse secret key - // operations for this - let sk2 = SecretKey::from_slice(self.into_inner().as_ref()).expect("Valid key"); - let sk = SecretKey::from_slice(other.into_inner().as_ref()).expect("Valid key"); - // The only reason that secret key addition can fail - // is when the keys add up to zero since we have already checked - // keys are in valid secret keys - match sk.add_tweak(&sk2.into()) { - Ok(sk_tweaked) => *self = ValueBlindingFactor::from_slice(sk_tweaked.as_ref()).expect("Valid Tweak"), - Err(_) => *self = Self::zero(), - } - } - } -} - -impl Neg for ValueBlindingFactor { - type Output = Self; - - fn neg(self) -> Self::Output { - if self.0.as_ref() == &[0u8; 32] { - self - } else { - let sk = SecretKey::from_slice(self.into_inner().as_ref()).expect("Valid key").negate(); - ValueBlindingFactor::from_slice(sk.as_ref()).expect("Valid Tweak") - } - } -} - -impl core::borrow::Borrow<[u8]> for ValueBlindingFactor { - fn borrow(&self) -> &[u8] { &self.0[..] } -} - -hex::impl_fmt_traits! { - #[display_backward(true)] - impl fmt_traits for ValueBlindingFactor { - const LENGTH: usize = 32; - } -} - -impl str::FromStr for ValueBlindingFactor { - type Err = encode::Error; - - fn from_str(s: &str) -> Result { - let mut slice: [u8; 32] = hex::decode_to_array(s)?; - slice.reverse(); - - let inner = Tweak::from_inner(slice)?; - Ok(ValueBlindingFactor(inner)) - } -} - -#[cfg(feature = "serde")] -impl Serialize for ValueBlindingFactor { - fn serialize(&self, s: S) -> Result { - if s.is_human_readable() { - s.collect_str(&self) - } else { - s.serialize_bytes(&self.0[..]) - } - } -} - -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for ValueBlindingFactor { - fn deserialize>(d: D) -> Result { - if d.is_human_readable() { - struct HexVisitor; - - impl ::serde::de::Visitor<'_> for HexVisitor { - type Value = ValueBlindingFactor; - - fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - formatter.write_str("an ASCII hex string") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: ::serde::de::Error, - { - if let Ok(hex) = ::std::str::from_utf8(v) { - hex.parse().map_err(E::custom) - } else { - Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self)) - } - } - - fn visit_str(self, v: &str) -> Result - where - E: ::serde::de::Error, - { - v.parse().map_err(E::custom) - } - } - - d.deserialize_str(HexVisitor) - } else { - struct BytesVisitor; - - impl ::serde::de::Visitor<'_> for BytesVisitor { - type Value = ValueBlindingFactor; - - fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - formatter.write_str("a bytestring") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: ::serde::de::Error, - { - use core::convert::TryFrom; - - match <[u8; 32]>::try_from(v) { - Ok(ret) => { - let inner = Tweak::from_inner(ret).map_err(E::custom)?; - Ok(ValueBlindingFactor(inner)) - } - Err(_) => Err(E::invalid_length(v.len(), &stringify!($len))), - } - } - } - - d.deserialize_bytes(BytesVisitor) - } - } -} - #[cfg(test)] mod tests { use super::*; + use crate::encode::Encodable as _; + #[cfg(feature = "serde")] use std::str::FromStr; diff --git a/src/confidential/nonce.rs b/src/confidential/nonce.rs new file mode 100644 index 00000000..f3174997 --- /dev/null +++ b/src/confidential/nonce.rs @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Confiential Nonces + +use core::fmt; +use std::io; + +use secp256k1_zkp::{self, PublicKey, Secp256k1, SecretKey, Signing}; +use secp256k1_zkp::rand::{RngCore, CryptoRng}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use crate::encode::{self, Decodable, Encodable}; +use crate::hashes::sha256d; + +/// A CT commitment to an output nonce (i.e. a public key) +#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum Nonce { + /// No value + #[default] + Null, + /// There should be no such thing as an "explicit nonce", but Elements will deserialize + /// such a thing (and insists that its size be 32 bytes). So we stick a 32-byte type here + /// that implements all the traits we need. + Explicit([u8; 32]), + /// Nonce is committed + Confidential(PublicKey), +} + +impl Nonce { + /// Create nonce commitment. + pub fn new_confidential( + rng: &mut R, + secp: &Secp256k1, + receiver_blinding_pk: &PublicKey, + ) -> (Self, SecretKey) { + let ephemeral_sk = SecretKey::new(rng); + Self::with_ephemeral_sk(secp, ephemeral_sk, receiver_blinding_pk) + } + + /// Similar to [`Nonce::new_confidential`], but with a given `ephemeral_sk` + /// instead of sampling it from rng. + pub fn with_ephemeral_sk( + secp: &Secp256k1, + ephemeral_sk: SecretKey, + receiver_blinding_pk: &PublicKey + ) -> (Self, SecretKey) { + let sender_pk = PublicKey::from_secret_key(secp, &ephemeral_sk); + let shared_secret = Self::make_shared_secret(receiver_blinding_pk, &ephemeral_sk); + (Nonce::Confidential(sender_pk), shared_secret) + } + + /// Calculate the shared secret. + pub fn shared_secret(&self, receiver_blinding_sk: &SecretKey) -> Option { + match self { + Nonce::Confidential(sender_pk) => { + Some(Self::make_shared_secret(sender_pk, receiver_blinding_sk)) + } + _ => None, + } + } + + /// Create the shared secret. + fn make_shared_secret(pk: &PublicKey, sk: &SecretKey) -> SecretKey { + let xy = secp256k1_zkp::ecdh::shared_secret_point(pk, sk); + let shared_secret = { + // Yes, what follows is the compressed representation of a Bitcoin public key. + // However, this is more by accident then by design, see here: https://github.com/rust-bitcoin/rust-secp256k1/pull/255#issuecomment-744146282 + + let mut dh_secret = [0u8; 33]; + dh_secret[0] = if xy.last().unwrap() % 2 == 0 { + 0x02 + } else { + 0x03 + }; + dh_secret[1..].copy_from_slice(&xy[0..32]); + + sha256d::Hash::hash(&dh_secret).to_byte_array() + }; + + SecretKey::from_slice(&shared_secret[..32]).expect("always has exactly 32 bytes") + } + + /// Serialized length, in bytes + pub fn encoded_length(&self) -> usize { + match *self { + Nonce::Null => 1, + Nonce::Explicit(..) => 33, + Nonce::Confidential(..) => 33, + } + } + + /// Create from commitment. + pub fn from_commitment(bytes: &[u8]) -> Result { + Ok(Nonce::Confidential( + PublicKey::from_slice(bytes).map_err(secp256k1_zkp::Error::Upstream)?, + )) + } + + /// Check if the object is null. + pub fn is_null(&self) -> bool { + matches!(*self, Nonce::Null) + } + + /// Check if the object is explicit. + pub fn is_explicit(&self) -> bool { + matches!(*self, Nonce::Explicit(_)) + } + + /// Check if the object is confidential. + pub fn is_confidential(&self) -> bool { + matches!(*self, Nonce::Confidential(_)) + } + + /// Returns the explicit inner value. + /// Returns [None] if [`Nonce::is_explicit`] returns false. + pub fn explicit(&self) -> Option<[u8; 32]> { + match *self { + Nonce::Explicit(i) => Some(i), + _ => None, + } + } + + /// Returns the confidential commitment in case of a confidential value. + /// Returns [None] if [`Nonce::is_confidential`] returns false. + pub fn commitment(&self) -> Option { + match *self { + Nonce::Confidential(i) => Some(i), + _ => None, + } + } +} + +impl From for Nonce { + fn from(from: PublicKey) -> Self { + Nonce::Confidential(from) + } +} + +impl fmt::Display for Nonce { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Nonce::Null => f.write_str("null"), + Nonce::Explicit(n) => { + for b in &n { + write!(f, "{:02x}", b)?; + } + Ok(()) + } + Nonce::Confidential(pk) => write!(f, "{:02x}", pk), + } + } +} + +impl Encodable for Nonce { + fn consensus_encode(&self, mut s: S) -> Result { + match *self { + Nonce::Null => 0u8.consensus_encode(s), + Nonce::Explicit(n) => { + 1u8.consensus_encode(&mut s)?; + Ok(1 + n.consensus_encode(&mut s)?) + } + Nonce::Confidential(commitment) => { + s.write_all(&commitment.serialize())?; + Ok(33) + } + } + } +} + +impl Decodable for Nonce { + fn consensus_decode(mut d: D) -> Result { + let prefix = u8::consensus_decode(&mut d)?; + + match prefix { + 0 => Ok(Nonce::Null), + 1 => { + let explicit = Decodable::consensus_decode(&mut d)?; + Ok(Nonce::Explicit(explicit)) + } + p if p == 0x02 || p == 0x03 => { + let mut comm = [0u8; 33]; + comm[0] = p; + d.read_exact(&mut comm[1..])?; + Ok(Nonce::Confidential(PublicKey::from_slice(&comm)?)) + } + p => Err(encode::Error::InvalidConfidentialPrefix(p)), + } + } +} + +#[cfg(feature = "serde")] +impl Serialize for Nonce { + fn serialize(&self, s: S) -> Result { + use serde::ser::SerializeSeq; + + let seq_len = match *self { + Nonce::Null => 1, + Nonce::Explicit(_) | Nonce::Confidential(_) => 2 + }; + let mut seq = s.serialize_seq(Some(seq_len))?; + + match *self { + Nonce::Null => seq.serialize_element(&0u8)?, + Nonce::Explicit(n) => { + seq.serialize_element(&1u8)?; + seq.serialize_element(&n)?; + } + Nonce::Confidential(commitment) => { + seq.serialize_element(&2u8)?; + seq.serialize_element(&commitment)?; + } + } + seq.end() + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Nonce { + fn deserialize>(d: D) -> Result { + use serde::de::{Error, SeqAccess, Visitor}; + struct CommitVisitor; + + impl<'de> Visitor<'de> for CommitVisitor { + type Value = Nonce; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("a committed value") + } + + fn visit_seq>(self, mut access: A) -> Result { + let prefix = access.next_element::()?; + match prefix { + Some(0) => Ok(Nonce::Null), + Some(1) => { + match access.next_element()? { + Some(x) => Ok(Nonce::Explicit(x)), + None => Err(A::Error::custom("missing explicit nonce")), + } + } + Some(2) => { + match access.next_element()? { + Some(x) => Ok(Nonce::Confidential(x)), + None => Err(A::Error::custom("missing nonce")), + } + } + _ => Err(A::Error::custom("wrong or missing prefix")) + } + } + } + + d.deserialize_seq(CommitVisitor) + } +} + diff --git a/src/confidential/value.rs b/src/confidential/value.rs new file mode 100644 index 00000000..47483ae4 --- /dev/null +++ b/src/confidential/value.rs @@ -0,0 +1,416 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Confiential Values + +use core::{fmt, ops::{AddAssign, Neg}, str}; +use std::io; + +use secp256k1_zkp::{self, CommitmentSecrets, PedersenCommitment, Generator, Secp256k1, SecretKey, Signing, Tweak, ZERO_TWEAK}; +use secp256k1_zkp::compute_adaptive_blinding_factor; +use secp256k1_zkp::rand::Rng; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use crate::confidential::{AssetBlindingFactor}; +use crate::encode::{self, Decodable, Encodable}; +use crate::issuance::AssetId; + +/// A CT commitment to an amount +#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum Value { + /// No value + #[default] + Null, + /// Value is explicitly encoded + Explicit(u64), + /// Value is committed + Confidential(PedersenCommitment), +} + +impl Value { + /// Create value commitment. + pub fn new_confidential( + secp: &Secp256k1, + value: u64, + asset: Generator, + bf: ValueBlindingFactor, + ) -> Self { + Value::Confidential(PedersenCommitment::new(secp, value, bf.0, asset)) + } + + /// Create value commitment from assetID, asset blinding factor, + /// value and value blinding factor + pub fn new_confidential_from_assetid( + secp: &Secp256k1, + value: u64, + asset: AssetId, + v_bf: ValueBlindingFactor, + a_bf: AssetBlindingFactor, + ) -> Self { + let generator = Generator::new_blinded(secp, asset.into_tag(), a_bf.0); + let comm = PedersenCommitment::new(secp, value, v_bf.0, generator); + + Value::Confidential(comm) + } + + /// Serialized length, in bytes + pub fn encoded_length(&self) -> usize { + match *self { + Value::Null => 1, + Value::Explicit(..) => 9, + Value::Confidential(..) => 33, + } + } + + /// Create from commitment. + pub fn from_commitment(bytes: &[u8]) -> Result { + Ok(Value::Confidential(PedersenCommitment::from_slice(bytes)?)) + } + + /// Check if the object is null. + pub fn is_null(&self) -> bool { + matches!(*self, Value::Null) + } + + /// Check if the object is explicit. + pub fn is_explicit(&self) -> bool { + matches!(*self, Value::Explicit(_)) + } + + /// Check if the object is confidential. + pub fn is_confidential(&self) -> bool { + matches!(*self, Value::Confidential(_)) + } + + /// Returns the explicit inner value. + /// Returns [None] if [`Value::is_explicit`] returns false. + pub fn explicit(&self) -> Option { + match *self { + Value::Explicit(i) => Some(i), + _ => None, + } + } + + /// Returns the confidential commitment in case of a confidential value. + /// Returns [None] if [`Value::is_confidential`] returns false. + pub fn commitment(&self) -> Option { + match *self { + Value::Confidential(i) => Some(i), + _ => None, + } + } +} + +impl From for Value { + fn from(from: PedersenCommitment) -> Self { + Value::Confidential(from) + } +} + +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Value::Null => f.write_str("null"), + Value::Explicit(n) => write!(f, "{}", n), + Value::Confidential(commitment) => write!(f, "{:02x}", commitment), + } + } +} + +impl Encodable for Value { + fn consensus_encode(&self, mut s: S) -> Result { + match *self { + Value::Null => 0u8.consensus_encode(s), + Value::Explicit(n) => { + 1u8.consensus_encode(&mut s)?; + Ok(1 + u64::swap_bytes(n).consensus_encode(&mut s)?) + } + Value::Confidential(commitment) => { + s.write_all(&commitment.serialize())?; + Ok(33) + } + } + } +} + +impl Decodable for Value { + fn consensus_decode(mut d: D) -> Result { + let prefix = u8::consensus_decode(&mut d)?; + + match prefix { + 0 => Ok(Value::Null), + 1 => { + let explicit = u64::swap_bytes(Decodable::consensus_decode(&mut d)?); + Ok(Value::Explicit(explicit)) + } + p if p == 0x08 || p == 0x09 => { + let mut comm = [0u8; 33]; + comm[0] = p; + d.read_exact(&mut comm[1..])?; + Ok(Value::Confidential(PedersenCommitment::from_slice(&comm)?)) + } + p => Err(encode::Error::InvalidConfidentialPrefix(p)), + } + } +} + +#[cfg(feature = "serde")] +impl Serialize for Value { + fn serialize(&self, s: S) -> Result { + use serde::ser::SerializeSeq; + + let seq_len = match *self { + Value::Null => 1, + Value::Explicit(_) | Value::Confidential(_) => 2 + }; + let mut seq = s.serialize_seq(Some(seq_len))?; + + match *self { + Value::Null => seq.serialize_element(&0u8)?, + Value::Explicit(n) => { + seq.serialize_element(&1u8)?; + seq.serialize_element(&u64::swap_bytes(n))?; + } + Value::Confidential(commitment) => { + seq.serialize_element(&2u8)?; + seq.serialize_element(&commitment)?; + } + } + seq.end() + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Value { + fn deserialize>(d: D) -> Result { + use serde::de::{Error, SeqAccess, Visitor}; + struct CommitVisitor; + + impl<'de> Visitor<'de> for CommitVisitor { + type Value = Value; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("a committed value") + } + + fn visit_seq>(self, mut access: A) -> Result { + let prefix = access.next_element::()?; + match prefix { + Some(0) => Ok(Value::Null), + Some(1) => { + match access.next_element()? { + Some(x) => Ok(Value::Explicit(u64::swap_bytes(x))), + None => Err(A::Error::custom("missing explicit value")), + } + } + Some(2) => { + match access.next_element()? { + Some(x) => Ok(Value::Confidential(x)), + None => Err(A::Error::custom("missing pedersen commitment")), + } + } + _ => Err(A::Error::custom("wrong or missing prefix")), + } + } + } + + d.deserialize_seq(CommitVisitor) + } +} + +/// Blinding factor used for value commitments. +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] +pub struct ValueBlindingFactor(pub(crate) Tweak); + +impl ValueBlindingFactor { + /// Generate random value blinding factor. + pub fn new(rng: &mut R) -> Self { + ValueBlindingFactor(Tweak::new(rng)) + } + + /// Parse a blinding factor from a 64-character hex string. + #[deprecated(since = "0.27.0", note = "use s.parse() instead")] + pub fn from_hex(s: &str) -> Result { + s.parse() + } + + /// Create the value blinding factor of the last output of a transaction. + pub fn last( + secp: &Secp256k1, + value: u64, + abf: AssetBlindingFactor, + inputs: &[(u64, AssetBlindingFactor, ValueBlindingFactor)], + outputs: &[(u64, AssetBlindingFactor, ValueBlindingFactor)], + ) -> Self { + let set_a = inputs + .iter() + .map(|(value, abf, vbf)| CommitmentSecrets { + value: *value, + value_blinding_factor: vbf.0, + generator_blinding_factor: abf.into_inner(), + }) + .collect::>(); + let set_b = outputs + .iter() + .map(|(value, abf, vbf)| CommitmentSecrets { + value: *value, + value_blinding_factor: vbf.0, + generator_blinding_factor: abf.into_inner(), + }) + .collect::>(); + + ValueBlindingFactor(compute_adaptive_blinding_factor( + secp, value, abf.0, &set_a, &set_b, + )) + } + + /// Create from bytes. + pub fn from_slice(bytes: &[u8]) -> Result { + Ok(ValueBlindingFactor(Tweak::from_slice(bytes)?)) + } + + /// Returns the inner value. + pub fn into_inner(self) -> Tweak { + self.0 + } + + /// Get a unblinded/zero `AssetBlinding` factor + pub fn zero() -> Self { + ValueBlindingFactor(ZERO_TWEAK) + } +} + +impl AddAssign for ValueBlindingFactor { + fn add_assign(&mut self, other: Self) { + if self.0.as_ref() == &[0u8; 32] { + *self = other; + } else if other.0.as_ref() == &[0u8; 32] { + // nothing to do + } else { + // Since libsecp does not expose low level APIs + // for scalar arethematic, we need to abuse secret key + // operations for this + let sk2 = SecretKey::from_slice(self.into_inner().as_ref()).expect("Valid key"); + let sk = SecretKey::from_slice(other.into_inner().as_ref()).expect("Valid key"); + // The only reason that secret key addition can fail + // is when the keys add up to zero since we have already checked + // keys are in valid secret keys + match sk.add_tweak(&sk2.into()) { + Ok(sk_tweaked) => *self = ValueBlindingFactor::from_slice(sk_tweaked.as_ref()).expect("Valid Tweak"), + Err(_) => *self = Self::zero(), + } + } + } +} + +impl Neg for ValueBlindingFactor { + type Output = Self; + + fn neg(self) -> Self::Output { + if self.0.as_ref() == &[0u8; 32] { + self + } else { + let sk = SecretKey::from_slice(self.into_inner().as_ref()).expect("Valid key").negate(); + ValueBlindingFactor::from_slice(sk.as_ref()).expect("Valid Tweak") + } + } +} + +impl core::borrow::Borrow<[u8]> for ValueBlindingFactor { + fn borrow(&self) -> &[u8] { &self.0[..] } +} + +hex::impl_fmt_traits! { + #[display_backward(true)] + impl fmt_traits for ValueBlindingFactor { + const LENGTH: usize = 32; + } +} + +impl str::FromStr for ValueBlindingFactor { + type Err = encode::Error; + + fn from_str(s: &str) -> Result { + let mut slice: [u8; 32] = hex::decode_to_array(s)?; + slice.reverse(); + + let inner = Tweak::from_inner(slice)?; + Ok(ValueBlindingFactor(inner)) + } +} + +#[cfg(feature = "serde")] +impl Serialize for ValueBlindingFactor { + fn serialize(&self, s: S) -> Result { + if s.is_human_readable() { + s.collect_str(&self) + } else { + s.serialize_bytes(&self.0[..]) + } + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for ValueBlindingFactor { + fn deserialize>(d: D) -> Result { + if d.is_human_readable() { + struct HexVisitor; + + impl ::serde::de::Visitor<'_> for HexVisitor { + type Value = ValueBlindingFactor; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + formatter.write_str("an ASCII hex string") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: ::serde::de::Error, + { + if let Ok(hex) = ::std::str::from_utf8(v) { + hex.parse().map_err(E::custom) + } else { + Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self)) + } + } + + fn visit_str(self, v: &str) -> Result + where + E: ::serde::de::Error, + { + v.parse().map_err(E::custom) + } + } + + d.deserialize_str(HexVisitor) + } else { + struct BytesVisitor; + + impl ::serde::de::Visitor<'_> for BytesVisitor { + type Value = ValueBlindingFactor; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + formatter.write_str("a bytestring") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: ::serde::de::Error, + { + use core::convert::TryFrom; + + match <[u8; 32]>::try_from(v) { + Ok(ret) => { + let inner = Tweak::from_inner(ret).map_err(E::custom)?; + Ok(ValueBlindingFactor(inner)) + } + Err(_) => Err(E::invalid_length(v.len(), &stringify!($len))), + } + } + } + + d.deserialize_bytes(BytesVisitor) + } + } +} + From 18101a89b5768b1f626da611d685e7b1820ddbd1 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Tue, 30 Jun 2026 15:49:48 +0000 Subject: [PATCH 09/12] run rustfmt on src/confidential/* tree --- rustfmt.toml | 3 +- src/confidential/asset.rs | 70 +++++++---------------- src/confidential/mod.rs | 114 ++++++++++++++------------------------ src/confidential/nonce.rs | 56 +++++++------------ src/confidential/value.rs | 76 ++++++++++--------------- 5 files changed, 112 insertions(+), 207 deletions(-) diff --git a/rustfmt.toml b/rustfmt.toml index 13332480..09b6229f 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,8 +1,7 @@ ignore = [ "/", "!/src/lib.rs", - "!/src/confidential/range_proof.rs", - "!/src/confidential/surjection_proof.rs", + "!/src/confidential/*.rs", ] hard_tabs = false tab_spaces = 4 diff --git a/src/confidential/asset.rs b/src/confidential/asset.rs index 1b782f7a..7098bf76 100644 --- a/src/confidential/asset.rs +++ b/src/confidential/asset.rs @@ -5,8 +5,8 @@ use core::{fmt, str}; use std::io; -use secp256k1_zkp::{self, Generator, Secp256k1, Signing, Tweak, ZERO_TWEAK}; use secp256k1_zkp::rand::Rng; +use secp256k1_zkp::{self, Generator, Secp256k1, Signing, Tweak, ZERO_TWEAK}; #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -32,11 +32,7 @@ impl Asset { asset: AssetId, bf: AssetBlindingFactor, ) -> Self { - Asset::Confidential(Generator::new_blinded( - secp, - asset.into_tag(), - bf.into_inner(), - )) + Asset::Confidential(Generator::new_blinded(secp, asset.into_tag(), bf.into_inner())) } /// Serialized length, in bytes @@ -54,19 +50,13 @@ impl Asset { } /// Check if the object is null. - pub fn is_null(&self) -> bool { - matches!(*self, Asset::Null) - } + pub fn is_null(&self) -> bool { matches!(*self, Asset::Null) } /// Check if the object is explicit. - pub fn is_explicit(&self) -> bool { - matches!(*self, Asset::Explicit(_)) - } + pub fn is_explicit(&self) -> bool { matches!(*self, Asset::Explicit(_)) } /// Check if the object is confidential. - pub fn is_confidential(&self) -> bool { - matches!(*self, Asset::Confidential(_)) - } + pub fn is_confidential(&self) -> bool { matches!(*self, Asset::Confidential(_)) } /// Returns the explicit inner value. /// Returns [None] if [`Asset::is_explicit`] returns false. @@ -91,7 +81,7 @@ impl Asset { /// Returns [`None`] is the asset is [`Asset::Null`] /// Converts a explicit asset into a generator and returns the confidential /// generator as is. - pub fn into_asset_gen ( + pub fn into_asset_gen( self, secp: &Secp256k1, ) -> Option { @@ -99,18 +89,14 @@ impl Asset { // Only error is Null error which is dealt with later // when we have more context information about it. Asset::Null => None, - Asset::Explicit(x) => { - Some(Generator::new_unblinded(secp, x.into_tag())) - } + Asset::Explicit(x) => Some(Generator::new_unblinded(secp, x.into_tag())), Asset::Confidential(gen) => Some(gen), } } } impl From for Asset { - fn from(from: Generator) -> Self { - Asset::Confidential(from) - } + fn from(from: Generator) -> Self { Asset::Confidential(from) } } impl fmt::Display for Asset { @@ -167,7 +153,7 @@ impl Serialize for Asset { let seq_len = match *self { Asset::Null => 1, - Asset::Explicit(_) | Asset::Confidential(_) => 2 + Asset::Explicit(_) | Asset::Confidential(_) => 2, }; let mut seq = s.serialize_seq(Some(seq_len))?; @@ -203,18 +189,14 @@ impl<'de> Deserialize<'de> for Asset { let prefix = access.next_element::()?; match prefix { Some(0) => Ok(Asset::Null), - Some(1) => { - match access.next_element()? { - Some(x) => Ok(Asset::Explicit(x)), - None => Err(A::Error::custom("missing explicit asset")), - } - } - Some(2) => { - match access.next_element()? { - Some(x) => Ok(Asset::Confidential(x)), - None => Err(A::Error::custom("missing generator")), - } - } + Some(1) => match access.next_element()? { + Some(x) => Ok(Asset::Explicit(x)), + None => Err(A::Error::custom("missing explicit asset")), + }, + Some(2) => match access.next_element()? { + Some(x) => Ok(Asset::Confidential(x)), + None => Err(A::Error::custom("missing generator")), + }, _ => Err(A::Error::custom("wrong or missing prefix")), } } @@ -224,22 +206,17 @@ impl<'de> Deserialize<'de> for Asset { } } - /// Blinding factor used for asset commitments. #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] pub struct AssetBlindingFactor(pub(crate) Tweak); impl AssetBlindingFactor { /// Generate random asset blinding factor. - pub fn new(rng: &mut R) -> Self { - AssetBlindingFactor(Tweak::new(rng)) - } + pub fn new(rng: &mut R) -> Self { AssetBlindingFactor(Tweak::new(rng)) } /// Parse a blinding factor from a 64-character hex string. #[deprecated(since = "0.27.0", note = "use s.parse() instead")] - pub fn from_hex(s: &str) -> Result { - s.parse() - } + pub fn from_hex(s: &str) -> Result { s.parse() } /// Create from bytes. pub fn from_byte_array(bytes: [u8; 32]) -> Result { @@ -252,14 +229,10 @@ impl AssetBlindingFactor { } /// Returns the inner value. - pub fn into_inner(self) -> Tweak { - self.0 - } + pub fn into_inner(self) -> Tweak { self.0 } /// Get a unblinded/zero `AssetBlinding` factor - pub fn zero() -> Self { - AssetBlindingFactor(ZERO_TWEAK) - } + pub fn zero() -> Self { AssetBlindingFactor(ZERO_TWEAK) } } impl core::borrow::Borrow<[u8]> for AssetBlindingFactor { @@ -359,4 +332,3 @@ impl<'de> Deserialize<'de> for AssetBlindingFactor { } } } - diff --git a/src/confidential/mod.rs b/src/confidential/mod.rs index c10a50e7..0c9556a6 100644 --- a/src/confidential/mod.rs +++ b/src/confidential/mod.rs @@ -23,18 +23,17 @@ mod range_proof; mod surjection_proof; mod value; -use secp256k1_zkp; - use core::fmt; -use crate::encode; -use crate::issuance::AssetId; +use secp256k1_zkp; pub use self::asset::{Asset, AssetBlindingFactor}; pub use self::nonce::Nonce; pub use self::range_proof::RangeProof; pub use self::surjection_proof::SurjectionProof; pub use self::value::{Value, ValueBlindingFactor}; +use crate::encode; +use crate::issuance::AssetId; /// Error decoding hexadecimal string into tweak-like value. #[derive(Debug, Clone, PartialEq, Eq)] @@ -60,16 +59,12 @@ impl fmt::Display for TweakHexDecodeError { #[doc(hidden)] impl From for TweakHexDecodeError { - fn from(err: hex::DecodeFixedLengthBytesError) -> Self { - TweakHexDecodeError::InvalidHex(err) - } + fn from(err: hex::DecodeFixedLengthBytesError) -> Self { TweakHexDecodeError::InvalidHex(err) } } #[doc(hidden)] impl From for TweakHexDecodeError { - fn from(err: secp256k1_zkp::Error) -> Self { - TweakHexDecodeError::InvalidTweak(err) - } + fn from(err: secp256k1_zkp::Error) -> Self { TweakHexDecodeError::InvalidTweak(err) } } impl From for encode::Error { @@ -91,17 +86,16 @@ impl std::error::Error for TweakHexDecodeError { } #[cfg(test)] mod tests { - use super::*; - - use crate::encode::Encodable as _; - #[cfg(feature = "serde")] use std::str::FromStr; #[cfg(feature = "serde")] use bincode; - const VALUE_EXPLICIT: [u8; 9] = [ 1, 0, 0, 0, 0, 0, 0, 3, 232 ]; + use super::*; + use crate::encode::Encodable as _; + + const VALUE_EXPLICIT: [u8; 9] = [1, 0, 0, 0, 0, 0, 0, 3, 232]; const VALUE_COMMITMENT1: [u8; 33] = [ 0x08, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -239,8 +233,8 @@ mod tests { Token::Seq { len: Some(2) }, Token::U8(1), Token::U64(63_601_271_583_539_200), - Token::SeqEnd - ] + Token::SeqEnd, + ], ); let value = Value::from_commitment(&VALUE_COMMITMENT1).unwrap(); @@ -249,11 +243,9 @@ mod tests { &[ Token::Seq { len: Some(2) }, Token::U8(2), - Token::Str( - "080101010101010101010101010101010101010101010101010101010101010101" - ), - Token::SeqEnd - ] + Token::Str("080101010101010101010101010101010101010101010101010101010101010101"), + Token::SeqEnd, + ], ); assert_tokens( &value.compact(), @@ -261,19 +253,12 @@ mod tests { Token::Seq { len: Some(2) }, Token::U8(2), Token::Bytes(&VALUE_COMMITMENT1), - Token::SeqEnd - ] + Token::SeqEnd, + ], ); let value = Value::Null; - assert_tokens( - &value, - &[ - Token::Seq { len: Some(1) }, - Token::U8(0), - Token::SeqEnd - ] - ); + assert_tokens(&value, &[Token::Seq { len: Some(1) }, Token::U8(0), Token::SeqEnd]); } #[cfg(feature = "serde")] @@ -281,34 +266,30 @@ mod tests { fn asset_serde() { use serde_test::{assert_tokens, Configure, Token}; - let asset_id = AssetId::from_str( - "630ed6f9b176af03c0cd3f8aa430f9e7b4d988cf2d0b2f204322488f03b00bf8" - ).unwrap(); + let asset_id = + AssetId::from_str("630ed6f9b176af03c0cd3f8aa430f9e7b4d988cf2d0b2f204322488f03b00bf8") + .unwrap(); let asset = Asset::Explicit(asset_id); assert_tokens( &asset.readable(), &[ Token::Seq { len: Some(2) }, Token::U8(1), - Token::Str( - "630ed6f9b176af03c0cd3f8aa430f9e7b4d988cf2d0b2f204322488f03b00bf8" - ), - Token::SeqEnd - ] + Token::Str("630ed6f9b176af03c0cd3f8aa430f9e7b4d988cf2d0b2f204322488f03b00bf8"), + Token::SeqEnd, + ], ); assert_tokens( &asset.compact(), &[ Token::Seq { len: Some(2) }, Token::U8(1), - Token::Bytes( - &[ - 248, 11, 176, 3, 143, 72, 34, 67, 32, 47, 11, 45, 207, 136, 217, 180, - 231, 249, 48, 164, 138, 63, 205, 192, 3, 175, 118, 177, 249, 214, 14, 99 - ] - ), - Token::SeqEnd - ] + Token::Bytes(&[ + 248, 11, 176, 3, 143, 72, 34, 67, 32, 47, 11, 45, 207, 136, 217, 180, 231, 249, + 48, 164, 138, 63, 205, 192, 3, 175, 118, 177, 249, 214, 14, 99, + ]), + Token::SeqEnd, + ], ); let asset = Asset::from_commitment(&ASSET_COMMITMENT1).unwrap(); @@ -317,11 +298,9 @@ mod tests { &[ Token::Seq { len: Some(2) }, Token::U8(2), - Token::Str( - "0a0101010101010101010101010101010101010101010101010101010101010101" - ), - Token::SeqEnd - ] + Token::Str("0a0101010101010101010101010101010101010101010101010101010101010101"), + Token::SeqEnd, + ], ); assert_tokens( &asset.compact(), @@ -329,23 +308,17 @@ mod tests { Token::Seq { len: Some(2) }, Token::U8(2), Token::Bytes(&ASSET_COMMITMENT1), - Token::SeqEnd - ] + Token::SeqEnd, + ], ); let asset = Asset::Null; - assert_tokens( - &asset, - &[ - Token::Seq { len: Some(1) }, - Token::U8(0), - Token::SeqEnd - ] - ); + assert_tokens(&asset, &[Token::Seq { len: Some(1) }, Token::U8(0), Token::SeqEnd]); } #[cfg(feature = "serde")] #[test] + #[rustfmt::skip] fn nonce_serde() { use serde_test::{assert_tokens, Configure, Token}; @@ -418,9 +391,10 @@ mod tests { #[cfg(feature = "serde")] #[test] fn bf_serde() { - use serde_json; use std::str::FromStr; + use serde_json; + let abf_str = "a5b3d111cdaa5fc111e2723df4caf315864f25fb4610cc737f10d5a55cd4096f"; let abf_str_quoted = format!("\"{}\"", abf_str); let abf_from_serde: AssetBlindingFactor = serde_json::from_str(&abf_str_quoted).unwrap(); @@ -450,14 +424,10 @@ mod tests { fn test_value_bincode_le() { use bincode::Options; let value = Value::Explicit(500); - let bytes = bincode::DefaultOptions::default() - .with_little_endian() - .serialize(&value) - .unwrap(); - let decoded: Value = bincode::DefaultOptions::default() - .with_little_endian() - .deserialize(&bytes) - .unwrap(); + let bytes = + bincode::DefaultOptions::default().with_little_endian().serialize(&value).unwrap(); + let decoded: Value = + bincode::DefaultOptions::default().with_little_endian().deserialize(&bytes).unwrap(); assert_eq!(value, decoded); } } diff --git a/src/confidential/nonce.rs b/src/confidential/nonce.rs index f3174997..02df1742 100644 --- a/src/confidential/nonce.rs +++ b/src/confidential/nonce.rs @@ -5,8 +5,8 @@ use core::fmt; use std::io; +use secp256k1_zkp::rand::{CryptoRng, RngCore}; use secp256k1_zkp::{self, PublicKey, Secp256k1, SecretKey, Signing}; -use secp256k1_zkp::rand::{RngCore, CryptoRng}; #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -43,7 +43,7 @@ impl Nonce { pub fn with_ephemeral_sk( secp: &Secp256k1, ephemeral_sk: SecretKey, - receiver_blinding_pk: &PublicKey + receiver_blinding_pk: &PublicKey, ) -> (Self, SecretKey) { let sender_pk = PublicKey::from_secret_key(secp, &ephemeral_sk); let shared_secret = Self::make_shared_secret(receiver_blinding_pk, &ephemeral_sk); @@ -53,9 +53,8 @@ impl Nonce { /// Calculate the shared secret. pub fn shared_secret(&self, receiver_blinding_sk: &SecretKey) -> Option { match self { - Nonce::Confidential(sender_pk) => { - Some(Self::make_shared_secret(sender_pk, receiver_blinding_sk)) - } + Nonce::Confidential(sender_pk) => + Some(Self::make_shared_secret(sender_pk, receiver_blinding_sk)), _ => None, } } @@ -68,11 +67,7 @@ impl Nonce { // However, this is more by accident then by design, see here: https://github.com/rust-bitcoin/rust-secp256k1/pull/255#issuecomment-744146282 let mut dh_secret = [0u8; 33]; - dh_secret[0] = if xy.last().unwrap() % 2 == 0 { - 0x02 - } else { - 0x03 - }; + dh_secret[0] = if xy.last().unwrap() % 2 == 0 { 0x02 } else { 0x03 }; dh_secret[1..].copy_from_slice(&xy[0..32]); sha256d::Hash::hash(&dh_secret).to_byte_array() @@ -98,19 +93,13 @@ impl Nonce { } /// Check if the object is null. - pub fn is_null(&self) -> bool { - matches!(*self, Nonce::Null) - } + pub fn is_null(&self) -> bool { matches!(*self, Nonce::Null) } /// Check if the object is explicit. - pub fn is_explicit(&self) -> bool { - matches!(*self, Nonce::Explicit(_)) - } + pub fn is_explicit(&self) -> bool { matches!(*self, Nonce::Explicit(_)) } /// Check if the object is confidential. - pub fn is_confidential(&self) -> bool { - matches!(*self, Nonce::Confidential(_)) - } + pub fn is_confidential(&self) -> bool { matches!(*self, Nonce::Confidential(_)) } /// Returns the explicit inner value. /// Returns [None] if [`Nonce::is_explicit`] returns false. @@ -132,9 +121,7 @@ impl Nonce { } impl From for Nonce { - fn from(from: PublicKey) -> Self { - Nonce::Confidential(from) - } + fn from(from: PublicKey) -> Self { Nonce::Confidential(from) } } impl fmt::Display for Nonce { @@ -196,7 +183,7 @@ impl Serialize for Nonce { let seq_len = match *self { Nonce::Null => 1, - Nonce::Explicit(_) | Nonce::Confidential(_) => 2 + Nonce::Explicit(_) | Nonce::Confidential(_) => 2, }; let mut seq = s.serialize_seq(Some(seq_len))?; @@ -232,19 +219,15 @@ impl<'de> Deserialize<'de> for Nonce { let prefix = access.next_element::()?; match prefix { Some(0) => Ok(Nonce::Null), - Some(1) => { - match access.next_element()? { - Some(x) => Ok(Nonce::Explicit(x)), - None => Err(A::Error::custom("missing explicit nonce")), - } - } - Some(2) => { - match access.next_element()? { - Some(x) => Ok(Nonce::Confidential(x)), - None => Err(A::Error::custom("missing nonce")), - } - } - _ => Err(A::Error::custom("wrong or missing prefix")) + Some(1) => match access.next_element()? { + Some(x) => Ok(Nonce::Explicit(x)), + None => Err(A::Error::custom("missing explicit nonce")), + }, + Some(2) => match access.next_element()? { + Some(x) => Ok(Nonce::Confidential(x)), + None => Err(A::Error::custom("missing nonce")), + }, + _ => Err(A::Error::custom("wrong or missing prefix")), } } } @@ -252,4 +235,3 @@ impl<'de> Deserialize<'de> for Nonce { d.deserialize_seq(CommitVisitor) } } - diff --git a/src/confidential/value.rs b/src/confidential/value.rs index 47483ae4..2705c557 100644 --- a/src/confidential/value.rs +++ b/src/confidential/value.rs @@ -2,16 +2,19 @@ //! Confiential Values -use core::{fmt, ops::{AddAssign, Neg}, str}; +use core::ops::{AddAssign, Neg}; +use core::{fmt, str}; use std::io; -use secp256k1_zkp::{self, CommitmentSecrets, PedersenCommitment, Generator, Secp256k1, SecretKey, Signing, Tweak, ZERO_TWEAK}; -use secp256k1_zkp::compute_adaptive_blinding_factor; use secp256k1_zkp::rand::Rng; +use secp256k1_zkp::{ + self, compute_adaptive_blinding_factor, CommitmentSecrets, Generator, PedersenCommitment, + Secp256k1, SecretKey, Signing, Tweak, ZERO_TWEAK, +}; #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use crate::confidential::{AssetBlindingFactor}; +use crate::confidential::AssetBlindingFactor; use crate::encode::{self, Decodable, Encodable}; use crate::issuance::AssetId; @@ -68,19 +71,13 @@ impl Value { } /// Check if the object is null. - pub fn is_null(&self) -> bool { - matches!(*self, Value::Null) - } + pub fn is_null(&self) -> bool { matches!(*self, Value::Null) } /// Check if the object is explicit. - pub fn is_explicit(&self) -> bool { - matches!(*self, Value::Explicit(_)) - } + pub fn is_explicit(&self) -> bool { matches!(*self, Value::Explicit(_)) } /// Check if the object is confidential. - pub fn is_confidential(&self) -> bool { - matches!(*self, Value::Confidential(_)) - } + pub fn is_confidential(&self) -> bool { matches!(*self, Value::Confidential(_)) } /// Returns the explicit inner value. /// Returns [None] if [`Value::is_explicit`] returns false. @@ -102,9 +99,7 @@ impl Value { } impl From for Value { - fn from(from: PedersenCommitment) -> Self { - Value::Confidential(from) - } + fn from(from: PedersenCommitment) -> Self { Value::Confidential(from) } } impl fmt::Display for Value { @@ -161,7 +156,7 @@ impl Serialize for Value { let seq_len = match *self { Value::Null => 1, - Value::Explicit(_) | Value::Confidential(_) => 2 + Value::Explicit(_) | Value::Confidential(_) => 2, }; let mut seq = s.serialize_seq(Some(seq_len))?; @@ -197,18 +192,14 @@ impl<'de> Deserialize<'de> for Value { let prefix = access.next_element::()?; match prefix { Some(0) => Ok(Value::Null), - Some(1) => { - match access.next_element()? { - Some(x) => Ok(Value::Explicit(u64::swap_bytes(x))), - None => Err(A::Error::custom("missing explicit value")), - } - } - Some(2) => { - match access.next_element()? { - Some(x) => Ok(Value::Confidential(x)), - None => Err(A::Error::custom("missing pedersen commitment")), - } - } + Some(1) => match access.next_element()? { + Some(x) => Ok(Value::Explicit(u64::swap_bytes(x))), + None => Err(A::Error::custom("missing explicit value")), + }, + Some(2) => match access.next_element()? { + Some(x) => Ok(Value::Confidential(x)), + None => Err(A::Error::custom("missing pedersen commitment")), + }, _ => Err(A::Error::custom("wrong or missing prefix")), } } @@ -224,15 +215,11 @@ pub struct ValueBlindingFactor(pub(crate) Tweak); impl ValueBlindingFactor { /// Generate random value blinding factor. - pub fn new(rng: &mut R) -> Self { - ValueBlindingFactor(Tweak::new(rng)) - } + pub fn new(rng: &mut R) -> Self { ValueBlindingFactor(Tweak::new(rng)) } /// Parse a blinding factor from a 64-character hex string. #[deprecated(since = "0.27.0", note = "use s.parse() instead")] - pub fn from_hex(s: &str) -> Result { - s.parse() - } + pub fn from_hex(s: &str) -> Result { s.parse() } /// Create the value blinding factor of the last output of a transaction. pub fn last( @@ -259,9 +246,7 @@ impl ValueBlindingFactor { }) .collect::>(); - ValueBlindingFactor(compute_adaptive_blinding_factor( - secp, value, abf.0, &set_a, &set_b, - )) + ValueBlindingFactor(compute_adaptive_blinding_factor(secp, value, abf.0, &set_a, &set_b)) } /// Create from bytes. @@ -270,14 +255,10 @@ impl ValueBlindingFactor { } /// Returns the inner value. - pub fn into_inner(self) -> Tweak { - self.0 - } + pub fn into_inner(self) -> Tweak { self.0 } /// Get a unblinded/zero `AssetBlinding` factor - pub fn zero() -> Self { - ValueBlindingFactor(ZERO_TWEAK) - } + pub fn zero() -> Self { ValueBlindingFactor(ZERO_TWEAK) } } impl AddAssign for ValueBlindingFactor { @@ -296,8 +277,10 @@ impl AddAssign for ValueBlindingFactor { // is when the keys add up to zero since we have already checked // keys are in valid secret keys match sk.add_tweak(&sk2.into()) { - Ok(sk_tweaked) => *self = ValueBlindingFactor::from_slice(sk_tweaked.as_ref()).expect("Valid Tweak"), - Err(_) => *self = Self::zero(), + Ok(sk_tweaked) => + *self = + ValueBlindingFactor::from_slice(sk_tweaked.as_ref()).expect("Valid Tweak"), + Err(_) => *self = Self::zero(), } } } @@ -413,4 +396,3 @@ impl<'de> Deserialize<'de> for ValueBlindingFactor { } } } - From 969f9f9f76b2efd429711cf9f3dcba17030846a9 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 25 Jun 2026 14:35:39 +0000 Subject: [PATCH 10/12] confidential: enable use_self lint This greatly reduces the diff between the different confidential commitments. --- src/confidential/asset.rs | 82 +++++++++++++-------------- src/confidential/mod.rs | 18 +++--- src/confidential/nonce.rs | 64 ++++++++++----------- src/confidential/range_proof.rs | 2 +- src/confidential/surjection_proof.rs | 2 +- src/confidential/value.rs | 85 ++++++++++++++-------------- 6 files changed, 127 insertions(+), 126 deletions(-) diff --git a/src/confidential/asset.rs b/src/confidential/asset.rs index 7098bf76..dd86ff1c 100644 --- a/src/confidential/asset.rs +++ b/src/confidential/asset.rs @@ -32,53 +32,53 @@ impl Asset { asset: AssetId, bf: AssetBlindingFactor, ) -> Self { - Asset::Confidential(Generator::new_blinded(secp, asset.into_tag(), bf.into_inner())) + Self::Confidential(Generator::new_blinded(secp, asset.into_tag(), bf.into_inner())) } /// Serialized length, in bytes pub fn encoded_length(&self) -> usize { match *self { - Asset::Null => 1, - Asset::Explicit(..) => 33, - Asset::Confidential(..) => 33, + Self::Null => 1, + Self::Explicit(..) => 33, + Self::Confidential(..) => 33, } } /// Create from commitment. pub fn from_commitment(bytes: &[u8]) -> Result { - Ok(Asset::Confidential(Generator::from_slice(bytes)?)) + Ok(Self::Confidential(Generator::from_slice(bytes)?)) } /// Check if the object is null. - pub fn is_null(&self) -> bool { matches!(*self, Asset::Null) } + pub fn is_null(&self) -> bool { matches!(*self, Self::Null) } /// Check if the object is explicit. - pub fn is_explicit(&self) -> bool { matches!(*self, Asset::Explicit(_)) } + pub fn is_explicit(&self) -> bool { matches!(*self, Self::Explicit(_)) } /// Check if the object is confidential. - pub fn is_confidential(&self) -> bool { matches!(*self, Asset::Confidential(_)) } + pub fn is_confidential(&self) -> bool { matches!(*self, Self::Confidential(_)) } /// Returns the explicit inner value. - /// Returns [None] if [`Asset::is_explicit`] returns false. + /// Returns [None] if [`Self::is_explicit`] returns false. pub fn explicit(&self) -> Option { match *self { - Asset::Explicit(i) => Some(i), + Self::Explicit(i) => Some(i), _ => None, } } /// Returns the confidential commitment in case of a confidential value. - /// Returns [None] if [`Asset::is_confidential`] returns false. + /// Returns [None] if [`Self::is_confidential`] returns false. pub fn commitment(&self) -> Option { match *self { - Asset::Confidential(i) => Some(i), + Self::Confidential(i) => Some(i), _ => None, } } /// Internally used function for getting the generator from asset /// Used in the amount verification check - /// Returns [`None`] is the asset is [`Asset::Null`] + /// Returns [`None`] is the asset is [`Self::Null`] /// Converts a explicit asset into a generator and returns the confidential /// generator as is. pub fn into_asset_gen( @@ -88,23 +88,23 @@ impl Asset { match self { // Only error is Null error which is dealt with later // when we have more context information about it. - Asset::Null => None, - Asset::Explicit(x) => Some(Generator::new_unblinded(secp, x.into_tag())), - Asset::Confidential(gen) => Some(gen), + Self::Null => None, + Self::Explicit(x) => Some(Generator::new_unblinded(secp, x.into_tag())), + Self::Confidential(gen) => Some(gen), } } } impl From for Asset { - fn from(from: Generator) -> Self { Asset::Confidential(from) } + fn from(from: Generator) -> Self { Self::Confidential(from) } } impl fmt::Display for Asset { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - Asset::Null => f.write_str("null"), - Asset::Explicit(n) => write!(f, "{}", n), - Asset::Confidential(generator) => write!(f, "{:02x}", generator), + Self::Null => f.write_str("null"), + Self::Explicit(n) => write!(f, "{}", n), + Self::Confidential(generator) => write!(f, "{:02x}", generator), } } } @@ -112,12 +112,12 @@ impl fmt::Display for Asset { impl Encodable for Asset { fn consensus_encode(&self, mut s: S) -> Result { match *self { - Asset::Null => 0u8.consensus_encode(s), - Asset::Explicit(n) => { + Self::Null => 0u8.consensus_encode(s), + Self::Explicit(n) => { 1u8.consensus_encode(&mut s)?; Ok(1 + n.consensus_encode(&mut s)?) } - Asset::Confidential(generator) => { + Self::Confidential(generator) => { s.write_all(&generator.serialize())?; Ok(33) } @@ -130,16 +130,16 @@ impl Decodable for Asset { let prefix = u8::consensus_decode(&mut d)?; match prefix { - 0 => Ok(Asset::Null), + 0 => Ok(Self::Null), 1 => { let explicit = Decodable::consensus_decode(&mut d)?; - Ok(Asset::Explicit(explicit)) + Ok(Self::Explicit(explicit)) } p if p == 0x0a || p == 0x0b => { let mut comm = [0u8; 33]; comm[0] = p; d.read_exact(&mut comm[1..])?; - Ok(Asset::Confidential(Generator::from_slice(&comm[..])?)) + Ok(Self::Confidential(Generator::from_slice(&comm[..])?)) } p => Err(encode::Error::InvalidConfidentialPrefix(p)), } @@ -152,18 +152,18 @@ impl Serialize for Asset { use serde::ser::SerializeSeq; let seq_len = match *self { - Asset::Null => 1, - Asset::Explicit(_) | Asset::Confidential(_) => 2, + Self::Null => 1, + Self::Explicit(_) | Self::Confidential(_) => 2, }; let mut seq = s.serialize_seq(Some(seq_len))?; match *self { - Asset::Null => seq.serialize_element(&0u8)?, - Asset::Explicit(n) => { + Self::Null => seq.serialize_element(&0u8)?, + Self::Explicit(n) => { seq.serialize_element(&1u8)?; seq.serialize_element(&n)?; } - Asset::Confidential(commitment) => { + Self::Confidential(commitment) => { seq.serialize_element(&2u8)?; seq.serialize_element(&commitment)?; } @@ -185,16 +185,16 @@ impl<'de> Deserialize<'de> for Asset { f.write_str("a committed value") } - fn visit_seq>(self, mut access: A) -> Result { + fn visit_seq>(self, mut access: A) -> Result { let prefix = access.next_element::()?; match prefix { - Some(0) => Ok(Asset::Null), + Some(0) => Ok(Self::Value::Null), Some(1) => match access.next_element()? { - Some(x) => Ok(Asset::Explicit(x)), + Some(x) => Ok(Self::Value::Explicit(x)), None => Err(A::Error::custom("missing explicit asset")), }, Some(2) => match access.next_element()? { - Some(x) => Ok(Asset::Confidential(x)), + Some(x) => Ok(Self::Value::Confidential(x)), None => Err(A::Error::custom("missing generator")), }, _ => Err(A::Error::custom("wrong or missing prefix")), @@ -212,7 +212,7 @@ pub struct AssetBlindingFactor(pub(crate) Tweak); impl AssetBlindingFactor { /// Generate random asset blinding factor. - pub fn new(rng: &mut R) -> Self { AssetBlindingFactor(Tweak::new(rng)) } + pub fn new(rng: &mut R) -> Self { Self(Tweak::new(rng)) } /// Parse a blinding factor from a 64-character hex string. #[deprecated(since = "0.27.0", note = "use s.parse() instead")] @@ -220,19 +220,19 @@ impl AssetBlindingFactor { /// Create from bytes. pub fn from_byte_array(bytes: [u8; 32]) -> Result { - Ok(AssetBlindingFactor(Tweak::from_inner(bytes)?)) + Ok(Self(Tweak::from_inner(bytes)?)) } /// Create from bytes. pub fn from_slice(bytes: &[u8]) -> Result { - Ok(AssetBlindingFactor(Tweak::from_slice(bytes)?)) + Ok(Self(Tweak::from_slice(bytes)?)) } /// Returns the inner value. pub fn into_inner(self) -> Tweak { self.0 } /// Get a unblinded/zero `AssetBlinding` factor - pub fn zero() -> Self { AssetBlindingFactor(ZERO_TWEAK) } + pub fn zero() -> Self { Self(ZERO_TWEAK) } } impl core::borrow::Borrow<[u8]> for AssetBlindingFactor { @@ -254,7 +254,7 @@ impl str::FromStr for AssetBlindingFactor { slice.reverse(); let inner = Tweak::from_inner(slice)?; - Ok(AssetBlindingFactor(inner)) + Ok(Self(inner)) } } @@ -271,7 +271,7 @@ impl Serialize for AssetBlindingFactor { #[cfg(feature = "serde")] impl<'de> Deserialize<'de> for AssetBlindingFactor { - fn deserialize>(d: D) -> Result { + fn deserialize>(d: D) -> Result { if d.is_human_readable() { struct HexVisitor; diff --git a/src/confidential/mod.rs b/src/confidential/mod.rs index 0c9556a6..67cf4662 100644 --- a/src/confidential/mod.rs +++ b/src/confidential/mod.rs @@ -17,6 +17,8 @@ //! Structures representing Pedersen commitments of various types //! +#![warn(clippy::use_self)] + mod asset; mod nonce; mod range_proof; @@ -47,10 +49,10 @@ pub enum TweakHexDecodeError { impl fmt::Display for TweakHexDecodeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - TweakHexDecodeError::InvalidHex(err) => { + Self::InvalidHex(err) => { write!(f, "Invalid hex: {}", err) } - TweakHexDecodeError::InvalidTweak(err) => { + Self::InvalidTweak(err) => { write!(f, "Invalid tweak: {}", err) } } @@ -59,19 +61,19 @@ impl fmt::Display for TweakHexDecodeError { #[doc(hidden)] impl From for TweakHexDecodeError { - fn from(err: hex::DecodeFixedLengthBytesError) -> Self { TweakHexDecodeError::InvalidHex(err) } + fn from(err: hex::DecodeFixedLengthBytesError) -> Self { Self::InvalidHex(err) } } #[doc(hidden)] impl From for TweakHexDecodeError { - fn from(err: secp256k1_zkp::Error) -> Self { TweakHexDecodeError::InvalidTweak(err) } + fn from(err: secp256k1_zkp::Error) -> Self { Self::InvalidTweak(err) } } impl From for encode::Error { fn from(value: TweakHexDecodeError) -> Self { match value { - TweakHexDecodeError::InvalidHex(err) => encode::Error::HexFixedError(err), - TweakHexDecodeError::InvalidTweak(err) => encode::Error::Secp256k1zkp(err), + TweakHexDecodeError::InvalidHex(err) => Self::HexFixedError(err), + TweakHexDecodeError::InvalidTweak(err) => Self::Secp256k1zkp(err), } } } @@ -79,8 +81,8 @@ impl From for encode::Error { impl std::error::Error for TweakHexDecodeError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { - TweakHexDecodeError::InvalidHex(err) => Some(err), - TweakHexDecodeError::InvalidTweak(err) => Some(err), + Self::InvalidHex(err) => Some(err), + Self::InvalidTweak(err) => Some(err), } } } diff --git a/src/confidential/nonce.rs b/src/confidential/nonce.rs index 02df1742..41725422 100644 --- a/src/confidential/nonce.rs +++ b/src/confidential/nonce.rs @@ -38,7 +38,7 @@ impl Nonce { Self::with_ephemeral_sk(secp, ephemeral_sk, receiver_blinding_pk) } - /// Similar to [`Nonce::new_confidential`], but with a given `ephemeral_sk` + /// Similar to [`Self::new_confidential`], but with a given `ephemeral_sk` /// instead of sampling it from rng. pub fn with_ephemeral_sk( secp: &Secp256k1, @@ -47,13 +47,13 @@ impl Nonce { ) -> (Self, SecretKey) { let sender_pk = PublicKey::from_secret_key(secp, &ephemeral_sk); let shared_secret = Self::make_shared_secret(receiver_blinding_pk, &ephemeral_sk); - (Nonce::Confidential(sender_pk), shared_secret) + (Self::Confidential(sender_pk), shared_secret) } /// Calculate the shared secret. pub fn shared_secret(&self, receiver_blinding_sk: &SecretKey) -> Option { match self { - Nonce::Confidential(sender_pk) => + Self::Confidential(sender_pk) => Some(Self::make_shared_secret(sender_pk, receiver_blinding_sk)), _ => None, } @@ -79,62 +79,62 @@ impl Nonce { /// Serialized length, in bytes pub fn encoded_length(&self) -> usize { match *self { - Nonce::Null => 1, - Nonce::Explicit(..) => 33, - Nonce::Confidential(..) => 33, + Self::Null => 1, + Self::Explicit(..) => 33, + Self::Confidential(..) => 33, } } /// Create from commitment. pub fn from_commitment(bytes: &[u8]) -> Result { - Ok(Nonce::Confidential( + Ok(Self::Confidential( PublicKey::from_slice(bytes).map_err(secp256k1_zkp::Error::Upstream)?, )) } /// Check if the object is null. - pub fn is_null(&self) -> bool { matches!(*self, Nonce::Null) } + pub fn is_null(&self) -> bool { matches!(*self, Self::Null) } /// Check if the object is explicit. - pub fn is_explicit(&self) -> bool { matches!(*self, Nonce::Explicit(_)) } + pub fn is_explicit(&self) -> bool { matches!(*self, Self::Explicit(_)) } /// Check if the object is confidential. - pub fn is_confidential(&self) -> bool { matches!(*self, Nonce::Confidential(_)) } + pub fn is_confidential(&self) -> bool { matches!(*self, Self::Confidential(_)) } /// Returns the explicit inner value. - /// Returns [None] if [`Nonce::is_explicit`] returns false. + /// Returns [None] if [`Self::is_explicit`] returns false. pub fn explicit(&self) -> Option<[u8; 32]> { match *self { - Nonce::Explicit(i) => Some(i), + Self::Explicit(i) => Some(i), _ => None, } } /// Returns the confidential commitment in case of a confidential value. - /// Returns [None] if [`Nonce::is_confidential`] returns false. + /// Returns [None] if [`Self::is_confidential`] returns false. pub fn commitment(&self) -> Option { match *self { - Nonce::Confidential(i) => Some(i), + Self::Confidential(i) => Some(i), _ => None, } } } impl From for Nonce { - fn from(from: PublicKey) -> Self { Nonce::Confidential(from) } + fn from(from: PublicKey) -> Self { Self::Confidential(from) } } impl fmt::Display for Nonce { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - Nonce::Null => f.write_str("null"), - Nonce::Explicit(n) => { + Self::Null => f.write_str("null"), + Self::Explicit(n) => { for b in &n { write!(f, "{:02x}", b)?; } Ok(()) } - Nonce::Confidential(pk) => write!(f, "{:02x}", pk), + Self::Confidential(pk) => write!(f, "{:02x}", pk), } } } @@ -142,12 +142,12 @@ impl fmt::Display for Nonce { impl Encodable for Nonce { fn consensus_encode(&self, mut s: S) -> Result { match *self { - Nonce::Null => 0u8.consensus_encode(s), - Nonce::Explicit(n) => { + Self::Null => 0u8.consensus_encode(s), + Self::Explicit(n) => { 1u8.consensus_encode(&mut s)?; Ok(1 + n.consensus_encode(&mut s)?) } - Nonce::Confidential(commitment) => { + Self::Confidential(commitment) => { s.write_all(&commitment.serialize())?; Ok(33) } @@ -160,16 +160,16 @@ impl Decodable for Nonce { let prefix = u8::consensus_decode(&mut d)?; match prefix { - 0 => Ok(Nonce::Null), + 0 => Ok(Self::Null), 1 => { let explicit = Decodable::consensus_decode(&mut d)?; - Ok(Nonce::Explicit(explicit)) + Ok(Self::Explicit(explicit)) } p if p == 0x02 || p == 0x03 => { let mut comm = [0u8; 33]; comm[0] = p; d.read_exact(&mut comm[1..])?; - Ok(Nonce::Confidential(PublicKey::from_slice(&comm)?)) + Ok(Self::Confidential(PublicKey::from_slice(&comm)?)) } p => Err(encode::Error::InvalidConfidentialPrefix(p)), } @@ -182,18 +182,18 @@ impl Serialize for Nonce { use serde::ser::SerializeSeq; let seq_len = match *self { - Nonce::Null => 1, - Nonce::Explicit(_) | Nonce::Confidential(_) => 2, + Self::Null => 1, + Self::Explicit(_) | Self::Confidential(_) => 2, }; let mut seq = s.serialize_seq(Some(seq_len))?; match *self { - Nonce::Null => seq.serialize_element(&0u8)?, - Nonce::Explicit(n) => { + Self::Null => seq.serialize_element(&0u8)?, + Self::Explicit(n) => { seq.serialize_element(&1u8)?; seq.serialize_element(&n)?; } - Nonce::Confidential(commitment) => { + Self::Confidential(commitment) => { seq.serialize_element(&2u8)?; seq.serialize_element(&commitment)?; } @@ -218,13 +218,13 @@ impl<'de> Deserialize<'de> for Nonce { fn visit_seq>(self, mut access: A) -> Result { let prefix = access.next_element::()?; match prefix { - Some(0) => Ok(Nonce::Null), + Some(0) => Ok(Self::Value::Null), Some(1) => match access.next_element()? { - Some(x) => Ok(Nonce::Explicit(x)), + Some(x) => Ok(Self::Value::Explicit(x)), None => Err(A::Error::custom("missing explicit nonce")), }, Some(2) => match access.next_element()? { - Some(x) => Ok(Nonce::Confidential(x)), + Some(x) => Ok(Self::Value::Confidential(x)), None => Err(A::Error::custom("missing nonce")), }, _ => Err(A::Error::custom("wrong or missing prefix")), diff --git a/src/confidential/range_proof.rs b/src/confidential/range_proof.rs index e9a6c6ab..89894333 100644 --- a/src/confidential/range_proof.rs +++ b/src/confidential/range_proof.rs @@ -38,7 +38,7 @@ impl RangeProof { exp: i32, min_bits: u8, additional_generator: Generator, - ) -> Result { + ) -> Result { secp256k1_zkp::RangeProof::new( secp, min_value, diff --git a/src/confidential/surjection_proof.rs b/src/confidential/surjection_proof.rs index ad3d3e3f..6fb6f8ea 100644 --- a/src/confidential/surjection_proof.rs +++ b/src/confidential/surjection_proof.rs @@ -72,7 +72,7 @@ impl SurjectionProof { abf: AssetBlindingFactor, ) -> Result { let gen = Generator::new_unblinded(secp, asset.into_tag()); - SurjectionProof::new(secp, rng, asset, abf, [(gen, asset.into_tag(), ZERO_TWEAK)]) + Self::new(secp, rng, asset, abf, [(gen, asset.into_tag(), ZERO_TWEAK)]) } /// Verifies a [`SurjectionProof`] proving that an asset matches an exact asset ID. diff --git a/src/confidential/value.rs b/src/confidential/value.rs index 2705c557..2f128281 100644 --- a/src/confidential/value.rs +++ b/src/confidential/value.rs @@ -38,7 +38,7 @@ impl Value { asset: Generator, bf: ValueBlindingFactor, ) -> Self { - Value::Confidential(PedersenCommitment::new(secp, value, bf.0, asset)) + Self::Confidential(PedersenCommitment::new(secp, value, bf.0, asset)) } /// Create value commitment from assetID, asset blinding factor, @@ -53,61 +53,61 @@ impl Value { let generator = Generator::new_blinded(secp, asset.into_tag(), a_bf.0); let comm = PedersenCommitment::new(secp, value, v_bf.0, generator); - Value::Confidential(comm) + Self::Confidential(comm) } /// Serialized length, in bytes pub fn encoded_length(&self) -> usize { match *self { - Value::Null => 1, - Value::Explicit(..) => 9, - Value::Confidential(..) => 33, + Self::Null => 1, + Self::Explicit(..) => 9, + Self::Confidential(..) => 33, } } /// Create from commitment. pub fn from_commitment(bytes: &[u8]) -> Result { - Ok(Value::Confidential(PedersenCommitment::from_slice(bytes)?)) + Ok(Self::Confidential(PedersenCommitment::from_slice(bytes)?)) } /// Check if the object is null. - pub fn is_null(&self) -> bool { matches!(*self, Value::Null) } + pub fn is_null(&self) -> bool { matches!(*self, Self::Null) } /// Check if the object is explicit. - pub fn is_explicit(&self) -> bool { matches!(*self, Value::Explicit(_)) } + pub fn is_explicit(&self) -> bool { matches!(*self, Self::Explicit(_)) } /// Check if the object is confidential. - pub fn is_confidential(&self) -> bool { matches!(*self, Value::Confidential(_)) } + pub fn is_confidential(&self) -> bool { matches!(*self, Self::Confidential(_)) } /// Returns the explicit inner value. - /// Returns [None] if [`Value::is_explicit`] returns false. + /// Returns [None] if [`Self::is_explicit`] returns false. pub fn explicit(&self) -> Option { match *self { - Value::Explicit(i) => Some(i), + Self::Explicit(i) => Some(i), _ => None, } } /// Returns the confidential commitment in case of a confidential value. - /// Returns [None] if [`Value::is_confidential`] returns false. + /// Returns [None] if [`Self::is_confidential`] returns false. pub fn commitment(&self) -> Option { match *self { - Value::Confidential(i) => Some(i), + Self::Confidential(i) => Some(i), _ => None, } } } impl From for Value { - fn from(from: PedersenCommitment) -> Self { Value::Confidential(from) } + fn from(from: PedersenCommitment) -> Self { Self::Confidential(from) } } impl fmt::Display for Value { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - Value::Null => f.write_str("null"), - Value::Explicit(n) => write!(f, "{}", n), - Value::Confidential(commitment) => write!(f, "{:02x}", commitment), + Self::Null => f.write_str("null"), + Self::Explicit(n) => write!(f, "{}", n), + Self::Confidential(commitment) => write!(f, "{:02x}", commitment), } } } @@ -115,12 +115,12 @@ impl fmt::Display for Value { impl Encodable for Value { fn consensus_encode(&self, mut s: S) -> Result { match *self { - Value::Null => 0u8.consensus_encode(s), - Value::Explicit(n) => { + Self::Null => 0u8.consensus_encode(s), + Self::Explicit(n) => { 1u8.consensus_encode(&mut s)?; Ok(1 + u64::swap_bytes(n).consensus_encode(&mut s)?) } - Value::Confidential(commitment) => { + Self::Confidential(commitment) => { s.write_all(&commitment.serialize())?; Ok(33) } @@ -129,20 +129,20 @@ impl Encodable for Value { } impl Decodable for Value { - fn consensus_decode(mut d: D) -> Result { + fn consensus_decode(mut d: D) -> Result { let prefix = u8::consensus_decode(&mut d)?; match prefix { - 0 => Ok(Value::Null), + 0 => Ok(Self::Null), 1 => { let explicit = u64::swap_bytes(Decodable::consensus_decode(&mut d)?); - Ok(Value::Explicit(explicit)) + Ok(Self::Explicit(explicit)) } p if p == 0x08 || p == 0x09 => { let mut comm = [0u8; 33]; comm[0] = p; d.read_exact(&mut comm[1..])?; - Ok(Value::Confidential(PedersenCommitment::from_slice(&comm)?)) + Ok(Self::Confidential(PedersenCommitment::from_slice(&comm)?)) } p => Err(encode::Error::InvalidConfidentialPrefix(p)), } @@ -155,18 +155,18 @@ impl Serialize for Value { use serde::ser::SerializeSeq; let seq_len = match *self { - Value::Null => 1, - Value::Explicit(_) | Value::Confidential(_) => 2, + Self::Null => 1, + Self::Explicit(_) | Self::Confidential(_) => 2, }; let mut seq = s.serialize_seq(Some(seq_len))?; match *self { - Value::Null => seq.serialize_element(&0u8)?, - Value::Explicit(n) => { + Self::Null => seq.serialize_element(&0u8)?, + Self::Explicit(n) => { seq.serialize_element(&1u8)?; seq.serialize_element(&u64::swap_bytes(n))?; } - Value::Confidential(commitment) => { + Self::Confidential(commitment) => { seq.serialize_element(&2u8)?; seq.serialize_element(&commitment)?; } @@ -191,13 +191,13 @@ impl<'de> Deserialize<'de> for Value { fn visit_seq>(self, mut access: A) -> Result { let prefix = access.next_element::()?; match prefix { - Some(0) => Ok(Value::Null), + Some(0) => Ok(Self::Value::Null), Some(1) => match access.next_element()? { - Some(x) => Ok(Value::Explicit(u64::swap_bytes(x))), + Some(x) => Ok(Self::Value::Explicit(u64::swap_bytes(x))), None => Err(A::Error::custom("missing explicit value")), }, Some(2) => match access.next_element()? { - Some(x) => Ok(Value::Confidential(x)), + Some(x) => Ok(Self::Value::Confidential(x)), None => Err(A::Error::custom("missing pedersen commitment")), }, _ => Err(A::Error::custom("wrong or missing prefix")), @@ -215,7 +215,7 @@ pub struct ValueBlindingFactor(pub(crate) Tweak); impl ValueBlindingFactor { /// Generate random value blinding factor. - pub fn new(rng: &mut R) -> Self { ValueBlindingFactor(Tweak::new(rng)) } + pub fn new(rng: &mut R) -> Self { Self(Tweak::new(rng)) } /// Parse a blinding factor from a 64-character hex string. #[deprecated(since = "0.27.0", note = "use s.parse() instead")] @@ -226,8 +226,8 @@ impl ValueBlindingFactor { secp: &Secp256k1, value: u64, abf: AssetBlindingFactor, - inputs: &[(u64, AssetBlindingFactor, ValueBlindingFactor)], - outputs: &[(u64, AssetBlindingFactor, ValueBlindingFactor)], + inputs: &[(u64, AssetBlindingFactor, Self)], + outputs: &[(u64, AssetBlindingFactor, Self)], ) -> Self { let set_a = inputs .iter() @@ -246,19 +246,19 @@ impl ValueBlindingFactor { }) .collect::>(); - ValueBlindingFactor(compute_adaptive_blinding_factor(secp, value, abf.0, &set_a, &set_b)) + Self(compute_adaptive_blinding_factor(secp, value, abf.0, &set_a, &set_b)) } /// Create from bytes. pub fn from_slice(bytes: &[u8]) -> Result { - Ok(ValueBlindingFactor(Tweak::from_slice(bytes)?)) + Ok(Self(Tweak::from_slice(bytes)?)) } /// Returns the inner value. pub fn into_inner(self) -> Tweak { self.0 } /// Get a unblinded/zero `AssetBlinding` factor - pub fn zero() -> Self { ValueBlindingFactor(ZERO_TWEAK) } + pub fn zero() -> Self { Self(ZERO_TWEAK) } } impl AddAssign for ValueBlindingFactor { @@ -278,8 +278,7 @@ impl AddAssign for ValueBlindingFactor { // keys are in valid secret keys match sk.add_tweak(&sk2.into()) { Ok(sk_tweaked) => - *self = - ValueBlindingFactor::from_slice(sk_tweaked.as_ref()).expect("Valid Tweak"), + *self = Self::from_slice(sk_tweaked.as_ref()).expect("Valid Tweak"), Err(_) => *self = Self::zero(), } } @@ -294,7 +293,7 @@ impl Neg for ValueBlindingFactor { self } else { let sk = SecretKey::from_slice(self.into_inner().as_ref()).expect("Valid key").negate(); - ValueBlindingFactor::from_slice(sk.as_ref()).expect("Valid Tweak") + Self::from_slice(sk.as_ref()).expect("Valid Tweak") } } } @@ -318,7 +317,7 @@ impl str::FromStr for ValueBlindingFactor { slice.reverse(); let inner = Tweak::from_inner(slice)?; - Ok(ValueBlindingFactor(inner)) + Ok(Self(inner)) } } @@ -335,7 +334,7 @@ impl Serialize for ValueBlindingFactor { #[cfg(feature = "serde")] impl<'de> Deserialize<'de> for ValueBlindingFactor { - fn deserialize>(d: D) -> Result { + fn deserialize>(d: D) -> Result { if d.is_human_readable() { struct HexVisitor; From 8a60c405267d1f11dfaceb3ea7574b63a824ba67 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 25 Jun 2026 14:40:13 +0000 Subject: [PATCH 11/12] confidential: add a bunch of consts and type aliases More work to make the different confidential types more uniform. --- src/confidential/asset.rs | 62 ++++++++++++++++++++---------------- src/confidential/mod.rs | 4 +-- src/confidential/nonce.rs | 46 ++++++++++++++++----------- src/confidential/value.rs | 66 ++++++++++++++++++++++----------------- 4 files changed, 101 insertions(+), 77 deletions(-) diff --git a/src/confidential/asset.rs b/src/confidential/asset.rs index dd86ff1c..3d5d8082 100644 --- a/src/confidential/asset.rs +++ b/src/confidential/asset.rs @@ -13,6 +13,14 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::encode::{self, Decodable, Encodable}; use crate::issuance::AssetId; +type ExplicitInner = AssetId; +type ConfInner = Generator; + +const EXPLICIT_LEN: usize = 32; +const CONFIDENTIAL_LEN: usize = 33; +const CONF_PREFIX_1: u8 = 0x0a; +const CONF_PREFIX_2: u8 = 0x0b; + /// A CT commitment to an asset #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] pub enum Asset { @@ -20,9 +28,9 @@ pub enum Asset { #[default] Null, /// Asset entropy is explicitly encoded - Explicit(AssetId), + Explicit(ExplicitInner), /// Asset is committed - Confidential(Generator), + Confidential(ConfInner), } impl Asset { @@ -30,23 +38,23 @@ impl Asset { pub fn new_confidential( secp: &Secp256k1, asset: AssetId, - bf: AssetBlindingFactor, + bf: BlindingFactor, ) -> Self { - Self::Confidential(Generator::new_blinded(secp, asset.into_tag(), bf.into_inner())) + Self::Confidential(ConfInner::new_blinded(secp, asset.into_tag(), bf.into_inner())) } /// Serialized length, in bytes pub fn encoded_length(&self) -> usize { match *self { Self::Null => 1, - Self::Explicit(..) => 33, - Self::Confidential(..) => 33, + Self::Explicit(..) => 1 + EXPLICIT_LEN, + Self::Confidential(..) => CONFIDENTIAL_LEN, } } /// Create from commitment. pub fn from_commitment(bytes: &[u8]) -> Result { - Ok(Self::Confidential(Generator::from_slice(bytes)?)) + Ok(Self::Confidential(ConfInner::from_slice(bytes)?)) } /// Check if the object is null. @@ -60,7 +68,7 @@ impl Asset { /// Returns the explicit inner value. /// Returns [None] if [`Self::is_explicit`] returns false. - pub fn explicit(&self) -> Option { + pub fn explicit(&self) -> Option { match *self { Self::Explicit(i) => Some(i), _ => None, @@ -69,7 +77,7 @@ impl Asset { /// Returns the confidential commitment in case of a confidential value. /// Returns [None] if [`Self::is_confidential`] returns false. - pub fn commitment(&self) -> Option { + pub fn commitment(&self) -> Option { match *self { Self::Confidential(i) => Some(i), _ => None, @@ -84,19 +92,19 @@ impl Asset { pub fn into_asset_gen( self, secp: &Secp256k1, - ) -> Option { + ) -> Option { match self { // Only error is Null error which is dealt with later // when we have more context information about it. Self::Null => None, - Self::Explicit(x) => Some(Generator::new_unblinded(secp, x.into_tag())), + Self::Explicit(x) => Some(ConfInner::new_unblinded(secp, x.into_tag())), Self::Confidential(gen) => Some(gen), } } } -impl From for Asset { - fn from(from: Generator) -> Self { Self::Confidential(from) } +impl From for Asset { + fn from(from: ConfInner) -> Self { Self::Confidential(from) } } impl fmt::Display for Asset { @@ -119,7 +127,7 @@ impl Encodable for Asset { } Self::Confidential(generator) => { s.write_all(&generator.serialize())?; - Ok(33) + Ok(CONFIDENTIAL_LEN) } } } @@ -135,11 +143,11 @@ impl Decodable for Asset { let explicit = Decodable::consensus_decode(&mut d)?; Ok(Self::Explicit(explicit)) } - p if p == 0x0a || p == 0x0b => { - let mut comm = [0u8; 33]; + p if p == CONF_PREFIX_1 || p == CONF_PREFIX_2 => { + let mut comm = [0u8; CONFIDENTIAL_LEN]; comm[0] = p; d.read_exact(&mut comm[1..])?; - Ok(Self::Confidential(Generator::from_slice(&comm[..])?)) + Ok(Self::Confidential(ConfInner::from_slice(&comm[..])?)) } p => Err(encode::Error::InvalidConfidentialPrefix(p)), } @@ -208,9 +216,9 @@ impl<'de> Deserialize<'de> for Asset { /// Blinding factor used for asset commitments. #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub struct AssetBlindingFactor(pub(crate) Tweak); +pub struct BlindingFactor(pub(crate) Tweak); -impl AssetBlindingFactor { +impl BlindingFactor { /// Generate random asset blinding factor. pub fn new(rng: &mut R) -> Self { Self(Tweak::new(rng)) } @@ -235,18 +243,18 @@ impl AssetBlindingFactor { pub fn zero() -> Self { Self(ZERO_TWEAK) } } -impl core::borrow::Borrow<[u8]> for AssetBlindingFactor { +impl core::borrow::Borrow<[u8]> for BlindingFactor { fn borrow(&self) -> &[u8] { &self.0[..] } } hex::impl_fmt_traits! { #[display_backward(true)] - impl fmt_traits for AssetBlindingFactor { + impl fmt_traits for BlindingFactor { const LENGTH: usize = 32; } } -impl str::FromStr for AssetBlindingFactor { +impl str::FromStr for BlindingFactor { type Err = encode::Error; fn from_str(s: &str) -> Result { @@ -259,7 +267,7 @@ impl str::FromStr for AssetBlindingFactor { } #[cfg(feature = "serde")] -impl Serialize for AssetBlindingFactor { +impl Serialize for BlindingFactor { fn serialize(&self, s: S) -> Result { if s.is_human_readable() { s.collect_str(&self) @@ -270,13 +278,13 @@ impl Serialize for AssetBlindingFactor { } #[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for AssetBlindingFactor { +impl<'de> Deserialize<'de> for BlindingFactor { fn deserialize>(d: D) -> Result { if d.is_human_readable() { struct HexVisitor; impl ::serde::de::Visitor<'_> for HexVisitor { - type Value = AssetBlindingFactor; + type Value = BlindingFactor; fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { formatter.write_str("an ASCII hex string") @@ -306,7 +314,7 @@ impl<'de> Deserialize<'de> for AssetBlindingFactor { struct BytesVisitor; impl ::serde::de::Visitor<'_> for BytesVisitor { - type Value = AssetBlindingFactor; + type Value = BlindingFactor; fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { formatter.write_str("a bytestring") @@ -321,7 +329,7 @@ impl<'de> Deserialize<'de> for AssetBlindingFactor { match <[u8; 32]>::try_from(v) { Ok(ret) => { let inner = Tweak::from_inner(ret).map_err(E::custom)?; - Ok(AssetBlindingFactor(inner)) + Ok(BlindingFactor(inner)) } Err(_) => Err(E::invalid_length(v.len(), &stringify!($len))), } diff --git a/src/confidential/mod.rs b/src/confidential/mod.rs index 67cf4662..b6dbe807 100644 --- a/src/confidential/mod.rs +++ b/src/confidential/mod.rs @@ -29,11 +29,11 @@ use core::fmt; use secp256k1_zkp; -pub use self::asset::{Asset, AssetBlindingFactor}; +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::{Value, ValueBlindingFactor}; +pub use self::value::{BlindingFactor as ValueBlindingFactor, Value}; use crate::encode; use crate::issuance::AssetId; diff --git a/src/confidential/nonce.rs b/src/confidential/nonce.rs index 41725422..67f4c09a 100644 --- a/src/confidential/nonce.rs +++ b/src/confidential/nonce.rs @@ -13,6 +13,14 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::encode::{self, Decodable, Encodable}; use crate::hashes::sha256d; +type ExplicitInner = [u8; 32]; +type ConfInner = PublicKey; + +const EXPLICIT_LEN: usize = 32; +const CONFIDENTIAL_LEN: usize = 33; +const CONF_PREFIX_1: u8 = 0x02; +const CONF_PREFIX_2: u8 = 0x03; + /// A CT commitment to an output nonce (i.e. a public key) #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] pub enum Nonce { @@ -22,9 +30,9 @@ pub enum Nonce { /// There should be no such thing as an "explicit nonce", but Elements will deserialize /// such a thing (and insists that its size be 32 bytes). So we stick a 32-byte type here /// that implements all the traits we need. - Explicit([u8; 32]), + Explicit(ExplicitInner), /// Nonce is committed - Confidential(PublicKey), + Confidential(ConfInner), } impl Nonce { @@ -32,7 +40,7 @@ impl Nonce { pub fn new_confidential( rng: &mut R, secp: &Secp256k1, - receiver_blinding_pk: &PublicKey, + receiver_blinding_pk: &ConfInner, ) -> (Self, SecretKey) { let ephemeral_sk = SecretKey::new(rng); Self::with_ephemeral_sk(secp, ephemeral_sk, receiver_blinding_pk) @@ -43,9 +51,9 @@ impl Nonce { pub fn with_ephemeral_sk( secp: &Secp256k1, ephemeral_sk: SecretKey, - receiver_blinding_pk: &PublicKey, + receiver_blinding_pk: &ConfInner, ) -> (Self, SecretKey) { - let sender_pk = PublicKey::from_secret_key(secp, &ephemeral_sk); + let sender_pk = ConfInner::from_secret_key(secp, &ephemeral_sk); let shared_secret = Self::make_shared_secret(receiver_blinding_pk, &ephemeral_sk); (Self::Confidential(sender_pk), shared_secret) } @@ -60,14 +68,14 @@ impl Nonce { } /// Create the shared secret. - fn make_shared_secret(pk: &PublicKey, sk: &SecretKey) -> SecretKey { + fn make_shared_secret(pk: &ConfInner, sk: &SecretKey) -> SecretKey { let xy = secp256k1_zkp::ecdh::shared_secret_point(pk, sk); let shared_secret = { // Yes, what follows is the compressed representation of a Bitcoin public key. // However, this is more by accident then by design, see here: https://github.com/rust-bitcoin/rust-secp256k1/pull/255#issuecomment-744146282 - let mut dh_secret = [0u8; 33]; - dh_secret[0] = if xy.last().unwrap() % 2 == 0 { 0x02 } else { 0x03 }; + let mut dh_secret = [0u8; CONFIDENTIAL_LEN]; + dh_secret[0] = if xy.last().unwrap() % 2 == 0 { CONF_PREFIX_1 } else { CONF_PREFIX_2 }; dh_secret[1..].copy_from_slice(&xy[0..32]); sha256d::Hash::hash(&dh_secret).to_byte_array() @@ -80,15 +88,15 @@ impl Nonce { pub fn encoded_length(&self) -> usize { match *self { Self::Null => 1, - Self::Explicit(..) => 33, - Self::Confidential(..) => 33, + Self::Explicit(..) => 1 + EXPLICIT_LEN, + Self::Confidential(..) => CONFIDENTIAL_LEN, } } /// Create from commitment. pub fn from_commitment(bytes: &[u8]) -> Result { Ok(Self::Confidential( - PublicKey::from_slice(bytes).map_err(secp256k1_zkp::Error::Upstream)?, + ConfInner::from_slice(bytes).map_err(secp256k1_zkp::Error::Upstream)?, )) } @@ -103,7 +111,7 @@ impl Nonce { /// Returns the explicit inner value. /// Returns [None] if [`Self::is_explicit`] returns false. - pub fn explicit(&self) -> Option<[u8; 32]> { + pub fn explicit(&self) -> Option { match *self { Self::Explicit(i) => Some(i), _ => None, @@ -112,7 +120,7 @@ impl Nonce { /// Returns the confidential commitment in case of a confidential value. /// Returns [None] if [`Self::is_confidential`] returns false. - pub fn commitment(&self) -> Option { + pub fn commitment(&self) -> Option { match *self { Self::Confidential(i) => Some(i), _ => None, @@ -120,8 +128,8 @@ impl Nonce { } } -impl From for Nonce { - fn from(from: PublicKey) -> Self { Self::Confidential(from) } +impl From for Nonce { + fn from(from: ConfInner) -> Self { Self::Confidential(from) } } impl fmt::Display for Nonce { @@ -149,7 +157,7 @@ impl Encodable for Nonce { } Self::Confidential(commitment) => { s.write_all(&commitment.serialize())?; - Ok(33) + Ok(CONFIDENTIAL_LEN) } } } @@ -165,11 +173,11 @@ impl Decodable for Nonce { let explicit = Decodable::consensus_decode(&mut d)?; Ok(Self::Explicit(explicit)) } - p if p == 0x02 || p == 0x03 => { - let mut comm = [0u8; 33]; + p if p == CONF_PREFIX_1 || p == CONF_PREFIX_2 => { + let mut comm = [0u8; CONFIDENTIAL_LEN]; comm[0] = p; d.read_exact(&mut comm[1..])?; - Ok(Self::Confidential(PublicKey::from_slice(&comm)?)) + Ok(Self::Confidential(ConfInner::from_slice(&comm)?)) } p => Err(encode::Error::InvalidConfidentialPrefix(p)), } diff --git a/src/confidential/value.rs b/src/confidential/value.rs index 2f128281..9c5313cb 100644 --- a/src/confidential/value.rs +++ b/src/confidential/value.rs @@ -18,6 +18,14 @@ use crate::confidential::AssetBlindingFactor; use crate::encode::{self, Decodable, Encodable}; use crate::issuance::AssetId; +type ExplicitInner = u64; +type ConfInner = PedersenCommitment; + +const EXPLICIT_LEN: usize = 8; +const CONFIDENTIAL_LEN: usize = 33; +const CONF_PREFIX_1: u8 = 0x08; +const CONF_PREFIX_2: u8 = 0x09; + /// A CT commitment to an amount #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] pub enum Value { @@ -25,9 +33,9 @@ pub enum Value { #[default] Null, /// Value is explicitly encoded - Explicit(u64), + Explicit(ExplicitInner), /// Value is committed - Confidential(PedersenCommitment), + Confidential(ConfInner), } impl Value { @@ -36,9 +44,9 @@ impl Value { secp: &Secp256k1, value: u64, asset: Generator, - bf: ValueBlindingFactor, + bf: BlindingFactor, ) -> Self { - Self::Confidential(PedersenCommitment::new(secp, value, bf.0, asset)) + Self::Confidential(ConfInner::new(secp, value, bf.0, asset)) } /// Create value commitment from assetID, asset blinding factor, @@ -47,11 +55,11 @@ impl Value { secp: &Secp256k1, value: u64, asset: AssetId, - v_bf: ValueBlindingFactor, + v_bf: BlindingFactor, a_bf: AssetBlindingFactor, ) -> Self { let generator = Generator::new_blinded(secp, asset.into_tag(), a_bf.0); - let comm = PedersenCommitment::new(secp, value, v_bf.0, generator); + let comm = ConfInner::new(secp, value, v_bf.0, generator); Self::Confidential(comm) } @@ -60,14 +68,14 @@ impl Value { pub fn encoded_length(&self) -> usize { match *self { Self::Null => 1, - Self::Explicit(..) => 9, - Self::Confidential(..) => 33, + Self::Explicit(..) => 1 + EXPLICIT_LEN, + Self::Confidential(..) => CONFIDENTIAL_LEN, } } /// Create from commitment. pub fn from_commitment(bytes: &[u8]) -> Result { - Ok(Self::Confidential(PedersenCommitment::from_slice(bytes)?)) + Ok(Self::Confidential(ConfInner::from_slice(bytes)?)) } /// Check if the object is null. @@ -81,7 +89,7 @@ impl Value { /// Returns the explicit inner value. /// Returns [None] if [`Self::is_explicit`] returns false. - pub fn explicit(&self) -> Option { + pub fn explicit(&self) -> Option { match *self { Self::Explicit(i) => Some(i), _ => None, @@ -90,7 +98,7 @@ impl Value { /// Returns the confidential commitment in case of a confidential value. /// Returns [None] if [`Self::is_confidential`] returns false. - pub fn commitment(&self) -> Option { + pub fn commitment(&self) -> Option { match *self { Self::Confidential(i) => Some(i), _ => None, @@ -98,8 +106,8 @@ impl Value { } } -impl From for Value { - fn from(from: PedersenCommitment) -> Self { Self::Confidential(from) } +impl From for Value { + fn from(from: ConfInner) -> Self { Self::Confidential(from) } } impl fmt::Display for Value { @@ -122,7 +130,7 @@ impl Encodable for Value { } Self::Confidential(commitment) => { s.write_all(&commitment.serialize())?; - Ok(33) + Ok(CONFIDENTIAL_LEN) } } } @@ -138,11 +146,11 @@ impl Decodable for Value { let explicit = u64::swap_bytes(Decodable::consensus_decode(&mut d)?); Ok(Self::Explicit(explicit)) } - p if p == 0x08 || p == 0x09 => { - let mut comm = [0u8; 33]; + p if p == CONF_PREFIX_1 || p == CONF_PREFIX_2 => { + let mut comm = [0u8; CONFIDENTIAL_LEN]; comm[0] = p; d.read_exact(&mut comm[1..])?; - Ok(Self::Confidential(PedersenCommitment::from_slice(&comm)?)) + Ok(Self::Confidential(ConfInner::from_slice(&comm)?)) } p => Err(encode::Error::InvalidConfidentialPrefix(p)), } @@ -211,9 +219,9 @@ impl<'de> Deserialize<'de> for Value { /// Blinding factor used for value commitments. #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub struct ValueBlindingFactor(pub(crate) Tweak); +pub struct BlindingFactor(pub(crate) Tweak); -impl ValueBlindingFactor { +impl BlindingFactor { /// Generate random value blinding factor. pub fn new(rng: &mut R) -> Self { Self(Tweak::new(rng)) } @@ -261,7 +269,7 @@ impl ValueBlindingFactor { pub fn zero() -> Self { Self(ZERO_TWEAK) } } -impl AddAssign for ValueBlindingFactor { +impl AddAssign for BlindingFactor { fn add_assign(&mut self, other: Self) { if self.0.as_ref() == &[0u8; 32] { *self = other; @@ -285,7 +293,7 @@ impl AddAssign for ValueBlindingFactor { } } -impl Neg for ValueBlindingFactor { +impl Neg for BlindingFactor { type Output = Self; fn neg(self) -> Self::Output { @@ -298,18 +306,18 @@ impl Neg for ValueBlindingFactor { } } -impl core::borrow::Borrow<[u8]> for ValueBlindingFactor { +impl core::borrow::Borrow<[u8]> for BlindingFactor { fn borrow(&self) -> &[u8] { &self.0[..] } } hex::impl_fmt_traits! { #[display_backward(true)] - impl fmt_traits for ValueBlindingFactor { + impl fmt_traits for BlindingFactor { const LENGTH: usize = 32; } } -impl str::FromStr for ValueBlindingFactor { +impl str::FromStr for BlindingFactor { type Err = encode::Error; fn from_str(s: &str) -> Result { @@ -322,7 +330,7 @@ impl str::FromStr for ValueBlindingFactor { } #[cfg(feature = "serde")] -impl Serialize for ValueBlindingFactor { +impl Serialize for BlindingFactor { fn serialize(&self, s: S) -> Result { if s.is_human_readable() { s.collect_str(&self) @@ -333,13 +341,13 @@ impl Serialize for ValueBlindingFactor { } #[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for ValueBlindingFactor { +impl<'de> Deserialize<'de> for BlindingFactor { fn deserialize>(d: D) -> Result { if d.is_human_readable() { struct HexVisitor; impl ::serde::de::Visitor<'_> for HexVisitor { - type Value = ValueBlindingFactor; + type Value = BlindingFactor; fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { formatter.write_str("an ASCII hex string") @@ -369,7 +377,7 @@ impl<'de> Deserialize<'de> for ValueBlindingFactor { struct BytesVisitor; impl ::serde::de::Visitor<'_> for BytesVisitor { - type Value = ValueBlindingFactor; + type Value = BlindingFactor; fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { formatter.write_str("a bytestring") @@ -384,7 +392,7 @@ impl<'de> Deserialize<'de> for ValueBlindingFactor { match <[u8; 32]>::try_from(v) { Ok(ret) => { let inner = Tweak::from_inner(ret).map_err(E::custom)?; - Ok(ValueBlindingFactor(inner)) + Ok(BlindingFactor(inner)) } Err(_) => Err(E::invalid_length(v.len(), &stringify!($len))), } From cf24680388f86cd0a97d3a3fc9503a108645212c Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Sat, 27 Jun 2026 15:10:15 +0000 Subject: [PATCH 12/12] confidential: directly use Read/Write in Encodable/Decodable We eventually want to remove Encodable and Decodable. To help with that, start by cleaning up a few things: * don't use the traits on u8 or u64 * especially don't use the trait on u64 then call `swap_bytes` to deal with the fact that we want a BE encoding --- src/confidential/asset.rs | 26 +++++++++++++++----------- src/confidential/nonce.rs | 26 +++++++++++++++----------- src/confidential/value.rs | 26 +++++++++++++++----------- 3 files changed, 45 insertions(+), 33 deletions(-) diff --git a/src/confidential/asset.rs b/src/confidential/asset.rs index 3d5d8082..d419ebde 100644 --- a/src/confidential/asset.rs +++ b/src/confidential/asset.rs @@ -120,10 +120,14 @@ impl fmt::Display for Asset { impl Encodable for Asset { fn consensus_encode(&self, mut s: S) -> Result { match *self { - Self::Null => 0u8.consensus_encode(s), + Self::Null => { + s.write_all(&[0u8])?; + Ok(1) + } Self::Explicit(n) => { - 1u8.consensus_encode(&mut s)?; - Ok(1 + n.consensus_encode(&mut s)?) + s.write_all(&[1u8])?; + s.write_all(n.as_byte_array())?; + Ok(1 + EXPLICIT_LEN) } Self::Confidential(generator) => { s.write_all(&generator.serialize())?; @@ -135,19 +139,19 @@ impl Encodable for Asset { impl Decodable for Asset { fn consensus_decode(mut d: D) -> Result { - let prefix = u8::consensus_decode(&mut d)?; + let mut buf = [0u8; CONFIDENTIAL_LEN]; + d.read_exact(&mut buf[0..1])?; - match prefix { + match buf[0] { 0 => Ok(Self::Null), 1 => { - let explicit = Decodable::consensus_decode(&mut d)?; - Ok(Self::Explicit(explicit)) + let mut buf = [0; EXPLICIT_LEN]; + d.read_exact(&mut buf)?; + Ok(Self::Explicit(AssetId::from_byte_array(buf))) } p if p == CONF_PREFIX_1 || p == CONF_PREFIX_2 => { - let mut comm = [0u8; CONFIDENTIAL_LEN]; - comm[0] = p; - d.read_exact(&mut comm[1..])?; - Ok(Self::Confidential(ConfInner::from_slice(&comm[..])?)) + d.read_exact(&mut buf[1..])?; + Ok(Self::Confidential(ConfInner::from_slice(&buf[..])?)) } p => Err(encode::Error::InvalidConfidentialPrefix(p)), } diff --git a/src/confidential/nonce.rs b/src/confidential/nonce.rs index 67f4c09a..0d9ca000 100644 --- a/src/confidential/nonce.rs +++ b/src/confidential/nonce.rs @@ -150,10 +150,14 @@ impl fmt::Display for Nonce { impl Encodable for Nonce { fn consensus_encode(&self, mut s: S) -> Result { match *self { - Self::Null => 0u8.consensus_encode(s), + Self::Null => { + s.write_all(&[0u8])?; + Ok(1) + } Self::Explicit(n) => { - 1u8.consensus_encode(&mut s)?; - Ok(1 + n.consensus_encode(&mut s)?) + s.write_all(&[1u8])?; + s.write_all(&n)?; + Ok(1 + EXPLICIT_LEN) } Self::Confidential(commitment) => { s.write_all(&commitment.serialize())?; @@ -165,19 +169,19 @@ impl Encodable for Nonce { impl Decodable for Nonce { fn consensus_decode(mut d: D) -> Result { - let prefix = u8::consensus_decode(&mut d)?; + let mut buf = [0u8; CONFIDENTIAL_LEN]; + d.read_exact(&mut buf[0..1])?; - match prefix { + match buf[0] { 0 => Ok(Self::Null), 1 => { - let explicit = Decodable::consensus_decode(&mut d)?; - Ok(Self::Explicit(explicit)) + let mut buf = [0; EXPLICIT_LEN]; + d.read_exact(&mut buf)?; + Ok(Self::Explicit(buf)) } p if p == CONF_PREFIX_1 || p == CONF_PREFIX_2 => { - let mut comm = [0u8; CONFIDENTIAL_LEN]; - comm[0] = p; - d.read_exact(&mut comm[1..])?; - Ok(Self::Confidential(ConfInner::from_slice(&comm)?)) + d.read_exact(&mut buf[1..])?; + Ok(Self::Confidential(ConfInner::from_slice(&buf)?)) } p => Err(encode::Error::InvalidConfidentialPrefix(p)), } diff --git a/src/confidential/value.rs b/src/confidential/value.rs index 9c5313cb..a8267148 100644 --- a/src/confidential/value.rs +++ b/src/confidential/value.rs @@ -123,10 +123,14 @@ impl fmt::Display for Value { impl Encodable for Value { fn consensus_encode(&self, mut s: S) -> Result { match *self { - Self::Null => 0u8.consensus_encode(s), + Self::Null => { + s.write_all(&[0u8])?; + Ok(1) + } Self::Explicit(n) => { - 1u8.consensus_encode(&mut s)?; - Ok(1 + u64::swap_bytes(n).consensus_encode(&mut s)?) + s.write_all(&[1u8])?; + s.write_all(&n.to_be_bytes())?; + Ok(1 + EXPLICIT_LEN) } Self::Confidential(commitment) => { s.write_all(&commitment.serialize())?; @@ -138,19 +142,19 @@ impl Encodable for Value { impl Decodable for Value { fn consensus_decode(mut d: D) -> Result { - let prefix = u8::consensus_decode(&mut d)?; + let mut buf = [0u8; CONFIDENTIAL_LEN]; + d.read_exact(&mut buf[0..1])?; - match prefix { + match buf[0] { 0 => Ok(Self::Null), 1 => { - let explicit = u64::swap_bytes(Decodable::consensus_decode(&mut d)?); - Ok(Self::Explicit(explicit)) + let mut buf = [0; EXPLICIT_LEN]; + d.read_exact(&mut buf)?; + Ok(Self::Explicit(u64::from_be_bytes(buf))) } p if p == CONF_PREFIX_1 || p == CONF_PREFIX_2 => { - let mut comm = [0u8; CONFIDENTIAL_LEN]; - comm[0] = p; - d.read_exact(&mut comm[1..])?; - Ok(Self::Confidential(ConfInner::from_slice(&comm)?)) + d.read_exact(&mut buf[1..])?; + Ok(Self::Confidential(ConfInner::from_slice(&buf)?)) } p => Err(encode::Error::InvalidConfidentialPrefix(p)), }