@byok-sdk/core 0.30.0 → 0.30.2

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.
@@ -10,11 +10,13 @@
10
10
  export { SEALED_CATEGORIES, SEALED_CONTENT_SUITE, SEALED_CONTENT_VERSION, SEALED_DIRECTIONS, SealedContentHeaderV1Schema, openSealedContent, sealContent, } from './sealed-content';
11
11
  export type { OpenSealedContentInput, SealContentInput, SealedBinding, SealedCategory, SealedContent, SealedContentHeaderV1, SealedContentOpenFailure, SealedContentOpenResult, SealedDirection, SealedScopePolicy, } from './sealed-content';
12
12
  export { deriveScopeKeyId, generateScopeKey, openScopeKeyPackage, sealScopeKeyPackage } from './scope-key';
13
- export type { OpenScopeKeyPackageInput, ScopeKeyPackageOpenResult, ScopeKeyPackageV1, SealScopeKeyPackageInput, } from './scope-key';
13
+ export type { OpenScopeKeyPackageInput, ScopeKeyPackage, ScopeKeyPackageOpenResult, ScopeKeyPackageV1, ScopeKeyPackageV2, SealScopeKeyPackageInput, } from './scope-key';
14
14
  export { MEMBER_CHAIN_OPS, deriveMemberId, memberChainEntryHash, signMemberChainEntry, verifyMemberChainEntry, } from './member-chain';
15
15
  export type { MemberChainEntryV1, MemberChainEntryVerifyResult, MemberChainOp, UnsignedMemberChainEntryV1, } from './member-chain';
16
16
  export { MEMBER_KEY_BUNDLE_VERSION, createMemberKeyBundle, parseMemberKeyBundle, verifyMemberKem } from './member-bundle';
17
17
  export type { CreateMemberKeyBundleInput, MemberKeyBundleV1 } from './member-bundle';
18
+ export { beginMemberKemRotation, finishMemberKemRotation, generateMemberKeyRecord, openMemberKeyRecord, } from './member-key-record';
19
+ export type { MemberKemKey, MemberKeyBinding, MemberKeyStore, MemberKeys } from './member-key-record';
18
20
  export { MEMBER_FINGERPRINT_DOMAIN, memberApprovalCode, memberFingerprint } from './member-fingerprint';
19
21
  export type { MemberFingerprintInput } from './member-fingerprint';
20
22
  export { CLIENT_PAIRING_MAC_DOMAIN, DEVICE_PAIRING_MAC_DOMAIN, PAIRING_VERIFICATION_SECRET_BYTES, computeClientPairingMac, computeDevicePairingMac, decodePairingVerificationSecret, encodePairingVerificationSecret, formatPairingInput, generatePairingVerificationSecret, parsePairingInput, verifyClientPairingMac, verifyDevicePairingMac, } from './pairing-verification';
@@ -679,6 +679,87 @@ async function verifyMemberChainEntry(entry, signerPublicJwk) {
679
679
  }
680
680
  return { ok: true, entry: parsed.data, hash: `sha256:${hex(await sha256(input))}` };
681
681
  }
