Browse Source

Add renew_cfd_transactions API

integrate-protocol-maker-side
Lucas Soriano del Pino 3 years ago
parent
commit
641faceb8a
No known key found for this signature in database GPG Key ID: EE611E973A1530E7
  1. 116
      cfd_protocol/src/lib.rs
  2. 450
      cfd_protocol/tests/cfds.rs

116
cfd_protocol/src/lib.rs

@ -55,7 +55,7 @@ where
}
}
pub fn build_cfd_transactions(
pub fn create_cfd_transactions(
(maker, maker_punish_params): (PartyParams, PunishParams),
(taker, taker_punish_params): (PartyParams, PunishParams),
oracle_params: OracleParams,
@ -63,13 +63,6 @@ pub fn build_cfd_transactions(
payouts: Vec<Payout>,
identity_sk: SecretKey,
) -> Result<CfdTransactions> {
/// Relative timelock used for every CET.
///
/// This is used to allow parties to punish the publication of revoked commitment transactions.
///
/// TODO: Should this be an argument to this function?
const CET_TIMELOCK: u32 = 12;
let lock_tx = lock_transaction(
maker.lock_psbt.clone(),
taker.lock_psbt.clone(),
@ -78,15 +71,102 @@ pub fn build_cfd_transactions(
maker.lock_amount + taker.lock_amount,
);
build_cfds(
lock_tx,
(
maker.identity_pk,
maker.lock_amount,
maker.address,
maker_punish_params,
),
(
taker.identity_pk,
taker.lock_amount,
taker.address,
taker_punish_params,
),
oracle_params,
refund_timelock,
payouts,
identity_sk,
)
}
pub fn renew_cfd_transactions(
lock_tx: PartiallySignedTransaction,
(maker_pk, maker_lock_amount, maker_address, maker_punish_params): (
PublicKey,
Amount,
Address,
PunishParams,
),
(taker_pk, taker_lock_amount, taker_address, taker_punish_params): (
PublicKey,
Amount,
Address,
PunishParams,
),
oracle_params: OracleParams,
refund_timelock: u32,
payouts: Vec<Payout>,
identity_sk: SecretKey,
) -> Result<CfdTransactions> {
build_cfds(
lock_tx,
(
maker_pk,
maker_lock_amount,
maker_address,
maker_punish_params,
),
(
taker_pk,
taker_lock_amount,
taker_address,
taker_punish_params,
),
oracle_params,
refund_timelock,
payouts,
identity_sk,
)
}
fn build_cfds(
lock_tx: PartiallySignedTransaction,
(maker_pk, maker_lock_amount, maker_address, maker_punish_params): (
PublicKey,
Amount,
Address,
PunishParams,
),
(taker_pk, taker_lock_amount, taker_address, taker_punish_params): (
PublicKey,
Amount,
Address,
PunishParams,
),
oracle_params: OracleParams,
refund_timelock: u32,
payouts: Vec<Payout>,
identity_sk: SecretKey,
) -> Result<CfdTransactions> {
/// Relative timelock used for every CET.
///
/// This is used to allow parties to punish the publication of revoked commitment transactions.
///
/// TODO: Should this be an argument to this function?
const CET_TIMELOCK: u32 = 12;
let commit_tx = CommitTransaction::new(
&lock_tx.global.unsigned_tx,
(
maker.identity_pk,
maker_pk,
maker_punish_params.revocation_pk,
maker_punish_params.publish_pk,
),
(
taker.identity_pk,
taker_pk,
taker_punish_params.revocation_pk,
taker_punish_params.publish_pk,
),
@ -94,9 +174,9 @@ pub fn build_cfd_transactions(
.context("cannot build commit tx")?;
let identity_pk = secp256k1_zkp::PublicKey::from_secret_key(SECP256K1, &identity_sk);
let commit_encsig = if identity_pk == maker.identity_pk.key {
let commit_encsig = if identity_pk == maker_pk.key {
commit_tx.encsign(identity_sk, &taker_punish_params.publish_pk)
} else if identity_pk == taker.identity_pk.key {
} else if identity_pk == taker_pk.key {
commit_tx.encsign(identity_sk, &maker_punish_params.publish_pk)
} else {
bail!("identity sk does not belong to taker or maker")
@ -106,10 +186,10 @@ pub fn build_cfd_transactions(
let tx = RefundTransaction::new(
&commit_tx,
refund_timelock,
&maker.address,
&taker.address,
maker.lock_amount,
taker.lock_amount,
&maker_address,
&taker_address,
maker_lock_amount,
taker_lock_amount,
);
let sighash = tx.sighash().to_message();
@ -125,8 +205,8 @@ pub fn build_cfd_transactions(
let cet = ContractExecutionTransaction::new(
&commit_tx,
payout,
&maker.address,
&taker.address,
&maker_address,
&taker_address,
CET_TIMELOCK,
)?;

450
cfd_protocol/tests/cfds.rs

@ -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

Loading…
Cancel
Save