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

Add initial support for KeyExpr #443

Open
wants to merge 1 commit 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
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),
Copy link
Member

Choose a reason for hiding this comment

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

Add a test for_each_key for a fragment containing musig.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a test for this test_for_each_key in miniscript/mod.rs

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