OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-room / lib / label-signer.js

48 lines · javascript ref:
// LabelSigner — the lease-holder's job for a co-authored labeler: materialize the LabelBase intent
// view into signed com.atproto.label records on the label feed. The analogue of signer.js's
// materialize(), but far simpler: a label feed is N INDEPENDENT signed leaves (no MST, no commit
// chain, no rev/prev). Every label is signed with the labeler's ONE #atproto_label key and carries
// src = the labeler DID — so N mods (the StateBase writers) produce ONE labeler identity on the wire.
//
// Because ECDSA here is RFC6979-deterministic, any lease-holder that signs the same intent produces
// byte-identical output — so a lease handoff mid-stream never forks the feed.
//
// This slice owns ONLY the pure materialize + the emit DIFF (which intents are new/changed since last
// emit). Lease-gating (only the holder emits) and the feed-append loop are wired in the daemon slice,
// exactly as signer.js deferred chain-advancement/firehose/lease to later slices.
import { buildLabel, signLabel } from 'atproto-identity/lib/labels.js'

const SEP = '\x00'

// Pure: one label intent { uri, val, neg, cts } -> one signed com.atproto.label under the labeler key.
export function materializeLabel (intent, { src, privateKey }) {
  return signLabel(buildLabel({ src, uri: intent.uri, val: intent.val, cts: intent.cts, neg: intent.neg }), privateKey)
}

export class LabelSigner {
  // src: the labeler DID (every label's src). privateKey: the shared #atproto_label signing key.
  constructor ({ src, privateKey } = {}) {
    if (!src || !privateKey) throw new Error('LabelSigner: src + privateKey required')
    this.src = src
    this.privateKey = privateKey
    this._emitted = new Map() // "uri\x00val" -> "cts\x00neg" last emitted (so we don't re-sign unchanged state)
  }

  // Given the current LabelBase intents (LabelBase.intents()), return the signed labels to APPEND to
  // the feed — those NEW or whose (cts, neg) state changed since the last emit. Updates internal emit
  // state; the caller appends the returned labels to the feed core. Re-emitting the same state is
  // suppressed (idempotent); the consumer's LWW-by-cts makes any re-emit harmless anyway.
  emit (intents) {
    const out = []
    for (const i of intents) {
      if (!i || !i.uri || !i.val) continue
      const key = i.uri + SEP + i.val
      const state = String(i.cts) + SEP + (i.neg ? '1' : '0')
      if (this._emitted.get(key) === state) continue
      this._emitted.set(key, state)
      out.push(materializeLabel(i, { src: this.src, privateKey: this.privateKey }))
    }
    return out
  }
}