ripplelib
Version:
A JavaScript API for interacting with Ripple in Node.js and the browser
153 lines (127 loc) • 4.11 kB
JavaScript
;
//
// Seed support
//
var extend = require('extend');
var utils = require('./utils');
var sjcl = utils.sjcl;
var Base = require('./base').Base;
var UInt = require('./uint').UInt;
var UInt160 = require('./uint160').UInt160;
var KeyPair = require('./keypair').KeyPair;
var rfc1751 = require('./rfc1751');
var Seed = extend(function () {
this._curve = sjcl.ecc.curves.k256;
this._value = NaN;
}, UInt);
Seed.width = 16;
Seed.prototype = Object.create(extend({}, UInt.prototype));
Seed.prototype.constructor = Seed;
Seed.getRandom = function () {
return this.from_bits(sjcl.random.randomWords(4, 6));
}
// value = NaN on error.
Seed.prototype.parse_json = function (j) {
this._value = NaN;
if (typeof j === 'string' && j.length) {
if (j[0] === 's') {
this._value = Base.decode_check(Base.VER_FAMILY_SEED, j);
} else if (/^[0-9a-fA-F]{32}$/.test(j)) {
this.parse_hex(j);
} else if (/^([A-Z]{1,4} +)+[A-Z]{1,4}$/.test(j)){
try {
this.parse_bytes(rfc1751.decode(j));
} catch (e) {};
}
}
return this;
};
Seed.prototype.parse_passphrase = function (j) {
if (typeof j !== 'string') {
throw new Error('Passphrase must be a string');
}
var hash = sjcl.hash.sha512.hash(sjcl.codec.utf8String.toBits(j));
var bits = sjcl.bitArray.bitSlice(hash, 0, 128);
this.parse_bits(bits);
return this;
};
Seed.prototype.to_json = function () {
if (!this.is_valid()) {
return NaN;
}
var output = Base.encode_check(Base.VER_FAMILY_SEED, this.to_bytes());
return output;
};
Seed.prototype.to_human =
Seed.prototype.to_rfc1751 = function () {
if (!this.is_valid()) {
return NaN;
}
return rfc1751.encode(this.to_bytes());
};
function append_int(a, i) {
return [].concat(a, i >> 24, i >> 16 & 0xff, i >> 8 & 0xff, i & 0xff);
}
function firstHalfOfSHA512(bytes) {
return sjcl.bitArray.bitSlice(sjcl.hash.sha512.hash(sjcl.codec.bytes.toBits(bytes)), 0, 256);
}
// get the FAMILY_GENERATOR
// return as KeyPair
Seed.prototype.get_generator = function () {
if (!this.is_valid()) {
throw new Error('Cannot generate keys from invalid seed!');
}
var private_gen = undefined;
var curve = this._curve;
var i = 0;
do {
private_gen = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(this.to_bytes(), i)));
i++;
} while (!curve.r.greaterEquals(private_gen));
return KeyPair.from_bn_secret(private_gen);
};
// Removed a `*` so this JSDoc-ish syntax is ignored.
// This will soon all change anyway.
/*
* @param account
* {undefined} take first, default, KeyPair
*
* {Number} specifies the account number of the KeyPair
* desired.
*
* {Uint160} (from_json able), specifies the address matching the KeyPair
* that is desired.
*
* @param maxLoops (optional)
* {Number} specifies the amount of attempts taken
* to generate a matching KeyPair
*
*/
Seed.prototype.get_key = function (account, maxLoops) {
var account_number = 0,
address = undefined;
var max_loops = maxLoops || 1;
if (!this.is_valid()) {
throw new Error('Cannot generate keys from invalid seed!');
}
if (account) {
if (typeof account === 'number') {
account_number = account;
} else {
address = UInt160.from_json(account);
}
}
var g = this.get_generator();
var key_pair = undefined;
do {
key_pair = g.get_child(account_number);
account_number++;
if (max_loops-- <= 0) {
// We are almost certainly looking for an account that would take same
// value of $too_long {forever, ...}
throw new Error('Too many loops looking for KeyPair yielding ' + address.to_json() + ' from ' + this.to_json());
}
} while (address && !key_pair.get_address().equals(address));
return key_pair;
};
exports.Seed = Seed;