Sr25519 (sr25519 v0.1.0)

Copy Markdown View Source

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 signed
  • signature — the bare 64-byte sr25519 signature (strip any MultiSignature 0x01 tag and any hex/SS58 encoding first)
  • public_key — the raw 32-byte public key
  • context — the signing context bytes (e.g. "substrate")

Return contract

ReturnMeaning
{: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

Types

A typed verification error.

The result of a verification call.

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

error()

@type error() ::
  :invalid_type
  | :invalid_length
  | :message_too_large
  | :context_too_large
  | :invalid_public_key

A typed verification error.

result()

@type result() :: {:ok, boolean()} | {:error, error()}

The result of a verification call.

Functions

max_context_bytes()

@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}.

max_message_bytes()

@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_raw(message, signature, public_key, context)

@spec verify_raw(binary(), binary(), binary(), binary()) :: result()

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}