Skip to content

Commit 89e7cb3

Browse files
committed
add multi_a
Iter keys in `MultiA` Fix witness generation for `MultiA`
1 parent 87c9849 commit 89e7cb3

File tree

10 files changed

+229
-17
lines changed

10 files changed

+229
-17
lines changed

src/miniscript/astelem.rs

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,9 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
119119
&& c.real_for_each_key(pred)
120120
}
121121
Terminal::Thresh(_, ref subs) => subs.iter().all(|sub| sub.real_for_each_key(pred)),
122-
Terminal::Multi(_, ref keys) => keys.iter().all(|key| pred(ForEach::Key(key))),
122+
Terminal::Multi(_, ref keys) | Terminal::MultiA(_, ref keys) => {
123+
keys.iter().all(|key| pred(ForEach::Key(key)))
124+
}
123125
}
124126
}
125127
pub(super) fn real_translate_pk<FPk, FPkh, Q, Error>(
@@ -209,6 +211,10 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
209211
let keys: Result<Vec<Q>, _> = keys.iter().map(&mut *translatefpk).collect();
210212
Terminal::Multi(k, keys?)
211213
}
214+
Terminal::MultiA(k, ref keys) => {
215+
let keys: Result<Vec<Q>, _> = keys.iter().map(&mut *translatefpk).collect();
216+
Terminal::MultiA(k, keys?)
217+
}
212218
};
213219
Ok(frag)
214220
}
@@ -312,6 +318,13 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> fmt::Debug for Terminal<Pk, Ctx> {
312318
}
313319
f.write_str(")")
314320
}
321+
Terminal::MultiA(k, ref keys) => {
322+
write!(f, "multi_a({}", k)?;
323+
for k in keys {
324+
write!(f, ",{}", k)?;
325+
}
326+
f.write_str(")")
327+
}
315328
_ => unreachable!(),
316329
}
317330
}
@@ -368,6 +381,13 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> fmt::Display for Terminal<Pk, Ctx> {
368381
}
369382
f.write_str(")")
370383
}
384+
Terminal::MultiA(k, ref keys) => {
385+
write!(f, "multi_a({}", k)?;
386+
for k in keys {
387+
write!(f, ",{}", k)?;
388+
}
389+
f.write_str(")")
390+
}
371391
// wrappers
372392
_ => {
373393
if let Some((ch, sub)) = self.wrap_char() {
@@ -540,7 +560,7 @@ where
540560

541561
Ok(Terminal::Thresh(k, subs?))
542562
}
543-
("multi", n) => {
563+
("multi", n) | ("multi_a", n) => {
544564
if n == 0 {
545565
return Err(errstr("no arguments given"));
546566
}
@@ -554,7 +574,12 @@ where
554574
.map(|sub| expression::terminal(sub, Pk::from_str))
555575
.collect();
556576

557-
pks.map(|pks| Terminal::Multi(k, pks))
577+
if frag_name == "multi" {
578+
pks.map(|pks| Terminal::Multi(k, pks))
579+
} else {
580+
// must be multi_a
581+
pks.map(|pks| Terminal::MultiA(k, pks))
582+
}
558583
}
559584
_ => Err(Error::Unexpected(format!(
560585
"{}({} args) while parsing Miniscript",
@@ -745,6 +770,19 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
745770
.push_int(keys.len() as i64)
746771
.push_opcode(opcodes::all::OP_CHECKMULTISIG)
747772
}
773+
Terminal::MultiA(k, ref keys) => {
774+
debug_assert!(Ctx::is_tap());
775+
// keys must be atleast len 1 here, guaranteed by typing rules
776+
builder = builder.push_ms_key::<_, Ctx>(&keys[0]);
777+
builder = builder.push_opcode(opcodes::all::OP_CHECKSIG);
778+
for pk in keys.iter().skip(1) {
779+
builder = builder.push_ms_key::<_, Ctx>(pk);
780+
builder = builder.push_opcode(opcodes::all::OP_CHECKSIGADD);
781+
}
782+
builder
783+
.push_int(k as i64)
784+
.push_opcode(opcodes::all::OP_NUMEQUAL)
785+
}
748786
}
749787
}
750788

@@ -799,6 +837,12 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
799837
+ script_num_size(pks.len())
800838
+ pks.iter().map(|pk| Ctx::pk_len(pk)).sum::<usize>()
801839
}
840+
Terminal::MultiA(k, ref pks) => {
841+
script_num_size(k)
842+
+ 1 // NUMEQUAL
843+
+ pks.iter().map(|pk| Ctx::pk_len(pk)).sum::<usize>() // n keys
844+
+ pks.len() // n times CHECKSIGADD
845+
}
802846
}
803847
}
804848
}

src/miniscript/context.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ pub enum ScriptContextError {
7070
StackSizeLimitExceeded { actual: usize, limit: usize },
7171
/// More than 20 keys in a Multi fragment
7272
CheckMultiSigLimitExceeded,
73+
/// MultiA is only allowed in post tapscript
74+
MultiANotAllowed,
7375
}
7476

7577
impl fmt::Display for ScriptContextError {
@@ -141,6 +143,9 @@ impl fmt::Display for ScriptContextError {
141143
"CHECkMULTISIG ('multi()' descriptor) only supports up to 20 pubkeys"
142144
)
143145
}
146+
ScriptContextError::MultiANotAllowed => {
147+
write!(f, "Multi a(CHECKSIGADD) only allowed post tapscript")
148+
}
144149
}
145150
}
146151
}
@@ -339,9 +344,11 @@ impl ScriptContext for Legacy {
339344
return Err(ScriptContextError::CheckMultiSigLimitExceeded);
340345
}
341346
}
347+
Terminal::MultiA(..) => {
348+
return Err(ScriptContextError::MultiANotAllowed);
349+
}
342350
_ => {}
343351
}
344-
345352
Ok(())
346353
}
347354

@@ -437,6 +444,9 @@ impl ScriptContext for Segwitv0 {
437444
}
438445
Ok(())
439446
}
447+
Terminal::MultiA(..) => {
448+
return Err(ScriptContextError::MultiANotAllowed);
449+
}
440450
_ => Ok(()),
441451
}
442452
}
@@ -628,6 +638,9 @@ impl ScriptContext for BareCtx {
628638
if ms.ext.pk_cost > MAX_SCRIPT_SIZE {
629639
return Err(ScriptContextError::MaxWitnessScriptSizeExceeded);
630640
}
641+
if let Terminal::MultiA(..) = ms.node {
642+
return Err(ScriptContextError::MultiANotAllowed);
643+
}
631644
Ok(())
632645
}
633646

src/miniscript/decode.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ pub enum Terminal<Pk: MiniscriptKey, Ctx: ScriptContext> {
187187
Thresh(usize, Vec<Arc<Miniscript<Pk, Ctx>>>),
188188
/// k (<key>)* n CHECKMULTISIG
189189
Multi(usize, Vec<Pk>),
190+
/// <key> CHECKSIG (<key> CHECKSIGADD)*(n-1) k NUMEQUAL
191+
MultiA(usize, Vec<Pk>),
190192
}
191193

192194
macro_rules! match_token {
@@ -487,6 +489,25 @@ pub fn parse<Ctx: ScriptContext>(
487489
keys.reverse();
488490
term.reduce0(Terminal::Multi(k as usize, keys))?;
489491
},
492+
// MultiA
493+
Tk::NumEqual, Tk::Num(k) => {
494+
let mut keys = Vec::with_capacity(k as usize); // atleast k capacity
495+
while tokens.peek() == Some(&Tk::CheckSigAdd) {
496+
match_token!(
497+
tokens,
498+
Tk::CheckSigAdd, Tk::Bytes32(pk) => keys.push(<Ctx::Key>::from_slice(pk)
499+
.map_err(|e| Error::PubKeyCtxError(e, Ctx::name_str()))?),
500+
);
501+
}
502+
// Last key must be with a CheckSig
503+
match_token!(
504+
tokens,
505+
Tk::CheckSig, Tk::Bytes32(pk) => keys.push(<Ctx::Key>::from_slice(pk)
506+
.map_err(|e| Error::PubKeyCtxError(e, Ctx::name_str()))?),
507+
);
508+
keys.reverse();
509+
term.reduce0(Terminal::MultiA(k as usize, keys))?;
510+
},
490511
);
491512
}
492513
Some(NonTerm::MaybeAndV) => {

src/miniscript/iter.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
121121
pub fn get_leaf_pk(&self) -> Vec<Pk> {
122122
match self.node {
123123
Terminal::PkK(ref key) => vec![key.clone()],
124-
Terminal::Multi(_, ref keys) => keys.clone(),
124+
Terminal::Multi(_, ref keys) | Terminal::MultiA(_, ref keys) => keys.clone(),
125125
_ => vec![],
126126
}
127127
}
@@ -139,7 +139,9 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
139139
match self.node {
140140
Terminal::PkH(ref hash) => vec![hash.clone()],
141141
Terminal::PkK(ref key) => vec![key.to_pubkeyhash()],
142-
Terminal::Multi(_, ref keys) => keys.iter().map(Pk::to_pubkeyhash).collect(),
142+
Terminal::Multi(_, ref keys) | Terminal::MultiA(_, ref keys) => {
143+
keys.iter().map(Pk::to_pubkeyhash).collect()
144+
}
143145
_ => vec![],
144146
}
145147
}
@@ -155,7 +157,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
155157
match self.node {
156158
Terminal::PkH(ref hash) => vec![PkPkh::HashedPubkey(hash.clone())],
157159
Terminal::PkK(ref key) => vec![PkPkh::PlainPubkey(key.clone())],
158-
Terminal::Multi(_, ref keys) => keys
160+
Terminal::Multi(_, ref keys) | Terminal::MultiA(_, ref keys) => keys
159161
.into_iter()
160162
.map(|key| PkPkh::PlainPubkey(key.clone()))
161163
.collect(),
@@ -170,7 +172,9 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
170172
pub fn get_nth_pk(&self, n: usize) -> Option<Pk> {
171173
match (&self.node, n) {
172174
(&Terminal::PkK(ref key), 0) => Some(key.clone()),
173-
(&Terminal::Multi(_, ref keys), _) => keys.get(n).cloned(),
175+
(&Terminal::Multi(_, ref keys), _) | (&Terminal::MultiA(_, ref keys), _) => {
176+
keys.get(n).cloned()
177+
}
174178
_ => None,
175179
}
176180
}
@@ -186,7 +190,9 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
186190
match (&self.node, n) {
187191
(&Terminal::PkH(ref hash), 0) => Some(hash.clone()),
188192
(&Terminal::PkK(ref key), 0) => Some(key.to_pubkeyhash()),
189-
(&Terminal::Multi(_, ref keys), _) => keys.get(n).map(Pk::to_pubkeyhash),
193+
(&Terminal::Multi(_, ref keys), _) | (&Terminal::MultiA(_, ref keys), _) => {
194+
keys.get(n).map(Pk::to_pubkeyhash)
195+
}
190196
_ => None,
191197
}
192198
}
@@ -199,7 +205,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
199205
match (&self.node, n) {
200206
(&Terminal::PkH(ref hash), 0) => Some(PkPkh::HashedPubkey(hash.clone())),
201207
(&Terminal::PkK(ref key), 0) => Some(PkPkh::PlainPubkey(key.clone())),
202-
(&Terminal::Multi(_, ref keys), _) => {
208+
(&Terminal::Multi(_, ref keys), _) | (&Terminal::MultiA(_, ref keys), _) => {
203209
keys.get(n).map(|key| PkPkh::PlainPubkey(key.clone()))
204210
}
205211
_ => None,

src/miniscript/lex.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ pub enum Token<'s> {
3131
BoolOr,
3232
Add,
3333
Equal,
34+
NumEqual,
3435
CheckSig,
36+
CheckSigAdd,
3537
CheckMultiSig,
3638
CheckSequenceVerify,
3739
CheckLockTimeVerify,
@@ -129,13 +131,24 @@ pub fn lex<'s>(script: &'s script::Script) -> Result<Vec<Token<'s>>, Error> {
129131
ret.push(Token::Equal);
130132
ret.push(Token::Verify);
131133
}
134+
script::Instruction::Op(opcodes::all::OP_NUMEQUAL) => {
135+
ret.push(Token::NumEqual);
136+
}
137+
script::Instruction::Op(opcodes::all::OP_NUMEQUALVERIFY) => {
138+
ret.push(Token::NumEqual);
139+
ret.push(Token::Verify);
140+
}
132141
script::Instruction::Op(opcodes::all::OP_CHECKSIG) => {
133142
ret.push(Token::CheckSig);
134143
}
135144
script::Instruction::Op(opcodes::all::OP_CHECKSIGVERIFY) => {
136145
ret.push(Token::CheckSig);
137146
ret.push(Token::Verify);
138147
}
148+
// Change once the opcode name is updated
149+
script::Instruction::Op(opcodes::all::OP_CHECKSIGADD) => {
150+
ret.push(Token::CheckSigAdd);
151+
}
139152
script::Instruction::Op(opcodes::all::OP_CHECKMULTISIG) => {
140153
ret.push(Token::CheckMultiSig);
141154
}

src/miniscript/mod.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ serde_string_impl_pk!(Miniscript, "a miniscript", Ctx; ScriptContext);
448448

