From 7dc5a78dcd5bf406ef7fb42aec301922770fdfe3 Mon Sep 17 00:00:00 2001 From: Leonardo Comandini Date: Wed, 13 Aug 2025 09:39:33 +0200 Subject: [PATCH 1/5] pset: blind_non_last: return ephemeral secret key --- src/pset/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pset/mod.rs b/src/pset/mod.rs index 532826ec..fae35a98 100644 --- a/src/pset/mod.rs +++ b/src/pset/mod.rs @@ -478,7 +478,7 @@ impl PartiallySignedTransaction { rng: &mut R, secp: &secp256k1_zkp::Secp256k1, inp_txout_sec: &HashMap, - ) -> Result, PsetBlindError> { + ) -> Result, PsetBlindError> { let (inp_secrets, outs_to_blind) = self.blind_checks(inp_txout_sec)?; if outs_to_blind.is_empty() { @@ -491,7 +491,7 @@ impl PartiallySignedTransaction { let mut ret = vec![]; // return all the random values used for i in outs_to_blind { let txout = self.outputs[i].to_txout(); - let (txout, abf, vbf, _) = txout + let (txout, abf, vbf, ephemeral_sk) = txout .to_non_last_confidential( rng, secp, @@ -538,7 +538,7 @@ impl PartiallySignedTransaction { )); } // return blinding factors used - ret.push((abf, vbf)); + ret.push((abf, vbf, ephemeral_sk)); } // safe to unwrap because we have checked that there is atleast one output to blind From 7e3328323e2351745c476618aa79755b25b0ee1d Mon Sep 17 00:00:00 2001 From: Leonardo Comandini Date: Wed, 13 Aug 2025 09:40:52 +0200 Subject: [PATCH 2/5] pset: blind_last: return blinders and ephemeral secret key --- src/pset/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pset/mod.rs b/src/pset/mod.rs index fae35a98..6da0c149 100644 --- a/src/pset/mod.rs +++ b/src/pset/mod.rs @@ -579,9 +579,10 @@ impl PartiallySignedTransaction { rng: &mut R, secp: &secp256k1_zkp::Secp256k1, inp_txout_sec: &HashMap, - ) -> Result<(), PsetBlindError> { + ) -> Result, PsetBlindError> { let (mut inp_secrets, mut outs_to_blind) = self.blind_checks(inp_txout_sec)?; + let mut ret = vec![]; if outs_to_blind.is_empty() { // Atleast one output must be marked for blinding for pset blind_last return Err(PsetBlindError::AtleastOneOutputBlind); @@ -594,7 +595,7 @@ impl PartiallySignedTransaction { let ind = self.outputs[last_out_index].blinder_index; self.outputs[last_out_index].blinder_index = None; // Blind normally without the last index - self.blind_non_last(rng, secp, inp_txout_sec)?; + ret = self.blind_non_last(rng, secp, inp_txout_sec)?; // Restore who blinded the last output self.outputs[last_out_index].blinder_index = ind; // inp_secrets contributed to self.global.scalars, unset it so we don't count them @@ -657,6 +658,7 @@ impl PartiallySignedTransaction { ); let (value_commitment, nonce, rangeproof) = blind_res.map_err(|e| PsetBlindError::ConfidentialTxOutError(last_out_index, e))?; + ret.push((out_abf, final_vbf, ephemeral_sk)); // mutate the pset { @@ -690,7 +692,7 @@ impl PartiallySignedTransaction { self.global.scalars.clear(); } - Ok(()) + Ok(ret) } } From d66b028988cb28cd330872d1e6308fc9ddf3245b Mon Sep 17 00:00:00 2001 From: Leonardo Comandini Date: Wed, 13 Aug 2025 10:08:14 +0200 Subject: [PATCH 3/5] blind: export TxInType --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index b54fc1f5..bfee714a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,7 +78,7 @@ pub use bitcoin::hashes; pub use crate::address::{Address, AddressError, AddressParams}; pub use crate::blind::{ BlindAssetProofs, BlindError, BlindValueProofs, ConfidentialTxOutError, RangeProofMessage, - SurjectionInput, TxOutError, TxOutSecrets, UnblindError, VerificationError, + SurjectionInput, TxOutError, TxOutSecrets, UnblindError, VerificationError, TxInType, }; pub use crate::block::ExtData as BlockExtData; pub use crate::block::{Block, BlockHeader}; From e4144e6c5727c412ccf2b41f9372a1075a25beed Mon Sep 17 00:00:00 2001 From: Leonardo Comandini Date: Wed, 13 Aug 2025 10:10:44 +0200 Subject: [PATCH 4/5] pset: blind: return map instead of vec --- src/pset/mod.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/pset/mod.rs b/src/pset/mod.rs index 6da0c149..cd693dd9 100644 --- a/src/pset/mod.rs +++ b/src/pset/mod.rs @@ -20,7 +20,7 @@ //! Extension for PSET is based on PSET defined in BIP370. //! -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::{cmp, io}; mod error; @@ -46,7 +46,10 @@ use crate::{ confidential::{AssetBlindingFactor, ValueBlindingFactor}, TxOutSecrets, }; -use crate::{OutPoint, LockTime, Sequence, SurjectionInput, Transaction, TxIn, TxInWitness, TxOut, TxOutWitness, Txid}; +use crate::{ + LockTime, OutPoint, Sequence, SurjectionInput, Transaction, TxIn, TxInType, + TxInWitness, TxOut, TxOutWitness, Txid, +}; use secp256k1_zkp::rand::{CryptoRng, RngCore}; use secp256k1_zkp::{self, RangeProof, SecretKey, SurjectionProof}; @@ -478,17 +481,17 @@ impl PartiallySignedTransaction { rng: &mut R, secp: &secp256k1_zkp::Secp256k1, inp_txout_sec: &HashMap, - ) -> Result, PsetBlindError> { + ) -> Result, PsetBlindError> { let (inp_secrets, outs_to_blind) = self.blind_checks(inp_txout_sec)?; + let mut ret = BTreeMap::new(); // return all the random values used if outs_to_blind.is_empty() { // Return empty values if no outputs are marked for blinding - return Ok(Vec::new()); + return Ok(ret); } // Blind each output as non-last and save the secrets let surject_inputs = self.surjection_inputs(inp_txout_sec)?; let mut out_secrets = vec![]; - let mut ret = vec![]; // return all the random values used for i in outs_to_blind { let txout = self.outputs[i].to_txout(); let (txout, abf, vbf, ephemeral_sk) = txout @@ -538,7 +541,7 @@ impl PartiallySignedTransaction { )); } // return blinding factors used - ret.push((abf, vbf, ephemeral_sk)); + ret.insert(TxInType::Input(i), (abf, vbf, ephemeral_sk)); } // safe to unwrap because we have checked that there is atleast one output to blind @@ -579,10 +582,10 @@ impl PartiallySignedTransaction { rng: &mut R, secp: &secp256k1_zkp::Secp256k1, inp_txout_sec: &HashMap, - ) -> Result, PsetBlindError> { + ) -> Result, PsetBlindError> { let (mut inp_secrets, mut outs_to_blind) = self.blind_checks(inp_txout_sec)?; - let mut ret = vec![]; + let mut ret = BTreeMap::new(); if outs_to_blind.is_empty() { // Atleast one output must be marked for blinding for pset blind_last return Err(PsetBlindError::AtleastOneOutputBlind); @@ -658,7 +661,7 @@ impl PartiallySignedTransaction { ); let (value_commitment, nonce, rangeproof) = blind_res.map_err(|e| PsetBlindError::ConfidentialTxOutError(last_out_index, e))?; - ret.push((out_abf, final_vbf, ephemeral_sk)); + ret.insert(TxInType::Input(last_out_index), (out_abf, final_vbf, ephemeral_sk)); // mutate the pset { From cc12b8ed36d86b4959ed8524a81ae3207ac03a03 Mon Sep 17 00:00:00 2001 From: Leonardo Comandini Date: Wed, 13 Aug 2025 16:57:23 +0200 Subject: [PATCH 5/5] blind: TxInType -> CtLocation --- src/blind.rs | 39 +++++++++++++++++++++++++++------------ src/lib.rs | 2 +- src/pset/mod.rs | 14 ++++++++------ 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/blind.rs b/src/blind.rs index 340e62c5..424a4e7d 100644 --- a/src/blind.rs +++ b/src/blind.rs @@ -916,17 +916,30 @@ impl TxIn { } } -/// Data structure for Unifying inputs and pseudo-inputs. +/// Inputs or pseudo-inputs. #[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)] -pub enum TxInType { +pub enum CtLocationType { /// Regular input - Input(usize), - /// Issuance Pseudo-input - Issuance(usize), - /// Re-issuance pseudo-input - ReIssuance(usize), + Input, + + /// Issuance pseudo-input + Issuance, + + /// Reissuance pseudo-input + Reissuance, } +/// Data structure for Unifying inputs and pseudo-inputs. +#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub struct CtLocation { + /// Input index + pub input_index: usize, + + /// Input or pseudo-input type + pub ty: CtLocationType, +} + + impl Transaction { /// Verify that the transaction has correctly calculated blinding /// factors and they CT verification equation holds. @@ -1078,7 +1091,7 @@ impl Transaction { secp: &Secp256k1, spent_utxo_secrets: &[TxOutSecrets], blind_issuances: bool, - ) -> Result, BlindError> + ) -> Result, BlindError> where R: RngCore + CryptoRng, C: Signing, @@ -1090,13 +1103,13 @@ impl Transaction { let (iss_vbf, iss_sk, tkn_vbf, tkn_sk) = txin.blind_issuances(secp, rng)?; if txin.asset_issuance.amount.is_confidential() { blinds.insert( - TxInType::Issuance(i), + CtLocation{ input_index: i, ty: CtLocationType::Issuance }, (AssetBlindingFactor::zero(), iss_vbf, iss_sk), ); } if txin.asset_issuance.inflation_keys.is_confidential() { blinds.insert( - TxInType::ReIssuance(i), + CtLocation{ input_index: i, ty: CtLocationType::Reissuance }, (AssetBlindingFactor::zero(), tkn_vbf, tkn_sk), ); } @@ -1144,7 +1157,8 @@ impl Transaction { spent_utxo_secrets, )?; - blinds.insert(TxInType::Input(i), (abf, vbf, ephemeral_sk)); + let location = CtLocation { input_index: i, ty: CtLocationType::Input}; + blinds.insert(location, (abf, vbf, ephemeral_sk)); out_secrets.push(TxOutSecrets::new( out.asset.explicit().unwrap(), abf, @@ -1184,7 +1198,8 @@ impl Transaction { &out_secrets, )?; - blinds.insert(TxInType::Input(last_index), (abf, vbf, ephemeral_sk)); + let location = CtLocation{ input_index: last_index, ty: CtLocationType::Input }; + blinds.insert(location, (abf, vbf, ephemeral_sk)); self.output[last_index] = conf_out; Ok(blinds) } diff --git a/src/lib.rs b/src/lib.rs index bfee714a..f90057ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,7 +78,7 @@ pub use bitcoin::hashes; pub use crate::address::{Address, AddressError, AddressParams}; pub use crate::blind::{ BlindAssetProofs, BlindError, BlindValueProofs, ConfidentialTxOutError, RangeProofMessage, - SurjectionInput, TxOutError, TxOutSecrets, UnblindError, VerificationError, TxInType, + SurjectionInput, TxOutError, TxOutSecrets, UnblindError, VerificationError, CtLocation, CtLocationType, }; pub use crate::block::ExtData as BlockExtData; pub use crate::block::{Block, BlockHeader}; diff --git a/src/pset/mod.rs b/src/pset/mod.rs index cd693dd9..34106095 100644 --- a/src/pset/mod.rs +++ b/src/pset/mod.rs @@ -47,8 +47,8 @@ use crate::{ TxOutSecrets, }; use crate::{ - LockTime, OutPoint, Sequence, SurjectionInput, Transaction, TxIn, TxInType, - TxInWitness, TxOut, TxOutWitness, Txid, + 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}; @@ -481,7 +481,7 @@ impl PartiallySignedTransaction { rng: &mut R, secp: &secp256k1_zkp::Secp256k1, inp_txout_sec: &HashMap, - ) -> Result, PsetBlindError> { + ) -> Result, PsetBlindError> { let (inp_secrets, outs_to_blind) = self.blind_checks(inp_txout_sec)?; let mut ret = BTreeMap::new(); // return all the random values used @@ -541,7 +541,8 @@ impl PartiallySignedTransaction { )); } // return blinding factors used - ret.insert(TxInType::Input(i), (abf, vbf, ephemeral_sk)); + let location = CtLocation{ input_index: i, ty: CtLocationType::Input}; + ret.insert(location, (abf, vbf, ephemeral_sk)); } // safe to unwrap because we have checked that there is atleast one output to blind @@ -582,7 +583,7 @@ impl PartiallySignedTransaction { rng: &mut R, secp: &secp256k1_zkp::Secp256k1, inp_txout_sec: &HashMap, - ) -> Result, PsetBlindError> { + ) -> Result, PsetBlindError> { let (mut inp_secrets, mut outs_to_blind) = self.blind_checks(inp_txout_sec)?; let mut ret = BTreeMap::new(); @@ -661,7 +662,8 @@ impl PartiallySignedTransaction { ); let (value_commitment, nonce, rangeproof) = blind_res.map_err(|e| PsetBlindError::ConfidentialTxOutError(last_out_index, e))?; - ret.insert(TxInType::Input(last_out_index), (out_abf, final_vbf, ephemeral_sk)); + let location = CtLocation{ input_index: last_out_index, ty: CtLocationType::Input}; + ret.insert(location, (out_abf, final_vbf, ephemeral_sk)); // mutate the pset {