Skip to content

Commit

Permalink
[Feature] Generate Wallet from secret numbers (#1799)
Browse files Browse the repository at this point in the history
* add the possibility to create a wallet from Secret Numbers

* Move it off the Wallet class
  • Loading branch information
nixer89 authored Aug 15, 2023
1 parent 3d94a24 commit aa75806
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 12 deletions.
36 changes: 27 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"@types/node": "^14.18.35",
"@types/ws": "^8.2.0",
"@typescript-eslint/eslint-plugin": "^5.28.0",
"@typescript-eslint/parser": "^5.28 .0",
"@typescript-eslint/parser": "^5.28.0",
"@xrplf/eslint-config": "^1.9.1",
"@xrplf/prettier-config": "^1.9.1",
"assert": "^2.0.0",
Expand Down
3 changes: 3 additions & 0 deletions packages/xrpl/HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xrpl-announce) for release announcements. We recommend that xrpl.js (ripple-lib) users stay up-to-date with the latest stable release.
## Unreleased

### Added
* Add `walletFromSecretNumbers` to derive a wallet from [XLS-12](https://github.com/XRPLF/XRPL-Standards/issues/15). Currently only works with `secp256k1` keys, but will work with `ED25519` keys as part of 3.0 via [#2376](https://github.com/XRPLF/xrpl.js/pull/2376).

## 2.10.0 (2023-08-07)

### Added
Expand Down
3 changes: 2 additions & 1 deletion packages/xrpl/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"ripple-address-codec": "^4.3.0",
"ripple-binary-codec": "^1.8.0",
"ripple-keypairs": "^1.3.0",
"ws": "^8.2.2"
"ws": "^8.2.2",
"xrpl-secret-numbers": "^0.3.3"
},
"devDependencies": {
"@geut/browser-node-core": "^2.0.13",
Expand Down
37 changes: 37 additions & 0 deletions packages/xrpl/src/Wallet/walletFromSecretNumbers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Account } from 'xrpl-secret-numbers'

import ECDSA from '../ECDSA'

import { Wallet } from '.'

/**
* Derives a wallet from secret numbers.
* NOTE: This uses a default encoding algorithm of secp256k1 to match the popular wallet
* [Xumm (aka Xaman)](https://xumm.app/)'s behavior.
* This may be different from the DEFAULT_ALGORITHM for other ways to generate a Wallet.
*
* @param secretNumbers - A string consisting of 8 times 6 numbers (whitespace delimited) used to derive a wallet.
* @param opts - (Optional) Options to derive a Wallet.
* @param opts.masterAddress - Include if a Wallet uses a Regular Key Pair. It must be the master address of the account.
* @param opts.algorithm - The digital signature algorithm to generate an address for.
* @returns A Wallet derived from secret numbers.
* @throws ValidationError if unable to derive private key from secret number input.
*/
export function walletFromSecretNumbers(
secretNumbers: string[] | string,
opts?: { masterAddress?: string; algorithm?: ECDSA },
): Wallet {
const secret = new Account(secretNumbers).getFamilySeed()
const updatedOpts: { masterAddress?: string; algorithm?: ECDSA } = {
masterAddress: undefined,
algorithm: undefined,
}
// Use secp256k1 since that's the algorithm used by popular wallets like Xumm when generating secret number accounts
if (opts === undefined) {
updatedOpts.algorithm = ECDSA.secp256k1
} else {
updatedOpts.masterAddress = opts.masterAddress
updatedOpts.algorithm = opts.algorithm ?? ECDSA.secp256k1
}
return Wallet.fromSecret(secret, updatedOpts)
}
2 changes: 2 additions & 0 deletions packages/xrpl/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export * from './errors'

export { Wallet } from './Wallet'

export { walletFromSecretNumbers } from './Wallet/walletFromSecretNumbers'

export { keyToRFC1751Mnemonic, rfc1751MnemonicToKey } from './Wallet/rfc1751'

