diff --git a/bitcore.js b/bitcore.js index 4e2b37052..aa0de4bc0 100644 --- a/bitcore.js +++ b/bitcore.js @@ -49,6 +49,7 @@ requireWhenAccessed('Wallet', './lib/Wallet'); requireWhenAccessed('WalletKey', './lib/WalletKey'); requireWhenAccessed('PeerManager', './lib/PeerManager'); requireWhenAccessed('Message', './lib/Message'); +requireWhenAccessed('Electrum', './lib/Electrum'); module.exports.Buffer = Buffer; if (typeof process.versions === 'undefined') { diff --git a/browser/build.js b/browser/build.js index 46801754f..a7c0805a3 100644 --- a/browser/build.js +++ b/browser/build.js @@ -30,6 +30,7 @@ var modules = [ 'lib/Bloom', 'lib/Connection', 'lib/Deserialize', + 'lib/Electrum', 'lib/Message', 'lib/Opcode', 'lib/Peer', diff --git a/examples/ElectrumMPK.js b/examples/ElectrumMPK.js new file mode 100644 index 000000000..c6bbb13a3 --- /dev/null +++ b/examples/ElectrumMPK.js @@ -0,0 +1,11 @@ +var Electrum = require('../lib/Electrum'); +var Address = require('../lib/Address'); + +var mpk = '92eea4d2f5263651db9e3222caded1fd4c89772f79a7c03fb6afc00e9d2c9d2ed9b86c2c95fc1171e49163079dacb7f048b3c509a27a490e1df9e7128362d468'; + +mpk = new Electrum(mpk); + +var key0 = mpk.generatePubKey(0); +var addr0 = Address.fromPubKey(key0); + +console.log(addr0.as('base58')); diff --git a/lib/Electrum.js b/lib/Electrum.js new file mode 100644 index 000000000..1cf499e1a --- /dev/null +++ b/lib/Electrum.js @@ -0,0 +1,54 @@ +var Key = require('./Key'), + Point = require('./Point'), + twoSha256 = require('../util').twoSha256, + buffertools = require('buffertools'), + bignum = require('bignum'); + +/** + * Pre-BIP32 Electrum public key derivation (electrum <2.0) + * + * For now, this class can only understands master public keys. + * It doesn't support derivation from a private master key (TODO). + * + * @example examples/ElectrumMPK.js + */ +function Electrum (master_public_key) { + this.mpk = new Buffer(master_public_key, 'hex'); +} + +Electrum.prototype.getSequence = function (for_change, n) { + var mode = for_change ? 1 : 0; + var buf = Buffer.concat([ new Buffer(n + ':' + mode + ':', 'utf8'), this.mpk ]); + return bignum.fromBuffer(twoSha256(buf)); +}; + +Electrum.prototype.generatePubKey = function (n, for_change) { + var x = bignum.fromBuffer(this.mpk.slice(0, 32), { size: 32 }); + var y = bignum.fromBuffer(this.mpk.slice(32, 64), { size: 32 }); + var mpk_pt = new Point(x, y); + + var sequence = this.getSequence(for_change, n); + var sequence_key = new Key(); + sequence_key.private = sequence.toBuffer(); + sequence_key.regenerateSync(); + + var sequence_pt = Point.fromKey(sequence_key); + + pt = Point.add(mpk_pt, sequence_pt); + + var xbuf = pt.x.toBuffer({ size: 32 }); + var ybuf = pt.y.toBuffer({ size: 32 }); + var prefix = new Buffer([0x04]); + + var key = new Key(); + key.compressed = false; + key.public = Buffer.concat([prefix, xbuf, ybuf]); + + return key.public; +}; + +Electrum.prototype.generateChangePubKey = function (sequence) { + return this.generatePubKey(sequence, true); +}; + +module.exports = Electrum; diff --git a/src/eckey.cc b/src/eckey.cc index 41993682e..8f279a61b 100644 --- a/src/eckey.cc +++ b/src/eckey.cc @@ -317,6 +317,7 @@ Key::SetPublic(Local property, Local value, const AccessorInfo& i if (!ret) { // TODO: Error + VException("Invalid public key."); return; } diff --git a/test/index.html b/test/index.html index 3e823e41d..551c46d80 100644 --- a/test/index.html +++ b/test/index.html @@ -22,6 +22,7 @@ + diff --git a/test/test.Electrum.js b/test/test.Electrum.js new file mode 100644 index 000000000..8e82c18ad --- /dev/null +++ b/test/test.Electrum.js @@ -0,0 +1,78 @@ +'use strict'; + +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); + +var should = chai.should(); + +var Electrum = bitcore.Electrum; +var Address = bitcore.Address; +var mpk = '92eea4d2f5263651db9e3222caded1fd4c89772f79a7c03fb6afc00e9d2c9d2ed9b86c2c95fc1171e49163079dacb7f048b3c509a27a490e1df9e7128362d468'; + +/** + * The hard coded values (expected public key / address hash) were retrieved + * by creating an Electrum wallet and looking which addresses were generated for + * master public key mpk. Not very rigorous but it will help catch regressions. + */ +describe('Electrum', function() { + it('should initialze the main object', function() { + should.exist(Electrum); + }); + it('should be able to create instance', function() { + var elec = new Electrum(mpk); + should.exist(elec); + }); + it('should be able to call generatePubKey', function() { + var elec = new Electrum(mpk); + (function () { + elec.generatePubKey(0); + }).should.not.throw(Error); + }); + it('should be able to call generateChangePubKey', function() { + var elec = new Electrum(mpk); + (function () { + elec.generateChangePubKey(0); + }).should.not.throw(Error); + }); + it('should generate correct public key at sequence 0', function() { + var elec = new Electrum(mpk); + var pubkey = elec.generatePubKey(0); + var addr = Address.fromPubKey(pubkey); + addr.as('base58').should.equal('15Ur7LV4hZFvFYQHkB12g1mdnKuHyHBDiW'); + }); + it('should generate correct (change) public keys at sequence 0,1,2', function() { + var expected_values = { + receiving: [ + '15Ur7LV4hZFvFYQHkB12g1mdnKuHyHBDiW', + '19K48MhyXK4qChGCUJzAHxLFjbQRg9tb9F', + '1EfoxVmRVzYf1a1WELv2qMvEEpu2u5pXsy' + ], + change: [ + '138EGyTTyXFuqWBr8Jd7qmPbnfJUFmmuQg', + '12H7HZhtn7aNpTySXysKXiyxULybMktukh', + '1KMEUhmmiSdjX9fsv8aCAiYnVZnTTjqx7W' + ] + }; + var elec = new Electrum(mpk); + + var pubkey, addr, addr_change; + + for (var i = 0; i < expected_values.receiving.length; i++) { + //receiving + pubkey = elec.generatePubKey(i); + addr = Address.fromPubKey(pubkey); + addr.as('base58').should.equal(expected_values.receiving[i]); + + //change + pubkey = elec.generateChangePubKey(i); + addr = Address.fromPubKey(pubkey); + console.log('change'); + addr.as('base58').should.equal(expected_values.change[i]); + } + }); +}); + + + + +