From 40907ae285317eaaf6449ff17d40b58a1a5df665 Mon Sep 17 00:00:00 2001 From: tv Date: Sun, 8 Nov 2026 13:48:01 +0000 Subject: screen: a signature vouches for its change and what it builds on Review finding 5: vouching by (actor, seq) isn't bound to a hash. A member can make a change under another member's actor at a seq that member's signature covers. The change is then taken as vouched, and if it breaks a rule, the honest member is refused as a whole and logged as the one whose own change broke the rules. A signature now vouches for its change and the changes of its actor it builds on: in the document, as before; among received changes, those it reaches through the batch's dependencies. A received change under an actor at a seq whose change the document holds (a signature verified up to it) is a fork: refused as forged, never as its actor's node's, not told back in REFUSALS, and logged with the peer that brought it. --- src/document/signatures.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) (limited to 'src/document') diff --git a/src/document/signatures.rs b/src/document/signatures.rs index 5d8731b..19c13b8 100644 --- a/src/document/signatures.rs +++ b/src/document/signatures.rs @@ -4,8 +4,11 @@ //! //! One signature vouches for more than its change: a change's hash covers its //! dependencies, and a node's change builds on its previous one, so a signed change of -//! an actor vouches for all that actor's changes up to it (`check`: the actor and seq -//! it verifies up to). So not every change needs one: a change is signed when it +//! an actor vouches for the changes of that actor it builds on: in the document, all +//! up to it (`check`: the actor and seq it verifies up to); received, not in it yet, +//! those among them it builds on (`access::screen`). Only those: anyone can make a +//! change under any actor, and one at a seq whose change the document holds is a fork +//! (`forks`). So not every change needs one: a change is signed when it //! touches protocol data (`_continuum`) or a path the swarm lists as signed //! (`_continuum/access/signed`, `listed`), or when its write asks for it (`Sign`); a //! change left unsigned is vouched for by the node's next signed one, which, as @@ -388,6 +391,22 @@ impl Signatures { } } + /// Whether a valid signature of `hash` is kept: it vouches for its change and the + /// changes of its actor it builds on. + pub(crate) fn signed(&self, hash: &ChangeHash) -> bool { + self.by_change.contains_key(hash) + } + + /// Whether `change`, not in `doc`, forks its actor's changes: `doc` holds a change + /// a signature verified of that actor at a seq as late, so its real change at + /// `change`'s seq too. Anyone can make a change under any actor; only its node can + /// sign one. + pub(crate) fn forks(&self, doc: &Document, change: &super::Change) -> bool { + self.latest.get(change.actor_id()).is_some_and(|latest| { + latest.seq >= change.seq() && doc.get_change_meta_by_hash(&latest.hash).is_some() + }) + } + /// Whether a signature vouches for `actor`'s change `seq`. pub(crate) fn verified(&self, actor: &super::ActorId, seq: u64) -> bool { self.latest -- cgit v1.3.1