Skip to content

Commit

Permalink
Add support for KeyExpr
Browse files Browse the repository at this point in the history
  • Loading branch information
kanishk779 committed Aug 23, 2022
1 parent d589fe9 commit 79f9fed
Show file tree
Hide file tree
Showing 16 changed files with 1,222 additions and 167 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 2 additions & 1 deletion 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
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
7 changes: 7 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ extern crate alloc;
#[cfg(not(feature = "std"))]
extern crate hashbrown;

#[cfg(feature = "std")]
extern crate secp256k1_zkp;

#[cfg(any(feature = "std", test))]
extern crate core;

Expand Down Expand Up @@ -775,6 +778,8 @@ pub enum Error {
TrNoScriptCode,
/// No explicit script for Tr descriptors
TrNoExplicitScript,
/// Parsing error for single key
SingleKeyParseError,
}

// https://github.com/sipa/miniscript/pull/5 for discussion on this number
Expand Down Expand Up @@ -848,6 +853,7 @@ impl fmt::Display for Error {
Error::TaprootSpendInfoUnavialable => write!(f, "Taproot Spend Info not computed."),
Error::TrNoScriptCode => write!(f, "No script code for Tr descriptors"),
Error::TrNoExplicitScript => write!(f, "No script code for Tr descriptors"),
Error::SingleKeyParseError => f.write_str("not able to parse the single key"),
}
}
}
Expand Down Expand Up @@ -888,6 +894,7 @@ impl error::Error for Error {
| BareDescriptorAddr
| TaprootSpendInfoUnavialable
| TrNoScriptCode
| SingleKeyParseError
| TrNoExplicitScript => None,
Script(e) => Some(e),
AddrError(e) => Some(e),
Expand Down
34 changes: 19 additions & 15 deletions src/miniscript/astelem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use bitcoin::blockdata::{opcodes, script};
use sync::Arc;

use crate::miniscript::context::SigType;
use crate::miniscript::musig_key::KeyExpr;
use crate::miniscript::types::{self, Property};
use crate::miniscript::ScriptContext;
use crate::prelude::*;
Expand Down Expand Up @@ -80,7 +81,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
Pk::RawPkHash: 'a,
{
match *self {
Terminal::PkK(ref p) => pred(p),
Terminal::PkK(ref p) => p.for_each_key(pred),
Terminal::PkH(ref p) => pred(p),
Terminal::RawPkH(..)
| Terminal::After(..)
Expand Down Expand Up @@ -112,9 +113,8 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
&& c.real_for_each_key(pred)
}
Terminal::Thresh(_, ref subs) => subs.iter().all(|sub| sub.real_for_each_key(pred)),
Terminal::Multi(_, ref keys) | Terminal::MultiA(_, ref keys) => {
keys.iter().all(|key| pred(key))
}
Terminal::Multi(_, ref keys) => keys.iter().all(|key| pred(key)),
Terminal::MultiA(_, ref keys) => keys.iter().all(|key| key.for_each_key(&mut *pred)),
}
}

