Skip to content

Commit 60f5c6d

Browse files
committed
Add new parameter knowns for satisfier, which is the complimentary to unknowns. API change
1 parent 421e618 commit 60f5c6d

File tree

6 files changed

+120
-16
lines changed

6 files changed

+120
-16
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ It includes a novel Miniscript Satisfier for generating explicit script witnesse
1212

1313
For example, Miniscript `and_v(v:pk(key),after(10))` can be satisfied with `[{ asm: '<sig(key)>', nLockTime: 10 }]`.
1414

15-
- The ability to generate different satisfactions depending on the presence of `unknowns`.
15+
- The ability to generate different satisfactions depending on the presence of `unknowns` (or complimentary `knowns`).
1616

1717
For example, Miniscript `c:and_v(or_c(pk(key1),v:ripemd160(H)),pk_k(key2))` can be satisfied with: `[{ asm: '<sig(key2)> <ripemd160_preimage(H)> 0' }]`.
1818

@@ -99,7 +99,7 @@ const unknowns = ['<sig(key1)>', '<sig(key2)>'];
9999

100100
const { nonMalleableSats, malleableSats, unknownSats } = satisfier(
101101
miniscript,
102-
unknowns
102+
{ unknowns }
103103
);
104104
```
105105

@@ -113,6 +113,8 @@ nonMalleableSats: [
113113
unknownSats: [ {asm: "<sig(key2)> <key2> <sig(key1)> <key1> 1"} ]
114114
```
115115

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 `['<sig(key3)>', '<sig(key4)>']`. 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+
116118
The objects returned in the `nonMalleableSats`, `malleableSats` and `unknownSats` arrays consist of the following properties:
117119

118120
- `asm`: a string with the script witness.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"descriptors"
1212
],
1313
"homepage": "https://bitcoinerlab.com/modules/miniscript",
14-
"version": "1.1.1",
14+
"version": "1.2.0",
1515
"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.",
1616
"main": "dist/index.js",
1717
"types": "types/index.d.ts",

