Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/bluesky-social/atproto/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The @atproto/crypto package provides cryptographic primitives for the AT Protocol, including key generation, signing, verification, and encoding utilities. It supports both P-256 (secp256r1) and K-256 (secp256k1) elliptic curve cryptography.

Installation

npm install @atproto/crypto

Supported Cryptographic Systems

The package implements two elliptic curve systems:
  • P-256 (ES256): NIST P-256, aka secp256r1, aka prime256v1
  • K-256 (ES256K): NIST K-256, aka secp256k1
Both use SHA-256 hashing and produce “low-S” signatures as specified in the AT Protocol cryptography specification.

Keypair Classes

P256Keypair

P-256 elliptic curve keypair for ES256 signatures.
import { P256Keypair } from '@atproto/crypto'

// Generate a new random keypair
const keypair = await P256Keypair.create({ exportable: true })

// Get the public key as a did:key
const didKey = keypair.did()
console.log(didKey) // 'did:key:zDna...'

// Sign data
const data = new Uint8Array([1, 2, 3, 4, 5])
const signature = await keypair.sign(data)

// Export private key (only if exportable: true)
const privateKey = await keypair.export()

// Import from existing private key
const imported = await P256Keypair.import(privateKey, { exportable: true })
P256Keypair.create
Promise<P256Keypair>
static async create(opts?: Partial<P256KeypairOptions>): Promise<P256Keypair>
Creates a new random P-256 keypair.
opts.exportable
boolean
default:"false"
Whether the private key can be exported. Set to true if you need to call export().
P256Keypair.import
Promise<P256Keypair>
static async import(
  privKey: Uint8Array | string,
  opts?: Partial<P256KeypairOptions>
): Promise<P256Keypair>
Imports a keypair from an existing private key.
privKey
Uint8Array | string
required
Private key bytes or hex string
opts.exportable
boolean
default:"false"
Whether the imported key can be re-exported
Instance Methods:
did
string
did(): string
Returns the public key formatted as a did:key identifier.
sign
Promise<Uint8Array>
async sign(msg: Uint8Array): Promise<Uint8Array>
Signs a message. The message is hashed with SHA-256 before signing. Returns a 64-byte compact signature (not DER-encoded).
msg
Uint8Array
required
Message bytes to sign
publicKeyBytes
Uint8Array
publicKeyBytes(): Uint8Array
Returns the raw public key bytes (compressed format).
publicKeyStr
string
publicKeyStr(encoding?: SupportedEncodings): string
Returns the public key as an encoded string.
encoding
SupportedEncodings
default:"base64pad"
Encoding format: ‘base64pad’, ‘base64’, ‘hex’, etc.
export
Promise<Uint8Array>
async export(): Promise<Uint8Array>
Exports the private key bytes. Throws if the keypair was not created with exportable: true.
Properties:
  • jwtAlg: string - Always 'ES256' for P-256 keypairs

Secp256k1Keypair

K-256 elliptic curve keypair for ES256K signatures.
import { Secp256k1Keypair } from '@atproto/crypto'

// Generate a new random keypair
const keypair = await Secp256k1Keypair.create({ exportable: true })

// Get the public key as a did:key
const didKey = keypair.did()
console.log(didKey) // 'did:key:zQ3s...'

// Sign data
const data = new Uint8Array([1, 2, 3, 4, 5])
const signature = await keypair.sign(data)

// Export and import
const privateKey = await keypair.export()
const imported = await Secp256k1Keypair.import(privateKey)
The API is identical to P256Keypair, but uses K-256 cryptography.
Secp256k1Keypair.create
Promise<Secp256k1Keypair>
static async create(opts?: Partial<Secp256k1KeypairOptions>): Promise<Secp256k1Keypair>
Creates a new random K-256 keypair.
Secp256k1Keypair.import
Promise<Secp256k1Keypair>
static async import(
  privKey: Uint8Array | string,
  opts?: Partial<Secp256k1KeypairOptions>
): Promise<Secp256k1Keypair>
Imports a keypair from an existing private key.
Properties:
  • jwtAlg: string - Always 'ES256K' for K-256 keypairs

Signature Verification

verifySignature

Verifies a signature using a public key in did:key format.
import { verifySignature } from '@atproto/crypto'

const didKey = 'did:key:zQ3s...'
const data = new Uint8Array([1, 2, 3, 4, 5])
const signature = new Uint8Array([...])

const isValid = await verifySignature(didKey, data, signature)
if (isValid) {
  console.log('Signature is valid')
} else {
  console.log('Signature is invalid')
}
verifySignature
Promise<boolean>
function verifySignature(
  didKey: string,
  data: Uint8Array,
  sig: Uint8Array,
  opts?: VerifyOptions & { jwtAlg?: string }
): Promise<boolean>
Verifies a signature against a public key.
didKey
string
required
Public key in did:key format
data
Uint8Array
required
Message bytes that were signed
sig
Uint8Array
required
Signature bytes to verify
opts.jwtAlg
string
Expected JWT algorithm (‘ES256’ or ‘ES256K’). If provided, verification fails if the key type doesn’t match.
opts.allowMalleableSig
boolean
default:"false"
Whether to allow non-canonical (high-S) signatures

verifySignatureUtf8

Convenience function for verifying UTF-8 string signatures.
import { verifySignatureUtf8 } from '@atproto/crypto'

