use anyhow::{bail, Context, Result}; use bdk::bitcoin::util::bip32::ExtendedPrivKey; use bdk::bitcoin::{Address, Amount, Network, PrivateKey, PublicKey, Transaction}; use bdk::descriptor::Descriptor; use bdk::miniscript::DescriptorTrait; use bdk::wallet::AddressIndex; use bdk::SignOptions; use bitcoin::util::psbt::PartiallySignedTransaction; use cfd_protocol::{ close_transaction, commit_descriptor, compute_adaptor_pk, create_cfd_transactions, finalize_spend_transaction, interval, lock_descriptor, punish_transaction, renew_cfd_transactions, spending_tx_sighash, CfdTransactions, Payout, PunishParams, TransactionExt, WalletExt, }; use rand::{thread_rng, CryptoRng, RngCore}; use secp256k1_zkp::{schnorrsig, EcdsaAdaptorSignature, SecretKey, Signature, SECP256K1}; use std::str::FromStr; #[test] fn create_cfd() { let mut rng = thread_rng(); let maker_lock_amount = Amount::ONE_BTC; let taker_lock_amount = Amount::ONE_BTC; let maker_wallet = build_wallet(&mut rng, Amount::from_btc(0.4).unwrap(), 5).unwrap(); let taker_wallet = build_wallet(&mut rng, Amount::from_btc(0.4).unwrap(), 5).unwrap(); let oracle_data = OliviaData::example(); let oracle_pk = oracle_data.pk; let nonce_pks = oracle_data.nonce_pks.clone(); let payouts = vec![ Payout::new( 0..=40_000, Amount::from_btc(1.5).unwrap(), Amount::from_btc(0.5).unwrap(), ) .unwrap(), Payout::new( 40_001..=70_000, Amount::ZERO, Amount::from_btc(2.0).unwrap(), ) .unwrap(), ] .concat(); let cet_timelock = 0; let refund_timelock = 0; let (maker_cfd_txs, taker_cfd_txs, maker, taker, maker_addr, taker_addr) = create_cfd_txs( &mut rng, (&maker_wallet, maker_lock_amount), (&taker_wallet, taker_lock_amount), (oracle_pk, &nonce_pks), payouts, (cet_timelock, refund_timelock), ); let lock_desc = lock_descriptor(maker.pk, taker.pk); let lock_amount = maker_lock_amount + taker_lock_amount; let commit_desc = commit_descriptor( (maker.pk, maker.rev_pk, maker.pub_pk), (taker.pk, taker.rev_pk, taker.pub_pk), ); let commit_amount = Amount::from_sat(maker_cfd_txs.commit.0.output[0].value); verify_cfd_sigs( (&maker_cfd_txs, maker.pk, maker.pub_pk), (&taker_cfd_txs, taker.pk, taker.pub_pk), (oracle_pk, &nonce_pks), (&lock_desc, lock_amount), (&commit_desc, commit_amount), ); check_cfd_txs( ( maker_wallet, maker_cfd_txs, maker.sk, maker.pk, maker.pub_sk, maker.pub_pk, maker.rev_sk, maker_addr, ), ( taker_wallet, taker_cfd_txs, taker.sk, taker.pk, taker.pub_sk, taker.pub_pk, taker.rev_sk, taker_addr, ), oracle_data, (lock_desc, lock_amount), (commit_desc, commit_amount), ); } #[test] fn renew_cfd() { let mut rng = thread_rng(); let maker_lock_amount = Amount::ONE_BTC; let taker_lock_amount = Amount::ONE_BTC; let maker_wallet = build_wallet(&mut rng, Amount::from_btc(0.4).unwrap(), 5).unwrap(); let taker_wallet = build_wallet(&mut rng, Amount::from_btc(0.4).unwrap(), 5).unwrap(); let oracle_data = OliviaData::example(); let oracle_pk = oracle_data.pk; let nonce_pks = oracle_data.nonce_pks.clone(); let payouts = vec![ Payout::new(0..=10_000, Amount::from_btc(2.0).unwrap(), Amount::ZERO).unwrap(), Payout::new( 10_001..=50_000, Amount::ZERO, Amount::from_btc(2.0).unwrap(), ) .unwrap(), ] .concat(); let cet_timelock = 0; let refund_timelock = 0; let (maker_cfd_txs, taker_cfd_txs, maker, taker, maker_addr, taker_addr) = create_cfd_txs( &mut rng, (&maker_wallet, maker_lock_amount), (&taker_wallet, taker_lock_amount), (oracle_pk, &nonce_pks), payouts, (cet_timelock, refund_timelock), ); // renew cfd transactions let (maker_rev_sk, maker_rev_pk) = make_keypair(&mut rng); let (maker_pub_sk, maker_pub_pk) = make_keypair(&mut rng); let (taker_rev_sk, taker_rev_pk) = make_keypair(&mut rng); let (taker_pub_sk, taker_pub_pk) = make_keypair(&mut rng); let payouts = vec![ Payout::new( 0..=50_000, Amount::from_btc(1.5).unwrap(), Amount::from_btc(0.5).unwrap(), ) .unwrap(), Payout::new( 50_001..=70_000, Amount::from_btc(0.5).unwrap(), Amount::from_btc(1.5).unwrap(), ) .unwrap(), ] .concat(); let maker_cfd_txs = renew_cfd_transactions( maker_cfd_txs.lock, ( maker.pk, maker_lock_amount, maker_addr.clone(), PunishParams { revocation_pk: maker_rev_pk, publish_pk: maker_pub_pk, }, ), ( taker.pk, taker_lock_amount, taker_addr.clone(), PunishParams { revocation_pk: taker_rev_pk, publish_pk: taker_pub_pk, }, ), (oracle_pk, &nonce_pks), (cet_timelock, refund_timelock), payouts.clone(), maker.sk, ) .unwrap(); let taker_cfd_txs = renew_cfd_transactions( taker_cfd_txs.lock, ( maker.pk, maker_lock_amount, maker_addr.clone(), PunishParams { revocation_pk: maker_rev_pk, publish_pk: maker_pub_pk, }, ), ( taker.pk, taker_lock_amount, taker_addr.clone(), PunishParams { revocation_pk: taker_rev_pk, publish_pk: taker_pub_pk, }, ), (oracle_pk, &nonce_pks), (cet_timelock, refund_timelock), payouts, taker.sk, ) .unwrap(); let lock_desc = lock_descriptor(maker.pk, taker.pk); let lock_amount = maker_lock_amount + taker_lock_amount; let commit_desc = commit_descriptor( (maker.pk, maker_rev_pk, maker_pub_pk), (taker.pk, taker_rev_pk, taker_pub_pk), ); let commit_amount = Amount::from_sat(maker_cfd_txs.commit.0.output[0].value); verify_cfd_sigs( (&maker_cfd_txs, maker.pk, maker_pub_pk), (&taker_cfd_txs, taker.pk, taker_pub_pk), (oracle_pk, &nonce_pks), (&lock_desc, lock_amount), (&commit_desc, commit_amount), ); check_cfd_txs( ( maker_wallet, maker_cfd_txs, maker.sk, maker.pk, maker_pub_sk, maker_pub_pk, maker_rev_sk, maker_addr, ), ( taker_wallet, taker_cfd_txs, taker.sk, taker.pk, taker_pub_sk, taker_pub_pk, taker_rev_sk, taker_addr, ), oracle_data, (lock_desc, lock_amount), (commit_desc, commit_amount), ) } #[test] fn collaboratively_close_cfd() { let mut rng = thread_rng(); let maker_lock_amount = Amount::ONE_BTC; let taker_lock_amount = Amount::ONE_BTC; let maker_wallet = build_wallet(&mut rng, Amount::from_btc(0.4).unwrap(), 5).unwrap(); let taker_wallet = build_wallet(&mut rng, Amount::from_btc(0.4).unwrap(), 5).unwrap(); let oracle_data = OliviaData::example(); let oracle_pk = oracle_data.pk; let nonce_pks = oracle_data.nonce_pks; let payouts = vec![Payout::new( 0..=100_000, Amount::from_btc(1.5).unwrap(), Amount::from_btc(0.5).unwrap(), ) .unwrap()] .concat(); let cet_timelock = 0; let refund_timelock = 0; let (maker_cfd_txs, _, maker, taker, maker_addr, taker_addr) = create_cfd_txs( &mut rng, (&maker_wallet, maker_lock_amount), (&taker_wallet, taker_lock_amount), (oracle_pk, &nonce_pks), payouts, (cet_timelock, refund_timelock), ); let lock_tx = maker_cfd_txs.lock.extract_tx(); let lock_desc = lock_descriptor(maker.pk, taker.pk); let (lock_outpoint, lock_amount) = { let outpoint = lock_tx .outpoint(&lock_desc.script_pubkey()) .expect("lock script to be in lock tx"); let amount = Amount::from_sat(lock_tx.output[outpoint.vout as usize].value); (outpoint, amount) }; let maker_amount = Amount::ONE_BTC; let taker_amount = Amount::ONE_BTC; let (close_tx, close_sighash) = close_transaction( &lock_desc, lock_outpoint, lock_amount, (&maker_addr, maker_amount), (&taker_addr, taker_amount), ) .expect("to build close tx"); let sig_maker = SECP256K1.sign(&close_sighash, &maker.sk); let sig_taker = SECP256K1.sign(&close_sighash, &taker.sk); let signed_close_tx = finalize_spend_transaction( close_tx, &lock_desc, (maker.pk, sig_maker), (taker.pk, sig_taker), ) .expect("to sign close tx"); check_tx(&lock_tx, &signed_close_tx, &lock_desc).expect("valid close tx"); } fn create_cfd_txs( rng: &mut (impl RngCore + CryptoRng), (maker_wallet, maker_lock_amount): (&bdk::Wallet<(), bdk::database::MemoryDatabase>, Amount), (taker_wallet, taker_lock_amount): (&bdk::Wallet<(), bdk::database::MemoryDatabase>, Amount), (oracle_pk, nonce_pks): (schnorrsig::PublicKey, &[schnorrsig::PublicKey]), payouts: Vec, (cet_timelock, refund_timelock): (u32, u32), ) -> ( CfdTransactions, CfdTransactions, CfdKeys, CfdKeys, Address, Address, ) { let (maker_sk, maker_pk) = make_keypair(rng); let (taker_sk, taker_pk) = make_keypair(rng); let maker_addr = maker_wallet.get_address(AddressIndex::New).unwrap(); let taker_addr = taker_wallet.get_address(AddressIndex::New).unwrap(); let (maker_rev_sk, maker_rev_pk) = make_keypair(rng); let (maker_pub_sk, maker_pub_pk) = make_keypair(rng); let (taker_rev_sk, taker_rev_pk) = make_keypair(rng); let (taker_pub_sk, taker_pub_pk) = make_keypair(rng); let maker_params = maker_wallet .build_party_params(maker_lock_amount, maker_pk) .unwrap(); let taker_params = taker_wallet .build_party_params(taker_lock_amount, taker_pk) .unwrap(); let maker_cfd_txs = create_cfd_transactions( ( maker_params.clone(), PunishParams { revocation_pk: maker_rev_pk, publish_pk: maker_pub_pk, }, ), ( taker_params.clone(), PunishParams { revocation_pk: taker_rev_pk, publish_pk: taker_pub_pk, }, ), (oracle_pk, nonce_pks), (cet_timelock, refund_timelock), payouts.clone(), maker_sk, ) .unwrap(); let taker_cfd_txs = create_cfd_transactions( ( maker_params, PunishParams { revocation_pk: maker_rev_pk, publish_pk: maker_pub_pk, }, ), ( taker_params, PunishParams { revocation_pk: taker_rev_pk, publish_pk: taker_pub_pk, }, ), (oracle_pk, nonce_pks), (cet_timelock, refund_timelock), payouts, taker_sk, ) .unwrap(); ( maker_cfd_txs, taker_cfd_txs, CfdKeys { sk: maker_sk, pk: maker_pk, rev_sk: maker_rev_sk, rev_pk: maker_rev_pk, pub_sk: maker_pub_sk, pub_pk: maker_pub_pk, }, CfdKeys { sk: taker_sk, pk: taker_pk, rev_sk: taker_rev_sk, rev_pk: taker_rev_pk, pub_sk: taker_pub_sk, pub_pk: taker_pub_pk, }, maker_addr.address, taker_addr.address, ) } struct CfdKeys { sk: SecretKey, pk: PublicKey, rev_sk: SecretKey, rev_pk: PublicKey, pub_sk: SecretKey, pub_pk: PublicKey, } fn verify_cfd_sigs( (maker_cfd_txs, maker_pk, maker_publish_pk): (&CfdTransactions, PublicKey, PublicKey), (taker_cfd_txs, taker_pk, taker_publish_pk): (&CfdTransactions, PublicKey, PublicKey), (oracle_pk, nonce_pks): (schnorrsig::PublicKey, &[schnorrsig::PublicKey]), (lock_desc, lock_amount): (&Descriptor, Amount), (commit_desc, commit_amount): (&Descriptor, Amount), ) { verify_spend( &taker_cfd_txs.refund.0, &maker_cfd_txs.refund.1, commit_desc, commit_amount, &maker_pk.key, ) .expect("valid maker refund sig"); verify_spend( &maker_cfd_txs.refund.0, &taker_cfd_txs.refund.1, commit_desc, commit_amount, &taker_pk.key, ) .expect("valid taker refund sig"); for (tx, _, digits) in taker_cfd_txs.cets.iter() { maker_cfd_txs .cets .iter() .find(|(maker_tx, maker_encsig, _)| { maker_tx.txid() == tx.txid() && verify_cet_encsig( tx, maker_encsig, digits, &maker_pk.key, (oracle_pk, nonce_pks), commit_desc, commit_amount, ) .is_ok() }) .expect("one valid maker cet encsig per cet"); } for (tx, _, msg_nonce_pairs) in maker_cfd_txs.cets.iter() { taker_cfd_txs .cets .iter() .find(|(taker_tx, taker_encsig, _)| { taker_tx.txid() == tx.txid() && verify_cet_encsig( tx, taker_encsig, msg_nonce_pairs, &taker_pk.key, (oracle_pk, nonce_pks), commit_desc, commit_amount, ) .is_ok() }) .expect("one valid taker cet encsig per cet"); } encverify_spend( &taker_cfd_txs.commit.0, &maker_cfd_txs.commit.1, lock_desc, lock_amount, &taker_publish_pk.key, &maker_pk.key, ) .expect("valid maker commit encsig"); encverify_spend( &maker_cfd_txs.commit.0, &taker_cfd_txs.commit.1, lock_desc, lock_amount, &maker_publish_pk.key, &taker_pk.key, ) .expect("valid taker commit encsig"); } fn check_cfd_txs( ( maker_wallet, maker_cfd_txs, maker_sk, maker_pk, maker_pub_sk, maker_pub_pk, maker_rev_sk, maker_addr, ): ( bdk::Wallet<(), bdk::database::MemoryDatabase>, CfdTransactions, SecretKey, PublicKey, SecretKey, PublicKey, SecretKey, Address, ), ( taker_wallet, taker_cfd_txs, taker_sk, taker_pk, taker_pub_sk, taker_pub_pk, taker_rev_sk, taker_addr, ): ( bdk::Wallet<(), bdk::database::MemoryDatabase>, CfdTransactions, SecretKey, PublicKey, SecretKey, PublicKey, SecretKey, Address, ), oracle_data: OliviaData, (lock_desc, lock_amount): (Descriptor, Amount), (commit_desc, commit_amount): (Descriptor, Amount), ) { // Lock transaction (either party can do this): let signed_lock_tx = sign_lock_tx(maker_cfd_txs.lock.clone(), maker_wallet, taker_wallet) .expect("to build signed lock tx"); // Commit transactions: let signed_commit_tx_maker = decrypt_and_sign( maker_cfd_txs.commit.0.clone(), (&maker_sk, &maker_pk), &maker_pub_sk, &taker_pk, &taker_cfd_txs.commit.1, &lock_desc, lock_amount, ) .expect("maker to build signed commit tx"); check_tx(&signed_lock_tx, &signed_commit_tx_maker, &lock_desc).expect("valid maker commit tx"); let signed_commit_tx_taker = decrypt_and_sign( taker_cfd_txs.commit.0.clone(), (&taker_sk, &taker_pk), &taker_pub_sk, &maker_pk, &maker_cfd_txs.commit.1, &lock_desc, lock_amount, ) .expect("taker to build signed commit tx"); check_tx(&signed_lock_tx, &signed_commit_tx_taker, &lock_desc).expect("valid taker commit tx"); // Refund transaction (both parties would produce the same one): let signed_refund_tx = finalize_spend_transaction( maker_cfd_txs.refund.0.clone(), &commit_desc, (maker_pk, maker_cfd_txs.refund.1), (taker_pk, taker_cfd_txs.refund.1), ) .expect("to build signed refund tx"); check_tx(&signed_commit_tx_maker, &signed_refund_tx, &commit_desc).expect("valid refund tx"); // CETs: let unlocked_cets = maker_cfd_txs.cets.clone().into_iter().filter_map({ |(tx, _, digits)| { let price = oracle_data.price; if !digits.range().contains(&price) { return None; } let oracle_attestations = oracle_data.attestations.clone(); build_and_check_cet( tx, &taker_cfd_txs.cets, (&maker_sk, &maker_pk), &taker_pk, (price, &oracle_attestations), (&signed_commit_tx_maker, &commit_desc, commit_amount), ) .expect("valid maker cet"); Some(()) } }); assert_eq!(unlocked_cets.count(), 1, "Expected to unlock only 1 CET"); let unlocked_cets = taker_cfd_txs .cets .into_iter() .filter_map(|(tx, _, digits)| { let price = oracle_data.price; if !digits.range().contains(&price) { return None; } build_and_check_cet( tx, &maker_cfd_txs.cets, (&taker_sk, &taker_pk), &maker_pk, (price, &oracle_data.attestations), (&signed_commit_tx_maker, &commit_desc, commit_amount), ) .expect("valid taker cet"); Some(()) }); assert_eq!(unlocked_cets.count(), 1, "Expected to unlock only 1 CET"); // Punish transactions: let punish_tx_maker = punish_transaction( &commit_desc, &maker_addr, maker_cfd_txs.commit.1, maker_sk, taker_rev_sk, taker_pub_pk, &signed_commit_tx_taker, ) .expect("maker to build punish tx"); check_tx(&signed_commit_tx_taker, &punish_tx_maker, &commit_desc) .expect("valid maker punish tx"); let punish_tx_taker = punish_transaction( &commit_desc, &taker_addr, taker_cfd_txs.commit.1, taker_sk, maker_rev_sk, maker_pub_pk, &signed_commit_tx_maker, ) .expect("taker to build punish tx"); check_tx(&signed_commit_tx_maker, &punish_tx_taker, &commit_desc) .expect("valid taker punish tx"); } fn build_and_check_cet( cet: Transaction, cets_other: &[(Transaction, EcdsaAdaptorSignature, interval::Digits)], (sk, pk): (&SecretKey, &PublicKey), pk_other: &PublicKey, (price, oracle_attestations): (u64, &[SecretKey]), (commit_tx, commit_desc, commit_amount): (&Transaction, &Descriptor, Amount), ) -> Result<()> { let (encsig_other, n_bits) = cets_other .iter() .find_map(|(_, encsig, digits)| { (digits.range().contains(&price)).then(|| (encsig, digits.len())) }) .expect("one encsig per cet, per party"); let (oracle_attestations, _) = oracle_attestations.split_at(n_bits); let mut decryption_sk = oracle_attestations[0]; for oracle_attestation in oracle_attestations[1..].iter() { decryption_sk.add_assign(oracle_attestation.as_ref())?; } let signed_cet = decrypt_and_sign( cet, (sk, pk), &decryption_sk, pk_other, encsig_other, commit_desc, commit_amount, ) .context("failed to build signed cet")?; check_tx(commit_tx, &signed_cet, commit_desc).context("invalid cet")?; Ok(()) } fn check_tx( spent_tx: &Transaction, spend_tx: &Transaction, spent_descriptor: &Descriptor, ) -> Result<()> { let spent_script_pubkey = spent_descriptor.script_pubkey(); let spent_outpoint = spent_tx .outpoint(&spent_script_pubkey) .context("spend tx doesn't spend from spent tx")?; let spent_amount = spent_tx.output[spent_outpoint.vout as usize].value; check_tx_fee(&[spent_tx], spend_tx)?; spent_descriptor.script_pubkey().verify( 0, spent_amount, bitcoin::consensus::serialize(spend_tx).as_slice(), )?; Ok(()) } fn decrypt_and_sign( spend_tx: Transaction, (sk, pk): (&SecretKey, &PublicKey), decryption_sk: &SecretKey, pk_other: &PublicKey, encsig_other: &EcdsaAdaptorSignature, spent_descriptor: &Descriptor, spent_amount: Amount, ) -> Result { let sighash = spending_tx_sighash(&spend_tx, spent_descriptor, spent_amount); let sig_self = SECP256K1.sign(&sighash, sk); encsig_other .verify( SECP256K1, &sighash, &pk_other.key, &secp256k1_zkp::PublicKey::from_secret_key(SECP256K1, decryption_sk), ) .expect("wrong decryption key"); let sig_other = encsig_other.decrypt(decryption_sk)?; let signed_commit_tx = finalize_spend_transaction( spend_tx, spent_descriptor, (*pk, sig_self), (*pk_other, sig_other), )?; Ok(signed_commit_tx) } fn sign_lock_tx( mut lock_tx: PartiallySignedTransaction, maker_wallet: bdk::Wallet<(), bdk::database::MemoryDatabase>, taker_wallet: bdk::Wallet<(), bdk::database::MemoryDatabase>, ) -> Result { maker_wallet .sign( &mut lock_tx, SignOptions { trust_witness_utxo: true, ..Default::default() }, ) .context("maker could not sign lock tx")?; taker_wallet .sign( &mut lock_tx, SignOptions { trust_witness_utxo: true, ..Default::default() }, ) .context("taker could not sign lock tx")?; Ok(lock_tx.extract_tx()) } fn verify_spend( tx: &Transaction, sig: &Signature, spent_descriptor: &Descriptor, spent_amount: Amount, pk: &secp256k1_zkp::PublicKey, ) -> Result<()> { let sighash = spending_tx_sighash(tx, spent_descriptor, spent_amount); SECP256K1 .verify(&sighash, sig, pk) .context("failed to verify sig on spend tx") } fn verify_cet_encsig( tx: &Transaction, encsig: &EcdsaAdaptorSignature, digits: &interval::Digits, pk: &secp256k1_zkp::PublicKey, (oracle_pk, nonce_pks): (schnorrsig::PublicKey, &[schnorrsig::PublicKey]), spent_descriptor: &Descriptor, spent_amount: Amount, ) -> Result<()> { let index_nonce_pairs = &digits .to_indices() .into_iter() .zip(nonce_pks.iter().cloned()) .collect::>(); let adaptor_point = compute_adaptor_pk(&oracle_pk, index_nonce_pairs) .context("could not calculate adaptor point")?; encverify_spend( tx, encsig, spent_descriptor, spent_amount, &adaptor_point, pk, ) } fn encverify_spend( tx: &Transaction, encsig: &EcdsaAdaptorSignature, spent_descriptor: &Descriptor, spent_amount: Amount, encryption_point: &secp256k1_zkp::PublicKey, pk: &secp256k1_zkp::PublicKey, ) -> Result<()> { let sighash = spending_tx_sighash(tx, spent_descriptor, spent_amount); encsig .verify(SECP256K1, &sighash, pk, encryption_point) .context("failed to verify encsig spend tx") } fn check_tx_fee(input_txs: &[&Transaction], spend_tx: &Transaction) -> Result<()> { let input_amount = spend_tx .input .iter() .try_fold::<_, _, Result<_>>(0, |acc, input| { let value = input_txs .iter() .find_map(|tx| { (tx.txid() == input.previous_output.txid) .then(|| tx.output[input.previous_output.vout as usize].value) }) .with_context(|| { format!( "spend tx input {} not found in input_txs", input.previous_output ) })?; Ok(acc + value) })?; let output_amount = spend_tx .output .iter() .fold(0, |acc, output| acc + output.value); let fee = input_amount - output_amount; let min_relay_fee = spend_tx.get_virtual_size(); if (fee as f64) < min_relay_fee { bail!("min relay fee not met, {} < {}", fee, min_relay_fee) } Ok(()) } fn build_wallet( rng: &mut (impl RngCore + CryptoRng), utxo_amount: Amount, num_utxos: u8, ) -> Result> { use bdk::{populate_test_db, testutils}; let mut seed = [0u8; 32]; rng.fill_bytes(&mut seed); let key = ExtendedPrivKey::new_master(Network::Regtest, &seed)?; let descriptors = testutils!(@descriptors (&format!("wpkh({}/*)", key))); let mut database = bdk::database::MemoryDatabase::new(); for index in 0..num_utxos { populate_test_db!( &mut database, testutils! { @tx ( (@external descriptors, index as u32) => utxo_amount.as_sat() ) (@confirmations 1) }, Some(100) ); } let wallet = bdk::Wallet::new_offline(&descriptors.0, None, Network::Regtest, database)?; Ok(wallet) } fn make_keypair(rng: &mut (impl RngCore + CryptoRng)) -> (SecretKey, PublicKey) { let sk = SecretKey::new(rng); let pk = PublicKey::from_private_key( SECP256K1, &PrivateKey { compressed: true, network: Network::Regtest, key: sk, }, ); (sk, pk) } struct OliviaData { pk: schnorrsig::PublicKey, nonce_pks: Vec, price: u64, attestations: Vec, } impl OliviaData { /// An example of all the data necessary from `olivia` to test the /// CFD protocol. /// /// Data comes from this event: /// https://outcome.observer/h00.ooo/x/BitMEX/BXBT/2021-10-05T02:00:00.price[n:20]. pub fn example() -> Self { let pk = schnorrsig::PublicKey::from_str( "ddd4636845a90185991826be5a494cde9f4a6947b1727217afedc6292fa4caf7", ) .unwrap(); let nonce_pks = [ "d02d163cf9623f567c4e3faf851a9266ac1ede13da4ca4141f3a7717fba9a739", "bc310f26aa5addbc382f653d8530aaead7c25e3546abc24639f490e36d4bdb88", "2661375f570dcc32300d442e85b6d72dfa3232dccda45e8fb4a2d1e758d1d374", "fcc68fbf071d391b14c0867cb4defb5a8abc12418dff3dfc2f84fd4025cb2716", "cf5c2b7fe3851c64a7ff9635a9bfc50cdd301401d002f2da049f4c6a20e8457b", "14f1005d8c2832a2c4666dd732dd9bb3af9c8f70ebcdaec96869b1ca0c8e0de6", "299ee1c9c20fab8b067adf452a7d7661b5e7f5dd6bc707562805002e7cb8443e", "bcb4e5a594346de298993a7a31762f598b5224b977e23182369e9ed3e5127f78", "25e09a16ee5d469069abfb62cd5e1f20af50cf15241f571e64fa28b127304574", "3ed5a1422f43299caf281123aba88bd4bc61ec863f4afb79c7ce7663ad44db5d", "a7e0f61212735c192c4bf16e0a3e925e65f9f3feb6f1e5e8d6f5c18cf2dbb5a8", "a36a631015d9036d0c321fea7cf12f589aa196e7279b4a290de5112c2940e540", "b5bdd931f81970139e7301ac654b378077c3ed993ca7893ed93fee5fc6f7a782", "00090816e256b41e042dce38bde99ab3cf9482f9b066836988d3ed54833638e8", "3530408e93c251f5f488d3b1c608157177c459d6fab1966abebf765bcc9338d2", "603269ce88d112ff7fcfcaab82f228be97deca37f8190084d509c71b51a30432", "f0587414fcc6c56aef11d4a1d287ad6b55b237c5b8a5d5d93eb9ca06f6466ccf", "763009afb0ffd99c7b835488cb3b0302f3b78f59bbfd5292bedab8ef9da8c1b7", "3867af9048309a05004a164bdea09899f23ff1d83b6491b2b53a1b7b92e0eb2e", "688118e6b59e27944c277513db2711a520f4283c7c53a11f58d9f6a46d82c964", ] .iter() .map(|pk| schnorrsig::PublicKey::from_str(pk).unwrap()) .collect(); let attestations = [ "5bc7663195971daaa1e3e6a81b4bca65882791644bc446fc060cbc118a3ace0f", "721d0cb56a0778a1ca7907f81a0787f34385b13f854c845c4c5539f7f6267958", "044aeef0d525c8ff48758c80939e95807bc640990cc03f53ab6fc0b262045221", "79f5175423ec6ee69c8d0e55251db85f3015c2edfa5a03095443fbbf35eb2282", "233b9ec549e9cc7c702109d29636db85a3ec63a66f3b53444bcc7586d36ca439", "2961a00320b7c9a70220060019a6ca88e18c205fadd2f873c174e5ccbbed527e", "bdb76e8f81c39ade4205ead9b68118757fc49ec22769605f26ef904b235283d6", "6e75dafedf4ed685513ec1f5c93508de4fad2be05b46001ac00c03474f4690e1", "cfcfc27eb9273b343b3042f0386e77efe329066be079788bb00ab47d72f26780", "2d931ffd2963e74566365674583abc427bdb6ae571c4887d81f1920f0850665d", "33b6f1112fa046cbc04be44c615e70519702662c1f72d8d49b3c4613614a8a46", "19e569b15410fa9a758c1a6c211eae8c1547efbe0ac6a7709902be93415f2f09", "d859dd5c9a58e1836d1eea3ebe7f48198a681d29e5a5cd6922532d2e94a53a1d", "3387eb2ad5e64cd102167766bb72b447f4a2e5129d161e422f9d41cd7d1cc281", "db35a9778a1e3abc8d8ab2f4a79346ae2154c9e0b4932d859d1f3e244f67ae76", "c3be969e8b889cfb2ece71123e6be5538a2d3a1229637b18bccc179073c38059", "6f73263f430e10b82d0fd06c4ddd3b8a6b58c3e756745bd0d9e71a399e517921", "0818c9c245d7d2162cd393c562a121f80405a27d22ae465e95030c31ebb4bd24", "b7c03f0bd6d63bd78ad4ea0f3452ff9717ba65ca42038e6e90a1aa558b7942dc", "90c4d8ec9f408ccb62a62daa993c20f2f86799e1fdea520c6d060418e55fd216", ] .iter() .map(|pk| SecretKey::from_str(pk).unwrap()) .collect(); Self { pk, nonce_pks, attestations, price: 49262, } } }