bip352 0.1.0-alpha.3

Implementation of BIP 352 Silent Payments
Documentation
use bitcoin::absolute::LockTime;
use bitcoin::consensus::deserialize;
use bitcoin::hashes::hex::FromHex;
use bitcoin::hashes::{sha256, Hash};
use bitcoin::key::{TapTweak, TweakedPublicKey};
use bitcoin::secp256k1::{All, Message, Secp256k1, SecretKey, XOnlyPublicKey};
use bitcoin::transaction::Version;
use bitcoin::{Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid};
use serde::Deserialize;
use std::fs::File;
use std::io::BufReader;
use std::str::FromStr;

#[test]
#[cfg(any(feature = "receive", feature = "send", feature = "spend"))]
fn bip352_test_vector() {
    let f = File::open("tests/data/send_and_receive_test_vectors.json").unwrap();
    let reader = BufReader::new(f);

    let secp = bitcoin::secp256k1::Secp256k1::new();

    serde_json::from_reader::<_, Vec<Test>>(reader)
        .unwrap()
        .iter()
        .for_each(|t| {
            #[cfg(feature = "send")]
            test_sending(&t.sending, &t.comment, &secp);
            #[cfg(feature = "receive")]
            test_receiving(&t.receiving, &t.comment, &secp);
        });
}

#[cfg(feature = "receive")]
fn test_receiving(receiving: &[Receiving], test: &str, secp: &Secp256k1<All>) {
    use std::collections::HashSet;

    use bip352::{label::LabelIndex, receive::Receive, ScanSecretKey, SpendPublicKey};

    receiving.iter().for_each(|r| {
        let spend_key =
            SecretKey::from_slice(&Vec::from_hex(&r.given.key_material.spend_priv_key).unwrap())
                .unwrap();

        let receive = Receive::new(
            ScanSecretKey::new(
                SecretKey::from_slice(&Vec::from_hex(&r.given.key_material.scan_priv_key).unwrap())
                    .unwrap(),
            ),
            SpendPublicKey::new(spend_key.public_key(secp)),
            r.given
                .labels
                .iter()
                .map(|l| LabelIndex::try_from(l).unwrap())
                .collect(),
        );

        let expected_addresses: HashSet<String> = r.expected.addresses.iter().cloned().collect();
        let given_addresses: HashSet<String> = receive
            .addresses()
            .iter()
            .map(|spa| spa.to_bech32(false))
            .collect();
        assert_eq!(given_addresses, expected_addresses, "{test}");

        let prevouts = r
            .given
            .vin
            .iter()
            .map(|vin| {
                (
                    vin.out_point(),
                    TxOut {
                        value: Amount::from_sat(123),
                        script_pubkey: vin.prevout.script_pub_key.hex.clone(),
                    },
                )
            })
            .collect();

        let silent_payment_outputs = receive.scan_transaction(&prevouts, &r.given.to_tx());

        let calculated_outputs = silent_payment_outputs
            .iter()
            .map(|o| o.public_key())
            .collect::<HashSet<_>>();

        let expected_outputs = r
            .expected
            .outputs
            .iter()
            .map(|o| XOnlyPublicKey::from_str(&o.pub_key).unwrap())
            .collect();

        assert_eq!(
            calculated_outputs.len(),
            r.expected.n_outputs,
            "Found different number of outputs than expected in `{test}`."
        );

        assert!(
            calculated_outputs.is_subset(&expected_outputs),
            "Found different outputs than expected in `{test}`."
        );

        #[cfg(feature = "spend")]
        test_spending(r, &silent_payment_outputs, test, secp);
    });
}

#[cfg(feature = "send")]
fn test_sending(sending: &[Sending], test: &str, secp: &Secp256k1<All>) {
    use std::collections::HashSet;

    use bip352::send::SilentPayment;

    sending.iter().for_each(|s| {
        let mut payment = SilentPayment::new(secp);
        s.given.recipients.iter().for_each(|addr| {
            payment.add_recipient(addr.parse().unwrap());
        });
        s.given.vin.iter().for_each(|vin| {
            payment.add_outpoint(vin.to_txin().previous_output);
            if let Some(_pk) =
                bip352::input_public_key(&vin.prevout.script_pub_key.hex, &vin.to_txin())
            {
                let key = SecretKey::from_slice(
                    &Vec::from_hex(&vin.private_key.clone().unwrap()).unwrap(),
                )
                .unwrap();

                if vin.prevout.script_pub_key.hex.is_p2tr() {
                    payment.add_taproot_private_key(key);
                } else {
                    payment.add_private_key(key);
                }
            }
        });

        let given_scripts = payment
            .generate_output_scripts()
            .into_iter()
            .collect::<HashSet<_>>();

        let expected_scripts = s
            .expected
            .outputs
            .iter()
            .map(|expected_key| {
                ScriptBuf::new_p2tr_tweaked(
                    XOnlyPublicKey::from_str(expected_key)
                        .unwrap()
                        .dangerous_assume_tweaked(),
                )
            })
            .collect();

        if test.starts_with(
            "Multiple outputs with labels: multiple outputs for labeled address; same recipient",
        ) {
            assert!(
                given_scripts.is_subset(&expected_scripts),
                "output script in '{test}'"
            );
        } else {
            assert_eq!(given_scripts, expected_scripts, "output script in '{test}'");
        }
    })
}

#[cfg(feature = "spend")]
fn test_spending(
    receiving: &Receiving,
    silent_payment_outputs: &std::collections::HashSet<bip352::SilentPaymentOutput>,
    test: &str,
    secp: &Secp256k1<All>,
) {
    use std::collections::HashSet;

    use bip352::{spend, ScanSecretKey, SpendSecretKey};

    let spend_key = SpendSecretKey::new(
        SecretKey::from_slice(
            &Vec::from_hex(&receiving.given.key_material.spend_priv_key).unwrap(),
        )
        .unwrap(),
    );

    let scan_key = ScanSecretKey::new(
        SecretKey::from_slice(&Vec::from_hex(&receiving.given.key_material.scan_priv_key).unwrap())
            .unwrap(),
    );

    let expected_signatures: HashSet<_> = receiving
        .expected
        .outputs
        .iter()
        .map(|o| o.signature.clone())
        .collect();

    let calculated_signatures: HashSet<_> = receiving
        .expected
        .outputs
        .iter()
        .filter_map(|o| {
            let public_key =
                XOnlyPublicKey::from_slice(&Vec::from_hex(&o.pub_key).unwrap()).unwrap();

            silent_payment_outputs
                .iter()
                .find(|o| o.public_key() == public_key)
                .map(|spo| {
                    let keypair =
                        spend::signing_keypair(spend_key, scan_key, spo.tweak(), spo.label())
                            .unwrap();

                    let msg = Message::from_digest(
                        sha256::Hash::hash(&"message".to_string().into_bytes()).to_byte_array(),
                    );
                    let aux = sha256::Hash::hash(&"random auxiliary data".to_string().into_bytes())
                        .to_byte_array();

                    let sig = secp.sign_schnorr_with_aux_rand(&msg, &keypair, &aux);

                    hex::encode(sig.as_ref())
                })
        })
        .collect();

    assert_eq!(
        receiving.expected.n_outputs,
        calculated_signatures.len(),
        "Number of calcuated signatures does not match the expected number in '{test}'"
    );
    assert!(
        calculated_signatures.is_subset(&expected_signatures),
        "Some calculated signatures are missing in '{test}'"
    );
}

#[derive(Debug, Deserialize)]
struct ScriptPubKey {
    hex: ScriptBuf,
}

#[derive(Debug, Deserialize)]
struct Prevout {
    #[serde(rename = "scriptPubKey")]
    script_pub_key: ScriptPubKey,
}

#[derive(Debug, Deserialize)]
struct Vin {
    txid: Txid,
    vout: u32,
    #[serde(rename = "scriptSig")]
    script_sig: ScriptBuf,
    txinwitness: String,
    prevout: Prevout,
    private_key: Option<String>,
}

impl Vin {
    fn out_point(&self) -> OutPoint {
        OutPoint::new(self.txid, self.vout)
    }

    fn to_txin(&self) -> TxIn {
        let w = hex::decode(&self.txinwitness).unwrap();
        TxIn {
            previous_output: self.out_point(),
            script_sig: self.script_sig.clone(),
            sequence: Sequence::MAX,
            witness: deserialize(&w).unwrap_or_default(),
        }
    }
}

#[derive(Debug, Deserialize)]
struct SendingGiven {
    vin: Vec<Vin>,
    recipients: Vec<String>,
}

#[derive(Debug, Deserialize)]
struct SendingExpected {
    outputs: Vec<String>,
}

#[derive(Debug, Deserialize)]
struct Sending {
    given: SendingGiven,
    expected: SendingExpected,
}

#[derive(Debug, Deserialize)]
struct KeyMaterial {
    scan_priv_key: String,
    spend_priv_key: String,
}

#[derive(Debug, Deserialize)]
struct ReceivingGiven {
    vin: Vec<Vin>,
    outputs: Vec<String>,
    key_material: KeyMaterial,
    labels: Vec<u32>,
}
impl ReceivingGiven {
    fn to_tx(&self) -> Transaction {
        let input = self.vin.iter().map(|vin| vin.to_txin()).collect();
        let output = self
            .outputs
            .iter()
            .map(|out| TxOut {
                value: Amount::from_sat(123),
                script_pubkey: ScriptBuf::new_p2tr_tweaked(
                    TweakedPublicKey::dangerous_assume_tweaked(
                        XOnlyPublicKey::from_str(out).unwrap(),
                    ),
                ),
            })
            .collect();
        Transaction {
            version: Version::TWO,
            lock_time: LockTime::ZERO,
            input,
            output,
        }
    }
}

#[derive(Debug, Deserialize)]
struct ReceivingOutput {
    pub_key: String,
    signature: String,
}

#[derive(Debug, Deserialize)]
struct ReceivingExpected {
    addresses: Vec<String>,
    outputs: Vec<ReceivingOutput>,
    n_outputs: usize,
}

#[derive(Debug, Deserialize)]
struct Receiving {
    given: ReceivingGiven,
    expected: ReceivingExpected,
}

#[derive(Debug, Deserialize)]
struct Test {
    comment: String,
    sending: Vec<Sending>,
    receiving: Vec<Receiving>,
}