Skip to content

Commit f2ac1c4

Browse files
authored
Merge pull request #168 from sanket1729/resource_limits_new
Complete Resource limits
2 parents ca60da2 + 4a8554b commit f2ac1c4

20 files changed

+774
-171
lines changed
64 KB
Binary file not shown.

doc/resource_limitations.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
TODO: Rust-miniscript behaviour for resource limitations:
2+
3+
# Safe vs Valid vs Analyzable/Liftable
4+
This document refers to bitcoin consensus and standardness rules as of bitcoin core release 0.20.
5+
6+
One of miniscript’s goals are to make advanced Script functionality accommodate both machine and human analysis. However such a analysis is not possible in all cases.
7+
8+
- **Validity**: Validity refers to whether the miniscript tree constructions follows the grammer rules. For eg: Top level must be `B`, or `thresh` must have all of it's arguments being dissatifyable.
9+
- **Safety**: Whether all satisfactions of miniscript require a digital signature.
10+
- **Analyzable/Liftable**: Even if the given is valid and safe, it does not imply that miniscript is consensus and standardness complete. That is, there may exist some semantics implied by the lifted miniscript which cannot be realized in bitcoin network rules. This maybe because of two main reasons
11+
- Miniscript may contain a invalid timelock and heightlock combination[article](https://medium.com/blockstream/dont-mix-your-timelocks-d9939b665094).
12+
- Resource limitations: Discussed in the next section
13+
14+
This library accepts all miniscripts that are safe and valid and the signing logic will correctly work for all of those scripts. However, analyzing/lifting such miniscripts would fail.
15+
16+
# Resouce Limitations
17+
18+
Various types of Bitcoin Scripts have different resource limitations, either through consensus or standardness. Some of them affect otherwise valid Miniscripts.
19+
20+
There are two types of limitations within the resource limitations: 1) Those that depend on the satisfactions and 2) limitations independent of satisfactions.
21+
22+
## Limitations independent of satisfactions
23+
24+
Certain limitations like script size are independent of satisfactions and as such those can script creation time. If there is any script that does not satisfy these
25+
- Scripts over 520 bytes are invalid by consensus (P2SH).
26+
- Scripts over 10000 bytes are invalid by consensus (bare, P2SH, P2WSH, P2SH-P2WSH).
27+
- Anything but c:pk(key) (P2PK), c:pk_h(key) (P2PKH), and thresh_m(k,...) up to n=3 is invalid by standardness (bare).
28+
- Scripts over 3600 bytes are invalid by standardness (P2WSH, P2SH-P2WSH).
29+
30+
rust-miniscript errors on parsing descriptors with these limitations and the compiler would not create these scripts.
31+
32+
## Limitations dependent on satisfactions
33+
34+
Some limitations are dependant on satisfaction path taken by script. It is possible that certain script satisfaction paths are not valid by consensus rules because they exceed the following limits:
35+
36+
- Script satisfactions where the total number of non-push opcodes plus the number of keys participating in all executed thresh_ms, is above 201, are invalid by consensus (bare, P2SH, P2WSH, P2SH-P2WSH).
37+
- Script satisfactions with a serialized scriptSig over 1650 bytes are invalid by standardness (P2SH).
38+
- Script satisfactions with a witness consisting of over 100 stack elements (excluding the script itself) are invalid by standardness (P2WSH, P2SH-P2WSH).
39+
40+
rust-miniscript correctly parses these miniscripts, but does not allow lifting/analyzing these scripts if any of the spend paths exceeds the above limits. The satisfier logic does **not** gurantee to find the satisfactions for these scripts.

