Skip to content

Commit 50a25de

Browse files
committed
Add TapCtx
1 parent c18056e commit 50a25de

File tree

8 files changed

+192
-19
lines changed

8 files changed

+192
-19
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# Taproot updates
2+
- Changed the ToPublicKey trait to support x-only keys.
13
# 6.0.1 - Aug 5, 2021
24

35
- The `lift` method on a Miniscript node was fixed. It would previously mix up

src/descriptor/bare.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ impl<Pk: MiniscriptKey> DescriptorTrait<Pk> for Pkh<Pk> {
339339
}
340340

341341
fn max_satisfaction_weight(&self) -> Result<usize, Error> {
342-
Ok(4 * (1 + 73 + self.pk.serialized_len()))
342+
Ok(4 * (1 + 73 + BareCtx::pk_len(&self.pk)))
343343
}
344344

345345
fn script_code(&self) -> Script

src/descriptor/segwitv0.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ impl<Pk: MiniscriptKey> DescriptorTrait<Pk> for Wpkh<Pk> {
430430
}
431431

432432
fn max_satisfaction_weight(&self) -> Result<usize, Error> {
433-
Ok(4 + 1 + 73 + self.pk.serialized_len())
433+
Ok(4 + 1 + 73 + Segwitv0::pk_len(&self.pk))
434434
}
435435

436436
fn script_code(&self) -> Script

src/descriptor/sortedmulti.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
178178
script_num_size(self.k)
179179
+ 1
180180
+ script_num_size(self.pks.len())
181-
+ self.pks.iter().map(|pk| pk.serialized_len()).sum::<usize>()
181+
+ self.pks.iter().map(|pk| Ctx::pk_len(pk)).sum::<usize>()
182182
}
183183

184184
/// Maximum number of witness elements used to satisfy the Miniscript

src/lib.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -141,16 +141,6 @@ pub trait MiniscriptKey: Clone + Eq + Ord + fmt::Debug + fmt::Display + hash::Ha
141141

142142
/// Converts an object to PublicHash
143143
fn to_pubkeyhash(&self) -> Self::Hash;
144-
145-
/// Computes the size of a public key when serialized in a script,
146-
/// including the length bytes
147-
fn serialized_len(&self) -> usize {
148-
if self.is_uncompressed() {
149-
66
150-
} else {
151-
34
152-
}
153-
}
154144
}
155145

156146
impl MiniscriptKey for bitcoin::PublicKey {
@@ -182,6 +172,21 @@ pub trait ToPublicKey: MiniscriptKey {
182172
/// Converts an object to a public key
183173
fn to_public_key(&self) -> bitcoin::PublicKey;
184174

175+
/// Convert an object to x-only pubkey
176+
fn to_x_only_pubkey(&self) -> bitcoin::schnorr::PublicKey {
177+
let pk = self.to_public_key();
178+
bitcoin::schnorr::PublicKey::from(pk.key)
179+
}
180+
181+
/// Serialize the key as bytes based on script context. Used when encoding miniscript into bitcoin script
182+
fn push_to_builder<Ctx: ScriptContext>(&self, builder: script::Builder) -> script::Builder {
183+
if Ctx::is_tapctx() {
184+
builder.push_slice(&self.to_x_only_pubkey().serialize())
185+
} else {
186+
builder.push_slice(&self.to_public_key().to_bytes())
187+
}
188+
}
189+
185190
/// Converts a hashed version of the public key to a `hash160` hash.
186191
///
187192
/// This method must be consistent with `to_public_key`, in the sense

src/miniscript/astelem.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
644644
Pk: ToPublicKey,
645645
{
646646
match *self {
647-
Terminal::PkK(ref pk) => builder.push_key(&pk.to_public_key()),
647+
Terminal::PkK(ref pk) => pk.push_to_builder::<Ctx>(builder),
648648
Terminal::PkH(ref hash) => builder
649649
.push_opcode(opcodes::all::OP_DUP)
650650
.push_opcode(opcodes::all::OP_HASH160)
@@ -750,6 +750,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
750750
.push_opcode(opcodes::all::OP_EQUAL)
751751
}
752752
Terminal::Multi(k, ref keys) => {
753+
debug_assert!(!Ctx::is_tapctx());
753754
builder = builder.push_int(k as i64);
754755
for pk in keys {
755756
builder = builder.push_key(&pk.to_public_key());
@@ -770,7 +771,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
770771
/// will handle the segwit/non-segwit technicalities for you.
771772
pub fn script_size(&self) -> usize {
772773
match *self {
773-
Terminal::PkK(ref pk) => pk.serialized_len(),
774+
Terminal::PkK(ref pk) => Ctx::pk_len(pk),
774775
Terminal::PkH(..) => 24,
775776
Terminal::After(n) => script_num_size(n as usize) + 1,
776777
Terminal::Older(n) => script_num_size(n as usize) + 1,
@@ -810,7 +811,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
810811
script_num_size(k)
811812
+ 1
812813
+ script_num_size(pks.len())
813-
+ pks.iter().map(|pk| pk.serialized_len()).sum::<usize>()
814+
+ pks.iter().map(|pk| Ctx::pk_len(pk)).sum::<usize>()
814815
}
815816
}
816817
}

src/miniscript/context.rs

Lines changed: 162 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414

1515
use std::{fmt, hash};
1616

17+
use bitcoin::blockdata::constants::MAX_BLOCK_WEIGHT;
1718
use miniscript::limits::{
1819
MAX_OPS_PER_SCRIPT, MAX_SCRIPTSIG_SIZE, MAX_SCRIPT_ELEMENT_SIZE, MAX_SCRIPT_SIZE,
19-
MAX_STANDARD_P2WSH_SCRIPT_SIZE, MAX_STANDARD_P2WSH_STACK_ITEMS,
20+
MAX_STACK_SIZE, MAX_STANDARD_P2WSH_SCRIPT_SIZE, MAX_STANDARD_P2WSH_STACK_ITEMS,
2021
};
2122
use miniscript::types;
2223
use util::witness_to_scriptsig;
2324
use Error;
25+
2426
use {Miniscript, MiniscriptKey, Terminal};
2527

2628
/// Error for Script Context
@@ -39,6 +41,9 @@ pub enum ScriptContextError {
3941
/// Only Compressed keys allowed under current descriptor
4042
/// Segwitv0 fragments do not allow uncompressed pubkeys
4143
CompressedOnly,
44+
/// Tapscript descriptors cannot contain uncompressed keys
45+
/// Tap context can contain compressed or xonly
46+
UncompressedKeysNotAllowed,
4247
/// At least one satisfaction path in the Miniscript fragment has more than
4348
/// `MAX_STANDARD_P2WSH_STACK_ITEMS` (100) witness elements.
4449
MaxWitnessItemssExceeded,
@@ -55,6 +60,8 @@ pub enum ScriptContextError {
5560
MaxScriptSigSizeExceeded,
5661
/// Impossible to satisfy the miniscript under the current context
5762
ImpossibleSatisfaction,
63+
/// No Multi Node in Taproot context
64+
TaprootMultiDisabled,
5865
}
5966

6067
impl fmt::Display for ScriptContextError {
@@ -66,7 +73,17 @@ impl fmt::Display for ScriptContextError {
6673
write!(f, "DupIf is malleable under Legacy rules")
6774
}
6875
ScriptContextError::CompressedOnly => {
69-
write!(f, "Uncompressed pubkeys not allowed in segwit context")
76+
write!(
77+
f,
78+
"Only Compressed pubkeys not allowed in segwit context. X-only and uncompressed keys are forbidden"
79+
)
80+
}
81+
ScriptContextError::UncompressedKeysNotAllowed => {
82+
write!(
83+
f,
84+
"Only x-only keys are allowed in tapscript checksig. \
85+
Compressed keys maybe specified in descriptor."
86+
)
7087
}
7188
ScriptContextError::MaxWitnessItemssExceeded => write!(
7289
f,
@@ -99,6 +116,9 @@ impl fmt::Display for ScriptContextError {
99116
"Impossible to satisfy Miniscript under the current context"
100117
)
101118
}
119+
ScriptContextError::TaprootMultiDisabled => {
120+
write!(f, "No Multi node in taproot context")
121+
}
102122
}
103123
}
104124
}
@@ -243,6 +263,19 @@ pub trait ScriptContext:
243263
Self::top_level_type_check(ms)?;
244264
Self::other_top_level_checks(ms)
245265
}
266+
267+
/// Reverse lookup to store whether the context is tapscript.
268+
/// pk(33-byte key) is a valid under both tapscript context and segwitv0 context
269+
/// We need to context decide whether the serialize pk to 33 byte or 32 bytes.
270+
fn is_tapctx() -> bool {
271+
return false;
272+
}
273+
274+
/// Get the len of public key when serialized based on context
275+
/// Note that this includes the serialization prefix. Returns
276+
/// 34/66 for Bare/Legacy based on key compressedness
277+
/// 34 for Segwitv0, 33 for Tap
278+
fn pk_len<Pk: MiniscriptKey>(pk: &Pk) -> usize;
246279
}
247280

248281
/// Legacy ScriptContext
@@ -317,6 +350,14 @@ impl ScriptContext for Legacy {
317350
// The scriptSig cost is the second element of the tuple
318351
ms.ext.max_sat_size.map(|x| x.1)
319352
}
353+
354+
fn pk_len<Pk: MiniscriptKey>(pk: &Pk) -> usize {
355+
if pk.is_uncompressed() {
356+
66
357+
} else {
358+
34
359+
}
360+
}
320361
}
321362

