fi-verifiable-data 0.0.5

VC and VP wrapper for rust and WASM. fi-digital-signatures crate is used to sign and verify the data objects.
Documentation
#[cfg(not(feature = "wasm"))]
use std::borrow::{Borrow, BorrowMut};
use std::collections::HashMap;

#[cfg(feature = "wasm")]
use fi_digital_signatures::algorithms::Algorithm;
#[cfg(feature = "wasm")]
use js_sys::{Array, Object};
#[cfg(not(feature = "wasm"))]
use serde::{Deserialize, Serialize};
#[cfg(not(feature = "wasm"))]
use serde_json::Value;

#[cfg(feature = "wasm")]
use crate::proof::ProofType;
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::wasm_bindgen;
#[cfg(feature = "wasm")]
use wasm_bindgen::JsValue;

#[cfg(not(feature = "wasm"))]
use crate::proof::{FiProof, Proof};

use crate::{document::VerificationDocument, error::FiError, vc::VC};

#[cfg(not(feature = "wasm"))]
#[derive(Serialize, Deserialize)]
pub struct VP {
    #[serde(rename = "@context")]
    contexts: Vec<Value>,
    holder: Option<String>,
    id: String,
    #[serde(rename = "type")]
    types: Vec<String>,
    #[serde(rename = "proof", skip_serializing_if = "Option::is_none")]
    proof: Option<FiProof>,
    #[serde(rename = "verifiableCredential")]
    verifiable_credential: Vec<VC>,
    #[serde(skip_serializing, skip_deserializing)]
    optional_fields: HashMap<String, Box<Value>>,
}

#[wasm_bindgen]
#[cfg(feature = "wasm")]
pub struct VP(HashMap<String, Box<JsValue>>);

#[cfg(not(feature = "wasm"))]
impl VP {
    pub fn new(id: String, holder: Option<String>) -> Self {
        let mut vp = VP {
            contexts: Vec::new(),
            holder,
            id,
            proof: None,
            types: Vec::new(),
            verifiable_credential: Vec::new(),
            optional_fields: HashMap::new(),
        };

        vp.types.push(String::from("VerifiablePresentation"));

        vp
    }

    pub fn set_holder(&mut self, holder: Option<String>) {
        self.holder = holder;
    }

    pub fn set_context(&mut self, contexts: Vec<Value>) {
        self.contexts = contexts;
    }

    pub fn add_context(&mut self, context: Value) {
        self.contexts.push(context)
    }

    pub fn add_verifiable_credentials(&mut self, verifiable_credential: VC) {
        self.verifiable_credential.push(verifiable_credential);
    }

    pub fn set_verifiable_credentials(&mut self, verifiable_credentials: Vec<VC>) {
        self.verifiable_credential = verifiable_credentials;
    }

    pub fn set_types(&mut self, types: Vec<String>) {
        self.types = types;
    }

    pub fn get_proof(&self) -> &Option<FiProof> {
        self.proof.borrow()
    }

    pub fn get_proof_mut(&mut self) -> &mut Option<FiProof> {
        self.proof.borrow_mut()
    }

    pub fn sign(
        &mut self,
        doc: &mut VerificationDocument,
        mut proof: FiProof,
    ) -> Result<(), FiError> {
        let signable_values = match self.get_signable_content() {
            Err(error) => {
                return Err(error);
            }
            Ok(val) => val,
        };

        match proof.sign(doc, signable_values.to_string()) {
            Err(error) => {
                return Err(error);
            }
            Ok(val) => val,
        };

        self.proof = Some(proof);
        return Ok(());
    }

    pub fn verify(&mut self, doc: &mut VerificationDocument) -> Result<bool, FiError> {
        let signable_values = match self.get_signable_content() {
            Err(error) => {
                return Err(error);
            }
            Ok(val) => val,
        };

        let proof = match self.proof.as_mut() {
            None => return Err(FiError::new("Cannot get proof as a mutable reference")),
            Some(val) => val,
        };

        proof.verify(doc, signable_values.to_string())
    }

