Syntax Reference¶
Bitcoin Syntax¶
| Examples | Evaluates to | |
|---|---|---|
| Script Fragment | `2 $alice $bob $charlie 3 OP_CHECKMULTISIG` |
Script |
| Address | bc1qdfac40sy0uk2g3z9g8z75z3xkrvnsjd0h0u8q5 |
Address |
| Xpub | xpub661MyMwAqRbcFvXwqbgwigDczeocqeBEibKMCk… |
PubKey<Xpub> |
| Xpriv | xprv9s21ZrQH143K37CBUxq5bSXTyHwKc5KwYWsm6o… |
SecKey<Xpriv> |
| WIF | L4oC7AMPJkKPwuVipfFaKcJKBhsR3kmXy89f7oAFhSQSPWLNXocJ |
SecKey<SingleKey> |
| Key w/ Origin | [df786318/0/15]0211fff5af35167762f616224aaa6a… |
PubKey<SingleKey> |
| Key Derivation | xpub661MyMwAqRbcFvXwqbgwigDczeo…/84'/0'/1'/<0;1>/* |
PubKey<Xpub> |
| Amount | 1.3 BTC, 1300 bits |
Int |
| Relative Sequence Time | 30 days, 2 months 2 weeks |
Int |
| Absolute Lock Time | 2009-03-01T |
Int |
Script Fragments¶
TBD
Addresses¶
Bitcoin addresses encoded in Bech32(m)/Base58Check can be written directly in Minsc code
and are parsed into an Address.
$spk = 1Ai2dBSc1bmF3Nyc5VBQiNhjrtfL9SVtpw->script_pubkey;
$tx = tx[
"inputs": [ aab9cedeca3dd53acc35e80d3e91bb89a947fae88df4c562a50b637bb4fd8f7f:1 ],
"outputs": [
1Ai2dBSc1bmF3Nyc5VBQiNhjrtfL9SVtpw:150000,
bc1qdfac40sy0uk2g3z9g8z75z3xkrvnsjd0h0u8q5:20000,
]
];
assert::eq(typeof(1Ai2dBSc1bmF3Nyc5VBQiNhjrtfL9SVtpw), "address");
$spk = `OP_DUP OP_HASH160 <0x6a7b8abe047f2ca4444541c5ea0a26b0d93849af> OP_EQUALVERIFY OP_CHECKSIG` // script
$tx = tx[
"version": 2,
"inputs": [
aab9cedeca3dd53acc35e80d3e91bb89a947fae88df4c562a50b637bb4fd8f7f:1
],
"outputs": [
`OP_DUP OP_HASH160 <0x6a7b8abe047f2ca4444541c5ea0a26b0d93849af> OP_EQUALVERIFY OP_CHECKSIG`:0.0015 BTC,
`<0> <0x6a7b8abe047f2ca4444541c5ea0a26b0d93849af>`:0.0002 BTC
]
] // transaction
Keys¶
Xpub, Xpriv and WIF-encoded single secret keys can be written directly in Minsc code
and are parsed into a PubKey/SecKey.
// Xpub (used to construct a WPKH descriptor)
$descriptor = wpkh(xpub661MyMwAqRbcFvXwqbgwigDczeocqeBEibKMCkoc31RiyB464Ybc1z8sWMnR38JdeCBJPPkSM7mKahcBX2nPX9KYVTz3cotpLmSkMxrp99L);
// Xpriv (converted into an Xpub)
$xpub = pubkey(xprv9s21ZrQH143K37CBUxq5bSXTyHwKc5KwYWsm6onCYDmiPBBDyiz3bYwvHYH9NXzsY6mDiXmhf77Ym2EJkGreLHB3s6MH5tRkfKQT9uDQ4r2);
// WIF (used to sign)
$signature = ecdsa::sign(L4oC7AMPJkKPwuVipfFaKcJKBhsR3kmXy89f7oAFhSQSPWLNXocJ, hash::sha256("Hello World"));
$descriptor = wpkh(xpub661MyMwAqRbcFvXwqbgwigDczeocqeBEibKMCkoc31RiyB464Ybc1z8sWMnR38JdeCBJPPkSM7mKahcBX2nPX9KYVTz3cotpLmSkMxrp99L) // descriptor
$xpub = xpub661MyMwAqRbcFbGeazN5xaUCXKmp1Y3nujoMuCBp6ZJhFyWNXGJJ9MGQ8otkj3MgGcpcCx7u1A88yVZ2RmmPqPuUHJ5TsD1f7G2PoFNJqLc // pubkey
$signature = 0x304402201bd3260a7e1d76160df2e458d03828a2ed4c517bc0fefbaf1d2e097a75c0f1bf022007f60ca216d20196c3a0a306e779552e4ab8b53d09a0d209089bee14d501fe12 // bytes
Keys with an origin enclosed in [] brackets are supported too:
See Key Construction for more information.
Output Amounts¶
Amounts can be written using the following denomination suffixes:
BTC, mBTC/uBTC, bit(s), sat(s)/satoshi(s), msat(s)
Evaluates into an Int with the amount in satoshis.
assert::eq(0.2 BTC, 20000000);
assert::eq(1 BTC, 1000 mBTC);
assert::eq(1 mBTC, 1000 bits);
$psbt = psbt[
"inputs": [ aab9cedeca3dd53acc35e80d3e91bb89a947fae88df4c562a50b637bb4fd8f7f:1 ],
"utxos": [ wpkh($alice/0):2 BTC ],
"outputs": [
1Ai2dBSc1bmF3Nyc5VBQiNhjrtfL9SVtpw:5 mBTC,
wpkh($alice/1):2 BTC - 5 mBTC - 300 sat,
]
];
$psbt = psbt[
"unsigned_tx": tx[
"version": 2,
"inputs": [
aab9cedeca3dd53acc35e80d3e91bb89a947fae88df4c562a50b637bb4fd8f7f:1
],
"outputs": [
`OP_DUP OP_HASH160 <0x6a7b8abe047f2ca4444541c5ea0a26b0d93849af> OP_EQUALVERIFY OP_CHECKSIG`:0.005 BTC,
`<0> <0xe984e78b34b183b0c1812999165ac7986ed45299>`:1.994997 BTC
]
],
"version": 0,
"inputs": [
[
"utxo": `<0> <0x8813a55e1876baedc7144daba04d703c81489183>`:2 BTC,
"bip32_derivation": [
[df786318/0]03e0ec52bdc52856b2144e8affa02c0182d4291586d37c84cc8350204e4d7fd09b
]
]
],
"outputs": [
[ ],
[ "bip32_derivation": [ [df786318/1]02e4dce697a2987b086a147e1fb422cddc1d68232b94a7aaddbed1c815c78810b8 ] ]
]
] // psbt
Relative Sequence Time¶
Relative timelock durations can be written using the following time unit suffixes:
year(s), month(s), week(s), day(s), hour(s), minute(s), second(s)
Evaluates into an Int with the duration encoded as an input sequence number.
Used as the TxIn->sequence, to construct older() policies,
and with OP_CSV in Script.
// Evaluates into an Int
assert::eq(100 days, 4211179);
// Used as the input sequence
$tx = tx[
"input": [
"prevout": aab9cedeca3dd53acc35e80d3e91bb89a947fae88df4c562a50b637bb4fd8f7f:1,
"sequence": 1 month 10 days,
],
"output": 1Ai2dBSc1bmF3Nyc5VBQiNhjrtfL9SVtpw:5000,
];
// Used with Miniscript Policy
$policy = pk($alice) && older(3 days);
// Used with Script
$script = `$alice OP_CHECKSIGVERIFY <3 days> OP_CSV`;
$tx = tx[
"version": 2,
"inputs": [
[ "prevout": aab9cedeca3dd53acc35e80d3e91bb89a947fae88df4c562a50b637bb4fd8f7f:1, "sequence": 3493888 seconds ]
],
"outputs": [
`OP_DUP OP_HASH160 <0x6a7b8abe047f2ca4444541c5ea0a26b0d93849af> OP_EQUALVERIFY OP_CHECKSIG`:0.00005 BTC
]
] // transaction
$policy = and(pk(xpub661MyMwAqRbcGxLpmSg6J2PK7LafNMyws1wApmxpFmqKvgfAuxe7fBvjzhsAmM3QJ9bpzBLAB4NskRYDmeYrWiRLbC4JpTYf8zahjeEn6Z8),older(4194811)) // policy
$script = `<0x022a46eb3296a185c5887405fe4ff8ff2cb5003980c624b173520cacb37e958e3d> OP_CHECKSIGVERIFY <0xfb0140> OP_CHECKSEQUENCEVERIFY` // script
Time durations are encoded in granularity of 512 seconds and are rounded up (ceil(seconds/512)), so for example 513 seconds becomes 1024 seconds.
Relative block count locks can be written using the blocks suffix.
Absolute Lock Time¶
Absolute datetimes can be formatted as [YYYY]-[MM]-[DD]T or [YYYY]-[MM]-[DD]T[HH]:[mm]:[ss]Z?.
Evaluates into an Int with the datetime as a Unix timestamp.
Used as the Transaction->locktime, to construct after() policies,
and with OP_CLTV in Script.
// Evaluates into an Int
assert::eq(2009-03-01T, 1235865600);
assert::eq(2009-03-01T18:15:05, 1235931305);
assert::eq(2009-03-01T18:15:05Z, 1235931305);
// Used as the transaction locktime
$tx = tx[
"locktime": 2030-01-01T,
"input": aab9cedeca3dd53acc35e80d3e91bb89a947fae88df4c562a50b637bb4fd8f7f:1,
"output": 1Ai2dBSc1bmF3Nyc5VBQiNhjrtfL9SVtpw:5000,
];
// Used with Miniscript Policy
$policy = pk($alice) && after(2030-01-01T);
// Used with Script
$script = `$alice OP_CHECKSIGVERIFY 2030-01-01T OP_CLTV`;
$tx = tx[
"version": 2,
"locktime": 2030-01-01T,
"inputs": [
aab9cedeca3dd53acc35e80d3e91bb89a947fae88df4c562a50b637bb4fd8f7f:1
],
"outputs": [
`OP_DUP OP_HASH160 <0x6a7b8abe047f2ca4444541c5ea0a26b0d93849af> OP_EQUALVERIFY OP_CHECKSIG`:0.00005 BTC
]
] // transaction
$policy = and(pk(xpub661MyMwAqRbcGxLpmSg6J2PK7LafNMyws1wApmxpFmqKvgfAuxe7fBvjzhsAmM3QJ9bpzBLAB4NskRYDmeYrWiRLbC4JpTYf8zahjeEn6Z8),after(1893456000)) // policy
$script = `<0x022a46eb3296a185c5887405fe4ff8ff2cb5003980c624b173520cacb37e958e3d> OP_CHECKSIGVERIFY <0x80d8db70> OP_CLTV` // script
Bitcoin Operators¶
From Operators › Bitcoin Operators, reproduced here for convenience.
| Operator | Operands | Examples |
|---|---|---|
BIP 32 Derivation / |
PubKey / Int SecKey / Int ' PubKey / * PubKey / Array PubKey / Hash |
xpub661MyMwAqRZ90rTa…bcFvZ98rNvB/5$alice_sk/84'/0h/0'/1/*$alice_sk/$account'/($i+1)$alice/<0;1>/*$alice/hash::sha256("pay-to-contract-hash") |
Execution Probability@ |
Int @ PolicyInt @ Script |
wsh(2@pk($alice) || 1@pk($bob))tr[ 2@`$a OP_CHECKSIG`, 1@`$b OP_CHECKSIG` ] |
Script Repetition * |
Script * Int |
OP_CAT*2 → `OP_CAT OP_CAT``OP_DROP 3*`OP_ROT OP_ADD` OP_SUB` → |
Policy Composition[1] && || of |
Policy && PolicyPolicy || PolicyInt of Array<Policy> |
pk($a) && pk($b) → and(pk($a), pk($b))$pk1 || $pk2 || $pk3 → thresh(1, $pk1, $pk2, $pk3)2 of [ $pk1, $pk2, $pk3 ] → thresh(2, $pk1, $pk2, $pk3) |
Combine PSBT + |
Psbt + Psbt |
$psbt1+$psbt2 → psbt::combine[$psbt1, $psbt2] |
Taproot Tweak[2] + |
PubKey + ScriptPubKey + PolicyPubKey + ArrayPubKey + Hash |
NUMS+`<1 year> OP_CSV` → tr(NUMS, `<1 year> OP_CSV`) $alice+[ pk($b), pk($c) ] → tr($alice, [ … ]) $bob+0xcb40ac3f58… → tr($bob, 0xcb40ac3f58…) |
Unhardened child key derivation. Can also derive SecKey, Policy, Descriptor and Arrays of derivable types.
Hardened child key derivation. Also supported using h instead of '. Only possible on SecKey.
Enables the wildcard modifier. Supports /*' and /*h. Can also be used with SecKey.
Multi-path derivation. Supported using the <M;N;K> syntax or with standard arrays:
$alice/<0;1> → $alice/[0,1].
Can also derive SecKey, Policy, Descriptor and Arrays of derivable types.
Unhardened child key derivation using a 256-bit hash. Can also derive SecKey, Policy, Descriptor and Arrays of derivable types.
Tweak using the script tree merkle root hash. Returns a TapInfo that supports key-path spends only.
-
&&and||support >2 branches by translating them into anN-of-Nor1-of-Nthresh()policy. The Miniscript Policy standard forand()/or()only supports 2 branches.Compatible operands are coerced into a
Policy, so for example$alice && $bob→pk($alice) && pk($bob). -
Taproot tweak (
+) returns aDescriptor<Tr>or aTapInfo, depending on whether Miniscript or raw Script was used. Seetr()for more information.
Core Syntax¶
Identifiers¶
Identifiers can contain _ $ 0-9 a-z A-Z
and optionally any number of :: separators.
:: is simply part of the identifier name, used to namespace related functions together.
$ is typically used to prefix variable names, but this is entirely optional.
Alphanumeric-only identifiers are limited to 25 characters, to prevent conflicts with the native Bitcoin syntax for addresses/keys and with 0x-less bytes. This limit does not apply if the identifier contain a $, _ or ::.
Functions¶
Named functions are defined using the fn keyword.
When the function body is a single expression, it can be written as:
The function body can also include statements and a final expression used as the return value:
There's no explicit
returnkeyword or early return.
Anonymous functions are defined using a Rust-like |args| expr or |args| { body } syntax:
filter([ 1, 2, 3 ], |$n| $n > 2)
Default parameter values are supported, however the parser currently requires wrapping () even for some seemingly parsable expressions:
fn foo($a, $b=3, $c=false, $d="hello", $e=($b+1), $f=(-1)) =
print($a, $b, $c, $d, $e);
Destructuring¶
Destructuring assignment (unpacking) is supported in variable assignment and function signatures. It works with arrays and array-like types.
[$a, [$b, $c]] = [ 1, [ 2, 3 ] ];
[$receive, $change] = wpkh($alice_sk/84'/0'/0'/<0;1>/*);
[$psbt_, $succeed, $failed] = psbt::try_sign($psbt, $sk);
fn foo([$left, $right]) = $left + $right;
If Expressions¶
$is_big = if $n > 5 then true else false;
$how_big = if $n > 500 then "really big"
else if $n > 5 then "big"
else "small";
When the if/else body includes statements, it can be written as:
Block Expressions¶
An expression that immediately evaluates the {} block body under a new child scope
and returns its final expression. (Rust-like)
TAPLEAF_TAG = { $h=hash::sha256("TapLeaf"); $h+$h };
Bytes Expressions¶
Bytes can be constructed from hex using the 0x prefix:
For compatibility with the descriptors syntax, the 0x prefix is optional for lengths 20/32/33.
Base64 is supported using the 0z prefix:
Comments¶
Standard // … for single-line comments,
non-standard /** … */ for multi-line.
Yes, two opening
*. This is because/*conflicts with the wildcard modifier syntax for keys.
Syntactic Sugar¶
Array Call¶
| Syntax | Example | |
|---|---|---|
| Array Call | Function [ ...Any ] |
f[ A, B ] → f([ A, B ]) |
When a function is called with a single argument that is a literal [ … ] array,
the () may be omitted.
sum[ 4, 5, 10 ] // → sum([ 4, 5, 10 ])
psbt[
"unsigned_tx": tx[
"input": 331d3f920595ba389c35e3ecdd91ca85ae24313501d44401567f7ddc626e926a:1,
"output": tb1qxtympy2gdtwm87t0t5xdgklac66a73a6pqx26r:0.01 BTC,
],
"utxos": [ wpkh($alice/5):0.011 BTC ],
]
tr[ `$alice OP_CHECKSIG`, `$bob OP_CHECKSIG` ]
Pipe Call¶
| Syntax | Example | |
|---|---|---|
| Pipe Call | Any | Function ( ...Any )Any | Function [ ...Any ] |
A | f(B, C) → f(A, B, C)A | f[B, C] → f(A, [B, C]) |
Alternative function calling syntax, convenient for chaining.
$paid_to_addr = $tx->outputs
| filter(|$out| $out->script_pubkey == tb1qqecgzhegje3vuu6wazq9amkznj52jkdjad70cg->script_pubkey)
| map(|$out| $out->amount)
| sum();
// → sum(map(filter($tx->outputs, |$out| $out->script_pubkey == …), |$out| $out->amount))
psbt::create($tx)
| psbt::update[ "inputs": [ 0: [ "utxo": $utxo ] ] ]
| psbt::sign($bob_sk)
| psbt::finalize()
| psbt::extract()
// → psbt::extract(psbt::finalize(psbt::sign(psbt::update(psbt::create($tx), […])), $bob_sk))
The second example could actually be written much more simply:
psbt::sign_finalize([ "unsigned_tx": $tx, "utxos": [ $utxo ] ], $bob_sk)
Colon Tuple¶
From Operators › Colon Tuple, reproduced here for convenience.
| Operator | Operands | Example | |
|---|---|---|---|
| Colon Tuple | : |
Any : Any |
A:B → [ A, B ] |
The colon operator provides syntactic sugar for constructing 2-tuple arrays,
using A:B as shorthand for [ A, B ].
It is commonly used in Minsc to represent simple 2-field data structures, such as:
-
OutPointas atxid:vouttuple -
TxOutas ascriptPubKey:amounttuplewpkh($alice):1.5 BTC // → [ wpkh($alice), 150000000 ]
It is also used for constructing "object"-like tagged arrays of key-value pairs. For example:
$tx = tx([
[ "version", 2 ],
[ "locktime", 4194473 ],
[
"inputs",
[
[
[ "prevout", [ 0x5e876d922f10e1b7382c72b850f1796d5259c62b72266fb995368ec859afb07e, 0 ] ],
[ "witness", [ 0x010203f0e0d0 ] ],
]
],
],
[
"outputs",
[
[ wpkh($alice), 150000000 ],
[ bc1qu6p58lnd32acn0rpzkevk76c7g9aant6k4s2ly, 9000 ],
]
]
]);
Unlike all other operators in Minsc, the colon operator is right-associative.
This makes line 6 in the last example parse as [ "prevout", [ 5e876d922f…, 0 ] ] rather than [ [ "prevout", 5e876d922f… ], 0 ].
When 2-tuple arrays are formatted as strings (e.g. through
str()or on the web playground), there's a heuristic that decides whether to format them as[A,B]orA:B. It should typically get it right, but it could be surprising. For example,1:2is re-formatted as[1,2].
Standards Compatibility¶
Minsc implements several syntax features and coercions that aim to maximize compatibility with standardized string representations
of common Bitcoin data structures,
including Bitcoin addresses, Xpubs, Xprivs, WIFs, single keys, Miniscript policies, descriptor keys and (some) descriptors.
This enables seamless copy-paste of existing string representations into Minsc code, without requiring any changes. Some caveats apply.
0x-less Bytes¶
For compatibility with the BIP 380 Key Expression syntax for single public keys and with the Miniscript Policy syntax for hashes,
the 0x prefix is optional for bytes of length 20, 32 or 33.
wpkh(02372b2c6b9452181083a4777bf6ec6cbf4f363d25c1a4ee9fd646f5a90ef2e2aa)
// → wpkh(0x02372b2c6b9452181083a4777bf6ec6cbf4f363d25c1a4ee9fd646f5a90ef2e2aa)
Aside from compatibility, this is also commonly used with txids and txid:vot outpoints.
For example (together with the colon operator): 331d3f920595ba389c35e3ecdd91ca85ae24313501d44401567f7ddc626e926a:1
<A;B;…> Arrays¶
For compatibility with the BIP 389 syntax for multi-path keys and descriptors,
<A;B;…> is supported as an alternative array construction syntax equivalent to [ A, B, … ].
wpkh(xpub68hYLoZ9YjcBZuLp4uq1…EuJzCq/<0;1>/*)
// → wpkh(xpub68hYLoZ9YjcBZuLp4uq1…EuJzCq/[0,1]/*)
Only works with two or more array elements.
{A,B} Arrays¶
For compatibility with the BIP 386 syntax for Taproot tr() descriptors with binary script trees,
{A,B} is supported as an alternative array construction syntax equivalent to [ A, B ].
tr(xpub661MyMwAqRbcGxLpmSg6…J2PK7La,{pk(xpub661MyMwAqRbcFmLNCYgX…GZtNT5a),pk(xpub661MyMwAqRbcFaqugwCZA…how4KY)})
// → tr(xpub661MyMwAqRbcGxLpmSg6J…J2PK7La,[pk(xpub661MyMwAqRbcFmLNCYgXG…GZtNT5a),pk(xpub661MyMwAqRbcFaqugwCZA…how4KY)])
Only works with exactly two array elements.