mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-12-06 17:27:59 +00:00
JS: Local signing bugfixes.
This commit is contained in:
67
src/js/keypair.js
Normal file
67
src/js/keypair.js
Normal file
@@ -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;
|
||||||
@@ -196,6 +196,7 @@ var Remote = function (opts, trace) {
|
|||||||
this.websocket_ssl = opts.websocket_ssl;
|
this.websocket_ssl = opts.websocket_ssl;
|
||||||
this.local_sequence = opts.local_sequence; // Locally track sequence numbers
|
this.local_sequence = opts.local_sequence; // Locally track sequence numbers
|
||||||
this.local_fee = opts.local_fee; // Locally set fees
|
this.local_fee = opts.local_fee; // Locally set fees
|
||||||
|
this.local_signing = opts.local_signing;
|
||||||
this.id = 0;
|
this.id = 0;
|
||||||
this.trace = opts.trace || trace;
|
this.trace = opts.trace || trace;
|
||||||
this._server_fatal = false; // True, if we know server exited.
|
this._server_fatal = false; // True, if we know server exited.
|
||||||
|
|||||||
@@ -5,50 +5,24 @@
|
|||||||
var sjcl = require('../../build/sjcl');
|
var sjcl = require('../../build/sjcl');
|
||||||
var utils = require('./utils');
|
var utils = require('./utils');
|
||||||
var jsbn = require('./jsbn');
|
var jsbn = require('./jsbn');
|
||||||
|
var extend = require('extend');
|
||||||
|
|
||||||
var BigInteger = jsbn.BigInteger;
|
var BigInteger = jsbn.BigInteger;
|
||||||
|
|
||||||
var Base = require('./base').Base,
|
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
|
// Internal form: NaN or BigInteger
|
||||||
|
this._curve = sjcl.ecc.curves['c256'];
|
||||||
this._value = NaN;
|
this._value = NaN;
|
||||||
};
|
}, UInt);
|
||||||
|
|
||||||
Seed.json_rewrite = function (j) {
|
Seed.width = 16;
|
||||||
return Seed.from_json(j).to_json();
|
Seed.prototype = extend({}, UInt.prototype);
|
||||||
};
|
Seed.prototype.constructor = Seed;
|
||||||
|
|
||||||
// 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
// value = NaN on error.
|
// value = NaN on error.
|
||||||
// One day this will support rfc1751 too.
|
// One day this will support rfc1751 too.
|
||||||
@@ -69,21 +43,11 @@ Seed.prototype.parse_json = function (j) {
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Convert from internal form.
|
|
||||||
Seed.prototype.to_json = function () {
|
Seed.prototype.to_json = function () {
|
||||||
if (!(this._value instanceof BigInteger))
|
if (!(this._value instanceof BigInteger))
|
||||||
return NaN;
|
return NaN;
|
||||||
|
|
||||||
var bytes = this._value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0; });
|
var output = Base.encode_check(Base.VER_FAMILY_SEED, this.to_bytes());
|
||||||
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);
|
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
};
|
};
|
||||||
@@ -103,27 +67,31 @@ function SHA256_RIPEMD160(bits) {
|
|||||||
return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
|
return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
|
||||||
}
|
}
|
||||||
|
|
||||||
Seed.prototype.generate_private = function (account_id) {
|
Seed.prototype.get_key = function (account_id) {
|
||||||
// XXX If account_id is given, should loop over keys until we find the right one
|
// XXX Should loop over keys until we find the right one
|
||||||
|
|
||||||
|
var curve = this._curve;
|
||||||
|
|
||||||
var seq = 0;
|
var seq = 0;
|
||||||
|
|
||||||
var private_gen, public_gen, i = 0;
|
var private_gen, public_gen, i = 0;
|
||||||
do {
|
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++;
|
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;
|
var sec;
|
||||||
i = 0;
|
i = 0;
|
||||||
do {
|
do {
|
||||||
sec = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(append_int(public_gen.toBytesCompressed(), seq), i)));
|
sec = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(append_int(public_gen.toBytesCompressed(), seq), i)));
|
||||||
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;
|
exports.Seed = Seed;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ sjcl.bn.prototype.divRem = function (that) {
|
|||||||
this.initWith(0);
|
this.initWith(0);
|
||||||
return this;
|
return this;
|
||||||
} else if (thisa.equals(thata)) {
|
} else if (thisa.equals(thata)) {
|
||||||
this.initWith(sign);
|
this.initWith(1);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 () {
|
Transaction.prototype.serialize = function () {
|
||||||
return SerializedObject.from_json(this.tx_json);
|
return SerializedObject.from_json(this.tx_json);
|
||||||
};
|
};
|
||||||
@@ -181,11 +201,10 @@ Transaction.prototype.signing_hash = function () {
|
|||||||
|
|
||||||
Transaction.prototype.sign = function () {
|
Transaction.prototype.sign = function () {
|
||||||
var seed = Seed.from_json(this._secret),
|
var seed = Seed.from_json(this._secret),
|
||||||
priv = seed.generate_private(this.tx_json.Account),
|
|
||||||
hash = this.signing_hash();
|
hash = this.signing_hash();
|
||||||
|
|
||||||
var key = new sjcl.ecc.ecdsa.secretKey(sjcl.ecc.curves['c256'], priv.to_bn()),
|
var key = seed.get_key(this.tx_json.Account),
|
||||||
sig = key.signDER(hash.to_bits(), 0),
|
sig = key.sign(hash, 0),
|
||||||
hex = sjcl.codec.hex.fromBits(sig).toUpperCase();
|
hex = sjcl.codec.hex.fromBits(sig).toUpperCase();
|
||||||
|
|
||||||
this.tx_json.TxnSignature = hex;
|
this.tx_json.TxnSignature = hex;
|
||||||
@@ -221,9 +240,7 @@ Transaction.prototype.submit = function (callback) {
|
|||||||
|
|
||||||
// YYY Might check paths for invalid accounts.
|
// YYY Might check paths for invalid accounts.
|
||||||
|
|
||||||
if (this.remote.local_fee && undefined === tx_json.Fee) {
|
this.complete();
|
||||||
tx_json.Fee = Transaction.fees['default'].to_json();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.callback || this.listeners('final').length || this.listeners('lost').length || this.listeners('pending').length) {
|
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.
|
// There are listeners for callback, 'final', 'lost', or 'pending' arrange to emit them.
|
||||||
|
|||||||
Reference in New Issue
Block a user