├── .gitignore ├── src ├── index.js ├── satisfier │ ├── maxLock.js │ ├── index.js │ └── satisfactions.js └── miniscript.js ├── rollup.config.js ├── test ├── miniscript.test.js ├── satisfier.test.js ├── maxLock.test.js └── fixtures.js ├── types └── index.d.ts ├── example.js ├── Makefile ├── package.json ├── README.md ├── js_bindings.cpp └── compiler.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | node_modules/ 4 | docs/ 5 | webdocs/ 6 | dist/ 7 | miniscript/ 8 | src/bindings.js 9 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com 2 | // Distributed under the MIT software license 3 | 4 | import { satisfier } from './satisfier/index.js'; 5 | import { compilePolicy, compileMiniscript } from './miniscript.js'; 6 | 7 | export { compilePolicy, compileMiniscript, satisfier }; 8 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from "@rollup/plugin-commonjs"; 2 | 3 | export default { 4 | input: "./src/index.js", 5 | output: { 6 | file: "./dist/index.js", 7 | format: "cjs", 8 | }, 9 | plugins: [commonjs()], 10 | // Tell Rollup not to bundle dependencies listed in package.json 11 | external: [...Object.keys(require("./package.json").dependencies || {})], 12 | }; 13 | -------------------------------------------------------------------------------- /test/miniscript.test.js: -------------------------------------------------------------------------------- 1 | import { primitives, timeLocks, other } from './fixtures.js'; 2 | import { compileMiniscript } from '../dist/index.js'; 3 | 4 | const createGroupTest = (description, fixtures) => 5 | describe(description, () => { 6 | for (const [testName, fixture] of Object.entries(fixtures)) { 7 | if (!fixture.throws) { 8 | test(testName, () => { 9 | const script = compileMiniscript(fixture.miniscript).asm; 10 | expect(script).toEqual(fixture.script); 11 | }); 12 | } 13 | } 14 | }); 15 | 16 | createGroupTest('Primitives', primitives); 17 | createGroupTest('Timelocks', timeLocks); 18 | createGroupTest('Other', other); 19 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | export declare const compileMiniscript: (miniscript: string) => { 2 | asm: string; 3 | issane: boolean; 4 | issanesublevel: boolean; 5 | }; 6 | 7 | export declare const compilePolicy: (miniscript: string) => { 8 | miniscript: string; 9 | asm: string; 10 | issane: boolean; 11 | issanesublevel: boolean; 12 | }; 13 | 14 | export declare const satisfier: ( 15 | miniscript: string, 16 | options?: 17 | | { 18 | unknowns?: string[] | undefined; 19 | knowns?: string[] | undefined; 20 | } 21 | | undefined 22 | ) => { 23 | unknownSats?: Array<{ 24 | asm: string; 25 | nLockTime?: number; 26 | nSequence?: number; 27 | }>; 28 | nonMalleableSats?: Array<{ 29 | asm: string; 30 | nLockTime?: number; 31 | nSequence?: number; 32 | }>; 33 | malleableSats?: Array<{ 34 | asm: string; 35 | nLockTime?: number; 36 | nSequence?: number; 37 | }>; 38 | }; 39 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | //To run it: "node ./example.js" 2 | 3 | const { 4 | compilePolicy, 5 | compileMiniscript, 6 | satisfier 7 | } = require('./dist/index.js'); 8 | 9 | const policy = 'or(and(pk(A),older(8640)),pk(B))'; 10 | 11 | const { 12 | miniscript, 13 | asm: asmFromPolicy, 14 | issane: issaneFromPolicy 15 | } = compilePolicy(policy); 16 | 17 | const { asm: asmFromMiniscript, issane: issaneFromMiniscript } = 18 | compileMiniscript(miniscript); 19 | 20 | const satisfactions = satisfier(miniscript); 21 | 22 | console.assert(asmFromPolicy === asmFromMiniscript, 'ERROR: Asm mismatch.'); 23 | console.assert( 24 | issaneFromPolicy === issaneFromMiniscript, 25 | 'ERROR: issane mismatch.' 26 | ); 27 | 28 | console.log({ 29 | miniscript, 30 | asm: asmFromMiniscript, 31 | issane: issaneFromMiniscript, 32 | satisfactions 33 | }); 34 | 35 | console.log( 36 | compileMiniscript('and_v(v:pk(key),or_b(l:after(100),al:after(200)))') 37 | ); 38 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | HEADERS := miniscript/bitcoin/util/vector.h miniscript/bitcoin/util/strencodings.h miniscript/bitcoin/span.h miniscript/bitcoin/util/spanparsing.h miniscript/bitcoin/script/script.h miniscript/bitcoin/script/miniscript.h miniscript/compiler.h miniscript/bitcoin/crypto/common.h miniscript/bitcoin/serialize.h miniscript/bitcoin/prevector.h miniscript/bitcoin/compat/endian.h miniscript/bitcoin/compat/byteswap.h miniscript/bitcoin/attributes.h miniscript/bitcoin/tinyformat.h miniscript/bitcoin/primitives/transaction.h 2 | SOURCES := miniscript/bitcoin/util/strencodings.cpp miniscript/bitcoin/util/spanparsing.cpp miniscript/bitcoin/script/script.cpp miniscript/bitcoin/script/miniscript.cpp miniscript/compiler.cpp 3 | src/bindings.js: miniscript $(HEADERS) $(SOURCES) miniscript/js_bindings.cpp 4 | em++ -O3 -g0 -Wall -std=c++17 -fno-rtti -flto -Iminiscript/bitcoin $(SOURCES) miniscript/js_bindings.cpp -s WASM=0 -s EXPORT_ES6=0 --memory-init-file 0 -s MODULARIZE=1 -s MALLOC=emmalloc -s WASM_ASYNC_COMPILATION=0 -s FILESYSTEM=0 -s ENVIRONMENT=web -s DISABLE_EXCEPTION_CATCHING=0 -s EXPORTED_FUNCTIONS='["_miniscript_compile","_miniscript_analyze","_malloc","_free"]' -s EXPORTED_RUNTIME_METHODS='["cwrap","UTF8ToString"]' -o src/bindings.js 5 | miniscript: 6 | git clone https://github.com/sipa/miniscript 7 | #484386a50dbda962669cc163f239fe16e101b6f0 is the last commit where this Makefile has been checked to work: 8 | git -C miniscript reset --hard 484386a50dbda962669cc163f239fe16e101b6f0 9 | cp js_bindings.cpp miniscript/ 10 | #See: https://github.com/sipa/miniscript/pull/132 11 | cp compiler.cpp miniscript/ 12 | clean: 13 | rm -rf miniscript src/bindings.js 14 | -------------------------------------------------------------------------------- /test/satisfier.test.js: -------------------------------------------------------------------------------- 1 | import { primitives, timeLocks, other, knowns } from './fixtures.js'; 2 | import { satisfier } from '../dist/index.js'; 3 | 4 | const createGroupTest = (description, fixtures) => 5 | describe(description, () => { 6 | for (const [testName, fixture] of Object.entries(fixtures)) { 7 | const options = 8 | fixture.unknowns || fixture.knowns 9 | ? { unknowns: fixture.unknowns, knowns: fixture.knowns } 10 | : undefined; 11 | if (fixture.throws) { 12 | test(testName, () => { 13 | expect(() => satisfier(fixture.miniscript, options)).toThrow( 14 | fixture.throws 15 | ); 16 | }); 17 | } else { 18 | test(testName, () => { 19 | const result = satisfier(fixture.miniscript, options); 20 | expect(result.nonMalleableSats).toEqual( 21 | expect.arrayContaining(fixture.nonMalleableSats) 22 | ); 23 | expect(result.nonMalleableSats).toHaveLength( 24 | fixture.nonMalleableSats.length 25 | ); 26 | 27 | const malleableSats = fixture.malleableSats; 28 | const unknownSats = fixture.unknownSats || []; 29 | 30 | expect(result.malleableSats).toEqual( 31 | expect.arrayContaining(malleableSats) 32 | ); 33 | expect(result.malleableSats).toHaveLength(malleableSats.length); 34 | expect(result.unknownSats).toEqual( 35 | expect.arrayContaining(unknownSats) 36 | ); 37 | expect(result.unknownSats).toHaveLength(unknownSats.length); 38 | }); 39 | } 40 | } 41 | }); 42 | 43 | createGroupTest('Timelocks', timeLocks); 44 | createGroupTest('Primitives', primitives); 45 | createGroupTest('Other', other); 46 | createGroupTest('Knowns & unknowns combinations', knowns); 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bitcoinerlab/miniscript", 3 | "author": "Jose-Luis Landabaso ", 4 | "license": "MIT", 5 | "keywords": [ 6 | "miniscript", 7 | "satisfier", 8 | "bitcoin", 9 | "policy", 10 | "asm", 11 | "descriptors" 12 | ], 13 | "homepage": "https://bitcoinerlab.com/modules/miniscript", 14 | "version": "1.4.3", 15 | "description": "Bitcoin Miniscript, a high-level language for describing Bitcoin spending conditions. It includes a Policy and Miniscript compiler, as well as a novel Satisfier for generating expressive witness scripts.", 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 | "example": "node ./example.js", 24 | "docs": "typedoc --options ./node_modules/@bitcoinerlab/configs/js_typedoc.json", 25 | "webdocs": "typedoc --options ./node_modules/@bitcoinerlab/configs/js_webtypedoc.json" 26 | }, 27 | "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.", 28 | "babel": { 29 | "env": { 30 | "test": { 31 | "plugins": [ 32 | "@babel/plugin-transform-modules-commonjs", 33 | "babel-plugin-transform-import-meta" 34 | ] 35 | } 36 | } 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "git+https://github.com/bitcoinerlab/miniscript.git" 41 | }, 42 | "devDependencies": { 43 | "@babel/plugin-transform-modules-commonjs": "^7.19.6", 44 | "@bitcoinerlab/configs": "^2.0.0", 45 | "@rollup/plugin-commonjs": "^29.0.0", 46 | "babel-plugin-transform-import-meta": "^2.2.0", 47 | "rollup": "^3.29.5" 48 | }, 49 | "dependencies": { 50 | "bip68": "^1.0.4" 51 | }, 52 | "bugs": { 53 | "url": "https://github.com/bitcoinerlab/miniscript/issues" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/maxLock.test.js: -------------------------------------------------------------------------------- 1 | import bip68 from 'bip68'; 2 | import { maxLock, ABSOLUTE, RELATIVE } from '../src/satisfier/maxLock.js'; 3 | describe('maxLock', () => { 4 | test('throws an error if lockType is undefined', () => { 5 | expect(() => maxLock(1, 2)).toThrow('lockType must be specified'); 6 | }); 7 | 8 | test('should throw an error if lockType is not ABSOLUTE or RELATIVE', () => { 9 | expect(() => { 10 | maxLock(1, 2, 'INVALID'); 11 | }).toThrow('lockType must be either "ABSOLUTE" or "RELATIVE"'); 12 | }); 13 | 14 | test('should throw an error if a and b are not integers', () => { 15 | expect(() => { 16 | maxLock('a', 2, ABSOLUTE); 17 | }).toThrow('nSequence/nLockTime must be an integer: a'); 18 | expect(() => { 19 | maxLock(1.1, 1, ABSOLUTE); 20 | }).toThrow('nSequence/nLockTime must be an integer: 1.1'); 21 | }); 22 | 23 | test('returns undefined if a and b are undefined', () => { 24 | expect(maxLock(undefined, undefined, ABSOLUTE)).toBe(undefined); 25 | expect(maxLock(undefined, undefined, RELATIVE)).toBe(undefined); 26 | }); 27 | 28 | test('should throw an error if nLockTime values are not both below 500000000 or both above or equal 500000000', () => { 29 | expect(() => { 30 | maxLock(1, 500000000, ABSOLUTE); 31 | }).toThrow( 32 | 'nLockTime values must be either below 500000000 or both above or equal 500000000' 33 | ); 34 | expect(() => { 35 | maxLock(500000000, 1, ABSOLUTE); 36 | }).toThrow( 37 | 'nLockTime values must be either below 500000000 or both above or equal 500000000' 38 | ); 39 | }); 40 | 41 | test('should throw an error if a and b are not representing the same unit (seconds or blocks)', () => { 42 | expect(() => { 43 | maxLock( 44 | bip68.encode({ seconds: 1 * 512 }), 45 | bip68.encode({ blocks: 2 }), 46 | RELATIVE 47 | ); 48 | }).toThrow('a and b must both be either represent seconds or block height'); 49 | }); 50 | 51 | test('should return the maximum value of a and b if both are specified', () => { 52 | expect(maxLock(1, 2, ABSOLUTE)).toBe(2); 53 | expect(maxLock(2, 1, ABSOLUTE)).toBe(2); 54 | expect( 55 | maxLock( 56 | bip68.encode({ seconds: 1 * 512 }), 57 | bip68.encode({ seconds: 2 * 512 }), 58 | RELATIVE 59 | ) 60 | ).toBe(bip68.encode({ seconds: 2 * 512 })); 61 | expect( 62 | maxLock( 63 | bip68.encode({ seconds: 2 * 512 }), 64 | bip68.encode({ seconds: 1 * 512 }), 65 | RELATIVE 66 | ) 67 | ).toBe(bip68.encode({ seconds: 2 * 512 })); 68 | }); 69 | 70 | test('should return the value of a if b is not specified', () => { 71 | expect(maxLock(1, undefined, ABSOLUTE)).toBe(1); 72 | expect(maxLock(undefined, 1, ABSOLUTE)).toBe(1); 73 | expect( 74 | maxLock(bip68.encode({ seconds: 1 * 512 }), undefined, RELATIVE) 75 | ).toBe(bip68.encode({ seconds: 1 * 512 })); 76 | expect( 77 | maxLock(undefined, bip68.encode({ seconds: 1 * 512 }), RELATIVE) 78 | ).toBe(bip68.encode({ seconds: 1 * 512 })); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /src/satisfier/maxLock.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Jose-Luis Landabaso - https://bitcoinerlab.com 2 | // Distributed under the MIT software license 3 | 4 | /** @module maxLock */ 5 | import bip68 from 'bip68'; 6 | 7 | export const ABSOLUTE = 'ABSOLUTE'; 8 | export const RELATIVE = 'RELATIVE'; 9 | 10 | /** 11 | * Calculates the maximum lock time value between two lock time values (a and b) 12 | * using the specified lock type (ABSOLUTE or RELATIVE). 13 | * It asserts that there are no timeLock mixings. 14 | * It asserts that a or b are correctly encoded in bip68 format if they are 15 | * RELATIVE. 16 | * Either both a and b are block based or time based. 17 | * https://medium.com/blockstream/dont-mix-your-timelocks-d9939b665094 18 | * 19 | * If only a or b is undefined it asserts the value and returns it. 20 | * If none are defined it returns undefined. 21 | * @param {string|number} [a] - The first lock time value to compare. 22 | * @param {string|number} [b] - The second lock time value to compare. 23 | * @param {string} lockType - The type of lock time to use. Can be either "ABSOLUTE" or "RELATIVE". 24 | * @return {number} - The maximum lock time value between a and b. 25 | */ 26 | 27 | export function maxLock(a, b, lockType) { 28 | if (typeof a === 'undefined' && typeof b === 'undefined') { 29 | return undefined; 30 | } 31 | if (typeof lockType === 'undefined') 32 | throw new Error('lockType must be specified'); 33 | // Check that lockType is either "ABSOLUTE" or "RELATIVE" 34 | if (lockType !== ABSOLUTE && lockType !== RELATIVE) 35 | throw new Error('lockType must be either "ABSOLUTE" or "RELATIVE"'); 36 | 37 | function isInteger(number) { 38 | const isNumeric = !isNaN(number) && !isNaN(parseFloat(number)); 39 | if (isNumeric && Number.isInteger(Number(number))) return true; 40 | else return false; 41 | } 42 | if (typeof a !== 'undefined') { 43 | if (isInteger(a) === false) 44 | throw new Error('nSequence/nLockTime must be an integer: ' + a); 45 | a = Number(a); 46 | if ( 47 | lockType === RELATIVE && 48 | !bip68.decode(a).hasOwnProperty('seconds') && 49 | !bip68.decode(a).hasOwnProperty('blocks') 50 | ) 51 | throw new Error('Invalid bip68 encoded a value: ' + a); 52 | } 53 | if (typeof b !== 'undefined') { 54 | if (isInteger(b) === false) 55 | throw new Error('nSequence/nLockTime must be an integer: ' + b); 56 | b = Number(b); 57 | if ( 58 | lockType === RELATIVE && 59 | !bip68.decode(b).hasOwnProperty('seconds') && 60 | !bip68.decode(b).hasOwnProperty('blocks') 61 | ) 62 | throw new Error('Invalid bip68 encoded b value: ' + b); 63 | } 64 | 65 | if (typeof a !== 'undefined' && typeof b !== 'undefined') { 66 | if (lockType === ABSOLUTE) { 67 | // Both a and b must be either below 500000000 or both above or equal 500000000 68 | if ( 69 | (a < 500000000 && b >= 500000000) || 70 | (a >= 500000000 && b < 500000000) 71 | ) { 72 | throw new Error( 73 | 'nLockTime values must be either below 500000000 or both above or equal 500000000' 74 | ); 75 | } 76 | } else { 77 | const decodedA = bip68.decode(a); 78 | const decodedB = bip68.decode(b); 79 | 80 | if ( 81 | decodedA.hasOwnProperty('seconds') !== 82 | decodedB.hasOwnProperty('seconds') 83 | ) { 84 | throw new Error( 85 | 'a and b must both be either represent seconds or block height' 86 | ); 87 | } 88 | } 89 | return Math.max(a, b); 90 | } 91 | 92 | if (typeof a !== 'undefined') return a; 93 | if (typeof b !== 'undefined') return b; 94 | return undefined; 95 | } 96 | -------------------------------------------------------------------------------- /src/miniscript.js: -------------------------------------------------------------------------------- 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 | const Module = bindings(); 7 | 8 | const em_miniscript_compile = Module.cwrap('miniscript_compile', 'none', [ 9 | 'string', 10 | 'number', 11 | 'number', 12 | 'number', 13 | 'number', 14 | 'number', 15 | 'number' 16 | ]); 17 | const em_miniscript_analyze = Module.cwrap('miniscript_analyze', 'none', [ 18 | 'string', 19 | 'number', 20 | 'number', 21 | 'number', 22 | 'number' 23 | ]); 24 | 25 | const cleanAsm = asm => 26 | asm 27 | .trim() 28 | .replace(/\n/g, ' ') 29 | .replace(/ +(?= )/g, ''); 30 | 31 | /** 32 | * @typedef {Object} CompilePolicyResult 33 | * @property {string} miniscript - The compiled miniscript expression. 34 | * @property {string} asm - The compiled miniscript as Bitcoin asm code. 35 | * @property {boolean} issane - Whether the miniscript is sane at the top level. 36 | * @property {boolean} issanesublevel - Whether the miniscript is sane at the sublevel. 37 | */ 38 | 39 | /** 40 | * @typedef {Object} CompileMiniscriptResult 41 | * @property {string} asm - The Bitcoin asm code of the compiled miniscript expression. 42 | * @property {boolean} issane - Whether the miniscript is sane at the top level. 43 | * @property {boolean} issanesublevel - Whether the miniscript is sane at the sublevel. 44 | */ 45 | 46 | 47 | /** 48 | * Compiles a miniscript policy into a miniscript expression (if possible). 49 | * @function 50 | * 51 | * @param {string} policy - The miniscript policy to compile. 52 | * @returns {CompilePolicyResult} 53 | */ 54 | export const compilePolicy = policy => { 55 | const miniscript = Module._malloc(10000); 56 | const cost = Module._malloc(500); 57 | const asm = Module._malloc(100000); 58 | const issane = Module._malloc(10); 59 | const issanesublevel = Module._malloc(10); 60 | em_miniscript_compile( 61 | policy, 62 | miniscript, 63 | 10000, 64 | cost, 65 | 500, 66 | asm, 67 | 100000, 68 | issane, 69 | 10, 70 | issanesublevel, 71 | 10 72 | ); 73 | const result = { 74 | miniscript: Module.UTF8ToString(miniscript), 75 | asm: cleanAsm(Module.UTF8ToString(asm)), 76 | issane: Module.UTF8ToString(issane) === 'true' ? true : false, 77 | issanesublevel: 78 | Module.UTF8ToString(issanesublevel) === 'true' ? true : false 79 | }; 80 | Module._free(miniscript); 81 | Module._free(cost); 82 | Module._free(asm); 83 | Module._free(issane); 84 | Module._free(issanesublevel); 85 | 86 | return result; 87 | }; 88 | 89 | /** 90 | * Compiles a miniscript expression and returns its asm code. 91 | * @function 92 | * 93 | * @param {string} miniscript - A miniscript expression. 94 | * @returns {CompileMiniscriptResult} 95 | */ 96 | export const compileMiniscript = miniscript => { 97 | const analysis = Module._malloc(50000); 98 | const asm = Module._malloc(100000); 99 | const issane = Module._malloc(10); 100 | const issanesublevel = Module._malloc(10); 101 | em_miniscript_analyze( 102 | miniscript, 103 | analysis, 104 | 50000, 105 | asm, 106 | 100000, 107 | issane, 108 | 10, 109 | issanesublevel, 110 | 10 111 | ); 112 | const result_asm = Module.UTF8ToString(asm); 113 | const result_issane = Module.UTF8ToString(issane); 114 | const result_issanesublebel = Module.UTF8ToString(issanesublevel); 115 | Module._free(analysis); 116 | Module._free(asm); 117 | Module._free(issane); 118 | Module._free(issanesublevel); 119 | 120 | return { 121 | asm: cleanAsm(result_asm), 122 | issane: result_issane === 'true' ? true : false, 123 | issanesublevel: result_issanesublebel === 'true' ? true : false 124 | }; 125 | }; 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bitcoin Miniscript 2 | 3 | This project is a JavaScript implementation of [Bitcoin Miniscript](https://bitcoin.sipa.be/miniscript/), a high-level language for describing Bitcoin spending conditions. 4 | 5 | It includes a novel Miniscript Satisfier for generating explicit script witnesses that are decoupled from the tx signer, as well as a transpilation of [Peter Wuille's C++ code](https://github.com/sipa/miniscript) for compiling spending policies into Miniscript and Bitcoin scripts. 6 | 7 | ## Features 8 | 9 | - Compile Policies into Miniscript and Bitcoin scripts. 10 | - A Miniscript Satisfier that discards malleable solutions. 11 | - The Miniscript Satisfier is able to generate explicit script witnesses from Miniscript expressions using variables, such as `pk(key)`. 12 | 13 | For example, Miniscript `and_v(v:pk(key),after(10))` can be satisfied with `[{ asm: '', nLockTime: 10 }]`. 14 | 15 | - The ability to generate different satisfactions depending on the presence of `unknowns` (or complimentary `knowns`). 16 | 17 | For example, Miniscript `c:and_v(or_c(pk(key1),v:ripemd160(H)),pk_k(key2))` can be satisfied with: `[{ asm: ' 0' }]`. 18 | 19 | However, if `unknowns: ['']` is set, then the Miniscript can be satisfied with: `[{ asm: ' ' }]` because this solution can no longer be considered malleable, given then assumption that an attacker does not have access to the preimage. 20 | 21 | - Thoroughly tested. 22 | 23 | ## Installation 24 | 25 | To install the package, use npm: 26 | 27 | ``` 28 | npm install @bitcoinerlab/miniscript 29 | ``` 30 | 31 | ## Usage 32 | 33 | You can test the examples in this section using the online playground demo available at https://bitcoinerlab.com/modules/miniscript. 34 | 35 | ### Compiling Policies into Miniscript and Bitcoin script 36 | 37 | To compile a Policy into a Miniscript and Bitcoin ASM, you can use the `compilePolicy` function: 38 | 39 | ```javascript 40 | const { compilePolicy } = require('@bitcoinerlab/miniscript'); 41 | 42 | const policy = 'or(and(pk(A),older(8640)),pk(B))'; 43 | 44 | const { miniscript, asm, issane } = compilePolicy(policy); 45 | ``` 46 | 47 | `issane` is a boolean that indicates whether the Miniscript is valid and follows the consensus and standardness rules for Bitcoin scripts. A sane Miniscript should have non-malleable solutions, not mix different timelock units on a single branch of the script, and not contain duplicate keys. In other words, it should be a well-formed and standards-compliant script that can be safely used in transactions. 48 | 49 | ### Compiling Miniscript into Bitcoin script 50 | 51 | To compile a Miniscript into Bitcoin ASM you can use the `compileMiniscript` function: 52 | 53 | ```javascript 54 | const { compileMiniscript } = require('@bitcoinerlab/miniscript'); 55 | 56 | const miniscript = 'and_v(v:pk(key),or_b(l:after(100),al:after(200)))'; 57 | 58 | const { asm, issane } = compileMiniscript(miniscript); 59 | ``` 60 | 61 | ### Generating explicit script witnesses 62 | 63 | To generate explicit script witnesses from a Miniscript, you can use the `satisfier` function: 64 | 65 | ```javascript 66 | const { satisfier } = require('@bitcoinerlab/miniscript'); 67 | 68 | const miniscript = 69 | 'c:or_i(andor(c:pk_h(key1),pk_h(key2),pk_h(key3)),pk_k(key4))'; 70 | 71 | const { nonMalleableSats, malleableSats } = satisfier(miniscript); 72 | ``` 73 | 74 | `satisfier` makes sure that output `satisfactions` are non-malleable and that the `miniscript` is sane by itself and it returns an object with keys: 75 | 76 | - `nonMalleableSats`: an array of objects representing good, non-malleable witnesses. 77 | - `malleableSats`: an array of objects representing malleable witnesses that should not be used. 78 | 79 | In the example above `nonMalleableSats` is: 80 | 81 | ```javascript 82 | nonMalleableSats: [ 83 | {asm: " 0"} 84 | {asm: " 0 1"} 85 | {asm: " 1"} 86 | ] 87 | ``` 88 | 89 | Where satisfactions are ordered in ascending Weight Unit size. 90 | 91 | In addition, `unknowns` can be set with the pieces of information the user does not have, f.ex., `` or ``: 92 | 93 | ```javascript 94 | const { satisfier } = require('@bitcoinerlab/miniscript'); 95 | 96 | const miniscript = 97 | 'c:or_i(andor(c:pk_h(key1),pk_h(key2),pk_h(key3)),pk_k(key4))'; 98 | const unknowns = ['', '']; 99 | 100 | const { nonMalleableSats, malleableSats, unknownSats } = satisfier( 101 | miniscript, 102 | { unknowns } 103 | ); 104 | ``` 105 | 106 | When passing `unknowns`, `satisfier` returns an additional object: `{ unknownSats }` with an array of objects representing satisfactions that containt some of the `unknown` pieces of information: 107 | 108 | ```javascript 109 | nonMalleableSats: [ 110 | {asm: " 0"} 111 | {asm: " 0 1"} 112 | ] 113 | unknownSats: [ {asm: " 1"} ] 114 | ``` 115 | 116 | Instead of `unknowns`, the user has the option to provide the complementary argument `knowns`: `satisfier( miniscript, { knowns })`. This argument corresponds to the only pieces of information that are known. For instance, in the example above, `knowns` would be `['', '']`. It's important to note that either `knowns` or `unknowns` must be provided, but not both. If neither argument is provided, it's assumed that all signatures and preimages are known. 117 | 118 | The objects returned in the `nonMalleableSats`, `malleableSats` and `unknownSats` arrays consist of the following properties: 119 | 120 | - `asm`: a string with the script witness. 121 | - `nSequence`: an integer representing the nSequence value, if needed. 122 | - `nLockTime`: an integer representing the nLockTime value, if needed. 123 | 124 | ## Authors and Contributors 125 | 126 | The project was initially developed and is currently maintained by [Jose-Luis Landabaso](https://github.com/landabaso). Contributions and help from other developers are welcome. 127 | 128 | Here are some resources to help you get started with contributing: 129 | 130 | ### Building from source 131 | 132 | To download the source code and build the project, follow these steps: 133 | 134 | 1. Clone the repository: 135 | 136 | ``` 137 | git clone https://github.com/bitcoinerlab/miniscript.git 138 | ``` 139 | 140 | 2. Install the dependencies: 141 | 142 | ``` 143 | npm install 144 | ``` 145 | 146 | 3. Make sure you have the [`em++` compiler](https://emscripten.org/) in your PATH. 147 | 148 | 4. Run the Makefile: 149 | 150 | ``` 151 | make 152 | ``` 153 | 154 | This will download and build Wuille's sources and generate the necessary Javascript files for the compilers. 155 | 156 | 5. Build the project: 157 | 158 | ``` 159 | npm run build 160 | ``` 161 | 162 | This will build the project and generate the necessary files in the `dist` directory. 163 | 164 | ### Documentation 165 | 166 | To generate the programmers's documentation, which describes the library's programming interface, use the following command: 167 | 168 | ``` 169 | npm run docs 170 | ``` 171 | 172 | This will generate the documentation in the `docs` directory. 173 | 174 | ### Testing 175 | 176 | Before committing any code, make sure it passes all tests by running: 177 | 178 | ``` 179 | npm run tests 180 | ``` 181 | 182 | ## License 183 | 184 | This project is licensed under the MIT License. 185 | -------------------------------------------------------------------------------- /js_bindings.cpp: -------------------------------------------------------------------------------- 1 | // Initial author: Pieter Wuille 2 | // Adapted by Jose-Luis Landabaso so that miniscript_compile and miniscript_analyze 3 | // return issane & issanesublevel 4 | #include 5 | 6 | #include