# 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
| 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).