@byok-sdk/core 0.28.3 → 0.30.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.
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The member key bundle (#654 design §7.1, §7.2, E2b; owner decision 10a).
3
+ *
4
+ * A bundle is the public half of one member: its id, its ECDSA P-256 signing
5
+ * key, its P-256 KEM key, the KEM epoch, and `kemSig`. A device member also
6
+ * names its SDK device id. A client member does not.
7
+ *
8
+ * `kemSig` is the member's own signature over its KEM key and epoch. The
9
+ * fingerprint covers only the signing key, so a KEM rotation keeps the member
10
+ * id and the fingerprint. The self-signature binds the KEM key to the member:
11
+ * a Host that swaps the KEM key breaks the signature.
12
+ *
13
+ * An `add` chain entry carries the bundle. The pairing MACs bind it. In a
14
+ * later slice, the device registers it with device proof.
15
+ */
16
+ import { z } from 'zod';
17
+ import type { WebCryptoKey } from '../webcrypto';
18
+ import { type Bytes, type P256PublicJwk } from './primitives';
19
+ export declare const MEMBER_KEY_BUNDLE_VERSION = 1;
20
+ /** Prepended to the canonical KEM statement, so `kemSig` never verifies as another signed object. */
21
+ export declare const MEMBER_KEM_DOMAIN_PREFIX = "byok-member-kem-v1\n";
22
+ export interface MemberKeyBundleV1 {
23
+ readonly v: typeof MEMBER_KEY_BUNDLE_VERSION;
24
+ readonly memberId: string;
25
+ readonly signingPublicJwk: P256PublicJwk;
26
+ readonly kemPublicJwk: P256PublicJwk;
27
+ readonly kemEpoch: number;
28
+ /** ECDSA P-256 / SHA-256 by the signing key over the KEM statement, base64url of the 64-byte P1363 form. */
29
+ readonly kemSig: string;
30
+ /** The SDK device id of a device member. A client member has none. */
31
+ readonly deviceId?: string;
32
+ }
33
+ export declare const KemEpochSchema: z.ZodNumber;
34
+ export declare const KemSigSchema: z.ZodString;
35
+ /** `MEMBER_KEM_DOMAIN_PREFIX || canonicalJson({ memberId, kemPublicKey: { x, y }, kemEpoch })`. */
36
+ export declare function memberKemSigningInput(memberId: string, kemPublicJwk: P256PublicJwk, kemEpoch: number): Bytes;
37
+ export interface CreateMemberKeyBundleInput {
38
+ readonly signingPublicJwk: P256PublicJwk;
39
+ readonly signingPrivateKey: WebCryptoKey;
40
+ readonly kemPublicJwk: P256PublicJwk;
41
+ readonly kemEpoch: number;
42
+ readonly deviceId?: string;
43
+ }
44
+ /** Build a bundle: derive the member id and sign the KEM statement. Bad input throws `sealed_secret_invalid`. */
45
+ export declare function createMemberKeyBundle(input: CreateMemberKeyBundleInput): Promise<MemberKeyBundleV1>;
46
+ /** Both keys are points on P-256, and the KEM key is not the signing key. */
47
+ export declare function isValidMemberKemKey(kemPublicJwk: P256PublicJwk, signingPublicJwk: P256PublicJwk): Promise<boolean>;
48
+ /** The KEM key is valid for the member, and `kemSig` is the member's signature over it. Never throws. */
49
+ export declare function verifyMemberKem(member: {
50
+ readonly memberId: string;
51
+ readonly signingPublicJwk: P256PublicJwk;
52
+ }, kem: {
53
+ readonly kemPublicJwk: P256PublicJwk;
54
+ readonly kemEpoch: number;
55
+ readonly kemSig: string;
56
+ }): Promise<boolean>;
57
+ /**
58
+ * Parse a received bundle. It is valid when its shape is strict, `memberId`
59
+ * is the id of `signingPublicJwk` (which also checks that key is on the
60
+ * curve), the KEM key is a different point on the curve, and `kemSig`
61
+ * verifies. Returns `undefined` for anything else; it never throws for
62
+ * hostile input.
63
+ */
64
+ export declare function parseMemberKeyBundle(value: unknown): Promise<MemberKeyBundleV1 | undefined>;
@@ -0,0 +1,77 @@
1
+ import { type MemberKeyBundleV1 } from './member-bundle';
2
+ import { type P256PublicJwk } from './primitives';
3
+ import { type SealedCategory } from './sealed-content';
4
+ export interface MemberChainScope {
5
+ readonly categories: readonly SealedCategory[];
6
+ /** Member ids from the latest `scope-policy` entry, sorted. Some may be removed since. */
7
+ readonly members: readonly string[];
8
+ /** `seq` of that entry. */
9
+ readonly policySeq: number;
10
+ }
11
+ export interface MemberChainHead {
12
+ readonly seq: number;
13
+ readonly hash: string;
14
+ }
15
+ export interface MemberChainState {
16
+ readonly tenantId: string;
17
+ /** `null` before the genesis entry. */
18
+ readonly head: MemberChainHead | null;
19
+ /** Current members by member id, with their current KEM key. */
20
+ readonly members: ReadonlyMap<string, MemberKeyBundleV1>;
21
+ readonly removed: ReadonlySet<string>;
22
+ readonly scopes: ReadonlyMap<string, MemberChainScope>;
23
+ readonly shredded: ReadonlySet<string>;
24
+ }
25
+ export type MemberChainWalkFailure = 'member_chain_entry_invalid' | 'member_chain_link_invalid' | 'member_chain_signer_invalid' | 'member_chain_signature_invalid' | 'member_chain_subject_invalid';
26
+ export type MemberChainWalkResult = {
27
+ readonly ok: true;
28
+ readonly state: MemberChainState;
29
+ } | {
30
+ readonly ok: false;
31
+ readonly reason: MemberChainWalkFailure;
32
+ readonly index: number;
33
+ };
34
+ /** The state before the genesis entry of `tenantId`'s chain. */
35
+ export declare function emptyMemberChainState(tenantId: string): MemberChainState;
36
+ /** The latest policy's members that are still current: the members that get key packages for the scope. */
37
+ export declare function memberChainScopeRecipients(state: MemberChainState, scopeId: string): readonly MemberKeyBundleV1[];
38
+ /** Apply `entries` in order after `state`. Never throws for hostile entries. */
39
+ export declare function applyMemberChainEntries(state: MemberChainState, entries: readonly unknown[]): Promise<MemberChainWalkResult>;
40
+ export type MemberChainAnchorFailure = MemberChainWalkFailure | 'member_chain_anchor_missing' | 'member_chain_head_mismatch';
41
+ export type MemberChainAnchorResult = {
42
+ readonly ok: true;
43
+ readonly state: MemberChainState;
44
+ /** `seq` of the anchor entry: the `add` of this member, signed by the pinned approver. */
45
+ readonly anchorSeq: number;
46
+ } | {
47
+ readonly ok: false;
48
+ readonly reason: MemberChainAnchorFailure;
49
+ readonly index?: number;
50
+ };
51
+ export interface VerifyMemberChainFromAnchorInput {
52
+ readonly tenantId: string;
53
+ /** The whole chain, from the genesis entry (seq 1). */
54
+ readonly entries: readonly unknown[];
55
+ /** This member's own bundle, exactly as it was approved. */
56
+ readonly self: MemberKeyBundleV1;
57
+ /** The approver's signing key, pinned out of band: by the pv MAC or by the two-way fingerprint (10a). */
58
+ readonly approverSigningPublicJwk: P256PublicJwk;
59
+ /**
60
+ * A chain head the approver authenticated out of band, for example the
61
+ * head in the client pairing MAC. When given, it must be the anchor entry.
62
+ */
63
+ readonly authenticatedHead?: MemberChainHead;
64
+ }
65
+ /**
66
+ * Verify a chain as a member that joined after the genesis entry (11b):
67
+ *
68
+ * 1. The member pins the approver's signing key K out of band.
69
+ * 2. Entry E is an `add` whose `member` is byte-identical to `self` (KEM key
70
+ * included) and whose signer is `memberId(K)`, verified with K.
71
+ * 3. The walk from genesis reaches E, so E is on the chain path.
72
+ * 4. The entries after E verify forward by signature.
73
+ *
74
+ * The first E counts; `self` can be added only once. Never throws for
75
+ * hostile entries.
76
+ */
77
+ export declare function verifyMemberChainFromAnchor(input: VerifyMemberChainFromAnchorInput): Promise<MemberChainAnchorResult>;
@@ -0,0 +1,58 @@
1
+ import { type JsonObject } from '../attestation';
2
+ import type { WebCryptoKey } from '../webcrypto';
3
+ import { type Bytes, type P256PublicJwk } from './primitives';
4
+ /** Domain separation for {@link deriveMemberId}. */
5
+ export declare const MEMBER_ID_DOMAIN = "byok-member-id-v1\n";
6
+ /** `base64url(sha256(MEMBER_ID_DOMAIN || 0x04 || x || y))`: 43 characters, no padding. */
7
+ export declare const MEMBER_ID_PATTERN: RegExp;
8
+ export declare const MEMBER_CHAIN_VERSION = 1;
9
+ /** Prepended to the canonical entry bytes, so a chain signature never verifies as another signed object. */
10
+ export declare const MEMBER_CHAIN_DOMAIN_PREFIX = "byok-member-chain-v1\n";
11
+ export declare const MEMBER_CHAIN_OPS: readonly ['add', 'remove', 'rotate', 'scope-policy', 'shred'];
12
+ export type MemberChainOp = (typeof MEMBER_CHAIN_OPS)[number];
13
+ /** One entry before it is signed. `seq` starts at 1; the first entry, and only it, has `prev: null`. */
14
+ export interface UnsignedMemberChainEntryV1 {
15
+ readonly v: typeof MEMBER_CHAIN_VERSION;
16
+ readonly tenantId: string;
17
+ readonly seq: number;
18
+ /** `sha256:<hex>` of the previous entry's signing input. */
19
+ readonly prev: string | null;
20
+ readonly op: MemberChainOp;
21
+ readonly subject: JsonObject;
22
+ /** Member id of the signer. */
23
+ readonly signer: string;
24
+ }
25
+ export interface MemberChainEntryV1 extends UnsignedMemberChainEntryV1 {
26
+ /** ECDSA P-256 / SHA-256 over the signing input, base64url of the 64-byte P1363 form. */
27
+ readonly sig: string;
28
+ }
29
+ /** The member id of a signing public key. The key must be a point on P-256. */
30
+ export declare function deriveMemberId(signingPublicJwk: P256PublicJwk): Promise<string>;
31
+ /** `MEMBER_CHAIN_DOMAIN_PREFIX || canonicalJson(entry without sig)`. */
32
+ export declare function memberChainSigningInput(entry: UnsignedMemberChainEntryV1): Bytes;
33
+ /** `sha256:<hex>` of the signing input. The signature is not hashed, so the hash names the entry's content. */
34
+ export declare function memberChainEntryHash(entry: UnsignedMemberChainEntryV1): Promise<string>;
35
+ /**
36
+ * Sign one entry. The signer's public key is required so an entry is never
37
+ * signed under a member id that is not the signer's own. A private key that
38
+ * does not pair with that public key throws `sealed_secret_invalid`.
39
+ */
40
+ export declare function signMemberChainEntry(entry: UnsignedMemberChainEntryV1, signer: {
41
+ readonly publicJwk: P256PublicJwk;
42
+ readonly privateKey: WebCryptoKey;
43
+ }): Promise<MemberChainEntryV1>;
44
+ export type MemberChainEntryVerifyResult = {
45
+ readonly ok: true;
46
+ readonly entry: MemberChainEntryV1;
47
+ readonly hash: string;
48
+ } | {
49
+ readonly ok: false;
50
+ readonly reason: 'member_chain_entry_invalid' | 'member_chain_signature_invalid';
51
+ };
52
+ /**
53
+ * Verify one received entry against the signing key the verifier already
54
+ * pins for `entry.signer`. A malformed entry, or a key whose member id is not
55
+ * the entry's signer, is `member_chain_entry_invalid`; a bad signature is
56
+ * `member_chain_signature_invalid`. Nothing here throws for hostile input.
57
+ */
58
+ export declare function verifyMemberChainEntry(entry: unknown, signerPublicJwk: P256PublicJwk): Promise<MemberChainEntryVerifyResult>;
@@ -0,0 +1,14 @@
1
+ import { type P256PublicJwk } from './primitives';
2
+ export declare const MEMBER_FINGERPRINT_DOMAIN = "byok-member-fingerprint-v1\n";
3
+ export interface MemberFingerprintInput {
4
+ readonly tenantId: string;
5
+ readonly signingPublicJwk: P256PublicJwk;
6
+ }
7
+ /** The 25-digit fingerprint of one member, as 5 groups of 5 digits joined by single spaces. */
8
+ export declare function memberFingerprint(input: MemberFingerprintInput): Promise<string>;
9
+ /**
10
+ * The 50-digit two-way approval code of two members of one tenant: their
11
+ * fingerprints ordered by member id, as 10 groups of 5 digits joined by
12
+ * single spaces. The order of the arguments does not matter.
13
+ */
14
+ export declare function memberApprovalCode(tenantId: string, first: P256PublicJwk, second: P256PublicJwk): Promise<string>;
@@ -0,0 +1,51 @@
1
+ import type { MemberKeyBundleV1 } from './member-bundle';
2
+ import { type Bytes } from './primitives';
3
+ export declare const PAIRING_VERIFICATION_SECRET_BYTES = 16;
4
+ export declare const PAIRING_VERIFICATION_TEXT_LENGTH = 26;
5
+ /** 128 fresh CSPRNG bits. */
6
+ export declare function generatePairingVerificationSecret(): Bytes;
7
+ export declare function encodePairingVerificationSecret(pv: Uint8Array): string;
8
+ export declare function decodePairingVerificationSecret(text: string): Bytes;
9
+ /** `<pairingCode>.<pv>`, the string the client shows and the user gives to the device. */
10
+ export declare function formatPairingInput(pairingCode: string, pv: Uint8Array): string;
11
+ export interface PairingInput {
12
+ /** The only part the device sends to the Host. */
13
+ readonly pairingCode: string;
14
+ /** Kept on the device; never sent to the Host. */
15
+ readonly pv: Bytes;
16
+ }
17
+ /** Split `<pairingCode>.<pv>` on the last `.`. Both parts are required. */
18
+ export declare function parsePairingInput(text: string): PairingInput;
19
+ /** Domain tag of the MAC the device sends: step 3, `HMAC(pv, device bundle, tenantId, deviceId)`. */
20
+ export declare const DEVICE_PAIRING_MAC_DOMAIN = "byok-pairing-device-mac-v1\n";
21
+ /** Domain tag of the MAC the client sends: step 4, `HMAC(pv, client bundle, chain head)`. */
22
+ export declare const CLIENT_PAIRING_MAC_DOMAIN = "byok-pairing-client-mac-v1\n";
23
+ export interface DevicePairingMacInput {
24
+ readonly tenantId: string;
25
+ readonly deviceId: string;
26
+ /** The device's own bundle. Its `deviceId` must equal `deviceId`. */
27
+ readonly member: MemberKeyBundleV1;
28
+ }
29
+ export interface ClientPairingMacInput {
30
+ /** The client's own bundle. A client bundle has no `deviceId`. */
31
+ readonly member: MemberKeyBundleV1;
32
+ /** The chain head after the client signed `add(device)`. */
33
+ readonly chainHead: {
34
+ readonly seq: number;
35
+ readonly hash: string;
36
+ };
37
+ }
38
+ export declare function computeDevicePairingMac(pv: Uint8Array, input: DevicePairingMacInput): Promise<string>;
39
+ export declare function computeClientPairingMac(pv: Uint8Array, input: ClientPairingMacInput): Promise<string>;
40
+ /**
41
+ * Check a received device MAC. Parse the received bundle with
42
+ * `parseMemberKeyBundle` first. Returns `false` for a wrong or malformed MAC,
43
+ * or a bundle that does not name `deviceId`. Only a malformed `pv` throws.
44
+ */
45
+ export declare function verifyDevicePairingMac(pv: Uint8Array, input: DevicePairingMacInput, mac: unknown): Promise<boolean>;
46
+ /**
47
+ * Check a received client MAC. Parse the received bundle with
48
+ * `parseMemberKeyBundle` first. Returns `false` for a wrong or malformed MAC,
49
+ * a device bundle, or a malformed chain head. Only a malformed `pv` throws.
50
+ */
51
+ export declare function verifyClientPairingMac(pv: Uint8Array, input: ClientPairingMacInput, mac: unknown): Promise<boolean>;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Byte helpers shared by the sealed-payload modules (#654 E1a).
3
+ *
4
+ * Every function is WebCrypto-only and `node:`-free, so a browser, Workers,
5
+ * Node and Bun run the same code. The subpath `@byok-sdk/core/sealed-payload`
6
+ * exports the sealed-payload interface, but not these helpers. The package
7
+ * root exports none of the sealed-payload modules.
8
+ *
9
+ * Caller input errors throw `sealed_secret_invalid`, the code `hpke.ts`
10
+ * already raises for bad HPKE input. Receiver checks never throw for hostile
11
+ * bytes; they return a typed failure.
12
+ */
13
+ import { z } from 'zod';
14
+ import { ByokCoreError } from '../errors';
15
+ import { decodeBase64Url, encodeBase64Url, type SealingPublicJwk } from '../sealed-provider-secret';
16
+ import { type WebCryptoKey } from '../webcrypto';
17
+ export type Bytes = Uint8Array<ArrayBuffer>;
18
+ /**
19
+ * The strict EC P-256 public JWK core already uses for sealing keys. Member
20
+ * signing keys and member KEM keys use the same shape.
21
+ */
22
+ export type P256PublicJwk = SealingPublicJwk;
23
+ export declare const P256PublicJwkSchema: z.ZodObject<{
24
+ kty: z.ZodLiteral<"EC">;
25
+ crv: z.ZodLiteral<"P-256">;
26
+ x: z.ZodString;
27
+ y: z.ZodString;
28
+ }, z.core.$strict>;
29
+ export declare function invalidInput(message: string): ByokCoreError;
30
+ export declare function utf8(value: string): Bytes;
31
+ export declare function copy(bytes: Uint8Array): Bytes;
32
+ export declare function concat(...parts: readonly Uint8Array[]): Bytes;
33
+ export declare function hex(bytes: Uint8Array): string;
34
+ export { decodeBase64Url, encodeBase64Url };
35
+ /** Fresh CSPRNG bytes from `globalThis.crypto`, the same source WebCrypto keys come from. */
36
+ export declare function randomBytes(length: number): Bytes;
37
+ export declare function sha256(bytes: Uint8Array): Promise<Bytes>;
38
+ export declare function hmacSha256(key: Uint8Array, data: Uint8Array): Promise<Bytes>;
39
+ /** RFC 5869 HKDF-SHA-256 through WebCrypto's own HKDF. */
40
+ export declare function hkdfSha256(ikm: Uint8Array, salt: Uint8Array, info: string, length: number): Promise<Bytes>;
41
+ /** A zod string that must be canonical unpadded base64url of exactly `bytes` bytes. */
42
+ export declare function base64UrlBytes(bytes: number): z.ZodString;
43
+ /** Uncompressed SEC1 bytes of a strict P-256 public JWK, checked to be a point on the curve. */
44
+ export declare function p256PublicKeyBytes(publicJwk: P256PublicJwk): Promise<Bytes>;
45
+ /**
46
+ * ECDSA P-256 / SHA-256 signature over `data`, as base64url of the 64-byte
47
+ * P1363 form. The signature must verify under `publicJwk`, so a private key
48
+ * that does not pair with the caller's public key throws
49
+ * `sealed_secret_invalid` and signs nothing under another key's id.
50
+ */
51
+ export declare function ecdsaSign(publicJwk: P256PublicJwk, privateKey: WebCryptoKey, data: Uint8Array): Promise<string>;
52
+ /** True only for a canonical 64-byte P1363 signature that verifies under `publicJwk`. */
53
+ export declare function ecdsaVerify(publicJwk: P256PublicJwk, signature: string, data: Uint8Array): Promise<boolean>;
@@ -0,0 +1,91 @@
1
+ import type { WebCryptoKey } from '../webcrypto';
2
+ import { type Bytes, type P256PublicJwk } from './primitives';
3
+ export declare const SCOPE_KEY_BYTES = 32;
4
+ /** 16 bytes of the key-id HMAC, as unpadded base64url. */
5
+ export declare const SCOPE_KEY_ID_LENGTH = 22;
6
+ export declare const SCOPE_KEY_ID_PATTERN: RegExp;
7
+ /** Byte length of the per-content HKDF salt. */
8
+ export declare const SEALED_SALT_BYTES = 32;
9
+ export declare const SCOPE_KEY_PACKAGE_VERSION = 1;
10
+ export declare const SCOPE_KEY_HPKE_INFO = "byok-sdk.scope-key.v1";
11
+ /** Prepended to the canonical package bytes the sender signs. */
12
+ export declare const SCOPE_KEY_PACKAGE_DOMAIN_PREFIX = "byok-scope-key-package-v1\n";
13
+ /** A fresh scope key from the platform CSPRNG. */
14
+ export declare function generateScopeKey(): Bytes;
15
+ export declare function deriveScopeKeyId(scopeKey: Uint8Array): Promise<string>;
16
+ /** The AES-256-GCM key of one sealed content. Each content has its own salt, so each key encrypts once. */
17
+ export declare function deriveSealedContentKey(scopeKey: Uint8Array, salt: Uint8Array): Promise<Bytes>;
18
+ /**
19
+ * The content-MAC key of one scope epoch. The design's salt is empty; HMAC
20
+ * pads its key with zeros, so 32 zero bytes give the same output, and no
21
+ * runtime has to accept a zero-length HKDF salt.
22
+ */
23
+ export declare function deriveSealedMacKey(scopeKey: Uint8Array): Promise<Bytes>;
24
+ export interface ScopeKeyPackageV1 {
25
+ readonly v: typeof SCOPE_KEY_PACKAGE_VERSION;
26
+ readonly tenantId: string;
27
+ readonly scopeId: string;
28
+ readonly epoch: number;
29
+ readonly keyId: string;
30
+ /** Member id of the member that can open the package. */
31
+ readonly recipient: string;
32
+ /** Member id of the member that sealed and signed the package. */
33
+ readonly sender: string;
34
+ /** HPKE encapsulated key: base64url of a 65-byte uncompressed P-256 point. */
35
+ readonly enc: string;
36
+ /** HPKE ciphertext of the scope key: base64url of 48 bytes. */
37
+ readonly ciphertext: string;
38
+ /** Sender's ECDSA P-256 / SHA-256 signature, base64url of the 64-byte P1363 form. */
39
+ readonly sig: string;
40
+ }
41
+ export interface SealScopeKeyPackageInput {
42
+ readonly scopeKey: Uint8Array;
43
+ readonly tenantId: string;
44
+ readonly scopeId: string;
45
+ readonly epoch: number;
46
+ readonly recipient: {
47
+ readonly memberId: string;
48
+ readonly kemPublicJwk: P256PublicJwk;
49
+ };
50
+ readonly sender: {
51
+ readonly signingPublicJwk: P256PublicJwk;
52
+ readonly signingPrivateKey: WebCryptoKey;
53
+ };
54
+ }
55
+ /**
56
+ * Wrap one scope key to one member and sign the package. A signing private key
57
+ * that does not pair with `sender.signingPublicJwk` throws
58
+ * `sealed_secret_invalid`.
59
+ */
60
+ export declare function sealScopeKeyPackage(input: SealScopeKeyPackageInput): Promise<ScopeKeyPackageV1>;
61
+ export interface OpenScopeKeyPackageInput {
62
+ readonly package: unknown;
63
+ /** What the caller asked the Host for. A package for anything else is refused. */
64
+ readonly expected: {
65
+ readonly tenantId: string;
66
+ readonly scopeId: string;
67
+ readonly epoch: number;
68
+ };
69
+ readonly recipient: {
70
+ readonly memberId: string;
71
+ readonly kemPublicJwk: P256PublicJwk;
72
+ readonly kemPrivateKey: WebCryptoKey;
73
+ };
74
+ /** The signing key the caller's verified chain holds for `package.sender`. */
75
+ readonly senderSigningPublicJwk: P256PublicJwk;
76
+ }
77
+ export type ScopeKeyPackageOpenResult = {
78
+ readonly ok: true;
79
+ readonly scopeKey: Uint8Array;
80
+ readonly keyId: string;
81
+ } | {
82
+ readonly ok: false;
83
+ readonly reason: 'key_package_invalid' | 'key_package_signature_invalid' | 'key_package_open_failed';
84
+ };
85
+ /**
86
+ * Verify and open one key package, in this order: shape; addressed to this
87
+ * recipient for the expected tenant, scope and epoch; sender key matches the
88
+ * sender id; sender signature; HPKE open; key length; and `keyId` equal to
89
+ * the opened key's own id. Nothing here throws for hostile input.
90
+ */
91
+ export declare function openScopeKeyPackage(input: OpenScopeKeyPackageInput): Promise<ScopeKeyPackageOpenResult>;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Sealed blob bytes v1 (#654 design §8.1, E1c).
3
+ *
4
+ * A protocol field blob (`instruction.blobRef`, a progress spill, an artifact)
5
+ * is sealed before its outer content, by the same frame, key and AEAD rule.
6
+ * Its AAD is the 16 outer AAD members that do not depend on the outer
7
+ * plaintext, the blob's own `paddedLength`, and `fieldPath`: the RFC 6901
8
+ * JSON Pointer from the moved-fields object to its BlobRef. The blob's salt,
9
+ * nonce and padded length travel next to the BlobRef inside the outer
10
+ * plaintext, so the outer AEAD and content MAC cover them.
11
+ *
12
+ * Internal: the subpath index does not import this module, so it adds no
13
+ * bytes to the published closure.
14
+ */
15
+ import { z } from 'zod';
16
+ import { type SealedBinding, type SealedCategory, type SealedContentHeaderV1, type SealedContentOpenFailure, type SealedContentRandomness } from './sealed-content';
17
+ /** What the outer plaintext carries next to a sealed blob's BlobRef. */
18
+ export declare const SealedBlobParamsV1Schema: z.ZodObject<{
19
+ salt: z.ZodString;
20
+ nonce: z.ZodString;
21
+ paddedLength: z.ZodNumber;
22
+ }, z.core.$strict>;
23
+ export type SealedBlobParamsV1 = z.infer<typeof SealedBlobParamsV1Schema>;
24
+ /**
25
+ * The canonical AAD bytes of a blob sealed under an outer header (§8.1).
26
+ */
27
+ export declare function sealedBlobAad(header: SealedContentHeaderV1, binding: SealedBinding, paddedLength: number, fieldPath: string): Uint8Array;
28
+ /**
29
+ * The input of one blob seal. Scope, chain head, sender, category and binding
30
+ * must be the ones of the outer content that will carry the BlobRef.
31
+ */
32
+ export interface SealBlobInput {
33
+ readonly scopeKey: Uint8Array;
34
+ readonly scope: {
35
+ readonly id: string;
36
+ readonly epoch: number;
37
+ };
38
+ readonly chainHead: {
39
+ readonly seq: number;
40
+ readonly hash: string;
41
+ };
42
+ readonly sender: string;
43
+ readonly category: SealedCategory;
44
+ readonly binding: SealedBinding;
45
+ /** The pointer to the BlobRef node in the moved-fields object. */
46
+ readonly fieldPath: string;
47
+ /** The raw blob bytes. */
48
+ readonly bytes: Uint8Array;
49
+ /** The plaintext limit of this field, in bytes. */
50
+ readonly maxContentLength: number;
51
+ }
52
+ export interface SealedBlob {
53
+ readonly params: SealedBlobParamsV1;
54
+ /** AES-256-GCM output: the padded frame followed by the 16-byte tag. Upload these bytes. */
55
+ readonly ciphertext: Uint8Array;
56
+ }
57
+ /**
58
+ * Seal one blob once, before the outer content. A retry must reuse the
59
+ * uploaded blob, never seal again (§8.6).
60
+ */
61
+ export declare function sealBlob(input: SealBlobInput): Promise<SealedBlob>;
62
+ /**
63
+ * {@link sealBlob} with a caller-chosen salt and nonce, for test vectors only.
64
+ */
65
+ export declare function sealBlobWithRandomness(input: SealBlobInput, randomness: SealedContentRandomness): Promise<SealedBlob>;
66
+ export interface OpenSealedBlobInput {
67
+ /** The outer header that `openSealedContent` authenticated. */
68
+ readonly header: SealedContentHeaderV1;
69
+ /** The binding the outer content opened under. */
70
+ readonly binding: SealedBinding;
71
+ readonly scopeKey: Uint8Array;
72
+ /** The receiver derives it from where it found the BlobRef, never from a table. */
73
+ readonly fieldPath: string;
74
+ /** From the opened outer plaintext. Its shape is still checked here. */
75
+ readonly params: unknown;
76
+ /** The downloaded blob bytes. The caller checks the BlobRef `contentHash` against them (§9.3). */
77
+ readonly ciphertext: Uint8Array;
78
+ readonly maxContentLength: number;
79
+ }
80
+ export type SealedBlobOpenResult = {
81
+ readonly ok: true;
82
+ readonly bytes: Uint8Array;
83
+ } | {
84
+ readonly ok: false;
85
+ readonly reason: SealedContentOpenFailure;
86
+ };
87
+ /**
88
+ * Open one blob, fail closed. In order: binding and category
89
+ * (`sealing_policy_mismatch`); header, field path and params shape
90
+ * (`sealed_open_failed`); key id (`sealed_key_unavailable`); ciphertext
91
+ * length, AEAD under the rebuilt blob AAD and the five frame checks (all
92
+ * `sealed_open_failed`). Nothing here throws for hostile input.
93
+ */
94
+ export declare function openSealedBlob(input: OpenSealedBlobInput): Promise<SealedBlobOpenResult>;