OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-identity / lib / did-key.js

62 lines · javascript ref:
// did:key encoding/decoding for the two curves atproto accepts.
//
//   did:key:z<base58btc( <multicodec-varint> ++ <compressed-pubkey> )>
//
// Multicodec key-type prefixes (unsigned-varint):
//   secp256k1-pub = 0xe7   -> varint [0xe7, 0x01]   -> did:key:zQ3s...
//   p256-pub      = 0x1200 -> varint [0x80, 0x24]   -> did:key:zDna...
//
// The pubkey is the 33-byte SEC1 compressed point (0x02/0x03 ++ x). This matches
// @atproto/crypto's formatDidKey. Validated by the P-256 golden round-trip test.

import { base58btc } from 'multiformats/bases/base58'

export const CURVE_SECP256K1 = 'secp256k1'
export const CURVE_P256 = 'p256'

const MULTICODEC = {
  [CURVE_SECP256K1]: 0xe7,
  [CURVE_P256]: 0x1200
}
const BY_CODE = { 0xe7: CURVE_SECP256K1, 0x1200: CURVE_P256 }

function varintEncode (n) {
  const out = []
  while (n >= 0x80) { out.push((n & 0x7f) | 0x80); n >>>= 7 }
  out.push(n)
  return Uint8Array.from(out)
}

function varintDecode (bytes) {
  let n = 0, shift = 0, i = 0
  for (;;) {
    const b = bytes[i++]
    n |= (b & 0x7f) << shift
    if ((b & 0x80) === 0) break
    shift += 7
  }
  return { code: n >>> 0, length: i }
}

// compressed pubkey bytes (33) -> did:key string
export function encodeDidKey (curve, compressedPubkey) {
  const code = MULTICODEC[curve]
  if (code === undefined) throw new Error(`unsupported curve: ${curve}`)
  const prefix = varintEncode(code)
  const bytes = new Uint8Array(prefix.length + compressedPubkey.length)
  bytes.set(prefix, 0)
  bytes.set(compressedPubkey, prefix.length)
  return `did:key:${base58btc.encode(bytes)}` // base58btc.encode already prepends 'z'
}

// did:key string -> { curve, pubkey (compressed bytes) }
export function decodeDidKey (didKey) {
  if (!didKey.startsWith('did:key:')) throw new Error('not a did:key')
  const mb = didKey.slice('did:key:'.length)
  const bytes = base58btc.decode(mb) // expects leading 'z'
  const { code, length } = varintDecode(bytes)
  const curve = BY_CODE[code]
  if (!curve) throw new Error(`unknown multicodec 0x${code.toString(16)}`)
  return { curve, pubkey: bytes.slice(length) }
}