// Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 pub mod error; use getset::{CopyGetters, Getters}; use serde::{Deserialize, Serialize}; use error::CoconutInterfaceError; // We list these explicity instead of glob export due to shadowing warnings with the pub tests // module. pub use nym_coconut::{ aggregate_signature_shares, aggregate_verification_keys, blind_sign, hash_to_scalar, prepare_blind_sign, prove_bandwidth_credential, Attribute, Base58, BlindSignRequest, BlindedSignature, Bytable, CoconutError, KeyPair, Parameters, PrivateAttribute, PublicAttribute, Signature, SignatureShare, Theta, VerificationKey, }; #[derive(Debug, Serialize, Deserialize, Getters, CopyGetters, Clone, PartialEq, Eq)] pub struct Credential { #[getset(get = "pub")] n_params: u32, #[getset(get = "pub")] theta: Theta, voucher_value: u64, voucher_info: String, #[getset(get = "pub")] epoch_id: u64, } impl Credential { pub fn new( n_params: u32, theta: Theta, voucher_value: u64, voucher_info: String, epoch_id: u64, ) -> Credential { Credential { n_params, theta, voucher_value, voucher_info, epoch_id, } } pub fn blinded_serial_number(&self) -> String { self.theta.blinded_serial_number_bs58() } pub fn has_blinded_serial_number( &self, blinded_serial_number_bs58: &str, ) -> Result { Ok(self .theta .has_blinded_serial_number(blinded_serial_number_bs58)?) } pub fn voucher_value(&self) -> u64 { self.voucher_value } pub fn verify(&self, verification_key: &VerificationKey) -> bool { let params = Parameters::new(self.n_params).unwrap(); let public_attributes = [ self.voucher_value.to_string().as_bytes(), self.voucher_info.as_bytes(), ] .iter() .map(hash_to_scalar) .collect::>(); nym_coconut::verify_credential(¶ms, verification_key, &self.theta, &public_attributes) } pub fn as_bytes(&self) -> Vec { let n_params_bytes = self.n_params.to_be_bytes(); let theta_bytes = self.theta.to_bytes(); let theta_bytes_len = theta_bytes.len(); let voucher_value_bytes = self.voucher_value.to_be_bytes(); let epoch_id_bytes = self.epoch_id.to_be_bytes(); let voucher_info_bytes = self.voucher_info.as_bytes(); let voucher_info_len = voucher_info_bytes.len(); let mut bytes = Vec::with_capacity(28 + theta_bytes_len + voucher_info_len); bytes.extend_from_slice(&n_params_bytes); bytes.extend_from_slice(&(theta_bytes_len as u64).to_be_bytes()); bytes.extend_from_slice(&theta_bytes); bytes.extend_from_slice(&voucher_value_bytes); bytes.extend_from_slice(&epoch_id_bytes); bytes.extend_from_slice(voucher_info_bytes); bytes } pub fn from_bytes(bytes: &[u8]) -> Result { if bytes.len() < 28 { return Err(CoconutError::Deserialization(String::from( "To few bytes in credential", ))); } let mut four_byte = [0u8; 4]; let mut eight_byte = [0u8; 8]; four_byte.copy_from_slice(&bytes[..4]); let n_params = u32::from_be_bytes(four_byte); eight_byte.copy_from_slice(&bytes[4..12]); let theta_len = u64::from_be_bytes(eight_byte); if bytes.len() < 28 + theta_len as usize { return Err(CoconutError::Deserialization(String::from( "To few bytes in credential", ))); } let theta = Theta::from_bytes(&bytes[12..12 + theta_len as usize]) .map_err(|e| CoconutError::Deserialization(e.to_string()))?; eight_byte.copy_from_slice(&bytes[12 + theta_len as usize..20 + theta_len as usize]); let voucher_value = u64::from_be_bytes(eight_byte); eight_byte.copy_from_slice(&bytes[20 + theta_len as usize..28 + theta_len as usize]); let epoch_id = u64::from_be_bytes(eight_byte); let voucher_info = String::from_utf8(bytes[28 + theta_len as usize..].to_vec()) .map_err(|e| CoconutError::Deserialization(e.to_string()))?; Ok(Credential { n_params, theta, voucher_value, voucher_info, epoch_id, }) } } impl Bytable for Credential { fn to_byte_vec(&self) -> Vec { self.as_bytes() } fn try_from_byte_slice(slice: &[u8]) -> Result { Credential::from_bytes(slice) } } impl Base58 for Credential {} #[cfg(test)] mod tests { use nym_coconut::{prove_bandwidth_credential, Signature}; use super::*; #[test] fn serde_coconut_credential() { let voucher_value = 1000000u64; let voucher_info = String::from("BandwidthVoucher"); let serial_number = Attribute::try_from_bs58("7Rp3imcuNX3w9se9wm5th8gSvc2czsnMrGsdt5HsrycA").unwrap(); let binding_number = Attribute::try_from_bs58("Auf8yVEgyEAWNHaXUZmimS4n9g5YiYnNYqp6F9BtBe9E").unwrap(); let signature = Signature::try_from_bs58( "ta3pM9ffj5T6YGbwjSBp2W118rcwyP9PXStc\ 7ssb91g5GQYMQHhuTNajbdZcjxUFBFL5rhED8EHpRzE8r432ss3qbPBfpNev4CdkfMkQ3wepyM7hy7q1W6Rn9WmFoZL\ ZR9j", ) .unwrap(); let params = Parameters::new(4).unwrap(); let verification_key = VerificationKey::try_from_bs58("8CFtVVXdwLy4WHMQPE4\ woe89q3DRHoNxBSchftrEjSBPWA4r4xZv4Y9qSvS5x5bMmFtp7BX6ikECAnuXr5EjXWSsgjirZJmpS5XDUynVfht1cD\ FWGDvy2XFrRCuoCMotNXi3PoF6wYqdTR9Rqcfoj3i2H5Nid422WBaLtVoC9QNobvpvaqq6vX5PbsSyPayvU8HCXFxM6\ JjScYpbRTxQtdwefWLrk3LmXyJQBWi7c2VAhSxu9msp7VTBycqdwQNgxHETStZuwXsozxaGQ2KssVUCaaoYPR4g2RqK\ UAvtWwA7pMiAQNcbkXcbsjCgVjWaCpMWC37XA31cLcFf3zbjHD9e5tXjAcqa4M89fbFhuvvSXxowSAZ5NoWrN32kd5d\ wxJm1JW3Tt2h6yDDBe84oMy71462dZn7N78DVk2mFNGwBCibrZWA7oUzRBMfYxiQrksoFcou7QfLLd58zoNYmPQPt84\ 1VpQopEBfdQ7Nf9zoXxBt3zMy7g5NsFGvzh7KTbDUyeeXrdkKJPQBs6dqaizr9sS8CPPmR4uk96vDTRh8CJ5FbSsmb8\ nP71dRvvwRZJHGzwYirMo6SXS3ZYxFuiA3mkxYuqDHCwkTWDuRCcAaztrDYRZg7VCMo4Q446AaEso5eqpeWpHZQt53E\ ZRpqmNYKASGwMhTeEHPSLgSmtoAAUcaRWpGRzYfd6kzEma8tdGLwyP4rLXgvSvtDLP37dU7YgF3LEXbGAz57U9ATy46\ 6sroLpHPdaCWB8RF11wvB6Tu196JnJd2KyQBP1iUWP3rtZs3GhAF1QVcxquh8BqDZzAcpQ6wCS1P9c5GxKgww77FVF5\ Kp83XtoxSrw3GaYVyKTGxNh3vcKPR31txCjTxPaN2fg7TaPLhoQJX4YaAroFSXqrqbbRsisuHhhCeUP2YwDjHedes9y") .unwrap(); let theta = prove_bandwidth_credential( ¶ms, &verification_key, &signature, serial_number, binding_number, ) .unwrap(); let credential = Credential::new(4, theta, voucher_value, voucher_info, 42); let serialized_credential = credential.as_bytes(); let deserialized_credential = Credential::from_bytes(&serialized_credential).unwrap(); assert_eq!(credential, deserialized_credential); } }