mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-20 20:25:48 +00:00
- Removes need for bundlers to polyfill the `Buffer` class. `UInt8Array` are used instead which are native to the browser and node. - Reduces bundle size 7.1kb gzipped and eliminates 4 runtime dependencies: `base-x`, `base64-js`, `buffer`, and `ieee754`. BREAKING CHANGE: All methods that previously took a `Buffer` now accept a `UInt8Array`. --------- Co-authored-by: Jackson Mills <jmills@ripple.com>
96 lines
2.5 KiB
TypeScript
96 lines
2.5 KiB
TypeScript
import {
|
|
bytesToHex,
|
|
concat,
|
|
hexToBytes,
|
|
randomBytes,
|
|
} from '@xrplf/isomorphic/utils'
|
|
|
|
function randomEntropy(): Uint8Array {
|
|
return 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: Uint8Array): 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(bytesToHex(buffChunk), 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[]): Uint8Array {
|
|
return 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 hexToBytes(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,
|
|
}
|