examples/htlc.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,37 +28,42 @@ fn main() {
2828
let htlc_policy = Concrete::<bitcoin::PublicKey>::from_str(&format!("or(10@and(sha256({secret_hash}),pk({redeem_identity})),1@and(older({expiry}),pk({refund_identity})))",
2929
secret_hash = "1111111111111111111111111111111111111111111111111111111111111111",
3030
redeem_identity = "022222222222222222222222222222222222222222222222222222222222222222",
31-
refund_identity = "022222222222222222222222222222222222222222222222222222222222222222",
31+
refund_identity = "020202020202020202020202020202020202020202020202020202020202020202",
3232
expiry = "4444"
3333
)).unwrap();
3434

3535
let htlc_descriptor = Descriptor::Wsh(htlc_policy.compile().unwrap());
3636

37+
// Check whether the descriptor is safe
38+
// This checks whether all spend paths are accessible in bitcoin network.
39+
// It maybe possible that some of the spend require more than 100 elements in Wsh scripts
40+
// Or they contain a combination of timelock and heightlock.
41+
assert!(htlc_descriptor.sanity_check().is_ok());
3742
assert_eq!(
3843
format!("{}", htlc_descriptor),
39-
"wsh(andor(pk(022222222222222222222222222222222222222222222222222222222222222222),sha256(1111111111111111111111111111111111111111111111111111111111111111),and_v(v:pkh(4377a5acd66dc5cb67148a24818d1e51fa183bd2),older(4444))))"
44+
"wsh(andor(pk(022222222222222222222222222222222222222222222222222222222222222222),sha256(1111111111111111111111111111111111111111111111111111111111111111),and_v(v:pkh(51814f108670aced2d77c1805ddd6634bc9d4731),older(4444))))"
4045
);
4146

4247
assert_eq!(
4348
format!("{}", htlc_descriptor.lift().unwrap()),
44-
"or(and(pkh(4377a5acd66dc5cb67148a24818d1e51fa183bd2),pkh(4377a5acd66dc5cb67148a24818d1e51fa183bd2),older(4444)),sha256(1111111111111111111111111111111111111111111111111111111111111111))"
49+
"or(and(pkh(4377a5acd66dc5cb67148a24818d1e51fa183bd2),pkh(51814f108670aced2d77c1805ddd6634bc9d4731),older(4444)),sha256(1111111111111111111111111111111111111111111111111111111111111111))"
4550
);
4651

4752
assert_eq!(
4853
format!("{:x}", htlc_descriptor.script_pubkey(NullCtx)),
49-
"00203c0a59874cb570ff3093bcd67e846c967127c9e3fcd30f0a20857b504599e50a"
54+
"0020d853877af928a8d2a569c9c0ed14bd16f6a80ce9cccaf8a6150fd8f7f8867ae2"
5055
);
5156

5257
assert_eq!(
5358
format!("{:x}", htlc_descriptor.witness_script(NullCtx)),
54-
"21022222222222222222222222222222222222222222222222222222222222222222ac6476a9144377a5acd66dc5cb67148a24818d1e51fa183bd288ad025c11b26782012088a82011111111111111111111111111111111111111111111111111111111111111118768"
59+
"21022222222222222222222222222222222222222222222222222222222222222222ac6476a91451814f108670aced2d77c1805ddd6634bc9d473188ad025c11b26782012088a82011111111111111111111111111111111111111111111111111111111111111118768"
5560
);
5661

5762
assert_eq!(
5863
format!(
5964
"{}",
6065
htlc_descriptor.address(Network::Bitcoin, NullCtx).unwrap()
6166
),
62-
"bc1q8s99np6vk4c07vynhnt8aprvjecj0j0rlnfs7z3qs4a4q3veu59q8x3k8x"
67+
"bc1qmpfcw7he9z5d9ftfe8qw699azmm2sr8fen903fs4plv007yx0t3qxfmqv5"
6368
);
6469
}

examples/parse.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,13 @@ fn main() {
2626
)
2727
.unwrap();
2828

