OverviewFilesCommitsIssuesDocsReleasesPeers

favela-peer / test / helpers.js

84 lines · javascript ref:
// Test helpers — minting attested device credentials.
//
// Every feed is owned by an identity now, so a test that wants a feed needs a device credential:
// `{ keyPair, proof }`, where the proof chains the keypair to an identity root. That is the same
// shape production uses — the daemon holds exactly this and never the mnemonic.
//
// ⚠ These mint a THROWAWAY identity per call. Two feeds built from separate `identity()` calls are
// owned by different people, which is usually what a test means; two devices from ONE identity are
// two devices of one owner, which is what a roster test means. The distinction matters and is easy
// to get backwards.

import crypto from 'hypercore-crypto'
import b4a from 'b4a'
import IdentityKey from 'keet-identity-key'

const hex = (k) => b4a.toString(k, 'hex')

/**
 * A whole identity, and the devices under it.
 *
 * ⛔ A DEVICE is a real Ed25519 keypair — one that can sign, so it can attest the next device with
 * no mnemonic. That is the shape pear-git-v3's DeviceLinker ships over blind-pairing
 * ({ deviceSecretKey, deviceProof }, never the root), and it is why linking a second machine does
 * not need the vault.
 *
 * A hypercore WRITER key is a different thing entirely: manifest-derived, no secret half, cannot
 * sign. It is bound to a device by `bind()`, as attested DATA.
 */
export async function identity () {
  const idk = await IdentityKey.from({ mnemonic: IdentityKey.generateMnemonic() })
  const root = idk.identityKeyPair

  const mk = (keyPair, proof) => ({
    keyPair,
    hex: hex(keyPair.publicKey),
    proof: b4a.toString(proof, 'base64'),
    /** Bind a writer core key to this device — what an `add-device` op actually carries. */
    bind (writerKeyHex) {
      return b4a.toString(
        IdentityKey.attestData(b4a.from(writerKeyHex, 'hex'), keyPair, b4a.from(proof)), 'base64')
    },
    /** Attest a NEW device from this one. No mnemonic — the blind-pairing path. */
    async child () {
      const kp = crypto.keyPair()
      return mk(kp, IdentityKey.attestDevice(kp.publicKey, keyPair, b4a.from(proof)))
    }
  })

  return {
    rootHex: hex(root.publicKey),
    /** A device attested directly by the root — what the vault mints, once. */
    async device () {
      const kp = crypto.keyPair()
      return mk(kp, await IdentityKey.bootstrap({ identity: root }, kp.publicKey))
    }
  }
}

/** Found a feed under a throwaway identity: a device, bound to that feed's writer key. */
export async function attest (feed) {
  const d = await (await identity()).device()
  return d.bind(feed.deviceKey)
}

/**
 * Bound proofs for a fixed set of WRITER keys, all under ONE device of ONE identity.
 *
 * For the unit tests that drive `applyOp` with constant keys like `'d1'.repeat(32)`. Those stand in
 * for writer core keys, which is exactly right: they are opaque, they never sign, and a proof binds
 * one as attested data.
 *
 * ⚠ Keys minted here share an owner AND a device. A key meant to be a STRANGER must come from a
 * separate `identity()`, or a test named "a stranger cannot X" is quietly testing a co-owner.
 */
export async function proofsFor (keys) {
  const id = await identity()
  const d = await id.device()
  const out = { __root: id.rootHex, __device: d.hex }
  for (const k of keys) out[k] = d.bind(k)
  return out
}

export default { identity, attest, proofsFor }