Use ripple-keypairs and ripple-address-codec

This commit is contained in:
Nicholas Dudfield
2015-06-10 19:38:49 +07:00
parent fcbe7d3c98
commit 3263629ebe
14 changed files with 173 additions and 433 deletions

70
npm-shrinkwrap.json generated
View File

@@ -102,6 +102,76 @@
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.2.tgz"
},
"ripple-address-codec": {
"version": "1.6.0",
"from": "ripple-address-codec@>=1.6.0 <2.0.0",
"dependencies": {
"hash.js": {
"version": "1.0.3",
"from": "hash.js@>=1.0.3 <2.0.0",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.3.tgz",
"dependencies": {
"inherits": {
"version": "2.0.1",
"from": "inherits@>=2.0.1 <3.0.0",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
}
}
},
"x-address-codec": {
"version": "0.6.0",
"from": "x-address-codec@>=0.6.0 <0.7.0",
"resolved": "https://registry.npmjs.org/x-address-codec/-/x-address-codec-0.6.0.tgz",
"dependencies": {
"base-x": {
"version": "1.0.1",
"from": "base-x@>=1.0.1 <2.0.0",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-1.0.1.tgz"
}
}
}
}
},
"ripple-keypairs": {
"version": "0.7.3",
"from": "ripple-keypairs@>=0.7.3 <0.8.0",
"dependencies": {
"bn.js": {
"version": "3.1.1",
"from": "bn.js@>=3.1.1 <4.0.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.1.1.tgz"
},
"brorand": {
"version": "1.0.5",
"from": "brorand@>=1.0.5 <2.0.0",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.0.5.tgz"
},
"elliptic": {
"version": "5.1.0",
"from": "elliptic@>=5.1.0 <6.0.0",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-5.1.0.tgz",
"dependencies": {
"inherits": {
"version": "2.0.1",
"from": "inherits@>=2.0.1 <3.0.0",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
}
}
},
"hash.js": {
"version": "1.0.3",
"from": "hash.js@>=1.0.3 <2.0.0",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.3.tgz",
"dependencies": {
"inherits": {
"version": "2.0.1",
"from": "inherits@>=2.0.1 <3.0.0",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
}
}
}
}
},
"ripple-lib-transactionparser": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.5.0.tgz",

View File

