@monolythium/core-sdk 0.3.15 → 0.4.0

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.
@@ -1,5 +1,5 @@
1
- import { I as MlDsa65Backend } from '../submission-BeEYbbGi.js';
2
- export { iw as ADDRESS_DERIVATION_DOMAIN, ix as DKG_AEAD_TAG_LEN, iy as DKG_NONCE_LEN, iz as DecryptHint, iA as ENCRYPTED_SUBMISSION_UNAVAILABLE_MESSAGE, iB as ENUM_VARIANT_INDEX_ML_DSA_65, iC as EncryptedEnvelope, iD as EncryptedSubmission, E as EncryptionKey, iE as ML_DSA_65_PUBLIC_KEY_LEN, iF as ML_DSA_65_SEED_LEN, iG as ML_DSA_65_SIGNATURE_LEN, iH as ML_DSA_65_SIGNING_KEY_LEN, iI as ML_KEM_768_CIPHERTEXT_LEN, iJ as ML_KEM_768_ENCAPSULATION_KEY_LEN, iK as ML_KEM_768_SHARED_SECRET_LEN, F as MempoolClass, D as NativeEvmTxFields, iL as NativeTxExtension, iM as NativeTxExtensionDescriptor, iN as NativeTxExtensionLike, iO as NonceAad, iP as PlaintextSubmission, iQ as STANDARD_ALGO_NUMBER_ML_DSA_65, iR as bincodeDecryptHint, iS as bincodeEncryptedEnvelope, iT as bincodeNonceAad, iU as bincodeSignedTransaction, iV as buildEncryptedEnvelope, iW as buildEncryptedSubmission, iX as buildPlaintextSubmission, iY as encodeMlDsa65Opaque, iZ as encodeTransactionForHash, i_ as encryptInnerTx, i$ as fetchEncryptionKey, j0 as mlDsa65AddressBytes, j1 as mlDsa65AddressFromPublicKey, j2 as outerSigDigest, j3 as submitEncryptedEnvelope, j4 as submitPlaintextTransaction, j5 as submitTransactionWithPrivacy } from '../submission-BeEYbbGi.js';
1
+ import { I as MlDsa65Backend, H as RpcClient, iw as NonceAad } from '../submission-CKXlYstD.js';
2
+ export { ix as ADDRESS_DERIVATION_DOMAIN, iy as DKG_AEAD_TAG_LEN, iz as DKG_NONCE_LEN, iA as DecryptHint, iB as ENCRYPTED_SUBMISSION_UNAVAILABLE_MESSAGE, iC as ENUM_VARIANT_INDEX_ML_DSA_65, iD as EncryptedEnvelope, iE as EncryptedSubmission, E as EncryptionKey, iF as ML_DSA_65_PUBLIC_KEY_LEN, iG as ML_DSA_65_SEED_LEN, iH as ML_DSA_65_SIGNATURE_LEN, iI as ML_DSA_65_SIGNING_KEY_LEN, iJ as ML_KEM_768_CIPHERTEXT_LEN, iK as ML_KEM_768_ENCAPSULATION_KEY_LEN, iL as ML_KEM_768_SHARED_SECRET_LEN, F as MempoolClass, D as NativeEvmTxFields, iM as NativeTxExtension, iN as NativeTxExtensionDescriptor, iO as NativeTxExtensionLike, iP as PlaintextSubmission, iQ as STANDARD_ALGO_NUMBER_ML_DSA_65, iR as bincodeDecryptHint, iS as bincodeEncryptedEnvelope, iT as bincodeNonceAad, iU as bincodeSignedTransaction, iV as buildEncryptedEnvelope, iW as buildEncryptedSubmission, iX as buildPlaintextSubmission, iY as encodeMlDsa65Opaque, iZ as encodeTransactionForHash, i_ as encryptInnerTx, i$ as fetchEncryptionKey, j0 as mlDsa65AddressBytes, j1 as mlDsa65AddressFromPublicKey, j2 as outerSigDigest, j3 as submitEncryptedEnvelope, j4 as submitPlaintextTransaction, j5 as submitTransactionWithPrivacy } from '../submission-CKXlYstD.js';
3
3
 
