use std::sync::LazyLock;
use agent_mesh_protocol::Fingerprint;
use crate::wire_framing::push_field;
const TRANSCRIPT_DOMAIN: &[u8] = b"newt/passkey-transcript/v1";
const COMMITMENT_DOMAIN: &[u8] = b"newt/passkey-commitment/v1";
const WORDLIST: &str = include_str!("../data/sas-wordlist.txt");
pub const SAS_WORD_COUNT: usize = 6;
const WORD_BITS: usize = 11;
static WORDS: LazyLock<Vec<&'static str>> = LazyLock::new(|| WORDLIST.lines().collect());
#[derive(Debug, Clone, Copy)]
pub struct TranscriptInputs<'a> {
pub rp_id: &'a str,
pub issuer: &'a str,
pub subject: &'a str,
pub mesh_agent_fingerprint: &'a str,
pub cose_alg: i64,
pub cose_pubkey: &'a [u8],
pub commitment: &'a Fingerprint,
pub enroll_nonce: &'a [u8],
}
impl TranscriptInputs<'_> {
#[must_use]
pub fn transcript_id(&self) -> Fingerprint {
let mut payload = Vec::with_capacity(256);
payload.extend_from_slice(TRANSCRIPT_DOMAIN);
push_field(&mut payload, self.rp_id.as_bytes());
push_field(&mut payload, self.issuer.as_bytes());
push_field(&mut payload, self.subject.as_bytes());
push_field(&mut payload, self.mesh_agent_fingerprint.as_bytes());
push_field(&mut payload, &self.cose_alg.to_be_bytes());
push_field(&mut payload, self.cose_pubkey);
push_field(&mut payload, &self.commitment.0);
push_field(&mut payload, self.enroll_nonce);
Fingerprint::of_bytes(&payload)
}
}
#[must_use]
pub fn commit(cose_pubkey: &[u8], blinding: &[u8]) -> Fingerprint {
let mut payload = Vec::with_capacity(128);
payload.extend_from_slice(COMMITMENT_DOMAIN);
push_field(&mut payload, cose_pubkey);
push_field(&mut payload, blinding);
Fingerprint::of_bytes(&payload)
}
#[must_use]
pub fn opens(commitment: &Fingerprint, cose_pubkey: &[u8], blinding: &[u8]) -> bool {
let computed = commit(cose_pubkey, blinding);
let mut diff = 0u8;
for (a, b) in computed.0.iter().zip(commitment.0.iter()) {
diff |= a ^ b;
}
diff == 0
}
#[must_use]
pub fn sas_words(transcript: &Fingerprint) -> [&'static str; SAS_WORD_COUNT] {
std::array::from_fn(|i| WORDS[word_index(&transcript.0, i)])
}
fn word_index(digest: &[u8; 32], word: usize) -> usize {
let start = word * WORD_BITS;
(start..start + WORD_BITS).fold(0, |value, bit| {
let set = digest[bit / 8] & (0x80 >> (bit % 8)) != 0;
(value << 1) | usize::from(set)
})
}