diff --git a/packages/secret-numbers/HISTORY.md b/packages/secret-numbers/HISTORY.md index efe84eec..7f012b3b 100644 --- a/packages/secret-numbers/HISTORY.md +++ b/packages/secret-numbers/HISTORY.md @@ -4,12 +4,12 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr ## 1.0.0 Beta 1 (2023-10-19) -- Add `xrpl-secret-numbers` by @WietseWind to the mono repo. -- `unpkg` and `jsdelivr` support was simplified. -- Unit tests run in a browser and node. -- Remove `brorand` as a dependency and use `@xrplf/isomorphic` instead. +* Add `xrpl-secret-numbers` by @WietseWind to the mono repo. +* `unpkg` and `jsdelivr` support was simplified. +* Unit tests run in a browser and node. +* Remove `brorand` as a dependency and use `@xrplf/isomorphic` instead. ### BREAKING CHANGES: -- `xrpl-secret-numbers` is now `@xrplf/secret-numbers`. -- The bundled file produced changed from `dist/browerified.js` to `build/xrplf-secret-numbers-latest.js`. -- Bundle variable is `xrplf_secret_numbers` instead of using browserify's loader. +* `xrpl-secret-numbers` is now `@xrplf/secret-numbers`. +* The bundled file produced changed from `dist/browerified.js` to `build/xrplf-secret-numbers-latest.js`. +* Bundle variable is `xrplf_secret_numbers` instead of using browserify's loader. diff --git a/packages/secret-numbers/package.json b/packages/secret-numbers/package.json index 90fd01be..df8d20f3 100644 --- a/packages/secret-numbers/package.json +++ b/packages/secret-numbers/package.json @@ -32,6 +32,7 @@ "@xrplf/isomorphic": "^1.0.0-beta.0", "ripple-keypairs": "^2.0.0-beta.0" }, + "prettier": "@xrplf/prettier-config", "repository": { "type": "git", "url": "git@github.com:XRPLF/xrpl.js.git" diff --git a/packages/secret-numbers/src/index.ts b/packages/secret-numbers/src/index.ts index 919677c2..7086f22c 100644 --- a/packages/secret-numbers/src/index.ts +++ b/packages/secret-numbers/src/index.ts @@ -1,8 +1,8 @@ /* Methods ==================================================================== */ -import Account from "./schema/Account"; -import * as Utils from "./utils"; +import Account from './schema/Account' +import * as Utils from './utils' /* Types ==================================================================== */ /* Export ==================================================================== */ -export { Account, Utils }; +export { Account, Utils } diff --git a/packages/secret-numbers/src/schema/Account.ts b/packages/secret-numbers/src/schema/Account.ts index d8f221fa..a84d1154 100644 --- a/packages/secret-numbers/src/schema/Account.ts +++ b/packages/secret-numbers/src/schema/Account.ts @@ -1,99 +1,99 @@ -import * as keypairs from "ripple-keypairs"; +import * as keypairs from 'ripple-keypairs' -import * as utils from "../utils"; +import * as utils from '../utils' /* Types ==================================================================== */ // eslint-disable-next-line import/no-unused-modules -- it is returned by Account.getKeypair export interface Keypair { - publicKey: string; - privateKey: string; + publicKey: string + privateKey: string } interface AccountData { - familySeed: string; - address: string; - keypair: Keypair; + familySeed: string + address: string + keypair: Keypair } /* Class ==================================================================== */ export default class Account { - private readonly _secret: string[]; + private readonly _secret: string[] private readonly _account: AccountData = { - familySeed: "", - address: "", + familySeed: '', + address: '', keypair: { - publicKey: "", - privateKey: "", + publicKey: '', + privateKey: '', }, - }; + } constructor(secretNumbers?: string[] | string | Buffer) { - if (typeof secretNumbers === "string") { - this._secret = utils.parseSecretString(secretNumbers); + if (typeof secretNumbers === 'string') { + this._secret = utils.parseSecretString(secretNumbers) } else if (Array.isArray(secretNumbers)) { - this._secret = secretNumbers; + this._secret = secretNumbers } else if (Buffer.isBuffer(secretNumbers)) { - this._secret = utils.entropyToSecret(secretNumbers); + this._secret = utils.entropyToSecret(secretNumbers) } else { - this._secret = utils.randomSecret(); + this._secret = utils.randomSecret() } - validateLengths(this._secret); - this.derive(); + validateLengths(this._secret) + this.derive() } getSecret(): string[] { - return this._secret; + return this._secret } getSecretString(): string { - return this._secret.join(" "); + return this._secret.join(' ') } getAddress(): string { - return this._account.address; + return this._account.address } getFamilySeed(): string { - return this._account.familySeed; + return this._account.familySeed } getKeypair(): Keypair { - return this._account.keypair; + return this._account.keypair } toString(): string { - return this.getSecretString(); + return this.getSecretString() } private derive(): void { try { - const entropy = utils.secretToEntropy(this._secret); - this._account.familySeed = keypairs.generateSeed({ entropy }); - this._account.keypair = keypairs.deriveKeypair(this._account.familySeed); + const entropy = utils.secretToEntropy(this._secret) + this._account.familySeed = keypairs.generateSeed({ entropy }) + this._account.keypair = keypairs.deriveKeypair(this._account.familySeed) this._account.address = keypairs.deriveAddress( - this._account.keypair.publicKey - ); + this._account.keypair.publicKey, + ) } catch (error) { - let message = "Unknown Error"; + let message = 'Unknown Error' if (error instanceof Error) { - message = error.message; + message = error.message } // we'll proceed, but let's report it - throw new Error(message); + throw new Error(message) } } } function validateLengths(secretNumbers: string[]): void { if (secretNumbers.length !== 8) { - throw new Error("Secret must have 8 numbers"); + throw new Error('Secret must have 8 numbers') } secretNumbers.forEach((num) => { if (num.length !== 6) { - throw new Error("Each secret number must be 6 digits"); + throw new Error('Each secret number must be 6 digits') } - }); + }) } diff --git a/packages/secret-numbers/src/utils/index.ts b/packages/secret-numbers/src/utils/index.ts index 146d9d28..b75c4e4e 100644 --- a/packages/secret-numbers/src/utils/index.ts +++ b/packages/secret-numbers/src/utils/index.ts @@ -1,82 +1,82 @@ -import { randomBytes } from "@xrplf/isomorphic/utils"; +import { randomBytes } from '@xrplf/isomorphic/utils' function randomEntropy(): Buffer { - return Buffer.from(randomBytes(16)); + return Buffer.from(randomBytes(16)) } function calculateChecksum(position: number, value: number): number { - return (value * (position * 2 + 1)) % 9; + return (value * (position * 2 + 1)) % 9 } function checkChecksum( position: number, value: number | string, - checksum?: number + checksum?: number, ): boolean { - let normalizedChecksum: number; - let normalizedValue: number; + let normalizedChecksum: number + let normalizedValue: number - if (typeof value === "string") { + if (typeof value === 'string') { if (value.length !== 6) { - throw new Error("value must have a length of 6"); + throw new Error('value must have a length of 6') } - normalizedChecksum = parseInt(value.slice(5), 10); - normalizedValue = parseInt(value.slice(0, 5), 10); + normalizedChecksum = parseInt(value.slice(5), 10) + normalizedValue = parseInt(value.slice(0, 5), 10) } else { - if (typeof checksum !== "number") { - throw new Error("checksum must be a number when value is a number"); + if (typeof checksum !== 'number') { + throw new Error('checksum must be a number when value is a number') } - normalizedChecksum = checksum; - normalizedValue = value; + normalizedChecksum = checksum + normalizedValue = value } - return (normalizedValue * (position * 2 + 1)) % 9 === normalizedChecksum; + return (normalizedValue * (position * 2 + 1)) % 9 === normalizedChecksum } function entropyToSecret(entropy: Buffer): string[] { - const len = new Array(Math.ceil(entropy.length / 2)); + const len = new Array(Math.ceil(entropy.length / 2)) const chunks = Array.from(len, (_a, chunk) => { - const buffChunk = entropy.slice(chunk * 2, (chunk + 1) * 2); - const no = parseInt(buffChunk.toString("hex"), 16); - const fill = "0".repeat(5 - String(no).length); - return fill + String(no) + String(calculateChecksum(chunk, no)); - }); + const buffChunk = entropy.slice(chunk * 2, (chunk + 1) * 2) + const no = parseInt(buffChunk.toString('hex'), 16) + const fill = '0'.repeat(5 - String(no).length) + return fill + String(no) + String(calculateChecksum(chunk, no)) + }) if (chunks.length !== 8) { - throw new Error("Chucks must have 8 digits"); + throw new Error('Chucks must have 8 digits') } - return chunks; + return chunks } function randomSecret(): string[] { - return entropyToSecret(randomEntropy()); + return entropyToSecret(randomEntropy()) } function secretToEntropy(secret: string[]): Buffer { return Buffer.concat( secret.map((chunk, i) => { - const no = Number(chunk.slice(0, 5)); - const checksum = Number(chunk.slice(5)); + const no = Number(chunk.slice(0, 5)) + const checksum = Number(chunk.slice(5)) if (chunk.length !== 6) { - throw new Error("Invalid secret: number invalid"); + throw new Error('Invalid secret: number invalid') } if (!checkChecksum(i, no, checksum)) { - throw new Error("Invalid secret part: checksum invalid"); + throw new Error('Invalid secret part: checksum invalid') } - const hex = `0000${no.toString(16)}`.slice(-4); - return Buffer.from(hex, "hex"); - }) - ); + const hex = `0000${no.toString(16)}`.slice(-4) + return Buffer.from(hex, 'hex') + }), + ) } function parseSecretString(secret: string): string[] { - const normalizedSecret = secret.replace(/[^0-9]/gu, ""); + const normalizedSecret = secret.replace(/[^0-9]/gu, '') if (normalizedSecret.length !== 48) { throw new Error( - "Invalid secret string (should contain 8 blocks of 6 digits" - ); + 'Invalid secret string (should contain 8 blocks of 6 digits', + ) } return Array.from(new Array(8), (_a, index) => { - return normalizedSecret.slice(index * 6, (index + 1) * 6); - }); + return normalizedSecret.slice(index * 6, (index + 1) * 6) + }) } export { @@ -87,4 +87,4 @@ export { calculateChecksum, checkChecksum, parseSecretString, -}; +} diff --git a/packages/secret-numbers/test/api.test.ts b/packages/secret-numbers/test/api.test.ts index e6973ed6..38bfd02c 100644 --- a/packages/secret-numbers/test/api.test.ts +++ b/packages/secret-numbers/test/api.test.ts @@ -1,99 +1,95 @@ -import * as keypairs from "ripple-keypairs"; +import * as keypairs from 'ripple-keypairs' -import { Account, Utils } from "../src"; +import { Account, Utils } from '../src' -describe("API: XRPL Secret Numbers", () => { - describe("Generate new account", () => { - const account = new Account(); - it("Output sanity checks", () => { - expect(account.getAddress()).toMatch(/^r[a-zA-Z0-9]{19,}$/u); - const entropy = Utils.secretToEntropy(`${account.toString()}`.split(" ")); - const familySeed = keypairs.generateSeed({ entropy }); - const keypair = keypairs.deriveKeypair(familySeed); - const address = keypairs.deriveAddress(keypair.publicKey); - expect(address).toEqual(account.getAddress()); - expect(familySeed).toEqual(account.getFamilySeed()); - }); - }); +describe('API: XRPL Secret Numbers', () => { + describe('Generate new account', () => { + const account = new Account() + it('Output sanity checks', () => { + expect(account.getAddress()).toMatch(/^r[a-zA-Z0-9]{19,}$/u) + const entropy = Utils.secretToEntropy(`${account.toString()}`.split(' ')) + const familySeed = keypairs.generateSeed({ entropy }) + const keypair = keypairs.deriveKeypair(familySeed) + const address = keypairs.deriveAddress(keypair.publicKey) + expect(address).toEqual(account.getAddress()) + expect(familySeed).toEqual(account.getFamilySeed()) + }) + }) - describe("Account based on entropy", () => { - const entropy = Buffer.from("0123456789ABCDEF0123456789ABCDEF", "hex"); - const account = new Account(entropy); + describe('Account based on entropy', () => { + const entropy = Buffer.from('0123456789ABCDEF0123456789ABCDEF', 'hex') + const account = new Account(entropy) - it("familySeed as expected", () => { - expect(account.getFamilySeed()).toEqual("sp5DmDCut79BpgumfHhvRzdxXYQyU"); - }); - it("address as expected", () => { - expect(account.getAddress()).toEqual( - "rMCcybKHfwCSkDHd3M36PAeUniEoygwjR3" - ); - }); - it("Account object to string as expected", () => { + it('familySeed as expected', () => { + expect(account.getFamilySeed()).toEqual('sp5DmDCut79BpgumfHhvRzdxXYQyU') + }) + it('address as expected', () => { + expect(account.getAddress()).toEqual('rMCcybKHfwCSkDHd3M36PAeUniEoygwjR3') + }) + it('Account object to string as expected', () => { const accountAsStr = - "002913 177673 352434 527196 002910 177672 352435 527190"; - expect(`${account.toString()}`).toEqual(accountAsStr); - }); - }); + '002913 177673 352434 527196 002910 177672 352435 527190' + expect(`${account.toString()}`).toEqual(accountAsStr) + }) + }) - describe("Account based on existing secret", () => { + describe('Account based on existing secret', () => { const secret = [ - "084677", - "005323", - "580272", - "282388", - "626800", - "105300", - "560913", - "071783", - ]; + '084677', + '005323', + '580272', + '282388', + '626800', + '105300', + '560913', + '071783', + ] - const account = new Account(secret); + const account = new Account(secret) - it("familySeed as expected", () => { - expect(account.getFamilySeed()).toEqual("sswpWwri7Y11dNCSmXdphgcoPZk3y"); - }); - it("publicKey as expected", () => { + it('familySeed as expected', () => { + expect(account.getFamilySeed()).toEqual('sswpWwri7Y11dNCSmXdphgcoPZk3y') + }) + it('publicKey as expected', () => { const pubkey = - "020526A0EDC9123F7FBB7588402518B80FCD2C8D8AB4C45F5A68A2F220098EA06F"; - expect(account.getKeypair().publicKey).toEqual(pubkey); - }); - it("privateKey as expected", () => { + '020526A0EDC9123F7FBB7588402518B80FCD2C8D8AB4C45F5A68A2F220098EA06F' + expect(account.getKeypair().publicKey).toEqual(pubkey) + }) + it('privateKey as expected', () => { const privkey = - "005122B2127B4635FEE7D242FA6EC9B02B611C04494D0D7D49764374D90C8BC8D3"; - expect(account.getKeypair().privateKey).toEqual(privkey); - }); - it("address as expected", () => { - expect(account.getAddress()).toEqual( - "rfqJsRLLmr7wdWnEzW1mP6AVaPSdzmso9Z" - ); - }); - it("Account object to string as expected", () => { + '005122B2127B4635FEE7D242FA6EC9B02B611C04494D0D7D49764374D90C8BC8D3' + expect(account.getKeypair().privateKey).toEqual(privkey) + }) + it('address as expected', () => { + expect(account.getAddress()).toEqual('rfqJsRLLmr7wdWnEzW1mP6AVaPSdzmso9Z') + }) + it('Account object to string as expected', () => { const accountAsStr = - "084677 005323 580272 282388 626800 105300 560913 071783"; - expect(`${account.toString()}`).toEqual(accountAsStr); - }); - }); + '084677 005323 580272 282388 626800 105300 560913 071783' + expect(`${account.toString()}`).toEqual(accountAsStr) + }) + }) - describe("Checksum error", () => { + describe('Checksum error', () => { const secret = [ - "084677", - "005324", - "580272", - "626800", - "282388", - "105300", - "560913", - "071783", - ]; - it("Should throw an Checksum Error", () => { + '084677', + '005324', + '580272', + '626800', + '282388', + '105300', + '560913', + '071783', + ] + it('Should throw an Checksum Error', () => { expect(() => { // eslint-disable-next-line no-new -- Don't want unused variable - new Account(secret); + new Account(secret) }) // TODO: Remove if jest is removed. // eslint-disable-next-line @typescript-eslint/ban-ts-comment -- Jest and Jasmine have two different signatures. // @ts-expect-error - .toThrowError(Error, "Invalid secret part: checksum invalid"); - }); - }); -}); + .toThrowError(Error, 'Invalid secret part: checksum invalid') + }) + }) +}) diff --git a/packages/secret-numbers/test/utils.test.ts b/packages/secret-numbers/test/utils.test.ts index 190f943a..7baf264d 100644 --- a/packages/secret-numbers/test/utils.test.ts +++ b/packages/secret-numbers/test/utils.test.ts @@ -1,107 +1,107 @@ -import * as utils from "../src/utils"; +import * as utils from '../src/utils' -describe("Utils", () => { - it("randomEntropy: valid output", () => { - const data = utils.randomEntropy(); - expect(typeof data).toEqual("object"); - expect(data instanceof Buffer).toBeTruthy(); - expect(data.length).toEqual(16); - expect(data.toString("hex").length).toEqual(32); - expect(data.toString("hex")).toMatch(/^[a-f0-9]+$/u); - }); +describe('Utils', () => { + it('randomEntropy: valid output', () => { + const data = utils.randomEntropy() + expect(typeof data).toEqual('object') + expect(data instanceof Buffer).toBeTruthy() + expect(data.length).toEqual(16) + expect(data.toString('hex').length).toEqual(32) + expect(data.toString('hex')).toMatch(/^[a-f0-9]+$/u) + }) - it("calculateChecksum: 1st position", () => { - expect(utils.calculateChecksum(0, 55988)).toEqual(8); - }); + it('calculateChecksum: 1st position', () => { + expect(utils.calculateChecksum(0, 55988)).toEqual(8) + }) - it("calculateChecksum: 8th position", () => { - expect(utils.calculateChecksum(7, 49962)).toEqual(0); - }); + it('calculateChecksum: 8th position', () => { + expect(utils.calculateChecksum(7, 49962)).toEqual(0) + }) - it("checkChecksum: 2nd position, split numbers", () => { - expect(utils.checkChecksum(1, 55450, 3)).toBeTruthy(); - }); + it('checkChecksum: 2nd position, split numbers', () => { + expect(utils.checkChecksum(1, 55450, 3)).toBeTruthy() + }) - it("checkChecksum: 7th position, split numbers", () => { - expect(utils.checkChecksum(6, 18373, 7)).toBeTruthy(); - }); + it('checkChecksum: 7th position, split numbers', () => { + expect(utils.checkChecksum(6, 18373, 7)).toBeTruthy() + }) - it("checkChecksum: 4th position, as string", () => { - expect(utils.checkChecksum(3, "391566")).toBeTruthy(); - }); + it('checkChecksum: 4th position, as string', () => { + expect(utils.checkChecksum(3, '391566')).toBeTruthy() + }) - it("randomSecret: valid checksums", () => { - utils.randomSecret(); - expect(0).toEqual(0); - }); + it('randomSecret: valid checksums', () => { + utils.randomSecret() + expect(0).toEqual(0) + }) - it("randomSecret: valid output", () => { - const data = utils.randomSecret(); - expect(Array.isArray(data)).toBeTruthy(); - expect(data.length).toEqual(8); - expect(typeof data[0]).toEqual("string"); - expect(data[0].length).toEqual(6); - expect(data[7].length).toEqual(6); - }); + it('randomSecret: valid output', () => { + const data = utils.randomSecret() + expect(Array.isArray(data)).toBeTruthy() + expect(data.length).toEqual(8) + expect(typeof data[0]).toEqual('string') + expect(data[0].length).toEqual(6) + expect(data[7].length).toEqual(6) + }) - it("entropyToSecret", () => { - const entropy = Buffer.from("76ebb2d06879b45b7568fb9c1ded097c", "hex"); + it('entropyToSecret', () => { + const entropy = Buffer.from('76ebb2d06879b45b7568fb9c1ded097c', 'hex') const secret = [ - "304435", - "457766", - "267453", - "461717", - "300560", - "644127", - "076618", - "024286", - ]; - expect(utils.entropyToSecret(entropy)).toEqual(secret); - }); + '304435', + '457766', + '267453', + '461717', + '300560', + '644127', + '076618', + '024286', + ] + expect(utils.entropyToSecret(entropy)).toEqual(secret) + }) - it("secretToEntropy", () => { + it('secretToEntropy', () => { const secret = [ - "304435", - "457766", - "267453", - "461717", - "300560", - "644127", - "076618", - "024286", - ]; - const entropy = Buffer.from("76ebb2d06879b45b7568fb9c1ded097c", "hex"); - expect(utils.secretToEntropy(secret)).toEqual(entropy); - }); + '304435', + '457766', + '267453', + '461717', + '300560', + '644127', + '076618', + '024286', + ] + const entropy = Buffer.from('76ebb2d06879b45b7568fb9c1ded097c', 'hex') + expect(utils.secretToEntropy(secret)).toEqual(entropy) + }) - it("parseSecretString with spaces valid", () => { + it('parseSecretString with spaces valid', () => { const secret = [ - "304435", - "457766", - "267453", - "461717", - "300560", - "644127", - "076618", - "024286", - ]; + '304435', + '457766', + '267453', + '461717', + '300560', + '644127', + '076618', + '024286', + ] expect( utils.parseSecretString( - "304435 457766 267453 461717 300560 644127 076618 024286" - ) - ).toEqual(secret); + '304435 457766 267453 461717 300560 644127 076618 024286', + ), + ).toEqual(secret) expect( utils.parseSecretString( - "304435457766267453461717300560644127076618024286" - ) - ).toEqual(secret); + '304435457766267453461717300560644127076618024286', + ), + ).toEqual(secret) expect( utils.parseSecretString(` 304435 457766 267453 461717 300560 644127 076618 024286 - `) - ).toEqual(secret); - }); -}); + `), + ).toEqual(secret) + }) +})