diff --git a/lib/descriptor/abstractdescriptor.js b/lib/descriptor/abstractdescriptor.js index 97fa61ea9..cbc3fd26c 100644 --- a/lib/descriptor/abstractdescriptor.js +++ b/lib/descriptor/abstractdescriptor.js @@ -39,6 +39,7 @@ const stringType = { * (size 1 for PK, PKH, WPKH; any size for WSH and Multisig). * @property {AbstractDescriptor[]} subdescriptors - sub-descriptor arguments * for the descriptor (empty for everything but SH and WSH) + * @property {String} scriptContext * @property {Network} network */ @@ -52,6 +53,7 @@ class AbstractDescriptor { this.type = null; this.keyProviders = []; this.subdescriptors = []; + this.scriptContext = common.scriptContext.TOP; this.network = null; } @@ -232,6 +234,58 @@ class AbstractDescriptor { isSingleType() { return true; } + + /** + * Get scripts for the descriptor at a specified position. + * @param {Number} pos + * @returns {Script[]} + */ + + generateScripts(pos) { + const pubkeys = []; + const subscripts = []; + + for (const subdesc of this.subdescriptors) { + const outscripts = subdesc.generateScripts(pos); + assert(outscripts.length === 1); + subscripts.push(outscripts[0]); + } + + for (const provider of this.keyProviders) { + const pubkey = provider.getPublicKey(pos); + pubkeys.push(pubkey); + } + + return this._getScripts(pubkeys, subscripts); + } + + /** + * Get the scripts (helper function). + * @returns {Script[]} + */ + + _getScripts() { + throw new Error('Abstract method.'); + } + + /** + * Derive addresses for the descriptor at a specified position. + * @param {Number} pos + * @returns {Address[]} + */ + + getAddresses(pos) { + const scripts = this.generateScripts(pos); + const addresses = []; + + for (const script of scripts) { + const address = script.getAddress(); + assert(address, 'Descriptor does not have a corresponding address'); + addresses.push(address.toString(this.network)); + } + + return addresses; + } } /* diff --git a/lib/descriptor/keyprovider.js b/lib/descriptor/keyprovider.js index de7f57362..bbfabb519 100644 --- a/lib/descriptor/keyprovider.js +++ b/lib/descriptor/keyprovider.js @@ -263,6 +263,24 @@ class KeyProvider { hasPrivateKey() { return this.ring.privateKey !== null; } + + /** + * Get the public key + * @returns {Buffer} + */ + + getPublicKey() { + return this.ring.publicKey; + } + + /** + * Get the private key (if available) + * @returns {Buffer} + */ + + getPrivateKey() { + return this.ring.privateKey; + } } /** @@ -407,6 +425,87 @@ class HDKeyProvider extends KeyProvider { } return null; } + + /** + * Derive private key at a given position.. + * @returns {HDPrivateKey} + */ + + getHDPrivateKey(pos) { + assert( + this.hdkey.privateKey, + 'Private key not available for hardened derivation.' + ); + + let childkey = this.hdkey; + + if (this.path.length > 0) { + const path = 'm' + HD.common.format(this.path, this.hardenedMarker); + childkey = childkey.derivePath(path); + } + + if (this.type === deriveType.UNHARDENED) { + childkey = childkey.derive(pos, false); + } else if (this.type === deriveType.HARDENED) { + childkey = childkey.derive(pos, true); + } + + return childkey; + } + + /** + * Derive public key at a given position + * @param {Number} pos + * @returns {HDPublicKey} + */ + + getHDPublicKey(pos) { + if (this.isHardened()) { + const childprivkey = this.getHDPrivateKey(pos); + return childprivkey.toPublic(); + } + + let childkey = this.hdkey; + + if (this.hdkey instanceof HD.PrivateKey) { + childkey = this.hdkey.toPublic(); + } + + if (this.path.length > 0) { + const path = 'm' + HD.common.format(this.path, this.hardenedMarker); + childkey = childkey.derivePath(path); + } + + if (this.type === deriveType.UNHARDENED) { + childkey = childkey.derive(pos); + } + + assert(this.type !== deriveType.HARDENED); + + return childkey; + } + + /** + * Get public key at a given position + * @param {Number} pos + * @returns {Buffer} public key + */ + + getPublicKey(pos) { + const hdkey = this.getHDPublicKey(pos); + return hdkey.publicKey; + } + + /** + * Get private key at a given position + * @param {Number} pos + * @returns {Buffer} private key + */ + + getPrivateKey(pos) { + const hdkey = this.getHDPrivateKey(pos); + return hdkey.privateKey; + } } /** @@ -424,7 +523,7 @@ function getHardenedMarker(path) { let hardenedMarker = null; for (const p of path) { - const last = p[p.length - 1]; + const last = p[p.length - 1]; if (last === '\'' || last === 'h') { hardenedMarker = last; } diff --git a/lib/descriptor/type/addr.js b/lib/descriptor/type/addr.js index ae67076cd..8c95b6ffa 100644 --- a/lib/descriptor/type/addr.js +++ b/lib/descriptor/type/addr.js @@ -12,6 +12,7 @@ const common = require('../common'); const {isType, strip, checkChecksum, scriptContext, types} = common; const Address = require('../../primitives/address'); const Network = require('../../protocol/network'); +const Script = require('../../script/script'); /** * AddressDescriptor @@ -19,6 +20,7 @@ const Network = require('../../protocol/network'); * @see https://github.com/bitcoin/bips/blob/master/bip-0385.mediawiki#addr * @property {String} type * @property {Address} address + * @property {String} scriptContext * @property {Network} network * @extends AbstractDescriptor */ @@ -118,6 +120,15 @@ class AddressDescriptor extends AbstractDescriptor { isSolvable() { return false; } + + /** + * Get the scripts (helper function). + * @returns {Script[]} + */ + + _getScripts() { + return [Script.fromAddress(this.address)]; + } } /* diff --git a/lib/descriptor/type/combo.js b/lib/descriptor/type/combo.js index 63e566dd8..f95cadb9d 100644 --- a/lib/descriptor/type/combo.js +++ b/lib/descriptor/type/combo.js @@ -12,6 +12,8 @@ const common = require('../common'); const {isType, strip, checkChecksum, scriptContext, types} = common; const KeyProvider = require('../keyprovider'); const Network = require('../../protocol/network'); +const hash160 = require('bcrypto/lib/hash160'); +const Script = require('../../script/script'); /** * ComboDescriptor @@ -19,6 +21,7 @@ const Network = require('../../protocol/network'); * @see https://github.com/bitcoin/bips/blob/master/bip-0384.mediawiki * @property {String} type * @property {KeyProvider[]} keyProviders + * @property {String} scriptContext * @property {Network} network * @extends AbstractDescriptor */ @@ -112,6 +115,31 @@ class ComboDescriptor extends AbstractDescriptor { isSolvable() { return true; } + + /** + * Get the scripts (helper function). + * @param {Buffer[]} pubkeys + * @returns {Script[]} + */ + + _getScripts(pubkeys) { + assert(Array.isArray(pubkeys) && pubkeys.length === 1); + assert(Buffer.isBuffer(pubkeys[0])); + + const scripts = []; + scripts.push(Script.fromPubkey(pubkeys[0])); // P2PK + const pubkeyhash = hash160.digest(pubkeys[0]); + scripts.push(Script.fromPubkeyhash(pubkeyhash)); // P2PKH + + if (pubkeys[0].length === 33) { + const p2wpkh = Script.fromProgram(0, pubkeyhash); + scripts.push(p2wpkh); // P2WPKH + const p2sh = Script.fromScripthash(p2wpkh.hash160()); // P2SH-P2WPKH + scripts.push(p2sh); + } + + return scripts; + } } /* diff --git a/lib/descriptor/type/multisig.js b/lib/descriptor/type/multisig.js index 7bbfc4c5f..3264b148d 100644 --- a/lib/descriptor/type/multisig.js +++ b/lib/descriptor/type/multisig.js @@ -14,6 +14,7 @@ const {MAX_SCRIPT_PUSH, MAX_MULTISIG_PUBKEYS} = consensus; const KeyProvider = require('../keyprovider'); const Network = require('../../protocol/network'); const assert = require('bsert'); +const Script = require('../../script/script'); /** * MultisigDescriptor @@ -21,6 +22,7 @@ const assert = require('bsert'); * @see https://github.com/bitcoin/bips/blob/master/bip-0383.mediawiki * @property {String} type * @property {KeyProvider[]} keyProviders + * @property {String} scriptContext * @property {Number} threshold * @property {Boolean} isSorted - true if descriptor is sortedmulti * @property {Network} network @@ -148,6 +150,7 @@ class MultisigDescriptor extends AbstractDescriptor { this.keyProviders = providers; this.isSorted = isSorted; this.network = Network.get(network); + this.scriptContext = context; return this; } @@ -167,6 +170,36 @@ class MultisigDescriptor extends AbstractDescriptor { toStringExtra() { return this.threshold.toString(); } + + /** + * Get the scripts (helper function). + * @param {Buffer[]} pubkeys + * @returns {Script[]} + */ + + _getScripts(pubkeys) { + assert(Array.isArray(pubkeys) && pubkeys.length > 0); + + for (const pubkey of pubkeys) { + assert(Buffer.isBuffer(pubkey)); + } + + const m = this.threshold; + const n = pubkeys.length; + const isWitness = this.scriptContext === scriptContext.P2WSH; + + return [Script.fromMultisig(m, n, pubkeys, this.isSorted, isWitness)]; + } + + /** + * Derive addresses from scripts. + * @param {Script[]} scripts + * @returns {Address[]} + */ + + getAddresses(scripts) { + throw new Error('Descriptor does not have a corresponding address'); + } } /** diff --git a/lib/descriptor/type/pk.js b/lib/descriptor/type/pk.js index 0a61ec3a2..a30135002 100644 --- a/lib/descriptor/type/pk.js +++ b/lib/descriptor/type/pk.js @@ -12,12 +12,14 @@ const {isType, strip, checkChecksum, scriptContext, types} = common; const assert = require('bsert'); const KeyProvider = require('../keyprovider'); const Network = require('../../protocol/network'); +const Script = require('../../script/script'); /** * PKDescriptor * Represents a P2PK output script. * @see https://github.com/bitcoin/bips/blob/master/bip-0381.mediawiki#pk * @property {String} type + * @property {String} scriptContext * @property {KeyProvider[]} keyProviders * @property {Network} network * @extends AbstractDescriptor @@ -88,6 +90,7 @@ class PKDescriptor extends AbstractDescriptor { this.keyProviders = [provider]; this.network = Network.get(network); + this.scriptContext = context; return this; } @@ -103,6 +106,19 @@ class PKDescriptor extends AbstractDescriptor { static fromString(str, network, context = scriptContext.TOP) { return new this().fromString(str, network, context); } + + /** + * Get the scripts (helper function). + * @param {Buffer[]} pubkeys + * @returns {Script[]} + */ + + _getScripts(pubkeys) { + assert(Array.isArray(pubkeys) && pubkeys.length === 1); + assert(Buffer.isBuffer(pubkeys[0])); + + return [Script.fromPubkey(pubkeys[0])]; + } } /* diff --git a/lib/descriptor/type/pkh.js b/lib/descriptor/type/pkh.js index c18a30fd5..cda4f29fa 100644 --- a/lib/descriptor/type/pkh.js +++ b/lib/descriptor/type/pkh.js @@ -12,12 +12,15 @@ const {isType, strip, checkChecksum, scriptContext, types} = common; const assert = require('bsert'); const KeyProvider = require('../keyprovider'); const Network = require('../../protocol/network'); +const hash160 = require('bcrypto/lib/hash160'); +const Script = require('../../script/script'); /** * PKHDescriptor * Represents a P2PKH output script. * @see https://github.com/bitcoin/bips/blob/master/bip-0381.mediawiki#pkh * @property {String} type + * @property {String} scriptContext * @property {Network} network * @property {KeyProvider[]} keyProviders * @extends AbstractDescriptor @@ -95,6 +98,7 @@ class PKHDescriptor extends AbstractDescriptor { this.keyProviders = [provider]; this.network = Network.get(network); + this.scriptContext = context; return this; } @@ -110,6 +114,20 @@ class PKHDescriptor extends AbstractDescriptor { static fromString(str, network, context = scriptContext.TOP) { return new this().fromString(str, network, context); } + + /** + * Get the scripts (helper function). + * @param {Buffer[]} pubkeys + * @returns {Script[]} + */ + + _getScripts(pubkeys) { + assert(Array.isArray(pubkeys) && pubkeys.length === 1); + assert(Buffer.isBuffer(pubkeys[0])); + + const pubkeyhash = hash160.digest(pubkeys[0]); + return [Script.fromPubkeyhash(pubkeyhash)]; + } } /* diff --git a/lib/descriptor/type/raw.js b/lib/descriptor/type/raw.js index 2daf9a07c..898da314b 100644 --- a/lib/descriptor/type/raw.js +++ b/lib/descriptor/type/raw.js @@ -18,6 +18,7 @@ const assert = require('bsert'); * Represents the script represented by HEX in input. * @see https://github.com/bitcoin/bips/blob/master/bip-0385.mediawiki#raw * @property {String} type + * @property {String} scriptContext * @property {Script} script * @property {Network} network * @extends AbstractDescriptor @@ -115,6 +116,15 @@ class RawDescriptor extends AbstractDescriptor { toStringExtra() { return this.script.toJSON(); } + + /** + * Get the scripts (helper function). + * @returns {Script[]} + */ + + _getScripts() { + return [this.script]; + } } /* diff --git a/lib/descriptor/type/sh.js b/lib/descriptor/type/sh.js index cd12378c0..f73ad9594 100644 --- a/lib/descriptor/type/sh.js +++ b/lib/descriptor/type/sh.js @@ -15,14 +15,15 @@ const WSHDescriptor = require('./wsh'); const assert = require('bsert'); const common = require('../common'); const {isType, strip, getType, scriptContext, checkChecksum, types} = common; - const Network = require('../../protocol/network'); +const Script = require('../../script/script'); /** * SHDescriptor * Represents a P2SH output script. * @see https://github.com/bitcoin/bips/blob/master/bip-0381.mediawiki#sh * @property {String} type + * @property {String} scriptContext * @property {Descriptor[]} subdescriptors * @property {Network} network * @extends AbstractDescriptor @@ -63,6 +64,7 @@ class SHDescriptor extends AbstractDescriptor { ); const subdesc = options.subdescriptors[0]; + subdesc.scriptContext = scriptContext.P2SH; const isValid = this.isValidSubdescriptor(subdesc.type); assert(isValid, `Can not have ${subdesc.type}() inside sh()`); @@ -155,6 +157,21 @@ class SHDescriptor extends AbstractDescriptor { return validSubTypes.includes(type); } + + /** + * Get the scripts (helper function). + * @param {Buffer[]} pubkeys + * @param {Script[]} subscripts + * @returns {Script[]} + */ + + _getScripts(pubkeys, subscripts) { + assert(Array.isArray(pubkeys) && pubkeys.length === 0); + assert(Array.isArray(subscripts) && subscripts.length === 1); + assert(subscripts[0] instanceof Script); + + return [Script.fromScripthash(subscripts[0].hash160())]; + } } /* diff --git a/lib/descriptor/type/wpkh.js b/lib/descriptor/type/wpkh.js index 27db8401d..9dba3f065 100644 --- a/lib/descriptor/type/wpkh.js +++ b/lib/descriptor/type/wpkh.js @@ -12,12 +12,15 @@ const {isType, strip, checkChecksum, types, scriptContext} = common; const KeyProvider = require('../keyprovider'); const Network = require('../../protocol/network'); const assert = require('bsert'); +const hash160 = require('bcrypto/lib/hash160'); +const Script = require('../../script/script'); /** * WPKHDescriptor * Represents a P2WPKH output script. * @see https://github.com/bitcoin/bips/blob/master/bip-0382.mediawiki#wpkh * @property {String} type + * @property {String} scriptContext * @property {KeyProvider[]} keyProviders * @property {Network} network * @extends AbstractDescriptor @@ -89,11 +92,12 @@ class WPKHDescriptor extends AbstractDescriptor { ); str = strip(str); - context = scriptContext.P2WPKH; - const provider = KeyProvider.fromString(str, network, context); + + const provider = KeyProvider.fromString(str, network, scriptContext.P2WPKH); this.keyProviders = [provider]; this.network = Network.get(network); + this.scriptContext = context; return this; } @@ -109,6 +113,20 @@ class WPKHDescriptor extends AbstractDescriptor { static fromString(str, network, context = scriptContext.TOP) { return new this().fromString(str, network, context); } + + /** + * Get the scripts (helper function). + * @param {Buffer[]} pubkeys + * @returns {Script[]} + */ + + _getScripts(pubkeys) { + assert(Array.isArray(pubkeys) && pubkeys.length === 1); + assert(Buffer.isBuffer(pubkeys[0])); + + const pubkeyhash = hash160.digest(pubkeys[0]); + return [Script.fromProgram(0, pubkeyhash)]; + } } /* diff --git a/lib/descriptor/type/wsh.js b/lib/descriptor/type/wsh.js index 0e750d386..bec79ace7 100644 --- a/lib/descriptor/type/wsh.js +++ b/lib/descriptor/type/wsh.js @@ -14,12 +14,14 @@ const assert = require('bsert'); const common = require('../common'); const {isType, getType, strip, scriptContext, checkChecksum, types} = common; const Network = require('../../protocol/network'); +const Script = require('../../script/script'); /** * WSHDescriptor * Represents a P2WSH output script. * @see https://github.com/bitcoin/bips/blob/master/bip-0382.mediawiki#wsh * @property {String} type + * @property {String} scriptContext * @property {Descriptor[]} subdescriptors - Subdescriptors * @property {Network} network * @extends AbstractDescriptor @@ -60,6 +62,7 @@ class WSHDescriptor extends AbstractDescriptor { ); const subdesc = options.subdescriptors[0]; + subdesc.scriptContext = scriptContext.P2WSH; const isValid = this.isValidSubdescriptor(subdesc.type); assert(isValid, `Can not have ${subdesc.type}() inside sh()`); @@ -97,6 +100,7 @@ class WSHDescriptor extends AbstractDescriptor { str = strip(str); const subtype = getType(str); let subdesc = {}; + this.scriptContext = context; context = scriptContext.P2WSH; switch (subtype) { @@ -146,6 +150,21 @@ class WSHDescriptor extends AbstractDescriptor { return validSubTypes.includes(type); } + + /** + * Get the scripts (helper function). + * @param {Buffer[]} pubkeys + * @param {Script} subscripts + * @returns {Script[]} + */ + + _getScripts(pubkeys, subscripts) { + assert(Array.isArray(pubkeys) && pubkeys.length === 0); + assert(Array.isArray(subscripts) && subscripts.length === 1); + assert(subscripts[0] instanceof Script); + + return [(Script.fromProgram(0, subscripts[0].sha256()))]; + } } /* diff --git a/lib/node/rpc.js b/lib/node/rpc.js index aef49399e..409096e76 100644 --- a/lib/node/rpc.js +++ b/lib/node/rpc.js @@ -229,6 +229,7 @@ class RPC extends RPCBase { this.add('getmemoryinfo', this.getMemoryInfo); this.add('setloglevel', this.setLogLevel); this.add('getdescriptorinfo', this.getDescriptorInfo); + this.add('deriveaddresses', this.deriveAddresses); } /* @@ -2076,7 +2077,7 @@ class RPC extends RPCBase { keys[i] = key; } - const script = Script.fromMultisig(m, n, keys); + const script = Script.fromMultisig(m, n, keys, true, false); if (script.getSize() > consensus.MAX_SCRIPT_PUSH) { throw new RPCError(errs.VERIFY_ERROR, @@ -2346,10 +2347,97 @@ class RPC extends RPCBase { return result; } + async deriveAddresses(args, help) { + if (help || args.length > 2 || args.length === 0) + throw new RPCError( + errs.MISC_ERROR, 'deriveaddresses "descriptor" (range)' + ); + + const valid = new Validator(args); + + const desc = parseDescriptor(valid.str(0, ''), this.network, true); + + if (desc.isRange() && !valid.has(1)) { + throw new RPCError( + errs.INVALID_PARAMETER, + 'Range must be specified for ranged descriptor' + ); + } + + if (!desc.isRange() && valid.has(1)) { + throw new RPCError( + errs.INVALID_PARAMETER, + 'Range should not be specified for un-ranged descriptor' + ); + } + + const {low, high} = this.getDescriptorRangeParams(valid); + + const addresses = []; + for (let i = low; i <= high; i++) { + addresses.push(...desc.getAddresses(i)); + } + + return addresses; + } + /* * Helpers */ + getDescriptorRangeParams(valid) { + let low = 0, high = 0; + + // can be integer as well as array of integers + try { + high = valid.u32(1, 0); + } catch (e) { + try { + const arr = valid.array(1, []); + low = arr[0]; + high = arr[1]; + } catch (e) { + throw new RPCError( + errs.INVALID_PARAMETER, + 'Invalid range.' + ); + } + } + + if (!Number.isSafeInteger(low)) { + throw new RPCError( + errs.INVALID_PARAMETER, 'Range begin must be an integer' + ); + } + + if (!Number.isSafeInteger(high)) { + throw new RPCError( + errs.INVALID_PARAMETER, 'Range end must be an integer' + ); + } + + if (low > high) { + throw new RPCError( + errs.INVALID_PARAMETER, + 'Range specified as [begin,end] must not have begin after end' + ); + } + + if (low < 0) { + throw new RPCError(errs.INVALID_PARAMETER, 'Range should be >= 0'); + } + + if (high >= 0x80000000) { + throw new RPCError(errs.INVALID_PARAMETER, 'End of range is too high'); + } + + if (high >= low + 1000000) { + throw new RPCError(errs.INVALID_PARAMETER, 'Range is too large'); + } + + return {low, high}; + } + async handleLongpoll(lpid) { if (lpid.length !== 72) throw new RPCError(errs.INVALID_PARAMETER, 'Invalid longpoll ID.'); @@ -2884,9 +2972,9 @@ function parseAddress(raw, network) { } } -function parseDescriptor(raw, network) { +function parseDescriptor(raw, network, requireChecksum = false) { try { - return parse(raw, network, false); + return parse(raw, network, requireChecksum); } catch (e) { throw new RPCError(errs.INVALID_DESCRIPTOR, `Invalid descriptor: ${e.message}` diff --git a/lib/script/script.js b/lib/script/script.js index 3cd87cdef..cbce00952 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -28,6 +28,7 @@ const opcodes = common.opcodes; const scriptTypes = common.types; const {encoding} = bio; const {inspectSymbol} = require('../utils'); +const {MAX_MULTISIG_PUBKEYS, MAX_SCRIPT_PUSH} = consensus; /* * Constants @@ -1480,26 +1481,39 @@ class Script { * @param {Number} m * @param {Number} n * @param {Buffer[]} keys + * @param {Boolean?} isSorted + * @param {Boolean?} isWitness + * @returns {Script} */ - fromMultisig(m, n, keys) { + fromMultisig(m, n, keys, isSorted = true, isWitness = true) { assert((m & 0xff) === m && (n & 0xff) === n); assert(Array.isArray(keys)); assert(keys.length === n, '`n` keys are required for multisig.'); assert(m >= 1 && m <= n); - assert(n >= 1 && n <= 15); + + assert( + n >= 1 && n <= MAX_MULTISIG_PUBKEYS, + `${n} keys not allowed in script. Max allowed: ${MAX_MULTISIG_PUBKEYS}` + ); this.clear(); - this.pushSmall(m); + this.pushInt(m); - for (const key of sortKeys(keys)) + for (const key of isSorted ? sortKeys(keys) : keys) this.pushData(key); - this.pushSmall(n); + this.pushInt(n); this.pushOp(opcodes.OP_CHECKMULTISIG); - return this.compile(); + const script = this.compile(); + + if (!isWitness) { + assert(script.getSize() <= MAX_SCRIPT_PUSH, 'Script size is too large'); + } + + return script; } /** @@ -1507,11 +1521,13 @@ class Script { * @param {Number} m * @param {Number} n * @param {Buffer[]} keys + * @param {Boolean?} isSorted + * @param {Boolean?} isWitness * @returns {Script} */ - static fromMultisig(m, n, keys) { - return new this().fromMultisig(m, n, keys); + static fromMultisig(m, n, keys, isSorted = true, isWitness = true) { + return new this().fromMultisig(m, n, keys, isSorted, isWitness); } /** diff --git a/lib/wallet/account.js b/lib/wallet/account.js index acf4f6a12..cb89151c6 100644 --- a/lib/wallet/account.js +++ b/lib/wallet/account.js @@ -15,6 +15,7 @@ const Script = require('../script/script'); const WalletKey = require('./walletkey'); const {HDPublicKey} = require('../hd/hd'); const {inspectSymbol} = require('../utils'); +const consensus = require('../protocol/consensus'); /** * Account @@ -148,8 +149,14 @@ class Account { this.accountKey = options.accountKey; - if (this.n > 1) + if (this.n > 1) { this.type = Account.types.MULTISIG; + const maxAllowedKeys = this.witness ? consensus.MAX_MULTISIG_PUBKEYS : 15; + assert( + this.n <= maxAllowedKeys, + `n ranges between 1 and ${maxAllowedKeys}` + ); + } if (!this.name) this.name = this.accountIndex.toString(10); @@ -496,7 +503,9 @@ class Account { keys.push(key.publicKey); } - ring.script = Script.fromMultisig(this.m, this.n, keys); + ring.script = Script.fromMultisig( + this.m, this.n, keys, true, this.witness + ); break; } diff --git a/test/data/descriptors/desc-addresses.json b/test/data/descriptors/desc-addresses.json new file mode 100644 index 000000000..ee78c5c06 --- /dev/null +++ b/test/data/descriptors/desc-addresses.json @@ -0,0 +1,521 @@ +{ + "multisig": [ + { + "input": "multi(1,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1)", + "error": "Descriptor does not have a corresponding address", + "network": "regtest" + }, + { + "input": "sortedmulti(1,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1)", + "error": "Descriptor does not have a corresponding address", + "network": "regtest" + } + ], + "sh": [ + { + "input":"sh(wsh(sortedmulti(2,[e7dd1c50/48'/1'/40'/1']tpubDFh3VaUEs71ZMcVBmscSSnP4f4r6TvnLssu8yXvpj3uMfAehciMYTrgbfu4KCxXb7oSaz4kriuWRZtQVhZR2oA9toob6aELnsYLN94fXQLF/*,[e7dd1c50/48'/1'/20'/1']tpubDFPemvLnpMqE1BPuturDUh46KxsR8wGSQrA6HofYE7fqxpMAKCcoYWHGA46B6zKY4xcQAc1vLFTcqQ9BvsbHZ4UhzqqF5nUeeNBjNivHxPT/*,[aedb3d12/48'/1'/0'/1']tpubDEbuxto5Kftus28NyPddiEev2yUhzZGpkpQdCK732KBge5FJDhaMdhG1iVw3rMJ2qvABkaLR9HxobkeFkmQZ4RqQgN1KJadDjPn9ANBLo8V/*h)))", + "error": "Private key not available for hardened derivation.", + "network":"regtest" + }, + { + "input": "sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))#nc9gqlkc", + "addresses": ["2N4tqHmyAuKSygccnHUWSF5GimKWmimRphV"], + "network": "testnet" + }, + { + "input": "sh(wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd)))#6lwvnz5j", + "addresses": ["2NB1Ani7z7sL45jj1PNdTr6RgEkA3zcvhLD"], + "network": "testnet" + }, + { + "input": "sh(wsh(sortedmulti(2,[e7dd1c50/48'/1'/40'/1']tpubDFh3VaUEs71ZMcVBmscSSnP4f4r6TvnLssu8yXvpj3uMfAehciMYTrgbfu4KCxXb7oSaz4kriuWRZtQVhZR2oA9toob6aELnsYLN94fXQLF/*,[e7dd1c50/48'/1'/20'/1']tpubDFPemvLnpMqE1BPuturDUh46KxsR8wGSQrA6HofYE7fqxpMAKCcoYWHGA46B6zKY4xcQAc1vLFTcqQ9BvsbHZ4UhzqqF5nUeeNBjNivHxPT/*,[aedb3d12/48'/1'/0'/1']tpubDEbuxto5Kftus28NyPddiEev2yUhzZGpkpQdCK732KBge5FJDhaMdhG1iVw3rMJ2qvABkaLR9HxobkeFkmQZ4RqQgN1KJadDjPn9ANBLo8V/*)))#zlh5y6z5", + "addresses": [ + "2MtWBjxiAi4xYNUdtDe2sHkNw5kdAQqZZNb", + "2MstQfXgUwTR66bhMrbNU3qDqT6RT4hGHnJ", + "2N6QXTyf64KHWddFZ5swjaRmEwk4hEawYuo", + "2NCfXGhiA6EjK6o2JSejtzeP5fYkNxP1TQC", + "2NCU5HcmKUap923abUPEhGNnTFUf3K2hAYr", + "2N6K6jKKeuejPTeiPDbnq1qZsqGhigoApzK", + "2N4jy9MPJee7WvH3tfRVE3LeYQxVNhjt2yH", + "2NCXTtRLCjwWPeUoRz5qGoKSj84Ci4pbcWy", + "2NEwEq98wFu1EcSf5jFexCRZEuGjjeTo265", + "2MzcruaPLyHniY1qjJLXLXEuYRd5PJ1o1EW", + "2MuY8izcxH5KJfhvc432HQVos483krppmrf", + "2NBbUxCP1A2ovm23q2pX8eEjvkizh4cGitY", + "2N3kwN89Pc8YS7z3qoKJ73EJ62sUYpzmuwr", + "2NEr4VJ1o9f185VhbdhnQo5AKpTDdwk6Zu1", + "2Mye2rQcjbuAxCoXawufCJgJGkAf4wyc244", + "2NA6XSkzshVweAk5ikWERuTU5pphqFbvcj8", + "2NFZikZS2JnHZZoappaiyuNgSCiLpSWiGCa", + "2N3mvkUJWPMz8MerB5d6iQKGW9UfUhvGw5U", + "2MtFZLp5HxqsVrFdFEYuomjP4FehATu5EJ9", + "2N12vhzmsUExm3Dy9uEZbWvffpdpL3TodJb", + "2MtH9skf1LeH95u7um4zN61Mo8ZpJ95WqDB", + "2N13sxSC3Y7ztpjdhn7hjEcUEdZZj8pRu7R", + "2MuGZ8Tko2BCVrZgsbKyceK2a6cTkQhGmjL", + "2NEKMdaQrC3MY8KySQpXnpBuY7sr1NW38Kc", + "2MwCq5x132S71M4daBrnrf58KUh5P2TssPD", + "2ND9UvnvJFFpEKbSTVfGYSSbKuJrYyGBP5C", + "2NFJPfbzukmsuZwxX3i6Dz3KLwFoDZjYmYH", + "2N3JzgmgbabW72jTMxZnTS2JUF5NQxMW78d", + "2NCF6ZWFBYg1pWCCU9ANCtiPQsjgeKQginM", + "2NF5U4gUJf1A68Hi1TgQZF7K5QfERbzhUcH", + "2NEMcYSANdxuCAt87fnknhTonxVdMAWCnyW", + "2N9ZhmcTiNM3gMNbKef8Rwbs3no3An9XPgV", + "2NA8p6qADLDZyf7aGJBgB2bbs6Yo3bKfdzG", + "2MtE1QdFd9XGA3XVDiA6ZZLAnLxfPECdTbH", + "2N8nvuXncxLnnC5DZVKSw8mRsB9hJBmewQE", + "2MyCd7WHYrejeMi8b5WXd3URkcK1Vd5R1FN", + "2N9Mcwbkexr57c4geQy33gBaqQ4H9K7etUm", + "2N7dHnfoPiq7n4DeYzsN3TCeLSmYreh3jj5", + "2N2XMyU3Bzpk5RYLJecY8WJgBi93bqEGef2", + "2NEkY8VwBSjJEzGp1jUTZWRsC7H5i1RQFvk", + "2MyGqSd8x2dcfziiA3iDy5xFGaqSEx7egpa", + "2NF5iHQwg287sHBUayXYKCkAVxwFE4mv9wC", + "2Myi1e6mDQuehPeZyvnNKYCR1QyUmt5sZUS", + "2N6DJGHj8EdpDQidSpScyM2zSRgphsa6dwF", + "2NBGKcDAbED6ZkQoftJcErV7nUkU4W6zLf1", + "2MxdXmCmdaebCzuXmKG4LXJDiFY5mPsFY1v", + "2MxnEBquVCtcF5WpUCSA3kkybCMfcKZgCRB", + "2MtQk1QkRuumEe4HPD7D76MGGrRvvsPgCf6", + "2Mse83NXypCqesmpjzX7avePRR6RdCtQV2g", + "2N3oRSeakhjbM6hoUXdp2LCkuLKny3cf2QC", + "2NCPM8JMeBiw5fpDKvjDkxapupPpBCcJQ7G", + "2MxyDMAB86wXxVK27dGizgiSQB9TyvTUTCi", + "2NDiXgap3U8GSzpKHCTGzP92gskCGc4HsZz", + "2NEca6PBu4oEEpjyWc1BVv1SnY4HntHkRtH", + "2N5T5uhKtLA2P4zzd66afdQx9D4YTnLjE1X", + "2NGJrKZQ3s5WTpoUoM6obut4iMPErW66t4d", + "2NDwGErXMdRCcKFYqrxiteQcC7gHgtWMnZJ", + "2N6wDhGzWbf9WUPJZL49ZRF8z5YvvT2Z5DD", + "2N39jjyDcKH5KYUnjw1XxNpcSQj5g9pf2yU", + "2Muo3JUjrwV8G9d9A4uhGNTtewvq5XAujA7", + "2N9vdJt63Ts7nH6kaUG8ezgDsZNsk7gtw6T", + "2MtiLtBZSwh5nPwVNzkXiwee7CD4okH1WAn", + "2N4zgbNgd7D4xHMUMxvq8FNScrviyEfpRE3", + "2N8j8CBLnURWHneQEuoM5TxzdDC26jjK5ih", + "2NFZp1eZrZTCyQBxEZHSXLJyBubXCdxwyxa", + "2MtABd1ohmVeePntjHKms2Mzoo3upLfC3A9", + "2N4uNB7BjfoazKWVHrwyWwf4ceBbhgtVkaY", + "2ND99Zig8Z2kh4uzWKrwjcmVyo8cRLJiYhn", + "2N7oEjRX1chnF8QrP4nTF7kvE3M8n9RSFC8", + "2MwHxwHWxANuUL2SJjGRPekixA6K914de12", + "2NGGTfdK5ziMRkai5oSGSE9DFiHmhCecTx2", + "2MwWkX7uYtUPMy7QKDv7jT9rTWuwY5PG8wD", + "2N4qkdTcZbtBUCRdZUiqBD2wTiLifchmgoW", + "2MxQqYXpt4Vtq9yf5yg1J6hdmkARzeY38gJ", + "2MzYdMwNpTWzXKm4GnisKuPR2NU2rMfqvp3", + "2NDXJqc3sGy216JLyf8LmpfXZnqEMzvZqK2", + "2N3Mu6yvziGmKC6fKjfpemiUTnxFcNzVUmr", + "2MzVxxc8mny31feoXhNAjGtofFR5rBMZ52y", + "2MtGHx56wRxCmftfSAGMpLBMAN5hCRoE5V8", + "2MxkmrfNVsZ8bxJG7mvMXQQ9bMVCqhDM8oW", + "2N7FCgfbvz9LNgA3cdGzvXuUzAapwfhaCoD", + "2N5fWsDSacLgRAJtkAiNw5Vq8PrWYAvV6bk", + "2MtAvc4myNLdwRqCcKeTz4m41yF36R8EPAQ", + "2N9AJu5Wg52CSnQVPnPUxZQ4zPLdq7jd2qU", + "2MwuhGLZJSFzKSvPES7qJwLmLPo9pAnKBrt", + "2NEtxN4mEi62HxyShEdLk6K1FRzZTBoN6em", + "2NGGMBUJFMnCBUCbtYxGF7G54sipjtPZV1f", + "2N27815mYDcsuEeqebk1XbvkgHKXVREgdDw", + "2N624P1P9hvtU74Ya855755V8DvdyxF2aLB", + "2NC24X1vywZ8acm4BC46KjhbUYozp7AnTdw", + "2NBEmYujvadVnUsHcGuUmpZv9PsdYK7pPLD", + "2MxX717tfRyhAWqFc8Mb9t33YDbuHUyDh2x", + "2MwSNFpSJaeYZuzuqK92S8KKxqc1Gk2Hsew", + "2NG3GqNvGFHPsyLs18QgxhcoV6X5W1z4wDT", + "2N9aZFNbM2jCVZNpomVGeLpSwrvBgr4gcCn", + "2NEGzGDs19n9M2bqAU2UCwqLUv1vZWb1u58", + "2NFeYhpK4K7bAnwbQ19osxzV9c7JHJYr3W5", + "2MwWs4gLcGtXtZjQaTkxjymo6khbMddtmzJ", + "2N5q6yDqrFK7bpR4V7VGReGd7EmKzfexuoV", + "2Mz5Fqb5ggJ1UGdZjkJmRYwhkZyXGbbF2yV", + "2NEVMQoJpPVeuCEhNgf5ZX639mSeKHACg7H" + ], + "range": [0, 100], + "network": "testnet" + }, + { + "input": "sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))#2wtr0ej5", + "addresses": ["39XGHYpYmJV9sGFoGHZeU2rLkY6r1MJ6C1"] + }, + { + "input": "sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))#qkrrc7je", + "addresses": ["3LKyvRN6SmYXGBNn8fcQvYxW9MGKtwcinN"] + }, + { + "input": "sh(multi(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))#y9zthqta", + "addresses": ["3GtEB3yg3r5de2cDJG48SkQwxfxJumKQdN"] + }, + { + "input": "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", + "addresses": ["383MnCNZkvUjsBMroHZ9sSGbatoFPMxYV8"] + }, + { + "input": "sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))#qxqt7ymh", + "addresses": [ + "3Fktwfew1dVGUoDoA1g8jJHFmPTgdq7Wwk", + "3K64DrBy5RGfKv1n28ZVV1Wj5ALLNakCRW", + "3Dmgr4bwjjpXhTffttD7JL78Ef3N4FDHAn", + "3GvW7ZsAGp9JqSwWYYNrNqUGoyedfYVd1Q", + "3FrHBgmk777MjGLzqLH8AFCXvXhi5bimnQ", + "36A65mxza86A6sdrtS6mxWK3gMizPKnxpT", + "3KEdApGNDEZsHETs6QwbfA3aTHdn74QZam", + "3GhAgC6kwdvNYsu1ULeX2JwgX4AotUXfY3", + "3LdMmRmZo84CsrcDs5VDe4jGVMMwYikcb5", + "38AioCNFQmWY8v6MdPcW93sddnKWB4xpjr", + "3Q3ud6ao8QJcEMMpU7BG8xeeDMymmT5y3V" + ], + "range": [0, 10] + }, + { + "input": "sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))#qxqt7ymh", + "addresses": [ + "36A65mxza86A6sdrtS6mxWK3gMizPKnxpT", + "3KEdApGNDEZsHETs6QwbfA3aTHdn74QZam", + "3GhAgC6kwdvNYsu1ULeX2JwgX4AotUXfY3", + "3LdMmRmZo84CsrcDs5VDe4jGVMMwYikcb5", + "38AioCNFQmWY8v6MdPcW93sddnKWB4xpjr", + "3Q3ud6ao8QJcEMMpU7BG8xeeDMymmT5y3V" + ], + "range": [5, 10] + }, + { + "input": "sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))#0qtndeve", + "addresses": ["3DnW8JGpPViEZdpqat8qky1zc26EKbXnmM"] + }, + { + "input": "sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))#qxqt7ymh", + "addresses": [ + "3Fktwfew1dVGUoDoA1g8jJHFmPTgdq7Wwk", + "3K64DrBy5RGfKv1n28ZVV1Wj5ALLNakCRW", + "3Dmgr4bwjjpXhTffttD7JL78Ef3N4FDHAn", + "3GvW7ZsAGp9JqSwWYYNrNqUGoyedfYVd1Q", + "3FrHBgmk777MjGLzqLH8AFCXvXhi5bimnQ", + "36A65mxza86A6sdrtS6mxWK3gMizPKnxpT", + "3KEdApGNDEZsHETs6QwbfA3aTHdn74QZam", + "3GhAgC6kwdvNYsu1ULeX2JwgX4AotUXfY3", + "3LdMmRmZo84CsrcDs5VDe4jGVMMwYikcb5", + "38AioCNFQmWY8v6MdPcW93sddnKWB4xpjr", + "3Q3ud6ao8QJcEMMpU7BG8xeeDMymmT5y3V" + ], + "range": [0, 10] + }, + { + "input": "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))#s53ls94y", + "addresses": ["33ujBbb4DuSCh4kn6tYthaeyP37SgBipug"] + }, + { + "input": "sh(wsh(multi(1,03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8,03499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e4,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)))#ks05yr6p", + "addresses": ["3Hd7YQStg9gYpEt6hgK14ZHUABxSURzeuQ"] + }, + { + "input": "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", + "addresses": ["383MnCNZkvUjsBMroHZ9sSGbatoFPMxYV8"] + } + ], + "combo": [ + { + "input": "combo([01234567]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*)#zz0y29sj", + "addresses": [ + "1PK6Ke6kRCxd2Wbj3n16UR7TPZfmxWzJh", + "1PK6Ke6kRCxd2Wbj3n16UR7TPZfmxWzJh", + "bc1qqsuyph3glh0nrntrd33dnujggszvndelahu9ql", + "3MBHttNMdd7QPxgxadAAEXTqJ3wqZknQPX", + "19gyKJ2PcVqVH3yLmBEjbasEDEpZsbL48w", + "19gyKJ2PcVqVH3yLmBEjbasEDEpZsbL48w", + "bc1qtagr6re33as29rzywfk2c363vusafrwp0l5rhw", + "3KL3DnCmXDnfkZ8w4c1iSeCQHXmAUP2mUi", + "1PVMChDUkuT56HKcfH69MX6NmFfKCzqM9J", + "1PVMChDUkuT56HKcfH69MX6NmFfKCzqM9J", + "bc1q76h5wy5a9tjhask0f7gjlcyzq5vstjg28wfxca", + "3G6EHbFjSPuhuMsQ2F6mbyxwMD5QPqVmXH", + "1J7onHG1S64iacy2kDLP2XR5FEgUfB8zEn", + "1J7onHG1S64iacy2kDLP2XR5FEgUfB8zEn", + "bc1qh0p673t88s6e30nhclxmrd2m74j5h35gdz746v", + "3J8DsB27cs1bXD55c73v9TzN5md9iyU7CY", + "18cPyQpt3aRFttbRVozg7qNmz6XthW6TRL", + "18cPyQpt3aRFttbRVozg7qNmz6XthW6TRL", + "bc1q2dafm8e9qhnnsyx3wxyextkwm7c5rppwgfrw4g", + "3MQZNgHht2dZx9dNFw6eSj51dBBzV5Luos", + "15wTheH5rFGtgCP4XU33xaWuDGrh6tGCWm", + "15wTheH5rFGtgCP4XU33xaWuDGrh6tGCWm", + "bc1qxck5paq0zztae4qd8su547cev7u57ew8j9ee6x", + "3QqEkbt8qJnDhZBfFKWrUxvaD1DCAcL7Fa", + "1AGX2zybMXxXhyob8Sd94AYsDFdiCjkPof", + "1AGX2zybMXxXhyob8Sd94AYsDFdiCjkPof", + "bc1qvk58z6zc2sgfzvevsehhujvjrv3k6q62rpkcul", + "3GwU6griFHTvREci1nQKhDZH6MGRMNEKbH", + "1Fb3CjvJF8MrM1rZeeLQo9CXtbYPwzNWRM", + "1Fb3CjvJF8MrM1rZeeLQo9CXtbYPwzNWRM", + "bc1q5qqmml0dutymwgkngsjfer7cxg6psvshudneyh", + "3FrH5Y2hj5uvKTD8SYMgYnnPpEuiMjWbx3", + "1J4B83vokYz5BWDFHZ9gVFYsofQLquCt3g", + "1J4B83vokYz5BWDFHZ9gVFYsofQLquCt3g", + "bc1qhvfat57qe7d7pfzn2vu8jpd6yzxqntj5q0d5pm", + "3MuJJiLomAD8yCRcpvmkqFrBNVWgmfT1rX" + ], + "range": [3, 11] + }, + { + "input": "combo([01234567]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)#7a7j2khf", + "addresses": [ + "15XVotxCAV7sRx1PSCkQNsGw3W9jT9A94R", + "15XVotxCAV7sRx1PSCkQNsGw3W9jT9A94R", + "bc1qxxjs0wq4ty7lc50lcuj94el94m3sgfrwfvky9p", + "35aip4nbX3wM2V3NMxHgPz1wEUQ2T7BJPY" + ] + }, + { + "input": "combo(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)#my43jap3", + "addresses": [ + "1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN", + "1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN" + ] + }, + { + "input": "combo([01234567]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)#9jncyu3j", + "addresses": [ + "15XVotxCAV7sRx1PSCkQNsGw3W9jT9A94R", + "15XVotxCAV7sRx1PSCkQNsGw3W9jT9A94R", + "bc1qxxjs0wq4ty7lc50lcuj94el94m3sgfrwfvky9p", + "35aip4nbX3wM2V3NMxHgPz1wEUQ2T7BJPY" + ] + }, + { + "input": "combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)#f68555z5", + "addresses": [ + "1PhtFvwcfS55oKEd4MkW79bEFBnK9M7uh3", + "1PhtFvwcfS55oKEd4MkW79bEFBnK9M7uh3", + "bc1qly8rz7x2yhevszxuwe3yqvkn2t7mm7hjfxu9cu", + "32WMXg1g1VF4NuAbv2QuGKMiG9Brm2LB3D" + ] + }, + { + "input": "combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)#lq9sf04s", + "addresses": [ + "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH", + "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH", + "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4", + "3JvL6Ymt8MVWiCNHC7oWU6nLeHNJKLZGLN" + ] + }, + { + "input": "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", + "addresses": [ + "mx5u3nqdPpzvEZ3vfnuUQEyHg3gHd8zrrH", + "mx5u3nqdPpzvEZ3vfnuUQEyHg3gHd8zrrH" + ], + "network": "testnet" + } + ], + "wsh": [ + { + "input": "wsh(multi(1,xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/0,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))#eft0rtsu", + "addresses": [ + "bc1qev24fpsy3v36dk5hd4xxlcr35tdu3fa402hjykuf2hew9gnmtuqq0xexc7" + ] + }, + { + "input": "wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))#en3tu306", + "addresses": [ + "bc1qwu7hp9vckakyuw6htsy244qxtztrlyez4l7qlrpg68v6drgvj39qn4zazc" + ] + }, + { + "input": "wsh(pkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))#ra2jnv04", + "addresses": [ + "bc1qwmc3tpvtu023fkthn5ytt3yexg7wm8kvgegwvtehf9r45vwdjyksxa6yuw", + "bc1qj5msxgj7q38tzw466tawplrqp6v2nkhv6addjsumpq3wmj6v5e2q584w5u", + "bc1q79jezjp224vfhtgwpdyhepgaf2qnwzecmxdqz25nseggunpw3gvqsrkk44", + "bc1q8qfcue8ces7cy3yk3sypel86c0d7q4d0nttj4j2ne499qeq0ctjq835ztk", + "bc1q7k2c3v34p56tq2zhzhh3k9c6zy5nsxdeu80h332zhsfv7cxnn2wqlas3h7", + "bc1q90q432vpwsxp3cqqk8wju2qs9xgxnhf9fl06gntgdy9v96hftrwq20nddp", + "bc1qljvz43zzzed83ay6jwaym4pmyde3jkfyvc4nygy9hxw6xz4d4vpqgls33z", + "bc1quaxfprlm4kkvhqqnv9fv6qmt2k83x4v09ank8xxjr4lwr37hp5ds58dn25", + "bc1qw2fx03ez64acfdk6za70em2shhmztzva0duns4a2c0u6ervkzfusu4q9yx", + "bc1qjl0ca0ld99z48354yas0qps0k2ckmmc2v6e20cv8xn3jkzqeam5svwp4dr", + "bc1q94m8phpqpngvr2ethf5066gj65hn5gngj4nnwmfl4c5xqd3w098q3femjj" + ], + "range": [0, 10] + }, + { + "input": "wsh(multi(2,tprv8ZgxMBicQKsPePmENhT9N9yiSfTtDoC1f39P7nNmgEyCB6Nm4Qiv1muq4CykB9jtnQg2VitBrWh8PJU8LHzoGMHTrS2VKBSgAz7Ssjf9S3P/0/*,tpubDBYDcH8P2PedrEN3HxWYJJJMZEdgnrqMsjeKpPNzwe7jmGwk5M3HRdSf5vudAXwrJPfUsfvUPFooKWmz79Lh111U51RNotagXiGNeJe3i6t/1/*))#szn2yxkq", + "addresses": [ + "bcrt1qqsat6c82fvdy73rfzye8f7nwxcz3xny7t56azl73g95mt3tmzvgsgyd282", + "bcrt1q7sgx6gscgtau57jduend6a8l445ahpk3dt3u5zu58rx5qm27lhkq9s85ce", + "bcrt1q4f7nthf9e7uq5mt0hslhp2k5pxcpw4n6r2xm0s6cm05qhhnk22fsep6uxf" + ], + "range": [0, 2], + "network": "regtest" + } + ], + "wpkh": [ + { + "input": "wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)#t6wfjs64", + "addresses": ["bcrt1qjqmxmkpmxt80xz4y3746zgt0q3u3ferr34acd5"], + "network": "regtest" + }, + { + "input": "wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)#rgqxhqq9", + "addresses": [ + "bc1qxf4jyj0r5fw4m3sfxhcyfm5rt5ysh2zej5q0n2", + "bc1q4u9anz4u9uk2uehrdzt288l795efsnahdcv7nh", + "bc1qr7ne3m73e0u4e6leztqrrw9y5m5lh8e8y2j7hv", + "bc1qt5x0uwlsdv3l2p7cd6aqwfykdesenuzahxfaj5", + "bc1qdmyfsdvwxq7xc6dtpjn2k7kkuth9ea2jp2dah2", + "bc1qk65hp08n53rfvy2ewy2wkkkw4nz6xdrmql5j9p", + "bc1q9c5m3cqtz5p9urcrwh4muyc9r7w8edwvkkjjm2", + "bc1qkl27xhxw08lhtr4uprjqvgvqesndyq0lma9jx0", + "bc1qdyrh0hfx2w9vn9djv9k34qwrjlpd4v707upy4a", + "bc1qskjz7swecsnz3w9ce30ymydp4xd02w99hmuqlm", + "bc1qjz7sq6pdyqtcar74sewwmezrl05p2kd9q2vj6g" + ], + "range": [0, 10] + }, + { + "input": "wpkh([d34db33f/84h/0h/0h]0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2dce28d959f2815b16f81798)#qwlqgth7", + "addresses": ["bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"] + }, + { + "input": "wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)#8zl0zxma", + "addresses": ["bc1q0ht9tyks4vh7p5p904t340cr9nvahy7u3re7zg"] + } + ], + "pkh": [ + { + "input": "pkh([d34db33f/44'/0'/0']tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1'/1h)#hhj429qj", + "network": "testnet", + "error": "Private key not available for hardened derivation." + }, + { + "input": "pkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40)#qf72uex8", + "addresses": ["17yptZffncvv4xFa8EfkmAn8nwru5T79nQ"] + }, + { + "input": "pkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30h/40/*')", + "addresses": [ + "1PJSRWrRb4TVbCH7v2Zv7L1XLDxUptq4Rq", + "1GZzAjeG7QQbJkYaaibQfL4wpNi9SwX2GZ", + "12qMCKbNakCuUGXM8nBoFH5m2YoTAd2Zep", + "1FaQniN4w6Dg2PBuHcJJx5avd2WhCYP4RK", + "1NNAtXTBEDhdUoa5ZxTzELPiUqXDZ662rz", + "1L6WKakzyk5RW96aav4WvEQr9C9kmUzeFw", + "1Jxyz3wRgRFBAW33sJCnwYXJAKmzJner6p", + "1MXuo6XvjhDm3geKU4by2g3Asmaapng9Dt", + "1BjDtz8SwxN6p77Kgv8kvBdXQuWtiYWdSZ", + "1GWtiHeuJMR1K6Rkkexg4JAXkPbgzXMkGb", + "1C9j4byACwF1VNSRY6Ga8jBjirdxhQLgMC" + ], + "range": [0, 10] + }, + { + "input": "pkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*')", + "addresses": [ + "154MwpC33F5dMrejGLzhH2PoXgvZ6AhYK7", + "13cLMPFrPAKytcfqt54TFwrQh6pzgu9QJj", + "13KUq7LkWY9jyvKDfJ2nqggYtoWrDJaH57", + "12RwegYd6nJ8NYd79sR7N3QcFKxceVVAZ8", + "1F3G5AdM7nmFJVb1NNhjMQPonkYncwjHE4", + "1CmAWzQoiMwasUeHzJgS1PJkjwsZsdxJzA", + "1HKD33vx317qiGJgCmiPKf29hE36qBLuXA", + "18yu2p3UL99V474Xo7nSdUUjb7gmn3nprk", + "18RTmPva79rkXwhut1ZvsQhpK7KSfrjwSj", + "1DBvZS5x8L4XsLq7z87m9sCoskW5Fw3JwG", + "19Gp88ew8b91Y57A5KS4ckAEQSqQgDnx2r" + ], + "range": [0, 10] + }, + { + "input": "pkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*)", + "addresses": [ + "14c4nw5mpJ3s9Pg2gYRMkE4RraEy1sDjnK", + "16Q6JFoP25uxHCGJefvVgRxVzxgNPY83v9", + "1Q9p8nA4p4ci1AGAXhiBiXA35nq7PYJbER", + "1BXKKZuDrJuqznWoLrSNjxH5nPYCHiduJA", + "18Sk25tg6bhoFQn7uw5NsgkGwJNhXNS8US", + "19v9nL38bwjzHbp5Gz3Fs2VdM9gv8szXYH" + ], + "range": [0, 5] + }, + { + "input": "pkh(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)", + "addresses": ["1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH"] + }, + { + "input": "pkh([deadbeef/1/2h/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)#m40lc0fy", + "addresses": ["1F3sAm6ZtwLAUnj7d38pGFxtP3RVEvtsbV"] + }, + { + "input": "pkh([deadbeef/1h/2/3/4]03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)#vmgrp42f", + "addresses": ["1F3sAm6ZtwLAUnj7d38pGFxtP3RVEvtsbV"] + }, + { + "input": "pkh([deadbeef/1/2h/3/4h]03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)#v6qyyr0g", + "addresses": ["1F3sAm6ZtwLAUnj7d38pGFxtP3RVEvtsbV"] + }, + { + "input": "pkh([deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)#v0p5w8jl", + "addresses": ["1F3sAm6ZtwLAUnj7d38pGFxtP3RVEvtsbV"] + }, + { + "input": "pkh([01234567/10/20]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)#8ak5azvd", + "addresses": ["1NW82AiLUHSR7DSa1AwA72agkA5rgJrdcC"] + }, + { + "input": "pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)#8fhd9pwu", + "addresses": ["1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP"] + }, + { + "input": "pkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)#pcxrjydy", + "addresses": ["1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN"] + } + ], + "pk": [ + { + "input": "pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)#gn28ywm7", + "addresses": ["1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH"] + }, + { + "input": "pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)#tp6f86mw", + "addresses": ["1F3sAm6ZtwLAUnj7d38pGFxtP3RVEvtsbV"] + }, + { + "input": "pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)#gn28ywm7", + "addresses": ["1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH"] + }, + { + "input": "pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)#gn28ywm7", + "addresses": ["mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r"], + "network": "testnet" + } + ], + "raw": [ + { + "input": "raw(76a9145fcf526a4aa6a20ffa350310e59530cbeccc0e8188ac)#pj2nfx5g", + "addresses": ["19jbZCrh9gJGFpqpvaM1FYUtSgXe2p7qq2"] + }, + { + "input": "raw(160014a1a6c1de91960ea670edaba20b1cfdcd73127037)#dnudcv4v", + "error": "Descriptor does not have a corresponding address" + }, + { + "input": "raw(a9149a4d9901d6af519b2a23d4a2f51650fcba87ce7b87)#fj9v58cs", + "addresses": ["2N7K71Qaxd5zcgarLq9J1MFGWyjfrTtqwX6"], + "network": "regtest" + } + ], + "addr": [ + { + "input": "addr(bc1pu9s8vvvj6sqxtf26v0xhudgaqryyl3d642heejxrfphhumhgh9vsnugh93)#dg9g0rps", + "addresses": [ + "bc1pu9s8vvvj6sqxtf26v0xhudgaqryyl3d642heejxrfphhumhgh9vsnugh93" + ] + }, + { + "input": "addr(2N7K71Qaxd5zcgarLq9J1MFGWyjfrTtqwX6)#vgxqmle5", + "addresses": ["2N7K71Qaxd5zcgarLq9J1MFGWyjfrTtqwX6"], + "network": "regtest" + } + ] + } \ No newline at end of file diff --git a/test/descriptor-test.js b/test/descriptor-test.js index 13bcced59..4e10923de 100644 --- a/test/descriptor-test.js +++ b/test/descriptor-test.js @@ -13,6 +13,7 @@ const assert = require('bsert'); const parsable = require('./data/descriptors/desc-valid.json'); const unparsable = require('./data/descriptors/desc-invalid.json'); const privateKeyDescriptors = require('./data/descriptors/desc-privatekeys.json'); +const validAddresses = require('./data/descriptors/desc-addresses.json'); const common = require('../lib/descriptor/common'); const {parse} = require('../lib/descriptor/parser'); const PKDescriptor = require('../lib/descriptor/type/pk'); @@ -108,4 +109,29 @@ describe('Descriptor', () => { ); }); } + + for (const type in validAddresses) { + if (validAddresses.hasOwnProperty(type)) { + for (const data of validAddresses[type]) { + const {input, network, range, error} = data; + + it(`should derive addresses for ${input}`, () => { + const desc = parse(input, network); + const addresses = []; + + const l = range ? range[0] : 0; + const r = range ? range[1] : 0; + + try { + for (let i = l; i <= r; i++) { + addresses.push(...desc.getAddresses(i)); + } + assert.deepStrictEqual(addresses, data.addresses); + } catch (e) { + assert.strictEqual(e.message, error); + } + }); + } + } + } }); diff --git a/test/node-rpc-test.js b/test/node-rpc-test.js index 67c79f242..f9086463f 100644 --- a/test/node-rpc-test.js +++ b/test/node-rpc-test.js @@ -174,6 +174,66 @@ describe('RPC', function() { } }); + it('should rpc deriveaddresses', async () => { + const data = [ + { + "input": "sh(wsh(sortedmulti(2,[e7dd1c50/48'/1'/40'/1']tpubDFh3VaUEs71ZMcVBmscSSnP4f4r6TvnLssu8yXvpj3uMfAehciMYTrgbfu4KCxXb7oSaz4kriuWRZtQVhZR2oA9toob6aELnsYLN94fXQLF/*,[e7dd1c50/48'/1'/20'/1']tpubDFPemvLnpMqE1BPuturDUh46KxsR8wGSQrA6HofYE7fqxpMAKCcoYWHGA46B6zKY4xcQAc1vLFTcqQ9BvsbHZ4UhzqqF5nUeeNBjNivHxPT/*,[aedb3d12/48'/1'/0'/1']tpubDEbuxto5Kftus28NyPddiEev2yUhzZGpkpQdCK732KBge5FJDhaMdhG1iVw3rMJ2qvABkaLR9HxobkeFkmQZ4RqQgN1KJadDjPn9ANBLo8V/*)))#zlh5y6z5", + "range": [0, "a"], + "error": "Range end must be an integer" + }, + { + "input": "sh(wsh(sortedmulti(2,[e7dd1c50/48'/1'/40'/1']tpubDFh3VaUEs71ZMcVBmscSSnP4f4r6TvnLssu8yXvpj3uMfAehciMYTrgbfu4KCxXb7oSaz4kriuWRZtQVhZR2oA9toob6aELnsYLN94fXQLF/*,[e7dd1c50/48'/1'/20'/1']tpubDFPemvLnpMqE1BPuturDUh46KxsR8wGSQrA6HofYE7fqxpMAKCcoYWHGA46B6zKY4xcQAc1vLFTcqQ9BvsbHZ4UhzqqF5nUeeNBjNivHxPT/*,[aedb3d12/48'/1'/0'/1']tpubDEbuxto5Kftus28NyPddiEev2yUhzZGpkpQdCK732KBge5FJDhaMdhG1iVw3rMJ2qvABkaLR9HxobkeFkmQZ4RqQgN1KJadDjPn9ANBLo8V/*)))#zlh5y6z5", + "range": [10, 0], + "error": "Range specified as [begin,end] must not have begin after end" + }, + { + "input": "sh(wsh(sortedmulti(2,[e7dd1c50/48'/1'/40'/1']tpubDFh3VaUEs71ZMcVBmscSSnP4f4r6TvnLssu8yXvpj3uMfAehciMYTrgbfu4KCxXb7oSaz4kriuWRZtQVhZR2oA9toob6aELnsYLN94fXQLF/*,[e7dd1c50/48'/1'/20'/1']tpubDFPemvLnpMqE1BPuturDUh46KxsR8wGSQrA6HofYE7fqxpMAKCcoYWHGA46B6zKY4xcQAc1vLFTcqQ9BvsbHZ4UhzqqF5nUeeNBjNivHxPT/*,[aedb3d12/48'/1'/0'/1']tpubDEbuxto5Kftus28NyPddiEev2yUhzZGpkpQdCK732KBge5FJDhaMdhG1iVw3rMJ2qvABkaLR9HxobkeFkmQZ4RqQgN1KJadDjPn9ANBLo8V/*)))#zlh5y6z5", + "range": [-1, 2], + "error": "Range should be >= 0" + }, + { + "input": "sh(wsh(sortedmulti(2,[e7dd1c50/48'/1'/40'/1']tpubDFh3VaUEs71ZMcVBmscSSnP4f4r6TvnLssu8yXvpj3uMfAehciMYTrgbfu4KCxXb7oSaz4kriuWRZtQVhZR2oA9toob6aELnsYLN94fXQLF/*,[e7dd1c50/48'/1'/20'/1']tpubDFPemvLnpMqE1BPuturDUh46KxsR8wGSQrA6HofYE7fqxpMAKCcoYWHGA46B6zKY4xcQAc1vLFTcqQ9BvsbHZ4UhzqqF5nUeeNBjNivHxPT/*,[aedb3d12/48'/1'/0'/1']tpubDEbuxto5Kftus28NyPddiEev2yUhzZGpkpQdCK732KBge5FJDhaMdhG1iVw3rMJ2qvABkaLR9HxobkeFkmQZ4RqQgN1KJadDjPn9ANBLo8V/*)))#zlh5y6z5", + "error": "Range must be specified for ranged descriptor" + }, + { + "input": "sh(wsh(sortedmulti(2,[e7dd1c50/48'/1'/40'/1']tpubDFh3VaUEs71ZMcVBmscSSnP4f4r6TvnLssu8yXvpj3uMfAehciMYTrgbfu4KCxXb7oSaz4kriuWRZtQVhZR2oA9toob6aELnsYLN94fXQLF/*,[e7dd1c50/48'/1'/20'/1']tpubDFPemvLnpMqE1BPuturDUh46KxsR8wGSQrA6HofYE7fqxpMAKCcoYWHGA46B6zKY4xcQAc1vLFTcqQ9BvsbHZ4UhzqqF5nUeeNBjNivHxPT/*,[aedb3d12/48'/1'/0'/1']tpubDEbuxto5Kftus28NyPddiEev2yUhzZGpkpQdCK732KBge5FJDhaMdhG1iVw3rMJ2qvABkaLR9HxobkeFkmQZ4RqQgN1KJadDjPn9ANBLo8V/*)))#zlh5y6z5", + "range": 10, + "addresses": [ + "2MtWBjxiAi4xYNUdtDe2sHkNw5kdAQqZZNb", + "2MstQfXgUwTR66bhMrbNU3qDqT6RT4hGHnJ", + "2N6QXTyf64KHWddFZ5swjaRmEwk4hEawYuo", + "2NCfXGhiA6EjK6o2JSejtzeP5fYkNxP1TQC", + "2NCU5HcmKUap923abUPEhGNnTFUf3K2hAYr", + "2N6K6jKKeuejPTeiPDbnq1qZsqGhigoApzK", + "2N4jy9MPJee7WvH3tfRVE3LeYQxVNhjt2yH", + "2NCXTtRLCjwWPeUoRz5qGoKSj84Ci4pbcWy", + "2NEwEq98wFu1EcSf5jFexCRZEuGjjeTo265", + "2MzcruaPLyHniY1qjJLXLXEuYRd5PJ1o1EW", + "2MuY8izcxH5KJfhvc432HQVos483krppmrf" + ] + }, + { + "input": "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)#9907vvwz", + "range": [0, 5], + "error": "Range should not be specified for un-ranged descriptor" + }, + { + "input":"pkh([d34db33f/44h/0h/0h]tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1h/1h/*h)#u5f4r0y7", + "range": [1, 5], + "error": "Private key not available for hardened derivation." + } + ]; + + for (const test of data) { + try { + const result = test.range ? await nclient.execute("deriveaddresses", [test.input, test.range]) : await nclient.execute("deriveaddresses", [test.input]); + assert.deepStrictEqual(result, test.addresses); + } catch (e) { + assert.strictEqual(e.message, test.error); + } + } + }); + it('should rpc getblockhash', async () => { const info = await nclient.execute('getblockhash', [node.chain.tip.height]); assert.strictEqual(util.revHex(node.chain.tip.hash), info); diff --git a/test/script-test.js b/test/script-test.js index 9623168c5..f497e1a44 100644 --- a/test/script-test.js +++ b/test/script-test.js @@ -10,8 +10,9 @@ const Stack = require('../lib/script/stack'); const Opcode = require('../lib/script/opcode'); const TX = require('../lib/primitives/tx'); const consensus = require('../lib/protocol/consensus'); -const {fromFloat} = require('../lib/utils/fixed'); +const {HDPrivateKey} = require('../lib/hd'); +const {fromFloat} = require('../lib/utils/fixed'); // test files: https://github.com/bitcoin/bitcoin/tree/master/src/test/data const scripts = require('./data/core-data/script-tests.json'); @@ -345,4 +346,45 @@ describe('Script', function() { }); } } + + for (const isSorted of [true, false]) { + for (const isWitness of [true, false]) { + for (let n = 1; n <= 22; n++) { + for (let m = 1; m <= n; m++) { + it(`should handle script generation for ${n} keys. (${m} of ${n}) (isSorted = ${isSorted}) (isWitness = ${isWitness})`, () => { + const keys = []; + + for (let i = 0; i < n; i++) { + keys.push(HDPrivateKey.generate().publicKey); + } + + let script; + let error; + + try { + script = Script.fromMultisig(m, n, keys, isSorted, isWitness).toRaw().toString('hex'); + } catch (e) { + error = e; + } + + if (n > 20 || (n > 15 && !isWitness)) { + assert(error); + assert(error.message === (n > 20 ? `${n} keys not allowed in script. Max allowed: 20` : 'Script size is too large')); + } else { + if (isSorted) { + keys.sort((a, b) => a.compare(b)); + } + + const expectedScript = Opcode.fromInt(m).toRaw().toString('hex') // threshold + + keys.map(key => '21' + key.toString('hex')).join('') // keys + + Opcode.fromInt(n).toRaw().toString('hex') // number of keys + + 'ae'; // OP_CHECKMULTISIG + + assert(script === expectedScript); + } + }); + } + } + } + } }); diff --git a/test/wallet-test.js b/test/wallet-test.js index 3deb93e4d..154a6dc53 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -1501,6 +1501,46 @@ describe('Wallet', function() { }); } + for (const witness of [true, false]) { + it(`should handle multisig account for keys greater than 15 when witness is ${witness} `, async () => { + let wallet; + let error; + + try { + wallet = await wdb.create({ + type: 'multisig', + m: 15, + n: 18, + witness + }); + } catch (e) { + error = e; + } + + if (!witness) { + assert(error); + assert(error.message === 'n ranges between 1 and 15'); + } else { + const keys = []; + + for (let i = 1; i <= 17; i++) { + const xpriv = HD.PrivateKey.generate(); + let key = xpriv.deriveAccount(44, 0, 0).toPublic(); + await wallet.addSharedKey(0, key); + key = key.derivePath('m/0/0').publicKey; + keys.push(key); + } + + keys.push((await wallet.receiveKey()).publicKey); + const walletReceiveAddress = await wallet.receiveAddress(); + const script = Script.fromMultisig(15, 18, keys, true, true); + const expectedAddress = Script.fromProgram(0, script.sha256()).getAddress(); + + assert.deepStrictEqual(expectedAddress, walletReceiveAddress); + } + }); + } + it('should get range of txs', async () => { const wallet = currentWallet; const txs = await wallet.getRange(null, {