From 9d6ccdcab1fc237dbcfae41fc9e0ca1d2b7565ca Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Thu, 20 Mar 2014 05:09:34 -0700 Subject: [PATCH] [CHORE] Enable signature canonicalization. --- Gruntfile.js | 1 + src/js/ripple/keypair.js | 6 +++-- src/js/ripple/serializedtypes.js | 2 ++ src/js/ripple/transaction.js | 22 ++++++++++------- src/js/sjcl-custom/sjcl-ecdsa-canonical.js | 17 +++++++++++++ test/sjcl-ecdsa-canonical-test.js | 28 ++++++++++++++++++++++ 6 files changed, 65 insertions(+), 11 deletions(-) create mode 100644 src/js/sjcl-custom/sjcl-ecdsa-canonical.js create mode 100644 test/sjcl-ecdsa-canonical-test.js diff --git a/Gruntfile.js b/Gruntfile.js index 462a485f..a1b1bf5e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -41,6 +41,7 @@ module.exports = function(grunt) { "src/js/sjcl-custom/sjcl-extramath.js", "src/js/sjcl-custom/sjcl-montgomery.js", "src/js/sjcl-custom/sjcl-validecc.js", + "src/js/sjcl-custom/sjcl-ecdsa-canonical.js", "src/js/sjcl-custom/sjcl-ecdsa-der.js", "src/js/sjcl-custom/sjcl-jacobi.js" ], diff --git a/src/js/ripple/keypair.js b/src/js/ripple/keypair.js index db82e79d..518ae70d 100644 --- a/src/js/ripple/keypair.js +++ b/src/js/ripple/keypair.js @@ -89,8 +89,10 @@ KeyPair.prototype.get_address = function () { }; KeyPair.prototype.sign = function (hash) { - var hash = UInt256.from_json(hash); - return this._secret.signDER(hash.to_bits(), 0); + hash = UInt256.from_json(hash); + var sig = this._secret.sign(hash.to_bits(), 0); + sig = this._secret.canonicalizeSignature(sig); + return this._secret.encodeDER(sig); }; exports.KeyPair = KeyPair; diff --git a/src/js/ripple/serializedtypes.js b/src/js/ripple/serializedtypes.js index a79d3535..cdca9fe6 100644 --- a/src/js/ripple/serializedtypes.js +++ b/src/js/ripple/serializedtypes.js @@ -120,6 +120,8 @@ SerializedType.prototype.parse_varint = function (so) { * The result is appended to the serialized object ('so'). */ function append_byte_array(so, val, bytes) { + val = val >>> 0; + if (!isNumber(val)) { throw new Error('Value is not a number'); } diff --git a/src/js/ripple/transaction.js b/src/js/ripple/transaction.js index 58dcc131..da02827e 100644 --- a/src/js/ripple/transaction.js +++ b/src/js/ripple/transaction.js @@ -62,7 +62,9 @@ function Transaction(remote) { this.remote = remote; // Transaction data - this.tx_json = { Flags: 0 }; + this.tx_json = { + Flags: Transaction.defaultFlags + }; this._secret = void(0); this._build_path = false; @@ -80,15 +82,10 @@ function Transaction(remote) { this.submittedIDs = [ ] function finalize(message) { - if (self.result) { - self.result.ledger_index = message.ledger_index; - self.result.ledger_hash = message.ledger_hash; - } else { - self.result = message; - self.result.tx_json = self.tx_json; + if (!self.finalized) { + self.finalized = true; + self.emit('cleanup', message); } - - self.emit('cleanup', message); }; this.once('success', function(message) { @@ -120,6 +117,11 @@ Transaction.fee_units = { }; Transaction.flags = { + // Universal flags can apply to any transaction type + Universal: { + FullyCanonicalSig: 0x80000000 + }, + AccountSet: { RequireDestTag: 0x00010000, OptionalDestTag: 0x00020000, @@ -149,6 +151,8 @@ Transaction.flags = { } }; +Transaction.defaultFlags = 0 | Transaction.flags.Universal.FullyCanonicalSig; + Transaction.formats = require('./binformat').tx; Transaction.prototype.consts = { diff --git a/src/js/sjcl-custom/sjcl-ecdsa-canonical.js b/src/js/sjcl-custom/sjcl-ecdsa-canonical.js new file mode 100644 index 00000000..d56c5114 --- /dev/null +++ b/src/js/sjcl-custom/sjcl-ecdsa-canonical.js @@ -0,0 +1,17 @@ +sjcl.ecc.ecdsa.secretKey.prototype.canonicalizeSignature = function(rs) { + var w = sjcl.bitArray, + R = this._curve.r, + l = R.bitLength(); + + var r = sjcl.bn.fromBits(w.bitSlice(rs,0,l)), + s = sjcl.bn.fromBits(w.bitSlice(rs,l,2*l)); + + // For a canonical signature we want the lower of two possible values for s + // 0 < s <= n/2 + if (!R.copy().halveM().greaterEquals(s)) { + s = R.sub(s); + } + + return w.concat(r.toBits(l), s.toBits(l)); +}; + diff --git a/test/sjcl-ecdsa-canonical-test.js b/test/sjcl-ecdsa-canonical-test.js new file mode 100644 index 00000000..d0a270b7 --- /dev/null +++ b/test/sjcl-ecdsa-canonical-test.js @@ -0,0 +1,28 @@ +var assert = require('assert'); +var utils = require('./testutils'); +var sjcl = require('../build/sjcl'); +var Seed = require('../src/js/ripple/seed').Seed; + +describe('SJCL ECDSA Canonicalization', function() { + describe('canonicalizeSignature', function() { + it('should canonicalize non-canonical signatures', function () { + var seed = Seed.from_json('saESc82Vun7Ta5EJRzGJbrXb5HNYk'); + var key = seed.get_key('rBZ4j6MsoctipM6GEyHSjQKzXG3yambDnZ'); + + var rs = sjcl.codec.hex.toBits("27ce1b914045ba7e8c11a2f2882cb6e07a19d4017513f12e3e363d71dc3fff0fb0a0747ecc7b4ca46e45b3b32b6b2a066aa0249c027ef11e5bce93dab756549c"); + rs = sjcl.ecc.ecdsa.secretKey.prototype.canonicalizeSignature.call(key._secret, rs); + assert.strictEqual(sjcl.codec.hex.fromBits(rs), "27ce1b914045ba7e8c11a2f2882cb6e07a19d4017513f12e3e363d71dc3fff0f4f5f8b813384b35b91ba4c4cd494d5f8500eb84aacc9af1d6403cab218dfeca5"); + }); + + it('should not touch canonical signatures', function () { + var seed = Seed.from_json('saESc82Vun7Ta5EJRzGJbrXb5HNYk'); + var key = seed.get_key('rBZ4j6MsoctipM6GEyHSjQKzXG3yambDnZ'); + + var rs = sjcl.codec.hex.toBits("5c32bc2b4d34e27af9fb66eeea0f47f6afb3d433658af0f649ebae7b872471ab7d23860688aaf9d8131f84cfffa6c56bf9c32fd8b315b2ef9d6bcb243f7a686c"); + rs = sjcl.ecc.ecdsa.secretKey.prototype.canonicalizeSignature.call(key._secret, rs); + assert.strictEqual(sjcl.codec.hex.fromBits(rs), "5c32bc2b4d34e27af9fb66eeea0f47f6afb3d433658af0f649ebae7b872471ab7d23860688aaf9d8131f84cfffa6c56bf9c32fd8b315b2ef9d6bcb243f7a686c"); + }); + }); +}); + +// vim:sw=2:sts=2:ts=8:et