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, amax_message_bytes/0cap on the message and amax_context_bytes/0cap 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-jssignRawconvention, mirroringu8aWrapBytesexactly: 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)]andpanic = "unwind". - Frozen cross-implementation vector corpus from four oracles —
substrate-interface(production Substrate/Bittensor signer),@polkadot/util-crypto(the polkadot-js wasm signer + exactsignRawflow),@scure/sr25519(independent lineage), and theschnorrkelcrate 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_precompiledwith aSR25519_FORCE_BUILDsource-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 againstrelease.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: falseon every checkout;zizmorworkflow linting and an OpenSSF Scorecard workflow. - Elixir-side advisory scanning in CI (
mix hex.audit+mix deps.auditviamix_audit). - Dependabot coverage for the vector-generator manifests
(
vectors/rust_oraclecargo,vectors/pythonpip) and a pinnedvectors/python/requirements.txtmatching the frozen corpus metadata. .github/CODEOWNERS; the release job targets a protectablereleaseenvironment; RELEASING.md documents the required one-time repository settings and gates publishing onRelease verifybeing green.- README section "Verifying release artifacts" (trust chain,
gh attestation verifyinstructions, residual-trust statement). - Tests: 1 MiB signature/public-key cheap rejection,
Sr25519.Substratenon-binary:invalid_typecoverage, direct-NIFArgumentErrorboundary contract, and direct-NIF wrong-length backstop coverage.
Changed
verify_rawnow 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/4rejects 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-denynow fails on duplicate crate versions (was: warn).
Fixed
- SECURITY.md claimed builds use
--locked; the actual mechanism is acargo metadata --lockedintegrity gate in CI and release (rustler does not pass--lockedto cargo). - README/moduledoc/package wording no longer reads as if this wrapper were
audited —
schnorrkelis audited upstream; the wrapper is human-reviewed. - Documented that legacy pre-0.8 unmarked schnorrkel signatures verify as
{:ok, false}(the deprecatedpreaudit_deprecatedencoding is deliberately not enabled).