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

Example for spending taproot descriptor. #457

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ edition = "2018"

[features]
default = ["std"]
std = ["bitcoin/std", "bitcoin/secp-recovery"]
std = ["bitcoin/std", "bitcoin/secp-recovery", "secp256k1-zkp"]
no-std = ["hashbrown", "bitcoin/no-std"]
compiler = []
trace = []
Expand All @@ -24,6 +24,7 @@ rand = ["bitcoin/rand"]
bitcoin = { version = "0.28.1", default-features = false }
serde = { version = "1.0", optional = true }
hashbrown = { version = "0.11", optional = true }
secp256k1-zkp = { git = "https://github.com/sanket1729/rust-secp256k1-zkp", branch = "pr29", optional = true}

[dev-dependencies]
bitcoind = {version = "0.26.1", features=["22_0"]}
Expand Down
150 changes: 150 additions & 0 deletions examples/keyexpr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
use core::str::FromStr;
use std::collections::HashMap;

use actual_rand;
use actual_rand::RngCore;
use bitcoin::hashes::{hash160, ripemd160, sha256};
use bitcoin::secp256k1::XOnlyPublicKey;
use bitcoin::{self, secp256k1, Network};
use miniscript::{hash256, Descriptor, TranslatePk, Translator};
use secp256k1::{KeyPair, Secp256k1, SecretKey};
#[cfg(feature = "std")]
use secp256k1_zkp::{Message, MusigAggNonce, MusigKeyAggCache, MusigSession};

// xonly_keys generates a pair of vector containing public keys and secret keys
fn xonly_keys(n: usize) -> (Vec<bitcoin::XOnlyPublicKey>, Vec<SecretKey>) {
let mut pubkeys = Vec::with_capacity(n);
let mut seckeys = Vec::with_capacity(n);
let secp = secp256k1::Secp256k1::new();
for _ in 0..n {
let key_pair = KeyPair::new(&secp, &mut secp256k1::rand::thread_rng());
let pk = XOnlyPublicKey::from_keypair(&key_pair);
let sk = SecretKey::from_keypair(&key_pair);
pubkeys.push(pk);
seckeys.push(sk);
}
(pubkeys, seckeys)
}

// StrPkTranslator helps replacing string with actual keys in descriptor/miniscript
struct StrPkTranslator {
pk_map: HashMap<String, bitcoin::XOnlyPublicKey>,
}

impl Translator<String, bitcoin::XOnlyPublicKey, ()> for StrPkTranslator {
fn pk(&mut self, pk: &String) -> Result<bitcoin::XOnlyPublicKey, ()> {
self.pk_map.get(pk).copied().ok_or(())
}

fn pkh(&mut self, _pkh: &String) -> Result<hash160::Hash, ()> {
unreachable!("Policy doesn't contain any pkh fragment");
}

fn sha256(&mut self, _sha256: &String) -> Result<sha256::Hash, ()> {
unreachable!("Policy does not contain any sha256 fragment");
}

fn hash256(&mut self, _sha256: &String) -> Result<hash256::Hash, ()> {
unreachable!("Policy does not contain any hash256 fragment");
}

fn ripemd160(&mut self, _ripemd160: &String) -> Result<ripemd160::Hash, ()> {
unreachable!("Policy does not contain any ripemd160 fragment");
}

fn hash160(&mut self, _hash160: &String) -> Result<hash160::Hash, ()> {
unreachable!("Policy does not contain any hash160 fragment");
}
}

#[cfg(not(feature = "std"))]
fn main() {}

