mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-20 04:05:52 +00:00
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:
committed by
Mayukha Vadari
parent
b2b4b86f4d
commit
2347efc7d3
@@ -23,6 +23,8 @@
|
||||
"@types/lodash": "^4.14.136",
|
||||
"@types/ws": "^7.2.0",
|
||||
"bignumber.js": "^9.0.0",
|
||||
"bip32": "^2.0.6",
|
||||
"bip39": "^3.0.4",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"jsonschema": "1.2.2",
|
||||
"lodash": "^4.17.4",
|
||||
|
||||
112
src/Wallet.ts
Normal file
112
src/Wallet.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import {fromSeed} from 'bip32'
|
||||
import {mnemonicToSeedSync} from 'bip39'
|
||||
import {decode, encodeForSigning} from 'ripple-binary-codec'
|
||||
import {deriveKeypair, generateSeed, verify} from 'ripple-keypairs'
|
||||
import ECDSA from './common/ecdsa'
|
||||
import {SignedTransaction} from './common/types/objects'
|
||||
import {signOffline} from './transaction/sign'
|
||||
import {SignOptions} from './transaction/types'
|
||||
import {ValidationError} from './common/errors'
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
class Wallet {
|
||||
readonly publicKey: string
|
||||
readonly privateKey: string
|
||||
private static readonly defaultAlgorithm: ECDSA = ECDSA.ed25519
|
||||
private static readonly defaultDerivationPath: string = "m/44'/144'/0'/0/0"
|
||||
|
||||
constructor(publicKey: string, privateKey: string) {
|
||||
this.publicKey = publicKey
|
||||
this.privateKey = privateKey
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a wallet from a seed.
|
||||
* @param {string} seed A string used to generate a keypair (publicKey/privateKey) to derive a wallet.
|
||||
* @param {ECDSA} algorithm The digital signature algorithm to generate an address for.
|
||||
* @returns {Wallet} A Wallet derived from a seed.
|
||||
*/
|
||||
static fromSeed(seed: string, algorithm: ECDSA = Wallet.defaultAlgorithm): Wallet {
|
||||
return Wallet.deriveWallet(seed, algorithm)
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a wallet from a mnemonic.
|
||||
* @param {string} mnemonic A string consisting of words (whitespace delimited) used to derive a wallet.
|
||||
* @param {string} derivationPath The path to derive a keypair (publicKey/privateKey) from a seed (that was converted from a mnemonic).
|
||||
* @returns {Wallet} A Wallet derived from a mnemonic.
|
||||
*/
|
||||
static fromMnemonic(
|
||||
mnemonic: string,
|
||||
derivationPath: string = Wallet.defaultDerivationPath
|
||||
): Wallet {
|
||||
const seed = mnemonicToSeedSync(mnemonic)
|
||||
const masterNode = fromSeed(seed)
|
||||
const node = masterNode.derivePath(derivationPath)
|
||||
if (node.privateKey === undefined) {
|
||||
throw new ValidationError('Unable to derive privateKey from mnemonic input')
|
||||
}
|
||||
|
||||
const publicKey = Wallet.hexFromBuffer(node.publicKey)
|
||||
const privateKey = Wallet.hexFromBuffer(node.privateKey)
|
||||
return new Wallet(publicKey, `00${privateKey}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a wallet from an entropy (array of random numbers).
|
||||
* @param {Uint8Array | number[]} entropy An array of random numbers to generate a seed used to derive a wallet.
|
||||
* @param {ECDSA} algorithm The digital signature algorithm to generate an address for.
|
||||
* @returns {Wallet} A Wallet derived from an entropy.
|
||||
*/
|
||||
static fromEntropy(
|
||||
entropy: Uint8Array | number[],
|
||||
algorithm: ECDSA = Wallet.defaultAlgorithm
|
||||
): Wallet {
|
||||
const options = {
|
||||
entropy: Uint8Array.from(entropy),
|
||||
algorithm
|
||||
}
|
||||
const seed = generateSeed(options)
|
||||
return Wallet.deriveWallet(seed, algorithm)
|
||||
}
|
||||
|
||||
private static hexFromBuffer(buffer: Buffer): string {
|
||||
return buffer.toString('hex').toUpperCase()
|
||||
}
|
||||
|
||||
private static deriveWallet(seed: string, algorithm: ECDSA = Wallet.defaultAlgorithm): Wallet {
|
||||
const {publicKey, privateKey} = deriveKeypair(seed, {algorithm})
|
||||
return new Wallet(publicKey, privateKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs a transaction offline.
|
||||
* @param {object} transaction A transaction to be signed offline.
|
||||
* @param {SignOptions} options Options to include for signing.
|
||||
* @returns {SignedTransaction} A signed transaction.
|
||||
*/
|
||||
signTransaction(
|
||||
transaction: any, // TODO: transaction should be typed with Transaction type.
|
||||
options: SignOptions = {signAs: ''}
|
||||
): SignedTransaction {
|
||||
return signOffline(this, JSON.stringify(transaction), options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a signed transaction offline.
|
||||
* @param {string} signedTransaction A signed transaction (hex string of signTransaction result) to be verified offline.
|
||||
* @returns {boolean} Returns true if a signedTransaction is valid.
|
||||
*/
|
||||
verifyTransaction(signedTransaction: string): boolean {
|
||||
const tx = decode(signedTransaction)
|
||||
const messageHex: string = encodeForSigning(tx)
|
||||
const signature = tx.TxnSignature
|
||||
return verify(messageHex, signature, this.publicKey)
|
||||
}
|
||||
}
|
||||
|
||||
export default Wallet
|
||||
@@ -45,7 +45,7 @@ import prepareCheckCancel from './transaction/check-cancel'
|
||||
import prepareCheckCash from './transaction/check-cash'
|
||||
import prepareSettings from './transaction/settings'
|
||||
import prepareTicketCreate from './transaction/ticket'
|
||||
import sign from './transaction/sign'
|
||||
import {sign} from './transaction/sign'
|
||||
import combine from './transaction/combine'
|
||||
import submit from './transaction/submit'
|
||||
import {generateAddress, generateXAddress} from './offline/utils'
|
||||
|
||||
6
src/common/ecdsa.ts
Normal file
6
src/common/ecdsa.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
enum ECDSA {
|
||||
ed25519 = 'ed25519',
|
||||
secp256k1 = 'ecdsa-secp256k1',
|
||||
}
|
||||
|
||||
export default ECDSA
|
||||
@@ -20,3 +20,8 @@ export interface OfferCreateTransaction {
|
||||
Memos?: Memo[]
|
||||
OfferSequence?: number
|
||||
}
|
||||
|
||||
export interface SignedTransaction {
|
||||
signedTransaction: string
|
||||
id: string
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ export * from './transaction/types'
|
||||
|
||||
export * from './common/types/objects/ledger'
|
||||
|
||||
export * from './offline/utils';
|
||||
export * from './offline/utils'
|
||||
|
||||
// Broadcast api is experimental
|
||||
export {RippleAPIBroadcast} from './broadcast'
|
||||
|
||||
export * from './Wallet'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {classicAddressToXAddress} from 'ripple-address-codec'
|
||||
import keypairs from 'ripple-keypairs'
|
||||
import {errors, validate} from '../common'
|
||||
import ECDSA from '../common/ecdsa'
|
||||
|
||||
export type GeneratedAddress = {
|
||||
xAddress: string
|
||||
@@ -14,7 +15,7 @@ export interface GenerateAddressOptions {
|
||||
entropy?: Uint8Array | number[]
|
||||
|
||||
// The digital signature algorithm to generate an address for. Can be `ecdsa-secp256k1` (default) or `ed25519`.
|
||||
algorithm?: 'ecdsa-secp256k1' | 'ed25519'
|
||||
algorithm?: ECDSA
|
||||
|
||||
// Specifies whether the address is intended for use on a test network such as Testnet or Devnet.
|
||||
// If `true`, the address should only be used for testing, and will start with `T`.
|
||||
@@ -30,7 +31,7 @@ function generateAddressAPI(options: GenerateAddressOptions = {}): GeneratedAddr
|
||||
try {
|
||||
const generateSeedOptions: {
|
||||
entropy?: Uint8Array
|
||||
algorithm?: 'ecdsa-secp256k1' | 'ed25519'
|
||||
algorithm?: ECDSA
|
||||
} = {
|
||||
algorithm: options.algorithm
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import {SignOptions, KeyPair, TransactionJSON} from './types'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import {xrpToDrops} from '../common'
|
||||
import {RippleAPI} from '..'
|
||||
import Wallet from '../Wallet'
|
||||
import {SignedTransaction} from '../common/types/objects'
|
||||
const validate = utils.common.validate
|
||||
|
||||
function computeSignature(tx: object, privateKey: string, signAs?: string) {
|
||||
@@ -23,8 +25,9 @@ function signWithKeypair(
|
||||
options: SignOptions = {
|
||||
signAs: ''
|
||||
}
|
||||
): {signedTransaction: string; id: string} {
|
||||
): SignedTransaction {
|
||||
validate.sign({txJSON, keypair})
|
||||
const isOnline = !!api;
|
||||
|
||||
const tx = JSON.parse(txJSON)
|
||||
if (tx.TxnSignature || tx.Signers) {
|
||||
@@ -33,7 +36,9 @@ function signWithKeypair(
|
||||
)
|
||||
}
|
||||
|
||||
checkFee(api, tx.Fee)
|
||||
if (isOnline) {
|
||||
checkFee(api, tx.Fee)
|
||||
}
|
||||
|
||||
const txToSignAndEncode = Object.assign({}, tx)
|
||||
|
||||
@@ -219,7 +224,7 @@ function sign(
|
||||
secret?: any,
|
||||
options?: SignOptions,
|
||||
keypair?: KeyPair
|
||||
): {signedTransaction: string; id: string} {
|
||||
): SignedTransaction {
|
||||
if (typeof secret === 'string') {
|
||||
// we can't validate that the secret matches the account because
|
||||
// the secret could correspond to the regular key
|
||||
@@ -241,4 +246,19 @@ function sign(
|
||||
}
|
||||
}
|
||||
|
||||
export default sign
|
||||
// TODO: move this to Wallet class
|
||||
function signOffline(
|
||||
wallet: Wallet,
|
||||
txJSON: string,
|
||||
options?: SignOptions,
|
||||
): SignedTransaction {
|
||||
const {publicKey, privateKey} = wallet
|
||||
return signWithKeypair(
|
||||
null,
|
||||
txJSON,
|
||||
{publicKey, privateKey},
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
export {sign, signOffline}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
54
test/wallet/fromEntropy/index.ts
Normal file
54
test/wallet/fromEntropy/index.ts
Normal 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)
|
||||
},
|
||||
}
|
||||
39
test/wallet/fromMnemonic/index.ts
Normal file
39
test/wallet/fromMnemonic/index.ts
Normal 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)
|
||||
},
|
||||
}
|
||||
50
test/wallet/fromSeed/index.ts
Normal file
50
test/wallet/fromSeed/index.ts
Normal 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)
|
||||
},
|
||||
}
|
||||
38
test/wallet/signTransaction/index.ts
Normal file
38
test/wallet/signTransaction/index.ts
Normal 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)
|
||||
},
|
||||
}
|
||||
46
test/wallet/verifyTransaction/index.ts
Normal file
46
test/wallet/verifyTransaction/index.ts
Normal 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)
|
||||
},
|
||||
}
|
||||
97
yarn.lock
97
yarn.lock
@@ -323,6 +323,16 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.10.tgz#7aa732cc47341c12a16b7d562f519c2383b6d4fc"
|
||||
integrity sha512-S63Dlv4zIPb8x6MMTgDq5WWRJQe56iBEY0O3SOFA9JrRienkOVDXSXBjjJw6HTNQYSE2JI6GMCR6LVbIMHJVvA==
|
||||
|
||||
"@types/node@10.12.18":
|
||||
version "10.12.18"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67"
|
||||
integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==
|
||||
|
||||
"@types/node@11.11.6":
|
||||
version "11.11.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.6.tgz#df929d1bb2eee5afdda598a41930fe50b43eaa6a"
|
||||
integrity sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==
|
||||
|
||||
"@types/ws@^7.2.0":
|
||||
version "7.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702"
|
||||
@@ -731,7 +741,7 @@ balanced-match@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||
|
||||
base-x@3.0.8:
|
||||
base-x@3.0.8, base-x@^3.0.2:
|
||||
version "3.0.8"
|
||||
resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.8.tgz#1e1106c2537f0162e8b52474a557ebb09000018d"
|
||||
integrity sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==
|
||||
@@ -758,6 +768,36 @@ binary-extensions@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
|
||||
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
|
||||
|
||||
bindings@^1.3.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
|
||||
integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==
|
||||
dependencies:
|
||||
file-uri-to-path "1.0.0"
|
||||
|
||||
bip32@^2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/bip32/-/bip32-2.0.6.tgz#6a81d9f98c4cd57d05150c60d8f9e75121635134"
|
||||
integrity sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==
|
||||
dependencies:
|
||||
"@types/node" "10.12.18"
|
||||
bs58check "^2.1.1"
|
||||
create-hash "^1.2.0"
|
||||
create-hmac "^1.1.7"
|
||||
tiny-secp256k1 "^1.1.3"
|
||||
typeforce "^1.11.5"
|
||||
wif "^2.0.6"
|
||||
|
||||
bip39@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.0.4.tgz#5b11fed966840b5e1b8539f0f54ab6392969b2a0"
|
||||
integrity sha512-YZKQlb752TrUWqHWj7XAwCSjYEgGAk+/Aas3V7NyjQeZYsztO8JnQUaCWhcnL4T+jL8nvB8typ2jRPzTlgugNw==
|
||||
dependencies:
|
||||
"@types/node" "11.11.6"
|
||||
create-hash "^1.1.0"
|
||||
pbkdf2 "^3.0.9"
|
||||
randombytes "^2.0.1"
|
||||
|
||||
bl@^4.0.3:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
|
||||
@@ -767,7 +807,7 @@ bl@^4.0.3:
|
||||
inherits "^2.0.4"
|
||||
readable-stream "^3.4.0"
|
||||
|
||||
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9:
|
||||
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.8, bn.js@^4.11.9:
|
||||
version "4.12.0"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
|
||||
integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
|
||||
@@ -872,6 +912,22 @@ browserslist@^4.14.5, browserslist@^4.16.6:
|
||||
escalade "^3.1.1"
|
||||
node-releases "^1.1.71"
|
||||
|
||||
bs58@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a"
|
||||
integrity sha1-vhYedsNU9veIrkBx9j806MTwpCo=
|
||||
dependencies:
|
||||
base-x "^3.0.2"
|
||||
|
||||
bs58check@<3.0.0, bs58check@^2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc"
|
||||
integrity sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==
|
||||
dependencies:
|
||||
bs58 "^4.0.0"
|
||||
create-hash "^1.1.0"
|
||||
safe-buffer "^5.1.2"
|
||||
|
||||
buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
@@ -1381,7 +1437,7 @@ electron-to-chromium@^1.3.723:
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.792.tgz#791b0d8fcf7411885d086193fb49aaef0c1594ca"
|
||||
integrity sha512-RM2O2xrNarM7Cs+XF/OE2qX/aBROyOZqqgP+8FXMXSuWuUqCfUUzg7NytQrzZU3aSqk1Qq6zqnVkJsbfMkIatg==
|
||||
|
||||
elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.4:
|
||||
elliptic@^6.4.0, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.4:
|
||||
version "6.5.4"
|
||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
|
||||
integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
|
||||
@@ -1720,6 +1776,11 @@ file-entry-cache@^5.0.1:
|
||||
dependencies:
|
||||
flat-cache "^2.0.1"
|
||||
|
||||
file-uri-to-path@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
|
||||
integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
|
||||
|
||||
filelist@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b"
|
||||
@@ -2664,6 +2725,11 @@ mute-stream@0.0.8:
|
||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
|
||||
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
|
||||
|
||||
nan@^2.13.2:
|
||||
version "2.15.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee"
|
||||
integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==
|
||||
|
||||
nanoid@3.1.23:
|
||||
version "3.1.23"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81"
|
||||
@@ -2920,7 +2986,7 @@ path-parse@^1.0.6:
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||
|
||||
pbkdf2@^3.0.3:
|
||||
pbkdf2@^3.0.3, pbkdf2@^3.0.9:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075"
|
||||
integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==
|
||||
@@ -3624,6 +3690,17 @@ through@^2.3.6, through@^2.3.8:
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
||||
|
||||
tiny-secp256k1@^1.1.3:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz#7e224d2bee8ab8283f284e40e6b4acb74ffe047c"
|
||||
integrity sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==
|
||||
dependencies:
|
||||
bindings "^1.3.0"
|
||||
bn.js "^4.11.8"
|
||||
create-hmac "^1.1.7"
|
||||
elliptic "^6.4.0"
|
||||
nan "^2.13.2"
|
||||
|
||||
tmp@^0.0.33:
|
||||
version "0.0.33"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
||||
@@ -3732,6 +3809,11 @@ typedarray-to-buffer@^3.1.5:
|
||||
dependencies:
|
||||
is-typedarray "^1.0.0"
|
||||
|
||||
typeforce@^1.11.5:
|
||||
version "1.18.0"
|
||||
resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc"
|
||||
integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==
|
||||
|
||||
typescript@^3.9.9:
|
||||
version "3.9.10"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8"
|
||||
@@ -4015,6 +4097,13 @@ wide-align@1.1.3:
|
||||
dependencies:
|
||||
string-width "^1.0.2 || 2"
|
||||
|
||||
wif@^2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/wif/-/wif-2.0.6.tgz#08d3f52056c66679299726fade0d432ae74b4704"
|
||||
integrity sha1-CNP1IFbGZnkplyb63g1DKudLRwQ=
|
||||
dependencies:
|
||||
bs58check "<3.0.0"
|
||||
|
||||
wildcard@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec"
|
||||
|
||||
Reference in New Issue
Block a user