Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support combination of revocable / non-revocable credentials and timestamp override #102

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 95 additions & 5 deletions src/data_types/pres_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub struct PresentationRequestPayload {
pub requested_attributes: HashMap<String, AttributeInfo>,
#[serde(default)]
pub requested_predicates: HashMap<String, PredicateInfo>,
pub non_revoked: Option<NonRevocedInterval>,
pub non_revoked: Option<NonRevokedInterval>,
}

#[derive(Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -122,20 +122,70 @@ impl Serialize for PresentationRequest {
#[allow(unused)]
pub type PresentationRequestExtraQuery = HashMap<String, Query>;

#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct NonRevocedInterval {
#[derive(Clone, Default, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct NonRevokedInterval {
pub from: Option<u64>,
pub to: Option<u64>,
}

impl NonRevokedInterval {
pub fn new(from: Option<u64>, to: Option<u64>) -> Self {
Self { from, to }
}
// Returns the most stringent interval,
// i.e. the latest from and the earliest to
pub fn compare_and_set(&mut self, to_compare: &NonRevokedInterval) {
// Update if
// - the new `from` value is later, smaller interval
// - the new `from` value is Some if previouly was None
match (self.from, to_compare.from) {
(Some(old_from), Some(new_from)) => {
if old_from.lt(&new_from) {
self.from = to_compare.from
}
}
(None, Some(_)) => self.from = to_compare.from,
_ => (),
}
// Update if
// - the new `to` value is earlier, smaller interval
// - the new `to` value is Some if previouly was None
match (self.to, to_compare.to) {
(Some(old_to), Some(new_to)) => {
if new_to.lt(&old_to) {
self.to = to_compare.to
}
}
(None, Some(_)) => self.to = to_compare.to,
_ => (),
}
}

pub fn update_with_override(&mut self, override_map: &HashMap<u64, u64>) {
self.from.map(|from| {
override_map
.get(&from)
.map(|&override_timestamp| self.from = Some(override_timestamp))
});
}

pub fn is_valid(&self, timestamp: u64) -> Result<(), ValidationError> {
if timestamp.lt(&self.from.unwrap_or(0)) || timestamp.gt(&self.to.unwrap_or(u64::MAX)) {
Err(invalid!("Invalid timestamp"))
} else {
Ok(())
}
}
}

#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct AttributeInfo {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub names: Option<Vec<String>>,
pub restrictions: Option<Query>,
pub non_revoked: Option<NonRevocedInterval>,
pub non_revoked: Option<NonRevokedInterval>,
}

#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
Expand All @@ -144,7 +194,7 @@ pub struct PredicateInfo {
pub p_type: PredicateTypes,
pub p_value: i32,
pub restrictions: Option<Query>,
pub non_revoked: Option<NonRevocedInterval>,
pub non_revoked: Option<NonRevokedInterval>,
}

#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
Expand Down Expand Up @@ -330,4 +380,44 @@ mod tests {
serde_json::from_str::<PresentationRequest>(&req_json).unwrap_err();
}
}

#[test]
fn override_works() {
let mut interval = NonRevokedInterval::default();
let override_map = HashMap::from([(10u64, 5u64)]);

interval.from = Some(10);
interval.update_with_override(&override_map);
assert_eq!(interval.from.unwrap(), 5u64);
}

#[test]
fn compare_and_set_works() {
let mut int = NonRevokedInterval::default();
let wide_int = NonRevokedInterval::new(Some(1), Some(100));
let mid_int = NonRevokedInterval::new(Some(5), Some(80));
let narrow_int = NonRevokedInterval::new(Some(10), Some(50));

assert_eq!(int.from, None);
assert_eq!(int.to, None);

// From None to Some
int.compare_and_set(&wide_int);
assert_eq!(int.from, wide_int.from);
assert_eq!(int.to, wide_int.to);

// Update when more narrow
int.compare_and_set(&mid_int);
assert_eq!(int.from, mid_int.from);
assert_eq!(int.to, mid_int.to);

// Do Not Update when wider
int.compare_and_set(&wide_int);
assert_eq!(int.from, mid_int.from);
assert_eq!(int.to, mid_int.to);

int.compare_and_set(&narrow_int);
assert_eq!(int.from, narrow_int.from);
assert_eq!(int.to, narrow_int.to);
}
}
66 changes: 65 additions & 1 deletion src/ffi/presentation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl_anoncreds_object_from_json!(Presentation, anoncreds_presentation_from_json)
#[repr(C)]
pub struct FfiCredentialEntry {
credential: ObjectHandle,
timestamp: i64,
timestamp: i32,
rev_state: ObjectHandle,
}

