Substrate-compatible sr25519 (schnorrkel) signature verification for the BEAM.
This is a thin, safety-critical Rustler NIF over the
w3f schnorrkel crate (independently audited
upstream; this wrapper itself is human-reviewed, not independently audited — see
the project's SECURITY.md). It verifies
exact bytes and nothing more: it never decodes, normalizes, or canonicalizes input
(no hex, base64, SS58, SCALE, JSON, UTF-8, or MultiSignature tag handling). Every
Substrate/Bittensor convention lives in a named, vector-backed module —
Sr25519.Substrate.
What this module verifies
verify_raw/4 is the low-level primitive: the caller supplies the signing
context, and the signature is checked against context ‖ message through
schnorrkel's Merlin transcript. Most callers want the named conventions in
Sr25519.Substrate instead.
Inputs
All arguments are raw-byte binaries:
message— the exact bytes that were signedsignature— the bare 64-byte sr25519 signature (strip anyMultiSignature0x01tag and any hex/SS58 encoding first)public_key— the raw 32-byte public keycontext— the signing context bytes (e.g."substrate")
Return contract
| Return | Meaning |
|---|---|
{:ok, true} | valid signature over the exact bytes |
{:ok, false} | 32/64-byte inputs that parse but do not verify — including a length-correct but structurally-invalid signature (so random/tampered 64-byte values fail, they do not raise) |
{:error, :invalid_type} | a non-binary argument |
{:error, :invalid_length} | public key ≠ 32 bytes, or signature ≠ 64 bytes |
{:error, :message_too_large} | message exceeds max_message_bytes/0 |
{:error, :context_too_large} | signing context exceeds max_context_bytes/0 |
{:error, :invalid_public_key} | public-key bytes schnorrkel rejects structurally |
Both :error and {:ok, false} fail closed — the distinction is for
metrics/alerting, not control flow.
Legacy signature format
Signatures from pre-0.8 schnorrkel (missing the 0x80 "schnorrkel-marked"
bit in byte 63) parse-fail and return {:ok, false}, never an error. The
deprecated legacy encoding (preaudit_deprecated) is deliberately not
enabled; all modern Substrate/polkadot-js/subkey signatures carry the marker.
Summary
Functions
The maximum accepted signing context size, in bytes.
The maximum accepted message size, in bytes.
Verify a raw schnorrkel signature over context ‖ message.
Types
Functions
@spec max_context_bytes() :: pos_integer()
The maximum accepted signing context size, in bytes.
Signing contexts are short domain-separation labels (Substrate's is the 9-byte
"substrate"); an oversized one returns {:error, :context_too_large}.
@spec max_message_bytes() :: pos_integer()
The maximum accepted message size, in bytes.
Messages larger than this are rejected with {:error, :message_too_large} rather
than absorbing unbounded input into the transcript. Realistic Substrate extrinsics
and Bittensor/Epistula payloads are far below this cap.
Verify a raw schnorrkel signature over context ‖ message.
The caller supplies the exact signing context; the library validates lengths
and the size cap, then calls schnorrkel. See the module doc for the full return
contract.
Examples
iex> Sr25519.verify_raw("hi", <<0::512>>, <<0::256>>, "substrate")
{:ok, false}
iex> Sr25519.verify_raw("hi", <<0::512>>, <<0::248>>, "substrate")
{:error, :invalid_length}
iex> Sr25519.verify_raw("hi", <<0::512>>, 123, "substrate")
{:error, :invalid_type}