4
4
  declare class BincodeWriter {
5
5
  #private;
@@ -51,4 +51,253 @@ declare function pqm1MnemonicToMlDsa65Backend(mnemonic: string): MlDsa65Backend;
51
51
  declare function pqm1MnemonicToAddress(mnemonic: string): string;
52
52
  declare function generatePqm1Mnemonic(rng?: Pqm1Rng): string;
53
53
 
54
- export { BincodeWriter, MlDsa65Backend, PQM1_ALGO_TAG_FALCON512_RESERVED, PQM1_ALGO_TAG_MLDSA65, PQM1_ALGO_TAG_MLDSA87_RESERVED, PQM1_ALGO_TAG_SLHDSA128S_RESERVED, PQM1_ENTROPY_LEN, PQM1_PAYLOAD_LEN, PQM1_V1_MLDSA65_DOMAIN_TAG, PQM1_V1_MNEMONIC_WORDS, PQM1_VERSION_V1, Pqm1Error, type Pqm1ErrorKind, type Pqm1Payload, type Pqm1Rng, assemblePqm1Payload, bytesToHex, concatBytes, derivePqm1MlDsa65SeedFromPayload, expectBytes, generatePqm1Mnemonic, hexToBytes, parsePqm1Payload, pqm1MnemonicToAddress, pqm1MnemonicToMlDsa65Backend, pqm1MnemonicToMlDsa65Seed, pqm1MnemonicToPayload, pqm1PayloadToMnemonic };
54
+ /**
55
+ * LythiumSeal scheme-3 client-side seal primitive.
56
+ *
57
+ * Post-quantum cluster-threshold encrypted-mempool sealing:
58
+ * cluster-ML-KEM-768 (FIPS-203) + information-theoretic GF(256) Shamir
59
+ * `t`-of-`n` + committing ChaCha20-Poly1305 (with an explicit SHAKE256
60
+ * key-commitment). A signed transaction body is sealed to a committee of
61
+ * `n` operators such that any `t` of them, each holding only its own
62
+ * ML-KEM decapsulation key, must cooperate to recover the plaintext. No
63
+ * single operator (and no minority of `< t`) can read the body.
64
+ *
65
+ * This is a byte-exact port of the standalone `lythiumseal` Rust crate
66
+ * (github.com/monolythium/lythiumseal) plus the chain-side
67
+ * `LythiumSealEnvelope` wire shape from `mono-core`'s mempool
68
+ * (`seal_to_cluster`). Byte-compatibility is proven by a cross-language
69
+ * KAT (`tests/lythiumseal-kat.test.ts`) against vectors generated from the
70
+ * Rust reference: the same fixed roster + deterministic draw order
71
+ * reproduces the exact envelope bincode bytes the chain accepts, and a
72
+ * Rust-sealed envelope round-trips through the TS decoder.
73
+ *
74
+ * The cryptography is standardized: ML-KEM-768 from `@noble/post-quantum`,
75
+ * ChaCha20-Poly1305 from `@noble/ciphers`, and SHAKE256 from
76
+ * `@noble/hashes`. The GF(256) Shamir layer is the AES field (reduction
77
+ * polynomial 0x11b) implemented in-module to match the crate exactly.
78
+ */
79
+ /** ML-KEM-768 encapsulation-key byte length. */
80
+ declare const SEAL_EK_LEN = 1184;
81
+ /** ML-KEM-768 decapsulation-key byte length. */
82
+ declare const SEAL_DK_LEN = 2400;
83
+ /** ML-KEM-768 ciphertext byte length. */
84
+ declare const SEAL_KEM_CT_LEN = 1088;
85
+ /** ML-KEM-768 keygen seed length (`d || z`, FIPS-203). */
86
+ declare const SEAL_KEM_SEED_LEN = 64;
87
+ /** AEAD key length (ChaCha20-Poly1305 / body key). */
88
+ declare const SEAL_KEY_LEN = 32;
89
+ /** AEAD nonce length (96-bit). */
90
+ declare const SEAL_NONCE_LEN = 12;
91
+ /** Poly1305 tag length. */
92
+ declare const SEAL_TAG_LEN = 16;
93
+ /** Explicit SHAKE256 key-commitment length. */
94
+ declare const SEAL_COMMIT_LEN = 32;
95
+ /** Shamir share wire length (`index || value`). */
96
+ declare const SEAL_SHARE_LEN: number;
97
+ /** Scheme selector for the cluster-ML-KEM + Shamir threshold body. */
98
+ declare const CLUSTER_MLKEM_SHAMIR = 3;
99
+ /**
100
+ * Random source for a seal: fills `dest` with random bytes. Production
101
+ * callers pass a CSPRNG-backed source ({@link cryptoRandomSource}); the
102
+ * KAT passes a deterministic source so the seal bytes are reproducible.
103
+ *
104
+ * Each call must consume the source the same way the Rust reference does:
105
+ * the deterministic source models `rand_core`'s `fill_bytes`, which fills
106
+ * in 8-byte chunks (one `u64` per chunk) and discards the unused tail of
107
+ * the final chunk of each call.
108
+ */
109
+ interface SealRandomSource {
110
+ fillBytes(dest: Uint8Array): void;
111
+ }
112
+ /** CSPRNG-backed source (WebCrypto). The default for production seals. */
113
+ declare function cryptoRandomSource(): SealRandomSource;
114
+ interface CommittingBody {
115
+ nonce: Uint8Array;
116
+ ct: Uint8Array;
117
+ commitment: Uint8Array;
118
+ }
119
+ /**
120
+ * `keccak256(domain || cluster_id_le || t || n || concat(idx || ek)...)`.
121
+ * Commits to the exact recipient ek set + order. Operators and wallets
122
+ * MUST compute it identically; this is the single canonical site.
123
+ *
124
+ * keccak256 is taken from the ml-dsa module's hash import to avoid a second
125
+ * keccak dependency; passed in by the caller to keep this module
126
+ * cipher-only.
127
+ */
128
+ declare function sealRosterHash(keccak256: (input: Uint8Array) => Uint8Array, clusterId: number, t: number, n: number, roster: ReadonlyArray<{
129
+ operatorIndex: number;
130
+ ek: Uint8Array;
131
+ }>): Uint8Array;
132
+ /** One recipient slot in the scheme-3 envelope. */
133
+ interface SealRecipient {
134
+ operatorIndex: number;
135
+ kemCt: Uint8Array;
136
+ wrapped: CommittingBody;
137
+ }
138
+ /**
139
+ * Scheme-3 LythiumSeal envelope - the encrypted-tx body for the
140
+ * cluster-ML-KEM + Shamir threshold path. Bincode-encodes into the bytes
141
+ * that ride inside `EncryptedEnvelope.ciphertext`.
142
+ */
143
+ interface LythiumSealEnvelope {
144
+ clusterId: number;
145
+ epoch: bigint;
146
+ rosterHash: Uint8Array;
147
+ t: number;
148
+ n: number;
149
+ aeadBody: CommittingBody;
150
+ recipients: SealRecipient[];
151
+ }
152
+ /**
153
+ * Bincode-encode (bincode 1.3 defaults: LE fixint, `u64` length prefixes,
154
+ * raw fixed-size arrays) the envelope into the `EncryptedEnvelope.ciphertext`
155
+ * body bytes. Byte-identical to `LythiumSealEnvelope::encode` in mono-core.
156
+ */
157
+ declare function encodeSealEnvelope(env: LythiumSealEnvelope): Uint8Array;
158
+ /**
159
+ * Seal `plaintext` to the cluster's ordered `recipientEks` (`n` operators)
160
+ * at reconstruction threshold `t`, bound to `(clusterId, epoch,
161
+ * rosterHash)`. Draws a fresh body key for every call (nonce safety rests
162
+ * on body-key freshness, not nonce uniqueness - see the crate invariants),
163
+ * GF(256) Shamir `t`-of-`n` splits it, and ML-KEM-encapsulates one share
164
+ * to each operator's encapsulation key under a KDF-bound member KEK.
165
+ *
166
+ * The result is the `LythiumSealEnvelope` (scheme 3) that nests inside the
167
+ * outer `EncryptedEnvelope.ciphertext`. Recovering the plaintext requires
168
+ * `t` operators to each decapsulate their own slot; no single operator can.
169
+ *
170
+ * @param rng deterministic source for the KAT; defaults to a CSPRNG.
171
+ */
172
+ declare function sealToCluster(args: {
173
+ plaintext: Uint8Array;
174
+ recipientEks: ReadonlyArray<Uint8Array>;
175
+ t: number;
176
+ clusterId: number;
177
+ epoch: bigint;
178
+ rosterHash: Uint8Array;
179
+ rng?: SealRandomSource;
180
+ }): LythiumSealEnvelope;
181
+
182
+ /**
183
+ * Client-side scheme-3 LythiumSeal seal path for the wallet/SDK.
184
+ *
185
+ * `getClusterSealKeys` reads the cluster seal roster (per-operator ML-KEM-768
186
+ * encapsulation keys + `(t, n)` + roster hash + epoch). `sealTransaction`
187
+ * turns a signed inner transaction into the scheme-3 `LythiumSealEnvelope`,
188
+ * wraps it in an `EncryptedEnvelope` with the outer ML-DSA-65 signature, and
189
+ * yields the wire bytes mono-core's `lyth_submitEncrypted` accepts.
190
+ *
191
+ * Byte-compatibility with the chain is proven by the cross-language KAT in
192
+ * `tests/lythiumseal-kat.test.ts`.
193
+ */
194
+
195
+ /** Algorithm tag the node serves for the scheme-3 seal path. */
196
+ declare const CLUSTER_MLKEM_SHAMIR_ALGO = "cluster-mlkem768-shamir";
197
+ /**
198
+ * The cluster seal roster the SDK seals a transaction body to.
199
+ *
200
+ * Built from the `lyth_getClusterSealKeys(clusterId)` RPC response (or read
201
+ * from genesis when that RPC is disabled on the public profile): the ordered
202
+ * per-operator ML-KEM-768 encapsulation keys + the `(t, n)` threshold + the
203
+ * roster hash + the epoch.
204
+ */
205
+ interface ClusterSealKeys {
206
+ algo: string;
207
+ clusterId: number;
208
+ epoch: bigint;
209
+ /** 32-byte roster hash the seal context binds. */
210
+ rosterHash: Uint8Array;
211
+ /** Reconstruction threshold `t`. */
212
+ t: number;
213
+ /** Total operators `n`. */
214
+ n: number;
215
+ /** Per-operator 1184-byte ML-KEM-768 encapsulation keys, ordered `1..=n`. */
216
+ recipientEks: Uint8Array[];
217
+ }
218
+ /** One operator's entry in a roster source (RPC JSON or genesis). */
219
+ interface ClusterSealKeyEntryInput {
220
+ operatorIndex: number;
221
+ /** `0x`-hex of the operator's 1184-byte ML-KEM-768 encapsulation key. */
222
+ mlKemEk: string;
223
+ }
224
+ /** A cluster seal roster as served by the RPC or read from genesis. */
225
+ interface ClusterSealKeysSource {
226
+ algo?: string;
227
+ clusterId: number;
228
+ epoch: number | string | bigint;
229
+ /** `0x`-hex of the 32-byte roster hash (optional; recomputed + verified). */
230
+ rosterHash?: string;
231
+ t: number;
232
+ n: number;
233
+ roster: ClusterSealKeyEntryInput[];
234
+ }
235
+ /**
236
+ * Normalize a roster source into the typed {@link ClusterSealKeys} the SDK
237
+ * seals against. The roster hash is RECOMPUTED from the ordered ek set via
238
+ * the canonical `seal_roster_hash` and, when the source carries one, the
239
+ * recomputed value must match - so a wallet can never seal under a roster
240
+ * hash that does not commit to the exact recipient set it is sealing to.
241
+ *
242
+ * @throws if the roster is empty, an ek has the wrong length, the operator
243
+ * indices are not the contiguous `1..=n` order, the threshold is out of
244
+ * `2 <= t <= n`, or a supplied roster hash does not match the recomputed one.
245
+ */
246
+ declare function parseClusterSealKeys(source: ClusterSealKeysSource): ClusterSealKeys;
247
+ /**
248
+ * Fetch the cluster seal roster from a running node via
249
+ * `lyth_getClusterSealKeys(clusterId)`.
250
+ *
251
+ * NOTE: this RPC is DISABLED on the public node profile. When it returns
252
+ * "method not found" / "unavailable", read the roster from genesis instead
253
+ * and pass it through {@link parseClusterSealKeys} - the roster lives in the
254
+ * genesis `[[clusters.members]]` `seal_ek` fields, which is exactly what the
255
+ * RPC would otherwise serve.
256
+ *
257
+ * @throws if the RPC is unavailable (carry the roster as input instead) or
258
+ * the served roster does not validate.
259
+ */
260
+ declare function getClusterSealKeys(client: RpcClient, clusterId?: number): Promise<ClusterSealKeys>;
261
+ /** A built scheme-3 encrypted submission, ready for `lyth_submitEncrypted`. */
262
+ interface SealedSubmission {
263
+ /** Bincode `EncryptedEnvelope` wire bytes, `0x`-prefixed hex. */
264
+ envelopeWireHex: string;
265
+ /** Bincode `EncryptedEnvelope` wire bytes. */
266
+ envelopeWireBytes: Uint8Array;
267
+ /** Length of the inner scheme-3 ciphertext body in bytes. */
268
+ ciphertextBytes: number;
269
+ }
270
+ /**
271
+ * Seal a signed inner transaction to the cluster and wrap it in an
272
+ * `EncryptedEnvelope` with the outer ML-DSA-65 signature.
273
+ *
274
+ * `signedTxBincode` is the bincode `SignedTransaction` wire bytes (the body
275
+ * `mesh_submitTx` would otherwise carry in the clear). `aad` is the
276
+ * authenticated envelope header; per Law §3.6 / R3-H08 its fee fields MUST
277
+ * mirror the inner tx's fee fields exactly, so the chain's `verify_inner_match`
278
+ * passes on reveal - the caller is responsible for building it from the same
279
+ * fields it signed.
280
+ *
281
+ * The outer signature is taken over the canonical preimage
282
+ * `keccak256(bincode(aad) || ciphertext || bincode(hint) || sender_pubkey)`,
283
+ * identical to mono-core's `EncryptedEnvelope::signed_digest`.
284
+ *
285
+ * @param rng deterministic source for the KAT; defaults to a CSPRNG.
286
+ */
287
+ declare function sealTransaction(args: {
288
+ signedTxBincode: Uint8Array;
289
+ clusterSealKeys: ClusterSealKeys;
290
+ aad: NonceAad;
291
+ senderAddress: Uint8Array;
292
+ senderPubkey: Uint8Array;
293
+ signOuterDigest: (digest: Uint8Array) => Promise<Uint8Array> | Uint8Array;
294
+ rng?: SealRandomSource;
295
+ }): Promise<SealedSubmission>;
296
+ /**
297
+ * Submit a built scheme-3 encrypted envelope through `lyth_submitEncrypted`.
298
+ *
299
+ * @returns the mempool tx hash the node assigns on admission.
300
+ */
301
+ declare function submitSealedTransaction(client: RpcClient, submission: SealedSubmission): Promise<string>;
302
+
303
+ export { BincodeWriter, CLUSTER_MLKEM_SHAMIR, CLUSTER_MLKEM_SHAMIR_ALGO, type ClusterSealKeyEntryInput, type ClusterSealKeys, type ClusterSealKeysSource, type LythiumSealEnvelope, MlDsa65Backend, NonceAad, PQM1_ALGO_TAG_FALCON512_RESERVED, PQM1_ALGO_TAG_MLDSA65, PQM1_ALGO_TAG_MLDSA87_RESERVED, PQM1_ALGO_TAG_SLHDSA128S_RESERVED, PQM1_ENTROPY_LEN, PQM1_PAYLOAD_LEN, PQM1_V1_MLDSA65_DOMAIN_TAG, PQM1_V1_MNEMONIC_WORDS, PQM1_VERSION_V1, Pqm1Error, type Pqm1ErrorKind, type Pqm1Payload, type Pqm1Rng, SEAL_COMMIT_LEN, SEAL_DK_LEN, SEAL_EK_LEN, SEAL_KEM_CT_LEN, SEAL_KEM_SEED_LEN, SEAL_KEY_LEN, SEAL_NONCE_LEN, SEAL_SHARE_LEN, SEAL_TAG_LEN, type SealRandomSource, type SealRecipient, type SealedSubmission, assemblePqm1Payload, bytesToHex, concatBytes, cryptoRandomSource, derivePqm1MlDsa65SeedFromPayload, encodeSealEnvelope, expectBytes, generatePqm1Mnemonic, getClusterSealKeys, hexToBytes, parseClusterSealKeys, parsePqm1Payload, pqm1MnemonicToAddress, pqm1MnemonicToMlDsa65Backend, pqm1MnemonicToMlDsa65Seed, pqm1MnemonicToPayload, pqm1PayloadToMnemonic, sealRosterHash, sealToCluster, sealTransaction, submitSealedTransaction };
@@ -575,7 +575,322 @@ function bytesEqual(a, b) {
575
575
  }
576
576
  return true;
577
577
  }
