Atex.Crypto
(atex v0.9.1)
View Source
Cryptographic operations for the AT Protocol.
Supports the two elliptic curves required by atproto:
p256- NIST P-256 / secp256r1 (JWK curve"P-256")k256- secp256k1 (JWK curve"secp256k1")
Key encoding
Public keys are represented as JOSE.JWK structs throughout this module.
The multikey / did:key encoding used in DID documents is the canonical
external representation: a base58btc-encoded (multibase z prefix) binary
consisting of a varint multicodec prefix followed by the 33-byte compressed
EC point.
Signing and verification
Signatures are DER-encoded ECDSA byte sequences as produced by Erlang's
:public_key application. All produced signatures are normalised to the
low-S form required by the atproto specification.
Summary
Types
A multikey-encoded public key string, optionally prefixed with did:key:.
Functions
Decodes a multikey or did:key string into a JOSE.JWK public key struct.
Decodes a legacy (pre-Multikey) atproto verification method public key into a JOSE.JWK.
Encodes a JOSE.JWK public key as a multikey string.
Signs a payload with a private key, returning a low-S DER-encoded ECDSA signature.
Verifies a DER-encoded ECDSA signature against a payload and a public key.
Types
@type multikey() :: String.t()
A multikey-encoded public key string, optionally prefixed with did:key:.
Examples:
"zDnaembgSGUhZULN2Caob4HLJPaxBh92N7rtH21TErzqf8HQo"(P-256 multikey)"did:key:zQ3shqwJEJyMBsBXCWyCBpUBMqxcon9oHB7mCvx4sSpMdLJwc"(K-256 did:key)
Functions
@spec decode_did_key(multikey()) :: {:ok, JOSE.JWK.t()} | {:error, term()}
Decodes a multikey or did:key string into a JOSE.JWK public key struct.
Accepts both bare multikey strings (e.g. "z...") and full did:key URIs
(e.g. "did:key:z..."). Supports P-256 (p256-pub) and secp256k1
(secp256k1-pub) keys.
Examples
iex> {:ok, jwk} = Atex.Crypto.decode_did_key("zDnaembgSGUhZULN2Caob4HLJPaxBh92N7rtH21TErzqf8HQo")
iex> match?(%JOSE.JWK{}, jwk)
true
iex> {:ok, jwk} = Atex.Crypto.decode_did_key("did:key:zQ3shqwJEJyMBsBXCWyCBpUBMqxcon9oHB7mCvx4sSpMdLJwc")
iex> match?(%JOSE.JWK{}, jwk)
true
iex> Atex.Crypto.decode_did_key("not-a-valid-key")
{:error, :invalid_multikey}
@spec decode_legacy_multibase(type :: String.t(), multibase :: String.t()) :: {:ok, JOSE.JWK.t()} | {:error, term()}
Decodes a legacy (pre-Multikey) atproto verification method public key into a JOSE.JWK.
Legacy verificationMethod entries encode the public key as an uncompressed EC point
(65 bytes: 0x04 || x || y) in base58btc multibase, without any multicodec prefix. The
curve is identified by the type field of the verification method rather than a multicodec
byte.
Accepted type values:
"EcdsaSecp256r1VerificationKey2019"- P-256 / secp256r1"EcdsaSecp256k1VerificationKey2019"- secp256k1
Examples
iex> {:ok, jwk} = Atex.Crypto.decode_legacy_multibase(
...> "EcdsaSecp256k1VerificationKey2019",
...> "zQYEBzXeuTM9UR3rfvNag6L3RNAs5pQZyYPsomTsgQhsxLdEgCrPTLgFna8yqCnxPpNT7DBk6Ym3dgPKNu86vt9GR"
...> )
iex> match?(%JOSE.JWK{}, jwk)
true
iex> Atex.Crypto.decode_legacy_multibase("UnknownType", "zQYEBzXeuTM")
{:error, :unsupported_curve}
@spec encode_did_key( JOSE.JWK.t(), keyword() ) :: {:ok, multikey()} | {:error, term()}
Encodes a JOSE.JWK public key as a multikey string.
Accepts both public and private key JWKs; the private component is discarded. Supports P-256 and secp256k1 keys.
Options
:as_did_key- whentrue, prepends thedid:key:URI scheme to the returned string. Defaults tofalse.
Examples
iex> jwk = JOSE.JWK.generate_key({:ec, "P-256"})
iex> {:ok, mk} = Atex.Crypto.encode_did_key(jwk)
iex> String.starts_with?(mk, "z")
true
iex> jwk = JOSE.JWK.generate_key({:ec, "P-256"})
iex> {:ok, mk} = Atex.Crypto.encode_did_key(jwk, as_did_key: true)
iex> String.starts_with?(mk, "did:key:z")
true
@spec sign(payload :: binary(), private_key :: JOSE.JWK.t()) :: {:ok, binary()} | {:error, term()}
Signs a payload with a private key, returning a low-S DER-encoded ECDSA signature.
The payload is hashed with SHA-256 internally before signing, matching the atproto signing convention.
Examples
iex> jwk = JOSE.JWK.generate_key({:ec, "P-256"})
iex> {:ok, sig} = Atex.Crypto.sign("hello", jwk)
iex> is_binary(sig)
true
@spec verify(payload :: binary(), signature :: binary(), public_key :: JOSE.JWK.t()) :: :ok | {:error, term()}
Verifies a DER-encoded ECDSA signature against a payload and a public key.
The payload is hashed with SHA-256 internally before verification, matching the atproto signing convention.
Returns :ok on success, or {:error, :invalid_signature} if the
signature does not match.
Examples
iex> jwk = JOSE.JWK.generate_key({:ec, "P-256"})
iex> {:ok, sig} = Atex.Crypto.sign("hello", jwk)
iex> Atex.Crypto.verify("hello", sig, JOSE.JWK.to_public(jwk))
:ok
iex> jwk = JOSE.JWK.generate_key({:ec, "P-256"})
iex> {:ok, sig} = Atex.Crypto.sign("hello", jwk)
iex> Atex.Crypto.verify("tampered", sig, JOSE.JWK.to_public(jwk))
{:error, :invalid_signature}