449449
#[cfg(test)]
450450
mod tests {
451+
451452
use super::{Miniscript, ScriptContext};
452453
use super::{Segwitv0, Tap};
453454
use hex_script;
@@ -458,10 +459,12 @@ mod tests {
458459
use {DummyKey, DummyKeyHash, MiniscriptKey, TranslatePk, TranslatePk1};
459460

460461
use bitcoin::hashes::{hash160, sha256, Hash};
462+
use bitcoin::secp256k1::XOnlyPublicKey;
461463
use bitcoin::{self, secp256k1};
462464
use std::str;
463465
use std::str::FromStr;
464466
use std::sync::Arc;
467+
use TranslatePk2;
465468

466469
type Segwitv0Script = Miniscript<bitcoin::PublicKey, Segwitv0>;
467470
type Tapscript = Miniscript<bitcoin::secp256k1::XOnlyPublicKey, Tap>;
@@ -1000,4 +1003,33 @@ mod tests {
10001003
))
10011004
.unwrap();
10021005
}
1006+
1007+
#[test]
1008+
fn multi_a_tests() {
1009+
// Test from string tests
1010+
type Segwitv0Ms = Miniscript<String, Segwitv0>;
1011+
type TapMs = Miniscript<String, Tap>;
1012+
let segwit_multi_a_ms = Segwitv0Ms::from_str_insane("multi_a(1,A,B,C)");
1013+
assert_eq!(
1014+
segwit_multi_a_ms.unwrap_err().to_string(),
1015+
"Multi a(CHECKSIGADD) only allowed post tapscript"
1016+
);
1017+
let tap_multi_a_ms = TapMs::from_str_insane("multi_a(1,A,B,C)").unwrap();
1018+
assert_eq!(tap_multi_a_ms.to_string(), "multi_a(1,A,B,C)");
1019+
1020+
// Test encode/decode and translation tests
1021+
let tap_ms = tap_multi_a_ms.translate_pk2_infallible(|_| {
1022+
XOnlyPublicKey::from_str(
1023+
"e948a0bbf8b15ee47cf0851afbce8835b5f06d3003b8e7ed6104e82a1d41d6f8",
1024+
)
1025+
.unwrap()
1026+
});
1027+
// script rtt test
1028+
assert_eq!(
1029+
Miniscript::<XOnlyPublicKey, Tap>::parse_insane(&tap_ms.encode()).unwrap(),
1030+
tap_ms
1031+
);
1032+
assert_eq!(tap_ms.script_size(), 104);
1033+
assert_eq!(tap_ms.encode().len(), tap_ms.script_size());
1034+
}
10031035
}

src/miniscript/satisfy.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,44 @@ impl Satisfaction {
947947
}
948948
}
949949
}
950+
Terminal::MultiA(k, ref keys) => {
951+
// Collect all available signatures
952+
let mut sig_count = 0;
953+
let mut sigs = vec![vec![vec![]]; keys.len()];
954+
for (i, pk) in keys.iter().rev().enumerate() {
955+
match Witness::signature(stfr, pk) {
956+
Witness::Stack(sig) => {
957+
sigs[i] = sig;
958+
sig_count += 1;
959+
// This a privacy issue, we are only selecting the first available
960+
// sigs. Incase pk at pos 1 is not selected, we know we did not have access to it
961+
// bitcoin core also implements the same logic for MULTISIG, so I am not bothering
962+
// permuting the sigs for now
963+
if sig_count == k {
964+
break;
965+
}
966+
}
967+
Witness::Impossible => {}
968+
Witness::Unavailable => unreachable!(
969+
"Signature satisfaction without witness must be impossible"
970+
),
971+
}
972+
}
973+
974+
if sig_count < k {
975+
Satisfaction {
976+
stack: Witness::Impossible,
977+
has_sig: false,
978+
}
979+
} else {
980+
Satisfaction {
981+
stack: sigs.into_iter().fold(Witness::empty(), |acc, sig| {
982+
Witness::combine(acc, Witness::Stack(sig))
983+
}),
984+
has_sig: true,
985+
}
986+
}
987+
}
950988
}
951989
}
952990

@@ -1064,6 +1102,10 @@ impl Satisfaction {
10641102
stack: Witness::Stack(vec![vec![]; k + 1]),
10651103
has_sig: false,
10661104
},
1105+
Terminal::MultiA(_, ref pks) => Satisfaction {
1106+
stack: Witness::Stack(vec![vec![]; pks.len()]),
1107+
has_sig: false,
1108+
},
10671109
}
10681110
}
10691111

0 commit comments

Comments
 (0)