Skip to content

Commit

Permalink
Implement Resource Limits checks. See #6
Browse files Browse the repository at this point in the history
  • Loading branch information
landabaso committed Jan 23, 2023
1 parent 9345ac7 commit cec68f1
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 161 deletions.
2 changes: 1 addition & 1 deletion src/checksum.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Converted to Javascript by Jose-Luis Landabaso, 2023
// Converted to Javascript by Jose-Luis Landabaso, 2023 - https://bitcoinerlab.com
// Source: https://github.com/bitcoin/bitcoin/blob/master/src/script/descriptor.cpp
// Distributed under the MIT software license
const PolyMod = (c, val) => {
Expand Down
64 changes: 60 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
// Copyright (c) 2023 Jose-Luis Landabaso
// Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
// Distributed under the MIT software license

import { compileMiniscript } from '@bitcoinerlab/miniscript';
import { address, networks, payments, script, crypto } from 'bitcoinjs-lib';
import {
address,
networks,
payments,
script as bscript,
crypto
} from 'bitcoinjs-lib';
const { p2sh, p2wpkh, p2pkh, p2pk, p2wsh } = payments;

import BIP32Factory from 'bip32';
Expand All @@ -12,6 +18,12 @@ import { DescriptorChecksum, CHECKSUM_CHARSET } from './checksum';

import { numberEncodeAsm } from './numberEncodeAsm';

//See "Resource limitations" https://bitcoin.sipa.be/miniscript/
//https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2019-September/017306.html
const MAX_SCRIPT_ELEMENT_SIZE = 520;
const MAX_STANDARD_P2WSH_SCRIPT_SIZE = 3600;
const MAX_OPS_PER_SCRIPT = 201;

//Regular expressions cheat sheet:
//https://www.keycdn.com/support/regex-cheat-sheet

Expand Down Expand Up @@ -207,6 +219,11 @@ export function DescriptorsFactory(ecc) {
}
}

function countNonPushOnlyOPs(script) {
return bscript.decompile(script).filter(op => op > bscript.OPS.OP_16)
.length;
}

