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

Tr compiler #460

Open
wants to merge 4 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"]
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 = []
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);
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
Expand Up @@ -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);

Expand All @@ -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!(
Expand All @@ -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);
}
Expand All @@ -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);
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
Loading