Expand All @@ -125,7 +125,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
T: Translator<Pk, Q, E>,
{
let frag: Terminal<Q, CtxQ> = match *self {
Terminal::PkK(ref p) => Terminal::PkK(t.pk(p)?),
Terminal::PkK(ref p) => Terminal::PkK(p.translate_pk(t)?),
Terminal::PkH(ref p) => Terminal::PkH(t.pk(p)?),
Terminal::RawPkH(ref p) => Terminal::RawPkH(t.pkh(p)?),
Terminal::After(n) => Terminal::After(n),
Expand Down Expand Up @@ -186,7 +186,8 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
Terminal::Multi(k, keys?)
}
Terminal::MultiA(k, ref keys) => {
let keys: Result<Vec<Q>, _> = keys.iter().map(|k| t.pk(k)).collect();
let keys: Result<Vec<KeyExpr<Q>>, _> =
keys.iter().map(|k| k.translate_pk(t)).collect();
Terminal::MultiA(k, keys?)
}
};
Expand Down Expand Up @@ -455,7 +456,7 @@ impl_from_tree!(
}
let mut unwrapped = match (frag_name, top.args.len()) {
("pk_k", 1) => {
expression::terminal(&top.args[0], |x| Pk::from_str(x).map(Terminal::PkK))
expression::unary(top, Terminal::PkK)
}
("pk_h", 1) => expression::terminal(&top.args[0], |x| Pk::from_str(x).map(Terminal::PkH)),
("after", 1) => expression::terminal(&top.args[0], |x| {
Expand Down Expand Up @@ -522,15 +523,18 @@ impl_from_tree!(
return Err(errstr("higher threshold than there were keys in multi"));
}

let pks: Result<Vec<Pk>, _> = top.args[1..]
.iter()
.map(|sub| expression::terminal(sub, Pk::from_str))
.collect();

if frag_name == "multi" {
let pks: Result<Vec<Pk>, _> = top.args[1..]
.iter()
.map(|sub| expression::terminal(sub, Pk::from_str))
.collect();
pks.map(|pks| Terminal::Multi(k, pks))
} else {
// must be multi_a
let pks: Result<Vec<KeyExpr<Pk>>, _> = top.args[1..]
.iter()
.map(|sub| KeyExpr::<Pk>::from_tree(sub))
.collect();
pks.map(|pks| Terminal::MultiA(k, pks))
}
}
Expand Down Expand Up @@ -734,7 +738,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
builder = builder.push_ms_key::<_, Ctx>(&keys[0]);
builder = builder.push_opcode(opcodes::all::OP_CHECKSIG);
for pk in keys.iter().skip(1) {
builder = builder.push_ms_key::<_, Ctx>(pk);
builder = builder.push_ms_key::<_, Ctx>(&pk);
builder = builder.push_opcode(opcodes::all::OP_CHECKSIGADD);
}
builder
Expand All @@ -753,7 +757,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
/// will handle the segwit/non-segwit technicalities for you.
pub fn script_size(&self) -> usize {
match *self {
Terminal::PkK(ref pk) => Ctx::pk_len(pk),
Terminal::PkK(ref pk) => Ctx::key_expr_len(pk),
Terminal::PkH(..) | Terminal::RawPkH(..) => 24,
Terminal::After(n) => script_num_size(n as usize) + 1,
Terminal::Older(n) => script_num_size(n as usize) + 1,
Expand Down Expand Up @@ -798,7 +802,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
Terminal::MultiA(k, ref pks) => {
script_num_size(k)
+ 1 // NUMEQUAL
+ pks.iter().map(|pk| Ctx::pk_len(pk)).sum::<usize>() // n keys
+ pks.iter().map(|pk| Ctx::key_expr_len(pk)).sum::<usize>() // n keys
+ pks.len() // n times CHECKSIGADD
}
}
Expand Down
109 changes: 84 additions & 25 deletions src/miniscript/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use crate::miniscript::limits::{
MAX_SCRIPT_SIZE, MAX_STACK_SIZE, MAX_STANDARD_P2WSH_SCRIPT_SIZE,
MAX_STANDARD_P2WSH_STACK_ITEMS,
};
use crate::miniscript::musig_key::KeyExpr;
use crate::miniscript::types;
use crate::prelude::*;
use crate::util::witness_to_scriptsig;
Expand Down Expand Up @@ -77,6 +78,8 @@ pub enum ScriptContextError {
CheckMultiSigLimitExceeded,
/// MultiA is only allowed in post tapscript
MultiANotAllowed,
/// Musig is only allowed in tapscript and taproot descriptors
MusigNotAllowed(String),
}

#[cfg(feature = "std")]
Expand All @@ -100,6 +103,7 @@ impl error::Error for ScriptContextError {
| TaprootMultiDisabled
| StackSizeLimitExceeded { .. }
| CheckMultiSigLimitExceeded
| MusigNotAllowed(_)
| MultiANotAllowed => None,
}
}
Expand Down Expand Up @@ -180,6 +184,9 @@ impl fmt::Display for ScriptContextError {
ScriptContextError::MultiANotAllowed => {
write!(f, "Multi a(CHECKSIGADD) only allowed post tapscript")
}
ScriptContextError::MusigNotAllowed(ref err) => {
write!(f, "Musig is only allowed in tapscript : err {}", err)
}
}
}
}
Expand Down Expand Up @@ -334,6 +341,13 @@ where
/// 34 for Segwitv0, 33 for Tap
fn pk_len<Pk: MiniscriptKey>(pk: &Pk) -> usize;

/// Get the len of the keyexpr
fn key_expr_len<Pk: MiniscriptKey>(pk: &KeyExpr<Pk>) -> usize {
match pk {
KeyExpr::SingleKey(pk) => Self::pk_len(pk),
KeyExpr::MuSig(_) => 33,
}
}
/// Local helper function to display error messages with context
fn name_str() -> &'static str;
}
Expand Down Expand Up @@ -384,12 +398,21 @@ impl ScriptContext for Legacy {
}

