Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/use_only_spendable_output' into …
Browse files Browse the repository at this point in the history
…for_test

# Conflicts:
#	crates/wallet/tests/wallet.rs
  • Loading branch information
rantan committed Aug 16, 2024
2 parents 05681e2 + 0f7f7c4 commit 4109381
Show file tree
Hide file tree
Showing 11 changed files with 1,001 additions and 122 deletions.
1 change: 1 addition & 0 deletions crates/chain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ readme = "README.md"
# For no-std, remember to enable the bitcoin/no-std feature
tapyrus = { git = "https://github.com/chaintope/rust-tapyrus", branch = "update_on_bitcoin_0.31.x", default-features = false, subdirectory = "tapyrus" }
serde_crate = { package = "serde", version = "1", optional = true, features = ["derive", "rc"] }
num-bigint = { version = "=0.4.4", default-features = false }

# Use hashbrown as a feature flag to have HashSet and HashMap from it.
hashbrown = { version = "0.9.1", optional = true, features = ["serde"] }
Expand Down
107 changes: 106 additions & 1 deletion crates/chain/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@
use alloc::collections::BTreeMap;

use alloc::{string::String, vec::Vec};
use tapyrus::PublicKey;
use num_bigint::BigUint;
use tapyrus::key::{Error, Secp256k1};
use tapyrus::secp256k1::{All, Scalar};
use tapyrus::{
hashes::{Hash, HashEngine},
PrivateKey,
};
use tapyrus::{Network, PublicKey};

/// The [`ChangeSet`] represents changes to [`Contract`].
pub type ChangeSet = BTreeMap<String, Contract>;
Expand All @@ -26,3 +33,101 @@ pub struct Contract {
/// Set to 1 if available for payment, 0 if not
pub spendable: bool,
}

impl Contract {
/// Create private key for Pay-to-Contract
pub fn create_pay_to_contract_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 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.
pub fn create_pay_to_contract_commitment(
payment_base: &PublicKey,
contract: Vec<u8>,
) -> Scalar {
let mut engine = tapyrus::hashes::sha256::HashEngine::default();
engine.input(&payment_base.inner.serialize());
engine.input(&contract);
let result = tapyrus::hashes::sha256::Hash::from_engine(engine);
Self::scalar_from(&result.to_byte_array()[..])
}

/// Generate Scalar from bytes
pub fn scalar_from(bytes: &[u8]) -> Scalar {
let order: BigUint = BigUint::from_bytes_be(&Scalar::MAX.to_be_bytes()) + 1u32;
let n: BigUint = BigUint::from_bytes_be(bytes);
let n = n % order;
let bytes = n.to_bytes_be();
let mut value = [0u8; 32];
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_pay_to_contract_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_pay_to_contract_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()
);
}
}
3 changes: 2 additions & 1 deletion crates/chain/src/keychain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
//!
//! [`SpkTxOutIndex`]: crate::SpkTxOutIndex
/// Indexer for TxOut
#[cfg(feature = "miniscript")]
mod txout_index;
pub mod txout_index;
use tapyrus::Amount;
#[cfg(feature = "miniscript")]
pub use txout_index::*;
Expand Down
14 changes: 12 additions & 2 deletions crates/chain/src/keychain/txout_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use core::{
ops::{Bound, RangeBounds},
};
use tapyrus::{
hashes::Hash, script::color_identifier::ColorIdentifier, Amount, MalFixTxid, OutPoint, Script,
SignedAmount, Transaction, TxOut,
hashes::Hash, script::color_identifier::ColorIdentifier, Amount, MalFixTxid, OutPoint,
PublicKey, Script, ScriptBuf, SignedAmount, Transaction, TxOut,
};

use crate::Append;
Expand Down Expand Up @@ -378,6 +378,16 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
Some((keychain.clone(), *last_index))
}

/// Insert payment base key for pay-to-contract script pubkey
pub fn insert_p2c_spk(&mut self, spk: ScriptBuf, payment_base: PublicKey) {
self.inner.insert_p2c_spk(spk, payment_base);
}

/// Returns script pubkey of payment base for pay-to-contract script
pub fn p2c_spk(&self, spk: &ScriptBuf) -> Option<&PublicKey> {
self.inner.p2c_spk(spk)
}

/// Returns whether the spk under the `keychain`'s `index` has been used.
///
/// Here, "unused" means that after the script pubkey was stored in the index, the index has
Expand Down
42 changes: 34 additions & 8 deletions crates/chain/src/spk_txout_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use crate::{
indexed_tx_graph::Indexer,
};
use tapyrus::{
script::color_identifier::ColorIdentifier, Amount, MalFixTxid, OutPoint, Script, ScriptBuf,
SignedAmount, Transaction, TxOut,
script::color_identifier::ColorIdentifier, Amount, MalFixTxid, OutPoint, PublicKey, Script,
ScriptBuf, SignedAmount, Transaction, TxOut,
};

