Skip to content

Commit

Permalink
Merge pull request #57 from chainwayxyz/taproot_keypath_spend_fix
Browse files Browse the repository at this point in the history
`fund_raw_transaction` change position index
  • Loading branch information
ceyhunsen authored Sep 3, 2024
2 parents e6179ab + b78cbfa commit 0746bd8
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 21 deletions.
30 changes: 26 additions & 4 deletions src/client/rpc_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ impl RpcApi for Client {
/// This is the reason, this function will only throw errors in case of a
/// function calls this. Tester should implement corresponding function in
/// this impl block.
#[tracing::instrument(skip_all)]
fn call<T: for<'a> serde::de::Deserialize<'a>>(
&self,
cmd: &str,
Expand All @@ -54,6 +55,7 @@ impl RpcApi for Client {
Err(Error::ReturnedError(msg))
}

#[tracing::instrument(skip_all)]
fn send_raw_transaction<R: bitcoincore_rpc::RawTx>(
&self,
tx: R,
Expand All @@ -64,6 +66,7 @@ impl RpcApi for Client {

Ok(tx.compute_txid())
}
#[tracing::instrument(skip_all)]
fn get_raw_transaction(
&self,
txid: &bitcoin::Txid,
Expand All @@ -78,6 +81,7 @@ impl RpcApi for Client {
/// transaction's state in blockchain. It is recommmended to use
/// `get_raw_transaction` for information about transaction's inputs and
/// outputs.
#[tracing::instrument(skip_all)]
fn get_raw_transaction_info(
&self,
txid: &bitcoin::Txid,
Expand Down Expand Up @@ -176,6 +180,7 @@ impl RpcApi for Client {
})
}

#[tracing::instrument(skip_all)]
fn get_transaction(
&self,
txid: &bitcoin::Txid,
Expand Down Expand Up @@ -245,6 +250,7 @@ impl RpcApi for Client {
/// Reason this call behaves like this is there are no wallet
/// implementation. This is intended way to generate inputs for other
/// transactions.
#[tracing::instrument(skip_all)]
fn send_to_address(
&self,
address: &Address<NetworkChecked>,
Expand Down Expand Up @@ -274,6 +280,7 @@ impl RpcApi for Client {
/// Creates a random secret/public key pair and generates a Bitcoin address
/// from witness program. Please note that this address is not hold in
/// ledger in any way.
#[tracing::instrument(skip_all)]
fn get_new_address(
&self,
_label: Option<&str>,
Expand All @@ -286,6 +293,7 @@ impl RpcApi for Client {

/// Generates `block_num` amount of block rewards to `address`. Also mines
/// current mempool transactions to a block.
#[tracing::instrument(skip_all)]
fn generate_to_address(
&self,
block_num: u64,
Expand All @@ -305,6 +313,7 @@ impl RpcApi for Client {
///
/// This will include mempool txouts regardless of the `include_mempool`
/// flag. `coinbase` will be set to false regardless if it is or not.
#[tracing::instrument(skip_all)]
fn get_tx_out(
&self,
txid: &bitcoin::Txid,
Expand Down Expand Up @@ -334,6 +343,7 @@ impl RpcApi for Client {
}))
}

#[tracing::instrument(skip_all)]
fn get_best_block_hash(&self) -> bitcoincore_rpc::Result<bitcoin::BlockHash> {
let current_height = self.ledger.get_block_height()?;
let current_block = self.ledger.get_block_with_height(current_height)?;
Expand All @@ -342,25 +352,29 @@ impl RpcApi for Client {
Ok(block_hash)
}

#[tracing::instrument(skip_all)]
fn get_block(&self, hash: &bitcoin::BlockHash) -> bitcoincore_rpc::Result<bitcoin::Block> {
Ok(self.ledger.get_block_with_hash(*hash)?)
}

#[tracing::instrument(skip_all)]
fn get_block_header(
&self,
hash: &bitcoin::BlockHash,
) -> bitcoincore_rpc::Result<bitcoin::block::Header> {
Ok(self.ledger.get_block_with_hash(*hash)?.header)
}

#[tracing::instrument(skip_all)]
fn get_block_count(&self) -> bitcoincore_rpc::Result<u64> {
Ok(self.ledger.get_block_height()?.into())
}

#[tracing::instrument(skip_all)]
fn fund_raw_transaction<R: bitcoincore_rpc::RawTx>(
&self,
tx: R,
_options: Option<&json::FundRawTransactionOptions>,
options: Option<&json::FundRawTransactionOptions>,
_is_witness: Option<bool>,
) -> bitcoincore_rpc::Result<json::FundRawTransactionResult> {
let mut transaction: Transaction = encode::deserialize_hex(&tx.raw_hex())?;
Expand Down Expand Up @@ -407,18 +421,26 @@ impl RpcApi for Client {
..Default::default()
};

transaction.input.insert(0, txin);
let insert_idx = match options {
Some(option) => option
.change_position
.unwrap_or((transaction.input.len()) as u32),
None => (transaction.input.len()) as u32,
};

transaction.input.insert(insert_idx as usize, txin);
tracing::debug!("New transaction: {transaction:?}");

let hex = serialize(&transaction);

Ok(json::FundRawTransactionResult {
hex,
fee: Amount::from_sat(0),
change_position: 0,
change_position: insert_idx as i32,
})
}

#[tracing::instrument(skip_all)]
fn sign_raw_transaction_with_wallet<R: bitcoincore_rpc::RawTx>(
&self,
tx: R,
Expand Down Expand Up @@ -807,7 +829,7 @@ mod tests {
let tx = deserialize::<Transaction>(&res.hex).unwrap();

assert_ne!(og_tx, tx);
assert_eq!(res.change_position, 0);
assert_ne!(res.change_position, -1);

let res = rpc.fund_raw_transaction(&tx, None, None).unwrap();
let new_tx = String::consensus_decode(&mut res.hex.as_slice()).unwrap();
Expand Down
59 changes: 44 additions & 15 deletions src/ledger/spending_requirements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use bitcoin::{
taproot::{ControlBlock, LeafVersion},
TapLeafHash, XOnlyPublicKey,
};
use bitcoin::{Script, WitnessProgram};
use bitcoin::{Script, TapSighashType, WitnessProgram};
use secp256k1::Message;

#[derive(Default)]
Expand All @@ -23,6 +23,7 @@ pub struct SpendingRequirementsReturn {
}

impl Ledger {
#[tracing::instrument(skip_all)]
pub fn p2wpkh_check(
&self,
tx: &Transaction,
Expand Down Expand Up @@ -79,6 +80,7 @@ impl Ledger {
Ok(())
}

#[tracing::instrument(skip_all)]
pub fn p2wsh_check(
&self,
tx: &Transaction,
Expand Down Expand Up @@ -123,6 +125,7 @@ impl Ledger {
})
}

#[tracing::instrument(skip_all)]
pub fn p2tr_check(
&self,
tx: &Transaction,
Expand All @@ -147,24 +150,39 @@ impl Ledger {
}

let mut witness = tx.input[input_idx].witness.to_vec();
let mut annex: Option<Vec<u8>> = None;

// Key path spend.
if witness.len() == 1 {
let signature = witness.pop().unwrap();
let signature = bitcoin::taproot::Signature::from_slice(&signature).unwrap();
tracing::trace!("Signature {:?}", signature);

let x_only_public_key = XOnlyPublicKey::from_slice(&sig_pub_key_bytes[2..]).unwrap();
let mut sighashcache = SighashCache::new(tx.clone());
let h = sighashcache
tracing::trace!("X-only public key is {}", x_only_public_key);

let mut sighash_cache = SighashCache::new(tx);
let taproot_key_spend_signature_hash = sighash_cache
.taproot_key_spend_signature_hash(
input_idx,
&Prevouts::All(txouts),
&match signature.sighash_type {
TapSighashType::Default | TapSighashType::All | TapSighashType::None => {
Prevouts::All(txouts)
}
TapSighashType::SinglePlusAnyoneCanPay => {
Prevouts::One(input_idx, txouts[input_idx].clone())
}
_ => {
return Err(LedgerError::SpendingRequirements(format!(
"Unimplemented sighash type {}",
signature.sighash_type
)))
}
},
signature.sighash_type,
)
.unwrap();

let msg = Message::from(h);
let msg = Message::from(taproot_key_spend_signature_hash);

return match x_only_public_key.verify(&secp, &msg, &signature.signature) {
Ok(()) => Ok(SpendingRequirementsReturn {
Expand All @@ -177,23 +195,34 @@ impl Ledger {
x_only_public_key, signature.signature, e
))),
};
}

if witness.len() >= 2 && witness[witness.len() - 1][0] == 0x50 {
annex = Some(witness.pop().unwrap());
} else if witness.len() < 2 {
return Err(LedgerError::SpendingRequirements("The number of witness elements should be at least two (the script and the control block).".to_owned()));
return Err(
LedgerError::SpendingRequirements(
"The number of witness elements should be at least two (the script and the control block).".to_owned()
)
);
}

let annex: Option<Vec<u8>> = if witness.len() >= 2 && witness[witness.len() - 1][0] == 0x50
{
Some(witness.pop().unwrap())
} else {
None
};

let control_block = ControlBlock::decode(&witness.pop().unwrap()).unwrap();
let script_buf = witness.pop().unwrap();
let script = Script::from_bytes(&script_buf);

let out_pk = XOnlyPublicKey::from_slice(&sig_pub_key_bytes[2..]).unwrap();
let out_pk = TweakedPublicKey::dangerous_assume_tweaked(out_pk);
let x_only_public_key = XOnlyPublicKey::from_slice(&sig_pub_key_bytes[2..]).unwrap();
let tweaked_x_only_public_key =
TweakedPublicKey::dangerous_assume_tweaked(x_only_public_key);

let res = control_block.verify_taproot_commitment(&secp, out_pk.to_inner(), script);
if !res {
if !control_block.verify_taproot_commitment(
&secp,
tweaked_x_only_public_key.to_inner(),
script,
) {
return Err(LedgerError::SpendingRequirements(
"The taproot commitment does not match the Taproot public key.".to_owned(),
));
Expand Down
4 changes: 2 additions & 2 deletions src/ledger/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use rusqlite::params;

impl Ledger {
/// Adds transaction to blockchain, after verifying.
#[tracing::instrument]
#[tracing::instrument(skip_all)]
pub fn add_transaction(&self, transaction: Transaction) -> Result<Txid, LedgerError> {
self.check_transaction(&transaction)?;

Expand Down Expand Up @@ -160,7 +160,7 @@ impl Ledger {
/// 3. Is script execution successful?
///
/// No checks for if that UTXO is spendable or not.
#[tracing::instrument]
#[tracing::instrument(skip_all)]
pub fn check_transaction(&self, transaction: &Transaction) -> Result<(), LedgerError> {
self.check_transaction_funds(transaction)?;

Expand Down

0 comments on commit 0746bd8

Please sign in to comment.