@bitcoinerlab/miniscript-policies 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/example.js ADDED
@@ -0,0 +1,34 @@
1
+ // To run it: "node ./example.js"
2
+
3
+ const { compilePolicy, compileMiniscript, ready } = require("./dist/index.js");
4
+
5
+ (async () => {
6
+ await ready;
7
+
8
+ const policy = "or(and(pk(A),older(8640)),pk(B))";
9
+
10
+ const {
11
+ miniscript,
12
+ asm: asmFromPolicy,
13
+ issane: issaneFromPolicy,
14
+ } = compilePolicy(policy);
15
+
16
+ const { asm: asmFromMiniscript, issane: issaneFromMiniscript } =
17
+ compileMiniscript(miniscript);
18
+
19
+ console.assert(asmFromPolicy === asmFromMiniscript, "ERROR: Asm mismatch.");
20
+ console.assert(
21
+ issaneFromPolicy === issaneFromMiniscript,
22
+ "ERROR: issane mismatch.",
23
+ );
24
+
25
+ console.log({
26
+ miniscript,
27
+ asm: asmFromMiniscript,
28
+ issane: issaneFromMiniscript,
29
+ });
30
+
31
+ console.log(
32
+ compileMiniscript("and_v(v:pk(key),or_b(l:after(100),al:after(200)))"),
33
+ );
34
+ })();
@@ -0,0 +1,154 @@
1
+ // Initial author: Pieter Wuille
2
+ // Adapted by Jose-Luis Landabaso so that miniscript_compile and miniscript_analyze
3
+ // return issane & issanesublevel
4
+ #include <string>
5
+
6
+ #include <script/miniscript.h>
7
+
8
+ #include "compiler.h"
9
+
10
+ namespace {
11
+
12
+ using miniscript::operator"" _mst;
13
+
14
+ void Output(const std::string& str, char* out, int outlen) {
15
+ int maxlen = std::min<int>(outlen - 1, str.size());
16
+ memcpy(out, str.c_str(), maxlen);
17
+ out[maxlen] = 0;
18
+ }
19
+
20
+ std::string Props(const miniscript::NodeRef<std::string>& node, std::string in) {
21
+ std::string ret = "<span title=\"type: ";
22
+ if (node->GetType() == ""_mst) {
23
+ ret += "[invalid]";
24
+ } else {
25
+ if (node->GetType() << "B"_mst) ret += 'B';
26
+ if (node->GetType() << "V"_mst) ret += 'V';
27
+ if (node->GetType() << "W"_mst) ret += 'W';
28
+ if (node->GetType() << "K"_mst) ret += 'K';
29
+ if (node->GetType() << "z"_mst) ret += 'z';
30
+ if (node->GetType() << "o"_mst) ret += 'o';
31
+ if (node->GetType() << "n"_mst) ret += 'n';
32
+ if (node->GetType() << "d"_mst) ret += 'd';
33
+ if (node->GetType() << "f"_mst) ret += 'f';
34
+ if (node->GetType() << "e"_mst) ret += 'e';
35
+ if (node->GetType() << "m"_mst) ret += 'm';
36
+ if (node->GetType() << "u"_mst) ret += 'u';
37
+ if (node->GetType() << "s"_mst) ret += 's';
38
+ if (node->GetType() << "k"_mst) ret += 'k';
39
+ }
40
+ ret += "&#13;scriptlen: " + std::to_string(node->ScriptSize());
41
+ ret += "&#13;max ops: " + std::to_string(node->GetOps());
42
+ ret += "&#13;max stack size: " + std::to_string(node->GetStackSize());
43
+ return std::move(ret) + "\">" + std::move(in) + "</span>";
44
+ }
45
+
46
+ std::string Analyze(const miniscript::NodeRef<std::string>& node) {
47
+ switch (node->fragment) {
48
+ case miniscript::Fragment::PK_K: {
49
+ return Props(node, "pk_k(" + (*COMPILER_CTX.ToString(node->keys[0])) + ")");
50
+ }
51
+ case miniscript::Fragment::PK_H: {
52
+ return Props(node, "pk_h(" + (*COMPILER_CTX.ToString(node->keys[0])) + ")");
53
+ }
54
+ case miniscript::Fragment::MULTI: return Props(node, "multi(" + std::to_string(node->k) + " of " + std::to_string(node->keys.size()) + ")");
55
+ case miniscript::Fragment::AFTER: return Props(node, "after(" + std::to_string(node->k) + ")");
56
+ case miniscript::Fragment::OLDER: return Props(node, "older(" + std::to_string(node->k) + ")");
57
+ case miniscript::Fragment::SHA256: return Props(node, "sha256()");
58
+ case miniscript::Fragment::RIPEMD160: return Props(node, "ripemd160()");
59
+ case miniscript::Fragment::HASH256: return Props(node, "hash256()");
60
+ case miniscript::Fragment::HASH160: return Props(node, "hash160()");
61
+ case miniscript::Fragment::JUST_0: return Props(node, "false");
62
+ case miniscript::Fragment::JUST_1: return Props(node, "true");
63
+ case miniscript::Fragment::WRAP_A: return Props(node, "a:") + " " + Analyze(node->subs[0]);
64
+ case miniscript::Fragment::WRAP_S: return Props(node, "s:") + " " + Analyze(node->subs[0]);
65
+ case miniscript::Fragment::WRAP_C: return Props(node, "c:") + " " + Analyze(node->subs[0]);
66
+ case miniscript::Fragment::WRAP_D: return Props(node, "d:") + " " + Analyze(node->subs[0]);
67
+ case miniscript::Fragment::WRAP_V: return Props(node, "v:") + " " + Analyze(node->subs[0]);
68
+ case miniscript::Fragment::WRAP_N: return Props(node, "n:") + " " + Analyze(node->subs[0]);
69
+ case miniscript::Fragment::WRAP_J: return Props(node, "j:") + " " + Analyze(node->subs[0]);
70
+ case miniscript::Fragment::AND_V: return Props(node, "and_v") + "<ul style=\"list-style-type: disc;\"><li>" + Analyze(node->subs[0]) + "</li><li>" + Analyze(node->subs[1]) + "</li></ul>";
71
+ case miniscript::Fragment::AND_B: return Props(node, "and_b") + "<ul style=\"list-style-type: disc;\"><li>" + Analyze(node->subs[0]) + "</li><li>" + Analyze(node->subs[1]) + "</li></ul>";
72
+ case miniscript::Fragment::OR_B: return Props(node, "or_b") + "<ul style=\"list-style-type: disc;\"><li>" + Analyze(node->subs[0]) + "</li><li>" + Analyze(node->subs[1]) + "</li></ul>";
73
+ case miniscript::Fragment::OR_C: return Props(node, "or_c") + "<ul style=\"list-style-type: disc;\"><li>" + Analyze(node->subs[0]) + "</li><li>" + Analyze(node->subs[1]) + "</li></ul>";
74
+ case miniscript::Fragment::OR_D: return Props(node, "or_d") + "<ul style=\"list-style-type: disc;\"><li>" + Analyze(node->subs[0]) + "</li><li>" + Analyze(node->subs[1]) + "</li></ul>";
75
+ case miniscript::Fragment::OR_I: return Props(node, "or_i") + "<ul style=\"list-style-type: disc;\"><li>" + Analyze(node->subs[0]) + "</li><li>" + Analyze(node->subs[1]) + "</li></ul>";
76
+ case miniscript::Fragment::ANDOR: return Props(node, "andor [or]") + "<ul style=\"list-style-type: disc;\"><li>andor [and]<ul style=\"list-style-type: disc;\"><li>" + Analyze(node->subs[0]) + "</li><li>" + Analyze(node->subs[1]) + "</li></ul></li><li>" + Analyze(node->subs[2]) + "</li></ul>";
77
+ case miniscript::Fragment::THRESH: {
78
+ auto ret = Props(node, "thresh(" + std::to_string(node->k) + " of " + std::to_string(node->subs.size()) + ")") + "<ul style=\"list-style-type: disc;\">";
79
+ for (const auto& sub : node->subs) {
80
+ ret += "<li>" + Analyze(sub) + "</li>";
81
+ }
82
+ return std::move(ret) + "</ul>";
83
+ }
84
+ }
85
+ }
86
+
87
+ }
88
+
89
+ extern "C" {
90
+
91
+ void miniscript_compile(const char* desc, char* msout, int msoutlen, char* costout, int costoutlen, char* asmout, int asmoutlen, char* issane, int issanelen, char* issanesublevel, int issanesublevellen) {
92
+ try {
93
+ std::string str(desc);
94
+ str.erase(str.find_last_not_of(" \n\r\t") + 1);
95
+ miniscript::NodeRef<std::string> ret;
96
+ double avgcost;
97
+ if (!Compile(Expand(str), ret, avgcost)) {
98
+ Output("[compile error]", msout, msoutlen);
99
+ Output("[compile error]", costout, costoutlen);
100
+ Output("[compile error]", asmout, asmoutlen);
101
+ return;
102
+ }
103
+ Output(Abbreviate(*(ret->ToString(COMPILER_CTX))), msout, msoutlen);
104
+ std::string coststr = "<ul><li>Script: " + std::to_string(ret->ScriptSize()) + " WU</li><li>Input: " + std::to_string(avgcost) + " WU</li><li>Total: " + std::to_string(ret->ScriptSize() + avgcost) + " WU</li></ul>";
105
+ Output(coststr, costout, costoutlen);
106
+ Output(Disassemble(ret->ToScript(COMPILER_CTX)), asmout, asmoutlen);
107
+ if (ret->IsSane()) {
108
+ Output("true", issanesublevel, issanesublevellen);
109
+ } else {
110
+ Output("false", issanesublevel, issanesublevellen);
111
+ }
112
+ if (ret->IsValidTopLevel()) {
113
+ Output("true", issane, issanelen);
114
+ } else {
115
+ Output("false", issane, issanelen);
116
+ }
117
+ } catch (const std::exception& e) {
118
+ Output("[exception: " + std::string(e.what()) + "]", msout, msoutlen);
119
+ Output("", costout, costoutlen);
120
+ Output("", asmout, asmoutlen);
121
+ }
122
+ }
123
+
124
+ void miniscript_analyze(const char* ms, char* costout, int costoutlen, char* asmout, int asmoutlen, char* issane, int issanelen, char* issanesublevel, int issanesublevellen) {
125
+ try {
126
+ std::string str(ms);
127
+ str.erase(str.find_last_not_of(" \n\r\t") + 1);
128
+ miniscript::NodeRef<std::string> ret;
129
+ ret = miniscript::FromString(Expand(str), COMPILER_CTX);
130
+ if (!ret || !ret->IsValidTopLevel()) {
131
+ Output("[analysis error]", costout, costoutlen);
132
+ Output("[analysis error]", asmout, asmoutlen);
133
+ return;
134
+ }
135
+ std::string coststr = "Size: " + std::to_string(ret->ScriptSize()) + " bytes script<ul><li>" + Analyze(ret) + "</li></ul>";
136
+ Output(coststr, costout, costoutlen);
137
+ Output(Disassemble(ret->ToScript(COMPILER_CTX)), asmout, asmoutlen);
138
+ if (ret->IsSane()) {
139
+ Output("true", issanesublevel, issanesublevellen);
140
+ } else {
141
+ Output("false", issanesublevel, issanesublevellen);
142
+ }
143
+ if (ret->IsValidTopLevel()) {
144
+ Output("true", issane, issanelen);
145
+ } else {
146
+ Output("false", issane, issanelen);
147
+ }
148
+ } catch (const std::exception& e) {
149
+ Output("[exception: " + std::string(e.what()) + "]", costout, costoutlen);
150
+ Output("", asmout, asmoutlen);
151
+ }
152
+ }
153
+
154
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@bitcoinerlab/miniscript-policies",
3
+ "author": "Jose-Luis Landabaso <landabaso@gmail.com>",
4
+ "license": "MIT",
5
+ "keywords": [
6
+ "miniscript",
7
+ "policy",
8
+ "compiler",
9
+ "bitcoin",
10
+ "asm",
11
+ "descriptors"
12
+ ],
13
+ "homepage": "https://bitcoinerlab.com/modules/miniscript",
14
+ "version": "1.0.0",
15
+ "description": "Bitcoin Miniscript policy compiler",
16
+ "main": "dist/index.js",
17
+ "types": "types/index.d.ts",
18
+ "scripts": {
19
+ "build": "rollup -c --bundleConfigAsCjs",
20
+ "build:prod": "NODE_ENV=production rollup -c --bundleConfigAsCjs",
21
+ "prepublishOnly": "npm test",
22
+ "test": "make clean && make && npm run build:prod && jest"
23
+ },
24
+ "COMMENT_babel": "Babel plugins are are only needed for the jest testing environment. Jest needs to use commonjs. Also, jest cannot handle ESM converted code, since it uses 'import.meta.url'. See src/bindings.js. babel-plugin-transform-import-meta fixes it.",
25
+ "babel": {
26
+ "env": {
27
+ "test": {
28
+ "plugins": [
29
+ "@babel/plugin-transform-modules-commonjs",
30
+ "babel-plugin-transform-import-meta"
31
+ ]
32
+ }
33
+ }
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/bitcoinerlab/miniscript-policies.git"
38
+ },
39
+ "devDependencies": {
40
+ "@babel/plugin-transform-modules-commonjs": "^7.19.6",
41
+ "@bitcoinerlab/configs": "^2.0.0",
42
+ "@rollup/plugin-commonjs": "^29.0.0",
43
+ "babel-plugin-transform-import-meta": "^2.2.0",
44
+ "rollup": "^3.29.5"
45
+ },
46
+ "dependencies": {
47
+ "bip68": "^1.0.4"
48
+ },
49
+ "bugs": {
50
+ "url": "https://github.com/bitcoinerlab/miniscript-policies/issues"
51
+ }
52
+ }
@@ -0,0 +1,11 @@
1
+ --- compiler.cpp.bak 2026-01-30 13:12:43
2
+ +++ compiler.cpp 2026-01-30 13:12:54
3
+ @@ -848,7 +848,7 @@
4
+ }
5
+ if (data.size() == 20) {
6
+ if (data == std::vector<unsigned char>(20, 0x99)) {
7
+ - ret += "<h>";
8
+ + ret += "<H>";
9
+ } else if (data[0] == 'P' && data[1] == 'K' && data[2] == 'h') {
10
+ while (data.size() && data.back() == 0) data.pop_back();
11
+ ret += "<HASH160(" + std::string((const char*)data.data() + 3, data.size() - 3) + ")>";
@@ -0,0 +1,13 @@
1
+
2
+ import commonjs from "@rollup/plugin-commonjs";
3
+
4
+ export default {
5
+ input: "./src/index.js",
6
+ output: {
7
+ file: "./dist/index.js",
8
+ format: "cjs",
9
+ },
10
+ plugins: [commonjs()],
11
+ // Tell Rollup not to bundle dependencies listed in package.json
12
+ external: [...Object.keys(require("./package.json").dependencies || {})],
13
+ };
package/src/index.js ADDED
@@ -0,0 +1,5 @@
1
+ // Distributed under the MIT software license
2
+
3
+ import { compilePolicy, compileMiniscript, ready } from './miniscript.js';
4
+
5
+ export { compilePolicy, compileMiniscript, ready };
@@ -0,0 +1,149 @@
1
+ // Initial author: Pieter Wuille ( https://github.com/sipa/miniscript/blob/master/index.html)
2
+ // Adapted by Jose-Luis Landabaso - https://bitcoinerlab.com:
3
+ // compilePolicy, compileMiniscript with issane, issanesublevel and cleanAsm
4
+
5
+ import bindings from './bindings.js';
6
+
7
+ const cleanAsm = asm =>
8
+ asm
9
+ .trim()
10
+ .replace(/\n/g, ' ')
11
+ .replace(/ +(?= )/g, '');
12
+
13
+ let Module;
14
+ let em_miniscript_compile;
15
+ let em_miniscript_analyze;
16
+ let modulePromise;
17
+
18
+ const initModule = () => {
19
+ if (!modulePromise) {
20
+ modulePromise = Promise.resolve(bindings()).then(resolved => {
21
+ Module = resolved;
22
+ em_miniscript_compile = Module.cwrap('miniscript_compile', 'none', [
23
+ 'string',
24
+ 'number',
25
+ 'number',
26
+ 'number',
27
+ 'number',
28
+ 'number',
29
+ 'number'
30
+ ]);
31
+ em_miniscript_analyze = Module.cwrap('miniscript_analyze', 'none', [
32
+ 'string',
33
+ 'number',
34
+ 'number',
35
+ 'number',
36
+ 'number'
37
+ ]);
38
+ return Module;
39
+ });
40
+ }
41
+
42
+ return modulePromise;
43
+ };
44
+
45
+ export const ready = initModule();
46
+
47
+ const ensureReady = () => {
48
+ if (!Module || !em_miniscript_compile || !em_miniscript_analyze) {
49
+ throw new Error('Miniscript bindings not ready. Await ready before calling compile functions.');
50
+ }
51
+ };
52
+
53
+ /**
54
+ * @typedef {Object} CompilePolicyResult
55
+ * @property {string} miniscript - The compiled miniscript expression.
56
+ * @property {string} asm - The compiled miniscript as Bitcoin asm code.
57
+ * @property {boolean} issane - Whether the miniscript is sane at the top level.
58
+ * @property {boolean} issanesublevel - Whether the miniscript is sane at the sublevel.
59
+ */
60
+
61
+ /**
62
+ * @typedef {Object} CompileMiniscriptResult
63
+ * @property {string} asm - The Bitcoin asm code of the compiled miniscript expression.
64
+ * @property {boolean} issane - Whether the miniscript is sane at the top level.
65
+ * @property {boolean} issanesublevel - Whether the miniscript is sane at the sublevel.
66
+ */
67
+
68
+
69
+ /**
70
+ * Compiles a miniscript policy into a miniscript expression (if possible).
71
+ * @function
72
+ *
73
+ * @param {string} policy - The miniscript policy to compile.
74
+ * @returns {CompilePolicyResult}
75
+ */
76
+ export const compilePolicy = policy => {
77
+ ensureReady();
78
+ const miniscript = Module._malloc(10000);
79
+ const cost = Module._malloc(500);
80
+ const asm = Module._malloc(100000);
81
+ const issane = Module._malloc(10);
82
+ const issanesublevel = Module._malloc(10);
83
+ em_miniscript_compile(
84
+ policy,
85
+ miniscript,
86
+ 10000,
87
+ cost,
88
+ 500,
89
+ asm,
90
+ 100000,
91
+ issane,
92
+ 10,
93
+ issanesublevel,
94
+ 10
95
+ );
96
+ const result = {
97
+ miniscript: Module.UTF8ToString(miniscript),
98
+ asm: cleanAsm(Module.UTF8ToString(asm)),
99
+ issane: Module.UTF8ToString(issane) === 'true' ? true : false,
100
+ issanesublevel:
101
+ Module.UTF8ToString(issanesublevel) === 'true' ? true : false
102
+ };
103
+ Module._free(miniscript);
104
+ Module._free(cost);
105
+ Module._free(asm);
106
+ Module._free(issane);
107
+ Module._free(issanesublevel);
108
+
109
+ return result;
110
+ };
111
+
112
+ /**
113
+ * Compiles a miniscript expression and returns its asm code.
114
+ * @function
115
+ *
116
+ * @param {string} miniscript - A miniscript expression.
117
+ * @returns {CompileMiniscriptResult}
118
+ */
119
+ export const compileMiniscript = miniscript => {
120
+ ensureReady();
121
+ const analysis = Module._malloc(50000);
122
+ const asm = Module._malloc(100000);
123
+ const issane = Module._malloc(10);
124
+ const issanesublevel = Module._malloc(10);
125
+ em_miniscript_analyze(
126
+ miniscript,
127
+ analysis,
128
+ 50000,
129
+ asm,
130
+ 100000,
131
+ issane,
132
+ 10,
133
+ issanesublevel,
134
+ 10
135
+ );
136
+ const result_asm = Module.UTF8ToString(asm);
137
+ const result_issane = Module.UTF8ToString(issane);
138
+ const result_issanesublebel = Module.UTF8ToString(issanesublevel);
139
+ Module._free(analysis);
140
+ Module._free(asm);
141
+ Module._free(issane);
142
+ Module._free(issanesublevel);
143
+
144
+ return {
145
+ asm: cleanAsm(result_asm),
146
+ issane: result_issane === 'true' ? true : false,
147
+ issanesublevel: result_issanesublebel === 'true' ? true : false
148
+ };
149
+ };