    pub fn to_object(&mut self) -> Result<Value, FiError> {
        let mut value = match serde_json::to_value(&self) {
            Err(error) => {
                eprintln!("{}", error);
                return Err(FiError::new("Cannot create value object from VP"));
            }
            Ok(val) => val,
        };

        let obj = value.as_object_mut().unwrap();

        self.optional_fields.iter_mut().for_each(move |(key, val)| {
            obj.insert(String::from(key), val.take());
        });

        return Ok(value);
    }

    pub fn get_signable_content(&mut self) -> Result<Value, FiError> {
        let mut val = match self.to_object() {
            Err(error) => {
                return Err(error);
            }
            Ok(val) => val,
        };

        val.as_object_mut().unwrap().remove("proof");

        return Ok(val);
    }

    pub fn from(value: Value) -> Result<Self, FiError> {
        let mut map: HashMap<String, Box<Value>> = HashMap::new();

        match serde_ignored::deserialize(&value, |path| {
            let _path = path.to_string();
            let value_to_save = value[&_path].clone();
            map.insert(_path, Box::new(value_to_save));
        }) {
            Ok(val) => return Ok(val),
            Err(error) => return Err(FiError::new(error.to_string().as_str())),
        };
    }
}

#[cfg(feature = "wasm")]
#[wasm_bindgen]
impl VP {
    #[wasm_bindgen(constructor)]
    pub fn new(id: String, holder: JsValue) -> Self {
        let mut vp: HashMap<String, Box<JsValue>> = HashMap::new();

        let mut types: Vec<JsValue> = Vec::new();
        types.push(JsValue::from_str("VerifiablePresentation"));

        vp.insert(String::from("type"), Box::new(JsValue::from(types)));
        vp.insert(String::from("@context"), Box::new(Array::new().into()));
        vp.insert(
            String::from("verifiableCredential"),
            Box::new(Array::new().into()),
        );
        vp.insert(String::from("holder"), Box::new(holder));
        vp.insert(String::from("id"), Box::new(JsValue::from_str(id.as_str())));
        vp.insert(String::from("proof"), Box::new(JsValue::null()));

        VP(vp)
    }

    #[wasm_bindgen(js_name = "setHolder")]
    pub fn set_holder(&mut self, holder: JsValue) {
        self.0.insert(String::from("holder"), Box::new(holder));
    }

    #[wasm_bindgen(js_name = "setContext")]
    pub fn set_context(&mut self, contexts: JsValue) {
        self.0.insert(String::from("@context"), Box::new(contexts));
    }

    #[wasm_bindgen(js_name = "addContext")]
    pub fn add_context(&mut self, context: JsValue) -> Result<(), FiError> {
        if self.0["@context"].is_array() {
            let arr: Array = js_sys::Array::from(&*self.0["@context"]);

            arr.push(&context.clone());

            self.0
                .insert(String::from("@context"), Box::new(JsValue::from(arr)));
        } else {
            let arr = js_sys::Array::new();
            arr.push(&self.0["@context"].to_owned());
            arr.push(&context.clone());

            self.0
                .insert(String::from("@context"), Box::new(JsValue::from(arr)));
        }

        return Ok(());
    }

    #[wasm_bindgen(js_name = "addType")]
    pub fn add_type(&mut self, _type: JsValue) -> Result<(), FiError> {
        if self.0["type"].is_array() {
            let arr: Array = js_sys::Array::from(&*self.0["type"]);

            arr.push(&_type.clone());

            self.0
                .insert(String::from("type"), Box::new(JsValue::from(arr)));
        } else {
            let arr = js_sys::Array::new();
            arr.push(&self.0["type"].to_owned());
            arr.push(&_type.clone());

            self.0
                .insert(String::from("type"), Box::new(JsValue::from(arr)));
        }

        return Ok(());
    }

