reaching_link/
lib.rs

1// SPDX-FileCopyrightText: 2023—2025 eaon <eaon@posteo.net>
2// SPDX-FileCopyrightText: 2024 Gonzalo Bulnes Guilpain <gon.bulnes@fastmail.com>
3// SPDX-License-Identifier: EUPL-1.2
4
5use std::ops::Deref;
6
7use wasm_bindgen::prelude::*;
8use zeroize::Zeroizing;
9
10use reach_core::{
11    ProstDecode, RandomFromRng,
12    communication::{Communicable, CommunicableOwned, Communication},
13    memory::{
14        Key, Message, MessageVaultLink, MessageVaultPassport, VerifyingKeys, shuffle_participants,
15    },
16    wire::{
17        AttestantVerifyingKeys, Envelope, Init, MessageVault, Reach, Salts, SealedMessageVaultId,
18        verify_signature_chains,
19    },
20};
21use reach_encryption::{
22    DecryptableWithNonce, Encryptable, HintTaker, Nonce, authenticated_encrypt_key,
23    build_envelope_seed,
24};
25use reach_passphrase::WORDLIST_EN_V0;
26use reach_signatures::Verifiable;
27use reach_visual_key_identity::visual_key_identity;
28
29pub mod intermediary;
30pub use intermediary::*;
31
32pub mod memory;
33use memory::*;
34
35pub mod utils;
36
37type Request = Communication<reach_core::wire::Request>;
38type Response = Communication<reach_core::wire::Response>;
39
40#[wasm_bindgen]
41pub fn set_attestant_verifying_keys(state: &mut State, bytes: Box<[u8]>) -> Result<(), LinkError> {
42    state.attestant_verifying_keys = Some(AttestantVerifyingKeys::decode(bytes)?);
43
44    Ok(())
45}
46
47#[wasm_bindgen]
48pub fn set_salts(state: &mut State, bytes: Box<[u8]>) -> Result<(), LinkError> {
49    let attestant_verifying_keys = get!(
50        &state.attestant_verifying_keys,
51        "Attestant Verifying Keys not set"
52    )?;
53    let salts = Salts::decode(bytes)?;
54
55    salts
56        .verify(attestant_verifying_keys)
57        .then(|| {
58            state.salts = Some(salts);
59        })
60        .ok_or("Could not verify Salts")?;
61
62    Ok(())
63}
64
65#[wasm_bindgen]
66pub fn set_language(state: &mut State, language: &str) {
67    state.language = language.to_string();
68}
69
70#[wasm_bindgen]
71pub fn get_attestant_visual_key_identity(state: &mut State) -> Result<String, LinkError> {
72    let attestant_verifying_keys = get!(
73        &state.attestant_verifying_keys,
74        "Attestant Verifying Keys not set"
75    )?;
76    let salts = get!(&state.salts, "Salts not set")?;
77
78    Ok(visual_key_identity(
79        attestant_verifying_keys,
80        &salts.attestant_identity,
81    )?)
82}
83
84#[wasm_bindgen]
85pub fn validate_passphrase(state: &mut State, passphrase: String) -> Result<(), LinkError> {
86    let passphrase = Zeroizing::new(passphrase);
87
88    let salts = get!(&state.salts, "Salts not set")?;
89
90    reach_passphrase::validate_passphrase(&passphrase, &salts.reaching_static, WORDLIST_EN_V0)
91        .map_err(|e| format!("{e:?}"))?;
92
93    state.passphrase = Some(passphrase);
94
95    Ok(())
96}
97
98#[wasm_bindgen]
99pub fn derive_secrets(state: &mut State) -> Result<(), LinkError> {
100    get!(
101        &state.attestant_verifying_keys,
102        "Attestant Verifying Keys not set"
103    )?;
104    let salts = get!(&state.salts, "Salts not set")?;
105    let passphrase = get!(&state.passphrase, "passhprase not set")?;
106
107    let secrets = ReachingSecrets::from_passphrase(passphrase, &WORDLIST_EN_V0, salts)
108        .map_err(|e| format!("{e:?}"))?;
109
110    let reaching_keys = secrets.reaching_keys(&mut state.csprng);
111    let previous_public_keys = secrets.previous_public_keys();
112
113    state.passphrase = None;
114    state.keys = Some((secrets, reaching_keys, previous_public_keys));
115
116    Ok(())
117}
118
119#[wasm_bindgen]
120pub fn generate_new_passphrase(state: &mut State) -> Result<String, LinkError> {
121    get!(
122        &state.attestant_verifying_keys,
123        "Attestant Verifying Keys not set"
124    )?;
125    let salts = get!(&state.salts, "Salts not set")?;
126
127    let passphrase = reach_passphrase::generate_passphrase(
128        WORDLIST_EN_V0,
129        &salts.reaching_static,
130        &mut state.csprng,
131    );
132    state.passphrase = Some(passphrase.clone());
133
134    Ok(passphrase.deref().clone())
135}
136
137#[wasm_bindgen]
138pub fn reach_init(_: &mut State) -> Result<Vec<u8>, LinkError> {
139    Ok(Init(0).to_communication().encode())
140}
141
142#[wasm_bindgen]
143pub fn reach(state: &mut State, bytes: Box<[u8]>) -> Result<Vec<LinkHintedEnvelopeId>, LinkError> {
144    let attestant_verifying_keys = get!(
145        &state.attestant_verifying_keys,
146        "Attestant Verifying Keys not set"
147    )?;
148    let salts = get!(&state.salts, "Salts not set")?;
149    let (secrets, _, _) = get!(&state.keys, "Reaching Secrets not set")?;
150
151    let communication = Response::decode(bytes)?;
152    let reach = Reach::try_from_communication(&communication)?;
153
154    verify_signature_chains(attestant_verifying_keys, &reach.reachable_signed_keys)?;
155
156    state
157        .reachable_signed_keys
158        .push_back(reach.reachable_signed_keys);
159
160    let hinted_envelope_ids_previous = secrets
161        .secret_keys_previous
162        .take_all_the_hints(&reach.envelope_id_hints, salts)?;
163    let hinted_envelope_ids_current = secrets
164        .secret_keys_current
165        .take_all_the_hints(&reach.envelope_id_hints, salts)?;
166
167    let mut hei_objects = Vec::new();
168
169    for hei in hinted_envelope_ids_previous
170        .iter()
171        .chain(hinted_envelope_ids_current.iter())
172    {
173        hei_objects.push(LinkHintedEnvelopeId::from(hei));
174        state.envelope_ids.insert(hei.envelope_id);
175    }
176
177    Ok(hei_objects)
178}
179
180#[wasm_bindgen]
181pub fn open_envelope(state: &mut State, bytes: Box<[u8]>) -> Result<Vec<u8>, LinkError> {
182    let salts = get!(&state.salts, "Salts not set")?;
183    let (secrets, reaching_keys, previous_public_keys) =
184        get!(&state.keys, "Reaching Secrets not set")?;
185
186    let communication = Response::decode(bytes)?;
187    let envelope = Envelope::try_from_communication(&communication)?;
188
189    let opened_envelope = reach_encryption::open_envelope(
190        &secrets.secret_keys_current,
191        &reaching_keys.public_keys,
192        &envelope,
193        salts,
194    )
195    .or(reach_encryption::open_envelope(
196        &secrets.secret_keys_previous,
197        previous_public_keys,
198        &envelope,
199        salts,
200    ))?;
201
202    let message_vault_id = match opened_envelope.message_vault_passport.message_vault_link {
203        MessageVaultLink::Link(message_vault_id) => Ok(message_vault_id),
204        MessageVaultLink::SealedLink(_) => Err("Encountered unexpected MessageLink::SealedLink"),
205    }?;
206
207    state
208        .envelopes
209        .insert(message_vault_id, (envelope, opened_envelope));
210
211    let mvi: Request = message_vault_id.to_communication();
212
213    Ok(mvi.encode())
214}
215
216#[wasm_bindgen]
217pub fn open_message_vault(state: &mut State, bytes: Box<[u8]>) -> Result<LinkMessage, LinkError> {
218    let communication = Response::decode(bytes)?;
219    let message_vault = MessageVault::try_from_communication(&communication)?;
220
221    let (envelope, opened_envelope) = state
222        .envelopes
223        .get(&message_vault.id)
224        .ok_or("No envelopes found for this message vault id")?;
225
226    let message = Message::decrypt_with_nonce(
227        &opened_envelope.credentials.key,
228        envelope.credential_vaults.nonce(),
229        &message_vault,
230    )?;
231
232    let link_message = LinkMessage::try_from(&message)?;
233    state.messages.push(message);
234
235    Ok(link_message)
236}
237
238#[wasm_bindgen]
239pub fn new_message_vault(state: &mut State, content: String) -> Result<Vec<u8>, LinkError> {
240    let salts = get!(&state.salts, "Salts not set")?;
241    let (secrets, reaching_keys, _) = get!(&state.keys, "Reaching Secrets not set")?;
242
243    let reachable_signed_keys = state
244        .reachable_signed_keys
245        .pop_front()
246        .ok_or("No Reachable Signed Keys available")?;
247
248    let msg = new_contact(content, state.language.clone(), reaching_keys);
249
250    let key = Key::random_from_rng(&mut state.csprng);
251    let credential_vaults = authenticated_encrypt_key(
252        &key,
253        &secrets.signing_keys,
254        &reaching_keys.verifying_keys,
255        &shuffle_participants(&reachable_signed_keys, None, &mut state.csprng),
256        salts,
257        &mut state.csprng,
258    )?;
259    let nonce = credential_vaults.nonce();
260
261    let message_vault_seed = msg
262        .encrypt_owned_with_nonce(&key, nonce)?
263        .to_communication();
264
265    state.envelope_seed_parts = Some((reachable_signed_keys, key, credential_vaults));
266
267    Ok(message_vault_seed.encode())
268}
269
270#[wasm_bindgen]
271pub fn new_envelope_seed(state: &mut State, bytes: Box<[u8]>) -> Result<Vec<u8>, LinkError> {
272    let ongoing_communication = state.envelope_seed_parts.take();
273    let (reachable_signed_keys, key, credential_vaults) =
274        get!(ongoing_communication, "Communication not initialised")?;
275
276    let (_, reaching_keys, _) = get!(&state.keys, "Reaching Secrets not set")?;
277
278    let communication = Response::decode(bytes)?;
279    let sealed_message_vault_id = SealedMessageVaultId::try_from_communication(&communication)?;
280
281    let message_vault_passport = MessageVaultPassport {
282        message_vault_link: MessageVaultLink::SealedLink(Box::new(sealed_message_vault_id)),
283        verifying_keys: VerifyingKeys::Reaching(reaching_keys.verifying_keys.clone()),
284    };
285
286    let envelope_seed: Request = build_envelope_seed(
287        message_vault_passport,
288        &reachable_signed_keys,
289        &key,
290        credential_vaults,
291        &mut state.csprng,
292    )?
293    .to_communication();
294
295    Ok(envelope_seed.encode())
296}