29-
// Sometimes it is necessary to have additional information to get the bitcoin::PublicKey
29+
// Check whether the descriptor is safe
30+
// This checks whether all spend paths are accessible in bitcoin network.
31+
// It maybe possible that some of the spend require more than 100 elements in Wsh scripts
32+
// Or they contain a combination of timelock and heightlock.
33+
assert!(my_descriptor.sanity_check().is_ok());
34+
35+
// Sometimes it is necesarry to have additional information to get the bitcoin::PublicKey
3036
// from the MiniscriptKey which can supplied by `to_pk_ctx` parameter. For example,
3137
// when calculating the script pubkey of a descriptor with xpubs, the secp context and
3238
// child information maybe required.

src/descriptor/create_descriptor.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use bitcoin::blockdata::script::Instruction;
99
use descriptor::satisfied_constraints::Error as IntError;
1010
use descriptor::satisfied_constraints::{Stack, StackElement};
1111
use descriptor::Descriptor;
12-
use miniscript::{Legacy, Miniscript, Segwitv0};
12+
use miniscript::{Bare, Legacy, Miniscript, Segwitv0};
1313
use Error;
1414
use NullCtx;
1515
use ToPublicKey;
@@ -235,7 +235,7 @@ pub fn from_txin_with_witness_stack<'txin>(
235235
if !witness.is_empty() {
236236
return Err(Error::NonEmptyWitness);
237237
}
238-
let ms = Miniscript::<bitcoin::PublicKey, Legacy>::parse(script_pubkey)?;
238+
let ms = Miniscript::<bitcoin::PublicKey, Bare>::parse(script_pubkey)?;
239239
Ok((Descriptor::Bare(ms), Stack(stack?)))
240240
}
241241
}

src/descriptor/mod.rs

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ use miniscript::{decode::Terminal, satisfy, Legacy, Miniscript, Segwitv0};
4545
use policy;
4646
use push_opcode_size;
4747
use script_num_size;
48+
use Bare;
4849
use Error;
4950
use MiniscriptKey;
5051
use NullCtx;
@@ -72,7 +73,7 @@ pub type KeyMap = HashMap<DescriptorPublicKey, DescriptorSecretKey>;
7273
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
7374
pub enum Descriptor<Pk: MiniscriptKey> {
7475
/// A raw scriptpubkey (including pay-to-pubkey) under Legacy context
75-
Bare(Miniscript<Pk, Legacy>),
76+
Bare(Miniscript<Pk, Bare>),
7677
/// Pay-to-Pubkey
7778
Pk(Pk),
7879
/// Pay-to-PubKey-Hash
@@ -731,6 +732,34 @@ impl<Pk: MiniscriptKey> Descriptor<Pk> {
731732
)),
732733
}
733734
}
735+
736+
/// Whether the descriptor is safe
737+
/// Checks whether all the spend paths in the descriptor are possible
738+
/// on the bitcoin network under the current standardness and consensus rules
739+
/// Also checks whether the descriptor requires signauture on all spend paths
740+
/// And whether the script is malleable.
741+
/// In general, all the guarantees of miniscript hold only for safe scripts.
742+
/// All the analysis gurantees of miniscript only hold safe scripts.
743+
/// The signer may not be able to find satisfactions even if one exists
744+
pub fn sanity_check(&self) -> Result<(), Error> {
745+
match *self {
746+
Descriptor::Bare(ref ms) => ms.sanity_check()?,
747+
Descriptor::Pk(ref _pk)
748+
| Descriptor::Pkh(ref _pk)
749+
| Descriptor::Wpkh(ref _pk)
750+
| Descriptor::ShWpkh(ref _pk) => {}
751+
Descriptor::Wsh(ref ms) | Descriptor::ShWsh(ref ms) => ms.sanity_check()?,
752+
Descriptor::Sh(ref ms) => ms.sanity_check()?,
753+
Descriptor::WshSortedMulti(ref svm) | Descriptor::ShWshSortedMulti(ref svm) => {
754+
// extra allocation using clone allows us to reuse
755+
// check safety function from Miniscript fragment instead
756+
// of implemneting more code for safety checks
757+
svm.clone().sanity_check()?
758+
}
759+
Descriptor::ShSortedMulti(ref svm) => svm.clone().sanity_check()?,
760+
}
761+
Ok(())
762+
}
734763
}
735764