function miniscript2Script({
miniscript,
isSegwit = true,
Expand All @@ -225,14 +242,20 @@ export function DescriptorsFactory(ecc) {
}).toString('hex');
return key;
});
const pubKeys = Object.values(keyMap);
if (new Set(pubKeys).size !== pubKeys.length) {
throw new Error(
`Error: miniscript ${miniscript} is not sane: contains duplicate public keys.`
);
}
const compiled = compileMiniscript(bareM);
if (compiled.issane !== true) {
throw new Error(`Error: Miniscript ${bareM} is not sane`);
}
//Replace back variables into the pubKeys previously computed.
const asm = Object.keys(keyMap).reduce((accAsm, key) => {
return accAsm
.replaceAll(`<${key}>`, '<' + keyMap[key] + '>')
.replaceAll(`<${key}>`, `<${keyMap[key]}>`)
.replaceAll(
`<HASH160\(${key}\)>`,
`<${crypto.hash160(Buffer.from(keyMap[key], 'hex')).toString('hex')}>`
Expand All @@ -254,7 +277,7 @@ export function DescriptorsFactory(ecc) {
//we don't have numbers anymore, now it's safe to remove < and > since we
//know that every remaining is either an op_code or a hex encoded number
.replace(/[<>]/g, '');
return script.fromASM(parsedAsm);
return bscript.fromASM(parsedAsm);
}

/**
Expand Down Expand Up @@ -356,6 +379,17 @@ export function DescriptorsFactory(ecc) {
else if (isolatedDesc.match(reShWshMiniscriptAnchored)) {
const miniscript = isolatedDesc.match(reShWshMiniscriptAnchored)[1]; //[1]-> whatever is found sh(wsh(->HERE<-))
const script = miniscript2Script({ miniscript, network });
if (script.byteLength > MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
throw new Error(
`Error: script is too large, ${ret.output.byteLength} bytes is larger than ${MAX_STANDARD_P2WSH_SCRIPT_SIZE} bytes`
);
}
const nonPushOnlyOps = countNonPushOnlyOPs(script);
if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
throw new Error(
`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`
);
}
return p2sh({
redeem: p2wsh({ redeem: { output: script, network }, network }),
network
Expand All @@ -369,12 +403,34 @@ export function DescriptorsFactory(ecc) {
isSegwit: false,
network
});
if (script.byteLength > MAX_SCRIPT_ELEMENT_SIZE) {
throw new Error(
`Error: P2SH script is too large, ${script.byteLength} bytes is larger than ${MAX_SCRIPT_ELEMENT_SIZE} bytes`
);
}
const nonPushOnlyOps = countNonPushOnlyOPs(script);
if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
throw new Error(
`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`
);
}
return p2sh({ redeem: { output: script, network }, network });
}
//wsh(miniscript)
else if (isolatedDesc.match(reWshMiniscriptAnchored)) {
const miniscript = isolatedDesc.match(reWshMiniscriptAnchored)[1]; //[1]-> whatever is found wsh(->HERE<-)
const script = miniscript2Script({ miniscript, network });
if (script.byteLength > MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
throw new Error(
`Error: script is too large, ${ret.output.byteLength} bytes is larger than ${MAX_STANDARD_P2WSH_SCRIPT_SIZE} bytes`
);
}
const nonPushOnlyOps = countNonPushOnlyOPs(script);
if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
throw new Error(
`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`
);
}
return p2wsh({ redeem: { output: script, network }, network });
} else {
throw new Error(`Error: Could not parse descriptor ${desc}`);
Expand Down
2 changes: 1 addition & 1 deletion src/numberEncodeAsm.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2023 Jose-Luis Landabaso
// Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
// Distributed under the MIT software license

import { script } from 'bitcoinjs-lib';
Expand Down
7 changes: 4 additions & 3 deletions test/createFixtures.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2023 Jose-Luis Landabaso
// Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
// Distributed under the MIT software license

// Generate fixtures from Bitcoin Core tests.
Expand Down Expand Up @@ -64,6 +64,7 @@ const isSupported = parsed => {
let supported = true;
parsed.descs.forEach(desc => {
if (desc.match(/tr\(/)) supported = false;
if (desc.match(/^multi\(/)) supported = false; //Top-level multi not supported; It must be within sh or wsh
if (desc.match(/combo\(/)) supported = false;
if (desc.match(/sortedmulti\(/)) supported = false;
if (desc.match(/raw\(/)) supported = false;
Expand Down Expand Up @@ -117,7 +118,7 @@ fs.readFile(filePath, 'utf8', (err, data) => {
}
const fixture = {};
fixture.desc = desc;
fixture.outputScript = script;
fixture.script = script;
if (desc.indexOf('*') !== -1) {
fixture.index = index;
}
Expand All @@ -142,7 +143,7 @@ fs.readFile(filePath, 'utf8', (err, data) => {
}
}
const fixtures = { valid, invalid };
const contents = `// Copyright (c) 2023 Jose-Luis Landabaso
const contents = `// Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
// Distributed under the MIT software license
//This is generated automatically. Do not edit this file!
Expand Down
8 changes: 4 additions & 4 deletions test/descriptors.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2023 Jose-Luis Landabaso
// Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
// Distributed under the MIT software license

import { DescriptorsFactory } from '../src/index';
Expand All @@ -14,10 +14,10 @@ for (const fixtures of [customFixtures, bitcoinCoreFixtures]) {
for (const fixture of fixtures.valid) {
test(`Parse valid ${fixture.desc}`, () => {
const parsed = descriptors.parse(fixture);
if (!fixture.outputScript && !fixture.address)
if (!fixture.script && !fixture.address)
throw new Error(`Error: pass a valid test for ${fixture.desc}`);
if (fixture.outputScript) {
expect(parsed.output.toString('hex')).toEqual(fixture.outputScript);
if (fixture.script) {
expect(parsed.output.toString('hex')).toEqual(fixture.script);
}
if (fixture.address) {
expect(parsed.address).toEqual(fixture.address);
Expand Down
Loading

0 comments on commit cec68f1

Please sign in to comment.