Cryptographic operations for Exosphere.ATProto.
Handles signing and verification using the two key types supported by Exosphere.ATProto:
- secp256k1 (K-256): Used for signing keys, compatible with Bitcoin/Ethereum
- NIST P-256 (secp256r1): Alternative curve, widely supported
Key Representation
Public keys are represented in did:key format for interoperability:
did:key:zQ3shXjHeiBuRCKmM36cuYnm7YEMzhGnCmCyW92sRJ9pribSFThe multicodec prefixes (unsigned varints) are:
0xe7 0x01for secp256k1 (compressed)0x80 0x24for P-256 (compressed)
Examples
# Generate a new keypair
{:ok, keypair} = Exosphere.ATProto.Crypto.generate_keypair(:secp256k1)
# Sign data
{:ok, signature} = Exosphere.ATProto.Crypto.sign(data, keypair.private_key, :secp256k1)
# Verify signature
:ok = Exosphere.ATProto.Crypto.verify(data, signature, keypair.public_key, :secp256k1)
# Convert to did:key
{:ok, did_key} = Exosphere.ATProto.Crypto.to_did_key(keypair.public_key, :secp256k1)
Summary
Functions
Compress an uncompressed public key (65 bytes, 0x04 prefix) into the
33-byte compressed form. Compressed input passes through unchanged.
Decompress a compressed public key (33 bytes, 0x02/0x03 prefix) into
the uncompressed 65-byte form (0x04 prefix). Uncompressed input passes
through unchanged.
Parse a did:key string to extract the public key and curve type.
Generate a new keypair for the specified curve.
Sign data using ECDSA-SHA256.
Convert a public key to did:key format.
Convert a public key to multibase format for DID documents.
Verify an ECDSA-SHA256 signature.
Types
Functions
Compress an uncompressed public key (65 bytes, 0x04 prefix) into the
33-byte compressed form. Compressed input passes through unchanged.
Examples
iex> {:ok, <<prefix, _::binary-32>>} = Exosphere.ATProto.Crypto.compress_public_key(key, :p256)
iex> prefix in [2, 3]
true
Decompress a compressed public key (33 bytes, 0x02/0x03 prefix) into
the uncompressed 65-byte form (0x04 prefix). Uncompressed input passes
through unchanged.
Examples
iex> {:ok, <<0x04, _::binary-64>>} = Exosphere.ATProto.Crypto.decompress(keypair.public_key, :p256)
@spec from_did_key(String.t()) :: {:ok, binary(), curve()} | {:error, :invalid_did_key_format | :unsupported_multibase | :invalid_base58 | :unsupported_key_type}
Parse a did:key string to extract the public key and curve type.
Returns:
{:error, :invalid_did_key_format}if the input doesn't start withdid:key:{:error, :unsupported_multibase}if the multibase prefix isn'tz(base58btc){:error, :invalid_base58}if base58 decoding fails{:error, :unsupported_key_type}if the multicodec prefix isn't recognised
Examples
iex> {:ok, public_key, :secp256k1} = Exosphere.ATProto.Crypto.from_did_key("did:key:zQ3sh...")
Generate a new keypair for the specified curve.
Examples
iex> {:ok, keypair} = Exosphere.ATProto.Crypto.generate_keypair(:secp256k1)
iex> byte_size(keypair.private_key)
32
Sign data using ECDSA-SHA256.
Returns the signature in "low-S" canonical form as required by Exosphere.ATProto. The signature is encoded as raw (r, s) bytes (64 bytes total).
Examples
iex> {:ok, sig} = Exosphere.ATProto.Crypto.sign("hello", private_key, :secp256k1)
iex> byte_size(sig)
64
Convert a public key to did:key format.
Accepts either a compressed (33-byte, 0x02/0x03 prefix) or uncompressed
(65-byte, 0x04 prefix) public key. Returns {:error, :invalid_public_key}
for any other shape.
Examples
iex> {:ok, did} = Exosphere.ATProto.Crypto.to_did_key(public_key, :secp256k1)
iex> did
"did:key:zQ3shXjHeiBuRCKmM36cuYnm7YEMzhGnCmCyW92sRJ9pribSF"
Convert a public key to multibase format for DID documents.
Uses the Multikey format with base58btc encoding.
Verify an ECDSA-SHA256 signature.
atproto requires signatures to be in "low-S" form for both curves, so signatures whose S value is in the upper half of the curve order (malleable / "high-S") are rejected even when otherwise cryptographically valid.
Examples
iex> Exosphere.ATProto.Crypto.verify("hello", signature, public_key, :secp256k1)
:ok
iex> Exosphere.ATProto.Crypto.verify("tampered", signature, public_key, :secp256k1)
{:error, :invalid_signature}