elements 0.26.1

Library with support for de/serialization, parsing and executing on data structures and network messages related to Elements
Documentation
//! PSET coinjoin example
//! 1. Person `A` create a transaction with 1 input and 3 outputs(1 fee output)
//! 2. Person `B` takes the transaction from A and adds one input and two outputs
//!    which transact another confidential asset
//! 3. Person `B` blinds it's own outputs and gives the pset back to A
//! 4. B completely blinds the transaction
//! 5. B signs the blinded Transaction and sends it back to A
//! 6. A signs it's input
//! 7. A finalizes the pset
//! 8. A extracts and broadcasts the transaction
//!
//! During the entire interaction, the output blinding factors for A and B are not
//! shared with each other.
extern crate bitcoin;
extern crate elements;
extern crate rand;
extern crate serde_json;

use std::{collections::HashMap, str::FromStr};

use bitcoin::PublicKey;
use elements::confidential::{AssetBlindingFactor, ValueBlindingFactor};
use elements::{
    pset::PartiallySignedTransaction as Pset, OutPoint,
    Script, TxOutSecrets, TxOutWitness, Txid, WScriptHash,
};
use elements::{pset, secp256k1_zkp};

use elements::encode::{deserialize, serialize_hex};
use elements::{confidential, AssetId, TxOut};
use elements::hex::FromHex;
use rand::SeedableRng;

// Assume txouts are simple pay to wpkh
// and keep the secrets corresponding to
// confidential txouts
#[derive(Debug, Clone)]
struct Secrets {
    _sk: bitcoin::PrivateKey,
    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();

    let txout = TxOut {
        asset: deserialize::<confidential::Asset>(
            &Vec::<u8>::from_hex(v["assetcommitment"].as_str().unwrap()).unwrap(),
        )
        .unwrap(),
        value: deserialize::<confidential::Value>(
            &Vec::<u8>::from_hex(v["amountcommitment"].as_str().unwrap()).unwrap(),
        )
        .unwrap(),
        nonce: deserialize::<confidential::Nonce>(
            &Vec::<u8>::from_hex(v["commitmentnonce"].as_str().unwrap()).unwrap(),
        )
        .unwrap(),
        script_pubkey: Script::from_hex(v["scriptPubKey"].as_str().unwrap()).unwrap(),
        witness: TxOutWitness::default(),
    };

    let txoutsecrets = Secrets {
        _sk: bitcoin::PrivateKey::from_wif(v["skwif"].as_str().unwrap()).unwrap(),
        sec: TxOutSecrets {
            asset_bf: AssetBlindingFactor::from_str(v["assetblinder"].as_str().unwrap()).unwrap(),
            value_bf: ValueBlindingFactor::from_str(v["amountblinder"].as_str().unwrap()).unwrap(),
            value: bitcoin::Amount::from_str_in(
                v["amount"].as_str().unwrap(),
                bitcoin::Denomination::Bitcoin,
            )
            .unwrap()
            .to_sat(),
            asset: AssetId::from_str(v["asset"].as_str().unwrap()).unwrap(),
        },
    };

    let inp = pset::Input::from_prevout(OutPoint::new(
        Txid::from_str(v["txid"].as_str().unwrap()).unwrap(),
        v["vout"].as_u64().unwrap() as u32,
    ));

    (txout, txoutsecrets, inp)
}

