@wishknish/knishio-client-ts 0.9.8 → 1.1.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.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import JsSHA from 'jssha';
3
- import { ml_kem768 } from '@noble/post-quantum/ml-kem.js';
3
+ import { ml_kem768, ml_kem1024 } from '@noble/post-quantum/ml-kem.js';
4
4
  import { cacheExchange, fetchExchange, subscriptionExchange, createClient as createClient$1, gql } from '@urql/core';
5
5
  import { createClient } from 'graphql-ws';
6
6
  import { pipe, subscribe } from 'wonka';
@@ -566,6 +566,14 @@ var init_SecretStorageException = __esm({
566
566
  }
567
567
  );
568
568
  }
569
+ /**
570
+ * Validation error for storage options or parameters
571
+ */
572
+ static validationError(message) {
573
+ return new _SecretStorageException(message, {
574
+ code: "VALIDATION_ERROR"
575
+ });
576
+ }
569
577
  };
570
578
  }
571
579
  });
@@ -3655,6 +3663,11 @@ var TokenUnit = class _TokenUnit {
3655
3663
  };
3656
3664
  }
3657
3665
  };
3666
+ var ML_KEM_PARAMS = {
3667
+ 1024: { kem: ml_kem1024, pkBytes: 1568, skBytes: 3168, ctBytes: 1568 },
3668
+ 768: { kem: ml_kem768, pkBytes: 1184, skBytes: 2400, ctBytes: 1088 }
3669
+ };
3670
+ var DEFAULT_ML_KEM_PARAMETER_SET = 1024;
3658
3671
  var Wallet = class _Wallet {
3659
3672
  token;
3660
3673
  balance;
@@ -3669,6 +3682,7 @@ var Wallet = class _Wallet {
3669
3682
  tokenUnits;
3670
3683
  tradeRates;
3671
3684
  molecules;
3685
+ mlKemParameterSet;
3672
3686
  // Token metadata (populated from query responses)
3673
3687
  tokenName;
3674
3688
  tokenAmount;
@@ -3682,8 +3696,14 @@ var Wallet = class _Wallet {
3682
3696
  address = null,
3683
3697
  position = null,
3684
3698
  batchId = null,
3685
- characters = null
3699
+ characters = null,
3700
+ mlKemParameterSet = DEFAULT_ML_KEM_PARAMETER_SET
3686
3701
  } = {}) {
3702
+ const paramSetNum = Number(mlKemParameterSet);
3703
+ if (!ML_KEM_PARAMS[paramSetNum]) {
3704
+ throw new Error(`KnishIO: unsupported ML-KEM parameter set ${mlKemParameterSet}; expected 1024 or 768.`);
3705
+ }
3706
+ this.mlKemParameterSet = paramSetNum;
3687
3707
  this.token = token;
3688
3708
  this.balance = "0";
3689
3709
  this.molecules = {};
@@ -3719,7 +3739,8 @@ var Wallet = class _Wallet {
3719
3739
  bundle = null,
3720
3740
  token = "USER",
3721
3741
  batchId = null,
3722
- characters = null
3742
+ characters = null,
3743
+ mlKemParameterSet = DEFAULT_ML_KEM_PARAMETER_SET
3723
3744
  }) {
3724
3745
  let position = null;
3725
3746
  if (!secret && !bundle) {
@@ -3735,7 +3756,8 @@ var Wallet = class _Wallet {
3735
3756
  token,
3736
3757
  position,
3737
3758
  batchId,
3738
- characters
3759
+ characters,
3760
+ mlKemParameterSet
3739
3761
  });
3740
3762
  }
3741
3763
  /**
@@ -3894,20 +3916,80 @@ var Wallet = class _Wallet {
3894
3916
  return (typeof this.position === "undefined" || this.position === null) && (typeof this.address === "undefined" || this.address === null);
3895
3917
  }
3896
3918
  // =============================================================================
3897
- // POST-QUANTUM CRYPTOGRAPHY - ML-KEM768 INTEGRATION
3919
+ // POST-QUANTUM CRYPTOGRAPHY - ML-KEM INTEGRATION
3898
3920
  // =============================================================================
3899
3921
  /**
3900
- * Initializes the ML-KEM key pair (matches JavaScript SDK exactly)
3922
+ * Derive an ML-KEM keypair for an arbitrary parameter set from the wallet's key seed,
3923
+ * without mutating the wallet. The 64-byte `d‖z` seed takes no parameter-set input — only
3924
+ * the final `keygen` call differs — so one KnishIO wallet owns both an ML-KEM-768 and an
3925
+ * ML-KEM-1024 identity and either can be reconstructed on demand.
3926
+ *
3927
+ * Returns `null` when the wallet holds no key — a secret-less wallet, which is what a molecule
3928
+ * deserializer builds for validation context. `generateSecret(null, …)` does NOT throw, so
3929
+ * without this the wallet would derive a plausible-looking identity from a bogus seed and fail
3930
+ * three layers down at AES-GCM instead of at the missing key. The guard lives here rather than
3931
+ * at each call site so a new caller cannot miss it.
3932
+ *
3933
+ * @param parameterSet - 1024 or 768
3901
3934
  */
3902
- initializeMLKEM() {
3935
+ deriveMlKemKeypair(parameterSet) {
3936
+ const params = ML_KEM_PARAMS[parameterSet];
3937
+ if (!params) {
3938
+ throw new Error(`KnishIO: unsupported ML-KEM parameter set ${parameterSet}; expected 1024 or 768.`);
3939
+ }
3940
+ if (!this.key) {
3941
+ return null;
3942
+ }
3903
3943
  const seedHex = generateSecret(this.key, 128);
3904
3944
  const seed = new Uint8Array(64);
3905
3945
  for (let i = 0; i < 64; i++) {
3906
3946
  seed[i] = parseInt(seedHex.substr(i * 2, 2), 16);
3907
3947
  }
3908
- const { publicKey, secretKey } = ml_kem768.keygen(seed);
3909
- this.pubkey = this.serializeKey(publicKey);
3910
- this.privkey = secretKey;
3948
+ const { publicKey, secretKey } = params.kem.keygen(seed);
3949
+ return {
3950
+ pubkey: this.serializeKey(publicKey),
3951
+ privkey: secretKey,
3952
+ params
3953
+ };
3954
+ }
3955
+ /**
3956
+ * ML-KEM parameter set implied by a serialized public key's raw byte length. FIPS 203's key
3957
+ * lengths are disjoint (1568 bytes → ML-KEM-1024, 1184 bytes → ML-KEM-768), so a stored peer
3958
+ * key recovers the parameter set of the session it belongs to without a wire-format change.
3959
+ * Used by AuthToken.restore to resolve a snapshot that predates the field.
3960
+ *
3961
+ * @param pubkey - Base64-serialized ML-KEM public key
3962
+ * @return 1024, 768, or null when the length matches neither
3963
+ */
3964
+ static mlKemParameterSetFromPubkey(pubkey) {
3965
+ if (!pubkey) {
3966
+ return null;
3967
+ }
3968
+ let byteLength;
3969
+ try {
3970
+ byteLength = typeof Buffer !== "undefined" ? Buffer.from(pubkey, "base64").length : atob(pubkey).length;
3971
+ } catch {
3972
+ return null;
3973
+ }
3974
+ if (byteLength === ML_KEM_PARAMS[1024].pkBytes) {
3975
+ return 1024;
3976
+ }
3977
+ if (byteLength === ML_KEM_PARAMS[768].pkBytes) {
3978
+ return 768;
3979
+ }
3980
+ return null;
3981
+ }
3982
+ /**
3983
+ * Initializes the ML-KEM key pair (matches JavaScript SDK exactly). Only ever reached from the
3984
+ * constructor's `secret` branch, so the derivation cannot come back empty here.
3985
+ */
3986
+ initializeMLKEM() {
3987
+ const derived = this.deriveMlKemKeypair(this.mlKemParameterSet);
3988
+ if (!derived) {
3989
+ return;
3990
+ }
3991
+ this.pubkey = derived.pubkey;
3992
+ this.privkey = derived.privkey;
3911
3993
  }
3912
3994
  // =============================================================================
3913
3995
  // HIGH-LEVEL MESSAGE ENCRYPTION (JavaScript SDK Compatibility)
@@ -3916,13 +3998,13 @@ var Wallet = class _Wallet {
3916
3998
  const messageString = JSON.stringify(message);
3917
3999
  const messageUint8 = new TextEncoder().encode(messageString);
3918
4000
  const deserializedPubkey = this.deserializeKey(recipientPubkey);
3919
- const ML_KEM_768_PUBLIC_KEY_BYTES = 1184;
3920
- if (deserializedPubkey.length !== ML_KEM_768_PUBLIC_KEY_BYTES) {
4001
+ const params = ML_KEM_PARAMS[this.mlKemParameterSet];
4002
+ if (deserializedPubkey.length !== params.pkBytes) {
3921
4003
  throw new Error(
3922
- `KnishIO: cannot ML-KEM-encrypt \u2014 recipient public key is ${deserializedPubkey.length} bytes, expected ${ML_KEM_768_PUBLIC_KEY_BYTES} (ML-KEM-768). The node likely did not advertise an ML-KEM public key (upgrade the validator to a PQ-transport build), or authenticate with { encrypt: false }.`
4004
+ `KnishIO: cannot ML-KEM-encrypt \u2014 recipient public key is ${deserializedPubkey.length} bytes, expected ${params.pkBytes} (ML-KEM-${this.mlKemParameterSet}). The peer is not running ML-KEM-${this.mlKemParameterSet}; upgrade the peer, or step this client back to the other parameter set.`
3923
4005
  );
3924
4006
  }
3925
- const { cipherText, sharedSecret } = ml_kem768.encapsulate(deserializedPubkey);
4007
+ const { cipherText, sharedSecret } = params.kem.encapsulate(deserializedPubkey);
3926
4008
  const encryptedMessage = await this.encryptWithSharedSecret(messageUint8, sharedSecret);
3927
4009
  return {
3928
4010
  cipherText: this.serializeKey(cipherText),
@@ -3934,15 +4016,37 @@ var Wallet = class _Wallet {
3934
4016
  return decryptedString === null ? null : JSON.parse(decryptedString);
3935
4017
  }
3936
4018
  /**
3937
- * ML-KEM768 decapsulate + AES-256-GCM decrypt → the RAW decrypted UTF-8 string (no JSON.parse).
4019
+ * ML-KEM decapsulate + AES-256-GCM decrypt → the RAW decrypted UTF-8 string (no JSON.parse).
3938
4020
  * Shared by {@link decryptMessage} (which JSON.parses the result) and the PQ CipherHash transport
3939
- * ({@link decryptMyMessageML768}, which needs the raw response JSON text). PQ-transport Phase E.
4021
+ * ({@link decryptMyMessageML}, which needs the raw response JSON text). PQ-transport Phase E.
3940
4022
  */
3941
4023
  async _mlkemDecryptToString(encryptedData) {
3942
4024
  const { cipherText, encryptedMessage } = encryptedData;
4025
+ const configuredParams = ML_KEM_PARAMS[this.mlKemParameterSet];
4026
+ const otherSet = this.mlKemParameterSet === 1024 ? 768 : 1024;
4027
+ const deserializedCipherText = this.deserializeKey(cipherText);
4028
+ let params = configuredParams;
4029
+ let decapsPrivkey = this.privkey;
4030
+ if (deserializedCipherText.length !== configuredParams.ctBytes) {
4031
+ if (deserializedCipherText.length !== ML_KEM_PARAMS[otherSet].ctBytes) {
4032
+ console.error(
4033
+ `Wallet::decryptMessage() - Ciphertext length mismatch: got ${deserializedCipherText.length}, expected ${configuredParams.ctBytes}`
4034
+ );
4035
+ return null;
4036
+ }
4037
+ const derived = this.deriveMlKemKeypair(otherSet);
4038
+ if (!derived) {
4039
+ console.error(
4040
+ `Wallet::decryptMessage() - cannot derive the ML-KEM-${otherSet} identity: wallet has no key`
4041
+ );
4042
+ return null;
4043
+ }
4044
+ params = derived.params;
4045
+ decapsPrivkey = derived.privkey;
4046
+ }
3943
4047
  let sharedSecret;
3944
4048
  try {
3945
- sharedSecret = ml_kem768.decapsulate(this.deserializeKey(cipherText), this.privkey);
4049
+ sharedSecret = params.kem.decapsulate(deserializedCipherText, decapsPrivkey);
3946
4050
  } catch (e) {
3947
4051
  console.error("Wallet::decryptMessage() - Decapsulation failed", e);
3948
4052
  console.info("Wallet::decryptMessage() - my public key", this.pubkey);
@@ -3992,11 +4096,11 @@ var Wallet = class _Wallet {
3992
4096
  return this.serializeKey(bytes);
3993
4097
  }
3994
4098
  /**
3995
- * Post-quantum (ML-KEM768) CipherHash request envelope: a stringified single-recipient map
4099
+ * Post-quantum (ML-KEM) CipherHash request envelope: a stringified single-recipient map
3996
4100
  * `{ "<hashShare(recipientPubkey)>": {cipherText, encryptedMessage} }` (object-valued, via
3997
4101
  * {@link encryptMessage}). Matches the Rust validator's CipherHash handler. PQ-transport Phase E.
3998
4102
  */
3999
- async encryptStringML768(message, recipientPubkey) {
4103
+ async encryptStringML(message, recipientPubkey) {
4000
4104
  const envelope = await this.encryptMessage(message, recipientPubkey);
4001
4105
  return JSON.stringify({ [this.hashShare(recipientPubkey)]: envelope });
4002
4106
  }
@@ -4004,9 +4108,21 @@ var Wallet = class _Wallet {
4004
4108
  * Decrypt a CipherHash response map addressed to THIS wallet's ML-KEM pubkey
4005
4109
  * (`hashShare(this.pubkey)`) → the RAW decrypted GraphQL response JSON text (NOT JSON.parsed;
4006
4110
  * it replaces the HTTP response body for the normal parser). `null` if no entry / decrypt fails.
4111
+ *
4112
+ * A pre-bump peer addressed its envelope to `hashShare(our_768_pubkey)`, which a wallet
4113
+ * configured at ML-KEM-1024 would never find — so the other identity's share is tried too.
4114
+ * Without this, the permissive length dispatch in {@link _mlkemDecryptToString} is
4115
+ * unreachable on the transport path.
4007
4116
  */
4008
- async decryptMyMessageML768(map) {
4009
- const envelope = map[this.hashShare(this.pubkey)];
4117
+ async decryptMyMessageML(map) {
4118
+ let envelope = map[this.hashShare(this.pubkey)];
4119
+ if (!envelope) {
4120
+ const otherSet = this.mlKemParameterSet === 1024 ? 768 : 1024;
4121
+ const other = this.deriveMlKemKeypair(otherSet);
4122
+ if (other) {
4123
+ envelope = map[this.hashShare(other.pubkey)];
4124
+ }
4125
+ }
4010
4126
  if (!envelope) {
4011
4127
  return null;
4012
4128
  }
@@ -4160,7 +4276,8 @@ z.object({
4160
4276
  serverSdkVersion: z.number().int().min(1).optional(),
4161
4277
  logging: z.boolean().optional(),
4162
4278
  defaultRequestPolicy: z.enum(["cache-first", "cache-only", "network-only", "cache-and-network"]).nullable().optional(),
4163
- secretStorage: z.unknown().optional()
4279
+ secretStorage: z.unknown().optional(),
4280
+ mlKemParameterSet: z.union([z.literal(1024), z.literal(768)]).optional()
4164
4281
  }).strict();
4165
4282
  z.object({
4166
4283
  token: z.string().min(1, "Auth token cannot be empty"),
@@ -5249,6 +5366,7 @@ var Molecule = class _Molecule {
5249
5366
  continuIdPosition;
5250
5367
  parentHashes;
5251
5368
  local;
5369
+ mlKemParameterSet = 1024;
5252
5370
  /**
5253
5371
  * Create new Molecule instance
5254
5372
  * Matches JavaScript SDK constructor signature
@@ -5260,7 +5378,8 @@ var Molecule = class _Molecule {
5260
5378
  remainderWallet = null,
5261
5379
  cellSlug = null,
5262
5380
  version = null,
5263
- continuIdPosition = null
5381
+ continuIdPosition = null,
5382
+ mlKemParameterSet = null
5264
5383
  } = {}) {
5265
5384
  this.status = null;
5266
5385
  this.molecularHash = null;
@@ -5272,6 +5391,7 @@ var Molecule = class _Molecule {
5272
5391
  this.continuIdPosition = continuIdPosition;
5273
5392
  this.atoms = [];
5274
5393
  this.parentHashes = [];
5394
+ this.mlKemParameterSet = mlKemParameterSet || sourceWallet?.mlKemParameterSet || 1024;
5275
5395
  const versionRegistry = versions_default;
5276
5396
  if (version !== null && Object.prototype.hasOwnProperty.call(versionRegistry, version)) {
5277
5397
  this.version = String(version);
@@ -5282,7 +5402,8 @@ var Molecule = class _Molecule {
5282
5402
  bundle,
5283
5403
  token: sourceWallet.token,
5284
5404
  batchId: sourceWallet.batchId,
5285
- characters: sourceWallet.characters
5405
+ characters: sourceWallet.characters,
5406
+ mlKemParameterSet: this.mlKemParameterSet
5286
5407
  });
5287
5408
  } else {
5288
5409
  this.remainderWallet = null;
@@ -5343,7 +5464,8 @@ var Molecule = class _Molecule {
5343
5464
  if (!this.remainderWallet || this.remainderWallet.token !== "USER") {
5344
5465
  this.remainderWallet = Wallet.create({
5345
5466
  secret: this.secret,
5346
- bundle: this.bundle
5467
+ bundle: this.bundle,
5468
+ mlKemParameterSet: this.mlKemParameterSet
5347
5469
  });
5348
5470
  }
5349
5471
  const continuIdMeta = {};
@@ -5730,7 +5852,8 @@ var Molecule = class _Molecule {
5730
5852
  }
5731
5853
  const burnWallet = new Wallet({
5732
5854
  bundle: "0000000000000000000000000000000000000000000000000000000000000000",
5733
- token: this.sourceWallet.token
5855
+ token: this.sourceWallet.token,
5856
+ mlKemParameterSet: this.mlKemParameterSet
5734
5857
  });
5735
5858
  this.addAtom(Atom.create({
5736
5859
  isotope: "V",
@@ -6002,7 +6125,8 @@ var Molecule = class _Molecule {
6002
6125
  position: data.sourceWallet.position,
6003
6126
  bundle: data.sourceWallet.bundle,
6004
6127
  batchId: data.sourceWallet.batchId,
6005
- characters: data.sourceWallet.characters
6128
+ characters: data.sourceWallet.characters,
6129
+ mlKemParameterSet: molecule.mlKemParameterSet
6006
6130
  });
6007
6131
  molecule.sourceWallet.balance = String(data.sourceWallet.balance != null ? data.sourceWallet.balance : 0);
6008
6132
  molecule.sourceWallet.address = data.sourceWallet.address;
@@ -6020,7 +6144,8 @@ var Molecule = class _Molecule {
6020
6144
  position: data.remainderWallet.position,
6021
6145
  bundle: data.remainderWallet.bundle,
6022
6146
  batchId: data.remainderWallet.batchId,
6023
- characters: data.remainderWallet.characters
6147
+ characters: data.remainderWallet.characters,
6148
+ mlKemParameterSet: molecule.mlKemParameterSet
6024
6149
  });
6025
6150
  molecule.remainderWallet.balance = String(data.remainderWallet.balance != null ? data.remainderWallet.balance : 0);
6026
6151
  molecule.remainderWallet.address = data.remainderWallet.address;
@@ -6118,7 +6243,8 @@ var Molecule = class _Molecule {
6118
6243
  secret: this.secret,
6119
6244
  bundle: this.bundle,
6120
6245
  token: this.sourceWallet.token,
6121
- batchId: this.sourceWallet.batchId
6246
+ batchId: this.sourceWallet.batchId,
6247
+ mlKemParameterSet: this.mlKemParameterSet
6122
6248
  });
6123
6249
  if (tradeRates) {
6124
6250
  bufferWallet.tradeRates = tradeRates;
@@ -6389,7 +6515,7 @@ var GraphQLClient = class {
6389
6515
  let encryptedRequest = false;
6390
6516
  let requestInit = init;
6391
6517
  if (wallet && serverPubkey && init && typeof init.body === "string" && this.shouldEncrypt(init.body)) {
6392
- const hashVar = await wallet.encryptStringML768(init.body, serverPubkey);
6518
+ const hashVar = await wallet.encryptStringML(init.body, serverPubkey);
6393
6519
  requestInit = { ...init, body: JSON.stringify({ query: CIPHER_HASH_QUERY, variables: { Hash: hashVar } }) };
6394
6520
  encryptedRequest = true;
6395
6521
  }
@@ -6409,7 +6535,7 @@ var GraphQLClient = class {
6409
6535
  if (typeof hash !== "string") {
6410
6536
  return new Response(text, init2);
6411
6537
  }
6412
- const decrypted = await wallet.decryptMyMessageML768(JSON.parse(hash));
6538
+ const decrypted = await wallet.decryptMyMessageML(JSON.parse(hash));
6413
6539
  return new Response(decrypted != null ? decrypted : text, init2);
6414
6540
  }
6415
6541
  setAuthData({
@@ -6578,6 +6704,22 @@ var AuthToken = class _AuthToken {
6578
6704
  authToken.setWallet(wallet);
6579
6705
  return authToken;
6580
6706
  }
6707
+ /**
6708
+ * ML-KEM parameter set a restored session must use, resolved in three tiers:
6709
+ * an explicit snapshot field, then the stored validator key's length, then ML-KEM-768.
6710
+ *
6711
+ * The final tier is deliberately NOT the constructor default. A snapshot with neither an
6712
+ * explicit field nor a recognisable key can only have come from a pre-bump build, and every
6713
+ * pre-bump build was 768-only — defaulting to 1024 would make the restored wallet advertise
6714
+ * a public key the validator never recorded for that token.
6715
+ */
6716
+ static resolveMlKemParameterSet(snapshot) {
6717
+ const explicit = snapshot.wallet?.mlKemParameterSet;
6718
+ if (explicit) {
6719
+ return Number(explicit) === 768 ? 768 : 1024;
6720
+ }
6721
+ return Wallet.mlKemParameterSetFromPubkey(snapshot.pubkey) ?? 768;
6722
+ }
6581
6723
  /**
6582
6724
  * Restore AuthToken from snapshot
6583
6725
  */
@@ -6585,8 +6727,9 @@ var AuthToken = class _AuthToken {
6585
6727
  const wallet = new Wallet({
6586
6728
  secret,
6587
6729
  token: "AUTH",
6588
- position: snapshot.wallet.position,
6589
- characters: snapshot.wallet.characters
6730
+ position: snapshot.wallet?.position ?? null,
6731
+ characters: snapshot.wallet?.characters ?? null,
6732
+ mlKemParameterSet: _AuthToken.resolveMlKemParameterSet(snapshot)
6590
6733
  });
6591
6734
  return _AuthToken.create({
6592
6735
  token: snapshot.token,
@@ -6654,7 +6797,9 @@ var AuthToken = class _AuthToken {
6654
6797
  };
6655
6798
  }
6656
6799
  /**
6657
- * Create snapshot for persistence
6800
+ * Create snapshot for persistence. The wallet's ML-KEM parameter set is recorded beside its
6801
+ * position and characters so a stepped-back ML-KEM-768 session restores as 768 rather than
6802
+ * silently taking the constructor default.
6658
6803
  */
6659
6804
  toSnapshot() {
6660
6805
  return {
@@ -6665,7 +6810,8 @@ var AuthToken = class _AuthToken {
6665
6810
  ...this.$__wallet ? {
6666
6811
  wallet: {
6667
6812
  position: this.$__wallet.position,
6668
- characters: this.$__wallet.characters
6813
+ characters: this.$__wallet.characters,
6814
+ mlKemParameterSet: this.$__wallet.mlKemParameterSet
6669
6815
  }
6670
6816
  } : {}
6671
6817
  };
@@ -6962,7 +7108,8 @@ var KnishIOClientConfigSchema2 = z.object({
6962
7108
  // isn't rejected.
6963
7109
  defaultRequestPolicy: z.enum(["cache-first", "cache-only", "network-only", "cache-and-network"]).nullable().optional(),
6964
7110
  // Pluggable hardware envelope encryption secret storage provider
6965
- secretStorage: z.unknown().optional()
7111
+ secretStorage: z.unknown().optional(),
7112
+ mlKemParameterSet: z.union([z.literal(1024), z.literal(768)]).optional()
6966
7113
  }).strict();
6967
7114
  var EnvironmentConfigSchema = z.object({
6968
7115
  NODE_ENV: z.enum(["development", "production", "test"]).optional(),
@@ -10552,10 +10699,121 @@ function constantTimeCompare(a, b) {
10552
10699
  return result === 0;
10553
10700
  }
10554
10701
 
10702
+ // src/storage/secretEnvelope.ts
10703
+ init_SecretStorageException();
10704
+ var ENVELOPE_ALGORITHM = "AES-GCM";
10705
+ var DEFAULT_ITERATIONS = 1e5;
10706
+ var SECRET_KEY_PREFIX = "knishio:secret:";
10707
+ var RECOVERY_KEY_PREFIX = "knishio:recovery:";
10708
+ var GCM_IV_LENGTH = 12;
10709
+ var SALT_LENGTH = 16;
10710
+ var textEncoder2 = new TextEncoder();
10711
+ function uint8ArrayToBase64(bytes) {
10712
+ let binary = "";
10713
+ const len = bytes.byteLength;
10714
+ for (let i = 0; i < len; i++) {
10715
+ const byte = bytes[i];
10716
+ if (byte !== void 0) {
10717
+ binary += String.fromCharCode(byte);
10718
+ }
10719
+ }
10720
+ return btoa(binary);
10721
+ }
10722
+ function base64ToUint8Array(base64) {
10723
+ const binary = atob(base64);
10724
+ const len = binary.length;
10725
+ const bytes = new Uint8Array(len);
10726
+ for (let i = 0; i < len; i++) {
10727
+ bytes[i] = binary.charCodeAt(i);
10728
+ }
10729
+ return bytes;
10730
+ }
10731
+ async function deriveEnvelopeKey(passphrase, salt, iterations = DEFAULT_ITERATIONS) {
10732
+ if (typeof globalThis.crypto === "undefined" || typeof globalThis.crypto.subtle === "undefined") {
10733
+ throw new SecretStorageException("WebCrypto API is not available");
10734
+ }
10735
+ const passphraseBytes = textEncoder2.encode(passphrase);
10736
+ try {
10737
+ const baseKey = await globalThis.crypto.subtle.importKey(
10738
+ "raw",
10739
+ passphraseBytes,
10740
+ "PBKDF2",
10741
+ false,
10742
+ ["deriveKey"]
10743
+ );
10744
+ return await globalThis.crypto.subtle.deriveKey(
10745
+ {
10746
+ name: "PBKDF2",
10747
+ salt,
10748
+ iterations,
10749
+ hash: "SHA-256"
10750
+ },
10751
+ baseKey,
10752
+ { name: "AES-GCM", length: 256 },
10753
+ false,
10754
+ ["encrypt", "decrypt"]
10755
+ );
10756
+ } finally {
10757
+ zeroizeBytes(passphraseBytes);
10758
+ }
10759
+ }
10760
+ async function sealEnvelope(secret, passphrase, metadata) {
10761
+ if (typeof globalThis.crypto === "undefined" || typeof globalThis.crypto.subtle === "undefined") {
10762
+ throw new SecretStorageException("WebCrypto API is not available");
10763
+ }
10764
+ const salt = new Uint8Array(SALT_LENGTH);
10765
+ const iv = new Uint8Array(GCM_IV_LENGTH);
10766
+ globalThis.crypto.getRandomValues(salt);
10767
+ globalThis.crypto.getRandomValues(iv);
10768
+ const key = await deriveEnvelopeKey(passphrase, salt, DEFAULT_ITERATIONS);
10769
+ const secretBytes = textEncoder2.encode(secret);
10770
+ try {
10771
+ const encryptedBuffer = await globalThis.crypto.subtle.encrypt(
10772
+ {
10773
+ name: ENVELOPE_ALGORITHM,
10774
+ iv
10775
+ },
10776
+ key,
10777
+ secretBytes
10778
+ );
10779
+ const ciphertext = uint8ArrayToBase64(new Uint8Array(encryptedBuffer));
10780
+ return {
10781
+ version: 1,
10782
+ ciphertext,
10783
+ iv: uint8ArrayToBase64(iv),
10784
+ salt: uint8ArrayToBase64(salt),
10785
+ algorithm: ENVELOPE_ALGORITHM,
10786
+ iterations: DEFAULT_ITERATIONS,
10787
+ metadata
10788
+ };
10789
+ } finally {
10790
+ zeroizeBytes(secretBytes);
10791
+ }
10792
+ }
10793
+ async function openEnvelope(payload, passphrase) {
10794
+ if (typeof globalThis.crypto === "undefined" || typeof globalThis.crypto.subtle === "undefined") {
10795
+ throw new SecretStorageException("WebCrypto API is not available");
10796
+ }
10797
+ const salt = base64ToUint8Array(payload.salt);
10798
+ const iv = base64ToUint8Array(payload.iv);
10799
+ const ciphertext = base64ToUint8Array(payload.ciphertext);
10800
+ const key = await deriveEnvelopeKey(passphrase, salt, payload.iterations ?? DEFAULT_ITERATIONS);
10801
+ const decryptedBuffer = await globalThis.crypto.subtle.decrypt(
10802
+ {
10803
+ name: ENVELOPE_ALGORITHM,
10804
+ iv
10805
+ },
10806
+ key,
10807
+ ciphertext
10808
+ );
10809
+ return new Uint8Array(decryptedBuffer);
10810
+ }
10811
+
10555
10812
  // src/storage/MemorySecretStorageProvider.ts
10556
10813
  var MemorySecretStorageProvider = class {
10557
10814
  providerType = "memory";
10558
10815
  secrets = /* @__PURE__ */ new Map();
10816
+ recoverySecrets = /* @__PURE__ */ new Map();
10559
10817
  /**
10560
10818
  * Memory storage is not hardware backed
10561
10819
  */
@@ -10586,6 +10844,17 @@ var MemorySecretStorageProvider = class {
10586
10844
  providerType: this.providerType
10587
10845
  };
10588
10846
  this.secrets.set(bundleHash, { secret, metadata });
10847
+ if (options?.recoveryPassphrase) {
10848
+ const recoveryMetadata = {
10849
+ bundleHash,
10850
+ label: options?.label,
10851
+ createdAt: Date.now(),
10852
+ hardwareBacked: false,
10853
+ providerType: "webcrypto-aes-gcm"
10854
+ };
10855
+ const recoveryPayload = await sealEnvelope(secret, options.recoveryPassphrase, recoveryMetadata);
10856
+ this.recoverySecrets.set(bundleHash, JSON.stringify(recoveryPayload));
10857
+ }
10589
10858
  }
10590
10859
  /**
10591
10860
  * Retrieve a secret from memory
@@ -10598,6 +10867,7 @@ var MemorySecretStorageProvider = class {
10598
10867
  * Delete a stored secret
10599
10868
  */
10600
10869
  async deleteSecret(bundleHash) {
10870
+ this.recoverySecrets.delete(bundleHash);
10601
10871
  return this.secrets.delete(bundleHash);
10602
10872
  }
10603
10873
  /**
@@ -10627,6 +10897,48 @@ var MemorySecretStorageProvider = class {
10627
10897
  */
10628
10898
  clear() {
10629
10899
  this.secrets.clear();
10900
+ this.recoverySecrets.clear();
10901
+ }
10902
+ /**
10903
+ * Recover a secret using its recovery envelope and restore it
10904
+ */
10905
+ async recoverSecret(bundleHash, recoveryPassphrase, options) {
10906
+ if (!bundleHash) {
10907
+ throw new SecretStorageException("Bundle hash cannot be empty");
10908
+ }
10909
+ if (!recoveryPassphrase) {
10910
+ throw new SecretStorageException("Recovery passphrase cannot be empty");
10911
+ }
10912
+ const raw = this.recoverySecrets.get(bundleHash);
10913
+ if (!raw) {
10914
+ throw SecretStorageException.notFound(bundleHash);
10915
+ }
10916
+ let payload;
10917
+ try {
10918
+ payload = JSON.parse(raw);
10919
+ } catch {
10920
+ throw SecretStorageException.decryptionFailed("Corrupted recovery payload format");
10921
+ }
10922
+ let decryptedBytes;
10923
+ try {
10924
+ decryptedBytes = await openEnvelope(payload, recoveryPassphrase);
10925
+ } catch (err) {
10926
+ if (err instanceof SecretStorageException) {
10927
+ throw err;
10928
+ }
10929
+ const msg = err instanceof Error ? err.message : String(err);
10930
+ throw SecretStorageException.decryptionFailed(msg);
10931
+ }
10932
+ let secretStr;
10933
+ try {
10934
+ secretStr = new TextDecoder().decode(decryptedBytes);
10935
+ } finally {
10936
+ zeroizeBytes(decryptedBytes);
10937
+ }
10938
+ await this.storeSecret(bundleHash, secretStr, {
10939
+ ...options,
10940
+ recoveryPassphrase
10941
+ });
10630
10942
  }
10631
10943
  };
10632
10944
 
@@ -10648,6 +10960,7 @@ var KnishIOClient = class {
10648
10960
  $__authTokenObjects = {};
10649
10961
  $__authToken = null;
10650
10962
  $__authInProcess = false;
10963
+ $__mlKemParameterSet = 1024;
10651
10964
  $__remainderWallet = null;
10652
10965
  lastMoleculeQuery = null;
10653
10966
  abortControllers = /* @__PURE__ */ new Map();
@@ -10692,7 +11005,8 @@ var KnishIOClient = class {
10692
11005
  client,
10693
11006
  serverSdkVersion,
10694
11007
  logging,
10695
- defaultRequestPolicy
11008
+ defaultRequestPolicy,
11009
+ mlKemParameterSet: config.mlKemParameterSet ?? 1024
10696
11010
  });
10697
11011
  if (config.secretStorage) {
10698
11012
  this.$__secretStorage = config.secretStorage;
@@ -10708,10 +11022,12 @@ var KnishIOClient = class {
10708
11022
  client = null,
10709
11023
  serverSdkVersion = 3,
10710
11024
  logging = false,
10711
- defaultRequestPolicy = null
11025
+ defaultRequestPolicy = null,
11026
+ mlKemParameterSet = 1024
10712
11027
  }) {
10713
11028
  this.reset();
10714
11029
  this.$__logging = logging;
11030
+ this.setMlKemParameterSet(mlKemParameterSet);
10715
11031
  this.$__authTokenObjects = {};
10716
11032
  this.setUri(uri);
10717
11033
  if (cellSlug) {
@@ -10732,6 +11048,17 @@ var KnishIOClient = class {
10732
11048
  this.$__serverSdkVersion = serverSdkVersion;
10733
11049
  this.$__defaultRequestPolicy = defaultRequestPolicy;
10734
11050
  }
11051
+ getMlKemParameterSet() {
11052
+ return this.$__mlKemParameterSet || 1024;
11053
+ }
11054
+ setMlKemParameterSet(parameterSet) {
11055
+ const paramNum = Number(parameterSet);
11056
+ if (![1024, 768].includes(paramNum)) {
11057
+ throw new Error(`KnishIO: unsupported ML-KEM parameter set ${parameterSet}; expected 1024 or 768.`);
11058
+ }
11059
+ this.$__mlKemParameterSet = paramNum;
11060
+ return this;
11061
+ }
10735
11062
  /**
10736
11063
  * Get random uri from specified this.$__uris
10737
11064
  */
@@ -10968,7 +11295,8 @@ var KnishIOClient = class {
10968
11295
  bundle,
10969
11296
  token: "USER",
10970
11297
  batchId: sourceWallet.batchId,
10971
- characters: sourceWallet.characters
11298
+ characters: sourceWallet.characters,
11299
+ mlKemParameterSet: this.getMlKemParameterSet()
10972
11300
  }));
10973
11301
  return new Molecule({
10974
11302
  secret,
@@ -10977,7 +11305,8 @@ var KnishIOClient = class {
10977
11305
  remainderWallet: this.getRemainderWallet(),
10978
11306
  cellSlug: this.getCellSlug(),
10979
11307
  version: this.getServerSdkVersion(),
10980
- continuIdPosition
11308
+ continuIdPosition,
11309
+ mlKemParameterSet: this.getMlKemParameterSet()
10981
11310
  });
10982
11311
  }
10983
11312
  /**
@@ -11089,7 +11418,8 @@ var KnishIOClient = class {
11089
11418
  }))?.payload();
11090
11419
  if (!sourceWallet) {
11091
11420
  sourceWallet = new Wallet({
11092
- secret: this.getSecret()
11421
+ secret: this.getSecret(),
11422
+ mlKemParameterSet: this.getMlKemParameterSet()
11093
11423
  });
11094
11424
  } else {
11095
11425
  sourceWallet.key = Wallet.generateKey({
@@ -11132,7 +11462,8 @@ var KnishIOClient = class {
11132
11462
  }
11133
11463
  const recipientWallet = Wallet.create({
11134
11464
  bundle: bundleHash,
11135
- token
11465
+ token,
11466
+ mlKemParameterSet: this.getMlKemParameterSet()
11136
11467
  });
11137
11468
  if (batchId !== null) {
11138
11469
  recipientWallet.batchId = batchId;
@@ -11199,7 +11530,8 @@ var KnishIOClient = class {
11199
11530
  const recipientWallets = recipients.map((recipient) => {
11200
11531
  const recipientWallet = Wallet.create({
11201
11532
  bundle: recipient.bundleHash,
11202
- token
11533
+ token,
11534
+ mlKemParameterSet: this.getMlKemParameterSet()
11203
11535
  });
11204
11536
  if (recipient.batchId !== null && recipient.batchId !== void 0) {
11205
11537
  recipientWallet.batchId = recipient.batchId;
@@ -11823,7 +12155,8 @@ var KnishIOClient = class {
11823
12155
  secret: this.getSecret(),
11824
12156
  bundle: this.getBundle(),
11825
12157
  token,
11826
- batchId
12158
+ batchId,
12159
+ mlKemParameterSet: this.getMlKemParameterSet()
11827
12160
  });
11828
12161
  await mutation.fillMolecule({
11829
12162
  recipientWallet,
@@ -11891,7 +12224,8 @@ var KnishIOClient = class {
11891
12224
  const recipientWallet = new Wallet({
11892
12225
  secret: this.getSecret(),
11893
12226
  bundle: "0000000000000000000000000000000000000000000000000000000000000000",
11894
- token
12227
+ token,
12228
+ mlKemParameterSet: this.getMlKemParameterSet()
11895
12229
  });
11896
12230
  await mutation.fillMolecule({
11897
12231
  recipientWallet,
@@ -11968,7 +12302,8 @@ var KnishIOClient = class {
11968
12302
  const newWallet = new Wallet({
11969
12303
  secret: this.getSecret(),
11970
12304
  bundle: this.getBundle(),
11971
- token
12305
+ token,
12306
+ mlKemParameterSet: this.getMlKemParameterSet()
11972
12307
  });
11973
12308
  await mutation.fillMolecule(newWallet);
11974
12309
  const response = await this.executeQuery(mutation);
@@ -12296,7 +12631,8 @@ var KnishIOClient = class {
12296
12631
  this.setSecret(secret);
12297
12632
  const wallet = new Wallet({
12298
12633
  secret,
12299
- token: "AUTH"
12634
+ token: "AUTH",
12635
+ mlKemParameterSet: this.getMlKemParameterSet()
12300
12636
  });
12301
12637
  const molecule = await this.createMolecule({
12302
12638
  secret,
@@ -12424,45 +12760,25 @@ var MemoryStorageBackend = class {
12424
12760
  return Array.from(this.store.keys());
12425
12761
  }
12426
12762
  };
12427
- function uint8ArrayToBase64(bytes) {
12428
- let binary = "";
12429
- const len = bytes.byteLength;
12430
- for (let i = 0; i < len; i++) {
12431
- const byte = bytes[i];
12432
- if (byte !== void 0) {
12433
- binary += String.fromCharCode(byte);
12434
- }
12435
- }
12436
- return btoa(binary);
12437
- }
12438
- function base64ToUint8Array(base64) {
12439
- const binary = atob(base64);
12440
- const len = binary.length;
12441
- const bytes = new Uint8Array(len);
12442
- for (let i = 0; i < len; i++) {
12443
- bytes[i] = binary.charCodeAt(i);
12444
- }
12445
- return bytes;
12446
- }
12447
- var textEncoder2 = new TextEncoder();
12448
12763
  var textDecoder = new TextDecoder();
12449
- var KEY_PREFIX = "knishio:secret:";
12450
- var DEFAULT_ITERATIONS = 1e5;
12764
+ var KEY_PREFIX = SECRET_KEY_PREFIX;
12451
12765
  var WebCryptoSecretStorageProvider = class {
12452
12766
  providerType = "webcrypto-aes-gcm";
12453
12767
  backend;
12454
12768
  defaultPassphrase;
12455
- hardwareBacked;
12456
12769
  constructor(options = {}) {
12457
12770
  this.backend = options.backend ?? new MemoryStorageBackend();
12458
12771
  this.defaultPassphrase = options.defaultPassphrase;
12459
- this.hardwareBacked = options.hardwareBacked ?? false;
12460
12772
  }
12461
12773
  /**
12462
- * Whether this provider is backed by hardware (e.g. WebAuthn PRF wrapping)
12774
+ * True only when this provider holds a non-exportable key inside platform-secure
12775
+ * hardware (Android TEE/StrongBox, Secure Enclave, TPM) and learned that from the
12776
+ * platform itself — never from a caller argument. Software envelope providers
12777
+ * return false. The value is persisted as `metadata.hardwareBacked` in every
12778
+ * envelope this provider writes.
12463
12779
  */
12464
12780
  isHardwareBacked() {
12465
- return this.hardwareBacked;
12781
+ return false;
12466
12782
  }
12467
12783
  /**
12468
12784
  * Check if WebCrypto subtle API is available
@@ -12470,38 +12786,6 @@ var WebCryptoSecretStorageProvider = class {
12470
12786
  async isAvailable() {
12471
12787
  return typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined";
12472
12788
  }
12473
- /**
12474
- * Derive an AES-GCM CryptoKey from a passphrase and salt using PBKDF2
12475
- */
12476
- async deriveKey(passphrase, salt, iterations = DEFAULT_ITERATIONS) {
12477
- if (!await this.isAvailable()) {
12478
- throw SecretStorageException.unavailable(this.providerType, "WebCrypto API is not available");
12479
- }
12480
- const passphraseBytes = textEncoder2.encode(passphrase);
12481
- try {
12482
- const baseKey = await globalThis.crypto.subtle.importKey(
12483
- "raw",
12484
- passphraseBytes,
12485
- "PBKDF2",
12486
- false,
12487
- ["deriveKey"]
12488
- );
12489
- return await globalThis.crypto.subtle.deriveKey(
12490
- {
12491
- name: "PBKDF2",
12492
- salt,
12493
- iterations,
12494
- hash: "SHA-256"
12495
- },
12496
- baseKey,
12497
- { name: "AES-GCM", length: 256 },
12498
- false,
12499
- ["encrypt", "decrypt"]
12500
- );
12501
- } finally {
12502
- zeroizeBytes(passphraseBytes);
12503
- }
12504
- }
12505
12789
  /**
12506
12790
  * Store and encrypt a master secret
12507
12791
  */
@@ -12516,44 +12800,36 @@ var WebCryptoSecretStorageProvider = class {
12516
12800
  if (!passphrase) {
12517
12801
  throw new SecretStorageException("Passphrase required for envelope encryption");
12518
12802
  }
12519
- const salt = new Uint8Array(16);
12520
- const iv = new Uint8Array(12);
12521
- globalThis.crypto.getRandomValues(salt);
12522
- globalThis.crypto.getRandomValues(iv);
12523
- const key = await this.deriveKey(passphrase, salt, DEFAULT_ITERATIONS);
12524
- const secretBytes = textEncoder2.encode(secret);
12803
+ if (!await this.isAvailable()) {
12804
+ throw SecretStorageException.unavailable(this.providerType, "WebCrypto API is not available");
12805
+ }
12525
12806
  try {
12526
- const encryptedBuffer = await globalThis.crypto.subtle.encrypt(
12527
- {
12528
- name: "AES-GCM",
12529
- iv
12530
- },
12531
- key,
12532
- secretBytes
12533
- );
12534
- const ciphertext = uint8ArrayToBase64(new Uint8Array(encryptedBuffer));
12535
12807
  const metadata = {
12536
12808
  bundleHash,
12537
12809
  label: options?.label,
12538
12810
  createdAt: Date.now(),
12539
- hardwareBacked: this.hardwareBacked,
12811
+ hardwareBacked: false,
12540
12812
  providerType: this.providerType
12541
12813
  };
12542
- const payload = {
12543
- version: 1,
12544
- ciphertext,
12545
- iv: uint8ArrayToBase64(iv),
12546
- salt: uint8ArrayToBase64(salt),
12547
- algorithm: "AES-GCM",
12548
- iterations: DEFAULT_ITERATIONS,
12549
- metadata
12550
- };
12814
+ const payload = await sealEnvelope(secret, passphrase, metadata);
12551
12815
  await this.backend.setItem(`${KEY_PREFIX}${bundleHash}`, JSON.stringify(payload));
12816
+ if (options?.recoveryPassphrase) {
12817
+ const recoveryMetadata = {
12818
+ bundleHash,
12819
+ label: options?.label,
12820
+ createdAt: Date.now(),
12821
+ hardwareBacked: false,
12822
+ providerType: "webcrypto-aes-gcm"
12823
+ };
12824
+ const recoveryPayload = await sealEnvelope(secret, options.recoveryPassphrase, recoveryMetadata);
12825
+ await this.backend.setItem(`${RECOVERY_KEY_PREFIX}${bundleHash}`, JSON.stringify(recoveryPayload));
12826
+ }
12552
12827
  } catch (err) {
12828
+ if (err instanceof SecretStorageException) {
12829
+ throw err;
12830
+ }
12553
12831
  const msg = err instanceof Error ? err.message : String(err);
12554
12832
  throw new SecretStorageException(`Encryption failed: ${msg}`);
12555
- } finally {
12556
- zeroizeBytes(secretBytes);
12557
12833
  }
12558
12834
  }
12559
12835
  /**
@@ -12574,26 +12850,20 @@ var WebCryptoSecretStorageProvider = class {
12574
12850
  if (!passphrase) {
12575
12851
  throw new SecretStorageException("Passphrase required for secret decryption");
12576
12852
  }
12577
- const salt = base64ToUint8Array(payload.salt);
12578
- const iv = base64ToUint8Array(payload.iv);
12579
- const ciphertext = base64ToUint8Array(payload.ciphertext);
12853
+ if (!await this.isAvailable()) {
12854
+ throw SecretStorageException.unavailable(this.providerType, "WebCrypto API is not available");
12855
+ }
12580
12856
  try {
12581
- const key = await this.deriveKey(passphrase, salt, payload.iterations ?? DEFAULT_ITERATIONS);
12582
- const decryptedBuffer = await globalThis.crypto.subtle.decrypt(
12583
- {
12584
- name: "AES-GCM",
12585
- iv
12586
- },
12587
- key,
12588
- ciphertext
12589
- );
12590
- const decryptedBytes = new Uint8Array(decryptedBuffer);
12857
+ const decryptedBytes = await openEnvelope(payload, passphrase);
12591
12858
  try {
12592
12859
  return textDecoder.decode(decryptedBytes);
12593
12860
  } finally {
12594
12861
  zeroizeBytes(decryptedBytes);
12595
12862
  }
12596
12863
  } catch (err) {
12864
+ if (err instanceof SecretStorageException) {
12865
+ throw err;
12866
+ }
12597
12867
  const msg = err instanceof Error ? err.message : String(err);
12598
12868
  throw SecretStorageException.decryptionFailed(msg);
12599
12869
  }
@@ -12603,7 +12873,9 @@ var WebCryptoSecretStorageProvider = class {
12603
12873
  */
12604
12874
  async deleteSecret(bundleHash) {
12605
12875
  const key = `${KEY_PREFIX}${bundleHash}`;
12876
+ const recoveryKey = `${RECOVERY_KEY_PREFIX}${bundleHash}`;
12606
12877
  const result = await this.backend.removeItem(key);
12878
+ await this.backend.removeItem(recoveryKey);
12607
12879
  return result !== false;
12608
12880
  }
12609
12881
  /**
@@ -12618,7 +12890,7 @@ var WebCryptoSecretStorageProvider = class {
12618
12890
  */
12619
12891
  async listSecrets() {
12620
12892
  const keys = await this.backend.keys();
12621
- const matchingKeys = keys.filter((k) => k.startsWith(KEY_PREFIX));
12893
+ const matchingKeys = keys.filter((k) => k.startsWith(KEY_PREFIX) && !k.startsWith(RECOVERY_KEY_PREFIX));
12622
12894
  const results = [];
12623
12895
  for (const key of matchingKeys) {
12624
12896
  const raw = await this.backend.getItem(key);
@@ -12652,20 +12924,11 @@ var WebCryptoSecretStorageProvider = class {
12652
12924
  if (!passphrase) {
12653
12925
  throw new SecretStorageException("Passphrase required for secret decryption");
12654
12926
  }
12655
- const salt = base64ToUint8Array(payload.salt);
12656
- const iv = base64ToUint8Array(payload.iv);
12657
- const ciphertext = base64ToUint8Array(payload.ciphertext);
12927
+ if (!await this.isAvailable()) {
12928
+ throw SecretStorageException.unavailable(this.providerType, "WebCrypto API is not available");
12929
+ }
12658
12930
  try {
12659
- const key = await this.deriveKey(passphrase, salt, payload.iterations ?? DEFAULT_ITERATIONS);
12660
- const decryptedBuffer = await globalThis.crypto.subtle.decrypt(
12661
- {
12662
- name: "AES-GCM",
12663
- iv
12664
- },
12665
- key,
12666
- ciphertext
12667
- );
12668
- const decryptedBytes = new Uint8Array(decryptedBuffer);
12931
+ const decryptedBytes = await openEnvelope(payload, passphrase);
12669
12932
  return await withSecureBytes(decryptedBytes, async (bytes) => {
12670
12933
  const secretString = textDecoder.decode(bytes);
12671
12934
  return await fn(secretString);
@@ -12678,6 +12941,1071 @@ var WebCryptoSecretStorageProvider = class {
12678
12941
  throw SecretStorageException.decryptionFailed(msg);
12679
12942
  }
12680
12943
  }
12944
+ /**
12945
+ * Recover a secret using its recovery envelope and re-enroll it
12946
+ */
12947
+ async recoverSecret(bundleHash, recoveryPassphrase, options) {
12948
+ if (!bundleHash) {
12949
+ throw new SecretStorageException("Bundle hash cannot be empty");
12950
+ }
12951
+ if (!recoveryPassphrase) {
12952
+ throw new SecretStorageException("Recovery passphrase cannot be empty");
12953
+ }
12954
+ const raw = await this.backend.getItem(`${RECOVERY_KEY_PREFIX}${bundleHash}`);
12955
+ if (!raw) {
12956
+ throw SecretStorageException.notFound(bundleHash);
12957
+ }
12958
+ let payload;
12959
+ try {
12960
+ payload = JSON.parse(raw);
12961
+ } catch {
12962
+ throw SecretStorageException.decryptionFailed("Corrupted recovery payload format");
12963
+ }
12964
+ let decryptedBytes;
12965
+ try {
12966
+ decryptedBytes = await openEnvelope(payload, recoveryPassphrase);
12967
+ } catch (err) {
12968
+ if (err instanceof SecretStorageException) {
12969
+ throw err;
12970
+ }
12971
+ const msg = err instanceof Error ? err.message : String(err);
12972
+ throw SecretStorageException.decryptionFailed(msg);
12973
+ }
12974
+ let secretStr;
12975
+ try {
12976
+ secretStr = textDecoder.decode(decryptedBytes);
12977
+ } finally {
12978
+ zeroizeBytes(decryptedBytes);
12979
+ }
12980
+ const storePassphrase = options?.passphrase ?? this.defaultPassphrase ?? recoveryPassphrase;
12981
+ await this.storeSecret(bundleHash, secretStr, {
12982
+ ...options,
12983
+ passphrase: storePassphrase,
12984
+ recoveryPassphrase
12985
+ });
12986
+ }
12987
+ };
12988
+
12989
+ // src/storage/FileStorageBackend.ts
12990
+ init_SecretStorageException();
12991
+ var FileStorageBackend = class {
12992
+ filePath;
12993
+ store = /* @__PURE__ */ new Map();
12994
+ loaded = false;
12995
+ constructor(filePath) {
12996
+ if (!filePath) {
12997
+ throw new SecretStorageException("Storage file path cannot be empty");
12998
+ }
12999
+ this.filePath = filePath;
13000
+ }
13001
+ async getFs() {
13002
+ try {
13003
+ const fs = await import('fs/promises');
13004
+ const path = await import('path');
13005
+ return { fs, path };
13006
+ } catch {
13007
+ throw SecretStorageException.unavailable(
13008
+ "file-storage",
13009
+ "FileStorageBackend is only supported in Node.js environments with node:fs access"
13010
+ );
13011
+ }
13012
+ }
13013
+ async ensureLoaded() {
13014
+ if (this.loaded) {
13015
+ return this.store;
13016
+ }
13017
+ const { fs } = await this.getFs();
13018
+ try {
13019
+ const content = await fs.readFile(this.filePath, "utf8");
13020
+ let parsed;
13021
+ try {
13022
+ parsed = JSON.parse(content);
13023
+ } catch {
13024
+ throw SecretStorageException.decryptionFailed("Corrupted storage file format");
13025
+ }
13026
+ if (parsed && typeof parsed === "object") {
13027
+ this.store = new Map(Object.entries(parsed).map(([k, v]) => [k, String(v)]));
13028
+ }
13029
+ } catch (err) {
13030
+ if (err instanceof SecretStorageException) {
13031
+ throw err;
13032
+ }
13033
+ const nodeErr = err;
13034
+ if (nodeErr?.code !== "ENOENT") {
13035
+ const msg = err instanceof Error ? err.message : String(err);
13036
+ throw new SecretStorageException(`Failed to read storage file: ${msg}`);
13037
+ }
13038
+ this.store = /* @__PURE__ */ new Map();
13039
+ }
13040
+ this.loaded = true;
13041
+ return this.store;
13042
+ }
13043
+ async persist() {
13044
+ const { fs, path } = await this.getFs();
13045
+ const dir = path.dirname(this.filePath);
13046
+ if (dir && dir !== ".") {
13047
+ await fs.mkdir(dir, { recursive: true });
13048
+ }
13049
+ const tmpPath = `${this.filePath}.tmp.${Date.now()}_${Math.random().toString(36).slice(2)}`;
13050
+ const data = JSON.stringify(Object.fromEntries(this.store), null, 2);
13051
+ try {
13052
+ await fs.writeFile(tmpPath, data, { mode: 384, encoding: "utf8" });
13053
+ if (typeof process !== "undefined" && process.platform !== "win32") {
13054
+ try {
13055
+ await fs.chmod(tmpPath, 384);
13056
+ } catch {
13057
+ }
13058
+ }
13059
+ await fs.rename(tmpPath, this.filePath);
13060
+ } catch (err) {
13061
+ try {
13062
+ await fs.unlink(tmpPath);
13063
+ } catch {
13064
+ }
13065
+ const msg = err instanceof Error ? err.message : String(err);
13066
+ throw new SecretStorageException(`Failed to persist storage file: ${msg}`);
13067
+ }
13068
+ }
13069
+ async getItem(key) {
13070
+ await this.ensureLoaded();
13071
+ return this.store.get(key) ?? null;
13072
+ }
13073
+ async setItem(key, value) {
13074
+ await this.ensureLoaded();
13075
+ this.store.set(key, value);
13076
+ await this.persist();
13077
+ }
13078
+ async removeItem(key) {
13079
+ await this.ensureLoaded();
13080
+ const existed = this.store.delete(key);
13081
+ if (existed) {
13082
+ await this.persist();
13083
+ }
13084
+ return existed;
13085
+ }
13086
+ async keys() {
13087
+ await this.ensureLoaded();
13088
+ return Array.from(this.store.keys());
13089
+ }
13090
+ };
13091
+
13092
+ // src/storage/WebStorageBackend.ts
13093
+ init_SecretStorageException();
13094
+ var WebStorageBackend = class {
13095
+ storage;
13096
+ prefix;
13097
+ constructor(storage, prefix = "knishio:") {
13098
+ if (storage) {
13099
+ this.storage = storage;
13100
+ } else if (typeof globalThis !== "undefined" && globalThis.localStorage) {
13101
+ this.storage = globalThis.localStorage;
13102
+ } else {
13103
+ throw SecretStorageException.unavailable(
13104
+ "web-storage",
13105
+ "WebStorageBackend requires a Storage object or global localStorage"
13106
+ );
13107
+ }
13108
+ this.prefix = prefix;
13109
+ }
13110
+ getItem(key) {
13111
+ return this.storage.getItem(key);
13112
+ }
13113
+ setItem(key, value) {
13114
+ this.storage.setItem(key, value);
13115
+ }
13116
+ removeItem(key) {
13117
+ const existed = this.storage.getItem(key) !== null;
13118
+ this.storage.removeItem(key);
13119
+ return existed;
13120
+ }
13121
+ keys() {
13122
+ const result = [];
13123
+ const len = this.storage.length;
13124
+ for (let i = 0; i < len; i++) {
13125
+ const k = this.storage.key(i);
13126
+ if (k !== null) {
13127
+ if (!this.prefix || k.startsWith(this.prefix)) {
13128
+ result.push(k);
13129
+ }
13130
+ }
13131
+ }
13132
+ return result;
13133
+ }
13134
+ };
13135
+
13136
+ // src/storage/WebAuthnPrfSecretStorageProvider.ts
13137
+ init_SecretStorageException();
13138
+ var PRF_SALT_LABEL = "knishio:secret-storage:webauthn-prf:v1";
13139
+ var KEK_INFO = "knishio:secret-storage:kek:v1";
13140
+ var KEY_PREFIX2 = SECRET_KEY_PREFIX;
13141
+ var GCM_IV_LENGTH2 = 12;
13142
+ var textEncoder3 = new TextEncoder();
13143
+ var textDecoder2 = new TextDecoder();
13144
+ function base64UrlEncode(bytes) {
13145
+ return uint8ArrayToBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
13146
+ }
13147
+ function base64UrlDecode(str) {
13148
+ let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
13149
+ while (base64.length % 4 !== 0) {
13150
+ base64 += "=";
13151
+ }
13152
+ return base64ToUint8Array(base64);
13153
+ }
13154
+ function toUint8Array(buf) {
13155
+ if (buf instanceof Uint8Array) {
13156
+ return buf;
13157
+ }
13158
+ if (ArrayBuffer.isView(buf)) {
13159
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
13160
+ }
13161
+ return new Uint8Array(buf);
13162
+ }
13163
+ async function computePrfSalt() {
13164
+ const hash = await globalThis.crypto.subtle.digest("SHA-256", textEncoder3.encode(PRF_SALT_LABEL));
13165
+ return new Uint8Array(hash);
13166
+ }
13167
+ async function deriveKekFromPrf(prfOutput, prfSalt) {
13168
+ const hkdfKey = await globalThis.crypto.subtle.importKey(
13169
+ "raw",
13170
+ prfOutput,
13171
+ "HKDF",
13172
+ false,
13173
+ ["deriveKey"]
13174
+ );
13175
+ return await globalThis.crypto.subtle.deriveKey(
13176
+ {
13177
+ name: "HKDF",
13178
+ hash: "SHA-256",
13179
+ salt: prfSalt,
13180
+ info: textEncoder3.encode(KEK_INFO)
13181
+ },
13182
+ hkdfKey,
13183
+ { name: "AES-GCM", length: 256 },
13184
+ false,
13185
+ ["encrypt", "decrypt"]
13186
+ );
13187
+ }
13188
+ var WebAuthnPrfSecretStorageProvider = class {
13189
+ providerType = "webauthn-prf";
13190
+ backend;
13191
+ rp;
13192
+ user;
13193
+ credentialsContainer;
13194
+ alias;
13195
+ cachedPassphrase;
13196
+ constructor(options) {
13197
+ this.backend = options.backend;
13198
+ this.rp = options.rp;
13199
+ this.user = options.user;
13200
+ this.credentialsContainer = options.credentials;
13201
+ this.alias = options.alias ?? "default";
13202
+ }
13203
+ get credentials() {
13204
+ if (this.credentialsContainer) {
13205
+ return this.credentialsContainer;
13206
+ }
13207
+ if (typeof globalThis.navigator !== "undefined" && globalThis.navigator.credentials) {
13208
+ return globalThis.navigator.credentials;
13209
+ }
13210
+ throw SecretStorageException.unavailable(
13211
+ this.providerType,
13212
+ "WebAuthn credentials container is not available"
13213
+ );
13214
+ }
13215
+ get recordKey() {
13216
+ return `knishio:webauthn-prf:${this.alias}`;
13217
+ }
13218
+ isHardwareBacked() {
13219
+ return false;
13220
+ }
13221
+ async isAvailable() {
13222
+ const hasWebCrypto = typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined";
13223
+ const hasCredentials = Boolean(
13224
+ this.credentialsContainer || typeof globalThis.navigator !== "undefined" && globalThis.navigator.credentials && typeof globalThis.PublicKeyCredential !== "undefined"
13225
+ );
13226
+ return hasWebCrypto && hasCredentials;
13227
+ }
13228
+ /**
13229
+ * Enroll a new passkey credential with PRF support and wrap a random device passphrase
13230
+ */
13231
+ async enroll() {
13232
+ const existing = await this.backend.getItem(this.recordKey);
13233
+ if (existing) {
13234
+ return;
13235
+ }
13236
+ if (!await this.isAvailable()) {
13237
+ throw SecretStorageException.unavailable(this.providerType, "WebAuthn PRF is not available");
13238
+ }
13239
+ const challenge = new Uint8Array(32);
13240
+ globalThis.crypto.getRandomValues(challenge);
13241
+ const credential = await this.credentials.create({
13242
+ publicKey: {
13243
+ rp: this.rp,
13244
+ user: {
13245
+ id: this.user.id,
13246
+ name: this.user.name,
13247
+ displayName: this.user.displayName
13248
+ },
13249
+ challenge,
13250
+ pubKeyCredParams: [
13251
+ { type: "public-key", alg: -7 },
13252
+ { type: "public-key", alg: -257 }
13253
+ ],
13254
+ authenticatorSelection: {
13255
+ residentKey: "required",
13256
+ userVerification: "required"
13257
+ },
13258
+ extensions: {
13259
+ prf: {}
13260
+ }
13261
+ }
13262
+ });
13263
+ if (!credential) {
13264
+ throw SecretStorageException.unavailable(this.providerType, "Authenticator creation returned null");
13265
+ }
13266
+ const extResults = credential.getClientExtensionResults?.();
13267
+ if (extResults?.prf?.enabled !== true) {
13268
+ throw SecretStorageException.unavailable(
13269
+ this.providerType,
13270
+ "authenticator does not support the PRF extension"
13271
+ );
13272
+ }
13273
+ const credentialIdBytes = new Uint8Array(credential.rawId);
13274
+ const prfSalt = await computePrfSalt();
13275
+ const getChallenge = new Uint8Array(32);
13276
+ globalThis.crypto.getRandomValues(getChallenge);
13277
+ let assertion;
13278
+ try {
13279
+ assertion = await this.credentials.get({
13280
+ publicKey: {
13281
+ challenge: getChallenge,
13282
+ rpId: this.rp.id,
13283
+ allowCredentials: [
13284
+ {
13285
+ type: "public-key",
13286
+ id: credentialIdBytes
13287
+ }
13288
+ ],
13289
+ userVerification: "required",
13290
+ extensions: {
13291
+ prf: {
13292
+ eval: {
13293
+ first: prfSalt
13294
+ }
13295
+ }
13296
+ }
13297
+ }
13298
+ });
13299
+ } catch (err) {
13300
+ const isNotAllowed = err instanceof Error && err.name === "NotAllowedError" || err?.name === "NotAllowedError";
13301
+ if (isNotAllowed) {
13302
+ throw SecretStorageException.unavailable(
13303
+ this.providerType,
13304
+ "authenticator refused or credential missing"
13305
+ );
13306
+ }
13307
+ throw err;
13308
+ }
13309
+ if (!assertion) {
13310
+ throw SecretStorageException.unavailable(
13311
+ this.providerType,
13312
+ "authenticator refused or credential missing"
13313
+ );
13314
+ }
13315
+ const getExtResults = assertion?.getClientExtensionResults?.();
13316
+ const firstOutput = getExtResults?.prf?.results?.first;
13317
+ if (!firstOutput) {
13318
+ throw SecretStorageException.unavailable(
13319
+ this.providerType,
13320
+ "authenticator returned no PRF result"
13321
+ );
13322
+ }
13323
+ const prfBytes = toUint8Array(firstOutput);
13324
+ const kek = await deriveKekFromPrf(prfBytes, prfSalt);
13325
+ const devicePassphraseBytes = new Uint8Array(32);
13326
+ globalThis.crypto.getRandomValues(devicePassphraseBytes);
13327
+ const devicePassphrase = uint8ArrayToBase64(devicePassphraseBytes);
13328
+ const iv = new Uint8Array(GCM_IV_LENGTH2);
13329
+ globalThis.crypto.getRandomValues(iv);
13330
+ const passphraseBytes = textEncoder3.encode(devicePassphrase);
13331
+ try {
13332
+ const encryptedBuffer = await globalThis.crypto.subtle.encrypt(
13333
+ {
13334
+ name: "AES-GCM",
13335
+ iv
13336
+ },
13337
+ kek,
13338
+ passphraseBytes
13339
+ );
13340
+ const record = {
13341
+ version: 1,
13342
+ credentialId: base64UrlEncode(credentialIdBytes),
13343
+ iv: uint8ArrayToBase64(iv),
13344
+ ciphertext: uint8ArrayToBase64(new Uint8Array(encryptedBuffer))
13345
+ };
13346
+ await this.backend.setItem(this.recordKey, JSON.stringify(record));
13347
+ this.cachedPassphrase = devicePassphrase;
13348
+ } finally {
13349
+ zeroizeBytes(passphraseBytes);
13350
+ zeroizeBytes(devicePassphraseBytes);
13351
+ }
13352
+ }
13353
+ /**
13354
+ * Unlock the device passphrase using the enrolled WebAuthn PRF credential
13355
+ */
13356
+ async unlock() {
13357
+ if (this.cachedPassphrase) {
13358
+ return this.cachedPassphrase;
13359
+ }
13360
+ const rawRecord = await this.backend.getItem(this.recordKey);
13361
+ if (!rawRecord) {
13362
+ throw SecretStorageException.unavailable(
13363
+ this.providerType,
13364
+ "no enrolled credential; call enroll() first"
13365
+ );
13366
+ }
13367
+ let record;
13368
+ try {
13369
+ record = JSON.parse(rawRecord);
13370
+ } catch {
13371
+ throw SecretStorageException.decryptionFailed("Corrupted PRF record format");
13372
+ }
13373
+ const credentialIdBytes = base64UrlDecode(record.credentialId);
13374
+ const prfSalt = await computePrfSalt();
13375
+ const challenge = new Uint8Array(32);
13376
+ globalThis.crypto.getRandomValues(challenge);
13377
+ let assertion;
13378
+ try {
13379
+ assertion = await this.credentials.get({
13380
+ publicKey: {
13381
+ challenge,
13382
+ rpId: this.rp.id,
13383
+ allowCredentials: [
13384
+ {
13385
+ type: "public-key",
13386
+ id: credentialIdBytes
13387
+ }
13388
+ ],
13389
+ userVerification: "required",
13390
+ extensions: {
13391
+ prf: {
13392
+ eval: {
13393
+ first: prfSalt
13394
+ }
13395
+ }
13396
+ }
13397
+ }
13398
+ });
13399
+ } catch (err) {
13400
+ const isNotAllowed = err instanceof Error && err.name === "NotAllowedError" || err?.name === "NotAllowedError";
13401
+ if (isNotAllowed) {
13402
+ throw SecretStorageException.unavailable(
13403
+ this.providerType,
13404
+ "authenticator refused or credential missing"
13405
+ );
13406
+ }
13407
+ throw err;
13408
+ }
13409
+ if (!assertion) {
13410
+ throw SecretStorageException.unavailable(
13411
+ this.providerType,
13412
+ "authenticator refused or credential missing"
13413
+ );
13414
+ }
13415
+ const extResults = assertion?.getClientExtensionResults?.();
13416
+ const firstOutput = extResults?.prf?.results?.first;
13417
+ if (!firstOutput) {
13418
+ throw SecretStorageException.unavailable(
13419
+ this.providerType,
13420
+ "authenticator returned no PRF result"
13421
+ );
13422
+ }
13423
+ const prfBytes = toUint8Array(firstOutput);
13424
+ const kek = await deriveKekFromPrf(prfBytes, prfSalt);
13425
+ const iv = base64ToUint8Array(record.iv);
13426
+ const ciphertext = base64ToUint8Array(record.ciphertext);
13427
+ try {
13428
+ const decryptedBuffer = await globalThis.crypto.subtle.decrypt(
13429
+ {
13430
+ name: "AES-GCM",
13431
+ iv
13432
+ },
13433
+ kek,
13434
+ ciphertext
13435
+ );
13436
+ const decryptedBytes = new Uint8Array(decryptedBuffer);
13437
+ try {
13438
+ this.cachedPassphrase = textDecoder2.decode(decryptedBytes);
13439
+ return this.cachedPassphrase;
13440
+ } finally {
13441
+ zeroizeBytes(decryptedBytes);
13442
+ }
13443
+ } catch {
13444
+ throw SecretStorageException.decryptionFailed(
13445
+ "wrapped device passphrase failed authentication under the enrolled credential"
13446
+ );
13447
+ }
13448
+ }
13449
+ /**
13450
+ * Lock the provider by clearing cached passphrase material
13451
+ */
13452
+ lock() {
13453
+ this.cachedPassphrase = void 0;
13454
+ }
13455
+ /**
13456
+ * Unenroll the current credential, removing the stored PRF record and clearing cached passphrase
13457
+ */
13458
+ async unenroll() {
13459
+ this.lock();
13460
+ await this.backend.removeItem(this.recordKey);
13461
+ }
13462
+ async storeSecret(bundleHash, secret, options) {
13463
+ if (!bundleHash) {
13464
+ throw new SecretStorageException("Bundle hash cannot be empty");
13465
+ }
13466
+ if (!secret) {
13467
+ throw new SecretStorageException("Secret cannot be empty");
13468
+ }
13469
+ if (options?.passphrase) {
13470
+ throw new SecretStorageException(
13471
+ "WebAuthnPrfSecretStorageProvider derives its passphrase from the authenticator; options.passphrase is not accepted"
13472
+ );
13473
+ }
13474
+ if (!options?.recoveryPassphrase && !options?.allowUnrecoverable) {
13475
+ throw SecretStorageException.validationError(
13476
+ "Recovery passphrase required for non-exportable hardware key unless allowUnrecoverable is true"
13477
+ );
13478
+ }
13479
+ const passphrase = await this.unlock();
13480
+ const metadata = {
13481
+ bundleHash,
13482
+ label: options?.label,
13483
+ createdAt: Date.now(),
13484
+ hardwareBacked: false,
13485
+ providerType: this.providerType
13486
+ };
13487
+ const payload = await sealEnvelope(secret, passphrase, metadata);
13488
+ await this.backend.setItem(`${KEY_PREFIX2}${bundleHash}`, JSON.stringify(payload));
13489
+ if (options?.recoveryPassphrase) {
13490
+ const recoveryMetadata = {
13491
+ bundleHash,
13492
+ label: options?.label,
13493
+ createdAt: Date.now(),
13494
+ hardwareBacked: false,
13495
+ providerType: "webcrypto-aes-gcm"
13496
+ };
13497
+ const recoveryPayload = await sealEnvelope(secret, options.recoveryPassphrase, recoveryMetadata);
13498
+ await this.backend.setItem(`${RECOVERY_KEY_PREFIX}${bundleHash}`, JSON.stringify(recoveryPayload));
13499
+ }
13500
+ }
13501
+ async retrieveSecret(bundleHash, options) {
13502
+ if (options?.passphrase) {
13503
+ throw new SecretStorageException(
13504
+ "WebAuthnPrfSecretStorageProvider derives its passphrase from the authenticator; options.passphrase is not accepted"
13505
+ );
13506
+ }
13507
+ const raw = await this.backend.getItem(`${KEY_PREFIX2}${bundleHash}`);
13508
+ if (!raw) {
13509
+ return null;
13510
+ }
13511
+ let payload;
13512
+ try {
13513
+ payload = JSON.parse(raw);
13514
+ } catch {
13515
+ throw SecretStorageException.decryptionFailed("Corrupted payload format");
13516
+ }
13517
+ const passphrase = await this.unlock();
13518
+ try {
13519
+ const decryptedBytes = await openEnvelope(payload, passphrase);
13520
+ try {
13521
+ return textDecoder2.decode(decryptedBytes);
13522
+ } finally {
13523
+ zeroizeBytes(decryptedBytes);
13524
+ }
13525
+ } catch (err) {
13526
+ if (err instanceof SecretStorageException) {
13527
+ throw err;
13528
+ }
13529
+ const msg = err instanceof Error ? err.message : String(err);
13530
+ throw SecretStorageException.decryptionFailed(msg);
13531
+ }
13532
+ }
13533
+ async withSecret(bundleHash, fn, options) {
13534
+ if (options?.passphrase) {
13535
+ throw new SecretStorageException(
13536
+ "WebAuthnPrfSecretStorageProvider derives its passphrase from the authenticator; options.passphrase is not accepted"
13537
+ );
13538
+ }
13539
+ const raw = await this.backend.getItem(`${KEY_PREFIX2}${bundleHash}`);
13540
+ if (!raw) {
13541
+ throw SecretStorageException.notFound(bundleHash);
13542
+ }
13543
+ let payload;
13544
+ try {
13545
+ payload = JSON.parse(raw);
13546
+ } catch {
13547
+ throw SecretStorageException.decryptionFailed("Corrupted payload format");
13548
+ }
13549
+ const passphrase = await this.unlock();
13550
+ try {
13551
+ const decryptedBytes = await openEnvelope(payload, passphrase);
13552
+ return await withSecureBytes(decryptedBytes, async (bytes) => {
13553
+ const secretString = textDecoder2.decode(bytes);
13554
+ return await fn(secretString);
13555
+ });
13556
+ } catch (err) {
13557
+ if (err instanceof SecretStorageException) {
13558
+ throw err;
13559
+ }
13560
+ const msg = err instanceof Error ? err.message : String(err);
13561
+ throw SecretStorageException.decryptionFailed(msg);
13562
+ }
13563
+ }
13564
+ async deleteSecret(bundleHash) {
13565
+ const key = `${KEY_PREFIX2}${bundleHash}`;
13566
+ const recoveryKey = `${RECOVERY_KEY_PREFIX}${bundleHash}`;
13567
+ const result = await this.backend.removeItem(key);
13568
+ await this.backend.removeItem(recoveryKey);
13569
+ return result !== false;
13570
+ }
13571
+ async hasSecret(bundleHash) {
13572
+ const raw = await this.backend.getItem(`${KEY_PREFIX2}${bundleHash}`);
13573
+ return raw !== null;
13574
+ }
13575
+ async listSecrets() {
13576
+ const keys = await this.backend.keys();
13577
+ const matchingKeys = keys.filter((k) => k.startsWith(KEY_PREFIX2) && !k.startsWith(RECOVERY_KEY_PREFIX));
13578
+ const results = [];
13579
+ for (const key of matchingKeys) {
13580
+ const raw = await this.backend.getItem(key);
13581
+ if (raw) {
13582
+ try {
13583
+ const payload = JSON.parse(raw);
13584
+ if (payload.metadata) {
13585
+ results.push(payload.metadata);
13586
+ }
13587
+ } catch {
13588
+ }
13589
+ }
13590
+ }
13591
+ return results;
13592
+ }
13593
+ /**
13594
+ * Recover a secret using its recovery envelope and re-enroll it under a fresh WebAuthn PRF credential
13595
+ */
13596
+ async recoverSecret(bundleHash, recoveryPassphrase, options) {
13597
+ if (!bundleHash) {
13598
+ throw new SecretStorageException("Bundle hash cannot be empty");
13599
+ }
13600
+ if (!recoveryPassphrase) {
13601
+ throw new SecretStorageException("Recovery passphrase cannot be empty");
13602
+ }
13603
+ const raw = await this.backend.getItem(`${RECOVERY_KEY_PREFIX}${bundleHash}`);
13604
+ if (!raw) {
13605
+ throw SecretStorageException.notFound(bundleHash);
13606
+ }
13607
+ let payload;
13608
+ try {
13609
+ payload = JSON.parse(raw);
13610
+ } catch {
13611
+ throw SecretStorageException.decryptionFailed("Corrupted recovery payload format");
13612
+ }
13613
+ let decryptedBytes;
13614
+ try {
13615
+ decryptedBytes = await openEnvelope(payload, recoveryPassphrase);
13616
+ } catch (err) {
13617
+ if (err instanceof SecretStorageException) {
13618
+ throw err;
13619
+ }
13620
+ const msg = err instanceof Error ? err.message : String(err);
13621
+ throw SecretStorageException.decryptionFailed(msg);
13622
+ }
13623
+ let secretStr;
13624
+ try {
13625
+ secretStr = textDecoder2.decode(decryptedBytes);
13626
+ } finally {
13627
+ zeroizeBytes(decryptedBytes);
13628
+ }
13629
+ await this.storeSecret(bundleHash, secretStr, {
13630
+ ...options,
13631
+ recoveryPassphrase
13632
+ });
13633
+ }
13634
+ };
13635
+
13636
+ // src/storage/NonExtractableKeySecretStorageProvider.ts
13637
+ init_SecretStorageException();
13638
+ var KEY_PREFIX3 = SECRET_KEY_PREFIX;
13639
+ var GCM_IV_LENGTH3 = 12;
13640
+ var textEncoder4 = new TextEncoder();
13641
+ var textDecoder3 = new TextDecoder();
13642
+ var MemoryKeyStore = class {
13643
+ keys = /* @__PURE__ */ new Map();
13644
+ async get(name) {
13645
+ return this.keys.get(name);
13646
+ }
13647
+ async put(name, key) {
13648
+ this.keys.set(name, key);
13649
+ }
13650
+ async delete(name) {
13651
+ return this.keys.delete(name);
13652
+ }
13653
+ };
13654
+ var IndexedDbKeyStore = class {
13655
+ dbName;
13656
+ storeName = "keys";
13657
+ constructor(dbName = "knishio-secret-storage") {
13658
+ this.dbName = dbName;
13659
+ }
13660
+ // Executor form intentionally retained for browser runtime compatibility with ES2022 / browsers without Promise.withResolvers polyfill
13661
+ async getDb() {
13662
+ if (typeof globalThis.indexedDB === "undefined") {
13663
+ throw SecretStorageException.unavailable(
13664
+ "webcrypto-nonextractable",
13665
+ "IndexedDB is not available"
13666
+ );
13667
+ }
13668
+ return new Promise((resolve, reject) => {
13669
+ const request = globalThis.indexedDB.open(this.dbName, 1);
13670
+ request.onupgradeneeded = () => {
13671
+ const db = request.result;
13672
+ if (!db.objectStoreNames.contains(this.storeName)) {
13673
+ db.createObjectStore(this.storeName);
13674
+ }
13675
+ };
13676
+ request.onsuccess = () => resolve(request.result);
13677
+ request.onerror = () => reject(request.error);
13678
+ });
13679
+ }
13680
+ async get(name) {
13681
+ const db = await this.getDb();
13682
+ return new Promise((resolve, reject) => {
13683
+ const tx = db.transaction(this.storeName, "readonly");
13684
+ const store = tx.objectStore(this.storeName);
13685
+ const request = store.get(name);
13686
+ request.onsuccess = () => resolve(request.result);
13687
+ request.onerror = () => reject(request.error);
13688
+ });
13689
+ }
13690
+ async put(name, key) {
13691
+ const db = await this.getDb();
13692
+ return new Promise((resolve, reject) => {
13693
+ const tx = db.transaction(this.storeName, "readwrite");
13694
+ const store = tx.objectStore(this.storeName);
13695
+ const request = store.put(key, name);
13696
+ request.onsuccess = () => resolve();
13697
+ request.onerror = () => reject(request.error);
13698
+ });
13699
+ }
13700
+ async delete(name) {
13701
+ const db = await this.getDb();
13702
+ return new Promise((resolve, reject) => {
13703
+ const tx = db.transaction(this.storeName, "readwrite");
13704
+ const store = tx.objectStore(this.storeName);
13705
+ const request = store.delete(name);
13706
+ request.onsuccess = () => resolve(true);
13707
+ request.onerror = () => reject(request.error);
13708
+ });
13709
+ }
13710
+ };
13711
+ var NonExtractableKeySecretStorageProvider = class {
13712
+ providerType = "webcrypto-nonextractable";
13713
+ backend;
13714
+ keyStore;
13715
+ alias;
13716
+ cachedPassphrase;
13717
+ constructor(options) {
13718
+ this.backend = options.backend;
13719
+ this.keyStore = options.keyStore ?? new IndexedDbKeyStore();
13720
+ this.alias = options.alias ?? "default";
13721
+ }
13722
+ get recordKey() {
13723
+ return `knishio:kek:webcrypto-nonextractable:${this.alias}`;
13724
+ }
13725
+ get kekStoreKey() {
13726
+ return `knishio:kek:${this.alias}`;
13727
+ }
13728
+ isHardwareBacked() {
13729
+ return false;
13730
+ }
13731
+ async isAvailable() {
13732
+ return typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined";
13733
+ }
13734
+ /**
13735
+ * Unlock or initialize the device passphrase using the non-extractable KEK
13736
+ */
13737
+ async unlock() {
13738
+ if (this.cachedPassphrase) {
13739
+ return this.cachedPassphrase;
13740
+ }
13741
+ if (!await this.isAvailable()) {
13742
+ throw SecretStorageException.unavailable(
13743
+ this.providerType,
13744
+ "WebCrypto API is not available"
13745
+ );
13746
+ }
13747
+ const rawRecord = await this.backend.getItem(this.recordKey);
13748
+ if (!rawRecord) {
13749
+ let kek2 = await this.keyStore.get(this.kekStoreKey);
13750
+ if (!kek2) {
13751
+ kek2 = await globalThis.crypto.subtle.generateKey(
13752
+ { name: "AES-GCM", length: 256 },
13753
+ false,
13754
+ ["encrypt", "decrypt"]
13755
+ );
13756
+ await this.keyStore.put(this.kekStoreKey, kek2);
13757
+ }
13758
+ const devicePassphraseBytes = new Uint8Array(32);
13759
+ globalThis.crypto.getRandomValues(devicePassphraseBytes);
13760
+ const devicePassphrase = uint8ArrayToBase64(devicePassphraseBytes);
13761
+ const iv2 = new Uint8Array(GCM_IV_LENGTH3);
13762
+ globalThis.crypto.getRandomValues(iv2);
13763
+ const passphraseBytes = textEncoder4.encode(devicePassphrase);
13764
+ try {
13765
+ const encryptedBuffer = await globalThis.crypto.subtle.encrypt(
13766
+ {
13767
+ name: "AES-GCM",
13768
+ iv: iv2
13769
+ },
13770
+ kek2,
13771
+ passphraseBytes
13772
+ );
13773
+ const record2 = {
13774
+ version: 1,
13775
+ iv: uint8ArrayToBase64(iv2),
13776
+ ciphertext: uint8ArrayToBase64(new Uint8Array(encryptedBuffer))
13777
+ };
13778
+ await this.backend.setItem(this.recordKey, JSON.stringify(record2));
13779
+ this.cachedPassphrase = devicePassphrase;
13780
+ return devicePassphrase;
13781
+ } finally {
13782
+ zeroizeBytes(passphraseBytes);
13783
+ zeroizeBytes(devicePassphraseBytes);
13784
+ }
13785
+ }
13786
+ let record;
13787
+ try {
13788
+ record = JSON.parse(rawRecord);
13789
+ } catch {
13790
+ throw SecretStorageException.decryptionFailed("Corrupted key record format");
13791
+ }
13792
+ const kek = await this.keyStore.get(this.kekStoreKey);
13793
+ if (!kek) {
13794
+ throw SecretStorageException.unavailable(
13795
+ this.providerType,
13796
+ `no non-extractable key found for alias '${this.alias}'`
13797
+ );
13798
+ }
13799
+ const iv = base64ToUint8Array(record.iv);
13800
+ const ciphertext = base64ToUint8Array(record.ciphertext);
13801
+ try {
13802
+ const decryptedBuffer = await globalThis.crypto.subtle.decrypt(
13803
+ {
13804
+ name: "AES-GCM",
13805
+ iv
13806
+ },
13807
+ kek,
13808
+ ciphertext
13809
+ );
13810
+ const decryptedBytes = new Uint8Array(decryptedBuffer);
13811
+ try {
13812
+ this.cachedPassphrase = textDecoder3.decode(decryptedBytes);
13813
+ return this.cachedPassphrase;
13814
+ } finally {
13815
+ zeroizeBytes(decryptedBytes);
13816
+ }
13817
+ } catch {
13818
+ throw SecretStorageException.decryptionFailed(
13819
+ "wrapped device passphrase failed authentication under non-extractable key"
13820
+ );
13821
+ }
13822
+ }
13823
+ /**
13824
+ * Lock the provider by clearing cached passphrase material
13825
+ */
13826
+ lock() {
13827
+ this.cachedPassphrase = void 0;
13828
+ }
13829
+ /**
13830
+ * Unenroll the non-extractable key, removing the stored wrapped record and KEK
13831
+ */
13832
+ async unenroll() {
13833
+ this.lock();
13834
+ await this.backend.removeItem(this.recordKey);
13835
+ await this.keyStore.delete(this.kekStoreKey);
13836
+ }
13837
+ async storeSecret(bundleHash, secret, options) {
13838
+ if (!bundleHash) {
13839
+ throw new SecretStorageException("Bundle hash cannot be empty");
13840
+ }
13841
+ if (!secret) {
13842
+ throw new SecretStorageException("Secret cannot be empty");
13843
+ }
13844
+ if (options?.passphrase) {
13845
+ throw new SecretStorageException(
13846
+ "NonExtractableKeySecretStorageProvider derives its passphrase from the non-extractable device key; options.passphrase is not accepted"
13847
+ );
13848
+ }
13849
+ if (!options?.recoveryPassphrase && !options?.allowUnrecoverable) {
13850
+ throw SecretStorageException.validationError(
13851
+ "Recovery passphrase required for non-exportable hardware key unless allowUnrecoverable is true"
13852
+ );
13853
+ }
13854
+ const passphrase = await this.unlock();
13855
+ const metadata = {
13856
+ bundleHash,
13857
+ label: options?.label,
13858
+ createdAt: Date.now(),
13859
+ hardwareBacked: false,
13860
+ providerType: this.providerType
13861
+ };
13862
+ const payload = await sealEnvelope(secret, passphrase, metadata);
13863
+ await this.backend.setItem(`${KEY_PREFIX3}${bundleHash}`, JSON.stringify(payload));
13864
+ if (options?.recoveryPassphrase) {
13865
+ const recoveryMetadata = {
13866
+ bundleHash,
13867
+ label: options?.label,
13868
+ createdAt: Date.now(),
13869
+ hardwareBacked: false,
13870
+ providerType: "webcrypto-aes-gcm"
13871
+ };
13872
+ const recoveryPayload = await sealEnvelope(secret, options.recoveryPassphrase, recoveryMetadata);
13873
+ await this.backend.setItem(`${RECOVERY_KEY_PREFIX}${bundleHash}`, JSON.stringify(recoveryPayload));
13874
+ }
13875
+ }
13876
+ async retrieveSecret(bundleHash, options) {
13877
+ if (options?.passphrase) {
13878
+ throw new SecretStorageException(
13879
+ "NonExtractableKeySecretStorageProvider derives its passphrase from the non-extractable device key; options.passphrase is not accepted"
13880
+ );
13881
+ }
13882
+ const raw = await this.backend.getItem(`${KEY_PREFIX3}${bundleHash}`);
13883
+ if (!raw) {
13884
+ return null;
13885
+ }
13886
+ let payload;
13887
+ try {
13888
+ payload = JSON.parse(raw);
13889
+ } catch {
13890
+ throw SecretStorageException.decryptionFailed("Corrupted payload format");
13891
+ }
13892
+ const passphrase = await this.unlock();
13893
+ try {
13894
+ const decryptedBytes = await openEnvelope(payload, passphrase);
13895
+ try {
13896
+ return textDecoder3.decode(decryptedBytes);
13897
+ } finally {
13898
+ zeroizeBytes(decryptedBytes);
13899
+ }
13900
+ } catch (err) {
13901
+ if (err instanceof SecretStorageException) {
13902
+ throw err;
13903
+ }
13904
+ const msg = err instanceof Error ? err.message : String(err);
13905
+ throw SecretStorageException.decryptionFailed(msg);
13906
+ }
13907
+ }
13908
+ async withSecret(bundleHash, fn, options) {
13909
+ if (options?.passphrase) {
13910
+ throw new SecretStorageException(
13911
+ "NonExtractableKeySecretStorageProvider derives its passphrase from the non-extractable device key; options.passphrase is not accepted"
13912
+ );
13913
+ }
13914
+ const raw = await this.backend.getItem(`${KEY_PREFIX3}${bundleHash}`);
13915
+ if (!raw) {
13916
+ throw SecretStorageException.notFound(bundleHash);
13917
+ }
13918
+ let payload;
13919
+ try {
13920
+ payload = JSON.parse(raw);
13921
+ } catch {
13922
+ throw SecretStorageException.decryptionFailed("Corrupted payload format");
13923
+ }
13924
+ const passphrase = await this.unlock();
13925
+ try {
13926
+ const decryptedBytes = await openEnvelope(payload, passphrase);
13927
+ return await withSecureBytes(decryptedBytes, async (bytes) => {
13928
+ const secretString = textDecoder3.decode(bytes);
13929
+ return await fn(secretString);
13930
+ });
13931
+ } catch (err) {
13932
+ if (err instanceof SecretStorageException) {
13933
+ throw err;
13934
+ }
13935
+ const msg = err instanceof Error ? err.message : String(err);
13936
+ throw SecretStorageException.decryptionFailed(msg);
13937
+ }
13938
+ }
13939
+ async deleteSecret(bundleHash) {
13940
+ const key = `${KEY_PREFIX3}${bundleHash}`;
13941
+ const recoveryKey = `${RECOVERY_KEY_PREFIX}${bundleHash}`;
13942
+ const result = await this.backend.removeItem(key);
13943
+ await this.backend.removeItem(recoveryKey);
13944
+ return result !== false;
13945
+ }
13946
+ async hasSecret(bundleHash) {
13947
+ const raw = await this.backend.getItem(`${KEY_PREFIX3}${bundleHash}`);
13948
+ return raw !== null;
13949
+ }
13950
+ async listSecrets() {
13951
+ const keys = await this.backend.keys();
13952
+ const matchingKeys = keys.filter((k) => k.startsWith(KEY_PREFIX3) && !k.startsWith(RECOVERY_KEY_PREFIX));
13953
+ const results = [];
13954
+ for (const key of matchingKeys) {
13955
+ const raw = await this.backend.getItem(key);
13956
+ if (raw) {
13957
+ try {
13958
+ const payload = JSON.parse(raw);
13959
+ if (payload.metadata) {
13960
+ results.push(payload.metadata);
13961
+ }
13962
+ } catch {
13963
+ }
13964
+ }
13965
+ }
13966
+ return results;
13967
+ }
13968
+ /**
13969
+ * Recover a secret using its recovery envelope and re-enroll it under a fresh non-extractable KEK
13970
+ */
13971
+ async recoverSecret(bundleHash, recoveryPassphrase, options) {
13972
+ if (!bundleHash) {
13973
+ throw new SecretStorageException("Bundle hash cannot be empty");
13974
+ }
13975
+ if (!recoveryPassphrase) {
13976
+ throw new SecretStorageException("Recovery passphrase cannot be empty");
13977
+ }
13978
+ const raw = await this.backend.getItem(`${RECOVERY_KEY_PREFIX}${bundleHash}`);
13979
+ if (!raw) {
13980
+ throw SecretStorageException.notFound(bundleHash);
13981
+ }
13982
+ let payload;
13983
+ try {
13984
+ payload = JSON.parse(raw);
13985
+ } catch {
13986
+ throw SecretStorageException.decryptionFailed("Corrupted recovery payload format");
13987
+ }
13988
+ let decryptedBytes;
13989
+ try {
13990
+ decryptedBytes = await openEnvelope(payload, recoveryPassphrase);
13991
+ } catch (err) {
13992
+ if (err instanceof SecretStorageException) {
13993
+ throw err;
13994
+ }
13995
+ const msg = err instanceof Error ? err.message : String(err);
13996
+ throw SecretStorageException.decryptionFailed(msg);
13997
+ }
13998
+ let secretStr;
13999
+ try {
14000
+ secretStr = textDecoder3.decode(decryptedBytes);
14001
+ } finally {
14002
+ zeroizeBytes(decryptedBytes);
14003
+ }
14004
+ await this.storeSecret(bundleHash, secretStr, {
14005
+ ...options,
14006
+ recoveryPassphrase
14007
+ });
14008
+ }
12681
14009
  };
12682
14010
 
12683
14011
  // src/storage/index.ts
@@ -12688,15 +14016,14 @@ function createDefaultSecretStorage(options = {}) {
12688
14016
  if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined") {
12689
14017
  return new WebCryptoSecretStorageProvider({
12690
14018
  backend: options.backend,
12691
- defaultPassphrase: options.defaultPassphrase,
12692
- hardwareBacked: options.hardwareBacked
14019
+ defaultPassphrase: options.defaultPassphrase
12693
14020
  });
12694
14021
  }
12695
14022
  return new MemorySecretStorageProvider();
12696
14023
  }
12697
14024
 
12698
14025
  // src/index.ts
12699
- var SDK_VERSION = "0.9.8";
14026
+ var SDK_VERSION = "1.1.0";
12700
14027
  var SDK_NAME = "KnishIO-Client-TS";
12701
14028
  var COMPATIBLE_SERVER_VERSIONS = [4, 5];
12702
14029
  var SDK_INFO = {
@@ -12705,7 +14032,7 @@ var SDK_INFO = {
12705
14032
  description: "TypeScript SDK for Knish.IO post-blockchain distributed ledger",
12706
14033
  compatibleServerVersions: COMPATIBLE_SERVER_VERSIONS,
12707
14034
  features: [
12708
- "Post-quantum cryptography (XMSS, ML-KEM768)",
14035
+ "Post-quantum cryptography (XMSS, ML-KEM-1024)",
12709
14036
  "Cross-platform compatibility",
12710
14037
  "Type-safe APIs",
12711
14038
  "DAG-based transaction processing",
@@ -12786,6 +14113,6 @@ var KnishIO = {
12786
14113
  SDK_INFO
12787
14114
  };
12788
14115
 
12789
- export { Atom, AtomIndexException, AtomMeta, AtomsMissingException, AuthToken, BaseException, COMPATIBLE_SERVER_VERSIONS, CRYPTO_CONSTANTS, CheckMolecule, DevUtils, Dot, EXCEPTION_CODES, EXCEPTION_TYPES, EXTENDED_COMPATIBILITY_TEST_VECTORS, ExceptionFactory, GraphQLClient, InvalidResponseException, KnishIO, KnishIOClient, MemorySecretStorageProvider, MemoryStorageBackend, Meta, MolecularHashMismatchException, Molecule, Mutation, MutationAppendRequest, MutationCreateMeta, MutationCreateToken, MutationCreateWallet, MutationPeering, MutationProposeMolecule, MutationRequestAuthorization, MutationRequestTokens, MutationTransferTokens, PolicyMeta, Query, QueryAtom, QueryBalance, QueryBatch, QueryContinuId, QueryEmbeddingStatus, QueryMetaType, QueryMetaTypeViaAtom, QueryWalletBundle, QueryWalletList, Response2 as Response, ResponseAppendRequest, ResponseAtom, ResponseBalance, ResponseContinuId, ResponseCreateMeta, ResponseCreateToken, ResponseCreateWallet, ResponseEmbeddingStatus, ResponseMetaType, ResponseMetaTypeViaAtom, ResponsePeering, ResponseProposeMolecule, ResponseRequestAuthorization, ResponseRequestTokens, ResponseTransferTokens, ResponseWalletBundle, ResponseWalletList, SDK_INFO, SDK_NAME, SDK_VERSION, SecretStorageException, SignatureMismatchException, TokenUnit, TransferBalanceException, Wallet, WalletCredentialException, WebCryptoSecretStorageProvider, base64ToHex, bufferToHexString, capitalize, charsetBaseConvert, chunkArray, chunkSubstr, configureSDK, constantTimeCompare, convertToBase17, createBundleHash, createDefaultSecretStorage, createMolecularHash, createPosition, createTokenSlug, createWalletAddress, deepCloning, diff, enumerateMolecularHash, generateBatchId, generateBundleHash, generateOTSSignature, generatePosition, generateSecret, generateWalletAddress, generateWalletKey, getSDKConfig, hexStringToBuffer, hexToBase64, intersect, isAtomIsotope, isBundleHash, isHex, isHexString, isMolecularHash, isNumeric, isPosition2 as isPosition, isWalletAddress2 as isWalletAddress, normalizeMolecularHash, randomString, runCompatibilityTests, runExtendedCompatibilityTests, shake256, toCamelCase, toSnakeCase, truncate, validateBundleHash, validateMolecularHashForSignature, validateOTSSignature, validatePosition, validateSecret, validateWalletAddress, verifyOTSSignature, withSecureBytes, withSecureString, zeroizeBytes };
14116
+ export { Atom, AtomIndexException, AtomMeta, AtomsMissingException, AuthToken, BaseException, COMPATIBLE_SERVER_VERSIONS, CRYPTO_CONSTANTS, CheckMolecule, DevUtils, Dot, EXCEPTION_CODES, EXCEPTION_TYPES, EXTENDED_COMPATIBILITY_TEST_VECTORS, ExceptionFactory, FileStorageBackend, GraphQLClient, IndexedDbKeyStore, InvalidResponseException, KnishIO, KnishIOClient, MemoryKeyStore, MemorySecretStorageProvider, MemoryStorageBackend, Meta, MolecularHashMismatchException, Molecule, Mutation, MutationAppendRequest, MutationCreateMeta, MutationCreateToken, MutationCreateWallet, MutationPeering, MutationProposeMolecule, MutationRequestAuthorization, MutationRequestTokens, MutationTransferTokens, NonExtractableKeySecretStorageProvider, PolicyMeta, Query, QueryAtom, QueryBalance, QueryBatch, QueryContinuId, QueryEmbeddingStatus, QueryMetaType, QueryMetaTypeViaAtom, QueryWalletBundle, QueryWalletList, RECOVERY_KEY_PREFIX, Response2 as Response, ResponseAppendRequest, ResponseAtom, ResponseBalance, ResponseContinuId, ResponseCreateMeta, ResponseCreateToken, ResponseCreateWallet, ResponseEmbeddingStatus, ResponseMetaType, ResponseMetaTypeViaAtom, ResponsePeering, ResponseProposeMolecule, ResponseRequestAuthorization, ResponseRequestTokens, ResponseTransferTokens, ResponseWalletBundle, ResponseWalletList, SDK_INFO, SDK_NAME, SDK_VERSION, SECRET_KEY_PREFIX, SecretStorageException, SignatureMismatchException, TokenUnit, TransferBalanceException, Wallet, WalletCredentialException, WebAuthnPrfSecretStorageProvider, WebCryptoSecretStorageProvider, WebStorageBackend, base64ToHex, bufferToHexString, capitalize, charsetBaseConvert, chunkArray, chunkSubstr, configureSDK, constantTimeCompare, convertToBase17, createBundleHash, createDefaultSecretStorage, createMolecularHash, createPosition, createTokenSlug, createWalletAddress, deepCloning, diff, enumerateMolecularHash, generateBatchId, generateBundleHash, generateOTSSignature, generatePosition, generateSecret, generateWalletAddress, generateWalletKey, getSDKConfig, hexStringToBuffer, hexToBase64, intersect, isAtomIsotope, isBundleHash, isHex, isHexString, isMolecularHash, isNumeric, isPosition2 as isPosition, isWalletAddress2 as isWalletAddress, normalizeMolecularHash, openEnvelope, randomString, runCompatibilityTests, runExtendedCompatibilityTests, sealEnvelope, shake256, toCamelCase, toSnakeCase, truncate, validateBundleHash, validateMolecularHashForSignature, validateOTSSignature, validatePosition, validateSecret, validateWalletAddress, verifyOTSSignature, withSecureBytes, withSecureString, zeroizeBytes };
12790
14117
  //# sourceMappingURL=index.js.map
12791
14118
  //# sourceMappingURL=index.js.map