@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.cjs CHANGED
@@ -572,6 +572,14 @@ var init_SecretStorageException = __esm({
572
572
  }
573
573
  );
574
574
  }
575
+ /**
576
+ * Validation error for storage options or parameters
577
+ */
578
+ static validationError(message) {
579
+ return new _SecretStorageException(message, {
580
+ code: "VALIDATION_ERROR"
581
+ });
582
+ }
575
583
  };
576
584
  }
577
585
  });
@@ -3661,6 +3669,11 @@ var TokenUnit = class _TokenUnit {
3661
3669
  };
3662
3670
  }
3663
3671
  };
3672
+ var ML_KEM_PARAMS = {
3673
+ 1024: { kem: mlKem_js.ml_kem1024, pkBytes: 1568, skBytes: 3168, ctBytes: 1568 },
3674
+ 768: { kem: mlKem_js.ml_kem768, pkBytes: 1184, skBytes: 2400, ctBytes: 1088 }
3675
+ };
3676
+ var DEFAULT_ML_KEM_PARAMETER_SET = 1024;
3664
3677
  var Wallet = class _Wallet {
3665
3678
  token;
3666
3679
  balance;
@@ -3675,6 +3688,7 @@ var Wallet = class _Wallet {
3675
3688
  tokenUnits;
3676
3689
  tradeRates;
3677
3690
  molecules;
3691
+ mlKemParameterSet;
3678
3692
  // Token metadata (populated from query responses)
3679
3693
  tokenName;
3680
3694
  tokenAmount;
@@ -3688,8 +3702,14 @@ var Wallet = class _Wallet {
3688
3702
  address = null,
3689
3703
  position = null,
3690
3704
  batchId = null,
3691
- characters = null
3705
+ characters = null,
3706
+ mlKemParameterSet = DEFAULT_ML_KEM_PARAMETER_SET
3692
3707
  } = {}) {
3708
+ const paramSetNum = Number(mlKemParameterSet);
3709
+ if (!ML_KEM_PARAMS[paramSetNum]) {
3710
+ throw new Error(`KnishIO: unsupported ML-KEM parameter set ${mlKemParameterSet}; expected 1024 or 768.`);
3711
+ }
3712
+ this.mlKemParameterSet = paramSetNum;
3693
3713
  this.token = token;
3694
3714
  this.balance = "0";
3695
3715
  this.molecules = {};
@@ -3725,7 +3745,8 @@ var Wallet = class _Wallet {
3725
3745
  bundle = null,
3726
3746
  token = "USER",
3727
3747
  batchId = null,
3728
- characters = null
3748
+ characters = null,
3749
+ mlKemParameterSet = DEFAULT_ML_KEM_PARAMETER_SET
3729
3750
  }) {
3730
3751
  let position = null;
3731
3752
  if (!secret && !bundle) {
@@ -3741,7 +3762,8 @@ var Wallet = class _Wallet {
3741
3762
  token,
3742
3763
  position,
3743
3764
  batchId,
3744
- characters
3765
+ characters,
3766
+ mlKemParameterSet
3745
3767
  });
3746
3768
  }
3747
3769
  /**
@@ -3900,20 +3922,80 @@ var Wallet = class _Wallet {
3900
3922
  return (typeof this.position === "undefined" || this.position === null) && (typeof this.address === "undefined" || this.address === null);
3901
3923
  }
3902
3924
  // =============================================================================
3903
- // POST-QUANTUM CRYPTOGRAPHY - ML-KEM768 INTEGRATION
3925
+ // POST-QUANTUM CRYPTOGRAPHY - ML-KEM INTEGRATION
3904
3926
  // =============================================================================
3905
3927
  /**
3906
- * Initializes the ML-KEM key pair (matches JavaScript SDK exactly)
3928
+ * Derive an ML-KEM keypair for an arbitrary parameter set from the wallet's key seed,
3929
+ * without mutating the wallet. The 64-byte `d‖z` seed takes no parameter-set input — only
3930
+ * the final `keygen` call differs — so one KnishIO wallet owns both an ML-KEM-768 and an
3931
+ * ML-KEM-1024 identity and either can be reconstructed on demand.
3932
+ *
3933
+ * Returns `null` when the wallet holds no key — a secret-less wallet, which is what a molecule
3934
+ * deserializer builds for validation context. `generateSecret(null, …)` does NOT throw, so
3935
+ * without this the wallet would derive a plausible-looking identity from a bogus seed and fail
3936
+ * three layers down at AES-GCM instead of at the missing key. The guard lives here rather than
3937
+ * at each call site so a new caller cannot miss it.
3938
+ *
3939
+ * @param parameterSet - 1024 or 768
3907
3940
  */
3908
- initializeMLKEM() {
3941
+ deriveMlKemKeypair(parameterSet) {
3942
+ const params = ML_KEM_PARAMS[parameterSet];
3943
+ if (!params) {
3944
+ throw new Error(`KnishIO: unsupported ML-KEM parameter set ${parameterSet}; expected 1024 or 768.`);
3945
+ }
3946
+ if (!this.key) {
3947
+ return null;
3948
+ }
3909
3949
  const seedHex = generateSecret(this.key, 128);
3910
3950
  const seed = new Uint8Array(64);
3911
3951
  for (let i = 0; i < 64; i++) {
3912
3952
  seed[i] = parseInt(seedHex.substr(i * 2, 2), 16);
3913
3953
  }
3914
- const { publicKey, secretKey } = mlKem_js.ml_kem768.keygen(seed);
3915
- this.pubkey = this.serializeKey(publicKey);
3916
- this.privkey = secretKey;
3954
+ const { publicKey, secretKey } = params.kem.keygen(seed);
3955
+ return {
3956
+ pubkey: this.serializeKey(publicKey),
3957
+ privkey: secretKey,
3958
+ params
3959
+ };
3960
+ }
3961
+ /**
3962
+ * ML-KEM parameter set implied by a serialized public key's raw byte length. FIPS 203's key
3963
+ * lengths are disjoint (1568 bytes → ML-KEM-1024, 1184 bytes → ML-KEM-768), so a stored peer
3964
+ * key recovers the parameter set of the session it belongs to without a wire-format change.
3965
+ * Used by AuthToken.restore to resolve a snapshot that predates the field.
3966
+ *
3967
+ * @param pubkey - Base64-serialized ML-KEM public key
3968
+ * @return 1024, 768, or null when the length matches neither
3969
+ */
3970
+ static mlKemParameterSetFromPubkey(pubkey) {
3971
+ if (!pubkey) {
3972
+ return null;
3973
+ }
3974
+ let byteLength;
3975
+ try {
3976
+ byteLength = typeof Buffer !== "undefined" ? Buffer.from(pubkey, "base64").length : atob(pubkey).length;
3977
+ } catch {
3978
+ return null;
3979
+ }
3980
+ if (byteLength === ML_KEM_PARAMS[1024].pkBytes) {
3981
+ return 1024;
3982
+ }
3983
+ if (byteLength === ML_KEM_PARAMS[768].pkBytes) {
3984
+ return 768;
3985
+ }
3986
+ return null;
3987
+ }
3988
+ /**
3989
+ * Initializes the ML-KEM key pair (matches JavaScript SDK exactly). Only ever reached from the
3990
+ * constructor's `secret` branch, so the derivation cannot come back empty here.
3991
+ */
3992
+ initializeMLKEM() {
3993
+ const derived = this.deriveMlKemKeypair(this.mlKemParameterSet);
3994
+ if (!derived) {
3995
+ return;
3996
+ }
3997
+ this.pubkey = derived.pubkey;
3998
+ this.privkey = derived.privkey;
3917
3999
  }
3918
4000
  // =============================================================================
3919
4001
  // HIGH-LEVEL MESSAGE ENCRYPTION (JavaScript SDK Compatibility)
@@ -3922,13 +4004,13 @@ var Wallet = class _Wallet {
3922
4004
  const messageString = JSON.stringify(message);
3923
4005
  const messageUint8 = new TextEncoder().encode(messageString);
3924
4006
  const deserializedPubkey = this.deserializeKey(recipientPubkey);
3925
- const ML_KEM_768_PUBLIC_KEY_BYTES = 1184;
3926
- if (deserializedPubkey.length !== ML_KEM_768_PUBLIC_KEY_BYTES) {
4007
+ const params = ML_KEM_PARAMS[this.mlKemParameterSet];
4008
+ if (deserializedPubkey.length !== params.pkBytes) {
3927
4009
  throw new Error(
3928
- `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 }.`
4010
+ `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.`
3929
4011
  );
3930
4012
  }
3931
- const { cipherText, sharedSecret } = mlKem_js.ml_kem768.encapsulate(deserializedPubkey);
4013
+ const { cipherText, sharedSecret } = params.kem.encapsulate(deserializedPubkey);
3932
4014
  const encryptedMessage = await this.encryptWithSharedSecret(messageUint8, sharedSecret);
3933
4015
  return {
3934
4016
  cipherText: this.serializeKey(cipherText),
@@ -3940,15 +4022,37 @@ var Wallet = class _Wallet {
3940
4022
  return decryptedString === null ? null : JSON.parse(decryptedString);
3941
4023
  }
3942
4024
  /**
3943
- * ML-KEM768 decapsulate + AES-256-GCM decrypt → the RAW decrypted UTF-8 string (no JSON.parse).
4025
+ * ML-KEM decapsulate + AES-256-GCM decrypt → the RAW decrypted UTF-8 string (no JSON.parse).
3944
4026
  * Shared by {@link decryptMessage} (which JSON.parses the result) and the PQ CipherHash transport
3945
- * ({@link decryptMyMessageML768}, which needs the raw response JSON text). PQ-transport Phase E.
4027
+ * ({@link decryptMyMessageML}, which needs the raw response JSON text). PQ-transport Phase E.
3946
4028
  */
3947
4029
  async _mlkemDecryptToString(encryptedData) {
3948
4030
  const { cipherText, encryptedMessage } = encryptedData;
4031
+ const configuredParams = ML_KEM_PARAMS[this.mlKemParameterSet];
4032
+ const otherSet = this.mlKemParameterSet === 1024 ? 768 : 1024;
4033
+ const deserializedCipherText = this.deserializeKey(cipherText);
4034
+ let params = configuredParams;
4035
+ let decapsPrivkey = this.privkey;
4036
+ if (deserializedCipherText.length !== configuredParams.ctBytes) {
4037
+ if (deserializedCipherText.length !== ML_KEM_PARAMS[otherSet].ctBytes) {
4038
+ console.error(
4039
+ `Wallet::decryptMessage() - Ciphertext length mismatch: got ${deserializedCipherText.length}, expected ${configuredParams.ctBytes}`
4040
+ );
4041
+ return null;
4042
+ }
4043
+ const derived = this.deriveMlKemKeypair(otherSet);
4044
+ if (!derived) {
4045
+ console.error(
4046
+ `Wallet::decryptMessage() - cannot derive the ML-KEM-${otherSet} identity: wallet has no key`
4047
+ );
4048
+ return null;
4049
+ }
4050
+ params = derived.params;
4051
+ decapsPrivkey = derived.privkey;
4052
+ }
3949
4053
  let sharedSecret;
3950
4054
  try {
3951
- sharedSecret = mlKem_js.ml_kem768.decapsulate(this.deserializeKey(cipherText), this.privkey);
4055
+ sharedSecret = params.kem.decapsulate(deserializedCipherText, decapsPrivkey);
3952
4056
  } catch (e) {
3953
4057
  console.error("Wallet::decryptMessage() - Decapsulation failed", e);
3954
4058
  console.info("Wallet::decryptMessage() - my public key", this.pubkey);
@@ -3998,11 +4102,11 @@ var Wallet = class _Wallet {
3998
4102
  return this.serializeKey(bytes);
3999
4103
  }
4000
4104
  /**
4001
- * Post-quantum (ML-KEM768) CipherHash request envelope: a stringified single-recipient map
4105
+ * Post-quantum (ML-KEM) CipherHash request envelope: a stringified single-recipient map
4002
4106
  * `{ "<hashShare(recipientPubkey)>": {cipherText, encryptedMessage} }` (object-valued, via
4003
4107
  * {@link encryptMessage}). Matches the Rust validator's CipherHash handler. PQ-transport Phase E.
4004
4108
  */
4005
- async encryptStringML768(message, recipientPubkey) {
4109
+ async encryptStringML(message, recipientPubkey) {
4006
4110
  const envelope = await this.encryptMessage(message, recipientPubkey);
4007
4111
  return JSON.stringify({ [this.hashShare(recipientPubkey)]: envelope });
4008
4112
  }
@@ -4010,9 +4114,21 @@ var Wallet = class _Wallet {
4010
4114
  * Decrypt a CipherHash response map addressed to THIS wallet's ML-KEM pubkey
4011
4115
  * (`hashShare(this.pubkey)`) → the RAW decrypted GraphQL response JSON text (NOT JSON.parsed;
4012
4116
  * it replaces the HTTP response body for the normal parser). `null` if no entry / decrypt fails.
4117
+ *
4118
+ * A pre-bump peer addressed its envelope to `hashShare(our_768_pubkey)`, which a wallet
4119
+ * configured at ML-KEM-1024 would never find — so the other identity's share is tried too.
4120
+ * Without this, the permissive length dispatch in {@link _mlkemDecryptToString} is
4121
+ * unreachable on the transport path.
4013
4122
  */
4014
- async decryptMyMessageML768(map) {
4015
- const envelope = map[this.hashShare(this.pubkey)];
4123
+ async decryptMyMessageML(map) {
4124
+ let envelope = map[this.hashShare(this.pubkey)];
4125
+ if (!envelope) {
4126
+ const otherSet = this.mlKemParameterSet === 1024 ? 768 : 1024;
4127
+ const other = this.deriveMlKemKeypair(otherSet);
4128
+ if (other) {
4129
+ envelope = map[this.hashShare(other.pubkey)];
4130
+ }
4131
+ }
4016
4132
  if (!envelope) {
4017
4133
  return null;
4018
4134
  }
@@ -4166,7 +4282,8 @@ zod.z.object({
4166
4282
  serverSdkVersion: zod.z.number().int().min(1).optional(),
4167
4283
  logging: zod.z.boolean().optional(),
4168
4284
  defaultRequestPolicy: zod.z.enum(["cache-first", "cache-only", "network-only", "cache-and-network"]).nullable().optional(),
4169
- secretStorage: zod.z.unknown().optional()
4285
+ secretStorage: zod.z.unknown().optional(),
4286
+ mlKemParameterSet: zod.z.union([zod.z.literal(1024), zod.z.literal(768)]).optional()
4170
4287
  }).strict();
4171
4288
  zod.z.object({
4172
4289
  token: zod.z.string().min(1, "Auth token cannot be empty"),
@@ -5255,6 +5372,7 @@ var Molecule = class _Molecule {
5255
5372
  continuIdPosition;
5256
5373
  parentHashes;
5257
5374
  local;
5375
+ mlKemParameterSet = 1024;
5258
5376
  /**
5259
5377
  * Create new Molecule instance
5260
5378
  * Matches JavaScript SDK constructor signature
@@ -5266,7 +5384,8 @@ var Molecule = class _Molecule {
5266
5384
  remainderWallet = null,
5267
5385
  cellSlug = null,
5268
5386
  version = null,
5269
- continuIdPosition = null
5387
+ continuIdPosition = null,
5388
+ mlKemParameterSet = null
5270
5389
  } = {}) {
5271
5390
  this.status = null;
5272
5391
  this.molecularHash = null;
@@ -5278,6 +5397,7 @@ var Molecule = class _Molecule {
5278
5397
  this.continuIdPosition = continuIdPosition;
5279
5398
  this.atoms = [];
5280
5399
  this.parentHashes = [];
5400
+ this.mlKemParameterSet = mlKemParameterSet || sourceWallet?.mlKemParameterSet || 1024;
5281
5401
  const versionRegistry = versions_default;
5282
5402
  if (version !== null && Object.prototype.hasOwnProperty.call(versionRegistry, version)) {
5283
5403
  this.version = String(version);
@@ -5288,7 +5408,8 @@ var Molecule = class _Molecule {
5288
5408
  bundle,
5289
5409
  token: sourceWallet.token,
5290
5410
  batchId: sourceWallet.batchId,
5291
- characters: sourceWallet.characters
5411
+ characters: sourceWallet.characters,
5412
+ mlKemParameterSet: this.mlKemParameterSet
5292
5413
  });
5293
5414
  } else {
5294
5415
  this.remainderWallet = null;
@@ -5349,7 +5470,8 @@ var Molecule = class _Molecule {
5349
5470
  if (!this.remainderWallet || this.remainderWallet.token !== "USER") {
5350
5471
  this.remainderWallet = Wallet.create({
5351
5472
  secret: this.secret,
5352
- bundle: this.bundle
5473
+ bundle: this.bundle,
5474
+ mlKemParameterSet: this.mlKemParameterSet
5353
5475
  });
5354
5476
  }
5355
5477
  const continuIdMeta = {};
@@ -5736,7 +5858,8 @@ var Molecule = class _Molecule {
5736
5858
  }
5737
5859
  const burnWallet = new Wallet({
5738
5860
  bundle: "0000000000000000000000000000000000000000000000000000000000000000",
5739
- token: this.sourceWallet.token
5861
+ token: this.sourceWallet.token,
5862
+ mlKemParameterSet: this.mlKemParameterSet
5740
5863
  });
5741
5864
  this.addAtom(Atom.create({
5742
5865
  isotope: "V",
@@ -6008,7 +6131,8 @@ var Molecule = class _Molecule {
6008
6131
  position: data.sourceWallet.position,
6009
6132
  bundle: data.sourceWallet.bundle,
6010
6133
  batchId: data.sourceWallet.batchId,
6011
- characters: data.sourceWallet.characters
6134
+ characters: data.sourceWallet.characters,
6135
+ mlKemParameterSet: molecule.mlKemParameterSet
6012
6136
  });
6013
6137
  molecule.sourceWallet.balance = String(data.sourceWallet.balance != null ? data.sourceWallet.balance : 0);
6014
6138
  molecule.sourceWallet.address = data.sourceWallet.address;
@@ -6026,7 +6150,8 @@ var Molecule = class _Molecule {
6026
6150
  position: data.remainderWallet.position,
6027
6151
  bundle: data.remainderWallet.bundle,
6028
6152
  batchId: data.remainderWallet.batchId,
6029
- characters: data.remainderWallet.characters
6153
+ characters: data.remainderWallet.characters,
6154
+ mlKemParameterSet: molecule.mlKemParameterSet
6030
6155
  });
6031
6156
  molecule.remainderWallet.balance = String(data.remainderWallet.balance != null ? data.remainderWallet.balance : 0);
6032
6157
  molecule.remainderWallet.address = data.remainderWallet.address;
@@ -6124,7 +6249,8 @@ var Molecule = class _Molecule {
6124
6249
  secret: this.secret,
6125
6250
  bundle: this.bundle,
6126
6251
  token: this.sourceWallet.token,
6127
- batchId: this.sourceWallet.batchId
6252
+ batchId: this.sourceWallet.batchId,
6253
+ mlKemParameterSet: this.mlKemParameterSet
6128
6254
  });
6129
6255
  if (tradeRates) {
6130
6256
  bufferWallet.tradeRates = tradeRates;
@@ -6395,7 +6521,7 @@ var GraphQLClient = class {
6395
6521
  let encryptedRequest = false;
6396
6522
  let requestInit = init;
6397
6523
  if (wallet && serverPubkey && init && typeof init.body === "string" && this.shouldEncrypt(init.body)) {
6398
- const hashVar = await wallet.encryptStringML768(init.body, serverPubkey);
6524
+ const hashVar = await wallet.encryptStringML(init.body, serverPubkey);
6399
6525
  requestInit = { ...init, body: JSON.stringify({ query: CIPHER_HASH_QUERY, variables: { Hash: hashVar } }) };
6400
6526
  encryptedRequest = true;
6401
6527
  }
@@ -6415,7 +6541,7 @@ var GraphQLClient = class {
6415
6541
  if (typeof hash !== "string") {
6416
6542
  return new Response(text, init2);
6417
6543
  }
6418
- const decrypted = await wallet.decryptMyMessageML768(JSON.parse(hash));
6544
+ const decrypted = await wallet.decryptMyMessageML(JSON.parse(hash));
6419
6545
  return new Response(decrypted != null ? decrypted : text, init2);
6420
6546
  }
6421
6547
  setAuthData({
@@ -6584,6 +6710,22 @@ var AuthToken = class _AuthToken {
6584
6710
  authToken.setWallet(wallet);
6585
6711
  return authToken;
6586
6712
  }
6713
+ /**
6714
+ * ML-KEM parameter set a restored session must use, resolved in three tiers:
6715
+ * an explicit snapshot field, then the stored validator key's length, then ML-KEM-768.
6716
+ *
6717
+ * The final tier is deliberately NOT the constructor default. A snapshot with neither an
6718
+ * explicit field nor a recognisable key can only have come from a pre-bump build, and every
6719
+ * pre-bump build was 768-only — defaulting to 1024 would make the restored wallet advertise
6720
+ * a public key the validator never recorded for that token.
6721
+ */
6722
+ static resolveMlKemParameterSet(snapshot) {
6723
+ const explicit = snapshot.wallet?.mlKemParameterSet;
6724
+ if (explicit) {
6725
+ return Number(explicit) === 768 ? 768 : 1024;
6726
+ }
6727
+ return Wallet.mlKemParameterSetFromPubkey(snapshot.pubkey) ?? 768;
6728
+ }
6587
6729
  /**
6588
6730
  * Restore AuthToken from snapshot
6589
6731
  */
@@ -6591,8 +6733,9 @@ var AuthToken = class _AuthToken {
6591
6733
  const wallet = new Wallet({
6592
6734
  secret,
6593
6735
  token: "AUTH",
6594
- position: snapshot.wallet.position,
6595
- characters: snapshot.wallet.characters
6736
+ position: snapshot.wallet?.position ?? null,
6737
+ characters: snapshot.wallet?.characters ?? null,
6738
+ mlKemParameterSet: _AuthToken.resolveMlKemParameterSet(snapshot)
6596
6739
  });
6597
6740
  return _AuthToken.create({
6598
6741
  token: snapshot.token,
@@ -6660,7 +6803,9 @@ var AuthToken = class _AuthToken {
6660
6803
  };
6661
6804
  }
6662
6805
  /**
6663
- * Create snapshot for persistence
6806
+ * Create snapshot for persistence. The wallet's ML-KEM parameter set is recorded beside its
6807
+ * position and characters so a stepped-back ML-KEM-768 session restores as 768 rather than
6808
+ * silently taking the constructor default.
6664
6809
  */
6665
6810
  toSnapshot() {
6666
6811
  return {
@@ -6671,7 +6816,8 @@ var AuthToken = class _AuthToken {
6671
6816
  ...this.$__wallet ? {
6672
6817
  wallet: {
6673
6818
  position: this.$__wallet.position,
6674
- characters: this.$__wallet.characters
6819
+ characters: this.$__wallet.characters,
6820
+ mlKemParameterSet: this.$__wallet.mlKemParameterSet
6675
6821
  }
6676
6822
  } : {}
6677
6823
  };
@@ -6968,7 +7114,8 @@ var KnishIOClientConfigSchema2 = zod.z.object({
6968
7114
  // isn't rejected.
6969
7115
  defaultRequestPolicy: zod.z.enum(["cache-first", "cache-only", "network-only", "cache-and-network"]).nullable().optional(),
6970
7116
  // Pluggable hardware envelope encryption secret storage provider
6971
- secretStorage: zod.z.unknown().optional()
7117
+ secretStorage: zod.z.unknown().optional(),
7118
+ mlKemParameterSet: zod.z.union([zod.z.literal(1024), zod.z.literal(768)]).optional()
6972
7119
  }).strict();
6973
7120
  var EnvironmentConfigSchema = zod.z.object({
6974
7121
  NODE_ENV: zod.z.enum(["development", "production", "test"]).optional(),
@@ -10558,10 +10705,121 @@ function constantTimeCompare(a, b) {
10558
10705
  return result === 0;
10559
10706
  }
10560
10707
 
10708
+ // src/storage/secretEnvelope.ts
10709
+ init_SecretStorageException();
10710
+ var ENVELOPE_ALGORITHM = "AES-GCM";
10711
+ var DEFAULT_ITERATIONS = 1e5;
10712
+ var SECRET_KEY_PREFIX = "knishio:secret:";
10713
+ var RECOVERY_KEY_PREFIX = "knishio:recovery:";
10714
+ var GCM_IV_LENGTH = 12;
10715
+ var SALT_LENGTH = 16;
10716
+ var textEncoder2 = new TextEncoder();
10717
+ function uint8ArrayToBase64(bytes) {
10718
+ let binary = "";
10719
+ const len = bytes.byteLength;
10720
+ for (let i = 0; i < len; i++) {
10721
+ const byte = bytes[i];
10722
+ if (byte !== void 0) {
10723
+ binary += String.fromCharCode(byte);
10724
+ }
10725
+ }
10726
+ return btoa(binary);
10727
+ }
10728
+ function base64ToUint8Array(base64) {
10729
+ const binary = atob(base64);
10730
+ const len = binary.length;
10731
+ const bytes = new Uint8Array(len);
10732
+ for (let i = 0; i < len; i++) {
10733
+ bytes[i] = binary.charCodeAt(i);
10734
+ }
10735
+ return bytes;
10736
+ }
10737
+ async function deriveEnvelopeKey(passphrase, salt, iterations = DEFAULT_ITERATIONS) {
10738
+ if (typeof globalThis.crypto === "undefined" || typeof globalThis.crypto.subtle === "undefined") {
10739
+ throw new exports.SecretStorageException("WebCrypto API is not available");
10740
+ }
10741
+ const passphraseBytes = textEncoder2.encode(passphrase);
10742
+ try {
10743
+ const baseKey = await globalThis.crypto.subtle.importKey(
10744
+ "raw",
10745
+ passphraseBytes,
10746
+ "PBKDF2",
10747
+ false,
10748
+ ["deriveKey"]
10749
+ );
10750
+ return await globalThis.crypto.subtle.deriveKey(
10751
+ {
10752
+ name: "PBKDF2",
10753
+ salt,
10754
+ iterations,
10755
+ hash: "SHA-256"
10756
+ },
10757
+ baseKey,
10758
+ { name: "AES-GCM", length: 256 },
10759
+ false,
10760
+ ["encrypt", "decrypt"]
10761
+ );
10762
+ } finally {
10763
+ zeroizeBytes(passphraseBytes);
10764
+ }
10765
+ }
10766
+ async function sealEnvelope(secret, passphrase, metadata) {
10767
+ if (typeof globalThis.crypto === "undefined" || typeof globalThis.crypto.subtle === "undefined") {
10768
+ throw new exports.SecretStorageException("WebCrypto API is not available");
10769
+ }
10770
+ const salt = new Uint8Array(SALT_LENGTH);
10771
+ const iv = new Uint8Array(GCM_IV_LENGTH);
10772
+ globalThis.crypto.getRandomValues(salt);
10773
+ globalThis.crypto.getRandomValues(iv);
10774
+ const key = await deriveEnvelopeKey(passphrase, salt, DEFAULT_ITERATIONS);
10775
+ const secretBytes = textEncoder2.encode(secret);
10776
+ try {
10777
+ const encryptedBuffer = await globalThis.crypto.subtle.encrypt(
10778
+ {
10779
+ name: ENVELOPE_ALGORITHM,
10780
+ iv
10781
+ },
10782
+ key,
10783
+ secretBytes
10784
+ );
10785
+ const ciphertext = uint8ArrayToBase64(new Uint8Array(encryptedBuffer));
10786
+ return {
10787
+ version: 1,
10788
+ ciphertext,
10789
+ iv: uint8ArrayToBase64(iv),
10790
+ salt: uint8ArrayToBase64(salt),
10791
+ algorithm: ENVELOPE_ALGORITHM,
10792
+ iterations: DEFAULT_ITERATIONS,
10793
+ metadata
10794
+ };
10795
+ } finally {
10796
+ zeroizeBytes(secretBytes);
10797
+ }
10798
+ }
10799
+ async function openEnvelope(payload, passphrase) {
10800
+ if (typeof globalThis.crypto === "undefined" || typeof globalThis.crypto.subtle === "undefined") {
10801
+ throw new exports.SecretStorageException("WebCrypto API is not available");
10802
+ }
10803
+ const salt = base64ToUint8Array(payload.salt);
10804
+ const iv = base64ToUint8Array(payload.iv);
10805
+ const ciphertext = base64ToUint8Array(payload.ciphertext);
10806
+ const key = await deriveEnvelopeKey(passphrase, salt, payload.iterations ?? DEFAULT_ITERATIONS);
10807
+ const decryptedBuffer = await globalThis.crypto.subtle.decrypt(
10808
+ {
10809
+ name: ENVELOPE_ALGORITHM,
10810
+ iv
10811
+ },
10812
+ key,
10813
+ ciphertext
10814
+ );
10815
+ return new Uint8Array(decryptedBuffer);
10816
+ }
10817
+
10561
10818
  // src/storage/MemorySecretStorageProvider.ts
10562
10819
  var MemorySecretStorageProvider = class {
10563
10820
  providerType = "memory";
10564
10821
  secrets = /* @__PURE__ */ new Map();
10822
+ recoverySecrets = /* @__PURE__ */ new Map();
10565
10823
  /**
10566
10824
  * Memory storage is not hardware backed
10567
10825
  */
@@ -10592,6 +10850,17 @@ var MemorySecretStorageProvider = class {
10592
10850
  providerType: this.providerType
10593
10851
  };
10594
10852
  this.secrets.set(bundleHash, { secret, metadata });
10853
+ if (options?.recoveryPassphrase) {
10854
+ const recoveryMetadata = {
10855
+ bundleHash,
10856
+ label: options?.label,
10857
+ createdAt: Date.now(),
10858
+ hardwareBacked: false,
10859
+ providerType: "webcrypto-aes-gcm"
10860
+ };
10861
+ const recoveryPayload = await sealEnvelope(secret, options.recoveryPassphrase, recoveryMetadata);
10862
+ this.recoverySecrets.set(bundleHash, JSON.stringify(recoveryPayload));
10863
+ }
10595
10864
  }
10596
10865
  /**
10597
10866
  * Retrieve a secret from memory
@@ -10604,6 +10873,7 @@ var MemorySecretStorageProvider = class {
10604
10873
  * Delete a stored secret
10605
10874
  */
10606
10875
  async deleteSecret(bundleHash) {
10876
+ this.recoverySecrets.delete(bundleHash);
10607
10877
  return this.secrets.delete(bundleHash);
10608
10878
  }
10609
10879
  /**
@@ -10633,6 +10903,48 @@ var MemorySecretStorageProvider = class {
10633
10903
  */
10634
10904
  clear() {
10635
10905
  this.secrets.clear();
10906
+ this.recoverySecrets.clear();
10907
+ }
10908
+ /**
10909
+ * Recover a secret using its recovery envelope and restore it
10910
+ */
10911
+ async recoverSecret(bundleHash, recoveryPassphrase, options) {
10912
+ if (!bundleHash) {
10913
+ throw new exports.SecretStorageException("Bundle hash cannot be empty");
10914
+ }
10915
+ if (!recoveryPassphrase) {
10916
+ throw new exports.SecretStorageException("Recovery passphrase cannot be empty");
10917
+ }
10918
+ const raw = this.recoverySecrets.get(bundleHash);
10919
+ if (!raw) {
10920
+ throw exports.SecretStorageException.notFound(bundleHash);
10921
+ }
10922
+ let payload;
10923
+ try {
10924
+ payload = JSON.parse(raw);
10925
+ } catch {
10926
+ throw exports.SecretStorageException.decryptionFailed("Corrupted recovery payload format");
10927
+ }
10928
+ let decryptedBytes;
10929
+ try {
10930
+ decryptedBytes = await openEnvelope(payload, recoveryPassphrase);
10931
+ } catch (err) {
10932
+ if (err instanceof exports.SecretStorageException) {
10933
+ throw err;
10934
+ }
10935
+ const msg = err instanceof Error ? err.message : String(err);
10936
+ throw exports.SecretStorageException.decryptionFailed(msg);
10937
+ }
10938
+ let secretStr;
10939
+ try {
10940
+ secretStr = new TextDecoder().decode(decryptedBytes);
10941
+ } finally {
10942
+ zeroizeBytes(decryptedBytes);
10943
+ }
10944
+ await this.storeSecret(bundleHash, secretStr, {
10945
+ ...options,
10946
+ recoveryPassphrase
10947
+ });
10636
10948
  }
10637
10949
  };
10638
10950
 
@@ -10654,6 +10966,7 @@ var KnishIOClient = class {
10654
10966
  $__authTokenObjects = {};
10655
10967
  $__authToken = null;
10656
10968
  $__authInProcess = false;
10969
+ $__mlKemParameterSet = 1024;
10657
10970
  $__remainderWallet = null;
10658
10971
  lastMoleculeQuery = null;
10659
10972
  abortControllers = /* @__PURE__ */ new Map();
@@ -10698,7 +11011,8 @@ var KnishIOClient = class {
10698
11011
  client,
10699
11012
  serverSdkVersion,
10700
11013
  logging,
10701
- defaultRequestPolicy
11014
+ defaultRequestPolicy,
11015
+ mlKemParameterSet: config.mlKemParameterSet ?? 1024
10702
11016
  });
10703
11017
  if (config.secretStorage) {
10704
11018
  this.$__secretStorage = config.secretStorage;
@@ -10714,10 +11028,12 @@ var KnishIOClient = class {
10714
11028
  client = null,
10715
11029
  serverSdkVersion = 3,
10716
11030
  logging = false,
10717
- defaultRequestPolicy = null
11031
+ defaultRequestPolicy = null,
11032
+ mlKemParameterSet = 1024
10718
11033
  }) {
10719
11034
  this.reset();
10720
11035
  this.$__logging = logging;
11036
+ this.setMlKemParameterSet(mlKemParameterSet);
10721
11037
  this.$__authTokenObjects = {};
10722
11038
  this.setUri(uri);
10723
11039
  if (cellSlug) {
@@ -10738,6 +11054,17 @@ var KnishIOClient = class {
10738
11054
  this.$__serverSdkVersion = serverSdkVersion;
10739
11055
  this.$__defaultRequestPolicy = defaultRequestPolicy;
10740
11056
  }
11057
+ getMlKemParameterSet() {
11058
+ return this.$__mlKemParameterSet || 1024;
11059
+ }
11060
+ setMlKemParameterSet(parameterSet) {
11061
+ const paramNum = Number(parameterSet);
11062
+ if (![1024, 768].includes(paramNum)) {
11063
+ throw new Error(`KnishIO: unsupported ML-KEM parameter set ${parameterSet}; expected 1024 or 768.`);
11064
+ }
11065
+ this.$__mlKemParameterSet = paramNum;
11066
+ return this;
11067
+ }
10741
11068
  /**
10742
11069
  * Get random uri from specified this.$__uris
10743
11070
  */
@@ -10974,7 +11301,8 @@ var KnishIOClient = class {
10974
11301
  bundle,
10975
11302
  token: "USER",
10976
11303
  batchId: sourceWallet.batchId,
10977
- characters: sourceWallet.characters
11304
+ characters: sourceWallet.characters,
11305
+ mlKemParameterSet: this.getMlKemParameterSet()
10978
11306
  }));
10979
11307
  return new Molecule({
10980
11308
  secret,
@@ -10983,7 +11311,8 @@ var KnishIOClient = class {
10983
11311
  remainderWallet: this.getRemainderWallet(),
10984
11312
  cellSlug: this.getCellSlug(),
10985
11313
  version: this.getServerSdkVersion(),
10986
- continuIdPosition
11314
+ continuIdPosition,
11315
+ mlKemParameterSet: this.getMlKemParameterSet()
10987
11316
  });
10988
11317
  }
10989
11318
  /**
@@ -11095,7 +11424,8 @@ var KnishIOClient = class {
11095
11424
  }))?.payload();
11096
11425
  if (!sourceWallet) {
11097
11426
  sourceWallet = new Wallet({
11098
- secret: this.getSecret()
11427
+ secret: this.getSecret(),
11428
+ mlKemParameterSet: this.getMlKemParameterSet()
11099
11429
  });
11100
11430
  } else {
11101
11431
  sourceWallet.key = Wallet.generateKey({
@@ -11138,7 +11468,8 @@ var KnishIOClient = class {
11138
11468
  }
11139
11469
  const recipientWallet = Wallet.create({
11140
11470
  bundle: bundleHash,
11141
- token
11471
+ token,
11472
+ mlKemParameterSet: this.getMlKemParameterSet()
11142
11473
  });
11143
11474
  if (batchId !== null) {
11144
11475
  recipientWallet.batchId = batchId;
@@ -11205,7 +11536,8 @@ var KnishIOClient = class {
11205
11536
  const recipientWallets = recipients.map((recipient) => {
11206
11537
  const recipientWallet = Wallet.create({
11207
11538
  bundle: recipient.bundleHash,
11208
- token
11539
+ token,
11540
+ mlKemParameterSet: this.getMlKemParameterSet()
11209
11541
  });
11210
11542
  if (recipient.batchId !== null && recipient.batchId !== void 0) {
11211
11543
  recipientWallet.batchId = recipient.batchId;
@@ -11829,7 +12161,8 @@ var KnishIOClient = class {
11829
12161
  secret: this.getSecret(),
11830
12162
  bundle: this.getBundle(),
11831
12163
  token,
11832
- batchId
12164
+ batchId,
12165
+ mlKemParameterSet: this.getMlKemParameterSet()
11833
12166
  });
11834
12167
  await mutation.fillMolecule({
11835
12168
  recipientWallet,
@@ -11897,7 +12230,8 @@ var KnishIOClient = class {
11897
12230
  const recipientWallet = new Wallet({
11898
12231
  secret: this.getSecret(),
11899
12232
  bundle: "0000000000000000000000000000000000000000000000000000000000000000",
11900
- token
12233
+ token,
12234
+ mlKemParameterSet: this.getMlKemParameterSet()
11901
12235
  });
11902
12236
  await mutation.fillMolecule({
11903
12237
  recipientWallet,
@@ -11974,7 +12308,8 @@ var KnishIOClient = class {
11974
12308
  const newWallet = new Wallet({
11975
12309
  secret: this.getSecret(),
11976
12310
  bundle: this.getBundle(),
11977
- token
12311
+ token,
12312
+ mlKemParameterSet: this.getMlKemParameterSet()
11978
12313
  });
11979
12314
  await mutation.fillMolecule(newWallet);
11980
12315
  const response = await this.executeQuery(mutation);
@@ -12302,7 +12637,8 @@ var KnishIOClient = class {
12302
12637
  this.setSecret(secret);
12303
12638
  const wallet = new Wallet({
12304
12639
  secret,
12305
- token: "AUTH"
12640
+ token: "AUTH",
12641
+ mlKemParameterSet: this.getMlKemParameterSet()
12306
12642
  });
12307
12643
  const molecule = await this.createMolecule({
12308
12644
  secret,
@@ -12430,45 +12766,25 @@ var MemoryStorageBackend = class {
12430
12766
  return Array.from(this.store.keys());
12431
12767
  }
12432
12768
  };
12433
- function uint8ArrayToBase64(bytes) {
12434
- let binary = "";
12435
- const len = bytes.byteLength;
12436
- for (let i = 0; i < len; i++) {
12437
- const byte = bytes[i];
12438
- if (byte !== void 0) {
12439
- binary += String.fromCharCode(byte);
12440
- }
12441
- }
12442
- return btoa(binary);
12443
- }
12444
- function base64ToUint8Array(base64) {
12445
- const binary = atob(base64);
12446
- const len = binary.length;
12447
- const bytes = new Uint8Array(len);
12448
- for (let i = 0; i < len; i++) {
12449
- bytes[i] = binary.charCodeAt(i);
12450
- }
12451
- return bytes;
12452
- }
12453
- var textEncoder2 = new TextEncoder();
12454
12769
  var textDecoder = new TextDecoder();
12455
- var KEY_PREFIX = "knishio:secret:";
12456
- var DEFAULT_ITERATIONS = 1e5;
12770
+ var KEY_PREFIX = SECRET_KEY_PREFIX;
12457
12771
  var WebCryptoSecretStorageProvider = class {
12458
12772
  providerType = "webcrypto-aes-gcm";
12459
12773
  backend;
12460
12774
  defaultPassphrase;
12461
- hardwareBacked;
12462
12775
  constructor(options = {}) {
12463
12776
  this.backend = options.backend ?? new MemoryStorageBackend();
12464
12777
  this.defaultPassphrase = options.defaultPassphrase;
12465
- this.hardwareBacked = options.hardwareBacked ?? false;
12466
12778
  }
12467
12779
  /**
12468
- * Whether this provider is backed by hardware (e.g. WebAuthn PRF wrapping)
12780
+ * True only when this provider holds a non-exportable key inside platform-secure
12781
+ * hardware (Android TEE/StrongBox, Secure Enclave, TPM) and learned that from the
12782
+ * platform itself — never from a caller argument. Software envelope providers
12783
+ * return false. The value is persisted as `metadata.hardwareBacked` in every
12784
+ * envelope this provider writes.
12469
12785
  */
12470
12786
  isHardwareBacked() {
12471
- return this.hardwareBacked;
12787
+ return false;
12472
12788
  }
12473
12789
  /**
12474
12790
  * Check if WebCrypto subtle API is available
@@ -12476,38 +12792,6 @@ var WebCryptoSecretStorageProvider = class {
12476
12792
  async isAvailable() {
12477
12793
  return typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined";
12478
12794
  }
12479
- /**
12480
- * Derive an AES-GCM CryptoKey from a passphrase and salt using PBKDF2
12481
- */
12482
- async deriveKey(passphrase, salt, iterations = DEFAULT_ITERATIONS) {
12483
- if (!await this.isAvailable()) {
12484
- throw exports.SecretStorageException.unavailable(this.providerType, "WebCrypto API is not available");
12485
- }
12486
- const passphraseBytes = textEncoder2.encode(passphrase);
12487
- try {
12488
- const baseKey = await globalThis.crypto.subtle.importKey(
12489
- "raw",
12490
- passphraseBytes,
12491
- "PBKDF2",
12492
- false,
12493
- ["deriveKey"]
12494
- );
12495
- return await globalThis.crypto.subtle.deriveKey(
12496
- {
12497
- name: "PBKDF2",
12498
- salt,
12499
- iterations,
12500
- hash: "SHA-256"
12501
- },
12502
- baseKey,
12503
- { name: "AES-GCM", length: 256 },
12504
- false,
12505
- ["encrypt", "decrypt"]
12506
- );
12507
- } finally {
12508
- zeroizeBytes(passphraseBytes);
12509
- }
12510
- }
12511
12795
  /**
12512
12796
  * Store and encrypt a master secret
12513
12797
  */
@@ -12522,44 +12806,36 @@ var WebCryptoSecretStorageProvider = class {
12522
12806
  if (!passphrase) {
12523
12807
  throw new exports.SecretStorageException("Passphrase required for envelope encryption");
12524
12808
  }
12525
- const salt = new Uint8Array(16);
12526
- const iv = new Uint8Array(12);
12527
- globalThis.crypto.getRandomValues(salt);
12528
- globalThis.crypto.getRandomValues(iv);
12529
- const key = await this.deriveKey(passphrase, salt, DEFAULT_ITERATIONS);
12530
- const secretBytes = textEncoder2.encode(secret);
12809
+ if (!await this.isAvailable()) {
12810
+ throw exports.SecretStorageException.unavailable(this.providerType, "WebCrypto API is not available");
12811
+ }
12531
12812
  try {
12532
- const encryptedBuffer = await globalThis.crypto.subtle.encrypt(
12533
- {
12534
- name: "AES-GCM",
12535
- iv
12536
- },
12537
- key,
12538
- secretBytes
12539
- );
12540
- const ciphertext = uint8ArrayToBase64(new Uint8Array(encryptedBuffer));
12541
12813
  const metadata = {
12542
12814
  bundleHash,
12543
12815
  label: options?.label,
12544
12816
  createdAt: Date.now(),
12545
- hardwareBacked: this.hardwareBacked,
12817
+ hardwareBacked: false,
12546
12818
  providerType: this.providerType
12547
12819
  };
12548
- const payload = {
12549
- version: 1,
12550
- ciphertext,
12551
- iv: uint8ArrayToBase64(iv),
12552
- salt: uint8ArrayToBase64(salt),
12553
- algorithm: "AES-GCM",
12554
- iterations: DEFAULT_ITERATIONS,
12555
- metadata
12556
- };
12820
+ const payload = await sealEnvelope(secret, passphrase, metadata);
12557
12821
  await this.backend.setItem(`${KEY_PREFIX}${bundleHash}`, JSON.stringify(payload));
12822
+ if (options?.recoveryPassphrase) {
12823
+ const recoveryMetadata = {
12824
+ bundleHash,
12825
+ label: options?.label,
12826
+ createdAt: Date.now(),
12827
+ hardwareBacked: false,
12828
+ providerType: "webcrypto-aes-gcm"
12829
+ };
12830
+ const recoveryPayload = await sealEnvelope(secret, options.recoveryPassphrase, recoveryMetadata);
12831
+ await this.backend.setItem(`${RECOVERY_KEY_PREFIX}${bundleHash}`, JSON.stringify(recoveryPayload));
12832
+ }
12558
12833
  } catch (err) {
12834
+ if (err instanceof exports.SecretStorageException) {
12835
+ throw err;
12836
+ }
12559
12837
  const msg = err instanceof Error ? err.message : String(err);
12560
12838
  throw new exports.SecretStorageException(`Encryption failed: ${msg}`);
12561
- } finally {
12562
- zeroizeBytes(secretBytes);
12563
12839
  }
12564
12840
  }
12565
12841
  /**
@@ -12580,26 +12856,20 @@ var WebCryptoSecretStorageProvider = class {
12580
12856
  if (!passphrase) {
12581
12857
  throw new exports.SecretStorageException("Passphrase required for secret decryption");
12582
12858
  }
12583
- const salt = base64ToUint8Array(payload.salt);
12584
- const iv = base64ToUint8Array(payload.iv);
12585
- const ciphertext = base64ToUint8Array(payload.ciphertext);
12859
+ if (!await this.isAvailable()) {
12860
+ throw exports.SecretStorageException.unavailable(this.providerType, "WebCrypto API is not available");
12861
+ }
12586
12862
  try {
12587
- const key = await this.deriveKey(passphrase, salt, payload.iterations ?? DEFAULT_ITERATIONS);
12588
- const decryptedBuffer = await globalThis.crypto.subtle.decrypt(
12589
- {
12590
- name: "AES-GCM",
12591
- iv
12592
- },
12593
- key,
12594
- ciphertext
12595
- );
12596
- const decryptedBytes = new Uint8Array(decryptedBuffer);
12863
+ const decryptedBytes = await openEnvelope(payload, passphrase);
12597
12864
  try {
12598
12865
  return textDecoder.decode(decryptedBytes);
12599
12866
  } finally {
12600
12867
  zeroizeBytes(decryptedBytes);
12601
12868
  }
12602
12869
  } catch (err) {
12870
+ if (err instanceof exports.SecretStorageException) {
12871
+ throw err;
12872
+ }
12603
12873
  const msg = err instanceof Error ? err.message : String(err);
12604
12874
  throw exports.SecretStorageException.decryptionFailed(msg);
12605
12875
  }
@@ -12609,7 +12879,9 @@ var WebCryptoSecretStorageProvider = class {
12609
12879
  */
12610
12880
  async deleteSecret(bundleHash) {
12611
12881
  const key = `${KEY_PREFIX}${bundleHash}`;
12882
+ const recoveryKey = `${RECOVERY_KEY_PREFIX}${bundleHash}`;
12612
12883
  const result = await this.backend.removeItem(key);
12884
+ await this.backend.removeItem(recoveryKey);
12613
12885
  return result !== false;
12614
12886
  }
12615
12887
  /**
@@ -12624,7 +12896,7 @@ var WebCryptoSecretStorageProvider = class {
12624
12896
  */
12625
12897
  async listSecrets() {
12626
12898
  const keys = await this.backend.keys();
12627
- const matchingKeys = keys.filter((k) => k.startsWith(KEY_PREFIX));
12899
+ const matchingKeys = keys.filter((k) => k.startsWith(KEY_PREFIX) && !k.startsWith(RECOVERY_KEY_PREFIX));
12628
12900
  const results = [];
12629
12901
  for (const key of matchingKeys) {
12630
12902
  const raw = await this.backend.getItem(key);
@@ -12658,20 +12930,11 @@ var WebCryptoSecretStorageProvider = class {
12658
12930
  if (!passphrase) {
12659
12931
  throw new exports.SecretStorageException("Passphrase required for secret decryption");
12660
12932
  }
12661
- const salt = base64ToUint8Array(payload.salt);
12662
- const iv = base64ToUint8Array(payload.iv);
12663
- const ciphertext = base64ToUint8Array(payload.ciphertext);
12933
+ if (!await this.isAvailable()) {
12934
+ throw exports.SecretStorageException.unavailable(this.providerType, "WebCrypto API is not available");
12935
+ }
12664
12936
  try {
12665
- const key = await this.deriveKey(passphrase, salt, payload.iterations ?? DEFAULT_ITERATIONS);
12666
- const decryptedBuffer = await globalThis.crypto.subtle.decrypt(
12667
- {
12668
- name: "AES-GCM",
12669
- iv
12670
- },
12671
- key,
12672
- ciphertext
12673
- );
12674
- const decryptedBytes = new Uint8Array(decryptedBuffer);
12937
+ const decryptedBytes = await openEnvelope(payload, passphrase);
12675
12938
  return await withSecureBytes(decryptedBytes, async (bytes) => {
12676
12939
  const secretString = textDecoder.decode(bytes);
12677
12940
  return await fn(secretString);
@@ -12684,6 +12947,1071 @@ var WebCryptoSecretStorageProvider = class {
12684
12947
  throw exports.SecretStorageException.decryptionFailed(msg);
12685
12948
  }
12686
12949
  }
12950
+ /**
12951
+ * Recover a secret using its recovery envelope and re-enroll it
12952
+ */
12953
+ async recoverSecret(bundleHash, recoveryPassphrase, options) {
12954
+ if (!bundleHash) {
12955
+ throw new exports.SecretStorageException("Bundle hash cannot be empty");
12956
+ }
12957
+ if (!recoveryPassphrase) {
12958
+ throw new exports.SecretStorageException("Recovery passphrase cannot be empty");
12959
+ }
12960
+ const raw = await this.backend.getItem(`${RECOVERY_KEY_PREFIX}${bundleHash}`);
12961
+ if (!raw) {
12962
+ throw exports.SecretStorageException.notFound(bundleHash);
12963
+ }
12964
+ let payload;
12965
+ try {
12966
+ payload = JSON.parse(raw);
12967
+ } catch {
12968
+ throw exports.SecretStorageException.decryptionFailed("Corrupted recovery payload format");
12969
+ }
12970
+ let decryptedBytes;
12971
+ try {
12972
+ decryptedBytes = await openEnvelope(payload, recoveryPassphrase);
12973
+ } catch (err) {
12974
+ if (err instanceof exports.SecretStorageException) {
12975
+ throw err;
12976
+ }
12977
+ const msg = err instanceof Error ? err.message : String(err);
12978
+ throw exports.SecretStorageException.decryptionFailed(msg);
12979
+ }
12980
+ let secretStr;
12981
+ try {
12982
+ secretStr = textDecoder.decode(decryptedBytes);
12983
+ } finally {
12984
+ zeroizeBytes(decryptedBytes);
12985
+ }
12986
+ const storePassphrase = options?.passphrase ?? this.defaultPassphrase ?? recoveryPassphrase;
12987
+ await this.storeSecret(bundleHash, secretStr, {
12988
+ ...options,
12989
+ passphrase: storePassphrase,
12990
+ recoveryPassphrase
12991
+ });
12992
+ }
12993
+ };
12994
+
12995
+ // src/storage/FileStorageBackend.ts
12996
+ init_SecretStorageException();
12997
+ var FileStorageBackend = class {
12998
+ filePath;
12999
+ store = /* @__PURE__ */ new Map();
13000
+ loaded = false;
13001
+ constructor(filePath) {
13002
+ if (!filePath) {
13003
+ throw new exports.SecretStorageException("Storage file path cannot be empty");
13004
+ }
13005
+ this.filePath = filePath;
13006
+ }
13007
+ async getFs() {
13008
+ try {
13009
+ const fs = await import('fs/promises');
13010
+ const path = await import('path');
13011
+ return { fs, path };
13012
+ } catch {
13013
+ throw exports.SecretStorageException.unavailable(
13014
+ "file-storage",
13015
+ "FileStorageBackend is only supported in Node.js environments with node:fs access"
13016
+ );
13017
+ }
13018
+ }
13019
+ async ensureLoaded() {
13020
+ if (this.loaded) {
13021
+ return this.store;
13022
+ }
13023
+ const { fs } = await this.getFs();
13024
+ try {
13025
+ const content = await fs.readFile(this.filePath, "utf8");
13026
+ let parsed;
13027
+ try {
13028
+ parsed = JSON.parse(content);
13029
+ } catch {
13030
+ throw exports.SecretStorageException.decryptionFailed("Corrupted storage file format");
13031
+ }
13032
+ if (parsed && typeof parsed === "object") {
13033
+ this.store = new Map(Object.entries(parsed).map(([k, v]) => [k, String(v)]));
13034
+ }
13035
+ } catch (err) {
13036
+ if (err instanceof exports.SecretStorageException) {
13037
+ throw err;
13038
+ }
13039
+ const nodeErr = err;
13040
+ if (nodeErr?.code !== "ENOENT") {
13041
+ const msg = err instanceof Error ? err.message : String(err);
13042
+ throw new exports.SecretStorageException(`Failed to read storage file: ${msg}`);
13043
+ }
13044
+ this.store = /* @__PURE__ */ new Map();
13045
+ }
13046
+ this.loaded = true;
13047
+ return this.store;
13048
+ }
13049
+ async persist() {
13050
+ const { fs, path } = await this.getFs();
13051
+ const dir = path.dirname(this.filePath);
13052
+ if (dir && dir !== ".") {
13053
+ await fs.mkdir(dir, { recursive: true });
13054
+ }
13055
+ const tmpPath = `${this.filePath}.tmp.${Date.now()}_${Math.random().toString(36).slice(2)}`;
13056
+ const data = JSON.stringify(Object.fromEntries(this.store), null, 2);
13057
+ try {
13058
+ await fs.writeFile(tmpPath, data, { mode: 384, encoding: "utf8" });
13059
+ if (typeof process !== "undefined" && process.platform !== "win32") {
13060
+ try {
13061
+ await fs.chmod(tmpPath, 384);
13062
+ } catch {
13063
+ }
13064
+ }
13065
+ await fs.rename(tmpPath, this.filePath);
13066
+ } catch (err) {
13067
+ try {
13068
+ await fs.unlink(tmpPath);
13069
+ } catch {
13070
+ }
13071
+ const msg = err instanceof Error ? err.message : String(err);
13072
+ throw new exports.SecretStorageException(`Failed to persist storage file: ${msg}`);
13073
+ }
13074
+ }
13075
+ async getItem(key) {
13076
+ await this.ensureLoaded();
13077
+ return this.store.get(key) ?? null;
13078
+ }
13079
+ async setItem(key, value) {
13080
+ await this.ensureLoaded();
13081
+ this.store.set(key, value);
13082
+ await this.persist();
13083
+ }
13084
+ async removeItem(key) {
13085
+ await this.ensureLoaded();
13086
+ const existed = this.store.delete(key);
13087
+ if (existed) {
13088
+ await this.persist();
13089
+ }
13090
+ return existed;
13091
+ }
13092
+ async keys() {
13093
+ await this.ensureLoaded();
13094
+ return Array.from(this.store.keys());
13095
+ }
13096
+ };
13097
+
13098
+ // src/storage/WebStorageBackend.ts
13099
+ init_SecretStorageException();
13100
+ var WebStorageBackend = class {
13101
+ storage;
13102
+ prefix;
13103
+ constructor(storage, prefix = "knishio:") {
13104
+ if (storage) {
13105
+ this.storage = storage;
13106
+ } else if (typeof globalThis !== "undefined" && globalThis.localStorage) {
13107
+ this.storage = globalThis.localStorage;
13108
+ } else {
13109
+ throw exports.SecretStorageException.unavailable(
13110
+ "web-storage",
13111
+ "WebStorageBackend requires a Storage object or global localStorage"
13112
+ );
13113
+ }
13114
+ this.prefix = prefix;
13115
+ }
13116
+ getItem(key) {
13117
+ return this.storage.getItem(key);
13118
+ }
13119
+ setItem(key, value) {
13120
+ this.storage.setItem(key, value);
13121
+ }
13122
+ removeItem(key) {
13123
+ const existed = this.storage.getItem(key) !== null;
13124
+ this.storage.removeItem(key);
13125
+ return existed;
13126
+ }
13127
+ keys() {
13128
+ const result = [];
13129
+ const len = this.storage.length;
13130
+ for (let i = 0; i < len; i++) {
13131
+ const k = this.storage.key(i);
13132
+ if (k !== null) {
13133
+ if (!this.prefix || k.startsWith(this.prefix)) {
13134
+ result.push(k);
13135
+ }
13136
+ }
13137
+ }
13138
+ return result;
13139
+ }
13140
+ };
13141
+
13142
+ // src/storage/WebAuthnPrfSecretStorageProvider.ts
13143
+ init_SecretStorageException();
13144
+ var PRF_SALT_LABEL = "knishio:secret-storage:webauthn-prf:v1";
13145
+ var KEK_INFO = "knishio:secret-storage:kek:v1";
13146
+ var KEY_PREFIX2 = SECRET_KEY_PREFIX;
13147
+ var GCM_IV_LENGTH2 = 12;
13148
+ var textEncoder3 = new TextEncoder();
13149
+ var textDecoder2 = new TextDecoder();
13150
+ function base64UrlEncode(bytes) {
13151
+ return uint8ArrayToBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
13152
+ }
13153
+ function base64UrlDecode(str) {
13154
+ let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
13155
+ while (base64.length % 4 !== 0) {
13156
+ base64 += "=";
13157
+ }
13158
+ return base64ToUint8Array(base64);
13159
+ }
13160
+ function toUint8Array(buf) {
13161
+ if (buf instanceof Uint8Array) {
13162
+ return buf;
13163
+ }
13164
+ if (ArrayBuffer.isView(buf)) {
13165
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
13166
+ }
13167
+ return new Uint8Array(buf);
13168
+ }
13169
+ async function computePrfSalt() {
13170
+ const hash = await globalThis.crypto.subtle.digest("SHA-256", textEncoder3.encode(PRF_SALT_LABEL));
13171
+ return new Uint8Array(hash);
13172
+ }
13173
+ async function deriveKekFromPrf(prfOutput, prfSalt) {
13174
+ const hkdfKey = await globalThis.crypto.subtle.importKey(
13175
+ "raw",
13176
+ prfOutput,
13177
+ "HKDF",
13178
+ false,
13179
+ ["deriveKey"]
13180
+ );
13181
+ return await globalThis.crypto.subtle.deriveKey(
13182
+ {
13183
+ name: "HKDF",
13184
+ hash: "SHA-256",
13185
+ salt: prfSalt,
13186
+ info: textEncoder3.encode(KEK_INFO)
13187
+ },
13188
+ hkdfKey,
13189
+ { name: "AES-GCM", length: 256 },
13190
+ false,
13191
+ ["encrypt", "decrypt"]
13192
+ );
13193
+ }
13194
+ var WebAuthnPrfSecretStorageProvider = class {
13195
+ providerType = "webauthn-prf";
13196
+ backend;
13197
+ rp;
13198
+ user;
13199
+ credentialsContainer;
13200
+ alias;
13201
+ cachedPassphrase;
13202
+ constructor(options) {
13203
+ this.backend = options.backend;
13204
+ this.rp = options.rp;
13205
+ this.user = options.user;
13206
+ this.credentialsContainer = options.credentials;
13207
+ this.alias = options.alias ?? "default";
13208
+ }
13209
+ get credentials() {
13210
+ if (this.credentialsContainer) {
13211
+ return this.credentialsContainer;
13212
+ }
13213
+ if (typeof globalThis.navigator !== "undefined" && globalThis.navigator.credentials) {
13214
+ return globalThis.navigator.credentials;
13215
+ }
13216
+ throw exports.SecretStorageException.unavailable(
13217
+ this.providerType,
13218
+ "WebAuthn credentials container is not available"
13219
+ );
13220
+ }
13221
+ get recordKey() {
13222
+ return `knishio:webauthn-prf:${this.alias}`;
13223
+ }
13224
+ isHardwareBacked() {
13225
+ return false;
13226
+ }
13227
+ async isAvailable() {
13228
+ const hasWebCrypto = typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined";
13229
+ const hasCredentials = Boolean(
13230
+ this.credentialsContainer || typeof globalThis.navigator !== "undefined" && globalThis.navigator.credentials && typeof globalThis.PublicKeyCredential !== "undefined"
13231
+ );
13232
+ return hasWebCrypto && hasCredentials;
13233
+ }
13234
+ /**
13235
+ * Enroll a new passkey credential with PRF support and wrap a random device passphrase
13236
+ */
13237
+ async enroll() {
13238
+ const existing = await this.backend.getItem(this.recordKey);
13239
+ if (existing) {
13240
+ return;
13241
+ }
13242
+ if (!await this.isAvailable()) {
13243
+ throw exports.SecretStorageException.unavailable(this.providerType, "WebAuthn PRF is not available");
13244
+ }
13245
+ const challenge = new Uint8Array(32);
13246
+ globalThis.crypto.getRandomValues(challenge);
13247
+ const credential = await this.credentials.create({
13248
+ publicKey: {
13249
+ rp: this.rp,
13250
+ user: {
13251
+ id: this.user.id,
13252
+ name: this.user.name,
13253
+ displayName: this.user.displayName
13254
+ },
13255
+ challenge,
13256
+ pubKeyCredParams: [
13257
+ { type: "public-key", alg: -7 },
13258
+ { type: "public-key", alg: -257 }
13259
+ ],
13260
+ authenticatorSelection: {
13261
+ residentKey: "required",
13262
+ userVerification: "required"
13263
+ },
13264
+ extensions: {
13265
+ prf: {}
13266
+ }
13267
+ }
13268
+ });
13269
+ if (!credential) {
13270
+ throw exports.SecretStorageException.unavailable(this.providerType, "Authenticator creation returned null");
13271
+ }
13272
+ const extResults = credential.getClientExtensionResults?.();
13273
+ if (extResults?.prf?.enabled !== true) {
13274
+ throw exports.SecretStorageException.unavailable(
13275
+ this.providerType,
13276
+ "authenticator does not support the PRF extension"
13277
+ );
13278
+ }
13279
+ const credentialIdBytes = new Uint8Array(credential.rawId);
13280
+ const prfSalt = await computePrfSalt();
13281
+ const getChallenge = new Uint8Array(32);
13282
+ globalThis.crypto.getRandomValues(getChallenge);
13283
+ let assertion;
13284
+ try {
13285
+ assertion = await this.credentials.get({
13286
+ publicKey: {
13287
+ challenge: getChallenge,
13288
+ rpId: this.rp.id,
13289
+ allowCredentials: [
13290
+ {
13291
+ type: "public-key",
13292
+ id: credentialIdBytes
13293
+ }
13294
+ ],
13295
+ userVerification: "required",
13296
+ extensions: {
13297
+ prf: {
13298
+ eval: {
13299
+ first: prfSalt
13300
+ }
13301
+ }
13302
+ }
13303
+ }
13304
+ });
13305
+ } catch (err) {
13306
+ const isNotAllowed = err instanceof Error && err.name === "NotAllowedError" || err?.name === "NotAllowedError";
13307
+ if (isNotAllowed) {
13308
+ throw exports.SecretStorageException.unavailable(
13309
+ this.providerType,
13310
+ "authenticator refused or credential missing"
13311
+ );
13312
+ }
13313
+ throw err;
13314
+ }
13315
+ if (!assertion) {
13316
+ throw exports.SecretStorageException.unavailable(
13317
+ this.providerType,
13318
+ "authenticator refused or credential missing"
13319
+ );
13320
+ }
13321
+ const getExtResults = assertion?.getClientExtensionResults?.();
13322
+ const firstOutput = getExtResults?.prf?.results?.first;
13323
+ if (!firstOutput) {
13324
+ throw exports.SecretStorageException.unavailable(
13325
+ this.providerType,
13326
+ "authenticator returned no PRF result"
13327
+ );
13328
+ }
13329
+ const prfBytes = toUint8Array(firstOutput);
13330
+ const kek = await deriveKekFromPrf(prfBytes, prfSalt);
13331
+ const devicePassphraseBytes = new Uint8Array(32);
13332
+ globalThis.crypto.getRandomValues(devicePassphraseBytes);
13333
+ const devicePassphrase = uint8ArrayToBase64(devicePassphraseBytes);
13334
+ const iv = new Uint8Array(GCM_IV_LENGTH2);
13335
+ globalThis.crypto.getRandomValues(iv);
13336
+ const passphraseBytes = textEncoder3.encode(devicePassphrase);
13337
+ try {
13338
+ const encryptedBuffer = await globalThis.crypto.subtle.encrypt(
13339
+ {
13340
+ name: "AES-GCM",
13341
+ iv
13342
+ },
13343
+ kek,
13344
+ passphraseBytes
13345
+ );
13346
+ const record = {
13347
+ version: 1,
13348
+ credentialId: base64UrlEncode(credentialIdBytes),
13349
+ iv: uint8ArrayToBase64(iv),
13350
+ ciphertext: uint8ArrayToBase64(new Uint8Array(encryptedBuffer))
13351
+ };
13352
+ await this.backend.setItem(this.recordKey, JSON.stringify(record));
13353
+ this.cachedPassphrase = devicePassphrase;
13354
+ } finally {
13355
+ zeroizeBytes(passphraseBytes);
13356
+ zeroizeBytes(devicePassphraseBytes);
13357
+ }
13358
+ }
13359
+ /**
13360
+ * Unlock the device passphrase using the enrolled WebAuthn PRF credential
13361
+ */
13362
+ async unlock() {
13363
+ if (this.cachedPassphrase) {
13364
+ return this.cachedPassphrase;
13365
+ }
13366
+ const rawRecord = await this.backend.getItem(this.recordKey);
13367
+ if (!rawRecord) {
13368
+ throw exports.SecretStorageException.unavailable(
13369
+ this.providerType,
13370
+ "no enrolled credential; call enroll() first"
13371
+ );
13372
+ }
13373
+ let record;
13374
+ try {
13375
+ record = JSON.parse(rawRecord);
13376
+ } catch {
13377
+ throw exports.SecretStorageException.decryptionFailed("Corrupted PRF record format");
13378
+ }
13379
+ const credentialIdBytes = base64UrlDecode(record.credentialId);
13380
+ const prfSalt = await computePrfSalt();
13381
+ const challenge = new Uint8Array(32);
13382
+ globalThis.crypto.getRandomValues(challenge);
13383
+ let assertion;
13384
+ try {
13385
+ assertion = await this.credentials.get({
13386
+ publicKey: {
13387
+ challenge,
13388
+ rpId: this.rp.id,
13389
+ allowCredentials: [
13390
+ {
13391
+ type: "public-key",
13392
+ id: credentialIdBytes
13393
+ }
13394
+ ],
13395
+ userVerification: "required",
13396
+ extensions: {
13397
+ prf: {
13398
+ eval: {
13399
+ first: prfSalt
13400
+ }
13401
+ }
13402
+ }
13403
+ }
13404
+ });
13405
+ } catch (err) {
13406
+ const isNotAllowed = err instanceof Error && err.name === "NotAllowedError" || err?.name === "NotAllowedError";
13407
+ if (isNotAllowed) {
13408
+ throw exports.SecretStorageException.unavailable(
13409
+ this.providerType,
13410
+ "authenticator refused or credential missing"
13411
+ );
13412
+ }
13413
+ throw err;
13414
+ }
13415
+ if (!assertion) {
13416
+ throw exports.SecretStorageException.unavailable(
13417
+ this.providerType,
13418
+ "authenticator refused or credential missing"
13419
+ );
13420
+ }
13421
+ const extResults = assertion?.getClientExtensionResults?.();
13422
+ const firstOutput = extResults?.prf?.results?.first;
13423
+ if (!firstOutput) {
13424
+ throw exports.SecretStorageException.unavailable(
13425
+ this.providerType,
13426
+ "authenticator returned no PRF result"
13427
+ );
13428
+ }
13429
+ const prfBytes = toUint8Array(firstOutput);
13430
+ const kek = await deriveKekFromPrf(prfBytes, prfSalt);
13431
+ const iv = base64ToUint8Array(record.iv);
13432
+ const ciphertext = base64ToUint8Array(record.ciphertext);
13433
+ try {
13434
+ const decryptedBuffer = await globalThis.crypto.subtle.decrypt(
13435
+ {
13436
+ name: "AES-GCM",
13437
+ iv
13438
+ },
13439
+ kek,
13440
+ ciphertext
13441
+ );
13442
+ const decryptedBytes = new Uint8Array(decryptedBuffer);
13443
+ try {
13444
+ this.cachedPassphrase = textDecoder2.decode(decryptedBytes);
13445
+ return this.cachedPassphrase;
13446
+ } finally {
13447
+ zeroizeBytes(decryptedBytes);
13448
+ }
13449
+ } catch {
13450
+ throw exports.SecretStorageException.decryptionFailed(
13451
+ "wrapped device passphrase failed authentication under the enrolled credential"
13452
+ );
13453
+ }
13454
+ }
13455
+ /**
13456
+ * Lock the provider by clearing cached passphrase material
13457
+ */
13458
+ lock() {
13459
+ this.cachedPassphrase = void 0;
13460
+ }
13461
+ /**
13462
+ * Unenroll the current credential, removing the stored PRF record and clearing cached passphrase
13463
+ */
13464
+ async unenroll() {
13465
+ this.lock();
13466
+ await this.backend.removeItem(this.recordKey);
13467
+ }
13468
+ async storeSecret(bundleHash, secret, options) {
13469
+ if (!bundleHash) {
13470
+ throw new exports.SecretStorageException("Bundle hash cannot be empty");
13471
+ }
13472
+ if (!secret) {
13473
+ throw new exports.SecretStorageException("Secret cannot be empty");
13474
+ }
13475
+ if (options?.passphrase) {
13476
+ throw new exports.SecretStorageException(
13477
+ "WebAuthnPrfSecretStorageProvider derives its passphrase from the authenticator; options.passphrase is not accepted"
13478
+ );
13479
+ }
13480
+ if (!options?.recoveryPassphrase && !options?.allowUnrecoverable) {
13481
+ throw exports.SecretStorageException.validationError(
13482
+ "Recovery passphrase required for non-exportable hardware key unless allowUnrecoverable is true"
13483
+ );
13484
+ }
13485
+ const passphrase = await this.unlock();
13486
+ const metadata = {
13487
+ bundleHash,
13488
+ label: options?.label,
13489
+ createdAt: Date.now(),
13490
+ hardwareBacked: false,
13491
+ providerType: this.providerType
13492
+ };
13493
+ const payload = await sealEnvelope(secret, passphrase, metadata);
13494
+ await this.backend.setItem(`${KEY_PREFIX2}${bundleHash}`, JSON.stringify(payload));
13495
+ if (options?.recoveryPassphrase) {
13496
+ const recoveryMetadata = {
13497
+ bundleHash,
13498
+ label: options?.label,
13499
+ createdAt: Date.now(),
13500
+ hardwareBacked: false,
13501
+ providerType: "webcrypto-aes-gcm"
13502
+ };
13503
+ const recoveryPayload = await sealEnvelope(secret, options.recoveryPassphrase, recoveryMetadata);
13504
+ await this.backend.setItem(`${RECOVERY_KEY_PREFIX}${bundleHash}`, JSON.stringify(recoveryPayload));
13505
+ }
13506
+ }
13507
+ async retrieveSecret(bundleHash, options) {
13508
+ if (options?.passphrase) {
13509
+ throw new exports.SecretStorageException(
13510
+ "WebAuthnPrfSecretStorageProvider derives its passphrase from the authenticator; options.passphrase is not accepted"
13511
+ );
13512
+ }
13513
+ const raw = await this.backend.getItem(`${KEY_PREFIX2}${bundleHash}`);
13514
+ if (!raw) {
13515
+ return null;
13516
+ }
13517
+ let payload;
13518
+ try {
13519
+ payload = JSON.parse(raw);
13520
+ } catch {
13521
+ throw exports.SecretStorageException.decryptionFailed("Corrupted payload format");
13522
+ }
13523
+ const passphrase = await this.unlock();
13524
+ try {
13525
+ const decryptedBytes = await openEnvelope(payload, passphrase);
13526
+ try {
13527
+ return textDecoder2.decode(decryptedBytes);
13528
+ } finally {
13529
+ zeroizeBytes(decryptedBytes);
13530
+ }
13531
+ } catch (err) {
13532
+ if (err instanceof exports.SecretStorageException) {
13533
+ throw err;
13534
+ }
13535
+ const msg = err instanceof Error ? err.message : String(err);
13536
+ throw exports.SecretStorageException.decryptionFailed(msg);
13537
+ }
13538
+ }
13539
+ async withSecret(bundleHash, fn, options) {
13540
+ if (options?.passphrase) {
13541
+ throw new exports.SecretStorageException(
13542
+ "WebAuthnPrfSecretStorageProvider derives its passphrase from the authenticator; options.passphrase is not accepted"
13543
+ );
13544
+ }
13545
+ const raw = await this.backend.getItem(`${KEY_PREFIX2}${bundleHash}`);
13546
+ if (!raw) {
13547
+ throw exports.SecretStorageException.notFound(bundleHash);
13548
+ }
13549
+ let payload;
13550
+ try {
13551
+ payload = JSON.parse(raw);
13552
+ } catch {
13553
+ throw exports.SecretStorageException.decryptionFailed("Corrupted payload format");
13554
+ }
13555
+ const passphrase = await this.unlock();
13556
+ try {
13557
+ const decryptedBytes = await openEnvelope(payload, passphrase);
13558
+ return await withSecureBytes(decryptedBytes, async (bytes) => {
13559
+ const secretString = textDecoder2.decode(bytes);
13560
+ return await fn(secretString);
13561
+ });
13562
+ } catch (err) {
13563
+ if (err instanceof exports.SecretStorageException) {
13564
+ throw err;
13565
+ }
13566
+ const msg = err instanceof Error ? err.message : String(err);
13567
+ throw exports.SecretStorageException.decryptionFailed(msg);
13568
+ }
13569
+ }
13570
+ async deleteSecret(bundleHash) {
13571
+ const key = `${KEY_PREFIX2}${bundleHash}`;
13572
+ const recoveryKey = `${RECOVERY_KEY_PREFIX}${bundleHash}`;
13573
+ const result = await this.backend.removeItem(key);
13574
+ await this.backend.removeItem(recoveryKey);
13575
+ return result !== false;
13576
+ }
13577
+ async hasSecret(bundleHash) {
13578
+ const raw = await this.backend.getItem(`${KEY_PREFIX2}${bundleHash}`);
13579
+ return raw !== null;
13580
+ }
13581
+ async listSecrets() {
13582
+ const keys = await this.backend.keys();
13583
+ const matchingKeys = keys.filter((k) => k.startsWith(KEY_PREFIX2) && !k.startsWith(RECOVERY_KEY_PREFIX));
13584
+ const results = [];
13585
+ for (const key of matchingKeys) {
13586
+ const raw = await this.backend.getItem(key);
13587
+ if (raw) {
13588
+ try {
13589
+ const payload = JSON.parse(raw);
13590
+ if (payload.metadata) {
13591
+ results.push(payload.metadata);
13592
+ }
13593
+ } catch {
13594
+ }
13595
+ }
13596
+ }
13597
+ return results;
13598
+ }
13599
+ /**
13600
+ * Recover a secret using its recovery envelope and re-enroll it under a fresh WebAuthn PRF credential
13601
+ */
13602
+ async recoverSecret(bundleHash, recoveryPassphrase, options) {
13603
+ if (!bundleHash) {
13604
+ throw new exports.SecretStorageException("Bundle hash cannot be empty");
13605
+ }
13606
+ if (!recoveryPassphrase) {
13607
+ throw new exports.SecretStorageException("Recovery passphrase cannot be empty");
13608
+ }
13609
+ const raw = await this.backend.getItem(`${RECOVERY_KEY_PREFIX}${bundleHash}`);
13610
+ if (!raw) {
13611
+ throw exports.SecretStorageException.notFound(bundleHash);
13612
+ }
13613
+ let payload;
13614
+ try {
13615
+ payload = JSON.parse(raw);
13616
+ } catch {
13617
+ throw exports.SecretStorageException.decryptionFailed("Corrupted recovery payload format");
13618
+ }
13619
+ let decryptedBytes;
13620
+ try {
13621
+ decryptedBytes = await openEnvelope(payload, recoveryPassphrase);
13622
+ } catch (err) {
13623
+ if (err instanceof exports.SecretStorageException) {
13624
+ throw err;
13625
+ }
13626
+ const msg = err instanceof Error ? err.message : String(err);
13627
+ throw exports.SecretStorageException.decryptionFailed(msg);
13628
+ }
13629
+ let secretStr;
13630
+ try {
13631
+ secretStr = textDecoder2.decode(decryptedBytes);
13632
+ } finally {
13633
+ zeroizeBytes(decryptedBytes);
13634
+ }
13635
+ await this.storeSecret(bundleHash, secretStr, {
13636
+ ...options,
13637
+ recoveryPassphrase
13638
+ });
13639
+ }
13640
+ };
13641
+
13642
+ // src/storage/NonExtractableKeySecretStorageProvider.ts
13643
+ init_SecretStorageException();
13644
+ var KEY_PREFIX3 = SECRET_KEY_PREFIX;
13645
+ var GCM_IV_LENGTH3 = 12;
13646
+ var textEncoder4 = new TextEncoder();
13647
+ var textDecoder3 = new TextDecoder();
13648
+ var MemoryKeyStore = class {
13649
+ keys = /* @__PURE__ */ new Map();
13650
+ async get(name) {
13651
+ return this.keys.get(name);
13652
+ }
13653
+ async put(name, key) {
13654
+ this.keys.set(name, key);
13655
+ }
13656
+ async delete(name) {
13657
+ return this.keys.delete(name);
13658
+ }
13659
+ };
13660
+ var IndexedDbKeyStore = class {
13661
+ dbName;
13662
+ storeName = "keys";
13663
+ constructor(dbName = "knishio-secret-storage") {
13664
+ this.dbName = dbName;
13665
+ }
13666
+ // Executor form intentionally retained for browser runtime compatibility with ES2022 / browsers without Promise.withResolvers polyfill
13667
+ async getDb() {
13668
+ if (typeof globalThis.indexedDB === "undefined") {
13669
+ throw exports.SecretStorageException.unavailable(
13670
+ "webcrypto-nonextractable",
13671
+ "IndexedDB is not available"
13672
+ );
13673
+ }
13674
+ return new Promise((resolve, reject) => {
13675
+ const request = globalThis.indexedDB.open(this.dbName, 1);
13676
+ request.onupgradeneeded = () => {
13677
+ const db = request.result;
13678
+ if (!db.objectStoreNames.contains(this.storeName)) {
13679
+ db.createObjectStore(this.storeName);
13680
+ }
13681
+ };
13682
+ request.onsuccess = () => resolve(request.result);
13683
+ request.onerror = () => reject(request.error);
13684
+ });
13685
+ }
13686
+ async get(name) {
13687
+ const db = await this.getDb();
13688
+ return new Promise((resolve, reject) => {
13689
+ const tx = db.transaction(this.storeName, "readonly");
13690
+ const store = tx.objectStore(this.storeName);
13691
+ const request = store.get(name);
13692
+ request.onsuccess = () => resolve(request.result);
13693
+ request.onerror = () => reject(request.error);
13694
+ });
13695
+ }
13696
+ async put(name, key) {
13697
+ const db = await this.getDb();
13698
+ return new Promise((resolve, reject) => {
13699
+ const tx = db.transaction(this.storeName, "readwrite");
13700
+ const store = tx.objectStore(this.storeName);
13701
+ const request = store.put(key, name);
13702
+ request.onsuccess = () => resolve();
13703
+ request.onerror = () => reject(request.error);
13704
+ });
13705
+ }
13706
+ async delete(name) {
13707
+ const db = await this.getDb();
13708
+ return new Promise((resolve, reject) => {
13709
+ const tx = db.transaction(this.storeName, "readwrite");
13710
+ const store = tx.objectStore(this.storeName);
13711
+ const request = store.delete(name);
13712
+ request.onsuccess = () => resolve(true);
13713
+ request.onerror = () => reject(request.error);
13714
+ });
13715
+ }
13716
+ };
13717
+ var NonExtractableKeySecretStorageProvider = class {
13718
+ providerType = "webcrypto-nonextractable";
13719
+ backend;
13720
+ keyStore;
13721
+ alias;
13722
+ cachedPassphrase;
13723
+ constructor(options) {
13724
+ this.backend = options.backend;
13725
+ this.keyStore = options.keyStore ?? new IndexedDbKeyStore();
13726
+ this.alias = options.alias ?? "default";
13727
+ }
13728
+ get recordKey() {
13729
+ return `knishio:kek:webcrypto-nonextractable:${this.alias}`;
13730
+ }
13731
+ get kekStoreKey() {
13732
+ return `knishio:kek:${this.alias}`;
13733
+ }
13734
+ isHardwareBacked() {
13735
+ return false;
13736
+ }
13737
+ async isAvailable() {
13738
+ return typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined";
13739
+ }
13740
+ /**
13741
+ * Unlock or initialize the device passphrase using the non-extractable KEK
13742
+ */
13743
+ async unlock() {
13744
+ if (this.cachedPassphrase) {
13745
+ return this.cachedPassphrase;
13746
+ }
13747
+ if (!await this.isAvailable()) {
13748
+ throw exports.SecretStorageException.unavailable(
13749
+ this.providerType,
13750
+ "WebCrypto API is not available"
13751
+ );
13752
+ }
13753
+ const rawRecord = await this.backend.getItem(this.recordKey);
13754
+ if (!rawRecord) {
13755
+ let kek2 = await this.keyStore.get(this.kekStoreKey);
13756
+ if (!kek2) {
13757
+ kek2 = await globalThis.crypto.subtle.generateKey(
13758
+ { name: "AES-GCM", length: 256 },
13759
+ false,
13760
+ ["encrypt", "decrypt"]
13761
+ );
13762
+ await this.keyStore.put(this.kekStoreKey, kek2);
13763
+ }
13764
+ const devicePassphraseBytes = new Uint8Array(32);
13765
+ globalThis.crypto.getRandomValues(devicePassphraseBytes);
13766
+ const devicePassphrase = uint8ArrayToBase64(devicePassphraseBytes);
13767
+ const iv2 = new Uint8Array(GCM_IV_LENGTH3);
13768
+ globalThis.crypto.getRandomValues(iv2);
13769
+ const passphraseBytes = textEncoder4.encode(devicePassphrase);
13770
+ try {
13771
+ const encryptedBuffer = await globalThis.crypto.subtle.encrypt(
13772
+ {
13773
+ name: "AES-GCM",
13774
+ iv: iv2
13775
+ },
13776
+ kek2,
13777
+ passphraseBytes
13778
+ );
13779
+ const record2 = {
13780
+ version: 1,
13781
+ iv: uint8ArrayToBase64(iv2),
13782
+ ciphertext: uint8ArrayToBase64(new Uint8Array(encryptedBuffer))
13783
+ };
13784
+ await this.backend.setItem(this.recordKey, JSON.stringify(record2));
13785
+ this.cachedPassphrase = devicePassphrase;
13786
+ return devicePassphrase;
13787
+ } finally {
13788
+ zeroizeBytes(passphraseBytes);
13789
+ zeroizeBytes(devicePassphraseBytes);
13790
+ }
13791
+ }
13792
+ let record;
13793
+ try {
13794
+ record = JSON.parse(rawRecord);
13795
+ } catch {
13796
+ throw exports.SecretStorageException.decryptionFailed("Corrupted key record format");
13797
+ }
13798
+ const kek = await this.keyStore.get(this.kekStoreKey);
13799
+ if (!kek) {
13800
+ throw exports.SecretStorageException.unavailable(
13801
+ this.providerType,
13802
+ `no non-extractable key found for alias '${this.alias}'`
13803
+ );
13804
+ }
13805
+ const iv = base64ToUint8Array(record.iv);
13806
+ const ciphertext = base64ToUint8Array(record.ciphertext);
13807
+ try {
13808
+ const decryptedBuffer = await globalThis.crypto.subtle.decrypt(
13809
+ {
13810
+ name: "AES-GCM",
13811
+ iv
13812
+ },
13813
+ kek,
13814
+ ciphertext
13815
+ );
13816
+ const decryptedBytes = new Uint8Array(decryptedBuffer);
13817
+ try {
13818
+ this.cachedPassphrase = textDecoder3.decode(decryptedBytes);
13819
+ return this.cachedPassphrase;
13820
+ } finally {
13821
+ zeroizeBytes(decryptedBytes);
13822
+ }
13823
+ } catch {
13824
+ throw exports.SecretStorageException.decryptionFailed(
13825
+ "wrapped device passphrase failed authentication under non-extractable key"
13826
+ );
13827
+ }
13828
+ }
13829
+ /**
13830
+ * Lock the provider by clearing cached passphrase material
13831
+ */
13832
+ lock() {
13833
+ this.cachedPassphrase = void 0;
13834
+ }
13835
+ /**
13836
+ * Unenroll the non-extractable key, removing the stored wrapped record and KEK
13837
+ */
13838
+ async unenroll() {
13839
+ this.lock();
13840
+ await this.backend.removeItem(this.recordKey);
13841
+ await this.keyStore.delete(this.kekStoreKey);
13842
+ }
13843
+ async storeSecret(bundleHash, secret, options) {
13844
+ if (!bundleHash) {
13845
+ throw new exports.SecretStorageException("Bundle hash cannot be empty");
13846
+ }
13847
+ if (!secret) {
13848
+ throw new exports.SecretStorageException("Secret cannot be empty");
13849
+ }
13850
+ if (options?.passphrase) {
13851
+ throw new exports.SecretStorageException(
13852
+ "NonExtractableKeySecretStorageProvider derives its passphrase from the non-extractable device key; options.passphrase is not accepted"
13853
+ );
13854
+ }
13855
+ if (!options?.recoveryPassphrase && !options?.allowUnrecoverable) {
13856
+ throw exports.SecretStorageException.validationError(
13857
+ "Recovery passphrase required for non-exportable hardware key unless allowUnrecoverable is true"
13858
+ );
13859
+ }
13860
+ const passphrase = await this.unlock();
13861
+ const metadata = {
13862
+ bundleHash,
13863
+ label: options?.label,
13864
+ createdAt: Date.now(),
13865
+ hardwareBacked: false,
13866
+ providerType: this.providerType
13867
+ };
13868
+ const payload = await sealEnvelope(secret, passphrase, metadata);
13869
+ await this.backend.setItem(`${KEY_PREFIX3}${bundleHash}`, JSON.stringify(payload));
13870
+ if (options?.recoveryPassphrase) {
13871
+ const recoveryMetadata = {
13872
+ bundleHash,
13873
+ label: options?.label,
13874
+ createdAt: Date.now(),
13875
+ hardwareBacked: false,
13876
+ providerType: "webcrypto-aes-gcm"
13877
+ };
13878
+ const recoveryPayload = await sealEnvelope(secret, options.recoveryPassphrase, recoveryMetadata);
13879
+ await this.backend.setItem(`${RECOVERY_KEY_PREFIX}${bundleHash}`, JSON.stringify(recoveryPayload));
13880
+ }
13881
+ }
13882
+ async retrieveSecret(bundleHash, options) {
13883
+ if (options?.passphrase) {
13884
+ throw new exports.SecretStorageException(
13885
+ "NonExtractableKeySecretStorageProvider derives its passphrase from the non-extractable device key; options.passphrase is not accepted"
13886
+ );
13887
+ }
13888
+ const raw = await this.backend.getItem(`${KEY_PREFIX3}${bundleHash}`);
13889
+ if (!raw) {
13890
+ return null;
13891
+ }
13892
+ let payload;
13893
+ try {
13894
+ payload = JSON.parse(raw);
13895
+ } catch {
13896
+ throw exports.SecretStorageException.decryptionFailed("Corrupted payload format");
13897
+ }
13898
+ const passphrase = await this.unlock();
13899
+ try {
13900
+ const decryptedBytes = await openEnvelope(payload, passphrase);
13901
+ try {
13902
+ return textDecoder3.decode(decryptedBytes);
13903
+ } finally {
13904
+ zeroizeBytes(decryptedBytes);
13905
+ }
13906
+ } catch (err) {
13907
+ if (err instanceof exports.SecretStorageException) {
13908
+ throw err;
13909
+ }
13910
+ const msg = err instanceof Error ? err.message : String(err);
13911
+ throw exports.SecretStorageException.decryptionFailed(msg);
13912
+ }
13913
+ }
13914
+ async withSecret(bundleHash, fn, options) {
13915
+ if (options?.passphrase) {
13916
+ throw new exports.SecretStorageException(
13917
+ "NonExtractableKeySecretStorageProvider derives its passphrase from the non-extractable device key; options.passphrase is not accepted"
13918
+ );
13919
+ }
13920
+ const raw = await this.backend.getItem(`${KEY_PREFIX3}${bundleHash}`);
13921
+ if (!raw) {
13922
+ throw exports.SecretStorageException.notFound(bundleHash);
13923
+ }
13924
+ let payload;
13925
+ try {
13926
+ payload = JSON.parse(raw);
13927
+ } catch {
13928
+ throw exports.SecretStorageException.decryptionFailed("Corrupted payload format");
13929
+ }
13930
+ const passphrase = await this.unlock();
13931
+ try {
13932
+ const decryptedBytes = await openEnvelope(payload, passphrase);
13933
+ return await withSecureBytes(decryptedBytes, async (bytes) => {
13934
+ const secretString = textDecoder3.decode(bytes);
13935
+ return await fn(secretString);
13936
+ });
13937
+ } catch (err) {
13938
+ if (err instanceof exports.SecretStorageException) {
13939
+ throw err;
13940
+ }
13941
+ const msg = err instanceof Error ? err.message : String(err);
13942
+ throw exports.SecretStorageException.decryptionFailed(msg);
13943
+ }
13944
+ }
13945
+ async deleteSecret(bundleHash) {
13946
+ const key = `${KEY_PREFIX3}${bundleHash}`;
13947
+ const recoveryKey = `${RECOVERY_KEY_PREFIX}${bundleHash}`;
13948
+ const result = await this.backend.removeItem(key);
13949
+ await this.backend.removeItem(recoveryKey);
13950
+ return result !== false;
13951
+ }
13952
+ async hasSecret(bundleHash) {
13953
+ const raw = await this.backend.getItem(`${KEY_PREFIX3}${bundleHash}`);
13954
+ return raw !== null;
13955
+ }
13956
+ async listSecrets() {
13957
+ const keys = await this.backend.keys();
13958
+ const matchingKeys = keys.filter((k) => k.startsWith(KEY_PREFIX3) && !k.startsWith(RECOVERY_KEY_PREFIX));
13959
+ const results = [];
13960
+ for (const key of matchingKeys) {
13961
+ const raw = await this.backend.getItem(key);
13962
+ if (raw) {
13963
+ try {
13964
+ const payload = JSON.parse(raw);
13965
+ if (payload.metadata) {
13966
+ results.push(payload.metadata);
13967
+ }
13968
+ } catch {
13969
+ }
13970
+ }
13971
+ }
13972
+ return results;
13973
+ }
13974
+ /**
13975
+ * Recover a secret using its recovery envelope and re-enroll it under a fresh non-extractable KEK
13976
+ */
13977
+ async recoverSecret(bundleHash, recoveryPassphrase, options) {
13978
+ if (!bundleHash) {
13979
+ throw new exports.SecretStorageException("Bundle hash cannot be empty");
13980
+ }
13981
+ if (!recoveryPassphrase) {
13982
+ throw new exports.SecretStorageException("Recovery passphrase cannot be empty");
13983
+ }
13984
+ const raw = await this.backend.getItem(`${RECOVERY_KEY_PREFIX}${bundleHash}`);
13985
+ if (!raw) {
13986
+ throw exports.SecretStorageException.notFound(bundleHash);
13987
+ }
13988
+ let payload;
13989
+ try {
13990
+ payload = JSON.parse(raw);
13991
+ } catch {
13992
+ throw exports.SecretStorageException.decryptionFailed("Corrupted recovery payload format");
13993
+ }
13994
+ let decryptedBytes;
13995
+ try {
13996
+ decryptedBytes = await openEnvelope(payload, recoveryPassphrase);
13997
+ } catch (err) {
13998
+ if (err instanceof exports.SecretStorageException) {
13999
+ throw err;
14000
+ }
14001
+ const msg = err instanceof Error ? err.message : String(err);
14002
+ throw exports.SecretStorageException.decryptionFailed(msg);
14003
+ }
14004
+ let secretStr;
14005
+ try {
14006
+ secretStr = textDecoder3.decode(decryptedBytes);
14007
+ } finally {
14008
+ zeroizeBytes(decryptedBytes);
14009
+ }
14010
+ await this.storeSecret(bundleHash, secretStr, {
14011
+ ...options,
14012
+ recoveryPassphrase
14013
+ });
14014
+ }
12687
14015
  };
12688
14016
 
12689
14017
  // src/storage/index.ts
@@ -12694,15 +14022,14 @@ function createDefaultSecretStorage(options = {}) {
12694
14022
  if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined") {
12695
14023
  return new WebCryptoSecretStorageProvider({
12696
14024
  backend: options.backend,
12697
- defaultPassphrase: options.defaultPassphrase,
12698
- hardwareBacked: options.hardwareBacked
14025
+ defaultPassphrase: options.defaultPassphrase
12699
14026
  });
12700
14027
  }
12701
14028
  return new MemorySecretStorageProvider();
12702
14029
  }
12703
14030
 
12704
14031
  // src/index.ts
12705
- var SDK_VERSION = "0.9.8";
14032
+ var SDK_VERSION = "1.1.0";
12706
14033
  var SDK_NAME = "KnishIO-Client-TS";
12707
14034
  var COMPATIBLE_SERVER_VERSIONS = [4, 5];
12708
14035
  var SDK_INFO = {
@@ -12711,7 +14038,7 @@ var SDK_INFO = {
12711
14038
  description: "TypeScript SDK for Knish.IO post-blockchain distributed ledger",
12712
14039
  compatibleServerVersions: COMPATIBLE_SERVER_VERSIONS,
12713
14040
  features: [
12714
- "Post-quantum cryptography (XMSS, ML-KEM768)",
14041
+ "Post-quantum cryptography (XMSS, ML-KEM-1024)",
12715
14042
  "Cross-platform compatibility",
12716
14043
  "Type-safe APIs",
12717
14044
  "DAG-based transaction processing",
@@ -12800,9 +14127,12 @@ exports.CRYPTO_CONSTANTS = CRYPTO_CONSTANTS;
12800
14127
  exports.CheckMolecule = CheckMolecule;
12801
14128
  exports.DevUtils = DevUtils;
12802
14129
  exports.EXTENDED_COMPATIBILITY_TEST_VECTORS = EXTENDED_COMPATIBILITY_TEST_VECTORS;
14130
+ exports.FileStorageBackend = FileStorageBackend;
12803
14131
  exports.GraphQLClient = GraphQLClient;
14132
+ exports.IndexedDbKeyStore = IndexedDbKeyStore;
12804
14133
  exports.KnishIO = KnishIO;
12805
14134
  exports.KnishIOClient = KnishIOClient;
14135
+ exports.MemoryKeyStore = MemoryKeyStore;
12806
14136
  exports.MemorySecretStorageProvider = MemorySecretStorageProvider;
12807
14137
  exports.MemoryStorageBackend = MemoryStorageBackend;
12808
14138
  exports.Meta = Meta;
@@ -12817,6 +14147,7 @@ exports.MutationProposeMolecule = MutationProposeMolecule;
12817
14147
  exports.MutationRequestAuthorization = MutationRequestAuthorization;
12818
14148
  exports.MutationRequestTokens = MutationRequestTokens;
12819
14149
  exports.MutationTransferTokens = MutationTransferTokens;
14150
+ exports.NonExtractableKeySecretStorageProvider = NonExtractableKeySecretStorageProvider;
12820
14151
  exports.PolicyMeta = PolicyMeta;
12821
14152
  exports.Query = Query;
12822
14153
  exports.QueryAtom = QueryAtom;
@@ -12828,6 +14159,7 @@ exports.QueryMetaType = QueryMetaType;
12828
14159
  exports.QueryMetaTypeViaAtom = QueryMetaTypeViaAtom;
12829
14160
  exports.QueryWalletBundle = QueryWalletBundle;
12830
14161
  exports.QueryWalletList = QueryWalletList;
14162
+ exports.RECOVERY_KEY_PREFIX = RECOVERY_KEY_PREFIX;
12831
14163
  exports.ResponseAppendRequest = ResponseAppendRequest;
12832
14164
  exports.ResponseAtom = ResponseAtom;
12833
14165
  exports.ResponseBalance = ResponseBalance;
@@ -12848,9 +14180,12 @@ exports.ResponseWalletList = ResponseWalletList;
12848
14180
  exports.SDK_INFO = SDK_INFO;
12849
14181
  exports.SDK_NAME = SDK_NAME;
12850
14182
  exports.SDK_VERSION = SDK_VERSION;
14183
+ exports.SECRET_KEY_PREFIX = SECRET_KEY_PREFIX;
12851
14184
  exports.TokenUnit = TokenUnit;
12852
14185
  exports.Wallet = Wallet;
14186
+ exports.WebAuthnPrfSecretStorageProvider = WebAuthnPrfSecretStorageProvider;
12853
14187
  exports.WebCryptoSecretStorageProvider = WebCryptoSecretStorageProvider;
14188
+ exports.WebStorageBackend = WebStorageBackend;
12854
14189
  exports.base64ToHex = base64ToHex;
12855
14190
  exports.bufferToHexString = bufferToHexString;
12856
14191
  exports.capitalize = capitalize;
@@ -12889,9 +14224,11 @@ exports.isNumeric = isNumeric;
12889
14224
  exports.isPosition = isPosition2;
12890
14225
  exports.isWalletAddress = isWalletAddress2;
12891
14226
  exports.normalizeMolecularHash = normalizeMolecularHash;
14227
+ exports.openEnvelope = openEnvelope;
12892
14228
  exports.randomString = randomString;
12893
14229
  exports.runCompatibilityTests = runCompatibilityTests;
12894
14230
  exports.runExtendedCompatibilityTests = runExtendedCompatibilityTests;
14231
+ exports.sealEnvelope = sealEnvelope;
12895
14232
  exports.shake256 = shake256;
12896
14233
  exports.toCamelCase = toCamelCase;
12897
14234
  exports.toSnakeCase = toSnakeCase;