You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: canton/README.md
+36-17Lines changed: 36 additions & 17 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -529,23 +529,36 @@ the digest — verify + consume + act in one transaction.
529
529
530
530
Properties:
531
531
532
-
-**No archive-and-replay, no shadow roots.** Archival alone only bricks
533
-
(fail-closed); the replay vector is node *re-creation*. Nodes are co-signed,
534
-
so neither the consumer nor the operator can archive or create one alone;
535
-
splits inherit both signatures from the archived parent (the operator is not
536
-
involved per consume). Roots are granted exactly once per consumer through
537
-
the operator's `ReplayRootRegistry` — a *positive*, on-ledger
538
-
one-root-per-consumer check (the registry carries the granted set and every
539
-
grant consumes it), via a one-time propose-accept ceremony
540
-
(`ReplayRootRequest`), mirroring emitter registration. The irreducible
541
-
remainder: consumer and operator *colluding* can rebuild a trie —
542
-
signatories can always unmake their own contracts.
532
+
-**Nobody can touch someone else's scope; your own scope is your own
533
+
problem.** Archival alone only bricks (fail-closed); the replay vector is
534
+
node *re-creation*. Nodes are co-signed, so no third party can archive or
535
+
re-create them (splits inherit both signatures from the archived parent —
536
+
the operator is not involved per consume), and roots come only from the
537
+
operator's `ReplayRootRegistry` — a stateless, never-churning anchor whose
538
+
nonconsuming `ClaimReplayRoot` lends the operator's *inherited* signature
539
+
to a consumer-submitted claim (the `RegisterEmitter` pattern: no crank; the
540
+
operator multisig signs nothing after genesis). Claiming a root for a
541
+
consumer requires **that consumer's authority**, so nobody can fork another
542
+
app's replay scope. There is deliberately *no* one-root-per-consumer
543
+
enforcement: maintaining a single trie is the consumer's own responsibility
544
+
(its disclosure service defines which trie its consumes resolve into, and
545
+
detects forks — more than one covering node — for free). This is EVM
546
+
parity: nobody grants replay storage there either; every integrator owns
547
+
its mapping. A consumer minting parallel roots, like a consumer whose key
548
+
is stolen, only harms itself — and a stolen consumer key defeats the app
549
+
more directly anyway. (If key-compromise resilience is ever wanted, the
550
+
hardening is trie-identity pinning: thread the original root's cid through
551
+
successors via `self` and pin it in the app contract — documented, not
552
+
built.) Spam claims cost the spammer synchronizer traffic per transaction
553
+
and the operator ~300 B of storage per worthless root; no shared contract
554
+
grows, nothing bricks.
543
555
-**Scoping and contention.** Tries are per consumer party: different apps
544
556
consume the same VAA independently. Consumes racing on the *same node*
545
557
conflict — exactly one commits (validator-checked, regardless of submitter);
546
558
the loser re-resolves the covering node and retries. A blind retry of a
547
-
consume that actually committed fails on membership ("VAA already consumed")
548
-
— a clean idempotency signal. Until the first split, all of one app's
559
+
consume that actually committed fails on membership ("digest already
560
+
consumed") — a clean idempotency signal. Until the first split, all of one
561
+
app's
549
562
consumes serialize through its root; 16-way fan-out begins at
550
563
`splitThreshold` consumes.
551
564
-**Cost.**`defaultSplitThreshold = 128`: suffixes are ≤64-char hex (~66 B
@@ -607,7 +620,7 @@ it must serve:
607
620
|`CoreState`| the one true instance | integrators/users exercising §4.3 verify or §4.6 consume | yes — every non-stakeholder submitter | per governance action (rare) |
608
621
|`ReplayNode`| (consumer, prefix covering digest) | every `VerifyAndConsumeVAA` / integrator redeem | yes for non-stakeholders (e.g. end users); the consumer itself only needs the cid | every consume under that node |
609
622
|`EmitterRegistry`| operator |`RegisterEmitter`| yes — the requester submits | per emitter registration |
610
-
|`ReplayRootRegistry`| operator |`ApproveReplayRoot`| no — operator submits, own ACS | per root grant |
623
+
|`ReplayRootRegistry`| operator |`ClaimReplayRoot`| yes — the consumer submits (static blob, cacheable) | never (stateless anchor)|
611
624
|`Emitter`| (operator, owner, emitterId) |`PublishMessage`| no — owner submits, own ACS | per publish |
612
625
| app contracts (e.g. `ExampleIntegrator`) | app-defined | the app's own choices | app's concern (its users are observers) | app-defined |
613
626
@@ -1096,10 +1109,16 @@ the quorum and turn guardian downtime into a chain-halt).
1096
1109
> access to the operator party and its namespace key. Prefer the dedicated
1097
1110
> `guardianObserver`; use operator replica-hosting only as a migration stopgap.
1098
1111
1112
+
Note the operator party's ACTIVE signing surface after genesis is empty: every
0 commit comments