OTP cryptographic primitives over exact signature-base bytes, for custodians.
Algorithms and encodings follow RFC 9421 Section 3.3, RFC 7518, and RFC 8032.
This low-level boundary returns only signature bytes or mathematical validity.
It does not establish trust, identity, policy, or authorization. A custodian calls
it inside its secret boundary; private material is never stored in a public key,
message, or result. Caller-owned signing functions connect these primitives
to RequestSeal.sign/4.
Select an exact identifier from algorithms/0, or explicitly select a supported
RFC 9421 Section 3.3.7 extension as {:jws, name}. Extensions are RS256,
PS256, PS384, PS512, HS256, ES256, ES384, and Ed25519-only EdDSA.
Unknown, case-altered, none, and encryption identifiers reject. JWS names never
alias HTTP alg parameters: callers using JWS must omit the HTTP alg parameter
and select the extension through trusted configuration/key metadata. These
primitives consume the entire base directly, with no JOSE header or Base64 step.
sign/4 takes operation-local signing material and verify/5 a
RequestSeal.PublicKey; their documentation lists the descriptors. HMAC secrets must be
32–16,384 bytes for both "hmac-sha256" and {:jws, "HS256"}; there is no
key-equivalence derivation. HMAC compares
with OTP :crypto.hash_equals/2. HMAC proves shared-secret possession only.
The per-call max_bytes: bound of sign/4 and verify/5 does not change
RequestSeal.SignatureBase's separate 1,048,576-byte ceiling. ECDSA uses fixed-width r || s
(64/96 bytes), never DER on the wire. RSA-PSS fixes MGF1 to the selected digest
and salt length to its digest width (64 for HTTP SHA-512). Ed25519 has no prehash
and rejects noncanonical S. RSA signatures have the exact modulus width.
Errors are bounded RequestSeal.Crypto.Error values and contain no input bytes.
Summary
Functions
The six registered, case-sensitive HTTP signature algorithm identifiers.
The inclusive byte ceiling shared by cryptographic and custody operations.
Sign exact bytes using operation-local material within the caller's custodian.
Verify exact bytes; :ok establishes cryptographic validity only.
Types
Functions
@spec algorithms() :: [binary()]
The six registered, case-sensitive HTTP signature algorithm identifiers.
Returns
- a list of six binaries, in registry order.
Examples
iex> RequestSeal.Crypto.algorithms()
["rsa-pss-sha512", "rsa-v1_5-sha256", "hmac-sha256", "ecdsa-p256-sha256", "ecdsa-p384-sha384", "ed25519"]
@spec max_bytes_ceiling() :: pos_integer()
The inclusive byte ceiling shared by cryptographic and custody operations.
It is the largest max_bytes: value accepted by sign/4, verify/5,
RequestSeal.Custody.sign/3, and RequestSeal.Custody.verify/4.
Returns
16_777_216.
Examples
iex> RequestSeal.Crypto.max_bytes_ceiling()
16_777_216
@spec sign(algorithm(), binary(), tuple(), keyword()) :: {:ok, binary()} | {:error, RequestSeal.Crypto.Error.t()}
Sign exact bytes using operation-local material within the caller's custodian.
Checks run in this order: options, algorithm, bytes, then material, so
invalid options take precedence over an unsupported algorithm.
Parameters
algorithm- an identifier fromalgorithms/0or{:jws, name}for a JWS extension listed in the module documentation.bytes- the entire signature base as a binary, at most:max_bytesbytes.material- an operation-local descriptor that fitsalgorithm:{:rsa, otp_private_key}- a two-prime:RSAPrivateKeyrecord with a valid 2048–8192-bit public part and consistent CRT components.{:ec, "P-256" | "P-384", scalar_bytes}- a 32- or 48-byte scalar in1..order-1on the algorithm's curve.{:ed25519, seed_bytes}- a 32-byte seed.{:hmac, secret_bytes}- a 32–16,384-byte secret.
opts- keyword list; see Options.
Options
:max_bytes- largest acceptedbytessize, integer 1–16,777,216 (max_bytes_ceiling/0). Default1_048_576. It is the only option; any other key, a duplicate, or an out-of-range or non-integer value returns:invalid_options.
Returns
{:ok, signature}- RSA signatures have the modulus width, ECDSA signatures are fixed-width r || s (64 or 96 bytes), Ed25519 signatures are 64 bytes, and HMAC-SHA256 tags are 32 bytes.{:error, %RequestSeal.Crypto.Error{}}- reasons this function can return::invalid_options,:unsupported_algorithm,:invalid_data(bytesis not a binary or exceeds:max_bytes),:invalid_key(malformed, out-of-range, or inconsistent material),:key_mismatch(material of a type or curve that does not fitalgorithm), and:invalid_signature(OTP produced an ECDSA r or s outside the curve order). Meanings:RequestSeal.Crypto.Error.
Examples
RFC 8032 Section 7.1, TEST 1 (Ed25519, empty message):
iex> seed = Base.decode16!("9D61B19DEFFD5A60BA844AF492EC2CC44449C5697B326919703BAC031CAE7F60")
iex> {:ok, signature} = RequestSeal.Crypto.sign("ed25519", "", {:ed25519, seed})
iex> Base.encode16(signature, case: :lower)
"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"
iex> RequestSeal.Crypto.sign("ed25519", "", {:ed25519, seed}, timeout: 1_000)
{:error, %RequestSeal.Crypto.Error{reason: :invalid_options}}
iex> RequestSeal.Crypto.sign({:jws, "ES256"}, "", {:ed25519, seed})
{:error, %RequestSeal.Crypto.Error{reason: :key_mismatch}}
iex> RequestSeal.Crypto.sign("hmac-sha256", "message 41 delivered", {:hmac, "short secret"})
{:error, %RequestSeal.Crypto.Error{reason: :invalid_key}}
@spec verify( algorithm(), binary(), binary(), RequestSeal.PublicKey.t() | tuple(), keyword() ) :: :ok | {:error, RequestSeal.Crypto.Error.t()}
Verify exact bytes; :ok establishes cryptographic validity only.
Checks run in this order: options, algorithm, bytes, key, then signature,
so invalid options take precedence over an unsupported algorithm.
Parameters
algorithm- an identifier fromalgorithms/0or{:jws, name}for a JWS extension listed in the module documentation.bytes- the entire signature base as a binary, at most:max_bytesbytes.signature- the signature binary in the formatsign/4returns.key- aRequestSeal.PublicKey.t/0that fitsalgorithm: its type and curve must match, its JWKalgmust be absent or equal the JOSE name, itsuseabsent or"sig", and itskey_opsabsent or include"verify". Only inside the HMAC custodian,{:hmac, secret_bytes}(32–16,384 bytes) for an HMAC algorithm.opts- keyword list; see Options.
Options
:max_bytes- largest acceptedbytessize, integer 1–16,777,216 (max_bytes_ceiling/0). Default1_048_576. It is the only option; any other key, a duplicate, or an out-of-range or non-integer value returns:invalid_options.
Returns
:ok- the signature is mathematically valid forbytesunderkey.{:error, %RequestSeal.Crypto.Error{}}- reasons this function can return::invalid_options,:unsupported_algorithm,:invalid_data(bytesis not a binary or exceeds:max_bytes),:invalid_key(keyis malformed, is neither a public key nor an HMAC descriptor, or holds an HMAC secret outside 32–16,384 bytes),:key_mismatch(keydoes not fitalgorithmor its restrictions forbid verification), and:invalid_signature(not a binary, wrong width, ECDSA r or s outside the curve order, noncanonical Ed25519 S, or failed verification). Meanings:RequestSeal.Crypto.Error.
Examples
RFC 8032 Section 7.1, TEST 1 (Ed25519, empty message):
iex> public = Base.decode16!("D75A980182B10AB7D54BFED3C964073A0EE172F3DAA62325AF021A68F707511A")
iex> {:ok, key} = RequestSeal.PublicKey.import({:ed25519, public}, :raw)
iex> signature = Base.decode16!("E5564300C360AC729086E2CC806E828A84877F1EB8E5D974D873E065224901555FB8821590A33BACC61E39701CF9B46BD25BF5F0595BBE24655141438E7A100B")
iex> RequestSeal.Crypto.verify("ed25519", "", signature, key)
:ok
iex> RequestSeal.Crypto.verify("ed25519", "x", signature, key)
{:error, %RequestSeal.Crypto.Error{reason: :invalid_signature}}
iex> RequestSeal.Crypto.verify("ecdsa-p256-sha256", "", signature, key)
{:error, %RequestSeal.Crypto.Error{reason: :key_mismatch}}
iex> RequestSeal.Crypto.verify("ed25519", "", signature, %{key | operations: ["sign"]})
{:error, %RequestSeal.Crypto.Error{reason: :key_mismatch}}
iex> RequestSeal.Crypto.verify("ed25519", "", signature, key, max_bytes: 0)
{:error, %RequestSeal.Crypto.Error{reason: :invalid_options}}