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

Wallet: support import of x/y/zpubkey (BIP49 and BIP84) #616

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Prev Previous commit
Next Next commit
HD: encode x/y/z purpose to database
  • Loading branch information
pinheadmz committed Nov 20, 2019
commit 668eaefe3be37e7bc5e17451c762fd63cbc6ba73
50 changes: 24 additions & 26 deletions lib/hd/private.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class HDPrivateKey {
* Create an hd private key.
* @constructor
* @param {Object|String} options
* @param {string} options.keyType
* @param {string} options.purpose
* @param {Number?} options.depth
* @param {Number?} options.parentFingerPrint
* @param {Number?} options.childIndex
Expand All @@ -51,7 +51,7 @@ class HDPrivateKey {
*/

constructor(options) {
this.keyType = 'x';
this.purpose = 'x';
this.depth = 0;
this.parentFingerPrint = 0;
this.childIndex = 0;
Expand Down Expand Up @@ -81,14 +81,14 @@ class HDPrivateKey {
assert(Buffer.isBuffer(options.chainCode));
assert(Buffer.isBuffer(options.privateKey));

if (options.keyType){
if (options.purpose) {
assert(
['x','y','z'].indexOf(options.keyType) !== -1,
'Bad key type'
['x','y','z'].indexOf(options.purpose) !== -1,
'Bad purpose'
);
this.keyType = options.keyType;
this.purpose = options.purpose;
} else {
this.keyType = 'x';
this.purpose = 'x';
pinheadmz marked this conversation as resolved.
Show resolved Hide resolved
}

this.depth = options.depth;
Expand Down Expand Up @@ -121,7 +121,7 @@ class HDPrivateKey {

if (!key) {
key = new HDPublicKey();
key.keyType = this.keyType;
key.purpose = this.purpose;
key.depth = this.depth;
key.parentFingerPrint = this.parentFingerPrint;
key.childIndex = this.childIndex;
Expand Down Expand Up @@ -231,7 +231,7 @@ class HDPrivateKey {
}

const child = new this.constructor();
child.keyType = this.keyType;
child.purpose = this.purpose;
child.depth = this.depth + 1;
child.parentFingerPrint = this.fingerPrint;
child.childIndex = index;
Expand Down Expand Up @@ -450,7 +450,7 @@ class HDPrivateKey {
if (!secp256k1.privateKeyVerify(left))
throw new Error('Master private key is invalid.');

this.keyType = 'x';
this.purpose = 'x';
this.depth = 0;
this.parentFingerPrint = 0;
this.childIndex = 0;
Expand Down Expand Up @@ -578,30 +578,28 @@ class HDPrivateKey {

fromReader(br, network) {
const version = br.readU32BE();

const confirmNetwork = Network.fromPrivate(version, network);
let type = null;
for (const label in confirmNetwork.keyPrefix){
if (confirmNetwork.keyPrefix[label] === version){
type = label;

let purpose = null;
for (const label in confirmNetwork.keyPrefix) {
if (confirmNetwork.keyPrefix[label] === version) {
purpose = label;
break;
}
}

switch (type) {
switch (purpose) {
case 'xprivkey':
this.keyType = 'x';
this.purpose = 'x';
break;
case 'yprivkey':
this.keyType = 'y';
this.purpose = 'y';
break;
case 'zprivkey':
this.keyType = 'z';
this.purpose = 'z';
break;
default:
throw new Error('Bad version/type bytes');
break;
throw new Error('Bad purpose bytes');
}

this.depth = br.readU8();
Expand Down Expand Up @@ -656,16 +654,16 @@ class HDPrivateKey {
toWriter(bw, network) {
network = Network.get(network);

switch (this.keyType) {
switch (this.purpose) {
case 'x':
bw.writeU32BE(network.keyPrefix.xprivkey);
break;
case 'y':
bw.writeU32BE(network.keyPrefix.yprivkey);
break;
case 'z':
bw.writeU32BE(network.keyPrefix.zprivkey);
break;
case 'x':
default:
bw.writeU32BE(network.keyPrefix.xprivkey);
}
bw.writeU8(this.depth);
bw.writeU32BE(this.parentFingerPrint);
Expand Down
43 changes: 21 additions & 22 deletions lib/hd/public.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class HDPublicKey {
* @constructor
* @param {Object|Base58String} options
* @param {Base58String?} options.xkey - Serialized base58 key.
* @param {string} options.keyType
* @param {string} options.purpose
* @param {Number?} options.depth
* @param {Number?} options.parentFingerPrint
* @param {Number?} options.childIndex
Expand All @@ -43,7 +43,7 @@ class HDPublicKey {
*/

constructor(options) {
this.keyType = 'x';
this.purpose = 'x';
this.depth = 0;
this.parentFingerPrint = 0;
this.childIndex = 0;
Expand All @@ -70,14 +70,14 @@ class HDPublicKey {
assert(Buffer.isBuffer(options.chainCode));
assert(Buffer.isBuffer(options.publicKey));

if (options.keyType){
if (options.purpose) {
assert(
['x','y','z'].indexOf(options.keyType) !== -1,
'Bad key type'
['x','y','z'].indexOf(options.purpose) !== -1,
'Bad purpose'
);
this.keyType = options.keyType;
this.purpose = options.purpose;
} else {
this.keyType = 'x';
this.purpose = 'x';
}

this.depth = options.depth;
Expand Down Expand Up @@ -448,27 +448,26 @@ class HDPublicKey {

const confirmNetwork = Network.fromPublic(version, network);

let type = null;
for (const label in confirmNetwork.keyPrefix){
if (confirmNetwork.keyPrefix[label] === version){
type = label;
let purpose = null;
for (const label in confirmNetwork.keyPrefix) {
if (confirmNetwork.keyPrefix[label] === version) {
purpose = label;
break;
}
}

switch (type) {
switch (purpose) {
case 'xpubkey':
this.keyType = 'x';
this.purpose = 'x';
break;
case 'ypubkey':
this.keyType = 'y';
this.purpose = 'y';
break;
case 'zpubkey':
this.keyType = 'z';
this.purpose = 'z';
break;
default:
throw new Error('Bad version/type bytes');
break;
throw new Error('Bad purpose bytes');
}

this.depth = br.readU8();
Expand Down Expand Up @@ -507,22 +506,22 @@ class HDPublicKey {
* Write the key to a buffer writer.
* @param {BufferWriter} bw
* @param {(Network|NetworkType)?} network
* @param {string} type
* @param {string} type
*/

toWriter(bw, network) {
network = Network.get(network);

switch (this.keyType) {
switch (this.purpose) {
case 'x':
bw.writeU32BE(network.keyPrefix.xpubkey);
break;
case 'y':
bw.writeU32BE(network.keyPrefix.ypubkey);
break;
case 'z':
bw.writeU32BE(network.keyPrefix.zpubkey);
break;
case 'x':
default:
bw.writeU32BE(network.keyPrefix.xpubkey);
}
bw.writeU8(this.depth);
bw.writeU32BE(this.parentFingerPrint);
Expand Down
2 changes: 1 addition & 1 deletion lib/primitives/keyring.js
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ class KeyRing {
*/

getAddress(enc, network) {
if (this.nested || this.addressFromKeyType === 'y')
if (this.nested || this.purpose === 'y')
return this.getNestedAddress(enc, network);

if (this.script)
Expand Down
50 changes: 36 additions & 14 deletions lib/wallet/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class Account {
this.initialized = false;
this.witness = wdb.options.witness === true;
this.watchOnly = false;
this.keyType = 'x',
this.purpose = 'x',
this.type = Account.types.PUBKEYHASH;
this.m = 1;
this.n = 1;
Expand Down Expand Up @@ -103,16 +103,16 @@ class Account {
this.watchOnly = options.watchOnly;
}

if (options.keyType != null) {
assert(typeof options.keyType === 'string');
if (options.purpose != null) {
assert(typeof options.purpose === 'string');
assert(
['x','y','z'].indexOf(options.keyType) !== -1,
'Bad key type'
['x','y','z'].indexOf(options.purpose) !== -1,
'Bad purpose'
);
this.keyType = options.keyType;
this.purpose = options.purpose;
}

if (this.keyType === 'y' || this.keyType === 'z')
if (this.purpose === 'y' || this.purpose === 'z')
this.witness = true;

if (options.type != null) {
Expand Down Expand Up @@ -482,8 +482,8 @@ class Account {
let key;
if (master && master.key && !this.watchOnly) {
const coinType = this.network.keyPrefix.coinType;
switch (this.keyType) {

switch (this.purpose) {
case 'x':
key = master.key.deriveAccount(44, coinType, this.accountIndex);
break;
Expand All @@ -500,7 +500,7 @@ class Account {
key = this.accountKey.derive(branch).derive(index);
}

key.keyType = this.keyType;
key.purpose = this.purpose;
const ring = WalletKey.fromHD(this, key, branch, index);

switch (this.type) {
Expand Down Expand Up @@ -563,9 +563,6 @@ class Account {

for (let i = 0; i <= this.lookahead; i++) {
const key = this.deriveReceive(i);

console.log(key); // ------ right here, the address is correct (nested)

await this.saveKey(b, key);
}

Expand Down Expand Up @@ -893,6 +890,9 @@ console.log(key); // ------ right here, the address is correct (nested)
if (this.witness)
flags |= 2;

const purposeBits = Account.purposes[this.purpose];
flags |= (purposeBits << 6);

bw.writeU8(flags);
bw.writeU8(this.type);
bw.writeU8(this.m);
Expand Down Expand Up @@ -921,6 +921,9 @@ console.log(key); // ------ right here, the address is correct (nested)
const br = bio.read(data);
const flags = br.readU8();

const purposeBits = flags >> 6;
this.purpose = Account.purposesByVal[purposeBits];

this.initialized = (flags & 1) !== 0;
this.witness = (flags & 2) !== 0;
this.type = br.readU8();
Expand All @@ -931,7 +934,6 @@ console.log(key); // ------ right here, the address is correct (nested)
this.nestedDepth = br.readU32();
this.lookahead = br.readU8();
this.accountKey = readKey(br);

assert(this.type < Account.typesByVal.length);

const count = br.readU8();
Expand Down Expand Up @@ -987,6 +989,26 @@ Account.typesByVal = [
'MULTISIG'
];

/**
* Account purposes.
* @enum {Number}
* @default
*/

Account.purposes = {
x: 0,
y: 1,
z: 2
};

/**
* Account purposes by value.
* @enum {Number}
* @default
*/

Account.purposesByVal = ['x', 'y', 'z'];

/**
* Default address lookahead.
* @const {Number}
Expand Down
10 changes: 5 additions & 5 deletions lib/wallet/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class Wallet extends EventEmitter {
this.writeLock = new Lock();
this.fundLock = new Lock();

this.keyType = 'x';
this.purpose = 'x';
this.wid = 0;
this.id = null;
this.watchOnly = false;
Expand Down Expand Up @@ -95,7 +95,7 @@ class Wallet extends EventEmitter {
assert(HD.isPrivate(key),
'Must create wallet with hd private key.');

this.keyType = key.keyType;
this.purpose = key.purpose;
} else {
mnemonic = new Mnemonic(options.mnemonic);
key = HD.fromMnemonic(mnemonic, options.password);
Expand Down Expand Up @@ -596,11 +596,11 @@ class Wallet extends EventEmitter {
if (!HD.isPublic(key))
throw new Error('Must add HD public keys to watch only wallet.');

this.keyType = key.keyType;
this.purpose = key.purpose;
} else {
assert(this.master.key);
const coinType = this.network.keyPrefix.coinType;
switch (this.keyType) {
switch (this.purpose) {
case 'x':
key = this.master.key.deriveAccount(44, coinType, this.accountDepth);
break;
Expand All @@ -623,7 +623,7 @@ class Wallet extends EventEmitter {
accountKey: key,
accountIndex: this.accountDepth,
type: options.type,
keyType: this.keyType,
purpose: this.purpose,
m: options.m,
n: options.n,
keys: options.keys
Expand Down
Loading