Skip to content

Commit

Permalink
feat<node>: added deriveaddresses rpc
Browse files Browse the repository at this point in the history
  • Loading branch information
Vasu-08 committed Aug 4, 2023
1 parent f7dca8c commit 82f8c8d
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 2 deletions.
92 changes: 90 additions & 2 deletions lib/node/rpc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/*
Expand Down Expand Up @@ -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.');
Expand Down Expand Up @@ -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}`
Expand Down
60 changes: 60 additions & 0 deletions test/node-rpc-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 82f8c8d

Please sign in to comment.