did-btcr2-js

ADR 008: Aggregation Subsystem Inception

Status: Accepted

Date: 2025-11-12

Commit: 1539e71

Context

did:btcr2 defines three beacon types: singleton (single-party), CAS (content-addressed-store aggregate), and SMT (Sparse Merkle Tree aggregate). Singleton is straightforward: one controller, one beacon address, one signature per update. The aggregate beacons are fundamentally multi-party: many controllers’ updates share a single on-chain signal, announced by a single transaction output signed by a single aggregated signature. Someone: something: has to coordinate that.

Three questions had to be answered together before any code was worth writing:

  1. Should the reference implementation ship aggregation at all, or just the singleton beacon? The spec describes aggregate beacons normatively, but implementing them is a large piece of work. Deferring aggregation would let the library ship a singleton-only MVP. But singleton-only pushes the multi-party coordination problem onto every service operator, each of whom would reinvent the protocol.
  2. What signing scheme? Aggregate beacons need one on-chain signature that represents N participants’ authorization. Options include a k-of-n multisig (Bitcoin-script), a threshold Schnorr (FROST), a plain Schnorr aggregate (MuSig2), or Taproot key-path with MuSig2. Each has dramatically different security, setup, communication, and bandwidth profiles.
  3. What coordination model? Fully decentralized peer-to-peer, a coordinator-orchestrated model, or something between? And how do participants actually talk to each other: over what transport, with what message shape, with what trust model?

The early code in method/src/core/beacon/aggregation/ had partial answers from the spec-draft era, but they were scattered across a handful of files, the coordinator was half-written, the participant was a stub, and messages were loosely typed. The decision window was: either continue the incremental path: which had already drifted enough that the beacon-aggregation branch couldn’t be cleanly merged back to main: or commit to a structured subsystem with clear protocol shape.

Options considered

On shipping aggregation at all:

  1. Defer. Ship singleton-only; leave aggregate beacons for a future release. Operators of aggregate beacons build their own coordination layer. The reference library stays smaller.
  2. Ship now. The reference implementation implements what the spec defines, end-to-end. Operators who want aggregate beacons get a working reference, and downstream spec work can compare against real code.

On signing scheme (assuming we ship aggregation):

  1. k-of-n P2SH/P2WSH multisig. Well-established, Bitcoin-native. But: each signature on-chain (or the script revealing n pubkeys) reveals the cohort size and structure, ballooning transaction size and identifying participants.
  2. FROST / threshold Schnorr. True threshold signing; any t-of-n subset can sign. Cutting-edge but still stabilizing in 2025-2026; protocol implementations are in flux.
  3. MuSig2 (BIP-327) over Taproot key-path. All n participants must sign (no threshold). Aggregates to a single Schnorr signature indistinguishable from a single-signer Schnorr. Well-specified, reviewed, implementable today.

On coordination model:

  1. Fully decentralized P2P. Every participant talks to every other participant; no coordinator. Maximally trust-minimized; worst scaling and worst UX for participant discovery.
  2. Coordinator-orchestrated. A designated coordinator builds the cohort, collects nonces/signatures, and aggregates the result. The coordinator can be self-hosted by the service operator or anyone else; participants stay self-custodial. Good scaling, recognizable UX, but requires clear trust boundaries.

On communication transport:

  1. Bespoke protocol over TCP/WebSockets. Full control, no external dependencies; but reinvents message delivery, storage, relay, and participant identity.
  2. Leverage an existing messaging substrate. Nostr (relay-based, keyed identity, encrypted DMs) and DIDComm (DID-keyed, relay-agnostic, more formal) are both viable. Both have existing client libraries and relay/mediator infrastructure.

Decision

Ship aggregation now (Option 2 on the first question). The reference implementation implements what the spec defines. Downstream implementations (Rust, Python, others) compare against real TypeScript code. Operators wanting aggregate beacons run something rather than improvising.

MuSig2 (BIP-327) over Taproot key-path (Option 3 on scheme). Unique aggregated Schnorr signatures; indistinguishable on-chain from single-signer Schnorr (cohort size and participant set are private); well-specified; implementable with @noble/curves primitives. The “all n must sign” constraint is acceptable for aggregate beacons because a controller who won’t sign simply isn’t in the cohort: participation is opt-in per update round, not per lifetime.

Coordinator/participant model (Option 2 on coordination). One coordinator role, many participant roles. Coordinator builds the cohort from inbound participant opt-ins, distributes the aggregated data for validation, runs the MuSig2 nonce and signing rounds, and broadcasts the resulting Bitcoin transaction. Participants sign only for their own updates, validate the coordinator’s aggregation before authorizing, and contribute MuSig2 nonces and partial signatures. The coordinator holds no signing authority over participants’ keys: participants can refuse to sign if they don’t like what the coordinator assembled. This matches ADR 027’s trust model (don’t trust the coordinator with custodial authority).

Pluggable communication (Option 2 on transport). The initial implementation carries two adapters behind a Transport / communication-service interface:

The Transport seam means a third transport (HTTP, WebSocket, bespoke) can slot in without touching the protocol state machines: which is later exercised by ADR 028.

Subsystem layout at inception:

The protocol has four sequenced phases: keygen (cohort formation, opt-in, aggregated pubkey), update distribution (participants submit updates, coordinator assembles aggregate), signing (MuSig2 nonce to partial sig to aggregated sig), broadcast (final Bitcoin transaction). Each message type in the protocol has its own class; each phase has its own state tracking.

This is the design that ADR 020 later rewrites into the layered AggregationService / AggregationParticipant / AggregationCohort / BeaconSigningSession state machines, and that ADR 027 later hardens. The core shape: coordinator + participants, MuSig2 signing, pluggable transport: was committed to here and survived both rewrites.

Consequences

Positive

Negative

Explicitly accepted trade-offs

References