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
151 changes: 151 additions & 0 deletions src/ecpair.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
var assert = require('assert')
var base58check = require('bs58check')
var bcrypto = require('./crypto')
var ecdsa = require('./ecdsa')
var ecurve = require('ecurve')
var networks = require('./networks')
var randomBytes = require('randombytes')
var typeForce = require('typeforce')

var Address = require('./address')
var BigInteger = require('bigi')

function findNetworkByWIFVersion (version) {
for (var networkName in networks) {
var network = networks[networkName]

if (network.wif === version) return network
}

assert(false, 'Unknown network')
}

function ECPair (d, Q, options) {
options = options || {}

var compressed = options.compressed === undefined ? true : options.compressed
var network = options.network === undefined ? networks.bitcoin : options.network

typeForce('Boolean', compressed)
assert('pubKeyHash' in network, 'Unknown pubKeyHash constants for network')

if (d) {
assert(d.signum() > 0, 'Private key must be greater than 0')
assert(d.compareTo(ECPair.curve.n) < 0, 'Private key must be less than the curve order')

assert(!Q, 'Unexpected publicKey parameter')
this.d = d

// enforce Q is a public key if no private key given
} else {
typeForce('Point', Q)
this.__Q = Q
}

this.compressed = compressed
this.network = network
}

Object.defineProperty(ECPair.prototype, 'Q', {
get: function () {
if (!this.__Q && this.d) {
this.__Q = ECPair.curve.G.multiply(this.d)
}

return this.__Q
}
})

// Public access to secp256k1 curve
ECPair.curve = ecurve.getCurveByName('secp256k1')

ECPair.fromPublicKeyBuffer = function (buffer, network) {
var Q = ecurve.Point.decodeFrom(ECPair.curve, buffer)

return new ECPair(null, Q, {
compressed: Q.compressed,
network: network
})
}

ECPair.fromWIF = function (string) {
var payload = base58check.decode(string)
var version = payload.readUInt8(0)
var compressed

if (payload.length === 34) {
assert.strictEqual(payload[33], 0x01, 'Invalid compression flag')

// truncate the version/compression bytes
payload = payload.slice(1, -1)
compressed = true

// no compression flag
} else {
assert.equal(payload.length, 33, 'Invalid WIF payload length')

// Truncate the version byte
payload = payload.slice(1)
compressed = false
}

var network = findNetworkByWIFVersion(version)
var d = BigInteger.fromBuffer(payload)

return new ECPair(d, null, {
compressed: compressed,
network: network
})
}

ECPair.makeRandom = function (options) {
options = options || {}

var rng = options.rng || randomBytes
var buffer = rng(32)
typeForce('Buffer', buffer)
assert.equal(buffer.length, 32, 'Expected 256-bit Buffer from RNG')

var d = BigInteger.fromBuffer(buffer)
d = d.mod(ECPair.curve.n)

return new ECPair(d, null, options)
}

ECPair.prototype.toWIF = function () {
assert(this.d, 'Missing private key')

var bufferLen = this.compressed ? 34 : 33
var buffer = new Buffer(bufferLen)

buffer.writeUInt8(this.network.wif, 0)
this.d.toBuffer(32).copy(buffer, 1)

if (this.compressed) {
buffer.writeUInt8(0x01, 33)
}

return base58check.encode(buffer)
}

ECPair.prototype.getAddress = function () {
var pubKey = this.getPublicKeyBuffer()

return new Address(bcrypto.hash160(pubKey), this.network.pubKeyHash)
}

ECPair.prototype.getPublicKeyBuffer = function () {
return this.Q.getEncoded(this.compressed)
}

ECPair.prototype.sign = function (hash) {
assert(this.d, 'Missing private key')

return ecdsa.sign(ECPair.curve, hash, this.d)
}

ECPair.prototype.verify = function (hash, signature) {
return ecdsa.verify(ECPair.curve, hash, signature, this.Q)
}

module.exports = ECPair
89 changes: 47 additions & 42 deletions src/hdnode.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ var typeForce = require('typeforce')
var networks = require('./networks')

