Exosphere.ATProto.Crypto (Exosphere v0.3.0)

Copy Markdown View Source

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:zQ3shXjHeiBuRCKmM36cuYnm7YEMzhGnCmCyW92sRJ9pribSF

The multicodec prefixes (unsigned varints) are:

  • 0xe7 0x01 for secp256k1 (compressed)
  • 0x80 0x24 for 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

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

curve()

@type curve() :: :secp256k1 | :p256

keypair()

@type keypair() :: %{public_key: binary(), private_key: binary()}

signature()

@type signature() :: binary()

Functions

from_did_key(arg1)

@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 with did:key:
  • {:error, :unsupported_multibase} if the multibase prefix isn't z (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_keypair(atom)

@spec generate_keypair(curve()) :: {:ok, keypair()} | {:error, term()}

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, private_key, atom)

@spec sign(binary(), binary(), curve()) :: {:ok, signature()} | {:error, term()}

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

to_did_key(public_key, atom)

@spec to_did_key(binary(), curve()) ::
  {:ok, String.t()} | {:error, :invalid_public_key}

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"

to_multibase(public_key, curve)

@spec to_multibase(binary(), curve()) ::
  {:ok, String.t()} | {:error, :invalid_public_key}

Convert a public key to multibase format for DID documents.

Uses the Multikey format with base58btc encoding.

verify(data, signature, public_key, curve)

@spec verify(binary(), signature(), binary(), curve()) ::
  :ok | {:error, :invalid_signature}

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}