322363
/// Segwitv0 ScriptContext
@@ -353,6 +394,12 @@ impl ScriptContext for Segwitv0 {
353394
}
354395
Ok(())
355396
}
397+
Terminal::Multi(_k, ref pks) => {
398+
if pks.iter().any(|pk| pk.is_uncompressed()) {
399+
return Err(ScriptContextError::CompressedOnly);
400+
}
401+
Ok(())
402+
}
356403
_ => Ok(()),
357404
}
358405
}
@@ -400,6 +447,105 @@ impl ScriptContext for Segwitv0 {
400447
// The witness stack cost is the first element of the tuple
401448
ms.ext.max_sat_size.map(|x| x.0)
402449
}
450+
451+
fn pk_len<Pk: MiniscriptKey>(_pk: &Pk) -> usize {
452+
34
453+
}
454+
}
455+
456+
/// Tap ScriptContext
457+
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
458+
pub enum Tap {}
459+
460+
impl ScriptContext for Tap {
461+
fn check_terminal_non_malleable<Pk: MiniscriptKey, Ctx: ScriptContext>(
462+
_frag: &Terminal<Pk, Ctx>,
463+
) -> Result<(), ScriptContextError> {
464+
// No fragment is malleable in tapscript context.
465+
// Certain fragments like Multi are invalid, but are not malleable
466+
Ok(())
467+
}
468+
469+
fn check_witness<Pk: MiniscriptKey, Ctx: ScriptContext>(
470+
witness: &[Vec<u8>],
471+
) -> Result<(), ScriptContextError> {
472+
if witness.len() > MAX_STACK_SIZE {
473+
return Err(ScriptContextError::MaxWitnessItemssExceeded);
474+
}
475+
Ok(())
476+
}
477+
478+
fn check_global_consensus_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
479+
ms: &Miniscript<Pk, Ctx>,
480+
) -> Result<(), ScriptContextError> {
481+
// No script size checks for global consensus rules
482+
// Should we really check for block limits here.
483+
// When the transaction sizes get close to block limits,
484+
// some guarantees are not easy to satisfy because of knapsack
485+
// constraints
486+
if ms.ext.pk_cost > MAX_BLOCK_WEIGHT as usize {
487+
return Err(ScriptContextError::MaxWitnessScriptSizeExceeded);
488+
}
489+
490+
match ms.node {
491+
Terminal::PkK(ref pk) => {
492+
if pk.is_uncompressed() {
493+
return Err(ScriptContextError::UncompressedKeysNotAllowed);
494+
}
495+
Ok(())
496+
}
497+
Terminal::Multi(..) => {
498+
return Err(ScriptContextError::TaprootMultiDisabled);
499+
}
500+
// What happens to the Multi node in tapscript? Do we use it, create
501+
// a new fragment?
502+
_ => Ok(()),
503+
}
504+
}
505+
506+
fn check_local_consensus_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
507+
_ms: &Miniscript<Pk, Ctx>,
508+
) -> Result<(), ScriptContextError> {
509+
// Taproot introduces the concept of sigops budget.
510+
// In all possible valid miniscripts satisfy the given sigops constraint
511+
// Whenever we add new fragment that uses pk(pk() or multi based on checksigadd)
512+
// miniscript typing rules ensure that pk when executed successfully has it's
513+
// own unique signature. That is there is no way to re-use signatures for another
514+
// checksig. Therefore, for each successfully executed checksig, we will have
515+
// 64 bytes signature and thus sigops budget is always covered.
516+
// There is overall limit of consensus
517+
// TODO: track height during execution
518+
Ok(())
519+
}
520+
521+
fn check_global_policy_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
522+
_ms: &Miniscript<Pk, Ctx>,
523+
) -> Result<(), ScriptContextError> {
524+
// No script rules, rules are subject to entire tx rules
525+
Ok(())
526+
}
527+
528+
fn check_local_policy_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
529+
_ms: &Miniscript<Pk, Ctx>,
530+
) -> Result<(), ScriptContextError> {
531+
// TODO: check for policy execution.
532+
Ok(())
533+
}
534+
535+
fn max_satisfaction_size<Pk: MiniscriptKey, Ctx: ScriptContext>(
536+
ms: &Miniscript<Pk, Ctx>,
537+
) -> Option<usize> {
538+
// The witness stack cost is the first element of the tuple
539+
ms.ext.max_sat_size.map(|x| x.0)
540+
}
541+
542+
fn is_tapctx() -> bool {
543+
true
544+
}
545+
546+
fn pk_len<Pk: MiniscriptKey>(_pk: &Pk) -> usize {
547+
33
548+
}
403549
}
404550

