Skip to content

Commit

Permalink
Fix/invalid signature error in external signing flow (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
kobby-pentangeli committed Apr 3, 2024
1 parent 25d4d3d commit 54371f5
Show file tree
Hide file tree
Showing 13 changed files with 498 additions and 88 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ edition = "2021"
async-trait = "0.1"
bitcoin = { version = "0.31", features = ["rand"] }
ciborium = "0.2"
hex = "0.4"
http = "1"
log = "0.4"
rand = { version = "0.8" }
Expand Down
6 changes: 3 additions & 3 deletions examples/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use argh::FromArgs;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::{Address, Network, PrivateKey};
use log::{debug, info};
use ord_rs::wallet::{CreateCommitTransactionArgs, RevealTransactionArgs};
use ord_rs::wallet::{CreateCommitTransactionArgsV2, RevealTransactionArgs};
use ord_rs::{Brc20, OrdTransactionBuilder};
use utils::rpc_client;

Expand Down Expand Up @@ -84,9 +84,9 @@ async fn main() -> anyhow::Result<()> {
};

let commit_tx = builder
.build_commit_transaction(
.build_commit_transaction_with_fixed_fees(
network,
CreateCommitTransactionArgs {
CreateCommitTransactionArgsV2 {
inputs,
inscription: Brc20::deploy(ticker, amount, Some(limit), None),
txin_script_pubkey: sender_address.script_pubkey(),
Expand Down
6 changes: 3 additions & 3 deletions examples/mint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use argh::FromArgs;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::{Address, Network, PrivateKey};
use log::{debug, info};
use ord_rs::wallet::{CreateCommitTransactionArgs, RevealTransactionArgs};
use ord_rs::wallet::{CreateCommitTransactionArgsV2, RevealTransactionArgs};
use ord_rs::{Brc20, OrdTransactionBuilder};

use self::utils::rpc_client;
Expand Down Expand Up @@ -79,9 +79,9 @@ async fn main() -> anyhow::Result<()> {
};

let commit_tx = builder
.build_commit_transaction(
.build_commit_transaction_with_fixed_fees(
network,
CreateCommitTransactionArgs {
CreateCommitTransactionArgsV2 {
inputs,
inscription: Brc20::mint(ticker, amount),
txin_script_pubkey: sender_address.script_pubkey(),
Expand Down
6 changes: 3 additions & 3 deletions examples/transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use argh::FromArgs;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::{Address, Network, PrivateKey};
use log::{debug, info};
use ord_rs::wallet::{CreateCommitTransactionArgs, RevealTransactionArgs};
use ord_rs::wallet::{CreateCommitTransactionArgsV2, RevealTransactionArgs};
use ord_rs::{Brc20, OrdTransactionBuilder};

use self::utils::rpc_client;
Expand Down Expand Up @@ -78,9 +78,9 @@ async fn main() -> anyhow::Result<()> {
};

let commit_tx = builder
.build_commit_transaction(
.build_commit_transaction_with_fixed_fees(
network,
CreateCommitTransactionArgs {
CreateCommitTransactionArgsV2 {
inputs,
inscription: Brc20::transfer(ticker, amount),
txin_script_pubkey: sender_address.script_pubkey(),
Expand Down
1 change: 1 addition & 0 deletions examples/utils/fee.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use bitcoin::{Amount, Network};

#[allow(dead_code)]
pub struct Fees {
pub commit_fee: Amount,
pub reveal_fee: Amount,
Expand Down
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use thiserror::Error;
/// Ordinal transaction handling error types
#[derive(Error, Debug)]
pub enum OrdError {
#[error("Hex codec error: {0}")]
HexCodec(#[from] hex::FromHexError),
#[error("Ord codec error: {0}")]
Codec(#[from] serde_json::Error),
#[error("Bitcoin sighash error: {0}")]
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub use inscription::brc20::Brc20;
pub use inscription::nft::Nft;
pub use inscription::Inscription;
pub use result::OrdResult;
pub use utils::fees::{self, MultisigConfig};
pub use wallet::{
CreateCommitTransaction, CreateCommitTransactionArgs, ExternalSigner, OrdParser,
OrdTransactionBuilder, RevealTransactionArgs, Utxo, Wallet, WalletType,
Expand Down
2 changes: 2 additions & 0 deletions src/utils/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ pub const CONTENT_ENCODING_TAG: [u8; 1] = [9];
/// Tag 11, representing a nominated inscription.
#[allow(unused)]
pub const DELEGATE_TAG: [u8; 1] = [11];

pub const POSTAGE: u64 = 333;
257 changes: 257 additions & 0 deletions src/utils/fees.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
use bitcoin::absolute::LockTime;
use bitcoin::transaction::Version;
use bitcoin::{
Address, Amount, FeeRate, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness,
};
use serde::{Deserialize, Serialize};

use super::constants::POSTAGE;
use crate::wallet::ScriptType;

/// Single ECDSA signature + SIGHASH type size in bytes.
const ECDSA_SIGHASH_SIZE: usize = 72 + 1;
/// Single Schnorr signature + SIGHASH type size for Taproot in bytes.
const SCHNORR_SIGHASH_SIZE: usize = 64 + 1;

/// Represents multisig configuration (m of n) for a transaction, if applicable.
/// Encapsulates the number of required signatures and the total number of signatories.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct MultisigConfig {
/// Number of required signatures (m)
pub required: usize,
/// Total number of signatories (n)
pub total: usize,
}

pub fn estimate_commit_fee(
unsigned_commit_tx: Transaction,
script_type: ScriptType,
current_fee_rate: FeeRate,
multisig_config: &Option<MultisigConfig>,
) -> Amount {
estimate_transaction_fees(
script_type,
unsigned_commit_tx.vsize(),
current_fee_rate,
multisig_config,
)
}

pub fn estimate_reveal_fee(
inputs: Vec<OutPoint>,
recipient_address: Address,
redeem_script: ScriptBuf,
script_type: ScriptType,
current_fee_rate: FeeRate,
multisig_config: &Option<MultisigConfig>,
) -> Amount {
let tx_out = vec![TxOut {
value: Amount::from_sat(POSTAGE),
script_pubkey: recipient_address.script_pubkey(),
}];

let mut tx_in: Vec<TxIn> = inputs
.iter()
.map(|outpoint| TxIn {
previous_output: *outpoint,
script_sig: ScriptBuf::new(),
sequence: Sequence::from_consensus(0xffffffff),
witness: Witness::new(),
})
.collect();

tx_in[0].witness.push(redeem_script.into_bytes());

let unsigned_reveal_tx = Transaction {
version: Version::TWO,
lock_time: LockTime::ZERO,
input: tx_in,
output: tx_out,
};

estimate_transaction_fees(
script_type,
unsigned_reveal_tx.vsize(),
current_fee_rate,
multisig_config,
)
}

fn estimate_transaction_fees(
script_type: ScriptType,
unsigned_tx_size: usize,
current_fee_rate: FeeRate,
multisig_config: &Option<MultisigConfig>,
) -> Amount {
let estimated_sig_size = estimate_signature_size(script_type, multisig_config);
let total_estimated_tx_size = unsigned_tx_size + estimated_sig_size;

current_fee_rate
.fee_vb(total_estimated_tx_size as u64)
.unwrap()
}

/// Estimates the total size of signatures for a given script type and multisig configuration.
fn estimate_signature_size(
script_type: ScriptType,
multisig_config: &Option<MultisigConfig>,
) -> usize {
match script_type {
// For P2WSH, calculate based on the multisig configuration if provided.
ScriptType::P2WSH => match multisig_config {
Some(config) => ECDSA_SIGHASH_SIZE * config.required,
None => ECDSA_SIGHASH_SIZE, // Default to single signature size if no multisig config is provided.
},
// For P2TR, use the fixed Schnorr signature size.
ScriptType::P2TR => SCHNORR_SIGHASH_SIZE,
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn estimate_transaction_fees_p2wsh_single_signature() {
let script_type = ScriptType::P2WSH;
let unsigned_tx_size = 100; // in vbytes
let current_fee_rate = FeeRate::from_sat_per_vb(5_u64).unwrap();
let multisig_config: Option<MultisigConfig> = None; // No multisig config for single signature

let fee = estimate_transaction_fees(
script_type,
unsigned_tx_size,
current_fee_rate,
&multisig_config,
);

// Expected fee calculation: (100 + 73) * 5 = 865 satoshis
assert_eq!(fee, Amount::from_sat(865));
}

#[test]
fn estimate_transaction_fees_p2wsh_multisig() {
let script_type = ScriptType::P2WSH;
let unsigned_tx_size = 200; // in vbytes
let current_fee_rate = FeeRate::from_sat_per_vb(10_u64).unwrap();
let multisig_config = Some(MultisigConfig {
required: 2,
total: 3,
}); // 2-of-3 multisig

let fee = estimate_transaction_fees(
script_type,
unsigned_tx_size,
current_fee_rate,
&multisig_config,
);

// Expected fee calculation: (200 + (73 * 2)) * 10 = 3460 satoshis
assert_eq!(fee, Amount::from_sat(3460));
}

#[test]
fn estimate_transaction_fees_p2tr() {
let script_type = ScriptType::P2TR;
let unsigned_tx_size = 150; // in vbytes
let current_fee_rate = FeeRate::from_sat_per_vb(1_u64).unwrap();
let multisig_config = None;

let fee = estimate_transaction_fees(
script_type,
unsigned_tx_size,
current_fee_rate,
&multisig_config,
);

// Expected fee calculation: (150 + 65) * 1 = 215 satoshis
assert_eq!(fee, Amount::from_sat(215));
}

#[test]
#[should_panic]
fn estimate_transaction_fees_overflow() {
let script_type = ScriptType::P2TR;
let unsigned_tx_size = usize::MAX;
let current_fee_rate = FeeRate::from_sat_per_vb(1_u64).unwrap();
let multisig_config = None;

let _fee = estimate_transaction_fees(
script_type,
unsigned_tx_size,
current_fee_rate,
&multisig_config,
);
}

#[test]
fn estimate_transaction_fees_low_fee_rate() {
let script_type = ScriptType::P2WSH;
let unsigned_tx_size = 250; // in vbytes
let current_fee_rate = FeeRate::from_sat_per_vb(1_u64).unwrap(); // Low fee rate
let multisig_config = Some(MultisigConfig {
required: 3,
total: 5,
}); // 3-of-5 multisig

let fee = estimate_transaction_fees(
script_type,
unsigned_tx_size,
current_fee_rate,
&multisig_config,
);

// Expected fee calculation: (250 + (73 * 3)) * 1 = 469 satoshis
assert_eq!(fee, Amount::from_sat(469));
}

#[test]
fn estimate_transaction_fees_high_fee_rate() {
let script_type = ScriptType::P2TR;
let unsigned_tx_size = 180; // in vbytes
let current_fee_rate = FeeRate::from_sat_per_vb(50_u64).unwrap(); // High fee rate
let multisig_config = None;

let fee = estimate_transaction_fees(
script_type,
unsigned_tx_size,
current_fee_rate,
&multisig_config,
);

// Expected fee calculation: (180 + 65) * 50 = 12250 satoshis
assert_eq!(fee, Amount::from_sat(12250));
}

#[test]
fn estimate_transaction_fees_varying_fee_rate() {
let script_type = ScriptType::P2WSH;
let unsigned_tx_size = 300; // in vbytes
// Simulating a fee rate that might be seen during network congestion
let fee_rates: Vec<u64> = vec![5, 10, 20, 30, 40];

for fee_rate in fee_rates {
let current_fee_rate = FeeRate::from_sat_per_vb(fee_rate).unwrap();
let multisig_config = Some(MultisigConfig {
required: 2,
total: 3,
}); // 2-of-3 multisig

let fee = estimate_transaction_fees(
script_type,
unsigned_tx_size,
current_fee_rate,
&multisig_config,
);

// Expected fee calculation changes with the fee_rate
let expected_fee = (300 + (73 * 2)) as u64 * fee_rate;
assert_eq!(
fee,
Amount::from_sat(expected_fee),
"Fee mismatch at rate: {}",
fee_rate
);
}
}
}
1 change: 1 addition & 0 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod constants;
pub mod fees;
mod push_bytes;
#[cfg(test)]
pub mod test_utils;
Expand Down
4 changes: 2 additions & 2 deletions src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ mod parser;

pub use builder::signer::{ExternalSigner, Wallet, WalletType};
pub use builder::{
CreateCommitTransaction, CreateCommitTransactionArgs, OrdTransactionBuilder,
RedeemScriptPubkey, RevealTransactionArgs, ScriptType, Utxo,
CreateCommitTransaction, CreateCommitTransactionArgs, CreateCommitTransactionArgsV2,
OrdTransactionBuilder, RedeemScriptPubkey, RevealTransactionArgs, ScriptType, Utxo,
};
pub use parser::OrdParser;
Loading

0 comments on commit 54371f5

Please sign in to comment.