use alloc_shim::BTreeMap;
use crate::component::exports::aethel::core::identity::{
DisclosureAttributes, 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;
pub const MIN_ISSUER_SEED_BYTES: usize = 32;
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>,
pub(crate) issued: usize,
}
impl core::fmt::Debug for Credential {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Credential")
.field("attributes", &self.attribute_names())
.field(
"values",
&format_args!("<held in the component, never here>"),
)
.finish()
}
}
impl Credential {
pub fn attribute_names(&self) -> &[String] {
&self.schema[..self.issued]
}
fn mask_for(&self, disclose: &[&str]) -> Result<DisclosureAttributes, Error> {
let mut mask = DisclosureAttributes::empty();
for name in disclose {
let slot = self.schema[..self.issued]
.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: crate::identity::Projection,
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) -> &crate::identity::Projection {
&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 issued = 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,
issued,
})
}
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: crate::identity::Projection::from_component(projection)?,
schema: credential.schema.clone(),
})
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct IssuerPublicParameters {
bytes: Vec<u8>,
}
impl IssuerPublicParameters {
pub fn derive(issuer_seed: &[u8]) -> Result<Self, Error> {
let (mut store, bindings) = crate::component::load()?;
let resource = bindings
.aethel_core_identity()
.issuer_public_parameters()
.call_derive(&mut store, issuer_seed)??;
let bytes = bindings
.aethel_core_identity()
.issuer_public_parameters()
.call_serialize(&mut store, resource)?;
Ok(Self { bytes })
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
let (mut store, bindings) = crate::component::load()?;
bindings
.aethel_core_identity()
.issuer_public_parameters()
.call_deserialize(&mut store, bytes)??;
Ok(Self {
bytes: bytes.to_vec(),
})
}
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
}
impl core::fmt::Debug for IssuerPublicParameters {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("IssuerPublicParameters")
.field("bytes", &self.bytes.len())
.finish()
}
}
pub fn verify_presentation(
issuer: &IssuerPublicParameters,
presentation: &Presentation,
expected_context: &[u8],
) -> Result<bool, Error> {
let (mut store, bindings) = crate::component::load()?;
let resource = bindings
.aethel_core_identity()
.issuer_public_parameters()
.call_deserialize(&mut store, &issuer.bytes)??;
let verified = bindings
.aethel_core_identity()
.call_saap_verify_presentation(
&mut store,
resource,
&presentation.inner,
presentation.projection.as_component(),
expected_context,
)??;
Ok(verified)
}