Cryptography
The AT Protocol uses ECDSA with two elliptic curves: P-256 (NIST) and K-256 (secp256k1). The FSharp.ATProto.Crypto package handles key generation, signing, verification, and encoding to the did:key format used in DID documents.
P-256 uses the native .NET ECDsa API. K-256 uses BouncyCastle, since .NET does not support secp256k1 natively.
Algorithm
The Algorithm discriminated union selects a curve:
type Algorithm = P256 | K256
All functions that deal with keys or signatures take an Algorithm to identify the curve. Use Algorithm.P256 for P-256 and Algorithm.K256 for secp256k1.
Key Types
type PublicKey =
{ Algorithm : Algorithm
CompressedBytes : byte[] } // 33 bytes: 0x02/0x03 prefix + 32-byte X
type KeyPair =
{ Algorithm : Algorithm
PrivateKeyBytes : byte[] // 32 bytes
CompressedPublicKey : byte[] } // 33 bytes
Key Management
The Keys module provides generation, import, and decompression:
open FSharp.ATProto.Crypto
// Generate a new key pair
let keyPair = Keys.generate Algorithm.P256
// Import from raw private key bytes (32 bytes)
let imported = Keys.importPrivateKey Algorithm.K256 somePrivateBytes
// Import a compressed public key (33 bytes) -- returns Result
match Keys.importPublicKey Algorithm.P256 compressedBytes with
| Ok pubKey ->
// Decompress to 65-byte uncompressed form (0x04 + X + Y)
let uncompressed = Keys.decompress pubKey
printfn "Uncompressed: %d bytes" uncompressed.Length
| Error msg -> printfn "Import failed: %s" msg
// Extract the public key from a key pair
let pub = Keys.publicKey keyPair
Signing and Verification
The Signing module produces 64-byte compact ECDSA signatures (r || s, 32 bytes each) with low-S normalization as required by the AT Protocol. Data is hashed with SHA-256 internally.
let data = System.Text.Encoding.UTF8.GetBytes "hello"
// Sign -- returns 64-byte compact signature
let signature = Signing.sign keyPair2 data
// Verify -- rejects high-S signatures
let isValid = Signing.verify (Keys.publicKey keyPair2) data signature
// true
Signing.verify returns false for invalid, high-S, or DER-encoded signatures. It never throws.
Multikey and did:key Encoding
The Multikey module encodes and decodes public keys in the did:key format used by AT Protocol DID documents. Keys are serialized as base58btc with a multicodec prefix (P-256: 0x1200, K-256: 0xe7).
// Encode as did:key (e.g. "did:key:zDnae...")
let didKey = Multikey.keyPairToDid keyPair3
// Encode just the multibase portion (e.g. "zDnae...")
let multibase = Multikey.encodeMultibase (Keys.publicKey keyPair3)
// Decode a did:key string back to a PublicKey
match Multikey.decodeDid didKey with
| Ok pubKey -> printfn "Algorithm: %A" pubKey.Algorithm
| Error msg -> printfn "Invalid did:key: %s" msg
// Decode a multibase string
match Multikey.decodeMultibase multibase with
| Ok pubKey -> printfn "Got key with %d bytes" pubKey.CompressedBytes.Length
| Error msg -> printfn "Decode failed: %s" msg
Base58
The Base58 module provides standalone base58btc encoding and decoding using the Bitcoin alphabet. This is used internally by Multikey but is available for direct use if needed.
let encoded = Base58.encode [| 0uy; 1uy; 2uy |]
// "12c"
match Base58.decode encoded with
| Ok bytes -> printfn "Decoded %d bytes" bytes.Length
| Error msg -> printfn "Invalid base58: %s" msg
namespace FSharp
--------------------
namespace Microsoft.FSharp
val byte: value: 'T -> byte (requires member op_Explicit)
--------------------
type byte = System.Byte
--------------------
type byte<'Measure> = byte
<summary> Key management for P-256 and secp256k1 curves. </summary>
<summary> Generate a new key pair for the given algorithm. </summary>
<summary> The elliptic curve algorithm used for a key. </summary>
union case Algorithm.P256: Algorithm
--------------------
union case Algorithm.P256: Algorithm
<summary> Import a private key from raw bytes. </summary>
union case Algorithm.K256: Algorithm
--------------------
union case Algorithm.K256: Algorithm
<summary> Import a compressed public key (33 bytes). </summary>
<summary> Decompress a 33-byte compressed key to 65-byte uncompressed (0x04 + X + Y). </summary>
<summary>Gets the total number of elements in all the dimensions of the <see cref="T:System.Array" />.</summary>
<exception cref="T:System.OverflowException">The array is multidimensional and contains more than <see cref="F:System.Int32.MaxValue">Int32.MaxValue</see> elements.</exception>
<returns>The total number of elements in all the dimensions of the <see cref="T:System.Array" />; zero if there are no elements in the array.</returns>
<summary> Get the public key from a key pair. </summary>
<summary>Represents a character encoding.</summary>
<summary>Gets an encoding for the UTF-8 format.</summary>
<returns>An encoding for the UTF-8 format.</returns>
System.Text.Encoding.GetBytes(chars: char array) : byte array
System.Text.Encoding.GetBytes(chars: System.ReadOnlySpan<char>, bytes: System.Span<byte>) : int
System.Text.Encoding.GetBytes(s: string, index: int, count: int) : byte array
System.Text.Encoding.GetBytes(chars: char array, index: int, count: int) : byte array
System.Text.Encoding.GetBytes(chars: nativeptr<char>, charCount: int, bytes: nativeptr<byte>, byteCount: int) : int
System.Text.Encoding.GetBytes(s: string, charIndex: int, charCount: int, bytes: byte array, byteIndex: int) : int
System.Text.Encoding.GetBytes(chars: char array, charIndex: int, charCount: int, bytes: byte array, byteIndex: int) : int
<summary> ECDSA signing and verification with low-S normalization as required by AT Protocol. All signatures use compact format (64 bytes: r || s, 32 bytes each). </summary>
<summary> Sign data with a key pair. Returns a 64-byte compact signature with low-S. The data is hashed with SHA-256 internally. </summary>
<summary> Verify a compact signature against data and a public key. Enforces low-S requirement. Returns false for high-S or DER-encoded signatures. </summary>
<summary> Multicodec and did:key encoding/decoding for AT Protocol public keys. Supports P-256 (multicodec 0x1200) and secp256k1 (multicodec 0xe7). </summary>
<summary> Format a key pair's public key as a did:key string. </summary>
<summary> Encode a public key to multibase (base58btc, 'z' prefix) format. This is the format used in DID document verificationMethod publicKeyMultibase fields. </summary>
<summary> Decode a did:key string to a public key. </summary>
<summary> Decode a multibase-encoded public key string. Expects 'z' prefix (base58btc) followed by multicodec prefix + compressed key. </summary>
<summary> Compressed public key bytes (33 bytes: 0x02/0x03 prefix + 32-byte X). </summary>
<summary> Base58btc encoding and decoding using the Bitcoin alphabet. Used by multibase (prefix 'z') for encoding public keys in did:key and multikey formats. </summary>
<summary> Encode bytes to base58btc string. </summary>
<summary> Decode a base58btc string to bytes. Returns Error on invalid input. </summary>