Skip to content
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
6 changes: 3 additions & 3 deletions src/address.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
var base58 = require('./base58')
var base58check = require('./base58check')
var convert = require('./convert')
var mainnet = require('./network').mainnet.addressVersion
var bitcoin = require('./network').bitcoin.pubKeyHash

function Address(bytes, version) {
if (!(this instanceof Address)) {
Expand All @@ -21,15 +21,15 @@ function Address(bytes, version) {
}
else if (bytes.length <= 40) {
this.hash = convert.hexToBytes(bytes)
this.version = version || mainnet
this.version = version || bitcoin
}
else {
throw new Error('Invalid or unrecognized input')
}
}
else {
this.hash = bytes
this.version = version || mainnet
this.version = version || bitcoin
}
}

Expand Down
8 changes: 5 additions & 3 deletions src/eckey.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ ECKey.prototype.import = function (input, compressed) {
input instanceof ECKey ? input.priv
: input instanceof BigInteger ? input.mod(ecparams.getN())
: Array.isArray(input) ? fromBin(input.slice(0, 32))
: Buffer.isBuffer(input) ? fromBin(input.slice(0, 32))
: typeof input != "string" ? null
: input.length == 44 ? fromBin(convert.base64ToBytes(input))
: input.length == 51 && input[0] == '5' ? fromBin(base58check.decode(input).payload)
Expand Down Expand Up @@ -74,7 +75,7 @@ ECKey.version_bytes = {
}

ECKey.prototype.toWif = function(version) {
version = version || Network.mainnet.addressVersion
version = version || Network.bitcoin.pubKeyHash

return base58check.encode(this.toBytes(), ECKey.version_bytes[version])
}
Expand Down Expand Up @@ -136,6 +137,7 @@ ECPubKey.prototype.import = function(input, compressed) {
: input instanceof ECPubKey ? input.pub
: typeof input == "string" ? decode(convert.hexToBytes(input))
: Array.isArray(input) ? decode(input)
: Buffer.isBuffer(input) ? decode(input)
: null

assert(this.pub !== null)
Expand Down Expand Up @@ -169,15 +171,15 @@ ECPubKey.prototype.toBin = function(compressed) {
}

ECPubKey.prototype.toWif = function(version) {
version = version || Network.mainnet.addressVersion
version = version || Network.bitcoin.pubKeyHash

return base58check.encode(this.toBytes(), version)
}

ECPubKey.prototype.toString = ECPubKey.prototype.toHex

ECPubKey.prototype.getAddress = function(version) {
version = version || Network.mainnet.addressVersion
version = version || Network.bitcoin.pubKeyHash

return new Address(crypto.hash160(this.toBytes()), version)
}
Expand Down
154 changes: 74 additions & 80 deletions src/hdwallet.js
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
Copy link
Contributor

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.


var I = HmacSHA512(seed, 'Bitcoin seed')
this.chaincode = I.slice(32)
this.network = network || 'mainnet'
this.network = netstr || 'bitcoin'
Copy link
Contributor

Choose a reason for hiding this comment

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

Grrr the name netstr... you mean networkString?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Expand All @@ -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) {
Expand All @@ -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) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Now with fromBuffer and the constructor that accepts buffer, shall we get rid of fromHex and fromSeedHex?

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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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
Expand All @@ -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)
Expand All @@ -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([
Expand All @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The 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)
Copy link
Contributor

Choose a reason for hiding this comment

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

What was this concat doing here (and above) originally?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Adding the 0x01 compression flag onto the private key, an unnecessary addition when all we need is the private key in its raw form.
Compression only matters in the serialization of the public key, nothing else.

It could also have just been new ECKey(IL, true).
This format is actually deprecated in a future ECKey branch, as it is a WIF specific behaviour anyway.

}

// 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
Expand All @@ -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
Expand Down
Loading