feat: add xrpl-secret-numbers to the monorepo (#2445)

- Add `xrpl-secret-numbers` by @WietseWind  to the mono repo.
- `unpkg` and `jsdelivr` support was simplified by adding fields to package.json.
- Unit tests run in a browser and node.

BREAKING CHANGES:
- `xrpl-secret-numbers` is now `@xrplf/secret-numbers`.
- Changed 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.
- If using CDN instructions in README will need to update to `https://cdn.jsdelivr.net/npm/@xrplf/secret-numbers`

Making this library part of the monorepo was discussed in #1788.  This solves several problems with the upcoming `xrpl.js@3.0`.

`xrpl-secret-numbers` has a dependency of `ripple-keypairs` and `xrpl` has a depenendecy on `xrpl-secret-numbers`.  This means that any change would require a separate release of `ripple-keypairs`, then a release of `xrpl-secret-numbers`, followed by `xrpl`.  Now they can be released all at once

In 3.0 we eliminated the need for many polyfills like `util`, `assert`, and soon to be `buffer` (after noble libs PR is merged).  `xrpl-secret-numbers` still needs those.  This will also eliminate them and anytime similar changes in the future will be much easier.  This further reduces the bundle size of 3.0 branch by over 10% as well as reducing bundler setup.
This commit is contained in:
Caleb Kniffen
2023-08-31 19:48:39 -05:00
parent 3d86318195
commit 1a83997e49
30 changed files with 1007 additions and 174 deletions

View File

@@ -0,0 +1,90 @@
import brorand from "brorand";
function randomEntropy(): Buffer {
return Buffer.from(brorand(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,
};