obcrypt 1.0.0

Bytes-in/bytes-out cryptographic core of the oboron protocol (authenticated core schemes, no encoding).
Documentation
# Changelog

All notable changes to `obcrypt` are documented here. The format
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

### Added

### Changed

### Fixed


## [1.0.0] - 2026-06-26

First stable release, tracking the oboron 1.0 protocol. This is a
clean break from the 0.x line — schemes are renamed, the framing is
gone, and the unauthenticated scheme has moved out. Outputs are **not**
compatible with 0.x. Supersedes the `1.0.0-rc1` prerelease, adding the
conformance, hardening, buffer-contract, doc, and test fixes below; the
rc1 core rework (the bullets further down) is unchanged.

### Security

- **Canonical lowercase hex keys enforced.** `Key::from_hex` now
  rejects uppercase and other non-canonical hex (oboron spec §3.3),
  rather than case-folding via the `hex` crate. Use `Key::from_bytes`
  for non-canonical input.
- **`mock1` / `mock2` are no longer parseable from a string.**
  `Scheme::from_str` rejects them even when the `mock` feature is
  enabled, so a no-encryption scheme can never be selected through a
  string/config channel. Explicit `Scheme::Mock1` / `Scheme::Mock2`
  construction is unaffected.

### Fixed

- **`encrypt_into` / `decrypt_into` are now all-or-nothing.** On any
  error — including an AEAD authentication failure — the caller's
  buffer is truncated back to its length on entry, never left extended
  with partial or unverified bytes.
- **Clear error for a no-scheme build.** Building with no scheme
  feature now fails with an explicit `compile_error!` message instead
  of a cryptic non-exhaustive-match error; a `mock`-only build is
  warning-clean.
- **Documentation corrections.** The `dsiv` module doc described its
  output layout backwards (it is `SIV tag || ciphertext`); the
  `documentation` metadata URL pointed at a 404 (now docs.rs); the
  README license badge said MIT only (the crate is dual MIT/Apache); a
  SECURITY.md AES-SIV sub-key count was wrong; and the
  `EncryptionFailed` doc overstated when it occurs. SECURITY.md gains
  GCM-SIV fixed-nonce usage limits (RFC 8452 §6), an audit-status
  disclaimer, and a wrong-scheme-relabeling caveat.

### Internal

- GCM-SIV key derivation is a single shared helper used by both
  `dgcmsiv` and `pgcmsiv`, instead of duplicated verbatim per scheme.
- Added known-answer tests pinning each deterministic scheme's wire
  format, extended the adversarial suite (wrong key, tamper,
  too-short, all-or-nothing buffer) to every scheme, and added a
  GitLab CI pipeline running the full test / feature matrix.

### Changed

- **Breaking: scheme identifiers renamed** to a `<property><algorithm>`
  shape. The leading letter is the property — `d` (deterministic) or
  `p` (probabilistic) — and the rest names the AEAD (`siv` = AES-SIV,
  `gcmsiv` = AES-GCM-SIV). The 0.x tier-prefixed names map as:
  `aasv``dsiv`, `apsv``psiv`, `aags``dgcmsiv`,
  `apgs``pgcmsiv`. Feature flags and `obcrypt::schemes::*` modules
  are renamed to match.
