Skip to main content

coven_crypto/
confirmation_number.rs

1//! The six digits an invite's requester and admin compare (§13.2, D11).
2
3use crate::{cipher, derivation::CONFIRMATION_NUMBER, MemberPublicKeys};
4use coven_foundation::id_source::InviteId;
5use sha2::{Digest, Sha256};
6use std::fmt;
7
8/// Six digits naming a join request's keys, which the requester's device and
9/// the admin's both compute (§13.2). It names only the keys and the invite:
10/// the invite secret adds nothing a stranger who saw the code lacks, and the
11/// device name is the requester's free choice. Displays as `041 778`.
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13pub struct ConfirmationNumber(pub u32);
14
15impl ConfirmationNumber {
16    /// SHA-256 of the context of the label, the invite id and the keys'
17    /// `MemberKeys` encoding; its first 8 bytes, big-endian, modulo 1,000,000 (D11).
18    pub fn of(invite: InviteId, keys: &MemberPublicKeys) -> Self {
19        let mut encoded = [0; 64];
20        encoded[..32].copy_from_slice(keys.signing.as_bytes());
21        encoded[32..].copy_from_slice(keys.sealing.as_bytes());
22        let digest =
23            Sha256::digest(cipher::context(&[CONFIRMATION_NUMBER, invite.0.as_bytes(), &encoded]));
24        let first = u64::from_be_bytes(digest[..8].try_into().expect("8 of 32 bytes"));
25        Self(u32::try_from(first % 1_000_000).expect("below 1,000,000"))
26    }
27}
28
29impl fmt::Display for ConfirmationNumber {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        write!(f, "{:03} {:03}", self.0 / 1000, self.0 % 1000)
32    }
33}
34
35#[cfg(test)]
36#[path = "confirmation_number_tests.rs"]
37mod tests;