Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test Framework + Claim/Refund Integration tests #24

Merged
merged 6 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ bitcoin = {version = "0.31.1", features = ["rand", "base64", "rand-std"]}
elements = { version = "0.24.0", features = ["serde"] }
lightning-invoice = "0.26.0"

[dev-dependencies]
bitcoind = {version = "0.34.1", features = ["25_0"] }

#Empty default feature set, (helpful to generalise in github actions)
[features]
default = []
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ pub mod util;
pub use bitcoin::secp256k1::{Keypair, Secp256k1};
pub use elements::secp256k1_zkp::{Keypair as ZKKeyPair, Secp256k1 as ZKSecp256k1};
pub use lightning_invoice::Bolt11Invoice;

pub use swaps::bitcoin::{BtcSwapScript, BtcSwapTx};
pub use swaps::boltz::{SwapTxKind, SwapType};
1 change: 1 addition & 0 deletions src/network/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod electrum;
pub enum Chain {
Bitcoin,
BitcoinTestnet,
BitcoinRegtest,
Liquid,
LiquidTestnet,
}
149 changes: 89 additions & 60 deletions src/swaps/bitcoin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use bitcoin::consensus::Decodable;
use bitcoin::ecdsa::Signature;
use bitcoin::hashes::Hash;
use bitcoin::hex::{DisplayHex, FromHex};
use bitcoin::script::{PushBytes, PushBytesBuf};
use bitcoin::secp256k1::{Keypair, Message, Secp256k1};
use bitcoin::transaction::Version;
use bitcoin::{
Expand All @@ -10,9 +11,9 @@ use bitcoin::{
Address, OutPoint, PublicKey,
};
use bitcoin::{sighash::SighashCache, Network, Sequence, Transaction, TxIn, TxOut, Witness};
use bitcoin::{Amount, Txid};
use bitcoin::{Amount, EcdsaSighashType, Txid};
use electrum_client::ElectrumApi;
use std::ops::Add;
use std::ops::{Add, Index};
use std::str::FromStr;

use crate::{
Expand All @@ -27,6 +28,7 @@ use bitcoin::{blockdata::locktime::absolute::LockTime, hashes::hash160};
use super::boltz::SwapType;

/// Bitcoin swap script helper.
// TODO: This should encode the network at global level.
#[derive(Debug, PartialEq, Clone)]
pub struct BtcSwapScript {
pub swap_type: SwapType,
Expand Down Expand Up @@ -233,9 +235,15 @@ impl BtcSwapScript {
/// Submarine swaps use p2shwsh. Reverse swaps use p2wsh.
pub fn to_address(&self, network: Chain) -> Result<Address, Error> {
let script = self.to_script()?;
let network = match network {
let mut network = match network {
Chain::Bitcoin => Network::Bitcoin,
_ => Network::Testnet,
Chain::BitcoinRegtest => Network::Regtest,
Chain::BitcoinTestnet => Network::Testnet,
_ => {
return Err(Error::Protocol(
"Liquid chain used for Bitcoin operations".to_string(),
))
}
};
match self.swap_type {
SwapType::Submarine => Ok(Address::p2shwsh(&script, network)),
Expand Down Expand Up @@ -285,11 +293,11 @@ fn bytes_to_u32_little_endian(bytes: &[u8]) -> u32 {
/// A structure representing either a Claim or a Refund Tx.
/// This Tx spends from the HTLC.
pub struct BtcSwapTx {
kind: SwapTxKind,
swap_script: BtcSwapScript,
output_address: Address,
pub kind: SwapTxKind, // These fields needs to be public to do manual creation in IT.
pub swap_script: BtcSwapScript,
pub output_address: Address,
// The HTLC utxo in (Outpoint, Amount) Pair
utxo: (OutPoint, u64),
pub utxo: (OutPoint, u64),
}
impl BtcSwapTx {
/// Craft a new ClaimTx. Only works for Reverse Swaps.
Expand Down Expand Up @@ -367,10 +375,15 @@ impl BtcSwapTx {
absolute_fees: u64,
) -> Result<Transaction, Error> {
debug_assert!(
self.swap_script.swap_type != SwapType::Submarine && self.kind != SwapTxKind::Refund,
self.swap_script.swap_type != SwapType::Submarine,
"Cannot sign claim tx, for a normal-swap, refund-type"
);

debug_assert!(
self.kind != SwapTxKind::Refund,
"Cannot sign claim with Refund type BTCSwapTx"
);

let preimage_bytes = if let Some(value) = preimage.bytes {
value
} else {
Expand All @@ -379,9 +392,8 @@ impl BtcSwapTx {
)));
};

let sequence = Sequence::from_consensus(0xFFFFFFFF);
let unsigned_input: TxIn = TxIn {
sequence: sequence,
sequence: Sequence::MAX,
previous_output: self.utxo.0,
script_sig: ScriptBuf::new(),
witness: Witness::new(),
Expand All @@ -391,106 +403,123 @@ impl BtcSwapTx {
script_pubkey: self.output_address.payload().script_pubkey(),
value: output_amount,
};
let unsigned_tx = Transaction {
let mut unsigned_tx = Transaction {
version: Version(1),
lock_time: self.swap_script.locktime,
input: vec![unsigned_input],
output: vec![output.clone()],
};

let redeem_script = self.swap_script.to_script()?;
// Compute the signature
let witness_script = self.swap_script.to_script()?;
let secp = Secp256k1::new();
let hash_type = bitcoin::sighash::EcdsaSighashType::All;
let sighash = SighashCache::new(unsigned_tx.clone()).p2wsh_signature_hash(
let sighash = SighashCache::new(&unsigned_tx).p2wsh_signature_hash(
0,
&redeem_script,
&witness_script,
Amount::from_sat(self.utxo.1),
hash_type,
)?;
let sighash_message = Message::from_digest_slice(&sighash[..])?;
let sighash_message = Message::from_digest(sighash.to_byte_array());
let signature = secp.sign_ecdsa(&sighash_message, &keys.secret_key());
signature.verify(&sighash_message, &keys.public_key())?;
let ecdsa_signature = Signature::sighash_all(signature);

// https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki
let mut witness = Witness::new();
witness.push_ecdsa_signature(&ecdsa_signature);
witness.push(preimage_bytes);
witness.push(redeem_script.as_bytes());
witness.push(witness_script.as_bytes());

let signed_txin = TxIn {
previous_output: self.utxo.0,
script_sig: ScriptBuf::new(),
sequence: sequence,
witness: witness,
};
let signed_tx = Transaction {
version: Version(1),
lock_time: self.swap_script.locktime,
input: vec![signed_txin],
output: vec![output.clone()],
};
Ok(signed_tx)
unsigned_tx
.input
.get_mut(0)
.expect("input expected")
.witness = witness;

Ok(unsigned_tx)
}

/// Sign a submarine swap refund transaction.
/// Panics if called on Reverse Swap, Claim type.
pub fn sign_refund(&self, keys: &Keypair, absolute_fees: u64) -> Result<Transaction, Error> {
debug_assert!(
self.swap_script.swap_type != SwapType::Submarine && self.kind != SwapTxKind::Refund,
"Cannot sign refund tx, for a reverse-swap, claim-type"
self.swap_script.swap_type != SwapType::ReverseSubmarine,
"Cannot sign refund tx, for a reverse-swap"
);

debug_assert!(
self.kind != SwapTxKind::Claim,
"Cannot sign refund with a claim-type BtcSwapTx"
);

let sequence = Sequence::from_consensus(0xFFFFFFFF);
let unsigned_input: TxIn = TxIn {
sequence: sequence,
sequence: Sequence::ZERO, // enables absolute locktime
previous_output: self.utxo.0,
script_sig: ScriptBuf::new(),
witness: Witness::new(),
};
let output_amount: Amount = Amount::from_sat(self.utxo.1 - absolute_fees);
let output: TxOut = TxOut {
script_pubkey: self.output_address.payload().script_pubkey(),
script_pubkey: self.output_address.script_pubkey(),
value: output_amount,
};
let unsigned_tx = Transaction {
version: Version(1),
let mut unsigned_tx = Transaction {
version: Version(2),
lock_time: self.swap_script.locktime,
input: vec![unsigned_input],
output: vec![output.clone()],
output: vec![output],
};

let redeem_script = self.swap_script.to_script()?;
// The whole witness script of the swap tx.
let witness_script = self.swap_script.to_script()?;

// a p2wsh script pubkey, from the witness_script to set the script_sig field
let redeem_script = witness_script.to_p2wsh();
let mut script_sig = ScriptBuf::new();
let mut push_bytes = PushBytesBuf::new();
push_bytes.extend_from_slice(redeem_script.as_bytes());
script_sig.push_slice(push_bytes);

// The script pubkey of the previous output for sighash calculation
let script_pubkey = self
.swap_script
.to_address(Chain::BitcoinTestnet)
.unwrap()
.script_pubkey();

// Create signature
let secp = Secp256k1::new();
let hash_type = bitcoin::sighash::EcdsaSighashType::All;
let sighash = SighashCache::new(unsigned_tx.clone()).p2wpkh_signature_hash(
let sighash = SighashCache::new(&unsigned_tx).p2wsh_signature_hash(
0,
&redeem_script,
&witness_script,
Amount::from_sat(self.utxo.1),
hash_type,
EcdsaSighashType::All,
)?;
let sighash_message = Message::from_digest_slice(&sighash[..])?;
let sighash_message = Message::from_digest(sighash.to_byte_array());
let signature = secp.sign_ecdsa(&sighash_message, &keys.secret_key());
let ecdsa_signature = Signature::sighash_all(signature);

// Assemble the witness data
// https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki
let mut witness = Witness::new();
witness.push_ecdsa_signature(&ecdsa_signature);
witness.push([0]);
witness.push(redeem_script.as_bytes());

let signed_txin = TxIn {
previous_output: self.utxo.0,
script_sig: ScriptBuf::new(),
sequence: sequence,
witness: witness,
};
let signed_tx = Transaction {
version: Version(1),
lock_time: self.swap_script.locktime,
input: vec![signed_txin],
output: vec![output.clone()],
};
Ok(signed_tx)
witness.push(Vec::new()); // empty push to activate the timelock branch of the script
witness.push(witness_script);

// set scriptsig and witness field
unsigned_tx
.input
.get_mut(0)
.expect("input expected")
.script_sig = script_sig;
unsigned_tx
.input
.get_mut(0)
.expect("input expected")
.witness = witness;

Ok(unsigned_tx)
}

/// Calculate the size of a transaction.
Expand Down
4 changes: 2 additions & 2 deletions src/swaps/boltz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@ impl CreateSwapResponse {
chain: Chain,
) -> Result<(), Error> {
match chain {
Chain::Bitcoin | Chain::BitcoinTestnet => {
Chain::Bitcoin | Chain::BitcoinTestnet | Chain::BitcoinRegtest => {
let boltz_sub_script =
BtcSwapScript::submarine_from_str(&self.get_redeem_script()?)?;

Expand Down Expand Up @@ -847,7 +847,7 @@ impl CreateSwapResponse {
}
}
match chain {
Chain::Bitcoin | Chain::BitcoinTestnet => {
Chain::Bitcoin | Chain::BitcoinTestnet | Chain::BitcoinRegtest => {
let boltz_rev_script = BtcSwapScript::reverse_from_str(&self.get_redeem_script()?)?;

let constructed_rev_script = BtcSwapScript {
Expand Down
Loading
Loading