From fef05afb5e59873975e8ec26de803776cf1e94e8 Mon Sep 17 00:00:00 2001 From: Yamaguchi Date: Fri, 16 Aug 2024 02:05:41 +0900 Subject: [PATCH] Fix signing for pay to contract transaction --- crates/chain/src/contract.rs | 69 ++++++++++++++++++++++-- crates/chain/src/keychain/txout_index.rs | 2 +- crates/chain/src/spk_txout_index.rs | 19 +++---- crates/wallet/src/wallet/mod.rs | 53 ++++++++++-------- crates/wallet/src/wallet/signer.rs | 7 ++- 5 files changed, 112 insertions(+), 38 deletions(-) diff --git a/crates/chain/src/contract.rs b/crates/chain/src/contract.rs index 8388fb30..4f94bf6f 100644 --- a/crates/chain/src/contract.rs +++ b/crates/chain/src/contract.rs @@ -5,8 +5,8 @@ use alloc::collections::BTreeMap; use alloc::{string::String, vec::Vec}; use num_bigint::BigUint; -use tapyrus::key::Error; -use tapyrus::secp256k1::{Scalar, SecretKey}; +use tapyrus::key::{Error, Secp256k1}; +use tapyrus::secp256k1::{All, Scalar}; use tapyrus::{ hashes::{Hash, HashEngine}, PrivateKey, @@ -38,13 +38,14 @@ impl Contract { /// Create private key for Pay-to-Contract pub fn create_private_key( &self, + payment_base_private_key: &PrivateKey, payment_base: &PublicKey, network: Network, ) -> Result { let commitment: Scalar = Self::create_pay_to_contract_commitment(payment_base, self.contract.clone()); - let sk = SecretKey::from_slice(&commitment.to_be_bytes())?; - Ok(PrivateKey::new(sk, network)) + let p2c_private_key = payment_base_private_key.inner.add_tweak(&commitment)?; + Ok(PrivateKey::new(p2c_private_key, network)) } /// Compute pay-to-contract commitment as Scalar. @@ -69,4 +70,64 @@ impl Contract { value[32 - bytes.len()..].copy_from_slice(&bytes); Scalar::from_be_bytes(value).unwrap() } + + /// Generate public key for Pay-to-Contract + pub fn create_pay_to_contract_public_key( + payment_base: &PublicKey, + contracts: Vec, + secp: &Secp256k1, + ) -> PublicKey { + let commitment: Scalar = + Self::create_pay_to_contract_commitment(payment_base, contracts.clone()); + let pubkey = payment_base.inner.add_exp_tweak(secp, &commitment).unwrap(); + PublicKey { + compressed: true, + inner: pubkey, + } + } +} + +#[cfg(test)] +mod signers_container_tests { + use core::str::FromStr; + use std::string::ToString; + + use tapyrus::key::Secp256k1; + + use super::*; + use crate::tapyrus::hashes::hex::FromHex; + + #[test] + fn test_create_private_key() { + let payment_base_private_key = PrivateKey::from_slice( + &Vec::::from_hex( + "c5580f6c26f83fb513dd5e0d1b03c36be26fcefa139b1720a7ca7c0dedd439c2", + ) + .unwrap(), + Network::Dev, + ) + .unwrap(); + let payment_base = + PublicKey::from_private_key(&Secp256k1::signing_only(), &payment_base_private_key); + let contract = Contract { + contract_id: "contract_id".to_string(), + contract: "metadata".as_bytes().to_vec(), + payment_base, + spendable: true, + }; + let key = + contract.create_private_key(&payment_base_private_key, &payment_base, Network::Dev); + assert!(key.is_ok()); + assert_eq!( + key.unwrap(), + PrivateKey::from_slice( + &Vec::::from_hex( + "78612a8498322787104379330ec41f749fd2ada016e0c0a6c2b233ed13fc8978" + ) + .unwrap(), + Network::Dev + ) + .unwrap() + ); + } } diff --git a/crates/chain/src/keychain/txout_index.rs b/crates/chain/src/keychain/txout_index.rs index c0a448d0..20109707 100644 --- a/crates/chain/src/keychain/txout_index.rs +++ b/crates/chain/src/keychain/txout_index.rs @@ -384,7 +384,7 @@ impl KeychainTxOutIndex { } /// Returns script pubkey of payment base for pay-to-contract script - pub fn p2c_spk(&self, spk: &ScriptBuf) -> Option<&ScriptBuf> { + pub fn p2c_spk(&self, spk: &ScriptBuf) -> Option<&PublicKey> { self.inner.p2c_spk(spk) } diff --git a/crates/chain/src/spk_txout_index.rs b/crates/chain/src/spk_txout_index.rs index 7b9a76b8..25c205d3 100644 --- a/crates/chain/src/spk_txout_index.rs +++ b/crates/chain/src/spk_txout_index.rs @@ -42,7 +42,7 @@ pub struct SpkTxOutIndex { /// Lookup from spk index to outpoints that had that spk spk_txouts: BTreeSet<(I, OutPoint)>, /// Pay-to-contract payment_base lookup by p2c spk - p2c_spks: HashMap, + p2c_spks: HashMap, } impl Default for SpkTxOutIndex { @@ -114,7 +114,8 @@ impl SpkTxOutIndex { let payment_base = self.p2c_spks.get(&script_pubkey); let spk_i = if let Some(p) = payment_base { - self.spk_indices.get(p.as_script()) + self.spk_indices + .get(&ScriptBuf::new_p2pkh(&p.pubkey_hash())) } else { self.spk_indices.get(&script_pubkey) }; @@ -202,12 +203,11 @@ impl SpkTxOutIndex { /// Insert payment base key for pay-to-contract script pubkey pub fn insert_p2c_spk(&mut self, spk: ScriptBuf, payment_base: PublicKey) { - let p2c_spk = ScriptBuf::new_p2pkh(&payment_base.pubkey_hash()); - self.p2c_spks.insert(spk, p2c_spk); + self.p2c_spks.insert(spk, payment_base); } /// Returns script pubkey of payment base for pay-to-contract script - pub fn p2c_spk(&self, spk: &ScriptBuf) -> Option<&ScriptBuf> { + pub fn p2c_spk(&self, spk: &ScriptBuf) -> Option<&PublicKey> { self.p2c_spks.get(spk) } @@ -328,12 +328,13 @@ impl SpkTxOutIndex { txout.script_pubkey.clone() }; let payment_base = self.p2c_spks.get(&script_pubkey); - let script_pubkey_ref = if let Some(p) = payment_base { - p + let script_pubkey = if let Some(p) = payment_base { + let hash = p.pubkey_hash(); + ScriptBuf::new_p2pkh(&hash) } else { - &script_pubkey + script_pubkey }; - if let Some(index) = self.index_of_spk(script_pubkey_ref) { + if let Some(index) = self.index_of_spk(&script_pubkey) { if range.contains(index) && txout.script_pubkey.color_id().unwrap_or_default() == *color_id { diff --git a/crates/wallet/src/wallet/mod.rs b/crates/wallet/src/wallet/mod.rs index 9c98eee3..c4f64460 100644 --- a/crates/wallet/src/wallet/mod.rs +++ b/crates/wallet/src/wallet/mod.rs @@ -21,14 +21,14 @@ use alloc::{ vec::Vec, }; -use core::fmt; use core::ops::Deref; +use core::{fmt, str::FromStr}; use descriptor::error::Error as DescriptorError; use miniscript::{ + descriptor::{SinglePub, SinglePubKey}, psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier}, - Descriptor, DescriptorPublicKey, + DefiniteDescriptorKey, Descriptor, DescriptorPublicKey, ToPublicKey, }; -use tapyrus::sighash::{EcdsaSighashType, TapSighashType}; use tapyrus::{ absolute, psbt, script::color_identifier::ColorIdentifier, Address, Block, FeeRate, MalFixTxid, Network, OutPoint, PublicKey, Script, ScriptBuf, Sequence, Transaction, TxOut, Witness, @@ -36,6 +36,10 @@ use tapyrus::{ use tapyrus::{address::NetworkChecked, secp256k1::Scalar}; use tapyrus::{consensus::encode::serialize, transaction, BlockHash, Psbt}; use tapyrus::{constants::mainnet_genesis_block, constants::testnet_genesis_block, Amount}; +use tapyrus::{ + hex::DisplayHex, + sighash::{EcdsaSighashType, TapSighashType}, +}; use tapyrus::{secp256k1::SecretKey, PrivateKey}; use tapyrus::{ secp256k1::{All, Secp256k1}, @@ -1055,16 +1059,8 @@ impl Wallet { }) } }; - let commitment: Scalar = - Contract::create_pay_to_contract_commitment(payment_base, contract); - let pubkey = payment_base - .inner - .add_exp_tweak(&self.secp, &commitment) - .unwrap(); - let key = PublicKey { - compressed: true, - inner: pubkey, - }; + let key = + Contract::create_pay_to_contract_public_key(payment_base, contract, self.secp_ctx()); Ok(key) } @@ -2371,14 +2367,27 @@ impl Wallet { fn get_descriptor_for_txout(&self, txout: &TxOut) -> Option { let payment_base = self.spk_index().p2c_spk(&txout.script_pubkey); - let script_pubkey_ref = if let Some(p) = payment_base { - p - } else { - &txout.script_pubkey - }; - - let (keychain, child) = self.indexed_graph.index.index_of_spk(script_pubkey_ref)?; - let descriptor = self.get_descriptor_for_keychain(keychain); + if let Some(p) = payment_base { + // find pay-to-contract + let contract = self.contracts.values().find(|c| *p == c.payment_base); + if let Some(c) = contract { + let public_key = Contract::create_pay_to_contract_public_key( + &c.payment_base, + c.contract.clone(), + &self.secp_ctx(), + ); + if let Ok(ddk) = DefiniteDescriptorKey::from_str(&public_key.to_string()) { + return Some(Descriptor::::new_pk(ddk)); + } + } + return None; + } + let (keychain, child) = self + .indexed_graph + .index + .index_of_spk(&txout.script_pubkey)?; + let descriptor: &Descriptor = + self.get_descriptor_for_keychain(keychain); descriptor.at_derivation_index(child).ok() } @@ -2617,7 +2626,7 @@ impl Wallet { let (keychain, child) = if let Some(p) = payment_base { self.indexed_graph .index - .index_of_spk(p.clone().as_script()) + .index_of_spk(&ScriptBuf::new_p2pkh(&p.pubkey_hash())) .ok_or(CreateTxError::UnknownUtxo)? } else { self.indexed_graph diff --git a/crates/wallet/src/wallet/signer.rs b/crates/wallet/src/wallet/signer.rs index c6945df2..c53dd937 100644 --- a/crates/wallet/src/wallet/signer.rs +++ b/crates/wallet/src/wallet/signer.rs @@ -433,11 +433,12 @@ impl SignerCommon for SignerWrapper { } impl SignerWrapper { - // Return if a script is related + /// Return if a script is related fn is_relevant_script(&self, script_pubkey: &ScriptBuf) -> bool { script_pubkey.is_cp2pkh() || script_pubkey.is_p2pkh() } + /// Return if script_pubkey equals to p2pkh generated with specified public key fn same_pubkey_hash(&self, script_pubkey: &ScriptBuf, public_key: &PublicKey) -> bool { *script_pubkey == ScriptBuf::new_p2pkh(&public_key.pubkey_hash()) } @@ -450,7 +451,9 @@ impl SignerWrapper { secp: &SecpCtx, ) -> Option<(SecretKey, PublicKey)> { sign_options.contracts.iter().find_map(|(_, contract)| { - let p2c_private_key = contract.create_private_key(pubkey, self.network).ok()?; + let p2c_private_key = contract + .create_private_key(&self, pubkey, self.network) + .ok()?; let p2c_public_key = p2c_private_key.public_key(secp); if self.same_pubkey_hash(script_pubkey, &p2c_public_key) { Some((p2c_private_key.inner, p2c_public_key))