736765
impl<Pk: MiniscriptKey> Descriptor<Pk> {
@@ -1328,11 +1357,8 @@ where
13281357
)?));
13291358
}
13301359
let sub = Miniscript::from_tree(&newtop.args[0])?;
1331-
if sub.ty.corr.base != miniscript::types::Base::B {
1332-
Err(Error::NonTopLevel(format!("{:?}", sub)))
1333-
} else {
1334-
Ok(Descriptor::ShWsh(sub))
1335-
}
1360+
Segwitv0::top_level_checks(&sub)?;
1361+
Ok(Descriptor::ShWsh(sub))
13361362
}
13371363
("wpkh", 1) => {
13381364
let wpkh = expression::terminal(&newtop.args[0], |pk| Pk::from_str(pk))?;
@@ -1347,11 +1373,8 @@ where
13471373
)?)),
13481374
_ => {
13491375
let sub = Miniscript::from_tree(&top.args[0])?;
1350-
if sub.ty.corr.base != miniscript::types::Base::B {
1351-
Err(Error::NonTopLevel(format!("{:?}", sub)))
1352-
} else {
1353-
Ok(Descriptor::Sh(sub))
1354-
}
1376+
Legacy::top_level_checks(&sub)?;
1377+
Ok(Descriptor::Sh(sub))
13551378
}
13561379
}
13571380
}
@@ -1362,19 +1385,13 @@ where
13621385
)?));
13631386
}
13641387
let sub = Miniscript::from_tree(&top.args[0])?;
1365-
if sub.ty.corr.base != miniscript::types::Base::B {
1366-
Err(Error::NonTopLevel(format!("{:?}", sub)))
1367-
} else {
1368-
Ok(Descriptor::Wsh(sub))
1369-
}
1388+
Segwitv0::top_level_checks(&sub)?;
1389+
Ok(Descriptor::Wsh(sub))
13701390
}
13711391
_ => {
13721392
let sub = Miniscript::from_tree(&top)?;
1373-
if sub.ty.corr.base != miniscript::types::Base::B {
1374-
Err(Error::NonTopLevel(format!("{:?}", sub)))
1375-
} else {
1376-
Ok(Descriptor::Bare(sub))
1377-
}
1393+
Bare::top_level_checks(&sub)?;
1394+
Ok(Descriptor::Bare(sub))
13781395
}
13791396
}
13801397
}
@@ -1460,7 +1477,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
14601477

14611478
// This would check all the consensus rules for p2sh/p2wsh and
14621479
// even tapscript in future
1463-
Ctx::check_frag_validity(&ms)?;
1480+
Ctx::check_local_validity(&ms)?;
14641481

14651482
Ok(Self {
14661483
k,
@@ -1502,6 +1519,16 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
15021519
})
15031520
}
15041521
}
1522+
impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
1523+
// utility function to sanity a sorted multi vec
1524+
fn sanity_check(self) -> Result<(), Error> {
1525+
let ms: Miniscript<Pk, Ctx> =
1526+
Miniscript::from_ast(Terminal::Multi(self.k, self.pks)).expect("Must typecheck");
1527+
// '?' for doing From conversion
1528+
ms.sanity_check()?;
1529+
Ok(())
1530+
}
1531+
}
15051532

