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 whatsubstrate-interface/subkeyproduce forsign(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 /signRawmessage-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 tosignRaw.
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
@spec signing_context() :: binary()
The Substrate signing context bytes ("substrate").
@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.
@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.