Skip to content

Commit 7914acf

Browse files
committed
Add TapCtx
1 parent 7d33643 commit 7914acf

File tree

7 files changed

+170
-10
lines changed

7 files changed

+170
-10
lines changed

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 + self.pk.serialized_len::<BareCtx>()))
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 + self.pk.serialized_len::<Segwitv0>())
434434
}
435435

436436
fn script_code(&self) -> Script

src/descriptor/sortedmulti.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,11 @@ 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
182+
.pks
183+
.iter()
184+
.map(|pk| pk.serialized_len::<Ctx>())
185+
.sum::<usize>()
182186
}
183187

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

src/lib.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,10 @@ pub trait MiniscriptKey: Clone + Eq + Ord + fmt::Debug + fmt::Display + hash::Ha
145145

146146
/// Computes the size of a public key when serialized in a script,
147147
/// including the length bytes
148-
fn serialized_len(&self) -> usize {
149-
if self.is_uncompressed() {
148+
fn serialized_len<Ctx: ScriptContext>(&self) -> usize {
149+
if Ctx::is_tapctx() {
150+
33
151+
} else if self.is_uncompressed() {
150152
66
151153
} else {
152154
34
@@ -183,6 +185,24 @@ pub trait ToPublicKey: MiniscriptKey {
183185
/// Converts an object to a public key
184186
fn to_public_key(&self) -> bitcoin::PublicKey;
185187

188+
/// Convert an object to x-only pubkey
189+
fn to_x_only_pubkey(&self) -> bitcoin::schnorr::PublicKey {
190+
let pk = self.to_public_key();
191+
bitcoin::schnorr::PublicKey::from(pk.key)
192+
}
193+
194+
/// Serialize the key as bytes based on script context. Used when encoding miniscript into bitcoin script
195+
// / - Why do we need both [`MiniscriptKey::is_xonly()`] and [`ScriptContext::Tap`]?
196+
// / Is it possible to have
197+
// Named as `serialize_bytes` instead of `serialize` because we already have many functions named serialize
198+
fn serialize_bytes<Ctx: ScriptContext>(&self) -> Vec<u8> {
199+
if Ctx::is_tapctx() {
200+
self.to_x_only_pubkey().serialize().to_vec()
201+
} else {
202+
self.to_public_key().to_bytes()
203+
}
204+
}
205+
186206
/// Converts a hashed version of the public key to a `hash160` hash.
187207
///
188208
/// This method must be consistent with `to_public_key`, in the sense

src/miniscript/astelem.rs

Lines changed: 7 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) => builder.push_slice(&pk.serialize_bytes::<Ctx>()),
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) => pk.serialized_len::<Ctx>(),
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,10 @@ 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
815+
.iter()
816+
.map(|pk| pk.serialized_len::<Ctx>())
817+
.sum::<usize>()
814818
}
815819
}
816820
}

src/miniscript/context.rs

Lines changed: 128 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@
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,
1920
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+
26+
use super::limits::MAX_STACK_ITEM_LIMIT;
2427
use {Miniscript, MiniscriptKey, Terminal};
2528

2629
/// Error for Script Context
@@ -39,6 +42,9 @@ pub enum ScriptContextError {
3942
/// Only Compressed keys allowed under current descriptor
4043
/// Segwitv0 fragments do not allow uncompressed pubkeys
4144
CompressedOnly,
45+
/// Tapscript descriptors cannot contain uncompressed keys
46+
/// Tap context can contain compressed or xonly
47+
UncompressedKeysNotAllowed,
4248
/// At least one satisfaction path in the Miniscript fragment has more than
4349
/// `MAX_STANDARD_P2WSH_STACK_ITEMS` (100) witness elements.
4450
MaxWitnessItemssExceeded,
@@ -55,6 +61,8 @@ pub enum ScriptContextError {
5561
MaxScriptSigSizeExceeded,
5662
/// Impossible to satisfy the miniscript under the current context
5763
ImpossibleSatisfaction,
64+
/// No Multi Node in Taproot context
65+
TaprootMultiDisabled,
5866
}
5967

6068
impl fmt::Display for ScriptContextError {
@@ -66,7 +74,17 @@ impl fmt::Display for ScriptContextError {
6674
write!(f, "DupIf is malleable under Legacy rules")
6775
}
6876
ScriptContextError::CompressedOnly => {
69-
write!(f, "Uncompressed pubkeys not allowed in segwit context")
77+
write!(
78+
f,
79+
"Only Compressed pubkeys not allowed in segwit context. X-only and uncompressed keys are forbidden"
80+
)
81+
}
82+
ScriptContextError::UncompressedKeysNotAllowed => {
83+
write!(
84+
f,
85+
"Only x-only keys are allowed in tapscript checksig. \
86+
Compressed keys maybe specified in descriptor."
87+
)
7088
}
7189
ScriptContextError::MaxWitnessItemssExceeded => write!(
7290
f,
@@ -99,6 +117,9 @@ impl fmt::Display for ScriptContextError {
99117
"Impossible to satisfy Miniscript under the current context"
100118
)
101119
}
120+
ScriptContextError::TaprootMultiDisabled => {
121+
write!(f, "No Multi node in taproot context")
122+
}
102123
}
103124
}
104125
}
@@ -243,6 +264,13 @@ pub trait ScriptContext:
243264
Self::top_level_type_check(ms)?;
244265
Self::other_top_level_checks(ms)
245266
}
267+
268+
/// Reverse lookup to store whether the context is tapscript.
269+
/// pk(33-byte key) is a valid under both tapscript context and segwitv0 context
270+
/// We need to context decide whether the serialize pk to 33 byte or 32 bytes.
271+
fn is_tapctx() -> bool {
272+
return false;
273+
}
246274
}
247275

