Skip to content

Tr compiler #460

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

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
@@ -12,7 +12,7 @@ edition = "2018"

[features]
default = ["std"]
std = ["bitcoin/std", "bitcoin/secp-recovery"]
std = ["bitcoin/std", "bitcoin/secp-recovery", "secp256k1-zkp"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to this. But I don't think we should be using secp recovery.

no-std = ["hashbrown", "bitcoin/no-std"]
compiler = []
trace = []
@@ -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"]}
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also print what the internal key is

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


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())
}
17 changes: 5 additions & 12 deletions examples/taproot.rs
Original file line number Diff line number Diff line change
@@ -60,7 +60,7 @@ fn main() {
let desc = pol.compile_tr(Some("UNSPENDABLE_KEY".to_string())).unwrap();

let expected_desc =
Descriptor::<String>::from_str("tr(Ca,{and_v(v:pk(In),older(9)),multi_a(2,hA,S)})")
Descriptor::<String>::from_str("tr(musig(hA,S),{and_v(v:pk(In),older(9)),c:pk_k(Ca)})")
.unwrap();
assert_eq!(desc, expected_desc);

@@ -73,10 +73,6 @@ fn main() {
assert_eq!(desc_type.segwit_version().unwrap(), WitnessVersion::V1);

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");

// Iterate through scripts
let mut iter = p.iter_scripts();
assert_eq!(
@@ -88,10 +84,7 @@ fn main() {
);
assert_eq!(
iter.next().unwrap(),
(
1u8,
&Miniscript::<String, Tap>::from_str("multi_a(2,hA,S)").unwrap()
)
(1u8, &Miniscript::<String, Tap>::from_str("pk(Ca)").unwrap())
);
assert_eq!(iter.next(), None);
}
@@ -118,15 +111,15 @@ fn main() {
// `multi_a(2,PUBKEY_1,PUBKEY_2) at taptree depth 1, having
// Max Witness Size = scriptSig len + control_block size + varint(script_size) + script_size +
// varint(max satisfaction elements) + max satisfaction size
// = 4 + 65 + 1 + 70 + 1 + 132
// = 4 + 65 + 1 + 34 + 1 + 66
let max_sat_wt = real_desc.max_satisfaction_weight().unwrap();
assert_eq!(max_sat_wt, 273);
assert_eq!(max_sat_wt, 173);

// Compute the bitcoin address and check if it matches
let network = Network::Bitcoin;
let addr = real_desc.address(network).unwrap();
let expected_addr = bitcoin::Address::from_str(
"bc1pcc8ku64slu3wu04a6g376d2s8ck9y5alw5sus4zddvn8xgpdqw2swrghwx",
"bc1pfd2zwn9zcnej0348txmkumecgg26cgey44u3xlrjzckdsrv3nqfsxmln7g",
)
.unwrap();
assert_eq!(addr, expected_addr);
5 changes: 3 additions & 2 deletions src/descriptor/mod.rs
Original file line number Diff line number Diff line change
@@ -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::{
@@ -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");
@@ -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)?))
}

Loading