Files
xahau.js/packages/secret-numbers/src/utils/index.ts
Nicholas Dudfield 217b111ef2 feat: use @noble and @scure libraries for cryptography (#2273)
Switch to using `@noble/hashes`, `@noble/curves`, `@scure/base`,
`@scure/bip32`, and `@scure/bip39`. This replaces `crypto` polyfills
(such as `crypto-browserify`), `create-hash`, `elliptic`, `hash.js`,
`bn.js` (both versions), and their many dependencies.  This also means
there are 33 less dependencies downloaded when running a fresh
`npm install` and will make the project much easier to maintain.

This reduces the bundle size by 44% (82kb minified and gzipped) over
the current 3.0 branch as well as reducing the amount of configuration
required to bundle.

Closes #1814, #1817, #2272, and #2306

Co-authored-by: Caleb Kniffen <ckniffen@ripple.com>
2024-02-01 13:50:19 -06:00

91 lines
2.5 KiB
TypeScript

import { randomBytes } from "@xrplf/isomorphic/utils";
function randomEntropy(): Buffer {
return Buffer.from(randomBytes(16));
}
function calculateChecksum(position: number, value: number): number {
return (value * (position * 2 + 1)) % 9;
}
function checkChecksum(
position: number,
value: number | string,
checksum?: number
): boolean {
let normalizedChecksum: number;
let normalizedValue: number;
if (typeof value === "string") {
if (value.length !== 6) {
throw new Error("value must have a length of 6");
}
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");
}
normalizedChecksum = checksum;
normalizedValue = value;
}
return (normalizedValue * (position * 2 + 1)) % 9 === normalizedChecksum;
}
function entropyToSecret(entropy: Buffer): string[] {
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));
});
if (chunks.length !== 8) {
throw new Error("Chucks must have 8 digits");
}
return chunks;
}
function randomSecret(): string[] {
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));
if (chunk.length !== 6) {
throw new Error("Invalid secret: number invalid");
}
if (!checkChecksum(i, no, checksum)) {
throw new Error("Invalid secret part: checksum invalid");
}
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, "");
if (normalizedSecret.length !== 48) {
throw new Error(
"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);
});
}
export {
randomEntropy,
randomSecret,
entropyToSecret,
secretToEntropy,
calculateChecksum,
checkChecksum,
parseSecretString,
};