|
|
@ -5,9 +5,9 @@ use bdk::miniscript::DescriptorTrait; |
|
|
|
use bdk::wallet::AddressIndex; |
|
|
|
use bdk::SignOptions; |
|
|
|
use cfd_protocol::{ |
|
|
|
build_cfd_transactions, commit_descriptor, compute_signature_point, finalize_spend_transaction, |
|
|
|
lock_descriptor, punish_transaction, spending_tx_sighash, OracleParams, Payout, PunishParams, |
|
|
|
TransactionExt, WalletExt, |
|
|
|
commit_descriptor, compute_signature_point, create_cfd_transactions, |
|
|
|
finalize_spend_transaction, lock_descriptor, punish_transaction, renew_cfd_transactions, |
|
|
|
spending_tx_sighash, OracleParams, Payout, PunishParams, TransactionExt, WalletExt, |
|
|
|
}; |
|
|
|
use rand::{CryptoRng, RngCore, SeedableRng}; |
|
|
|
use rand_chacha::ChaChaRng; |
|
|
@ -63,7 +63,7 @@ fn create_cfd() { |
|
|
|
.build_party_params(taker_lock_amount, taker_pk) |
|
|
|
.unwrap(); |
|
|
|
|
|
|
|
let maker_cfd_txs = build_cfd_transactions( |
|
|
|
let maker_cfd_txs = create_cfd_transactions( |
|
|
|
( |
|
|
|
maker_params.clone(), |
|
|
|
PunishParams { |
|
|
@ -88,7 +88,7 @@ fn create_cfd() { |
|
|
|
) |
|
|
|
.unwrap(); |
|
|
|
|
|
|
|
let taker_cfd_txs = build_cfd_transactions( |
|
|
|
let taker_cfd_txs = create_cfd_transactions( |
|
|
|
( |
|
|
|
maker_params, |
|
|
|
PunishParams { |
|
|
@ -372,6 +372,446 @@ fn create_cfd() { |
|
|
|
.expect("valid punish transaction signed by taker"); |
|
|
|
} |
|
|
|
|
|
|
|
#[test] |
|
|
|
fn renew_cfd() { |
|
|
|
let mut rng = ChaChaRng::seed_from_u64(0); |
|
|
|
|
|
|
|
let maker_lock_amount = Amount::ONE_BTC; |
|
|
|
let taker_lock_amount = Amount::ONE_BTC; |
|
|
|
|
|
|
|
let oracle = Oracle::new(&mut rng); |
|
|
|
let (event, _announcement) = announce(&mut rng); |
|
|
|
|
|
|
|
let (maker_sk, maker_pk) = make_keypair(&mut rng); |
|
|
|
let (taker_sk, taker_pk) = make_keypair(&mut rng); |
|
|
|
|
|
|
|
let payouts = vec![ |
|
|
|
Payout::new( |
|
|
|
b"win".to_vec(), |
|
|
|
Amount::from_btc(2.0).unwrap(), |
|
|
|
Amount::ZERO, |
|
|
|
), |
|
|
|
Payout::new( |
|
|
|
b"lose".to_vec(), |
|
|
|
Amount::ZERO, |
|
|
|
Amount::from_btc(2.0).unwrap(), |
|
|
|
), |
|
|
|
]; |
|
|
|
|
|
|
|
let refund_timelock = 0; |
|
|
|
|
|
|
|
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 maker_address = maker_wallet.get_address(AddressIndex::New).unwrap(); |
|
|
|
let taker_address = taker_wallet.get_address(AddressIndex::New).unwrap(); |
|
|
|
|
|
|
|
let lock_amount = maker_lock_amount + taker_lock_amount; |
|
|
|
let (_maker_revocation_sk, maker_revocation_pk) = make_keypair(&mut rng); |
|
|
|
let (_maker_publish_sk, maker_publish_pk) = make_keypair(&mut rng); |
|
|
|
|
|
|
|
let (_taker_revocation_sk, taker_revocation_pk) = make_keypair(&mut rng); |
|
|
|
let (_taker_publish_sk, taker_publish_pk) = make_keypair(&mut 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_revocation_pk, |
|
|
|
publish_pk: maker_publish_pk, |
|
|
|
}, |
|
|
|
), |
|
|
|
( |
|
|
|
taker_params.clone(), |
|
|
|
PunishParams { |
|
|
|
revocation_pk: taker_revocation_pk, |
|
|
|
publish_pk: taker_publish_pk, |
|
|
|
}, |
|
|
|
), |
|
|
|
OracleParams { |
|
|
|
pk: oracle.public_key(), |
|
|
|
nonce_pk: event.nonce_pk, |
|
|
|
}, |
|
|
|
refund_timelock, |
|
|
|
payouts.clone(), |
|
|
|
maker_sk, |
|
|
|
) |
|
|
|
.unwrap(); |
|
|
|
|
|
|
|
let taker_cfd_txs = create_cfd_transactions( |
|
|
|
( |
|
|
|
maker_params, |
|
|
|
PunishParams { |
|
|
|
revocation_pk: maker_revocation_pk, |
|
|
|
publish_pk: maker_publish_pk, |
|
|
|
}, |
|
|
|
), |
|
|
|
( |
|
|
|
taker_params, |
|
|
|
PunishParams { |
|
|
|
revocation_pk: taker_revocation_pk, |
|
|
|
publish_pk: taker_publish_pk, |
|
|
|
}, |
|
|
|
), |
|
|
|
OracleParams { |
|
|
|
pk: oracle.public_key(), |
|
|
|
nonce_pk: event.nonce_pk, |
|
|
|
}, |
|
|
|
refund_timelock, |
|
|
|
payouts, |
|
|
|
taker_sk, |
|
|
|
) |
|
|
|
.unwrap(); |
|
|
|
|
|
|
|
// renew cfd transactions
|
|
|
|
|
|
|
|
let (maker_revocation_sk, maker_revocation_pk) = make_keypair(&mut rng); |
|
|
|
let (maker_publish_sk, maker_publish_pk) = make_keypair(&mut rng); |
|
|
|
|
|
|
|
let (taker_revocation_sk, taker_revocation_pk) = make_keypair(&mut rng); |
|
|
|
let (taker_publish_sk, taker_publish_pk) = make_keypair(&mut rng); |
|
|
|
|
|
|
|
let (event, announcement) = announce(&mut rng); |
|
|
|
|
|
|
|
let payouts = vec![ |
|
|
|
Payout::new( |
|
|
|
b"win".to_vec(), |
|
|
|
Amount::from_btc(1.5).unwrap(), |
|
|
|
Amount::from_btc(0.5).unwrap(), |
|
|
|
), |
|
|
|
Payout::new( |
|
|
|
b"lose".to_vec(), |
|
|
|
Amount::from_btc(0.5).unwrap(), |
|
|
|
Amount::from_btc(1.5).unwrap(), |
|
|
|
), |
|
|
|
]; |
|
|
|
|
|
|
|
let maker_cfd_txs = renew_cfd_transactions( |
|
|
|
maker_cfd_txs.lock, |
|
|
|
( |
|
|
|
maker_pk, |
|
|
|
maker_lock_amount, |
|
|
|
maker_address.address.clone(), |
|
|
|
PunishParams { |
|
|
|
revocation_pk: maker_revocation_pk, |
|
|
|
publish_pk: maker_publish_pk, |
|
|
|
}, |
|
|
|
), |
|
|
|
( |
|
|
|
taker_pk, |
|
|
|
taker_lock_amount, |
|
|
|
taker_address.address.clone(), |
|
|
|
PunishParams { |
|
|
|
revocation_pk: taker_revocation_pk, |
|
|
|
publish_pk: taker_publish_pk, |
|
|
|
}, |
|
|
|
), |
|
|
|
OracleParams { |
|
|
|
pk: oracle.public_key(), |
|
|
|
nonce_pk: announcement.nonce_pk(), |
|
|
|
}, |
|
|
|
refund_timelock, |
|
|
|
payouts.clone(), |
|
|
|
maker_sk, |
|
|
|
) |
|
|
|
.unwrap(); |
|
|
|
|
|
|
|
let taker_cfd_txs = renew_cfd_transactions( |
|
|
|
taker_cfd_txs.lock, |
|
|
|
( |
|
|
|
maker_pk, |
|
|
|
maker_lock_amount, |
|
|
|
maker_address.address.clone(), |
|
|
|
PunishParams { |
|
|
|
revocation_pk: maker_revocation_pk, |
|
|
|
publish_pk: maker_publish_pk, |
|
|
|
}, |
|
|
|
), |
|
|
|
( |
|
|
|
taker_pk, |
|
|
|
taker_lock_amount, |
|
|
|
taker_address.address.clone(), |
|
|
|
PunishParams { |
|
|
|
revocation_pk: taker_revocation_pk, |
|
|
|
publish_pk: taker_publish_pk, |
|
|
|
}, |
|
|
|
), |
|
|
|
OracleParams { |
|
|
|
pk: oracle.public_key(), |
|
|
|
nonce_pk: announcement.nonce_pk(), |
|
|
|
}, |
|
|
|
refund_timelock, |
|
|
|
payouts, |
|
|
|
taker_sk, |
|
|
|
) |
|
|
|
.unwrap(); |
|
|
|
|
|
|
|
let commit_descriptor = commit_descriptor( |
|
|
|
(maker_pk, maker_revocation_pk, maker_publish_pk), |
|
|
|
(taker_pk, taker_revocation_pk, taker_publish_pk), |
|
|
|
); |
|
|
|
|
|
|
|
let commit_amount = Amount::from_sat(maker_cfd_txs.commit.0.output[0].value); |
|
|
|
assert_eq!( |
|
|
|
commit_amount.as_sat(), |
|
|
|
taker_cfd_txs.commit.0.output[0].value |
|
|
|
); |
|
|
|
|
|
|
|
{ |
|
|
|
let refund_sighash = |
|
|
|
spending_tx_sighash(&taker_cfd_txs.refund.0, &commit_descriptor, commit_amount); |
|
|
|
SECP256K1 |
|
|
|
.verify(&refund_sighash, &maker_cfd_txs.refund.1, &maker_pk.key) |
|
|
|
.expect("valid maker refund sig") |
|
|
|
}; |
|
|
|
|
|
|
|
{ |
|
|
|
let refund_sighash = |
|
|
|
spending_tx_sighash(&maker_cfd_txs.refund.0, &commit_descriptor, commit_amount); |
|
|
|
SECP256K1 |
|
|
|
.verify(&refund_sighash, &taker_cfd_txs.refund.1, &taker_pk.key) |
|
|
|
.expect("valid taker refund sig") |
|
|
|
}; |
|
|
|
|
|
|
|
// TODO: We should not rely on order
|
|
|
|
for (maker_cet, taker_cet) in maker_cfd_txs.cets.iter().zip(taker_cfd_txs.cets.iter()) { |
|
|
|
let cet_sighash = { |
|
|
|
let maker_sighash = |
|
|
|
spending_tx_sighash(&maker_cet.0, &commit_descriptor, commit_amount); |
|
|
|
let taker_sighash = |
|
|
|
spending_tx_sighash(&taker_cet.0, &commit_descriptor, commit_amount); |
|
|
|
|
|
|
|
assert_eq!(maker_sighash, taker_sighash); |
|
|
|
maker_sighash |
|
|
|
}; |
|
|
|
|
|
|
|
let encryption_point = { |
|
|
|
let maker_encryption_point = compute_signature_point( |
|
|
|
&oracle.public_key(), |
|
|
|
&announcement.nonce_pk(), |
|
|
|
&maker_cet.2, |
|
|
|
) |
|
|
|
.unwrap(); |
|
|
|
let taker_encryption_point = compute_signature_point( |
|
|
|
&oracle.public_key(), |
|
|
|
&announcement.nonce_pk(), |
|
|
|
&taker_cet.2, |
|
|
|
) |
|
|
|
.unwrap(); |
|
|
|
|
|
|
|
assert_eq!(maker_encryption_point, taker_encryption_point); |
|
|
|
maker_encryption_point |
|
|
|
}; |
|
|
|
|
|
|
|
let maker_encsig = maker_cet.1; |
|
|
|
maker_encsig |
|
|
|
.verify(SECP256K1, &cet_sighash, &maker_pk.key, &encryption_point) |
|
|
|
.expect("valid maker cet encsig"); |
|
|
|
|
|
|
|
let taker_encsig = taker_cet.1; |
|
|
|
taker_encsig |
|
|
|
.verify(SECP256K1, &cet_sighash, &taker_pk.key, &encryption_point) |
|
|
|
.expect("valid taker cet encsig"); |
|
|
|
} |
|
|
|
|
|
|
|
let lock_descriptor = lock_descriptor(maker_pk, taker_pk); |
|
|
|
|
|
|
|
{ |
|
|
|
let commit_sighash = |
|
|
|
spending_tx_sighash(&maker_cfd_txs.commit.0, &lock_descriptor, lock_amount); |
|
|
|
let commit_encsig = maker_cfd_txs.commit.1; |
|
|
|
commit_encsig |
|
|
|
.verify( |
|
|
|
SECP256K1, |
|
|
|
&commit_sighash, |
|
|
|
&maker_pk.key, |
|
|
|
&taker_publish_pk.key, |
|
|
|
) |
|
|
|
.expect("valid maker commit encsig"); |
|
|
|
}; |
|
|
|
|
|
|
|
{ |
|
|
|
let commit_sighash = |
|
|
|
spending_tx_sighash(&taker_cfd_txs.commit.0, &lock_descriptor, lock_amount); |
|
|
|
let commit_encsig = taker_cfd_txs.commit.1; |
|
|
|
commit_encsig |
|
|
|
.verify( |
|
|
|
SECP256K1, |
|
|
|
&commit_sighash, |
|
|
|
&taker_pk.key, |
|
|
|
&maker_publish_pk.key, |
|
|
|
) |
|
|
|
.expect("valid taker commit encsig"); |
|
|
|
}; |
|
|
|
|
|
|
|
// sign lock transaction
|
|
|
|
|
|
|
|
let mut signed_lock_tx = maker_cfd_txs.lock; |
|
|
|
maker_wallet |
|
|
|
.sign( |
|
|
|
&mut signed_lock_tx, |
|
|
|
SignOptions { |
|
|
|
trust_witness_utxo: true, |
|
|
|
..Default::default() |
|
|
|
}, |
|
|
|
) |
|
|
|
.unwrap(); |
|
|
|
|
|
|
|
taker_wallet |
|
|
|
.sign( |
|
|
|
&mut signed_lock_tx, |
|
|
|
SignOptions { |
|
|
|
trust_witness_utxo: true, |
|
|
|
..Default::default() |
|
|
|
}, |
|
|
|
) |
|
|
|
.unwrap(); |
|
|
|
|
|
|
|
let signed_lock_tx = signed_lock_tx.extract_tx(); |
|
|
|
|
|
|
|
// verify commit transaction
|
|
|
|
|
|
|
|
let commit_tx = maker_cfd_txs.commit.0; |
|
|
|
let maker_sig = maker_cfd_txs.commit.1.decrypt(&taker_publish_sk).unwrap(); |
|
|
|
let taker_sig = taker_cfd_txs.commit.1.decrypt(&maker_publish_sk).unwrap(); |
|
|
|
let signed_commit_tx = finalize_spend_transaction( |
|
|
|
commit_tx, |
|
|
|
&lock_descriptor, |
|
|
|
(maker_pk, maker_sig), |
|
|
|
(taker_pk, taker_sig), |
|
|
|
) |
|
|
|
.unwrap(); |
|
|
|
|
|
|
|
check_tx_fee(&[&signed_lock_tx], &signed_commit_tx).expect("correct fees for commit tx"); |
|
|
|
|
|
|
|
lock_descriptor |
|
|
|
.script_pubkey() |
|
|
|
.verify( |
|
|
|
0, |
|
|
|
lock_amount.as_sat(), |
|
|
|
bitcoin::consensus::serialize(&signed_commit_tx).as_slice(), |
|
|
|
) |
|
|
|
.expect("valid signed commit transaction"); |
|
|
|
|
|
|
|
// verify refund transaction
|
|
|
|
|
|
|
|
let maker_sig = maker_cfd_txs.refund.1; |
|
|
|
let taker_sig = taker_cfd_txs.refund.1; |
|
|
|
let signed_refund_tx = finalize_spend_transaction( |
|
|
|
maker_cfd_txs.refund.0, |
|
|
|
&commit_descriptor, |
|
|
|
(maker_pk, maker_sig), |
|
|
|
(taker_pk, taker_sig), |
|
|
|
) |
|
|
|
.unwrap(); |
|
|
|
|
|
|
|
check_tx_fee(&[&signed_commit_tx], &signed_refund_tx).expect("correct fees for refund tx"); |
|
|
|
|
|
|
|
commit_descriptor |
|
|
|
.script_pubkey() |
|
|
|
.verify( |
|
|
|
0, |
|
|
|
commit_amount.as_sat(), |
|
|
|
bitcoin::consensus::serialize(&signed_refund_tx).as_slice(), |
|
|
|
) |
|
|
|
.expect("valid signed refund transaction"); |
|
|
|
|
|
|
|
// verify cets
|
|
|
|
|
|
|
|
let attestations = ["win".as_bytes(), "lose".as_bytes()] |
|
|
|
.iter() |
|
|
|
.map(|msg| (*msg, oracle.attest(&event, msg))) |
|
|
|
.collect::<HashMap<&[u8], _>>(); |
|
|
|
|
|
|
|
maker_cfd_txs |
|
|
|
.cets |
|
|
|
.into_iter() |
|
|
|
.zip(taker_cfd_txs.cets) |
|
|
|
.try_for_each(|((cet, maker_encsig, msg), (_, taker_encsig, _))| { |
|
|
|
let oracle_sig = attestations |
|
|
|
.get(msg.as_slice()) |
|
|
|
.expect("oracle to sign all messages in test"); |
|
|
|
let (_nonce_pk, signature_scalar) = schnorrsig_decompose(oracle_sig); |
|
|
|
|
|
|
|
let maker_sig = maker_encsig |
|
|
|
.decrypt(&signature_scalar) |
|
|
|
.context("could not decrypt maker encsig on cet")?; |
|
|
|
let taker_sig = taker_encsig |
|
|
|
.decrypt(&signature_scalar) |
|
|
|
.context("could not decrypt taker encsig on cet")?; |
|
|
|
|
|
|
|
let signed_cet = finalize_spend_transaction( |
|
|
|
cet, |
|
|
|
&commit_descriptor, |
|
|
|
(maker_pk, maker_sig), |
|
|
|
(taker_pk, taker_sig), |
|
|
|
)?; |
|
|
|
|
|
|
|
check_tx_fee(&[&signed_commit_tx], &signed_cet).expect("correct fees for cet"); |
|
|
|
|
|
|
|
commit_descriptor |
|
|
|
.script_pubkey() |
|
|
|
.verify( |
|
|
|
0, |
|
|
|
commit_amount.as_sat(), |
|
|
|
bitcoin::consensus::serialize(&signed_cet).as_slice(), |
|
|
|
) |
|
|
|
.context("failed to verify cet") |
|
|
|
}) |
|
|
|
.expect("all cets to be properly signed"); |
|
|
|
|
|
|
|
// verify punishment transactions
|
|
|
|
|
|
|
|
let punish_tx = punish_transaction( |
|
|
|
&commit_descriptor, |
|
|
|
&maker_address, |
|
|
|
maker_cfd_txs.commit.1, |
|
|
|
maker_sk, |
|
|
|
taker_revocation_sk, |
|
|
|
taker_publish_pk, |
|
|
|
&signed_commit_tx, |
|
|
|
) |
|
|
|
.unwrap(); |
|
|
|
|
|
|
|
check_tx_fee(&[&signed_commit_tx], &punish_tx).expect("correct fees for punish tx"); |
|
|
|
|
|
|
|
commit_descriptor |
|
|
|
.script_pubkey() |
|
|
|
.verify( |
|
|
|
0, |
|
|
|
commit_amount.as_sat(), |
|
|
|
bitcoin::consensus::serialize(&punish_tx).as_slice(), |
|
|
|
) |
|
|
|
.expect("valid punish transaction signed by maker"); |
|
|
|
|
|
|
|
let punish_tx = punish_transaction( |
|
|
|
&commit_descriptor, |
|
|
|
&taker_address, |
|
|
|
taker_cfd_txs.commit.1, |
|
|
|
taker_sk, |
|
|
|
maker_revocation_sk, |
|
|
|
maker_publish_pk, |
|
|
|
&signed_commit_tx, |
|
|
|
) |
|
|
|
.unwrap(); |
|
|
|
|
|
|
|
commit_descriptor |
|
|
|
.script_pubkey() |
|
|
|
.verify( |
|
|
|
0, |
|
|
|
commit_amount.as_sat(), |
|
|
|
bitcoin::consensus::serialize(&punish_tx).as_slice(), |
|
|
|
) |
|
|
|
.expect("valid punish transaction signed by taker"); |
|
|
|
} |
|
|
|
|
|
|
|
fn check_tx_fee(input_txs: &[&Transaction], spend_tx: &Transaction) -> Result<()> { |
|
|
|
let input_amount = spend_tx |
|
|
|
.input |
|
|
|