From 3d7230d952aa4a71a1aeb64fee9841d16b70ed91 Mon Sep 17 00:00:00 2001 From: tv Date: Sat, 7 Nov 2026 22:35:21 +0100 Subject: access: signatures required "signatures": "required": a change counts only once a signature by its committer vouches for it, and waits until then; every node then signs every change it makes. Auth plan M6. --- src/document/replica.rs | 15 ++++++++++++++- src/document/signatures.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) (limited to 'src/document') diff --git a/src/document/replica.rs b/src/document/replica.rs index a409969..cdf789c 100644 --- a/src/document/replica.rs +++ b/src/document/replica.rs @@ -229,6 +229,19 @@ impl Replica { pub(crate) fn logged( &mut self, f: impl FnOnce(&mut Document, &mut automerge::PatchLog) -> (T, bool), + ) -> T { + self.logged_signed(|doc, _, log| f(doc, log)) + } + + /// [`logged`](Self::logged), `f` given the document's signatures too + /// (`access::screen`, judging received changes, checks theirs). + pub(crate) fn logged_signed( + &mut self, + f: impl FnOnce( + &mut Document, + &mut super::signatures::Signatures, + &mut automerge::PatchLog, + ) -> (T, bool), ) -> T { use crate::locks::MutexExt; let before = sorted_heads(&self.doc); @@ -238,7 +251,7 @@ impl Replica { // lead here. *log = Log::at(self.epoch_seq, before); } - let (out, whole) = f(&mut self.doc, &mut log.patches); + let (out, whole) = f(&mut self.doc, &mut self.signatures, &mut log.patches); let after = sorted_heads(&self.doc); if whole { log.to = after; diff --git a/src/document/signatures.rs b/src/document/signatures.rs index 47e5917..63b19a5 100644 --- a/src/document/signatures.rs +++ b/src/document/signatures.rs @@ -340,6 +340,54 @@ impl Signatures { (signatures, next) } + /// Received signatures waiting for their changes (`offer`) checked against + /// `changes`, not in the document yet, each made by its committer (`committers`, as + /// `access::screen` finds them): one valid vouches for its actor's changes up to it, + /// these among them. + pub(crate) fn vouch_batch(&mut self, changes: &[super::Change], committers: &[Option]) { + for (change, committer) in changes.iter().zip(committers) { + let hash = change.hash(); + let Some(signature) = self.pending.remove(&hash) else { + continue; + }; + let valid = committer + .as_deref() + .and_then(crate::identity::public_of) + .is_some_and(|key| crate::identity::verify(&key, &message(&hash), &signature)); + if !valid { + continue; + } + self.insert(hash, signature); + let actor = change.actor_id(); + if self + .latest + .get(actor) + .is_none_or(|latest| latest.seq < change.seq()) + { + self.stamp += 1; + self.latest.insert( + actor.clone(), + Latest { + seq: change.seq(), + hash, + stamp: self.stamp, + }, + ); + } + } + } + + /// Signs this node's latest change in `doc` with `key` if no signature of it is kept: + /// after a switch or a rejoin, the changes it replayed there (`remake::delta`), made + /// without one, vouched for at once. + pub(crate) fn sign_own_latest(&mut self, doc: &Document, key: &crate::identity::NodeKey) { + if let Some(change) = doc.get_last_local_change() + && !self.by_change.contains_key(&change.hash()) + { + self.sign(doc, change.hash(), key); + } + } + /// 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