Commit be952dd6 authored by kenshin-samourai's avatar kenshin-samourai
Browse files

add initialization from a seed and support of derivation from a notif pubkey

parent 3c30539a
...@@ -3,4 +3,5 @@ Object.defineProperty(exports, "__esModule", { value: true }); ...@@ -3,4 +3,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
var bip47 = require("./payment-code"); var bip47 = require("./payment-code");
exports.fromBuffer = bip47.fromBuffer; exports.fromBuffer = bip47.fromBuffer;
exports.fromBase58 = bip47.fromBase58; exports.fromBase58 = bip47.fromBase58;
exports.fromWalletSeed = bip47.fromWalletSeed;
exports.utils = require('./utils'); exports.utils = require('./utils');
\ No newline at end of file
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const ecc = require('tiny-secp256k1'); const ecc = require('tiny-secp256k1');
const { networks, getP2pkhAddress, sha256 } = require('./utils'); const { networks, getP2pkhAddress, sha256 } = require('./utils');
const { fromPublicKey } = require('bip32'); const { fromPublicKey, fromSeed } = require('bip32');
const { encode, decode } = require('bs58check'); const { encode, decode } = require('bs58check');
...@@ -25,6 +25,23 @@ class PaymentCode { ...@@ -25,6 +25,23 @@ class PaymentCode {
this.root = fromPublicKey(this.pubKey, this.chainCode, this.network); this.root = fromPublicKey(this.pubKey, this.chainCode, this.network);
} }
static fromSeed(bSeed, id, network) {
var reserved = Buffer.alloc(13, 0);
const root = fromSeed(bSeed);
const root_bip47 = root.derivePath(`m/47'/0'/${id}'`);
let pc = Buffer.from('0100', 'hex'); // version + options
pc = Buffer.concat([pc, root_bip47.publicKey]);
pc = Buffer.concat([pc, root_bip47.chainCode]);
if (pc.length !== 67)
throw new TypeError('Missing or wrong publicKey or chainCode');
pc = Buffer.concat([pc, reserved]); // reserved bytes
const pcode = new PaymentCode(pc, network);
pcode.root = root_bip47; // store the privkey
return pcode;
}
get features() { get features() {
return this.buf.slice(1, 1); return this.buf.slice(1, 1);
} }
...@@ -47,6 +64,10 @@ class PaymentCode { ...@@ -47,6 +64,10 @@ class PaymentCode {
return encode(buf); return encode(buf);
} }
_hasPrivKeys() {
return this.root.privateKey != null;
}
derive(index) { derive(index) {
return this.root.derive(index); return this.root.derive(index);
} }
...@@ -61,15 +82,31 @@ class PaymentCode { ...@@ -61,15 +82,31 @@ class PaymentCode {
} }
derivePaymentPublicKey(a, idx) { derivePaymentPublicKey(a, idx) {
if (!ecc.isPrivate(a)) if (!ecc.isPrivate(a) && !ecc.isPoint(a))
throw new TypeError('Invalid private key'); throw new TypeError('Argument is neither a valid private key or public key');
const B = this.derive(idx).publicKey; let B = null;
let S = null;
if (ecc.isPrivate(a)) {
// a is a private key
B = this.derive(idx).publicKey;
S = ecc.pointMultiply(B, a);
} else if (ecc.isPoint(a)) {
if (!this._hasPrivKeys())
throw new Error('Unable to compute the derivation with a public key provided as argument');
// a is a public key
const A = a;
const b_node = this.derive(idx);
const b = b_node.privateKey;
B = b_node.publicKey;
S = ecc.pointMultiply(A, b);
}
if (!ecc.isPoint(B)) if (!ecc.isPoint(B))
throw new TypeError('Invalid derived public key'); throw new TypeError('Invalid derived public key');
const S = ecc.pointMultiply(B, a);
const Sx = S.slice(1, 33); const Sx = S.slice(1, 33);
const s = sha256(Sx); const s = sha256(Sx);
...@@ -102,3 +139,9 @@ function fromBuffer(buf, network) { ...@@ -102,3 +139,9 @@ function fromBuffer(buf, network) {
return new PaymentCode(buf, network); return new PaymentCode(buf, network);
} }
exports.fromBuffer = fromBuffer; exports.fromBuffer = fromBuffer;
function fromWalletSeed(bSeed, id, network) {
return PaymentCode.fromSeed(bSeed, id, network);
}
exports.fromWalletSeed = fromWalletSeed;
...@@ -16,6 +16,7 @@ const PC_1 = { ...@@ -16,6 +16,7 @@ const PC_1 = {
}; };
const PC_2 = { const PC_2 = {
seed: '87eaaac5a539ab028df44d9110defbef3797ddb805ca309f61a69ff96dbaa7ab5b24038cf029edec5235d933110f0aea8aeecf939ed14fc20730bba71e4b1110',
pcBase58: 'PM8TJS2JxQ5ztXUpBBRnpTbcUXbUHy2T1abfrb3KkAAtMEGNbey4oumH7Hc578WgQJhPjBxteQ5GHHToTYHE3A1w6p7tU6KSoFmWBVbFGjKPisZDbP97', pcBase58: 'PM8TJS2JxQ5ztXUpBBRnpTbcUXbUHy2T1abfrb3KkAAtMEGNbey4oumH7Hc578WgQJhPjBxteQ5GHHToTYHE3A1w6p7tU6KSoFmWBVbFGjKPisZDbP97',
notifAddr: '1ChvUUvht2hUQufHBXF8NgLhW8SwE2ecGV', notifAddr: '1ChvUUvht2hUQufHBXF8NgLhW8SwE2ecGV',
notifPrivkey: '04448fd1be0c9c13a5ca0b530e464b619dc091b299b98c5cab9978b32b4a1b8b', notifPrivkey: '04448fd1be0c9c13a5ca0b530e464b619dc091b299b98c5cab9978b32b4a1b8b',
...@@ -74,6 +75,19 @@ describe('payment-code', function() { ...@@ -74,6 +75,19 @@ describe('payment-code', function() {
}); });
}); });
describe('fromWalletSeed()', function() {
it('should successfully initialize a PaymentCode from a seed', function() {
try {
const seed = Buffer.from(PC_2.seed, 'hex');
const pc = bip47.fromWalletSeed(seed, 0);
const pc_b58 = pc.toBase58();
assert(pc_b58 == PC_2.pcBase58);
} catch(e) {
assert(false);
}
});
});
describe('PaymentCode.toBase58()', function() { describe('PaymentCode.toBase58()', function() {
it('should successfully reencode a payment code in base58', function() { it('should successfully reencode a payment code in base58', function() {
try { try {
...@@ -107,7 +121,7 @@ describe('payment-code', function() { ...@@ -107,7 +121,7 @@ describe('payment-code', function() {
}); });
describe('PaymentCode.derivePaymentPublicKey()', function() { describe('PaymentCode.derivePaymentPublicKey()', function() {
it('should successfully derive public keys for payments', function() { it('should successfully derive public keys from a payment code and a notif privkey', function() {
try { try {
const privkey1 = Buffer.from(PC_1.notifPrivkey, 'hex'); const privkey1 = Buffer.from(PC_1.notifPrivkey, 'hex');
const pc2 = bip47.fromBase58(PC_2.pcBase58); const pc2 = bip47.fromBase58(PC_2.pcBase58);
...@@ -122,10 +136,38 @@ describe('payment-code', function() { ...@@ -122,10 +136,38 @@ describe('payment-code', function() {
assert(false); assert(false);
} }
}); });
it('should successfully derive public keys from a payment code and a notif pubkey', function() {
try {
const pubkey1 = Buffer.from(PC_1.notifPubKey, 'hex');
const seed = Buffer.from(PC_2.seed, 'hex');
const pc2 = bip47.fromWalletSeed(seed, 0);
for (let i=0; i<10; i++) {
const pubkeyPayment = pc2.derivePaymentPublicKey(pubkey1, i);
const addrPayment = utils.getP2pkhAddress(pubkeyPayment, utils.networks.bitcoin);
if (addrPayment !== PC_2_PAYMENT_ADDR[i])
assert(false);
}
assert(true);
} catch(e) {
assert(false);
}
});
it('should fail to derive public keys from a notif pubkey if master privkey is unknown', function() {
try {
const pubkey1 = Buffer.from(PC_1.notifPubKey, 'hex');
const pc2 = bip47.fromBase58(PC_2.pcBase58);
const pubkeyPayment = pc2.derivePaymentPublicKey(pubkey1, 0);
assert(false);
} catch(e) {
assert(true);
}
});
}); });
describe('PaymentCode.getPaymentAddress()', function() { describe('PaymentCode.getPaymentAddress()', function() {
it('should successfully derive P2PKH addresses for payments', function() { it('should successfully derive P2PKH addresses from a payment code and a notif privkey', function() {
try { try {
const privkey1 = Buffer.from(PC_1.notifPrivkey, 'hex'); const privkey1 = Buffer.from(PC_1.notifPrivkey, 'hex');
const pc2 = bip47.fromBase58(PC_2.pcBase58); const pc2 = bip47.fromBase58(PC_2.pcBase58);
...@@ -139,6 +181,22 @@ describe('payment-code', function() { ...@@ -139,6 +181,22 @@ describe('payment-code', function() {
assert(false); assert(false);
} }
}); });
it('should successfully derive P2PKH addresses from a payment code and a notif pubkey', function() {
try {
const pubkey1 = Buffer.from(PC_1.notifPubKey, 'hex');
const seed = Buffer.from(PC_2.seed, 'hex');
const pc2 = bip47.fromWalletSeed(seed, 0);
for (let i=0; i<10; i++) {
const addrPayment = pc2.getPaymentAddress(pubkey1, i);
if (addrPayment !== PC_2_PAYMENT_ADDR[i])
assert(false);
}
assert(true);
} catch(e) {
assert(false);
}
});
}); });
}); });
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment