<!--
SPDX-FileCopyrightText: 2026 OpenBasil Contributors

SPDX-License-Identifier: Apache-2.0
-->

# basil-cose

A strict, deterministic COSE (RFC 9052/9053) profile for basil's sealed
invocations. Broker-free and publishable, it is shared by the basil broker, the
basil client crate, and edgebox.

## Constructions

| Construction | Shape | Build | Verify / open |
| --- | --- | --- | --- |
| Signed | bare `COSE_Sign1` (tag 18) | `build_signed` | `verify_signed` |
| Sealed | `COSE_Sign1` over an embedded tagged `COSE_Encrypt` | `build_sealed` | `verify_sealed`, then `VerifiedSealed::open` |
| Seal-only | bare `COSE_Encrypt` (tag 96) | `build_encrypted` | `decode_encrypted`, then `EncryptedMessage::open` |

Algorithms: EdDSA (−8, Ed25519) or ES256 (−7, ECDSA P-256 + SHA-256)
signatures; ECDH-ES + HKDF-256 (−25) with X25519 key agreement; A256GCM (3) or
ChaCha20-Poly1305 (24) content encryption. The content algorithm is a
parameter of every encrypting entry point. ES256 signing is deterministic
(RFC 6979), so both signature algorithms re-sign byte-identically.

## Profile guarantees

- **Deterministic encoding by construction** (in-tree codec over `minicbor`:
  canonical map-key order, definite lengths, minimal integer heads), plus an
  RFC 8949 §4.2 re-encode-and-compare check on **every** decode, release
  builds included.
- **Strict decode**: rejects untagged/wrong-tag structures, indefinite
  lengths, non-minimal or otherwise non-deterministic encodings, duplicate or
  unknown labels, text labels, wrong CBOR types, unknown algorithm
  codepoints, `crit` violations, claims in unprotected headers, and recipient
  arrays with length ≠ 1. Error enums are closed and diagnostic, with no
  forward-compatible `Unknown` arms, and no secret bytes in any error.
- **Secure by construction**: nonces and X25519 ephemerals are generated by
  the library (`getrandom`): there is no caller-supplied-nonce path. Every
  secret intermediate (private keys, ECDH shared secret, derived CEK,
  recovered plaintext) is `Zeroizing`. Low-order (non-contributory) ephemeral
  keys are rejected before key derivation. Ed25519 verification uses
  `verify_strict`.
- **Caller-supplied `external_aad`** on both `Sig_structure` and
  `Enc_structure`, and KDF PartyU/PartyV identities carried in the recipient
  protected headers (−21/−24) with the RFC 9053 §5.2 `COSE_KDF_Context` as
  the HKDF info, so an opener that is not the sealer (for example a broker
  unseal-in-place RPC) can rebuild the KDF context from the message alone,
  and may pin expected identities.

## Claims

Claims ride in the protected header as a CWT map (header 15) plus the basil
private labels (`-70001..=-70005`, module `label`): in-reply-to, request
hash (SHA3-256, helper `request_hash`), sender key id (must equal the outer
`kid`), response key id, and response subject. `MessageRole`
(request/response/peer) validators enforce claim shape;
`ValidationParams` parameterizes clock skew, TTL bounds, and the audience
allow-list (`now` is injected, never sampled: the crate is state-free and
owns no time policy).

## Keys

Key material stays behind the AFIT traits `Signer`, `Verifier`, and
`Recipient` (async so broker-backed implementations can await an RPC; the
shipped local implementations complete synchronously): `Ed25519Signer`,
`Ed25519Verifier`, `X25519Recipient` (+ `X25519RecipientPublic` as the seal
target).

## no_std

The crate is `#![no_std]` + `alloc` and obtains production randomness through
`getrandom`.

## Features

- `fixtures`: deterministic build variants (`build_sealed_with_parts`,
  `build_encrypted_with_parts`) that accept caller-supplied ephemeral/nonce
  parts, for test vectors only. Never enable on production paths.

## Design

The authoritative API/wire design is
[`designs/basil-cose-api.md`](../../designs/basil-cose-api.md) (see §7.1 for
C1 implementation notes, including the 2026-07-02 reviewer amendment that
made `minicbor` the CBOR backend, aligning with edgebox-rs).
