Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use only spendable output related with contract #29

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
109 changes: 108 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,103 @@ 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 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
Loading