From e69cb9fbdc9f4bccccd811281b20878555a642ac Mon Sep 17 00:00:00 2001 From: Vasu08 <20bcs241@iiitdmj.ac.in> Date: Tue, 18 Jul 2023 02:10:50 +0530 Subject: [PATCH] feat: added deriveaddresses rpc --- lib/node/rpc.js | 85 ++++++++++++++++++++++++++++++++++++++++++- test/node-rpc-test.js | 70 +++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 2 deletions(-) diff --git a/lib/node/rpc.js b/lib/node/rpc.js index ab67affb9..dbbe0a535 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); } /* @@ -2346,10 +2347,90 @@ 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"'); + + const valid = new Validator(args); + + const desc = parseDescriptor(valid.str(0, ''), this.network, true); + + 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 (innerErr) { + 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 address = desc.getAddresses(i); + + 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 = 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/test/node-rpc-test.js b/test/node-rpc-test.js index cdef5b4dd..59286711e 100644 --- a/test/node-rpc-test.js +++ b/test/node-rpc-test.js @@ -174,6 +174,76 @@ 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,2", + "error": "Range must be specified as end or as [begin,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": [0, "10"], + "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": ["0", 10], + "error": "Range begin 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": "Cannot derive script without private keys" + } + ]; + + 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);