Browse Source

Add close_transaction API to cfd_protocol lib

fix-bad-api-calls
Lucas Soriano del Pino 3 years ago
parent
commit
8e4d3254f2
No known key found for this signature in database GPG Key ID: EE611E973A1530E7
  1. 7
      cfd_protocol/src/lib.rs
  2. 2
      cfd_protocol/src/protocol.rs
  3. 55
      cfd_protocol/src/protocol/transactions.rs
  4. 70
      cfd_protocol/tests/cfds.rs

7
cfd_protocol/src/lib.rs

@ -5,8 +5,9 @@ pub mod interval;
pub use oracle::{attest, nonce}; pub use oracle::{attest, nonce};
pub use protocol::{ pub use protocol::{
commit_descriptor, compute_adaptor_point, create_cfd_transactions, finalize_spend_transaction, close_transaction, commit_descriptor, compute_adaptor_point, create_cfd_transactions,
lock_descriptor, punish_transaction, renew_cfd_transactions, spending_tx_sighash, finalize_spend_transaction, lock_descriptor, punish_transaction, renew_cfd_transactions,
CfdTransactions, PartyParams, Payout, PunishParams, TransactionExt, WalletExt, spending_tx_sighash, CfdTransactions, PartyParams, Payout, PunishParams, TransactionExt,
WalletExt,
}; };
pub use secp256k1_zkp; pub use secp256k1_zkp;

2
cfd_protocol/src/protocol.rs

@ -1,5 +1,5 @@
pub use transaction_ext::TransactionExt; pub use transaction_ext::TransactionExt;
pub use transactions::punish_transaction; pub use transactions::{close_transaction, punish_transaction};
use crate::interval; use crate::interval;
use crate::protocol::sighash_ext::SigHashExt; use crate::protocol::sighash_ext::SigHashExt;

55
cfd_protocol/src/protocol/transactions.rs

@ -345,6 +345,61 @@ impl RefundTransaction {
} }
} }
/// Build a transaction which closes the CFD contract.
///
/// This transaction spends directly from the lock transaction. Both
/// parties must agree on the split of coins between `maker_amount`
/// and `taker_amount`.
pub fn close_transaction(
lock_descriptor: &Descriptor<PublicKey>,
lock_outpoint: OutPoint,
lock_amount: Amount,
(maker_address, maker_amount): (&Address, Amount),
(taker_address, taker_amount): (&Address, Amount),
) -> Result<(Transaction, secp256k1_zkp::Message)> {
/// Expected size of signed transaction in virtual bytes, plus a
/// buffer to account for different signature lengths.
const SIGNED_VBYTES: f64 = 167.5 + (3.0 * 2.0) / 4.0;
let lock_input = TxIn {
previous_output: lock_outpoint,
..Default::default()
};
// TODO: The fee could take into account the network state in this
// case, since this transaction is to be broadcast immediately
// after building and signing it
let fee = SIGNED_VBYTES * SATS_PER_VBYTE as f64;
let fee_per_party = (fee / 2.0) as u64;
let maker_output = TxOut {
value: maker_amount.as_sat() - fee_per_party,
script_pubkey: maker_address.script_pubkey(),
};
let taker_output = TxOut {
value: taker_amount.as_sat() - fee_per_party,
script_pubkey: taker_address.script_pubkey(),
};
let tx = Transaction {
version: 2,
lock_time: 0,
input: vec![lock_input],
output: vec![maker_output, taker_output],
};
let sighash = SigHashCache::new(&tx)
.signature_hash(
0,
&lock_descriptor.script_code(),
lock_amount.as_sat(),
SigHashType::All,
)
.to_message();
Ok((tx, sighash))
}
pub fn punish_transaction( pub fn punish_transaction(
commit_descriptor: &Descriptor<PublicKey>, commit_descriptor: &Descriptor<PublicKey>,
address: &Address, address: &Address,

70
cfd_protocol/tests/cfds.rs

@ -8,7 +8,7 @@ use bdk::SignOptions;
use bit_vec::BitVec; use bit_vec::BitVec;
use bitcoin::util::psbt::PartiallySignedTransaction; use bitcoin::util::psbt::PartiallySignedTransaction;
use cfd_protocol::{ use cfd_protocol::{
attest, commit_descriptor, compute_adaptor_point, create_cfd_transactions, attest, close_transaction, commit_descriptor, compute_adaptor_point, create_cfd_transactions,
finalize_spend_transaction, interval, lock_descriptor, nonce, punish_transaction, finalize_spend_transaction, interval, lock_descriptor, nonce, punish_transaction,
renew_cfd_transactions, spending_tx_sighash, CfdTransactions, Payout, PunishParams, renew_cfd_transactions, spending_tx_sighash, CfdTransactions, Payout, PunishParams,
TransactionExt, WalletExt, TransactionExt, WalletExt,
@ -262,6 +262,74 @@ fn renew_cfd() {
) )
} }
#[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 = Oracle::new(&mut rng);
let (_, announcement) = announce(&mut rng);
let payouts = vec![Payout::new(
0..=10_000,
Amount::from_btc(1.5).unwrap(),
Amount::from_btc(0.5).unwrap(),
)
.unwrap()]
.concat();
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.public_key(), &announcement.nonce_pks()),
payouts,
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( fn create_cfd_txs(
rng: &mut (impl RngCore + CryptoRng), rng: &mut (impl RngCore + CryptoRng),
(maker_wallet, maker_lock_amount): (&bdk::Wallet<(), bdk::database::MemoryDatabase>, Amount), (maker_wallet, maker_lock_amount): (&bdk::Wallet<(), bdk::database::MemoryDatabase>, Amount),

Loading…
Cancel
Save