use alloc_shim::BTreeMap;
use crate::component::exports::aethel::core::identity::{
DisclosureAttributes, EphemeralProjection, SaapPresentation as WitPresentation,
};
use crate::identity::{Error, Identity};
use wasmtime::component::ResourceAny;
mod alloc_shim {
pub use std::collections::BTreeMap;
}
pub const MAX_ATTRIBUTES: usize = 8;
const RANDOMNESS_BYTES: usize = 32;
fn fresh_randomness() -> Result<[u8; RANDOMNESS_BYTES], Error> {
let mut bytes = [0u8; RANDOMNESS_BYTES];
getrandom::getrandom(&mut bytes).map_err(Error::Entropy)?;
Ok(bytes)
}
pub struct Credential {
pub(crate) handle: ResourceAny,
pub(crate) schema: Vec<String>,
}
impl core::fmt::Debug for Credential {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Credential")
.field("attributes", &self.schema)
.field("values", &format_args!("<held in the component, never here>"))
.finish()
}
}
impl Credential {
pub fn attribute_names(&self) -> &[String] {
&self.schema
}
fn mask_for(&self, disclose: &[&str]) -> Result<DisclosureAttributes, Error> {
let mut mask = DisclosureAttributes::empty();
for name in disclose {
let slot = self
.schema
.iter()
.position(|n| n == name)
.ok_or_else(|| Error::UnknownAttribute((*name).to_string()))?;
mask |= slot_flag(slot);
}
Ok(mask)
}
}
fn slot_flag(slot: usize) -> DisclosureAttributes {
match slot {
0 => DisclosureAttributes::ATTRIBUTE0,
1 => DisclosureAttributes::ATTRIBUTE1,
2 => DisclosureAttributes::ATTRIBUTE2,
3 => DisclosureAttributes::ATTRIBUTE3,
4 => DisclosureAttributes::ATTRIBUTE4,
5 => DisclosureAttributes::ATTRIBUTE5,
6 => DisclosureAttributes::ATTRIBUTE6,
_ => DisclosureAttributes::ATTRIBUTE7,
}
}
pub struct Presentation {
pub(crate) inner: WitPresentation,
pub(crate) projection: EphemeralProjection,
pub(crate) schema: Vec<String>,
}
impl core::fmt::Debug for Presentation {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Presentation")
.field("disclosed", &self.disclosed())
.finish_non_exhaustive()
}
}
impl Presentation {
pub fn disclosed(&self) -> BTreeMap<&str, u64> {
let mut out = BTreeMap::new();
for (slot, name) in self.schema.iter().enumerate() {
if slot < MAX_ATTRIBUTES && self.inner.disclosed.contains(slot_flag(slot)) {
out.insert(name.as_str(), self.inner.disclosed_values[slot]);
}
}
out
}
pub fn projection(&self) -> &EphemeralProjection {
&self.projection
}
}
impl Identity {
pub fn issue_credential(
&mut self,
issuer_seed: &[u8],
attributes: &[(&str, u64)],
) -> Result<Credential, Error> {
if attributes.len() > MAX_ATTRIBUTES {
return Err(Error::TooManyAttributes(attributes.len()));
}
let mut names: Vec<String> = attributes.iter().map(|(n, _)| (*n).to_string()).collect();
let mut values = [0u64; MAX_ATTRIBUTES];
for (i, (_, v)) in attributes.iter().enumerate() {
values[i] = *v;
}
while names.len() < MAX_ATTRIBUTES {
names.push(format!("__unused_{}", names.len()));
}
let randomness = fresh_randomness()?;
let handle = self
.bindings
.aethel_core_identity()
.credential()
.call_issue(
&mut self.store,
self.handle,
issuer_seed,
&values,
&randomness,
)??;
Ok(Credential { handle, schema: names })
}
pub fn present(
&mut self,
credential: &Credential,
context: &[u8],
disclose: &[&str],
) -> Result<Presentation, Error> {
let mask = credential.mask_for(disclose)?;
let projection_randomness = fresh_randomness()?;
let blinding_randomness = fresh_randomness()?;
let presentation_randomness = fresh_randomness()?;
let inner = self
.bindings
.aethel_core_identity()
.credential()
.call_present(
&mut self.store,
credential.handle,
self.handle,
context,
&projection_randomness,
mask,
&blinding_randomness,
&presentation_randomness,
)??;
let projection = self
.bindings
.aethel_core_identity()
.master_identity()
.call_project_at_context(
&mut self.store,
self.handle,
context,
&projection_randomness,
)??;
Ok(Presentation { inner, projection, schema: credential.schema.clone() })
}
}
pub fn verify_presentation(
issuer_seed: &[u8],
presentation: &Presentation,
expected_context: &[u8],
) -> Result<bool, Error> {
let (mut store, bindings) = crate::component::load()?;
let verified = bindings
.aethel_core_identity()
.call_saap_verify_presentation(
&mut store,
issuer_seed,
&presentation.inner,
&presentation.projection,
expected_context,
)??;
Ok(verified)
}