248276
/// Legacy ScriptContext
@@ -353,6 +381,12 @@ impl ScriptContext for Segwitv0 {
353381
}
354382
Ok(())
355383
}
384+
Terminal::Multi(_k, ref pks) => {
385+
if pks.iter().any(|pk| pk.is_uncompressed()) {
386+
return Err(ScriptContextError::CompressedOnly);
387+
}
388+
Ok(())
389+
}
356390
_ => Ok(()),
357391
}
358392
}
@@ -402,6 +436,97 @@ impl ScriptContext for Segwitv0 {
402436
}
403437
}
404438

439+
/// Tap ScriptContext
440+
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
441+
pub enum Tap {}
442+
443+
impl ScriptContext for Tap {
444+
fn check_terminal_non_malleable<Pk: MiniscriptKey, Ctx: ScriptContext>(
445+
_frag: &Terminal<Pk, Ctx>,
446+
) -> Result<(), ScriptContextError> {
447+
// No fragment is malleable in tapscript context.
448+
// Certain fragments like Multi are invalid, but are not malleable
449+
Ok(())
450+
}
451+
452+
fn check_witness<Pk: MiniscriptKey, Ctx: ScriptContext>(
453+
witness: &[Vec<u8>],
454+
) -> Result<(), ScriptContextError> {
455+
if witness.len() > MAX_STACK_ITEM_LIMIT {
456+
return Err(ScriptContextError::MaxWitnessItemssExceeded);
457+
}
458+
Ok(())
459+
}
460+
461+
fn check_global_consensus_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
462+
ms: &Miniscript<Pk, Ctx>,
463+
) -> Result<(), ScriptContextError> {
464+
// No script size checks for global consensus rules
465+
// Should we really check for block limits here.
466+
// When the transaction sizes get close to block limits,
467+
// some guarantees are not easy to satisfy because of knapsack
468+
// constraints
469+
if ms.ext.pk_cost > MAX_BLOCK_WEIGHT as usize {
470+
return Err(ScriptContextError::MaxWitnessScriptSizeExceeded);
471+
}
472+
473+
match ms.node {
474+
Terminal::PkK(ref pk) => {
475+
if pk.is_uncompressed() {
476+
return Err(ScriptContextError::UncompressedKeysNotAllowed);
477+
}
478+
Ok(())
479+
}
480+
Terminal::Multi(..) => {
481+
return Err(ScriptContextError::TaprootMultiDisabled);
482+
}
483+
// What happens to the Multi node in tapscript? Do we use it, create
484+
// a new fragment?
485+
_ => Ok(()),
486+
}
487+
}
488+
489+
fn check_local_consensus_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
490+
_ms: &Miniscript<Pk, Ctx>,
491+
) -> Result<(), ScriptContextError> {
492+
// Taproot introduces the concept of sigops budget.
493+
// In all possible valid miniscripts satisfy the given sigops constraint
494+
// Whenever we add new fragment that uses pk(pk() or multi based on checksigadd)
495+
// miniscript typing rules ensure that pk when executed successfully has it's
496+
// own unique signature. That is there is no way to re-use signatures for another
497+
// checksig. Therefore, for each successfully executed checksig, we will have
498+
// 64 bytes signature and thus sigops budget is always covered.
499+
// There is overall limit of consensus
500+
// TODO: track height during execution
501+
Ok(())
502+
}
503+
504+
fn check_global_policy_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
505+
_ms: &Miniscript<Pk, Ctx>,
506+
) -> Result<(), ScriptContextError> {
507+
// No script rules, rules are subject to entire tx rules
508+
Ok(())
509+
}
510+
511+
fn check_local_policy_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
512+
_ms: &Miniscript<Pk, Ctx>,
513+
) -> Result<(), ScriptContextError> {
514+
// TODO: check for policy execution.
515+
Ok(())
516+
}
517+
518+
fn max_satisfaction_size<Pk: MiniscriptKey, Ctx: ScriptContext>(
519+
ms: &Miniscript<Pk, Ctx>,
520+
) -> Option<usize> {
521+
// The witness stack cost is the first element of the tuple
522+
ms.ext.max_sat_size.map(|x| x.0)
523+
}
524+
525+
fn is_tapctx() -> bool {
526+
true
527+
}
528+
}
529+
405530
/// Bare ScriptContext
406531
/// To be used as raw script pubkeys
407532
/// In general, it is not recommended to use Bare descriptors
@@ -510,13 +635,14 @@ impl ScriptContext for NoChecks {
510635

511636
/// Private Mod to prevent downstream from implementing this public trait
512637
mod private {
513-
use super::{BareCtx, Legacy, NoChecks, Segwitv0};
638+
use super::{BareCtx, Legacy, NoChecks, Segwitv0, Tap};
514639

515640
pub trait Sealed {}
516641

517642
// Implement for those same types, but no others.
518643
impl Sealed for BareCtx {}
519644
impl Sealed for Legacy {}
520645
impl Sealed for Segwitv0 {}
646+
impl Sealed for Tap {}
521647
impl Sealed for NoChecks {}
522648
}

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+
// TODO: Refer to online url
46+
pub const MAX_STACK_ITEM_LIMIT: 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)