Skip to content

Commit

Permalink
wallet rpc: add getdescriptorinfo rpc command
Browse files Browse the repository at this point in the history
  • Loading branch information
Vasu-08 committed May 23, 2023
1 parent e5d2968 commit 918ed8c
Show file tree
Hide file tree
Showing 10 changed files with 667 additions and 10 deletions.
2 changes: 2 additions & 0 deletions lib/hd/hd.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const common = require('./common');
const Mnemonic = require('./mnemonic');
const HDPrivateKey = require('./private');
const HDPublicKey = require('./public');
const KeyOriginInfo = require('./keyorigin');
const wordlist = require('./wordlist');

/**
Expand Down Expand Up @@ -182,4 +183,5 @@ HD.PrivateKey = HDPrivateKey;
HD.PublicKey = HDPublicKey;
HD.HDPrivateKey = HDPrivateKey;
HD.HDPublicKey = HDPublicKey;
HD.KeyOriginInfo = KeyOriginInfo;
HD.wordlist = wordlist;
179 changes: 179 additions & 0 deletions lib/hd/keyorigin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/* eslint-disable max-len */
'use strict';

const assert = require('assert');
const common = require('./common');
const bio = require('bufio');
const hash160 = require('bcrypto/lib/hash160');
const WalletKey = require('../wallet/walletkey');

/**
* Helper class to represent hd key path for arbitrary wallets.
* @property {Number} fingerPrint - master key fingerprint (uint32)
* @property {Array} path - bip32 derivation path in uint32 array
*/
class KeyOriginInfo {
constructor(options) {
this.fingerPrint = -1;
this.path = [];
if (options) {
this.fromOptions(options);
}
}

fromOptions(options) {
assert(options, 'requires options');

if (options.fingerPrint !== undefined) {
assert(
(options.fingerPrint >>> 0) === options.fingerPrint &&
options.fingerPrint <= 0xFFFFFFFF,
'fingerPrint must be uint32'
);
this.fingerPrint = options.fingerPrint;
}

if (options.path !== undefined) {
if (Array.isArray(options.path)) {
assert(
options.path.every(p => (p >>> 0) === p && p <= 0xFFFFFFFF),
'all path index must be uint32'
);
this.path = options.path;
} else {
this.path = common.parsePath(options.path, true);
}
}
return this;
}

static fromOptions(options) {
return new this().fromOptions(options);
}

equals(keyInfo) {
assert(KeyOriginInfo.isKeyOriginInfo(keyInfo));
if (this.fingerPrint !== keyInfo.fingerPrint)
return false;
assert(this.path.length === keyInfo.path.length);
for (let i = 0; i < this.path.length; i++) {
if (this.path[i] !== keyInfo.path[i])
return false;
}
return true;
}

inspect() {
return this.format();
}

format() {
let path = 'm';
for (const p of this.path) {
const hardened = (p & common.HARDENED) ? '\'' : '';
path += `/${p & 0x7fffffff}${hardened}`;
}
return {
fingerPrint: this.fingerPrint,
path
};
}

static fromRaw(data) {
return new this().fromRaw(data);
}

fromRaw(data) {
return this.fromReader(bio.read(data));
};

static fromReader(br) {
return new this().fromReader(br);
}

fromReader(br) {
this.fingerPrint = br.readU32BE();
while (br.left()) {
this.path.push(br.readU32());
}
return this;
}

toRaw() {
const size = this.getSize();
return this.toWriter(bio.write(size)).render();
}

toWriter(bw) {
bw.writeU32BE(this.fingerPrint);
for (const p of this.path) {
bw.writeU32(p);
}
return bw;
}

toJSON() {
return {
fingerPrint: this.fingerPrint.toString(16),
path: this.format().path
};
}

static fromJSON(json) {
return new this().fromJSON(json);
}

fromJSON(json) {
if (json.fingerPrint) {
assert((json.fingerPrint >>> 0) === json.fingerPrint);
this.fingerPrint = json.fingerPrint;
}

if (json.path) {
if (Array.isArray(json.path) && json.path.length > 0) {
for (const p of json.path) {
assert((p >>> 0) === p);
this.path.push(p);
}
} else {
this.path = common.parsePath(json.path, true);
}
}

return this;
}

static isKeyOriginInfo(obj) {
return obj instanceof KeyOriginInfo;
}

clone() {
const path = this.path.slice();
return new KeyOriginInfo({ fingerPrint: this.fingerPrint, path });
}

clear() {
this.fingerPrint = -1;
this.path = [];
}

getSize() {
return 4 + this.path.length * 4;
}

static fromWalletKey(wk) {
return new this().fromWalletKey(wk);
}

fromWalletKey(wk) {
assert(WalletKey.isWalletKey(wk));
const fp = hash160.digest(wk.publicKey);
this.fingerPrint = fp.readUInt32BE(0, true);
this.path.push(wk.account | common.HARDENED);
this.path.push(wk.branch);
this.path.push(wk.index);
return this;
}
}

module.exports = KeyOriginInfo;
52 changes: 52 additions & 0 deletions lib/utils/stringparsing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/* eslint-disable max-len */
'use strict';
const secp256k1 = require('bcrypto/lib/secp256k1');
const assert = require('bsert');

exports.checkScriptType = function checkScriptType(str, s) {
if (s.length >= str.length + 2 && s[str.length] === '(' && s[s.length - 1] === ')' && str === s.substring(0, str.length)) {
// s = s.substring(str.length + 1, s.length - 1);
return true;
}
return false;
};

exports.stringAfterScriptType = function stringAfterScriptType(str, s) {
s = s.substring(str.length + 1, s.length - 1);
return s;
};

exports.giveExpr = function giveExpr(str) {
let level = 0;
let it = 0;

while (it < str.length) {
if (str[it] === '(' || str[it] === '{') {
level++;
} else if (level && (str[it] === ')' || str[it] === '}')) {
level--;
} else if (level === 0 && (str[it] === ')' || str[it] === '}' || str[it] === ',')) {
break;
}
it++;
}

const ret = str.slice(0, it);
// str = str.slice(it);
return ret;
};

// eslint-disable-next-line valid-jsdoc
/**
* @TODO write tests
*/
exports.isHex = function isHex(str) {
const regexp = /^[0-9a-fA-F]+$/;
return regexp.test(str);
};

exports.isCompressed = function isCompressed(key) {
assert(secp256k1.publicKeyVerify(key), 'Invalid public key');
const iscompressed = key.length === 33 && key[0] === 0x02 || key[0] === 0x03;
return iscompressed;
};
Loading

0 comments on commit 918ed8c

Please sign in to comment.