fn txout_data() -> [(TxOut, Secrets, pset::Input); 2] {
    // Some JSON input data as a &str. Maybe this comes from the user.
    let asset_txout = r#"
    {
        "txid": "55855ab698631f8dc9c11aaa299fbd62f05869b082ecb14395372a6c6e95ff7a",
        "vout": 1,
        "scriptPubKey": "0014011d384302576b408aa3686db874e2b17cc2b01b",
        "amount": "10.00000000",
        "assetcommitment": "0ac55f449ddb6853f2508766d5afb9f3b45e41a8ef5368cad75fb88e5e249395d1",
        "asset": "4fa41f2929d4bf6975a55967d9da5b650b6b9bfddeae4d7b54b04394be328f7f",
        "amountcommitment": "097d88c92ca814a207f73441c56cee943f0bb2556da194c14a4b912b078c2238ae",
        "amountblinder": "bcfa96f8068c91cf7b80c197066d1fc0d756606bf6666c9f78e120a653b7d13e",
        "assetblinder": "f4ba5cf033c0557bbaab295a057a87c943f3639a05a62bef37f29dc18aa45886",
        "commitmentnonce": "025341cb5e4e2d8cb69e694cb20e5ea4cc8ddf2801180096fd071addfcd8bc4445",
        "skwif": "cU52mfNAru457o7DQmmb1TpkNasXmg63QLPH1F94LEZSzJe2uK3V"
    }"#;

    let btc_txout = r#"
    {
        "txid": "70478b6898407362d43e9e56fd72a89b0556ac2593ed6e025c16376bba315180",
        "vout": 0,
        "scriptPubKey": "0014d2cbec8783bd01c9f178348b08500a830a89a7f9",
        "amount": "2.30000000",
        "assetcommitment": "0bb9325c276764451bbc2eb82a4c8c4bb6f4007ba803e5a5ba72d0cd7c09848e1a",
        "asset": "b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23",
        "amountcommitment": "091622d935953bf06e0b7393239c68c6f810a00fe19d11c6ae343cffd3037077da",
        "amountblinder": "0f155ac96c49e39c0501e3448e9aac89f5b43c16bf9156e6c1694e310c80f374",
        "assetblinder": "de6ecd62ab6fc66597b2144f38c3be873ba583970aacdfcc8978a1a0b6cb872c",
        "commitmentnonce": "02535fe4ad0fcd675cd0f62bf73b60a554dc1569b80f1f76a2bbfc9f00d439bf4b",
        "skwif": "cRrq6NyygXvNBHW7ozK6b33F1qZqbNbKTVtTSQph947jgPKN8WCH"
      }"#;

    [parse_txout(btc_txout), parse_txout(asset_txout)]
}

fn test_data() -> HashMap<String, String> {
    let mut tests = HashMap::new();
    tests.insert(
        String::from("empty"),
        String::from("70736574ff01020402000000010401000105010001fb040200000000"),
    );
    tests.insert(String::from("A_pset_unblinded") , String::from("70736574ff01020402000000010401010105010301fb04020000000001017a0bb9325c276764451bbc2eb82a4c8c4bb6f4007ba803e5a5ba72d0cd7c09848e1a091622d935953bf06e0b7393239c68c6f810a00fe19d11c6ae343cffd3037077da02535fe4ad0fcd675cd0f62bf73b60a554dc1569b80f1f76a2bbfc9f00d439bf4b160014d2cbec8783bd01c9f178348b08500a830a89a7f9010e20805131ba6b37165c026eed9325ac56059ba872fd569e3ed462734098688b4770010f04000000000001030820a107000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104220020e5793ad956ee91ebf3543b37d110701118ed4078ffa0d477eacb8885e486ad8507fc047073657406210212bf0ea45b733dfde8ecb5e896306c4165c666c99fc5d1ab887f71393a975cea07fc0470736574080400000000000103086ce2ad0d0000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104220020f6b43d56e004e9d0b1ec2fc3c95511d81af08420992be8dec7f86cdf8970b3e207fc04707365740621027d07ae478c0aa607321643cb5e8ed59ee1f5ff4d9d55efedec066ccb1f5d537d07fc047073657408040000000000010308f40100000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b201040000"));
    tests.insert(String::from("pset_coinjoined_unblinded") , String::from("70736574ff01020402000000010401020105010501fb04020000000001017a0bb9325c276764451bbc2eb82a4c8c4bb6f4007ba803e5a5ba72d0cd7c09848e1a091622d935953bf06e0b7393239c68c6f810a00fe19d11c6ae343cffd3037077da02535fe4ad0fcd675cd0f62bf73b60a554dc1569b80f1f76a2bbfc9f00d439bf4b160014d2cbec8783bd01c9f178348b08500a830a89a7f9010e20805131ba6b37165c026eed9325ac56059ba872fd569e3ed462734098688b4770010f04000000000001017a0ac55f449ddb6853f2508766d5afb9f3b45e41a8ef5368cad75fb88e5e249395d1097d88c92ca814a207f73441c56cee943f0bb2556da194c14a4b912b078c2238ae025341cb5e4e2d8cb69e694cb20e5ea4cc8ddf2801180096fd071addfcd8bc4445160014011d384302576b408aa3686db874e2b17cc2b01b010e207aff956e6c2a379543b1ec82b06958f062bd9f29aa1ac1c98d1f6398b65a8555010f04010000000001030820a107000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104220020e5793ad956ee91ebf3543b37d110701118ed4078ffa0d477eacb8885e486ad8507fc047073657406210212bf0ea45b733dfde8ecb5e896306c4165c666c99fc5d1ab887f71393a975cea07fc0470736574080400000000000103086ce2ad0d0000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104220020f6b43d56e004e9d0b1ec2fc3c95511d81af08420992be8dec7f86cdf8970b3e207fc04707365740621027d07ae478c0aa607321643cb5e8ed59ee1f5ff4d9d55efedec066ccb1f5d537d07fc047073657408040000000000010308f40100000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104000001030840420f000000000007fc047073657402207f8f32be9443b0547b4daedefd9b6b0b655bdad96759a57569bfd429291fa44f010422002037831b3ee29fc96f8e61ccb98fbe2dcb03e189dd29cfecc691b5a7442d8548e807fc0470736574062103d559d2a5a4180f418a69c4bed5508971cda9313722fff71e053d3d82fee9d7bd07fc047073657408040100000000010308c0878b3b0000000007fc047073657402207f8f32be9443b0547b4daedefd9b6b0b655bdad96759a57569bfd429291fa44f0104220020e7da55d19cc85b0420c539a90b667d4d85f59ee0ed417493a947c3a2256cc0aa07fc04707365740621029e5980b4f9b9a9fd568c1c4b48631a800c310405ae8b2ac41ddaf87add3062f107fc047073657408040100000000"));
    tests.insert(String::from("pset_coinjoined_B_blinded") , include_str!("test_vector/pset_blind_coinjoin/pset_coinjoined_B_blinded.hex").to_string());
    tests.insert(String::from("pset_coinjoined_blinded") , include_str!("test_vector/pset_blind_coinjoin/pset_coinjoined_blinded.hex").to_string());
    tests
}

