first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
# `groups-1024`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
RFC 5054 Appendix A 1024-bit safe-prime group. Required by `@suaveplan/srp`'s conformance test suite because RFC 5054 Appendix B publishes test vectors only against this 1024-bit group (with SHA-1). Otherwise legacy — modern deployments SHOULD use the bundled 2048-bit group from `@suaveplan/srp/groups` or larger. This subpath is opt-in so consumers that only use the 2048-bit group do not pay for the 1024-bit modulus in their bundle.
## Features
- `N_1024` byte-exact to RFC 5054 Appendix A "1024-bit Group".
- `g_1024 = Uint8Array([2])`.
- `BIT_LENGTH` literal `1024`.
- Subpath-only — `import` from `@suaveplan/srp/groups/1024`.
## Usage: Basic Example
```ts
import { N_1024, g_1024, BIT_LENGTH } from "@suaveplan/srp/groups/1024";
console.log(N_1024.byteLength); // 128
console.log(BIT_LENGTH); // 1024
```
## Usage: Advanced Example
```ts
import { N_1024 } from "@suaveplan/srp/groups/1024";
import { nobleHashBridge } from "@suaveplan/srp/hash";
import type { SrpParams } from "@suaveplan/srp/params";
// Build a custom 1024/SHA-1 params object for replaying RFC 5054 Appendix B.
const params: SrpParams = {
N: N_1024,
g: new Uint8Array([2]),
bitLength: 1024,
hash: nobleHashBridge("SHA1"),
algorithm: "SHA1",
};
```
## API Reference
| Export | Type | Description |
| ------------ | ------------ | --------------------------------- |
| `N_1024` | `Uint8Array` | 128-byte 1024-bit modulus. |
| `g_1024` | `Uint8Array` | Single-byte generator `0x02`. |
| `BIT_LENGTH` | `1024` | Literal bit length. |
## Implementation Notes
The 1024-bit group is too small for production today (Pollard's rho on a 1024-bit safe-prime discrete log is within reach of nation-state actors, per modern guidance), so this subpath exists solely as a test-vector substrate. The package's spec-conformance test runs the full RFC 5054 Appendix B handshake against this group and asserts every intermediate (`k`, `x`, `v`, `u`, `S`, `K`, `M1`, `M2`) byte-exactly against the RFC's reference values. The 2048-bit group ships with no RFC test vector, so we use the 1024-bit run to anchor correctness and a locally-derived 2048/SHA-256 fixture to anchor the modern default.

View File

@@ -0,0 +1,56 @@
# `groups-1536`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
RFC 5054 Appendix A 1536-bit safe-prime group. Reachable via `@suaveplan/srp/groups/1536`. The 1536-bit group sits between the legacy 1024-bit floor and the modern 2048-bit default; it is rarely chosen for new deployments today but is documented in RFC 5054 for interoperability with older SRP deployments that pinned the size. Included here for completeness so a consumer migrating an existing SRP database does not have to vendor the modulus.
## Features
- `N_1536` byte-exact to RFC 5054 Appendix A "1536-bit Group".
- `g_1536 = Uint8Array([2])`.
- `BIT_LENGTH` literal `1536`.
- Subpath-only — tree-shaken out of every consumer that doesn't import it.
## Usage: Basic Example
```ts
import { N_1536, g_1536, BIT_LENGTH } from "@suaveplan/srp/groups/1536";
console.log(N_1536.byteLength); // 192
console.log(BIT_LENGTH); // 1536
```
## Usage: Advanced Example
```ts
import { N_1536 } from "@suaveplan/srp/groups/1536";
import { defaultHashBridge } from "@suaveplan/srp/hash";
import type { SrpParams } from "@suaveplan/srp/params";
const params: SrpParams = {
N: N_1536,
g: new Uint8Array([2]),
bitLength: 1536,
hash: defaultHashBridge("SHA256"),
algorithm: "SHA256",
};
```
## API Reference
| Export | Type | Description |
| ------------ | ------------ | -------------------------- |
| `N_1536` | `Uint8Array` | 192-byte 1536-bit modulus. |
| `g_1536` | `Uint8Array` | Generator `0x02`. |
| `BIT_LENGTH` | `1536` | Literal bit length. |
## Implementation Notes
The 1536-bit group is exported as a tree-shakeable subpath so the default 2048-bit consumer never pulls these 192 bytes into its bundle. The byte-exact correctness check lives in the spec-conformance suite, which scans every group module against the RFC 5054 Appendix A literals at test time.
A 1536-bit `modPow` is roughly 1.7x slower than the 2048-bit default at the language layer, but in practice falls within the same perceptual budget as the default (still under 50 ms on contemporary hardware for one handshake step). The size choice is appropriate for legacy interoperability — for greenfield deployments the 2048-bit group is preferred because it crosses the 112-bit symmetric-equivalent threshold by a comfortable margin while remaining cheaper than 3072 or 4096.
The generator `g = 2` matches every other small RFC 5054 group (1024, 1536, 2048); the larger groups (3072, 4096, 6144) use `g = 5` and 8192 uses `g = 19`. Consumers building custom `SrpParams` from these constants MUST use `g_1536` rather than hard-coding any other value, because the generator was chosen at RFC-design time to produce a large prime-order subgroup of `Z_N*` — substituting an arbitrary generator would weaken the discrete-log hardness.

View File

@@ -0,0 +1,55 @@
# `groups-3072`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
RFC 5054 Appendix A 3072-bit safe-prime group with generator `g = 5`. Reachable via `@suaveplan/srp/groups/3072`. A 3072-bit modulus crosses the ~128-bit symmetric-security threshold and is appropriate for deployments that anticipate long secret lifetimes (multi-year auth-token rotation) or that store the verifier database in an environment with a future-quantum-threat tolerance one step above the 2048-bit default.
## Features
- `N_3072` byte-exact to RFC 5054 Appendix A "3072-bit Group".
- `g_3072 = Uint8Array([5])` (RFC 5054 mandates `g = 5` for this size).
- `BIT_LENGTH` literal `3072`.
- Subpath-only — bundle-size cost paid only by importers.
## Usage: Basic Example
```ts
import { N_3072, g_3072, BIT_LENGTH } from "@suaveplan/srp/groups/3072";
console.log(N_3072.byteLength); // 384
console.log(g_3072[0]); // 5
console.log(BIT_LENGTH); // 3072
```
## Usage: Advanced Example
```ts
import { N_3072, g_3072 } from "@suaveplan/srp/groups/3072";
import { defaultHashBridge } from "@suaveplan/srp/hash";
import type { SrpParams } from "@suaveplan/srp/params";
const params: SrpParams = {
N: N_3072,
g: g_3072,
bitLength: 3072,
hash: defaultHashBridge("SHA256"),
algorithm: "SHA256",
};
```
## API Reference
| Export | Type | Description |
| ------------ | ------------ | -------------------------- |
| `N_3072` | `Uint8Array` | 384-byte 3072-bit modulus. |
| `g_3072` | `Uint8Array` | Generator `0x05`. |
| `BIT_LENGTH` | `3072` | Literal bit length. |
## Implementation Notes
Unlike the 1024/1536/2048-bit groups which all use `g = 2`, RFC 5054 specifies `g = 5` for 3072, 4096, 6144, and 8192-bit groups (the chosen generator depends on the modulus's quadratic-residue structure, picked at RFC-design time so `g` generates a large prime-order subgroup of `Z_N*`). Consumers building `SrpParams` from raw constants MUST use `g_3072` rather than hard-coding `0x02`.
`modPow` over a 3072-bit modulus takes roughly 4x the wall-clock time of the 2048-bit default; expect ~20–80 ms per handshake step. For interactive login this is still well under perceptual latency.

View File

@@ -0,0 +1,56 @@
# `groups-4096`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
RFC 5054 Appendix A 4096-bit safe-prime group with generator `g = 5`. Reachable via `@suaveplan/srp/groups/4096`. Suitable for deployments that want headroom above the contemporary 2048-bit default at the cost of roughly 8x the `modPow` wall-clock time. Recommended when verifier-database lifetime is measured in decades or when the threat model includes well-resourced precomputed-discrete-log attacks against bundled groups.
## Features
- `N_4096` byte-exact to RFC 5054 Appendix A "4096-bit Group".
- `g_4096 = Uint8Array([5])`.
- `BIT_LENGTH` literal `4096`.
- Subpath-only.
## Usage: Basic Example
```ts
import { N_4096, g_4096, BIT_LENGTH } from "@suaveplan/srp/groups/4096";
console.log(N_4096.byteLength); // 512
console.log(BIT_LENGTH); // 4096
```
## Usage: Advanced Example
```ts
import { N_4096, g_4096 } from "@suaveplan/srp/groups/4096";
import { defaultHashBridge } from "@suaveplan/srp/hash";
import type { SrpParams } from "@suaveplan/srp/params";
const params: SrpParams = {
N: N_4096,
g: g_4096,
bitLength: 4096,
hash: defaultHashBridge("SHA256"),
algorithm: "SHA256",
};
```
## API Reference
| Export | Type | Description |
| ------------ | ------------ | -------------------------- |
| `N_4096` | `Uint8Array` | 512-byte 4096-bit modulus. |
| `g_4096` | `Uint8Array` | Generator `0x05`. |
| `BIT_LENGTH` | `4096` | Literal bit length. |
## Implementation Notes
A 4096-bit `modPow` takes roughly 8x the time of a 2048-bit `modPow`. On a contemporary laptop this is ~40–160 ms per handshake step. Interactive login flows that target sub-200 ms perceptual budgets SHOULD pre-warm `modPow` (run a no-op call during page load) or schedule the handshake outside the critical path.
The byte-exact correctness gate lives in the spec-conformance suite. The constant is reviewed against RFC 5054 Appendix A's literal at every package release.
The generator `g = 5` is unique to the larger RFC 5054 groups (3072, 4096, 6144); the smaller groups (1024, 1536, 2048) all use `g = 2`. The chosen generator depends on the modulus's quadratic-residue structure — RFC 5054 picked it once at design time so `g` generates a large prime-order subgroup of `Z_N*`. Consumers building `SrpParams` from raw constants MUST use `g_4096` rather than hard-coding `0x02`; using the wrong generator weakens the discrete-log hardness guarantee that backs SRP-6a's zero-knowledge property.

View File

@@ -0,0 +1,56 @@
# `groups-6144`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
RFC 5054 Appendix A 6144-bit safe-prime group with generator `g = 5`. Reachable via `@suaveplan/srp/groups/6144`. Niche size, included for completeness with RFC 5054. Use only when interoperating with an existing deployment that pinned this size; modern greenfield deployments should choose 2048-bit or 4096-bit instead, since 6144 imposes ~27x the `modPow` cost of 2048 without a proportional security headroom over 4096.
## Features
- `N_6144` byte-exact to RFC 5054 Appendix A "6144-bit Group".
- `g_6144 = Uint8Array([5])`.
- `BIT_LENGTH` literal `6144`.
- Subpath-only.
## Usage: Basic Example
```ts
import { N_6144, g_6144, BIT_LENGTH } from "@suaveplan/srp/groups/6144";
console.log(N_6144.byteLength); // 768
console.log(BIT_LENGTH); // 6144
```
## Usage: Advanced Example
```ts
import { N_6144, g_6144 } from "@suaveplan/srp/groups/6144";
import { defaultHashBridge } from "@suaveplan/srp/hash";
import type { SrpParams } from "@suaveplan/srp/params";
const params: SrpParams = {
N: N_6144,
g: g_6144,
bitLength: 6144,
hash: defaultHashBridge("SHA256"),
algorithm: "SHA256",
};
```
## API Reference
| Export | Type | Description |
| ------------ | ------------ | -------------------------- |
| `N_6144` | `Uint8Array` | 768-byte 6144-bit modulus. |
| `g_6144` | `Uint8Array` | Generator `0x05`. |
| `BIT_LENGTH` | `6144` | Literal bit length. |
## Implementation Notes
A 6144-bit `modPow` is roughly 27x slower than the 2048-bit default and typically not worth the cost compared to either 4096 (similar headroom against precomputation, ~3x faster) or 8192 (significantly more headroom for the same wall-clock pain). This subpath exists to preserve RFC 5054 interoperability rather than to offer a recommended choice.
The generator `g = 5` matches the 3072 and 4096-bit groups; only 8192 uses a different generator (`g = 19`). The choice was fixed at RFC-design time to produce a large prime-order subgroup of `Z_N*` — substituting any other generator would weaken the discrete-log hardness that backs SRP-6a's zero-knowledge property. Consumers building custom `SrpParams` from raw constants MUST use `g_6144` rather than hard-coding any other value.
Interactive login flows that select the 6144-bit group SHOULD schedule the handshake off the perceptual critical path — for example, in a Web Worker or as a deferred background promise — because the per-step wall time will dominate any concurrent rendering work. The benchmark suite includes 6144-bit numbers for capacity planning but the package default remains the 2048-bit group, which sits at the contemporary security floor while keeping a `modPow` cheap enough for an interactive login.

View File

@@ -0,0 +1,55 @@
# `groups-8192`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
RFC 5054 Appendix A 8192-bit safe-prime group with generator `g = 19`. Reachable via `@suaveplan/srp/groups/8192`. The largest bundled group RFC 5054 specifies. Use only when verifier-database lifetime extends beyond the practical horizon of precomputed-discrete-log attacks against bundled primes; for almost every contemporary deployment the 2048-bit default is sufficient and the 8192-bit group's ~64x `modPow` slowdown is not justified.
## Features
- `N_8192` byte-exact to RFC 5054 Appendix A "8192-bit Group".
- `g_8192 = Uint8Array([19])` — RFC 5054 mandates `g = 19` for this size.
- `BIT_LENGTH` literal `8192`.
- Subpath-only.
## Usage: Basic Example
```ts
import { N_8192, g_8192, BIT_LENGTH } from "@suaveplan/srp/groups/8192";
console.log(N_8192.byteLength); // 1024
console.log(g_8192[0]); // 19
console.log(BIT_LENGTH); // 8192
```
## Usage: Advanced Example
```ts
import { N_8192, g_8192 } from "@suaveplan/srp/groups/8192";
import { defaultHashBridge } from "@suaveplan/srp/hash";
import type { SrpParams } from "@suaveplan/srp/params";
const params: SrpParams = {
N: N_8192,
g: g_8192,
bitLength: 8192,
hash: defaultHashBridge("SHA256"),
algorithm: "SHA256",
};
```
## API Reference
| Export | Type | Description |
| ------------ | ------------ | --------------------------- |
| `N_8192` | `Uint8Array` | 1024-byte 8192-bit modulus. |
| `g_8192` | `Uint8Array` | Generator `0x13` (19). |
| `BIT_LENGTH` | `8192` | Literal bit length. |
## Implementation Notes
An 8192-bit `modPow` typically costs hundreds of milliseconds on contemporary hardware. Interactive login flows MUST NOT use this group on the request critical path; instead schedule the handshake off the perceptual deadline (e.g. background-thread the auth call, show a placeholder for the duration). The benchmark suite skips 8192 by default because the per-iteration cost dominates the warmup statistics.
The generator `g = 19` is unique to this size in RFC 5054. Consumers building `SrpParams` from raw constants MUST use `g_8192` rather than hard-coding `0x02` or `0x05`.

View File

@@ -0,0 +1,58 @@
# `groups-2048`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
- [groups-1024](./1024/groups-1024.md) — <!-- begin:auto-docs --> _(no submodules)_- [1536](./1536/groups-1536.md) — <!-- begin:auto-docs --> _(no submodules)_ - [groups-1536](./1536/groups-1536.md) — <!-- begin:auto-docs --> _(no submodules)_- [3072](./3072/groups-3072.md) — <!-- begin:auto-docs --> _(no submodules)_ - [groups-3072](./3072/groups-3072.md) — <!-- begin:auto-docs --> _(no submodules)_- [4096](./4096/groups-4096.md) — <!-- begin:auto-docs --> _(no submodules)_ - [groups-4096](./4096/groups-4096.md) — <!-- begin:auto-docs --> _(no submodules)_- [6144](./6144/groups-6144.md) — <!-- begin:auto-docs --> _(no submodules)_ - [groups-6144](./6144/groups-6144.md) — <!-- begin:auto-docs --> _(no submodules)_- [8192](./8192/groups-8192.md) — <!-- begin:auto-docs --> _(no submodules)_ - [groups-8192](./8192/groups-8192.md) — <!-- begin:auto-docs --> _(no submodules)_- [groups-2048](./groups-2048.md) — <!-- begin:auto-docs --> _(no submodules)_- groups-1024 — <!-- begin:auto-docs --> _(no submodul…
- [groups-1024](./1024/groups-1024.md) — <!-- begin:auto-docs --> _(no submodules)_- [1536](./1536/groups-1536.md) — <!-- begin:auto-docs --> _(no submodules)_- [groups-1536](./1536/groups-1536.md) — <!-- begin:auto-docs --> _(no submodules)_- [3072](./3072/groups-3072.md) — <!-- begin:auto-docs --> _(no submodules)_- [groups-3072](./3072/groups-3072.md) — <!-- begin:auto-docs --> _(no submodules)_- [4096](./4096/groups-4096.md) — <!-- begin:auto-docs --> _(no submodules)_- [groups-4096](./4096/groups-4096.md) — <!-- begin:auto-docs --> _(no submodules)_- [6144](./6144/groups-6144.md) — <!-- begin:auto-docs --> _(no submodules)_- [groups-6144](./6144/groups-6144.md) — <!-- begin:auto-docs --> _(no submodules)_- [8192](./8192/groups-8192.md) — <!-- begin:auto-docs --> _(no submodules)_- [groups-8192](./8192/groups-8192.md) — <!-- begin:auto-docs --> _(no submodules)_- [groups-2048](./groups-2048.md) — <!-- begin:auto-docs --> _(no submodules)_- [groups-1024](./1024/groups-1024.md) — <!-- begin:auto-docs --> _(no submodules)_- [1536](./1536/groups-1536.md) — <!-- begin:auto-docs --> _(no submodules)_- [groups-1536](./1536/groups-1536.md) — <!-- begin:auto-docs --> _(no submodules)_- [3072](./3072/groups-3072.md) — <!-- begin:auto-docs --> _(no submodules)_- [groups-3072](./3072/groups-3072.md) — <!-- begin:auto-docs --> _(no submodules)_- [4096](./4096/groups-4096.md) — <!-- begin:auto-docs --> _(no submodules)_- [groups-4096](./4096/groups-4096.md) — <!-- begin:auto-docs --> _(no submodules)_- [6144](./6144/groups-6144.md) — <!-- begin:auto-docs --> _(no submodules)_- [groups-6144](./6144/groups-6144.md) — <!-- begin:auto-docs --> _(no submodules)_- [8192](./8192/groups-8192.md) — <!-- begin:auto-docs --> _(no submodules)_- [groups-8192](./8192/groups-8192.md) — <!-- begin:auto-docs --> _(no submodules)_- [groups-2048](./groups-2048.md) — <!-- begin:auto-docs --> _(no submodules)_- groups-1024 — <!-- begin:aut…
- [groups-1024](./1024/groups-1024.md) — <!-- begin:auto-docs --> _(no submodules)_- [1536](./1536/groups-1536.md) — <!-- begin:auto-docs --> _(no submodules)_- [groups-1536](./1536/groups-1536.md) — <!-- begin:auto-docs --> _(no submodules)_- [3072](./3072/groups-3072.md) — <!-- begin:auto-docs --> _(no submodules)_- [groups-3072](./3072/groups-3072.md) — <!-- begin:auto-docs --> _(no submodules)_- [4096](./4096/groups-4096.md) — <!-- begin:auto-docs --> _(no submodules)_- [groups-4096](./4096/groups-4096.md) — <!-- begin:auto-docs --> _(no submodules)_- [6144](./6144/groups-6144.md) — <!-- begin:auto-docs --> _(no submodules)_- [groups-6144](./6144/groups-6144.md) — <!-- begin:auto-docs --> _(no submodules)_- [8192](./8192/groups-8192.md) — <!-- begin:auto-docs --> _(no submodules)_- [groups-8192](./8192/groups-8192.md) — <!-- begin:auto-docs --> _(no submodules)_- [groups-2048](./groups-2048.md) — <!-- begin:auto-docs --> _(no submodules)_- groups-1024 — <!-- begin:auto-docs --> _(no submodu…
- [groups-1024](./1024/groups-1024.md) — <!-- begin:auto-docs --> _(no submodules)_- [1536](./1536/groups-1536.md) — <!-- begin:auto-docs --> _(no submodules)_ - [groups-1536](./1536/groups-1536.md) — <!-- begin:auto-docs --> _(no submodules)_- [3072](./3072/groups-3072.md) — <!-- begin:auto-docs --> _(no submodules)_ - [groups-3072](./3072/groups-3072.md) — <!-- begin:auto-docs --> _(no submodules)_- [4096](./4096/groups-4096.md) — <!-- begin:auto-docs --> _(no submodules)_ - [groups-4096](./4096/groups-4096.md) — <!-- begin:auto-docs --> _(no submodules)_- [6144](./6144/groups-6144.md) — <!-- begin:auto-docs --> _(no submodules)_ - [groups-6144](./6144/groups-6144.md) — <!-- begin:auto-docs --> _(no submodules)_- [8192](./8192/groups-8192.md) — <!-- begin:auto-docs --> _(no submodules)_ - [groups-8192](./8192/groups-8192.md) — <!-- begin:auto-docs --> _(no submodules)_- [groups-2048](./groups-2048.md) — <!-- begin:auto-docs --> _(no submodules)_
## Purpose
The bundled 2048-bit safe-prime group from RFC 5054 Appendix A. This is the default SRP-6a group every consumer of `@suaveplan/srp` gets without any additional import: `N_2048` is a 256-byte modulus, `g_2048` is the single byte `0x02`, and `BIT_LENGTH` is the literal `2048`. RFC 5054 deliberately bundles a small set of fixed safe primes to side-step trust-on-first-use over arbitrary moduli; we ship the 2048-bit one as the default because it sits at the contemporary security floor while keeping a 2048-bit `modPow` cheap enough (~5–20 ms) for an interactive login.
## Features
- `N_2048` byte-exact to RFC 5054 Appendix A "2048-bit Group" literal.
- `g_2048` equal to `Uint8Array([2])`.
- `BIT_LENGTH` literal-typed `2048`.
- Frozen `Uint8Array`s — consumers cannot mutate the shared constant.
- Reachable from the root `@suaveplan/srp` barrel AND from `@suaveplan/srp/groups`.
## Usage: Basic Example
```ts
import { N_2048, g_2048, BIT_LENGTH } from "@suaveplan/srp/groups";
console.log(N_2048.byteLength); // 256
console.log(g_2048[0]); // 2
console.log(BIT_LENGTH); // 2048
```
## Usage: Advanced Example
```ts
import { N_2048, g_2048 } from "@suaveplan/srp/groups";
import { defaultHashBridge } from "@suaveplan/srp/hash";
import { params2048Sha256 } from "@suaveplan/srp/params";
const params = params2048Sha256(defaultHashBridge("SHA256"));
// params.N is the same shared reference as N_2048.
// Use this params object for client + server handshakes.
```
## API Reference
| Export | Type | Description |
| ------------ | -------------- | -------------------------------------------------------------- |
| `N_2048` | `Uint8Array` | 256-byte modulus from RFC 5054 Appendix A. |
| `g_2048` | `Uint8Array` | Single-byte generator `0x02`. |
| `BIT_LENGTH` | `2048` | Literal bit length of `N_2048`. |
## Implementation Notes
The modulus is encoded as a hex literal in source rather than embedded as a base64 blob so a human reader can diff it against RFC 5054 Appendix A line-by-line. The hex string is split into eight 64-char chunks matching the eight rows the RFC prints. The byte-exact unit test (`groups-2048.test.ts`) reconstructs the same hex from the `Uint8Array`, uppercases it, and asserts equality against the same literal.
`g_2048` is `Uint8Array([2])` rather than a `bigint` because every place SRP mixes group elements into a hash uses `PAD(g)` over the group modulus; expressing `g` as a one-byte buffer up-front avoids a runtime `bigint`-to-bytes conversion at every hash site. The single-byte representation also matches the byte-shape every `Uint8Array`-keyed helper in this package expects.
The `BIT_LENGTH` literal is typed `2048` (not `number`) so consumers narrowing on the constant get a literal type back. This matters in test files where we assert that the bundled group is exactly the RFC's 2048-bit choice and not, say, a future 3072-bit upgrade.