From 5ad885fae18beb829766b471573159f1b2d0c5d5 Mon Sep 17 00:00:00 2001 From: Vasu08 <20bcs241@iiitdmj.ac.in> Date: Sun, 9 Jul 2023 19:37:25 +0530 Subject: [PATCH] feat: added scripts and addresses generation functions --- lib/descriptor/abstractdescriptor.js | 50 +++ lib/descriptor/keyprovider.js | 81 +++- lib/descriptor/type/addr.js | 13 + lib/descriptor/type/combo.js | 26 ++ lib/descriptor/type/multisig.js | 25 ++ lib/descriptor/type/pk.js | 14 + lib/descriptor/type/pkh.js | 16 + lib/descriptor/type/raw.js | 9 + lib/descriptor/type/sh.js | 15 +- lib/descriptor/type/wpkh.js | 16 + lib/descriptor/type/wsh.js | 14 + lib/hd/common.js | 2 +- lib/node/rpc.js | 99 ++++- lib/script/script.js | 11 +- test/data/descriptors/desc-addresses.json | 487 ++++++++++++++++++++++ test/descriptor-test.js | 31 ++ test/node-rpc-test.js | 31 ++ 17 files changed, 924 insertions(+), 16 deletions(-) create mode 100644 test/data/descriptors/desc-addresses.json diff --git a/lib/descriptor/abstractdescriptor.js b/lib/descriptor/abstractdescriptor.js index 450fa1e4b..6c883d4bf 100644 --- a/lib/descriptor/abstractdescriptor.js +++ b/lib/descriptor/abstractdescriptor.js @@ -10,6 +10,7 @@ const assert = require('bsert'); const common = require('./common'); const Network = require('../protocol/network'); const KeyProvider = require('./keyprovider'); +const Address = require('../primitives/address'); /** * Constants @@ -210,6 +211,55 @@ class AbstractDescriptor { isSingleType() { return true; } + + /** + * Get scripts for the descriptor at a specified position. + * @param {Number} pos + * @returns {Scripts[]} + */ + + generateScripts(pos) { + const pubkeys = []; + let subscript = null; + + for (const subdesc of this.subdescriptors) { + subscript = subdesc.generateScripts(pos)[0]; + } + + for (const provider of this.keyProviders) { + const pubkey = provider.getDerivedPublicKey(pos); + assert(pubkey, 'Cannot derive script without private keys'); + pubkeys.push(pubkey); + } + + return this.getScripts(pubkeys, subscript); + } + + /** + * Get the scriptsig(s). + * @returns {Scripts[]} + */ + + getScripts() { + return []; + } + + /** + * Derive addresses from scripts. + * @param {Scripts[]} scripts + * @returns {Address[]} + */ + + getAddresses(scripts) { + const addresses = []; + for (const script of scripts) { + const address = Address.fromScript(script, this.network); + 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 1de70088b..d5efd0fe0 100644 --- a/lib/descriptor/keyprovider.js +++ b/lib/descriptor/keyprovider.js @@ -11,6 +11,7 @@ const HD = require('../hd/hd'); const KeyRing = require('../primitives/keyring'); const {scriptContext, isHex} = require('./common'); const Network = require('../protocol/network'); +// const hash160 = require('bcrypto/lib/hash160'); /** * Constants @@ -272,6 +273,14 @@ class KeyProvider { hasPrivateKey() { return this.ring.privateKey !== null; } + + /** + * Derive the public key + */ + + getPublicKey() { + return null; + } } /** @@ -326,6 +335,15 @@ class ConstKeyProvider extends KeyProvider { } return null; } + + /** + * Derive the public key. + * @param {KeyOriginInfo} info + */ + + getDerivedPublicKey(pos) { + return this.ring.publicKey; + } } /** @@ -423,6 +441,67 @@ class HDKeyProvider extends KeyProvider { } return null; } + + /** + * Derive tha last xpriv key. + * @returns {HDPrivateKey} + */ + + getDerivedPrivateKey(pos) { + if (!this.hdkey.privateKey) { + return null; + } + + 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 {Buffer} + */ + + getDerivedPublicKey(pos) { + if (this.isHardened()) { + const childprivkey = this.getDerivedPrivateKey(pos); + if (childprivkey) { + return childprivkey.publicKey; + } + return null; + } + + 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.publicKey; + } } /** @@ -440,7 +519,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 1784a5314..613c0c2bb 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 @@ -120,6 +121,18 @@ class AddressDescriptor extends AbstractDescriptor { isSolvable() { return false; } + + /** + * Get scriptPubkey(s) for the descriptor. + * @returns {Script[]} + */ + + getScripts() { + const scripts = []; + const script = Script.fromAddress(this.address); + scripts.push(script); + return scripts; + } } module.exports = AddressDescriptor; diff --git a/lib/descriptor/type/combo.js b/lib/descriptor/type/combo.js index d0368ba7d..242fe7b9c 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 @@ -111,6 +113,30 @@ class ComboDescriptor extends AbstractDescriptor { isSolvable() { return true; } + + /** + * Get scriptPubkey(s) for the descriptor. + * @param {Buffer[]} pubkeys + * @returns {Script[]} + */ + + getScripts(pubkeys) { + 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; + } } module.exports = ComboDescriptor; diff --git a/lib/descriptor/type/multisig.js b/lib/descriptor/type/multisig.js index d7cb4d953..cad40d256 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 @@ -163,6 +164,30 @@ class MultisigDescriptor extends AbstractDescriptor { toStringExtra() { return this.threshold.toString(); } + + /** + * Get scriptPubkey(s) for the descriptor. + * @param {Buffer[]} pubkeys + * @returns {Script[]} + */ + + getScripts(pubkeys) { + const scripts = []; + const m = this.threshold; + const n = pubkeys.length; + scripts.push(Script.fromMultisig(m, n, pubkeys, this.isSorted)); + return scripts; + } + + /** + * Derive addresses from scripts. + * @param {Scripts[]} 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 5657fd2fa..90c0cd6b9 100644 --- a/lib/descriptor/type/pk.js +++ b/lib/descriptor/type/pk.js @@ -12,6 +12,7 @@ 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 @@ -102,6 +103,19 @@ class PKDescriptor extends AbstractDescriptor { static fromString(str, network, context = scriptContext.TOP) { return new this().fromString(str, network, context); } + + /** + * Get scriptPubkey(s) for the descriptor. + * @param {Buffer[]} pubkeys + * @returns {Script[]} + */ + + getScripts(pubkeys) { + const scripts = []; + const script = Script.fromPubkey(pubkeys[0]); + scripts.push(script); + return scripts; + } } module.exports = PKDescriptor; diff --git a/lib/descriptor/type/pkh.js b/lib/descriptor/type/pkh.js index b2c0b7470..0d0466458 100644 --- a/lib/descriptor/type/pkh.js +++ b/lib/descriptor/type/pkh.js @@ -12,6 +12,8 @@ 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 @@ -108,6 +110,20 @@ class PKHDescriptor extends AbstractDescriptor { static fromString(str, network, context = scriptContext.TOP) { return new this().fromString(str, network, context); } + + /** + * Get scriptPubkey(s) for the descriptor. + * @param {Buffer[]} pubkeys + * @returns {Script[]} + */ + + getScripts(pubkeys) { + const scripts = []; + const pubkeyhash = hash160.digest(pubkeys[0]); + const script = Script.fromPubkeyhash(pubkeyhash); + scripts.push(script); + return scripts; + } } module.exports = PKHDescriptor; diff --git a/lib/descriptor/type/raw.js b/lib/descriptor/type/raw.js index 342fc2f82..0c2c251b5 100644 --- a/lib/descriptor/type/raw.js +++ b/lib/descriptor/type/raw.js @@ -117,6 +117,15 @@ class RawDescriptor extends AbstractDescriptor { toStringExtra() { return this.script.toJSON(); } + + /** + * Get scriptPubkey(s) for the descriptor. + * @returns {Script[]} + */ + + getScripts() { + return [this.script]; + } } module.exports = RawDescriptor; diff --git a/lib/descriptor/type/sh.js b/lib/descriptor/type/sh.js index fc3ad9060..013e4f5d9 100644 --- a/lib/descriptor/type/sh.js +++ b/lib/descriptor/type/sh.js @@ -15,8 +15,8 @@ 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 @@ -154,6 +154,19 @@ class SHDescriptor extends AbstractDescriptor { return validSubTypes.includes(name); } + + /** + * Get scriptPubkey(s) for the descriptor. + * @param {Buffer[]} pubkeys + * @param {Script} script + * @returns {Script[]} + */ + + getScripts(pubkeys, script) { + const scripts = []; + scripts.push(Script.fromScripthash(script.hash160())); + return scripts; + } } module.exports = SHDescriptor; diff --git a/lib/descriptor/type/wpkh.js b/lib/descriptor/type/wpkh.js index 6b657ebfd..335ade75c 100644 --- a/lib/descriptor/type/wpkh.js +++ b/lib/descriptor/type/wpkh.js @@ -12,6 +12,8 @@ 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 @@ -107,6 +109,20 @@ class WPKHDescriptor extends AbstractDescriptor { static fromString(str, network, context = scriptContext.TOP) { return new this().fromString(str, network, context); } + + /** + * Get scriptPubkey(s) for the descriptor. + * @param {Buffer[]} pubkeys + * @returns {Script[]} + */ + + getScripts(pubkeys) { + const scripts = []; + const pubkeyhash = hash160.digest(pubkeys[0]); + const script = Script.fromProgram(0, pubkeyhash); + scripts.push(script); + return scripts; + } } module.exports = WPKHDescriptor; diff --git a/lib/descriptor/type/wsh.js b/lib/descriptor/type/wsh.js index 834b03224..11ae25ddb 100644 --- a/lib/descriptor/type/wsh.js +++ b/lib/descriptor/type/wsh.js @@ -14,6 +14,7 @@ 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 @@ -145,6 +146,19 @@ class WSHDescriptor extends AbstractDescriptor { return validSubTypes.includes(name); } + + /** + * Get scriptPubkey(s) for the descriptor.. + * @param {Buffer[]} pubkeys + * @param {Script} script + * @returns {Script[]} + */ + + getScripts(pubkeys, script) { + const scripts = []; + scripts.push(Script.fromProgram(0, script.sha256())); + return scripts; + } } module.exports = WSHDescriptor; diff --git a/lib/hd/common.js b/lib/hd/common.js index 940fdbfd3..001c26252 100644 --- a/lib/hd/common.js +++ b/lib/hd/common.js @@ -45,7 +45,7 @@ common.cache = new LRU(500); * Parse a derivation path. * @param {Array} path * @param {Boolean} hard - * @returns {Number[]} + * @returns {Array} */ common.parsePathToArray = function parsePathToArray(path, hard) { diff --git a/lib/node/rpc.js b/lib/node/rpc.js index ab67affb9..f782d963e 100644 --- a/lib/node/rpc.js +++ b/lib/node/rpc.js @@ -133,11 +133,11 @@ class RPC extends RPCBase { handleCall(cmd, query) { if (cmd.method !== 'getwork' - && cmd.method !== 'getblocktemplate' - && cmd.method !== 'getbestblockhash') { + && cmd.method !== 'getblocktemplate' + && cmd.method !== 'getbestblockhash') { this.logger.debug('Handling RPC call: %s.', cmd.method); if (cmd.method !== 'submitblock' - && cmd.method !== 'getmemorypool') { + && cmd.method !== 'getmemorypool') { this.logger.debug(cmd.params); } } @@ -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); } /* @@ -519,8 +520,8 @@ class RPC extends RPCBase { const action = valid.str(1, ''); if (help - || args.length < 2 - || (action !== 'add' && action !== 'remove')) { + || args.length < 2 + || (action !== 'add' && action !== 'remove')) { throw new RPCError(errs.MISC_ERROR, 'setban "ip(/netmask)" "add|remove" (bantime) (absolute)'); } @@ -1162,7 +1163,7 @@ class RPC extends RPCBase { const header = Headers.fromHead(raw); if (header.prevBlock !== attempt.prevBlock - || header.bits !== attempt.bits) { + || header.bits !== attempt.bits) { return false; } @@ -2333,7 +2334,7 @@ class RPC extends RPCBase { const valid = new Validator(args); const desc = valid.str(0, ''); - const descriptor = parseDescriptor(desc, this.network); + const descriptor = parseDescriptor(desc, this.network, false); const result = { descriptor: descriptor.toString(), @@ -2346,10 +2347,90 @@ class RPC extends RPCBase { return result; } + async deriveAddresses(args, help) { + if (help || args.length > 2) + throw new RPCError(errs.MISC_ERROR, 'deriveaddresses "descriptor"'); + + const valid = new Validator(args); + const desc = parseDescriptor(valid.str(0, ''), this.network); + + if (desc.isRange() && args.length === 1) { + throw new RPCError( + errs.INVALID_PARAMETER, + 'Range must be specified for ranged descriptor' + ); + } + + if (!desc.isRange() && args.length > 1) { + throw new RPCError( + errs.INVALID_PARAMETER, + 'Range should not be specified for un-ranged descriptor' + ); + } + + let low = 0, high = 0; + + 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, + 'Range must be specified as end or as [begin,end]' + ); + } + } + + this.isValidRange(low, high); + + const addresses = []; + + for (let i = low; i <= high; i++) { + const scripts = desc.generateScripts(i); + const address = desc.getAddresses(scripts); + + for (let j = 0; j < address.length; j++) { + addresses.push(address[j]); + }; + } + + return addresses; + } + /* * Helpers */ + isValidRange(low, high) { + assert(Number.isInteger(low), 'Range begin must be an integer'); + assert(Number.isInteger(high), '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 >> 31) !== 0) { + 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 +2965,9 @@ function parseAddress(raw, network) { } } -function parseDescriptor(raw, network) { +function parseDescriptor(raw, network, requireChecksum = true) { 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..eb3773602 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -1480,9 +1480,11 @@ class Script { * @param {Number} m * @param {Number} n * @param {Buffer[]} keys + * @param {Boolean?} isSorted + * @returns {Script} */ - fromMultisig(m, n, keys) { + fromMultisig(m, n, keys, isSorted = true) { assert((m & 0xff) === m && (n & 0xff) === n); assert(Array.isArray(keys)); assert(keys.length === n, '`n` keys are required for multisig.'); @@ -1493,7 +1495,7 @@ class Script { this.pushSmall(m); - for (const key of sortKeys(keys)) + for (const key of isSorted ? sortKeys(keys) : keys) this.pushData(key); this.pushSmall(n); @@ -1507,11 +1509,12 @@ class Script { * @param {Number} m * @param {Number} n * @param {Buffer[]} keys + * @param {Boolean?} isSorted * @returns {Script} */ - static fromMultisig(m, n, keys) { - return new this().fromMultisig(m, n, keys); + static fromMultisig(m, n, keys, isSorted = true) { + return new this().fromMultisig(m, n, keys, isSorted); } /** diff --git a/test/data/descriptors/desc-addresses.json b/test/data/descriptors/desc-addresses.json new file mode 100644 index 000000000..8194d800d --- /dev/null +++ b/test/data/descriptors/desc-addresses.json @@ -0,0 +1,487 @@ +{ + "multisig": [ + { + "input": "multi(1,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1)", + "error": "Descriptor does not have a corresponding address", + "network": "regtest" + } + ], + "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" + ] + } + ], + "sh": [ + { + "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"] + } + ], + "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": "Cannot derive script without private keys" + }, + { + "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" + } + ], + "addr": [ + { + "input": "addr(bc1pu9s8vvvj6sqxtf26v0xhudgaqryyl3d642heejxrfphhumhgh9vsnugh93)#dg9g0rps", + "addresses": [ + "bc1pu9s8vvvj6sqxtf26v0xhudgaqryyl3d642heejxrfphhumhgh9vsnugh93" + ] + } + ] +} diff --git a/test/descriptor-test.js b/test/descriptor-test.js index d647c4b77..3c115c662 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'); @@ -116,4 +117,34 @@ 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++) { + const scripts = desc.generateScripts(i); + const address = desc.getAddresses(scripts); + + for (let j = 0; j < address.length; j++) { + addresses.push(address[j]); + }; + } + 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 cdef5b4dd..cb5e5e1ba 100644 --- a/test/node-rpc-test.js +++ b/test/node-rpc-test.js @@ -174,6 +174,37 @@ 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", + "addresses": [ + "2MtWBjxiAi4xYNUdtDe2sHkNw5kdAQqZZNb", + "2MstQfXgUwTR66bhMrbNU3qDqT6RT4hGHnJ", + "2N6QXTyf64KHWddFZ5swjaRmEwk4hEawYuo", + "2NCfXGhiA6EjK6o2JSejtzeP5fYkNxP1TQC", + "2NCU5HcmKUap923abUPEhGNnTFUf3K2hAYr", + "2N6K6jKKeuejPTeiPDbnq1qZsqGhigoApzK", + "2N4jy9MPJee7WvH3tfRVE3LeYQxVNhjt2yH", + "2NCXTtRLCjwWPeUoRz5qGoKSj84Ci4pbcWy", + "2NEwEq98wFu1EcSf5jFexCRZEuGjjeTo265", + "2MzcruaPLyHniY1qjJLXLXEuYRd5PJ1o1EW", + "2MuY8izcxH5KJfhvc432HQVos483krppmrf" + ], + "range": [0, 10] + } + ]; + + 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);