fn main() {
    let tests = test_data();
    // Initially secp context and rng global state
    let secp = secp256k1_zkp::Secp256k1::new();

    // NOTE: Zero is not a reasonable seed for production code.
    // It is used here so that we can match test vectors.
    let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]);

    let txouts = txout_data();
    let (btc_txout, btc_txout_secrets, btc_inp) = txouts[0].clone();
    let (asset_txout, asset_txout_secrets, asset_inp) = txouts[1].clone();

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

    // Add the btc asset input
    let mut btc_inp = btc_inp;
    btc_inp.witness_utxo = Some(btc_txout.clone());
    pset.add_input(btc_inp);

    // Create the first txout
    let dest_btc_wsh =
        WScriptHash::from_str("e5793ad956ee91ebf3543b37d110701118ed4078ffa0d477eacb8885e486ad85")
            .unwrap();
    let dest_btc_amt = 500_000; // sat
    let dest_btc_blind_pk =
        PublicKey::from_str("0212bf0ea45b733dfde8ecb5e896306c4165c666c99fc5d1ab887f71393a975cea")
            .unwrap();
    let dest_btc_txout = TxOut {
        asset: confidential::Asset::Explicit(btc_txout_secrets.sec.asset),
        value: confidential::Value::Explicit(dest_btc_amt),
        nonce: confidential::Nonce::Confidential(dest_btc_blind_pk.inner),
        script_pubkey: Script::new_v0_wsh(&dest_btc_wsh),
        witness: TxOutWitness::default(),
    };

    // Create the change txout
    let btc_fees_amt = 500; // sat
    let change_amt = btc_txout_secrets.sec.value - dest_btc_amt - btc_fees_amt;
    let change_btc_blind_pk =
        PublicKey::from_str("027d07ae478c0aa607321643cb5e8ed59ee1f5ff4d9d55efedec066ccb1f5d537d")
            .unwrap();
    let change_btc_wsh =
        WScriptHash::from_str("f6b43d56e004e9d0b1ec2fc3c95511d81af08420992be8dec7f86cdf8970b3e2")
            .unwrap();
    let change_btc_txout = TxOut {
        asset: confidential::Asset::Explicit(btc_txout_secrets.sec.asset),
        value: confidential::Value::Explicit(change_amt),
        nonce: confidential::Nonce::Confidential(change_btc_blind_pk.inner),
        script_pubkey: Script::new_v0_wsh(&change_btc_wsh),
        witness: TxOutWitness::default(),
    };

    // Create the fee txout
    let btc_fees_txout = TxOut::new_fee(btc_fees_amt, btc_txout_secrets.sec.asset);

    pset.add_output(pset::Output::from_txout(dest_btc_txout));
    pset.add_output(pset::Output::from_txout(change_btc_txout));
    pset.add_output(pset::Output::from_txout(btc_fees_txout));

    // Mark owned outputs for blinding later
    // This tells that person that controls input zero is responsible for
    // blinding outputs 0, 1
    // Output 2 is the fees output
    pset.outputs_mut()[0].blinding_key = Some(dest_btc_blind_pk);
    pset.outputs_mut()[0].blinder_index = Some(0);
    pset.outputs_mut()[1].blinding_key = Some(change_btc_blind_pk);
    pset.outputs_mut()[1].blinder_index = Some(0);

    // pset after adding the information about the bitcoin input from A
    // Pset with 2 input and 3 outputs
    assert_eq!(pset, deser_pset(&tests["A_pset_unblinded"]));
    // ----------------------------------------------------------
    // Party A sends unblinded pset to B. Step 1 completed

    // Add the asset input
    let mut asset_inp = asset_inp;
    asset_inp.witness_utxo = Some(asset_txout.clone());
    pset.add_input(asset_inp);

    // Add outputs
    // Send 5_000 worth of asset units to new address
    // Create the first asset txout(fourth output)
    let dest_asset_wsh =
        WScriptHash::from_str("37831b3ee29fc96f8e61ccb98fbe2dcb03e189dd29cfecc691b5a7442d8548e8")
            .unwrap();
    let dest_asset_amt = 1_000_000; // sat
    let dest_asset_blind_pk =
        PublicKey::from_str("03d559d2a5a4180f418a69c4bed5508971cda9313722fff71e053d3d82fee9d7bd")
            .unwrap();
    let dest_asset_txout = TxOut {
        asset: confidential::Asset::Explicit(asset_txout_secrets.sec.asset),
        value: confidential::Value::Explicit(dest_asset_amt),
        nonce: confidential::Nonce::Confidential(dest_asset_blind_pk.inner),
        script_pubkey: Script::new_v0_wsh(&dest_asset_wsh),
        witness: TxOutWitness::default(),
    };

    // Create the change txout
    let change_asset_amt = asset_txout_secrets.sec.value - dest_asset_amt;
    let change_asset_blind_pk =
        PublicKey::from_str("029e5980b4f9b9a9fd568c1c4b48631a800c310405ae8b2ac41ddaf87add3062f1")
            .unwrap();
    let change_asset_wsh =
        WScriptHash::from_str("e7da55d19cc85b0420c539a90b667d4d85f59ee0ed417493a947c3a2256cc0aa")
            .unwrap();
    let change_asset_txout = TxOut {
        asset: confidential::Asset::Explicit(asset_txout_secrets.sec.asset),
        value: confidential::Value::Explicit(change_asset_amt),
        nonce: confidential::Nonce::Confidential(change_asset_blind_pk.inner),
        script_pubkey: Script::new_v0_wsh(&change_asset_wsh),
        witness: TxOutWitness::default(),
    };

    // Add the outputs
    pset.add_output(pset::Output::from_txout(dest_asset_txout));
    pset.add_output(pset::Output::from_txout(change_asset_txout));

    // This tells that person that controls input index one is responsible for
    // blinding outputs 3, 4.
    pset.outputs_mut()[3].blinding_key = Some(dest_asset_blind_pk);
    pset.outputs_mut()[3].blinder_index = Some(1);
    pset.outputs_mut()[4].blinding_key = Some(change_asset_blind_pk);
    pset.outputs_mut()[4].blinder_index = Some(1);

    // pset after adding the information about the bitcoin input from A
    // and adding B's input. Two inputs and 5 outputs
    assert_eq!(pset, deser_pset(&tests["pset_coinjoined_unblinded"]));
    // ----------------------------------------------------------
    // B Adds it's own outputs. Step 2 completed
    // ----- Step 3: B to blind it's own outputs
    let mut inp_txout_sec = HashMap::new();
    inp_txout_sec.insert(1, asset_txout_secrets.sec);

    pset.blind_non_last(&mut rng, &secp, &inp_txout_sec).unwrap();
    assert_eq!(pset, deser_pset(&tests["pset_coinjoined_B_blinded"]));

    // Step 4: A blinds it's own inputs
    let mut inp_txout_sec_a = HashMap::new();
    inp_txout_sec_a.insert(0, btc_txout_secrets.sec);
    pset.blind_last(&mut rng, &secp, &inp_txout_sec_a).unwrap();
    assert_eq!(pset, deser_pset(&tests["pset_coinjoined_blinded"]));

    // check whether the blinding was correct
    // Verify the balance checks
    let tx = pset.extract_tx().unwrap();
    // println!("{}", serialize_hex(&tx));
    tx.verify_tx_amt_proofs(&secp, &[btc_txout, asset_txout])
        .unwrap();
}