const didKey = 'did:key:zQ3s...'
const message = 'Hello, world!'
const sigBase64Url = 'eW91ci1zaWduYXR1cmUtaGVyZQ'

const isValid = await verifySignatureUtf8(didKey, message, sigBase64Url)
verifySignatureUtf8
Promise<boolean>
function verifySignatureUtf8(
  didKey: string,
  data: string,
  sig: string,
  opts?: VerifyOptions
): Promise<boolean>
Verifies a signature with UTF-8 string message and base64url-encoded signature.

DID Key Utilities

formatDidKey

Formats a public key as a did:key identifier.
import { formatDidKey, P256_JWT_ALG } from '@atproto/crypto'

const publicKeyBytes = new Uint8Array([...])
const didKey = formatDidKey(P256_JWT_ALG, publicKeyBytes)
console.log(didKey) // 'did:key:zDna...'
formatDidKey
string
function formatDidKey(jwtAlg: string, keyBytes: Uint8Array): `did:key:${string}`
jwtAlg
string
required
JWT algorithm: ‘ES256’ or ‘ES256K’
keyBytes
Uint8Array
required
Uncompressed public key bytes

parseDidKey

Parses a did:key identifier to extract key type and bytes.
import { parseDidKey } from '@atproto/crypto'

const parsed = parseDidKey('did:key:zQ3s...')
console.log(parsed.jwtAlg)    // 'ES256K'
console.log(parsed.keyBytes)  // Uint8Array(...)
parseDidKey
ParsedMultikey
function parseDidKey(did: string): ParsedMultikey

type ParsedMultikey = {
  jwtAlg: string
  keyBytes: Uint8Array
}
Extracts the algorithm and key bytes from a did:key identifier.

Multibase Utilities

multibaseToBytes

Decodes a multibase-encoded string to bytes.
import { multibaseToBytes } from '@atproto/crypto'

const multibase = 'zQ3shVRtgqTRHC7...'
const bytes = multibaseToBytes(multibase)
multibaseToBytes
Uint8Array
function multibaseToBytes(mb: string): Uint8Array
Decodes a multibase string (base58btc with ‘z’ prefix) to bytes.

bytesToMultibase

Encodes bytes as a multibase string.
import { bytesToMultibase } from '@atproto/crypto'

const bytes = new Uint8Array([...])
const multibase = bytesToMultibase(bytes)
bytesToMultibase
string
function bytesToMultibase(bytes: Uint8Array): string
Encodes bytes as multibase (base58btc with ‘z’ prefix).

Hashing Utilities

sha256

Computes SHA-256 hash.
import { sha256 } from '@atproto/crypto'

const data = new Uint8Array([1, 2, 3, 4, 5])
const hash = await sha256(data)
sha256
Promise<Uint8Array>
function sha256(data: Uint8Array): Promise<Uint8Array>
Computes SHA-256 hash of the input data.

sha256Hex

Computes SHA-256 hash as hex string.
import { sha256Hex } from '@atproto/crypto'

const data = new Uint8Array([1, 2, 3, 4, 5])
const hashHex = await sha256Hex(data)
console.log(hashHex) // '74f81fe167d9...'

Random Utilities

randomBytes

Generates cryptographically secure random bytes.
import { randomBytes } from '@atproto/crypto'

const random = randomBytes(32)
randomBytes
Uint8Array
function randomBytes(length: number): Uint8Array
Generates secure random bytes of the specified length.

Constants

import {
  P256_JWT_ALG,      // 'ES256'
  SECP256K1_JWT_ALG, // 'ES256K'
  DID_KEY_PREFIX,    // 'did:key:'
  BASE58_MULTIBASE_PREFIX, // 'z'
  P256_DID_PREFIX,
  SECP256K1_DID_PREFIX
} from '@atproto/crypto'

TypeScript Interfaces

Keypair

interface Keypair extends Signer, Didable {
  jwtAlg: string
  sign(msg: Uint8Array): Promise<Uint8Array>
  did(): string
}

ExportableKeypair

interface ExportableKeypair extends Keypair {
  export(): Promise<Uint8Array>
}

VerifyOptions

type VerifyOptions = {
  allowMalleableSig?: boolean
}

Complete Example

import { 
  Secp256k1Keypair, 
  P256Keypair,
  verifySignature 
} from '@atproto/crypto'

// Generate keypairs
const k256Key = await Secp256k1Keypair.create({ exportable: true })
const p256Key = await P256Keypair.create({ exportable: true })

const message = new Uint8Array([1, 2, 3, 4, 5])

// Sign with K-256
const k256Sig = await k256Key.sign(message)
const k256Did = k256Key.did()
console.log('K-256 DID:', k256Did)

// Sign with P-256
const p256Sig = await p256Key.sign(message)
const p256Did = p256Key.did()
console.log('P-256 DID:', p256Did)

// Verify signatures
const k256Valid = await verifySignature(k256Did, message, k256Sig)
const p256Valid = await verifySignature(p256Did, message, p256Sig)

console.log('K-256 valid:', k256Valid) // true
console.log('P-256 valid:', p256Valid) // true

// Export and re-import keys
const k256Private = await k256Key.export()
const k256Restored = await Secp256k1Keypair.import(k256Private)

// Verify signature with restored key
const restoredSig = await k256Restored.sign(message)
const restoredValid = await verifySignature(
  k256Restored.did(), 
  message, 
  restoredSig
)
console.log('Restored key works:', restoredValid) // true

Additional Resources

Build docs developers (and LLMs) love