diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 51179f06..46cd1a7d 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -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", diff --git a/package.json b/package.json index eeec501b..c88d6096 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/src/api/common/utils.js b/src/api/common/utils.js index 10637ffe..bb231ef6 100644 --- a/src/api/common/utils.js +++ b/src/api/common/utils.js @@ -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 diff --git a/src/api/common/validate.js b/src/api/common/validate.js index 176e3c50..3c700080 100644 --- a/src/api/common/validate.js +++ b/src/api/common/validate.js @@ -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'); } } diff --git a/src/api/transaction/sign.js b/src/api/transaction/sign.js index 23df9efb..e5c7ee2f 100644 --- a/src/api/transaction/sign.js +++ b/src/api/transaction/sign.js @@ -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(), diff --git a/src/core/account.js b/src/core/account.js index e4133d92..3e3267bf 100644 --- a/src/core/account.js +++ b/src/core/account.js @@ -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)) { diff --git a/src/core/amount.js b/src/core/amount.js index 98dd8bef..fdf99190 100644 --- a/src/core/amount.js +++ b/src/core/amount.js @@ -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 diff --git a/src/core/index.js b/src/core/index.js index 97d5c76d..04a4109d 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -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; diff --git a/src/core/seed.js b/src/core/seed.js deleted file mode 100644 index e44c7a51..00000000 --- a/src/core/seed.js +++ /dev/null @@ -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; diff --git a/src/core/transaction.js b/src/core/transaction.js index e36d9196..90e455fe 100644 --- a/src/core/transaction.js +++ b/src/core/transaction.js @@ -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(); - } - + this.tx_json.SigningPubKey = key; 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; diff --git a/test/api-test.js b/test/api-test.js index b6e84d57..1dbf408e 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -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); }); diff --git a/test/seed-test.js b/test/seed-test.js deleted file mode 100644 index 77de9413..00000000 --- a/test/seed-test.js +++ /dev/null @@ -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 diff --git a/test/sign-test.js b/test/sign-test.js deleted file mode 100644 index 24d3ebc7..00000000 --- a/test/sign-test.js +++ /dev/null @@ -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'); - }); - }); - }); -}); diff --git a/test/transaction-test.js b/test/transaction-test.js index f1865501..5f9c516a 100644 --- a/test/transaction-test.js +++ b/test/transaction-test.js @@ -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);