diff --git a/src/pset/map/input.rs b/src/pset/map/input.rs index 618f2eef..098a2469 100644 --- a/src/pset/map/input.rs +++ b/src/pset/map/input.rs @@ -23,7 +23,7 @@ use std::{ use crate::taproot::{ControlBlock, LeafVersion, TapNodeHash, TapLeafHash}; use crate::{schnorr, AssetId, ContractHash}; -use crate::{confidential, locktime}; +use crate::{confidential::{self, AssetBlindingFactor}, locktime}; use crate::encode::{self, Decodable}; use crate::hashes::{self, hash160, ripemd160, sha256, sha256d, Hash}; use crate::pset::map::Map; @@ -168,6 +168,8 @@ const PSBT_ELEMENTS_IN_ASSET_PROOF: u8 = 0x14; /// Note that this does not indicate actual blinding status, /// but rather the expected blinding status prior to signing. const PSBT_ELEMENTS_IN_BLINDED_ISSUANCE: u8 = 0x15; +/// The 32 byte asset blinding factor for the input being spent. +const PSBT_ELEMENTS_IN_ASSET_BLINDING_FACTOR: u8 = 0x16; /// A key-value map for an input of the corresponding index in the unsigned /// transaction. #[derive(Clone, Debug, PartialEq)] @@ -301,6 +303,8 @@ pub struct Input { pub blind_asset_proof: Option>, /// Whether the issuance is blinded pub blinded_issuance: Option, + /// The input asset blinding factor + pub asset_blinding_factor: Option, /// Other fields #[cfg_attr( feature = "serde", @@ -317,7 +321,7 @@ pub struct Input { impl Default for Input { fn default() -> Self { - Self { non_witness_utxo: Default::default(), witness_utxo: Default::default(), partial_sigs: Default::default(), sighash_type: Default::default(), redeem_script: Default::default(), witness_script: Default::default(), bip32_derivation: Default::default(), final_script_sig: Default::default(), final_script_witness: Default::default(), ripemd160_preimages: Default::default(), sha256_preimages: Default::default(), hash160_preimages: Default::default(), hash256_preimages: Default::default(), previous_txid: Txid::all_zeros(), previous_output_index: Default::default(), sequence: Default::default(), required_time_locktime: Default::default(), required_height_locktime: Default::default(), tap_key_sig: Default::default(), tap_script_sigs: Default::default(), tap_scripts: Default::default(), tap_key_origins: Default::default(), tap_internal_key: Default::default(), tap_merkle_root: Default::default(), issuance_value_amount: Default::default(), issuance_value_comm: Default::default(), issuance_value_rangeproof: Default::default(), issuance_keys_rangeproof: Default::default(), pegin_tx: Default::default(), pegin_txout_proof: Default::default(), pegin_genesis_hash: Default::default(), pegin_claim_script: Default::default(), pegin_value: Default::default(), pegin_witness: Default::default(), issuance_inflation_keys: Default::default(), issuance_inflation_keys_comm: Default::default(), issuance_blinding_nonce: Default::default(), issuance_asset_entropy: Default::default(), in_utxo_rangeproof: Default::default(), in_issuance_blind_value_proof: Default::default(), in_issuance_blind_inflation_keys_proof: Default::default(), amount: Default::default(), blind_value_proof: Default::default(), asset: Default::default(), blind_asset_proof: Default::default(), blinded_issuance: Default::default(), proprietary: Default::default(), unknown: Default::default() } + Self { non_witness_utxo: Default::default(), witness_utxo: Default::default(), partial_sigs: Default::default(), sighash_type: Default::default(), redeem_script: Default::default(), witness_script: Default::default(), bip32_derivation: Default::default(), final_script_sig: Default::default(), final_script_witness: Default::default(), ripemd160_preimages: Default::default(), sha256_preimages: Default::default(), hash160_preimages: Default::default(), hash256_preimages: Default::default(), previous_txid: Txid::all_zeros(), previous_output_index: Default::default(), sequence: Default::default(), required_time_locktime: Default::default(), required_height_locktime: Default::default(), tap_key_sig: Default::default(), tap_script_sigs: Default::default(), tap_scripts: Default::default(), tap_key_origins: Default::default(), tap_internal_key: Default::default(), tap_merkle_root: Default::default(), issuance_value_amount: Default::default(), issuance_value_comm: Default::default(), issuance_value_rangeproof: Default::default(), issuance_keys_rangeproof: Default::default(), pegin_tx: Default::default(), pegin_txout_proof: Default::default(), pegin_genesis_hash: Default::default(), pegin_claim_script: Default::default(), pegin_value: Default::default(), pegin_witness: Default::default(), issuance_inflation_keys: Default::default(), issuance_inflation_keys_comm: Default::default(), issuance_blinding_nonce: Default::default(), issuance_asset_entropy: Default::default(), in_utxo_rangeproof: Default::default(), in_issuance_blind_value_proof: Default::default(), in_issuance_blind_inflation_keys_proof: Default::default(), amount: Default::default(), blind_value_proof: Default::default(), asset: Default::default(), blind_asset_proof: Default::default(), blinded_issuance: Default::default(), asset_blinding_factor: Default::default(), proprietary: Default::default(), unknown: Default::default() } } } @@ -750,6 +754,9 @@ impl Map for Input { PSBT_ELEMENTS_IN_BLINDED_ISSUANCE => { impl_pset_prop_insert_pair!(self.blinded_issuance <= | ) } + PSBT_ELEMENTS_IN_ASSET_BLINDING_FACTOR => { + impl_pset_prop_insert_pair!(self.asset_blinding_factor <= | ) + } _ => match self.proprietary.entry(prop_key) { Entry::Vacant(empty_key) => { empty_key.insert(raw_value); @@ -968,6 +975,10 @@ impl Map for Input { rv.push_prop(self.blinded_issuance as ) } + impl_pset_get_pair! { + rv.push_prop(self.asset_blinding_factor as ) + } + for (key, value) in self.proprietary.iter() { rv.push(raw::Pair { key: key.to_key(), @@ -1047,6 +1058,7 @@ impl Map for Input { merge!(asset, self, other); merge!(blind_asset_proof, self, other); merge!(blinded_issuance, self, other); + merge!(asset_blinding_factor, self, other); Ok(()) } } diff --git a/src/pset/map/output.rs b/src/pset/map/output.rs index ad8df6ff..bba375b7 100644 --- a/src/pset/map/output.rs +++ b/src/pset/map/output.rs @@ -22,7 +22,7 @@ use crate::encode::Decodable; use crate::pset::map::Map; use crate::pset::raw; use crate::pset::Error; -use crate::{confidential, pset}; +use crate::{confidential::{self, AssetBlindingFactor}, pset}; use crate::{encode, Script, TxOutWitness}; use bitcoin::bip32::KeySource; use bitcoin::{PublicKey, key::XOnlyPublicKey}; @@ -83,6 +83,8 @@ const PSBT_ELEMENTS_OUT_BLIND_VALUE_PROOF: u8 = 0x09; /// PSBT_ELEMENTS_OUT_ASSET. If provided, PSBT_ELEMENTS_OUT_ASSET_COMMITMENT must /// be provided too. const PSBT_ELEMENTS_OUT_BLIND_ASSET_PROOF: u8 = 0x0a; +/// The 32 byte asset blinding factor for this output. +const PSBT_ELEMENTS_OUT_ASSET_BLINDING_FACTOR: u8 = 0x0b; /// A key-value map for an output of the corresponding index in the unsigned /// transaction. @@ -129,6 +131,8 @@ pub struct Output { pub blind_value_proof: Option>, /// The blind asset surjection proof pub blind_asset_proof: Option>, + /// The 32 byte asset blinding factor + pub asset_blinding_factor: Option, /// Pset /// Other fields #[cfg_attr( @@ -374,6 +378,9 @@ impl Map for Output { PSBT_ELEMENTS_OUT_BLIND_ASSET_PROOF => { impl_pset_prop_insert_pair!(self.blind_asset_proof <= | >) } + PSBT_ELEMENTS_OUT_ASSET_BLINDING_FACTOR => { + impl_pset_prop_insert_pair!(self.asset_blinding_factor <= | ) + } _ => match self.proprietary.entry(prop_key) { Entry::Vacant(empty_key) => { empty_key.insert(raw_value); @@ -488,6 +495,10 @@ impl Map for Output { rv.push_prop(self.blind_asset_proof as ) } + impl_pset_get_pair! { + rv.push_prop(self.asset_blinding_factor as ) + } + for (key, value) in self.proprietary.iter() { rv.push(raw::Pair { key: key.to_key(), @@ -525,6 +536,7 @@ impl Map for Output { merge!(blinder_index, self, other); merge!(blind_value_proof, self, other); merge!(blind_asset_proof, self, other); + merge!(asset_blinding_factor, self, other); Ok(()) } } diff --git a/src/pset/mod.rs b/src/pset/mod.rs index 351202e0..de80556e 100644 --- a/src/pset/mod.rs +++ b/src/pset/mod.rs @@ -1043,4 +1043,53 @@ mod tests { let pset_des = encode::deserialize(&pset_bytes).unwrap(); assert_eq!(pset, pset_des); } + + #[test] + fn pset_abf() { + use std::str::FromStr; + use rand::{self, SeedableRng}; + let secp = secp256k1_zkp::Secp256k1::new(); + #[allow(deprecated)] + let mut rng = rand::rngs::StdRng::seed_from_u64(0); + + let policy = crate::AssetId::from_str("5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225").unwrap(); + let pk = bitcoin::key::PublicKey::from_str("020202020202020202020202020202020202020202020202020202020202020202").unwrap(); + let script = crate::Script::from_hex("0014d2bcde17e7744f6377466ca1bd35d212954674c8").unwrap(); + let sats_in = 10000; + let sats_fee = 1000; + let asset_bf = AssetBlindingFactor::from_str("3311111111111111111111111111111111111111111111111111111111111111").unwrap(); + let btc_txout_secrets = TxOutSecrets { + asset_bf, + value_bf: ValueBlindingFactor::from_str("2222222222222222222222222222222222222222222222222222222222222222").unwrap(), + value: sats_in, + asset: policy, + }; + let previous_output = TxOut::default(); // Does not match btc_txout_secrets + let txid = Txid::from_str("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(); + let prevout = OutPoint::new(txid, 0); + + let mut pset = PartiallySignedTransaction::new_v2(); + let mut input = Input::from_prevout(prevout); + input.witness_utxo = Some(previous_output); + input.asset_blinding_factor = Some(asset_bf); + pset.add_input(input); + + // Add policy + let mut output = Output::new_explicit(script.clone(), sats_in - sats_fee, policy, Some(pk)); + output.blinder_index = Some(0); + pset.add_output(output); + // Add fee + let output = Output::new_explicit(crate::Script::new(), sats_fee, policy, None); + pset.add_output(output); + + let mut inp_txout_sec = HashMap::new(); + inp_txout_sec.insert(0, btc_txout_secrets); + pset.blind_last(&mut rng, &secp, &inp_txout_sec).unwrap(); + let output = &mut pset.outputs_mut()[0]; + // TODO: output the blinding factors and use the correct one + output.asset_blinding_factor = Some(asset_bf); + let pset_bytes = encode::serialize(&pset); + let pset_des = encode::deserialize(&pset_bytes).unwrap(); + assert_eq!(pset, pset_des); + } } diff --git a/src/pset/serialize.rs b/src/pset/serialize.rs index cdc7cf89..0530c85a 100644 --- a/src/pset/serialize.rs +++ b/src/pset/serialize.rs @@ -20,7 +20,7 @@ use std::convert::TryFrom; use std::io; -use crate::confidential; +use crate::confidential::{self, AssetBlindingFactor}; use crate::encode::{self, deserialize, deserialize_partial, serialize, Decodable, Encodable}; use crate::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; use crate::{AssetId, BlockHash, Script, Transaction, TxOut, Txid}; @@ -96,6 +96,19 @@ impl Deserialize for Tweak { } } +impl Serialize for AssetBlindingFactor { + fn serialize(&self) -> Vec { + encode::serialize(self.into_inner().as_ref()) + } +} + +impl Deserialize for AssetBlindingFactor { + fn deserialize(bytes: &[u8]) -> Result { + let x = deserialize::<[u8; 32]>(bytes)?; + AssetBlindingFactor::from_slice(&x).map_err(|_| encode::Error::ParseFailed("invalid AssetBlindingFactor")) + } +} + impl Serialize for Script { fn serialize(&self) -> Vec { self.to_bytes()