@qbix/q 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +678 -0
  3. package/dist/Metrics.js +2873 -0
  4. package/dist/Metrics.min.js +95 -0
  5. package/dist/Q.js +15677 -0
  6. package/dist/Q.min.js +385 -0
  7. package/dist/Q.minimal.js +11594 -0
  8. package/dist/Q.minimal.min.js +286 -0
  9. package/dist/handlebars-v4.0.10.min.js +29 -0
  10. package/dist/handlebars.minimal.min.js +1 -0
  11. package/dist/img/hints/rotate-left.gif +0 -0
  12. package/dist/img/hints/swipe-down.gif +0 -0
  13. package/dist/img/hints/swipe-up.gif +0 -0
  14. package/dist/img/hints/tap.gif +0 -0
  15. package/dist/img/throbbers/loading.gif +0 -0
  16. package/dist/jquery.minimal.min.js +18 -0
  17. package/dist/methods/Q/Audio/load.js +29 -0
  18. package/dist/methods/Q/Audio/loadVoices.js +36 -0
  19. package/dist/methods/Q/Audio/play.js +47 -0
  20. package/dist/methods/Q/Audio/speak.js +149 -0
  21. package/dist/methods/Q/Crypto/delegate.js +186 -0
  22. package/dist/methods/Q/Crypto/internalKeypair.js +170 -0
  23. package/dist/methods/Q/Crypto/sign.js +200 -0
  24. package/dist/methods/Q/Crypto/verify.js +212 -0
  25. package/dist/methods/Q/Crypto/verifyDelegated.js +214 -0
  26. package/dist/methods/Q/Data/Bloom/_internal.js +163 -0
  27. package/dist/methods/Q/Data/Bloom/create.js +23 -0
  28. package/dist/methods/Q/Data/Bloom/fromBase64.js +21 -0
  29. package/dist/methods/Q/Data/Bloom/fromBytes.js +15 -0
  30. package/dist/methods/Q/Data/Bloom/fromElements.js +33 -0
  31. package/dist/methods/Q/Data/Merkle/_internal.js +68 -0
  32. package/dist/methods/Q/Data/Merkle/build.js +29 -0
  33. package/dist/methods/Q/Data/Merkle/proof.js +50 -0
  34. package/dist/methods/Q/Data/Merkle/verify.js +32 -0
  35. package/dist/methods/Q/Data/Prolly/_internal.js +190 -0
  36. package/dist/methods/Q/Data/Prolly/build.js +28 -0
  37. package/dist/methods/Q/Data/Prolly/delete.js +28 -0
  38. package/dist/methods/Q/Data/Prolly/diff.js +70 -0
  39. package/dist/methods/Q/Data/Prolly/get.js +38 -0
  40. package/dist/methods/Q/Data/Prolly/set.js +32 -0
  41. package/dist/methods/Q/Data/compress.js +45 -0
  42. package/dist/methods/Q/Data/decompress.js +35 -0
  43. package/dist/methods/Q/Data/decrypt.js +55 -0
  44. package/dist/methods/Q/Data/derive.js +76 -0
  45. package/dist/methods/Q/Data/digest.js +29 -0
  46. package/dist/methods/Q/Data/encrypt.js +61 -0
  47. package/dist/methods/Q/Data/generateKey.js +40 -0
  48. package/dist/methods/Q/Data/hkdf.js +44 -0
  49. package/dist/methods/Q/Data/importKey.js +34 -0
  50. package/dist/methods/Q/Data/sign.js +42 -0
  51. package/dist/methods/Q/Data/verify.js +45 -0
  52. package/dist/methods/Q/Onboarding/handle.js +50 -0
  53. package/dist/methods/Q/Onboarding/start.js +165 -0
  54. package/dist/methods/Q/Onboarding/stop.js +21 -0
  55. package/dist/methods/Q/Sandbox/run.js +392 -0
  56. package/dist/methods/Q/Tool/define/component.js +218 -0
  57. package/dist/methods/Q/globalMemoryWalk.js +82 -0
  58. package/dist/methods/Q/leaves.js +34 -0
  59. package/dist/methods/Q/registerWebComponent.js +0 -0
  60. package/dist/methods/Q/sanitize.js +142 -0
  61. package/dist/test.html +6 -0
  62. package/dist/tools/Q/lazyload.js +433 -0
  63. package/package.json +26 -0
