add getXAddress to Wallet (#1558)

- add getXAddress to Wallet
- refactor wallet tests
This commit is contained in:
Omar Khan
2021-08-29 12:44:33 -04:00
committed by Mayukha Vadari
parent b8be6c2f1b
commit 43802f9e22
8 changed files with 208 additions and 250 deletions

View File

@@ -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;

View File

@@ -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",

View File

@@ -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 <TestSuite>{
"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);
},
};

View File

@@ -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 <TestSuite>{
"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);
},
};

View File

@@ -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 <TestSuite>{
"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);
},
};

183
test/wallet/index.ts Normal file
View File

@@ -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)
})
})
})

View File

@@ -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 <TestSuite>{
"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);
},
};

View File

@@ -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 <TestSuite>{
"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);
},
};