match ms.node {
Terminal::PkK(ref key) if key.is_x_only_key() => {
return Err(ScriptContextError::XOnlyKeysNotAllowed(
key.to_string(),
Self::name_str(),
))
}
Terminal::PkK(ref key) => match key {
KeyExpr::<Pk>::SingleKey(pk) => {
if pk.is_x_only_key() {
return Err(ScriptContextError::XOnlyKeysNotAllowed(
pk.to_string(),
Self::name_str(),
));
}
}
KeyExpr::<Pk>::MuSig(_) => {
return Err(ScriptContextError::MusigNotAllowed(String::from(
Self::name_str(),
)))
}
},
Terminal::Multi(_k, ref pks) => {
if pks.len() > MAX_PUBKEYS_PER_MULTISIG {
return Err(ScriptContextError::CheckMultiSigLimitExceeded);
Expand Down Expand Up @@ -490,17 +513,24 @@ impl ScriptContext for Segwitv0 {
}

match ms.node {
Terminal::PkK(ref pk) => {
if pk.is_uncompressed() {
return Err(ScriptContextError::CompressedOnly(pk.to_string()));
} else if pk.is_x_only_key() {
return Err(ScriptContextError::XOnlyKeysNotAllowed(
pk.to_string(),
Terminal::PkK(ref key) => match key {
KeyExpr::<Pk>::SingleKey(pk) => {
if pk.is_uncompressed() {
return Err(ScriptContextError::CompressedOnly(pk.to_string()));
} else if pk.is_x_only_key() {
return Err(ScriptContextError::XOnlyKeysNotAllowed(
pk.to_string(),
Self::name_str(),
));
}
Ok(())
}
KeyExpr::<Pk>::MuSig(_) => {
return Err(ScriptContextError::MusigNotAllowed(String::from(
Self::name_str(),
));
)));
}
Ok(())
}
},
Terminal::Multi(_k, ref pks) => {
if pks.len() > MAX_PUBKEYS_PER_MULTISIG {
return Err(ScriptContextError::CheckMultiSigLimitExceeded);
Expand Down Expand Up @@ -618,13 +648,24 @@ impl ScriptContext for Tap {
}

match ms.node {
Terminal::PkK(ref pk) => {
if pk.is_uncompressed() {
return Err(ScriptContextError::UncompressedKeysNotAllowed);
Terminal::PkK(ref key) => {
if key.iter().any(|pk| pk.is_uncompressed()) {
Err(ScriptContextError::UncompressedKeysNotAllowed)
} else {
Ok(())
}
Ok(())
}
Terminal::Multi(..) => Err(ScriptContextError::TaprootMultiDisabled),
Terminal::MultiA(_, ref keys) => {
if keys
.iter()
.all(|keyexpr| keyexpr.iter().any(|pk| pk.is_uncompressed()))
{
Err(ScriptContextError::UncompressedKeysNotAllowed)
} else {
Ok(())
}
}
_ => Ok(()),
}
}
Expand Down Expand Up @@ -712,11 +753,23 @@ impl ScriptContext for BareCtx {
return Err(ScriptContextError::MaxWitnessScriptSizeExceeded);
}
match ms.node {
Terminal::PkK(ref key) if key.is_x_only_key() => {
return Err(ScriptContextError::XOnlyKeysNotAllowed(
key.to_string(),
Self::name_str(),
))
Terminal::PkK(ref key) => {
match key {
KeyExpr::<Pk>::SingleKey(pk) => {
if pk.is_x_only_key() {
return Err(ScriptContextError::XOnlyKeysNotAllowed(
key.to_string(),
Self::name_str(),
));
}
}
KeyExpr::<Pk>::MuSig(_) => {
return Err(ScriptContextError::MusigNotAllowed(String::from(
Self::name_str(),
)))
}
}
Ok(())
}
Terminal::Multi(_k, ref pks) => {
if pks.len() > MAX_PUBKEYS_PER_MULTISIG {
Expand Down Expand Up @@ -753,7 +806,13 @@ impl ScriptContext for BareCtx {
match &ms.node {
Terminal::Check(ref ms) => match &ms.node {
Terminal::RawPkH(_pkh) => Ok(()),
Terminal::PkK(_pk) | Terminal::PkH(_pk) => Ok(()),
Terminal::PkH(_pk) => Ok(()),
Terminal::PkK(key) => match key {
KeyExpr::<Pk>::SingleKey(_pk) => Ok(()),
KeyExpr::<Pk>::MuSig(_) => Err(Error::ContextError(
ScriptContextError::MusigNotAllowed(String::from(Self::name_str())),
)),
},
_ => Err(Error::NonStandardBareScript),
},
Terminal::Multi(_k, subs) if subs.len() <= 3 => Ok(()),
Expand Down
Loading

0 comments on commit 79f9fed

Please sign in to comment.