All notable changes to this project are documented here. The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

A change to the pinned schnorrkel version or to verification behavior is treated as potentially breaking and versioned deliberately.

Unreleased

0.1.0 - 2026-07-02

The first public release: the verification-only core, plus a pre-publish security-hardening pass (no vulnerabilities found; all hardening changes are defense-in-depth or supply-chain robustness).

Added

  • Verification-only v0.1 core.
    • Sr25519.verify_raw/4 — low-level schnorrkel verify over (message, signature, public_key, context); validates lengths, a max_message_bytes/0 cap on the message and a max_context_bytes/0 cap on the signing context ({:error, :context_too_large}), and maps every fallible step to a typed result.
    • Sr25519.Substrate.verify_raw_message/3 — the "substrate" context, no wrapping.
    • Sr25519.Substrate.verify_wrapped_bytes/3 — the polkadot-js signRaw convention, mirroring u8aWrapBytes exactly: wraps in <Bytes>…</Bytes> unless the message is already wrapped or Ethereum-prefixed (passthrough, vector-backed).
  • Rust NIF over schnorrkel = "=0.11.5" with #![forbid(unsafe_code)] and panic = "unwind".
  • Frozen cross-implementation vector corpus from four oracles — substrate-interface (production Substrate/Bittensor signer), @polkadot/util-crypto (the polkadot-js wasm signer + exact signRaw flow), @scure/sr25519 (independent lineage), and the schnorrkel crate itself — and a single-command conformance ladder (mix conformance, rungs L0–L7).
  • Concurrency and memory-stability tests (parallel verification from 64 processes; 20k-call sustained-load memory bound).
  • NIF-safety suite: separate-process deliberate-panic survival test, input fuzzing, and a p99 < 1 ms latency gate.
  • Precompiled distribution via rustler_precompiled with a SR25519_FORCE_BUILD source-build escape hatch.
  • release-verify.yml — a pre-publish gate triggered by the checksum commit: committed checksums must exactly match the GitHub release assets (both directions), every asset's build-provenance attestation is verified against release.yml, and the real consumer install path (precompiled download + checksum verification + NIF load + known-answer verifies) is exercised on Linux/macOS/Windows.
  • All GitHub Actions pinned to commit SHAs (with dependabot-refreshed version comments); persist-credentials: false on every checkout; zizmor workflow linting and an OpenSSF Scorecard workflow.
  • Elixir-side advisory scanning in CI (mix hex.audit + mix deps.audit via mix_audit).
  • Dependabot coverage for the vector-generator manifests (vectors/rust_oracle cargo, vectors/python pip) and a pinned vectors/python/requirements.txt matching the frozen corpus metadata.
  • .github/CODEOWNERS; the release job targets a protectable release environment; RELEASING.md documents the required one-time repository settings and gates publishing on Release verify being green.
  • README section "Verifying release artifacts" (trust chain, gh attestation verify instructions, residual-trust statement).
  • Tests: 1 MiB signature/public-key cheap rejection, Sr25519.Substrate non-binary :invalid_type coverage, direct-NIF ArgumentError boundary contract, and direct-NIF wrong-length backstop coverage.

Changed

  • verify_raw now runs on a dirty CPU scheduler. The 64 KiB cap-sized transcript absorb could exceed the BEAM's ~1 ms regular-scheduler guideline on slow release targets (armv7, riscv64); scheduler fairness no longer depends on verify latency. The p99 < 1 ms benchmark stays as a perf-regression gate. Verification behavior is unchanged.
  • schnorrkel is built with default-features = false (alloc): the OS-RNG stack (getrandom, rand, rand_chacha, ppv-lite86, zerocopy, aead, wasi) is no longer linked into the verify-only NIF — 8 fewer supply-chain crates. Proven behavior-identical by the frozen vector corpus.
  • Sr25519.verify_raw/4 rejects wrong-length signatures/public keys before calling the NIF (the Rust checks remain the authoritative backstop, still covered by direct-NIF tests).
  • The release profile builds with overflow-checks = true (an overflow panics and unwinds into a typed error instead of silently wrapping).
  • cargo-deny now fails on duplicate crate versions (was: warn).

Fixed

  • SECURITY.md claimed builds use --locked; the actual mechanism is a cargo metadata --locked integrity gate in CI and release (rustler does not pass --locked to cargo).
  • README/moduledoc/package wording no longer reads as if this wrapper were audited — schnorrkel is audited upstream; the wrapper is human-reviewed.
  • Documented that legacy pre-0.8 unmarked schnorrkel signatures verify as {:ok, false} (the deprecated preaudit_deprecated encoding is deliberately not enabled).