From 55526c378c4b93f2b36727ad1774c30ba5b28a2a Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Wed, 6 Feb 2013 20:58:59 +0100 Subject: [PATCH] JS: Local signing bugfixes. --- src/js/keypair.js | 67 ++++++++++++++++++++++++ src/js/remote.js | 1 + src/js/seed.js | 78 ++++++++-------------------- src/js/sjcl-custom/sjcl-extramath.js | 2 +- src/js/transaction.js | 29 ++++++++--- 5 files changed, 115 insertions(+), 62 deletions(-) create mode 100644 src/js/keypair.js diff --git a/src/js/keypair.js b/src/js/keypair.js new file mode 100644 index 000000000..7c6a559de --- /dev/null +++ b/src/js/keypair.js @@ -0,0 +1,67 @@ +var sjcl = require('../../build/sjcl'); + +var UInt256 = require('./uint256').UInt256; + +var KeyPair = function () +{ + this._curve = sjcl.ecc.curves['c256']; + this._secret = null; + this._pubkey = null; +}; + +KeyPair.from_bn_secret = function (j) +{ + if (j instanceof this) { + return j.clone(); + } else { + return (new this()).parse_bn_secret(j); + } +}; + +KeyPair.prototype.parse_bn_secret = function (j) +{ + this._secret = new sjcl.ecc.ecdsa.secretKey(sjcl.ecc.curves['c256'], j); + return this; +}; + +/** + * Returns public key as sjcl public key. + * + * @private + */ +KeyPair.prototype._pub = function () +{ + var curve = this._curve; + + if (!this._pubkey && this._secret) { + var exponent = this._secret._exponent; + this._pubkey = new sjcl.ecc.ecdsa.publicKey(curve, curve.G.mult(exponent)); + } + + return this._pubkey; +}; + +/** + * Returns public key as hex. + * + * Key will be returned as a compressed pubkey - 33 bytes converted to hex. + */ +KeyPair.prototype.to_hex_pub = function () +{ + var pub = this._pub(); + if (!pub) return null; + + var point = pub._point, y_even = point.y.mod(2).equals(0); + return sjcl.codec.hex.fromBits(sjcl.bitArray.concat( + [sjcl.bitArray.partial(8, y_even ? 0x02 : 0x03)], + point.x.toBits(this._curve.r.bitLength()) + )).toUpperCase(); +}; + +KeyPair.prototype.sign = function (hash) +{ + hash = UInt256.from_json(hash); + return this._secret.signDER(hash.to_bits(), 0); +}; + +exports.KeyPair = KeyPair; diff --git a/src/js/remote.js b/src/js/remote.js index 23c2296e8..c208dd1a5 100644 --- a/src/js/remote.js +++ b/src/js/remote.js @@ -196,6 +196,7 @@ var Remote = function (opts, trace) { this.websocket_ssl = opts.websocket_ssl; this.local_sequence = opts.local_sequence; // Locally track sequence numbers this.local_fee = opts.local_fee; // Locally set fees + this.local_signing = opts.local_signing; this.id = 0; this.trace = opts.trace || trace; this._server_fatal = false; // True, if we know server exited. diff --git a/src/js/seed.js b/src/js/seed.js index ee519566c..fcd332ea8 100644 --- a/src/js/seed.js +++ b/src/js/seed.js @@ -5,50 +5,24 @@ var sjcl = require('../../build/sjcl'); var utils = require('./utils'); var jsbn = require('./jsbn'); +var extend = require('extend'); var BigInteger = jsbn.BigInteger; var Base = require('./base').Base, - UInt256 = require('./uint256').UInt256; + UInt = require('./uint').UInt, + UInt256 = require('./uint256').UInt256, + KeyPair = require('./keypair').KeyPair; -var Seed = function () { +var Seed = extend(function () { // Internal form: NaN or BigInteger - this._value = NaN; -}; + this._curve = sjcl.ecc.curves['c256']; + this._value = NaN; +}, UInt); -Seed.json_rewrite = function (j) { - return Seed.from_json(j).to_json(); -}; - -// Return a new Seed from j. -Seed.from_json = function (j) { - return (j instanceof Seed) - ? j.clone() - : (new Seed()).parse_json(j); -}; - -Seed.is_valid = function (j) { - return Seed.from_json(j).is_valid(); -}; - -Seed.prototype.clone = function () { - return this.copyTo(new Seed()); -}; - -// Returns copy. -Seed.prototype.copyTo = function (d) { - d._value = this._value; - - return d; -}; - -Seed.prototype.equals = function (d) { - return this._value instanceof BigInteger && d._value instanceof BigInteger && this._value.equals(d._value); -}; - -Seed.prototype.is_valid = function () { - return this._value instanceof BigInteger; -}; +Seed.width = 16; +Seed.prototype = extend({}, UInt.prototype); +Seed.prototype.constructor = Seed; // value = NaN on error. // One day this will support rfc1751 too. @@ -69,21 +43,11 @@ Seed.prototype.parse_json = function (j) { return this; }; -// Convert from internal form. Seed.prototype.to_json = function () { if (!(this._value instanceof BigInteger)) return NaN; - var bytes = this._value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0; }); - var target = 20; - - // XXX Make sure only trim off leading zeros. - var array = bytes.length < target - ? bytes.length - ? [].concat(utils.arraySet(target - bytes.length, 0), bytes) - : utils.arraySet(target, 0) - : bytes.slice(target - bytes.length); - var output = Base.encode_check(Base.VER_FAMILY_SEED, array); + var output = Base.encode_check(Base.VER_FAMILY_SEED, this.to_bytes()); return output; }; @@ -103,27 +67,31 @@ function SHA256_RIPEMD160(bits) { return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits)); } -Seed.prototype.generate_private = function (account_id) { - // XXX If account_id is given, should loop over keys until we find the right one +Seed.prototype.get_key = function (account_id) { + // XXX Should loop over keys until we find the right one + + var curve = this._curve; var seq = 0; var private_gen, public_gen, i = 0; do { - private_gen = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(this.seed, i))); + private_gen = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(this.to_bytes(), i))); i++; - } while (!sjcl.ecc.curves.c256.r.greaterEquals(private_gen)); + } while (!curve.r.greaterEquals(private_gen)); - public_gen = sjcl.ecc.curves.c256.G.mult(private_gen); + public_gen = curve.G.mult(private_gen); var sec; i = 0; do { sec = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(append_int(public_gen.toBytesCompressed(), seq), i))); i++; - } while (!sjcl.ecc.curves.c256.r.greaterEquals(sec)); + } while (!curve.r.greaterEquals(sec)); - return UInt256.from_bn(sec); + sec = sec.add(private_gen).mod(curve.r); + + return KeyPair.from_bn_secret(sec); }; exports.Seed = Seed; diff --git a/src/js/sjcl-custom/sjcl-extramath.js b/src/js/sjcl-custom/sjcl-extramath.js index 4dd5f326a..03b338b7a 100755 --- a/src/js/sjcl-custom/sjcl-extramath.js +++ b/src/js/sjcl-custom/sjcl-extramath.js @@ -9,7 +9,7 @@ sjcl.bn.prototype.divRem = function (that) { this.initWith(0); return this; } else if (thisa.equals(thata)) { - this.initWith(sign); + this.initWith(1); return this; } diff --git a/src/js/transaction.js b/src/js/transaction.js index a2ad368a3..9013ec4e0 100644 --- a/src/js/transaction.js +++ b/src/js/transaction.js @@ -167,6 +167,26 @@ Transaction.prototype.set_state = function (state) { } }; +/** + * Attempts to complete the transaction for submission. + * + * This function seeks to fill out certain fields, such as Fee and + * SigningPubKey, which can be determined by the library based on network + * information and other fields. + */ +Transaction.prototype.complete = function () { + var tx_json = this.tx_json; + + if (this.remote.local_fee && undefined === tx_json.Fee) { + tx_json.Fee = Transaction.fees['default'].to_json(); + } + if (this.remote.local_signing && undefined === tx_json.SigningPubKey) { + var seed = Seed.from_json(this._secret); + var key = seed.get_key(this.tx_json.Account); + tx_json.SigningPubKey = key.to_hex_pub(); + } +}; + Transaction.prototype.serialize = function () { return SerializedObject.from_json(this.tx_json); }; @@ -181,11 +201,10 @@ Transaction.prototype.signing_hash = function () { Transaction.prototype.sign = function () { var seed = Seed.from_json(this._secret), - priv = seed.generate_private(this.tx_json.Account), hash = this.signing_hash(); - var key = new sjcl.ecc.ecdsa.secretKey(sjcl.ecc.curves['c256'], priv.to_bn()), - sig = key.signDER(hash.to_bits(), 0), + var key = seed.get_key(this.tx_json.Account), + sig = key.sign(hash, 0), hex = sjcl.codec.hex.fromBits(sig).toUpperCase(); this.tx_json.TxnSignature = hex; @@ -221,9 +240,7 @@ Transaction.prototype.submit = function (callback) { // YYY Might check paths for invalid accounts. - if (this.remote.local_fee && undefined === tx_json.Fee) { - tx_json.Fee = Transaction.fees['default'].to_json(); - } + this.complete(); if (this.callback || this.listeners('final').length || this.listeners('lost').length || this.listeners('pending').length) { // There are listeners for callback, 'final', 'lost', or 'pending' arrange to emit them.