Move to new ripple-keypairs API

This commit is contained in:
Chris Clark
2015-09-22 15:31:10 -07:00
parent 7b5d6e9fc5
commit 715c648d52
14 changed files with 52 additions and 288 deletions

4
npm-shrinkwrap.json generated
View File

@@ -133,8 +133,8 @@
}
},
"ripple-keypairs": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-0.8.0.tgz",
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-0.9.0.tgz",
"dependencies": {
"brorand": {
"version": "1.0.5",

View File

@@ -27,7 +27,7 @@
"lodash": "^3.1.0",
"lru-cache": "~2.5.0",
"ripple-address-codec": "^1.6.0",
"ripple-keypairs": "^0.8.0",
"ripple-keypairs": "^0.9.0",
"ripple-lib-transactionparser": "^0.5.0",
"sjcl-codec": "0.1.0",
"ws": "~0.7.1"

View File

@@ -29,8 +29,10 @@ function toRippledAmount(amount: Amount): string|Amount {
}
function generateAddress(options?: Object): Object {
const {accountID, seed} = keypairs.generateWallet(options);
return {secret: seed, address: accountID};
const secret = keypairs.generateSeed(options);
const keypair = keypairs.deriveKeypair(secret);
const address = keypairs.deriveAddress(keypair.publicKey);
return {secret, address};
}
type AsyncFunction = (...x: any) => void

View File

@@ -1,7 +1,7 @@
/* @flow */
'use strict';
const _ = require('lodash');
const core = require('./utils').core;
const deriveKeypair = require('ripple-keypairs').deriveKeypair;
const ValidationError = require('./errors').ValidationError;
const schemaValidate = require('./schema-validator').schemaValidate;
@@ -9,6 +9,15 @@ function error(text) {
return new ValidationError(text);
}
function isValidSecret(secret) {
try {
deriveKeypair(secret);
return true;
} catch (err) {
return false;
}
}
function validateAddressAndSecret(obj: {address: string, secret: string}
): void {
const address = obj.address;
@@ -17,8 +26,8 @@ function validateAddressAndSecret(obj: {address: string, secret: string}
if (!secret) {
throw error('Parameter missing: secret');
}
if (!core.Seed.from_json(secret).is_valid()) {
throw error('secret is invalid');
if (!isValidSecret(secret)) {
throw error('Invalid parameter: secret');
}
}
@@ -26,13 +35,9 @@ function validateSecret(secret: string): void {
if (!secret) {
throw error('Parameter missing: secret');
}
if (typeof secret !== 'string' || secret[0] !== 's') {
throw error('Invalid parameter');
}
const seed = new core.Seed().parse_base58(secret);
if (!seed.is_valid()) {
throw error('invalid seed');
if (typeof secret !== 'string' || secret[0] !== 's'
|| !isValidSecret(secret)) {
throw error('Invalid parameter: secret');
}
}

View File

@@ -1,6 +1,7 @@
/* @flow */
'use strict';
const utils = require('./utils');
const keypairs = require('ripple-keypairs');
const core = utils.common.core;
const validate = utils.common.validate;
@@ -16,14 +17,6 @@ const validate = utils.common.validate;
*/
const HASH_TX_ID = 0x54584E00; // 'TXN'
function getKeyPair(secret) {
return core.Seed.from_json(secret).get_key();
}
function getPublicKeyHex(keypair) {
return keypair.pubKeyHex();
}
function serialize(txJSON) {
return core.SerializedObject.from_json(txJSON);
}
@@ -36,8 +29,8 @@ function signingData(txJSON) {
return core.Transaction.from_json(txJSON).signingData().buffer;
}
function computeSignature(txJSON, keypair) {
return keypair.signHex(signingData(txJSON));
function computeSignature(txJSON, privateKey) {
return keypairs.sign(signingData(txJSON), privateKey);
}
function sign(txJSON: string, secret: string
@@ -46,11 +39,11 @@ function sign(txJSON: string, secret: string
validate.txJSON(tx);
validate.secret(secret);
const keypair = getKeyPair(secret);
const keypair = keypairs.deriveKeypair(secret);
if (tx.SigningPubKey === undefined) {
tx.SigningPubKey = getPublicKeyHex(keypair);
tx.SigningPubKey = keypair.publicKey;
}
tx.TxnSignature = computeSignature(tx, keypair);
tx.TxnSignature = computeSignature(tx, keypair.privateKey);
const serialized = serialize(tx);
return {
signedTransaction: serialized.to_hex(),

View File

@@ -14,10 +14,8 @@ const _ = require('lodash');
const async = require('async');
const extend = require('extend');
const util = require('util');
const {createAccountID} = require('ripple-keypairs');
const {encodeAccountID} = require('ripple-address-codec');
const {deriveAddress} = require('ripple-keypairs');
const {EventEmitter} = require('events');
const {hexToArray} = require('./utils');
const {TransactionManager} = require('./transactionmanager');
const {UInt160} = require('./uint160');
@@ -377,7 +375,7 @@ Account.prototype.publicKeyIsActive = function(public_key, callback) {
Account._publicKeyToAddress = function(public_key) {
// Based on functions in /src/js/ripple/keypair.js
function hexToUInt160(publicKey) {
return encodeAccountID(createAccountID(hexToArray(publicKey)));
return deriveAddress(publicKey);
}
if (UInt160.is_valid(public_key)) {

View File

@@ -7,7 +7,6 @@ const assert = require('assert');
const extend = require('extend');
const utils = require('./utils');
const UInt160 = require('./uint160').UInt160;
const Seed = require('./seed').Seed;
const Currency = require('./currency').Currency;
const Value = require('./value').Value;
const IOUValue = require('./iouvalue').IOUValue;
@@ -1008,10 +1007,4 @@ Amount.prototype.not_equals_why = function(d, ignore_issuer) {
};
exports.Amount = Amount;
// DEPRECATED: Include the corresponding files instead.
exports.Currency = Currency;
exports.Seed = Seed;
exports.UInt160 = UInt160;
// vim:sw=2:sts=2:ts=8:et

View File

@@ -9,7 +9,6 @@ exports.Base = require('./base').Base;
exports.UInt128 = require('./uint128').UInt128;
exports.UInt160 = require('./uint160').UInt160;
exports.UInt256 = require('./uint256').UInt256;
exports.Seed = require('./seed').Seed;
exports.Meta = require('./meta').Meta;
exports.SerializedObject = require('./serializedobject').SerializedObject;
exports.RippleError = require('./rippleerror').RippleError;

View File

@@ -1,97 +0,0 @@
'use strict';
//
// Seed support
//
const {KeyPair, KeyType} = require('ripple-keypairs');
const {decodeSeed, encodeSeed} = require('ripple-address-codec');
const extend = require('extend');
const sjclcodec = require('sjcl-codec');
const BN = require('bn.js');
const hashjs = require('hash.js');
const UInt = require('./uint').UInt;
const Seed = extend(function() {
this._value = NaN;
this._type = KeyType.secp256k1;
}, UInt);
Seed.width = 16;
Seed.prototype = Object.create(extend({}, UInt.prototype));
Seed.prototype.constructor = Seed;
// value = NaN on error.
// One day this will support rfc1751 too.
Seed.prototype.parse_json = function(j) {
if (typeof j === 'string') {
if (!j.length) {
this._value = NaN;
} else {
this.parse_base58(j);
if (!this.is_valid()) {
this.parse_hex(j);
// XXX Should also try 1751
}
if (!this.is_valid() && j[0] !== 's') {
this.parse_passphrase(j);
}
}
} else {
this._value = NaN;
}
return this;
};
Seed.prototype.parse_base58 = function(j) {
if (typeof j !== 'string') {
throw new Error('Value must be a string');
}
if (!j.length || j[0] !== 's') {
this._value = NaN;
} else {
try {
const {bytes, type} = decodeSeed(j);
this._value = new BN(bytes);
this._type = type;
} catch (e) {
this._value = NaN;
}
}
return this;
};
Seed.prototype.set_ed25519 = function() {
this._type = KeyType.ed25519;
return this;
};
Seed.prototype.parse_passphrase = function(j) {
if (typeof j !== 'string') {
throw new Error('Passphrase must be a string');
}
const phraseBytes = sjclcodec.bytes.fromBits(sjclcodec.utf8String.toBits(j));
const hash = hashjs.sha512().update(phraseBytes).digest();
this.parse_bytes(hash.slice(0, 16));
return this;
};
Seed.prototype.to_json = function() {
if (!(this.is_valid())) {
return NaN;
}
return encodeSeed(this.to_bytes(), this._type);
};
Seed.prototype.get_key = function() {
if (!this.is_valid()) {
throw new Error('Cannot generate keys from invalid seed!');
}
return KeyPair.fromSeed(this.to_bytes(), this._type);
};
exports.Seed = Seed;

View File

@@ -3,14 +3,13 @@
const assert = require('assert');
const util = require('util');
const _ = require('lodash');
const {deriveKeypair, sign} = require('ripple-keypairs');
const EventEmitter = require('events').EventEmitter;
const utils = require('./utils');
const sjclcodec = require('sjcl-codec');
const Amount = require('./amount').Amount;
const Currency = require('./amount').Currency;
const UInt160 = require('./amount').UInt160;
const Seed = require('./seed').Seed;
const KeyPair = require('ripple-keypairs').KeyPair;
const Currency = require('./currency').Currency;
const UInt160 = require('./uint160').UInt160;
const SerializedObject = require('./serializedobject').SerializedObject;
const RippleError = require('./rippleerror').RippleError;
const hashprefixes = require('./hashprefixes');
@@ -430,31 +429,12 @@ Transaction.prototype.complete = function() {
return this.tx_json;
};
Transaction.prototype.getKeyPair = function(secret_) {
if (this._keyPair) {
return this._keyPair;
}
const secret = secret_ || this._secret;
assert(secret, 'Secret missing');
const keyPair = Seed.from_json(secret).get_key();
this._keyPair = keyPair;
return keyPair;
};
Transaction.prototype.getSigningPubKey = function(secret) {
return this.getKeyPair(secret).pubKeyHex();
return deriveKeypair(secret || this._secret).publicKey;
};
Transaction.prototype.setSigningPubKey = function(key) {
if (_.isString(key)) {
this.tx_json.SigningPubKey = key;
} else if (key instanceof KeyPair) {
this.tx_json.SigningPubKey = key.pubKeyHex();
}
return this;
};
@@ -509,16 +489,13 @@ Transaction.prototype.hash = function(prefix_, asUINT256, serialized) {
return asUINT256 ? hash : hash.to_hex();
};
Transaction.prototype.sign = function() {
Transaction.prototype.sign = function(secret) {
if (this.hasMultiSigners()) {
return this;
}
const keyPair = this.getKeyPair();
const prev_sig = this.tx_json.TxnSignature;
delete this.tx_json.TxnSignature;
const hash = this.signingHash();
// If the hash is the same, we can re-use the previous signature
@@ -527,7 +504,9 @@ Transaction.prototype.sign = function() {
return this;
}
this.tx_json.TxnSignature = keyPair.signHex(this.signingData().buffer);
const keypair = deriveKeypair(secret || this._secret);
this.tx_json.TxnSignature = sign(this.signingData().buffer,
keypair.privateKey);
this.previousSigningHash = hash;
return this;
@@ -1675,12 +1654,12 @@ Transaction.prototype.getMultiSigningJson = function() {
Transaction.prototype.multiSign = function(account, secret) {
const signingData = this.multiSigningData(account);
const keyPair = Seed.from_json(secret).get_key();
const keypair = deriveKeypair(secret);
const signer = {
Account: account,
TxnSignature: keyPair.signHex(signingData.buffer),
SigningPubKey: keyPair.pubKeyHex()
TxnSignature: sign(signingData.buffer, keypair.privateKey),
SigningPubKey: keypair.publicKey
};
return signer;

View File

@@ -481,7 +481,7 @@ describe('RippleAPI', function() {
function random() {
return _.fill(Array(16), 0);
}
assert.deepEqual(this.api.generateAddress({random}),
assert.deepEqual(this.api.generateAddress({entropy: random()}),
responses.generateAddress);
});

View File

@@ -1,46 +0,0 @@
/* eslint max-len: 0 */
/* eslint-disable max-nested-callbacks */
'use strict';
const assert = require('assert');
const Seed = require('ripple-lib').Seed;
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');
});
it('sp6iDHnmiPN7tQFHm5sCW59ax3hfE using parse_base58', function() {
const seed = new Seed().parse_base58('sp6iDHnmiPN7tQFHm5sCW59ax3hfE');
assert.strictEqual(seed.to_hex(), '00AD8DA764C3C8AF5F9B8D51C94B9E49');
});
it('parse_base58 should throw on non-string input', function() {
assert.throws(function() {
new Seed().parse_base58(1);
});
});
it('parse_base58 should make invalid seed from empty string', function() {
const seed = new Seed().parse_base58('');
assert(!seed.is_valid());
});
it('parse_base58 should make invalid seed from invalid input', function() {
const seed = new Seed().parse_base58('Xs');
assert(!seed.is_valid());
});
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();
assert.strictEqual(keyPair.accountID(), address);
assert.strictEqual(keyPair.pubKeyHex(), '02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8');
});
});
// vim:sw=2:sts=2:ts=8:et

View File

@@ -1,62 +0,0 @@
'use strict';
const assert = require('assert');
const Seed = require('ripple-lib').Seed;
function _isNaN(n) {
return typeof n === 'number' && isNaN(n);
}
describe('Signing', function() {
describe('Keys', function() {
it('SigningPubKey 1 (ripple-client issue #245)', function() {
const seed = Seed.from_json('saESc82Vun7Ta5EJRzGJbrXb5HNYk');
const key = seed.get_key('rBZ4j6MsoctipM6GEyHSjQKzXG3yambDnZ');
const pub = key.pubKeyHex();
assert.strictEqual(
pub,
'0396941B22791A448E5877A44CE98434DB217D6FB97D63F0DAD23BE49ED45173C9');
});
it('SigningPubKey 2 (master seed)', function() {
const seed = Seed.from_json('snoPBrXtMeMyMHUVTgbuqAfg1SUTb');
const key = seed.get_key('rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
const pub = key.pubKeyHex();
assert.strictEqual(
pub,
'0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020');
});
});
describe('parse_json', function() {
it('empty string', function() {
assert(_isNaN(new Seed().parse_json('').to_json()));
});
it('hex string', function() {
// 32 0s is a valid hex repr of seed bytes
const str = new Array(33).join('0');
assert.strictEqual((new Seed().parse_json(str).to_json()),
'sp6JS7f14BuwFY8Mw6bTtLKWauoUs');
});
it('passphrase', function() {
const str = new Array(60).join('0');
assert.strictEqual('snFRPnVL3secohdpwSie8ANXdFQvG',
new Seed().parse_json(str).to_json());
});
it('null', function() {
assert(_isNaN(new Seed().parse_json(null).to_json()));
});
});
describe('parse_passphrase', function() {
it('invalid passphrase', function() {
assert.throws(function() {
new Seed().parse_passphrase(null);
});
});
});
describe('get_key', function() {
it('get key from invalid seed', function() {
assert.throws(function() {
new Seed().get_key('rBZ4j6MsoctipM6GEyHSjQKzXG3yambDnZ');
});
});
});
});

View File

@@ -2,8 +2,9 @@
'use strict';
const assert = require('assert');
const assert = require('assert-diff');
const lodash = require('lodash');
const addresses = require('./fixtures/addresses');
const ripple = require('ripple-lib');
const Transaction = require('ripple-lib').Transaction;
const TransactionQueue = require('ripple-lib').TransactionQueue;
@@ -340,7 +341,7 @@ describe('Transaction', function() {
const dst = 'rGihwhaqU8g7ahwAvTq6iX5rvsfcbgZw6v';
transaction.payment(src, dst, '100');
remote.setSecret(src, 'masterpassphrase');
remote.setSecret(src, addresses.SECRET);
assert(transaction.complete());
const json = transaction.serialize().to_json();
@@ -2278,20 +2279,19 @@ describe('Transaction', function() {
const a1 = 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK';
const a2 = 'rH4KEcG9dEwGwpn6AyoWK9cZPLL4RLSmWW';
const s1 = t1.multiSign(a1, 'alice');
const s1 = t1.multiSign(a1, addresses.SECRET);
assert.deepEqual(s1, {
Account: 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK',
TxnSignature: '3045022100DB13DC794DDFA1E27D099CDBFC7DB5B1EE892AD1725B0CEEE97D8B1C4C2055C7022030B3372C96D08106594B3CF8CDF88E05CC6260C51954F02387289CB69B839D7A',
SigningPubKey: '0388935426E0D08083314842EDFBB2D517BD47699F9A4527318A8E10468C97C052'
TxnSignature: '30440220613DF9410B4844C7FAB637FD707F5185A2107DD10D0C2F59155844CD1910AB99022004A2AE607C15DD0959FDB3AAEE6A0337AA5337515230CE6EC11E32B74EEE896E',
SigningPubKey: '02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8'
});
const s2 = t1.multiSign(a2, 'bob');
const s2 = t1.multiSign(a2, addresses.SECRET);
assert.deepEqual(s2, {
Account: 'rH4KEcG9dEwGwpn6AyoWK9cZPLL4RLSmWW',
TxnSignature: '304402207A22109088069C5ABE3E961C2F85B2B8111C5666C869E8BA3F2A57C2ECEA7FC402205F9D87FB42266CC498FCE9B4904955D0E6D5F44D092596F5DE3E25843F6D10AB',
SigningPubKey: '02691AC5AE1C4C333AE5DF8A93BDC495F0EEBFC6DB0DA7EB6EF808F3AFC006E3FE'
TxnSignature: '3044022011762BC175E166EF540ABB162F0E8B48250E7C95DE5E8464E3F648EAF9A94A50022022439146DC3C6BB943C719F89696E7EBED14888A653F4618F62F8DA5CE202A45',
SigningPubKey: '02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8'
});
transaction.addMultiSigner(s1);