@monolythium/core-sdk 0.4.23 → 0.5.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,11 +1,8 @@
1
1
  import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';
2
2
  import { blake3 } from '@noble/hashes/blake3.js';
3
3
  import { keccak_256, shake256 } from '@noble/hashes/sha3.js';
4
- import { entropyToMnemonic, mnemonicToEntropy } from '@scure/bip39';
4
+ import { entropyToMnemonic, validateMnemonic as validateMnemonic$1, mnemonicToSeedSync } from '@scure/bip39';
5
5
  import { wordlist } from '@scure/bip39/wordlists/english.js';
6
- import { ml_kem768 } from '@noble/post-quantum/ml-kem.js';
7
- import { chacha20poly1305 } from '@noble/ciphers/chacha.js';
8
- import { randomBytes } from '@noble/hashes/utils.js';
9
6
 
10
7
  // src/crypto/bincode.ts
11
8
  var BincodeWriter = class {
@@ -353,105 +350,69 @@ function encodeMlDsa65Opaque(raw) {
353
350
  out.set(bytes, 14);
354
351
  return out;
355
352
  }
356
- var PQM1_ALGO_TAG_MLDSA65 = 1;
357
- var PQM1_ALGO_TAG_MLDSA87_RESERVED = 2;
358
- var PQM1_ALGO_TAG_SLHDSA128S_RESERVED = 3;
359
- var PQM1_ALGO_TAG_FALCON512_RESERVED = 4;
360
- var PQM1_VERSION_V1 = 1;
361
- var PQM1_PAYLOAD_LEN = 32;
362
- var PQM1_ENTROPY_LEN = 30;
363
- var PQM1_V1_MNEMONIC_WORDS = 24;
364
- var PQM1_V1_MLDSA65_DOMAIN_TAG = "monolythium.pqm1.v1.mldsa65";
365
- var Pqm1Error = class extends Error {
353
+ var MLDSA65_MNEMONIC_WORDS = 24;
354
+ var MLDSA65_SEED_DOMAIN = "monolythium.mldsa65.v1";
355
+ var MLDSA65_ENTROPY_LEN = 32;
356
+ var DOMAIN_BYTES = new TextEncoder().encode(MLDSA65_SEED_DOMAIN);
357
+ var MnemonicError = class extends Error {
366
358
  constructor(kind, message) {
367
359
  super(message);
368
360
  this.kind = kind;
369
- this.name = "Pqm1Error";
361
+ this.name = "MnemonicError";
370
362
  }
371
363
  kind;
372
364
  };
373
- var DOMAIN_BYTES = new TextEncoder().encode(PQM1_V1_MLDSA65_DOMAIN_TAG);
374
365
  function normalizeMnemonic(mnemonic) {
375
366
  return mnemonic.trim().toLowerCase().replace(/\s+/g, " ");
376
367
  }
377
- function ensureSupportedPayload(bytes) {
378
- if (bytes.length !== PQM1_PAYLOAD_LEN) {
379
- throw new Pqm1Error("badPayloadLength", `PQM-1 payload must be ${PQM1_PAYLOAD_LEN} bytes, got ${bytes.length}`);
380
- }
381
- if (bytes[0] !== PQM1_ALGO_TAG_MLDSA65) {
382
- throw new Pqm1Error("unsupportedAlgorithm", `unsupported PQM-1 algorithm tag 0x${bytes[0].toString(16).padStart(2, "0")}`);
383
- }
384
- if (bytes[1] !== PQM1_VERSION_V1) {
385
- throw new Pqm1Error("unsupportedVersion", `unsupported PQM-1 version 0x${bytes[1].toString(16).padStart(2, "0")}`);
386
- }
368
+ function wordCount(normalized) {
369
+ return normalized.length === 0 ? 0 : normalized.split(" ").length;
387
370
  }
388
371
  function defaultRandomFill(bytes) {
389
372
  const cryptoObj = globalThis.crypto;
390
373
  if (!cryptoObj?.getRandomValues) {
391
- throw new Pqm1Error("missingRandom", "globalThis.crypto.getRandomValues is unavailable");
374
+ throw new MnemonicError("missingRandom", "globalThis.crypto.getRandomValues is unavailable");
392
375
  }
393
376
  cryptoObj.getRandomValues(bytes);
394
377
  }
395
- function assemblePqm1Payload(entropy) {
396
- const ent = expectBytes(entropy, PQM1_ENTROPY_LEN, "PQM-1 entropy");
397
- const payload = new Uint8Array(PQM1_PAYLOAD_LEN);
398
- payload[0] = PQM1_ALGO_TAG_MLDSA65;
399
- payload[1] = PQM1_VERSION_V1;
400
- payload.set(ent, 2);
401
- return payload;
402
- }
403
- function parsePqm1Payload(payload) {
404
- const bytes = expectBytes(payload, PQM1_PAYLOAD_LEN, "PQM-1 payload").slice();
405
- ensureSupportedPayload(bytes);
406
- return {
407
- algoTag: PQM1_ALGO_TAG_MLDSA65,
408
- version: PQM1_VERSION_V1,
409
- entropy: bytes.slice(2),
410
- bytes
411
- };
378
+ function generateMnemonic(rng = defaultRandomFill) {
379
+ const entropy = new Uint8Array(MLDSA65_ENTROPY_LEN);
380
+ rng(entropy);
381
+ return entropyToMnemonic(entropy, wordlist);
412
382
  }
413
- function pqm1PayloadToMnemonic(payload) {
414
- const parsed = parsePqm1Payload(payload);
415
- return entropyToMnemonic(parsed.bytes, wordlist);
383
+ function validateMnemonic(mnemonic) {
384
+ const normalized = normalizeMnemonic(mnemonic);
385
+ if (wordCount(normalized) !== MLDSA65_MNEMONIC_WORDS) {
386
+ return false;
387
+ }
388
+ return validateMnemonic$1(normalized, wordlist);
416
389
  }
417
- function pqm1MnemonicToPayload(mnemonic) {
390
+ function mnemonicToMlDsa65Seed(mnemonic) {
418
391
  const normalized = normalizeMnemonic(mnemonic);
419
- const words = normalized.length === 0 ? [] : normalized.split(" ");
420
- if (words.length !== PQM1_V1_MNEMONIC_WORDS) {
421
- throw new Pqm1Error("badWordCount", `PQM-1 mnemonic must be ${PQM1_V1_MNEMONIC_WORDS} words, got ${words.length}`);
392
+ const words = wordCount(normalized);
393
+ if (words !== MLDSA65_MNEMONIC_WORDS) {
394
+ throw new MnemonicError(
395
+ "badWordCount",
396
+ `mnemonic must be ${MLDSA65_MNEMONIC_WORDS} words, got ${words}`
397
+ );
422
398
  }
423
- let payload;
424
- try {
425
- payload = mnemonicToEntropy(normalized, wordlist);
426
- } catch (e) {
427
- throw new Pqm1Error("bip39Decode", `invalid PQM-1 mnemonic: ${e.message}`);
399
+ if (!validateMnemonic$1(normalized, wordlist)) {
400
+ throw new MnemonicError(
401
+ "bip39Decode",
402
+ "invalid BIP-39 mnemonic (unknown word or bad checksum)"
403
+ );
428
404
  }
429
- return parsePqm1Payload(payload);
430
- }
431
- function derivePqm1MlDsa65SeedFromPayload(payload) {
432
- const parsed = parsePqm1Payload(payload);
433
- return shake256(concatBytes(DOMAIN_BYTES, parsed.bytes), { dkLen: ML_DSA_65_SEED_LEN });
405
+ const seed64 = mnemonicToSeedSync(normalized, "");
406
+ return shake256(concatBytes(DOMAIN_BYTES, seed64), { dkLen: ML_DSA_65_SEED_LEN });
434
407
  }
435
- function pqm1MnemonicToMlDsa65Seed(mnemonic) {
436
- return derivePqm1MlDsa65SeedFromPayload(pqm1MnemonicToPayload(mnemonic).bytes);
408
+ function mnemonicToMlDsa65Backend(mnemonic) {
409
+ return MlDsa65Backend.fromSeed(mnemonicToMlDsa65Seed(mnemonic));
437
410
  }
438
- function pqm1MnemonicToMlDsa65Backend(mnemonic) {
439
- return MlDsa65Backend.fromSeed(pqm1MnemonicToMlDsa65Seed(mnemonic));
411
+ function mnemonicToAddress(mnemonic) {
412
+ return mnemonicToMlDsa65Backend(mnemonic).getAddress();
440
413
  }
441
- function pqm1MnemonicToAddress(mnemonic) {
442
- return pqm1MnemonicToMlDsa65Backend(mnemonic).getAddress();
443
- }
444
- function generatePqm1Mnemonic(rng = defaultRandomFill) {
445
- const entropy = new Uint8Array(PQM1_ENTROPY_LEN);
446
- rng(entropy);
447
- return pqm1PayloadToMnemonic(assemblePqm1Payload(entropy));
448
- }
449
- var DKG_AEAD_DOMAIN_TAG = new TextEncoder().encode("protocore/v2/mempool/dkg-mlkem768/1");
450
- var ML_KEM_768_CIPHERTEXT_LEN = 1088;
451
- var ML_KEM_768_ENCAPSULATION_KEY_LEN = 1184;
452
- var ML_KEM_768_SHARED_SECRET_LEN = 32;
453
- var DKG_NONCE_LEN = 12;
454
- var DKG_AEAD_TAG_LEN = 16;
414
+
415
+ // src/crypto/envelope.ts
455
416
  var MempoolClass = {
456
417
  Transfer: 0,
457
418
  ContractCall: 1,
@@ -463,430 +424,8 @@ var MempoolClass = {
463
424
  GovernanceOp: 5,
464
425
  RWAOp: 6
465
426
  };
466
- function bincodeNonceAad(aad) {
467
- const w = new BincodeWriter();
468
- w.bytes(expectBytes(aad.sender, 20, "NonceAad.sender"));
469
- w.u64(aad.nonce);
470
- w.u64(aad.chainId);
471
- w.enumVariant(aad.class);
472
- w.u128(aad.maxFeePerGas);
473
- w.u128(aad.maxPriorityFeePerGas);
474
- w.u64(aad.gasLimit);
475
- return w.toBytes();
476
- }
477
- function bincodeDecryptHint(hint) {
478
- const w = new BincodeWriter();
479
- w.u64(hint.epoch);
480
- w.u16(hint.scheme);
481
- return w.toBytes();
482
- }
483
- function bincodeEncryptedEnvelope(env) {
484
- const w = new BincodeWriter();
485
- w.rawBytes(bincodeNonceAad(env.nonceAad));
486
- w.bytes(env.ciphertext);
487
- w.rawBytes(bincodeDecryptHint(env.decryptionHint));
488
- bincodeMlDsa65OpaqueInto2(w, expectBytes(env.senderPubkey, ML_DSA_65_PUBLIC_KEY_LEN, "senderPubkey"));
489
- bincodeMlDsa65OpaqueInto2(w, expectBytes(env.outerSignature, ML_DSA_65_SIGNATURE_LEN, "outerSignature"));
490
- w.bytes(expectBytes(env.sender, 20, "sender"));
491
- return w.toBytes();
492
- }
493
- function encryptInnerTx(signedInnerTxBincode, nonceAad, kemEncapsulationKey) {
494
- expectBytes(kemEncapsulationKey, ML_KEM_768_ENCAPSULATION_KEY_LEN, "kemEncapsulationKey");
495
- const { cipherText: kemCt, sharedSecret } = ml_kem768.encapsulate(kemEncapsulationKey);
496
- const nonce = randomBytes(DKG_NONCE_LEN);
497
- const cipher = chacha20poly1305(sharedSecret, nonce, aadFor(nonceAad));
498
- const aeadCt = cipher.encrypt(signedInnerTxBincode);
499
- sharedSecret.fill(0);
500
- return concatBytes(kemCt, nonce, aeadCt);
501
- }
502
- function outerSigDigest(nonceAad, ciphertext, decryptionHint, senderPubkey) {
503
- const aad = bincodeNonceAad(nonceAad);
504
- const hint = bincodeDecryptHint(decryptionHint);
505
- return keccak_256(concatBytes(aad, ciphertext, hint, expectBytes(senderPubkey, ML_DSA_65_PUBLIC_KEY_LEN, "senderPubkey")));
506
- }
507
- async function buildEncryptedEnvelope(args) {
508
- const ciphertext = encryptInnerTx(args.signedInnerTxBincode, args.nonceAad, args.kemEncapsulationKey);
509
- const digest = outerSigDigest(args.nonceAad, ciphertext, args.decryptionHint, args.senderPubkey);
510
- const outerSignature = await args.signOuterDigest(digest);
511
- const envelope = {
512
- nonceAad: args.nonceAad,
513
- ciphertext,
514
- decryptionHint: args.decryptionHint,
515
- senderPubkey: expectBytes(args.senderPubkey, ML_DSA_65_PUBLIC_KEY_LEN, "senderPubkey"),
516
- outerSignature: expectBytes(outerSignature, ML_DSA_65_SIGNATURE_LEN, "outerSignature"),
517
- sender: expectBytes(args.senderAddress, 20, "senderAddress")
518
- };
519
- const wireBytes = bincodeEncryptedEnvelope(envelope);
520
- return { envelope, wireBytes, wireHex: bytesToHex(wireBytes) };
521
- }
522
- function aadFor(aad) {
523
- return concatBytes(DKG_AEAD_DOMAIN_TAG, bincodeNonceAad(aad));
524
- }
525
- function bincodeMlDsa65OpaqueInto2(w, raw) {
526
- w.enumVariant(ENUM_VARIANT_INDEX_ML_DSA_65);
527
- w.u16(STANDARD_ALGO_NUMBER_ML_DSA_65);
528
- w.bytes(raw);
529
- }
530
- var SEAL_EK_LEN = 1184;
531
- var SEAL_DK_LEN = 2400;
532
- var SEAL_KEM_CT_LEN = 1088;
533
- var SEAL_KEM_SEED_LEN = 64;
534
- var SEAL_KEY_LEN = 32;
535
- var SEAL_NONCE_LEN = 12;
536
- var SEAL_TAG_LEN = 16;
537
- var SEAL_COMMIT_LEN = 32;
538
- var SEAL_SECRET_LEN = 32;
539
- var SEAL_SHARE_LEN = 1 + SEAL_SECRET_LEN;
540
- var CLUSTER_MLKEM_SHAMIR = 3;
541
- var COMMIT_DOMAIN = new TextEncoder().encode("lythiumseal/commit/v1");
542
- var KEK_DOMAIN = new TextEncoder().encode("lythiumseal/kek/v1");
543
- var NONCE_DOMAIN = new TextEncoder().encode("lythiumseal/nonce/v1");
544
- var BODY_AAD_DOMAIN = new TextEncoder().encode("lythiumseal/body/v1");
545
- var SHARE_AAD_DOMAIN = new TextEncoder().encode("lythiumseal/share/v1");
546
- var ROSTER_DOMAIN = new TextEncoder().encode("lythiumseal/roster/v1");
547
- function cryptoRandomSource() {
548
- return {
549
- fillBytes(dest) {
550
- crypto.getRandomValues(dest);
551
- }
552
- };
553
- }
554
- function generateOperatorSealKeypair() {
555
- const { publicKey, secretKey } = ml_kem768.keygen();
556
- return {
557
- encapsulationKey: expectBytes(publicKey, SEAL_EK_LEN, "encapsulationKey").slice(),
558
- decapsulationKey: expectBytes(secretKey, SEAL_DK_LEN, "decapsulationKey").slice()
559
- };
560
- }
561
- function u32le(n) {
562
- const out = new Uint8Array(4);
563
- out[0] = n & 255;
564
- out[1] = n >>> 8 & 255;
565
- out[2] = n >>> 16 & 255;
566
- out[3] = n >>> 24 & 255;
567
- return out;
568
- }
569
- function u64le(n) {
570
- const out = new Uint8Array(8);
571
- let v = n;
572
- for (let i = 0; i < 8; i++) {
573
- out[i] = Number(v & 0xffn);
574
- v >>= 8n;
575
- }
576
- return out;
577
- }
578
- function framed(field) {
579
- return concatBytes(u32le(field.length), field);
580
- }
581
- function keyCommitment(key) {
582
- return shake256(concatBytes(framed(COMMIT_DOMAIN), key), { dkLen: SEAL_COMMIT_LEN });
583
- }
584
- function deriveKek(sharedSecret, domain, clusterId, epoch, opIndex) {
585
- const input = concatBytes(
586
- framed(KEK_DOMAIN),
587
- framed(sharedSecret),
588
- framed(domain),
589
- u32le(clusterId),
590
- u64le(epoch),
591
- Uint8Array.of(opIndex)
592
- );
593
- return shake256(input, { dkLen: SEAL_KEY_LEN });
594
- }
595
- function deriveNonce(domain, context) {
596
- const input = concatBytes(framed(NONCE_DOMAIN), framed(domain), framed(context));
597
- return shake256(input, { dkLen: SEAL_NONCE_LEN });
598
- }
599
- function bodyAad(ctx, k, n) {
600
- return concatBytes(
601
- BODY_AAD_DOMAIN,
602
- u32le(ctx.clusterId),
603
- u64le(ctx.epoch),
604
- Uint8Array.of(k),
605
- Uint8Array.of(n),
606
- ctx.rosterHash
607
- );
608
- }
609
- function shareAad(ctx, opIndex) {
610
- return concatBytes(
611
- SHARE_AAD_DOMAIN,
612
- u32le(ctx.clusterId),
613
- u64le(ctx.epoch),
614
- Uint8Array.of(opIndex),
615
- ctx.rosterHash
616
- );
617
- }
618
- function aeadSeal(key, nonce, plaintext, aad) {
619
- const cipher = chacha20poly1305(key, nonce, aad);
620
- const ct = cipher.encrypt(plaintext);
621
- return { nonce, ct, commitment: keyCommitment(key) };
622
- }
623
- function gfMul(a, b) {
624
- let product = 0;
625
- let x = a & 255;
626
- let y = b & 255;
627
- for (let i = 0; i < 8; i++) {
628
- const mask = -(y & 1) & 255;
629
- product ^= x & mask;
630
- const high = -(x >> 7 & 1) & 255;
631
- x = x << 1 & 255;
632
- x ^= 27 & high;
633
- y >>= 1;
634
- }
635
- return product & 255;
636
- }
637
- function polyEval(coeffs, x) {
638
- let acc = 0;
639
- for (let i = coeffs.length - 1; i >= 0; i--) {
640
- acc = gfMul(acc, x) ^ coeffs[i];
641
- }
642
- return acc & 255;
643
- }
644
- function shamirSplit(secret, t, n, rng) {
645
- const byteCoeffs = [];
646
- for (let j = 0; j < SEAL_SECRET_LEN; j++) {
647
- const c = new Uint8Array(t);
648
- c[0] = secret[j];
649
- if (t > 1) {
650
- const tail = new Uint8Array(t - 1);
651
- rng.fillBytes(tail);
652
- c.set(tail, 1);
653
- }
654
- byteCoeffs.push(c);
655
- }
656
- const shares = [];
657
- for (let k = 0; k < n; k++) {
658
- const x = k + 1 & 255;
659
- const value = new Uint8Array(SEAL_SECRET_LEN);
660
- for (let j = 0; j < SEAL_SECRET_LEN; j++) {
661
- value[j] = polyEval(byteCoeffs[j], x);
662
- }
663
- shares.push({ index: x, value });
664
- }
665
- return shares;
666
- }
667
- function shareToBytes(s) {
668
- const out = new Uint8Array(SEAL_SHARE_LEN);
669
- out[0] = s.index;
670
- out.set(s.value, 1);
671
- return out;
672
- }
673
- function sealRosterHash(keccak2562, clusterId, t, n, roster) {
674
- const chunks = [ROSTER_DOMAIN, u32le(clusterId), Uint8Array.of(t), Uint8Array.of(n)];
675
- for (const { operatorIndex, ek } of roster) {
676
- chunks.push(Uint8Array.of(operatorIndex), ek);
677
- }
678
- return keccak2562(concatBytes(...chunks));
679
- }
680
- function encodeSealEnvelope(env) {
681
- const chunks = [];
682
- chunks.push(u32le(env.clusterId));
683
- chunks.push(u64le(env.epoch));
684
- chunks.push(expectBytes(env.rosterHash, 32, "rosterHash"));
685
- chunks.push(Uint8Array.of(env.t));
686
- chunks.push(Uint8Array.of(env.n));
687
- pushAeadBody(chunks, env.aeadBody);
688
- chunks.push(u64le(BigInt(env.recipients.length)));
689
- for (const r of env.recipients) {
690
- chunks.push(Uint8Array.of(r.operatorIndex));
691
- chunks.push(u64le(BigInt(r.kemCt.length)));
692
- chunks.push(r.kemCt);
693
- pushAeadBody(chunks, r.wrapped);
694
- }
695
- return concatBytes(...chunks);
696
- }
697
- function pushAeadBody(chunks, body) {
698
- chunks.push(expectBytes(body.nonce, SEAL_NONCE_LEN, "aead nonce"));
699
- chunks.push(u64le(BigInt(body.ct.length)));
700
- chunks.push(body.ct);
701
- chunks.push(expectBytes(body.commitment, SEAL_COMMIT_LEN, "aead commitment"));
702
- }
703
- function sealToCluster(args) {
704
- const { plaintext, recipientEks, t, clusterId } = args;
705
- const epoch = args.epoch;
706
- const rosterHash = expectBytes(args.rosterHash, 32, "rosterHash");
707
- const rng = args.rng ?? cryptoRandomSource();
708
- const n = recipientEks.length;
709
- if (!Number.isInteger(t) || t < 1 || t > n || n < 1 || n > 255) {
710
- throw new Error(`invalid threshold/recipient count: t=${t} n=${n}`);
711
- }
712
- for (let i = 0; i < n; i++) {
713
- expectBytes(recipientEks[i], SEAL_EK_LEN, `recipientEks[${i}]`);
714
- }
715
- const ctx = { clusterId, epoch, rosterHash };
716
- const bodyKey = new Uint8Array(SEAL_KEY_LEN);
717
- rng.fillBytes(bodyKey);
718
- const aad = bodyAad(ctx, t, n);
719
- const bodyNonce = deriveNonce(new TextEncoder().encode("body"), aad);
720
- const aeadBody = aeadSeal(bodyKey, bodyNonce, plaintext, aad);
721
- const shares = shamirSplit(bodyKey, t, n, rng);
722
- const recipients = [];
723
- for (let i = 0; i < n; i++) {
724
- const opIndex = i + 1 & 255;
725
- const m = new Uint8Array(32);
726
- rng.fillBytes(m);
727
- const { cipherText: kemCt, sharedSecret } = ml_kem768.encapsulate(recipientEks[i], m);
728
- const kek = deriveKek(sharedSecret, rosterHash, clusterId, epoch, opIndex);
729
- const sAad = shareAad(ctx, opIndex);
730
- const wrapNonce = deriveNonce(new TextEncoder().encode("share"), sAad);
731
- const wrapped = aeadSeal(kek, wrapNonce, shareToBytes(shares[i]), sAad);
732
- recipients.push({ operatorIndex: opIndex, kemCt, wrapped });
733
- sharedSecret.fill(0);
734
- kek.fill(0);
735
- }
736
- bodyKey.fill(0);
737
- return {
738
- clusterId,
739
- epoch,
740
- rosterHash,
741
- t,
742
- n,
743
- aeadBody,
744
- recipients
745
- };
746
- }
747
-
748
- // src/crypto/seal.ts
749
- var CLUSTER_MLKEM_SHAMIR_ALGO = "cluster-mlkem768-shamir";
750
- function parseClusterSealKeys(source) {
751
- const n = source.roster.length;
752
- if (n === 0) {
753
- throw new Error("cluster seal roster is empty");
754
- }
755
- if (source.n !== n) {
756
- throw new Error(`cluster seal roster n=${source.n} disagrees with ${n} entries`);
757
- }
758
- if (!Number.isInteger(source.t) || source.t < 2 || source.t > n) {
759
- throw new Error(`cluster seal threshold t=${source.t} out of range 2..=${n}`);
760
- }
761
- const sorted = [...source.roster].sort((a, b) => a.operatorIndex - b.operatorIndex);
762
- const recipientEks = [];
763
- const hashInput = [];
764
- for (let i = 0; i < n; i++) {
765
- const entry = sorted[i];
766
- if (entry.operatorIndex !== i + 1) {
767
- throw new Error(
768
- `cluster seal roster operator indices must be 1..=${n}; got ${entry.operatorIndex} at slot ${i + 1}`
769
- );
770
- }
771
- const ek = expectBytes(hexToBytes(entry.mlKemEk, `operator ${entry.operatorIndex} mlKemEk`), SEAL_EK_LEN, `operator ${entry.operatorIndex} ek`);
772
- recipientEks.push(ek);
773
- hashInput.push({ operatorIndex: entry.operatorIndex, ek });
774
- }
775
- const recomputed = sealRosterHash(keccak256, source.clusterId, source.t, n, hashInput);
776
- if (source.rosterHash !== void 0) {
777
- const supplied = expectBytes(hexToBytes(source.rosterHash, "rosterHash"), 32, "rosterHash");
778
- if (!bytesEqual(supplied, recomputed)) {
779
- throw new Error(
780
- `cluster seal roster hash mismatch: source ${bytesToHex(supplied)} != recomputed ${bytesToHex(recomputed)} (the roster hash does not commit to this ek set)`
781
- );
782
- }
783
- }
784
- return {
785
- algo: source.algo ?? CLUSTER_MLKEM_SHAMIR_ALGO,
786
- clusterId: source.clusterId,
787
- epoch: toBigInt(source.epoch),
788
- rosterHash: recomputed,
789
- t: source.t,
790
- n,
791
- recipientEks
792
- };
793
- }
794
- async function getClusterSealKeys(client, clusterId = 0) {
795
- const result = await client.call(
796
- "lyth_getClusterSealKeys",
797
- [clusterId]
798
- );
799
- return parseClusterSealKeys({ ...result, clusterId: result.clusterId ?? clusterId });
800
- }
801
- async function sealTransaction(args) {
802
- const keys = args.clusterSealKeys;
803
- const senderPubkey = expectBytes(args.senderPubkey, ML_DSA_65_PUBLIC_KEY_LEN, "senderPubkey");
804
- const senderAddress = expectBytes(args.senderAddress, 20, "senderAddress");
805
- const env = sealToCluster({
806
- plaintext: args.signedTxBincode,
807
- recipientEks: keys.recipientEks,
808
- t: keys.t,
809
- clusterId: keys.clusterId,
810
- epoch: keys.epoch,
811
- rosterHash: keys.rosterHash,
812
- rng: args.rng
813
- });
814
- const ciphertext = encodeSealEnvelope(env);
815
- const decryptionHint = { epoch: keys.epoch, scheme: CLUSTER_MLKEM_SHAMIR };
816
- const digest = outerSigDigest(args.aad, ciphertext, decryptionHint, senderPubkey);
817
- const outerSignature = expectBytes(
818
- await args.signOuterDigest(digest),
819
- ML_DSA_65_SIGNATURE_LEN,
820
- "outerSignature"
821
- );
822
- const envelope = {
823
- nonceAad: args.aad,
824
- ciphertext,
825
- decryptionHint,
826
- senderPubkey,
827
- outerSignature,
828
- sender: senderAddress
829
- };
830
- const envelopeWireBytes = bincodeEncryptedEnvelope(envelope);
831
- return {
832
- envelopeWireHex: `0x${bytesToHex(envelopeWireBytes).slice(2)}`,
833
- envelopeWireBytes,
834
- ciphertextBytes: ciphertext.length
835
- };
836
- }
837
- async function submitSealedTransaction(client, submission) {
838
- return client.call("lyth_submitEncrypted", [submission.envelopeWireHex]);
839
- }
840
- function keccak256(input) {
841
- return keccak_256(input);
842
- }
843
- function toBigInt(value) {
844
- if (typeof value === "bigint") return value;
845
- return BigInt(value);
846
- }
847
- function bytesEqual(a, b) {
848
- if (a.length !== b.length) return false;
849
- for (let i = 0; i < a.length; i++) {
850
- if (a[i] !== b[i]) return false;
851
- }
852
- return true;
853
- }
854
427
 
855
428
  // src/crypto/submission.ts
856
- async function fetchEncryptionKey(client) {
857
- const result = await client.call(
858
- "lyth_getEncryptionKey",
859
- []
860
- );
861
- return {
862
- algo: result.algo ?? "ml-kem-768",
863
- epoch: typeof result.epoch === "string" ? BigInt(result.epoch) : BigInt(result.epoch),
864
- encapsulationKey: hexToBytes(result.encapsulationKey, "encapsulationKey")
865
- };
866
- }
867
- var ENCRYPTED_SUBMISSION_UNAVAILABLE_MESSAGE = "private submission requires cluster seal keys; pass clusterSealKeysSource or enable lyth_getClusterSealKeys";
868
- async function buildEncryptedSubmission(args) {
869
- const signed = args.backend.signEvmTx(args.tx);
870
- const clusterSealKeys = await resolveClusterSealKeys(args);
871
- const aad = nonceAadForTx(args.tx, args.backend.addressBytes(), args.class);
872
- const sealed = await sealTransaction({
873
- signedTxBincode: signed.wireBytes,
874
- clusterSealKeys,
875
- aad,
876
- senderAddress: args.backend.addressBytes(),
877
- senderPubkey: args.backend.publicKey(),
878
- signOuterDigest: (digest) => args.backend.signPrehash(digest)
879
- });
880
- return {
881
- envelopeWireHex: sealed.envelopeWireHex,
882
- innerSighashHex: bytesToHex(signed.sighash),
883
- innerTxHashHex: bytesToHex(signed.txHash),
884
- innerWireBytes: signed.wireBytes.length
885
- };
886
- }
887
- async function submitEncryptedEnvelope(client, envelopeWireHex) {
888
- return client.call("lyth_submitEncrypted", [envelopeWireHex]);
889
- }
890
429
  function buildPlaintextSubmission(args) {
891
430
  const signed = args.backend.signEvmTx(args.tx);
892
431
  return {
@@ -905,29 +444,14 @@ async function submitPlaintextTransaction(client, signedTxWireHex, expectedTxHas
905
444
  );
906
445
  }
907
446
  const expectedBytes = hexToBytes(expectedTxHashHex, "expected tx hash");
908
- if (!bytesEqual2(returnedBytes, expectedBytes)) {
447
+ if (!bytesEqual(returnedBytes, expectedBytes)) {
909
448
  throw new Error(
910
449
  `mesh_submitTx returned tx hash ${bytesToHex(returnedBytes)} but the locally computed canonical hash is ${bytesToHex(expectedBytes)}`
911
450
  );
912
451
  }
913
452
  return bytesToHex(returnedBytes);
914
453
  }
915
- async function submitTransactionWithPrivacy(args) {
916
- if (args.private) {
917
- const built = await buildEncryptedSubmission({
918
- client: args.client,
919
- backend: args.backend,
920
- tx: args.tx,
921
- encryptionKey: args.encryptionKey,
922
- clusterId: args.clusterId,
923
- clusterSealKeys: args.clusterSealKeys,
924
- clusterSealKeysSource: args.clusterSealKeysSource,
925
- class: args.class
926
- });
927
- const returned = await submitEncryptedEnvelope(args.client, built.envelopeWireHex);
928
- assertRpcHash(returned, "lyth_submitEncrypted tx hash");
929
- return built.innerTxHashHex;
930
- }
454
+ async function submitTransaction(args) {
931
455
  const plaintext = buildPlaintextSubmission({ backend: args.backend, tx: args.tx });
932
456
  return submitPlaintextTransaction(
933
457
  args.client,
@@ -935,58 +459,14 @@ async function submitTransactionWithPrivacy(args) {
935
459
  plaintext.innerTxHashHex
936
460
  );
937
461
  }
938
- function bytesEqual2(a, b) {
462
+ function bytesEqual(a, b) {
939
463
  if (a.length !== b.length) return false;
940
464
  for (let i = 0; i < a.length; i++) {
941
465
  if (a[i] !== b[i]) return false;
942
466
  }
943
467
  return true;
944
468
  }
945
- async function resolveClusterSealKeys(args) {
946
- if (args.clusterSealKeys !== void 0) return args.clusterSealKeys;
947
- if (args.clusterSealKeysSource !== void 0) {
948
- return parseClusterSealKeys(args.clusterSealKeysSource);
949
- }
950
- if (args.client === void 0) {
951
- throw new Error(ENCRYPTED_SUBMISSION_UNAVAILABLE_MESSAGE);
952
- }
953
- const clusterId = args.clusterId ?? 0;
954
- const result = await args.client.call(
955
- "lyth_getClusterSealKeys",
956
- [clusterId]
957
- );
958
- return parseClusterSealKeys({ ...result, clusterId: result.clusterId ?? clusterId });
959
- }
960
- function nonceAadForTx(tx, sender, mempoolClass) {
961
- return {
962
- sender,
963
- nonce: parseBigint(tx.nonce, "nonce"),
964
- chainId: parseBigint(tx.chainId, "chainId"),
965
- class: mempoolClass ?? inferMempoolClass(tx),
966
- maxFeePerGas: parseBigint(tx.maxFeePerGas, "maxFeePerGas"),
967
- maxPriorityFeePerGas: parseBigint(tx.maxPriorityFeePerGas, "maxPriorityFeePerGas"),
968
- gasLimit: parseBigint(tx.gasLimit, "gasLimit")
969
- };
970
- }
971
- function inferMempoolClass(tx) {
972
- if (tx.to === null || hasInput(tx.input)) return MempoolClass.ContractCall;
973
- return MempoolClass.Transfer;
974
- }
975
- function hasInput(input) {
976
- if (input === void 0) return false;
977
- if (typeof input === "string") {
978
- const stripped = input.startsWith("0x") || input.startsWith("0X") ? input.slice(2) : input;
979
- return stripped.length > 0;
980
- }
981
- return input.length > 0;
982
- }
983
- function assertRpcHash(value, label) {
984
- const bytes = hexToBytes(value, label);
985
- if (bytes.length !== 32) {
986
- throw new Error(`${label} must be 32 bytes, got ${bytes.length}`);
987
- }
988
- }
989
469
 
990
- 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, generateOperatorSealKeypair, generatePqm1Mnemonic, getClusterSealKeys, hexToBytes, mlDsa65AddressBytes, mlDsa65AddressFromPublicKey, outerSigDigest, parseClusterSealKeys, parsePqm1Payload, pqm1MnemonicToAddress, pqm1MnemonicToMlDsa65Backend, pqm1MnemonicToMlDsa65Seed, pqm1MnemonicToPayload, pqm1PayloadToMnemonic, sealRosterHash, sealToCluster, sealTransaction, submitEncryptedEnvelope, submitPlaintextTransaction, submitSealedTransaction, submitTransactionWithPrivacy };
470
+ export { ADDRESS_DERIVATION_DOMAIN, BincodeWriter, ENUM_VARIANT_INDEX_ML_DSA_65, MLDSA65_MNEMONIC_WORDS, MLDSA65_SEED_DOMAIN, ML_DSA_65_PUBLIC_KEY_LEN, ML_DSA_65_SEED_LEN, ML_DSA_65_SIGNATURE_LEN, ML_DSA_65_SIGNING_KEY_LEN, MempoolClass, MlDsa65Backend, MnemonicError, STANDARD_ALGO_NUMBER_ML_DSA_65, bincodeSignedTransaction, buildPlaintextSubmission, bytesToHex, concatBytes, encodeMlDsa65Opaque, encodeTransactionForHash, expectBytes, generateMnemonic, hexToBytes, mlDsa65AddressBytes, mlDsa65AddressFromPublicKey, mnemonicToAddress, mnemonicToMlDsa65Backend, mnemonicToMlDsa65Seed, submitPlaintextTransaction, submitTransaction, validateMnemonic };
991
471
  //# sourceMappingURL=index.js.map
992
472
  //# sourceMappingURL=index.js.map