src/satisfier/index.js

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,13 @@ const evaluate = miniscript => {
292292
* solutions. If the miniscript is sane, then unknowns can be set to produce
293293
* more possible solutions, including preimages, as described above.
294294
*
295+
* @param {string[]} knowns - An array with the only pieces of information
296+
* that can be used to build satisfactions. This is the complimentary to
297+
* unknowns. Only `knowns` or `unknowns` must be passed.
298+
*
299+
* If neither knowns and unknowns is passed then it is assumed that there are
300+
* no unknowns, in other words, that all pieces of information are known.
301+
*
295302
* @returns {Object} an object with three keys:
296303
* - `nonMalleableSats`: an array of {@link module:satisfier.Solution} objects
297304
* representing the non-malleable sat() expressions.
@@ -301,13 +308,24 @@ const evaluate = miniscript => {
301308
* representing the sat() expressions that contain some of the `unknown` pieces of information.
302309
* @see {@link module:satisfier.Solution}
303310
*/
304-
export const satisfier = (miniscript, unknowns = []) => {
311+
export const satisfier = (miniscript, { unknowns, knowns } = {}) => {
305312
const { issane, issanesublevel } = compileMiniscript(miniscript);
306313

307314
if (!issane) {
308315
throw new Error(`Miniscript ${miniscript} is not sane.`);
309316
}
310317

318+
if (typeof unknowns === 'undefined' && typeof knowns === 'undefined') {
319+
unknowns = [];
320+
} else if (typeof unknowns !== 'undefined' && typeof knowns !== 'undefined') {
321+
throw new Error(`Cannot pass both knowns and unknowns`);
322+
} else if (
323+
(knowns && !Array.isArray(knowns)) ||
324+
(unknowns && !Array.isArray(unknowns))
325+
) {
326+
throw new Error(`Incorrect types for unknowns / knowns`);
327+
}
328+
311329
const knownSats = [];
312330
const unknownSats = [];
313331
const sats = evaluate(miniscript).sats || [];
@@ -316,10 +334,29 @@ export const satisfier = (miniscript, unknowns = []) => {
316334
if (typeof sat.nLockTime === 'undefined') delete sat.nLockTime;
317335
//Clean format: 1 consecutive spaces at most, no leading & trailing spaces
318336
sat.asm = sat.asm.replace(/ +/g, ' ').trim();
319-
if (unknowns.some(unknown => sat.asm.includes(unknown))) {
320-
unknownSats.push(sat);
337+
338+
if (unknowns) {
339+
if (unknowns.some(unknown => sat.asm.includes(unknown))) {
340+
unknownSats.push(sat);
341+
} else {
342+
knownSats.push(sat);
343+
}
321344
} else {
322-
knownSats.push(sat);
345+
const delKnowns = knowns.reduce(
346+
(acc, known) => acc.replace(known, ''),
347+
sat.asm
348+
);
349+
if (
350+
delKnowns.match(
351+
/<sig\(|<sha256_preimage\(|<hash256_preimage\(|<ripemd160_preimage\(|<hash160_preimage\(/
352+
)
353+
) {
354+
//Even thought all known pieces of information are removed, there are
355+
//still other pieces of info needed. Thus, this sat is unkown.
356+
unknownSats.push(sat);
357+
} else {
358+
knownSats.push(sat);
359+
}
323360
}
324361
});
325362

test/fixtures.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,3 +838,61 @@ export const other = {
838838
malleableSats: []
839839
}
840840
};
841+
export const knowns = {
842+
'with unknowns - and_v(v:pk(k),sha256(H))': {
843+
miniscript: 'and_v(v:pk(k),sha256(H))',
844+
script:
845+
'<k> OP_CHECKSIGVERIFY OP_SIZE <20> OP_EQUALVERIFY OP_SHA256 <H> OP_EQUAL',
846+
unknowns: ['<sha256_preimage(H)>'],
847+
unknownSats: [{ asm: '<sha256_preimage(H)> <sig(k)>' }],
848+
//If the preimage is unknown we cannot compute any satisfaction
849+
nonMalleableSats: [],
850+
malleableSats: []
851+
},
852+
'with knowns - and_v(v:pk(k),sha256(H))': {
853+
miniscript: 'and_v(v:pk(k),sha256(H))',
854+
script:
855+
'<k> OP_CHECKSIGVERIFY OP_SIZE <20> OP_EQUALVERIFY OP_SHA256 <H> OP_EQUAL',
856+
knowns: ['<sig(k)>'],
857+
unknownSats: [{ asm: '<sha256_preimage(H)> <sig(k)>' }],
858+
//If the preimage is unknown we cannot compute any satisfaction
859+
nonMalleableSats: [],
860+
malleableSats: []
861+
},
862+
'with all knowns - and_v(v:pk(k),sha256(H))': {
863+
miniscript: 'and_v(v:pk(k),sha256(H))',
864+
script:
865+
'<k> OP_CHECKSIGVERIFY OP_SIZE <20> OP_EQUALVERIFY OP_SHA256 <H> OP_EQUAL',
866+
knowns: ['<sig(k)>', '<sha256_preimage(H)>'],
867+
unknownSats: [],
868+
//If the preimage is unknown we cannot compute any satisfaction
869+
nonMalleableSats: [{ asm: '<sha256_preimage(H)> <sig(k)>' }],
870+
malleableSats: []
871+
},
872+
'throws with both knowns and unknowns - and_v(v:pk(k),sha256(H))': {
873+
miniscript: 'and_v(v:pk(k),sha256(H))',
874+
script:
875+
'<k> OP_CHECKSIGVERIFY OP_SIZE <20> OP_EQUALVERIFY OP_SHA256 <H> OP_EQUAL',
876+
knowns: ['<sig(k)>'],
877+
unknowns: ['<sha256_preimage(H)>'],
878+
throws: 'Cannot pass both knowns and unknowns'
879+
},
880+
'with unknows set - c:and_v(or_c(pk(key1),v:ripemd160(H)),pk_k(key2))': {
881+
miniscript: 'c:and_v(or_c(pk(key1),v:ripemd160(H)),pk_k(key2))',
882+
script:
883+
'<key1> OP_CHECKSIG OP_NOTIF OP_SIZE <20> OP_EQUALVERIFY OP_RIPEMD160 <H> OP_EQUALVERIFY OP_ENDIF <key2> OP_CHECKSIG',
884+
unknowns: ['<ripemd160_preimage(H)>'],
885+
unknownSats: [{ asm: '<sig(key2)> <ripemd160_preimage(H)> 0' }],
886+
nonMalleableSats: [{ asm: '<sig(key2)> <sig(key1)>' }],
887+
malleableSats: []
888+
},
889+
'with knows set - c:and_v(or_c(pk(key1),v:ripemd160(H)),pk_k(key2))': {
890+
miniscript: 'c:and_v(or_c(pk(key1),v:ripemd160(H)),pk_k(key2))',
891+
script:
892+
'<key1> OP_CHECKSIG OP_NOTIF OP_SIZE <20> OP_EQUALVERIFY OP_RIPEMD160 <H> OP_EQUALVERIFY OP_ENDIF <key2> OP_CHECKSIG',
893+
knowns: ['<sig(key1)>', '<sig(key2)>'],
894+
unknownSats: [{ asm: '<sig(key2)> <ripemd160_preimage(H)> 0' }],
895+
nonMalleableSats: [{ asm: '<sig(key2)> <sig(key1)>' }],
896+
malleableSats: []
897+
}
898+
};

test/satisfier.test.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
1-
import { primitives, timeLocks, other } from './fixtures.js';
1+
import { primitives, timeLocks, other, knowns } from './fixtures.js';
22
import { satisfier } from '../src/satisfier/index.js';
33

44
const createGroupTest = (description, fixtures) =>
55
describe(description, () => {
66
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;
711
if (fixture.throws) {
812
test(testName, () => {
9-
expect(() => satisfier(fixture.miniscript, fixture.unknowns)).toThrow(
13+
expect(() => satisfier(fixture.miniscript, options)).toThrow(
1014
fixture.throws
1115
);
1216
});
1317
} else {
1418
test(testName, () => {
15-
const result = satisfier(fixture.miniscript, fixture.unknowns);
19+
const result = satisfier(fixture.miniscript, options);
1620
expect(result.nonMalleableSats).toEqual(
1721
expect.arrayContaining(fixture.nonMalleableSats)
1822
);
@@ -21,10 +25,7 @@ const createGroupTest = (description, fixtures) =>
2125
);
2226

2327
const malleableSats = fixture.malleableSats;
24-
const unknownSats =
25-
fixture.unknowns && fixture.unknowns.length
26-
? fixture.unknownSats
27-
: [];
28+
const unknownSats = fixture.unknownSats || [];
2829

2930
expect(result.malleableSats).toEqual(
3031
expect.arrayContaining(malleableSats)
@@ -39,6 +40,7 @@ const createGroupTest = (description, fixtures) =>
3940
}
4041
});
4142

42-
createGroupTest('Primitives', primitives);
4343
createGroupTest('Timelocks', timeLocks);
44+
createGroupTest('Primitives', primitives);
4445
createGroupTest('Other', other);
46+
createGroupTest('Knowns & unknowns combinations', knowns);

types/index.d.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ export declare const compilePolicy: (miniscript: string) => {
1313

1414
export declare const satisfier: (
1515
miniscript: string,
16-
unknowns?: string[]
16+
options:
17+
| {
18+
unknowns?: string[] | undefined;
19+
knowns?: string[] | undefined;
20+
}
21+
| undefined
1722
) => {
1823
unknownSats?: Array<{
1924
asm: string;

0 commit comments

Comments
 (0)