    #[wasm_bindgen(js_name = "setType")]
    pub fn set_type(&mut self, _type: JsValue) {
        self.0.insert(String::from("type"), Box::new(_type));
    }

    #[wasm_bindgen(js_name = "getProof")]
    pub fn get_proof(&self) -> JsValue {
        *self.0["proof"].clone()
    }

    #[wasm_bindgen(js_name = "addVerifiableCredential")]
    pub fn add_verifiable_credentials(&mut self, verifiable_credential: VC) {
        let arr: Array = js_sys::Array::from(&*self.0["verifiableCredential"]);

        arr.push(&JsValue::from(verifiable_credential));

        self.0.insert(
            String::from("verifiableCredential"),
            Box::new(JsValue::from(arr)),
        );
    }

    #[wasm_bindgen(js_name = "setVerifiableCredential")]
    pub fn set_verifiable_credentials(&mut self, verifiable_credentials: Vec<VC>) {
        self.0.insert(
            String::from("verifiableCredential"),
            Box::new(JsValue::from(verifiable_credentials)),
        );
    }

    #[wasm_bindgen]
    pub fn sign(
        &mut self,
        alg: Algorithm,
        purpose: String,
        doc: &mut VerificationDocument,
        proof_type: ProofType,
    ) -> Result<(), FiError> {
        let signable_values = match self.get_signable_content() {
            Err(error) => {
                return Err(error);
            }
            Ok(val) => val,
        };

        let proof = match proof_type.sign(alg, purpose, doc, signable_values.to_string()) {
            Err(error) => {
                return Err(error);
            }
            Ok(val) => val,
        };

        self.0.insert(String::from("proof"), Box::new(proof));
        return Ok(());
    }

    #[wasm_bindgen]
    pub fn verify(
        &mut self,
        doc: &mut VerificationDocument,
        proof_type: ProofType,
    ) -> Result<bool, FiError> {
        let signable_values = match self.get_signable_content() {
            Err(error) => {
                return Err(error);
            }
            Ok(val) => val,
        };

        proof_type.verify(doc, signable_values.to_string(), *self.0["proof"].clone())
    }

    #[wasm_bindgen(js_name = "toObject")]
    pub fn to_object(&mut self) -> Result<Object, FiError> {
        let value = js_sys::Object::new();
        self.0.iter().for_each(|(key, val)| {
            _ = js_sys::Reflect::set(&value, &JsValue::from_str(key), val);
        });

        return Ok(value);
    }

    #[wasm_bindgen(js_name = "getSignableContent")]
    pub fn get_signable_content(&mut self) -> Result<String, FiError> {
        let val = match self.to_object() {
            Err(error) => {
                return Err(error);
            }
            Ok(val) => val,
        };

        _ = js_sys::Reflect::delete_property(&val, &JsValue::from_str("proof"));

        return Ok(val.to_string().into());
    }

    #[wasm_bindgen(js_name = "addField")]
    pub fn add_field(&mut self, key: &str, val: JsValue) {
        self.0.insert(String::from(key), Box::new(val));
    }

    #[wasm_bindgen]
    pub fn from(value: JsValue) -> Result<VP, FiError> {
        let keys = match js_sys::Reflect::own_keys(&value) {
            Ok(val) => val,
            Err(error) => return Err(FiError::new(error.as_string().unwrap().as_str())),
        };

        let mut new_value: HashMap<String, Box<JsValue>> = HashMap::new();
        keys.iter().for_each(|key| {
            let val = match js_sys::Reflect::get(&value, &key) {
                Err(error) => {
                    eprintln!("{}", error.as_string().unwrap());
                    return;
                }
                Ok(_val) => _val,
            };

            new_value.insert(key.as_string().unwrap(), Box::new(val));
        });

        Ok(VP(new_value))
    }
}