/// An index storing [`TxOut`]s that have a script pubkey that matches those in a list.
Expand Down Expand Up @@ -41,6 +41,8 @@ pub struct SpkTxOutIndex<I> {
txouts: BTreeMap<OutPoint, (I, TxOut)>,
/// 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, PublicKey>,
}

impl<I> Default for SpkTxOutIndex<I> {
Expand All @@ -51,6 +53,7 @@ impl<I> Default for SpkTxOutIndex<I> {
spk_indices: Default::default(),
spk_txouts: Default::default(),
unused: Default::default(),
p2c_spks: Default::default(),
}
}
}
Expand Down Expand Up @@ -103,12 +106,18 @@ impl<I: Clone + Ord> SpkTxOutIndex<I> {
/// Scan a single `TxOut` for a matching script pubkey and returns the index that matches the
/// script pubkey (if any).
pub fn scan_txout(&mut self, op: OutPoint, txout: &TxOut) -> Option<&I> {
let spk_i = if txout.script_pubkey.is_colored() {
self.spk_indices.get(&ScriptBuf::from_bytes(
txout.script_pubkey.as_bytes()[35..].to_vec(),
))
let script_pubkey = if txout.script_pubkey.is_colored() {
txout.script_pubkey.remove_color()
} else {
self.spk_indices.get(&txout.script_pubkey)
txout.script_pubkey.clone()
};
let payment_base = self.p2c_spks.get(&script_pubkey);

let spk_i = if let Some(p) = payment_base {
self.spk_indices
.get(&ScriptBuf::new_p2pkh(&p.pubkey_hash()))
} else {
self.spk_indices.get(&script_pubkey)
};
if let Some(spk_i) = spk_i {
self.txouts.insert(op, (spk_i.clone(), txout.clone()));
Expand Down Expand Up @@ -192,6 +201,16 @@ impl<I: Clone + Ord> SpkTxOutIndex<I> {
&self.spks
}

/// Insert payment base key for pay-to-contract script pubkey
pub fn insert_p2c_spk(&mut self, spk: ScriptBuf, payment_base: PublicKey) {
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<&PublicKey> {
self.p2c_spks.get(spk)
}

/// Adds a script pubkey to scan for. Returns `false` and does nothing if spk already exists in the map
///
/// the index will look for outputs spending to this spk whenever it scans new data.
Expand Down Expand Up @@ -304,10 +323,17 @@ impl<I: Clone + Ord> SpkTxOutIndex<I> {
}
for txout in &tx.output {
let script_pubkey = if txout.script_pubkey.is_colored() {
ScriptBuf::from_bytes(txout.script_pubkey.as_bytes()[35..].to_vec())
txout.script_pubkey.remove_color()
} else {
txout.script_pubkey.clone()
};
let payment_base = self.p2c_spks.get(&script_pubkey);
let script_pubkey = if let Some(p) = payment_base {
let hash = p.pubkey_hash();
ScriptBuf::new_p2pkh(&hash)
} else {
script_pubkey
};
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
22 changes: 12 additions & 10 deletions crates/sqlite/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ impl<K, A> Store<K, A> {
let payment_base: Vec<u8> = c.payment_base.to_bytes();
let spendable: u32 = if c.spendable { 1 } else { 0 };
insert_contract_stmt.execute(named_params! {
":contract_id": contract_id, ":contract": contract, ":payment_base": payment_base, ":spendable": spendable})
":contract_id": contract_id, ":contract": contract, ":payment_base": payment_base, ":spendable": spendable })
.map_err(Error::Sqlite)?;
}
Ok(())
Expand Down Expand Up @@ -663,17 +663,17 @@ mod test {
agg_changeset.unwrap().contract.get("id").unwrap().spendable,
true
);

let payment_base = PublicKey::from_str(
"028bde91b10013e08949a318018fedbd896534a549a278e220169ee2a36517c7aa",
)
.unwrap();
let mut contract: contract::ChangeSet = contract::ChangeSet::new();
contract.insert(
"id".to_string(),
Contract {
contract_id: "id".to_string(),
contract: vec![0x00, 0x01, 0x02],
payment_base: PublicKey::from_str(
"028bde91b10013e08949a318018fedbd896534a549a278e220169ee2a36517c7aa",
)
.unwrap(),
payment_base,
spendable: false,
},
);
Expand Down Expand Up @@ -881,6 +881,11 @@ mod test {
indexer: keychain::ChangeSet::default(),
};

let payment_base = PublicKey::from_str(
"028bde91b10013e08949a318018fedbd896534a549a278e220169ee2a36517c7aa",
)
.unwrap();

let mut contract: contract::ChangeSet = contract::ChangeSet::new();
contract.insert(
"id".to_string(),
Expand All @@ -890,10 +895,7 @@ mod test {
0x00, 0x00, 0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44,
0x66, 0x55, 0x44, 0x00, 0x00,
],
payment_base: PublicKey::from_str(
"028bde91b10013e08949a318018fedbd896534a549a278e220169ee2a36517c7aa",
)
.unwrap(),
payment_base,
spendable: true,
},
);
Expand Down
Loading

0 comments on commit 4109381

Please sign in to comment.