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

feat: Cred Issuance V2 APIs #987

Open
wants to merge 61 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
2ad71ed
initial holder draft
gmulhearn-anonyome Sep 19, 2023
c4e5b2b
moved around
gmulhearn-anonyome Sep 19, 2023
5465287
renaming
gmulhearn-anonyome Sep 19, 2023
0d2f894
holder alt flows
gmulhearn-anonyome Sep 20, 2023
28cb51e
demos and such
gmulhearn-anonyome Sep 20, 2023
2b2283b
fmt
gmulhearn-anonyome Sep 20, 2023
3bb64c3
ok issuer impl, and moved mod
gmulhearn-anonyome Sep 20, 2023
6c15151
demoing and some cleaning
gmulhearn-anonyome Sep 20, 2023
bce7856
mock the ld demo
gmulhearn-anonyome Sep 20, 2023
9a3205c
cleaning
gmulhearn-anonyome Sep 20, 2023
ce0f5c0
replacement id
gmulhearn-anonyome Sep 20, 2023
6471cfc
comments
gmulhearn-anonyome Sep 20, 2023
0e9bb4f
embed the format in all states
gmulhearn-anonyome Sep 21, 2023
d384543
initial attempt
gmulhearn-anonyome Sep 21, 2023
72b5a58
type tests
gmulhearn-anonyome Sep 21, 2023
c3875eb
testing
gmulhearn-anonyome Sep 21, 2023
6db278e
fmt
gmulhearn-anonyome Sep 21, 2023
07022e3
error
gmulhearn-anonyome Sep 21, 2023
afd0f43
Merge branch 'main' into gm/issue-cred-v2-messages
gmulhearn-anonyome Sep 24, 2023
45e8c18
move v1 stuff together
gmulhearn-anonyome Sep 24, 2023
6edfeaa
fmt
gmulhearn-anonyome Sep 24, 2023
2cff482
refactored into ariesmessage enum layer
gmulhearn-anonyome Sep 24, 2023
2d6fc51
rename v1 messages to have v1 suffix
gmulhearn-anonyome Sep 24, 2023
0346322
derive from
gmulhearn-anonyome Sep 25, 2023
dceb4f5
lint
gmulhearn-anonyome Sep 25, 2023
881ed1a
fmt
gmulhearn-anonyome Sep 25, 2023
bac0381
Merge branch 'main' into gm/cred-issuance-v2-drafting
gmulhearn-anonyome Sep 25, 2023
339ee46
pull main and fmt
gmulhearn-anonyome Sep 25, 2023
dcc3f26
Merge branch 'gm/issue-cred-v2-messages' into gm/cred-issuance-v2-dra…
gmulhearn-anonyome Sep 25, 2023
428ba60
update imports
gmulhearn-anonyome Sep 25, 2023
1fa110c
message construction
gmulhearn-anonyome Sep 25, 2023
19a1c66
thids
gmulhearn-anonyome Sep 25, 2023
72f9d2e
rm many creds
gmulhearn-anonyome Sep 25, 2023
287abd9
attachment extraction
gmulhearn-anonyome Sep 25, 2023
c3dc7ae
better usage of transit
gmulhearn-anonyome Sep 25, 2023
afe8814
Merge branch 'main' into gm/issue-cred-v2-messages
gmulhearn-anonyome Sep 25, 2023
539ca91
Merge branch 'gm/issue-cred-v2-messages' into gm/cred-issuance-v2-dra…
gmulhearn-anonyome Sep 25, 2023
d64749d
unit test setup
gmulhearn-anonyome Sep 26, 2023
685beec
first sys test
gmulhearn-anonyome Sep 26, 2023
bb7ab5d
offer and proposal details APIs
gmulhearn-anonyome Sep 26, 2023
52a386e
thid checking
gmulhearn-anonyome Sep 26, 2023
35b86ba
state modules
gmulhearn-anonyome Sep 26, 2023
6d38661
into failed state
gmulhearn-anonyome Sep 26, 2023
672d17f
construction and deconstruction
gmulhearn-anonyome Sep 26, 2023
a40547b
in progress documenting
gmulhearn-anonyome Sep 26, 2023
21b2796
remove strip option setters
gmulhearn-anonyome Sep 26, 2023
dbce3a7
docco
gmulhearn-anonyome Sep 26, 2023
23cef4f
temp test against acapy
gmulhearn-anonyome Sep 27, 2023
6d3b5bb
Merge branch 'gm/issue-cred-v2-messages' into gm/cred-issuance-v2-dra…
gmulhearn-anonyome Sep 28, 2023
5d050cf
Merge branch 'main' into gm/cred-issuance-v2-drafting
gmulhearn-anonyome Sep 28, 2023
56d7e9a
Merge branch 'main' into gm/cred-issuance-v2-drafting
gmulhearn-anonyome Oct 5, 2023
b48fcd6
Merge branch 'main' into gm/cred-issuance-v2-drafting
gmulhearn-anonyome Oct 17, 2023
17acf7f
lock update
gmulhearn-anonyome Oct 17, 2023
8efba17
fix base64 usage
gmulhearn-anonyome Oct 17, 2023
866c989
trying splitting into processing and state_machines
gmulhearn-anonyome Oct 17, 2023
792f9ed
fix up attachment data extraction and some comments
gmulhearn-anonyome Oct 17, 2023
c709d5b
trying tuple msg approach
gmulhearn-anonyome Oct 17, 2023
88b5bac
rename to completed
gmulhearn-anonyome Oct 17, 2023
7e9a3fb
state fields private and use constructors
gmulhearn-anonyome Oct 17, 2023
b431ad7
remove credential from credreceived state
gmulhearn-anonyome Oct 17, 2023
83ce114
integration test using processing functions
gmulhearn-anonyome Oct 17, 2023
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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions aries_vcx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ android_logger = "0.13.3"
wallet_migrator = { path = "../wallet_migrator" }
async-channel = "1.7.1"
tokio = { version = "1.20", features = ["rt", "macros", "rt-multi-thread"] }
mockall = "0.11.4"
reqwest = "0.11.18" # TODO - DELETE ONLY FOR TEMPORARY TEST!!
Patrik-Stas marked this conversation as resolved.
Show resolved Hide resolved
51 changes: 51 additions & 0 deletions aries_vcx/src/handlers/util.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use base64::{engine::general_purpose, Engine};
use messages::{
decorators::attachment::{Attachment, AttachmentType},
msg_fields::protocols::{
connection::{invitation::Invitation, Connection},
cred_issuance::{v1::CredentialIssuanceV1, v2::CredentialIssuanceV2, CredentialIssuance},
Expand All @@ -19,6 +21,15 @@ use strum_macros::{AsRefStr, EnumString};

use crate::errors::error::{AriesVcxError, AriesVcxErrorKind, VcxResult};

macro_rules! get_thread_id_or_message_id {
($msg:expr) => {
$msg.decorators
.thread
.as_ref()
.map_or($msg.id.clone(), |t| t.thid.clone())
};
}

macro_rules! matches_thread_id {
($msg:expr, $id:expr) => {
$msg.decorators.thread.thid == $id || $msg.decorators.thread.pthid.as_deref() == Some($id)
Expand Down Expand Up @@ -71,10 +82,50 @@ macro_rules! make_attach_from_str {
}

pub(crate) use get_attach_as_string;
pub(crate) use get_thread_id_or_message_id;
pub(crate) use make_attach_from_str;
pub(crate) use matches_opt_thread_id;
pub(crate) use matches_thread_id;

/// Extract/decode the inner data of an [Attachment] as a [Vec<u8>], regardless of whether the inner
/// data is encoded as base64 or JSON.
pub fn extract_attachment_data(attachment: &Attachment) -> VcxResult<Vec<u8>> {
let data = match &attachment.data.content {
AttachmentType::Base64(encoded_attach) => general_purpose::URL_SAFE
.decode(encoded_attach)
.map_err(|_| {
AriesVcxError::from_msg(
AriesVcxErrorKind::EncodeError,
format!("Message attachment is not base64 as expected: {attachment:?}"),
)
})?,
AttachmentType::Json(json_attach) => serde_json::to_vec(json_attach)?,
_ => {
return Err(AriesVcxError::from_msg(
AriesVcxErrorKind::InvalidMessageFormat,
format!("Message attachment is not base64 or JSON: {attachment:?}"),
))
}
};

Ok(data)
}

/// Retrieve the first [Attachment] from a list, where the [Attachment] as an `id` matching the
/// supplied id. Returning an error if no attachment is found.
pub fn get_attachment_with_id<'a>(
attachments: &'a Vec<Attachment>,
id: &String,
) -> VcxResult<&'a Attachment> {
attachments
.iter()
.find(|attachment| attachment.id.as_ref() == Some(id))
.ok_or(AriesVcxError::from_msg(
AriesVcxErrorKind::InvalidMessageFormat,
format!("Message is missing an attachment with the expected ID : {id}."),
))
}

pub fn verify_thread_id(thread_id: &str, message: &AriesMessage) -> VcxResult<()> {
let is_match = match message {
AriesMessage::BasicMessage(msg) => matches_opt_thread_id!(msg, thread_id),
Expand Down
2 changes: 1 addition & 1 deletion aries_vcx/src/protocols/issuance/holder/state_machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ pub fn parse_cred_def_id_from_cred_offer(cred_offer: &str) -> VcxResult<String>
Ok(cred_def_id.to_string())
}

fn _parse_rev_reg_id_from_credential(credential: &str) -> VcxResult<Option<String>> {
pub fn _parse_rev_reg_id_from_credential(credential: &str) -> VcxResult<Option<String>> {
trace!("Holder::_parse_rev_reg_id_from_credential >>>");

let parsed_credential: serde_json::Value = serde_json::from_str(credential).map_err(|err| {
Expand Down
240 changes: 240 additions & 0 deletions aries_vcx/src/protocols/issuance_v2/formats/holder/hyperledger_indy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
use std::marker::PhantomData;

use aries_vcx_core::{
anoncreds::base_anoncreds::BaseAnonCreds, ledger::base_ledger::AnoncredsLedgerRead,
};
use async_trait::async_trait;
use messages::msg_fields::protocols::cred_issuance::v2::{
issue_credential::{IssueCredentialAttachmentFormatType, IssueCredentialV2},
offer_credential::{OfferCredentialAttachmentFormatType, OfferCredentialV2},
propose_credential::ProposeCredentialAttachmentFormatType,
request_credential::RequestCredentialAttachmentFormatType,
};
use shared_vcx::maybe_known::MaybeKnown;

use super::HolderCredentialIssuanceFormat;
use crate::{
errors::error::{AriesVcxError, AriesVcxErrorKind, VcxResult},
protocols::issuance::holder::state_machine::{
_parse_rev_reg_id_from_credential, create_anoncreds_credential_request,
parse_cred_def_id_from_cred_offer,
},
};

/// Structure which implements [HolderCredentialIssuanceFormat] functionality for the `hlindy/...`
/// family of issue-credential-v2 attachment formats.
///
/// This implementation expects and creates attachments of the following types:
/// * [ProposeCredentialAttachmentFormatType::HyperledgerIndyCredentialFilter2_0]
/// * [RequestCredentialAttachmentFormatType::HyperledgerIndyCredentialRequest2_0]
/// * [OfferCredentialAttachmentFormatType::HyperledgerIndyCredentialAbstract2_0]
/// * [IssueCredentialAttachmentFormatType::HyperledgerIndyCredential2_0]
///
/// This is done in accordance to the Aries RFC 0592 Spec:
///
/// https://github.com/hyperledger/aries-rfcs/blob/b3a3942ef052039e73cd23d847f42947f8287da2/features/0592-indy-attachments/README.md
pub struct HyperledgerIndyHolderCredentialIssuanceFormat<'a, R, A>
where
R: AnoncredsLedgerRead,
A: BaseAnonCreds,
{
_data: &'a PhantomData<()>,
_ledger_read: PhantomData<R>,
_anoncreds: PhantomData<A>,
}

pub struct HyperledgerIndyCreateProposalInput {
pub cred_filter: HyperledgerIndyCredentialFilter,
}

#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Builder)]
#[builder(setter(into, strip_option), default)]
pub struct HyperledgerIndyCredentialFilter {
#[serde(skip_serializing_if = "Option::is_none")]
pub schema_issuer_did: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub schema_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub schema_version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub schema_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub issuer_did: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cred_def_id: Option<String>,
}

// Simplified cred abstract, for purpose of easy viewing for consumer
// https://github.com/hyperledger/aries-rfcs/blob/main/features/0592-indy-attachments/README.md#cred-abstract-format
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct HyperledgerIndyOfferDetails {
pub schema_id: String,
pub cred_def_id: String,
}

pub struct HyperledgerIndyCreateRequestInput<'a, R, A>
where
R: AnoncredsLedgerRead,
A: BaseAnonCreds,
{
pub entropy_did: String,
pub ledger: &'a R,
pub anoncreds: &'a A,
}

#[derive(Clone, Debug)]
pub struct HyperledgerIndyCreatedRequestMetadata {
credential_request_metadata: String,
credential_def_json: String,
}

pub struct HyperledgerIndyStoreCredentialInput<'a, R, A>
where
R: AnoncredsLedgerRead,
A: BaseAnonCreds,
{
pub ledger: &'a R,
pub anoncreds: &'a A,
}

#[derive(Clone, Debug)]
pub struct HyperledgerIndyStoredCredentialMetadata {
pub credential_id: String,
}

#[async_trait]
impl<'a, R, A> HolderCredentialIssuanceFormat
for HyperledgerIndyHolderCredentialIssuanceFormat<'a, R, A>
where
R: AnoncredsLedgerRead + 'a,
A: BaseAnonCreds + 'a,
{
type CreateProposalInput = HyperledgerIndyCreateProposalInput;

type OfferDetails = HyperledgerIndyOfferDetails;

type CreateRequestInput = HyperledgerIndyCreateRequestInput<'a, R, A>;
type CreatedRequestMetadata = HyperledgerIndyCreatedRequestMetadata;

type StoreCredentialInput = HyperledgerIndyStoreCredentialInput<'a, R, A>;
type StoredCredentialMetadata = HyperledgerIndyStoredCredentialMetadata;

fn supports_request_independent_of_offer() -> bool {
false
}

fn get_proposal_attachment_format() -> MaybeKnown<ProposeCredentialAttachmentFormatType> {
MaybeKnown::Known(ProposeCredentialAttachmentFormatType::HyperledgerIndyCredentialFilter2_0)
}
fn get_request_attachment_format() -> MaybeKnown<RequestCredentialAttachmentFormatType> {
MaybeKnown::Known(
RequestCredentialAttachmentFormatType::HyperledgerIndyCredentialRequest2_0,
)
}
fn get_offer_attachment_format() -> MaybeKnown<OfferCredentialAttachmentFormatType> {
MaybeKnown::Known(OfferCredentialAttachmentFormatType::HyperledgerIndyCredentialAbstract2_0)
}
fn get_credential_attachment_format() -> MaybeKnown<IssueCredentialAttachmentFormatType> {
MaybeKnown::Known(IssueCredentialAttachmentFormatType::HyperledgerIndyCredential2_0)
}

async fn create_proposal_attachment_content(
data: &HyperledgerIndyCreateProposalInput,
) -> VcxResult<Vec<u8>> {
let filter_bytes = serde_json::to_vec(&data.cred_filter)?;

Ok(filter_bytes)
}

fn extract_offer_details(
offer_message: &OfferCredentialV2,
) -> VcxResult<HyperledgerIndyOfferDetails> {
let attachment = Self::extract_offer_attachment_content(offer_message)?;

Ok(serde_json::from_slice(&attachment)?)
}

async fn create_request_attachment_content(
offer_message: &OfferCredentialV2,
data: &Self::CreateRequestInput,
) -> VcxResult<(Vec<u8>, HyperledgerIndyCreatedRequestMetadata)> {
let offer_bytes = Self::extract_offer_attachment_content(&offer_message)?;
let offer_payload = String::from_utf8(offer_bytes).map_err(|_| {
AriesVcxError::from_msg(
AriesVcxErrorKind::EncodeError,
"Expected payload to be a utf8 string",
)
})?;

let cred_def_id = parse_cred_def_id_from_cred_offer(&offer_payload)?;
let entropy = &data.entropy_did;
let ledger = data.ledger;
let anoncreds = data.anoncreds;

let (credential_request, credential_request_metadata, _, credential_def_json) =
create_anoncreds_credential_request(
ledger,
anoncreds,
&cred_def_id,
&entropy,
&offer_payload,
)
.await?;

Ok((
credential_request.into(),
HyperledgerIndyCreatedRequestMetadata {
credential_request_metadata,
credential_def_json,
},
))
}

async fn create_request_attachment_content_independent_of_offer(
_: &Self::CreateRequestInput,
) -> VcxResult<(Vec<u8>, Self::CreatedRequestMetadata)> {
Err(AriesVcxError::from_msg(
AriesVcxErrorKind::ActionNotSupported,
"Anoncreds cannot create request payload independent of an offer",
))
}

async fn process_and_store_credential(
Patrik-Stas marked this conversation as resolved.
Show resolved Hide resolved
issue_credential_message: &IssueCredentialV2,
user_input: &HyperledgerIndyStoreCredentialInput<R, A>,
request_metadata: &HyperledgerIndyCreatedRequestMetadata,
) -> VcxResult<HyperledgerIndyStoredCredentialMetadata> {
let cred_bytes = Self::extract_credential_attachment_content(&issue_credential_message)?;
let credential_payload = String::from_utf8(cred_bytes).map_err(|_| {
AriesVcxError::from_msg(
AriesVcxErrorKind::EncodeError,
"Expected payload to be a utf8 string",
)
})?;

let ledger = user_input.ledger;
let anoncreds = user_input.anoncreds;

let rev_reg_id = _parse_rev_reg_id_from_credential(&credential_payload)?;
let rev_reg_def_json = if let Some(rev_reg_id) = rev_reg_id {
let json = ledger.get_rev_reg_def_json(&rev_reg_id).await?;
Some(json)
} else {
None
};

let cred_id = anoncreds
.prover_store_credential(
Patrik-Stas marked this conversation as resolved.
Show resolved Hide resolved
None,
&request_metadata.credential_request_metadata,
&credential_payload,
&request_metadata.credential_def_json,
rev_reg_def_json.as_deref(),
)
.await?;

Ok(HyperledgerIndyStoredCredentialMetadata {
credential_id: cred_id,
})
}
}
Loading
Loading