From 00308542cddc84ac8417761f5deaeebca4749b4d Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Mon, 14 Sep 2015 17:46:02 +1000 Subject: [PATCH] wip --- src/ecdsa.js | 56 ++++++++++++++++++- src/ecpair.js | 110 ++++++++++++++++++------------------- src/hdnode.js | 79 ++++++++++++-------------- test/ecpair.js | 27 ++++----- test/fixtures/ecpair.json | 12 ++-- test/integration/crypto.js | 4 +- 6 files changed, 163 insertions(+), 125 deletions(-) diff --git a/src/ecdsa.js b/src/ecdsa.js index 103095dbd..f0a090c33 100644 --- a/src/ecdsa.js +++ b/src/ecdsa.js @@ -237,6 +237,54 @@ function calcPubKeyRecoveryParam (e, signature, Q) { throw new Error('Unable to find valid recovery factor') } +function intAdd (a, b) { + typeforce(types.tuple(types.Buffer, types.Buffer), arguments) + + var A = BigInteger.fromBuffer(a) + var B = BigInteger.fromBuffer(b) + + return A.add(B).toBuffer(32) +} + +function intCheck (a) { + typeforce(types.tuple(types.Buffer), arguments) + + var A = BigInteger.fromBuffer(a) + + return A.signum() > 0 && A.compareTo(secp256k1.n) < 0 +} + +function intSign (a) { + typeforce(types.tuple(types.Buffer), arguments) + + return BigInteger.fromBuffer(a).signum() +} + +function pointAdd (p, q) { + typeforce(types.tuple(types.Buffer, types.Buffer), arguments) + + var P = ecurve.Point.decodeFrom(p) + var Q = ecurve.Point.decodeFrom(q) + var R = P.add(Q) + + if (secp256k1.isInfinity(R)) return null + return R.getEncoded(P.compressed) +} + +function pointDerive (d, compressed) { + typeforce(types.tuple(types.Buffer, types.Boolean), arguments) + + d = BigInteger.fromBuffer(d) + return secp256k1.G.multiply(d).getEncoded(compressed) +} + +function pointVerify (q) { + typeforce(types.tuple(types.Buffer), arguments) + var Q = ecurve.Point.decodeFrom(q) + + return secp256k1.validate(Q) +} + module.exports = { calcPubKeyRecoveryParam: calcPubKeyRecoveryParam, deterministicGenerateK: deterministicGenerateK, @@ -244,6 +292,10 @@ module.exports = { sign: sign, verify: verify, - // TODO: remove - __curve: secp256k1 + intAdd: intAdd, + intCheck: intCheck, + intSign: intSign, + pointAdd: pointAdd, + pointDerive: pointDerive, + pointVerify: pointVerify } diff --git a/src/ecpair.js b/src/ecpair.js index 17d19e12f..9bb73cf3d 100644 --- a/src/ecpair.js +++ b/src/ecpair.js @@ -1,60 +1,56 @@ +var baddress = require('./address') var bcrypto = require('./crypto') var bs58check = require('bs58check') -var ecdsa = require('./ecdsa') var randomBytes = require('randombytes') +var secp256k1 = require('./ecdsa') var typeforce = require('typeforce') var types = require('./types') var wif = require('wif') var NETWORKS = require('./networks') -var BigInteger = require('bigi') - -var ecurve = require('ecurve') -var secp256k1 = ecdsa.__curve function ECPair (d, Q, options) { if (options) { typeforce({ compressed: types.maybe(types.Boolean), - network: types.maybe(types.Network) + network: types.maybe(types.Network), + validate: types.maybe(types.Boolean) }, options) + } else { + options = {} } - options = options || {} + options.network = options.network || NETWORKS.bitcoin 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') + typeforce(types.Buffer256bit, d) - this.d = d - } else { - typeforce(types.ECPoint, Q) + if (Q) throw new TypeError('Unexpected public key parameter') + if (!secp256k1.intCheck(d)) throw new TypeError('Private key must be within the interval [1, n - 1]') - this.__Q = Q - } + this.__d = d + this.__compressed = options.compressed === undefined ? true : options.compressed + } else if (Q) { + typeforce(types.Buffer, Q) - this.compressed = options.compressed === undefined ? true : options.compressed - this.network = options.network || NETWORKS.bitcoin -} + if (options.validate) { + secp256k1.pointVerify(Q) + } -Object.defineProperty(ECPair.prototype, 'Q', { - get: function () { - if (!this.__Q && this.d) { - this.__Q = secp256k1.G.multiply(this.d) + if (options.compressed && Q.length !== 65) { + throw new TypeError('Expected compressed public key') + } else if (Q.length !== 33) { + throw new TypeError('Expected uncompressed public key') } - return this.__Q - } -}) + this.__Q = Q -ECPair.fromPublicKeyBuffer = function (buffer, network) { - var Q = ecurve.Point.decodeFrom(secp256k1, buffer) + // TODO: remove + this.__compressed = (Q.length === 33) + } - return new ECPair(null, Q, { - compressed: Q.compressed, - network: network - }) + typeforce(types.Network, options.network) + this.__network = options.network } ECPair.fromWIF = function (string, network) { @@ -69,10 +65,9 @@ ECPair.fromWIF = function (string, network) { }).pop() || {} } - var decoded = wif.decodeRaw(network.wif, buffer) - var d = BigInteger.fromBuffer(decoded.d) + var decoded = wif.decode(network.wif, string) - return new ECPair(d, null, { + return new ECPair(decoded.d, null, { compressed: decoded.compressed, network: network }) @@ -82,51 +77,52 @@ ECPair.makeRandom = function (options) { options = options || {} var rng = options.rng || randomBytes - var d do { - var buffer = rng(32) - typeforce(types.Buffer256bit, buffer) - - d = BigInteger.fromBuffer(buffer) - } while (d.signum() <= 0 || d.compareTo(secp256k1.n) >= 0) + d = rng(32) + typeforce(types.Buffer256bit, d) + } while (!secp256k1.intCheck(d)) return new ECPair(d, null, options) } ECPair.prototype.getAddress = function () { - var pubKey = this.getPublicKeyBuffer() - var pubKeyHash = bcrypto.hash160(pubKey) + return baddress.toBase58Check(bcrypto.hash160(this.getPublic()), this.getNetwork().pubKeyHash) +} - var payload = new Buffer(21) - payload.writeUInt8(this.network.pubKeyHash, 0) - pubKeyHash.copy(payload, 1) +ECPair.prototype.getNetwork = function () { + return this.__network +} - return bs58check.encode(payload) +ECPair.prototype.getPrivate = function () { + if (!this.__d) throw new Error('Missing private key') + return this.__d } -ECPair.prototype.getNetwork = function () { - return this.network +ECPair.prototype.getPublic = function () { + if (!this.__Q) { + this.__Q = secp256k1.pointDerive(this.getPrivate(), this.isCompressed()) + } + + return this.__Q } -ECPair.prototype.getPublicKeyBuffer = function () { - return this.Q.getEncoded(this.compressed) +ECPair.prototype.isCompressed = function () { + return this.__compressed +// return this.getPublic().length === 33 } ECPair.prototype.sign = function (hash) { - if (!this.d) throw new Error('Missing private key') - - return ecdsa.sign(hash, this.d) + return secp256k1.sign(hash, this.getPrivate()) } ECPair.prototype.toWIF = function () { - if (!this.d) throw new Error('Missing private key') - - return wif.encode(this.network.wif, this.d.toBuffer(32), this.compressed) + if (!this.__d) throw new Error('Missing private key') + return wif.encode(this.getNetwork().wif, this.getPrivate(), this.isCompressed()) } ECPair.prototype.verify = function (hash, signature) { - return ecdsa.verify(hash, signature, this.Q) + return secp256k1.verify(hash, signature, this.getPublic()) } module.exports = ECPair diff --git a/src/hdnode.js b/src/hdnode.js index 80ae630c8..d1f80541b 100644 --- a/src/hdnode.js +++ b/src/hdnode.js @@ -1,20 +1,17 @@ var base58check = require('bs58check') var bcrypto = require('./crypto') var createHmac = require('create-hmac') +var secp256k1 = require('./ecdsa') var typeforce = require('typeforce') var types = require('./types') -var NETWORKS = require('./networks') -var BigInteger = require('bigi') var ECPair = require('./ecpair') - -var ecurve = require('ecurve') -var curve = ecurve.getCurveByName('secp256k1') +var NETWORKS = require('./networks') function HDNode (keyPair, chainCode) { typeforce(types.tuple('ECPair', types.Buffer256bit), arguments) - if (!keyPair.compressed) throw new TypeError('BIP32 only allows compressed keyPairs') + if (!keyPair.__compressed) throw new TypeError('BIP32 only allows compressed keyPairs') this.keyPair = keyPair this.chainCode = chainCode @@ -37,10 +34,9 @@ HDNode.fromSeedBuffer = function (seed, network) { var IL = I.slice(0, 32) var IR = I.slice(32) - // 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, { + // if IL is <= 0 or >= n, the master key is invalid + // checked by the ECPair constructor + var keyPair = new ECPair(IL, null, { network: network }) @@ -94,25 +90,24 @@ HDNode.fromBase58 = function (string, networks) { // 33 bytes: private key data (0x00 + k) if (version === network.bip32.private) { - if (buffer.readUInt8(45) !== 0x00) throw new Error('Invalid private key') - - var d = BigInteger.fromBuffer(buffer.slice(46, 78)) + if (buffer[45] !== 0x00) throw new Error('Invalid private key') + var d = buffer.slice(46, 78) keyPair = new ECPair(d, null, { + compressed: true, 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') - - // 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) + var Q = buffer.slice(45, 78) keyPair = new ECPair(null, Q, { - network: network + compressed: true, + network: network, + + // verify the public point corresponds to a point on the curve + validate: true }) } @@ -129,7 +124,7 @@ HDNode.prototype.getAddress = function () { } HDNode.prototype.getIdentifier = function () { - return bcrypto.hash160(this.keyPair.getPublicKeyBuffer()) + return bcrypto.hash160(this.keyPair.getPublic()) } HDNode.prototype.getFingerprint = function () { @@ -193,12 +188,12 @@ HDNode.prototype.toBase58 = function (__isPrivate) { if (this.keyPair.d) { // 0x00 + k for private keys buffer.writeUInt8(0, 45) - this.keyPair.d.toBuffer(32).copy(buffer, 46) + this.keyPair.getPrivate().copy(buffer, 46) // 33 bytes: the public key } else { // X9.62 encoding for public keys - this.keyPair.getPublicKeyBuffer().copy(buffer, 45) + this.keyPair.getPublic().copy(buffer, 45) } return base58check.encode(buffer) @@ -209,20 +204,20 @@ HDNode.prototype.derive = function (index) { var isHardened = index >= HDNode.HIGHEST_BIT var data = new Buffer(37) - // Hardened child + // hardened child if (isHardened) { if (!this.keyPair.d) throw new TypeError('Could not derive hardened child key') // data = 0x00 || ser256(kpar) || ser32(index) data[0] = 0x00 - this.keyPair.d.toBuffer(32).copy(data, 1) + this.keyPair.getPrivate().copy(data, 1) data.writeUInt32BE(index, 33) - // Normal child + // normal child } else { // data = serP(point(kpar)) || ser32(index) // = serP(Kpar) || ser32(index) - this.keyPair.getPublicKeyBuffer().copy(data, 0) + this.keyPair.getPublic().copy(data, 0) data.writeUInt32BE(index, 33) } @@ -230,40 +225,40 @@ HDNode.prototype.derive = function (index) { 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 - if (pIL.compareTo(curve.n) >= 0) { + // proceed for the next value of i if IL isn't within the interval + if (!secp256k1.intCheck(IL)) { return this.derive(index + 1) } - // Private parent key -> private child key + // private parent key -> private child key var derivedKeyPair if (this.keyPair.d) { // ki = parse256(IL) + kpar (mod n) - var ki = pIL.add(this.keyPair.d).mod(curve.n) + var ki = secp256k1.intAdd(IL, this.keyPair.getPrivate()) - // In case ki == 0, proceed with the next value for i - if (ki.signum() === 0) { + // in case ki == 0, proceed with the next value for i + if (secp256k1.intSign(ki) === 0) { return this.derive(index + 1) } derivedKeyPair = new ECPair(ki, null, { + compressed: true, network: this.keyPair.network }) - // Public parent key -> public child key + // public parent key -> public child key } else { - // Ki = point(parse256(IL)) + Kpar - // = G*IL + Kpar - var Ki = curve.G.multiply(pIL).add(this.keyPair.Q) + // Ki = Kpar + point(parse256(IL)) + // = Kpar + G*IL + var Ki = secp256k1.pointAdd(this.keyPair.getPublic(), secp256k1.pointDerive(IL)) - // In case Ki is the point at infinity, proceed with the next value for i - if (curve.isInfinity(Ki)) { + // in case Ki is the point at infinity, proceed with the next value for i + if (Ki === null) { return this.derive(index + 1) } derivedKeyPair = new ECPair(null, Ki, { + compressed: true, network: this.keyPair.network }) } @@ -281,6 +276,4 @@ HDNode.prototype.deriveHardened = function (index) { return this.derive(index + HDNode.HIGHEST_BIT) } -HDNode.prototype.toString = HDNode.prototype.toBase58 - module.exports = HDNode diff --git a/test/ecpair.js b/test/ecpair.js index b7edbd049..e7abfc483 100644 --- a/test/ecpair.js +++ b/test/ecpair.js @@ -11,7 +11,6 @@ var BigInteger = require('bigi') var ECPair = require('../src/ecpair') var fixtures = require('./fixtures/ecpair.json') -var curve = ecdsa.__curve var NETWORKS = require('../src/networks') var NETWORKS_LIST = [] // Object.values(NETWORKS) @@ -19,46 +18,48 @@ for (var networkName in NETWORKS) { NETWORKS_LIST.push(NETWORKS[networkName]) } +var ONE = new Buffer('0000000000000000000000000000000000000000000000000000000000000001', 'hex') + describe('ECPair', function () { describe('constructor', function () { it('defaults to compressed', function () { - var keyPair = new ECPair(BigInteger.ONE) + var keyPair = new ECPair(ONE) - assert.strictEqual(keyPair.compressed, true) + assert.strictEqual(keyPair.__compressed, true) }) it('supports the uncompressed option', function () { - var keyPair = new ECPair(BigInteger.ONE, null, { + var keyPair = new ECPair(ONE, null, { compressed: false }) - assert.strictEqual(keyPair.compressed, false) + assert.strictEqual(keyPair.__compressed, false) }) it('supports the network option', function () { - var keyPair = new ECPair(BigInteger.ONE, null, { + var keyPair = new ECPair(ONE, null, { compressed: false, network: NETWORKS.testnet }) - assert.strictEqual(keyPair.network, NETWORKS.testnet) + assert.strictEqual(keyPair.getNetwork(), NETWORKS.testnet) }) fixtures.valid.forEach(function (f) { it('calculates the public point for ' + f.WIF, function () { - var d = new BigInteger(f.d) + var d = new BigInteger(f.d).toBuffer(32) var keyPair = new ECPair(d, null, { compressed: f.compressed }) - assert.strictEqual(keyPair.getPublicKeyBuffer().toString('hex'), f.Q) + assert.strictEqual(keyPair.getPublic().toString('hex'), f.Q) }) }) fixtures.invalid.constructor.forEach(function (f) { it('throws ' + f.exception, function () { - var d = f.d && new BigInteger(f.d) - var Q = f.Q && ecurve.Point.decodeFrom(curve, new Buffer(f.Q, 'hex')) + var d = f.d && new BigInteger(f.d).toBuffer(32) + var Q = f.Q && new Buffer(f.Q, 'hex') assert.throws(function () { new ECPair(d, Q, f.options) @@ -67,7 +68,7 @@ describe('ECPair', function () { }) }) - describe('getPublicKeyBuffer', function () { + describe('getPublic', function () { var keyPair beforeEach(function () { @@ -78,7 +79,7 @@ describe('ECPair', function () { this.mock(keyPair.Q).expects('getEncoded') .once().withArgs(keyPair.compressed) - keyPair.getPublicKeyBuffer() + keyPair.getPublic() })) }) diff --git a/test/fixtures/ecpair.json b/test/fixtures/ecpair.json index 652eb2d4c..ed4872db1 100644 --- a/test/fixtures/ecpair.json +++ b/test/fixtures/ecpair.json @@ -68,19 +68,15 @@ "invalid": { "constructor": [ { - "exception": "Private key must be greater than 0", - "d": "-1" - }, - { - "exception": "Private key must be greater than 0", + "exception": "Private key must be within the interval \\[1, n - 1\\]", "d": "0" }, { - "exception": "Private key must be less than the curve order", + "exception": "Private key must be within the interval \\[1, n - 1\\]", "d": "115792089237316195423570985008687907852837564279074904382605163141518161494337" }, { - "exception": "Private key must be less than the curve order", + "exception": "Private key must be within the interval \\[1, n - 1\\]", "d": "115792089237316195423570985008687907853269984665640564039457584007913129639935" }, { @@ -91,7 +87,7 @@ } }, { - "exception": "Unexpected publicKey parameter", + "exception": "Unexpected public key parameter", "d": "1", "Q": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" }, diff --git a/test/integration/crypto.js b/test/integration/crypto.js index 6eff7306c..df4124657 100644 --- a/test/integration/crypto.js +++ b/test/integration/crypto.js @@ -59,7 +59,7 @@ describe('bitcoinjs-lib (crypto)', function () { var curve = secp256k1 var QP = master.keyPair.Q - var serQP = master.keyPair.getPublicKeyBuffer() + var serQP = master.keyPair.getPublicKey() var d1 = child.keyPair.d var d2 @@ -145,7 +145,7 @@ describe('bitcoinjs-lib (crypto)', function () { var prevOutScript = prevOut.outs[prevVout].script var scriptSignature = bitcoin.ECSignature.parseScriptSignature(scriptChunks[0]) - var publicKey = bitcoin.ECPair.fromPublicKeyBuffer(scriptChunks[1]) + var publicKey = new bitcoin.ECPair(null, scriptChunks[1]) var m = transaction.hashForSignature(input.vout, prevOutScript, scriptSignature.hashType) assert(publicKey.verify(m, scriptSignature.signature), 'Invalid m')