@@ -25,6 +25,8 @@
"lodash": "^3.1.0",
"lru-cache": "~2.5.0",
"ripple-lib-transactionparser": "^0.5.0",
"ripple-address-codec": "^1.6.0",
"ripple-keypairs": "^0.7.3",
"ripple-wallet-generator": "^1.0.3",
"sjcl-extended": "ripple/sjcl-extended#1.0.3",
"ws": "~0.7.1"
@@ -59,7 +61,7 @@
"prepublish": "npm run clean && npm run compile",
"test": "istanbul test _mocha",
"coveralls": "cat ./coverage/lcov.info | coveralls",
"lint": "if ! [ -f eslintrc ]; then curl -o eslintrc 'https://raw.githubusercontent.com/ripple/javascript-style-guide/es6/eslintrc'; echo 'plugins:\n - flowtype' >> eslintrc; fi; eslint --reset -c eslintrc src/",
"lint": "if ! [ -f eslintrc ]; then curl -o eslintrc 'https://raw.githubusercontent.com/ripple/javascript-style-guide/es6/eslintrc'; echo 'plugins:\n - flowtype' >> eslintrc; fi; eslint -c eslintrc src/",
"perf": "./scripts/perf_test.sh"
},
"repository": {

View File

@@ -16,10 +16,8 @@ function validateAddressAndSecret(obj: {address: string, secret: string}): void
if (!secret) {
throw error('Parameter missing: secret');
}
try {
core.Seed.from_json(secret).get_key(address);
} catch (exception) {
throw error('secret does not match address');
if (!core.Seed.from_json(secret).is_valid()) {
throw error('secret is invalid');
}
}

View File

@@ -15,15 +15,13 @@ const validate = utils.common.validate;
* some arbitrary string. For example "TXN".
*/
const HASH_TX_ID = 0x54584E00; // 'TXN'
const HASH_TX_SIGN = 0x53545800; // 'STX'
const HASH_TX_SIGN_TESTNET = 0x73747800; // 'stx'
function getKeyPair(secret) {
return core.Seed.from_json(secret).get_key();
}
function getPublicKeyHex(keypair) {
return keypair.to_hex_pub();
return keypair.pubKeyHex();
}
function serialize(txJSON) {
@@ -34,17 +32,12 @@ function hashSerialization(serialized, prefix) {
return serialized.hash(prefix || HASH_TX_ID).to_hex();
}
function hashJSON(txJSON, prefix) {
return hashSerialization(serialize(txJSON), prefix);
}
function signingHash(txJSON, isTestNet=false) {
return hashJSON(txJSON, isTestNet ? HASH_TX_SIGN_TESTNET : HASH_TX_SIGN);
function signingData(txJSON) {
return core.Transaction.from_json(txJSON).signingData().buffer;
}
function computeSignature(txJSON, keypair) {
const signature = keypair.sign(signingHash(txJSON));
return core.sjcl.codec.hex.fromBits(signature).toUpperCase();
return keypair.signHex(signingData(txJSON));
}
function sign(txJSON: {Account: string; SigningPubKey: string,

View File

@@ -1,18 +1,11 @@
'use strict';
const _ = require('lodash');
const sjcl = require('./utils').sjcl;
const utils = require('./utils');
const extend = require('extend');
const convertBase = require('./baseconverter');
const {encode, decode} = require('ripple-address-codec');
const Base = {};
const alphabets = Base.alphabets = {
ripple: 'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz',
tipple: 'RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz',
bitcoin: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
};
extend(Base, {
VER_NONE: 1,
VER_NODE_PUBLIC: 28,
@@ -21,134 +14,44 @@ extend(Base, {
VER_ACCOUNT_PUBLIC: 35,
VER_ACCOUNT_PRIVATE: 34,
VER_FAMILY_GENERATOR: 41,
VER_FAMILY_SEED: 33
});
function sha256(bytes) {
return sjcl.codec.bytes.fromBits(
sjcl.hash.sha256.hash(sjcl.codec.bytes.toBits(bytes)));
}
function encodeString(alphabet, input) {
if (input.length === 0) {
return '';
}
const leadingZeros = _.takeWhile(input, function(d) {
return d === 0;
});
const out = convertBase(input, 256, 58).map(function(digit) {
if (digit < 0 || digit >= alphabet.length) {
throw new Error('Value ' + digit + ' is out of bounds for encoding');
}
return alphabet[digit];
});
const prefix = leadingZeros.map(function() {
return alphabet[0];
});
return prefix.concat(out).join('');
}
function decodeString(indexes, input) {
if (input.length === 0) {
return [];
}
const input58 = input.split('').map(function(c) {
const charCode = c.charCodeAt(0);
if (charCode >= indexes.length || indexes[charCode] === -1) {
throw new Error('Character ' + c + ' is not valid for encoding');
}
return indexes[charCode];
});
const leadingZeros = _.takeWhile(input58, function(d) {
return d === 0;
});
const out = convertBase(input58, 58, 256);
return leadingZeros.concat(out);
}
function Base58(alphabet) {
const indexes = utils.arraySet(128, -1);
for (let i = 0; i < alphabet.length; i++) {
indexes[alphabet.charCodeAt(i)] = i;
}
return {
decode: decodeString.bind(null, indexes),
encode: encodeString.bind(null, alphabet)
};
}
Base.encoders = {};
Object.keys(alphabets).forEach(function(alphabet) {
Base.encoders[alphabet] = new Base58(alphabets[alphabet]);
VER_FAMILY_SEED: 33,
VER_ED25519_SEED: [0x01, 0xE1, 0x4B]
});
// --> input: big-endian array of bytes.
// <-- string at least as long as input.
Base.encode = function(input, alpha) {
return this.encoders[alpha || 'ripple'].encode(input);
Base.encode = function(input, alphabet) {
return encode(input, {alphabet});
};
// --> input: String
// <-- array of bytes or undefined.
Base.decode = function(input, alpha) {
Base.decode = function(input, alphabet) {
if (typeof input !== 'string') {
return undefined;
}
try {
return this.encoders[alpha || 'ripple'].decode(input);
return decode(input, {alphabet});
} catch (e) {
return undefined;
}
};
Base.verify_checksum = function(bytes) {
const computed = sha256(sha256(bytes.slice(0, -4))).slice(0, 4);
const checksum = bytes.slice(-4);
return _.isEqual(computed, checksum);
};
// --> input: Array
// <-- String
Base.encode_check = function(version, input, alphabet) {
const buffer = [].concat(version, input);
const check = sha256(sha256(buffer)).slice(0, 4);
return Base.encode([].concat(buffer, check), alphabet);
return encode(input, {version, alphabet});
};
// --> input : String
// <-- NaN || sjcl.bn
Base.decode_check = function(version, input, alphabet) {
const buffer = Base.decode(input, alphabet);
if (!buffer || buffer.length < 5) {
try {
const decoded = decode(input, {version, alphabet});
return sjcl.bn.fromBits(sjcl.codec.bytes.toBits(decoded));
} catch (e) {
return NaN;
}
// Single valid version
if (typeof version === 'number' && buffer[0] !== version) {
return NaN;
}
// Multiple allowed versions
if (Array.isArray(version) && _.every(version, function(v) {
return v !== buffer[0];
})) {
return NaN;
}
if (!Base.verify_checksum(buffer)) {
return NaN;
}
// We'll use the version byte to add a leading zero, this ensures JSBN doesn't
// intrepret the value as a negative number
buffer[0] = 0;
return sjcl.bn.fromBits(
sjcl.codec.bytes.toBits(buffer.slice(0, -4)));
};
exports.Base = Base;

View File

@@ -1,102 +0,0 @@
'use strict';
/*eslint new-cap: 1*/
var sjcl = require('./utils').sjcl;
var UInt160 = require('./uint160').UInt160;
var UInt256 = require('./uint256').UInt256;
var Base = require('./base').Base;
function KeyPair() {
this._curve = sjcl.ecc.curves.k256;
this._secret = null;
this._pubkey = null;
}
KeyPair.from_bn_secret = function(j) {
return (j instanceof this) ? j.clone() : (new this()).parse_bn_secret(j);
};
KeyPair.prototype.parse_bn_secret = function(j) {
this._secret = new sjcl.ecc.ecdsa.secretKey(sjcl.ecc.curves.k256, j);
return this;
};
/**
* @private
*
* @return {sjcl.ecc.ecdsa.publicKey} public key
*/
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;
};
/**
* @private
*
* @return {sjcl.bitArray} public key bits in compressed form
*/
KeyPair.prototype._pub_bits = function() {
var pub = this._pub();
if (!pub) {
return null;
}
var point = pub._point, y_even = point.y.mod(2).equals(0);
return sjcl.bitArray.concat(
[sjcl.bitArray.partial(8, y_even ? 0x02 : 0x03)],
point.x.toBits(this._curve.r.bitLength())
);
};
/**
* @return {String} public key bytes in compressed form, hex encoded.
*/
KeyPair.prototype.to_hex_pub = function() {
var bits = this._pub_bits();
if (!bits) {
return null;
}
return sjcl.codec.hex.fromBits(bits).toUpperCase();
};
function sha256_ripemd160(bits) {
return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
}
KeyPair.prototype.get_address = function() {
var bits = this._pub_bits();
if (!bits) {
return null;
}
var hash = sha256_ripemd160(bits);
var address = UInt160.from_bits(hash);
address.set_version(Base.VER_ACCOUNT_ID);
return address;
};
KeyPair.prototype.sign = function(hash) {
var PARANOIA_256_BITS = 6; // sjcl constant for ensuring 256 bits of entropy
hash = UInt256.from_json(hash);
var sig = this._secret.sign(hash.to_bits(), PARANOIA_256_BITS);
sig = this._secret.canonicalizeSignature(sig);
return this._secret.encodeDER(sig);
};
exports.KeyPair = KeyPair;

View File

@@ -4,18 +4,18 @@
// Seed support
//
const {KeyPair, KeyType} = require('ripple-keypairs');
const codec = require('ripple-address-codec');
const extend = require('extend');
const utils = require('./utils');
const sjcl = utils.sjcl;
const Base = require('./base').Base;
const sjcl = utils.sjcl;
const UInt = require('./uint').UInt;
const UInt160 = require('./uint160').UInt160;
const KeyPair = require('./keypair').KeyPair;
const Seed = extend(function() {
this._curve = sjcl.ecc.curves.k256;
this._value = NaN;
this._type = KeyType.secp256k1;
}, UInt);
Seed.width = 16;
@@ -34,7 +34,7 @@ Seed.prototype.parse_json = function(j) {
this.parse_hex(j);
// XXX Should also try 1751
}
if (!this.is_valid()) {
if (!this.is_valid() && j[0] !== 's') {
this.parse_passphrase(j);
}
}
@@ -52,9 +52,19 @@ Seed.prototype.parse_base58 = function(j) {
if (!j.length || j[0] !== 's') {
this._value = NaN;
} else {
this._value = Base.decode_check(Base.VER_FAMILY_SEED, j);
try {
const {bytes, type} = codec.decodeSeed(j);
this._value = sjcl.bn.fromBits(sjcl.codec.bytes.toBits(bytes));
this._type = type;
} catch (e) {
this._value = NaN;
}
}
return this;
};
Seed.prototype.set_ed25519 = function() {
this._type = KeyType.ed25519;
return this;
};
@@ -75,100 +85,14 @@ Seed.prototype.to_json = function() {
if (!(this.is_valid())) {
return NaN;
}
const output = Base.encode_check(Base.VER_FAMILY_SEED, this.to_bytes());
return output;
return codec.encodeSeed(this.to_bytes(), this._type);
};
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
);
}
// 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) {
let account_number = 0, address;
let max_loops = maxLoops || 1;
Seed.prototype.get_key = function() {
if (!this.is_valid()) {
throw new Error('Cannot generate keys from invalid seed!');
}
if (account) {
if (typeof account === 'number') {
account_number = account;
max_loops = account_number + 1;
} else {
address = UInt160.from_json(account);
}
}
let private_gen, public_gen;
const curve = this._curve;
let i = 0;
do {
private_gen = sjcl.bn.fromBits(
firstHalfOfSHA512(append_int(this.to_bytes(), i)));
i++;
} while (!curve.r.greaterEquals(private_gen));
public_gen = curve.G.mult(private_gen);
let sec;
let key_pair;
do {
i = 0;
do {
sec = sjcl.bn.fromBits(
firstHalfOfSHA512(
append_int(
append_int(public_gen.toBytesCompressed(), account_number)
,
i
)));
i++;
} while (!curve.r.greaterEquals(sec));
account_number++;
sec = sec.add(private_gen).mod(curve.r);
key_pair = KeyPair.from_bn_secret(sec);
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;
return KeyPair.fromSeed(this.to_bytes(), this._type);
};
exports.Seed = Seed;

View File

@@ -344,8 +344,8 @@ Transaction.prototype._computeFee = function() {
}
switch (fees.length) {
case 0: return undefined;
case 1: return String(fees[0]);
case 0: return undefined;
case 1: return String(fees[0]);
}
fees.sort(function ascending(a, b) {
@@ -399,8 +399,8 @@ Transaction.prototype.complete = function() {
if (typeof this.tx_json.SigningPubKey === 'undefined') {
try {
const seed = Seed.from_json(this._secret);
const key = seed.get_key(this.tx_json.Account);
this.tx_json.SigningPubKey = key.to_hex_pub();
const key = seed.get_key();
this.tx_json.SigningPubKey = key.pubKeyHex();
} catch(e) {
this.emit('error', new RippleError(
'tejSecretInvalid', 'Invalid secret'));
@@ -469,13 +469,13 @@ Transaction.prototype.hash = function(prefix_, asUINT256, serialized) {
return asUINT256 ? hash : hash.to_hex();
};
Transaction.prototype.sign = function(testnet) {
Transaction.prototype.sign = function() {
const seed = Seed.from_json(this._secret);
const prev_sig = this.tx_json.TxnSignature;
delete this.tx_json.TxnSignature;
const hash = this.signingHash(testnet);
const hash = this.signingHash();
// If the hash is the same, we can re-use the previous signature
if (prev_sig && hash === this.previousSigningHash) {
@@ -483,10 +483,8 @@ Transaction.prototype.sign = function(testnet) {
return this;
}
const key = seed.get_key(this.tx_json.Account);
const sig = key.sign(hash);
const hex = sjcl.codec.hex.fromBits(sig).toUpperCase();
const key = seed.get_key();
const hex = key.signHex(this.signingData().buffer);
this.tx_json.TxnSignature = hex;
this.previousSigningHash = hash;

View File

@@ -151,11 +151,9 @@ describe('RippleAPI', function() {
it('sign', function() {
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
withDeterministicPRNG(() => {
const result = this.api.sign(requests.sign, secret);
assert.deepEqual(result, responses.sign);
schemaValidator.schemaValidate('sign', result);
});
const result = this.api.sign(requests.sign, secret);
assert.deepEqual(result, responses.sign);
schemaValidator.schemaValidate('sign', result);
});
it('submit', function() {
@@ -647,21 +645,14 @@ describe('RippleAPI', function() {
});
it('addressAndSecret', function() {
const wrongSecret = {address: address,
secret: 'shzjfakiK79YQdMjy4h8cGGfQSV6u'
};
assert.throws(_.partial(validate.addressAndSecret, wrongSecret),
this.api.errors.ValidationError);
const noSecret = {address: address};
assert.throws(_.partial(validate.addressAndSecret, noSecret),
this.api.errors.ValidationError);
assert.throws(_.partial(validate.addressAndSecret, noSecret),
/Parameter missing/);
const badSecret = {address: address, secret: 'bad'};
const badSecret = {address: address, secret: 'sbad'};
assert.throws(_.partial(validate.addressAndSecret, badSecret),
this.api.errors.ValidationError);
assert.throws(_.partial(validate.addressAndSecret, badSecret),
/not match/);
const goodWallet = {address: 'rpZMK8hwyrBvLorFNWHRCGt88nCJWbixur',
secret: 'shzjfakiK79YQdMjy4h8cGGfQSV6u'
};

View File

@@ -1,4 +1,4 @@
{
"signedTransaction": "12000322000000002400000017201B0086955368400000000000000C732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D87446304402207660BDEF67105CE1EBA9AD35DC7156BAB43FF1D47633199EE257D70B6B9AAFBF022045A812486A675750B5A3F37131E9F92299728D37FF6BB7195CA5EE881268CB4C770A726970706C652E636F6D81145E7B112523F68D2F5E879DB4EAC51C6698A69304",
"id": "29D23159EBA79170DCA5EF467CBC15114DBD35B7A8C3DBF76809BA354D00D250"
"signedTransaction": "12000322000000002400000017201B0086955368400000000000000C732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D874473045022100E3C85A98C3D48E2D746B3993D36C38A8922D9499E7D20B54883511C9CFF7F70A02201790168F671AC558E1B62B911CA7AC5B1D8E454898BE6F6E9D87758076683459770A726970706C652E636F6D81145E7B112523F68D2F5E879DB4EAC51C6698A69304",
"id": "7AFE2F2FBE72467C47CCDD6DBA890AB3C97A708C335983F77AF32C4308C73633"
}

View File

@@ -1,12 +0,0 @@
var assert = require('assert');
var Seed = require('ripple-lib').Seed;
describe('KeyPair', function() {
it('can generate an address', function () {
var seed = Seed.from_json("masterpassphrase");
var address = seed.get_key().get_address();
assert.strictEqual(address.to_json(), 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
});
// vim:sw=2:sts=2:ts=8:et

View File

@@ -4,17 +4,15 @@
const assert = require('assert');
const Seed = require('ripple-lib').Seed;
function assert_helper(seed_json, address_or_nth, expected) {
const seed = Seed.from_json(seed_json);
const keypair = seed.get_key(address_or_nth, 500);
assert.strictEqual(keypair.to_hex_pub(), expected);
}
describe('Seed', function() {
it('saESc82Vun7Ta5EJRzGJbrXb5HNYk', function() {
const seed = Seed.from_json('saESc82Vun7Ta5EJRzGJbrXb5HNYk');
assert.strictEqual(seed.to_hex(), 'FF1CF838D02B2CF7B45BAC27F5F24F4F');
});
it('can create ed25519 seeds from a phrase', function() {
const seed = Seed.from_json('phrase').set_ed25519().to_json();
assert.strictEqual(seed, 'sEdT7U4WpkoiH6wBoNeLzDi1eu9N64Y');
});
it('sp6iDHnmiPN7tQFHm5sCW59ax3hfE', function() {
const seed = Seed.from_json('sp6iDHnmiPN7tQFHm5sCW59ax3hfE');
assert.strictEqual(seed.to_hex(), '00AD8DA764C3C8AF5F9B8D51C94B9E49');
@@ -36,66 +34,13 @@ describe('Seed', function() {
const seed = new Seed().parse_base58('Xs');
assert(!seed.is_valid());
});
it('can generate many addresses', function() {
const test_data = [
// Format:
// [passphrase, address, nth-for-seed, expected-public-key]
['masterpassphrase', 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', 0,
'0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020'],
['masterpassphrase', 'r4bYF7SLUMD7QgSLLpgJx38WJSY12ViRjP', 1,
'02CD8C4CE87F86AAD1D9D18B03DE28E6E756F040BD72A9C127862833EB90D60BAD'],
['masterpassphrase', 'rLpAd4peHUMBPbVJASMYK5GTBUSwXRD9nx', 2,
'0259A57642A6F4AEFC9B8062AF453FDEEEAC5572BA602BB1DBD5EF011394C6F9FC'],
['otherpassphrase', 'rpe3YWSVwGU2PmUzebAPg2deBXHtmba7hJ', 0,
'022235A3DB2CAE57C60B7831929611D58867F86D28C0AD3C82473CC4A84990D01B'],
['otherpassphrase', 'raAPC2gALSmsTkXR4wUwQcPgX66kJuLv2S', 5,
'03F0619AFABE08D22D98C8721895FE3673B6174168949976F2573CE1138C124994'],
['yetanotherpassphrase', 'rKnM44fS48qrGiDxB5fB5u64vHVJwjDPUo', 0,
'0385AD049327EF7E5EC429350A15CEB23955037DE99660F6E70C11C5ABF4407036'],
['yetanotherpassphrase', 'rMvkT1RHPfsZwTFbKDKBEisa5U4d2a9V8n', 1,
'023A2876EA130CBE7BBA0573C2DB4C4CEB9A7547666915BD40366CDC6150CF54DC']
];
for (let nth = 0; nth < test_data.length; nth++) {
const seed_json = test_data[nth][0];
const address = test_data[nth][1];
const nth_for_seed = test_data[nth][2];
const expected = test_data[nth][3];
// `seed.get_key($ripple_address)` is arguably an ill concieved feature
// as it needs to generate `nth` many keypairs and generate hashed public
// keys (addresses) for equality tests ??
// Would need remote.set_secret(address, private_key_not_seed) ??
assert_helper(seed_json, address, expected);
// This isn't too bad as it only needs to generate one keypair `seq`
assert_helper(seed_json, nth_for_seed, expected);
}
});
it('should return the key_pair for a valid account and secret pair', function() {
const address = 'r3GgMwvgvP8h4yVWvjH1dPZNvC37TjzBBE';
const seed = Seed.from_json('shsWGZcmZz6YsWWmcnpfr6fLTdtFV');
const keyPair = seed.get_key(address);
assert.strictEqual(keyPair.get_address().to_json(), address);
assert.strictEqual(keyPair.to_hex_pub(), '02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8');
const keyPair = seed.get_key();
assert.strictEqual(keyPair.accountID(), address);
assert.strictEqual(keyPair.pubKeyHex(), '02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8');
});
it('should not find a KeyPair for a secret that does not belong to the given account', function() {
const address = 'r3GgMwvgvP8h4yVWvjH1dPZNvC37TjzBBE';
const secret = 'snoPBrXtMeMyMHUVTgbuqAfg1SUTb';
const seed = Seed.from_json('snoPBrXtMeMyMHUVTgbuqAfg1SUTb');
try {
seed.get_key(address);
assert(false, 'should throw an error');
} catch(e) {
assert.strictEqual(e.message, 'Too many loops looking for KeyPair yielding ' + address + ' from ' + secret);
}
});
});
// vim:sw=2:sts=2:ts=8:et

View File

@@ -12,7 +12,7 @@ describe('Signing', function() {
it('SigningPubKey 1 (ripple-client issue #245)', function() {
const seed = Seed.from_json('saESc82Vun7Ta5EJRzGJbrXb5HNYk');
const key = seed.get_key('rBZ4j6MsoctipM6GEyHSjQKzXG3yambDnZ');
const pub = key.to_hex_pub();
const pub = key.pubKeyHex();
assert.strictEqual(
pub,
'0396941B22791A448E5877A44CE98434DB217D6FB97D63F0DAD23BE49ED45173C9');
@@ -20,7 +20,7 @@ describe('Signing', function() {
it('SigningPubKey 2 (master seed)', function() {
const seed = Seed.from_json('snoPBrXtMeMyMHUVTgbuqAfg1SUTb');
const key = seed.get_key('rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
const pub = key.to_hex_pub();
const pub = key.pubKeyHex();
assert.strictEqual(
pub,
'0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020');

View File

@@ -454,7 +454,6 @@ describe('Transaction', function() {
transaction.SigningPubKey = undefined;
transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ';
transaction._secret = 'sh2pTicynUEG46jjR4EoexHcQEoijX';
transaction.once('error', function(err) {
assert.strictEqual(err.result, 'tejSecretInvalid');
done();
@@ -553,6 +552,36 @@ describe('Transaction', function() {
done();
});
describe('ed25519 signing', function() {
it('can accept an ed25519 seed for ._secret', function() {
const expectedPub = 'EDD3993CDC6647896C455F136648B7750' +
'723B011475547AF60691AA3D7438E021D';
const expectedSig = 'C3646313B08EED6AF4392261A31B961F' +
'10C66CB733DB7F6CD9EAB079857834C8' +
'B0334270A2C037E63CDCCC1932E08328' +
'82B7B7066ECD2FAEDEB4A83DF8AE6303';
const tx_json = {
Account: 'rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN',
Amount: '1000',
Destination: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
Fee: '10',
Flags: 2147483648,
Sequence: 1,
TransactionType: 'Payment'
};
const tx = Transaction.from_json(tx_json);
tx.setSecret('sEd7rBGm5kxzauRTAV2hbsNz7N45X91');
tx.complete();
tx.sign();
assert.strictEqual(tx_json.SigningPubKey, expectedPub);
assert.strictEqual(tx_json.TxnSignature, expectedSig);
});
});
describe('signing', function() {
const tx_json = {
SigningPubKey: '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED',
@@ -1318,11 +1347,11 @@ describe('Transaction', function() {
const expected = [
{
Memo:
{
MemoType: '6D657373616765',
MemoFormat: '6A736F6E',
MemoData: '7B22737472696E67223A2276616C7565222C22626F6F6C223A747275652C22696E7465676572223A317D'
}
{
MemoType: '6D657373616765',
MemoFormat: '6A736F6E',
MemoData: '7B22737472696E67223A2276616C7565222C22626F6F6C223A747275652C22696E7465676572223A317D'
}
}
];
@@ -2014,7 +2043,7 @@ describe('Transaction', function() {
const queue = new TransactionQueue();
// Randomized submit indexes
[
const indexes = [
28093,
456944,
347213,
@@ -2025,8 +2054,9 @@ describe('Transaction', function() {
925550,
872298,
543305
]
.forEach(function(index) {
];
indexes.forEach(function(index) {
const tx = new Transaction();
tx.initialSubmitIndex = index;
queue.push(tx);