682
+ var MEMBER_KEY_BUNDLE_VERSION = 1;
683
+ var MEMBER_KEM_DOMAIN_PREFIX = "byok-member-kem-v1\n";
684
+ var KemEpochSchema = z.number().int().positive().max(Number.MAX_SAFE_INTEGER);
685
+ var KemSigSchema = base64UrlBytes(64);
686
+ var MemberKeyBundleV1Schema = z.strictObject({
687
+ v: z.literal(MEMBER_KEY_BUNDLE_VERSION),
688
+ memberId: z.string().regex(MEMBER_ID_PATTERN),
689
+ signingPublicJwk: P256PublicJwkSchema,
690
+ kemPublicJwk: P256PublicJwkSchema,
691
+ kemEpoch: KemEpochSchema,
692
+ kemSig: KemSigSchema,
693
+ deviceId: z.string().min(1).optional()
694
+ });
695
+ var CreateInputSchema = z.object({
696
+ signingPublicJwk: P256PublicJwkSchema,
697
+ signingPrivateKey: z.custom(),
698
+ kemPublicJwk: P256PublicJwkSchema,
699
+ kemEpoch: KemEpochSchema,
700
+ deviceId: z.string().min(1).optional()
701
+ });
702
+ var KemSignerSchema = z.object({ memberId: z.string().regex(MEMBER_ID_PATTERN), signingPublicJwk: P256PublicJwkSchema });
703
+ var KemStatementSchema = z.object({ kemPublicJwk: P256PublicJwkSchema, kemEpoch: KemEpochSchema, kemSig: KemSigSchema });
704
+ function memberKemSigningInput(memberId, kemPublicJwk, kemEpoch) {
705
+ return concat2(
706
+ utf82(MEMBER_KEM_DOMAIN_PREFIX),
707
+ canonicalizeJsonBytes({ memberId, kemPublicKey: { x: kemPublicJwk.x, y: kemPublicJwk.y }, kemEpoch })
708
+ );
709
+ }
710
+ async function createMemberKeyBundle(input) {
711
+ const parsed = safeParseTotal(CreateInputSchema, input);
712
+ if (!parsed.success) throw invalidInput("Member key bundle input is invalid");
713
+ const { signingPublicJwk, signingPrivateKey, kemPublicJwk, kemEpoch, deviceId } = parsed.data;
714
+ const memberId = await deriveMemberId(signingPublicJwk);
715
+ if (!await isValidMemberKemKey(kemPublicJwk, signingPublicJwk)) throw invalidInput("Member KEM key is invalid");
716
+ const kemSig = await ecdsaSign(signingPublicJwk, signingPrivateKey, memberKemSigningInput(memberId, kemPublicJwk, kemEpoch));
717
+ return {
718
+ v: MEMBER_KEY_BUNDLE_VERSION,
719
+ memberId,
720
+ signingPublicJwk,
721
+ kemPublicJwk,
722
+ kemEpoch,
723
+ kemSig,
724
+ ...deviceId === void 0 ? {} : { deviceId }
725
+ };
726
+ }
727
+ async function isValidMemberKemKey(kemPublicJwk, signingPublicJwk) {
728
+ if (kemPublicJwk.x === signingPublicJwk.x && kemPublicJwk.y === signingPublicJwk.y) return false;
729
+ try {
730
+ await p256PublicKeyBytes(kemPublicJwk);
731
+ return true;
732
+ } catch {
733
+ return false;
734
+ }
735
+ }
736
+ async function verifyMemberKem(member, kem) {
737
+ const signer = safeParseTotal(KemSignerSchema, member);
738
+ const statement = safeParseTotal(KemStatementSchema, kem);
739
+ if (!signer.success || !statement.success) return false;
740
+ const { memberId, signingPublicJwk } = signer.data;
741
+ const { kemPublicJwk, kemEpoch, kemSig } = statement.data;
742
+ if (!await isValidMemberKemKey(kemPublicJwk, signingPublicJwk)) return false;
743
+ try {
744
+ return await ecdsaVerify(signingPublicJwk, kemSig, memberKemSigningInput(memberId, kemPublicJwk, kemEpoch));
745
+ } catch {
746
+ return false;
747
+ }
748
+ }
749
+ async function parseMemberKeyBundle(value) {
750
+ const parsed = safeParseTotal(MemberKeyBundleV1Schema, value);
751
+ if (!parsed.success) return void 0;
752
+ const bundle = parsed.data;
753
+ try {
754
+ if (await deriveMemberId(bundle.signingPublicJwk) !== bundle.memberId) return void 0;
755
+ } catch {
756
+ return void 0;
757
+ }
758
+ if (!await verifyMemberKem(bundle, bundle)) return void 0;
759
+ return bundle;
760
+ }
761
+
762
+ // src/sealed-payload/scope-key.ts
682
763
  var SCOPE_KEY_BYTES = 32;
683
764
  var SCOPE_KEY_ID_PATTERN = /^[A-Za-z0-9_-]{22}$/u;
684
765
  var KEY_ID_MESSAGE = "byok-scope-key-id-v1";
@@ -688,6 +769,9 @@ var SEALED_SALT_BYTES = 32;
688
769
  var SCOPE_KEY_PACKAGE_VERSION = 1;
689
770
  var SCOPE_KEY_HPKE_INFO = "byok-sdk.scope-key.v1";
690
771
  var SCOPE_KEY_PACKAGE_DOMAIN_PREFIX = "byok-scope-key-package-v1\n";