15061533
impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
15071534
/// Create Terminal::Multi containing sorted pubkeys
@@ -1649,6 +1676,7 @@ mod tests {
16491676
use descriptor::{
16501677
DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePub, DescriptorXKey,
16511678
};
1679+
use hex_script;
16521680
use miniscript::satisfy::BitcoinSig;
16531681
use std::cmp;
16541682
use std::collections::HashMap;
@@ -1715,14 +1743,25 @@ mod tests {
17151743
StdDescriptor::from_str(&format!("sh(wpkh({}))", uncompressed_pk)).unwrap_err();
17161744
StdDescriptor::from_str(&format!("wsh(pk{})", uncompressed_pk)).unwrap_err();
17171745
StdDescriptor::from_str(&format!("sh(wsh(pk{}))", uncompressed_pk)).unwrap_err();
1746+
StdDescriptor::from_str(&format!(
1747+
"or_i(pk({}),pk({}))",
1748+
uncompressed_pk, uncompressed_pk
1749+
))
1750+
.unwrap_err();
17181751
}
17191752

17201753
#[test]
17211754
pub fn script_pubkey() {
1722-
let bare = StdDescriptor::from_str("older(1000)").unwrap();
1755+
let bare = StdDescriptor::from_str(&format!(
1756+
"multi(1,020000000000000000000000000000000000000000000000000000000000000002)"
1757+
))
1758+
.unwrap();
1759+
println!("{:x}", bare.script_pubkey(NullCtx));
17231760
assert_eq!(
17241761
bare.script_pubkey(NullCtx),
1725-
bitcoin::Script::from(vec![0x02, 0xe8, 0x03, 0xb2])
1762+
hex_script(
1763+
"512102000000000000000000000000000000000000000000000000000000000000000251ae"
1764+
)
17261765
);
17271766
assert_eq!(bare.address(bitcoin::Network::Bitcoin, NullCtx), None);
17281767

@@ -2005,6 +2044,7 @@ mod tests {
20052044
.into_script()
20062045
);
20072046

2047+
let ms = ms_str!("c:pk_k({})", pk);
20082048
let sh = Descriptor::Sh(ms.clone());
20092049
sh.satisfy(&mut txin, &satisfier, NullCtx)
20102050
.expect("satisfaction");

src/descriptor/satisfied_constraints.rs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -322,21 +322,19 @@ where
322322
has_errored: false,
323323
}
324324
}
325-
&Descriptor::Sh(ref miniscript) | &Descriptor::Bare(ref miniscript) => {
326-
SatisfiedConstraints {
327-
verify_sig: verify_sig,
328-
public_key: None,
329-
state: vec![NodeEvaluationState {
330-
node: Any::from_legacy(miniscript),
331-
n_evaluated: 0,
332-
n_satisfied: 0,
333-
}],
334-
stack: stack,
335-
age,
336-
height,
337-
has_errored: false,
338-
}
339-
}
325+
&Descriptor::Sh(ref miniscript) => SatisfiedConstraints {
326+
verify_sig: verify_sig,
327+
public_key: None,
328+
state: vec![NodeEvaluationState {
329+
node: Any::from_legacy(miniscript),
330+
n_evaluated: 0,
331+
n_satisfied: 0,
332+
}],
333+
stack: stack,
334+
age,
335+
height,
336+
has_errored: false,
337+
},
340338
// We can leave this as unimplemented because this is supposed to be used to
341339
// Descriptor::from_txin_and_witness which outputs Stack required for the
342340
// constructor of this function.
@@ -357,6 +355,19 @@ where
357355
which cannot output a sorted multi descriptor and thus this code is \\
358356
currently unimplemented."
359357
),
358+
&Descriptor::Bare(ref miniscript) => SatisfiedConstraints {
359+
verify_sig: verify_sig,
360+
public_key: None,
361+
state: vec![NodeEvaluationState {
362+
node: Any::from_bare(miniscript),
363+
n_evaluated: 0,
364+
n_satisfied: 0,
365+
}],
366+
stack: stack,
367+
age,
368+
height,
369+
has_errored: false,
370+
},
360371
}
361372
}
362373
}
@@ -1093,7 +1104,6 @@ mod tests {
10931104
StackElement,
10941105
};
10951106
use miniscript::context::{Any, Legacy};
1096-
use std::str::FromStr;
10971107
use BitcoinSig;
10981108
use Miniscript;
10991109
use MiniscriptKey;

0 commit comments

Comments
 (0)