// keet-identity-key device chain — the P2P-verifiable half of device attestation.
//
// Runs in PARALLEL to the atproto PLC authority (device-attestation.js). atproto's
// rotationKeys/PLC-op-log carry the FEDERATION-visible authority (secp256k1); this keet
// chain carries the authority OUR P2P world verifies (Ed25519). Both anchor on the same
// root identity and vouch for the same device.
//
// Shape (from keet-identity-key): a proof is `{ identity, epoch, chain: [{publicKey,
// signature}], data? }`. `bootstrap` attests a device pubkey under the root identity;
// `attestData` lets the (leaf) device sign arbitrary data — here, the atproto DID — so a
// verifier learns "root → device → this device vouches for did:plc:…". `verify` walks the
// chain back to the identity and checks the data signature.
import IdentityKey from 'keet-identity-key'
import b4a from 'b4a'
// The device's keet-world Ed25519 keypair (derived from the device seed) — distinct from
// its secp256k1 atproto signing key. This is the device's identity in the keet chain.
export async function deviceKeetKeyPair (mnemonic, seed) {
const idk = await IdentityKey.from({ mnemonic, seed })
return idk.identityKeyPair // { publicKey, secretKey }
}
// The root identity's Ed25519 public key — the anchor every chain must resolve to.
export async function rootIdentityPublicKey (mnemonic, seed) {
const idk = await IdentityKey.from({ mnemonic, seed })
return b4a.from(idk.identityPublicKey)
}
// Root identity attests a device's Ed25519 public key → an encoded proof chain.
export async function attestDeviceChain ({ rootMnemonic, rootSeed, devicePublicKey }) {
return IdentityKey.bootstrap({ mnemonic: rootMnemonic, seed: rootSeed }, devicePublicKey)
}
// An ALREADY-ATTESTED device attests ANOTHER device, extending the chain: root → parent → child.
// keet's attestDevice signs { epoch, identity, device } with the PARENT's keypair and pushes
// { publicKey, signature } onto the parent's chain, so the result still resolves to the same root
// identity and the same epoch — a chained device passes the identical admission gate and the identical
// minimum-epoch receipt check as a root-attested one.
//
// WHY THIS MATTERS OPERATIONALLY: admitting a new device to a room needs a proof anchored at the room's
// root identity, and attestDeviceChain above needs the ROOT MNEMONIC — which is deliberately not stored
// on any node (config.js: a device "must never hold" it). Read alone, that makes adding a co-owner
// impossible without recovering a secret nobody kept. It is not: an existing attested device already has
// everything required to vouch for the next one. Only the root's PUBLIC key is needed to verify.
//
// parentKeyPair: the attesting device's Ed25519 identity keypair (deviceKeetKeyPair of its mnemonic).
// parentProof: that device's own proof (root-attested, or itself chained — chains nest).
export function attestDeviceViaDevice ({ parentKeyPair, parentProof, devicePublicKey }) {
if (!parentKeyPair || !parentProof || !devicePublicKey) throw new Error('attestDeviceViaDevice: parentKeyPair, parentProof, devicePublicKey required')
// Pass the proof as BYTES: keet decodes a fresh copy, so the caller's proof is never mutated by the
// chain.push inside attestDevice (it would otherwise grow the parent's own proof on every call).
const proof = b4a.isBuffer(parentProof) ? parentProof : b4a.from(parentProof)
return IdentityKey.attestDevice(b4a.from(devicePublicKey), parentKeyPair, proof)
}
// The attested (leaf) device vouches for the atproto DID by signing it as attested data,
// extending the proof with `proof.data`. Result: root → device → vouches-for(did).
export function attestAtprotoBinding ({ deviceKeyPair, did, proof }) {
return IdentityKey.attestData(b4a.from(did), deviceKeyPair, proof)
}
// Verify the full binding: the chain resolves to the root identity AND the device vouched
// for exactly this DID. Returns { receipt, identityPublicKey, devicePublicKey } or null.
export function verifyAtprotoBinding ({ proof, did, expectedIdentity, expectedDevice }) {
return IdentityKey.verify(proof, b4a.from(did), { expectedIdentity, expectedDevice })
}