@@ -0,0 +1,186 @@
1
+ Q.exports(function (Q) {
2
+
3
+ /**
4
+ * Perform a cryptographic delegation ceremony.
5
+ *
6
+ * Delegation is capability-based:
7
+ * - A child secret is deterministically derived from a parent secret
8
+ * - The parent signs a typed delegation statement
9
+ * - The result can be verified and chained
10
+ *
11
+ * SECURITY MODEL:
12
+ * - rootSecret is never returned
13
+ * - secret is the sole bearer of delegated capability
14
+ * - Authority is proven by the parent's signature
15
+ * - context is treated as an opaque, signed string
16
+ *
17
+ * SIGNING FORMATS:
18
+ * - "ES256" → P-256 + SHA-256. Digest = SHA-256(canonical JSON payload).
19
+ * - "EIP712" → secp256k1 + keccak256 + full EIP-712 struct encoding.
20
+ * Domain: { name: "Q.Crypto", version: "1", salt: keccak256(label) }
21
+ * The label is in the domain salt, making cross-label signature
22
+ * replay structurally impossible at the cryptographic level.
23
+ *
24
+ * @static
25
+ * @method delegate
26
+ *
27
+ * @param {Object} options
28
+ * @param {Uint8Array} options.rootSecret Parent secret material.
29
+ * @param {String} options.label Delegation label (domain-separated).
30
+ * @param {String} [options.context] Opaque, signed context string.
31
+ * @param {String} [options.format='es256'] "ES256" or "EIP712".
32
+ *
33
+ * @return {Q.Promise<Object>}
34
+ * @return {String} return.label Delegation label.
35
+ * @return {String} return.context Signed context string.
36
+ * @return {Uint8Array} return.secret Delegated capability secret.
37
+ * @return {Object} return.statement The signed statement.
38
+ * @return {Object} return.proof Signed delegation proof.
39
+ */
40
+ return function Q_Crypto_delegate(options) {
41
+
42
+ return new Q.Promise(async function (resolve, reject) {
43
+
44
+ try {
45
+
46
+ if (!options) {
47
+ throw new Error("options required");
48
+ }
49
+
50
+ const rootSecret = options.rootSecret;
51
+ const label = options.label;
52
+ const context = options.context;
53
+ const format = options.format || "ES256";
54
+
55
+ if (!(rootSecret instanceof Uint8Array)) {
56
+ throw new Error("rootSecret must be Uint8Array");
57
+ }
58
+ if (typeof label !== "string" || !label.length) {
59
+ throw new Error("label required");
60
+ }
61
+ if (context !== undefined && typeof context !== "string") {
62
+ throw new Error("context must be a string if provided");
63
+ }
64
+
65
+ const normalizedContext = context || "";
66
+
67
+ // -------------------------------------------------
68
+ // Derive delegated capability secret
69
+ // -------------------------------------------------
70
+ const derivedSecret = await Q.Data.derive(
71
+ rootSecret,
72
+ "q.crypto.delegate." + label,
73
+ { size: 32 }
74
+ );
75
+
76
+ // -------------------------------------------------
77
+ // Derive parent keypair + identity
78
+ // -------------------------------------------------
79
+ const parentKp = await Q.Crypto.internalKeypair({
80
+ secret: rootSecret,
81
+ format: format
82
+ });
83
+
84
+ let parentIdentity;
85
+ let parentType;
86
+
87
+ if (format === "EIP712") {
88
+ // eip712: parent identity is the Ethereum address
89
+ parentIdentity = parentKp.address;
90
+ parentType = "address";
91
+ } else {
92
+ // es256: parent identity is SHA-256 of the public key
93
+ const pubBytes = Q.Data.toUint8Array(parentKp.publicKey);
94
+ const pubDigest = await Q.Data.digest("SHA-256", pubBytes);
95
+ parentIdentity = Q.Data.toHex(pubDigest);
96
+ parentType = "bytes32";
97
+ }
98
+
99
+ // -------------------------------------------------
100
+ // Compute secret hash
101
+ // eip712: keccak256(derivedSecret)
102
+ // es256: SHA-256(derivedSecret)
103
+ // -------------------------------------------------
104
+ const { keccak_256 } = await import(
105
+ Q.url("{{Q}}/src/js/crypto/sha3.js")
106
+ );
107
+
108
+ let secretHashHex;
109
+
110
+ if (format === "EIP712") {
111
+ secretHashHex = Q.Data.toHex(keccak_256(derivedSecret));
112
+ } else {
113
+ secretHashHex = Q.Data.toHex(
114
+ await Q.Data.digest("SHA-256", derivedSecret)
115
+ );
116
+ }
117
+
118
+ // -------------------------------------------------
119
+ // Construct delegation statement (protocol-fixed)
120
+ // -------------------------------------------------
121
+ const statement = {
122
+ parent: parentIdentity,
123
+ label: label,
124
+ issuedTime: Math.floor(Date.now() / 1000),
125
+ context: normalizedContext,
126
+ secretHash: secretHashHex
127
+ };
128
+
129
+ // -------------------------------------------------
130
+ // Build domain
131
+ // eip712: { name, version, salt } where
132
+ // salt = keccak256(utf8(label)) as bytes32.
133
+ // Placing the label in the domain salt makes
134
+ // cross-label replay impossible at the digest level.
135
+ // es256: domain is unused (empty object).
136
+ // -------------------------------------------------
137
+ const domain = format === "EIP712"
138
+ ? {
139
+ name: "Q.Crypto",
140
+ version: "1",
141
+ salt: Q.Data.toHex(keccak_256(new TextEncoder().encode(label)))
142
+ }
143
+ : {};
144
+
145
+ // -------------------------------------------------
146
+ // Sign via Q.Crypto.sign()
147
+ // eip712 path uses hashTypedData() from eip712.js.
148
+ // es256 path uses SHA-256(canonical JSON).
149
+ // -------------------------------------------------
150
+ const proof = await Q.Crypto.sign({
151
+ secret: rootSecret,
152
+ domain: domain,
153
+ message: statement,
154
+ types: {
155
+ EIP712Domain: [
156
+ { name: "name", type: "string" },
157
+ { name: "version", type: "string" },
158
+ { name: "salt", type: "bytes32" }
159
+ ],
160
+ Delegation: [
161
+ { name: "parent", type: parentType },
162
+ { name: "label", type: "string" },
163
+ { name: "issuedTime", type: "uint64" },
164
+ { name: "context", type: "string" },
165
+ { name: "secretHash", type: "bytes32" }
166
+ ]
167
+ },
168
+ primaryType: "Delegation",
169
+ format: format
170
+ });
171
+
172
+ resolve({
173
+ label: label,
174
+ context: normalizedContext,
175
+ secret: derivedSecret,
176
+ statement: statement,
177
+ proof: proof
178
+ });
179
+
180
+ } catch (e) {
181
+ reject(e);
182
+ }
183
+ });
184
+ };
185
+
186
+ });
@@ -0,0 +1,170 @@
1
+ Q.exports(function (Q) {
2
+
3
+ /**
4
+ * Deterministically derive a signing keypair from a secret.
5
+ *
6
+ * SECURITY INVARIANTS:
7
+ * - This is the ONLY place secrets become private keys
8
+ * - No randomness
9
+ * - No storage
10
+ * - Deterministic & reproducible
11
+ *
12
+ * Supported formats:
13
+ * - "eip712" → secp256k1 (noble-curves). Public key and Ethereum
14
+ * address derived entirely from noble, no ethers dependency.
15
+ * - "es256" → NIST P-256 (noble-curves).
16
+ *
17
+ * Derivation method:
18
+ * - eip712: seed = keccak256("q.crypto.k256.private-key" || secret),
19
+ * scalar = seed mod curveOrder.
20
+ * - es256: scalar = HKDF-SHA256(secret, "q.crypto.p256.private-key")
21
+ * via Q.Data.derive().
22
+ *
23
+ * @static
24
+ * @method internalKeypair
25
+ *
26
+ * @param {Object} options
27
+ * @param {Uint8Array} options.secret Raw secret material (32 bytes recommended).
28
+ * @param {String} [options.format="es256"] Signing format: "eip712" or "es256".
29
+ *
30
+ * @return {Q.Promise<Object>} Resolves to an object with the following properties:
31
+ *
32
+ * format {String} Normalized format: "eip712" or "es256".
33
+ * curve {String} Curve name: "secp256k1" or "p256".
34
+ * hashAlg {String} Hash algorithm: "keccak256" or "sha256".
35
+ * privateKey {Uint8Array} Raw private key scalar (32 bytes).
36
+ * Never log, store, or transmit this value.
37
+ * publicKey {Uint8Array} Raw uncompressed public key (65 bytes): 0x04 || X || Y.
38
+ *
39
+ * The following property is only present for "eip712":
40
+ * address {String} Ethereum address: lowercase "0x" + last 20 bytes of
41
+ * keccak256(publicKey[1..64]). Suitable for use
42
+ * as an EVM signer identity.
43
+ *
44
+ * @throws {Error} If secret is missing or not a Uint8Array.
45
+ * @throws {Error} If format is unsupported.
46
+ * @throws {Error} If derived private key scalar is zero (astronomically unlikely).
47
+ */
48
+ return function Q_Crypto_internalKeypair(options) {
49
+
50
+ return new Q.Promise(async function (resolve, reject) {
51
+
52
+ try {
53
+
54
+ if (!options || !(options.secret instanceof Uint8Array)) {
55
+ throw new Error("secret must be Uint8Array");
56
+ }
57
+
58
+ const secret = options.secret;
59
+ const format = options.format || "ES256";
60
+
61
+ // -------------------------------------------------
62
+ // EIP-712 / secp256k1 (noble-curves, no ethers)
63
+ // -------------------------------------------------
64
+ if (format === "EIP712") {
65
+
66
+ const [{ keccak_256 }, secp] = await Promise.all([
67
+ import(Q.url("{{Q}}/src/js/crypto/sha3.js")),
68
+ import(Q.url("{{Q}}/src/js/crypto/secp256k1.js"))
69
+ ]);
70
+
71
+ // Domain-separated deterministic seed:
72
+ // keccak256("q.crypto.k256.private-key" || secret)
73
+ const info = new TextEncoder().encode("q.crypto.k256.private-key");
74
+ const material = new Uint8Array(info.length + secret.length);
75
+ material.set(info, 0);
76
+ material.set(secret, info.length);
77
+
78
+ const digest = keccak_256(material); // Uint8Array(32)
79
+
80
+ // Reduce mod curve order — matches PHP implementation
81
+ const n = secp.secp256k1.CURVE.n;
82
+ const k = bytesToBigInt(digest) % n;
83
+
84
+ if (k === 0n) {
85
+ throw new Error("Derived invalid secp256k1 scalar");
86
+ }
87
+
88
+ // Private key as Uint8Array(32)
89
+ const privateKey = bigIntToBytes32(k);
90
+
91
+ // Uncompressed public key: 0x04 || X(32) || Y(32)
92
+ const publicKey = secp.secp256k1.getPublicKey(privateKey, false);
93
+
94
+ // Ethereum address: "0x" + last 20 bytes of keccak256(pubkey[1..])
95
+ const pubKeyBody = publicKey.slice(1); // drop 0x04 prefix
96
+ const addrHash = keccak_256(pubKeyBody); // Uint8Array(32)
97
+ const address = "0x" + Q.Data.toHex(addrHash.slice(12)); // last 20 bytes
98
+
99
+ resolve({
100
+ format: "eip712",
101
+ curve: "secp256k1",
102
+ hashAlg: "keccak256",
103
+ privateKey: privateKey, // Uint8Array(32)
104
+ publicKey: publicKey, // Uint8Array(65)
105
+ address: address // "0x..." lowercase
106
+ });
107
+
108
+ return;
109
+ }
110
+
111
+ // -------------------------------------------------
112
+ // es256 / P-256 (noble-curves)
113
+ // -------------------------------------------------
114
+ if (format === "ES256") {
115
+
116
+ const noble = await import(
117
+ Q.url("{{Q}}/src/js/crypto/nist.js")
118
+ );
119
+
120
+ // Deterministic scalar via HKDF-SHA256
121
+ const privateKey = await Q.Data.derive(
122
+ secret,
123
+ "q.crypto.p256.private-key",
124
+ { size: 32 }
125
+ ); // Uint8Array(32)
126
+
127
+ // Uncompressed public key: 0x04 || X(32) || Y(32)
128
+ const publicKey = noble.p256.getPublicKey(privateKey, false);
129
+
130
+ resolve({
131
+ format: "es256",
132
+ curve: "p256",
133
+ hashAlg: "sha256",
134
+ privateKey: privateKey, // Uint8Array(32)
135
+ publicKey: publicKey // Uint8Array(65)
136
+ });
137
+
138
+ return;
139
+ }
140
+
141
+ throw new Error("Unsupported format: " + format);
142
+
143
+ } catch (e) {
144
+ reject(e);
145
+ }
146
+ });
147
+ };
148
+
149
+ // -------------------------------------------------
150
+ // Module-private helpers
151
+ // -------------------------------------------------
152
+
153
+ function bytesToBigInt(bytes) {
154
+ let result = 0n;
155
+ for (let i = 0; i < bytes.length; i++) {
156
+ result = (result << 8n) | BigInt(bytes[i]);
157
+ }
158
+ return result;
159
+ }
160
+
161
+ function bigIntToBytes32(n) {
162
+ const hex = n.toString(16).padStart(64, "0");
163
+ const out = new Uint8Array(32);
164
+ for (let i = 0; i < 32; i++) {
165
+ out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
166
+ }
167
+ return out;
168
+ }
169
+
170
+ });
@@ -0,0 +1,200 @@
1
+ Q.exports(function (Q) {
2
+
3
+ /**
4
+ * Sign a typed message using a deterministically derived keypair.
5
+ *
6
+ * This is the JS counterpart of Q_Crypto::sign() in PHP.
7
+ * It performs protocol-level typed signing (not raw message signing).
8
+ *
9
+ * Supported formats:
10
+ * - "ES256" → NIST P-256 + SHA-256 (Q-native)
11
+ * - "EIP712" → secp256k1 + keccak256 (Ethereum-style, full EIP-712 encoding)
12
+ *
13
+ * SECURITY PROPERTIES:
14
+ * - Secret is never stored
15
+ * - Keypair derivation is deterministic
16
+ * - Exactly one hash + one signature
17
+ * - Output is a verifiable proof object
18
+ *
19
+ * @static
20
+ * @method sign
21
+ *
22
+ * @param {Object} options
23
+ * @param {Uint8Array} options.secret Raw binary secret material.
24
+ * @param {Object} options.message Typed message payload.
25
+ * @param {Object} options.types Type definitions (EIP-712 style).
26
+ * @param {String} options.primaryType Root type name.
27
+ * @param {Object} [options.domain={}] Domain separator fields.
28
+ * @param {String} [options.format="ES256"] "ES256" or "EIP712".
29
+ *
30
+ * @return {Q.Promise<Object>} Resolves to an object with the following properties:
31
+ *
32
+ * format {String} Resolved format: "eip712" or "es256".
33
+ * curve {String} Elliptic curve: "secp256k1" or "p256".
34
+ * hashAlg {String} Hash algorithm: "keccak256" or "sha256".
35
+ * domain {Object} Domain separator used.
36
+ * primaryType {String} Root type name.
37
+ * digest {String} Hex-encoded message digest.
38
+ * signature {Uint8Array} Raw signature bytes.
39
+ * EIP712: 65 bytes r||s||v (v = 27 + recovery).
40
+ * ES256: DER-encoded ECDSA signature.
41
+ * signatureHex {String} Hex-encoded signature (no 0x prefix).
42
+ * publicKey {Uint8Array} Raw uncompressed public key (65 bytes).
43
+ *
44
+ * EIP712 only:
45
+ * address {String} Ethereum address of signer ("0x...").
46
+ */
47
+ return function Q_Crypto_sign(options) {
48
+
49
+ return new Q.Promise(async function (resolve, reject) {
50
+
51
+ try {
52
+
53
+ if (!options || !(options.secret instanceof Uint8Array)) {
54
+ throw new Error("secret must be Uint8Array");
55
+ }
56
+ if (!options.message || typeof options.message !== "object") {
57
+ throw new Error("message required");
58
+ }
59
+ if (!options.types || typeof options.types !== "object") {
60
+ throw new Error("types required");
61
+ }
62
+ if (typeof options.primaryType !== "string") {
63
+ throw new Error("primaryType required");
64
+ }
65
+
66
+ const domain = options.domain || {};
67
+ const format = options.format || "ES256";
68
+
69
+ // -------------------------------------------------
70
+ // Derive keypair ONCE
71
+ // -------------------------------------------------
72
+ const kp = await Q.Crypto.internalKeypair({
73
+ secret: options.secret,
74
+ format: format
75
+ });
76
+
77
+ /* =================================================
78
+ * EIP712 (secp256k1 + keccak256)
79
+ * ================================================= */
80
+ if (format === "EIP712") {
81
+
82
+ const [{ hashTypedData }, secp] = await Promise.all([
83
+ import(Q.url("{{Q}}/src/js/crypto/eip712.js")),
84
+ import(Q.url("{{Q}}/src/js/crypto/secp256k1.js"))
85
+ ]);
86
+
87
+ // Single source of truth for EIP-712 digest —
88
+ // byte-identical to Q_Crypto_EIP712::hashTypedData() in PHP
89
+ const digestBytes = hashTypedData(
90
+ domain,
91
+ options.primaryType,
92
+ options.message,
93
+ options.types
94
+ );
95
+
96
+ const sig = secp.secp256k1.sign(digestBytes, kp.privateKey, {
97
+ recovered: true,
98
+ der: false
99
+ });
100
+
101
+ let compact, recovery;
102
+
103
+ if (Array.isArray(sig)) {
104
+ compact = sig[0];
105
+ recovery = sig[1];
106
+ } else {
107
+ compact = sig.signature;
108
+ recovery = sig.recovery;
109
+ }
110
+
111
+ if (!(compact instanceof Uint8Array)) {
112
+ compact = new Uint8Array(compact);
113
+ }
114
+
115
+ // Ethereum-style: r||s||v (v = 27 + recovery)
116
+ const signature = new Uint8Array(65);
117
+ signature.set(compact, 0);
118
+ signature[64] = 27 + recovery;
119
+
120
+ resolve({
121
+ format: "eip712",
122
+ curve: "secp256k1",
123
+ hashAlg: "keccak256",
124
+ domain: domain,
125
+ primaryType: options.primaryType,
126
+ digest: Q.Data.toHex(digestBytes),
127
+ signature: signature,
128
+ signatureHex: Q.Data.toHex(signature),
129
+ publicKey: kp.publicKey,
130
+ address: kp.address
131
+ });
132
+
133
+ return;
134
+ }
135
+
136
+ /* =================================================
137
+ * ES256 (P-256 + SHA-256)
138
+ * ================================================= */
139
+
140
+ // Canonical JSON payload — must match Q_Crypto::sign() in PHP exactly
141
+ const payload = {
142
+ domain: domain,
143
+ primaryType: options.primaryType,
144
+ types: options.types,
145
+ message: options.message
146
+ };
147
+
148
+ const canonical = Q.serialize(payload);
149
+ const msgBytes = new TextEncoder().encode(canonical);
150
+ const digestBytes = await Q.Data.digest("SHA-256", msgBytes);
151
+
152
+ const noble = await import(
153
+ Q.url("{{Q}}/src/js/crypto/nist.js")
154
+ );
155
+ const { encodeEcdsaDer } = await import(
156
+ Q.url("{{Q}}/src/js/crypto/encoder.js")
157
+ );
158
+
159
+ const sig = noble.p256
160
+ .sign(digestBytes, kp.privateKey)
161
+ .normalizeS();
162
+
163
+ const signatureDer = encodeEcdsaDer(
164
+ bigIntTo32Bytes(sig.r),
165
+ bigIntTo32Bytes(sig.s)
166
+ );
167
+
168
+ resolve({
169
+ format: "es256",
170
+ curve: "p256",
171
+ hashAlg: "sha256",
172
+ domain: domain,
173
+ primaryType: options.primaryType,
174
+ digest: Q.Data.toHex(digestBytes),
175
+ signature: signatureDer,
176
+ signatureHex: Q.Data.toHex(signatureDer),
177
+ publicKey: kp.publicKey
178
+ });
179
+
180
+ } catch (e) {
181
+ reject(e);
182
+ }
183
+ });
184
+ };
185
+
186
+ // -------------------------------------------------
187
+ // Module-private helper
188
+ // -------------------------------------------------
189
+
190
+ // BigInt → 32-byte Uint8Array (big-endian)
191
+ function bigIntTo32Bytes(n) {
192
+ const hex = n.toString(16).padStart(64, "0");
193
+ const out = new Uint8Array(32);
194
+ for (let i = 0; i < 32; i++) {
195
+ out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
196
+ }
197
+ return out;
198
+ }
199
+
200
+ });