405551
/// Bare ScriptContext
@@ -461,6 +607,14 @@ impl ScriptContext for BareCtx {
461607
// The witness stack cost is the first element of the tuple
462608
ms.ext.max_sat_size.map(|x| x.1)
463609
}
610+
611+
fn pk_len<Pk: MiniscriptKey>(pk: &Pk) -> usize {
612+
if pk.is_uncompressed() {
613+
65
614+
} else {
615+
33
616+
}
617+
}
464618
}
465619

466620
/// "No Checks" Context
@@ -506,17 +660,22 @@ impl ScriptContext for NoChecks {
506660
) -> Option<usize> {
507661
panic!("Tried to compute a satisfaction size bound on a no-checks miniscript")
508662
}
663+
664+
fn pk_len<Pk: MiniscriptKey>(_pk: &Pk) -> usize {
665+
panic!("Tried to compute a pk len bound on a no-checks miniscript")
666+
}
509667
}
510668

511669
/// Private Mod to prevent downstream from implementing this public trait
512670
mod private {
513-
use super::{BareCtx, Legacy, NoChecks, Segwitv0};
671+
use super::{BareCtx, Legacy, NoChecks, Segwitv0, Tap};
514672

515673
pub trait Sealed {}
516674

517675
// Implement for those same types, but no others.
518676
impl Sealed for BareCtx {}
519677
impl Sealed for Legacy {}
520678
impl Sealed for Segwitv0 {}
679+
impl Sealed for Tap {}
521680
impl Sealed for NoChecks {}
522681
}

src/miniscript/limits.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,9 @@ pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 520;
4040
/// Maximum script sig size allowed by standardness rules
4141
// https://github.com/bitcoin/bitcoin/blob/42b66a6b814bca130a9ccf0a3f747cf33d628232/src/policy/policy.cpp#L102
4242
pub const MAX_SCRIPTSIG_SIZE: usize = 1650;
43+
/// Maximum items during stack execution
44+
// This limits also applies for initial stack satisfaction
45+
// https://github.com/bitcoin/bitcoin/blob/3af495d6972379b07530a5fcc2665aa626d01621/src/script/script.h#L35
46+
pub const MAX_STACK_SIZE: usize = 1000;
47+
/** The maximum allowed weight for a block, see BIP 141 (network rule) */
48+
pub const MAX_BLOCK_WEIGHT: usize = 4000000;

0 commit comments

Comments
 (0)