From 58fd12e66d004e418a1e6b6383640a6cb4328967 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Sat, 24 May 2014 16:25:38 +1000 Subject: [PATCH] ecdsa: always use signature object --- src/ecdsa.js | 66 +++++++++++++++++++------------------ src/ecpubkey.js | 2 +- src/message.js | 12 +++---- src/transaction.js | 6 ++-- test/ecdsa.js | 62 +++++++++++++++++++---------------- test/fixtures/ecdsa.json | 70 ++++++++++++++++++++++++---------------- 6 files changed, 122 insertions(+), 96 deletions(-) diff --git a/src/ecdsa.js b/src/ecdsa.js index 4fd95b3..2f3c11b 100644 --- a/src/ecdsa.js +++ b/src/ecdsa.js @@ -54,23 +54,21 @@ function sign(ecparams, hash, D) { return {r: r, s: s} } -function verify(ecparams, hash, r, s, Q) { +function verify(ecparams, hash, signature, Q) { var e = BigInteger.fromBuffer(hash) - return verifyRaw(ecparams, e, r, s, Q) + return verifyRaw(ecparams, e, signature, Q) } -function verifyRaw(ecparams, e, r, s, Q) { +function verifyRaw(ecparams, e, signature, Q) { var n = ecparams.getN() var G = ecparams.getG() - if (r.signum() === 0 || r.compareTo(n) >= 0) { - return false - } + var r = signature.r + var s = signature.s - if (s.signum() === 0 || s.compareTo(n) >= 0) { - return false - } + if (r.signum() === 0 || r.compareTo(n) >= 0) return false + if (s.signum() === 0 || s.compareTo(n) >= 0) return false var c = s.modInverse(n) @@ -88,9 +86,9 @@ function verifyRaw(ecparams, e, r, s, Q) { * * Takes two BigIntegers representing r and s and returns a byte array. */ -function serializeSig(r, s) { - var rBa = r.toDERInteger() - var sBa = s.toDERInteger() +function serializeSig(signature) { + var rBa = signature.r.toDERInteger() + var sBa = signature.s.toDERInteger() var sequence = [] sequence.push(0x02); // INTEGER @@ -136,7 +134,7 @@ function parseSig(buffer) { } } -function serializeSigCompact(r, s, i, compressed) { +function serializeSigCompact(signature, i, compressed) { if (compressed) { i += 4 } @@ -145,8 +143,9 @@ function serializeSigCompact(r, s, i, compressed) { var buffer = new Buffer(65) buffer.writeUInt8(i, 0) - r.toBuffer(32).copy(buffer, 1) - s.toBuffer(32).copy(buffer, 33) + + signature.r.toBuffer(32).copy(buffer, 1) + signature.s.toBuffer(32).copy(buffer, 33) return buffer } @@ -166,8 +165,10 @@ function parseSigCompact(buffer) { var s = BigInteger.fromBuffer(buffer.slice(33)) return { - r: r, - s: s, + signature: { + r: r, + s: s + }, i: i, compressed: compressed } @@ -181,9 +182,12 @@ function parseSigCompact(buffer) { * * http://www.secg.org/download/aid-780/sec1-v2.pdf */ -function recoverPubKey(ecparams, e, r, s, i) { +function recoverPubKey(ecparams, e, signature, i) { assert.strictEqual(i & 3, i, 'The recovery param is more than two bits') + var r = signature.r + var s = signature.s + // A set LSB signifies that the y-coordinate is odd // By reduction, the y-coordinate is even if it is clear var isYEven = !(i & 1) @@ -229,7 +233,7 @@ function recoverPubKey(ecparams, e, r, s, i) { var Q = R.multiplyTwo(s, G, eNeg).multiply(rInv) Q.validate() - if (!verifyRaw(ecparams, e, r, s, Q)) { + if (!verifyRaw(ecparams, e, signature, Q)) { throw new Error("Pubkey recovery unsuccessful") } @@ -247,9 +251,9 @@ function recoverPubKey(ecparams, e, r, s, i) { * This function simply tries all four cases and returns the value * that resulted in a successful pubkey recovery. */ -function calcPubKeyRecoveryParam(ecparams, e, r, s, Q) { +function calcPubKeyRecoveryParam(ecparams, e, signature, Q) { for (var i = 0; i < 4; i++) { - var Qprime = recoverPubKey(ecparams, e, r, s, i) + var Qprime = recoverPubKey(ecparams, e, signature, i) if (Qprime.equals(Q)) { return i @@ -260,14 +264,14 @@ function calcPubKeyRecoveryParam(ecparams, e, r, s, Q) { } module.exports = { -calcPubKeyRecoveryParam: calcPubKeyRecoveryParam, -deterministicGenerateK: deterministicGenerateK, -recoverPubKey: recoverPubKey, -sign: sign, -verify: verify, -verifyRaw: verifyRaw, -serializeSig: serializeSig, -parseSig: parseSig, -serializeSigCompact: serializeSigCompact, -parseSigCompact: parseSigCompact + calcPubKeyRecoveryParam: calcPubKeyRecoveryParam, + deterministicGenerateK: deterministicGenerateK, + recoverPubKey: recoverPubKey, + sign: sign, + verify: verify, + verifyRaw: verifyRaw, + serializeSig: serializeSig, + parseSig: parseSig, + serializeSigCompact: serializeSigCompact, + parseSigCompact: parseSigCompact } diff --git a/src/ecpubkey.js b/src/ecpubkey.js index 730bf5a..026be2b 100644 --- a/src/ecpubkey.js +++ b/src/ecpubkey.js @@ -37,7 +37,7 @@ ECPubKey.prototype.getAddress = function(version) { } ECPubKey.prototype.verify = function(hash, signature) { - return ecdsa.verify(ecparams, hash, signature.r, signature.s, this.Q) + return ecdsa.verify(ecparams, hash, signature, this.Q) } // Export functions diff --git a/src/message.js b/src/message.js index e931fea..bdd7e78 100644 --- a/src/message.js +++ b/src/message.js @@ -28,11 +28,11 @@ function sign(key, message, network) { network = network || networks.bitcoin var hash = magicHash(message, network) - var sig = key.sign(hash) + var signature = key.sign(hash) var e = BigInteger.fromBuffer(hash) - var i = ecdsa.calcPubKeyRecoveryParam(ecparams, e, sig.r, sig.s, key.pub.Q) + var i = ecdsa.calcPubKeyRecoveryParam(ecparams, e, signature, key.pub.Q) - return ecdsa.serializeSigCompact(sig.r, sig.s, i, key.pub.compressed) + return ecdsa.serializeSigCompact(signature, i, key.pub.compressed) } // TODO: network could be implied from address @@ -44,11 +44,11 @@ function verify(address, compactSig, message, network) { network = network || networks.bitcoin var hash = magicHash(message, network) - var sig = ecdsa.parseSigCompact(compactSig) + var parsed = ecdsa.parseSigCompact(compactSig) var e = BigInteger.fromBuffer(hash) - var Q = ecdsa.recoverPubKey(ecparams, e, sig.r, sig.s, sig.i) + var Q = ecdsa.recoverPubKey(ecparams, e, parsed.signature, parsed.i) - var pubKey = new ECPubKey(Q, sig.compressed) + var pubKey = new ECPubKey(Q, parsed.compressed) return pubKey.getAddress(address.version).toString() === address.toString() } diff --git a/src/transaction.js b/src/transaction.js index 09db26d..5d39d89 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -373,11 +373,11 @@ Transaction.prototype.signScriptSig = function(index, scriptPubKey, key, type) { // assert.equal(type & 0x7F, type, 'Invalid type') // TODO var hash = this.hashForSignature(scriptPubKey, index, type) - var sig = key.sign(hash) - var DERsig = ecdsa.serializeSig(sig.r, sig.s) + var signature = key.sign(hash) + var DERencoded = ecdsa.serializeSig(signature) return Buffer.concat([ - new Buffer(DERsig), + new Buffer(DERencoded), new Buffer([type]) ]) } diff --git a/test/ecdsa.js b/test/ecdsa.js index cbe1fd3..ca86af4 100644 --- a/test/ecdsa.js +++ b/test/ecdsa.js @@ -32,9 +32,9 @@ describe('ecdsa', function() { var Q = ecparams.getG().multiply(D) var hash = message.magicHash('1111', networks.bitcoin) var e = BigInteger.fromBuffer(hash) - var psig = ecdsa.parseSigCompact(signature) + var parsed = ecdsa.parseSigCompact(signature) - var Qprime = ecdsa.recoverPubKey(ecparams, e, psig.r, psig.s, psig.i) + var Qprime = ecdsa.recoverPubKey(ecparams, e, parsed.signature, parsed.i) assert(Q.equals(Qprime)) }) }) @@ -44,10 +44,10 @@ describe('ecdsa', function() { fixtures.valid.forEach(function(f) { var D = BigInteger.fromHex(f.D) var hash = crypto.sha256(f.message) - var sig = ecdsa.sign(ecparams, hash, D) + var signature = ecdsa.sign(ecparams, hash, D) - assert.equal(sig.r.toString(), f.signature.r) - assert.equal(sig.s.toString(), f.signature.s) + assert.equal(signature.r.toString(), f.signature.r) + assert.equal(signature.s.toString(), f.signature.s) }) }) @@ -67,11 +67,13 @@ describe('ecdsa', function() { var D = BigInteger.fromHex(f.D) var Q = ecparams.getG().multiply(D) - var r = new BigInteger(f.signature.r) - var s = new BigInteger(f.signature.s) + var signature = { + r: new BigInteger(f.signature.r), + s: new BigInteger(f.signature.s) + } var e = BigInteger.fromBuffer(crypto.sha256(f.message)) - assert(ecdsa.verifyRaw(ecparams, e, r, s, Q)) + assert(ecdsa.verifyRaw(ecparams, e, signature, Q)) }) }) @@ -79,11 +81,13 @@ describe('ecdsa', function() { it('fails to verify with ' + f.description, function() { var D = BigInteger.fromHex(f.D) var e = BigInteger.fromHex(f.e) - var r = new BigInteger(f.signature.r) - var s = new BigInteger(f.signature.s) + var signature = { + r: new BigInteger(f.signature.r), + s: new BigInteger(f.signature.s) + } var Q = ecparams.getG().multiply(D) - assert.equal(ecdsa.verifyRaw(ecparams, e, r, s, Q), false) + assert.equal(ecdsa.verifyRaw(ecparams, e, signature, Q), false) }) }) }) @@ -91,10 +95,12 @@ describe('ecdsa', function() { describe('serializeSig', function() { it('encodes a DER signature', function() { fixtures.valid.forEach(function(f) { - var r = new BigInteger(f.signature.r) - var s = new BigInteger(f.signature.s) + var signature = { + r: new BigInteger(f.signature.r), + s: new BigInteger(f.signature.s) + } - var signature = new Buffer(ecdsa.serializeSig(r, s)) + var signature = new Buffer(ecdsa.serializeSig(signature)) assert.equal(signature.toString('hex'), f.DER) }) }) @@ -125,13 +131,15 @@ describe('ecdsa', function() { describe('serializeSigCompact', function() { it('encodes a compact signature', function() { fixtures.valid.forEach(function(f) { - var r = new BigInteger(f.signature.r) - var s = new BigInteger(f.signature.s) - var i = f.signature.i - var compressed = f.signature.compressed - - var signature = ecdsa.serializeSigCompact(r, s, i, compressed) - assert.equal(signature.toString('hex'), f.compact) + var signature = { + r: new BigInteger(f.signature.r), + s: new BigInteger(f.signature.s) + } + var i = f.compact.i + var compressed = f.compact.compressed + + var signature = ecdsa.serializeSigCompact(signature, i, compressed) + assert.equal(signature.toString('hex'), f.compact.hex) }) }) }) @@ -139,13 +147,13 @@ describe('ecdsa', function() { describe('parseSigCompact', function() { it('decodes the correct signature', function() { fixtures.valid.forEach(function(f) { - var buffer = new Buffer(f.compact, 'hex') - var signature = ecdsa.parseSigCompact(buffer) + var buffer = new Buffer(f.compact.hex, 'hex') + var parsed = ecdsa.parseSigCompact(buffer) - assert.equal(signature.r.toString(), f.signature.r) - assert.equal(signature.s.toString(), f.signature.s) - assert.equal(signature.i, f.signature.i) - assert.equal(signature.compressed, f.signature.compressed) + assert.equal(parsed.signature.r.toString(), f.signature.r) + assert.equal(parsed.signature.s.toString(), f.signature.s) + assert.equal(parsed.i, f.compact.i) + assert.equal(parsed.compressed, f.compact.compressed) }) }) diff --git a/test/fixtures/ecdsa.json b/test/fixtures/ecdsa.json index f3ce488..ce56f9f 100644 --- a/test/fixtures/ecdsa.json +++ b/test/fixtures/ecdsa.json @@ -4,91 +4,105 @@ "D": "01", "k": "ec633bd56a5774a0940cb97e27a9e4e51dc94af737596a0c5cbb3d30332d92a5", "message": "Everything should be made as simple as possible, but not simpler.", - "compact": "1f33a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c96f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa54342262", + "compact": { + "hex": "1f33a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c96f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa54342262", + "compressed": true, + "i": 0 + }, "DER": "3044022033a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c902206f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa54342262", "signature": { "r": "23362334225185207751494092901091441011938859014081160902781146257181456271561", - "s": "50433721247292933944369538617440297985091596895097604618403996029256432099938", - "i": 0, - "compressed": true + "s": "50433721247292933944369538617440297985091596895097604618403996029256432099938" } }, { "D": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "k": "9dc74cbfd383980fb4ae5d2680acddac9dac956dca65a28c80ac9c847c2374e4", "message": "Equations are more important to me, because politics is for the present, but an equation is something for eternity.", - "compact": "1b54c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed07082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5", + "compact": { + "hex": "1b54c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed07082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5", + "compressed": false, + "i": 0 + }, "DER": "3044022054c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed022007082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5", "signature": { "r": "38341707918488238920692284707283974715538935465589664377561695343399725051885", - "s": "3180566392414476763164587487324397066658063772201694230600609996154610926757", - "i": 0, - "compressed": false + "s": "3180566392414476763164587487324397066658063772201694230600609996154610926757" } }, { "D": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "k": "fd27071f01648ebbdd3e1cfbae48facc9fa97edc43bbbc9a7fdc28eae13296f5", "message": "Not only is the Universe stranger than we think, it is stranger than we can think.", - "compact": "1fff466a9f1b7b273e2f4c3ffe032eb2e814121ed18ef84665d0f515360dab3dd06fc95f5132e5ecfdc8e5e6e616cc77151455d46ed48f5589b7db7771a332b283", + "compact": { + "hex": "1fff466a9f1b7b273e2f4c3ffe032eb2e814121ed18ef84665d0f515360dab3dd06fc95f5132e5ecfdc8e5e6e616cc77151455d46ed48f5589b7db7771a332b283", + "compressed": true, + "i": 0 + }, "DER": "3045022100ff466a9f1b7b273e2f4c3ffe032eb2e814121ed18ef84665d0f515360dab3dd002206fc95f5132e5ecfdc8e5e6e616cc77151455d46ed48f5589b7db7771a332b283", "signature": { "r": "115464191557905790016094131873849783294273568009648050793030031933291767741904", - "s": "50562520307781850052192542766631199590053690478900449960232079510155113443971", - "i": 0, - "compressed": true + "s": "50562520307781850052192542766631199590053690478900449960232079510155113443971" } }, { "D": "0000000000000000000000000000000000000000000000000000000000000001", "k": "f0cd2ba5fc7c183de589f6416220a36775a146740798756d8d949f7166dcc87f", "message": "How wonderful that we have met with a paradox. Now we have some hope of making progress.", - "compact": "1cc0dafec8251f1d5010289d210232220b03202cba34ec11fec58b3e93a85b91d375afdc06b7d6322a590955bf264e7aaa155847f614d80078a90292fe205064d3", + "compact": { + "hex": "1cc0dafec8251f1d5010289d210232220b03202cba34ec11fec58b3e93a85b91d375afdc06b7d6322a590955bf264e7aaa155847f614d80078a90292fe205064d3", + "compressed": false, + "i": 1 + }, "DER": "3045022100c0dafec8251f1d5010289d210232220b03202cba34ec11fec58b3e93a85b91d3022075afdc06b7d6322a590955bf264e7aaa155847f614d80078a90292fe205064d3", "signature": { "r": "87230998027579607140680851455601772643840468630989315269459846730712163783123", - "s": "53231320085894623106179381504478252331065330583563809963303318469380290929875", - "i": 1, - "compressed": false + "s": "53231320085894623106179381504478252331065330583563809963303318469380290929875" } }, { "D": "69ec59eaa1f4f2e36b639716b7c30ca86d9a5375c7b38d8918bd9c0ebc80ba64", "k": "6bb4a594ad57c1aa22dbe991a9d8501daf4688bf50a4892ef21bd7c711afda97", "message": "Computer science is no more about computers than astronomy is about telescopes.", - "compact": "1f7186363571d65e084e7f02b0b77c3ec44fb1b257dee26274c38c928986fea45d0de0b38e06807e46bda1f1e293f4f6323e854c86d58abdd00c46c16441085df6", + "compact": { + "hex": "1f7186363571d65e084e7f02b0b77c3ec44fb1b257dee26274c38c928986fea45d0de0b38e06807e46bda1f1e293f4f6323e854c86d58abdd00c46c16441085df6", + "compressed": true, + "i": 0 + }, "DER": "304402207186363571d65e084e7f02b0b77c3ec44fb1b257dee26274c38c928986fea45d02200de0b38e06807e46bda1f1e293f4f6323e854c86d58abdd00c46c16441085df6", "signature": { "r": "51348483531757779992459563033975330355971795607481991320287437101831125115997", - "s": "6277080015686056199074771961940657638578000617958603212944619747099038735862", - "i": 0, - "compressed": true + "s": "6277080015686056199074771961940657638578000617958603212944619747099038735862" } }, { "D": "00000000000000000000000000007246174ab1e92e9149c6e446fe194d072637", "k": "097b5c8ee22c3ea78a4d3635e0ff6fe85a1eb92ce317ded90b9e71aab2b861cb", "message": "...if you aren't, at any given time, scandalized by code you wrote five or even three years ago, you're not learning anywhere near enough", - "compact": "1cfbfe5076a15860ba8ed00e75e9bd22e05d230f02a936b653eb55b61c99dda4870e68880ebb0050fe4312b1b1eb0899e1b82da89baa5b895f612619edf34cbd37", + "compact": { + "hex": "1cfbfe5076a15860ba8ed00e75e9bd22e05d230f02a936b653eb55b61c99dda4870e68880ebb0050fe4312b1b1eb0899e1b82da89baa5b895f612619edf34cbd37", + "compressed": false, + "i": 1 + }, "DER": "3045022100fbfe5076a15860ba8ed00e75e9bd22e05d230f02a936b653eb55b61c99dda48702200e68880ebb0050fe4312b1b1eb0899e1b82da89baa5b895f612619edf34cbd37", "signature": { "r": "113979859486826658566290715281614250298918272782414232881639314569529560769671", - "s": "6517071009538626957379450615706485096874328019806177698938278220732027419959", - "i": 1, - "compressed": false + "s": "6517071009538626957379450615706485096874328019806177698938278220732027419959" } }, { "D": "000000000000000000000000000000000000000000056916d0f9b31dc9b637f3", "k": "19355c36c8cbcdfb2382e23b194b79f8c97bf650040fc7728dfbf6b39a97c25b", "message": "The question of whether computers can think is like the question of whether submarines can swim.", - "compact": "20cde1302d83f8dd835d89aef803c74a119f561fbaef3eb9129e45f30de86abbf906ce643f5049ee1f27890467b77a6a8e11ec4661cc38cd8badf90115fbd03cef", + "compact": { + "hex": "20cde1302d83f8dd835d89aef803c74a119f561fbaef3eb9129e45f30de86abbf906ce643f5049ee1f27890467b77a6a8e11ec4661cc38cd8badf90115fbd03cef", + "compressed": true, + "i": 1 + }, "DER": "3045022100cde1302d83f8dd835d89aef803c74a119f561fbaef3eb9129e45f30de86abbf9022006ce643f5049ee1f27890467b77a6a8e11ec4661cc38cd8badf90115fbd03cef", "signature": { "r": "93122007060065279508564838030979550535085999589142852106617159184757394422777", - "s": "3078539468410661027472930027406594684630312677495124015420811882501887769839", - "i": 1, - "compressed": true + "s": "3078539468410661027472930027406594684630312677495124015420811882501887769839" } } ],