diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 0da106f25e..47b07abf4b 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -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", diff --git a/package.json b/package.json index d1466df625..c81da3fd5c 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/api/common/validate.js b/src/api/common/validate.js index 63257aca79..3e16d78bb7 100644 --- a/src/api/common/validate.js +++ b/src/api/common/validate.js @@ -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'); } } diff --git a/src/api/transaction/sign.js b/src/api/transaction/sign.js index f1fd237c01..a02026626f 100644 --- a/src/api/transaction/sign.js +++ b/src/api/transaction/sign.js @@ -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, diff --git a/src/core/base.js b/src/core/base.js index b254dbb9f0..2a86ddf121 100644 --- a/src/core/base.js +++ b/src/core/base.js @@ -1,18 +1,19 @@ 'use strict'; -const _ = require('lodash'); + const sjcl = require('./utils').sjcl; -const utils = require('./utils'); const extend = require('extend'); -const convertBase = require('./baseconverter'); const Base = {}; -const alphabets = Base.alphabets = { +// Someone might be using these :) +Base.alphabets = { ripple: 'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz', tipple: 'RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz', bitcoin: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' }; +const {encode, decode} = require('ripple-address-codec'); + extend(Base, { VER_NONE: 1, VER_NODE_PUBLIC: 28, @@ -21,134 +22,79 @@ 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) { - 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]; - })) { + try { + const decoded = decode(input, {version, alphabet}); + return sjcl.bn.fromBits(sjcl.codec.bytes.toBits(decoded)); + } catch (e) { return NaN; } +}; - if (!Base.verify_checksum(buffer)) { - return NaN; +/** +* +* Decodes (with check) `input` into `expectedLength` number of bytes, trying +* multiple possible `versions`. +* +* +* @param {Array} versions - Array of possible versions. Each element can be +* an array of bytes, or just a single byte +* @param {String} input - the base58 encoded input +* @param {Number} expectedLength - of decoded bytes +* @param {String} [alphabet='ripple'] - name of alphabet to use. eg. 'bitcoin' +* +* If decoded bytes is not the `expectedLength` or none of the `versions` +* matched, will return struct with null members. +* +* @return {Object} result - struct +* @return {sjcl.bn} result.value - sjcl.bn interpretation of decoded bytes +* @return {Array|Number} result.version - the matching element from `versions` +* param +*/ +Base.decode_multi_versioned = function(versions, input, expectedLength, + alphabet) { + try { + // Will return valid struct or throw + const {bytes, version} = decode(input, {versions, expectedLength, + alphabet}); + return { + version, + value: sjcl.bn.fromBits(sjcl.codec.bytes.toBits(bytes)) + }; + } catch (e) { + return {value: null, version: null}; } - - // 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; diff --git a/src/core/keypair.js b/src/core/keypair.js deleted file mode 100644 index e84645d121..0000000000 --- a/src/core/keypair.js +++ /dev/null @@ -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; diff --git a/src/core/seed.js b/src/core/seed.js index 579ea0832b..ebe1aa397f 100644 --- a/src/core/seed.js +++ b/src/core/seed.js @@ -4,18 +4,20 @@ // 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 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 +36,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 +54,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; }; @@ -76,99 +88,17 @@ Seed.prototype.to_json = function() { return NaN; } - const output = Base.encode_check(Base.VER_FAMILY_SEED, this.to_bytes()); + // const output = Base.encode_check(this._version, this.to_bytes()); + const output = codec.encodeSeed(this.to_bytes(), this._type); return output; }; -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; diff --git a/src/core/transaction.js b/src/core/transaction.js index 465e9cde37..23176acb9b 100644 --- a/src/core/transaction.js +++ b/src/core/transaction.js @@ -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; diff --git a/test/api-test.js b/test/api-test.js index de2fb018ca..6343123d26 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -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' }; diff --git a/test/base-test.js b/test/base-test.js index c578962205..32c5bf7faf 100644 --- a/test/base-test.js +++ b/test/base-test.js @@ -38,6 +38,15 @@ describe('Base', function() { assert(decoded.equals(1)); }); }); + describe('decode_multi_versioned', function() { + it('can return the version', function() { + const seed = 'sEd7rBGm5kxzauRTAV2hbsNz7N45X91'; + const versions = [Base.VER_ED25519_SEED, Base.VER_FAMILY_SEED]; + const decoded = Base.decode_multi_versioned(versions, seed, 16); + assert.equal(16 * 8, decoded.value.bitLength()); + assert.deepEqual(decoded.version, Base.VER_ED25519_SEED); + }); + }); describe('decode-encode identity', function() { it('rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() { const decoded = Base.decode('rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); diff --git a/test/fixtures/api/responses/sign.json b/test/fixtures/api/responses/sign.json index a060e7731b..8161e776c7 100644 --- a/test/fixtures/api/responses/sign.json +++ b/test/fixtures/api/responses/sign.json @@ -1,4 +1,4 @@ { - "signedTransaction": "12000322000000002400000017201B0086955368400000000000000C732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D87446304402207660BDEF67105CE1EBA9AD35DC7156BAB43FF1D47633199EE257D70B6B9AAFBF022045A812486A675750B5A3F37131E9F92299728D37FF6BB7195CA5EE881268CB4C770A726970706C652E636F6D81145E7B112523F68D2F5E879DB4EAC51C6698A69304", - "id": "29D23159EBA79170DCA5EF467CBC15114DBD35B7A8C3DBF76809BA354D00D250" + "signedTransaction": "12000322000000002400000017201B0086955368400000000000000C732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D874473045022100E3C85A98C3D48E2D746B3993D36C38A8922D9499E7D20B54883511C9CFF7F70A02201790168F671AC558E1B62B911CA7AC5B1D8E454898BE6F6E9D87758076683459770A726970706C652E636F6D81145E7B112523F68D2F5E879DB4EAC51C6698A69304", + "id": "7AFE2F2FBE72467C47CCDD6DBA890AB3C97A708C335983F77AF32C4308C73633" } diff --git a/test/keypair-test.js b/test/keypair-test.js deleted file mode 100644 index b00be5fb29..0000000000 --- a/test/keypair-test.js +++ /dev/null @@ -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 diff --git a/test/seed-test.js b/test/seed-test.js index 545ed9e8c4..77de941376 100644 --- a/test/seed-test.js +++ b/test/seed-test.js @@ -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 diff --git a/test/sign-test.js b/test/sign-test.js index 159af08fb6..24d3ebc7e8 100644 --- a/test/sign-test.js +++ b/test/sign-test.js @@ -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'); diff --git a/test/transaction-test.js b/test/transaction-test.js index 9e85078114..e3dd4a0520 100644 --- a/test/transaction-test.js +++ b/test/transaction-test.js @@ -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);