|
@ -1,4 +1,5 @@ |
|
|
use anyhow::Result; |
|
|
use anyhow::Result; |
|
|
|
|
|
use bdk::bitcoin::hashes::*; |
|
|
use bdk::bitcoin::Txid; |
|
|
use bdk::bitcoin::Txid; |
|
|
use bdk::SignOptions; |
|
|
use bdk::SignOptions; |
|
|
use bdk::{ |
|
|
use bdk::{ |
|
@ -15,13 +16,12 @@ use bdk::{ |
|
|
miniscript::{descriptor::Wsh, DescriptorTrait}, |
|
|
miniscript::{descriptor::Wsh, DescriptorTrait}, |
|
|
wallet::AddressIndex, |
|
|
wallet::AddressIndex, |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
use rand::RngCore; |
|
|
use rand::RngCore; |
|
|
use rand::{CryptoRng, SeedableRng}; |
|
|
use rand::{CryptoRng, SeedableRng}; |
|
|
use rand_chacha::ChaChaRng; |
|
|
use rand_chacha::ChaChaRng; |
|
|
|
|
|
use secp256k1_zkp::EcdsaAdaptorSignature; |
|
|
|
|
|
use secp256k1_zkp::SECP256K1; |
|
|
use secp256k1_zkp::{self, schnorrsig, Signature}; |
|
|
use secp256k1_zkp::{self, schnorrsig, Signature}; |
|
|
use secp256k1_zkp::{bitcoin_hashes::sha256t_hash_newtype, SECP256K1}; |
|
|
|
|
|
use secp256k1_zkp::{bitcoin_hashes::*, EcdsaAdaptorSignature}; |
|
|
|
|
|
use secp256k1_zkp::{Secp256k1, SecretKey}; |
|
|
use secp256k1_zkp::{Secp256k1, SecretKey}; |
|
|
use std::collections::HashMap; |
|
|
use std::collections::HashMap; |
|
|
|
|
|
|
|
@ -149,6 +149,7 @@ impl ToString for Message { |
|
|
#[cfg(test)] |
|
|
#[cfg(test)] |
|
|
mod tests { |
|
|
mod tests { |
|
|
use super::*; |
|
|
use super::*; |
|
|
|
|
|
use anyhow::Context; |
|
|
|
|
|
|
|
|
/// Refund transaction fee. It is paid evenly by the maker and the
|
|
|
/// Refund transaction fee. It is paid evenly by the maker and the
|
|
|
/// taker.
|
|
|
/// taker.
|
|
@ -222,8 +223,22 @@ mod tests { |
|
|
let lock_tx = |
|
|
let lock_tx = |
|
|
LockTransaction::new(maker_psbt, taker_psbt, maker_pk, taker_pk, dlc_amount).unwrap(); |
|
|
LockTransaction::new(maker_psbt, taker_psbt, maker_pk, taker_pk, dlc_amount).unwrap(); |
|
|
|
|
|
|
|
|
let refund_tx = RefundTransaction::new( |
|
|
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 commit_tx = CommitTransaction::new( |
|
|
&lock_tx, |
|
|
&lock_tx, |
|
|
|
|
|
(maker_pk, maker_revocation_pk, maker_publish_pk), |
|
|
|
|
|
(taker_pk, taker_revocation_pk, taker_publish_pk), |
|
|
|
|
|
) |
|
|
|
|
|
.unwrap(); |
|
|
|
|
|
|
|
|
|
|
|
// Construct refund TX
|
|
|
|
|
|
let refund_tx = RefundTransaction::new( |
|
|
|
|
|
&commit_tx, |
|
|
refund_timelock, |
|
|
refund_timelock, |
|
|
&maker_address, |
|
|
&maker_address, |
|
|
&taker_address, |
|
|
&taker_address, |
|
@ -231,10 +246,17 @@ mod tests { |
|
|
taker_dlc_amount, |
|
|
taker_dlc_amount, |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// Construct CET TXs
|
|
|
let cets = payouts |
|
|
let cets = payouts |
|
|
.iter() |
|
|
.iter() |
|
|
.map(|payout| { |
|
|
.map(|payout| { |
|
|
ContractExecutionTransaction::new(&lock_tx, payout, &maker_address, &taker_address) |
|
|
ContractExecutionTransaction::new( |
|
|
|
|
|
&commit_tx, |
|
|
|
|
|
payout, |
|
|
|
|
|
&maker_address, |
|
|
|
|
|
&taker_address, |
|
|
|
|
|
12, |
|
|
|
|
|
) |
|
|
}) |
|
|
}) |
|
|
.collect::<Vec<_>>(); |
|
|
.collect::<Vec<_>>(); |
|
|
|
|
|
|
|
@ -319,6 +341,28 @@ mod tests { |
|
|
.collect::<Result<Vec<_>>>() |
|
|
.collect::<Result<Vec<_>>>() |
|
|
.expect("valid taker cet encsigs"); |
|
|
.expect("valid taker cet encsigs"); |
|
|
|
|
|
|
|
|
|
|
|
// sign commit transaction
|
|
|
|
|
|
|
|
|
|
|
|
let maker_commit_sig = { |
|
|
|
|
|
let sighash = secp256k1_zkp::Message::from_slice(&commit_tx.sighash()).unwrap(); |
|
|
|
|
|
let sig = secp.sign(&sighash, &maker_sk); |
|
|
|
|
|
|
|
|
|
|
|
secp.verify(&sighash, &sig, &maker_pk.key) |
|
|
|
|
|
.expect("valid maker commit sig"); |
|
|
|
|
|
sig |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
let taker_commit_sig = { |
|
|
|
|
|
let sighash = secp256k1_zkp::Message::from_slice(&commit_tx.sighash()).unwrap(); |
|
|
|
|
|
let sig = secp.sign(&sighash, &taker_sk); |
|
|
|
|
|
|
|
|
|
|
|
secp.verify(&sighash, &sig, &taker_pk.key) |
|
|
|
|
|
.expect("valid taker commit sig"); |
|
|
|
|
|
sig |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// sign lock transaction
|
|
|
|
|
|
|
|
|
let mut signed_lock_tx = lock_tx.to_psbt(); |
|
|
let mut signed_lock_tx = lock_tx.to_psbt(); |
|
|
maker_wallet |
|
|
maker_wallet |
|
|
.sign( |
|
|
.sign( |
|
@ -340,17 +384,36 @@ mod tests { |
|
|
) |
|
|
) |
|
|
.unwrap(); |
|
|
.unwrap(); |
|
|
|
|
|
|
|
|
|
|
|
let signed_commit_tx = commit_tx |
|
|
|
|
|
.clone() |
|
|
|
|
|
.add_signatures((maker_pk, maker_commit_sig), (taker_pk, taker_commit_sig)) |
|
|
|
|
|
.expect("To be signed"); |
|
|
|
|
|
|
|
|
|
|
|
let _ = lock_tx |
|
|
|
|
|
.descriptor() |
|
|
|
|
|
.address(Network::Regtest) |
|
|
|
|
|
.expect("can derive address from descriptor") |
|
|
|
|
|
.script_pubkey() |
|
|
|
|
|
.verify( |
|
|
|
|
|
0, |
|
|
|
|
|
lock_tx.amount().as_sat(), |
|
|
|
|
|
bitcoin::consensus::serialize(&signed_commit_tx).as_slice(), |
|
|
|
|
|
) |
|
|
|
|
|
.expect("valid signed commit transaction"); |
|
|
|
|
|
|
|
|
let signed_refund_tx = refund_tx |
|
|
let signed_refund_tx = refund_tx |
|
|
.add_signatures((maker_pk, maker_refund_sig), (taker_pk, taker_refund_sig)) |
|
|
.add_signatures((maker_pk, maker_refund_sig), (taker_pk, taker_refund_sig)) |
|
|
.unwrap(); |
|
|
.unwrap(); |
|
|
let _ = lock_tx |
|
|
|
|
|
|
|
|
let commit_tx_amount = commit_tx.amount().as_sat(); |
|
|
|
|
|
let _ = commit_tx |
|
|
.descriptor() |
|
|
.descriptor() |
|
|
.address(Network::Regtest) |
|
|
.address(Network::Regtest) |
|
|
.expect("can derive address from descriptor") |
|
|
.expect("can derive address from descriptor") |
|
|
.script_pubkey() |
|
|
.script_pubkey() |
|
|
.verify( |
|
|
.verify( |
|
|
0, |
|
|
0, |
|
|
dlc_amount.as_sat(), |
|
|
commit_tx_amount, |
|
|
bitcoin::consensus::serialize(&signed_refund_tx).as_slice(), |
|
|
bitcoin::consensus::serialize(&signed_refund_tx).as_slice(), |
|
|
) |
|
|
) |
|
|
.expect("valid signed refund transaction"); |
|
|
.expect("valid signed refund transaction"); |
|
@ -393,14 +456,14 @@ mod tests { |
|
|
signed_cets |
|
|
signed_cets |
|
|
.iter() |
|
|
.iter() |
|
|
.try_for_each(|cet| { |
|
|
.try_for_each(|cet| { |
|
|
lock_tx |
|
|
commit_tx |
|
|
.descriptor() |
|
|
.descriptor() |
|
|
.address(Network::Regtest) |
|
|
.address(Network::Regtest) |
|
|
.expect("can derive address from descriptor") |
|
|
.expect("can derive address from descriptor") |
|
|
.script_pubkey() |
|
|
.script_pubkey() |
|
|
.verify( |
|
|
.verify( |
|
|
0, |
|
|
0, |
|
|
dlc_amount.as_sat(), |
|
|
commit_tx_amount, |
|
|
bitcoin::consensus::serialize(&cet).as_slice(), |
|
|
bitcoin::consensus::serialize(&cet).as_slice(), |
|
|
) |
|
|
) |
|
|
}) |
|
|
}) |
|
@ -455,18 +518,20 @@ mod tests { |
|
|
inner: Transaction, |
|
|
inner: Transaction, |
|
|
message: Message, |
|
|
message: Message, |
|
|
sighash: SigHash, |
|
|
sighash: SigHash, |
|
|
lock_output_descriptor: Descriptor<PublicKey>, |
|
|
commit_descriptor: Descriptor<PublicKey>, |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
impl ContractExecutionTransaction { |
|
|
impl ContractExecutionTransaction { |
|
|
fn new( |
|
|
fn new( |
|
|
lock_tx: &LockTransaction, |
|
|
commit_tx: &CommitTransaction, |
|
|
payout: &Payout, |
|
|
payout: &Payout, |
|
|
maker_address: &Address, |
|
|
maker_address: &Address, |
|
|
taker_address: &Address, |
|
|
taker_address: &Address, |
|
|
|
|
|
relative_timelock_in_blocks: u32, |
|
|
) -> Self { |
|
|
) -> Self { |
|
|
let dlc_input = TxIn { |
|
|
let commit_input = TxIn { |
|
|
previous_output: lock_tx.dlc_outpoint(), |
|
|
previous_output: commit_tx.outpoint(), |
|
|
|
|
|
sequence: relative_timelock_in_blocks, |
|
|
..Default::default() |
|
|
..Default::default() |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
@ -475,14 +540,14 @@ mod tests { |
|
|
let tx = Transaction { |
|
|
let tx = Transaction { |
|
|
version: 2, |
|
|
version: 2, |
|
|
lock_time: 0, |
|
|
lock_time: 0, |
|
|
input: vec![dlc_input], |
|
|
input: vec![commit_input], |
|
|
output: payout.to_txouts(maker_address, taker_address, fee), |
|
|
output: payout.to_txouts(maker_address, taker_address, fee), |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
let sighash = SigHashCache::new(&tx).signature_hash( |
|
|
let sighash = SigHashCache::new(&tx).signature_hash( |
|
|
0, |
|
|
0, |
|
|
&lock_tx.dlc_descriptor.script_code(), |
|
|
&commit_tx.descriptor.script_code(), |
|
|
lock_tx.dlc_amount.as_sat(), |
|
|
commit_tx.amount.as_sat(), |
|
|
SigHashType::All, |
|
|
SigHashType::All, |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
@ -490,7 +555,7 @@ mod tests { |
|
|
inner: tx, |
|
|
inner: tx, |
|
|
message: payout.message, |
|
|
message: payout.message, |
|
|
sighash, |
|
|
sighash, |
|
|
lock_output_descriptor: lock_tx.descriptor(), |
|
|
commit_descriptor: commit_tx.descriptor(), |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -556,31 +621,161 @@ mod tests { |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
let mut tx_refund = self.inner; |
|
|
let mut tx_refund = self.inner; |
|
|
self.lock_output_descriptor |
|
|
self.commit_descriptor |
|
|
.satisfy(&mut tx_refund.input[0], satisfier)?; |
|
|
.satisfy(&mut tx_refund.input[0], satisfier)?; |
|
|
|
|
|
|
|
|
Ok(tx_refund) |
|
|
Ok(tx_refund) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)] |
|
|
|
|
|
struct PunishTransaction { |
|
|
|
|
|
inner: Transaction, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
impl PunishTransaction { |
|
|
|
|
|
fn new( |
|
|
|
|
|
commit_tx: &CommitTransaction, |
|
|
|
|
|
address: &Address, |
|
|
|
|
|
amount: Amount, |
|
|
|
|
|
encisg: EcdsaAdaptorSignature, |
|
|
|
|
|
sk: SecretKey, |
|
|
|
|
|
(revocation_them_sk, revocation_them_pk): (SecretKey, PublicKey), |
|
|
|
|
|
publishing_them_pk: PublicKey, |
|
|
|
|
|
revoked_commit_tx: Transaction, |
|
|
|
|
|
) -> Result<Self> { |
|
|
|
|
|
// CommitTransaction has only one input
|
|
|
|
|
|
let input = revoked_commit_tx.input[0].clone(); |
|
|
|
|
|
|
|
|
|
|
|
// Extract all signatures from witness stack
|
|
|
|
|
|
let mut sigs = Vec::new(); |
|
|
|
|
|
for witness in input.witness.iter() { |
|
|
|
|
|
let witness = witness.as_slice(); |
|
|
|
|
|
|
|
|
|
|
|
let res = bitcoin::secp256k1::Signature::from_der(&witness[..witness.len() - 1]); |
|
|
|
|
|
match res { |
|
|
|
|
|
Ok(sig) => sigs.push(sig), |
|
|
|
|
|
Err(_) => { |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if sigs.is_empty() { |
|
|
|
|
|
// No signature found, this should fail
|
|
|
|
|
|
unimplemented!() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Attempt to extract y_other from every signature
|
|
|
|
|
|
let publishing_them_sk = sigs |
|
|
|
|
|
.into_iter() |
|
|
|
|
|
.find_map(|sig| { |
|
|
|
|
|
encisg |
|
|
|
|
|
.recover(&SECP256K1, &sig, &publishing_them_pk.key) |
|
|
|
|
|
.ok() |
|
|
|
|
|
}) |
|
|
|
|
|
.context("Could not recover secret key from revoked transaction")?; |
|
|
|
|
|
|
|
|
|
|
|
// Fixme: need to subtract tx fee otherwise we won't be able to publish this transaction.
|
|
|
|
|
|
let mut punish_tx = { |
|
|
|
|
|
let output = TxOut { |
|
|
|
|
|
value: commit_tx.amount().as_sat(), |
|
|
|
|
|
script_pubkey: address.script_pubkey(), |
|
|
|
|
|
}; |
|
|
|
|
|
Transaction { |
|
|
|
|
|
version: 2, |
|
|
|
|
|
lock_time: 0, |
|
|
|
|
|
input: vec![TxIn { |
|
|
|
|
|
previous_output: commit_tx.outpoint(), |
|
|
|
|
|
..Default::default() |
|
|
|
|
|
}], |
|
|
|
|
|
output: vec![output], |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
let digest = Self::compute_digest(&punish_tx, &commit_tx); |
|
|
|
|
|
|
|
|
|
|
|
let satisfier = { |
|
|
|
|
|
let mut satisfier = HashMap::with_capacity(3); |
|
|
|
|
|
|
|
|
|
|
|
let pk = bitcoin::secp256k1::PublicKey::from_secret_key(SECP256K1, &sk); |
|
|
|
|
|
let pk_hash = hash160::Hash::hash(&pk.serialize()[..]); |
|
|
|
|
|
let pk = bitcoin::PublicKey { |
|
|
|
|
|
compressed: true, |
|
|
|
|
|
key: pk, |
|
|
|
|
|
}; |
|
|
|
|
|
let sig_sk = SECP256K1.sign(&secp256k1_zkp::Message::from_slice(&digest)?, &sk); |
|
|
|
|
|
|
|
|
|
|
|
let publishing_them_pk_hash = |
|
|
|
|
|
hash160::Hash::hash(&publishing_them_pk.key.serialize()[..]); |
|
|
|
|
|
let sig_publishing_other = SECP256K1.sign( |
|
|
|
|
|
&secp256k1_zkp::Message::from_slice(&digest)?, |
|
|
|
|
|
&publishing_them_sk, |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
let revocation_them_pk_hash = |
|
|
|
|
|
hash160::Hash::hash(&revocation_them_pk.key.serialize()[..]); |
|
|
|
|
|
let sig_revocation_other = SECP256K1.sign( |
|
|
|
|
|
&secp256k1_zkp::Message::from_slice(&digest)?, |
|
|
|
|
|
&revocation_them_sk, |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
satisfier.insert(pk_hash, (pk, (sig_sk.into(), SigHashType::All))); |
|
|
|
|
|
|
|
|
|
|
|
satisfier.insert( |
|
|
|
|
|
publishing_them_pk_hash, |
|
|
|
|
|
( |
|
|
|
|
|
publishing_them_pk, |
|
|
|
|
|
(sig_publishing_other.into(), SigHashType::All), |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
satisfier.insert( |
|
|
|
|
|
revocation_them_pk_hash, |
|
|
|
|
|
( |
|
|
|
|
|
revocation_them_pk, |
|
|
|
|
|
(sig_revocation_other.into(), SigHashType::All), |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
satisfier |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
commit_tx |
|
|
|
|
|
.descriptor() |
|
|
|
|
|
.satisfy(&mut punish_tx.input[0], satisfier)?; |
|
|
|
|
|
|
|
|
|
|
|
Ok(Self { inner: punish_tx }) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn compute_digest(punish_tx: &Transaction, commit_tx: &CommitTransaction) -> SigHash { |
|
|
|
|
|
SigHashCache::new(punish_tx).signature_hash( |
|
|
|
|
|
0, |
|
|
|
|
|
&commit_tx.descriptor().script_code(), |
|
|
|
|
|
commit_tx.amount().as_sat(), |
|
|
|
|
|
SigHashType::All, |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)] |
|
|
#[derive(Debug, Clone)] |
|
|
struct RefundTransaction { |
|
|
struct RefundTransaction { |
|
|
inner: Transaction, |
|
|
inner: Transaction, |
|
|
sighash: SigHash, |
|
|
sighash: SigHash, |
|
|
lock_output_descriptor: Descriptor<PublicKey>, |
|
|
commit_output_descriptor: Descriptor<PublicKey>, |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
impl RefundTransaction { |
|
|
impl RefundTransaction { |
|
|
fn new( |
|
|
fn new( |
|
|
lock_tx: &LockTransaction, |
|
|
commit_tx: &CommitTransaction, |
|
|
lock_time: u32, // FIXME: Must be relative once we go off-chain (goes on the input)
|
|
|
relative_locktime_in_blocks: u32, |
|
|
maker_address: &Address, |
|
|
maker_address: &Address, |
|
|
taker_address: &Address, |
|
|
taker_address: &Address, |
|
|
maker_amount: Amount, |
|
|
maker_amount: Amount, |
|
|
taker_amount: Amount, |
|
|
taker_amount: Amount, |
|
|
) -> Self { |
|
|
) -> Self { |
|
|
let dlc_input = TxIn { |
|
|
let dlc_input = TxIn { |
|
|
previous_output: lock_tx.dlc_outpoint(), |
|
|
previous_output: commit_tx.outpoint(), |
|
|
|
|
|
sequence: relative_locktime_in_blocks, |
|
|
..Default::default() |
|
|
..Default::default() |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
@ -598,24 +793,24 @@ mod tests { |
|
|
|
|
|
|
|
|
let tx = Transaction { |
|
|
let tx = Transaction { |
|
|
version: 2, |
|
|
version: 2, |
|
|
lock_time, |
|
|
lock_time: 0, |
|
|
input: vec![dlc_input], |
|
|
input: vec![dlc_input], |
|
|
output: vec![maker_output, taker_output], |
|
|
output: vec![maker_output, taker_output], |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
let lock_output_descriptor = lock_tx.dlc_descriptor.clone(); |
|
|
let commit_output_descriptor = commit_tx.descriptor().clone(); |
|
|
|
|
|
|
|
|
let sighash = SigHashCache::new(&tx).signature_hash( |
|
|
let sighash = SigHashCache::new(&tx).signature_hash( |
|
|
0, |
|
|
0, |
|
|
&lock_tx.dlc_descriptor.script_code(), |
|
|
&commit_tx.descriptor().script_code(), |
|
|
lock_tx.dlc_amount.as_sat(), |
|
|
commit_tx.amount().as_sat(), |
|
|
SigHashType::All, |
|
|
SigHashType::All, |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
Self { |
|
|
Self { |
|
|
inner: tx, |
|
|
inner: tx, |
|
|
sighash, |
|
|
sighash, |
|
|
lock_output_descriptor, |
|
|
commit_output_descriptor, |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -639,18 +834,162 @@ mod tests { |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
let mut tx_refund = self.inner; |
|
|
let mut tx_refund = self.inner; |
|
|
self.lock_output_descriptor |
|
|
self.commit_output_descriptor |
|
|
.satisfy(&mut tx_refund.input[0], satisfier)?; |
|
|
.satisfy(&mut tx_refund.input[0], satisfier)?; |
|
|
|
|
|
|
|
|
Ok(tx_refund) |
|
|
Ok(tx_refund) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)] |
|
|
|
|
|
struct CommitTransaction { |
|
|
|
|
|
inner: Transaction, |
|
|
|
|
|
descriptor: Descriptor<PublicKey>, |
|
|
|
|
|
amount: Amount, |
|
|
|
|
|
sighash: SigHash, |
|
|
|
|
|
lock_descriptor: Descriptor<PublicKey>, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
impl CommitTransaction { |
|
|
|
|
|
fn new( |
|
|
|
|
|
lock_tx: &LockTransaction, |
|
|
|
|
|
(maker_own_pk, maker_rev_pk, maker_publish_pk): (PublicKey, PublicKey, PublicKey), |
|
|
|
|
|
(taker_own_pk, taker_rev_pk, taker_publish_pk): (PublicKey, PublicKey, PublicKey), |
|
|
|
|
|
) -> Result<Self> { |
|
|
|
|
|
// FIXME: Fee to be paid by leftover lock output
|
|
|
|
|
|
let amount = lock_tx.amount(); |
|
|
|
|
|
|
|
|
|
|
|
let lock_input = TxIn { |
|
|
|
|
|
previous_output: lock_tx.lock_outpoint(), |
|
|
|
|
|
..Default::default() |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
let descriptor = Self::build_descriptor( |
|
|
|
|
|
(maker_own_pk, maker_rev_pk, maker_publish_pk), |
|
|
|
|
|
(taker_own_pk, taker_rev_pk, taker_publish_pk), |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
let output = TxOut { |
|
|
|
|
|
value: lock_tx.amount().as_sat(), |
|
|
|
|
|
script_pubkey: descriptor |
|
|
|
|
|
.address(Network::Regtest) |
|
|
|
|
|
.expect("can derive address from descriptor") |
|
|
|
|
|
.script_pubkey(), |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
let inner = Transaction { |
|
|
|
|
|
version: 2, |
|
|
|
|
|
lock_time: 0, |
|
|
|
|
|
input: vec![lock_input], |
|
|
|
|
|
output: vec![output], |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
let sighash = SigHashCache::new(&inner).signature_hash( |
|
|
|
|
|
0, |
|
|
|
|
|
&lock_tx.descriptor().script_code(), |
|
|
|
|
|
lock_tx.amount().as_sat(), |
|
|
|
|
|
SigHashType::All, |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
Ok(Self { |
|
|
|
|
|
inner, |
|
|
|
|
|
descriptor, |
|
|
|
|
|
lock_descriptor: lock_tx.descriptor(), |
|
|
|
|
|
amount, |
|
|
|
|
|
sighash, |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn outpoint(&self) -> OutPoint { |
|
|
|
|
|
let txid = self.inner.txid(); |
|
|
|
|
|
let vout = self |
|
|
|
|
|
.inner |
|
|
|
|
|
.output |
|
|
|
|
|
.iter() |
|
|
|
|
|
.position(|out| out.script_pubkey == self.descriptor.script_pubkey()) |
|
|
|
|
|
.expect("to find dlc output in lock tx"); |
|
|
|
|
|
|
|
|
|
|
|
OutPoint { |
|
|
|
|
|
txid, |
|
|
|
|
|
vout: vout as u32, |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn build_descriptor( |
|
|
|
|
|
(maker_own_pk, maker_rev_pk, maker_publish_pk): (PublicKey, PublicKey, PublicKey), |
|
|
|
|
|
(taker_own_pk, taker_rev_pk, taker_publish_pk): (PublicKey, PublicKey, PublicKey), |
|
|
|
|
|
) -> Descriptor<PublicKey> { |
|
|
|
|
|
// TODO: Optimize miniscript
|
|
|
|
|
|
|
|
|
|
|
|
let maker_own_pk_hash = hash160::Hash::hash(&maker_own_pk.key.serialize()[..]); |
|
|
|
|
|
let maker_own_pk = (&maker_own_pk.key.serialize().to_vec()).to_hex(); |
|
|
|
|
|
let taker_own_pk_hash = hash160::Hash::hash(&taker_own_pk.key.serialize()[..]); |
|
|
|
|
|
let taker_own_pk = (&taker_own_pk.key.serialize().to_vec()).to_hex(); |
|
|
|
|
|
|
|
|
|
|
|
let maker_rev_pk_hash = hash160::Hash::hash(&maker_rev_pk.key.serialize()[..]); |
|
|
|
|
|
let taker_rev_pk_hash = hash160::Hash::hash(&taker_rev_pk.key.serialize()[..]); |
|
|
|
|
|
|
|
|
|
|
|
let maker_publish_pk_hash = hash160::Hash::hash(&maker_publish_pk.key.serialize()[..]); |
|
|
|
|
|
let taker_publish_pk_hash = hash160::Hash::hash(&taker_publish_pk.key.serialize()[..]); |
|
|
|
|
|
|
|
|
|
|
|
let cet_or_refund_condition = |
|
|
|
|
|
format!("and_v(v:pk({}),pk_k({}))", maker_own_pk, taker_own_pk); |
|
|
|
|
|
let maker_punish_condition = format!( |
|
|
|
|
|
"and_v(v:pkh({}),and_v(v:pkh({}),pk_h({})))", |
|
|
|
|
|
maker_own_pk_hash, taker_publish_pk_hash, taker_rev_pk_hash |
|
|
|
|
|
); |
|
|
|
|
|
let taker_punish_condition = format!( |
|
|
|
|
|
"and_v(v:pkh({}),and_v(v:pkh({}),pk_h({})))", |
|
|
|
|
|
taker_own_pk_hash, maker_publish_pk_hash, maker_rev_pk_hash |
|
|
|
|
|
); |
|
|
|
|
|
let descriptor_str = format!( |
|
|
|
|
|
"wsh(c:or_i(or_i({},{}),{}))", |
|
|
|
|
|
maker_punish_condition, taker_punish_condition, cet_or_refund_condition |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
descriptor_str.parse().expect("a valid miniscript") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn amount(&self) -> Amount { |
|
|
|
|
|
self.amount |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn descriptor(&self) -> Descriptor<PublicKey> { |
|
|
|
|
|
self.descriptor.clone() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn sighash(&self) -> SigHash { |
|
|
|
|
|
self.sighash |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
pub fn add_signatures( |
|
|
|
|
|
self, |
|
|
|
|
|
(maker_pk, maker_sig): (PublicKey, Signature), |
|
|
|
|
|
(taker_pk, taker_sig): (PublicKey, Signature), |
|
|
|
|
|
) -> Result<Transaction> { |
|
|
|
|
|
let satisfier = { |
|
|
|
|
|
let mut satisfier = HashMap::with_capacity(2); |
|
|
|
|
|
|
|
|
|
|
|
// The order in which these are inserted doesn't matter
|
|
|
|
|
|
satisfier.insert(maker_pk, (maker_sig, SigHashType::All)); |
|
|
|
|
|
satisfier.insert(taker_pk, (taker_sig, SigHashType::All)); |
|
|
|
|
|
|
|
|
|
|
|
satisfier |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
let mut tx_commit = self.inner; |
|
|
|
|
|
self.lock_descriptor |
|
|
|
|
|
.satisfy(&mut tx_commit.input[0], satisfier)?; |
|
|
|
|
|
|
|
|
|
|
|
Ok(tx_commit) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)] |
|
|
#[derive(Debug, Clone)] |
|
|
struct LockTransaction { |
|
|
struct LockTransaction { |
|
|
inner: PartiallySignedTransaction, |
|
|
inner: PartiallySignedTransaction, |
|
|
dlc_descriptor: Descriptor<PublicKey>, |
|
|
lock_descriptor: Descriptor<PublicKey>, |
|
|
dlc_amount: Amount, |
|
|
amount: Amount, |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
impl LockTransaction { |
|
|
impl LockTransaction { |
|
@ -659,9 +998,9 @@ mod tests { |
|
|
taker_psbt: PartiallySignedTransaction, |
|
|
taker_psbt: PartiallySignedTransaction, |
|
|
maker_pk: PublicKey, |
|
|
maker_pk: PublicKey, |
|
|
taker_pk: PublicKey, |
|
|
taker_pk: PublicKey, |
|
|
dlc_amount: Amount, |
|
|
amount: Amount, |
|
|
) -> Result<Self> { |
|
|
) -> Result<Self> { |
|
|
let dlc_descriptor = build_dlc_descriptor(maker_pk, taker_pk); |
|
|
let lock_descriptor = Self::build_descriptor(maker_pk, taker_pk); |
|
|
|
|
|
|
|
|
let maker_change = maker_psbt |
|
|
let maker_change = maker_psbt |
|
|
.global |
|
|
.global |
|
@ -679,9 +1018,9 @@ mod tests { |
|
|
.filter(|out| !out.script_pubkey.is_empty()) |
|
|
.filter(|out| !out.script_pubkey.is_empty()) |
|
|
.collect(); |
|
|
.collect(); |
|
|
|
|
|
|
|
|
let dlc_output = TxOut { |
|
|
let lock_output = TxOut { |
|
|
value: dlc_amount.as_sat(), |
|
|
value: amount.as_sat(), |
|
|
script_pubkey: dlc_descriptor |
|
|
script_pubkey: lock_descriptor |
|
|
.address(Network::Regtest) |
|
|
.address(Network::Regtest) |
|
|
.expect("can derive address from descriptor") |
|
|
.expect("can derive address from descriptor") |
|
|
.script_pubkey(), |
|
|
.script_pubkey(), |
|
@ -695,7 +1034,7 @@ mod tests { |
|
|
taker_psbt.global.unsigned_tx.input, |
|
|
taker_psbt.global.unsigned_tx.input, |
|
|
] |
|
|
] |
|
|
.concat(), |
|
|
.concat(), |
|
|
output: vec![vec![dlc_output], maker_change, taker_change].concat(), |
|
|
output: vec![vec![lock_output], maker_change, taker_change].concat(), |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
let inner = PartiallySignedTransaction { |
|
|
let inner = PartiallySignedTransaction { |
|
@ -706,12 +1045,12 @@ mod tests { |
|
|
|
|
|
|
|
|
Ok(Self { |
|
|
Ok(Self { |
|
|
inner, |
|
|
inner, |
|
|
dlc_descriptor, |
|
|
lock_descriptor, |
|
|
dlc_amount, |
|
|
amount, |
|
|
}) |
|
|
}) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
fn dlc_outpoint(&self) -> OutPoint { |
|
|
fn lock_outpoint(&self) -> OutPoint { |
|
|
let txid = self.inner.global.unsigned_tx.txid(); |
|
|
let txid = self.inner.global.unsigned_tx.txid(); |
|
|
let vout = self |
|
|
let vout = self |
|
|
.inner |
|
|
.inner |
|
@ -719,7 +1058,7 @@ mod tests { |
|
|
.unsigned_tx |
|
|
.unsigned_tx |
|
|
.output |
|
|
.output |
|
|
.iter() |
|
|
.iter() |
|
|
.position(|out| out.script_pubkey == self.dlc_descriptor.script_pubkey()) |
|
|
.position(|out| out.script_pubkey == self.lock_descriptor.script_pubkey()) |
|
|
.expect("to find dlc output in lock tx"); |
|
|
.expect("to find dlc output in lock tx"); |
|
|
|
|
|
|
|
|
OutPoint { |
|
|
OutPoint { |
|
@ -733,7 +1072,26 @@ mod tests { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
fn descriptor(&self) -> Descriptor<PublicKey> { |
|
|
fn descriptor(&self) -> Descriptor<PublicKey> { |
|
|
self.dlc_descriptor.clone() |
|
|
self.lock_descriptor.clone() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn build_descriptor(maker_pk: PublicKey, taker_pk: PublicKey) -> Descriptor<PublicKey> { |
|
|
|
|
|
const MINISCRIPT_TEMPLATE: &str = "c:and_v(v:pk(A),pk_k(B))"; |
|
|
|
|
|
|
|
|
|
|
|
let maker_pk = ToHex::to_hex(&maker_pk.key); |
|
|
|
|
|
let taker_pk = ToHex::to_hex(&taker_pk.key); |
|
|
|
|
|
|
|
|
|
|
|
let miniscript = MINISCRIPT_TEMPLATE |
|
|
|
|
|
.replace("A", &maker_pk) |
|
|
|
|
|
.replace("B", &taker_pk); |
|
|
|
|
|
|
|
|
|
|
|
let miniscript = miniscript.parse().expect("a valid miniscript"); |
|
|
|
|
|
|
|
|
|
|
|
Descriptor::Wsh(Wsh::new(miniscript).expect("a valid descriptor")) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn amount(&self) -> Amount { |
|
|
|
|
|
self.amount |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -852,21 +1210,6 @@ mod tests { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
fn build_dlc_descriptor(maker_pk: PublicKey, taker_pk: PublicKey) -> Descriptor<PublicKey> { |
|
|
|
|
|
const MINISCRIPT_TEMPLATE: &str = "c:and_v(v:pk(A),pk_k(B))"; |
|
|
|
|
|
|
|
|
|
|
|
let maker_pk = ToHex::to_hex(&maker_pk.key); |
|
|
|
|
|
let taker_pk = ToHex::to_hex(&taker_pk.key); |
|
|
|
|
|
|
|
|
|
|
|
let miniscript = MINISCRIPT_TEMPLATE |
|
|
|
|
|
.replace("A", &maker_pk) |
|
|
|
|
|
.replace("B", &taker_pk); |
|
|
|
|
|
|
|
|
|
|
|
let miniscript = miniscript.parse().expect("a valid miniscript"); |
|
|
|
|
|
|
|
|
|
|
|
Descriptor::Wsh(Wsh::new(miniscript).expect("a valid descriptor")) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn make_keypair<R>(rng: &mut R) -> (SecretKey, PublicKey) |
|
|
fn make_keypair<R>(rng: &mut R) -> (SecretKey, PublicKey) |
|
|
where |
|
|
where |
|
|
R: RngCore + CryptoRng, |
|
|
R: RngCore + CryptoRng, |
|
|