Skip to content

Commit

Permalink
Fix signing for pay to contract transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
Yamaguchi committed Aug 15, 2024
1 parent 7204a96 commit fef05af
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 38 deletions.
69 changes: 65 additions & 4 deletions crates/chain/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<PrivateKey, Error> {
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.
Expand All @@ -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<u8>,
secp: &Secp256k1<All>,
) -> 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;

Check failure on line 92 in crates/chain/src/contract.rs

View workflow job for this annotation

GitHub Actions / clippy

unused import: `core::str::FromStr`

error: unused import: `core::str::FromStr` --> crates/chain/src/contract.rs:92:9 | 92 | use core::str::FromStr; | ^^^^^^^^^^^^^^^^^^ | = note: `-D unused-imports` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(unused_imports)]`
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::<u8>::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::<u8>::from_hex(
"78612a8498322787104379330ec41f749fd2ada016e0c0a6c2b233ed13fc8978"
)
.unwrap(),
Network::Dev
)
.unwrap()
);
}
}
2 changes: 1 addition & 1 deletion crates/chain/src/keychain/txout_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
}

/// 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)
}

Expand Down
19 changes: 10 additions & 9 deletions crates/chain/src/spk_txout_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub struct SpkTxOutIndex<I> {
/// 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<ScriptBuf, ScriptBuf>,
p2c_spks: HashMap<ScriptBuf, PublicKey>,
}

impl<I> Default for SpkTxOutIndex<I> {
Expand Down Expand Up @@ -114,7 +114,8 @@ impl<I: Clone + Ord> SpkTxOutIndex<I> {
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)
};
Expand Down Expand Up @@ -202,12 +203,11 @@ impl<I: Clone + Ord> SpkTxOutIndex<I> {

/// 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)
}

Expand Down Expand Up @@ -328,12 +328,13 @@ impl<I: Clone + Ord> SpkTxOutIndex<I> {
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
{
Expand Down
53 changes: 31 additions & 22 deletions crates/wallet/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,25 @@ 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,
};
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},
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -2371,14 +2367,27 @@ impl Wallet {

fn get_descriptor_for_txout(&self, txout: &TxOut) -> Option<DerivedDescriptor> {
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::<DefiniteDescriptorKey>::new_pk(ddk));
}
}
return None;
}
let (keychain, child) = self
.indexed_graph
.index
.index_of_spk(&txout.script_pubkey)?;
let descriptor: &Descriptor<DescriptorPublicKey> =
self.get_descriptor_for_keychain(keychain);
descriptor.at_derivation_index(child).ok()
}

Expand Down Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions crates/wallet/src/wallet/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,11 +433,12 @@ impl SignerCommon for SignerWrapper<PrivateKey> {
}

impl SignerWrapper<PrivateKey> {
// 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())
}
Expand All @@ -450,7 +451,9 @@ impl SignerWrapper<PrivateKey> {
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))
Expand Down

0 comments on commit fef05af

Please sign in to comment.