iris_chat_protocol/protocol_engine/
engine_handshake_proof.rs1use super::*;
2
3const HANDSHAKE_OWNER_PROOF_TAG: &str = "owner-proof";
6const HANDSHAKE_OWNER_PROOF_MAX_BYTES: usize = 32 * 1024;
7
8impl ProtocolEngine {
9 pub fn signed_local_device_authorization(&self) -> Option<bool> {
11 self.invite_owner_app_keys_evidence
12 .get(&self.local_owner)
13 .map(|_| self.local_handshake_owner_proof().is_some())
14 }
15
16 pub(super) fn local_handshake_owner_proof(&self) -> Option<&Event> {
17 let ProtocolAppKeysEvidence::Verified(event) =
18 self.invite_owner_app_keys_evidence.get(&self.local_owner)?
19 else {
20 return None;
21 };
22 let device = public_device(self.local_device).ok()?;
23 self.invite_owner_exact_app_keys_membership(self.local_owner, device)
24 .filter(|authorized| *authorized)?;
25 Some(event)
26 }
27
28 pub fn ingest_invite_response_owner_proof(
32 &mut self,
33 invite: &Invite,
34 response: &Event,
35 claimed_owner: PublicKey,
36 authenticated_device: PublicKey,
37 ) -> anyhow::Result<ProtocolRetryBatch> {
38 let Some(proof) = handshake_owner_proof(invite, response)
39 .filter(|proof| proof.pubkey == claimed_owner)
40 .filter(|proof| {
41 AppKeys::from_event(proof)
42 .is_ok_and(|keys| keys.get_device(&authenticated_device).is_some())
43 })
44 else {
45 return Ok(ProtocolRetryBatch::default());
46 };
47 self.ingest_app_keys_event(&proof)
50 }
51}
52
53pub(super) fn invite_response_with_owner_proof(
54 response: &nostr_double_ratchet::InviteResponseEnvelope,
55 proof: Option<&Event>,
56) -> anyhow::Result<Event> {
57 let event = invite_response_event(response)?;
58 let Some(proof) = proof else {
59 return Ok(event);
60 };
61 let json = serde_json::to_string(proof)?;
62 anyhow::ensure!(
63 json.len() <= HANDSHAKE_OWNER_PROOF_MAX_BYTES,
64 "owner proof too large"
65 );
66 let keys = Keys::new(nostr::SecretKey::from_slice(&response.signer_secret_key)?);
67 let encrypted = nostr::nips::nip44::encrypt(
68 keys.secret_key(),
69 &public_device(response.recipient)?,
70 json,
71 nostr::nips::nip44::Version::V2,
72 )?;
73 Ok(nostr::EventBuilder::new(event.kind, event.content.clone())
74 .tags(event.tags.iter().cloned())
75 .tag(nostr::Tag::parse([
76 HANDSHAKE_OWNER_PROOF_TAG,
77 encrypted.as_str(),
78 ])?)
79 .custom_created_at(event.created_at)
80 .sign_with_keys(&keys)?)
81}
82
83pub(super) fn handshake_owner_proof(invite: &Invite, response: &Event) -> Option<Event> {
84 let envelope = parse_invite_response_event(response).ok()?;
86 if envelope.recipient != invite.inviter_ephemeral_public_key {
87 return None;
88 }
89 let mut tags = response.tags.iter().filter(|tag| {
90 tag.as_slice()
91 .first()
92 .is_some_and(|name| name == HANDSHAKE_OWNER_PROOF_TAG)
93 });
94 let tag = tags.next()?.as_slice();
95 if tag.len() != 2 || tags.next().is_some() || tag[1].len() > HANDSHAKE_OWNER_PROOF_MAX_BYTES * 2
96 {
97 return None;
98 }
99 let secret = nostr::SecretKey::from_slice(&invite.inviter_ephemeral_private_key?).ok()?;
100 let json = nostr::nips::nip44::decrypt(&secret, &response.pubkey, &tag[1]).ok()?;
101 if json.len() > HANDSHAKE_OWNER_PROOF_MAX_BYTES {
102 return None;
103 }
104 let proof: Event = serde_json::from_str(&json).ok()?;
105 app_keys_event_is_acceptable_for_owner(proof.pubkey, &proof, unix_now().get()).then_some(proof)
106}