|
|
@ -1,20 +1,28 @@ |
|
|
|
#[cfg(test)] |
|
|
|
mod tests { |
|
|
|
use anyhow::Result; |
|
|
|
use bdk::bitcoin::Txid; |
|
|
|
use bdk::SignOptions; |
|
|
|
use bdk::{ |
|
|
|
bitcoin::{ |
|
|
|
hashes::hex::ToHex, |
|
|
|
secp256k1::{All, Secp256k1, SecretKey}, |
|
|
|
util::bip143::SigHashCache, |
|
|
|
util::{bip32::ExtendedPrivKey, psbt::PartiallySignedTransaction}, |
|
|
|
Address, Amount, Network, OutPoint, PrivateKey, PublicKey, Script, Transaction, TxIn, |
|
|
|
TxOut, |
|
|
|
Address, Amount, Network, OutPoint, PrivateKey, PublicKey, Script, SigHash, |
|
|
|
SigHashType, Transaction, TxIn, TxOut, |
|
|
|
}, |
|
|
|
descriptor::Descriptor, |
|
|
|
miniscript::{descriptor::Wsh, DescriptorTrait}, |
|
|
|
wallet::AddressIndex, |
|
|
|
}; |
|
|
|
use rand::{CryptoRng, RngCore, SeedableRng}; |
|
|
|
use bitcoin::util::psbt::Global; |
|
|
|
use rand::RngCore; |
|
|
|
use rand::{CryptoRng, SeedableRng}; |
|
|
|
use rand_chacha::ChaChaRng; |
|
|
|
use secp256k1_zkp::{self, schnorrsig}; |
|
|
|
use secp256k1_zkp::{bitcoin_hashes::sha256t_hash_newtype, SECP256K1}; |
|
|
|
use secp256k1_zkp::{bitcoin_hashes::*, EcdsaAdaptorSignature}; |
|
|
|
use secp256k1_zkp::{Secp256k1, SecretKey}; |
|
|
|
|
|
|
|
/// Refund transaction fee. It is paid evenly by the maker and the
|
|
|
|
/// taker.
|
|
|
@ -27,6 +35,9 @@ mod tests { |
|
|
|
/// transaction consists of 1 input and 2 outputs.
|
|
|
|
const REFUND_TX_FEE: u64 = 10_000; |
|
|
|
|
|
|
|
/// In satoshi per vbyte.
|
|
|
|
const MIN_RELAY_FEE: u64 = 1; |
|
|
|
|
|
|
|
#[test] |
|
|
|
fn run_cfd_protocol() { |
|
|
|
let mut rng = ChaChaRng::seed_from_u64(0); |
|
|
@ -35,9 +46,11 @@ mod tests { |
|
|
|
let maker_dlc_amount = Amount::ONE_BTC; |
|
|
|
let taker_dlc_amount = Amount::ONE_BTC; |
|
|
|
|
|
|
|
let (_oracle_sk, oracle_pk) = make_keypair(&mut rng, &secp); |
|
|
|
let (maker_sk, maker_pk) = make_keypair(&mut rng, &secp); |
|
|
|
let (taker_sk, taker_pk) = make_keypair(&mut rng, &secp); |
|
|
|
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 { |
|
|
@ -56,8 +69,8 @@ mod tests { |
|
|
|
// block height
|
|
|
|
let refund_timelock = 0; |
|
|
|
|
|
|
|
let maker_wallet = build_wallet(Amount::from_btc(0.4).unwrap(), 5).unwrap(); |
|
|
|
let taker_wallet = build_wallet(Amount::from_btc(0.4).unwrap(), 5).unwrap(); |
|
|
|
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(); |
|
|
@ -79,7 +92,7 @@ mod tests { |
|
|
|
builder.finish().unwrap() |
|
|
|
}; |
|
|
|
|
|
|
|
let (lock_psbt, dlc_outpoint) = create_lock_psbt( |
|
|
|
let lock_tx = LockTransaction::new( |
|
|
|
maker_psbt, |
|
|
|
taker_psbt, |
|
|
|
maker_pk, |
|
|
@ -88,8 +101,8 @@ mod tests { |
|
|
|
) |
|
|
|
.unwrap(); |
|
|
|
|
|
|
|
let refund_tx = create_refund_tx( |
|
|
|
dlc_outpoint, |
|
|
|
let refund_tx = RefundTransaction::new( |
|
|
|
&lock_tx, |
|
|
|
refund_timelock, |
|
|
|
&maker_address, |
|
|
|
&taker_address, |
|
|
@ -99,156 +112,339 @@ mod tests { |
|
|
|
|
|
|
|
let cets = payouts |
|
|
|
.iter() |
|
|
|
.map(|payout| create_cet(dlc_outpoint, payout, &maker_address, &taker_address)) |
|
|
|
.map(|payout| { |
|
|
|
ContractExecutionTransaction::new(&lock_tx, payout, &maker_address, &taker_address) |
|
|
|
}) |
|
|
|
.collect::<Vec<_>>(); |
|
|
|
|
|
|
|
let maker_refund_sig = secp.sign( |
|
|
|
&secp256k1_zkp::Message::from_slice(&refund_tx.sighash()).unwrap(), |
|
|
|
&maker_sk, |
|
|
|
); |
|
|
|
let taker_refund_sig = secp.sign( |
|
|
|
&secp256k1_zkp::Message::from_slice(&refund_tx.sighash()).unwrap(), |
|
|
|
&taker_sk, |
|
|
|
); |
|
|
|
|
|
|
|
let maker_cet_encsigs = cets |
|
|
|
.iter() |
|
|
|
.map(|cet| { |
|
|
|
( |
|
|
|
cet.txid(), |
|
|
|
cet.encsign(maker_sk, &oracle.public_key(), &announcement.nonce_pk()), |
|
|
|
) |
|
|
|
}) |
|
|
|
.collect::<Vec<_>>(); |
|
|
|
let taker_cet_encsigs = cets |
|
|
|
.iter() |
|
|
|
.map(|cet| { |
|
|
|
( |
|
|
|
cet.txid(), |
|
|
|
cet.encsign(taker_sk, &oracle.public_key(), &announcement.nonce_pk()), |
|
|
|
) |
|
|
|
}) |
|
|
|
.collect::<Vec<_>>(); |
|
|
|
|
|
|
|
dbg!(lock_psbt); |
|
|
|
dbg!(refund_tx); |
|
|
|
dbg!(cets); |
|
|
|
let mut signed_lock_tx = lock_tx.to_psbt(); |
|
|
|
maker_wallet |
|
|
|
.sign( |
|
|
|
&mut signed_lock_tx, |
|
|
|
SignOptions { |
|
|
|
trust_witness_utxo: true, |
|
|
|
..Default::default() |
|
|
|
}, |
|
|
|
) |
|
|
|
.unwrap(); |
|
|
|
|
|
|
|
maker_wallet |
|
|
|
.sign( |
|
|
|
&mut signed_lock_tx, |
|
|
|
SignOptions { |
|
|
|
trust_witness_utxo: true, |
|
|
|
..Default::default() |
|
|
|
}, |
|
|
|
) |
|
|
|
.unwrap(); |
|
|
|
|
|
|
|
// TODO: Exchange signatures on refund transaction
|
|
|
|
// TODO: Exchange encsignatures on all CETs
|
|
|
|
// TODO: Exchange signatures on lock transaction
|
|
|
|
// TODO: Verify validity of all tranasactions using bitcoinconsensus via bdk
|
|
|
|
// TODO: Verify signatures as we go
|
|
|
|
// TODO: Verify validity of all transactions using bitcoinconsensus via bdk
|
|
|
|
// TODO: Upstream -> Activate rust-bitcoin/bitcoinconsensus feature on bdk
|
|
|
|
} |
|
|
|
|
|
|
|
const BIP340_MIDSTATE: [u8; 32] = [ |
|
|
|
0x9c, 0xec, 0xba, 0x11, 0x23, 0x92, 0x53, 0x81, 0x11, 0x67, 0x91, 0x12, 0xd1, 0x62, 0x7e, |
|
|
|
0x0f, 0x97, 0xc8, 0x75, 0x50, 0x00, 0x3c, 0xc7, 0x65, 0x90, 0xf6, 0x11, 0x64, 0x33, 0xe9, |
|
|
|
0xb6, 0x6a, |
|
|
|
]; |
|
|
|
|
|
|
|
sha256t_hash_newtype!( |
|
|
|
BIP340Hash, |
|
|
|
BIP340HashTag, |
|
|
|
BIP340_MIDSTATE, |
|
|
|
64, |
|
|
|
doc = "bip340 hash", |
|
|
|
true |
|
|
|
); |
|
|
|
|
|
|
|
/// Compute a signature point for the given oracle public key, announcement nonce public key and message.
|
|
|
|
fn compute_signature_point( |
|
|
|
oracle_pk: &schnorrsig::PublicKey, |
|
|
|
nonce_pk: &schnorrsig::PublicKey, |
|
|
|
message: Message, |
|
|
|
) -> Result<secp256k1_zkp::PublicKey> { |
|
|
|
fn schnorr_pubkey_to_pubkey( |
|
|
|
pk: &schnorrsig::PublicKey, |
|
|
|
) -> Result<secp256k1_zkp::PublicKey> { |
|
|
|
let mut buf = Vec::<u8>::with_capacity(33); |
|
|
|
buf.push(0x02); |
|
|
|
buf.extend(&pk.serialize()); |
|
|
|
Ok(secp256k1_zkp::PublicKey::from_slice(&buf)?) |
|
|
|
} |
|
|
|
|
|
|
|
let hash = { |
|
|
|
let mut buf = Vec::<u8>::new(); |
|
|
|
buf.extend(&nonce_pk.serialize()); |
|
|
|
buf.extend(&oracle_pk.serialize()); |
|
|
|
buf.extend( |
|
|
|
secp256k1_zkp::Message::from(message) |
|
|
|
.as_ref() |
|
|
|
.iter() |
|
|
|
.cloned() |
|
|
|
.collect::<Vec<u8>>(), |
|
|
|
); |
|
|
|
BIP340Hash::hash(&buf).into_inner().to_vec() |
|
|
|
}; |
|
|
|
let mut oracle_pk = schnorr_pubkey_to_pubkey(oracle_pk)?; |
|
|
|
oracle_pk.mul_assign(SECP256K1, &hash)?; |
|
|
|
let nonce_pk = schnorr_pubkey_to_pubkey(nonce_pk)?; |
|
|
|
Ok(nonce_pk.combine(&oracle_pk)?) |
|
|
|
} |
|
|
|
|
|
|
|
#[derive(Debug, Clone)] |
|
|
|
struct ContractExecutionTransaction { |
|
|
|
inner: Transaction, |
|
|
|
message: Message, |
|
|
|
sighash: SigHash, |
|
|
|
} |
|
|
|
|
|
|
|
fn create_cet( |
|
|
|
dlc_outpoint: OutPoint, |
|
|
|
payout: &Payout, |
|
|
|
maker_address: &Address, |
|
|
|
taker_address: &Address, |
|
|
|
) -> ContractExecutionTransaction { |
|
|
|
let dlc_input = TxIn { |
|
|
|
previous_output: dlc_outpoint, |
|
|
|
..Default::default() |
|
|
|
}; |
|
|
|
impl ContractExecutionTransaction { |
|
|
|
fn new( |
|
|
|
lock_tx: &LockTransaction, |
|
|
|
payout: &Payout, |
|
|
|
maker_address: &Address, |
|
|
|
taker_address: &Address, |
|
|
|
) -> Self { |
|
|
|
let dlc_input = TxIn { |
|
|
|
previous_output: lock_tx.dlc_outpoint(), |
|
|
|
..Default::default() |
|
|
|
}; |
|
|
|
|
|
|
|
let fee = Self::calculate_vbytes() * MIN_RELAY_FEE; |
|
|
|
|
|
|
|
let tx = Transaction { |
|
|
|
version: 2, |
|
|
|
lock_time: 0, |
|
|
|
input: vec![dlc_input], |
|
|
|
output: payout.to_txouts(maker_address, taker_address, fee), |
|
|
|
}; |
|
|
|
|
|
|
|
let sighash = SigHashCache::new(&tx).signature_hash( |
|
|
|
0, |
|
|
|
&lock_tx.dlc_descriptor.script_code(), |
|
|
|
lock_tx.dlc_amount.as_sat(), |
|
|
|
SigHashType::All, |
|
|
|
); |
|
|
|
|
|
|
|
let inner = Transaction { |
|
|
|
version: 2, |
|
|
|
lock_time: 0, |
|
|
|
input: vec![dlc_input], |
|
|
|
output: payout.to_txouts(maker_address, taker_address), |
|
|
|
}; |
|
|
|
Self { |
|
|
|
inner: tx, |
|
|
|
message: payout.message, |
|
|
|
sighash, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
ContractExecutionTransaction { |
|
|
|
inner, |
|
|
|
message: payout.message, |
|
|
|
fn encsign( |
|
|
|
&self, |
|
|
|
sk: SecretKey, |
|
|
|
oracle_pk: &schnorrsig::PublicKey, |
|
|
|
nonce_pk: &schnorrsig::PublicKey, |
|
|
|
) -> Result<EcdsaAdaptorSignature> { |
|
|
|
let signature_point = compute_signature_point(oracle_pk, nonce_pk, self.message)?; |
|
|
|
|
|
|
|
Ok(EcdsaAdaptorSignature::encrypt( |
|
|
|
SECP256K1, |
|
|
|
&secp256k1_zkp::Message::from_slice(&self.sighash)?, |
|
|
|
&sk, |
|
|
|
&signature_point, |
|
|
|
)) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
fn create_refund_tx( |
|
|
|
dlc_outpoint: OutPoint, |
|
|
|
lock_time: u32, |
|
|
|
maker_address: &Address, |
|
|
|
taker_address: &Address, |
|
|
|
maker_amount: Amount, |
|
|
|
taker_amount: Amount, |
|
|
|
) -> Transaction { |
|
|
|
let dlc_input = TxIn { |
|
|
|
previous_output: dlc_outpoint, |
|
|
|
..Default::default() |
|
|
|
}; |
|
|
|
fn txid(&self) -> Txid { |
|
|
|
self.inner.txid() |
|
|
|
} |
|
|
|
|
|
|
|
let per_party_fee = REFUND_TX_FEE / 2; |
|
|
|
fn calculate_vbytes() -> u64 { |
|
|
|
// TODO: Do it properly
|
|
|
|
100 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
let maker_output = TxOut { |
|
|
|
value: maker_amount.as_sat() - per_party_fee, |
|
|
|
script_pubkey: maker_address.script_pubkey(), |
|
|
|
}; |
|
|
|
#[derive(Debug, Clone)] |
|
|
|
struct RefundTransaction { |
|
|
|
inner: Transaction, |
|
|
|
sighash: SigHash, |
|
|
|
} |
|
|
|
|
|
|
|
let taker_output = TxOut { |
|
|
|
value: taker_amount.as_sat() - per_party_fee, |
|
|
|
script_pubkey: taker_address.script_pubkey(), |
|
|
|
}; |
|
|
|
impl RefundTransaction { |
|
|
|
fn new( |
|
|
|
lock_tx: &LockTransaction, |
|
|
|
lock_time: u32, |
|
|
|
maker_address: &Address, |
|
|
|
taker_address: &Address, |
|
|
|
maker_amount: Amount, |
|
|
|
taker_amount: Amount, |
|
|
|
) -> Self { |
|
|
|
let dlc_input = TxIn { |
|
|
|
previous_output: lock_tx.dlc_outpoint(), |
|
|
|
..Default::default() |
|
|
|
}; |
|
|
|
|
|
|
|
let per_party_fee = REFUND_TX_FEE / 2; |
|
|
|
|
|
|
|
let maker_output = TxOut { |
|
|
|
value: maker_amount.as_sat() - per_party_fee, |
|
|
|
script_pubkey: maker_address.script_pubkey(), |
|
|
|
}; |
|
|
|
|
|
|
|
let taker_output = TxOut { |
|
|
|
value: taker_amount.as_sat() - per_party_fee, |
|
|
|
script_pubkey: taker_address.script_pubkey(), |
|
|
|
}; |
|
|
|
|
|
|
|
let tx = Transaction { |
|
|
|
version: 2, |
|
|
|
lock_time, |
|
|
|
input: vec![dlc_input], |
|
|
|
output: vec![maker_output, taker_output], |
|
|
|
}; |
|
|
|
|
|
|
|
let sighash = SigHashCache::new(&tx).signature_hash( |
|
|
|
0, |
|
|
|
&lock_tx.dlc_descriptor.script_code(), |
|
|
|
lock_tx.dlc_amount.as_sat(), |
|
|
|
SigHashType::All, |
|
|
|
); |
|
|
|
|
|
|
|
Transaction { |
|
|
|
version: 2, |
|
|
|
lock_time, |
|
|
|
input: vec![dlc_input], |
|
|
|
output: vec![maker_output, taker_output], |
|
|
|
Self { inner: tx, sighash } |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
fn dlc_outpoint(lock_tx: &Transaction, dlc_descriptor: &Descriptor<PublicKey>) -> OutPoint { |
|
|
|
let txid = lock_tx.txid(); |
|
|
|
let vout = lock_tx |
|
|
|
.output |
|
|
|
.iter() |
|
|
|
.position(|out| out.script_pubkey == dlc_descriptor.script_pubkey()) |
|
|
|
.expect("to find dlc output in lock tx"); |
|
|
|
|
|
|
|
OutPoint { |
|
|
|
txid, |
|
|
|
vout: vout as u32, |
|
|
|
fn sighash(&self) -> SigHash { |
|
|
|
self.sighash |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
fn create_lock_psbt( |
|
|
|
maker_psbt: PartiallySignedTransaction, |
|
|
|
taker_psbt: PartiallySignedTransaction, |
|
|
|
maker_pk: PublicKey, |
|
|
|
taker_pk: PublicKey, |
|
|
|
#[derive(Debug, Clone)] |
|
|
|
struct LockTransaction { |
|
|
|
inner: PartiallySignedTransaction, |
|
|
|
dlc_descriptor: Descriptor<PublicKey>, |
|
|
|
dlc_amount: Amount, |
|
|
|
) -> Result<(PartiallySignedTransaction, OutPoint)> { |
|
|
|
let dlc_descriptor = build_dlc_descriptor(maker_pk, taker_pk); |
|
|
|
|
|
|
|
let maker_change = maker_psbt |
|
|
|
.global |
|
|
|
.unsigned_tx |
|
|
|
.output |
|
|
|
.into_iter() |
|
|
|
.filter(|out| !out.script_pubkey.is_empty()) |
|
|
|
.collect::<Vec<_>>(); |
|
|
|
|
|
|
|
let taker_change = taker_psbt |
|
|
|
.global |
|
|
|
.unsigned_tx |
|
|
|
.output |
|
|
|
.into_iter() |
|
|
|
.filter(|out| !out.script_pubkey.is_empty()) |
|
|
|
.collect(); |
|
|
|
|
|
|
|
let dlc_output = TxOut { |
|
|
|
value: dlc_amount.as_sat(), |
|
|
|
script_pubkey: dlc_descriptor |
|
|
|
.address(Network::Regtest) |
|
|
|
.expect("can derive address from descriptor") |
|
|
|
.script_pubkey(), |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
let lock_tx = Transaction { |
|
|
|
version: 2, |
|
|
|
lock_time: 0, |
|
|
|
input: vec![ |
|
|
|
maker_psbt.global.unsigned_tx.input, |
|
|
|
taker_psbt.global.unsigned_tx.input, |
|
|
|
] |
|
|
|
.concat(), |
|
|
|
output: vec![vec![dlc_output], maker_change, taker_change].concat(), |
|
|
|
}; |
|
|
|
impl LockTransaction { |
|
|
|
fn new( |
|
|
|
maker_psbt: PartiallySignedTransaction, |
|
|
|
taker_psbt: PartiallySignedTransaction, |
|
|
|
maker_pk: PublicKey, |
|
|
|
taker_pk: PublicKey, |
|
|
|
dlc_amount: Amount, |
|
|
|
) -> Result<Self> { |
|
|
|
let dlc_descriptor = build_dlc_descriptor(maker_pk, taker_pk); |
|
|
|
|
|
|
|
let maker_change = maker_psbt |
|
|
|
.global |
|
|
|
.unsigned_tx |
|
|
|
.output |
|
|
|
.into_iter() |
|
|
|
.filter(|out| !out.script_pubkey.is_empty()) |
|
|
|
.collect::<Vec<_>>(); |
|
|
|
|
|
|
|
let taker_change = taker_psbt |
|
|
|
.global |
|
|
|
.unsigned_tx |
|
|
|
.output |
|
|
|
.into_iter() |
|
|
|
.filter(|out| !out.script_pubkey.is_empty()) |
|
|
|
.collect(); |
|
|
|
|
|
|
|
let dlc_output = TxOut { |
|
|
|
value: dlc_amount.as_sat(), |
|
|
|
script_pubkey: dlc_descriptor |
|
|
|
.address(Network::Regtest) |
|
|
|
.expect("can derive address from descriptor") |
|
|
|
.script_pubkey(), |
|
|
|
}; |
|
|
|
|
|
|
|
let lock_tx = Transaction { |
|
|
|
version: 2, |
|
|
|
lock_time: 0, |
|
|
|
input: vec![ |
|
|
|
maker_psbt.global.unsigned_tx.input, |
|
|
|
taker_psbt.global.unsigned_tx.input, |
|
|
|
] |
|
|
|
.concat(), |
|
|
|
output: vec![vec![dlc_output], maker_change, taker_change].concat(), |
|
|
|
}; |
|
|
|
|
|
|
|
let inner = PartiallySignedTransaction { |
|
|
|
global: Global::from_unsigned_tx(lock_tx)?, |
|
|
|
inputs: vec![maker_psbt.inputs, taker_psbt.inputs].concat(), |
|
|
|
outputs: vec![maker_psbt.outputs, taker_psbt.outputs].concat(), |
|
|
|
}; |
|
|
|
|
|
|
|
Ok(Self { |
|
|
|
inner, |
|
|
|
dlc_descriptor, |
|
|
|
dlc_amount, |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
let dlc_outpoint = dlc_outpoint(&lock_tx, &dlc_descriptor); |
|
|
|
let lock_psbt = PartiallySignedTransaction::from_unsigned_tx(lock_tx)?; |
|
|
|
fn dlc_outpoint(&self) -> OutPoint { |
|
|
|
let txid = self.inner.global.unsigned_tx.txid(); |
|
|
|
let vout = self |
|
|
|
.inner |
|
|
|
.global |
|
|
|
.unsigned_tx |
|
|
|
.output |
|
|
|
.iter() |
|
|
|
.position(|out| out.script_pubkey == self.dlc_descriptor.script_pubkey()) |
|
|
|
.expect("to find dlc output in lock tx"); |
|
|
|
|
|
|
|
OutPoint { |
|
|
|
txid, |
|
|
|
vout: vout as u32, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
Ok((lock_psbt, dlc_outpoint)) |
|
|
|
fn to_psbt(&self) -> PartiallySignedTransaction { |
|
|
|
self.inner.clone() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
fn build_wallet( |
|
|
|
fn build_wallet<R>( |
|
|
|
rng: &mut R, |
|
|
|
utxo_amount: Amount, |
|
|
|
num_utxos: u8, |
|
|
|
) -> Result<bdk::Wallet<(), bdk::database::MemoryDatabase>> { |
|
|
|
) -> Result<bdk::Wallet<(), bdk::database::MemoryDatabase>> |
|
|
|
where |
|
|
|
R: RngCore + CryptoRng, |
|
|
|
{ |
|
|
|
use bdk::populate_test_db; |
|
|
|
use bdk::testutils; |
|
|
|
|
|
|
|
// FIXME: Using the same key for every instance of the wallet will lead to bugs in the tests
|
|
|
|
let key = "tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m".parse::<ExtendedPrivKey>().unwrap(); |
|
|
|
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(); |
|
|
@ -278,19 +474,34 @@ mod tests { |
|
|
|
} |
|
|
|
|
|
|
|
impl Payout { |
|
|
|
fn to_txouts(self, maker_address: &Address, taker_address: &Address) -> Vec<TxOut> { |
|
|
|
[ |
|
|
|
fn to_txouts( |
|
|
|
self, |
|
|
|
maker_address: &Address, |
|
|
|
taker_address: &Address, |
|
|
|
fee: u64, |
|
|
|
) -> Vec<TxOut> { |
|
|
|
let mut txouts = [ |
|
|
|
(self.maker_amount, maker_address), |
|
|
|
(self.taker_amount, taker_address), |
|
|
|
] |
|
|
|
.iter() |
|
|
|
.filter_map(|(amount, address)| { |
|
|
|
(amount == &Amount::ZERO).then(|| TxOut { |
|
|
|
(amount != &Amount::ZERO).then(|| TxOut { |
|
|
|
value: amount.as_sat(), |
|
|
|
script_pubkey: address.script_pubkey(), |
|
|
|
}) |
|
|
|
}) |
|
|
|
.collect() |
|
|
|
.collect::<Vec<_>>(); |
|
|
|
|
|
|
|
// TODO: Rewrite this
|
|
|
|
if txouts.len() == 1 { |
|
|
|
txouts[0].value -= fee; |
|
|
|
} else if txouts.len() == 2 { |
|
|
|
txouts[0].value -= fee / 2; |
|
|
|
txouts[1].value -= fee / 2; |
|
|
|
} |
|
|
|
|
|
|
|
txouts |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
@ -300,7 +511,105 @@ mod tests { |
|
|
|
Lose, |
|
|
|
} |
|
|
|
|
|
|
|
pub fn build_dlc_descriptor(maker_pk: PublicKey, taker_pk: PublicKey) -> Descriptor<PublicKey> { |
|
|
|
impl From<Message> for secp256k1_zkp::Message { |
|
|
|
fn from(msg: Message) -> Self { |
|
|
|
// TODO: Tag hash with prefix and other public data
|
|
|
|
secp256k1_zkp::Message::from_hashed_data::<secp256k1_zkp::bitcoin_hashes::sha256::Hash>( |
|
|
|
msg.to_string().as_bytes(), |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
impl ToString for Message { |
|
|
|
fn to_string(&self) -> String { |
|
|
|
match self { |
|
|
|
Message::Win => "win".to_string(), |
|
|
|
Message::Lose => "lose".to_string(), |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
struct Oracle { |
|
|
|
key_pair: schnorrsig::KeyPair, |
|
|
|
} |
|
|
|
|
|
|
|
impl Oracle { |
|
|
|
fn new<R>(rng: &mut R) -> Self |
|
|
|
where |
|
|
|
R: RngCore + CryptoRng, |
|
|
|
{ |
|
|
|
let key_pair = schnorrsig::KeyPair::new(SECP256K1, rng); |
|
|
|
|
|
|
|
Self { key_pair } |
|
|
|
} |
|
|
|
|
|
|
|
fn public_key(&self) -> schnorrsig::PublicKey { |
|
|
|
schnorrsig::PublicKey::from_keypair(SECP256K1, &self.key_pair) |
|
|
|
} |
|
|
|
|
|
|
|
fn attest(&self, event: Event, msg: Message) -> schnorrsig::Signature { |
|
|
|
secp_utils::schnorr_sign_with_nonce(&msg.into(), &self.key_pair, &event.nonce) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
fn announce<R>(rng: &mut R) -> (Event, Announcement) |
|
|
|
where |
|
|
|
R: RngCore + CryptoRng, |
|
|
|
{ |
|
|
|
let event = Event::new(rng); |
|
|
|
let announcement = event.announcement(); |
|
|
|
|
|
|
|
(event, announcement) |
|
|
|
} |
|
|
|
|
|
|
|
/// Represents the oracle's commitment to a nonce that will be used to
|
|
|
|
/// sign a specific event in the future.
|
|
|
|
struct Event { |
|
|
|
/// Nonce.
|
|
|
|
///
|
|
|
|
/// Must remain secret.
|
|
|
|
nonce: SecretKey, |
|
|
|
nonce_pk: schnorrsig::PublicKey, |
|
|
|
} |
|
|
|
|
|
|
|
impl Event { |
|
|
|
fn new<R>(rng: &mut R) -> Self |
|
|
|
where |
|
|
|
R: RngCore + CryptoRng, |
|
|
|
{ |
|
|
|
let nonce = SecretKey::new(rng); |
|
|
|
|
|
|
|
let key_pair = schnorrsig::KeyPair::from_secret_key(SECP256K1, nonce); |
|
|
|
let nonce_pk = schnorrsig::PublicKey::from_keypair(SECP256K1, &key_pair); |
|
|
|
|
|
|
|
Self { nonce, nonce_pk } |
|
|
|
} |
|
|
|
|
|
|
|
fn announcement(&self) -> Announcement { |
|
|
|
Announcement { |
|
|
|
nonce_pk: self.nonce_pk, |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// Public message which can be used by anyone to perform a DLC
|
|
|
|
/// protocol based on a specific event.
|
|
|
|
///
|
|
|
|
/// These would normally include more information to identify the
|
|
|
|
/// specific event, but we omit this for simplicity. See:
|
|
|
|
/// https://github.com/discreetlogcontracts/dlcspecs/blob/master/Oracle.md#oracle-events
|
|
|
|
#[derive(Clone, Copy)] |
|
|
|
struct Announcement { |
|
|
|
nonce_pk: schnorrsig::PublicKey, |
|
|
|
} |
|
|
|
|
|
|
|
impl Announcement { |
|
|
|
fn nonce_pk(&self) -> schnorrsig::PublicKey { |
|
|
|
self.nonce_pk |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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))"; |
|
|
|
|
|
|
|
// NOTE: This shouldn't be a source of error, but maybe it is
|
|
|
@ -316,13 +625,13 @@ mod tests { |
|
|
|
Descriptor::Wsh(Wsh::new(miniscript).expect("a valid descriptor")) |
|
|
|
} |
|
|
|
|
|
|
|
fn make_keypair<R>(rng: &mut R, secp: &Secp256k1<All>) -> (SecretKey, PublicKey) |
|
|
|
fn make_keypair<R>(rng: &mut R) -> (SecretKey, PublicKey) |
|
|
|
where |
|
|
|
R: RngCore + CryptoRng, |
|
|
|
{ |
|
|
|
let sk = SecretKey::new(rng); |
|
|
|
let pk = PublicKey::from_private_key( |
|
|
|
secp, |
|
|
|
SECP256K1, |
|
|
|
&PrivateKey { |
|
|
|
compressed: true, |
|
|
|
network: Network::Regtest, |
|
|
@ -332,4 +641,52 @@ mod tests { |
|
|
|
|
|
|
|
(sk, pk) |
|
|
|
} |
|
|
|
|
|
|
|
mod secp_utils { |
|
|
|
use super::*; |
|
|
|
|
|
|
|
use secp256k1_zkp::secp256k1_zkp_sys::types::c_void; |
|
|
|
use secp256k1_zkp::secp256k1_zkp_sys::CPtr; |
|
|
|
use std::os::raw::c_int; |
|
|
|
use std::os::raw::c_uchar; |
|
|
|
use std::ptr; |
|
|
|
|
|
|
|
/// Create a Schnorr signature using the provided nonce instead of generating one.
|
|
|
|
pub fn schnorr_sign_with_nonce( |
|
|
|
msg: &secp256k1_zkp::Message, |
|
|
|
keypair: &schnorrsig::KeyPair, |
|
|
|
nonce: &SecretKey, |
|
|
|
) -> schnorrsig::Signature { |
|
|
|
unsafe { |
|
|
|
let mut sig = [0u8; secp256k1_zkp::constants::SCHNORRSIG_SIGNATURE_SIZE]; |
|
|
|
assert_eq!( |
|
|
|
1, |
|
|
|
secp256k1_zkp::ffi::secp256k1_schnorrsig_sign( |
|
|
|
*SECP256K1.ctx(), |
|
|
|
sig.as_mut_c_ptr(), |
|
|
|
msg.as_c_ptr(), |
|
|
|
keypair.as_ptr(), |
|
|
|
Some(constant_nonce_fn), |
|
|
|
nonce.as_c_ptr() as *const c_void |
|
|
|
) |
|
|
|
); |
|
|
|
|
|
|
|
schnorrsig::Signature::from_slice(&sig).unwrap() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
extern "C" fn constant_nonce_fn( |
|
|
|
nonce32: *mut c_uchar, |
|
|
|
_msg32: *const c_uchar, |
|
|
|
_key32: *const c_uchar, |
|
|
|
_xonly_pk32: *const c_uchar, |
|
|
|
_algo16: *const c_uchar, |
|
|
|
data: *mut c_void, |
|
|
|
) -> c_int { |
|
|
|
unsafe { |
|
|
|
ptr::copy_nonoverlapping(data as *const c_uchar, nonce32, 32); |
|
|
|
} |
|
|
|
1 |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|