var BigInteger = require('bigi')
var ECKey = require('./eckey')
var ECPubKey = require('./ecpubkey')
var ECPair = require('./ecpair')

var ecurve = require('ecurve')
var curve = ecurve.getCurveByName('secp256k1')
Expand All @@ -24,32 +23,19 @@ function findBIP32NetworkByVersion (version) {
assert(false, 'Could not find network for ' + version.toString(16))
}

function HDNode (K, chainCode, network) {
network = network || networks.bitcoin

function HDNode (keyPair, chainCode) {
typeForce('ECPair', keyPair)
typeForce('Buffer', chainCode)

assert.equal(chainCode.length, 32, 'Expected chainCode length of 32, got ' + chainCode.length)
assert(network.bip32, 'Unknown BIP32 constants for network')
assert('bip32' in keyPair.network, 'Unknown BIP32 constants for network')
assert.equal(keyPair.compressed, true, 'BIP32 only allows compressed keyPairs')

this.keyPair = keyPair
this.chainCode = chainCode
this.depth = 0
this.index = 0
this.parentFingerprint = 0x00000000
this.network = network

if (K instanceof BigInteger) {
this.privKey = new ECKey(K, true)
this.pubKey = this.privKey.pub
} else if (K instanceof ECKey) {
assert(K.pub.compressed, 'ECKey must be compressed')
this.privKey = K
} else if (K instanceof ECPubKey) {
assert(K.compressed, 'ECPubKey must be compressed')
this.pubKey = K
} else {
this.pubKey = new ECPubKey(K, true)
}
}

HDNode.MASTER_SECRET = new Buffer('Bitcoin seed')
Expand All @@ -67,10 +53,13 @@ HDNode.fromSeedBuffer = function (seed, network) {
var IR = I.slice(32)

// In case IL is 0 or >= n, the master key is invalid
// This is handled by `new ECKey` in the HDNode constructor
// This is handled by the ECPair constructor
var pIL = BigInteger.fromBuffer(IL)
var keyPair = new ECPair(pIL, null, {
network: network
})

return new HDNode(pIL, IR, network)
return new HDNode(keyPair, IR)
}

HDNode.fromSeedHex = function (hex, network) {
Expand Down Expand Up @@ -108,14 +97,17 @@ HDNode.fromBase58 = function (string, network) {

// 32 bytes: the chain code
var chainCode = buffer.slice(13, 45)
var data, hd
var data, keyPair

// 33 bytes: private key data (0x00 + k)
if (version === network.bip32.private) {
assert.strictEqual(buffer.readUInt8(45), 0x00, 'Invalid private key')
data = buffer.slice(46, 78)
var d = BigInteger.fromBuffer(data)
hd = new HDNode(d, chainCode, network)

keyPair = new ECPair(d, null, {
network: network
})

// 33 bytes: public key data (0x02 + X or 0x03 + X)
} else {
Expand All @@ -127,9 +119,12 @@ HDNode.fromBase58 = function (string, network) {
// If not, the extended public key is invalid.
curve.validate(Q)

hd = new HDNode(Q, chainCode, network)
keyPair = new ECPair(null, Q, {
network: network
})
}

var hd = new HDNode(keyPair, chainCode)
hd.depth = depth
hd.index = index
hd.parentFingerprint = parentFingerprint
Expand All @@ -138,19 +133,23 @@ HDNode.fromBase58 = function (string, network) {
}

HDNode.prototype.getIdentifier = function () {
return bcrypto.hash160(this.pubKey.toBuffer())
return bcrypto.hash160(this.keyPair.getPublicKeyBuffer())
}

HDNode.prototype.getFingerprint = function () {
return this.getIdentifier().slice(0, 4)
}

HDNode.prototype.getAddress = function () {
return this.pubKey.getAddress(this.network)
return this.keyPair.getAddress()
}

HDNode.prototype.neutered = function () {
var neutered = new HDNode(this.pubKey.Q, this.chainCode, this.network)
var neuteredKeyPair = new ECPair(null, this.keyPair.Q, {
network: this.keyPair.network
})

var neutered = new HDNode(neuteredKeyPair, this.chainCode)
neutered.depth = this.depth
neutered.index = this.index
neutered.parentFingerprint = this.parentFingerprint
Expand All @@ -162,7 +161,8 @@ HDNode.prototype.toBase58 = function (__isPrivate) {
assert.strictEqual(__isPrivate, undefined, 'Unsupported argument in 2.0.0')

// Version
var version = this.privKey ? this.network.bip32.private : this.network.bip32.public
var network = this.keyPair.network
var version = this.keyPair.d ? network.bip32.private : network.bip32.public
var buffer = new Buffer(HDNode.LENGTH)

// 4 bytes: version bytes
Expand All @@ -182,16 +182,16 @@ HDNode.prototype.toBase58 = function (__isPrivate) {
// 32 bytes: the chain code
this.chainCode.copy(buffer, 13)

// 33 bytes: the private key, or
if (this.privKey) {
// 33 bytes: the public key or private key data
if (this.keyPair.d) {
// 0x00 + k for private keys
buffer.writeUInt8(0, 45)
this.privKey.d.toBuffer(32).copy(buffer, 46)
this.keyPair.d.toBuffer(32).copy(buffer, 46)

// 33 bytes: the public key
} else {
// X9.62 encoding for public keys
this.pubKey.toBuffer().copy(buffer, 45)
this.keyPair.getPublicKeyBuffer().copy(buffer, 45)
}

return base58check.encode(buffer)
Expand All @@ -207,11 +207,11 @@ HDNode.prototype.derive = function (index) {

// Hardened child
if (isHardened) {
assert(this.privKey, 'Could not derive hardened child key')
assert(this.keyPair.d, 'Could not derive hardened child key')

// data = 0x00 || ser256(kpar) || ser32(index)
data = Buffer.concat([
this.privKey.d.toBuffer(33),
this.keyPair.d.toBuffer(33),
indexBuffer
])

Expand All @@ -220,7 +220,7 @@ HDNode.prototype.derive = function (index) {
// data = serP(point(kpar)) || ser32(index)
// = serP(Kpar) || ser32(index)
data = Buffer.concat([
this.pubKey.toBuffer(),
this.keyPair.getPublicKeyBuffer(),
indexBuffer
])
}
Expand All @@ -237,32 +237,37 @@ HDNode.prototype.derive = function (index) {
}

// Private parent key -> private child key
var hd
if (this.privKey) {
var derivedKeyPair
if (this.keyPair.d) {
// ki = parse256(IL) + kpar (mod n)
var ki = pIL.add(this.privKey.d).mod(curve.n)
var ki = pIL.add(this.keyPair.d).mod(curve.n)

// In case ki == 0, proceed with the next value for i
if (ki.signum() === 0) {
return this.derive(index + 1)
}

hd = new HDNode(ki, IR, this.network)
derivedKeyPair = new ECPair(ki, null, {
network: this.keyPair.network
})

// Public parent key -> public child key
} else {
// Ki = point(parse256(IL)) + Kpar
// = G*IL + Kpar
var Ki = curve.G.multiply(pIL).add(this.pubKey.Q)
var Ki = curve.G.multiply(pIL).add(this.keyPair.Q)

// In case Ki is the point at infinity, proceed with the next value for i
if (curve.isInfinity(Ki)) {
return this.derive(index + 1)
}

hd = new HDNode(Ki, IR, this.network)
derivedKeyPair = new ECPair(null, Ki, {
network: this.keyPair.network
})
}

var hd = new HDNode(derivedKeyPair, IR)
hd.depth = this.depth + 1
hd.index = index
hd.parentFingerprint = this.getFingerprint().readUInt32BE(0)
Expand Down
3 changes: 1 addition & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ module.exports = {
bufferutils: require('./bufferutils'),
crypto: require('./crypto'),
ecdsa: require('./ecdsa'),
ECKey: require('./eckey'),
ECPubKey: require('./ecpubkey'),
ECPair: require('./ecpair'),
ECSignature: require('./ecsignature'),
message: require('./message'),
opcodes: require('./opcodes'),
Expand Down
Loading