export * from './Wallet/signer'
87 changes: 86 additions & 1 deletion packages/xrpl/test/wallet/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { assert } from 'chai'
import { decode } from 'ripple-binary-codec'

import { NFTokenMint, Payment, Transaction } from '../../src'
import {
NFTokenMint,
Payment,
Transaction,
walletFromSecretNumbers,
} from '../../src'
import ECDSA from '../../src/ECDSA'
import { Wallet } from '../../src/Wallet'
import requests from '../fixtures/requests'
Expand Down Expand Up @@ -297,6 +302,86 @@ describe('Wallet', function () {
})
})

describe('fromSecretNumbers', function () {
const secretNumbersString =
'399150 474506 009147 088773 432160 282843 253738 605430'
const secretNumbersArray = [
'399150',
'474506',
'009147',
'088773',
'432160',
'282843',
'253738',
'605430',
]

const publicKey =
'03BFC2F7AE242C3493187FA0B72BE97B2DF71194FB772E507FF9DEA0AD13CA1625'
const privateKey =
'00B6FE8507D977E46E988A8A94DB3B8B35E404B60F8B11AC5213FA8B5ABC8A8D19'
// TODO: Uncomment when the `deriveKeypair` fix is merged so `deriveSecretNumbers` with ed25519 works.
// const publicKeyED25519 =
// 'ED8079E575450E256C496578480020A33E19B579D58A2DB8FF13FC6B05B9229DE3'
// const privateKeyED25519 =
// 'EDD2AF6288A903DED9860FC62E778600A985BDF804E40BD8266505553E3222C3DA'

it('derives a wallet using default algorithm', function () {
const wallet = walletFromSecretNumbers(secretNumbersString)

assert.equal(wallet.publicKey, publicKey)
assert.equal(wallet.privateKey, privateKey)
})

it('derives a wallet from secret numbers as an array using default algorithm', function () {
const wallet = walletFromSecretNumbers(secretNumbersArray)

assert.equal(wallet.publicKey, publicKey)
assert.equal(wallet.privateKey, privateKey)
})

it('derives a wallet using algorithm ecdsa-secp256k1', function () {
const algorithm = ECDSA.secp256k1
const wallet = walletFromSecretNumbers(secretNumbersString, {
algorithm,
})

assert.equal(wallet.publicKey, publicKey)
assert.equal(wallet.privateKey, privateKey)
})

// TODO: Uncomment this test when the `deriveKeypair` fix is merged
// it('derives a wallet using algorithm ed25519', function () {
// const algorithm = ECDSA.ed25519
// const wallet = Wallet.fromSecretNumbers(secretNumbersString, {
// algorithm,
// })

// assert.equal(wallet.publicKey, publicKeyED25519)
// assert.equal(wallet.privateKey, privateKeyED25519)
// })

it('derives a wallet using a Regular Key Pair', function () {
const masterAddress = 'rUAi7pipxGpYfPNg3LtPcf2ApiS8aw9A93'
const regularKeyPair = {
secretNumbers:
'399150 474506 009147 088773 432160 282843 253738 605430',
publicKey:
'03BFC2F7AE242C3493187FA0B72BE97B2DF71194FB772E507FF9DEA0AD13CA1625',
privateKey:
'00B6FE8507D977E46E988A8A94DB3B8B35E404B60F8B11AC5213FA8B5ABC8A8D19',
}

const wallet = walletFromSecretNumbers(regularKeyPair.secretNumbers, {
masterAddress,
})

assert.equal(wallet.publicKey, regularKeyPair.publicKey)
assert.equal(wallet.privateKey, regularKeyPair.privateKey)
assert.equal(wallet.classicAddress, masterAddress)
})
})

describe('fromEntropy', function () {
let entropy: number[]
const publicKey =
Expand Down

0 comments on commit aa75806

Please sign in to comment.