-
Notifications
You must be signed in to change notification settings - Fork 2.2k
HDWallet now uses Buffers internally #134
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
Changes from all commits
f047afe
bc05828
a4ab75d
814aa9e
bacf7e7
ddea4b1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,32 +1,37 @@ | ||
| var Address = require('./address') | ||
| var assert = require('assert') | ||
| var base58 = require('./base58') | ||
| var convert = require('./convert') | ||
|
|
||
| var Address = require('./address') | ||
| var BigInteger = require('./jsbn/jsbn') | ||
| var CJS = require('crypto-js') | ||
| var crypto = require('./crypto') | ||
| var ECKey = require('./eckey').ECKey | ||
| var ECPubKey = require('./eckey').ECPubKey | ||
| var format = require('util').format | ||
| var Network = require('./network') | ||
|
|
||
| var sec = require('./jsbn/sec') | ||
| var ecparams = sec("secp256k1") | ||
|
|
||
| function HmacSHA512(buffer, secret) { | ||
| var words = convert.bytesToWordArray(buffer) | ||
| var hash = CJS.HmacSHA512(words, secret) | ||
|
|
||
| return convert.wordArrayToBytes(hash) | ||
| return new Buffer(convert.wordArrayToBytes(hash)) | ||
| } | ||
|
|
||
| function HDWallet(seed, network) { | ||
| if (seed === undefined) return; | ||
| function HDWallet(seed, netstr) { | ||
| if (seed == undefined) return; // FIXME: Boo, should be stricter | ||
|
|
||
| var I = HmacSHA512(seed, 'Bitcoin seed') | ||
| this.chaincode = I.slice(32) | ||
| this.network = network || 'mainnet' | ||
| this.network = netstr || 'bitcoin' | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Grrr the name
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, bit late now but I'll make a small amendment. edit: 505e33c |
||
|
|
||
| if(!Network.hasOwnProperty(this.network)) { | ||
| throw new Error("Unknown network: " + this.network) | ||
| } | ||
|
|
||
| this.priv = new ECKey(I.slice(0, 32).concat([1]), true) | ||
| this.priv = new ECKey(I.slice(0, 32), true) | ||
| this.pub = this.priv.getPub() | ||
| this.index = 0 | ||
| this.depth = 0 | ||
|
|
@@ -35,16 +40,8 @@ function HDWallet(seed, network) { | |
| HDWallet.HIGHEST_BIT = 0x80000000 | ||
| HDWallet.LENGTH = 78 | ||
|
|
||
| function arrayEqual(a, b) { | ||
| return !(a < b || a > b) | ||
| } | ||
|
|
||
| HDWallet.fromSeedHex = function(hex, network) { | ||
| return new HDWallet(convert.hexToBytes(hex), network) | ||
| } | ||
|
|
||
| HDWallet.fromSeedString = function(string, network) { | ||
| return new HDWallet(convert.stringToBytes(string), network) | ||
| return new HDWallet(new Buffer(hex, 'hex'), network) | ||
| } | ||
|
|
||
| HDWallet.fromBase58 = function(string) { | ||
|
|
@@ -57,57 +54,49 @@ HDWallet.fromBase58 = function(string) { | |
| assert.deepEqual(newChecksum, checksum) | ||
| assert.equal(payload.length, HDWallet.LENGTH) | ||
|
|
||
| return HDWallet.fromBytes(payload) | ||
| return HDWallet.fromBuffer(payload) | ||
| } | ||
|
|
||
| HDWallet.fromHex = function(input) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now with |
||
| return HDWallet.fromBytes(convert.hexToBytes(input)) | ||
| return HDWallet.fromBuffer(new Buffer(input, 'hex')) | ||
| } | ||
|
|
||
| HDWallet.fromBytes = function(input) { | ||
| // This 78 byte structure can be encoded like other Bitcoin data in Base58. (+32 bits checksum) | ||
| if (input.length != HDWallet.LENGTH) { | ||
| throw new Error(format('Invalid input length, %s. Expected %s.', input.length, HDWallet.LENGTH)) | ||
| } | ||
|
|
||
| // FIXME: transitionary fix | ||
| if (Buffer.isBuffer(input)) { | ||
| input = Array.prototype.map.bind(input, function(x) { return x })() | ||
| } | ||
| HDWallet.fromBuffer = function(input) { | ||
| assert(input.length === HDWallet.LENGTH) | ||
|
|
||
| var hd = new HDWallet() | ||
|
|
||
| // 4 byte: version bytes (mainnet: 0x0488B21E public, 0x0488ADE4 private | ||
| // testnet: 0x043587CF public, 0x04358394 private) | ||
| var versionBytes = input.slice(0, 4) | ||
| var versionWord = convert.bytesToWords(versionBytes)[0] | ||
| var type | ||
| // 4 byte: version bytes | ||
| var version = input.readUInt32BE(0) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't know you can read off bytes like this. This comes really handy :) |
||
|
|
||
| var type | ||
| for(var name in Network) { | ||
| var network = Network[name] | ||
|
|
||
| for(var t in network.hdVersions) { | ||
| if (versionWord != network.hdVersions[t]) continue | ||
| for(var t in network.bip32) { | ||
| if (version != network.bip32[t]) continue | ||
|
|
||
| type = t | ||
| hd.network = name | ||
| } | ||
| } | ||
|
|
||
| if (!hd.network) { | ||
| throw new Error(format('Could not find version %s', convert.bytesToHex(versionBytes))) | ||
| throw new Error('Could not find version ' + version.toString(16)) | ||
| } | ||
|
|
||
| // 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ... | ||
| hd.depth = input[4] | ||
| hd.depth = input.readUInt8(4) | ||
|
|
||
| // 4 bytes: the fingerprint of the parent's key (0x00000000 if master key) | ||
| hd.parentFingerprint = input.slice(5, 9) | ||
| assert((hd.depth === 0) == arrayEqual(hd.parentFingerprint, [0, 0, 0, 0])) | ||
| hd.parentFingerprint = input.readUInt32BE(5) | ||
| if (hd.depth === 0) { | ||
| assert(hd.parentFingerprint === 0x00000000) | ||
| } | ||
|
|
||
| // 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized. | ||
| // This is encoded in MSB order. (0x00000000 if master key) | ||
| hd.index = convert.bytesToNum(input.slice(9, 13).reverse()) | ||
| hd.index = input.readUInt32BE(9) | ||
| assert(hd.depth > 0 || hd.index === 0) | ||
|
|
||
| // 32 bytes: the chain code | ||
|
|
@@ -116,7 +105,7 @@ HDWallet.fromBytes = function(input) { | |
| // 33 bytes: the public key or private key data (0x02 + X or 0x03 + X for | ||
| // public keys, 0x00 + k for private keys) | ||
| if (type == 'priv') { | ||
| hd.priv = new ECKey(input.slice(46, 78).concat([1]), true) | ||
| hd.priv = new ECKey(input.slice(46, 78), true) | ||
| hd.pub = hd.priv.getPub() | ||
| } else { | ||
| hd.pub = new ECPubKey(input.slice(45, 78), true) | ||
|
|
@@ -130,63 +119,57 @@ HDWallet.prototype.getIdentifier = function() { | |
| } | ||
|
|
||
| HDWallet.prototype.getFingerprint = function() { | ||
| return Array.prototype.slice.call(this.getIdentifier(), 0, 4) | ||
| return this.getIdentifier().slice(0, 4) | ||
| } | ||
|
|
||
| HDWallet.prototype.getAddress = function() { | ||
| return new Address(crypto.hash160(this.pub.toBytes()), this.getKeyVersion()) | ||
| } | ||
|
|
||
| HDWallet.prototype.toBytes = function(priv) { | ||
| var buffer = [] | ||
|
|
||
| HDWallet.prototype.toBuffer = function(priv) { | ||
| // Version | ||
| // 4 byte: version bytes (mainnet: 0x0488B21E public, 0x0488ADE4 private; testnet: 0x043587CF public, | ||
| // 0x04358394 private) | ||
| var version = Network[this.network].hdVersions[priv ? 'priv' : 'pub'] | ||
| var vBytes = convert.wordsToBytes([version]) | ||
| var version = Network[this.network].bip32[priv ? 'priv' : 'pub'] | ||
| var buffer = new Buffer(HDWallet.LENGTH) | ||
|
|
||
| buffer = buffer.concat(vBytes) | ||
| assert.equal(buffer.length, 4) | ||
| // 4 bytes: version bytes | ||
| buffer.writeUInt32BE(version, 0) | ||
|
|
||
| // Depth | ||
| // 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, .... | ||
| buffer.push(this.depth) | ||
| assert.equal(buffer.length, 4 + 1) | ||
| buffer.writeUInt8(this.depth, 4) | ||
|
|
||
| // 4 bytes: the fingerprint of the parent's key (0x00000000 if master key) | ||
| buffer = buffer.concat(this.depth ? this.parentFingerprint : [0, 0, 0, 0]) | ||
| assert.equal(buffer.length, 4 + 1 + 4) | ||
| var fingerprint = this.depth ? this.parentFingerprint : 0x00000000 | ||
| buffer.writeUInt32BE(fingerprint, 5) | ||
|
|
||
| // 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized. | ||
| // This is encoded in MSB order. (0x00000000 if master key) | ||
| buffer = buffer.concat(convert.numToBytes(this.index, 4).reverse()) | ||
| assert.equal(buffer.length, 4 + 1 + 4 + 4) | ||
| // This is encoded in Big endian. (0x00000000 if master key) | ||
| buffer.writeUInt32BE(this.index, 9) | ||
|
|
||
| // 32 bytes: the chain code | ||
| buffer = buffer.concat(this.chaincode) | ||
| assert.equal(buffer.length, 4 + 1 + 4 + 4 + 32) | ||
| this.chaincode.copy(buffer, 13) | ||
|
|
||
| // 33 bytes: the public key or private key data | ||
| // (0x02 + X or 0x03 + X for public keys, 0x00 + k for private keys) | ||
| if (priv) { | ||
| assert(this.priv, 'Cannot serialize to private without private key') | ||
| buffer.push(0) | ||
| buffer = buffer.concat(this.priv.toBytes().slice(0, 32)) | ||
|
|
||
| // 0x00 + k for private keys | ||
| buffer.writeUInt8(0, 45) | ||
| new Buffer(this.priv.toBytes()).copy(buffer, 46) | ||
| } else { | ||
| buffer = buffer.concat(this.pub.toBytes(true)) | ||
|
|
||
| // X9.62 encoding for public keys | ||
| new Buffer(this.pub.toBytes()).copy(buffer, 45) | ||
| } | ||
|
|
||
| return buffer | ||
| } | ||
|
|
||
| HDWallet.prototype.toHex = function(priv) { | ||
| var bytes = this.toBytes(priv) | ||
| return convert.bytesToHex(bytes) | ||
| return this.toBuffer(priv).toString('hex') | ||
| } | ||
|
|
||
| HDWallet.prototype.toBase58 = function(priv) { | ||
| var buffer = new Buffer(this.toBytes(priv)) | ||
| var buffer = new Buffer(this.toBuffer(priv)) | ||
| var checksum = crypto.hash256(buffer).slice(0, 4) | ||
|
|
||
| return base58.encode(Buffer.concat([ | ||
|
|
@@ -196,47 +179,58 @@ HDWallet.prototype.toBase58 = function(priv) { | |
| } | ||
|
|
||
| HDWallet.prototype.derive = function(i) { | ||
| var I | ||
| , iBytes = convert.numToBytes(i, 4).reverse() | ||
| var iBytes = convert.numToBytes(i, 4).reverse() | ||
| , cPar = this.chaincode | ||
| , usePriv = i >= HDWallet.HIGHEST_BIT | ||
| , SHA512 = CJS.algo.SHA512 | ||
|
|
||
| var I | ||
| if (usePriv) { | ||
| assert(this.priv, 'Private derive on public key') | ||
|
|
||
| // If 1, private derivation is used: | ||
| // let I = HMAC-SHA512(Key = cpar, Data = 0x00 || kpar || i) [Note:] | ||
| var kPar = this.priv.toBytes().slice(0, 32) | ||
|
|
||
| // FIXME: Dislikes buffers | ||
| I = HmacFromBytesToBytes(SHA512, [0].concat(kPar, iBytes), cPar) | ||
| } else { | ||
| // If 0, public derivation is used: | ||
| // let I = HMAC-SHA512(Key = cpar, Data = χ(kpar*G) || i) | ||
| var KPar = this.pub.toBytes(true) | ||
| var KPar = this.pub.toBytes() | ||
|
|
||
| // FIXME: Dislikes buffers | ||
| I = HmacFromBytesToBytes(SHA512, KPar.concat(iBytes), cPar) | ||
| } | ||
|
|
||
| // FIXME: Boo, CSJ.algo.SHA512 uses byte arrays | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I recall it also accepts WordArray, which shouldn't be hard to convert from buffer? |
||
| I = new Buffer(I) | ||
|
|
||
| // Split I = IL || IR into two 32-byte sequences, IL and IR. | ||
| var IL = I.slice(0, 32) | ||
| , IR = I.slice(32) | ||
| var ILb = I.slice(0, 32) | ||
| , IRb = I.slice(32) | ||
|
|
||
| var hd = new HDWallet() | ||
| hd.network = this.network | ||
|
|
||
| var IL = BigInteger.fromByteArrayUnsigned(ILb) | ||
|
|
||
| if (this.priv) { | ||
| // ki = IL + kpar (mod n). | ||
| hd.priv = this.priv.add(new ECKey(IL.concat([1]))) | ||
| hd.priv.compressed = true | ||
| hd.priv.version = this.getKeyVersion() | ||
| var ki = IL.add(this.priv.priv).mod(ecparams.getN()) | ||
|
|
||
| hd.priv = new ECKey(ki, true) | ||
| hd.pub = hd.priv.getPub() | ||
| } else { | ||
| // Ki = (IL + kpar)*G = IL*G + Kpar | ||
| hd.pub = this.pub.add(new ECKey(IL.concat([1]), true).getPub()) | ||
| var Ki = IL.multiply(ecparams.getG()).add(this.pub.pub) | ||
|
|
||
| hd.pub = new ECPubKey(Ki, true) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What was this concat doing here (and above) originally?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding the It could also have just been |
||
| } | ||
|
|
||
| // ci = IR. | ||
| hd.chaincode = IR | ||
| hd.parentFingerprint = this.getFingerprint() | ||
| hd.chaincode = IRb | ||
| hd.parentFingerprint = this.getFingerprint().readUInt32BE(0) | ||
| hd.depth = this.depth + 1 | ||
| hd.index = i | ||
| hd.pub.compressed = true | ||
|
|
@@ -248,7 +242,7 @@ HDWallet.prototype.derivePrivate = function(index) { | |
| } | ||
|
|
||
| HDWallet.prototype.getKeyVersion = function() { | ||
| return Network[this.network].addressVersion | ||
| return Network[this.network].pubKeyHash | ||
| } | ||
|
|
||
| HDWallet.prototype.toString = HDWallet.prototype.toBase58 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, we should do something better here.