Skip to main content

pakery_cpace/
transcript.rs

1//! Transcript construction and ISK/session-ID derivation per draft-irtf-cfrg-cpace-18.
2
3use alloc::vec::Vec;
4use pakery_core::crypto::Hash;
5use pakery_core::encoding::{lv_cat, o_cat};
6use pakery_core::SharedSecret;
7
8use crate::ciphersuite::CpaceCiphersuite;
9
10/// CPace protocol mode: determines transcript ordering.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum CpaceMode {
13    /// Initiator-responder mode: fixed ordering (Ya first, Yb second).
14    InitiatorResponder,
15    /// Symmetric mode: ordered concatenation (lexicographic ordering).
16    Symmetric,
17}
18
19/// Transcript for initiator-responder mode.
20///
21/// `transcript_ir(Ya, ADa, Yb, ADb) = lv_cat(Ya, ADa) || lv_cat(Yb, ADb)`
22pub fn transcript_ir(ya: &[u8], ad_a: &[u8], yb: &[u8], ad_b: &[u8]) -> Vec<u8> {
23    let mut result = lv_cat(&[ya, ad_a]);
24    result.extend_from_slice(&lv_cat(&[yb, ad_b]));
25    result
26}
27
28/// Transcript for symmetric (ordered concatenation) mode.
29///
30/// `transcript_oc(Ya, ADa, Yb, ADb) = o_cat(lv_cat(Ya, ADa), lv_cat(Yb, ADb))`
31pub fn transcript_oc(ya: &[u8], ad_a: &[u8], yb: &[u8], ad_b: &[u8]) -> Vec<u8> {
32    let part_a = lv_cat(&[ya, ad_a]);
33    let part_b = lv_cat(&[yb, ad_b]);
34    o_cat(&part_a, &part_b)
35}
36
37/// Derive the intermediate session key (ISK).
38///
39/// ```text
40/// DSI_ISK = DSI || "_ISK"
41/// ISK = H.hash(lv_cat(DSI_ISK, sid, K) || transcript)
42/// ```
43pub fn derive_isk<C: CpaceCiphersuite>(
44    sid: &[u8],
45    k: &[u8],
46    ya: &[u8],
47    ad_a: &[u8],
48    yb: &[u8],
49    ad_b: &[u8],
50    mode: CpaceMode,
51) -> SharedSecret {
52    let mut dsi_isk = Vec::from(C::DSI);
53    dsi_isk.extend_from_slice(b"_ISK");
54
55    let prefix = lv_cat(&[&dsi_isk, sid, k]);
56    let transcript = match mode {
57        CpaceMode::InitiatorResponder => transcript_ir(ya, ad_a, yb, ad_b),
58        CpaceMode::Symmetric => transcript_oc(ya, ad_a, yb, ad_b),
59    };
60
61    let mut hasher = C::Hash::new();
62    hasher.update(&prefix);
63    hasher.update(&transcript);
64    let hash = hasher.finalize();
65
66    SharedSecret::new(hash)
67}
68
69/// Derive the optional session ID output.
70///
71/// ```text
72/// sid_output = H.hash(b"CPaceSidOutput" || transcript)
73/// ```
74pub fn derive_session_id<C: CpaceCiphersuite>(
75    ya: &[u8],
76    ad_a: &[u8],
77    yb: &[u8],
78    ad_b: &[u8],
79    mode: CpaceMode,
80) -> Vec<u8> {
81    let transcript = match mode {
82        CpaceMode::InitiatorResponder => transcript_ir(ya, ad_a, yb, ad_b),
83        CpaceMode::Symmetric => transcript_oc(ya, ad_a, yb, ad_b),
84    };
85
86    let mut hasher = C::Hash::new();
87    hasher.update(b"CPaceSidOutput");
88    hasher.update(&transcript);
89    hasher.finalize()
90}