Skip to content

Instantly share code, notes, and snippets.

@bnewbold
Last active May 18, 2023 04:19
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bnewbold/9edbeb62686f7218ff136de2ab68cf7f to your computer and use it in GitHub Desktop.
Save bnewbold/9edbeb62686f7218ff136de2ab68cf7f to your computer and use it in GitHub Desktop.
atproto Cryptography Notes (Spring 2023)

Cryptography in atproto and did:plc

Public key cryptography is used in several aspects of atproto ("AT Protocol"). This short, informal, not-very-official document clarifies some details of which specific systems, keys, serialization formats, and signing/verification procedures are used.

For folks who are already familiar with atproto, the quick things to note are:

  • yes, it is expected for everybody to support two curve types
  • take the SHA-256 hash of repo commit nodes before signing/verifying them
  • depending on library/language used, p256 may have quirks with "compressed" encoding
  • yes, DID documents do require two similar-but-different encodings for public keys (did:web and multibase)

Supported Public Key Cryptographic Systems

Two elliptic curves are currently supported in the protocol, with full support in Bluesky's implementations:

  • p256 elliptic curve: aka “NIST P-256”, aka secp256r1 (note the r), aka prime256v1
    • NOTE: supported in WebCrypto
  • k256 elliptic curve: aka “NIST K-256”, aka secp256k1 (note the k)
    • NOTE: used in Bitcoin, other cryptocurrencies. Not supported in in WebCrypto
    • NOTE: Bluesky's Typescript PDS implementation defaults to this curve type

Because of the subtle visual distinction when the full curve names are written out, we often refer to them as p256 or k256. It can be helpful to add comments in source code or clarifications in discussions to reduce confusion.\

Key points for both systems have loss-less "compressed" representations, which are useful when sharing the public keys. This is usually supported natively for k256, but sometimes requires extra methods or jumping through hoops for p256. You can read more about this at: 02, 03 or 04? So What Are Compressed and Uncompressed Public Keys?.

atproto Repo Signing (v2)

The signing key indicated in the DID doc is used for signing and verification of repo "commit nodes". These are IPLD objects, encoded in DAG-CBOR.

The actual bytes used for signing (and verification) are the SHA-256 of the DAG-CBOR representation of the un-signed version of the commit node. To generate these bytes:

  • if starting with a signed "commit node", create an "unsigned commit node" which has all the fields except for the sig field. this should not be an object (CBOR, struct, etc) with the sig field set to "null"; the sig field should not exist at all on an "unsigned commit node"
  • encode the "unsigned commit node" as DAG-CBOR (the strictly-defined subset of CBOR used by IPLD) to get an array of bytes
  • take the SHA-256 hash of those bytes, and keep the binary output of the hash function. you should get 32-bytes (256 bits) of SHA-256 output. this should not be a 64-character hex-encoded string
  • sign or verify the resulting hash output bytes

The repo signature itself is stored and transmitted as bytes. In DAG-CBOR, this is the "bytes string" type. In other contexts (like a CLI tool printing to screen), you might want to use something like base64.

Note that neither the signature itself nor the "commit node" object indicate either the type of key used (curve type), or the specific public key used. That information must be fetched from the relevant DID document. With key rotation, verification of older commit signatures can become ambiguous. The most recent commit should always be possible to verify using the most recent DID document. When the signing key is rotated, a new commit should have been be created to ensure the signature is verifiable.

Cryptography in DID:PLC

Both p256 or k256 can be specified in did:plc operations, and used to sign the genesis block (creating the did:plc itself). Signing and recovery keys can be any combination of the two.

Refer to the DID standard (from the W3C) for details about the overall structure and contents of DID documents.

The W3C-standardized did:key encoding is used to represent public keys in both did:plc operations and in DID documents. This encoding includes metadata about the type of key, so they can be parsed and used unambiguously. The encoding process is:

  • encode the public key “point” as bytes. be sure to use the smaller "compact" or "compressed" representation. This is usually easy for k256, but might require extra research for p256 (see above)
  • prepend the appropriate per-key-type multicode indicator bytes in front of the key bytes:
    • p256: [0x80, 0x24]
    • k256: [0xE7, 0x01]
  • encode the combined bytes as base58btc, yielding a string
  • add did:key: as a prefix

For inclusion in DID Documents, the keys also need to be encoded in “multibase” format. This goes under the verificationMethod object, in the field publicKeyMultibase. The process for "multibase" encoding is:

  • base58btc encoding of the key bytes. Do not use the "compressed" or "compact" representation in this context (unlike for did:key)
  • add the character z as a prefix, and no other codec indicator

The sibling JSON keys contain the context of which key type is being indicated:

  • p256: EcdsaSecp256r1VerificationKey2019
  • k256: EcdsaSecp256k1VerificationKey2019

Possible Future Changes

Having multiple curve types supported in the protocol does increase complexity and can lead to bugs compared to a single-allowed-curve system. By supporting multiple curves from the start, we have forced this complexity to be tackled up-front, which should make future migrations easier. But we don't want to proliferate complexity by adding additional curves to the supported set without very persuasive reasons.

We are considering adding support for the Ed25519 elliptic curve at some point. This curve has undergone extensive review and has broad adoption. It is not currently included in WebCrypto. But to make it explicit, Ed25519 is not supported in atproto (or did:plc), as of 2023-05-17.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment