Sr25519.Substrate (sr25519 v0.1.0)

Copy Markdown View Source

Named Substrate/Polkadot sr25519 verification conventions.

Substrate does not use bare schnorrkel's defaults — it pins the signing context to the ASCII bytes "substrate", and some message-signing flows wrap the payload in <Bytes>…</Bytes>. Each convention here is a named function backed by real-tooling vectors; adding a convention means adding a named function plus its vectors, never a hidden branch inside an ambiguous "verify".

All inputs are raw-byte binaries: the bare 64-byte signature (strip any MultiSignature 0x01 tag first) and the raw 32-byte public key. The return contract is identical to Sr25519.verify_raw/4.

Which function do I want?

  • verify_raw_message/3 — the signer signed the message bytes directly under the "substrate" context, with no wrapping. This is what substrate-interface/subkey produce for sign(bytes), and what Bittensor hotkeys / the Epistula protocol use (Epistula signs the plain payload string "#{body}.#{uuid}.#{timestamp}.#{signed_for}" with no wrapping — construct that exact string on the caller side and pass it here).

  • verify_wrapped_bytes/3 — the signer used the polkadot-js extension / signRaw message-signing convention (u8aWrapBytes), which wraps the message in <Bytes>…</Bytes> unless it is already wrapped or carries the Ethereum signed-message prefix — this function mirrors that conditional behavior exactly, so pass the message precisely as the dapp passed it to signRaw.

If a caller already holds the exact signed bytes (wrapped or otherwise), use Sr25519.verify_raw/4 with the "substrate" context directly.

Summary

Functions

The Substrate signing context bytes ("substrate").

Verify an sr25519 signature over the raw message under the "substrate" context, with no wrapping.

Verify an sr25519 signature produced by the polkadot-js extension / signRaw message-signing convention, under the "substrate" context.

Functions

signing_context()

@spec signing_context() :: binary()

The Substrate signing context bytes ("substrate").

verify_raw_message(message, signature, public_key)

@spec verify_raw_message(binary(), binary(), binary()) :: Sr25519.result()

Verify an sr25519 signature over the raw message under the "substrate" context, with no wrapping.

Use this for substrate-interface/subkey sign(bytes) output and for Bittensor/Epistula payloads.

verify_wrapped_bytes(message, signature, public_key)

@spec verify_wrapped_bytes(binary(), binary(), binary()) :: Sr25519.result()

Verify an sr25519 signature produced by the polkadot-js extension / signRaw message-signing convention, under the "substrate" context.

Mirrors u8aWrapBytes from @polkadot/util exactly: the message is wrapped as <Bytes>message</Bytes> unless it is already <Bytes>…</Bytes>-wrapped or starts with the Ethereum signed-message prefix ("\x19Ethereum Signed Message:\n"), in which case the signer signed it as-is and so it is verified as-is. Pass the message exactly as the dapp passed it to signRaw.

Because wrapping adds 15 bytes, a message that gets wrapped must satisfy byte_size(message) <= Sr25519.max_message_bytes() - 15; larger ones return {:error, :message_too_large}. Already-wrapped / Ethereum-prefixed messages are capped at Sr25519.max_message_bytes() itself.