Skip to content

Commit

Permalink
Merge pull request #102 from whalelephant/nonrevoked-interval-checks
Browse files Browse the repository at this point in the history
Support combination of revocable / non-revocable credentials and timestamp override
  • Loading branch information
berendsliedrecht authored Feb 28, 2023
2 parents 36564ba + 0a57d4d commit 8c77f7b
Show file tree
Hide file tree
Showing 10 changed files with 568 additions and 339 deletions.
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

0 comments on commit 8c77f7b

Please sign in to comment.