OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-identity / test / binding.test.js

44 lines · javascript ref:
// The binding: one mnemonic -> keet Ed25519 identity + atproto secp256k1/did:plc.
// Async (keet-identity-key is async). Confirms both halves come from one phrase and
// are mutually consistent + deterministic.

import test from 'brittle'
import { deriveIdentity, bindingRecord, deriveAtprotoKey } from '../lib/binding.js'

const MNEMONIC = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
// keet's Ed25519 identity pubkey for this phrase (captured from keet-identity-key).
const KEET_ED25519 = 'de1ab94806ced05c64c0098de00ffadae052351b9fbdb6be4d645123ac9d8798'
const OPTS = { handle: 'alice.7rnx.net', pdsUrl: 'https://pds.7rnx.net' }

test('mnemonic binds keet Ed25519 identity to atproto secp256k1/did:plc', async (t) => {
  const id = await deriveIdentity(MNEMONIC, OPTS)

  // Ed25519 half comes from keet-identity-key, for this exact phrase.
  t.ok(id.ed25519PublicKey.length === 32 && Buffer.from(id.ed25519PublicKey).toString('hex') === KEET_ED25519,
    'ed25519 identity key is keet-derived (32 bytes, matches)')

  // secp256k1 half is consistent with the standalone atproto derivation (same seed).
  const standalone = deriveAtprotoKey(MNEMONIC)
  t.ok(Buffer.from(id.secp256k1PublicKey).equals(Buffer.from(standalone.publicKey)),
    'secp256k1 half matches standalone deriveAtprotoKey')
  t.ok(id.didKey.startsWith('did:key:zQ3s'), 'atproto did:key is secp256k1 (zQ3s…)')
  t.ok(/^did:plc:[a-z2-7]{24}$/.test(id.did), 'did:plc has the expected shape')

  // One phrase -> both keys, deterministically.
  const id2 = await deriveIdentity(MNEMONIC, OPTS)
  t.is(id.did, id2.did, 'same mnemonic -> same did:plc')
  t.ok(Buffer.from(id.ed25519PublicKey).equals(Buffer.from(id2.ed25519PublicKey)), 'same mnemonic -> same ed25519')
  t.ok(!Buffer.from(id.ed25519PublicKey).equals(Buffer.from(id.secp256k1PublicKey)),
    'the two keys differ (distinct curves/roles)')

  // JSON-safe binding record for the hyper-identity provider.
  const rec = await bindingRecord(MNEMONIC, OPTS)
  t.is(JSON.stringify(rec), JSON.stringify(JSON.parse(JSON.stringify(rec))), 'binding record is JSON round-trippable')
  t.is(rec.provider, 'atproto', 'record: provider=atproto')
  t.is(rec.ed25519PublicKey.length, 64, 'record: ed25519 hex is 64 chars')
  t.is(rec.secp256k1PublicKey.length, 66, 'record: secp256k1 hex is 66 chars (compressed)')
  t.ok(rec.did === id.did && rec.handle === OPTS.handle, 'record: did + handle carried')
  t.comment(`binding: ${rec.did}  handle: ${rec.handle}`)
  t.comment(`ed25519(keet): ${rec.ed25519PublicKey.slice(0, 16)}…  secp256k1(atproto): ${rec.secp256k1PublicKey.slice(0, 16)}…`)
})