578
+ var SEAL_EK_LEN = 1184;
579
+ var SEAL_DK_LEN = 2400;
580
+ var SEAL_KEM_CT_LEN = 1088;
581
+ var SEAL_KEM_SEED_LEN = 64;
582
+ var SEAL_KEY_LEN = 32;
583
+ var SEAL_NONCE_LEN = 12;
584
+ var SEAL_TAG_LEN = 16;
585
+ var SEAL_COMMIT_LEN = 32;
586
+ var SEAL_SECRET_LEN = 32;
587
+ var SEAL_SHARE_LEN = 1 + SEAL_SECRET_LEN;
588
+ var CLUSTER_MLKEM_SHAMIR = 3;
589
+ var COMMIT_DOMAIN = new TextEncoder().encode("lythiumseal/commit/v1");
590
+ var KEK_DOMAIN = new TextEncoder().encode("lythiumseal/kek/v1");
591
+ var NONCE_DOMAIN = new TextEncoder().encode("lythiumseal/nonce/v1");
592
+ var BODY_AAD_DOMAIN = new TextEncoder().encode("lythiumseal/body/v1");
593
+ var SHARE_AAD_DOMAIN = new TextEncoder().encode("lythiumseal/share/v1");
594
+ var ROSTER_DOMAIN = new TextEncoder().encode("lythiumseal/roster/v1");
595
+ function cryptoRandomSource() {
596
+ return {
597
+ fillBytes(dest) {
598
+ crypto.getRandomValues(dest);
599
+ }
600
+ };
601
+ }
602
+ function u32le(n) {
603
+ const out = new Uint8Array(4);
604
+ out[0] = n & 255;
605
+ out[1] = n >>> 8 & 255;
606
+ out[2] = n >>> 16 & 255;
607
+ out[3] = n >>> 24 & 255;
608
+ return out;
609
+ }
610
+ function u64le(n) {
611
+ const out = new Uint8Array(8);
612
+ let v = n;
613
+ for (let i = 0; i < 8; i++) {
614
+ out[i] = Number(v & 0xffn);
615
+ v >>= 8n;
616
+ }
617
+ return out;
618
+ }
619
+ function framed(field) {
620
+ return concatBytes(u32le(field.length), field);
621
+ }
622
+ function keyCommitment(key) {
623
+ return shake256(concatBytes(framed(COMMIT_DOMAIN), key), { dkLen: SEAL_COMMIT_LEN });
624
+ }
625
+ function deriveKek(sharedSecret, domain, clusterId, epoch, opIndex) {
626
+ const input = concatBytes(
627
+ framed(KEK_DOMAIN),
628
+ framed(sharedSecret),
629
+ framed(domain),
630
+ u32le(clusterId),
631
+ u64le(epoch),
632
+ Uint8Array.of(opIndex)
633
+ );
634
+ return shake256(input, { dkLen: SEAL_KEY_LEN });
635
+ }
636
+ function deriveNonce(domain, context) {
637
+ const input = concatBytes(framed(NONCE_DOMAIN), framed(domain), framed(context));
638
+ return shake256(input, { dkLen: SEAL_NONCE_LEN });
639
+ }
640
+ function bodyAad(ctx, k, n) {
641
+ return concatBytes(
642
+ BODY_AAD_DOMAIN,
643
+ u32le(ctx.clusterId),
644
+ u64le(ctx.epoch),
645
+ Uint8Array.of(k),
646
+ Uint8Array.of(n),
647
+ ctx.rosterHash
648
+ );
649
+ }
650
+ function shareAad(ctx, opIndex) {
651
+ return concatBytes(
652
+ SHARE_AAD_DOMAIN,
653
+ u32le(ctx.clusterId),
654
+ u64le(ctx.epoch),
655
+ Uint8Array.of(opIndex),
656
+ ctx.rosterHash
657
+ );
658
+ }
659
+ function aeadSeal(key, nonce, plaintext, aad) {
660
+ const cipher = chacha20poly1305(key, nonce, aad);
661
+ const ct = cipher.encrypt(plaintext);
662
+ return { nonce, ct, commitment: keyCommitment(key) };
663
+ }
664
+ function gfMul(a, b) {
665
+ let product = 0;
666
+ let x = a & 255;
667
+ let y = b & 255;
668
+ for (let i = 0; i < 8; i++) {
669
+ const mask = -(y & 1) & 255;
670
+ product ^= x & mask;
671
+ const high = -(x >> 7 & 1) & 255;
672
+ x = x << 1 & 255;
673
+ x ^= 27 & high;
674
+ y >>= 1;
675
+ }
676
+ return product & 255;
677
+ }
678
+ function polyEval(coeffs, x) {
679
+ let acc = 0;
680
+ for (let i = coeffs.length - 1; i >= 0; i--) {
681
+ acc = gfMul(acc, x) ^ coeffs[i];
682
+ }
683
+ return acc & 255;
684
+ }
685
+ function shamirSplit(secret, t, n, rng) {
686
+ const byteCoeffs = [];
687
+ for (let j = 0; j < SEAL_SECRET_LEN; j++) {
688
+ const c = new Uint8Array(t);
689
+ c[0] = secret[j];
690
+ if (t > 1) {
691
+ const tail = new Uint8Array(t - 1);
692
+ rng.fillBytes(tail);
693
+ c.set(tail, 1);
694
+ }
695
+ byteCoeffs.push(c);
696
+ }
697
+ const shares = [];
698
+ for (let k = 0; k < n; k++) {
699
+ const x = k + 1 & 255;
700
+ const value = new Uint8Array(SEAL_SECRET_LEN);
701
+ for (let j = 0; j < SEAL_SECRET_LEN; j++) {
702
+ value[j] = polyEval(byteCoeffs[j], x);
703
+ }
704
+ shares.push({ index: x, value });
705
+ }
706
+ return shares;
707
+ }
708
+ function shareToBytes(s) {
709
+ const out = new Uint8Array(SEAL_SHARE_LEN);
710
+ out[0] = s.index;
711
+ out.set(s.value, 1);
712
+ return out;
713
+ }
714
+ function sealRosterHash(keccak2562, clusterId, t, n, roster) {
715
+ const chunks = [ROSTER_DOMAIN, u32le(clusterId), Uint8Array.of(t), Uint8Array.of(n)];
716
+ for (const { operatorIndex, ek } of roster) {
717
+ chunks.push(Uint8Array.of(operatorIndex), ek);
718
+ }
719
+ return keccak2562(concatBytes(...chunks));
720
+ }
721
+ function encodeSealEnvelope(env) {
722
+ const chunks = [];
723
+ chunks.push(u32le(env.clusterId));
724
+ chunks.push(u64le(env.epoch));
725
+ chunks.push(expectBytes(env.rosterHash, 32, "rosterHash"));
726
+ chunks.push(Uint8Array.of(env.t));
727
+ chunks.push(Uint8Array.of(env.n));
728
+ pushAeadBody(chunks, env.aeadBody);
729
+ chunks.push(u64le(BigInt(env.recipients.length)));
730
+ for (const r of env.recipients) {
731
+ chunks.push(Uint8Array.of(r.operatorIndex));
732
+ chunks.push(u64le(BigInt(r.kemCt.length)));
733
+ chunks.push(r.kemCt);
734
+ pushAeadBody(chunks, r.wrapped);
735
+ }
736
+ return concatBytes(...chunks);
737
+ }
738
+ function pushAeadBody(chunks, body) {
739
+ chunks.push(expectBytes(body.nonce, SEAL_NONCE_LEN, "aead nonce"));
740
+ chunks.push(u64le(BigInt(body.ct.length)));
741
+ chunks.push(body.ct);
742
+ chunks.push(expectBytes(body.commitment, SEAL_COMMIT_LEN, "aead commitment"));
743
+ }
744
+ function sealToCluster(args) {
745
+ const { plaintext, recipientEks, t, clusterId } = args;
746
+ const epoch = args.epoch;
747
+ const rosterHash = expectBytes(args.rosterHash, 32, "rosterHash");
748
+ const rng = args.rng ?? cryptoRandomSource();
749
+ const n = recipientEks.length;
750
+ if (!Number.isInteger(t) || t < 1 || t > n || n < 1 || n > 255) {
751
+ throw new Error(`invalid threshold/recipient count: t=${t} n=${n}`);
752
+ }
753
+ for (let i = 0; i < n; i++) {
754
+ expectBytes(recipientEks[i], SEAL_EK_LEN, `recipientEks[${i}]`);
755
+ }
756
+ const ctx = { clusterId, epoch, rosterHash };
757
+ const bodyKey = new Uint8Array(SEAL_KEY_LEN);
758
+ rng.fillBytes(bodyKey);
759
+ const aad = bodyAad(ctx, t, n);
760
+ const bodyNonce = deriveNonce(new TextEncoder().encode("body"), aad);
761
+ const aeadBody = aeadSeal(bodyKey, bodyNonce, plaintext, aad);
762
+ const shares = shamirSplit(bodyKey, t, n, rng);
763
+ const recipients = [];
764
+ for (let i = 0; i < n; i++) {
765
+ const opIndex = i + 1 & 255;
766
+ const m = new Uint8Array(32);
767
+ rng.fillBytes(m);
768
+ const { cipherText: kemCt, sharedSecret } = ml_kem768.encapsulate(recipientEks[i], m);
769
+ const kek = deriveKek(sharedSecret, rosterHash, clusterId, epoch, opIndex);
770
+ const sAad = shareAad(ctx, opIndex);
771
+ const wrapNonce = deriveNonce(new TextEncoder().encode("share"), sAad);
772
+ const wrapped = aeadSeal(kek, wrapNonce, shareToBytes(shares[i]), sAad);
773
+ recipients.push({ operatorIndex: opIndex, kemCt, wrapped });
774
+ sharedSecret.fill(0);
775
+ kek.fill(0);
776
+ }
777
+ bodyKey.fill(0);
778
+ return {
779
+ clusterId,
780
+ epoch,
781
+ rosterHash,
782
+ t,
783
+ n,
784
+ aeadBody,
785
+ recipients
786
+ };
787
+ }
788
+ var CLUSTER_MLKEM_SHAMIR_ALGO = "cluster-mlkem768-shamir";
789
+ function parseClusterSealKeys(source) {
790
+ const n = source.roster.length;
791
+ if (n === 0) {
792
+ throw new Error("cluster seal roster is empty");
793
+ }
794
+ if (source.n !== n) {
795
+ throw new Error(`cluster seal roster n=${source.n} disagrees with ${n} entries`);
796
+ }
797
+ if (!Number.isInteger(source.t) || source.t < 2 || source.t > n) {
798
+ throw new Error(`cluster seal threshold t=${source.t} out of range 2..=${n}`);
799
+ }
800
+ const sorted = [...source.roster].sort((a, b) => a.operatorIndex - b.operatorIndex);
801
+ const recipientEks = [];
802
+ const hashInput = [];
803
+ for (let i = 0; i < n; i++) {
804
+ const entry = sorted[i];
805
+ if (entry.operatorIndex !== i + 1) {
806
+ throw new Error(
807
+ `cluster seal roster operator indices must be 1..=${n}; got ${entry.operatorIndex} at slot ${i + 1}`
808
+ );
809
+ }
810
+ const ek = expectBytes(hexToBytes(entry.mlKemEk, `operator ${entry.operatorIndex} mlKemEk`), SEAL_EK_LEN, `operator ${entry.operatorIndex} ek`);
811
+ recipientEks.push(ek);
812
+ hashInput.push({ operatorIndex: entry.operatorIndex, ek });
813
+ }
814
+ const recomputed = sealRosterHash(keccak256, source.clusterId, source.t, n, hashInput);
815
+ if (source.rosterHash !== void 0) {
816
+ const supplied = expectBytes(hexToBytes(source.rosterHash, "rosterHash"), 32, "rosterHash");
817
+ if (!bytesEqual2(supplied, recomputed)) {
818
+ throw new Error(
819
+ `cluster seal roster hash mismatch: source ${bytesToHex(supplied)} != recomputed ${bytesToHex(recomputed)} (the roster hash does not commit to this ek set)`
820
+ );
821
+ }
822
+ }
823
+ return {
824
+ algo: source.algo ?? CLUSTER_MLKEM_SHAMIR_ALGO,
825
+ clusterId: source.clusterId,
826
+ epoch: toBigInt(source.epoch),
827
+ rosterHash: recomputed,
828
+ t: source.t,
829
+ n,
830
+ recipientEks
831
+ };
832
+ }
833
+ async function getClusterSealKeys(client, clusterId = 0) {
834
+ const result = await client.call(
835
+ "lyth_getClusterSealKeys",
836
+ [clusterId]
837
+ );
838
+ return parseClusterSealKeys({ ...result, clusterId: result.clusterId ?? clusterId });
839
+ }
840
+ async function sealTransaction(args) {
841
+ const keys = args.clusterSealKeys;
842
+ const senderPubkey = expectBytes(args.senderPubkey, ML_DSA_65_PUBLIC_KEY_LEN, "senderPubkey");
843
+ const senderAddress = expectBytes(args.senderAddress, 20, "senderAddress");
844
+ const env = sealToCluster({
845
+ plaintext: args.signedTxBincode,
846
+ recipientEks: keys.recipientEks,
847
+ t: keys.t,
848
+ clusterId: keys.clusterId,
849
+ epoch: keys.epoch,
850
+ rosterHash: keys.rosterHash,
851
+ rng: args.rng
852
+ });
853
+ const ciphertext = encodeSealEnvelope(env);
854
+ const decryptionHint = { epoch: keys.epoch, scheme: CLUSTER_MLKEM_SHAMIR };
855
+ const digest = outerSigDigest(args.aad, ciphertext, decryptionHint, senderPubkey);
856
+ const outerSignature = expectBytes(
857
+ await args.signOuterDigest(digest),
858
+ ML_DSA_65_SIGNATURE_LEN,
859
+ "outerSignature"
860
+ );
861
+ const envelope = {
862
+ nonceAad: args.aad,
863
+ ciphertext,
864
+ decryptionHint,
865
+ senderPubkey,
866
+ outerSignature,
867
+ sender: senderAddress
868
+ };
869
+ const envelopeWireBytes = bincodeEncryptedEnvelope(envelope);
870
+ return {
871
+ envelopeWireHex: `0x${bytesToHex(envelopeWireBytes).slice(2)}`,
872
+ envelopeWireBytes,
873
+ ciphertextBytes: ciphertext.length
874
+ };
875
+ }
876
+ async function submitSealedTransaction(client, submission) {
877
+ return client.call("lyth_submitEncrypted", [submission.envelopeWireHex]);
878
+ }
879
+ function keccak256(input) {
880
+ return keccak_256(input);
881
+ }
882
+ function toBigInt(value) {
883
+ if (typeof value === "bigint") return value;
884
+ return BigInt(value);
885
+ }
886
+ function bytesEqual2(a, b) {
887
+ if (a.length !== b.length) return false;
888
+ for (let i = 0; i < a.length; i++) {
889
+ if (a[i] !== b[i]) return false;
890
+ }
891
+ return true;
892
+ }
578
893
 
579
- export { ADDRESS_DERIVATION_DOMAIN, BincodeWriter, DKG_AEAD_TAG_LEN, DKG_NONCE_LEN, ENCRYPTED_SUBMISSION_UNAVAILABLE_MESSAGE, ENUM_VARIANT_INDEX_ML_DSA_65, ML_DSA_65_PUBLIC_KEY_LEN, ML_DSA_65_SEED_LEN, ML_DSA_65_SIGNATURE_LEN, ML_DSA_65_SIGNING_KEY_LEN, ML_KEM_768_CIPHERTEXT_LEN, ML_KEM_768_ENCAPSULATION_KEY_LEN, ML_KEM_768_SHARED_SECRET_LEN, MempoolClass, MlDsa65Backend, PQM1_ALGO_TAG_FALCON512_RESERVED, PQM1_ALGO_TAG_MLDSA65, PQM1_ALGO_TAG_MLDSA87_RESERVED, PQM1_ALGO_TAG_SLHDSA128S_RESERVED, PQM1_ENTROPY_LEN, PQM1_PAYLOAD_LEN, PQM1_V1_MLDSA65_DOMAIN_TAG, PQM1_V1_MNEMONIC_WORDS, PQM1_VERSION_V1, Pqm1Error, STANDARD_ALGO_NUMBER_ML_DSA_65, assemblePqm1Payload, bincodeDecryptHint, bincodeEncryptedEnvelope, bincodeNonceAad, bincodeSignedTransaction, buildEncryptedEnvelope, buildEncryptedSubmission, buildPlaintextSubmission, bytesToHex, concatBytes, derivePqm1MlDsa65SeedFromPayload, encodeMlDsa65Opaque, encodeTransactionForHash, encryptInnerTx, expectBytes, fetchEncryptionKey, generatePqm1Mnemonic, hexToBytes, mlDsa65AddressBytes, mlDsa65AddressFromPublicKey, outerSigDigest, parsePqm1Payload, pqm1MnemonicToAddress, pqm1MnemonicToMlDsa65Backend, pqm1MnemonicToMlDsa65Seed, pqm1MnemonicToPayload, pqm1PayloadToMnemonic, submitEncryptedEnvelope, submitPlaintextTransaction, submitTransactionWithPrivacy };
894
+ export { ADDRESS_DERIVATION_DOMAIN, BincodeWriter, CLUSTER_MLKEM_SHAMIR, CLUSTER_MLKEM_SHAMIR_ALGO, DKG_AEAD_TAG_LEN, DKG_NONCE_LEN, ENCRYPTED_SUBMISSION_UNAVAILABLE_MESSAGE, ENUM_VARIANT_INDEX_ML_DSA_65, ML_DSA_65_PUBLIC_KEY_LEN, ML_DSA_65_SEED_LEN, ML_DSA_65_SIGNATURE_LEN, ML_DSA_65_SIGNING_KEY_LEN, ML_KEM_768_CIPHERTEXT_LEN, ML_KEM_768_ENCAPSULATION_KEY_LEN, ML_KEM_768_SHARED_SECRET_LEN, MempoolClass, MlDsa65Backend, PQM1_ALGO_TAG_FALCON512_RESERVED, PQM1_ALGO_TAG_MLDSA65, PQM1_ALGO_TAG_MLDSA87_RESERVED, PQM1_ALGO_TAG_SLHDSA128S_RESERVED, PQM1_ENTROPY_LEN, PQM1_PAYLOAD_LEN, PQM1_V1_MLDSA65_DOMAIN_TAG, PQM1_V1_MNEMONIC_WORDS, PQM1_VERSION_V1, Pqm1Error, SEAL_COMMIT_LEN, SEAL_DK_LEN, SEAL_EK_LEN, SEAL_KEM_CT_LEN, SEAL_KEM_SEED_LEN, SEAL_KEY_LEN, SEAL_NONCE_LEN, SEAL_SHARE_LEN, SEAL_TAG_LEN, STANDARD_ALGO_NUMBER_ML_DSA_65, assemblePqm1Payload, bincodeDecryptHint, bincodeEncryptedEnvelope, bincodeNonceAad, bincodeSignedTransaction, buildEncryptedEnvelope, buildEncryptedSubmission, buildPlaintextSubmission, bytesToHex, concatBytes, cryptoRandomSource, derivePqm1MlDsa65SeedFromPayload, encodeMlDsa65Opaque, encodeSealEnvelope, encodeTransactionForHash, encryptInnerTx, expectBytes, fetchEncryptionKey, generatePqm1Mnemonic, getClusterSealKeys, hexToBytes, mlDsa65AddressBytes, mlDsa65AddressFromPublicKey, outerSigDigest, parseClusterSealKeys, parsePqm1Payload, pqm1MnemonicToAddress, pqm1MnemonicToMlDsa65Backend, pqm1MnemonicToMlDsa65Seed, pqm1MnemonicToPayload, pqm1PayloadToMnemonic, sealRosterHash, sealToCluster, sealTransaction, submitEncryptedEnvelope, submitPlaintextTransaction, submitSealedTransaction, submitTransactionWithPrivacy };
580
895
  //# sourceMappingURL=index.js.map
581
896
  //# sourceMappingURL=index.js.map