reaching_link/
memory.rs

1// SPDX-FileCopyrightText: 2023—2025 eaon <eaon@posteo.net>
2// SPDX-License-Identifier: EUPL-1.2
3
4use fn_dsa_kgen::KeyPairGenerator;
5use ml_kem::{EncodedSizeUser, KemCore};
6use rand_core::CryptoRngCore;
7use time::OffsetDateTime;
8
9use reach_aliases::*;
10use reach_core::{
11    ParticipantPublicKeys, ParticipantType, RandomFromRng,
12    macros::digestible,
13    memory::{
14        Message, MessageChunk, MessageMetadata, MessageOrder, ReachingKeys, ReachingPublicKeys,
15        ReachingVerifyingKeys, ReplyData,
16    },
17    wire::Salts,
18};
19use reach_encryption::{Ciphertext, HintTaker, PublicKeyDecrypter, PublicKeyEncrypted};
20use reach_passphrase::{error::PassphraseError, passphrase_seeded_rng};
21use reach_proc_macros::*;
22use reach_signatures::{Sign, Signable, VerifyingKeys};
23
24#[derive(SigningKeys, VerifyingKeys)]
25pub struct ReachingSigningKeys {
26    pub ec_signing_key: Ed25519Signing,
27    pub pq_signing_key: FnDsaSigning,
28    pub pq_verifying_key: FnDsaVerifying,
29}
30
31impl ReachingSigningKeys {
32    pub fn from_passphrase<W>(
33        passphrase: &str,
34        wordlist: &W,
35        salts: &Salts,
36    ) -> Result<Self, PassphraseError>
37    where
38        W: AsRef<[&'static str]>,
39    {
40        let mut signing_keys_rng = passphrase_seeded_rng(
41            passphrase,
42            &salts.reaching_static,
43            &salts.reaching_static,
44            wordlist,
45        )?;
46
47        Ok(Self::random_from_rng(&mut signing_keys_rng))
48    }
49}
50
51impl VerifyingKeys<ReachingVerifyingKeys> for ReachingSigningKeys {}
52
53#[derive(ParticipantSecretKeys, SecretKeysRandomFromRng, UnsignedPublicKeys)]
54pub struct ReachingSecretKeys {
55    pub ec_secret_key: X25519Secret,
56    pub pq_secret_key: MlKemSecret,
57    pub pq_public_key: MlKemPublic,
58}
59
60impl<A> PublicKeyDecrypter<A> for ReachingSecretKeys where A: PublicKeyEncrypted + Ciphertext {}
61impl HintTaker for ReachingSecretKeys {}
62
63impl ReachingSecretKeys {
64    pub fn from_passphrase<W>(
65        passphrase: &str,
66        wordlist: &W,
67        salts: &Salts,
68    ) -> Result<(Self, Self), PassphraseError>
69    where
70        W: AsRef<[&'static str]>,
71    {
72        let mut current_rng = passphrase_seeded_rng(
73            passphrase,
74            &salts.reaching_static,
75            &salts.reaching_current,
76            wordlist,
77        )?;
78        let mut previous_rng = passphrase_seeded_rng(
79            passphrase,
80            &salts.reaching_static,
81            &salts.reaching_previous,
82            wordlist,
83        )?;
84
85        Ok((
86            Self::random_from_rng(&mut current_rng),
87            Self::random_from_rng(&mut previous_rng),
88        ))
89    }
90
91    pub fn unsigned_public_keys(&self) -> UnsignedReachingPublicKeys {
92        UnsignedReachingPublicKeys::from(self)
93    }
94}
95
96pub struct UnsignedReachingPublicKeys {
97    pub ec_public_key: X25519Public,
98    pub pq_public_key: MlKemPublic,
99}
100
101digestible!(UnsignedReachingPublicKeys, public_keys);
102
103impl ParticipantPublicKeys for UnsignedReachingPublicKeys {
104    fn ec_public_key(&self) -> &X25519Public {
105        &self.ec_public_key
106    }
107
108    fn pq_public_key(&self) -> &MlKemPublic {
109        &self.pq_public_key
110    }
111
112    fn participant_type(&self) -> ParticipantType {
113        ParticipantType::Reaching
114    }
115}
116
117impl Signable for UnsignedReachingPublicKeys {
118    type SignedType = ReachingPublicKeys;
119
120    fn with_signature(
121        self,
122        ec_signature: Ed25519Signature,
123        pq_signature: FnDsaSignature,
124    ) -> ReachingPublicKeys {
125        ReachingPublicKeys {
126            ec_public_key: self.ec_public_key,
127            pq_public_key: self.pq_public_key,
128            ec_signature,
129            pq_signature,
130        }
131    }
132}
133
134pub struct ReachingSecrets {
135    pub signing_keys: ReachingSigningKeys,
136    pub secret_keys_current: ReachingSecretKeys,
137    pub secret_keys_previous: ReachingSecretKeys,
138}
139
140impl ReachingSecrets {
141    pub fn from_passphrase<W>(
142        passphrase: &str,
143        wordlist: &W,
144        salts: &Salts,
145    ) -> Result<Self, PassphraseError>
146    where
147        W: AsRef<[&'static str]>,
148    {
149        let signing_keys = ReachingSigningKeys::from_passphrase(passphrase, wordlist, salts)?;
150        let (secret_keys_current, secret_keys_previous) =
151            ReachingSecretKeys::from_passphrase(passphrase, wordlist, salts)?;
152
153        Ok(Self {
154            signing_keys,
155            secret_keys_current,
156            secret_keys_previous,
157        })
158    }
159
160    pub fn reaching_keys(&self, csprng: &mut impl CryptoRngCore) -> ReachingKeys {
161        ReachingKeys {
162            verifying_keys: ReachingVerifyingKeys::from(&self.signing_keys),
163            public_keys: self
164                .signing_keys
165                .sign(self.secret_keys_current.unsigned_public_keys(), csprng),
166        }
167    }
168
169    pub fn previous_public_keys(&self) -> UnsignedReachingPublicKeys {
170        self.secret_keys_previous.unsigned_public_keys()
171    }
172}
173
174pub fn new_contact(content: String, language: String, reaching_keys: &ReachingKeys) -> Message {
175    let metadata = MessageMetadata {
176        order: MessageOrder::Datetime(
177            OffsetDateTime::now_local().unwrap_or(OffsetDateTime::now_utc()),
178        ),
179        reply_data: ReplyData::ReachingPublicKeys(Box::new(reaching_keys.public_keys.clone())),
180        envelope_link: None,
181    };
182
183    Message {
184        metadata,
185        chunk: MessageChunk::text_only(content, language),
186        additional_chunks: vec![],
187    }
188}