use affinidi_data_integrity::crypto_suites::CryptoSuite;
use crate::{DIDWebVHError, Multibase};
use serde::{Deserialize, Serialize};
use std::fmt::Display;
pub mod proofs;
pub mod validate;
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct WitnessVerifyOptions {
pub extra_allowed_suites: Vec<CryptoSuite>,
}
impl WitnessVerifyOptions {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_extra_allowed_suite(mut self, suite: CryptoSuite) -> Self {
self.extra_allowed_suites.push(suite);
self
}
pub fn suite_is_allowed(&self, suite: CryptoSuite) -> bool {
suite == CryptoSuite::EddsaJcs2022 || self.extra_allowed_suites.contains(&suite)
}
pub fn check_proof_shape(
&self,
proof: &affinidi_data_integrity::DataIntegrityProof,
) -> Result<(), DIDWebVHError> {
crate::log_entry::enforce_witness_proof_shape(proof, self)
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(untagged)]
pub enum Witnesses {
Value {
threshold: u32,
witnesses: Vec<Witness>,
},
Empty {},
}
impl Witnesses {
pub fn builder() -> WitnessesBuilder {
WitnessesBuilder {
threshold: 1,
witnesses: Vec::new(),
}
}
pub fn is_empty(&self) -> bool {
match self {
Witnesses::Empty {} => true,
Witnesses::Value { witnesses, .. } => witnesses.is_empty(),
}
}
pub fn validate(&self) -> Result<(), DIDWebVHError> {
if self.is_empty() {
return Err(DIDWebVHError::ValidationError(
"Witnesses are enabled, but no witness nodes are specified! Can not be empty!"
.to_string(),
));
}
match self {
Witnesses::Value {
threshold,
witnesses,
} => {
if threshold < &1 {
return Err(DIDWebVHError::ValidationError(
"Witness threshold must be 1 or more".to_string(),
));
} else if witnesses.len() < *threshold as usize {
return Err(DIDWebVHError::ValidationError(format!(
"Number of Witnesses ({}) is less than the threshold ({})",
witnesses.len(),
threshold
)));
}
}
_ => {
return Err(DIDWebVHError::ValidationError(
"Empty Witness Parameter config found, but it wasn't detected. INTERNAL ERROR STATE"
.to_string(),
));
}
}
Ok(())
}
pub fn witnesses(&self) -> Option<&[Witness]> {
match self {
Witnesses::Empty {} => None,
Witnesses::Value { witnesses, .. } => Some(witnesses),
}
}
pub fn threshold(&self) -> Option<u32> {
match self {
Witnesses::Empty {} => None,
Witnesses::Value { threshold, .. } => Some(*threshold),
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct Witness {
pub id: Multibase,
}
impl Display for Witness {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.id)
}
}
impl Witness {
pub fn as_did(&self) -> String {
let id = self.id.as_str();
if id.starts_with("did:key:") {
id.to_string()
} else {
["did:key:", id].concat()
}
}
pub fn as_did_key(&self) -> String {
let did = self.as_did();
let raw_key = did.strip_prefix("did:key:").unwrap_or(did.as_str());
[&did, "#", raw_key].concat()
}
}
pub struct WitnessesBuilder {
threshold: u32,
witnesses: Vec<Witness>,
}
impl WitnessesBuilder {
pub fn threshold(mut self, t: u32) -> Self {
self.threshold = t;
self
}
pub fn witness(mut self, id: Multibase) -> Self {
self.witnesses.push(Witness { id });
self
}
pub fn witnesses(mut self, ids: impl IntoIterator<Item = Multibase>) -> Self {
self.witnesses
.extend(ids.into_iter().map(|id| Witness { id }));
self
}
pub fn build(self) -> Result<Witnesses, DIDWebVHError> {
let w = Witnesses::Value {
threshold: self.threshold,
witnesses: self.witnesses,
};
w.validate()?;
Ok(w)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Multibase;
#[test]
fn test_validate_empty_error() {
let w = Witnesses::Empty {};
assert!(w.validate().is_err());
}
#[test]
fn test_validate_threshold_zero_error() {
let w = Witnesses::Value {
threshold: 0,
witnesses: vec![Witness {
id: Multibase::new("w1"),
}],
};
assert!(w.validate().is_err());
}
#[test]
fn test_validate_threshold_exceeds_witnesses_error() {
let w = Witnesses::Value {
threshold: 3,
witnesses: vec![Witness {
id: Multibase::new("w1"),
}],
};
let err = w.validate().unwrap_err();
assert!(err.to_string().contains("less than the threshold"));
}
#[test]
fn test_validate_ok() {
let w = Witnesses::Value {
threshold: 1,
witnesses: vec![Witness {
id: Multibase::new("w1"),
}],
};
assert!(w.validate().is_ok());
}
#[test]
fn test_witnesses_accessors() {
let empty = Witnesses::Empty {};
assert!(empty.witnesses().is_none());
assert!(empty.threshold().is_none());
let value = Witnesses::Value {
threshold: 2,
witnesses: vec![
Witness {
id: Multibase::new("w1"),
},
Witness {
id: Multibase::new("w2"),
},
],
};
assert_eq!(value.witnesses().unwrap().len(), 2);
assert_eq!(value.threshold(), Some(2));
}
#[test]
fn test_witness_as_did_from_raw_key() {
let w = Witness {
id: Multibase::new("z6Mktest"),
};
assert_eq!(w.as_did(), "did:key:z6Mktest");
}
#[test]
fn test_witness_as_did_from_full_did() {
let w = Witness {
id: Multibase::new("did:key:z6Mktest"),
};
assert_eq!(w.as_did(), "did:key:z6Mktest");
}
#[test]
fn test_witness_as_did_key_from_raw_key() {
let w = Witness {
id: Multibase::new("z6Mktest"),
};
assert_eq!(w.as_did_key(), "did:key:z6Mktest#z6Mktest");
}
#[test]
fn test_witness_as_did_key_from_full_did() {
let w = Witness {
id: Multibase::new("did:key:z6Mktest"),
};
assert_eq!(w.as_did_key(), "did:key:z6Mktest#z6Mktest");
}
#[test]
fn test_witness_display() {
let w = Witness {
id: Multibase::new("z6Mktest"),
};
assert_eq!(format!("{}", w), "z6Mktest");
}
#[test]
fn test_is_empty() {
assert!(Witnesses::Empty {}.is_empty());
assert!(
Witnesses::Value {
threshold: 0,
witnesses: vec![]
}
.is_empty()
);
assert!(
!Witnesses::Value {
threshold: 1,
witnesses: vec![Witness {
id: Multibase::new("w1")
}],
}
.is_empty()
);
}
#[test]
fn builder_valid_single_witness() {
let w = Witnesses::builder()
.threshold(1)
.witness(Multibase::new("z6Mktest"))
.build();
assert!(w.is_ok());
let w = w.unwrap();
assert_eq!(w.threshold(), Some(1));
assert_eq!(w.witnesses().unwrap().len(), 1);
}
#[test]
fn builder_valid_multiple_witnesses() {
let w = Witnesses::builder()
.threshold(2)
.witnesses(vec![
Multibase::new("z6Mk1"),
Multibase::new("z6Mk2"),
Multibase::new("z6Mk3"),
])
.build();
assert!(w.is_ok());
assert_eq!(w.unwrap().witnesses().unwrap().len(), 3);
}
#[test]
fn builder_threshold_zero_error() {
let result = Witnesses::builder()
.threshold(0)
.witness(Multibase::new("z6Mk1"))
.build();
assert!(result.is_err());
}
#[test]
fn builder_threshold_exceeds_witnesses_error() {
let result = Witnesses::builder()
.threshold(3)
.witness(Multibase::new("z6Mk1"))
.build();
assert!(result.is_err());
}
}