Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions examples/coinjoin.rs

Large diffs are not rendered by default.

37 changes: 24 additions & 13 deletions examples/pset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ use elements::{confidential, AssetId, TxOut};
use rand::SeedableRng;
/// Pset example workflow:
/// Simple transaction spending a confidential asset
/// with external signer and an external blinder
/// with external signer and blinding done by rust-elements using raw APIs
/// See also coinjoin example for external blinding example
static PARAMS: AddressParams = AddressParams::ELEMENTS;

// Assume txouts are simple pay to wpkh
Expand All @@ -30,6 +31,10 @@ struct Secrets {
sec: TxOutSecrets,
}

fn deser_pset(psbt_hex: &str) -> Pset {
deserialize::<Pset>(&Vec::<u8>::from_hex(psbt_hex).unwrap()).unwrap()
}

fn parse_txout(txout_info: &str) -> (TxOut, Secrets, pset::Input) {
// Parse the string of data into serde_json::Value.
let v: serde_json::Value = serde_json::from_str(txout_info).unwrap();
Expand Down Expand Up @@ -139,7 +144,7 @@ fn main() {
let (asset_txout, asset_txout_secrets, asset_inp) = txouts[1].clone();

let mut pset = Pset::new_v2();
assert_eq!(serialize_hex(&pset), tests["empty"]);
assert_eq!(pset, deser_pset(&tests["empty"]));

// Add the btc asset input
let mut btc_inp = btc_inp;
Expand All @@ -148,12 +153,12 @@ fn main() {

// pset after adding the information about the bitcoin input
// Pset with 1 input and 0 outputs
assert_eq!(serialize_hex(&pset), tests["one_inp_zero_out"]);
assert_eq!(pset, deser_pset(&tests["one_inp_zero_out"]));
// Add the asset input
let mut asset_inp = asset_inp;
asset_inp.witness_utxo = Some(asset_txout.clone());
pset.add_input(asset_inp);
assert_eq!(serialize_hex(&pset), tests["two_inp_zero_out"]);
assert_eq!(pset, deser_pset(&tests["two_inp_zero_out"]));

// Add outputs
// Send 5_000 worth of asset units to new address
Expand Down Expand Up @@ -206,10 +211,16 @@ fn main() {
.expect("Asset Change txOut creation failure");

// Add both assets to pset. 5_000 dest address, 15_000 change address
pset.add_output(pset::Output::from_txout(dest_asset_txout));
pset.add_output(pset::Output::from_txout(change_asset_txout));
pset.add_output(pset::Output::from_blinded_txout(dest_asset_txout));
pset.add_output(pset::Output::from_blinded_txout(change_asset_txout));

assert_eq!(serialize_hex(&pset), tests["two_inp_two_out"]);
// Add information about which input index blinded the outputs
// Spec mandates that blinded inputs must have this information
// pset.outputs[0].blinding_key = Some(dest_blind_pk);
// pset.outputs[1].blinding_key = Some(change_blind_pk);
// pset.outputs[0].blinder_index = Some(0);
// pset.outputs[1].blinder_index = Some(0);
assert_eq!(pset, deser_pset(&tests["two_inp_two_out"]));

// Add two more outputs: btc change amount and btc fees
let btc_fees_amt = 500; // sat
Expand Down Expand Up @@ -260,10 +271,10 @@ fn main() {
)
.expect("Asset Change txOut creation failure");
// Add both pset outputs to btc transaction
pset.add_output(pset::Output::from_txout(btc_fees_txout));
pset.add_output(pset::Output::from_txout(btc_change_txout));
pset.add_output(pset::Output::from_blinded_txout(btc_fees_txout));
pset.add_output(pset::Output::from_blinded_txout(btc_change_txout));

assert_eq!(serialize_hex(&pset), tests["blinded_unsigned"]);
assert_eq!(pset, deser_pset(&tests["blinded_unsigned"]));

// Verify the balance checks
let tx = pset.extract_tx().unwrap();
Expand All @@ -287,12 +298,12 @@ fn main() {
pset.inputs[0]
.partial_sigs
.insert(inp0_pk, inp0_sig.clone());
assert_eq!(serialize_hex(&pset), tests["blinded_one_inp_signed"]);
assert_eq!(pset, deser_pset(&tests["blinded_one_inp_signed"]));
// Input one adds signatures
pset.inputs[1]
.partial_sigs
.insert(inp1_pk, inp1_sig.clone());
assert_eq!(serialize_hex(&pset), tests["blinded_signed"]);
assert_eq!(pset, deser_pset(&tests["blinded_signed"]));

// Finalize(TODO in miniscript)
pset.inputs[0].partial_sigs.clear();
Expand All @@ -305,7 +316,7 @@ fn main() {
inp1_sig,
inp1_pk.to_bytes(),
]);
assert_eq!(serialize_hex(&pset), tests["finalized"]);
assert_eq!(pset, deser_pset(&tests["finalized"]));

// Extracted tx
let tx = pset.extract_tx().unwrap();
Expand Down
11 changes: 9 additions & 2 deletions src/pset/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ pub enum Error {
MissingOutputAsset,
/// Missing output Script Pubkey
MissingOutputSpk,
/// Blinded Output requires Blinded index
MissingBlinderIndex,
/// Output marked for blinding, but missing blinding information
MissingBlindingInfo,
/// Input Count Mismatch
InputCountMismatch,
/// Output Count Mismatch
Expand Down Expand Up @@ -148,9 +152,12 @@ impl fmt::Display for Error {
write!(f, "PSET blinding scalars must be 32 bytes. Found {} bytes", actual)
}
Error::MissingOutputValue => f.write_str("PSET output missing value. Must have \
exactly one of explicit/confidential value set"),
at least one of explicit/confidential value set"),
Error::MissingOutputAsset => f.write_str("PSET output missing asset. Must have \
exactly one of explicit/confidential asset set"),
at least one of explicit/confidential asset set"),
Error::MissingBlinderIndex => f.write_str("Output is blinded but does not have a blinder index"),
Error::MissingBlindingInfo => f.write_str("Output marked for blinding, but missing \
some blinding information"),
Error::MissingOutputSpk => f.write_str("PSET output missing script pubkey. Must have \
exactly one of explicit/confidential script pubkey set"),
Error::InputCountMismatch => f.write_str("PSET input count global field must \
Expand Down
2 changes: 1 addition & 1 deletion src/pset/map/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ impl Map for Global {
let key = raw::ProprietaryKey::from_pset_pair(PSBT_ELEMENTS_GLOBAL_SCALAR, scalar.as_ref().to_vec());
rv.push(raw::Pair {
key: key.to_key(),
value: vec![],
value: vec![], // This is a bug in elements core c++, parses this value as vec![0]
})
}

Expand Down
110 changes: 76 additions & 34 deletions src/pset/map/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ const PSBT_ELEMENTS_IN_ISSUANCE_ASSET_ENTROPY: u8 = 0x0d;
/// However the rangeproof is needed in order for stateless blinders to learn
/// the blinding factors for the UTXOs that they are involved in.
const PSBT_ELEMENTS_IN_UTXO_RANGEPROOF: u8 = 0x0e;
/// An explicit value rangeproof that proves that the value commitment in
/// PSBT_ELEMENTS_IN_ISSUANCE_VALUE_COMMITMENT matches the explicit value in
/// PSBT_ELEMENTS_IN_ISSUANCE_VALUE. If provided, PSBT_ELEMENTS_IN_ISSUANCE_VALUE_COMMITMENT
/// must be provided too.
const PSBT_ELEMENTS_IN_ISSUANCE_BLIND_VALUE_PROOF: u8 = 0x0f;
/// An explicit value rangeproof that proves that the value commitment in
/// PSBT_ELEMENTS_IN_ISSUANCE_INFLATION_KEYS_COMMITMENT matches the explicit value
/// in PSBT_ELEMENTS_IN_ISSUANCE_INFLATION_KEYS. If provided,
/// PSBT_ELEMENTS_IN_ISSUANCE_INFLATION_KEYS_COMMITMENT must be provided too.
const PSBT_ELEMENTS_IN_ISSUANCE_BLIND_INFLATION_KEYS_PROOF: u8 = 0x10;
/// A key-value map for an input of the corresponding index in the unsigned
/// transaction.
#[derive(Clone, Default, Debug, PartialEq)]
Expand Down Expand Up @@ -176,7 +186,9 @@ pub struct Input {
pub required_height_locktime: Option<u32>,
// Proprietary key-value pairs for this input.
/// The issuance value
pub issuance_value: Option<confidential::Value>,
pub issuance_value_amount: Option<u64>,
/// The issuance value commitment
pub issuance_value_comm: Option<secp256k1_zkp::PedersenCommitment>,
/// Issuance value rangeproof
pub issuance_value_rangeproof: Option<RangeProof>,
/// Issuance keys rangeproof
Expand All @@ -195,13 +207,19 @@ pub struct Input {
/// Pegin Witness
pub pegin_witness: Option<Vec<Vec<u8>>>,
/// Issuance inflation keys
pub issuance_inflation_keys: Option<confidential::Value>,
pub issuance_inflation_keys: Option<u64>,
/// Issuance inflation keys commitment
pub issuance_inflation_keys_comm: Option<secp256k1_zkp::PedersenCommitment>,
/// Issuance blinding nonce
pub issuance_blinding_nonce: Option<Tweak>,
/// Issuance asset entropy
pub issuance_asset_entropy: Option<[u8; 32]>,
/// input utxo rangeproof
pub in_utxo_rangeproof: Option<RangeProof>,
/// Proof that blinded issuance matches the commitment
pub in_issuance_blind_value_proof: Option<RangeProof>,
/// Proof that blinded inflation keys matches the corresponding commitment
pub in_issuance_blind_inflation_keys_proof: Option<RangeProof>,
/// Other fields
#[cfg_attr(feature = "serde", serde(with = "::serde_utils::btreemap_as_seq_byte_values"))]
pub proprietary: BTreeMap<raw::ProprietaryKey, Vec<u8>>,
Expand Down Expand Up @@ -236,8 +254,17 @@ impl Input{
ret.previous_output_index |= 1 << 31;
ret.issuance_blinding_nonce = Some(txin.asset_issuance.asset_blinding_nonce);
ret.issuance_asset_entropy = Some(txin.asset_issuance.asset_entropy);
ret.issuance_value = Some(txin.asset_issuance.amount);
ret.issuance_inflation_keys = Some(txin.asset_issuance.inflation_keys);
match txin.asset_issuance.amount {
confidential::Value::Null => { },
confidential::Value::Explicit(x) => ret.issuance_value_amount = Some(x),
confidential::Value::Confidential(comm) => ret.issuance_value_comm = Some(comm),
}
match txin.asset_issuance.inflation_keys {
confidential::Value::Null => { },
confidential::Value::Explicit(x) => ret.issuance_inflation_keys = Some(x),
confidential::Value::Confidential(comm) =>
ret.issuance_inflation_keys_comm = Some(comm),
}

// Witness
ret.issuance_keys_rangeproof = txin.witness.inflation_keys_rangeproof;
Expand All @@ -262,8 +289,16 @@ impl Input{
asset_blinding_nonce: *self.issuance_blinding_nonce.as_ref()
.unwrap_or(&ZERO_TWEAK),
asset_entropy: self.issuance_asset_entropy.unwrap_or_default(),
amount: self.issuance_value.unwrap_or_default(),
inflation_keys: self.issuance_inflation_keys.unwrap_or_default(),
amount: match (self.issuance_value_amount, self.issuance_value_comm) {
(None, None) => confidential::Value::Null,
(_, Some(comm)) => confidential::Value::Confidential(comm),
(Some(x), None) => confidential::Value::Explicit(x),
},
inflation_keys: match (self.issuance_inflation_keys, self.issuance_inflation_keys_comm) {
(None, None) => confidential::Value::Null,
(_, Some(comm)) => confidential::Value::Confidential(comm),
(Some(x), None) => confidential::Value::Explicit(x),
},
}
}
}
Expand Down Expand Up @@ -356,10 +391,10 @@ impl Map for Input {
if prop_key.is_pset_key() {
match prop_key.subtype {
PSBT_ELEMENTS_IN_ISSUANCE_VALUE => {
impl_pset_prop_insert_pair!(self.issuance_value <= <raw_key: _> | <raw_value : confidential::Value>)
impl_pset_prop_insert_pair!(self.issuance_value_amount <= <raw_key: _> | <raw_value : u64>)
}
PSBT_ELEMENTS_IN_ISSUANCE_VALUE_COMMITMENT => {
impl_pset_prop_insert_pair!(self.issuance_value <= <raw_key: _> | <raw_value : confidential::Value>)
impl_pset_prop_insert_pair!(self.issuance_value_comm <= <raw_key: _> | <raw_value : secp256k1_zkp::PedersenCommitment>)
}
PSBT_ELEMENTS_IN_ISSUANCE_VALUE_RANGEPROOF => {
impl_pset_prop_insert_pair!(self.issuance_value_rangeproof <= <raw_key: _> | <raw_value : RangeProof>)
Expand Down Expand Up @@ -387,10 +422,10 @@ impl Map for Input {
impl_pset_prop_insert_pair!(self.pegin_witness <= <raw_key: _> | <raw_value : Vec<Vec<u8>>>)
}
PSBT_ELEMENTS_IN_ISSUANCE_INFLATION_KEYS => {
impl_pset_prop_insert_pair!(self.issuance_inflation_keys <= <raw_key: _> | <raw_value : confidential::Value>)
impl_pset_prop_insert_pair!(self.issuance_inflation_keys <= <raw_key: _> | <raw_value : u64>)
}
PSBT_ELEMENTS_IN_ISSUANCE_INFLATION_KEYS_COMMITMENT => {
impl_pset_prop_insert_pair!(self.issuance_inflation_keys <= <raw_key: _> | <raw_value : confidential::Value>)
impl_pset_prop_insert_pair!(self.issuance_inflation_keys_comm <= <raw_key: _> | <raw_value : secp256k1_zkp::PedersenCommitment>)
}
PSBT_ELEMENTS_IN_ISSUANCE_BLINDING_NONCE => {
impl_pset_prop_insert_pair!(self.issuance_blinding_nonce <= <raw_key: _> | <raw_value : Tweak>)
Expand All @@ -401,6 +436,12 @@ impl Map for Input {
PSBT_ELEMENTS_IN_UTXO_RANGEPROOF => {
impl_pset_prop_insert_pair!(self.in_utxo_rangeproof <= <raw_key: _> | <raw_value : RangeProof>)
}
PSBT_ELEMENTS_IN_ISSUANCE_BLIND_VALUE_PROOF => {
impl_pset_prop_insert_pair!(self.in_issuance_blind_value_proof <= <raw_key: _> | <raw_value : RangeProof>)
}
PSBT_ELEMENTS_IN_ISSUANCE_BLIND_INFLATION_KEYS_PROOF => {
impl_pset_prop_insert_pair!(self.in_issuance_blind_inflation_keys_proof <= <raw_key: _> | <raw_value : RangeProof>)
}
_ => match self.proprietary.entry(prop_key) {
Entry::Vacant(empty_key) => {
empty_key.insert(raw_value);
Expand Down Expand Up @@ -502,18 +543,12 @@ impl Map for Input {
rv.push(self.required_height_locktime as <PSET_IN_REQUIRED_HEIGHT_LOCKTIME, _>)
}

// Elements Prop feilds
if let Some(v) = self.issuance_value {
match v.encoded_length() {
9 => impl_pset_get_pair! {
rv.push_prop(self.issuance_value as <PSBT_ELEMENTS_IN_ISSUANCE_VALUE, _>)
},
33 => impl_pset_get_pair! {
rv.push_prop(self.issuance_value as <PSBT_ELEMENTS_IN_ISSUANCE_VALUE_COMMITMENT, _>)
},
1 => {},
_ => unreachable!()
}
impl_pset_get_pair! {
rv.push_prop(self.issuance_value_amount as <PSBT_ELEMENTS_IN_ISSUANCE_VALUE, _>)
}

impl_pset_get_pair! {
rv.push_prop(self.issuance_value_comm as <PSBT_ELEMENTS_IN_ISSUANCE_VALUE_COMMITMENT, _>)
}

impl_pset_get_pair! {
Expand Down Expand Up @@ -548,17 +583,12 @@ impl Map for Input {
rv.push_prop(self.pegin_witness as <PSBT_ELEMENTS_IN_PEG_IN_WITNESS, _>)
}

if let Some(v) = self.issuance_inflation_keys {
match v.encoded_length() {
9 => impl_pset_get_pair! {
rv.push_prop(self.issuance_inflation_keys as <PSBT_ELEMENTS_IN_ISSUANCE_INFLATION_KEYS, _>)
},
33 => impl_pset_get_pair! {
rv.push_prop(self.issuance_inflation_keys as <PSBT_ELEMENTS_IN_ISSUANCE_INFLATION_KEYS_COMMITMENT, _>)
},
1 => {},
_ => unreachable!()
}
impl_pset_get_pair! {
rv.push_prop(self.issuance_inflation_keys as <PSBT_ELEMENTS_IN_ISSUANCE_INFLATION_KEYS, _>)
}

impl_pset_get_pair! {
rv.push_prop(self.issuance_inflation_keys_comm as <PSBT_ELEMENTS_IN_ISSUANCE_INFLATION_KEYS_COMMITMENT, _>)
}

impl_pset_get_pair! {
Expand All @@ -573,6 +603,14 @@ impl Map for Input {
rv.push_prop(self.in_utxo_rangeproof as <PSBT_ELEMENTS_IN_UTXO_RANGEPROOF, _>)
}

impl_pset_get_pair! {
rv.push_prop(self.in_issuance_blind_value_proof as <PSBT_ELEMENTS_IN_ISSUANCE_BLIND_VALUE_PROOF, _>)
}

impl_pset_get_pair! {
rv.push_prop(self.in_issuance_blind_inflation_keys_proof as <PSBT_ELEMENTS_IN_ISSUANCE_BLIND_INFLATION_KEYS_PROOF, _>)
}

for (key, value) in self.proprietary.iter() {
rv.push(raw::Pair {
key: key.to_key(),
Expand Down Expand Up @@ -620,7 +658,8 @@ impl Map for Input {
self.required_height_locktime = cmp::max(self.required_height_locktime, other.required_height_locktime);

// elements
merge!(issuance_value, self, other);
merge!(issuance_value_amount, self, other);
merge!(issuance_value_comm, self, other);
merge!(issuance_value_rangeproof, self, other);
merge!(issuance_keys_rangeproof, self, other);
merge!(pegin_tx, self, other);
Expand All @@ -630,9 +669,12 @@ impl Map for Input {
merge!(pegin_value, self, other);
merge!(pegin_witness, self, other);
merge!(issuance_inflation_keys, self, other);
merge!(issuance_inflation_keys_comm, self, other);
merge!(issuance_blinding_nonce, self, other);
merge!(issuance_asset_entropy, self, other);
merge!(in_utxo_rangeproof, self, other);
merge!(in_issuance_blind_value_proof, self, other);
merge!(in_issuance_blind_inflation_keys_proof, self, other);
Ok(())
}
}
Expand Down
Loading