diff --git a/Cargo.toml b/Cargo.toml index cb55febf..a3f6306b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ logger = ["env_logger"] vendored = ["anoncreds-clsignatures/openssl_vendored"] [dependencies] +anoncreds-clsignatures = "0.2" bs58 = "0.4.0" env_logger = { version = "0.9.3", optional = true } ffi-support = { version = "0.4.0", optional = true } @@ -36,10 +37,8 @@ serde = { version = "1.0.155", features = ["derive"] } bitvec = { version = "1.0.1", features = ["serde"] } serde_json = { version = "1.0.94", features = ["raw_value"] } sha2 = "0.10.6" -tempfile = "3.4.0" thiserror = "1.0.39" zeroize = { version = "1.5.7", optional = true, features = ["zeroize_derive"] } -anoncreds-clsignatures = "0.1" [profile.release] lto = true diff --git a/src/data_types/link_secret.rs b/src/data_types/link_secret.rs index 5078177a..9bd9422c 100644 --- a/src/data_types/link_secret.rs +++ b/src/data_types/link_secret.rs @@ -1,11 +1,12 @@ -use crate::cl::{bn::BigNumber, MasterSecret, Prover as CryptoProver}; +use crate::cl::{bn::BigNumber, LinkSecret as ClLinkSecret, Prover as CryptoProver}; use crate::error::ConversionError; use std::fmt; + pub struct LinkSecret(pub BigNumber); impl LinkSecret { pub fn new() -> Result { - let value = CryptoProver::new_master_secret() + let value = CryptoProver::new_link_secret() .and_then(|v| v.value()) .map_err(|err| { ConversionError::from_msg(format!("Error creating link secret: {err}")) @@ -31,10 +32,10 @@ impl fmt::Debug for LinkSecret { } } -impl TryInto for LinkSecret { +impl TryInto for LinkSecret { type Error = ConversionError; - fn try_into(self) -> Result { + fn try_into(self) -> Result { let j = serde_json::json!({ "ms": self.0 }); @@ -43,10 +44,10 @@ impl TryInto for LinkSecret { } } -impl TryInto for &LinkSecret { +impl TryInto for &LinkSecret { type Error = ConversionError; - fn try_into(self) -> Result { + fn try_into(self) -> Result { let j = serde_json::json!({ "ms": self.0 }); @@ -95,19 +96,19 @@ mod link_secret_tests { } #[test] - fn should_convert_between_master_secret() { + fn should_convert_between_link_secret() { let link_secret = LinkSecret::new().expect("Unable to create link secret"); - let master_secret: MasterSecret = link_secret + let cl_link_secret: ClLinkSecret = link_secret .try_clone() .expect("Error cloning link secret") .try_into() - .expect("error converting to master secret"); + .expect("error converting to CL link secret"); assert_eq!( link_secret.0, - master_secret + cl_link_secret .value() - .expect("Error getting value from master secret") + .expect("Error getting value from CL link secret") ); } diff --git a/src/data_types/rev_status_list.rs b/src/data_types/rev_status_list.rs index cae1525e..1dc0b947 100644 --- a/src/data_types/rev_status_list.rs +++ b/src/data_types/rev_status_list.rs @@ -89,30 +89,35 @@ impl RevocationStatusList { if let Some(reg) = registry { self.registry = Some(reg.into()); } + let slots_count = self.revocation_list.len(); if let Some(issued) = issued { + if let Some(max_idx) = issued.iter().last().copied() { + if max_idx as usize >= slots_count { + return Err(Error::from_msg( + crate::ErrorKind::Unexpected, + "Update Revocation List Index Out of Range", + )); + } + } // issued credentials are assigned `false` // i.e. NOT revoked for i in issued { - let mut bit = self.revocation_list.get_mut(i as usize).ok_or_else(|| { - Error::from_msg( - crate::ErrorKind::Unexpected, - "Update Revocation List Index Out of Range", - ) - })?; - *bit = false; + self.revocation_list.set(i as usize, false); } } if let Some(revoked) = revoked { + if let Some(max_idx) = revoked.iter().last().copied() { + if max_idx as usize >= slots_count { + return Err(Error::from_msg( + crate::ErrorKind::Unexpected, + "Update Revocation List Index Out of Range", + )); + } + } // revoked credentials are assigned `true` // i.e. IS revoked for i in revoked { - let mut bit = self.revocation_list.get_mut(i as usize).ok_or_else(|| { - Error::from_msg( - crate::ErrorKind::Unexpected, - "Update Revocation List Index Out of Range", - ) - })?; - *bit = true; + self.revocation_list.set(i as usize, true); } } // only update if input is Some diff --git a/src/ffi/credential.rs b/src/ffi/credential.rs index eb10d1d3..f69db723 100644 --- a/src/ffi/credential.rs +++ b/src/ffi/credential.rs @@ -12,7 +12,6 @@ use crate::error::Result; use crate::services::{ issuer::create_credential, prover::process_credential, - tails::TailsFileReader, types::{Credential, CredentialRevocationConfig, MakeCredentialValues}, utils::encode_credential_attribute, }; @@ -30,7 +29,6 @@ struct RevocationConfig { reg_def: AnoncredsObject, reg_def_private: AnoncredsObject, reg_idx: u32, - tails_path: String, } impl RevocationConfig { @@ -39,7 +37,6 @@ impl RevocationConfig { reg_def: self.reg_def.cast_ref()?, reg_def_private: self.reg_def_private.cast_ref()?, registry_idx: self.reg_idx, - tails_reader: TailsFileReader::new_tails_reader(self.tails_path.as_str()), }) } } @@ -103,11 +100,6 @@ pub extern "C" fn anoncreds_create_credential( None } else { let revocation = unsafe { &*revocation }; - let tails_path = revocation - .tails_path - .as_opt_str() - .ok_or_else(|| err_msg!("Missing tails file path"))? - .to_string(); Some(RevocationConfig { reg_def: revocation.reg_def.load()?, reg_def_private: revocation.reg_def_private.load()?, @@ -115,7 +107,6 @@ pub extern "C" fn anoncreds_create_credential( .reg_idx .try_into() .map_err(|_| err_msg!("Invalid revocation index"))?, - tails_path, }) }; diff --git a/src/services/helpers.rs b/src/services/helpers.rs index 431d372a..31eb3634 100644 --- a/src/services/helpers.rs +++ b/src/services/helpers.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; use crate::cl::{ - bn::BigNumber, CredentialSchema, CredentialValues, Issuer, MasterSecret, NonCredentialSchema, - SubProofRequest, Verifier, + bn::BigNumber, CredentialSchema, CredentialValues, Issuer, LinkSecret as ClLinkSecret, + NonCredentialSchema, SubProofRequest, Verifier, }; use crate::data_types::{ credential::AttributeValues, @@ -46,7 +46,7 @@ pub fn build_non_credential_schema() -> Result { pub fn build_credential_values( credential_values: &HashMap, - link_secret: Option<&MasterSecret>, + link_secret: Option<&ClLinkSecret>, ) -> Result { trace!( "build_credential_values >>> credential_values: {:?}", diff --git a/src/services/prover.rs b/src/services/prover.rs index 825bbfbc..fc6a5543 100644 --- a/src/services/prover.rs +++ b/src/services/prover.rs @@ -641,7 +641,7 @@ pub fn create_or_update_revocation_state( let mut issued = HashSet::::new(); let mut revoked = HashSet::::new(); - let tails_reader = TailsFileReader::new_tails_reader(tails_path); + let tails_reader = TailsFileReader::new(tails_path); let witness = if let (Some(source_rev_state), Some(source_rev_list)) = (rev_state, old_rev_status_list) { diff --git a/src/services/tails.rs b/src/services/tails.rs index b33f2329..ca9cf209 100644 --- a/src/services/tails.rs +++ b/src/services/tails.rs @@ -1,15 +1,17 @@ -use sha2::{Digest, Sha256}; use std::cell::RefCell; +use std::fmt::Debug; use std::fs::File; -use std::io::{Read, Seek, SeekFrom, Write}; -use std::path::PathBuf; -use tempfile; +use std::io::{BufRead, BufReader, BufWriter, Read, Seek, SeekFrom, Write}; +use std::path::{Path, PathBuf}; + +use rand::random; +use sha2::{Digest, Sha256}; use crate::cl::{ - Error as CryptoError, ErrorKind as CryptoErrorKind, RevocationTailsAccessor, - RevocationTailsGenerator, Tail, + Error as ClError, ErrorKind as ClErrorKind, RevocationTailsAccessor, RevocationTailsGenerator, + Tail, }; -use crate::error::Result; +use crate::error::Error; use crate::utils::base58; const TAILS_BLOB_TAG_SZ: u8 = 2; @@ -28,9 +30,9 @@ impl TailsReader { } } -pub trait TailsReaderImpl: std::fmt::Debug + Send { - fn hash(&mut self) -> Result>; - fn read(&mut self, size: usize, offset: usize) -> Result>; +pub trait TailsReaderImpl: Debug + Send { + fn hash(&mut self) -> Result, Error>; + fn read(&mut self, size: usize, offset: usize) -> Result, Error>; } impl RevocationTailsAccessor for TailsReader { @@ -38,7 +40,7 @@ impl RevocationTailsAccessor for TailsReader { &self, tail_id: u32, accessor: &mut dyn FnMut(&Tail), - ) -> std::result::Result<(), CryptoError> { + ) -> std::result::Result<(), ClError> { trace!("access_tail >>> tail_id: {:?}", tail_id); let tail_bytes = self @@ -48,12 +50,10 @@ impl RevocationTailsAccessor for TailsReader { TAIL_SIZE, TAIL_SIZE * tail_id as usize + TAILS_BLOB_TAG_SZ as usize, ) - .map_err(|_| { - CryptoError::new( - CryptoErrorKind::InvalidState, - "Can't read tail bytes from file", - ) - })?; // FIXME: IO error should be returned + .map_err(|e| { + error!("IO error reading tails file: {e}"); + ClError::new(ClErrorKind::InvalidState, "Could not read from tails file") + })?; let tail = Tail::from_bytes(tail_bytes.as_slice())?; accessor(&tail); @@ -66,13 +66,12 @@ impl RevocationTailsAccessor for TailsReader { #[derive(Debug)] pub struct TailsFileReader { path: String, - file: Option, + file: Option>, hash: Option>, } impl TailsFileReader { - #[must_use] - pub fn new_tails_reader(path: &str) -> TailsReader { + pub fn new(path: &str) -> TailsReader { TailsReader::new(Self { path: path.to_owned(), file: None, @@ -80,12 +79,12 @@ impl TailsFileReader { }) } - pub fn open(&mut self) -> Result<()> { + pub fn open(&mut self) -> Result<&mut BufReader, Error> { if self.file.is_none() { let file = File::open(self.path.clone())?; - self.file.replace(file); + self.file.replace(BufReader::new(file)); } - Ok(()) + Ok(self.file.as_mut().unwrap()) } pub fn close(&mut self) { @@ -94,32 +93,33 @@ impl TailsFileReader { } impl TailsReaderImpl for TailsFileReader { - fn hash(&mut self) -> Result> { - if self.hash.is_some() { - return Ok(self.hash.as_ref().unwrap().clone()); + fn hash(&mut self) -> Result, Error> { + if let Some(hash) = self.hash.as_ref() { + return Ok(hash.clone()); } - self.open()?; - let file = self.file.as_mut().unwrap(); - file.rewind()?; + let file = self.open()?; + file.seek(SeekFrom::Start(0))?; let mut hasher = Sha256::default(); - let mut buf = [0u8; 1024]; loop { - let sz = file.read(&mut buf)?; - if sz == 0 { - self.hash = Some(hasher.finalize().to_vec()); - return Ok(self.hash.as_ref().unwrap().clone()); + let buf = file.fill_buf()?; + let len = buf.len(); + if len == 0 { + break; } - hasher.update(&buf[0..sz]); + hasher.update(&buf); + file.consume(len); } + let hash = hasher.finalize().to_vec(); + self.hash.replace(hash.clone()); + Ok(hash) } - fn read(&mut self, size: usize, offset: usize) -> Result> { + fn read(&mut self, size: usize, offset: usize) -> Result, Error> { let mut buf = vec![0u8; size]; - self.open()?; - let file = self.file.as_mut().unwrap(); + let file = self.open()?; file.seek(SeekFrom::Start(offset as u64))?; file.read_exact(buf.as_mut_slice())?; @@ -128,7 +128,10 @@ impl TailsReaderImpl for TailsFileReader { } pub trait TailsWriter: std::fmt::Debug { - fn write(&mut self, generator: &mut RevocationTailsGenerator) -> Result<(String, String)>; + fn write( + &mut self, + generator: &mut RevocationTailsGenerator, + ) -> Result<(String, String), Error>; } #[derive(Debug)] @@ -139,38 +142,66 @@ pub struct TailsFileWriter { impl TailsFileWriter { pub fn new(root_path: Option) -> Self { Self { - root_path: root_path.map_or_else(std::env::temp_dir, PathBuf::from), + root_path: root_path + .map(PathBuf::from) + .unwrap_or_else(std::env::temp_dir), } } } impl TailsWriter for TailsFileWriter { - fn write(&mut self, generator: &mut RevocationTailsGenerator) -> Result<(String, String)> { - let mut tempf = tempfile::NamedTempFile::new_in(self.root_path.clone())?; - let file = tempf.as_file_mut(); + fn write( + &mut self, + generator: &mut RevocationTailsGenerator, + ) -> Result<(String, String), Error> { + struct TempFile<'a>(&'a Path); + impl TempFile<'_> { + pub fn rename(self, target: &Path) -> Result<(), Error> { + let path = std::mem::ManuallyDrop::new(self).0; + std::fs::rename(path, target) + .map_err(|e| err_msg!("Error moving tails temp file {path:?}: {e}")) + } + } + impl Drop for TempFile<'_> { + fn drop(&mut self) { + if let Err(e) = std::fs::remove_file(self.0) { + error!("Error removing tails temp file {:?}: {e}", self.0); + } + } + } + + let temp_name = format!("{:020}.tmp", random::()); + let temp_path = self.root_path.join(temp_name); + let file = File::options() + .read(true) + .write(true) + .create_new(true) + .open(temp_path.clone()) + .map_err(|e| err_msg!(IOError, "Error creating tails temp file {temp_path:?}: {e}"))?; + let temp_handle = TempFile(&temp_path); + let mut buf = BufWriter::new(file); let mut hasher = Sha256::default(); let version = &[0u8, 2u8]; - file.write_all(version)?; + buf.write(version)?; hasher.update(version); while let Some(tail) = generator.try_next()? { let tail_bytes = tail.to_bytes()?; - file.write_all(tail_bytes.as_slice())?; - hasher.update(tail_bytes); + buf.write(&tail_bytes)?; + hasher.update(&tail_bytes); } - let tails_size = file.stream_position()?; + let mut file = buf + .into_inner() + .map_err(|e| err_msg!("Error flushing output file: {e}"))?; + let tails_size = file.seek(SeekFrom::Current(0))?; let hash = base58::encode(hasher.finalize()); - let path = tempf.path().with_file_name(hash.clone()); - let _outf = match tempf.persist_noclobber(&path) { - Ok(f) => f, - Err(err) => { - return Err(err_msg!(IOError, "Error persisting tails file: {}", err,)); - } - }; - let path = path.to_string_lossy().into_owned(); + let target_path = self.root_path.join(&hash); + drop(file); + temp_handle.rename(&target_path)?; + let target_path = target_path.to_string_lossy().into_owned(); debug!( "TailsFileWriter: wrote tails file [size {}]: {}", - tails_size, path + tails_size, target_path ); - Ok((path, hash)) + Ok((target_path, hash)) } } diff --git a/src/services/types.rs b/src/services/types.rs index fde858f3..945728e9 100644 --- a/src/services/types.rs +++ b/src/services/types.rs @@ -1,4 +1,3 @@ -use super::tails::TailsReader; use crate::cl::{RevocationRegistry as CryptoRevocationRegistry, Witness}; pub use crate::data_types::{ cred_def::{CredentialDefinitionPrivate, CredentialKeyCorrectnessProof, SignatureType}, @@ -219,18 +218,16 @@ pub struct CredentialRevocationConfig<'a> { pub reg_def: &'a RevocationRegistryDefinition, pub reg_def_private: &'a RevocationRegistryDefinitionPrivate, pub registry_idx: u32, - pub tails_reader: TailsReader, } impl<'a> std::fmt::Debug for CredentialRevocationConfig<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "CredentialRevocationConfig {{ reg_def: {:?}, private: {:?}, idx: {}, reader: {:?} }}", + "CredentialRevocationConfig {{ reg_def: {:?}, private: {:?}, idx: {} }}", self.reg_def, secret!(self.reg_def_private), secret!(self.registry_idx), - self.tails_reader, ) } } diff --git a/tests/anoncreds_demos.rs b/tests/anoncreds_demos.rs index c4163ea4..f6330bb4 100644 --- a/tests/anoncreds_demos.rs +++ b/tests/anoncreds_demos.rs @@ -4,7 +4,7 @@ use anoncreds::data_types::rev_reg_def::RevocationRegistryDefinitionId; use anoncreds::data_types::schema::SchemaId; use anoncreds::issuer; use anoncreds::prover; -use anoncreds::tails::{TailsFileReader, TailsFileWriter}; +use anoncreds::tails::TailsFileWriter; use anoncreds::types::{CredentialRevocationConfig, PresentCredentials}; use anoncreds::verifier; use serde_json::json; @@ -243,7 +243,6 @@ fn anoncreds_demo_works_with_revocation_for_single_issuer_single_prover() { // Get the location of the tails_file so it can be read let tails_location = gvt_rev_reg_def.value.tails_location.clone(); - let tr = TailsFileReader::new_tails_reader(tails_location.as_str()); let issue_cred = issuer::create_credential( &gvt_cred_def, @@ -257,7 +256,6 @@ fn anoncreds_demo_works_with_revocation_for_single_issuer_single_prover() { reg_def: &gvt_rev_reg_def, reg_def_private: &gvt_rev_reg_def_priv, registry_idx: fixtures::GVT_REV_IDX, - tails_reader: tr, }), ) .expect("Error creating credential"); diff --git a/tests/utils/mock.rs b/tests/utils/mock.rs index e85e3d2e..49de14f9 100644 --- a/tests/utils/mock.rs +++ b/tests/utils/mock.rs @@ -15,7 +15,7 @@ use anoncreds::{ schema::{Schema, SchemaId}, }, issuer, prover, - tails::{TailsFileReader, TailsFileWriter}, + tails::TailsFileWriter, types::{ CredentialDefinitionConfig, CredentialRequest, CredentialRevocationConfig, MakeCredentialValues, PresentCredentials, PresentationRequest, RegistryType, SignatureType, @@ -268,20 +268,14 @@ impl<'a> Mock<'a> { } let (rev_config, rev_id) = match issuer_wallet.rev_defs.get(rev_reg_id) { - Some(stored_rev_def) => { - let tr = TailsFileReader::new_tails_reader( - stored_rev_def.public.value.tails_location.as_str(), - ); - ( - Some(CredentialRevocationConfig { - reg_def: &stored_rev_def.public, - reg_def_private: &stored_rev_def.private, - registry_idx: rev_idx, - tails_reader: tr, - }), - Some(RevocationRegistryId::new_unchecked(rev_reg_id)), - ) - } + Some(stored_rev_def) => ( + Some(CredentialRevocationConfig { + reg_def: &stored_rev_def.public, + reg_def_private: &stored_rev_def.private, + registry_idx: rev_idx, + }), + Some(RevocationRegistryId::new_unchecked(rev_reg_id)), + ), None => (None, None), }; diff --git a/wrappers/python/anoncreds/bindings.py b/wrappers/python/anoncreds/bindings.py index b7ea1233..bdf212fe 100644 --- a/wrappers/python/anoncreds/bindings.py +++ b/wrappers/python/anoncreds/bindings.py @@ -398,7 +398,6 @@ class RevocationConfig(Structure): ("rev_reg_def", ObjectHandle), ("rev_reg_def_private", ObjectHandle), ("rev_reg_index", c_int64), - ("tails_path", c_char_p), ] @classmethod @@ -407,13 +406,11 @@ def create( rev_reg_def: AnoncredsObject, rev_reg_def_private: AnoncredsObject, rev_reg_index: int, - tails_path: str, ) -> "RevocationConfig": config = RevocationConfig( rev_reg_def=rev_reg_def.handle, rev_reg_def_private=rev_reg_def_private.handle, rev_reg_index=rev_reg_index, - tails_path=encode_str(tails_path), ) keepalive(config, rev_reg_def, rev_reg_def_private) return config diff --git a/wrappers/python/anoncreds/types.py b/wrappers/python/anoncreds/types.py index 5b1b4b2a..6dc0ae07 100644 --- a/wrappers/python/anoncreds/types.py +++ b/wrappers/python/anoncreds/types.py @@ -630,7 +630,6 @@ def __init__( rev_reg_def: Union[str, "RevocationRegistryDefinition"], rev_reg_def_private: Union[str, "RevocationRegistryDefinitionPrivate"], rev_reg_index: int, - tails_path: str, ): if not isinstance(rev_reg_def, bindings.AnoncredsObject): rev_reg_def = RevocationRegistryDefinition.load(rev_reg_def) @@ -641,7 +640,6 @@ def __init__( ) self.rev_reg_def_private = rev_reg_def_private self.rev_reg_index = rev_reg_index - self.tails_path = tails_path @property def _native(self) -> bindings.RevocationConfig: @@ -649,7 +647,6 @@ def _native(self) -> bindings.RevocationConfig: self.rev_reg_def, self.rev_reg_def_private, self.rev_reg_index, - self.tails_path, ) class NonrevokedIntervalOverride: diff --git a/wrappers/python/demo/test.py b/wrappers/python/demo/test.py index cf70329f..b2da6d4b 100644 --- a/wrappers/python/demo/test.py +++ b/wrappers/python/demo/test.py @@ -1,5 +1,3 @@ -from time import time - from anoncreds import ( generate_nonce, create_link_secret, @@ -14,7 +12,6 @@ PresentCredentials, RevocationRegistryDefinition, RevocationStatusList, - NonrevokedIntervalOverride, Schema, ) @@ -92,7 +89,6 @@ rev_reg_def_pub, rev_reg_def_private, rev_idx, - rev_reg_def_pub.tails_location, ), )