Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
dcousens committed Oct 13, 2016
1 parent 9831c75 commit c9f0c43
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 60 deletions.
70 changes: 47 additions & 23 deletions src/ecpair.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,13 @@ function ECPair (d, Q, options) {
}, options)
}

options = options || {}

if (d) {
if (d.signum() <= 0) throw new Error('Private key must be greater than 0')
if (d.compareTo(secp256k1.n) >= 0) throw new Error('Private key must be less than the curve order')
if (Q) throw new TypeError('Unexpected publicKey parameter')

this.d = d
} else {
typeforce(types.ECPoint, Q)

this.__Q = Q
}

options = options || {}
this.compressed = options.compressed === undefined ? true : options.compressed
this.network = options.network || NETWORKS.bitcoin
}
Expand All @@ -48,7 +41,28 @@ Object.defineProperty(ECPair.prototype, 'Q', {
}
})

ECPair.fromPublicKeyBuffer = function (buffer, network) {
// XXX: internal use only
function __fromPrivateKeyInteger (d, options) {
if (d.signum() <= 0) throw new Error('Private key must be greater than 0')
if (d.compareTo(secp256k1.n) >= 0) throw new Error('Private key must be less than the curve order')

return new ECPair(d, null, options)
}

// XXX: internal use only
function __fromPublicKeyPoint (Q, options) {
return new ECPair(null, Q, options)
}

function fromPrivateKeyBuffer (buffer, options) {
typeforce(types.UInt256, buffer)
var d = BigInteger.fromBuffer(d)

return new ECPair(d, null, options)
}

function fromPublicKeyBuffer (buffer, network) {
typeforce(types.ECPoint, Q)
var Q = ecurve.Point.decodeFrom(secp256k1, buffer)

return new ECPair(null, Q, {
Expand All @@ -57,7 +71,7 @@ ECPair.fromPublicKeyBuffer = function (buffer, network) {
})
}

ECPair.fromWIF = function (string, network) {
function fromWIF (string, network) {
var decoded = wif.decode(string)
var version = decoded.version

Expand All @@ -76,28 +90,22 @@ ECPair.fromWIF = function (string, network) {
if (version !== network.wif) throw new Error('Invalid network version')
}

var d = BigInteger.fromBuffer(decoded.privateKey)

return new ECPair(d, null, {
return fromPrivateKeyBuffer(decoded.privateKey, {
compressed: decoded.compressed,
network: network
})
}

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

var rng = options.rng || randomBytes

var d
var buffer
do {
var buffer = rng(32)
typeforce(types.Buffer256bit, buffer)

d = BigInteger.fromBuffer(buffer)
} while (d.signum() <= 0 || d.compareTo(secp256k1.n) >= 0)
buffer = rng(32)
} while (!types.UInt256(buffer))

return new ECPair(d, null, options)
return fromPrivateKeyBuffer(buffer, options)
}

ECPair.prototype.getAddress = function () {
Expand All @@ -112,6 +120,13 @@ ECPair.prototype.getPublicKeyBuffer = function () {
return this.Q.getEncoded(this.compressed)
}

ECPair.prototype.neutered = function () {
return __fromPublicKeyPoint(this.Q, {
compressed: this.compressed,
network: this.network
})
}

ECPair.prototype.sign = function (hash) {
if (!this.d) throw new Error('Missing private key')

Expand All @@ -128,4 +143,13 @@ ECPair.prototype.verify = function (hash, signature) {
return ecdsa.verify(hash, signature, this.Q)
}

module.exports = ECPair
module.exports = {
fromPrivateKeyBuffer: fromPrivateKeyBuffer,
fromPublicKeyBuffer: fromPublicKeyBuffer,
fromWIF: fromWIF,
makeRandom: makeRandom,

// XXX: internal use only
__fromPrivateKeyInteger: __fromPrivateKeyInteger,
__fromPublicKeyPoint: __fromPublicKeyPoint
}
37 changes: 12 additions & 25 deletions src/hdnode.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,7 @@ HDNode.fromSeedBuffer = function (seed, network) {

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

return new HDNode(keyPair, IR)
}
Expand Down Expand Up @@ -98,24 +95,19 @@ HDNode.fromBase58 = function (string, networks) {
if (version === network.bip32.private) {
if (buffer.readUInt8(45) !== 0x00) throw new Error('Invalid private key')

var d = BigInteger.fromBuffer(buffer.slice(46, 78))

keyPair = new ECPair(d, null, {
network: network
})
var d = buffer.slice(46, 78)
keyPair = ECPair.fromPrivateKeyBuffer(d, { network: network })

// 33 bytes: public key data (0x02 + X or 0x03 + X)
} else {
var Q = ecurve.Point.decodeFrom(curve, buffer.slice(45, 78))
if (!Q.compressed) throw new Error('Invalid public key')
var Q = buffer.slice(45, 78)
keyPair = ECPair.fromPublicKeyBuffer(Q, { network: network })

if (!keyPair.compressed) throw new Error('Invalid public key')

// Verify that the X coordinate in the public point corresponds to a point on the curve.
// If not, the extended public key is invalid.
curve.validate(Q)

keyPair = new ECPair(null, Q, {
network: network
})
curve.validate(keyPair.Q)
}

var hd = new HDNode(keyPair, chainCode)
Expand Down Expand Up @@ -147,11 +139,7 @@ HDNode.prototype.getPublicKeyBuffer = function () {
}

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

var neutered = new HDNode(neuteredKeyPair, this.chainCode)
var neutered = new HDNode(this.keyPair.neutered(), this.chainCode)
neutered.depth = this.depth
neutered.index = this.index
neutered.parentFingerprint = this.parentFingerprint
Expand Down Expand Up @@ -233,7 +221,6 @@ HDNode.prototype.derive = function (index) {
var I = createHmac('sha512', this.chainCode).update(data).digest()
var IL = I.slice(0, 32)
var IR = I.slice(32)

var pIL = BigInteger.fromBuffer(IL)

// In case parse256(IL) >= n, proceed with the next value for i
Expand All @@ -252,7 +239,7 @@ HDNode.prototype.derive = function (index) {
return this.derive(index + 1)
}

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

Expand All @@ -267,7 +254,7 @@ HDNode.prototype.derive = function (index) {
return this.derive(index + 1)
}

derivedKeyPair = new ECPair(null, Ki, {
derivedKeyPair = ECPair.__fromPublicKeyPoint(Ki, {
network: this.keyPair.network
})
}
Expand All @@ -290,7 +277,7 @@ HDNode.prototype.deriveHardened = function (index) {
// Private === not neutered
// Public === neutered
HDNode.prototype.isNeutered = function () {
return !(this.keyPair.d)
return !this.keyPair.d
}

HDNode.prototype.derivePath = function (path) {
Expand Down
29 changes: 28 additions & 1 deletion src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,31 @@ function Satoshi (value) {
return typeforce.UInt53(value) && value <= SATOSHI_MAX
}

var EC_ZERO = new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex')
var EC_UINT_MAX = new Buffer('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 'hex')

function UInt256 (value) {
return Buffer.isBuffer(value) &&
value.length === 32 &&
value.compare(EC_ZERO) > 0 && // > 0
value.compare(EC_UINT_MAX) < 0 // < n-1
}

function ECPointB (value) {
if (!Buffer.isBuffer(value)) return false
if (value.length < 33) return false

switch (value[0]) {
case 0x02:
case 0x03:
return value.length === 33
case 0x04:
return value.length === 65
}

return false
}

// external dependent types
var BigInt = typeforce.quacksLike('BigInteger')
var ECPoint = typeforce.quacksLike('Point')
Expand All @@ -39,12 +64,14 @@ var types = {
BIP32Path: BIP32Path,
Buffer256bit: typeforce.BufferN(32),
ECPoint: ECPoint,
ECPointB: ECPointB,
ECSignature: ECSignature,
Hash160bit: typeforce.BufferN(20),
Hash256bit: typeforce.BufferN(32),
Network: Network,
Satoshi: Satoshi,
UInt31: UInt31
UInt31: UInt31,
UInt256: UInt256
}

for (var typeName in typeforce) {
Expand Down
33 changes: 22 additions & 11 deletions test/ecpair.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ var ecurve = require('ecurve')
var proxyquire = require('proxyquire')
var sinon = require('sinon')

var BigInteger = require('bigi')
var bigi = require('bigi')
var ECPair = require('../src/ecpair')

var fixtures = require('./fixtures/ecpair.json')
Expand All @@ -19,24 +19,26 @@ for (var networkName in NETWORKS) {
NETWORKS_LIST.push(NETWORKS[networkName])
}

var ONE = new Buffer('0000000000000000000000000000000000000000000000000000000000000001', 'hex')

describe('ECPair', function () {
describe('constructor', function () {
describe('fromPrivateKeyBuffer', function () {
it('defaults to compressed', function () {
var keyPair = new ECPair(BigInteger.ONE)
var keyPair = ECPair.fromPrivateKeyBuffer(ONE)

assert.strictEqual(keyPair.compressed, true)
})

it('supports the uncompressed option', function () {
var keyPair = new ECPair(BigInteger.ONE, null, {
var keyPair = ECPair.fromPrivateKeyBuffer(bigi.ONE, {
compressed: false
})

assert.strictEqual(keyPair.compressed, false)
})

it('supports the network option', function () {
var keyPair = new ECPair(BigInteger.ONE, null, {
var keyPair = ECPair.fromPrivateKeyBuffer(bigi.ONE, {
compressed: false,
network: NETWORKS.testnet
})
Expand All @@ -46,7 +48,7 @@ describe('ECPair', function () {

fixtures.valid.forEach(function (f) {
it('calculates the public point for ' + f.WIF, function () {
var d = new BigInteger(f.d)
var keyPair = ECPair.fromPrivateKeyBuffer(new Buffer(f.d, 'hex'), {
var keyPair = new ECPair(d, null, {
compressed: f.compressed
})
Expand All @@ -57,7 +59,7 @@ describe('ECPair', function () {

fixtures.invalid.constructor.forEach(function (f) {
it('throws ' + f.exception, function () {
var d = f.d && new BigInteger(f.d)
var d = f.d && new bigi(f.d)
var Q = f.Q && ecurve.Point.decodeFrom(curve, new Buffer(f.Q, 'hex'))

assert.throws(function () {
Expand All @@ -71,7 +73,7 @@ describe('ECPair', function () {
var keyPair

beforeEach(function () {
keyPair = new ECPair(BigInteger.ONE)
keyPair = new ECPair(bigi.ONE)
})

it('wraps Q.getEncoded', sinon.test(function () {
Expand Down Expand Up @@ -165,12 +167,21 @@ describe('ECPair', function () {
assert.strictEqual(keyPair.network, NETWORKS.testnet)
})

it('loops until d is within interval [1, n - 1]', sinon.test(function () {
it('loops until d is within interval [1, n - 1] : 1', sinon.test(function () {
var rng = this.mock()
rng.exactly(2)
rng.onCall(0).returns(bigi.ZERO) // invalid length
rng.onCall(1).returns(bigi.ONE.toBuffer(32)) // === 1

ECPair.makeRandom({ rng: rng })
}))

it('loops until d is within interval [1, n - 1] : n -1', sinon.test(function () {
var rng = this.mock()
rng.exactly(3)
rng.onCall(0).returns(new BigInteger('0').toBuffer(32)) // < 1
rng.onCall(0).returns(bigi.ZERO.toBuffer(32)) // < 1
rng.onCall(1).returns(curve.n.toBuffer(32)) // > n-1
rng.onCall(2).returns(new BigInteger('42').toBuffer(32)) // valid
rng.onCall(2).returns((curve.n.subtract(bigi.ONE)).toBuffer(32)) // === n-1

ECPair.makeRandom({ rng: rng })
}))
Expand Down

0 comments on commit c9f0c43

Please sign in to comment.