From b9b926e890de8526d953dbcfbb07dbe9c594aa00 Mon Sep 17 00:00:00 2001 From: Omar Khan Date: Sun, 29 Aug 2021 12:44:33 -0400 Subject: [PATCH] add getXAddress to Wallet (#1558) - add getXAddress to Wallet - refactor wallet tests --- src/Wallet.ts | 33 ++++-- test/.mocharc.json | 2 +- test/wallet/fromEntropy.ts | 55 ---------- test/wallet/fromMnemonic.ts | 40 ------- test/wallet/fromSeed.ts | 51 --------- test/wallet/index.ts | 183 +++++++++++++++++++++++++++++++ test/wallet/signTransaction.ts | 38 ------- test/wallet/verifyTransaction.ts | 56 ---------- 8 files changed, 208 insertions(+), 250 deletions(-) delete mode 100644 test/wallet/fromEntropy.ts delete mode 100644 test/wallet/fromMnemonic.ts delete mode 100644 test/wallet/fromSeed.ts create mode 100644 test/wallet/index.ts delete mode 100644 test/wallet/signTransaction.ts delete mode 100644 test/wallet/verifyTransaction.ts diff --git a/src/Wallet.ts b/src/Wallet.ts index 9a6cd58c..49481fe2 100644 --- a/src/Wallet.ts +++ b/src/Wallet.ts @@ -1,13 +1,14 @@ -import { fromSeed } from "bip32"; -import { mnemonicToSeedSync } from "bip39"; -import { decode, encodeForSigning } from "ripple-binary-codec"; -import { deriveKeypair, generateSeed, verify } from "ripple-keypairs"; +import { fromSeed } from 'bip32'; +import { mnemonicToSeedSync } from 'bip39'; +import { classicAddressToXAddress } from 'ripple-address-codec' +import { decode, encodeForSigning } from 'ripple-binary-codec'; +import { deriveAddress, deriveKeypair, generateSeed, verify } from 'ripple-keypairs'; -import ECDSA from "./common/ecdsa"; -import { ValidationError } from "./common/errors"; -import { SignedTransaction } from "./common/types/objects"; -import { signOffline } from "./transaction/sign"; -import { SignOptions } from "./transaction/types"; +import ECDSA from './common/ecdsa'; +import { ValidationError } from './common/errors'; +import { SignedTransaction } from './common/types/objects'; +import { signOffline } from './transaction/sign'; +import { SignOptions } from './transaction/types'; /** * A utility for deriving a wallet composed of a keypair (publicKey/privateKey). @@ -121,6 +122,20 @@ class Wallet { const signature = tx.TxnSignature; return verify(messageHex, signature, this.publicKey); } + + /** + * Gets an X-address in Testnet/Mainnet format. + * @param {number} tag A tag to be included within the X-address. + * @param {boolean} test A boolean to indicate if X-address should be in Testnet (true) or Mainnet (false) format. + * @returns {string} An X-address. + */ + getXAddress(tag: number, test: boolean = false): string { + return classicAddressToXAddress( + deriveAddress(this.publicKey), + tag, + test, + ) + } } export default Wallet; diff --git a/test/.mocharc.json b/test/.mocharc.json index 1f2ff0ed..03deee56 100644 --- a/test/.mocharc.json +++ b/test/.mocharc.json @@ -1,6 +1,6 @@ { "diff": true, - "spec": ["./test/*.ts", "./test/models/**/*.ts", "./test/utils/**/*.ts"], + "spec": ["./test/*.ts", "./test/models/**/*.ts", "./test/utils/**/*.ts", "./test/wallet/**/*.ts"], "extension": ["ts"], "package": "../package.json", "require": "ts-node/register", diff --git a/test/wallet/fromEntropy.ts b/test/wallet/fromEntropy.ts deleted file mode 100644 index 08ac7923..00000000 --- a/test/wallet/fromEntropy.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { assert } from "chai"; - -import ECDSA from "../../src/common/ecdsa"; -import Wallet from "../../src/Wallet"; -import { TestSuite } from "../testUtils"; - -const entropy: number[] = new Array(16).fill(0); -const publicKey = - "0390A196799EE412284A5D80BF78C3E84CBB80E1437A0AECD9ADF94D7FEAAFA284"; -const privateKey = - "002512BBDFDBB77510883B7DCCBEF270B86DEAC8B64AC762873D75A1BEE6298665"; -const publicKeyED25519 = - "ED1A7C082846CFF58FF9A892BA4BA2593151CCF1DBA59F37714CC9ED39824AF85F"; -const privateKeyED25519 = - "ED0B6CBAC838DFE7F47EA1BD0DF00EC282FDF45510C92161072CCFB84035390C4D"; - -/** - * Every test suite exports their tests in the default object. - * - Check out the "TestSuite" type for documentation on the interface. - * - Check out "test/api/index.ts" for more information about the test runner. - */ -export default { - "Wallet.fromEntropy with entropy only": async (api) => { - // WHEN deriving a wallet from an entropy - const wallet = Wallet.fromEntropy(entropy); - - // THEN we get a wallet with a keypair (publicKey/privateKey) - assert.equal(wallet.publicKey, publicKeyED25519); - assert.equal(wallet.privateKey, privateKeyED25519); - }, - - "Wallet.fromEntropy with algorithm ecdsa-secp256k1": async (api) => { - // GIVEN an entropy using ecdsa-secp256k1 - const algorithm = ECDSA.secp256k1; - - // WHEN deriving a wallet from an entropy - const wallet = Wallet.fromEntropy(entropy, algorithm); - - // THEN we get a wallet with a keypair (publicKey/privateKey) - assert.equal(wallet.publicKey, publicKey); - assert.equal(wallet.privateKey, privateKey); - }, - - "Wallet.fromEntropy with algorithm ed25519": async (api) => { - // GIVEN an entropy using ed25519 - const algorithm = ECDSA.ed25519; - - // WHEN deriving a wallet from an entropy - const wallet = Wallet.fromEntropy(entropy, algorithm); - - // THEN we get a wallet with a keypair (publicKey/privateKey) - assert.equal(wallet.publicKey, publicKeyED25519); - assert.equal(wallet.privateKey, privateKeyED25519); - }, -}; diff --git a/test/wallet/fromMnemonic.ts b/test/wallet/fromMnemonic.ts deleted file mode 100644 index ab6b6233..00000000 --- a/test/wallet/fromMnemonic.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { assert } from "chai"; - -import Wallet from "../../src/Wallet"; -import { TestSuite } from "../testUtils"; - -const mnemonic = - "try milk link drift aware pass obtain again music stick pluck fold"; -const publicKey = - "0257B550BA2FDCCF0ADDA3DEB2A5411700F3ADFDCC7C68E1DCD1E2B63E6B0C63E6"; -const privateKey = - "008F942B6E229C0E9CEE47E7A94253DABB6A9855F4BA2D8A741FA31851A1D423C3"; - -/** - * Every test suite exports their tests in the default object. - * - Check out the "TestSuite" type for documentation on the interface. - * - Check out "test/api/index.ts" for more information about the test runner. - */ -export default { - "Wallet.fromMnemonic using default derivation path": async (api) => { - // GIVEN no derivation path - // WHEN deriving a wallet from a mnemonic without a derivation path - const wallet = Wallet.fromMnemonic(mnemonic); - - // THEN we get a wallet with a keypair (publicKey/privateKey) - assert.equal(wallet.publicKey, publicKey); - assert.equal(wallet.privateKey, privateKey); - }, - - "Wallet.fromMnemonic using an input derivation path": async (api) => { - // GIVEN a derivation path - const derivationPath = "m/44'/144'/0'/0/0"; - - // WHEN deriving a wallet from a mnemonic without a derivation path - const wallet = Wallet.fromMnemonic(mnemonic, derivationPath); - - // THEN we get a wallet with a keypair (publicKey/privateKey) - assert.equal(wallet.publicKey, publicKey); - assert.equal(wallet.privateKey, privateKey); - }, -}; diff --git a/test/wallet/fromSeed.ts b/test/wallet/fromSeed.ts deleted file mode 100644 index 4a0ee797..00000000 --- a/test/wallet/fromSeed.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { assert } from "chai"; - -import ECDSA from "../../src/common/ecdsa"; -import Wallet from "../../src/Wallet"; -import { TestSuite } from "../testUtils"; - -const seed = "ssL9dv2W5RK8L3tuzQxYY6EaZhSxW"; -const publicKey = - "030E58CDD076E798C84755590AAF6237CA8FAE821070A59F648B517A30DC6F589D"; -const privateKey = - "00141BA006D3363D2FB2785E8DF4E44D3A49908780CB4FB51F6D217C08C021429F"; - -/** - * Every test suite exports their tests in the default object. - * - Check out the "TestSuite" type for documentation on the interface. - * - Check out "test/api/index.ts" for more information about the test runner. - */ -export default { - "Wallet.fromSeed with empty options object": async (api) => { - // WHEN deriving a wallet from a seed - const wallet = Wallet.fromSeed(seed); - - // THEN we get a wallet with a keypair (publicKey/privateKey) - assert.equal(wallet.publicKey, publicKey); - assert.equal(wallet.privateKey, privateKey); - }, - - "Wallet.fromSeed with algorithm ecdsa-secp256k1": async (api) => { - // GIVEN we want to use ecdsa-secp256k1 - const algorithm = ECDSA.secp256k1; - - // WHEN deriving a wallet from a seed - const wallet = Wallet.fromSeed(seed, algorithm); - - // THEN we get a wallet with a keypair (publicKey/privateKey) - assert.equal(wallet.publicKey, publicKey); - assert.equal(wallet.privateKey, privateKey); - }, - - "Wallet.fromSeed with algorithm ed25519": async (api) => { - // GIVEN we want to use ed25519 - const algorithm = ECDSA.ed25519; - - // WHEN deriving a wallet from a seed - const wallet = Wallet.fromSeed(seed, algorithm); - - // THEN we get a wallet with a keypair (publicKey/privateKey) - assert.equal(wallet.publicKey, publicKey); - assert.equal(wallet.privateKey, privateKey); - }, -}; diff --git a/test/wallet/index.ts b/test/wallet/index.ts new file mode 100644 index 00000000..6960f11e --- /dev/null +++ b/test/wallet/index.ts @@ -0,0 +1,183 @@ +import {assert} from 'chai' +import ECDSA from '../../src/common/ecdsa' +import Wallet from '../../src/Wallet' + +/** + * Wallet testing + * + * Provides tests for Wallet class + */ +describe('Wallet', () => { + describe('fromSeed', () => { + const seed = 'ssL9dv2W5RK8L3tuzQxYY6EaZhSxW' + const publicKey = + '030E58CDD076E798C84755590AAF6237CA8FAE821070A59F648B517A30DC6F589D' + const privateKey = + '00141BA006D3363D2FB2785E8DF4E44D3A49908780CB4FB51F6D217C08C021429F' + + it('derives a wallet using default algorithm', () => { + const wallet = Wallet.fromSeed(seed) + + assert.equal(wallet.publicKey, publicKey) + assert.equal(wallet.privateKey, privateKey) + }) + + it('derives a wallet using algorithm ecdsa-secp256k1', () => { + const algorithm = ECDSA.secp256k1 + const wallet = Wallet.fromSeed(seed, algorithm) + + assert.equal(wallet.publicKey, publicKey) + assert.equal(wallet.privateKey, privateKey) + }) + + it('derives a wallet using algorithm ed25519', () => { + const algorithm = ECDSA.ed25519 + const wallet = Wallet.fromSeed(seed, algorithm) + + assert.equal(wallet.publicKey, publicKey) + assert.equal(wallet.privateKey, privateKey) + }) + }) + + describe('fromMnemonic', () => { + const mnemonic = + 'try milk link drift aware pass obtain again music stick pluck fold' + const publicKey = + '0257B550BA2FDCCF0ADDA3DEB2A5411700F3ADFDCC7C68E1DCD1E2B63E6B0C63E6' + const privateKey = + '008F942B6E229C0E9CEE47E7A94253DABB6A9855F4BA2D8A741FA31851A1D423C3' + + it('derives a wallet using default derivation path', () => { + const wallet = Wallet.fromMnemonic(mnemonic) + + assert.equal(wallet.publicKey, publicKey) + assert.equal(wallet.privateKey, privateKey) + }) + + it('derives a wallet using an input derivation path', () => { + const derivationPath = "m/44'/144'/0'/0/0" + const wallet = Wallet.fromMnemonic(mnemonic, derivationPath) + + assert.equal(wallet.publicKey, publicKey) + assert.equal(wallet.privateKey, privateKey) + }) + }) + + describe('fromEntropy', () => { + const entropy: number[] = new Array(16).fill(0) + const publicKey: string = + '0390A196799EE412284A5D80BF78C3E84CBB80E1437A0AECD9ADF94D7FEAAFA284' + const privateKey: string = + '002512BBDFDBB77510883B7DCCBEF270B86DEAC8B64AC762873D75A1BEE6298665' + const publicKeyED25519: string = + 'ED1A7C082846CFF58FF9A892BA4BA2593151CCF1DBA59F37714CC9ED39824AF85F' + const privateKeyED25519: string = + 'ED0B6CBAC838DFE7F47EA1BD0DF00EC282FDF45510C92161072CCFB84035390C4D' + + it('derives a wallet using entropy', () => { + const wallet = Wallet.fromEntropy(entropy) + + assert.equal(wallet.publicKey, publicKeyED25519) + assert.equal(wallet.privateKey, privateKeyED25519) + }) + + it('derives a wallet using algorithm ecdsa-secp256k1', () => { + const algorithm = ECDSA.secp256k1 + const wallet = Wallet.fromEntropy(entropy, algorithm) + + assert.equal(wallet.publicKey, publicKey) + assert.equal(wallet.privateKey, privateKey) + }) + + it('derives a wallet using algorithm ed25519', () => { + const algorithm = ECDSA.ed25519 + const wallet = Wallet.fromEntropy(entropy, algorithm) + + assert.equal(wallet.publicKey, publicKeyED25519) + assert.equal(wallet.privateKey, privateKeyED25519) + }) + }) + + describe('signTransaction', () => { + const publicKey = + '030E58CDD076E798C84755590AAF6237CA8FAE821070A59F648B517A30DC6F589D' + const privateKey = + '00141BA006D3363D2FB2785E8DF4E44D3A49908780CB4FB51F6D217C08C021429F' + const address = 'rhvh5SrgBL5V8oeV9EpDuVszeJSSCEkbPc' + + it('signs a transaction offline', () => { + const txJSON = { + TransactionType: 'Payment', + Account: address, + Destination: 'rQ3PTWGLCbPz8ZCicV5tCX3xuymojTng5r', + Amount: '20000000', + Sequence: 1, + Fee: '12', + SigningPubKey: publicKey + } + const wallet = new Wallet(publicKey, privateKey) + const signedTx: {signedTransaction: string; id: string} = + wallet.signTransaction(txJSON) + + assert.hasAllKeys(signedTx, ['id', 'signedTransaction']) + assert.isString(signedTx.id) + assert.isString(signedTx.signedTransaction) + }) + }) + + describe('verifyTransaction', () => { + const publicKey = + '030E58CDD076E798C84755590AAF6237CA8FAE821070A59F648B517A30DC6F589D' + const privateKey = + '00141BA006D3363D2FB2785E8DF4E44D3A49908780CB4FB51F6D217C08C021429F' + const prepared = { + signedTransaction: + '1200002400000001614000000001312D0068400000000000000C7321030E58CDD076E798C84755590AAF6237CA8FAE821070A59F648B517A30DC6F589D74473045022100CAF99A63B241F5F62B456C68A593D2835397101533BB5D0C4DC17362AC22046F022016A2CA2CF56E777B10E43B56541A4C2FB553E7E298CDD39F7A8A844DA491E51D81142AF1861DEC1316AEEC995C94FF9E2165B1B784608314FDB08D07AAA0EB711793A3027304D688E10C3648', + id: '30D9ECA2A7FB568C5A8607E5850D9567572A9E7C6094C26BEFD4DC4C2CF2657A' + } + + it('returns true when verifying a transaction signed by the same wallet', () => { + const wallet = new Wallet(publicKey, privateKey) + const isVerified: boolean = wallet.verifyTransaction(prepared.signedTransaction) + + assert.equal(isVerified, true) + }) + + it('returns false when verifying a transaction signed by a different wallet', () => { + const diffPublicKey = + '02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8' + const diffPrivateKey = + '00ACCD3309DB14D1A4FC9B1DAE608031F4408C85C73EE05E035B7DC8B25840107A' + const wallet = new Wallet(diffPublicKey, diffPrivateKey) + const isVerified: boolean = wallet.verifyTransaction(prepared.signedTransaction) + + assert.equal(isVerified, false) + }) + }) + + describe('getXAddress', () => { + const publicKey = + '030E58CDD076E798C84755590AAF6237CA8FAE821070A59F648B517A30DC6F589D' + const privateKey = + '00141BA006D3363D2FB2785E8DF4E44D3A49908780CB4FB51F6D217C08C021429F' + const wallet = new Wallet(publicKey, privateKey) + const tag = 1337 + const mainnetXAddress = 'X7gJ5YK8abHf2eTPWPFHAAot8Knck11QGqmQ7a6a3Z8PJvk' + const testnetXAddress = 'T7bq3e7kxYq9pwDz8UZhqAZoEkcRGTXSNr5immvcj3DYRaV' + + it('returns a Testnet X-address when test is true', () => { + const result = wallet.getXAddress(tag, true) + assert.equal(result, testnetXAddress) + }) + + it('returns a Mainnet X-address when test is false', () => { + const result = wallet.getXAddress(tag, false) + assert.equal(result, mainnetXAddress) + }) + + it("returns a Mainnet X-address when test isn't provided", () => { + const result = wallet.getXAddress(tag) + assert.equal(result, mainnetXAddress) + }) + }) +}) diff --git a/test/wallet/signTransaction.ts b/test/wallet/signTransaction.ts deleted file mode 100644 index adc0a6c9..00000000 --- a/test/wallet/signTransaction.ts +++ /dev/null @@ -1,38 +0,0 @@ -import * as schemaValidator from "xrpl-local/common/schema-validator"; - -import Wallet from "../../src/Wallet"; -import { TestSuite } from "../testUtils"; - -const publicKey = - "030E58CDD076E798C84755590AAF6237CA8FAE821070A59F648B517A30DC6F589D"; -const privateKey = - "00141BA006D3363D2FB2785E8DF4E44D3A49908780CB4FB51F6D217C08C021429F"; -const address = "rhvh5SrgBL5V8oeV9EpDuVszeJSSCEkbPc"; - -/** - * Every test suite exports their tests in the default object. - * - Check out the "TestSuite" type for documentation on the interface. - * - Check out "test/api/index.ts" for more information about the test runner. - */ -export default { - "sign transaction offline with txJSON": async (api) => { - // GIVEN a transaction - const txJSON = { - TransactionType: "Payment", - Account: address, - Destination: "rQ3PTWGLCbPz8ZCicV5tCX3xuymojTng5r", - Amount: "20000000", - Sequence: 1, - Fee: "12", - SigningPubKey: publicKey, - }; - const wallet = new Wallet(publicKey, privateKey); - - // WHEN signing a transaction offline - const signedTx: { signedTransaction: string; id: string } = - wallet.signTransaction(txJSON); - - // THEN we get a signedTransaction - schemaValidator.schemaValidate("sign", signedTx); - }, -}; diff --git a/test/wallet/verifyTransaction.ts b/test/wallet/verifyTransaction.ts deleted file mode 100644 index d15e080d..00000000 --- a/test/wallet/verifyTransaction.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { assert } from "chai"; - -import Wallet from "xrpl-local/Wallet"; - -import { TestSuite } from "../testUtils"; - -const publicKey = - "030E58CDD076E798C84755590AAF6237CA8FAE821070A59F648B517A30DC6F589D"; -const privateKey = - "00141BA006D3363D2FB2785E8DF4E44D3A49908780CB4FB51F6D217C08C021429F"; -const prepared = { - signedTransaction: - "1200002400000001614000000001312D0068400000000000000C7321030E58CDD076E798C84755590AAF6237CA8FAE821070A59F648B517A30DC6F589D74473045022100CAF99A63B241F5F62B456C68A593D2835397101533BB5D0C4DC17362AC22046F022016A2CA2CF56E777B10E43B56541A4C2FB553E7E298CDD39F7A8A844DA491E51D81142AF1861DEC1316AEEC995C94FF9E2165B1B784608314FDB08D07AAA0EB711793A3027304D688E10C3648", - id: "30D9ECA2A7FB568C5A8607E5850D9567572A9E7C6094C26BEFD4DC4C2CF2657A", -}; - -/** - * Every test suite exports their tests in the default object. - * - Check out the "TestSuite" type for documentation on the interface. - * - Check out "test/api/index.ts" for more information about the test runner. - */ -export default { - "verify transaction offline when a signed transaction is valid": async ( - api - ) => { - // GIVEN a transaction that has been signed by the same wallet - const wallet = new Wallet(publicKey, privateKey); - - // WHEN verifying a signed transaction - const isVerified: boolean = wallet.verifyTransaction( - prepared.signedTransaction - ); - - // THEN we get a valid response - assert.equal(isVerified, true); - }, - - "verify transaction offline when signed transaction isn't valid": async ( - api - ) => { - // GIVEN a transaction that has been signed by a different wallet - const diffPublicKey = - "02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8"; - const diffPrivateKey = - "00ACCD3309DB14D1A4FC9B1DAE608031F4408C85C73EE05E035B7DC8B25840107A"; - const wallet = new Wallet(diffPublicKey, diffPrivateKey); - - // WHEN verifying a signed transaction - const isVerified: boolean = wallet.verifyTransaction( - prepared.signedTransaction - ); - - // THEN we get an invalid response - assert.equal(isVerified, false); - }, -};