diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c4dc9b5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,22 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node: [16, 18, 20, 22, 24] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + - run: corepack enable + - run: yarn install --immutable + - run: yarn test diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..cf029a3 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,47 @@ +name: Publish + +# Cut a release with `npm version patch|minor|major` on master. That runs the +# tests, dates the CHANGELOG entry, commits, tags `vX.Y.Z` and pushes. This +# workflow then publishes to npm with provenance and creates the GitHub release. +# +# Needs one repository secret: NPM_TOKEN, a granular npm access token with +# read/write on @provableio/provable-core and bypass 2FA for automation. + +on: + push: + tags: ["v[0-9]+.[0-9]+.[0-9]+"] + +permissions: + contents: write + id-token: write + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 24 + registry-url: https://registry.npmjs.org + - run: corepack enable + - run: yarn install --immutable + - run: yarn test + + - name: Tag must match package.json + run: | + version="v$(node -p "require('./package.json').version")" + test "$version" = "$GITHUB_REF_NAME" || { echo "tag $GITHUB_REF_NAME != package.json $version"; exit 1; } + + - name: Release notes from CHANGELOG.md + run: node scripts/release-changelog.js notes "$GITHUB_REF_NAME" > release-notes.md + + - name: Publish to npm + run: npm publish --provenance --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: GitHub release + run: gh release create "$GITHUB_REF_NAME" --title "$GITHUB_REF_NAME" --notes-file release-notes.md + env: + GH_TOKEN: ${{ github.token }} diff --git a/.gitignore b/.gitignore index bacf133..68fc02d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ node_modules .env *.log -dist \ No newline at end of file +dist +.yarn/ +.pnp.* diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..e030e5b --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +message=v%s diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..22efd3d --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1,3 @@ +nodeLinker: node-modules +enableGlobalCache: true +enableTelemetry: false diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5529358 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,79 @@ +# Changelog + +All notable changes to this project are documented here. The format follows +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project +follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +Because this library exists to make results reproducible, **any change to the +bytes, floats or integers produced for a given input is a breaking change and +requires a major version**, however small it looks. + +## [Unreleased] + +### Breaking + +- Inputs are validated. Seeds must be non-empty strings; `nonce`, `cursor`, + `count`, `max`, `min`, hash-chain `count`/`index` and hash-series `nonce` + must be safe integers or their canonical decimal string form (`"400"`). + Calls that previously coerced or ignored bad input (`ints()` returned `[]`, + a `NaN` nonce was hashed as the text `NaN`) now throw. +- `serverHash` is always derived from `serverSeed`. A supplied value is + replaced rather than trusted. +- `Provable(...)(config)` no longer mutates `config`; `state()` and the + emitted state are copies. +- `next(salt)` requires a non-empty `salt`. +- `HashChain` rejects `index` outside `[0, count - 1]` and non-integer `count` + with clear errors instead of `RangeError: Invalid array length` or silent + `undefined`. +- Requires Node.js 14.17 or later (`crypto.randomUUID`). + +### Fixed + +- A long-lived `Provable` instance reused its first nonce for every draw, so + the emitted state did not reproduce the outcome it was recorded against. + Each draw now opens a fresh byte stream for the current nonce, then advances + it. Instances that were re-created from persisted state before every draw + produce identical results before and after this fix. +- The nonce guard runs before state changes, so a failed draw leaves the + instance untouched. +- `HashChain` count assertion checked the seed (copy-paste). +- `HashChain()` with no argument threw `TypeError`. +- LICENSE copyright holder was blank. + +### Changed + +- README rewritten to describe the actual algorithm (HMAC-SHA256, the + `[min, min + max - 1]` range of `ints`, immutable `HashSeries`, no `new` on + generator functions) and to add a verification recipe. +- Test suite pins the byte stream, float and integer conversions and the + rotation formula with fixed vectors. +- Releases are cut with `npm version` and published from GitHub Actions on + `v*` tags with npm provenance. + +### Added + +- `HashChain.generateHashChain(count, seed)`. +- `utils.toInteger`, `utils.assertSeed`, `utils.randomUUID`. +- `files` field so the published tarball ships only the library. + +### Removed + +- Runtime dependencies `lodash` and `uuid`. +- The `release` npm script. + +## [1.0.1] - 2025-09-27 + +### Changed + +- Nonce advances eagerly and is capped at `Number.MAX_SAFE_INTEGER`. +- Documentation and typo fixes. + +## [1.0.0] - 2024-05-03 + +### Added + +- `Provable`, `HashSeries`, `HashChain` and `utils`. + +[Unreleased]: https://github.com/provableio/provable-core/compare/v1.0.1...HEAD +[1.0.1]: https://github.com/provableio/provable-core/compare/v1.0.0...v1.0.1 +[1.0.0]: https://github.com/provableio/provable-core/releases/tag/v1.0.0 diff --git a/LICENSE b/LICENSE index 244229e..edeafb0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) +Copyright (c) 2012-2026 Provable.io Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 0e7af90..5cdf46e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ # Provable.IO Core Library -This is a random number generator core library that uses various hashing functions, byte generation, and other utilities. The generator can produce both floating-point and integer numbers with configurable settings. +Provably fair random number generation. A server seed, a client seed and a +nonce are combined with HMAC-SHA256 into a byte stream, the bytes become +floats in `[0, 1)`, and the floats become game outcomes. Anyone holding the +three inputs can recompute every result. The package also ships a hash chain +and a hash series for games that commit to a sequence of hashes in advance. + +No runtime dependencies. Requires Node.js 14.17 or later. ## Install @@ -15,218 +21,218 @@ npm install @provableio/provable-core ## Example ```javascript -const { - Provable, - HashChain, - HashSeries, - utils, -} = require("@provableio/provable-core"); - -const config = { +const { Provable } = require("@provableio/provable-core"); + +const generator = Provable((state) => { + // called after every draw with the new state; persist it here + console.log(state); +})({ serverSeed: "your-server-seed", clientSeed: "your-client-seed", nonce: 0, - cursor: 0, -}; - -const generator = Provable((x) => { - // update database? - console.log(x) -})(config); +}); -// Generate 5 Floating point outcomes -const randomFloats = generator.floats(5); -console.log("floats:", randomFloats); +// 5 floats in [0, 1) +console.log("floats:", generator.floats(5)); -// generate 10 outcomes between 0 - 100 -const randomInts = generator.ints(10, 100, 0); -console.log("ints:", randomInts); +// 10 integers in [0, 99] +console.log("ints:", generator.ints(10, 100, 0)); ``` -The `config` object contains the following properties: -- `serverSeed` (string): The server seed used for generating random numbers. -- `clientSeed` (string): The client seed used for generating random numbers. -- `nonce` (number): The current nonce value. -- `cursor` (number): The current cursor value. +## How outcomes are generated -## Available methods +For a draw with nonce `N`: -The Provable instance has the following methods: +1. `round = 0, 1, 2, ...` Each round produces 32 bytes: + `HMAC-SHA256(key = serverSeed, message = "clientSeed:N:round")`. +2. Bytes are consumed four at a time. Each group becomes a float: + `b0/256 + b1/256^2 + b2/256^3 + b3/256^4`, a value in `[0, 1)` with 32 bits + of precision. +3. `ints(count, max, min)` maps each float to `floor(min + float * max)`, an + integer in `[min, min + max - 1]`. **`max` is the size of the range, not an + upper bound.** +4. After the draw the nonce becomes `N + 1` and the new state is emitted. -### next(salt, clientSeed) +Every draw uses the nonce as it stands and then advances it, so the outcome +recorded against nonce `N` is always reproducible from +`(serverSeed, clientSeed, N)` alone, whether the generator instance was reused +or re-created from persisted state before each call. -Returns an object containing the next `clientSeed` and `serverSeed` values based on the provided `salt` and `clientSeed` values. If `clientSeed` is not provided, it will be generated using the `md5` hashing function. +Before a seed pair is used, publish `serverHash = sha256(serverSeed)`. Reveal +`serverSeed` when the pair is rotated; players check the hash and recompute +their outcomes. -```javascript -const nextValues = generator.next("salt-value"); -console.log(nextValues); // { clientSeed: "generated-client-seed", serverSeed: "generated-server-seed" } -``` +## Provable(emit)(config) -### state() +`emit` is optional. It receives a copy of the state after every draw. -Returns the current configuration object. +`config` accepts: -```javascript -const currentConfig = generator.state(); -console.log(currentConfig); // { clientSeed: "your-client-seed", serverSeed: "your-server-seed", nonce: 0, emit: [Function: emit] } -``` +| key | default | notes | +| ------------ | ------------------- | ------------------------------------------------- | +| `serverSeed` | `sha256(uuid)` | non-empty string, kept private until rotation | +| `clientSeed` | `md5(uuid)` | non-empty string, chosen or visible to the player | +| `nonce` | `0` | non-negative safe integer | +| `cursor` | `0` | byte offset into the stream, normally `0` | +| `serverHash` | derived | always recomputed as `sha256(serverSeed)` | -### floats(count) +Integers may be passed as canonical decimal strings (`"400"`); anything else +throws. Extra keys (ids, timestamps) are carried through untouched. The config +object you pass in is never mutated. + +### state() -Generates `count` number of random floating-point numbers using the configured generator. The `count` argument is optional and defaults to `1`. +Returns a copy of the current state. ```javascript -const randomFloats = generator.floats(5); -console.log(randomFloats); // [0.123456789, 0.234567890, 0.345678901, 0.4567890123, 0.56789012345] +generator.state(); +// { serverSeed, clientSeed, serverHash, nonce, cursor } ``` -The generator's configuration is updated after generating the random numbers, and the `nonce` value is incremented. If an `emit` callback function is provided, it will be called with the updated configuration. +### floats(count = 1) -### ints(count, max, min) +Returns `count` floats in `[0, 1)` for the current nonce, then advances the +nonce and emits. -Generates `count` number of random integer numbers within the range of `min` and `max` (inclusive) using the configured generator. The `count`, `max`, and `min` arguments are optional and default to `1`, `100`, and `0`, respectively. +### ints(count, max, min = 0) + +Returns `count` integers in `[min, min + max - 1]` for the current nonce, then +advances the nonce and emits. `count` and `max` must be at least 1. ```javascript -const randomInts = generator.ints(10, 100, 0); -console.log(randomInts); // [100, 25, 23, 22, 21, 20, 19, 18, 17, 16] +generator.ints(1, 6, 1); // one die roll, 1..6 +generator.ints(1, 10001, 0); // one value 0..10000 ``` -The generator's configuration is updated after generating the random numbers, and the `nonce` value is incremented. If an `emit` callback function is provided, it will be called with the updated configuration. - ### tick() -Increments the `nonce` value and emits the updated configuration (if an `emit` callback function is provided). - -```javascript -generator.tick(); -``` +Advances the nonce without drawing, emits, and returns the new state. -## Additional Generators +### next(salt, clientSeed) -### HashSeries({ seed, salt, nonce }) +Returns the state for the next seed rotation. Nonce and cursor restart at 0. -This class is used to generate a series of hashes based on the provided `seed`, `salt`, and `nonce` values. It has the following methods: +- `serverSeed` becomes `sha256(":")`. +- `clientSeed` is the one supplied, or `md5(":")`. -- `getHash()`: Returns the current hash value. -- `next()`: Increments the `nonce` value and returns a new object with the updated `seed`, `salt`, and `nonce`. -- `peekHash()`: Returns the next hash value based on the updated `nonce` value. -- `calcHash(_seed, _salt, _nonce)`: Calculates the hash value using the provided `seed`, `salt`, and `nonce` values. -- `state()`: Returns an object containing the `seed`, `salt`, and `nonce` values. +Keep `salt` private. Because the next server seed is derived from the current +one, a player who knows the salt could predict the successor of a revealed seed. ```javascript -const hashSeries = new HashSeries({ seed: "your-seed", salt: "your-salt", nonce: 0 }); -console.log(hashSeries.getHash()); // Generated hash value -console.log(hashSeries.next()); // { seed: "your-seed", salt: "your-salt", nonce: 1 } -console.log(hashSeries.peekHash()); // Generated hash value based on nonce + 1 +const rotated = generator.next("private-salt", "player-chosen-seed"); +// { serverSeed, clientSeed, serverHash, nonce: 0, cursor: 0 } ``` -### HashChain({ seed, count, index }) - -This class is used to manage generating and iterating through a provable hash chain. It has the following methods: - -- `state()`: Returns an object containing the `count`, `seed`, and `index` values. -- `peek()`: Returns the next hash value in the chain based on the current `index`. -- `get()`: Returns the current hash value in the chain based on the current `index`. -- `next()`: Returns an object containing the next hash value in the chain, the `count`, and the updated `index`. -- `last()`: Returns the previous hash value in the chain based on the current `index`. +### Verifying an outcome ```javascript -const hashChain = new HashChain({ seed: "your-seed", count: 10 }); -console.log(hashChain.state()); // Generated state object -console.log(hashChain.peek()); // Generated next hash value in the chain -console.log(hashChain.get()); // Generated current hash value in the chain -console.log(hashChain.next()); // Generated next hash value in the chain, count, and updated index -console.log(hashChain.last()); // Generated previous hash value in the chain -``` - - -## Additional Utilities +const { Provable } = require("@provableio/provable-core"); -### defaults(state) - -This function sets default values for the `state` object and returns it. If the `state` object is missing some properties, they will be set to the default values. - -```javascript -const defaultState = defaults({}); -console.log(defaultState); // { serverSeed: sha256(), clientSeed: md5(), nonce: 0, cursor: 0, serverHash: sha256(serverSeed) } +function verify({ serverSeed, serverHash, clientSeed, nonce }) { + const generator = Provable()({ serverSeed, clientSeed, nonce }); + if (generator.state().serverHash !== serverHash) throw new Error("hash mismatch"); + return generator.ints(1, 10001, 0)[0]; // same call the game made +} ``` -### sha256(input) +## HashSeries({ seed, salt, nonce }) -This function generates a SHA-256 hash value for the provided `input` string. If no `input` is provided, it will generate a hash value for a UUID. +A series of hashes `HMAC-SHA256(key = seed, message = "salt:nonce")`. +Instances are immutable; `next()` returns the state for the following nonce. ```javascript -const hash = sha256("your-input"); -console.log(hash); // Generated SHA-256 hash value -``` - -### md5(input) +const { HashSeries } = require("@provableio/provable-core"); -This function generates an MD5 hash value for the provided `input` string. If no `input` is provided, it will generate a hash value for a UUID. - -```javascript -const hash = md5("your-input"); -console.log(hash); // Generated MD5 hash value +const series = HashSeries({ seed: "your-seed", salt: "your-salt", nonce: 0 }); +series.getHash(); // hash for nonce 0 +series.peekHash(); // hash for nonce 1 +series.next(); // { seed, salt, nonce: 1 } +HashSeries(series.next()).getHash(); // === series.peekHash() +series.calcHash(seed, salt, nonce); // hash for arbitrary inputs +series.state(); // { seed, salt, nonce } ``` -### ByteGenerator({ serverSeed, clientSeed, nonce, cursor }) +## HashChain({ seed, count = 1000000, index = 0 }) -This generator function produces a sequence of bytes based on the provided `serverSeed`, `clientSeed`, `nonce`, and `cursor` values. It uses the SHA-256 hashing function to generate bytes and yields them one by one. +Generates a chain of `count` SHA-256 hashes from `seed` and iterates it. +`chain[count - 1] = sha256(seed)` and `chain[i] = sha256(chain[i + 1])`, so +`chain[0]` is the terminating hash to publish before play, and every hash +handed out afterwards is the preimage of the one before it. A player checks +game `i` with `sha256(hash[i]) === hash[i - 1]`. -```javascript -const byteGenerator = new ByteGenerator({ serverSeed: "your-server-seed", clientSeed: "your-client-seed", nonce: 0, cursor: 0 }); -for (let byte of byteGenerator) { - console.log(byte); // Yielded bytes -} -``` +Generating the default one-million-hash chain takes about two seconds and +roughly 90 MB. Build it once at start-up and resume from `state()`. -### bytesToFloat(bytes) +```javascript +const { HashChain } = require("@provableio/provable-core"); -This function converts an array of bytes to a floating-point number. +const chain = HashChain({ seed: "your-secret-seed", count: 10 }); +chain.get(); // current hash (chain[index]) +chain.peek(); // next hash, or undefined at the end +chain.next(); // { hash, count, index } and advances; throws "chain has ended" at the end +chain.last(); // previous hash, or undefined at the start +chain.state(); // { count, seed, index } — includes the seed, persist privately -```javascript -const float = bytesToFloat([128, 64, 32, 0]); -console.log(float); // Generated float value +HashChain(chain.state()); // resumes the same chain at the same index ``` -### floatToInt(val, max, min) +The chain does not renew itself. When `next()` throws, decide how to commit to +the next chain (for example, publish its terminating hash) before using it. + +`HashChain.generateHashChain(count, seed)` returns the raw array. -This function converts a floating-point number to an integer within the range of `min` and `max` (inclusive). +## utils ```javascript -const int = floatToInt(0.5, 100, 0); -console.log(int); // Generated integer value +const { utils } = require("@provableio/provable-core"); ``` -### FloatGenerator(rng, count) - -This generator function produces a sequence of floating-point numbers based on the provided `rng` (random number generator) and `count` values. It uses the `ByteGenerator` to generate bytes and converts them to floating-point numbers using the `bytesToFloat` function. +- `sha256(input)`, `md5(input)`: hex digests. With no input, hash a random UUID. +- `defaults(state)`: normalise and validate a Provable state (see the config + table above). Returns a new object with `serverHash` derived. +- `ByteGenerator({ serverSeed, clientSeed, nonce, cursor })`: generator function + yielding the HMAC byte stream for a fixed nonce. Call it, do not `new` it. +- `FloatGenerator(rng, count)`: generator function turning four bytes at a time + from `rng` into floats. +- `floats(rng, count)`, `ints(rng, count, max, min = 0)`: array forms of the above. +- `bytesToFloat([b0, b1, b2, b3])`, `floatToInt(float, max, min = 0)`: the two + conversions described in "How outcomes are generated". +- `toInteger(value, name)`, `assertSeed(value, name)`: the validators used + throughout. ```javascript -const floatGenerator = new FloatGenerator(byteGenerator, 10); -for (let float of floatGenerator) { - console.log(float); // Yielded floating-point numbers -} +const rng = utils.ByteGenerator({ serverSeed, clientSeed, nonce: 0, cursor: 0 }); +utils.floats(rng, 2); // two floats from the start of the stream +utils.ints(rng, 3, 6, 1); // three dice rolls from the bytes that follow ``` -### floats(rng, count) +## Versioning and releases -This function generates an array of `count` number of random floating-point numbers using the provided `rng` (random number generator). +The package follows [semver](https://semver.org). Because its purpose is +reproducibility, the rules are stricter than usual: -```javascript -const randomFloats = floats(byteGenerator, 5); -console.log(randomFloats); // Generated array of floating-point numbers -``` +- **Major**: any change to the bytes, floats or integers produced for a given + input, any tightening of accepted input, or a higher Node.js requirement. +- **Minor**: new functions or options that leave existing outputs untouched. +- **Patch**: documentation, tests, tooling, internal refactors with identical + outputs. -### ints(rng, count, max, min) +Every change goes under `Unreleased` in [CHANGELOG.md](CHANGELOG.md) with the +pull request that made it. -This function generates an array of `count` number of random integer numbers within the range of `min` and `max` (inclusive) using the provided `rng` (random number generator). +To release, from an up-to-date `master`: -```javascript -const randomInts = ints(byteGenerator, 10, 50, -25); -console.log(randomInts); // Generated array of integer numbers ``` +npm version patch # or minor / major +``` + +That runs the tests, moves `Unreleased` under the new version with today's +date, commits, tags `vX.Y.Z` and pushes. The +[publish workflow](.github/workflows/publish.yml) then verifies the tag +against `package.json`, publishes to npm with provenance and creates the +GitHub release from the changelog entry. It needs an `NPM_TOKEN` repository +secret; nothing is published from a laptop. ## License -This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. +MIT. See [LICENSE](LICENSE). diff --git a/hashChain.js b/hashChain.js index a6ec5db..974c5be 100644 --- a/hashChain.js +++ b/hashChain.js @@ -1,28 +1,37 @@ const assert = require("assert"); -const { v4: uuid } = require("uuid"); -const { sha256 } = require("./utils"); +const { sha256, toInteger, assertSeed, randomUUID } = require("./utils"); -// generate a provable hash chain count to -1 -function generateHashChain(count, seed) { - assert(seed, "requires seed"); - assert(seed, "requires count"); +function chainLength(count) { + count = toInteger(count, "count"); + assert(count >= 1, "count must be 1 or more"); + return count; +} - var result = Array(count); +// Builds the chain in reverse: result[count - 1] = sha256(seed) and +// result[i] = sha256(result[i + 1]). result[0] is the terminating hash to +// publish before play; every later hash is the preimage of the one before it, +// so a player verifies hash i with sha256(hash[i]) === hash[i - 1]. +function generateHashChain(count, seed) { + count = chainLength(count); + assertSeed(seed, "seed"); - // generate chain in revese order - for (var i = count - 1; i >= 0; i--) { + const result = Array(count); + for (let i = count - 1; i >= 0; i--) { seed = sha256(seed); result[i] = seed; } - return result; } -// manages generating and itteratng through a provable hashchain -// the last hash of the previous chain will be used to generate the next. -function HashChain({ seed = uuid(), count = 1000000, index = 0 }) { - assert(count >= 1, "requires count"); - assert(seed, "requires seed"); +// Generates and iterates a provable hash chain. `state()` is enough to resume +// the same chain later; it includes the seed, so persist it privately. +// The chain does not renew itself: when `next()` reports it has ended, the +// caller decides how to commit to and start the following chain. +function HashChain({ seed = randomUUID(), count = 1000000, index = 0 } = {}) { + count = chainLength(count); + index = toInteger(index, "index"); + assert(index >= 0 && index < count, "index must be within the chain"); + assertSeed(seed, "seed"); const chain = generateHashChain(count, seed); @@ -30,6 +39,7 @@ function HashChain({ seed = uuid(), count = 1000000, index = 0 }) { return { count, seed, index }; } + // Next hash, or undefined at the end of the chain. function peek() { return chain[index + 1]; } @@ -41,26 +51,17 @@ function HashChain({ seed = uuid(), count = 1000000, index = 0 }) { function next() { const hash = peek(); assert(hash, "chain has ended"); - - // increment index and return hash - return { - hash, - count, - index: ++index, - }; + index += 1; + return { hash, count, index }; } + // Previous hash, or undefined at the start of the chain. function last() { return chain[index - 1]; } - return { - state, - peek, - get, - next, - last, - }; + return { state, peek, get, next, last }; } module.exports = HashChain; +module.exports.generateHashChain = generateHashChain; diff --git a/hashSeries.js b/hashSeries.js index 8112683..191fe26 100644 --- a/hashSeries.js +++ b/hashSeries.js @@ -1,7 +1,16 @@ -const { v4: uuid } = require("uuid"); +const assert = require("assert"); const crypto = require("crypto"); +const { toInteger, assertSeed, randomUUID } = require("./utils"); + +// A series of hashes HMAC-SHA256(key = seed, message = `salt:nonce`). +// Instances are immutable: `next()` returns the state for the following +// nonce, which you pass to a new HashSeries. +function HashSeries({ seed = randomUUID(), salt = randomUUID(), nonce = 0 } = {}) { + assertSeed(seed, "seed"); + assertSeed(salt, "salt"); + nonce = toInteger(nonce, "nonce"); + assert(nonce >= 0, "nonce must be 0 or more"); -function HashSeries({ seed = uuid(), salt = uuid(), nonce = 0 } = {}) { function calcHash(_seed = seed, _salt = salt, _nonce = nonce) { return crypto .createHmac("sha256", _seed) @@ -15,23 +24,13 @@ function HashSeries({ seed = uuid(), salt = uuid(), nonce = 0 } = {}) { return { seed, salt, nonce }; } function next() { - return { - seed, - salt, - nonce: nonce + 1, - }; + return { seed, salt, nonce: nonce + 1 }; } function peekHash() { return calcHash(seed, salt, nonce + 1); } - return { - getHash, - next, - peekHash, - calcHash, - state, - }; + return { getHash, next, peekHash, calcHash, state }; } module.exports = HashSeries; diff --git a/package.json b/package.json index 403efd5..254d27e 100644 --- a/package.json +++ b/package.json @@ -2,27 +2,37 @@ "name": "@provableio/provable-core", "packageManager": "yarn@3.3.0", "version": "1.0.1", + "description": "Provably fair random number generation: HMAC-SHA256 byte streams, hash chains and hash series.", "main": "index.js", - "dependencies": { - "lodash": "^4.17.21", - "uuid": "^9.0.1" + "files": [ + "index.js", + "provable.js", + "hashChain.js", + "hashSeries.js", + "utils.js" + ], + "engines": { + "node": ">=14.17.0" }, "devDependencies": { "tape": "^5.0.1" }, "scripts": { "test": "node test.js", - "release": "yarn test && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish" + "preversion": "node scripts/release-changelog.js check && npm test", + "version": "node scripts/release-changelog.js release && git add CHANGELOG.md", + "postversion": "git push --follow-tags" }, "repository": { "type": "git", - "url": "git@github.com:provableio/provable-core.git" + "url": "git+https://github.com/provableio/provable-core.git" }, "keywords": [ "rng", "provable", + "provably-fair", "random", - "reproducable", + "reproducible", "fair", "hash", "chain", @@ -30,5 +40,8 @@ ], "author": "tacyarg ", "license": "MIT", - "homepage": "https://provable.io" + "homepage": "https://provable.io", + "bugs": { + "url": "https://github.com/provableio/provable-core/issues" + } } diff --git a/provable.js b/provable.js index 661df5c..d46d03a 100644 --- a/provable.js +++ b/provable.js @@ -3,56 +3,70 @@ const { md5, sha256, ByteGenerator, - // bytesToFloat, floats, ints, defaults, + toInteger, + assertSeed, } = require("./utils"); +const MAX_NONCE = Number.MAX_SAFE_INTEGER; + +function positiveInteger(value, name) { + const result = toInteger(value, name); + assert(result >= 1, `${name} must be 1 or more`); + return result; +} + +// Provable(emit)(config) -> generator +// +// Every draw hashes with the nonce as it currently stands, then advances the +// nonce and emits the new state. A fresh byte stream is opened for each draw, +// so a long-lived instance yields exactly the same results as re-creating one +// from the emitted state before every call. Outcome for nonce N is therefore +// always reproducible from (serverSeed, clientSeed, N) alone. module.exports = (emit = (x) => x) => (config) => { config = defaults(config); - const generator = ByteGenerator(config); + + function draw(fn) { + assert(config.nonce + 1 < MAX_NONCE, "max nonce, rotate seed."); + const result = fn(ByteGenerator(config)); + config.nonce += 1; + emit({ ...config }); + return result; + } return { + // Derives the seeds for the next rotation. The new server seed depends + // on the current one and `salt`; keep `salt` private so a revealed seed + // does not disclose its successor. Nonce and cursor restart at 0. next(salt, clientSeed) { + assertSeed(salt, "salt"); + if (clientSeed) assertSeed(clientSeed, "clientSeed"); return defaults({ clientSeed: clientSeed || md5(`${config.clientSeed}:${salt}`), serverSeed: sha256(`${config.serverSeed}:${salt}`), }); }, state() { - return config; + return { ...config }; }, floats(count = 1) { - ++config.nonce; - assert( - Number.MAX_SAFE_INTEGER !== config.nonce, - "max nonce, rotate seed." - ); - const result = floats(generator, count); - emit(config); - return result; + count = positiveInteger(count, "count"); + return draw((rng) => floats(rng, count)); }, - ints(count, max, min) { - ++config.nonce; - assert( - Number.MAX_SAFE_INTEGER !== config.nonce, - "max nonce, rotate seed." - ); - const result = ints(generator, count, max, min); - emit(config); - return result; + // `count` integers in [min, min + max - 1]. `max` is the range size. + ints(count, max, min = 0) { + count = positiveInteger(count, "count"); + max = positiveInteger(max, "max"); + min = toInteger(min, "min"); + return draw((rng) => ints(rng, count, max, min)); }, tick() { - ++config.nonce; - assert( - Number.MAX_SAFE_INTEGER !== config.nonce, - "max nonce, rotate seed." - ); - emit(config); - return config; + draw(() => undefined); + return { ...config }; }, }; }; diff --git a/scripts/release-changelog.js b/scripts/release-changelog.js new file mode 100644 index 0000000..ba1de9b --- /dev/null +++ b/scripts/release-changelog.js @@ -0,0 +1,76 @@ +#!/usr/bin/env node +// Keeps CHANGELOG.md in step with package.json. +// +// release-changelog.js check fail unless "Unreleased" has content +// release-changelog.js release move "Unreleased" under the current +// package.json version, dated today +// release-changelog.js notes print the section for (used by +// the publish workflow for the GitHub +// release body) +// +// `npm version` runs the release form through the "version" script, after it +// has bumped package.json and before it commits. +const fs = require("fs"); +const path = require("path"); + +const REPO = "https://github.com/provableio/provable-core"; +const file = path.join(__dirname, "..", "CHANGELOG.md"); +const [mode, arg] = process.argv.slice(2); + +const changelog = fs.readFileSync(file, "utf8"); +const heading = /^## \[([^\]]+)\](?: - (\d{4}-\d{2}-\d{2}))?$/gm; + +function sections() { + const result = []; + let match; + while ((match = heading.exec(changelog))) { + result.push({ name: match[1], start: match.index, bodyStart: match.index + match[0].length }); + } + result.forEach((s, i) => { + const end = i + 1 < result.length ? result[i + 1].start : changelog.search(/^\[Unreleased\]:/m); + s.body = changelog.slice(s.bodyStart, end === -1 ? undefined : end).trim(); + }); + return result; +} + +function fail(message) { + console.error(`release-changelog: ${message}`); + process.exit(1); +} + +function unreleased() { + const section = sections()[0]; + if (!section || section.name !== "Unreleased") fail("CHANGELOG.md must start with an [Unreleased] section"); + if (!section.body) fail("the [Unreleased] section is empty; describe the change before releasing"); + return section; +} + +if (mode === "check") { + unreleased(); +} else if (mode === "notes") { + const version = (arg || "").replace(/^v/, ""); + const section = sections().find((s) => s.name === version); + if (!section) fail(`no CHANGELOG section for ${version}`); + process.stdout.write(section.body + "\n"); +} else if (mode === "release") { + const { version } = require(path.join(__dirname, "..", "package.json")); + unreleased(); + const all = sections(); + if (all.some((s) => s.name === version)) fail(`CHANGELOG.md already has a ${version} section`); + const previous = all[1] && all[1].name; + const today = new Date().toISOString().slice(0, 10); + + let next = changelog.replace( + /^## \[Unreleased\]$/m, + `## [Unreleased]\n\n## [${version}] - ${today}` + ); + next = next.replace( + /^\[Unreleased\]: .*$/m, + `[Unreleased]: ${REPO}/compare/v${version}...HEAD\n[${version}]: ` + + (previous ? `${REPO}/compare/v${previous}...v${version}` : `${REPO}/releases/tag/v${version}`) + ); + fs.writeFileSync(file, next); + console.log(`CHANGELOG.md: released ${version} (${today})`); +} else { + fail("usage: release-changelog.js check | release | notes "); +} diff --git a/test.js b/test.js index abeeb91..1919f69 100644 --- a/test.js +++ b/test.js @@ -1,67 +1,217 @@ const test = require("tape"); -const { Provable, HashChain, HashSeries } = require("./index"); +const { Provable, HashChain, HashSeries, utils } = require("./index"); -console.log({ - Provable, - HashChain, - HashSeries, -}); - -let config = { +// Fixed vectors. These pin the byte stream, so any change here is a breaking +// change for every outcome ever produced with this library. +const base = { clientSeed: "bba625387fb64d772ff7da0ed6e71b16", - created: 1597620193906, - cursor: 0, - id: "d2026ad4-480c-4d30-b963-9e3a59c5e22d", + serverSeed: "aa47ddbf021afd16d64756e7f32b6cea2feebdfddc8fd57f1522641fdf372375", + serverHash: "158e995ed494f58c06d93e22f71554cf2126614174fde336aedf9a457dbf16f4", nonce: 400, - serverHash: - "158e995ed494f58c06d93e22f71554cf2126614174fde336aedf9a457dbf16f4", - serverSeed: - "aa47ddbf021afd16d64756e7f32b6cea2feebdfddc8fd57f1522641fdf372375", - updated: 1598719893207, + cursor: 0, }; +// ints(1, 10001, 0) for nonces 400, 401, 402, ... const answers = [ 3842, 9426, 5011, 9503, 1378, 9933, 9048, 9339, 4585, 7847, 2357, 9807, 8488, 5542, ]; + +test("exports", (t) => { + t.equal(typeof Provable, "function"); + t.equal(typeof HashChain, "function"); + t.equal(typeof HashSeries, "function"); + t.equal(typeof utils.ByteGenerator, "function"); + t.end(); +}); + +test("byte stream vectors", (t) => { + const gen = utils.ByteGenerator(base); + const bytes = Array.from({ length: 8 }, () => gen.next().value); + t.deepEqual(bytes, [98, 92, 27, 175, 16, 51, 213, 32]); + t.equal(utils.sha256(base.serverSeed), base.serverHash); + t.end(); +}); + test("provable", (t) => { - let provable; - t.test("init", (t) => { - provable = Provable((x) => console.log(x))(config); - t.ok(provable); + t.test("ints, fresh instance per nonce", (t) => { + let config = { ...base }; + answers.forEach((answer, i) => { + const provable = Provable((x) => (config = x))(config); + const [result] = provable.ints(1, 10001, 0); + t.equal(result, answer, `nonce ${base.nonce + i}`); + t.equal(config.nonce, base.nonce + i + 1, "emitted nonce advanced"); + }); t.end(); }); - t.test("floats", (t) => { - answers.forEach((answer) => { - let provable = Provable((x) => (config = x))(config); - const [result] = provable.ints(1, 10001, 0); - // const [result] = provable.floats(1) - // t.equal(Math.round(result * 10000),answer) - t.equal(result, answer); - console.log(result); + + t.test("ints and floats from one long-lived instance match replay", (t) => { + const emitted = []; + const provable = Provable((x) => emitted.push(x))(base); + const live = answers.map(() => provable.ints(1, 10001, 0)[0]); + t.deepEqual(live, answers, "long-lived instance advances the nonce"); + + emitted.forEach((state, i) => { + t.equal(state.nonce, base.nonce + i + 1, "emitted state describes the next draw"); + // the draw that produced live[i] used the nonce before increment + const replay = Provable()({ ...state, nonce: state.nonce - 1 }).ints(1, 10001, 0)[0]; + t.equal(replay, live[i], `replay of draw ${i} from emitted state`); }); t.end(); }); + + t.test("floats vectors", (t) => { + t.deepEqual(Provable()(base).floats(4), [ + 0.3842179586645216, 0.06329090148210526, 0.9394600219093263, + 0.015737906098365784, + ]); + t.deepEqual(Provable()({ ...base, nonce: 401 }).floats(1), [ + 0.9425638655666262, + ]); + t.deepEqual(Provable()(base).floats(), Provable()(base).floats(1), "count defaults to 1"); + t.end(); + }); + + t.test("ints range is [min, min + max - 1]", (t) => { + t.deepEqual(Provable()(base).ints(3, 6, 1), [3, 1, 6]); + const many = Provable()(base).ints(500, 6, 1); + t.ok(many.every((n) => n >= 1 && n <= 6), "within range"); + t.ok(many.includes(1) && many.includes(6), "hits both ends"); + t.deepEqual(Provable()(base).ints(1, 10001), Provable()(base).ints(1, 10001, 0), "min defaults to 0"); + t.deepEqual(Provable()(base).ints(2, 10, -5).map((n) => n >= -5 && n <= 4), [true, true]); + t.equal(utils.floatToInt(0.99999999, 6, 1), 6); + t.equal(utils.floatToInt(0, 6, 1), 1); + t.end(); + }); + + t.test("cursor offsets the byte stream", (t) => { + t.deepEqual(Provable()({ ...base, cursor: 4 }).ints(1, 10001, 0), [632]); + t.deepEqual(Provable()({ ...base, cursor: 32 }).ints(1, 10001, 0), Provable()(base).ints(9, 10001, 0).slice(8)); + t.end(); + }); + + t.test("integer-like strings are accepted, anything else is rejected", (t) => { + t.deepEqual(Provable()({ ...base, nonce: "400" }).ints(1, 10001, 0), [answers[0]]); + t.deepEqual(Provable()(base).ints("1", "10001", "0"), [answers[0]]); + t.equal(Provable()({ ...base, nonce: null }).state().nonce, 0, "null means missing"); + for (const nonce of ["abc", "1e3", 1.5, -1, NaN, {}, [], Infinity, true]) { + t.throws(() => Provable()({ ...base, nonce }), /nonce/, `nonce ${String(nonce)}`); + } + t.throws(() => Provable()({ ...base, cursor: -1 }), /cursor/); + t.throws(() => Provable()({ ...base, serverSeed: 123 }), /serverSeed/); + t.throws(() => Provable()({ ...base, clientSeed: "" }), /clientSeed/); + t.throws(() => Provable()({ ...base, clientSeed: {} }), /clientSeed/); + t.throws(() => Provable()(base).ints(), /count/); + t.throws(() => Provable()(base).ints(0, 10), /count/); + t.throws(() => Provable()(base).ints(1), /max/); + t.throws(() => Provable()(base).ints(1, 0), /max/); + t.throws(() => Provable()(base).ints(1, 10, 1.5), /min/); + t.throws(() => Provable()(base).floats(0), /count/); + t.throws(() => Provable()(base).floats(2.5), /count/); + t.end(); + }); + + t.test("serverHash is always derived from serverSeed", (t) => { + const state = Provable()({ ...base, serverHash: "bogus" }).state(); + t.equal(state.serverHash, base.serverHash); + t.equal(Provable()({ serverSeed: "s" }).state().serverHash, utils.sha256("s")); + t.end(); + }); + + t.test("defaults fill missing seeds and carry unknown keys", (t) => { + const state = Provable()({ id: "abc" }).state(); + t.equal(state.id, "abc"); + t.equal(state.nonce, 0); + t.equal(state.cursor, 0); + t.equal(state.serverSeed.length, 64); + t.equal(state.clientSeed.length, 32); + t.equal(state.serverHash, utils.sha256(state.serverSeed)); + t.notEqual(Provable()({}).state().serverSeed, Provable()({}).state().serverSeed); + t.end(); + }); + + t.test("input is not mutated and state() is a copy", (t) => { + const input = { ...base }; + const provable = Provable()(input); + provable.tick(); + t.equal(input.nonce, base.nonce, "input untouched"); + const snapshot = provable.state(); + snapshot.nonce = 0; + t.equal(provable.state().nonce, base.nonce + 1, "state() copy"); + t.end(); + }); + + t.test("tick advances the nonce and emits", (t) => { + let emitted; + const provable = Provable((x) => (emitted = x))(base); + const state = provable.tick(); + t.equal(state.nonce, base.nonce + 1); + t.equal(emitted.nonce, base.nonce + 1); + t.deepEqual(provable.floats(1), Provable()({ ...base, nonce: base.nonce + 1 }).floats(1)); + t.end(); + }); + + t.test("max nonce", (t) => { + const provable = Provable()({ ...base, nonce: Number.MAX_SAFE_INTEGER - 1 }); + t.throws(() => provable.floats(), /max nonce/); + t.equal(provable.state().nonce, Number.MAX_SAFE_INTEGER - 1, "state unchanged on failure"); + t.doesNotThrow(() => Provable()({ ...base, nonce: Number.MAX_SAFE_INTEGER - 2 }).floats()); + t.end(); + }); + + t.test("next derives the rotation deterministically", (t) => { + const next = Provable()(base).next("salt-1"); + t.deepEqual(next, { + clientSeed: "6d342ac2abade213ef60ecbb7eee012b", + serverSeed: "80ce19c2df995ac44e2882e13df00d0be5e4910968c6de323d09f60c0aafcf04", + serverHash: "0108879296bb4617d3b101e3fc1d71eae17bba095c962d22645e4f93012c72f0", + nonce: 0, + cursor: 0, + }); + t.equal(next.serverSeed, utils.sha256(`${base.serverSeed}:salt-1`)); + t.equal(next.clientSeed, utils.md5(`${base.clientSeed}:salt-1`)); + const custom = Provable()(base).next("salt-1", "myclient"); + t.equal(custom.clientSeed, "myclient"); + t.equal(custom.serverSeed, next.serverSeed); + t.equal(Provable()(base).next("salt-1", "").clientSeed, next.clientSeed, "empty client seed falls back"); + t.throws(() => Provable()(base).next(), /salt/); + t.throws(() => Provable()(base).next("salt-1", 5), /clientSeed/); + t.end(); + }); }); -test("utils", (t) => { - t.test("hashseries", (t) => { - const series = HashSeries(); - const hash = series.getHash(); - t.ok(hash); - const next = series.peekHash(); - const nextSeries = HashSeries(series.next()); - t.equal(next, nextSeries.getHash()); + +test("hashseries", (t) => { + t.test("vectors", (t) => { + const series = HashSeries({ seed: "seed", salt: "salt", nonce: 0 }); + t.equal(series.getHash(), "18e6774026b67fdf7687651023db21e4aa7259889871fbad4daed033699a5651"); + t.equal(series.peekHash(), "593210434386ec6d9cd6cef9bab11c57abe76ce0b46ff9cdde19051836bfe1d1"); + t.equal(series.calcHash("seed", "salt", 1), series.peekHash()); t.end(); }); - t.test("hashseries", (t) => { + + t.test("next returns the following state without mutating", (t) => { const series = HashSeries(); - const result = series.state(); - t.ok(result); + const peek = series.peekHash(); + const following = HashSeries(series.next()); + t.equal(following.getHash(), peek); + t.equal(series.state().nonce, 0); + t.equal(following.state().nonce, 1); + t.deepEqual(Object.keys(series.state()).sort(), ["nonce", "salt", "seed"]); + t.end(); + }); + + t.test("validation", (t) => { + t.doesNotThrow(() => HashSeries()); + t.equal(HashSeries({ seed: "a", salt: "b", nonce: "3" }).state().nonce, 3); + t.throws(() => HashSeries({ seed: "" }), /seed/); + t.throws(() => HashSeries({ salt: 1 }), /salt/); + t.throws(() => HashSeries({ nonce: -1 }), /nonce/); + t.throws(() => HashSeries({ nonce: 1.5 }), /nonce/); t.end(); }); }); -// hash chain seed "test" should match these +// hash chain with seed "test" and count 10 const chainHashes = [ "bc89c6f72947bcd2f783d342a46cafcfccfcc2e7884a34f1cfe8f55bad2d200e", "d36e4f43c5243135e038611e679adee4bf197290e84e0203727cb6761929e072", @@ -76,28 +226,58 @@ const chainHashes = [ ]; test("hashchain", (t) => { - t.test("init", (t) => { + t.test("vectors and iteration", (t) => { const chain = HashChain({ count: 10, seed: "test", index: 0 }); - - // tests - const hash = chain.get(); - t.ok(hash); - t.equal(hash, chainHashes[0]); - - const peek = chain.peek(); - t.notEqual(hash, peek); - t.equal(peek, chainHashes[1]); - + t.equal(chain.get(), chainHashes[0]); + t.equal(chain.peek(), chainHashes[1]); + t.equal(chain.last(), undefined, "nothing before the first hash"); const next = chain.next(); - t.equal(next.hash, peek); + t.deepEqual(next, { hash: chainHashes[1], count: 10, index: 1 }); + t.equal(chain.get(), chainHashes[1]); + t.equal(chain.last(), chainHashes[0]); + t.deepEqual(chain.state(), { count: 10, seed: "test", index: 1 }); + t.end(); + }); - const last = chain.last(); - t.equal(hash, last); + t.test("every hash is the preimage of the one before it", (t) => { + t.equal(chainHashes[9], utils.sha256("test")); + for (let i = 1; i < chainHashes.length; i++) { + t.equal(utils.sha256(chainHashes[i]), chainHashes[i - 1], `hash ${i}`); + } + t.deepEqual(HashChain.generateHashChain(10, "test"), chainHashes); + t.end(); + }); - const state = chain.state(); - console.log("chain state:", state); - t.ok(state); + t.test("resumes from state", (t) => { + const chain = HashChain({ count: 10, seed: "test" }); + chain.next(); + chain.next(); + const resumed = HashChain(chain.state()); + t.equal(resumed.get(), chain.get()); + t.equal(resumed.peek(), chain.peek()); + t.equal(resumed.next().hash, chainHashes[3]); + t.end(); + }); + + t.test("end of chain", (t) => { + const chain = HashChain({ count: 3, seed: "x", index: 1 }); + t.equal(chain.next().index, 2); + t.equal(chain.peek(), undefined); + t.throws(() => chain.next(), /chain has ended/); + t.equal(chain.state().index, 2, "index unchanged after failure"); + t.equal(chain.get(), utils.sha256("x")); + t.end(); + }); + t.test("validation", (t) => { + t.doesNotThrow(() => HashChain({ count: 1 })); + t.equal(HashChain({ count: "5", index: "2", seed: "x" }).state().index, 2); + t.throws(() => HashChain({ seed: "x", count: 0 }), /count/); + t.throws(() => HashChain({ seed: "x", count: 2.5 }), /count/); + t.throws(() => HashChain({ seed: "x", count: 3, index: 3 }), /index/); + t.throws(() => HashChain({ seed: "x", count: 3, index: -1 }), /index/); + t.throws(() => HashChain({ seed: "", count: 3 }), /seed/); + t.throws(() => HashChain({ seed: 5, count: 3 }), /seed/); t.end(); }); }); diff --git a/utils.js b/utils.js index b457106..e3921ff 100644 --- a/utils.js +++ b/utils.js @@ -1,45 +1,74 @@ -const { v4: uuid } = require("uuid"); -const lodash = require("lodash"); +const assert = require("assert"); const crypto = require("crypto"); -function defaults(state = {}) { - lodash.defaults(state, { - serverSeed: sha256(), - clientSeed: md5(), - nonce: 0, - cursor: 0, - }); - state.serverHash = state.serverHash || sha256(state.serverSeed); - return state; +const BYTES_PER_ROUND = 32; // one HMAC-SHA256 digest +const BYTES_PER_FLOAT = 4; + +function randomUUID() { + return crypto.randomUUID(); +} + +function sha256(input = randomUUID()) { + return crypto.createHash("sha256").update(input).digest("hex"); +} + +function md5(input = randomUUID()) { + return crypto.createHash("md5").update(input).digest("hex"); +} + +// Accepts a safe integer, or its canonical decimal string form because values +// read back from a database or a query string often arrive as strings. +// Anything else (floats, NaN, "1e3", objects) throws. +function toInteger(value, name = "value") { + if (typeof value === "string" && /^-?\d+$/.test(value)) value = Number(value); + assert(Number.isSafeInteger(value), `${name} must be a safe integer`); + return value; } -function sha256(input = uuid()) { - const hash = crypto.createHash("sha256"); - hash.update(input); - return hash.digest("hex"); +function assertSeed(value, name = "seed") { + assert( + typeof value === "string" && value.length > 0, + `${name} must be a non-empty string` + ); } -function md5(input = uuid()) { - const hash = crypto.createHash("md5"); - hash.update(input); - return hash.digest("hex"); + +// Normalises and validates a provable state. Returns a new object; the input +// is never mutated. Unknown keys (ids, timestamps) are carried through. +// `serverHash` is always derived from `serverSeed`: a supplied value is never +// trusted, so the commitment can not drift from the seed it commits to. +function defaults(state = {}) { + assert(state && typeof state === "object", "state must be an object"); + + const result = { + ...state, + serverSeed: state.serverSeed ?? sha256(), + clientSeed: state.clientSeed ?? md5(), + nonce: toInteger(state.nonce ?? 0, "nonce"), + cursor: toInteger(state.cursor ?? 0, "cursor"), + }; + + assertSeed(result.serverSeed, "serverSeed"); + assertSeed(result.clientSeed, "clientSeed"); + assert(result.nonce >= 0, "nonce must be 0 or more"); + assert(result.cursor >= 0, "cursor must be 0 or more"); + + result.serverHash = sha256(result.serverSeed); + return result; } -// Random number generation based on following inputs: serverSeed, clientSeed, nonce and cursor -function* ByteGenerator({ serverSeed, clientSeed, nonce, cursor }) { - // Setup curser variables - let currentRound = Math.floor(cursor / 32); - let currentRoundCursor = cursor; - currentRoundCursor -= currentRound * 32; +// Yields bytes from HMAC-SHA256(key = serverSeed, message = `clientSeed:nonce:round`). +// Each round yields 32 bytes; `cursor` is the byte offset to start from. +// The nonce is fixed for the life of the generator: open a new one per draw. +function* ByteGenerator({ serverSeed, clientSeed, nonce, cursor = 0 }) { + let currentRound = Math.floor(cursor / BYTES_PER_ROUND); + let currentRoundCursor = cursor - currentRound * BYTES_PER_ROUND; - // Generate outputs until cursor requirement fullfilled while (true) { - // HMAC function used to output provided inputs into bytes const hmac = crypto.createHmac("sha256", serverSeed); hmac.update(`${clientSeed}:${nonce}:${currentRound}`); const buffer = hmac.digest(); - // Update curser for next iteration of loop - while (currentRoundCursor < 32) { + while (currentRoundCursor < BYTES_PER_ROUND) { yield Number(buffer[currentRoundCursor]); currentRoundCursor += 1; } @@ -48,23 +77,25 @@ function* ByteGenerator({ serverSeed, clientSeed, nonce, cursor }) { } } +// Four bytes become a float in [0, 1) with 32 bits of precision: +// b0/256 + b1/256^2 + b2/256^3 + b3/256^4 function bytesToFloat(bytes) { return bytes.reduce((result, value, i) => { const divider = 256 ** (i + 1); - const partialResult = value / divider; - return result + partialResult; + return result + value / divider; }, 0); } +// Maps a float in [0, 1) onto the `max` integers starting at `min`, i.e. the +// range [min, min + max - 1]. `max` is a range size, not an upper bound. function floatToInt(val, max, min = 0) { return Math.floor(min + val * max); } -// Convert the hash output from the rng byteGenerator to floats function* FloatGenerator(rng, count) { for (let i = 0; i < count; i++) { const bytes = []; - for (let j = 0; j < 4; j++) { + for (let j = 0; j < BYTES_PER_FLOAT; j++) { bytes.push(rng.next().value); } yield bytesToFloat(bytes); @@ -75,7 +106,7 @@ function floats(rng, count) { return [...FloatGenerator(rng, count)]; } -function ints(rng, count, max, min) { +function ints(rng, count, max, min = 0) { const result = []; const gen = FloatGenerator(rng, count); for (let i = 0; i < count; i++) { @@ -94,4 +125,7 @@ module.exports = { FloatGenerator, floatToInt, defaults, + toInteger, + assertSeed, + randomUUID, }; diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..de3c2ee --- /dev/null +++ b/yarn.lock @@ -0,0 +1,1447 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 6 + cacheKey: 8 + +"@ljharb/now@npm:^1.0.1": + version: 1.0.1 + resolution: "@ljharb/now@npm:1.0.1" + dependencies: + call-bind-apply-helpers: ^1.0.2 + call-bound: ^1.0.4 + get-intrinsic: ^1.3.0 + checksum: 922896dee6766007a9e1d82c18f0d1c40f7b709b764f617f5e8526fbf2341c8a16b030a18636f7e699710c540c8e5005ffc1ca5d319861973b43353f73be17a5 + languageName: node + linkType: hard + +"@ljharb/resumer@npm:^0.1.3": + version: 0.1.3 + resolution: "@ljharb/resumer@npm:0.1.3" + dependencies: + "@ljharb/through": ^2.3.13 + call-bind: ^1.0.7 + checksum: be3a3de2a0899a32e412426e0525f44b7be2b759d88d17fbe4291e3cfedd5ae14ec6aea9826d9ef033016db2d0697a022b2a75192de89704215ebb2a8678582b + languageName: node + linkType: hard + +"@ljharb/through@npm:^2.3.13, @ljharb/through@npm:^2.3.14": + version: 2.3.14 + resolution: "@ljharb/through@npm:2.3.14" + dependencies: + call-bind: ^1.0.8 + checksum: 70323a9d7a0d43a6867651770d2b965b6d774480eef0409454531ced499b625d16f7227886666bb9f52785c65c386686b9337f7f5c3e1182ce0d25bc4c81bc5f + languageName: node + linkType: hard + +"@provableio/provable-core@workspace:.": + version: 0.0.0-use.local + resolution: "@provableio/provable-core@workspace:." + dependencies: + tape: ^5.0.1 + languageName: unknown + linkType: soft + +"array-buffer-byte-length@npm:^1.0.0, array-buffer-byte-length@npm:^1.0.1, array-buffer-byte-length@npm:^1.0.2": + version: 1.0.2 + resolution: "array-buffer-byte-length@npm:1.0.2" + dependencies: + call-bound: ^1.0.3 + is-array-buffer: ^3.0.5 + checksum: 0ae3786195c3211b423e5be8dd93357870e6fb66357d81da968c2c39ef43583ef6eece1f9cb1caccdae4806739c65dea832b44b8593414313cd76a89795fca63 + languageName: node + linkType: hard + +"array.prototype.every@npm:^1.1.7": + version: 1.1.7 + resolution: "array.prototype.every@npm:1.1.7" + dependencies: + call-bound: ^1.0.2 + define-properties: ^1.2.1 + es-abstract: ^1.23.5 + es-object-atoms: ^1.0.0 + is-string: ^1.1.0 + checksum: 525ff981177a72afb140009e7cb29d24ece1416fbcb5396d0c40b58013f1adb7665b1e1b590a090b42716c350d9ecd64fb54992e2b58888cda9a0a89f4089030 + languageName: node + linkType: hard + +"array.prototype.flatmap@npm:^1.3.3": + version: 1.3.3 + resolution: "array.prototype.flatmap@npm:1.3.3" + dependencies: + call-bind: ^1.0.8 + define-properties: ^1.2.1 + es-abstract: ^1.23.5 + es-shim-unscopables: ^1.0.2 + checksum: 11b4de09b1cf008be6031bb507d997ad6f1892e57dc9153583de6ebca0f74ea403fffe0f203461d359de05048d609f3f480d9b46fed4099652d8b62cc972f284 + languageName: node + linkType: hard + +"arraybuffer.prototype.slice@npm:^1.0.4": + version: 1.0.4 + resolution: "arraybuffer.prototype.slice@npm:1.0.4" + dependencies: + array-buffer-byte-length: ^1.0.1 + call-bind: ^1.0.8 + define-properties: ^1.2.1 + es-abstract: ^1.23.5 + es-errors: ^1.3.0 + get-intrinsic: ^1.2.6 + is-array-buffer: ^3.0.4 + checksum: b1d1fd20be4e972a3779b1569226f6740170dca10f07aa4421d42cefeec61391e79c557cda8e771f5baefe47d878178cd4438f60916ce831813c08132bced765 + languageName: node + linkType: hard + +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 9102e246d1ed9b37ac36f57f0a6ca55226876553251a31fc80677e71471f463a54c872dc78d5d7f80740c8ba624395cccbe8b60f7b690c4418f487d8e9fd1106 + languageName: node + linkType: hard + +"async-generator-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-generator-function@npm:1.0.0" + checksum: 74a71a4a2dd7afd06ebb612f6d612c7f4766a351bedffde466023bf6dae629e46b0d2cd38786239e0fbf245de0c7df76035465e16d1213774a0efb22fec0d713 + languageName: node + linkType: hard + +"available-typed-arrays@npm:^1.0.7": + version: 1.0.7 + resolution: "available-typed-arrays@npm:1.0.7" + dependencies: + possible-typed-array-names: ^1.0.0 + checksum: 1aa3ffbfe6578276996de660848b6e95669d9a95ad149e3dd0c0cda77db6ee1dbd9d1dd723b65b6d277b882dd0c4b91a654ae9d3cf9e1254b7e93e4908d78fd3 + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 9706c088a283058a8a99e0bf91b0a2f75497f185980d9ffa8b304de1d9e58ebda7c72c07ebf01dadedaac5b2907b2c6f566f660d62bd336c3468e960403b9d65 + languageName: node + linkType: hard + +"brace-expansion@npm:^1.1.7": + version: 1.1.21 + resolution: "brace-expansion@npm:1.1.21" + dependencies: + balanced-match: ^1.0.0 + concat-map: 0.0.1 + checksum: b841b4198a0ab8a1b433920ed65b46a9b4368c0ad1f95b2ed229a682c7d7d5b5839620204b5644a05c967d29e66bdf0a59b1115e74e0f27761d0dc02f5e0d835 + languageName: node + linkType: hard + +"call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: ^1.3.0 + function-bind: ^1.1.2 + checksum: b2863d74fcf2a6948221f65d95b91b4b2d90cfe8927650b506141e669f7d5de65cea191bf788838bc40d13846b7886c5bc5c84ab96c3adbcf88ad69a72fcdc6b + languageName: node + linkType: hard + +"call-bind@npm:^1.0.2, call-bind@npm:^1.0.5, call-bind@npm:^1.0.7, call-bind@npm:^1.0.8, call-bind@npm:^1.0.9": + version: 1.0.9 + resolution: "call-bind@npm:1.0.9" + dependencies: + call-bind-apply-helpers: ^1.0.2 + es-define-property: ^1.0.1 + get-intrinsic: ^1.3.0 + set-function-length: ^1.2.2 + checksum: fb5a8037bd7e2417ebda428f7ba57cbb3152e92f355aa8a20a4b2be9657f67b84e3812502620047ccf12c6542584a7d5bfb8d080cb636eb178b270bec0bfc010 + languageName: node + linkType: hard + +"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3, call-bound@npm:^1.0.4": + version: 1.0.4 + resolution: "call-bound@npm:1.0.4" + dependencies: + call-bind-apply-helpers: ^1.0.2 + get-intrinsic: ^1.3.0 + checksum: 2f6399488d1c272f56306ca60ff696575e2b7f31daf23bc11574798c84d9f2759dceb0cb1f471a85b77f28962a7ac6411f51d283ea2e45319009a19b6ccab3b2 + languageName: node + linkType: hard + +"concat-map@npm:0.0.1": + version: 0.0.1 + resolution: "concat-map@npm:0.0.1" + checksum: 902a9f5d8967a3e2faf138d5cb784b9979bad2e6db5357c5b21c568df4ebe62bcb15108af1b2253744844eb964fc023fbd9afbbbb6ddd0bcc204c6fb5b7bf3af + languageName: node + linkType: hard + +"data-view-buffer@npm:^1.0.2": + version: 1.0.2 + resolution: "data-view-buffer@npm:1.0.2" + dependencies: + call-bound: ^1.0.3 + es-errors: ^1.3.0 + is-data-view: ^1.0.2 + checksum: 1e1cd509c3037ac0f8ba320da3d1f8bf1a9f09b0be09394b5e40781b8cc15ff9834967ba7c9f843a425b34f9fe14ce44cf055af6662c44263424c1eb8d65659b + languageName: node + linkType: hard + +"data-view-byte-length@npm:^1.0.2": + version: 1.0.2 + resolution: "data-view-byte-length@npm:1.0.2" + dependencies: + call-bound: ^1.0.3 + es-errors: ^1.3.0 + is-data-view: ^1.0.2 + checksum: 3600c91ced1cfa935f19ef2abae11029e01738de8d229354d3b2a172bf0d7e4ed08ff8f53294b715569fdf72dfeaa96aa7652f479c0f60570878d88e7e8bddf6 + languageName: node + linkType: hard + +"data-view-byte-offset@npm:^1.0.1": + version: 1.0.1 + resolution: "data-view-byte-offset@npm:1.0.1" + dependencies: + call-bound: ^1.0.2 + es-errors: ^1.3.0 + is-data-view: ^1.0.1 + checksum: 8dd492cd51d19970876626b5b5169fbb67ca31ec1d1d3238ee6a71820ca8b80cafb141c485999db1ee1ef02f2cc3b99424c5eda8d59e852d9ebb79ab290eb5ee + languageName: node + linkType: hard + +"deep-equal@npm:^2.2.3": + version: 2.2.3 + resolution: "deep-equal@npm:2.2.3" + dependencies: + array-buffer-byte-length: ^1.0.0 + call-bind: ^1.0.5 + es-get-iterator: ^1.1.3 + get-intrinsic: ^1.2.2 + is-arguments: ^1.1.1 + is-array-buffer: ^3.0.2 + is-date-object: ^1.0.5 + is-regex: ^1.1.4 + is-shared-array-buffer: ^1.0.2 + isarray: ^2.0.5 + object-is: ^1.1.5 + object-keys: ^1.1.1 + object.assign: ^4.1.4 + regexp.prototype.flags: ^1.5.1 + side-channel: ^1.0.4 + which-boxed-primitive: ^1.0.2 + which-collection: ^1.0.1 + which-typed-array: ^1.1.13 + checksum: ee8852f23e4d20a5626c13b02f415ba443a1b30b4b3d39eaf366d59c4a85e6545d7ec917db44d476a85ae5a86064f7e5f7af7479f38f113995ba869f3a1ddc53 + languageName: node + linkType: hard + +"define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.4": + version: 1.1.4 + resolution: "define-data-property@npm:1.1.4" + dependencies: + es-define-property: ^1.0.0 + es-errors: ^1.3.0 + gopd: ^1.0.1 + checksum: 8068ee6cab694d409ac25936eb861eea704b7763f7f342adbdfe337fc27c78d7ae0eff2364b2917b58c508d723c7a074326d068eef2e45c4edcd85cf94d0313b + languageName: node + linkType: hard + +"define-properties@npm:^1.2.1": + version: 1.2.1 + resolution: "define-properties@npm:1.2.1" + dependencies: + define-data-property: ^1.0.1 + has-property-descriptors: ^1.0.0 + object-keys: ^1.1.1 + checksum: b4ccd00597dd46cb2d4a379398f5b19fca84a16f3374e2249201992f36b30f6835949a9429669ee6b41b6e837205a163eadd745e472069e70dfc10f03e5fcc12 + languageName: node + linkType: hard + +"defined@npm:^1.0.1": + version: 1.0.1 + resolution: "defined@npm:1.0.1" + checksum: b1a852300bdb57f297289b55eafdd0c517afaa3ec8190e78fce91b9d8d0c0369d4505ecbdacfd3d98372e664f4a267d9bd793938d4a8c76209c9d9516fbe2101 + languageName: node + linkType: hard + +"dotignore@npm:^0.1.2": + version: 0.1.2 + resolution: "dotignore@npm:0.1.2" + dependencies: + minimatch: ^3.0.4 + bin: + ignored: bin/ignored + checksum: 06bab15e2a2400c6f823a0edbcd73661180f6245a4041a3fe3b9fde4b22ae74b896604df4520a877093f05c656bd080087376c9f605bccdea847664c59910f37 + languageName: node + linkType: hard + +"dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: ^1.0.1 + es-errors: ^1.3.0 + gopd: ^1.2.0 + checksum: 149207e36f07bd4941921b0ca929e3a28f1da7bd6b6ff8ff7f4e2f2e460675af4576eeba359c635723dc189b64cdd4787e0255897d5b135ccc5d15cb8685fc90 + languageName: node + linkType: hard + +"es-abstract-get@npm:^1.0.0": + version: 1.0.0 + resolution: "es-abstract-get@npm:1.0.0" + dependencies: + es-errors: ^1.3.0 + es-object-atoms: ^1.1.2 + is-callable: ^1.2.7 + object-inspect: ^1.13.4 + checksum: 625bb67d41ebc22e7585875a6ebb90dc9c153998c9866dc7d50ff7b1b9e178e216c0d23914e5dbfd0addb38aab344c142ad27ff4375c6d2acf095432a4d85fe3 + languageName: node + linkType: hard + +"es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.9, es-abstract@npm:^1.24.2": + version: 1.24.2 + resolution: "es-abstract@npm:1.24.2" + dependencies: + array-buffer-byte-length: ^1.0.2 + arraybuffer.prototype.slice: ^1.0.4 + available-typed-arrays: ^1.0.7 + call-bind: ^1.0.8 + call-bound: ^1.0.4 + data-view-buffer: ^1.0.2 + data-view-byte-length: ^1.0.2 + data-view-byte-offset: ^1.0.1 + es-define-property: ^1.0.1 + es-errors: ^1.3.0 + es-object-atoms: ^1.1.1 + es-set-tostringtag: ^2.1.0 + es-to-primitive: ^1.3.0 + function.prototype.name: ^1.1.8 + get-intrinsic: ^1.3.0 + get-proto: ^1.0.1 + get-symbol-description: ^1.1.0 + globalthis: ^1.0.4 + gopd: ^1.2.0 + has-property-descriptors: ^1.0.2 + has-proto: ^1.2.0 + has-symbols: ^1.1.0 + hasown: ^2.0.2 + internal-slot: ^1.1.0 + is-array-buffer: ^3.0.5 + is-callable: ^1.2.7 + is-data-view: ^1.0.2 + is-negative-zero: ^2.0.3 + is-regex: ^1.2.1 + is-set: ^2.0.3 + is-shared-array-buffer: ^1.0.4 + is-string: ^1.1.1 + is-typed-array: ^1.1.15 + is-weakref: ^1.1.1 + math-intrinsics: ^1.1.0 + object-inspect: ^1.13.4 + object-keys: ^1.1.1 + object.assign: ^4.1.7 + own-keys: ^1.0.1 + regexp.prototype.flags: ^1.5.4 + safe-array-concat: ^1.1.3 + safe-push-apply: ^1.0.0 + safe-regex-test: ^1.1.0 + set-proto: ^1.0.0 + stop-iteration-iterator: ^1.1.0 + string.prototype.trim: ^1.2.10 + string.prototype.trimend: ^1.0.9 + string.prototype.trimstart: ^1.0.8 + typed-array-buffer: ^1.0.3 + typed-array-byte-length: ^1.0.3 + typed-array-byte-offset: ^1.0.4 + typed-array-length: ^1.0.7 + unbox-primitive: ^1.1.0 + which-typed-array: ^1.1.19 + checksum: 25ddb06725159050d896986a10df5351c658a35113dcfb328bc2e117557440cb956e2ebf61c1a977974c14551fac3bd43449c96e63cb876c5e72bde306714b98 + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.0, es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 0512f4e5d564021c9e3a644437b0155af2679d10d80f21adaf868e64d30efdfbd321631956f20f42d655fedb2e3a027da479fad3fa6048f768eb453a80a5f80a + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: ec1414527a0ccacd7f15f4a3bc66e215f04f595ba23ca75cdae0927af099b5ec865f9f4d33e9d7e86f512f252876ac77d4281a7871531a50678132429b1271b5 + languageName: node + linkType: hard + +"es-get-iterator@npm:^1.1.3": + version: 1.1.3 + resolution: "es-get-iterator@npm:1.1.3" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.1.3 + has-symbols: ^1.0.3 + is-arguments: ^1.1.1 + is-map: ^2.0.2 + is-set: ^2.0.2 + is-string: ^1.0.7 + isarray: ^2.0.5 + stop-iteration-iterator: ^1.0.0 + checksum: 8fa118da42667a01a7c7529f8a8cca514feeff243feec1ce0bb73baaa3514560bd09d2b3438873cf8a5aaec5d52da248131de153b28e2638a061b6e4df13267d + languageName: node + linkType: hard + +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1, es-object-atoms@npm:^1.1.2": + version: 1.1.2 + resolution: "es-object-atoms@npm:1.1.2" + dependencies: + es-errors: ^1.3.0 + checksum: b821f39e4f48bd85b13fee80aea9068a674bcb78b95ff01267aa4da1111f83e6944049b3329b2ffdb3acaa266fb5b8b885476fe8cada05034dc7c87c8016c86d + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.1.0": + version: 2.1.0 + resolution: "es-set-tostringtag@npm:2.1.0" + dependencies: + es-errors: ^1.3.0 + get-intrinsic: ^1.2.6 + has-tostringtag: ^1.0.2 + hasown: ^2.0.2 + checksum: 789f35de4be3dc8d11fdcb91bc26af4ae3e6d602caa93299a8c45cf05d36cc5081454ae2a6d3afa09cceca214b76c046e4f8151e092e6fc7feeb5efb9e794fc6 + languageName: node + linkType: hard + +"es-shim-unscopables@npm:^1.0.2": + version: 1.1.0 + resolution: "es-shim-unscopables@npm:1.1.0" + dependencies: + hasown: ^2.0.2 + checksum: 33cfb1ebcb2f869f0bf528be1a8660b4fe8b6cec8fc641f330e508db2284b58ee2980fad6d0828882d22858c759c0806076427a3673b6daa60f753e3b558ee15 + languageName: node + linkType: hard + +"es-to-primitive@npm:^1.3.0": + version: 1.3.4 + resolution: "es-to-primitive@npm:1.3.4" + dependencies: + es-abstract-get: ^1.0.0 + es-define-property: ^1.0.1 + es-errors: ^1.3.0 + is-callable: ^1.2.7 + is-date-object: ^1.1.0 + is-symbol: ^1.1.1 + checksum: b152ec48ee2f962760751c1c181ef09d2c4483af50fbdd08c0d41004d71014c90ad4339b14d0328a14209d9ee638d13b4190aadc066f19466071cf2af5785d05 + languageName: node + linkType: hard + +"for-each@npm:^0.3.3, for-each@npm:^0.3.5": + version: 0.3.5 + resolution: "for-each@npm:0.3.5" + dependencies: + is-callable: ^1.2.7 + checksum: 3c986d7e11f4381237cc98baa0a2f87eabe74719eee65ed7bed275163082b940ede19268c61d04c6260e0215983b12f8d885e3c8f9aa8c2113bf07c37051745c + languageName: node + linkType: hard + +"fs.realpath@npm:^1.0.0": + version: 1.0.0 + resolution: "fs.realpath@npm:1.0.0" + checksum: 99ddea01a7e75aa276c250a04eedeffe5662bce66c65c07164ad6264f9de18fb21be9433ead460e54cff20e31721c811f4fb5d70591799df5f85dce6d6746fd0 + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 2b0ff4ce708d99715ad14a6d1f894e2a83242e4a52ccfcefaee5e40050562e5f6dafc1adbb4ce2d4ab47279a45dc736ab91ea5042d843c3c092820dfe032efb1 + languageName: node + linkType: hard + +"function.prototype.name@npm:^1.1.6, function.prototype.name@npm:^1.1.8": + version: 1.2.0 + resolution: "function.prototype.name@npm:1.2.0" + dependencies: + call-bind: ^1.0.9 + call-bound: ^1.0.4 + es-define-property: ^1.0.1 + es-errors: ^1.3.0 + functions-have-names: ^1.2.3 + has-property-descriptors: ^1.0.2 + hasown: ^2.0.4 + is-callable: ^1.2.7 + is-document.all: ^1.0.0 + checksum: 297f6966f6fe1ff756ec7f51956420273d55186697769ff8e815578c10bb9cc27de5e4383cc0da703873b988ad634b74b167f1f9f8ef6a04f7f096f8088fde5d + languageName: node + linkType: hard + +"functions-have-names@npm:^1.2.3": + version: 1.2.3 + resolution: "functions-have-names@npm:1.2.3" + checksum: c3f1f5ba20f4e962efb71344ce0a40722163e85bee2101ce25f88214e78182d2d2476aa85ef37950c579eb6cf6ee811c17b3101bb84004bb75655f3e33f3fdb5 + languageName: node + linkType: hard + +"generator-function@npm:^2.0.0": + version: 2.0.1 + resolution: "generator-function@npm:2.0.1" + checksum: 3bf87f7b0230de5d74529677e6c3ceb3b7b5d9618b5a22d92b45ce3876defbaf5a77791b25a61b0fa7d13f95675b5ff67a7769f3b9af33f096e34653519e873d + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.2, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7, get-intrinsic@npm:^1.3.0": + version: 1.3.1 + resolution: "get-intrinsic@npm:1.3.1" + dependencies: + async-function: ^1.0.0 + async-generator-function: ^1.0.0 + call-bind-apply-helpers: ^1.0.2 + es-define-property: ^1.0.1 + es-errors: ^1.3.0 + es-object-atoms: ^1.1.1 + function-bind: ^1.1.2 + generator-function: ^2.0.0 + get-proto: ^1.0.1 + gopd: ^1.2.0 + has-symbols: ^1.1.0 + hasown: ^2.0.2 + math-intrinsics: ^1.1.0 + checksum: c02b3b6a445f9cd53e14896303794ac60f9751f58a69099127248abdb0251957174c6524245fc68579dc8e6a35161d3d94c93e665f808274716f4248b269436a + languageName: node + linkType: hard + +"get-package-type@npm:^0.1.0": + version: 0.1.0 + resolution: "get-package-type@npm:0.1.0" + checksum: bba0811116d11e56d702682ddef7c73ba3481f114590e705fc549f4d868972263896af313c57a25c076e3c0d567e11d919a64ba1b30c879be985fc9d44f96148 + languageName: node + linkType: hard + +"get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: ^1.0.1 + es-object-atoms: ^1.0.0 + checksum: 4fc96afdb58ced9a67558698b91433e6b037aaa6f1493af77498d7c85b141382cf223c0e5946f334fb328ee85dfe6edd06d218eaf09556f4bc4ec6005d7f5f7b + languageName: node + linkType: hard + +"get-symbol-description@npm:^1.1.0": + version: 1.1.0 + resolution: "get-symbol-description@npm:1.1.0" + dependencies: + call-bound: ^1.0.3 + es-errors: ^1.3.0 + get-intrinsic: ^1.2.6 + checksum: 655ed04db48ee65ef2ddbe096540d4405e79ba0a7f54225775fef43a7e2afcb93a77d141c5f05fdef0afce2eb93bcbfb3597142189d562ac167ff183582683cd + languageName: node + linkType: hard + +"glob@npm:^7.2.3": + version: 7.2.3 + resolution: "glob@npm:7.2.3" + dependencies: + fs.realpath: ^1.0.0 + inflight: ^1.0.4 + inherits: 2 + minimatch: ^3.1.1 + once: ^1.3.0 + path-is-absolute: ^1.0.0 + checksum: 29452e97b38fa704dabb1d1045350fb2467cf0277e155aa9ff7077e90ad81d1ea9d53d3ee63bd37c05b09a065e90f16aec4a65f5b8de401d1dac40bc5605d133 + languageName: node + linkType: hard + +"globalthis@npm:^1.0.4": + version: 1.0.4 + resolution: "globalthis@npm:1.0.4" + dependencies: + define-properties: ^1.2.1 + gopd: ^1.0.1 + checksum: 39ad667ad9f01476474633a1834a70842041f70a55571e8dcef5fb957980a92da5022db5430fca8aecc5d47704ae30618c0bc877a579c70710c904e9ef06108a + languageName: node + linkType: hard + +"gopd@npm:^1.0.1, gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: cc6d8e655e360955bdccaca51a12a474268f95bb793fc3e1f2bdadb075f28bfd1fd988dab872daf77a61d78cbaf13744bc8727a17cfb1d150d76047d805375f3 + languageName: node + linkType: hard + +"has-bigints@npm:^1.0.2": + version: 1.1.0 + resolution: "has-bigints@npm:1.1.0" + checksum: 79730518ae02c77e4af6a1d1a0b6a2c3e1509785532771f9baf0241e83e36329542c3d7a0e723df8cbc85f74eff4f177828a2265a01ba576adbdc2d40d86538b + languageName: node + linkType: hard + +"has-dynamic-import@npm:^2.1.1": + version: 2.1.1 + resolution: "has-dynamic-import@npm:2.1.1" + dependencies: + call-bind: ^1.0.8 + call-bound: ^1.0.3 + get-intrinsic: ^1.2.6 + checksum: 84cf235c445c9bd8e14c1d3b56e6dc075a3c1c32045a9b403131157d42bb6e72ca479267693b6ed5488d8d822a65270cddace44e50505871c69353ceefd76da7 + languageName: node + linkType: hard + +"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.2": + version: 1.0.2 + resolution: "has-property-descriptors@npm:1.0.2" + dependencies: + es-define-property: ^1.0.0 + checksum: fcbb246ea2838058be39887935231c6d5788babed499d0e9d0cc5737494c48aba4fe17ba1449e0d0fbbb1e36175442faa37f9c427ae357d6ccb1d895fbcd3de3 + languageName: node + linkType: hard + +"has-proto@npm:^1.2.0": + version: 1.2.0 + resolution: "has-proto@npm:1.2.0" + dependencies: + dunder-proto: ^1.0.0 + checksum: f55010cb94caa56308041d77967c72a02ffd71386b23f9afa8447e58bc92d49d15c19bf75173713468e92fe3fb1680b03b115da39c21c32c74886d1d50d3e7ff + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: b2316c7302a0e8ba3aaba215f834e96c22c86f192e7310bdf689dd0e6999510c89b00fbc5742571507cebf25764d68c988b3a0da217369a73596191ac0ce694b + languageName: node + linkType: hard + +"has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: ^1.0.3 + checksum: 999d60bb753ad714356b2c6c87b7fb74f32463b8426e159397da4bde5bca7e598ab1073f4d8d4deafac297f2eb311484cd177af242776bf05f0d11565680468d + languageName: node + linkType: hard + +"hasown@npm:^2.0.2, hasown@npm:^2.0.4": + version: 2.0.4 + resolution: "hasown@npm:2.0.4" + dependencies: + function-bind: ^1.1.2 + checksum: 4bd8f916b629e06324853593ffbdd45e200022952a85ad0c967f3bd4c2e4c7e1f9a9766fbe6186f60bd394e0afc73e719730caa1da15cd9bd832b7cdf53fd26c + languageName: node + linkType: hard + +"inflight@npm:^1.0.4": + version: 1.0.6 + resolution: "inflight@npm:1.0.6" + dependencies: + once: ^1.3.0 + wrappy: 1 + checksum: f4f76aa072ce19fae87ce1ef7d221e709afb59d445e05d47fba710e85470923a75de35bfae47da6de1b18afc3ce83d70facf44cfb0aff89f0a3f45c0a0244dfd + languageName: node + linkType: hard + +"inherits@npm:2, inherits@npm:^2.0.4": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 4a48a733847879d6cf6691860a6b1e3f0f4754176e4d71494c41f3475553768b10f84b5ce1d40fbd0e34e6bfbb864ee35858ad4dd2cf31e02fc4a154b724d7f1 + languageName: node + linkType: hard + +"internal-slot@npm:^1.1.0": + version: 1.1.0 + resolution: "internal-slot@npm:1.1.0" + dependencies: + es-errors: ^1.3.0 + hasown: ^2.0.2 + side-channel: ^1.1.0 + checksum: 8e0991c2d048cc08dab0a91f573c99f6a4215075887517ea4fa32203ce8aea60fa03f95b177977fa27eb502e5168366d0f3e02c762b799691411d49900611861 + languageName: node + linkType: hard + +"is-arguments@npm:^1.1.1": + version: 1.2.0 + resolution: "is-arguments@npm:1.2.0" + dependencies: + call-bound: ^1.0.2 + has-tostringtag: ^1.0.2 + checksum: aae9307fedfe2e5be14aebd0f48a9eeedf6b8c8f5a0b66257b965146d1e94abdc3f08e3dce3b1d908e1fa23c70039a88810ee1d753905758b9b6eebbab0bafeb + languageName: node + linkType: hard + +"is-array-buffer@npm:^3.0.2, is-array-buffer@npm:^3.0.4, is-array-buffer@npm:^3.0.5": + version: 3.0.5 + resolution: "is-array-buffer@npm:3.0.5" + dependencies: + call-bind: ^1.0.8 + call-bound: ^1.0.3 + get-intrinsic: ^1.2.6 + checksum: f137a2a6e77af682cdbffef1e633c140cf596f72321baf8bba0f4ef22685eb4339dde23dfe9e9ca430b5f961dee4d46577dcf12b792b68518c8449b134fb9156 + languageName: node + linkType: hard + +"is-async-function@npm:^2.0.0": + version: 2.1.1 + resolution: "is-async-function@npm:2.1.1" + dependencies: + async-function: ^1.0.0 + call-bound: ^1.0.3 + get-proto: ^1.0.1 + has-tostringtag: ^1.0.2 + safe-regex-test: ^1.1.0 + checksum: 9bece45133da26636488ca127d7686b85ad3ca18927e2850cff1937a650059e90be1c71a48623f8791646bb7a241b0cabf602a0b9252dcfa5ab273f2399000e6 + languageName: node + linkType: hard + +"is-bigint@npm:^1.1.0": + version: 1.1.0 + resolution: "is-bigint@npm:1.1.0" + dependencies: + has-bigints: ^1.0.2 + checksum: ee1544f0e664f253306786ed1dce494b8cf242ef415d6375d8545b4d8816b0f054bd9f948a8988ae2c6325d1c28260dd02978236b2f7b8fb70dfc4838a6c9fa7 + languageName: node + linkType: hard + +"is-boolean-object@npm:^1.2.1": + version: 1.2.2 + resolution: "is-boolean-object@npm:1.2.2" + dependencies: + call-bound: ^1.0.3 + has-tostringtag: ^1.0.2 + checksum: 0415b181e8f1bfd5d3f8a20f8108e64d372a72131674eea9c2923f39d065b6ad08d654765553bdbffbd92c3746f1007986c34087db1bd89a31f71be8359ccdaa + languageName: node + linkType: hard + +"is-callable@npm:^1.2.7": + version: 1.2.7 + resolution: "is-callable@npm:1.2.7" + checksum: 61fd57d03b0d984e2ed3720fb1c7a897827ea174bd44402878e059542ea8c4aeedee0ea0985998aa5cc2736b2fa6e271c08587addb5b3959ac52cf665173d1ac + languageName: node + linkType: hard + +"is-core-module@npm:^2.16.2": + version: 2.17.0 + resolution: "is-core-module@npm:2.17.0" + dependencies: + hasown: ^2.0.4 + checksum: ab8a122dd92fc05b54e4f46c9859d6f8a9d9f20676a1b029605a4c33b151f07d8cd280e31f7806a29f29f8bfd8e3e647ba7b1cde7f07d60387c14be77774d346 + languageName: node + linkType: hard + +"is-data-view@npm:^1.0.1, is-data-view@npm:^1.0.2": + version: 1.0.2 + resolution: "is-data-view@npm:1.0.2" + dependencies: + call-bound: ^1.0.2 + get-intrinsic: ^1.2.6 + is-typed-array: ^1.1.13 + checksum: 31600dd19932eae7fd304567e465709ffbfa17fa236427c9c864148e1b54eb2146357fcf3aed9b686dee13c217e1bb5a649cb3b9c479e1004c0648e9febde1b2 + languageName: node + linkType: hard + +"is-date-object@npm:^1.0.5, is-date-object@npm:^1.1.0": + version: 1.1.0 + resolution: "is-date-object@npm:1.1.0" + dependencies: + call-bound: ^1.0.2 + has-tostringtag: ^1.0.2 + checksum: d6c36ab9d20971d65f3fc64cef940d57a4900a2ac85fb488a46d164c2072a33da1cb51eefcc039e3e5c208acbce343d3480b84ab5ff0983f617512da2742562a + languageName: node + linkType: hard + +"is-document.all@npm:^1.0.0": + version: 1.0.0 + resolution: "is-document.all@npm:1.0.0" + dependencies: + call-bound: ^1.0.4 + checksum: 383175789df98503dc0c15d39e80932b21cbec839b8840d7b75dc36db942e9dd2da0f36c464ea1aa2e751f5808c38e97bbf288b76d8114d5e570f49df26ed930 + languageName: node + linkType: hard + +"is-finalizationregistry@npm:^1.1.0": + version: 1.1.1 + resolution: "is-finalizationregistry@npm:1.1.1" + dependencies: + call-bound: ^1.0.3 + checksum: 38c646c506e64ead41a36c182d91639833311970b6b6c6268634f109eef0a1a9d2f1f2e499ef4cb43c744a13443c4cdd2f0812d5afdcee5e9b65b72b28c48557 + languageName: node + linkType: hard + +"is-generator-function@npm:^1.0.10": + version: 1.1.2 + resolution: "is-generator-function@npm:1.1.2" + dependencies: + call-bound: ^1.0.4 + generator-function: ^2.0.0 + get-proto: ^1.0.1 + has-tostringtag: ^1.0.2 + safe-regex-test: ^1.1.0 + checksum: 0b81c613752a5e534939e5b3835ff722446837a5b94c3a3934af5ded36a651d9aa31c3f11f8a3453884b9658bf26dbfb7eb855e744d920b07f084bd890a43414 + languageName: node + linkType: hard + +"is-map@npm:^2.0.2, is-map@npm:^2.0.3": + version: 2.0.3 + resolution: "is-map@npm:2.0.3" + checksum: e6ce5f6380f32b141b3153e6ba9074892bbbbd655e92e7ba5ff195239777e767a976dcd4e22f864accaf30e53ebf961ab1995424aef91af68788f0591b7396cc + languageName: node + linkType: hard + +"is-negative-zero@npm:^2.0.3": + version: 2.0.3 + resolution: "is-negative-zero@npm:2.0.3" + checksum: c1e6b23d2070c0539d7b36022d5a94407132411d01aba39ec549af824231f3804b1aea90b5e4e58e807a65d23ceb538ed6e355ce76b267bdd86edb757ffcbdcd + languageName: node + linkType: hard + +"is-number-object@npm:^1.1.1": + version: 1.1.1 + resolution: "is-number-object@npm:1.1.1" + dependencies: + call-bound: ^1.0.3 + has-tostringtag: ^1.0.2 + checksum: 6517f0a0e8c4b197a21afb45cd3053dc711e79d45d8878aa3565de38d0102b130ca8732485122c7b336e98c27dacd5236854e3e6526e0eb30cae64956535662f + languageName: node + linkType: hard + +"is-regex@npm:^1.1.4, is-regex@npm:^1.2.1": + version: 1.2.1 + resolution: "is-regex@npm:1.2.1" + dependencies: + call-bound: ^1.0.2 + gopd: ^1.2.0 + has-tostringtag: ^1.0.2 + hasown: ^2.0.2 + checksum: 99ee0b6d30ef1bb61fa4b22fae7056c6c9b3c693803c0c284ff7a8570f83075a7d38cda53b06b7996d441215c27895ea5d1af62124562e13d91b3dbec41a5e13 + languageName: node + linkType: hard + +"is-set@npm:^2.0.2, is-set@npm:^2.0.3": + version: 2.0.3 + resolution: "is-set@npm:2.0.3" + checksum: 36e3f8c44bdbe9496c9689762cc4110f6a6a12b767c5d74c0398176aa2678d4467e3bf07595556f2dba897751bde1422480212b97d973c7b08a343100b0c0dfe + languageName: node + linkType: hard + +"is-shared-array-buffer@npm:^1.0.2, is-shared-array-buffer@npm:^1.0.4": + version: 1.0.4 + resolution: "is-shared-array-buffer@npm:1.0.4" + dependencies: + call-bound: ^1.0.3 + checksum: 1611fedc175796eebb88f4dfc393dd969a4a8e6c69cadaff424ee9d4464f9f026399a5f84a90f7c62d6d7ee04e3626a912149726de102b0bd6c1ee6a9868fa5a + languageName: node + linkType: hard + +"is-string@npm:^1.0.7, is-string@npm:^1.1.0, is-string@npm:^1.1.1": + version: 1.1.1 + resolution: "is-string@npm:1.1.1" + dependencies: + call-bound: ^1.0.3 + has-tostringtag: ^1.0.2 + checksum: 2eeaaff605250f5e836ea3500d33d1a5d3aa98d008641d9d42fb941e929ffd25972326c2ef912987e54c95b6f10416281aaf1b35cdf81992cfb7524c5de8e193 + languageName: node + linkType: hard + +"is-symbol@npm:^1.1.1": + version: 1.1.1 + resolution: "is-symbol@npm:1.1.1" + dependencies: + call-bound: ^1.0.2 + has-symbols: ^1.1.0 + safe-regex-test: ^1.1.0 + checksum: bfafacf037af6f3c9d68820b74be4ae8a736a658a3344072df9642a090016e281797ba8edbeb1c83425879aae55d1cb1f30b38bf132d703692b2570367358032 + languageName: node + linkType: hard + +"is-typed-array@npm:^1.1.13, is-typed-array@npm:^1.1.14, is-typed-array@npm:^1.1.15": + version: 1.1.15 + resolution: "is-typed-array@npm:1.1.15" + dependencies: + which-typed-array: ^1.1.16 + checksum: ea7cfc46c282f805d19a9ab2084fd4542fed99219ee9dbfbc26284728bd713a51eac66daa74eca00ae0a43b61322920ba334793607dc39907465913e921e0892 + languageName: node + linkType: hard + +"is-weakmap@npm:^2.0.2": + version: 2.0.2 + resolution: "is-weakmap@npm:2.0.2" + checksum: f36aef758b46990e0d3c37269619c0a08c5b29428c0bb11ecba7f75203442d6c7801239c2f31314bc79199217ef08263787f3837d9e22610ad1da62970d6616d + languageName: node + linkType: hard + +"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.1": + version: 1.1.1 + resolution: "is-weakref@npm:1.1.1" + dependencies: + call-bound: ^1.0.3 + checksum: 1769b9aed5d435a3a989ffc18fc4ad1947d2acdaf530eb2bd6af844861b545047ea51102f75901f89043bed0267ed61d914ee21e6e8b9aa734ec201cdfc0726f + languageName: node + linkType: hard + +"is-weakset@npm:^2.0.3": + version: 2.0.4 + resolution: "is-weakset@npm:2.0.4" + dependencies: + call-bound: ^1.0.3 + get-intrinsic: ^1.2.6 + checksum: 5c6c8415a06065d78bdd5e3a771483aa1cd928df19138aa73c4c51333226f203f22117b4325df55cc8b3085a6716870a320c2d757efee92d7a7091a039082041 + languageName: node + linkType: hard + +"isarray@npm:^2.0.5": + version: 2.0.5 + resolution: "isarray@npm:2.0.5" + checksum: bd5bbe4104438c4196ba58a54650116007fa0262eccef13a4c55b2e09a5b36b59f1e75b9fcc49883dd9d4953892e6fc007eef9e9155648ceea036e184b0f930a + languageName: node + linkType: hard + +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 0e513b29d120f478c85a70f49da0b8b19bc638975eca466f2eeae0071f3ad00454c621bf66e16dd435896c208e719fc91ad79bbfba4e400fe0b372e7c1c9c9a2 + languageName: node + linkType: hard + +"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1": + version: 3.1.5 + resolution: "minimatch@npm:3.1.5" + dependencies: + brace-expansion: ^1.1.7 + checksum: 47ef6f412c08be045a7291d11b1c40777925accf7252dc6d3caa39b1bfbb3a7ea390ba7aba464d762d783265c644143d2c8a204e6b5763145024d52ee65a1941 + languageName: node + linkType: hard + +"minimist@npm:^1.2.8": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 75a6d645fb122dad29c06a7597bddea977258957ed88d7a6df59b5cd3fe4a527e253e9bbf2e783e4b73657f9098b96a5fe96ab8a113655d4109108577ecf85b0 + languageName: node + linkType: hard + +"mock-property@npm:^1.1.0": + version: 1.1.2 + resolution: "mock-property@npm:1.1.2" + dependencies: + define-data-property: ^1.1.4 + es-errors: ^1.3.0 + functions-have-names: ^1.2.3 + gopd: ^1.2.0 + has-property-descriptors: ^1.0.2 + hasown: ^2.0.4 + isarray: ^2.0.5 + object-inspect: ^1.13.4 + checksum: ffb31081367d85f8e3b95e8be32967db80cf2026a4fc368f2542a8f4855686e6800726126f24e2abae924ee8f61fe86ec19e67a12edea276d9335a429fc719e7 + languageName: node + linkType: hard + +"node-exports-info@npm:^1.6.0": + version: 1.6.2 + resolution: "node-exports-info@npm:1.6.2" + dependencies: + array.prototype.flatmap: ^1.3.3 + es-errors: ^1.3.0 + object.entries: ^1.1.9 + semver: ^6.3.1 + checksum: d8642f3dc0c03023a0249ab737ab052796b7ae6d51401b49cfcd0c8c41a22dc464e8aeb3207d6d2ea1f3807f37000bc88bcfad111e4ec191d0f9e1eafa768804 + languageName: node + linkType: hard + +"object-inspect@npm:^1.13.3, object-inspect@npm:^1.13.4": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 582810c6a8d2ef988ea0a39e69e115a138dad8f42dd445383b394877e5816eb4268489f316a6f74ee9c4e0a984b3eab1028e3e79d62b1ed67c726661d55c7a8b + languageName: node + linkType: hard + +"object-is@npm:^1.1.5, object-is@npm:^1.1.6": + version: 1.1.6 + resolution: "object-is@npm:1.1.6" + dependencies: + call-bind: ^1.0.7 + define-properties: ^1.2.1 + checksum: 3ea22759967e6f2380a2cbbd0f737b42dc9ddb2dfefdb159a1b927fea57335e1b058b564bfa94417db8ad58cddab33621a035de6f5e5ad56d89f2dd03e66c6a1 + languageName: node + linkType: hard + +"object-keys@npm:^1.1.1": + version: 1.1.1 + resolution: "object-keys@npm:1.1.1" + checksum: b363c5e7644b1e1b04aa507e88dcb8e3a2f52b6ffd0ea801e4c7a62d5aa559affe21c55a07fd4b1fd55fc03a33c610d73426664b20032405d7b92a1414c34d6a + languageName: node + linkType: hard + +"object.assign@npm:^4.1.4, object.assign@npm:^4.1.7": + version: 4.1.7 + resolution: "object.assign@npm:4.1.7" + dependencies: + call-bind: ^1.0.8 + call-bound: ^1.0.3 + define-properties: ^1.2.1 + es-object-atoms: ^1.0.0 + has-symbols: ^1.1.0 + object-keys: ^1.1.1 + checksum: 60e07d2651cf4f5528c485f1aa4dbded9b384c47d80e8187cefd11320abb1aebebf78df5483451dfa549059f8281c21f7b4bf7d19e9e5e97d8d617df0df298de + languageName: node + linkType: hard + +"object.entries@npm:^1.1.9": + version: 1.1.9 + resolution: "object.entries@npm:1.1.9" + dependencies: + call-bind: ^1.0.8 + call-bound: ^1.0.4 + define-properties: ^1.2.1 + es-object-atoms: ^1.1.1 + checksum: 0ab2ef331c4d6a53ff600a5d69182948d453107c3a1f7fd91bc29d387538c2aba21d04949a74f57c21907208b1f6fb175567fd1f39f1a7a4046ba1bca762fb41 + languageName: node + linkType: hard + +"once@npm:^1.3.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: 1 + checksum: cd0a88501333edd640d95f0d2700fbde6bff20b3d4d9bdc521bdd31af0656b5706570d6c6afe532045a20bb8dc0849f8332d6f2a416e0ba6d3d3b98806c7db68 + languageName: node + linkType: hard + +"own-keys@npm:^1.0.1": + version: 1.0.2 + resolution: "own-keys@npm:1.0.2" + dependencies: + call-bound: ^1.0.4 + get-intrinsic: ^1.3.0 + object-keys: ^1.1.1 + safe-push-apply: ^1.0.0 + checksum: 9c5092be16b13391938458205ce2d6aea9184ce0d6179d9bd18c112d3be83fe90b5b958f4e99a2a0c9699e2ae04c655c5eea0cd33b7170ef3243430feea33a94 + languageName: node + linkType: hard + +"path-is-absolute@npm:^1.0.0": + version: 1.0.1 + resolution: "path-is-absolute@npm:1.0.1" + checksum: 060840f92cf8effa293bcc1bea81281bd7d363731d214cbe5c227df207c34cd727430f70c6037b5159c8a870b9157cba65e775446b0ab06fd5ecc7e54615a3b8 + languageName: node + linkType: hard + +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 49abf3d81115642938a8700ec580da6e830dde670be21893c62f4e10bd7dd4c3742ddc603fe24f898cba7eb0c6bc1777f8d9ac14185d34540c6d4d80cd9cae8a + languageName: node + linkType: hard + +"possible-typed-array-names@npm:^1.0.0, possible-typed-array-names@npm:^1.1.0": + version: 1.1.0 + resolution: "possible-typed-array-names@npm:1.1.0" + checksum: cfcd4f05264eee8fd184cd4897a17890561d1d473434b43ab66ad3673d9c9128981ec01e0cb1d65a52cd6b1eebfb2eae1e53e39b2e0eca86afc823ede7a4f41b + languageName: node + linkType: hard + +"reflect.getprototypeof@npm:^1.0.10": + version: 1.0.10 + resolution: "reflect.getprototypeof@npm:1.0.10" + dependencies: + call-bind: ^1.0.8 + define-properties: ^1.2.1 + es-abstract: ^1.23.9 + es-errors: ^1.3.0 + es-object-atoms: ^1.0.0 + get-intrinsic: ^1.2.7 + get-proto: ^1.0.1 + which-builtin-type: ^1.2.1 + checksum: ccc5debeb66125e276ae73909cecb27e47c35d9bb79d9cc8d8d055f008c58010ab8cb401299786e505e4aab733a64cba9daf5f312a58e96a43df66adad221870 + languageName: node + linkType: hard + +"regexp.prototype.flags@npm:^1.5.1, regexp.prototype.flags@npm:^1.5.4": + version: 1.5.4 + resolution: "regexp.prototype.flags@npm:1.5.4" + dependencies: + call-bind: ^1.0.8 + define-properties: ^1.2.1 + es-errors: ^1.3.0 + get-proto: ^1.0.1 + gopd: ^1.2.0 + set-function-name: ^2.0.2 + checksum: 18cb667e56cb328d2dda569d7f04e3ea78f2683135b866d606538cf7b1d4271f7f749f09608c877527799e6cf350e531368f3c7a20ccd1bb41048a48926bdeeb + languageName: node + linkType: hard + +"resolve@npm:^2.0.0-next.7": + version: 2.0.0-next.7 + resolution: "resolve@npm:2.0.0-next.7" + dependencies: + es-errors: ^1.3.0 + is-core-module: ^2.16.2 + node-exports-info: ^1.6.0 + object-keys: ^1.1.1 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: 62d58c4b2fcd820da8ef4e0f37f14daaac3f1dd1bdc73b4c1b750c4705676a28f30281784d340a60240206f3398a7001d0feca35735bacdc90e01de6ebf606b8 + languageName: node + linkType: hard + +"resolve@patch:resolve@^2.0.0-next.7#~builtin": + version: 2.0.0-next.7 + resolution: "resolve@patch:resolve@npm%3A2.0.0-next.7#~builtin::version=2.0.0-next.7&hash=c3c19d" + dependencies: + es-errors: ^1.3.0 + is-core-module: ^2.16.2 + node-exports-info: ^1.6.0 + object-keys: ^1.1.1 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: 6385a1797ae12043ecdbdeed8d39da9099417edf3504495df4f395889cc3548afc1f71b51ae05b3235e82cef3551215b5e5b89cf5c9fb6360cd804df519b76bf + languageName: node + linkType: hard + +"safe-array-concat@npm:^1.1.3": + version: 1.1.4 + resolution: "safe-array-concat@npm:1.1.4" + dependencies: + call-bind: ^1.0.9 + call-bound: ^1.0.4 + get-intrinsic: ^1.3.0 + has-symbols: ^1.1.0 + isarray: ^2.0.5 + checksum: fd59dbf79f5ab6b56eb1b07bc7fd38ebdb9cb0478e7639606cd7a7f423d2fd22dc81eaf2371f74b5f3332ce75327669e1667272b34b33d06d07514e3e5305cf8 + languageName: node + linkType: hard + +"safe-push-apply@npm:^1.0.0": + version: 1.0.0 + resolution: "safe-push-apply@npm:1.0.0" + dependencies: + es-errors: ^1.3.0 + isarray: ^2.0.5 + checksum: 8c11cbee6dc8ff5cc0f3d95eef7052e43494591384015902e4292aef4ae9e539908288520ed97179cee17d6ffb450fe5f05a46ce7a1749685f7524fd568ab5db + languageName: node + linkType: hard + +"safe-regex-test@npm:^1.1.0": + version: 1.1.0 + resolution: "safe-regex-test@npm:1.1.0" + dependencies: + call-bound: ^1.0.2 + es-errors: ^1.3.0 + is-regex: ^1.2.1 + checksum: 3c809abeb81977c9ed6c869c83aca6873ea0f3ab0f806b8edbba5582d51713f8a6e9757d24d2b4b088f563801475ea946c8e77e7713e8c65cdd02305b6caedab + languageName: node + linkType: hard + +"semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" + bin: + semver: bin/semver.js + checksum: ae47d06de28836adb9d3e25f22a92943477371292d9b665fb023fae278d345d508ca1958232af086d85e0155aee22e313e100971898bbb8d5d89b8b1d4054ca2 + languageName: node + linkType: hard + +"set-function-length@npm:^1.2.2": + version: 1.2.2 + resolution: "set-function-length@npm:1.2.2" + dependencies: + define-data-property: ^1.1.4 + es-errors: ^1.3.0 + function-bind: ^1.1.2 + get-intrinsic: ^1.2.4 + gopd: ^1.0.1 + has-property-descriptors: ^1.0.2 + checksum: a8248bdacdf84cb0fab4637774d9fb3c7a8e6089866d04c817583ff48e14149c87044ce683d7f50759a8c50fb87c7a7e173535b06169c87ef76f5fb276dfff72 + languageName: node + linkType: hard + +"set-function-name@npm:^2.0.2": + version: 2.0.2 + resolution: "set-function-name@npm:2.0.2" + dependencies: + define-data-property: ^1.1.4 + es-errors: ^1.3.0 + functions-have-names: ^1.2.3 + has-property-descriptors: ^1.0.2 + checksum: d6229a71527fd0404399fc6227e0ff0652800362510822a291925c9d7b48a1ca1a468b11b281471c34cd5a2da0db4f5d7ff315a61d26655e77f6e971e6d0c80f + languageName: node + linkType: hard + +"set-proto@npm:^1.0.0": + version: 1.0.0 + resolution: "set-proto@npm:1.0.0" + dependencies: + dunder-proto: ^1.0.1 + es-errors: ^1.3.0 + es-object-atoms: ^1.0.0 + checksum: ec27cbbe334598547e99024403e96da32aca3e530583e4dba7f5db1c43cbc4affa9adfbd77c7b2c210b9b8b2e7b2e600bad2a6c44fd62e804d8233f96bbb62f4 + languageName: node + linkType: hard + +"side-channel-list@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-list@npm:1.0.1" + dependencies: + es-errors: ^1.3.0 + object-inspect: ^1.13.4 + checksum: 3499671cd52adaee739eac1e14d07530b8e3530192741aeb05e7fe4ad1b51d1368ceea2cd3c21b0f62b05410a5c70a7c4d997ba4b143303ef73d0c65dfd1c252 + languageName: node + linkType: hard + +"side-channel-map@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-map@npm:1.0.1" + dependencies: + call-bound: ^1.0.2 + es-errors: ^1.3.0 + get-intrinsic: ^1.2.5 + object-inspect: ^1.13.3 + checksum: 42501371cdf71f4ccbbc9c9e2eb00aaaab80a4c1c429d5e8da713fd4d39ef3b8d4a4b37ed4f275798a65260a551a7131fd87fe67e922dba4ac18586d6aab8b06 + languageName: node + linkType: hard + +"side-channel-weakmap@npm:^1.0.2": + version: 1.0.2 + resolution: "side-channel-weakmap@npm:1.0.2" + dependencies: + call-bound: ^1.0.2 + es-errors: ^1.3.0 + get-intrinsic: ^1.2.5 + object-inspect: ^1.13.3 + side-channel-map: ^1.0.1 + checksum: a815c89bc78c5723c714ea1a77c938377ea710af20d4fb886d362b0d1f8ac73a17816a5f6640f354017d7e292a43da9c5e876c22145bac00b76cfb3468001736 + languageName: node + linkType: hard + +"side-channel@npm:^1.0.4, side-channel@npm:^1.1.0": + version: 1.1.1 + resolution: "side-channel@npm:1.1.1" + dependencies: + es-errors: ^1.3.0 + object-inspect: ^1.13.4 + side-channel-list: ^1.0.1 + side-channel-map: ^1.0.1 + side-channel-weakmap: ^1.0.2 + checksum: e0f217140c463636ee556260bbc8f47ba2931f1248826f58e1502704135814c763b325dd9ab122a04176aa45b4a4f8cc556415bcab7a0179d85250fb7812f063 + languageName: node + linkType: hard + +"stop-iteration-iterator@npm:^1.0.0, stop-iteration-iterator@npm:^1.1.0": + version: 1.1.0 + resolution: "stop-iteration-iterator@npm:1.1.0" + dependencies: + es-errors: ^1.3.0 + internal-slot: ^1.1.0 + checksum: be944489d8829fb3bdec1a1cc4a2142c6b6eb317305eeace1ece978d286d6997778afa1ae8cb3bd70e2b274b9aa8c69f93febb1e15b94b1359b11058f9d3c3a1 + languageName: node + linkType: hard + +"string.prototype.trim@npm:^1.2.10, string.prototype.trim@npm:^1.2.11": + version: 1.2.11 + resolution: "string.prototype.trim@npm:1.2.11" + dependencies: + call-bind: ^1.0.9 + call-bound: ^1.0.4 + define-data-property: ^1.1.4 + define-properties: ^1.2.1 + es-abstract: ^1.24.2 + es-object-atoms: ^1.1.2 + has-property-descriptors: ^1.0.2 + safe-regex-test: ^1.1.0 + checksum: 1aa0868afe15a54e781cd7fdcc851af4b0d71522108006f136ba35ecfde65b042496ca6926f85192923e6e9287750b772afb13860db15574bf1da454343db0ca + languageName: node + linkType: hard + +"string.prototype.trimend@npm:^1.0.9": + version: 1.0.10 + resolution: "string.prototype.trimend@npm:1.0.10" + dependencies: + call-bind: ^1.0.9 + call-bound: ^1.0.4 + define-properties: ^1.2.1 + es-object-atoms: ^1.1.2 + checksum: 17684796ccd12accafaef0f7cafe7a88891e4a57ff8deb5a40665dbe627fb3bed2252f1b9f3b16da2229aa41ba24e082178b44afe91a0302d570149730be1ccb + languageName: node + linkType: hard + +"string.prototype.trimstart@npm:^1.0.8": + version: 1.0.8 + resolution: "string.prototype.trimstart@npm:1.0.8" + dependencies: + call-bind: ^1.0.7 + define-properties: ^1.2.1 + es-object-atoms: ^1.0.0 + checksum: df1007a7f580a49d692375d996521dc14fd103acda7f3034b3c558a60b82beeed3a64fa91e494e164581793a8ab0ae2f59578a49896a7af6583c1f20472bce96 + languageName: node + linkType: hard + +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 53b1e247e68e05db7b3808b99b892bd36fb096e6fba213a06da7fab22045e97597db425c724f2bbd6c99a3c295e1e73f3e4de78592289f38431049e1277ca0ae + languageName: node + linkType: hard + +"tape@npm:^5.0.1": + version: 5.10.2 + resolution: "tape@npm:5.10.2" + dependencies: + "@ljharb/now": ^1.0.1 + "@ljharb/resumer": ^0.1.3 + "@ljharb/through": ^2.3.14 + array.prototype.every: ^1.1.7 + call-bind: ^1.0.9 + call-bound: ^1.0.4 + deep-equal: ^2.2.3 + defined: ^1.0.1 + dotignore: ^0.1.2 + es-object-atoms: ^1.1.2 + for-each: ^0.3.5 + get-package-type: ^0.1.0 + glob: ^7.2.3 + has-dynamic-import: ^2.1.1 + hasown: ^2.0.4 + inherits: ^2.0.4 + is-regex: ^1.2.1 + minimist: ^1.2.8 + mock-property: ^1.1.0 + object-inspect: ^1.13.4 + object-is: ^1.1.6 + object-keys: ^1.1.1 + object.assign: ^4.1.7 + resolve: ^2.0.0-next.7 + string.prototype.trim: ^1.2.11 + bin: + tape: bin/tape + checksum: c10ff16aae0490451dc0f35c8a82c2d0c657eff011ca47d62e6e608f52457c497fafcb8d9417231e63d1d4151fed5420cb81385874009f18719399c498bdd72f + languageName: node + linkType: hard + +"typed-array-buffer@npm:^1.0.3": + version: 1.0.3 + resolution: "typed-array-buffer@npm:1.0.3" + dependencies: + call-bound: ^1.0.3 + es-errors: ^1.3.0 + is-typed-array: ^1.1.14 + checksum: 3fb91f0735fb413b2bbaaca9fabe7b8fc14a3fa5a5a7546bab8a57e755be0e3788d893195ad9c2b842620592de0e68d4c077d4c2c41f04ec25b8b5bb82fa9a80 + languageName: node + linkType: hard + +"typed-array-byte-length@npm:^1.0.3": + version: 1.0.3 + resolution: "typed-array-byte-length@npm:1.0.3" + dependencies: + call-bind: ^1.0.8 + for-each: ^0.3.3 + gopd: ^1.2.0 + has-proto: ^1.2.0 + is-typed-array: ^1.1.14 + checksum: cda9352178ebeab073ad6499b03e938ebc30c4efaea63a26839d89c4b1da9d2640b0d937fc2bd1f049eb0a38def6fbe8a061b601292ae62fe079a410ce56e3a6 + languageName: node + linkType: hard + +"typed-array-byte-offset@npm:^1.0.4": + version: 1.0.5 + resolution: "typed-array-byte-offset@npm:1.0.5" + dependencies: + available-typed-arrays: ^1.0.7 + call-bind: ^1.0.9 + for-each: ^0.3.5 + gopd: ^1.2.0 + is-typed-array: ^1.1.15 + reflect.getprototypeof: ^1.0.10 + checksum: b314f3f1779b1f978704f72ed9ded61e7c8235f2712cfebae169312638c9e4736015516fb55c0ee9fe2d861e6111a9e44a782add667164477397a929e08e5fee + languageName: node + linkType: hard + +"typed-array-length@npm:^1.0.7": + version: 1.0.8 + resolution: "typed-array-length@npm:1.0.8" + dependencies: + call-bind: ^1.0.9 + for-each: ^0.3.5 + gopd: ^1.2.0 + is-typed-array: ^1.1.15 + possible-typed-array-names: ^1.1.0 + reflect.getprototypeof: ^1.0.10 + checksum: 612a90b6c86fed3aa8de7be20e05fd1fcdf4a5a0f2ce7a04da664b8f5b1ff2fb74a50f91d67dea9dbf2e526fbaac4046093fc0314af4e28533a7d1052d37fbd6 + languageName: node + linkType: hard + +"unbox-primitive@npm:^1.1.0": + version: 1.1.0 + resolution: "unbox-primitive@npm:1.1.0" + dependencies: + call-bound: ^1.0.3 + has-bigints: ^1.0.2 + has-symbols: ^1.1.0 + which-boxed-primitive: ^1.1.1 + checksum: 729f13b84a5bfa3fead1d8139cee5c38514e63a8d6a437819a473e241ba87eeb593646568621c7fc7f133db300ef18d65d1a5a60dc9c7beb9000364d93c581df + languageName: node + linkType: hard + +"which-boxed-primitive@npm:^1.0.2, which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1": + version: 1.1.1 + resolution: "which-boxed-primitive@npm:1.1.1" + dependencies: + is-bigint: ^1.1.0 + is-boolean-object: ^1.2.1 + is-number-object: ^1.1.1 + is-string: ^1.1.1 + is-symbol: ^1.1.1 + checksum: ee41d0260e4fd39551ad77700c7047d3d281ec03d356f5e5c8393fe160ba0db53ef446ff547d05f76ffabfd8ad9df7c9a827e12d4cccdbc8fccf9239ff8ac21e + languageName: node + linkType: hard + +"which-builtin-type@npm:^1.2.1": + version: 1.2.1 + resolution: "which-builtin-type@npm:1.2.1" + dependencies: + call-bound: ^1.0.2 + function.prototype.name: ^1.1.6 + has-tostringtag: ^1.0.2 + is-async-function: ^2.0.0 + is-date-object: ^1.1.0 + is-finalizationregistry: ^1.1.0 + is-generator-function: ^1.0.10 + is-regex: ^1.2.1 + is-weakref: ^1.0.2 + isarray: ^2.0.5 + which-boxed-primitive: ^1.1.0 + which-collection: ^1.0.2 + which-typed-array: ^1.1.16 + checksum: 7a3617ba0e7cafb795f74db418df889867d12bce39a477f3ee29c6092aa64d396955bf2a64eae3726d8578440e26777695544057b373c45a8bcf5fbe920bf633 + languageName: node + linkType: hard + +"which-collection@npm:^1.0.1, which-collection@npm:^1.0.2": + version: 1.0.2 + resolution: "which-collection@npm:1.0.2" + dependencies: + is-map: ^2.0.3 + is-set: ^2.0.3 + is-weakmap: ^2.0.2 + is-weakset: ^2.0.3 + checksum: c51821a331624c8197916598a738fc5aeb9a857f1e00d89f5e4c03dc7c60b4032822b8ec5696d28268bb83326456a8b8216344fb84270d18ff1d7628051879d9 + languageName: node + linkType: hard + +"which-typed-array@npm:^1.1.13, which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.19": + version: 1.1.23 + resolution: "which-typed-array@npm:1.1.23" + dependencies: + available-typed-arrays: ^1.0.7 + call-bind: ^1.0.9 + call-bound: ^1.0.4 + for-each: ^0.3.5 + get-proto: ^1.0.1 + gopd: ^1.2.0 + has-tostringtag: ^1.0.2 + checksum: 56b195916bbc59a3a91c3d2ab743a6d7dfa56a873ca7ce8dfcd629faf214bcb082fdcbc1a55e890948acc8c48d059b89ef9a7eea886beeb3e65067bc5182329a + languageName: node + linkType: hard + +"wrappy@npm:1": + version: 1.0.2 + resolution: "wrappy@npm:1.0.2" + checksum: 159da4805f7e84a3d003d8841557196034155008f817172d4e986bd591f74aa82aa7db55929a54222309e01079a65a92a9e6414da5a6aa4b01ee44a511ac3ee5 + languageName: node + linkType: hard