define Wallet class (#1509)

* define Wallet class
- Wallet class is a utility for deriving a wallet composed of a keypair (publicKey/privateKey).
- A wallet can be derived from either a seed, mnemnoic, or entropy (array of random numbers).
- It provides functionality to sign/verify transactions offline.
This commit is contained in:
Omar Khan
2021-08-11 17:24:33 -04:00
committed by Mayukha Vadari
parent 51b4ff7a2c
commit c431e70900
16 changed files with 494 additions and 28 deletions

View File

@@ -2,6 +2,7 @@ import assert from 'assert-diff'
import responses from '../../fixtures/responses'
import {TestSuite} from '../../utils'
import {GenerateAddressOptions} from '../../../src/offline/generate-address'
import ECDSA from '../../../src/common/ecdsa'
const {generateAddress: RESPONSE_FIXTURES} = responses
/**
@@ -65,7 +66,7 @@ export default <TestSuite>{
'generateAddress with algorithm `ecdsa-secp256k1`': async (api) => {
// GIVEN we want to use 'ecdsa-secp256k1'
const options: GenerateAddressOptions = {algorithm: 'ecdsa-secp256k1'}
const options: GenerateAddressOptions = {algorithm: ECDSA.secp256k1}
// WHEN generating an address
const account = api.generateAddress(options)
@@ -86,7 +87,7 @@ export default <TestSuite>{
'generateAddress with algorithm `ed25519`': async (api) => {
// GIVEN we want to use 'ed25519'
const options: GenerateAddressOptions = {algorithm: 'ed25519'}
const options: GenerateAddressOptions = {algorithm: ECDSA.ed25519}
// WHEN generating an address
const account = api.generateAddress(options)
@@ -105,7 +106,7 @@ export default <TestSuite>{
) => {
// GIVEN we want to use 'ecdsa-secp256k1' with entropy of zero
const options: GenerateAddressOptions = {
algorithm: 'ecdsa-secp256k1',
algorithm: ECDSA.secp256k1,
entropy: new Array(16).fill(0)
}
@@ -119,7 +120,7 @@ export default <TestSuite>{
'generateAddress with algorithm `ed25519` and given entropy': async (api) => {
// GIVEN we want to use 'ed25519' with entropy of zero
const options: GenerateAddressOptions = {
algorithm: 'ed25519',
algorithm: ECDSA.ed25519,
entropy: new Array(16).fill(0)
}
@@ -142,7 +143,7 @@ export default <TestSuite>{
) => {
// GIVEN we want to use 'ecdsa-secp256k1' with entropy of zero
const options: GenerateAddressOptions = {
algorithm: 'ecdsa-secp256k1',
algorithm: ECDSA.secp256k1,
entropy: new Array(16).fill(0),
includeClassicAddress: true
}
@@ -159,7 +160,7 @@ export default <TestSuite>{
) => {
// GIVEN we want to use 'ed25519' with entropy of zero
const options: GenerateAddressOptions = {
algorithm: 'ed25519',
algorithm: ECDSA.ed25519,
entropy: new Array(16).fill(0),
includeClassicAddress: true
}
@@ -183,7 +184,7 @@ export default <TestSuite>{
) => {
// GIVEN we want to use 'ecdsa-secp256k1' with entropy of zero
const options: GenerateAddressOptions = {
algorithm: 'ecdsa-secp256k1',
algorithm: ECDSA.secp256k1,
entropy: new Array(16).fill(0),
includeClassicAddress: true,
test: true
@@ -205,7 +206,7 @@ export default <TestSuite>{
) => {
// GIVEN we want to use 'ed25519' with entropy of zero
const options: GenerateAddressOptions = {
algorithm: 'ed25519',
algorithm: ECDSA.ed25519,
entropy: new Array(16).fill(0),
includeClassicAddress: true,
test: true

View File

@@ -1,6 +1,7 @@
import assert from 'assert-diff'
import responses from '../../fixtures/responses'
import {TestSuite} from '../../utils'
import ECDSA from '../../../src/common/ecdsa'
import {GenerateAddressOptions} from '../../../src/offline/generate-address'
/**
@@ -70,7 +71,7 @@ export default <TestSuite>{
'generateXAddress with algorithm `ecdsa-secp256k1`': async (api) => {
// GIVEN we want to use 'ecdsa-secp256k1'
const options: GenerateAddressOptions = {algorithm: 'ecdsa-secp256k1'}
const options: GenerateAddressOptions = {algorithm: ECDSA.secp256k1}
// WHEN generating an X-address
const account = api.generateXAddress(options)
@@ -94,7 +95,7 @@ export default <TestSuite>{
'generateXAddress with algorithm `ed25519`': async (api) => {
// GIVEN we want to use 'ed25519'
const options: GenerateAddressOptions = {algorithm: 'ed25519'}
const options: GenerateAddressOptions = {algorithm: ECDSA.ed25519}
// WHEN generating an X-address
const account = api.generateXAddress(options)
@@ -116,7 +117,7 @@ export default <TestSuite>{
) => {
// GIVEN we want to use 'ecdsa-secp256k1' with entropy of zero
const options: GenerateAddressOptions = {
algorithm: 'ecdsa-secp256k1',
algorithm: ECDSA.secp256k1,
entropy: new Array(16).fill(0)
}
@@ -132,7 +133,7 @@ export default <TestSuite>{
) => {
// GIVEN we want to use 'ed25519' with entropy of zero
const options: GenerateAddressOptions = {
algorithm: 'ed25519',
algorithm: ECDSA.ed25519,
entropy: new Array(16).fill(0)
}
@@ -151,7 +152,7 @@ export default <TestSuite>{
) => {
// GIVEN we want to use 'ecdsa-secp256k1' with entropy of zero
const options: GenerateAddressOptions = {
algorithm: 'ecdsa-secp256k1',
algorithm: ECDSA.secp256k1,
entropy: new Array(16).fill(0),
includeClassicAddress: true
}
@@ -168,7 +169,7 @@ export default <TestSuite>{
) => {
// GIVEN we want to use 'ed25519' with entropy of zero
const options: GenerateAddressOptions = {
algorithm: 'ed25519',
algorithm: ECDSA.ed25519,
entropy: new Array(16).fill(0),
includeClassicAddress: true
}
@@ -190,7 +191,7 @@ export default <TestSuite>{
) => {
// GIVEN we want to use 'ecdsa-secp256k1' with entropy of zero
const options: GenerateAddressOptions = {
algorithm: 'ecdsa-secp256k1',
algorithm: ECDSA.secp256k1,
entropy: new Array(16).fill(0),
includeClassicAddress: true,
test: true
@@ -211,7 +212,7 @@ export default <TestSuite>{
) => {
// GIVEN we want to use 'ed25519' with entropy of zero
const options: GenerateAddressOptions = {
algorithm: 'ed25519',
algorithm: ECDSA.ed25519,
entropy: new Array(16).fill(0),
includeClassicAddress: true,
test: true

View File

@@ -0,0 +1,54 @@
import assert from 'assert-diff'
import {TestSuite} from '../../utils'
import ECDSA from '../../../src/common/ecdsa'
import Wallet from '../../../src/Wallet'
const entropy: number[] = new Array(16).fill(0)
const publicKey: string =
'0390A196799EE412284A5D80BF78C3E84CBB80E1437A0AECD9ADF94D7FEAAFA284'
const privateKey: string =
'002512BBDFDBB77510883B7DCCBEF270B86DEAC8B64AC762873D75A1BEE6298665'
const publicKeyED25519: string =
'ED1A7C082846CFF58FF9A892BA4BA2593151CCF1DBA59F37714CC9ED39824AF85F'
const privateKeyED25519: string =
'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

@@ -0,0 +1,39 @@
import assert from 'assert-diff'
import {TestSuite} from '../../utils'
import Wallet from '../../../src/Wallet'
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

@@ -0,0 +1,50 @@
import assert from 'assert-diff'
import {TestSuite} from '../../utils'
import ECDSA from '../../../src/common/ecdsa'
import Wallet from '../../../src/Wallet'
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)
},
}

View File

@@ -0,0 +1,38 @@
import {RippleAPI} from 'ripple-api'
import {TestSuite} from '../../utils'
import Wallet from '../../../src/Wallet'
const {schemaValidator} = RippleAPI._PRIVATE
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

@@ -0,0 +1,46 @@
import assert from 'assert-diff'
import {TestSuite} from '../../utils'
import Wallet from 'ripple-api/Wallet'
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)
},
}