Expand Down Expand Up @@ -195,6 +195,50 @@ pub extern "C" fn anoncreds_create_presentation(
})
}

/// Optional value for overriding the non-revoked interval in the PresentationRequest
/// This only overrides the `from` value as a Revocation Status List is deemed valid until the next
/// entry.
///
/// E.g. if the ledger has Revocation Status List at timestamps [0, 100, 200],
/// let's call them List0, List100, List200. Then:
///
/// ```txt
///
/// List0 is valid List100 is valid
/// ______|_______ _______|_______
/// | | |
/// List 0 ----------- 100 ----------- 200
/// ```
///
/// A `nonrevoked_interval = {from: 50, to: 150}` should accept both List0 and
/// List100.
///
#[derive(Debug)]
#[repr(C)]
pub struct FfiNonrevokedIntervalOverride<'a> {
rev_reg_def_id: FfiStr<'a>,
/// Timestamp in the `PresentationRequest`
requested_from_ts: i32,
/// Timestamp from which verifier accepts,
/// should be less than `req_timestamp`
override_rev_status_list_ts: i32,
}

impl<'a> FfiNonrevokedIntervalOverride<'a> {
fn load(&self) -> Result<(RevocationRegistryDefinitionId, u64, u64)> {
let id = RevocationRegistryDefinitionId::new(self.rev_reg_def_id.as_str().to_owned())?;
let requested_from_ts = self
.requested_from_ts
.try_into()
.map_err(|_| err_msg!("Invalid req timestamp "))?;
let override_rev_status_list_ts = self
.override_rev_status_list_ts
.try_into()
.map_err(|_| err_msg!("Invalid override timestamp "))?;
Ok((id, requested_from_ts, override_rev_status_list_ts))
}
}