- **Breaking: no scheme marker.** The 0.x framed payload (scheme
  output + an XOR'd 2-byte marker) is gone; `encrypt` now returns the
  scheme's AEAD output directly. `decrypt` takes the `Scheme`
  explicitly — there is no auto-detection. `decrypt_as` /
  `decrypt_as_into`, `Scheme::marker`, and `Scheme::from_marker` are
  removed; decrypting under the wrong scheme fails authentication.
- **Breaking: GCM-SIV key derivation via HKDF.** `dgcmsiv` / `pgcmsiv`
  derive their 32-byte AES-256-GCM-SIV key from the master with
  `HKDF-Expand` (HMAC-SHA-256, info `gcmsiv`, shared by both GCM-SIV
  schemes), replacing the fixed master slice used in 0.x. SIV schemes
  still use the full 64-byte master directly.
- **Dual-licensed** under `MIT OR Apache-2.0` (the 0.x line was MIT
  only); both license texts ship in the crate.

### Removed

- **`upbc` (AES-CBC) removed from the core.** obcrypt is now
  authenticated-only; the unauthenticated and obfuscation schemes live
  in the separate obu layer. The `Error::SchemeMarkerMismatch` and
  `Error::InvalidBlockLength` variants and the internal
  `Key::subkey` helper are removed.


## [0.2.0] - 2026-05-22

### Changed

- **Breaking: feature-flag cleanup.** The aggregate features
  `secure-schemes`, `atier`, and `utier` have been removed. obcrypt
  is all-secure at this layer (a-tier is authenticated, u-tier is
  unauthenticated but still real cryptography), so there is no
  unsecure subset for `secure-schemes` to distinguish; and the
  `atier` / `utier` tier-group names are not part of oboron's
  upstream feature vocabulary, so propagating them at the obcrypt
  layer violated the subset-by-name layering rule. `default` now
  enables the five individual scheme features directly
  (`aags`, `apgs`, `aasv`, `apsv`, `upbc`).

  **Migration.** Consumers that previously wrote
  `features = ["secure-schemes"]`, `features = ["atier"]`, or
  `features = ["utier"]` should switch to the explicit per-scheme
  list. A `default-features = true` consumer (the common case)
  needs no change — the default set is unchanged in composition,
  only in how it's named.

  No code, framed payload format, or `Scheme::marker` byte
  assignments changed — payloads produced by 0.1.x decrypt
  unchanged under 0.2.x with the matching scheme feature enabled.


## [0.1.1] - 2026-05-21

### Changed

- **Docs URLs** in `lib.rs`, `SECURITY.md`, and `FEATURES.md` now
  point at the public canonical repository
  (`gitlab.com/oboron/obcrypt-rs`) rather than the private mirror
  path. Cross-references on docs.rs and crates.io now resolve
  correctly for downstream readers.
- **Internal: per-scheme key extraction** centralized via a
  `Key::subkey<O, N>` crate-internal helper, replacing twelve
  hand-rolled `try_into().unwrap()` call sites across the scheme
  modules. Pure refactor — no public API change.
- **`Scheme::from_str`** no longer allocates a `String` per call
  (previously `s.to_lowercase()`; now ASCII-case-insensitive
  match guards).
- **`schemes::upbc::decrypt`** reduced from two heap allocations
  to one.
- **`schemes::apgs::encrypt`** nonce extraction simplified — the
  triple-nested `Nonce::from(*<&[u8;N]>::try_from(...).unwrap())`
  is replaced by a stack `[u8; NONCE_SIZE]` array, matching the
  cleaner pattern already used by the `_into` variant.
- **README versioning section** tightened — the wire-format
  stability statement no longer reads as contradicting the
  pre-1.0 API-evolution caveat in the same paragraph.


## [0.1.0] - 2026-05-20

### Added

- Initial release.
- Bytes-in / bytes-out cryptographic core lifted from `oboron-rs`.
- `a`-tier schemes: `aags`, `apgs`, `aasv`, `apsv`.
- `u`-tier schemes: `upbc`.
- Framed payload format (ciphertext + XOR'd 2-byte scheme marker).
- Dual API: `encrypt` / `encrypt_into`, `decrypt` / `decrypt_into`,
  `decrypt_as` / `decrypt_as_into`.
- Per-scheme raw primitives exposed under `obcrypt::schemes::*`,
  with parallel `encrypt_into` / `decrypt_into` variants on each that
  write directly into a caller-provided `&mut Vec<u8>` using AEAD's
  `encrypt_in_place` / `decrypt_in_place` (via a private `TailBuffer`
  adapter). The owned `encrypt` / `decrypt` are thin wrappers over the
  `_into` form.
- `Key` type with `Zeroize` / `ZeroizeOnDrop`.
- Testing-only `mock` feature (identity, reverse).

### Performance

- The top-level framed APIs (`encrypt_into`, `decrypt_into`,
  `decrypt_as_into`) are now genuinely zero-extra-allocation: ciphertext
  is written directly into the caller's buffer with no intermediate
  `Vec`. Previous implementation allocated a fresh `Vec` from the AEAD
  call and then copied into the caller's buffer.