Browse Source

Reorganize modules

no-contract-setup-message
Lucas Soriano del Pino 3 years ago
parent
commit
4e2e3914cb
No known key found for this signature in database GPG Key ID: EE611E973A1530E7
  1. 476
      cfd_protocol/src/protocol.rs
  2. 15
      cfd_protocol/src/protocol/sighash_ext.rs
  3. 26
      cfd_protocol/src/protocol/transaction_ext.rs
  4. 441
      cfd_protocol/src/protocol/transactions.rs

476
cfd_protocol/src/protocol.rs

@ -1,13 +1,14 @@
use crate::protocol::sighash_ext::SigHashExt;
use crate::protocol::transactions::{
lock_transaction, CommitTransaction, ContractExecutionTransaction, RefundTransaction,
};
use crate::{oracle, Interval};
use anyhow::{bail, Context, Result};
use bdk::bitcoin::hashes::hex::ToHex;
use bdk::bitcoin::hashes::Hash;
use bdk::bitcoin::util::bip143::SigHashCache;
use bdk::bitcoin::util::psbt::{Global, PartiallySignedTransaction};
use bdk::bitcoin::{
Address, Amount, OutPoint, PublicKey, Script, SigHash, SigHashType, Transaction, TxIn, TxOut,
};
use bdk::bitcoin::util::psbt::PartiallySignedTransaction;
use bdk::bitcoin::{Address, Amount, PublicKey, SigHashType, Transaction, TxOut};
use bdk::database::BatchDatabase;
use bdk::descriptor::Descriptor;
use bdk::miniscript::descriptor::Wsh;
@ -18,12 +19,15 @@ use itertools::Itertools;
use secp256k1_zkp::{self, schnorrsig, EcdsaAdaptorSignature, SecretKey, Signature, SECP256K1};
use std::collections::HashMap;
use std::iter::FromIterator;
mod sighash_ext;
mod transaction_ext;
mod transactions;
/// In satoshi per vbyte.
const SATS_PER_VBYTE: f64 = 1.0;
pub use transaction_ext::TransactionExt;
pub use transactions::punish_transaction;
/// Static script to be used to create lock tx
const DUMMY_2OF2_MULITISIG: &str =
const DUMMY_2OF2_MULTISIG: &str =
"0020b5aa99ed7e0fa92483eb045ab8b7a59146d4d9f6653f21ba729b4331895a5b46";
pub trait WalletExt {
@ -40,9 +44,7 @@ where
.ordering(bdk::wallet::tx_builder::TxOrdering::Bip69Lexicographic)
.fee_rate(FeeRate::from_sat_per_vb(1.0))
.add_recipient(
DUMMY_2OF2_MULITISIG
.parse()
.expect("Should be valid script"),
DUMMY_2OF2_MULTISIG.parse().expect("Should be valid script"),
amount.as_sat(),
);
let (lock_psbt, _) = builder.finish()?;
@ -196,7 +198,7 @@ fn build_cfds(
let sighash = tx.sighash().to_message();
let sig = SECP256K1.sign(&sighash, &identity_sk);
(tx.inner, sig)
(tx.into_inner(), sig)
};
let cets = payouts
@ -212,14 +214,14 @@ fn build_cfds(
let encsig = cet.encsign(identity_sk, &oracle_pk)?;
Ok((cet.inner, encsig, payout.msg_nonce_pairs))
Ok((cet.into_inner(), encsig, payout.msg_nonce_pairs))
})
.collect::<Result<Vec<_>>>()
.context("cannot build and sign all cets")?;
Ok(CfdTransactions {
lock: lock_tx,
commit: (commit_tx.inner, commit_encsig),
commit: (commit_tx.into_inner(), commit_encsig),
cets,
refund,
})
@ -306,109 +308,6 @@ pub fn finalize_spend_transaction(
Ok(tx)
}
pub fn punish_transaction(
commit_descriptor: &Descriptor<PublicKey>,
address: &Address,
encsig: EcdsaAdaptorSignature,
sk: SecretKey,
revocation_them_sk: SecretKey,
pub_them_pk: PublicKey,
revoked_commit_tx: &Transaction,
) -> Result<Transaction> {
/// Expected size of signed transaction in virtual bytes, plus a
/// buffer to account for different signature lengths.
const SIGNED_VBYTES: f64 = 219.5 + (3.0 * 3.0) / 4.0;
let input = revoked_commit_tx
.input
.clone()
.into_iter()
.exactly_one()
.context("commit transaction inputs != 1")?;
let publish_them_sk = input
.witness
.iter()
.filter_map(|elem| {
let elem = elem.as_slice();
Signature::from_der(&elem[..elem.len() - 1]).ok()
})
.find_map(|sig| encsig.recover(SECP256K1, &sig, &pub_them_pk.key).ok())
.context("could not recover publish sk from commit tx")?;
let commit_outpoint = revoked_commit_tx
.outpoint(&commit_descriptor.script_pubkey())
.expect("to find commit output in commit tx");
let commit_amount = revoked_commit_tx.output[commit_outpoint.vout as usize].value;
let mut punish_tx = {
let output = TxOut {
value: commit_amount,
script_pubkey: address.script_pubkey(),
};
let mut tx = Transaction {
version: 2,
lock_time: 0,
input: vec![TxIn {
previous_output: commit_outpoint,
..Default::default()
}],
output: vec![output],
};
let fee = SIGNED_VBYTES * SATS_PER_VBYTE;
tx.output[0].value = commit_amount - fee as u64;
tx
};
let sighash = SigHashCache::new(&punish_tx).signature_hash(
0,
&commit_descriptor.script_code(),
commit_amount,
SigHashType::All,
);
let satisfier = {
let pk = {
let key = secp256k1_zkp::PublicKey::from_secret_key(SECP256K1, &sk);
PublicKey {
compressed: true,
key,
}
};
let pk_hash = pk.pubkey_hash().as_hash();
let sig_sk = SECP256K1.sign(&sighash.to_message(), &sk);
let pub_them_pk_hash = pub_them_pk.pubkey_hash().as_hash();
let sig_pub_them = SECP256K1.sign(&sighash.to_message(), &publish_them_sk);
let rev_them_pk = {
let key = secp256k1_zkp::PublicKey::from_secret_key(SECP256K1, &revocation_them_sk);
PublicKey {
compressed: true,
key,
}
};
let rev_them_pk_hash = rev_them_pk.pubkey_hash().as_hash();
let sig_rev_them = SECP256K1.sign(&sighash.to_message(), &revocation_them_sk);
let sighash_all = SigHashType::All;
HashMap::from_iter(vec![
(pk_hash, (pk, (sig_sk, sighash_all))),
(pub_them_pk_hash, (pub_them_pk, (sig_pub_them, sighash_all))),
(rev_them_pk_hash, (rev_them_pk, (sig_rev_them, sighash_all))),
])
};
commit_descriptor.satisfy(&mut punish_tx.input[0], satisfier)?;
Ok(punish_tx)
}
// NOTE: We have decided to not order any verification utility because
// the APIs would be incredibly thin
#[derive(Clone)]
pub struct PartyParams {
pub lock_psbt: PartiallySignedTransaction,
@ -532,7 +431,7 @@ impl Payout {
/// Compute a signature point for the given oracle public key, announcement nonce public key and
/// message.
pub fn compute_signature_point(
fn compute_signature_point(
oracle_pk: &schnorrsig::PublicKey,
nonce_pk: &schnorrsig::PublicKey,
msg: &[u8],
@ -551,81 +450,6 @@ pub fn compute_signature_point(
Ok(nonce_pk.combine(&oracle_pk)?)
}
#[derive(Debug, Clone)]
struct ContractExecutionTransaction {
inner: Transaction,
msg_nonce_pairs: Vec<(Vec<u8>, schnorrsig::PublicKey)>,
sighash: SigHash,
commit_descriptor: Descriptor<PublicKey>,
}
impl ContractExecutionTransaction {
/// Expected size of signed transaction in virtual bytes, plus a
/// buffer to account for different signature lengths.
const SIGNED_VBYTES: f64 = 206.5 + (3.0 * 2.0) / 4.0;
fn new(
commit_tx: &CommitTransaction,
payout: Payout,
maker_address: &Address,
taker_address: &Address,
relative_timelock_in_blocks: u32,
) -> Result<Self> {
let msg_nonce_pairs = payout.msg_nonce_pairs.clone();
let commit_input = TxIn {
previous_output: commit_tx.outpoint(),
sequence: relative_timelock_in_blocks,
..Default::default()
};
let mut fee = Self::SIGNED_VBYTES * SATS_PER_VBYTE;
fee += commit_tx.fee() as f64;
let output = payout
.with_updated_fee(
Amount::from_sat(fee as u64),
maker_address.script_pubkey().dust_value(),
taker_address.script_pubkey().dust_value(),
)?
.into_txouts(maker_address, taker_address);
let tx = Transaction {
version: 2,
lock_time: 0,
input: vec![commit_input],
output,
};
let sighash = SigHashCache::new(&tx).signature_hash(
0,
&commit_tx.descriptor.script_code(),
commit_tx.amount.as_sat(),
SigHashType::All,
);
Ok(Self {
inner: tx,
msg_nonce_pairs,
sighash,
commit_descriptor: commit_tx.descriptor(),
})
}
fn encsign(
&self,
sk: SecretKey,
oracle_pk: &schnorrsig::PublicKey,
) -> Result<EcdsaAdaptorSignature> {
let adaptor_point = compute_adaptor_point(oracle_pk, &self.msg_nonce_pairs)?;
Ok(EcdsaAdaptorSignature::encrypt(
SECP256K1,
&self.sighash.to_message(),
&sk,
&adaptor_point,
))
}
}
pub fn compute_adaptor_point(
oracle_pk: &schnorrsig::PublicKey,
msg_nonce_pairs: &[(Vec<u8>, schnorrsig::PublicKey)],
@ -640,272 +464,6 @@ pub fn compute_adaptor_point(
Ok(adaptor_point)
}
#[derive(Debug, Clone)]
struct RefundTransaction {
inner: Transaction,
sighash: SigHash,
commit_output_descriptor: Descriptor<PublicKey>,
}
impl RefundTransaction {
/// Expected size of signed transaction in virtual bytes, plus a
/// buffer to account for different signature lengths.
const SIGNED_VBYTES: f64 = 206.5 + (3.0 * 2.0) / 4.0;
fn new(
commit_tx: &CommitTransaction,
relative_locktime_in_blocks: u32,
maker_address: &Address,
taker_address: &Address,
maker_amount: Amount,
taker_amount: Amount,
) -> Self {
let commit_input = TxIn {
previous_output: commit_tx.outpoint(),
sequence: relative_locktime_in_blocks,
..Default::default()
};
let maker_output = TxOut {
value: maker_amount.as_sat(),
script_pubkey: maker_address.script_pubkey(),
};
let taker_output = TxOut {
value: taker_amount.as_sat(),
script_pubkey: taker_address.script_pubkey(),
};
let mut tx = Transaction {
version: 2,
lock_time: 0,
input: vec![commit_input],
output: vec![maker_output, taker_output],
};
let mut fee = Self::SIGNED_VBYTES * SATS_PER_VBYTE;
fee += commit_tx.fee() as f64;
tx.output[0].value -= (fee / 2.0) as u64;
tx.output[1].value -= (fee / 2.0) as u64;
let commit_output_descriptor = commit_tx.descriptor();
let sighash = SigHashCache::new(&tx).signature_hash(
0,
&commit_tx.descriptor().script_code(),
commit_tx.amount().as_sat(),
SigHashType::All,
);
Self {
inner: tx,
sighash,
commit_output_descriptor,
}
}
fn sighash(&self) -> SigHash {
self.sighash
}
}
#[derive(Debug, Clone)]
struct CommitTransaction {
inner: Transaction,
descriptor: Descriptor<PublicKey>,
amount: Amount,
sighash: SigHash,
lock_descriptor: Descriptor<PublicKey>,
fee: u64,
}
impl CommitTransaction {
/// Expected size of signed transaction in virtual bytes, plus a
/// buffer to account for different signature lengths.
const SIGNED_VBYTES: f64 = 148.5 + (3.0 * 2.0) / 4.0;
fn new(
lock_tx: &Transaction,
(maker_pk, maker_rev_pk, maker_publish_pk): (PublicKey, PublicKey, PublicKey),
(taker_pk, taker_rev_pk, taker_publish_pk): (PublicKey, PublicKey, PublicKey),
) -> Result<Self> {
let lock_descriptor = lock_descriptor(maker_pk, taker_pk);
let (lock_outpoint, lock_amount) = {
let outpoint = lock_tx
.outpoint(&lock_descriptor.script_pubkey())
.context("lock script not found in lock tx")?;
let amount = lock_tx.output[outpoint.vout as usize].value;
(outpoint, amount)
};
let lock_input = TxIn {
previous_output: lock_outpoint,
..Default::default()
};
let descriptor = commit_descriptor(
(maker_pk, maker_rev_pk, maker_publish_pk),
(taker_pk, taker_rev_pk, taker_publish_pk),
);
let output = TxOut {
value: lock_amount,
script_pubkey: descriptor.script_pubkey(),
};
let mut inner = Transaction {
version: 2,
lock_time: 0,
input: vec![lock_input],
output: vec![output],
};
let fee = (Self::SIGNED_VBYTES * SATS_PER_VBYTE as f64) as u64;
let commit_tx_amount = lock_amount - fee as u64;
inner.output[0].value = commit_tx_amount;
let sighash = SigHashCache::new(&inner).signature_hash(
0,
&lock_descriptor.script_code(),
lock_amount,
SigHashType::All,
);
Ok(Self {
inner,
descriptor,
lock_descriptor,
amount: Amount::from_sat(commit_tx_amount),
sighash,
fee,
})
}
fn encsign(&self, sk: SecretKey, publish_them_pk: &PublicKey) -> EcdsaAdaptorSignature {
EcdsaAdaptorSignature::encrypt(
SECP256K1,
&self.sighash.to_message(),
&sk,
&publish_them_pk.key,
)
}
fn outpoint(&self) -> OutPoint {
self.inner
.outpoint(&self.descriptor.script_pubkey())
.expect("to find commit output in commit tx")
}
fn amount(&self) -> Amount {
self.amount
}
fn descriptor(&self) -> Descriptor<PublicKey> {
self.descriptor.clone()
}
fn fee(&self) -> u64 {
self.fee
}
}
fn lock_transaction(
maker_psbt: PartiallySignedTransaction,
taker_psbt: PartiallySignedTransaction,
maker_pk: PublicKey,
taker_pk: PublicKey,
amount: Amount,
) -> PartiallySignedTransaction {
let lock_descriptor = lock_descriptor(maker_pk, taker_pk);
let maker_change = maker_psbt
.global
.unsigned_tx
.output
.into_iter()
.filter(|out| {
out.script_pubkey != DUMMY_2OF2_MULITISIG.parse().expect("To be a valid script")
})
.collect::<Vec<_>>();
let taker_change = taker_psbt
.global
.unsigned_tx
.output
.into_iter()
.filter(|out| {
out.script_pubkey != DUMMY_2OF2_MULITISIG.parse().expect("To be a valid script")
})
.collect::<Vec<_>>();
let lock_output = TxOut {
value: amount.as_sat(),
script_pubkey: lock_descriptor.script_pubkey(),
};
let input = vec![
maker_psbt.global.unsigned_tx.input,
taker_psbt.global.unsigned_tx.input,
]
.concat();
let output = std::iter::once(lock_output)
.chain(maker_change)
.chain(taker_change)
.collect();
let lock_tx = Transaction {
version: 2,
lock_time: 0,
input,
output,
};
PartiallySignedTransaction {
global: Global::from_unsigned_tx(lock_tx).expect("to be unsigned"),
inputs: vec![maker_psbt.inputs, taker_psbt.inputs].concat(),
outputs: vec![maker_psbt.outputs, taker_psbt.outputs].concat(),
}
}
pub trait TransactionExt {
fn get_virtual_size(&self) -> f64;
fn outpoint(&self, script_pubkey: &Script) -> Result<OutPoint>;
}
impl TransactionExt for Transaction {
fn get_virtual_size(&self) -> f64 {
self.get_weight() as f64 / 4.0
}
fn outpoint(&self, script_pubkey: &Script) -> Result<OutPoint> {
let vout = self
.output
.iter()
.position(|out| &out.script_pubkey == script_pubkey)
.context("script pubkey not found in tx")?;
Ok(OutPoint {
txid: self.txid(),
vout: vout as u32,
})
}
}
trait SigHashExt {
fn to_message(self) -> secp256k1_zkp::Message;
}
impl SigHashExt for SigHash {
fn to_message(self) -> secp256k1_zkp::Message {
use secp256k1_zkp::bitcoin_hashes::Hash;
let hash = secp256k1_zkp::bitcoin_hashes::sha256d::Hash::from_inner(*self.as_inner());
hash.into()
}
}
#[cfg(test)]
mod tests {
use super::*;

15
cfd_protocol/src/protocol/sighash_ext.rs

@ -0,0 +1,15 @@
use bdk::bitcoin::hashes::Hash;
use bdk::bitcoin::SigHash;
pub(super) trait SigHashExt {
fn to_message(self) -> secp256k1_zkp::Message;
}
impl SigHashExt for SigHash {
fn to_message(self) -> secp256k1_zkp::Message {
use secp256k1_zkp::bitcoin_hashes::Hash;
let hash = secp256k1_zkp::bitcoin_hashes::sha256d::Hash::from_inner(*self.as_inner());
hash.into()
}
}

26
cfd_protocol/src/protocol/transaction_ext.rs

@ -0,0 +1,26 @@
use anyhow::{Context, Result};
use bdk::bitcoin::{OutPoint, Script, Transaction};
pub trait TransactionExt {
fn get_virtual_size(&self) -> f64;
fn outpoint(&self, script_pubkey: &Script) -> Result<OutPoint>;
}
impl TransactionExt for Transaction {
fn get_virtual_size(&self) -> f64 {
self.get_weight() as f64 / 4.0
}
fn outpoint(&self, script_pubkey: &Script) -> Result<OutPoint> {
let vout = self
.output
.iter()
.position(|out| &out.script_pubkey == script_pubkey)
.context("script pubkey not found in tx")?;
Ok(OutPoint {
txid: self.txid(),
vout: vout as u32,
})
}
}

441
cfd_protocol/src/protocol/transactions.rs

@ -0,0 +1,441 @@
use crate::protocol::sighash_ext::SigHashExt;
use crate::protocol::transaction_ext::TransactionExt;
use crate::protocol::{
commit_descriptor, compute_adaptor_point, lock_descriptor, Payout, DUMMY_2OF2_MULTISIG,
};
use anyhow::{Context, Result};
use bdk::bitcoin::util::bip143::SigHashCache;
use bdk::bitcoin::util::psbt::{Global, PartiallySignedTransaction};
use bdk::bitcoin::{
Address, Amount, OutPoint, PublicKey, SigHash, SigHashType, Transaction, TxIn, TxOut,
};
use bdk::descriptor::Descriptor;
use bdk::miniscript::DescriptorTrait;
use itertools::Itertools;
use secp256k1_zkp::{self, schnorrsig, EcdsaAdaptorSignature, SecretKey, Signature, SECP256K1};
use std::collections::HashMap;
use std::iter::FromIterator;
/// In satoshi per vbyte.
const SATS_PER_VBYTE: f64 = 1.0;
pub(super) fn lock_transaction(
maker_psbt: PartiallySignedTransaction,
taker_psbt: PartiallySignedTransaction,
maker_pk: PublicKey,
taker_pk: PublicKey,
amount: Amount,
) -> PartiallySignedTransaction {
let lock_descriptor = lock_descriptor(maker_pk, taker_pk);
let maker_change = maker_psbt
.global
.unsigned_tx
.output
.into_iter()
.filter(|out| {
out.script_pubkey != DUMMY_2OF2_MULTISIG.parse().expect("To be a valid script")
})
.collect::<Vec<_>>();
let taker_change = taker_psbt
.global
.unsigned_tx
.output
.into_iter()
.filter(|out| {
out.script_pubkey != DUMMY_2OF2_MULTISIG.parse().expect("To be a valid script")
})
.collect::<Vec<_>>();
let lock_output = TxOut {
value: amount.as_sat(),
script_pubkey: lock_descriptor.script_pubkey(),
};
let input = vec![
maker_psbt.global.unsigned_tx.input,
taker_psbt.global.unsigned_tx.input,
]
.concat();
let output = std::iter::once(lock_output)
.chain(maker_change)
.chain(taker_change)
.collect();
let lock_tx = Transaction {
version: 2,
lock_time: 0,
input,
output,
};
PartiallySignedTransaction {
global: Global::from_unsigned_tx(lock_tx).expect("to be unsigned"),
inputs: vec![maker_psbt.inputs, taker_psbt.inputs].concat(),
outputs: vec![maker_psbt.outputs, taker_psbt.outputs].concat(),
}
}
#[derive(Debug, Clone)]
pub(super) struct CommitTransaction {
inner: Transaction,
descriptor: Descriptor<PublicKey>,
amount: Amount,
sighash: SigHash,
lock_descriptor: Descriptor<PublicKey>,
fee: u64,
}
impl CommitTransaction {
/// Expected size of signed transaction in virtual bytes, plus a
/// buffer to account for different signature lengths.
const SIGNED_VBYTES: f64 = 148.5 + (3.0 * 2.0) / 4.0;
pub(super) fn new(
lock_tx: &Transaction,
(maker_pk, maker_rev_pk, maker_publish_pk): (PublicKey, PublicKey, PublicKey),
(taker_pk, taker_rev_pk, taker_publish_pk): (PublicKey, PublicKey, PublicKey),
) -> Result<Self> {
let lock_descriptor = lock_descriptor(maker_pk, taker_pk);
let (lock_outpoint, lock_amount) = {
let outpoint = lock_tx
.outpoint(&lock_descriptor.script_pubkey())
.context("lock script not found in lock tx")?;
let amount = lock_tx.output[outpoint.vout as usize].value;
(outpoint, amount)
};
let lock_input = TxIn {
previous_output: lock_outpoint,
..Default::default()
};
let descriptor = commit_descriptor(
(maker_pk, maker_rev_pk, maker_publish_pk),
(taker_pk, taker_rev_pk, taker_publish_pk),
);
let output = TxOut {
value: lock_amount,
script_pubkey: descriptor.script_pubkey(),
};
let mut inner = Transaction {
version: 2,
lock_time: 0,
input: vec![lock_input],
output: vec![output],
};
let fee = (Self::SIGNED_VBYTES * SATS_PER_VBYTE as f64) as u64;
let commit_tx_amount = lock_amount - fee as u64;
inner.output[0].value = commit_tx_amount;
let sighash = SigHashCache::new(&inner).signature_hash(
0,
&lock_descriptor.script_code(),
lock_amount,
SigHashType::All,
);
Ok(Self {
inner,
descriptor,
lock_descriptor,
amount: Amount::from_sat(commit_tx_amount),
sighash,
fee,
})
}
pub(super) fn encsign(
&self,
sk: SecretKey,
publish_them_pk: &PublicKey,
) -> EcdsaAdaptorSignature {
EcdsaAdaptorSignature::encrypt(
SECP256K1,
&self.sighash.to_message(),
&sk,
&publish_them_pk.key,
)
}
pub(super) fn into_inner(self) -> Transaction {
self.inner
}
fn outpoint(&self) -> OutPoint {
self.inner
.outpoint(&self.descriptor.script_pubkey())
.expect("to find commit output in commit tx")
}
fn amount(&self) -> Amount {
self.amount
}
fn descriptor(&self) -> Descriptor<PublicKey> {
self.descriptor.clone()
}
fn fee(&self) -> u64 {
self.fee
}
}
#[derive(Debug, Clone)]
pub(super) struct ContractExecutionTransaction {
inner: Transaction,
msg_nonce_pairs: Vec<(Vec<u8>, schnorrsig::PublicKey)>,
sighash: SigHash,
commit_descriptor: Descriptor<PublicKey>,
}
impl ContractExecutionTransaction {
/// Expected size of signed transaction in virtual bytes, plus a
/// buffer to account for different signature lengths.
const SIGNED_VBYTES: f64 = 206.5 + (3.0 * 2.0) / 4.0;
pub(super) fn new(
commit_tx: &CommitTransaction,
payout: Payout,
maker_address: &Address,
taker_address: &Address,
relative_timelock_in_blocks: u32,
) -> Result<Self> {
let msg_nonce_pairs = payout.msg_nonce_pairs.clone();
let commit_input = TxIn {
previous_output: commit_tx.outpoint(),
sequence: relative_timelock_in_blocks,
..Default::default()
};
let mut fee = Self::SIGNED_VBYTES * SATS_PER_VBYTE;
fee += commit_tx.fee() as f64;
let output = payout
.with_updated_fee(
Amount::from_sat(fee as u64),
maker_address.script_pubkey().dust_value(),
taker_address.script_pubkey().dust_value(),
)?
.into_txouts(maker_address, taker_address);
let tx = Transaction {
version: 2,
lock_time: 0,
input: vec![commit_input],
output,
};
let sighash = SigHashCache::new(&tx).signature_hash(
0,
&commit_tx.descriptor.script_code(),
commit_tx.amount.as_sat(),
SigHashType::All,
);
Ok(Self {
inner: tx,
msg_nonce_pairs,
sighash,
commit_descriptor: commit_tx.descriptor(),
})
}
pub(super) fn encsign(
&self,
sk: SecretKey,
oracle_pk: &schnorrsig::PublicKey,
) -> Result<EcdsaAdaptorSignature> {
let adaptor_point = compute_adaptor_point(oracle_pk, &self.msg_nonce_pairs)?;
Ok(EcdsaAdaptorSignature::encrypt(
SECP256K1,
&self.sighash.to_message(),
&sk,
&adaptor_point,
))
}
pub(super) fn into_inner(self) -> Transaction {
self.inner
}
}
#[derive(Debug, Clone)]
pub(super) struct RefundTransaction {
inner: Transaction,
sighash: SigHash,
commit_output_descriptor: Descriptor<PublicKey>,
}
impl RefundTransaction {
/// Expected size of signed transaction in virtual bytes, plus a
/// buffer to account for different signature lengths.
const SIGNED_VBYTES: f64 = 206.5 + (3.0 * 2.0) / 4.0;
pub(super) fn new(
commit_tx: &CommitTransaction,
relative_locktime_in_blocks: u32,
maker_address: &Address,
taker_address: &Address,
maker_amount: Amount,
taker_amount: Amount,
) -> Self {
let commit_input = TxIn {
previous_output: commit_tx.outpoint(),
sequence: relative_locktime_in_blocks,
..Default::default()
};
let maker_output = TxOut {
value: maker_amount.as_sat(),
script_pubkey: maker_address.script_pubkey(),
};
let taker_output = TxOut {
value: taker_amount.as_sat(),
script_pubkey: taker_address.script_pubkey(),
};
let mut tx = Transaction {
version: 2,
lock_time: 0,
input: vec![commit_input],
output: vec![maker_output, taker_output],
};
let mut fee = Self::SIGNED_VBYTES * SATS_PER_VBYTE;
fee += commit_tx.fee() as f64;
tx.output[0].value -= (fee / 2.0) as u64;
tx.output[1].value -= (fee / 2.0) as u64;
let commit_output_descriptor = commit_tx.descriptor();
let sighash = SigHashCache::new(&tx).signature_hash(
0,
&commit_tx.descriptor().script_code(),
commit_tx.amount().as_sat(),
SigHashType::All,
);
Self {
inner: tx,
sighash,
commit_output_descriptor,
}
}
pub(super) fn sighash(&self) -> SigHash {
self.sighash
}
pub(super) fn into_inner(self) -> Transaction {
self.inner
}
}
pub fn punish_transaction(
commit_descriptor: &Descriptor<PublicKey>,
address: &Address,
encsig: EcdsaAdaptorSignature,
sk: SecretKey,
revocation_them_sk: SecretKey,
pub_them_pk: PublicKey,
revoked_commit_tx: &Transaction,
) -> Result<Transaction> {
/// Expected size of signed transaction in virtual bytes, plus a
/// buffer to account for different signature lengths.
const SIGNED_VBYTES: f64 = 219.5 + (3.0 * 3.0) / 4.0;
let input = revoked_commit_tx
.input
.clone()
.into_iter()
.exactly_one()
.context("commit transaction inputs != 1")?;
let publish_them_sk = input
.witness
.iter()
.filter_map(|elem| {
let elem = elem.as_slice();
Signature::from_der(&elem[..elem.len() - 1]).ok()
})
.find_map(|sig| encsig.recover(SECP256K1, &sig, &pub_them_pk.key).ok())
.context("could not recover publish sk from commit tx")?;
let commit_outpoint = revoked_commit_tx
.outpoint(&commit_descriptor.script_pubkey())
.expect("to find commit output in commit tx");
let commit_amount = revoked_commit_tx.output[commit_outpoint.vout as usize].value;
let mut punish_tx = {
let output = TxOut {
value: commit_amount,
script_pubkey: address.script_pubkey(),
};
let mut tx = Transaction {
version: 2,
lock_time: 0,
input: vec![TxIn {
previous_output: commit_outpoint,
..Default::default()
}],
output: vec![output],
};
let fee = SIGNED_VBYTES * SATS_PER_VBYTE;
tx.output[0].value = commit_amount - fee as u64;
tx
};
let sighash = SigHashCache::new(&punish_tx).signature_hash(
0,
&commit_descriptor.script_code(),
commit_amount,
SigHashType::All,
);
let satisfier = {
let pk = {
let key = secp256k1_zkp::PublicKey::from_secret_key(SECP256K1, &sk);
PublicKey {
compressed: true,
key,
}
};
let pk_hash = pk.pubkey_hash().as_hash();
let sig_sk = SECP256K1.sign(&sighash.to_message(), &sk);
let pub_them_pk_hash = pub_them_pk.pubkey_hash().as_hash();
let sig_pub_them = SECP256K1.sign(&sighash.to_message(), &publish_them_sk);
let rev_them_pk = {
let key = secp256k1_zkp::PublicKey::from_secret_key(SECP256K1, &revocation_them_sk);
PublicKey {
compressed: true,
key,
}
};
let rev_them_pk_hash = rev_them_pk.pubkey_hash().as_hash();
let sig_rev_them = SECP256K1.sign(&sighash.to_message(), &revocation_them_sk);
let sighash_all = SigHashType::All;
HashMap::from_iter(vec![
(pk_hash, (pk, (sig_sk, sighash_all))),
(pub_them_pk_hash, (pub_them_pk, (sig_pub_them, sighash_all))),
(rev_them_pk_hash, (rev_them_pk, (sig_rev_them, sighash_all))),
])
};
commit_descriptor.satisfy(&mut punish_tx.input[0], satisfier)?;
Ok(punish_tx)
}
Loading…
Cancel
Save