#[cfg(feature = "std")]
fn main() {
let desc =
Descriptor::<String>::from_str("tr(musig(E,F),{pk(A),multi_a(1,B,musig(C,D))})").unwrap();

// generate the public and secret keys
let (pubkeys, seckeys) = xonly_keys(6);

// create the hashMap (from String to XonlyPublicKey)
let mut pk_map = HashMap::new();
pk_map.insert("A".to_string(), pubkeys[0]);
pk_map.insert("B".to_string(), pubkeys[1]);
pk_map.insert("C".to_string(), pubkeys[2]);
pk_map.insert("D".to_string(), pubkeys[3]);
pk_map.insert("E".to_string(), pubkeys[4]);
pk_map.insert("F".to_string(), pubkeys[5]);

let mut t = StrPkTranslator { pk_map };
// replace with actual keys
let real_desc = desc.translate_pk(&mut t).unwrap();

// bitcoin script for the descriptor
let script = real_desc.script_pubkey();
println!("The script is {}", script);

// address for the descriptor (bc1...)
let address = real_desc.address(Network::Bitcoin).unwrap();
println!("The address is {}", address);

let secp = Secp256k1::new();
// we are spending with the internal key (musig(E,F))
let key_agg_cache = MusigKeyAggCache::new(&secp, &[pubkeys[4], pubkeys[5]]);
// aggregated publickey
let agg_pk = key_agg_cache.agg_pk();

let mut session_id = [0; 32];
actual_rand::thread_rng().fill_bytes(&mut session_id);

// msg should actually be the hash of the transaction, but we use some random
// 32 byte array.
let msg = Message::from_slice(&[3; 32]).unwrap();
let mut pub_nonces = Vec::with_capacity(2);
let mut sec_nonces = Vec::with_capacity(2);
match &real_desc {
Descriptor::Tr(tr) => {
let mut ind = 4;
for _ in tr.internal_key().iter() {
// generate public and secret nonces
let (sec_nonce, pub_nonce) = key_agg_cache
.nonce_gen(&secp, session_id, seckeys[ind], msg, None)
.expect("Non zero session id");
pub_nonces.push(pub_nonce);
sec_nonces.push(sec_nonce);
ind += 1;
}
}
_ => (),
}

// aggregate nonces
let aggnonce = MusigAggNonce::new(&secp, pub_nonces.as_slice());
let session = MusigSession::new(&secp, &key_agg_cache, aggnonce, msg, None);
let mut partial_sigs = Vec::with_capacity(2);
match &real_desc {
Descriptor::Tr(tr) => {
let mut ind = 0;
for _ in tr.internal_key().iter() {
// generate the partial signature for this key
let partial_sig = session
.partial_sign(
&secp,
&mut sec_nonces[ind],
&KeyPair::from_secret_key(&secp, seckeys[4 + ind]),
&key_agg_cache,
)
.unwrap();
partial_sigs.push(partial_sig);
ind += 1;
}
}
_ => (),
}

// aggregate the signature
let signature = session.partial_sig_agg(partial_sigs.as_slice());
// now verify the signature
assert!(secp.verify_schnorr(&signature, &msg, &agg_pk).is_ok())
}
2 changes: 1 addition & 1 deletion examples/taproot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ fn main() {
if let Descriptor::Tr(ref p) = desc {
// Check if internal key is correctly inferred as Ca
// assert_eq!(p.internal_key(), &pubkeys[2]);
assert_eq!(p.internal_key(), "Ca");
assert_eq!(p.internal_key().single_key().unwrap(), "Ca");

// Iterate through scripts
let mut iter = p.iter_scripts();
Expand Down
5 changes: 3 additions & 2 deletions src/descriptor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ use bitcoin::{self, secp256k1, Address, Network, Script, TxIn};
use sync::Arc;

use self::checksum::verify_checksum;
use crate::miniscript::musig_key::KeyExpr;
use crate::miniscript::{Legacy, Miniscript, Segwitv0};
use crate::prelude::*;
use crate::{
Expand Down Expand Up @@ -180,7 +181,7 @@ impl<Pk: MiniscriptKey> Descriptor<Pk> {
// roundabout way to constuct `c:pk_k(pk)`
let ms: Miniscript<Pk, BareCtx> =
Miniscript::from_ast(miniscript::decode::Terminal::Check(Arc::new(
Miniscript::from_ast(miniscript::decode::Terminal::PkK(pk))
Miniscript::from_ast(miniscript::decode::Terminal::PkK(KeyExpr::SingleKey(pk)))
.expect("Type check cannot fail"),
)))
.expect("Type check cannot fail");
Expand Down Expand Up @@ -270,7 +271,7 @@ impl<Pk: MiniscriptKey> Descriptor<Pk> {

/// Create new tr descriptor
/// Errors when miniscript exceeds resource limits under Tap context
pub fn new_tr(key: Pk, script: Option<tr::TapTree<Pk>>) -> Result<Self, Error> {
pub fn new_tr(key: KeyExpr<Pk>, script: Option<tr::TapTree<Pk>>) -> Result<Self, Error> {
Ok(Descriptor::Tr(Tr::new(key, script)?))
}

Expand Down
66 changes: 39 additions & 27 deletions src/descriptor/tr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use sync::Arc;

use super::checksum::{desc_checksum, verify_checksum};
use crate::expression::{self, FromTree};
use crate::miniscript::musig_key::KeyExpr;
use crate::miniscript::Miniscript;
use crate::policy::semantic::Policy;
use crate::policy::Liftable;
Expand All @@ -39,7 +40,7 @@ pub enum TapTree<Pk: MiniscriptKey> {
/// A taproot descriptor
pub struct Tr<Pk: MiniscriptKey> {
/// A taproot internal key
internal_key: Pk,
internal_key: KeyExpr<Pk>,
/// Optional Taproot Tree with spending conditions
tree: Option<TapTree<Pk>>,
/// Optional spending information associated with the descriptor
Expand Down Expand Up @@ -163,9 +164,8 @@ impl<Pk: MiniscriptKey> fmt::Debug for TapTree<Pk> {

impl<Pk: MiniscriptKey> Tr<Pk> {
/// Create a new [`Tr`] descriptor from internal key and [`TapTree`]
pub fn new(internal_key: Pk, tree: Option<TapTree<Pk>>) -> Result<Self, Error> {
pub fn new(internal_key: KeyExpr<Pk>, tree: Option<TapTree<Pk>>) -> Result<Self, Error> {
let nodes = tree.as_ref().map(|t| t.taptree_height()).unwrap_or(0);

if nodes <= TAPROOT_CONTROL_MAX_NODE_COUNT {
Ok(Self {
internal_key,
Expand All @@ -186,7 +186,7 @@ impl<Pk: MiniscriptKey> Tr<Pk> {
}

/// Obtain the internal key of [`Tr`] descriptor
pub fn internal_key(&self) -> &Pk {
pub fn internal_key(&self) -> &KeyExpr<Pk> {
&self.internal_key
}

Expand Down Expand Up @@ -226,7 +226,7 @@ impl<Pk: MiniscriptKey> Tr<Pk> {
let secp = secp256k1::Secp256k1::verification_only();
// Key spend path with no merkle root
let data = if self.tree.is_none() {
TaprootSpendInfo::new_key_spend(&secp, self.internal_key.to_x_only_pubkey(), None)
TaprootSpendInfo::new_key_spend(&secp, self.internal_key.key_agg(), None)
} else {
let mut builder = TaprootBuilder::new();
for (depth, ms) in self.iter_scripts() {
Expand All @@ -236,7 +236,7 @@ impl<Pk: MiniscriptKey> Tr<Pk> {
.expect("Computing spend data on a valid Tree should always succeed");
}
// Assert builder cannot error here because we have a well formed descriptor
match builder.finalize(&secp, self.internal_key.to_x_only_pubkey()) {
match builder.finalize(&secp, self.internal_key.key_agg()) {
Ok(data) => data,
Err(e) => match e {
TaprootBuilderError::InvalidMerkleTreeDepth(_) => {
Expand Down Expand Up @@ -419,7 +419,7 @@ impl_from_tree!(
key.args.len()
)));
}
Tr::new(expression::terminal(key, Pk::from_str)?, None)
Tr::new(expression::terminal(key, KeyExpr::<Pk>::from_str)?, None)
}
2 => {
let key = &top.args[0];
Expand All @@ -431,7 +431,10 @@ impl_from_tree!(
}
let tree = &top.args[1];
let ret = Self::parse_tr_script_spend(tree)?;
Tr::new(expression::terminal(key, Pk::from_str)?, Some(ret))
Tr::new(
expression::terminal(key, KeyExpr::<Pk>::from_str)?,
Some(ret),
)
}
_ => {
return Err(Error::Unexpected(format!(
Expand Down Expand Up @@ -499,7 +502,7 @@ fn parse_tr_tree(s: &str) -> Result<expression::Tree, Error> {
});
}
// use str::split_once() method to refactor this when compiler version bumps up
let (key, script) = split_once(rest, ',')
let (key, script) = split_key_and_tree(rest)
.ok_or_else(|| Error::BadDescriptor("invalid taproot descriptor".to_string()))?;

let internal_key = expression::Tree {
Expand All @@ -526,23 +529,34 @@ fn parse_tr_tree(s: &str) -> Result<expression::Tree, Error> {
}
}

fn split_once(inp: &str, delim: char) -> Option<(&str, &str)> {
fn split_key_and_tree(inp: &str) -> Option<(&str, &str)> {
if inp.is_empty() {
None
} else {
let mut found = inp.len();
for (idx, ch) in inp.chars().enumerate() {
if ch == delim {
found = idx;
break;
// hit the first comma when the open_bracket == 0, we can split at that point
let mut open_brackets = 0;
let mut ind = 0;
for c in inp.chars() {
if c == '(' {
open_brackets += 1;
} else if c == ')' {
open_brackets -= 1;
} else if c == ',' {
if open_brackets == 0 {
break;
}
}
ind += 1;
}
// No comma or trailing comma found
if found >= inp.len() - 1 {
Some((inp, ""))
} else {
Some((&inp[..found], &inp[found + 1..]))
if ind == 0 {
return Some(("", &inp[1..]));
}
if inp.len() == ind {
return Some((inp, ""));
}
let key = &inp[..ind];
let script = &inp[ind + 1..];
Some((key, script))
}
}

Expand All @@ -567,12 +581,10 @@ impl<Pk: MiniscriptKey> Liftable<Pk> for Tr<Pk> {
match &self.tree {
Some(root) => Ok(Policy::Threshold(
1,
vec![
Policy::KeyHash(self.internal_key.to_pubkeyhash()),
root.lift()?,
],
vec![self.internal_key.lift()?, root.lift()?],
)),
None => Ok(Policy::KeyHash(self.internal_key.to_pubkeyhash())),
// None => Ok(Policy::KeyHash(self.internal_key.to_pubkeyhash())),
None => self.internal_key.lift(),
}
}
}
Expand All @@ -586,7 +598,7 @@ impl<Pk: MiniscriptKey> ForEachKey<Pk> for Tr<Pk> {
let script_keys_res = self
.iter_scripts()
.all(|(_d, ms)| ms.for_each_key(&mut pred));
script_keys_res && pred(&self.internal_key)
script_keys_res && self.internal_key().for_any_key(pred)
}
}

Expand All @@ -602,7 +614,7 @@ where
T: Translator<P, Q, E>,
{
let translate_desc = Tr {
internal_key: translate.pk(&self.internal_key)?,
internal_key: self.internal_key.translate_pk(translate)?,
tree: match &self.tree {
Some(tree) => Some(tree.translate_helper(translate)?),
None => None,
Expand Down
12 changes: 8 additions & 4 deletions src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,9 @@ where
Terminal::PkK(ref pk) => {
debug_assert_eq!(node_state.n_evaluated, 0);
debug_assert_eq!(node_state.n_satisfied, 0);
let pk = pk
.single_key()
.expect("Musig keys cannot be parsed from Script");
let res = self.stack.evaluate_pk(&mut self.verify_sig, *pk);
if res.is_some() {
return res;
Expand Down Expand Up @@ -868,10 +871,11 @@ where
// evaluate each key with as a pk
// note that evaluate_pk will error on non-empty incorrect sigs
// push 1 on satisfied sigs and push 0 on empty sigs
match self
.stack
.evaluate_pk(&mut self.verify_sig, subs[node_state.n_evaluated])
{
let pkk = subs[node_state.n_evaluated]
.single_key()
.expect("Musig keys cannot be parsed from Script");
let res = self.stack.evaluate_pk(&mut self.verify_sig, *pkk);
match res {
Some(Ok(x)) => {
self.push_evaluation_state(
node_state.node,
Expand Down
Loading