#[no_mangle]
pub extern "C" fn anoncreds_verify_presentation(
presentation: ObjectHandle,
Expand All @@ -206,6 +250,7 @@ pub extern "C" fn anoncreds_verify_presentation(
rev_reg_defs: FfiList<ObjectHandle>,
rev_reg_def_ids: FfiStrList,
rev_status_list: FfiList<ObjectHandle>,
nonrevoked_interval_override: FfiList<FfiNonrevokedIntervalOverride>,
result_p: *mut i8,
) -> ErrorCode {
catch_error(|| {
Expand Down Expand Up @@ -266,13 +311,32 @@ pub extern "C" fn anoncreds_verify_presentation(
let rev_status_list: Result<Vec<&RevocationStatusList>> = rev_status_list.refs();
let rev_status_list = rev_status_list.ok();

let override_entries = {
let override_ffi_entries = nonrevoked_interval_override.as_slice();
override_ffi_entries.iter().try_fold(
Vec::with_capacity(override_ffi_entries.len()),
|mut v, entry| -> Result<Vec<(RevocationRegistryDefinitionId, u64, u64)>> {
v.push(entry.load()?);
Ok(v)
},
)?
};
let mut map_nonrevoked_interval_override = HashMap::new();
for (id, req_timestamp, override_timestamp) in override_entries.iter() {
map_nonrevoked_interval_override
.entry(id)
.or_insert_with(HashMap::new)
.insert(*req_timestamp, *override_timestamp);
}

let verify = verify_presentation(
presentation.load()?.cast_ref()?,
pres_req.load()?.cast_ref()?,
&schemas,
&cred_defs,
rev_reg_defs,
rev_status_list,
Some(&map_nonrevoked_interval_override),
)?;
unsafe { *result_p = verify as i8 };
Ok(())
Expand Down
142 changes: 98 additions & 44 deletions src/services/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use std::collections::{HashMap, HashSet};
use crate::data_types::{
credential::AttributeValues,
nonce::Nonce,
pres_request::{AttributeInfo, NonRevocedInterval, PredicateInfo},
pres_request::{AttributeInfo, NonRevokedInterval, PredicateInfo, PresentationRequestPayload},
presentation::RequestedProof,
};
use crate::utils::hash::SHA256;

Expand Down Expand Up @@ -134,55 +135,108 @@ pub fn build_sub_proof_request(
Ok(res)
}

pub fn get_non_revoc_interval(
global_interval: &Option<NonRevocedInterval>,
local_interval: &Option<NonRevocedInterval>,
) -> Option<NonRevocedInterval> {
trace!(
"get_non_revoc_interval >>> global_interval: {:?}, local_interval: {:?}",
global_interval,
local_interval
);

let interval = local_interval
.clone()
.or_else(|| global_interval.clone().or(None));

trace!("get_non_revoc_interval <<< interval: {:?}", interval);

interval
}

pub fn new_nonce() -> Result<Nonce> {
Nonce::new().map_err(err_map!(Unexpected))
}

#[cfg(test)]
mod tests {
use super::*;
pub fn get_revealed_attributes_for_credential(
sub_proof_index: usize,
requested_proof: &RequestedProof,
pres_req: &PresentationRequestPayload,
) -> Result<(Vec<AttributeInfo>, Option<NonRevokedInterval>)> {
trace!("_get_revealed_attributes_for_credential >>> sub_proof_index: {:?}, requested_credentials: {:?}, pres_req: {:?}",
sub_proof_index, requested_proof, pres_req);
let mut non_revoked_interval: Option<NonRevokedInterval> = None;
let mut revealed_attrs_for_credential = requested_proof
.revealed_attrs
.iter()
.filter(|&(attr_referent, revealed_attr_info)| {
sub_proof_index == revealed_attr_info.sub_proof_index as usize
&& pres_req.requested_attributes.contains_key(attr_referent)
})
.map(|(attr_referent, _)| {
let info = pres_req.requested_attributes[attr_referent].clone();
if let Some(int) = &info.non_revoked {
match non_revoked_interval.as_mut() {
Some(ni) => {
ni.compare_and_set(int);
}
None => non_revoked_interval = Some(int.clone()),
}
};

info
})
.collect::<Vec<AttributeInfo>>();

revealed_attrs_for_credential.append(
&mut requested_proof
.revealed_attr_groups
.iter()
.filter(|&(attr_referent, revealed_attr_info)| {
sub_proof_index == revealed_attr_info.sub_proof_index as usize
&& pres_req.requested_attributes.contains_key(attr_referent)
})
.map(|(attr_referent, _)| {
let info = pres_req.requested_attributes[attr_referent].clone();
if let Some(int) = &info.non_revoked {
match non_revoked_interval.as_mut() {
Some(ni) => {
ni.compare_and_set(int);
}
None => non_revoked_interval = Some(NonRevokedInterval::default()),
}
};
info
})
.collect::<Vec<AttributeInfo>>(),
);

fn _interval() -> NonRevocedInterval {
NonRevocedInterval {
from: None,
to: Some(123),
}
}
trace!(
"_get_revealed_attributes_for_credential <<< revealed_attrs_for_credential: {:?}",
revealed_attrs_for_credential
);

#[test]
fn get_non_revoc_interval_for_global() {
let res = get_non_revoc_interval(&Some(_interval()), &None).unwrap();
assert_eq!(_interval(), res);
}
Ok((revealed_attrs_for_credential, non_revoked_interval))
}

#[test]
fn get_non_revoc_interval_for_local() {
let res = get_non_revoc_interval(&None, &Some(_interval())).unwrap();
assert_eq!(_interval(), res);
}
pub fn get_predicates_for_credential(
sub_proof_index: usize,
requested_proof: &RequestedProof,
pres_req: &PresentationRequestPayload,
) -> Result<(Vec<PredicateInfo>, Option<NonRevokedInterval>)> {
trace!("_get_predicates_for_credential >>> sub_proof_index: {:?}, requested_credentials: {:?}, pres_req: {:?}",
sub_proof_index, requested_proof, pres_req);

let mut non_revoked_interval: Option<NonRevokedInterval> = None;
let predicates_for_credential = requested_proof
.predicates
.iter()
.filter(|&(predicate_referent, requested_referent)| {
sub_proof_index == requested_referent.sub_proof_index as usize
&& pres_req
.requested_predicates
.contains_key(predicate_referent)
})
.map(|(predicate_referent, _)| {
let info = pres_req.requested_predicates[predicate_referent].clone();
if let Some(int) = &info.non_revoked {
match non_revoked_interval.as_mut() {
Some(ni) => {
ni.compare_and_set(int);
}
None => non_revoked_interval = Some(int.clone()),
}
};

info
})
.collect::<Vec<PredicateInfo>>();

#[test]
fn get_non_revoc_interval_for_none() {
let res = get_non_revoc_interval(&None, &None);
assert_eq!(None, res);
}
trace!(
"_get_predicates_for_credential <<< predicates_for_credential: {:?}",
predicates_for_credential
);

Ok((predicates_for_credential, non_revoked_interval))
}
Loading