Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds deriveaddresses rpc #1162

Merged
merged 5 commits into from
Aug 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions lib/descriptor/abstractdescriptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
* (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
*/

Expand All @@ -52,6 +53,7 @@
this.type = null;
this.keyProviders = [];
this.subdescriptors = [];
this.scriptContext = common.scriptContext.TOP;
this.network = null;
}

Expand Down Expand Up @@ -232,6 +234,58 @@
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.');

Check warning on line 268 in lib/descriptor/abstractdescriptor.js

View check run for this annotation

Codecov / codecov/patch

lib/descriptor/abstractdescriptor.js#L268

Added line #L268 was not covered by tests
}

/**
* 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');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in what case would a descriptor not have an address?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In few cases of raw descriptor and for multisig descriptor.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this because raw() and multi() get interpreted as raw output script at the top level? but if they are wrapped by sh() then they could have addresses

addresses.push(address.toString(this.network));
}

return addresses;
}
}

/*
Expand Down
101 changes: 100 additions & 1 deletion lib/descriptor/keyprovider.js
Vasu-08 marked this conversation as resolved.
Show resolved Hide resolved
Vasu-08 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,24 @@
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;

Check warning on line 282 in lib/descriptor/keyprovider.js

View check run for this annotation

Codecov / codecov/patch

lib/descriptor/keyprovider.js#L282

Added line #L282 was not covered by tests
}
}

/**
Expand Down Expand Up @@ -407,6 +425,87 @@
}
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);

Check warning on line 448 in lib/descriptor/keyprovider.js

View check run for this annotation

Codecov / codecov/patch

lib/descriptor/keyprovider.js#L448

Added line #L448 was not covered by tests
} 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;

Check warning on line 507 in lib/descriptor/keyprovider.js

View check run for this annotation

Codecov / codecov/patch

lib/descriptor/keyprovider.js#L506-L507

Added lines #L506 - L507 were not covered by tests
}
}

/**
Expand All @@ -424,7 +523,7 @@

let hardenedMarker = null;
for (const p of path) {
const last = p[p.length - 1];
const last = p[p.length - 1];
Vasu-08 marked this conversation as resolved.
Show resolved Hide resolved
if (last === '\'' || last === 'h') {
hardenedMarker = last;
}
Expand Down
11 changes: 11 additions & 0 deletions lib/descriptor/type/addr.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ 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
* Represents the output script produced by the address in the descriptor.
* @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
*/
Expand Down Expand Up @@ -118,6 +120,15 @@ class AddressDescriptor extends AbstractDescriptor {
isSolvable() {
return false;
}

/**
* Get the scripts (helper function).
* @returns {Script[]}
*/

_getScripts() {
return [Script.fromAddress(this.address)];
}
}

/*
Expand Down
28 changes: 28 additions & 0 deletions lib/descriptor/type/combo.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ 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
* Represents a P2PK, P2PKH, P2WPKH, and P2SH-P2WPKH output scripts.
* @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
*/
Expand Down Expand Up @@ -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;
}
}

/*
Expand Down
33 changes: 33 additions & 0 deletions lib/descriptor/type/multisig.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ 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
* Represents a multisig output script.
* @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
Expand Down Expand Up @@ -148,6 +150,7 @@ class MultisigDescriptor extends AbstractDescriptor {
this.keyProviders = providers;
this.isSorted = isSorted;
this.network = Network.get(network);
this.scriptContext = context;

return this;
}
Expand All @@ -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');
}
}

/**
Expand Down
16 changes: 16 additions & 0 deletions lib/descriptor/type/pk.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -88,6 +90,7 @@ class PKDescriptor extends AbstractDescriptor {

this.keyProviders = [provider];
this.network = Network.get(network);
this.scriptContext = context;

return this;
}
Expand All @@ -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])];
}
}

/*
Expand Down
Loading