spaces: leaving a room says so, so an owner stops publishing a seat nobody sits in

This commit is contained in:
KeelrLeek committed 2026-09-04 09:56:39 -05:00
1 parent d6102101af
commit 06baa5a805
3 files changed
+147 -7

No files matched your search

+39
View File
@@ -255,6 +255,24 @@ pub fn join_proof_valid(
crate::verify_sig_in(SIGN_DOMAIN, persona, &join_proof_payload(space_id, owner, room_key), proof)
}
/// What a member signs to give up their seat. Prefixed, so a join proof can
/// never be replayed as a leave, and signed by the key that HOLDS the seat —
/// holding it is the whole authority to vacate it, and the owner needs no
/// second fact to act.
pub fn leave_proof_payload(space_id: &[u8; 16], owner: &[u8; 32], worn: &[u8; 32]) -> Vec<u8> {
let mut v = Vec::with_capacity(5 + 16 + 32 + 32);
v.extend_from_slice(b"leave");
v.extend_from_slice(space_id);
v.extend_from_slice(owner);
v.extend_from_slice(worn);
blake3::hash(&v).as_bytes().to_vec()
}
/// True when whoever sits under `worn` really did ask to leave this Space.
pub fn leave_proof_valid(space_id: &[u8; 16], owner: &[u8; 32], worn: &[u8; 32], proof: &[u8]) -> bool {
crate::verify_sig_in(SIGN_DOMAIN, worn, &leave_proof_payload(space_id, owner, worn), proof)
}
pub fn checkpoint_valid(owner: &[u8; 32], space_id: &[u8; 16], c: &Checkpoint) -> bool {
c.schema_version == SCHEMA_VERSION
&& &c.space_id == space_id
@@ -453,6 +471,27 @@ mod tests {
assert!(!join_proof_valid(&[4; 16], &ok, &room, &mallory, &proof), "anyone could claim it");
}
/// A seat is given up by the key that holds it, and a join proof is not a
/// leave proof — otherwise the owner's own invitation answer could be
/// replayed to unseat the person who sent it.
#[test]
fn a_leave_proof_comes_from_the_seat_it_gives_up() {
let owner = SigningKey::from_bytes(&[1; 32]);
let ok = owner.verifying_key().to_bytes();
let room_secret = [9u8; 32];
let room = member_public(&room_secret);
let proof = sign_as_member(&room_secret, &leave_proof_payload(&[4; 16], &ok, &room));
assert!(leave_proof_valid(&[4; 16], &ok, &room, &proof));
// Not reusable in another Space, and not against another seat.
assert!(!leave_proof_valid(&[5; 16], &ok, &room, &proof), "replayed into another Space");
assert!(!leave_proof_valid(&[4; 16], &ok, &member_public(&[8; 32]), &proof), "bound to the wrong seat");
// A join proof carries a different payload, so it cannot stand in.
let bob_persona = SigningKey::from_bytes(&[2; 32]);
let bp = bob_persona.verifying_key().to_bytes();
let join = sign(&bob_persona, &join_proof_payload(&[4; 16], &ok, &bp));
assert!(!leave_proof_valid(&[4; 16], &ok, &bp, &join), "a join proof unseated its own sender");
}
#[test]
fn roster_is_the_authority_and_removal_is_an_epoch() {
let owner = SigningKey::from_bytes(&[1; 32]);