use std::collections::BTreeMap;
pub struct EncryptedExtFields<'a> {
map: &'a BTreeMap<String, String>,
}
impl<'a> EncryptedExtFields<'a> {
pub fn from_map(map: &'a BTreeMap<String, String>) -> Self {
EncryptedExtFields { map }
}
pub fn pbkdf(&self) -> Option<&str> {
self.map.get("pbkdf").map(|s| s.as_str())
}
pub fn cipher(&self) -> Option<&str> {
self.map.get("cipher").map(|s| s.as_str())
}
pub fn is_kem_mode(&self) -> bool {
self.map.contains_key("recipients")
}
pub fn recipients(&self) -> Option<&str> {
self.map.get("recipients").map(|s| s.as_str())
}
pub fn recipient_ct(&self, fp_hex: &str) -> Option<&str> {
self.map
.get(&format!("recipient-mlkem-{}", fp_hex))
.map(|s| s.as_str())
}
pub fn raw(&self) -> &BTreeMap<String, String> {
self.map
}
}
pub struct AnchorExtFields<'a> {
map: &'a BTreeMap<String, String>,
}
impl<'a> AnchorExtFields<'a> {
pub fn from_map(map: &'a BTreeMap<String, String>) -> Self {
AnchorExtFields { map }
}
pub fn signer(&self) -> Option<&str> {
self.map.get("signer").map(|s| s.as_str())
}
pub fn signers(&self) -> Option<&str> {
self.map.get("signers").map(|s| s.as_str())
}
pub fn payload(&self) -> Option<&str> {
self.map.get("payload").map(|s| s.as_str())
}
pub fn sig(&self) -> Option<&str> {
self.map.get("sig").map(|s| s.as_str())
}
pub fn sigs(&self) -> Option<&str> {
self.map.get("sigs").map(|s| s.as_str())
}
pub fn parents(&self) -> Option<&str> {
self.map.get("parents").map(|s| s.as_str())
}
pub fn timestamp(&self) -> Option<&str> {
self.map.get("ts").map(|s| s.as_str())
}
pub fn mutations(&self) -> Option<&str> {
self.map.get("mut").map(|s| s.as_str())
}
pub fn is_multi_sig(&self) -> bool {
self.map.contains_key("signers")
}
pub fn raw(&self) -> &BTreeMap<String, String> {
self.map
}
}
#[cfg(test)]
mod tests {
use super::*;
fn map(pairs: &[(&str, &str)]) -> BTreeMap<String, String> {
pairs
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect()
}
#[test]
fn encrypted_extfields_reads_known_fields() {
let m = map(&[
("pbkdf", "$argon2$m=65536,t=3,p=4$salt$"),
("cipher", "aes-256-gcm$iv=MDEyMzQ1Njc4OWFiY2RlZg=="),
]);
let ef = EncryptedExtFields::from_map(&m);
assert!(ef.pbkdf().is_some());
assert!(ef.cipher().is_some());
assert!(!ef.is_kem_mode());
assert!(ef.recipients().is_none());
}
#[test]
fn encrypted_extfields_detects_kem_mode() {
let m = map(&[
("recipients", "mlkem:1c8d2e...,mlkem:9f3a7b..."),
("recipient-mlkem-1c8d2e...", "YmFzZTY0Cg=="),
]);
let ef = EncryptedExtFields::from_map(&m);
assert!(ef.is_kem_mode());
assert!(ef.pbkdf().is_none());
assert!(ef.recipient_ct("1c8d2e...").is_some());
assert!(ef.recipient_ct("9f3a7b...").is_none()); }
#[test]
fn anchor_extfields_reads_single_and_multi_sig() {
let single = map(&[("signer", "ed25519:abc..."), ("sig", "deadbeef...")]);
let multi = map(&[
("signer", "ed25519:abc..."),
("sig", "deadbeef..."),
("signers", "ed25519:abc...,ed25519:def..."),
("sigs", "deadbeef...,cafebabe..."),
]);
let sef = AnchorExtFields::from_map(&single);
assert!(!sef.is_multi_sig());
assert!(sef.signer().is_some());
let mef = AnchorExtFields::from_map(&multi);
assert!(mef.is_multi_sig());
assert!(mef.signers().is_some());
assert!(mef.sigs().is_some());
}
}