772
+ var SCOPE_KEY_PACKAGE_V2 = 2;
773
+ var SCOPE_KEY_HPKE_INFO_V2 = "byok-sdk.scope-key.v2";
774
+ var SCOPE_KEY_PACKAGE_V2_DOMAIN_PREFIX = "byok-scope-key-package-v2\n";
691
775
  var P256_POINT_BYTES = 65;
692
776
  var AEAD_TAG_BYTES = 16;
693
777
  function assertScopeKey(scopeKey) {
@@ -722,49 +806,35 @@ var ScopeKeyPackageV1Schema = z.strictObject({
722
806
  ciphertext: base64UrlBytes(SCOPE_KEY_BYTES + AEAD_TAG_BYTES),
723
807
  sig: base64UrlBytes(64)
724
808
  });
725
- function packageAad(identity) {
726
- return canonicalizeJsonBytes({
727
- v: identity.v,
728
- tenantId: identity.tenantId,
729
- scopeId: identity.scopeId,
730
- epoch: identity.epoch,
731
- keyId: identity.keyId,
732
- recipient: identity.recipient,
733
- sender: identity.sender
734
- });
809
+ var ScopeKeyPackageV2Schema = z.strictObject({
810
+ ...ScopeKeyPackageV1Schema.shape,
811
+ v: z.literal(SCOPE_KEY_PACKAGE_V2),
812
+ recipientKemEpoch: KemEpochSchema
813
+ });
814
+ var ScopeKeyPackageSchema = z.union([ScopeKeyPackageV1Schema, ScopeKeyPackageV2Schema]);
815
+ function packageAad({ enc: _enc, ciphertext: _ciphertext, sig: _sig, ...identity }) {
816
+ return canonicalizeJsonBytes(identity);
735
817
  }
736
- function packageSigningInput(pkg) {
737
- return concat2(
738
- utf82(SCOPE_KEY_PACKAGE_DOMAIN_PREFIX),
739
- canonicalizeJsonBytes({
740
- v: pkg.v,
741
- tenantId: pkg.tenantId,
742
- scopeId: pkg.scopeId,
743
- epoch: pkg.epoch,
744
- keyId: pkg.keyId,
745
- recipient: pkg.recipient,
746
- sender: pkg.sender,
747
- enc: pkg.enc,
748
- ciphertext: pkg.ciphertext
749
- })
750
- );
818
+ function packageSigningInput({ sig: _sig, ...pkg }) {
819
+ return concat2(utf82(pkg.v === SCOPE_KEY_PACKAGE_V2 ? SCOPE_KEY_PACKAGE_V2_DOMAIN_PREFIX : SCOPE_KEY_PACKAGE_DOMAIN_PREFIX), canonicalizeJsonBytes(pkg));
751
820
  }
752
- var PackageIdentitySchema = ScopeKeyPackageV1Schema.omit({ enc: true, ciphertext: true, sig: true });
821
+ var PackageIdentitySchema = ScopeKeyPackageV2Schema.omit({ enc: true, ciphertext: true, sig: true });
753
822
  async function sealScopeKeyPackage(input) {
754
823
  const parsed = safeParseTotal(PackageIdentitySchema, {
755
- v: SCOPE_KEY_PACKAGE_VERSION,
824
+ v: SCOPE_KEY_PACKAGE_V2,
756
825
  tenantId: input.tenantId,
757
826
  scopeId: input.scopeId,
758
827
  epoch: input.epoch,
759
828
  keyId: await deriveScopeKeyId(input.scopeKey),
760
829
  recipient: input.recipient.memberId,
830
+ recipientKemEpoch: input.recipient.kemEpoch,
761
831
  sender: await deriveMemberId(input.sender.signingPublicJwk)
762
832
  });
763
833
  if (!parsed.success) throw invalidInput("Scope key package input is invalid");
764
834
  const identity = parsed.data;
765
835
  const { enc, ciphertext } = await hpkeSealBase({
766
836
  recipientPublicKey: await p256PublicKeyBytes(input.recipient.kemPublicJwk),
767
- info: utf82(SCOPE_KEY_HPKE_INFO),
837
+ info: utf82(SCOPE_KEY_HPKE_INFO_V2),
768
838
  aad: packageAad(identity),
769
839
  plaintext: input.scopeKey
770
840
  });
@@ -772,10 +842,10 @@ async function sealScopeKeyPackage(input) {
772
842
  return { ...unsigned, sig: await ecdsaSign(input.sender.signingPublicJwk, input.sender.signingPrivateKey, packageSigningInput(unsigned)) };
773
843
  }
774
844
  async function openScopeKeyPackage(input) {
775
- const parsed = safeParseTotal(ScopeKeyPackageV1Schema, input.package);
845
+ const parsed = safeParseTotal(ScopeKeyPackageSchema, input.package);
776
846
  if (!parsed.success) return { ok: false, reason: "key_package_invalid" };
777
847
  const pkg = parsed.data;
778
- if (pkg.recipient !== input.recipient.memberId || pkg.tenantId !== input.expected.tenantId || pkg.scopeId !== input.expected.scopeId || pkg.epoch !== input.expected.epoch || pkg.sender !== await deriveMemberId(input.senderSigningPublicJwk)) {
848
+ if (pkg.recipient !== input.recipient.memberId || pkg.v === SCOPE_KEY_PACKAGE_V2 && pkg.recipientKemEpoch !== input.recipient.kemEpoch || pkg.tenantId !== input.expected.tenantId || pkg.scopeId !== input.expected.scopeId || pkg.epoch !== input.expected.epoch || pkg.sender !== await deriveMemberId(input.senderSigningPublicJwk)) {
779
849
  return { ok: false, reason: "key_package_invalid" };
780
850
  }
781
851
  if (!await ecdsaVerify(input.senderSigningPublicJwk, pkg.sig, packageSigningInput(pkg))) {
@@ -787,7 +857,7 @@ async function openScopeKeyPackage(input) {
787
857
  recipientPrivateKey: input.recipient.kemPrivateKey,
788
858
  recipientPublicKey: await p256PublicKeyBytes(input.recipient.kemPublicJwk),
789
859
  enc: decodeBase64Url(pkg.enc, "enc"),
790
- info: utf82(SCOPE_KEY_HPKE_INFO),
860
+ info: utf82(pkg.v === SCOPE_KEY_PACKAGE_V2 ? SCOPE_KEY_HPKE_INFO_V2 : SCOPE_KEY_HPKE_INFO),
791
861
  aad: packageAad(pkg),
792
862
  ciphertext: decodeBase64Url(pkg.ciphertext, "ciphertext")
793
863
  });
@@ -1086,84 +1156,114 @@ async function openSealedContent(input) {
1086
1156
  frame.fill(0);
1087
1157
  }
1088
1158
  }
1089
- var MEMBER_KEY_BUNDLE_VERSION = 1;
1090
- var MEMBER_KEM_DOMAIN_PREFIX = "byok-member-kem-v1\n";
1091
- var KemEpochSchema = z.number().int().positive().max(Number.MAX_SAFE_INTEGER);
1092
- var KemSigSchema = base64UrlBytes(64);
1093
- var MemberKeyBundleV1Schema = z.strictObject({
1094
- v: z.literal(MEMBER_KEY_BUNDLE_VERSION),
1095
- memberId: z.string().regex(MEMBER_ID_PATTERN),
1096
- signingPublicJwk: P256PublicJwkSchema,
1097
- kemPublicJwk: P256PublicJwkSchema,
1098
- kemEpoch: KemEpochSchema,
1099
- kemSig: KemSigSchema,
1100
- deviceId: z.string().min(1).optional()
1101
- });
1102
- var CreateInputSchema = z.object({
1103
- signingPublicJwk: P256PublicJwkSchema,
1104
- signingPrivateKey: z.custom(),
1105
- kemPublicJwk: P256PublicJwkSchema,
1106
- kemEpoch: KemEpochSchema,
1107
- deviceId: z.string().min(1).optional()
1159
+ var MEMBER_KEY_RECORD_VERSION = 1;
1160
+ var PrivateJwkSchema = z.strictObject({
1161
+ kty: z.literal("EC"),
1162
+ crv: z.literal("P-256"),
1163
+ x: base64UrlBytes(32),
1164
+ y: base64UrlBytes(32),
1165
+ d: base64UrlBytes(32)
1108
1166
  });
1109
- var KemSignerSchema = z.object({ memberId: z.string().regex(MEMBER_ID_PATTERN), signingPublicJwk: P256PublicJwkSchema });
1110
- var KemStatementSchema = z.object({ kemPublicJwk: P256PublicJwkSchema, kemEpoch: KemEpochSchema, kemSig: KemSigSchema });
1111
- function memberKemSigningInput(memberId, kemPublicJwk, kemEpoch) {
1112
- return concat2(
1113
- utf82(MEMBER_KEM_DOMAIN_PREFIX),
1114
- canonicalizeJsonBytes({ memberId, kemPublicKey: { x: kemPublicJwk.x, y: kemPublicJwk.y }, kemEpoch })
1115
- );
1167
+ var KemEpochSchema2 = z.number().int().positive().max(Number.MAX_SAFE_INTEGER);
1168
+ var MemberKeyRecordV1Schema = z.strictObject({
1169
+ v: z.literal(MEMBER_KEY_RECORD_VERSION),
1170
+ tenantId: z.string().min(1),
1171
+ deviceId: z.string().min(1).optional(),
1172
+ signingJwk: PrivateJwkSchema,
1173
+ kemEpoch: KemEpochSchema2,
1174
+ kemJwk: PrivateJwkSchema,
1175
+ nextKem: z.strictObject({ epoch: KemEpochSchema2, jwk: PrivateJwkSchema }).optional()
1176
+ }).refine((record) => record.nextKem === void 0 || record.nextKem.epoch === record.kemEpoch + 1);
1177
+ var BindingSchema = z.object({ tenantId: z.string().min(1), deviceId: z.string().min(1).optional() });
1178
+ function keySubtle() {
1179
+ return webCryptoSubtle();
1116
1180
  }
1117
- async function createMemberKeyBundle(input) {
1118
- const parsed = safeParseTotal(CreateInputSchema, input);
1119
- if (!parsed.success) throw invalidInput("Member key bundle input is invalid");
1120
- const { signingPublicJwk, signingPrivateKey, kemPublicJwk, kemEpoch, deviceId } = parsed.data;
1121
- const memberId = await deriveMemberId(signingPublicJwk);
1122
- if (!await isValidMemberKemKey(kemPublicJwk, signingPublicJwk)) throw invalidInput("Member KEM key is invalid");
1123
- const kemSig = await ecdsaSign(signingPublicJwk, signingPrivateKey, memberKemSigningInput(memberId, kemPublicJwk, kemEpoch));
1124
- return {
1125
- v: MEMBER_KEY_BUNDLE_VERSION,
1126
- memberId,
1127
- signingPublicJwk,
1128
- kemPublicJwk,
1129
- kemEpoch,
1130
- kemSig,
1131
- ...deviceId === void 0 ? {} : { deviceId }
1132
- };
1181
+ var ECDSA_P2562 = { name: "ECDSA", namedCurve: "P-256" };
1182
+ var ECDSA_SHA2562 = { name: "ECDSA", hash: "SHA-256" };
1183
+ var ECDH_P256 = { name: "ECDH", namedCurve: "P-256" };
1184
+ async function generatePrivateJwk(algorithm, usages) {
1185
+ const pair = await keySubtle().generateKey(algorithm, true, usages);
1186
+ const { kty, crv, x, y, d } = await keySubtle().exportKey("jwk", pair.privateKey);
1187
+ return PrivateJwkSchema.parse({ kty, crv, x, y, d });
1188
+ }
1189
+ async function generateMemberKeyRecord(binding) {
1190
+ const parsed = safeParseTotal(BindingSchema, binding);
1191
+ if (!parsed.success) throw invalidInput("Member key binding is invalid");
1192
+ const { tenantId, deviceId } = parsed.data;
1193
+ return canonicalizeJson({
1194
+ v: MEMBER_KEY_RECORD_VERSION,
1195
+ tenantId,
1196
+ ...deviceId === void 0 ? {} : { deviceId },
1197
+ signingJwk: await generatePrivateJwk(ECDSA_P2562, ["sign", "verify"]),
1198
+ kemEpoch: 1,
1199
+ kemJwk: await generatePrivateJwk(ECDH_P256, ["deriveBits"])
1200
+ });
1133
1201
  }
1134
- async function isValidMemberKemKey(kemPublicJwk, signingPublicJwk) {
1135
- if (kemPublicJwk.x === signingPublicJwk.x && kemPublicJwk.y === signingPublicJwk.y) return false;
1202
+ function publicJwkOf(jwk) {
1203
+ return P256PublicJwkSchema.parse({ kty: jwk.kty, crv: jwk.crv, x: jwk.x, y: jwk.y });
1204
+ }
1205
+ function bytesEqual(left, right) {
1206
+ const a = new Uint8Array(left);
1207
+ const b = new Uint8Array(right);
1208
+ return a.length === b.length && a.every((byte, index) => byte === b[index]);
1209
+ }
1210
+ async function importSigningKey(jwk) {
1211
+ const subtle = keySubtle();
1212
+ const publicJwk = publicJwkOf(jwk);
1213
+ const privateKey = await subtle.importKey("jwk", { ...jwk, ext: false }, ECDSA_P2562, false, ["sign"]);
1214
+ const publicKey = await subtle.importKey("jwk", publicJwk, ECDSA_P2562, true, ["verify"]);
1215
+ const probe = randomBytes(32);
1216
+ if (!await subtle.verify(ECDSA_SHA2562, publicKey, await subtle.sign(ECDSA_SHA2562, privateKey, probe), probe)) {
1217
+ throw new TypeError("signing scalar does not match its public point");
1218
+ }
1219
+ return { publicJwk, privateKey };
1220
+ }
1221
+ async function importKemKey(jwk) {
1222
+ const subtle = keySubtle();
1223
+ const publicJwk = publicJwkOf(jwk);
1224
+ const privateKey = await subtle.importKey("jwk", { ...jwk, ext: false }, ECDH_P256, false, ["deriveBits"]);
1225
+ const publicKey = await subtle.importKey("jwk", publicJwk, ECDH_P256, true, []);
1226
+ const probe = await subtle.generateKey(ECDH_P256, false, ["deriveBits"]);
1227
+ const viaMember = await subtle.deriveBits({ name: "ECDH", public: probe.publicKey }, privateKey, 256);
1228
+ const viaProbe = await subtle.deriveBits({ name: "ECDH", public: publicKey }, probe.privateKey, 256);
1229
+ if (!bytesEqual(viaMember, viaProbe)) throw new TypeError("KEM scalar does not match its public point");
1230
+ return { publicJwk, privateKey };
1231
+ }
1232
+ async function openMemberKeyRecord(text) {
1233
+ const record = parseRecord(text);
1234
+ if (record === void 0) return void 0;
1136
1235
  try {
1137
- await p256PublicKeyBytes(kemPublicJwk);
1138
- return true;
1236
+ const signing = await importSigningKey(record.signingJwk);
1237
+ const kem = { epoch: record.kemEpoch, ...await importKemKey(record.kemJwk) };
1238
+ const binding = record.deviceId === void 0 ? { tenantId: record.tenantId } : { tenantId: record.tenantId, deviceId: record.deviceId };
1239
+ if (record.nextKem === void 0) return { binding, signing, kem };
1240
+ return { binding, signing, kem, nextKem: { epoch: record.nextKem.epoch, ...await importKemKey(record.nextKem.jwk) } };
1139
1241
  } catch {
1140
- return false;
1242
+ return void 0;
1141
1243
  }
1142
1244
  }
1143
- async function verifyMemberKem(member, kem) {
1144
- const signer = safeParseTotal(KemSignerSchema, member);
1145
- const statement = safeParseTotal(KemStatementSchema, kem);
1146
- if (!signer.success || !statement.success) return false;
1147
- const { memberId, signingPublicJwk } = signer.data;
1148
- const { kemPublicJwk, kemEpoch, kemSig } = statement.data;
1149
- if (!await isValidMemberKemKey(kemPublicJwk, signingPublicJwk)) return false;
1150
- try {
1151
- return await ecdsaVerify(signingPublicJwk, kemSig, memberKemSigningInput(memberId, kemPublicJwk, kemEpoch));
1152
- } catch {
1153
- return false;
1154
- }
1245
+ async function beginMemberKemRotation(text) {
1246
+ const record = parseRecord(text);
1247
+ if (record === void 0) throw invalidInput("Member key record is invalid");
1248
+ if (record.nextKem !== void 0) return text;
1249
+ return canonicalizeJson({ ...record, nextKem: { epoch: record.kemEpoch + 1, jwk: await generatePrivateJwk(ECDH_P256, ["deriveBits"]) } });
1155
1250
  }
1156
- async function parseMemberKeyBundle(value) {
1157
- const parsed = safeParseTotal(MemberKeyBundleV1Schema, value);
1158
- if (!parsed.success) return void 0;
1159
- const bundle = parsed.data;
1251
+ function finishMemberKemRotation(text, epoch) {
1252
+ const record = parseRecord(text);
1253
+ if (record === void 0) throw invalidInput("Member key record is invalid");
1254
+ if (record.nextKem === void 0) return record.kemEpoch === epoch ? text : void 0;
1255
+ if (record.nextKem.epoch !== epoch) return void 0;
1256
+ const { nextKem, ...rest } = record;
1257
+ return canonicalizeJson({ ...rest, kemEpoch: nextKem.epoch, kemJwk: nextKem.jwk });
1258
+ }
1259
+ function parseRecord(text) {
1260
+ if (typeof text !== "string") return void 0;
1160
1261
  try {
1161
- if (await deriveMemberId(bundle.signingPublicJwk) !== bundle.memberId) return void 0;
1262
+ const parsed = safeParseTotal(MemberKeyRecordV1Schema, JSON.parse(text));
1263
+ return parsed.success && canonicalizeJson(parsed.data) === text ? parsed.data : void 0;
1162
1264
  } catch {
1163
1265
  return void 0;
1164
1266
  }
1165
- if (!await verifyMemberKem(bundle, bundle)) return void 0;
1166
- return bundle;
1167
1267
  }
1168
1268
  var MEMBER_FINGERPRINT_DOMAIN = "byok-member-fingerprint-v1\n";
1169
1269
  var FINGERPRINT_GROUPS = 5;
@@ -1481,6 +1581,6 @@ async function verifyMemberChainFromAnchor(input) {
1481
1581
  return { ok: true, state: outcome.state, anchorSeq: anchor.seq };
1482
1582
  }
1483
1583
 
1484
- export { CLIENT_PAIRING_MAC_DOMAIN, DEVICE_PAIRING_MAC_DOMAIN, MEMBER_CHAIN_OPS, MEMBER_FINGERPRINT_DOMAIN, MEMBER_KEY_BUNDLE_VERSION, PAIRING_VERIFICATION_SECRET_BYTES, SEALED_CATEGORIES, SEALED_CONTENT_SUITE, SEALED_CONTENT_VERSION, SEALED_DIRECTIONS, SealedContentHeaderV1Schema, applyMemberChainEntries, computeClientPairingMac, computeDevicePairingMac, createMemberKeyBundle, decodePairingVerificationSecret, deriveMemberId, deriveScopeKeyId, emptyMemberChainState, encodePairingVerificationSecret, formatPairingInput, generatePairingVerificationSecret, generateScopeKey, memberApprovalCode, memberChainEntryHash, memberChainScopeRecipients, memberFingerprint, openScopeKeyPackage, openSealedContent, parseMemberKeyBundle, parsePairingInput, sealContent, sealScopeKeyPackage, signMemberChainEntry, verifyClientPairingMac, verifyDevicePairingMac, verifyMemberChainEntry, verifyMemberChainFromAnchor, verifyMemberKem };
1584
+ export { CLIENT_PAIRING_MAC_DOMAIN, DEVICE_PAIRING_MAC_DOMAIN, MEMBER_CHAIN_OPS, MEMBER_FINGERPRINT_DOMAIN, MEMBER_KEY_BUNDLE_VERSION, PAIRING_VERIFICATION_SECRET_BYTES, SEALED_CATEGORIES, SEALED_CONTENT_SUITE, SEALED_CONTENT_VERSION, SEALED_DIRECTIONS, SealedContentHeaderV1Schema, applyMemberChainEntries, beginMemberKemRotation, computeClientPairingMac, computeDevicePairingMac, createMemberKeyBundle, decodePairingVerificationSecret, deriveMemberId, deriveScopeKeyId, emptyMemberChainState, encodePairingVerificationSecret, finishMemberKemRotation, formatPairingInput, generateMemberKeyRecord, generatePairingVerificationSecret, generateScopeKey, memberApprovalCode, memberChainEntryHash, memberChainScopeRecipients, memberFingerprint, openMemberKeyRecord, openScopeKeyPackage, openSealedContent, parseMemberKeyBundle, parsePairingInput, sealContent, sealScopeKeyPackage, signMemberChainEntry, verifyClientPairingMac, verifyDevicePairingMac, verifyMemberChainEntry, verifyMemberChainFromAnchor, verifyMemberKem };
1485
1585
  //# sourceMappingURL=index.js.map
1486
1586
  //# sourceMappingURL=index.js.map