From 3c1f88b67774d806e342ba0904345597fba7b6f9 Mon Sep 17 00:00:00 2001 From: Miroslav Kovar Date: Mon, 15 May 2023 16:12:58 +0200 Subject: [PATCH 1/7] Ledger response parser Signed-off-by: Miroslav Kovar --- Cargo.lock | 39 ++- Cargo.toml | 3 +- .../src/core/profile/modular_libs_profile.rs | 15 +- .../src/core/profile/vdr_proxy_profile.rs | 16 +- aries_vcx_core/Cargo.toml | 5 +- aries_vcx_core/src/errors/mapping_vdrtools.rs | 24 +- aries_vcx_core/src/errors/mod.rs | 2 +- aries_vcx_core/src/ledger/indy_vdr_ledger.rs | 208 ++++----------- aries_vcx_core/src/lib.rs | 3 + indy_ledger_response_parser/Cargo.toml | 12 + .../src/domain/attrib.rs | 27 ++ .../src/domain/constants.rs | 6 + .../src/domain/cred_def.rs | 50 ++++ indy_ledger_response_parser/src/domain/did.rs | 43 +++ indy_ledger_response_parser/src/domain/mod.rs | 8 + .../src/domain/response.rs | 76 ++++++ .../src/domain/rev_reg.rs | 86 ++++++ .../src/domain/rev_reg_def.rs | 26 ++ .../src/domain/schema.rs | 52 ++++ indy_ledger_response_parser/src/lib.rs | 249 ++++++++++++++++++ libvdrtools/indy-api-types/Cargo.toml | 4 +- 21 files changed, 760 insertions(+), 194 deletions(-) create mode 100644 indy_ledger_response_parser/Cargo.toml create mode 100644 indy_ledger_response_parser/src/domain/attrib.rs create mode 100644 indy_ledger_response_parser/src/domain/constants.rs create mode 100644 indy_ledger_response_parser/src/domain/cred_def.rs create mode 100644 indy_ledger_response_parser/src/domain/did.rs create mode 100644 indy_ledger_response_parser/src/domain/mod.rs create mode 100644 indy_ledger_response_parser/src/domain/response.rs create mode 100644 indy_ledger_response_parser/src/domain/rev_reg.rs create mode 100644 indy_ledger_response_parser/src/domain/rev_reg_def.rs create mode 100644 indy_ledger_response_parser/src/domain/schema.rs create mode 100644 indy_ledger_response_parser/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 294c2a00bd..005e65d7b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -211,7 +211,7 @@ dependencies = [ "strum 0.16.0", "strum_macros 0.16.0", "thiserror", - "time 0.3.20", + "time 0.3.21", "tokio", "url", "uuid 0.8.2", @@ -242,6 +242,7 @@ dependencies = [ "derive_builder 0.12.0", "futures", "indy-credx", + "indy-ledger-response-parser", "indy-vdr", "indy-vdr-proxy-client", "lazy_static", @@ -251,7 +252,7 @@ dependencies = [ "serde", "serde_json", "thiserror", - "time 0.3.20", + "time 0.3.21", "tokio", "uuid 1.3.1", ] @@ -2314,6 +2315,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "indy-ledger-response-parser" +version = "0.1.0" +dependencies = [ + "indy-api-types", + "indy-vdr", + "serde", + "serde_json", + "time 0.3.21", + "ursa", +] + [[package]] name = "indy-utils" version = "0.1.0" @@ -2630,7 +2643,7 @@ dependencies = [ "serde_derive", "serde_json", "thiserror", - "time 0.3.20", + "time 0.3.21", "tokio", "uuid 0.7.4", ] @@ -2657,7 +2670,7 @@ dependencies = [ "serde_derive", "serde_json", "thiserror", - "time 0.3.20", + "time 0.3.21", "tokio", "url", "uuid 0.7.4", @@ -2693,7 +2706,7 @@ dependencies = [ "serde_json", "sha2 0.9.9", "sha3 0.9.1", - "time 0.3.20", + "time 0.3.21", "ursa", "uuid 0.8.2", "zeroize", @@ -3950,9 +3963,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.160" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" dependencies = [ "serde_derive", ] @@ -3968,9 +3981,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.160" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", @@ -4533,9 +4546,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" dependencies = [ "serde", "time-core", @@ -4543,9 +4556,9 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "tinyvec" diff --git a/Cargo.toml b/Cargo.toml index 1603116ac4..e9ca92e7be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,8 @@ members = [ "did_resolver", "did_resolver_registry", "did_resolver_sov", - "did_resolver_web" + "did_resolver_web", + "indy_ledger_response_parser" ] [workspace.package] diff --git a/aries_vcx/src/core/profile/modular_libs_profile.rs b/aries_vcx/src/core/profile/modular_libs_profile.rs index 0fd20e637f..717385bd60 100644 --- a/aries_vcx/src/core/profile/modular_libs_profile.rs +++ b/aries_vcx/src/core/profile/modular_libs_profile.rs @@ -3,9 +3,10 @@ use std::sync::Arc; use aries_vcx_core::anoncreds::base_anoncreds::BaseAnonCreds; use aries_vcx_core::anoncreds::credx_anoncreds::IndyCredxAnonCreds; use aries_vcx_core::ledger::base_ledger::BaseLedger; -use aries_vcx_core::ledger::indy_vdr_ledger::IndyVdrLedger; +use aries_vcx_core::ledger::indy_vdr_ledger::{IndyVdrLedger, IndyVdrLedgerConfig}; use aries_vcx_core::ledger::request_submitter::vdr_ledger::{IndyVdrLedgerPool, IndyVdrSubmitter, LedgerPoolConfig}; use aries_vcx_core::wallet::base_wallet::BaseWallet; +use aries_vcx_core::ResponseParser; use crate::errors::error::VcxResult; @@ -21,10 +22,16 @@ pub struct ModularLibsProfile { impl ModularLibsProfile { pub fn new(wallet: Arc, ledger_pool_config: LedgerPoolConfig) -> VcxResult { - let ledger_pool = Arc::new(IndyVdrLedgerPool::new(ledger_pool_config)?); - let submitter = Arc::new(IndyVdrSubmitter::new(ledger_pool)); - let ledger = Arc::new(IndyVdrLedger::new(Arc::clone(&wallet), submitter)); let anoncreds = Arc::new(IndyCredxAnonCreds::new(Arc::clone(&wallet))); + let ledger_pool = Arc::new(IndyVdrLedgerPool::new(ledger_pool_config)?); + let request_submitter = Arc::new(IndyVdrSubmitter::new(ledger_pool)); + let response_parser = Arc::new(ResponseParser::new()); + let config = IndyVdrLedgerConfig { + wallet: wallet.clone(), + request_submitter, + response_parser, + }; + let ledger = Arc::new(IndyVdrLedger::new(config)); Ok(ModularLibsProfile { wallet, ledger, diff --git a/aries_vcx/src/core/profile/vdr_proxy_profile.rs b/aries_vcx/src/core/profile/vdr_proxy_profile.rs index 3e64f4c574..51e9dcaca9 100644 --- a/aries_vcx/src/core/profile/vdr_proxy_profile.rs +++ b/aries_vcx/src/core/profile/vdr_proxy_profile.rs @@ -3,10 +3,12 @@ use std::sync::Arc; use aries_vcx_core::{ anoncreds::{base_anoncreds::BaseAnonCreds, indy_anoncreds::IndySdkAnonCreds}, ledger::{ - base_ledger::BaseLedger, indy_vdr_ledger::IndyVdrLedger, request_submitter::vdr_proxy::VdrProxySubmitter, + base_ledger::BaseLedger, + indy_vdr_ledger::{IndyVdrLedger, IndyVdrLedgerConfig}, + request_submitter::vdr_proxy::VdrProxySubmitter, }, wallet::{base_wallet::BaseWallet, indy_wallet::IndySdkWallet}, - VdrProxyClient, WalletHandle, + ResponseParser, VdrProxyClient, WalletHandle, }; use super::profile::Profile; @@ -21,9 +23,15 @@ pub struct VdrProxyProfile { impl VdrProxyProfile { pub fn new(wallet_handle: WalletHandle, client: VdrProxyClient) -> Self { let wallet = Arc::new(IndySdkWallet::new(wallet_handle)); - let submitter = Arc::new(VdrProxySubmitter::new(Arc::new(client))); - let ledger = Arc::new(IndyVdrLedger::new(wallet.clone(), submitter)); let anoncreds = Arc::new(IndySdkAnonCreds::new(wallet_handle)); + let request_submitter = Arc::new(VdrProxySubmitter::new(Arc::new(client))); + let response_parser = Arc::new(ResponseParser::new()); + let config = IndyVdrLedgerConfig { + wallet: wallet.clone(), + request_submitter, + response_parser, + }; + let ledger = Arc::new(IndyVdrLedger::new(config)); VdrProxyProfile { wallet, ledger, diff --git a/aries_vcx_core/Cargo.toml b/aries_vcx_core/Cargo.toml index 9f0eb5bfc5..b37fe3e148 100644 --- a/aries_vcx_core/Cargo.toml +++ b/aries_vcx_core/Cargo.toml @@ -8,8 +8,8 @@ edition = "2021" # Feature flag to include the libvdrtools dependency vdrtools = ["dep:libvdrtools"] # Feature flag to include the 'modular library' dependencies (vdrtools alternatives; indy-vdr, indy-credx) -modular_libs = ["dep:indy-vdr", "dep:indy-credx"] -vdr_proxy_ledger = ["dep:indy-vdr", "dep:indy-credx", "dep:indy-vdr-proxy-client"] +modular_libs = ["dep:indy-vdr", "dep:indy-credx", "dep:indy-ledger-response-parser"] +vdr_proxy_ledger = ["dep:indy-vdr", "dep:indy-credx", "dep:indy-vdr-proxy-client", "dep:indy-ledger-response-parser"] [dependencies] agency_client = { path = "../agency_client" } @@ -42,6 +42,7 @@ uuid = { version = "1.3.0", default-features = false, features = ["v4"] } tokio = { version = "1.20" } # TODO: Point to the official repo if / when vdr-proxy-client PR is merged: https://github.com/hyperledger/indy-vdr/pull/184 indy-vdr-proxy-client = { git = "https://github.com/mirgee/indy-vdr.git", rev = "fab0535", optional = true } +indy-ledger-response-parser = { path = "../indy_ledger_response_parser", optional = true } [dev-dependencies] tokio = { version = "1.20", features = ["rt", "macros", "rt-multi-thread"] } diff --git a/aries_vcx_core/src/errors/mapping_vdrtools.rs b/aries_vcx_core/src/errors/mapping_vdrtools.rs index edc7f56987..0a0e6ba399 100644 --- a/aries_vcx_core/src/errors/mapping_vdrtools.rs +++ b/aries_vcx_core/src/errors/mapping_vdrtools.rs @@ -1,11 +1,23 @@ -use vdrtools::types; -use vdrtools::types::errors::IndyErrorKind; +#[cfg(feature = "vdrtools")] +use vdrtools::types::{ + errors::{IndyError, IndyErrorKind}, + ErrorCode, +}; + +#[cfg(all( + not(feature = "vdrtools"), + any(feature = "modular_libs", feature = "vdr_proxy_ledger") +))] +use indy_ledger_response_parser::{ + errors::{IndyError, IndyErrorKind}, + ErrorCode, +}; use crate::errors::error::{AriesVcxCoreError, AriesVcxCoreErrorKind}; impl From for AriesVcxCoreErrorKind { fn from(indy: IndyErrorKind) -> Self { - use types::errors::IndyErrorKind::*; + use IndyErrorKind::*; match indy { InvalidParam(_) => AriesVcxCoreErrorKind::InvalidLibindyParam, @@ -33,15 +45,15 @@ impl From for AriesVcxCoreErrorKind { WalletAccessFailed => AriesVcxCoreErrorKind::WalletAccessFailed, ProofRejected => AriesVcxCoreErrorKind::ProofRejected, _ => { - let err_code = types::ErrorCode::from(indy) as u32; + let err_code = ErrorCode::from(indy) as u32; AriesVcxCoreErrorKind::VdrToolsError(err_code) } } } } -impl From for AriesVcxCoreError { - fn from(indy: types::errors::IndyError) -> Self { +impl From for AriesVcxCoreError { + fn from(indy: IndyError) -> Self { let vcx_kind: AriesVcxCoreErrorKind = indy.kind().into(); AriesVcxCoreError::from_msg(vcx_kind, indy.to_string()) } diff --git a/aries_vcx_core/src/errors/mod.rs b/aries_vcx_core/src/errors/mod.rs index abdf7777b6..7ce3175ba5 100644 --- a/aries_vcx_core/src/errors/mod.rs +++ b/aries_vcx_core/src/errors/mod.rs @@ -7,5 +7,5 @@ mod mapping_indyvdr; #[cfg(feature = "vdr_proxy_ledger")] mod mapping_indyvdr_proxy; mod mapping_others; -#[cfg(feature = "vdrtools")] +#[cfg(any(feature = "vdrtools", feature = "modular_libs", feature = "vdr_proxy_ledger"))] mod mapping_vdrtools; diff --git a/aries_vcx_core/src/ledger/indy_vdr_ledger.rs b/aries_vcx_core/src/ledger/indy_vdr_ledger.rs index 0587f5b964..0723078304 100644 --- a/aries_vcx_core/src/ledger/indy_vdr_ledger.rs +++ b/aries_vcx_core/src/ledger/indy_vdr_ledger.rs @@ -1,4 +1,5 @@ use indy_credx::ursa::cl::RevocationRegistryDelta as UrsaRevocationRegistryDelta; +use indy_ledger_response_parser::{ResponseParser, RevocationRegistryDeltaInfo, RevocationRegistryInfo}; use indy_vdr as vdr; use std::fmt::{Debug, Formatter}; use std::sync::Arc; @@ -27,22 +28,33 @@ use crate::wallet::base_wallet::BaseWallet; use super::base_ledger::BaseLedger; use super::request_submitter::RequestSubmitter; +pub struct IndyVdrLedgerConfig +where + T: RequestSubmitter + Send + Sync, +{ + pub wallet: Arc, + pub request_submitter: Arc, + pub response_parser: Arc, +} + pub struct IndyVdrLedger where T: RequestSubmitter + Send + Sync, { wallet: Arc, request_submitter: Arc, + response_parser: Arc, } impl IndyVdrLedger where T: RequestSubmitter + Send + Sync, { - pub fn new(wallet: Arc, request_submitter: Arc) -> Self { - IndyVdrLedger { - wallet, - request_submitter, + pub fn new(config: IndyVdrLedgerConfig) -> Self { + Self { + wallet: config.wallet, + request_submitter: config.request_submitter, + response_parser: config.response_parser, } } @@ -248,89 +260,20 @@ where self._sign_and_submit_request(submitter_did, request).await } - async fn get_schema(&self, schema_id: &str, submitter_did: Option<&str>) -> VcxCoreResult { - let _ = submitter_did; - // TODO - future - try from cache first - // TODO - future - do we need to handle someone submitting a schema request by seq number? - - let id = SchemaId::from_str(schema_id)?; - - let request = self.request_builder()?.build_get_schema_request(None, &id)?; - + async fn get_schema(&self, schema_id: &str, _submitter_did: Option<&str>) -> VcxCoreResult { + let request = self + .request_builder()? + .build_get_schema_request(None, &SchemaId::from_str(schema_id)?)?; let response = self._submit_request(request).await?; - - // process the response - let response_json: Value = serde_json::from_str(&response)?; - let result_json = (&response_json).try_get("result")?; - let data_json = result_json.try_get("data")?; - - let seq_no = result_json.get("seqNo").and_then(|x| x.as_u64().map(|x| x as u32)); - - let name = data_json.try_get("name")?; - let name = name.try_as_str()?; - let version = data_json.try_get("version")?; - let version = version.try_as_str()?; - let dest = result_json.try_get("dest")?; - let dest = dest.try_as_str()?; - let schema_id = SchemaId::new(&DidValue::from_str(dest)?, name, version); - - let attr_names = data_json.try_get("attr_names")?; - let attr_names: AttributeNames = serde_json::from_value(attr_names.to_owned())?; - - let schema = SchemaV1 { - id: schema_id, - name: name.to_string(), - version: version.to_string(), - attr_names, - seq_no, - }; - - // TODO - future - store in cache if submitter_did provided - - Ok(serde_json::to_string(&Schema::SchemaV1(schema))?) + let schema = self.response_parser.parse_get_schema_response(&response, None)?; + Ok(serde_json::to_string(&schema)?) } async fn get_cred_def(&self, cred_def_id: &str, submitter_did: Option<&str>) -> VcxCoreResult { - // todo - try from cache if submitter_did provided - let request = self._build_get_cred_def_request(submitter_did, cred_def_id).await?; - let response = self._submit_request(request).await?; - - // process the response - - let response_json: Value = serde_json::from_str(&response)?; - let result_json = (&response_json).try_get("result")?; - - let schema_id = result_json.try_get("ref")?; - let signature_type = result_json.try_get("signature_type")?; - let tag = result_json.get("tag").map_or(json!("default"), |x| x.to_owned()); - let origin_did = result_json.try_get("origin")?; - // (from ACApy) FIXME: issuer has a method to create a cred def ID - // may need to qualify the DID - let cred_def_id = format!( - "{}:3:{}:{}:{}", - origin_did.try_as_str()?, - signature_type.try_as_str()?, - schema_id, - (&tag).try_as_str()? - ); - let data = _get_response_json_data_field(&response)?; - - let cred_def_value = json!({ - "ver": "1.0", - "id": cred_def_id, - "schemaId": schema_id.to_string(), // expected as json string, not as json int - "type": signature_type, - "tag": tag, - "value": data - }); - - let cred_def_json = serde_json::to_string(&cred_def_value)?; - - // todo - store in cache if submitter_did provided - - Ok(cred_def_json) + let cred_def = self.response_parser.parse_get_cred_def_response(&response, None)?; + Ok(serde_json::to_string(&cred_def)?) } async fn get_attr(&self, target_did: &str, attr_name: &str) -> VcxCoreResult { @@ -351,11 +294,9 @@ where let request = self.request_builder()?.build_get_revoc_reg_def_request(None, &id)?; let res = self._submit_request(request).await?; - let mut data = _get_response_json_data_field(&res)?; + let rev_reg_def = self.response_parser.parse_get_revoc_reg_def_response(&res)?; - data["ver"] = Value::String("1.0".to_string()); - - Ok(serde_json::to_string(&data)?) + Ok(serde_json::to_string(&rev_reg_def)?) } async fn get_rev_reg_delta_json( @@ -375,56 +316,20 @@ where .build_get_revoc_reg_delta_request(None, &revoc_reg_def_id, from, to)?; let res = self._submit_request(request).await?; - let res_data = _get_response_json_data_field(&res)?; - let response_value = (&res_data).try_get("value")?; - - let empty_json_list = json!([]); + let RevocationRegistryDeltaInfo { + revoc_reg_def_id, + revoc_reg_delta, + timestamp, + } = self.response_parser.parse_get_revoc_reg_delta_response(&res)?; - let mut delta_value = json!({ - "accum": response_value.try_get("accum_to")?.try_get("value")?.try_get("accum")?, - "issued": if let Some(v) = response_value.get("issued") { v } else { &empty_json_list }, - "revoked": if let Some(v) = response_value.get("revoked") { v } else { &empty_json_list } - }); - - if let Some(accum_from) = response_value - .get("accum_from") - .and_then(|val| (!val.is_null()).then_some(val)) - { - let prev_accum = accum_from.try_get("value")?.try_get("accum")?; - // to check - should this be 'prevAccum'? - delta_value["prev_accum"] = prev_accum.to_owned(); - } - - let reg_delta = json!({"ver": "1.0", "value": delta_value}); - - let delta_timestamp = - response_value - .try_get("accum_to")? - .try_get("txnTime")? - .as_u64() - .ok_or(AriesVcxCoreError::from_msg( - AriesVcxCoreErrorKind::InvalidJson, - "Error parsing accum_to.txnTime value as u64", - ))?; - - let response_reg_def_id = (&res_data) - .try_get("revocRegDefId")? - .as_str() - .ok_or(AriesVcxCoreError::from_msg( - AriesVcxCoreErrorKind::InvalidJson, - "Erroring parsing revocRegDefId value as string", - ))?; - if response_reg_def_id != rev_reg_id { - return Err(AriesVcxCoreError::from_msg( - AriesVcxCoreErrorKind::InvalidRevocationDetails, - "ID of revocation registry response does not match requested ID", - )); - } + let delta_value = match revoc_reg_delta.clone() { + RevocationRegistryDelta::RevocationRegistryDeltaV1(delta) => delta.value, + }; Ok(( - rev_reg_id.to_string(), - serde_json::to_string(®_delta)?, - delta_timestamp, + revoc_reg_def_id.to_string(), + serde_json::to_string(&revoc_reg_delta)?, + timestamp, )) } @@ -438,22 +343,17 @@ where )?; let res = self._submit_request(request).await?; - let res_data = _get_response_json_data_field(&res)?; + let RevocationRegistryInfo { + revoc_reg_def_id, + revoc_reg, + timestamp, + } = self.response_parser.parse_get_revoc_reg_response(&res)?; - let rev_reg_def_id = res_data["revocRegDefId"] - .as_str() - .ok_or(AriesVcxCoreError::from_msg( - AriesVcxCoreErrorKind::InvalidJson, - "Error parsing revocRegDefId value as string", - ))? - .to_string(); - - let timestamp = res_data["txnTime"].as_u64().ok_or(AriesVcxCoreError::from_msg( - AriesVcxCoreErrorKind::InvalidJson, - "Error parsing txnTime value as u64", - ))?; - - Ok((rev_reg_def_id, res_data["value"].to_string(), timestamp)) + Ok(( + revoc_reg_def_id.to_string(), + serde_json::to_string(&revoc_reg)?, + timestamp, + )) } async fn get_ledger_txn(&self, seq_no: i32, submitter_did: Option<&str>) -> VcxCoreResult { @@ -533,17 +433,3 @@ async fn _append_txn_author_agreement_to_request(request: PreparedRequest) -> Vc Ok(request) } } - -fn _get_response_json_data_field(response_json: &str) -> VcxCoreResult { - let res: Value = serde_json::from_str(response_json)?; - let result = (&res).try_get("result")?; - let data = result.try_get("data")?.to_owned(); - if data.is_null() { - Err(AriesVcxCoreError::from_msg( - AriesVcxCoreErrorKind::LedgerItemNotFound, - "No data in response", - )) - } else { - Ok(data) - } -} diff --git a/aries_vcx_core/src/lib.rs b/aries_vcx_core/src/lib.rs index ca95cbe6d6..36ff1c678e 100644 --- a/aries_vcx_core/src/lib.rs +++ b/aries_vcx_core/src/lib.rs @@ -43,3 +43,6 @@ pub use vdrtools::{ #[cfg(feature = "vdr_proxy_ledger")] pub use indy_vdr_proxy_client::VdrProxyClient; + +#[cfg(any(feature = "modular_libs", feature = "vdr_proxy_ledger"))] +pub use indy_ledger_response_parser::ResponseParser; diff --git a/indy_ledger_response_parser/Cargo.toml b/indy_ledger_response_parser/Cargo.toml new file mode 100644 index 0000000000..63e46c5d85 --- /dev/null +++ b/indy_ledger_response_parser/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "indy-ledger-response-parser" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0.163", features = ["derive"] } +serde_json = "1.0.96" +time = "0.3.21" +ursa = { version = "0.3.7" } +indy-api-types = { path = "../libvdrtools/indy-api-types" } +indy-vdr = { version = "0.3.4" } diff --git a/indy_ledger_response_parser/src/domain/attrib.rs b/indy_ledger_response_parser/src/domain/attrib.rs new file mode 100644 index 0000000000..d68a216354 --- /dev/null +++ b/indy_ledger_response_parser/src/domain/attrib.rs @@ -0,0 +1,27 @@ +use indy_vdr::utils::did::ShortDidValue; + +use super::response::GetReplyResultV1; + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum GetAttrReplyResult { + GetAttrReplyResultV0(GetAttResultV0), + GetAttrReplyResultV1(GetReplyResultV1), +} + +#[derive(Deserialize, Eq, PartialEq, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GetAttResultV0 { + pub identifier: ShortDidValue, + pub data: String, + pub dest: ShortDidValue, + pub raw: String, +} + +#[derive(Deserialize, Eq, PartialEq, Debug)] +pub struct GetAttResultDataV1 { + pub ver: String, + pub id: String, + pub did: ShortDidValue, + pub raw: String, +} diff --git a/indy_ledger_response_parser/src/domain/constants.rs b/indy_ledger_response_parser/src/domain/constants.rs new file mode 100644 index 0000000000..10b377e0a6 --- /dev/null +++ b/indy_ledger_response_parser/src/domain/constants.rs @@ -0,0 +1,6 @@ +pub const GET_NYM: &str = "105"; +pub const GET_SCHEMA: &str = "107"; +pub const GET_CRED_DEF: &str = "108"; +pub const GET_REVOC_REG_DEF: &str = "115"; +pub const GET_REVOC_REG: &str = "116"; +pub const GET_REVOC_REG_DELTA: &str = "117"; diff --git a/indy_ledger_response_parser/src/domain/cred_def.rs b/indy_ledger_response_parser/src/domain/cred_def.rs new file mode 100644 index 0000000000..5e3e0fc8ec --- /dev/null +++ b/indy_ledger_response_parser/src/domain/cred_def.rs @@ -0,0 +1,50 @@ +use indy_vdr::{ + ledger::{ + identifiers::{CredentialDefinitionId, SchemaId}, + requests::cred_def::{CredentialDefinitionData, SignatureType}, + }, + utils::did::ShortDidValue, +}; + +use super::{ + constants::GET_CRED_DEF, + response::{GetReplyResultV1, ReplyType}, +}; + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum GetCredDefReplyResult { + GetCredDefReplyResultV0(GetCredDefResultV0), + GetCredDefReplyResultV1(GetReplyResultV1), +} + +impl ReplyType for GetCredDefReplyResult { + fn get_type<'a>() -> &'a str { + GET_CRED_DEF + } +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct GetCredDefResultV0 { + pub identifier: ShortDidValue, + #[serde(rename = "ref")] + pub ref_: u64, + #[serde(rename = "seqNo")] + pub seq_no: i32, + pub signature_type: SignatureType, + pub origin: ShortDidValue, + pub tag: Option, + pub data: CredentialDefinitionData, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GetCredDefResultDataV1 { + pub ver: String, + pub id: CredentialDefinitionId, + #[serde(rename = "type")] + pub type_: SignatureType, + pub tag: String, + pub schema_ref: SchemaId, + pub public_keys: CredentialDefinitionData, +} diff --git a/indy_ledger_response_parser/src/domain/did.rs b/indy_ledger_response_parser/src/domain/did.rs new file mode 100644 index 0000000000..1f11401759 --- /dev/null +++ b/indy_ledger_response_parser/src/domain/did.rs @@ -0,0 +1,43 @@ +use indy_vdr::utils::did::ShortDidValue; + +use super::{ + constants::GET_NYM, + response::{GetReplyResultV0, GetReplyResultV1, ReplyType}, +}; + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum GetNymReplyResult { + GetNymReplyResultV0(GetReplyResultV0), + GetNymReplyResultV1(GetReplyResultV1), +} + +impl ReplyType for GetNymReplyResult { + fn get_type<'a>() -> &'a str { + GET_NYM + } +} + +#[derive(Deserialize, Eq, PartialEq, Debug)] +pub struct GetNymResultDataV0 { + pub identifier: Option, + pub dest: ShortDidValue, + pub role: Option, + pub verkey: Option, +} + +#[derive(Deserialize, Eq, PartialEq, Debug)] +pub struct GetNymResultDataV1 { + pub ver: String, + pub id: String, + pub did: ShortDidValue, + pub verkey: Option, + pub role: Option, +} + +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] +pub struct NymData { + pub did: ShortDidValue, + pub verkey: Option, + pub role: Option, +} diff --git a/indy_ledger_response_parser/src/domain/mod.rs b/indy_ledger_response_parser/src/domain/mod.rs new file mode 100644 index 0000000000..2f59708aca --- /dev/null +++ b/indy_ledger_response_parser/src/domain/mod.rs @@ -0,0 +1,8 @@ +pub mod attrib; +pub mod constants; +pub mod cred_def; +pub mod did; +pub mod response; +pub mod rev_reg; +pub mod rev_reg_def; +pub mod schema; diff --git a/indy_ledger_response_parser/src/domain/response.rs b/indy_ledger_response_parser/src/domain/response.rs new file mode 100644 index 0000000000..85772045a2 --- /dev/null +++ b/indy_ledger_response_parser/src/domain/response.rs @@ -0,0 +1,76 @@ +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Response { + pub req_id: u64, + pub reason: String, +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum Reply { + ReplyV0(ReplyV0), + ReplyV1(ReplyV1), +} + +impl Reply { + pub fn result(self) -> T { + match self { + Reply::ReplyV0(reply) => reply.result, + Reply::ReplyV1(mut reply) => reply.data.result.remove(0).result, + } + } +} + +#[derive(Debug, Deserialize)] +pub struct ReplyV0 { + pub result: T, +} + +#[derive(Debug, Deserialize)] +pub struct ReplyV1 { + pub data: ReplyDataV1, +} + +#[derive(Debug, Deserialize)] +pub struct ReplyDataV1 { + pub result: Vec>, +} + +#[derive(Debug, Deserialize)] +pub struct GetReplyResultV0 { + pub data: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetReplyResultV1 { + pub txn: GetReplyTxnV1, + pub txn_metadata: TxnMetadata, +} + +#[derive(Debug, Deserialize)] +pub struct GetReplyTxnV1 { + pub data: T, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct TxnMetadata { + pub seq_no: u32, + pub creation_time: u64, +} + +#[derive(Deserialize, Debug)] +#[serde(tag = "op")] +pub enum Message { + #[serde(rename = "REQNACK")] + ReqNACK(Response), + #[serde(rename = "REPLY")] + Reply(Reply), + #[serde(rename = "REJECT")] + Reject(Response), +} + +pub trait ReplyType { + fn get_type<'a>() -> &'a str; +} diff --git a/indy_ledger_response_parser/src/domain/rev_reg.rs b/indy_ledger_response_parser/src/domain/rev_reg.rs new file mode 100644 index 0000000000..2ec938dd80 --- /dev/null +++ b/indy_ledger_response_parser/src/domain/rev_reg.rs @@ -0,0 +1,86 @@ +use super::constants::{GET_REVOC_REG, GET_REVOC_REG_DELTA}; + +use indy_vdr::ledger::{identifiers::RevocationRegistryId, requests::rev_reg::RevocationRegistryV1}; +use ursa::cl::RevocationRegistry; + +use super::response::{GetReplyResultV1, ReplyType}; + +use std::collections::HashSet; + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum GetRevocRegReplyResult { + GetRevocRegReplyResultV0(GetRevocRegResultV0), + GetRevocRegReplyResultV1(GetReplyResultV1), +} + +impl ReplyType for GetRevocRegReplyResult { + fn get_type<'a>() -> &'a str { + GET_REVOC_REG + } +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GetRevocRegResultV0 { + pub seq_no: i32, + pub revoc_reg_def_id: RevocationRegistryId, + pub data: RevocationRegistryV1, + pub txn_time: u64, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetRevocRegDataV1 { + pub revoc_reg_def_id: RevocationRegistryId, + pub value: RevocationRegistryV1, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RevocationRegistryDeltaData { + pub value: RevocationRegistryDeltaValue, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct RevocationRegistryDeltaValue { + pub accum_from: Option, + pub accum_to: AccumulatorState, + pub issued: HashSet, + pub revoked: HashSet, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AccumulatorState { + pub value: RevocationRegistry, + pub txn_time: u64, +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum GetRevocRegDeltaReplyResult { + GetRevocRegDeltaReplyResultV0(GetRevocRegDeltaResultV0), + GetRevocRegDeltaReplyResultV1(GetReplyResultV1), +} + +impl ReplyType for GetRevocRegDeltaReplyResult { + fn get_type<'a>() -> &'a str { + GET_REVOC_REG_DELTA + } +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GetRevocRegDeltaResultV0 { + pub seq_no: i32, + pub revoc_reg_def_id: RevocationRegistryId, + pub data: RevocationRegistryDeltaData, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetRevocRegDeltaDataV1 { + pub revoc_reg_def_id: RevocationRegistryId, + pub value: RevocationRegistryDeltaData, +} diff --git a/indy_ledger_response_parser/src/domain/rev_reg_def.rs b/indy_ledger_response_parser/src/domain/rev_reg_def.rs new file mode 100644 index 0000000000..73a3eee2f2 --- /dev/null +++ b/indy_ledger_response_parser/src/domain/rev_reg_def.rs @@ -0,0 +1,26 @@ +use indy_vdr::ledger::requests::rev_reg_def::RevocationRegistryDefinitionV1; + +use super::{ + constants::GET_REVOC_REG_DEF, + response::{GetReplyResultV1, ReplyType}, +}; + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum GetRevocRegDefReplyResult { + GetRevocRegDefReplyResultV0(GetRevocRegDefResultV0), + GetRevocRegDefReplyResultV1(GetReplyResultV1), +} + +impl ReplyType for GetRevocRegDefReplyResult { + fn get_type<'a>() -> &'a str { + GET_REVOC_REG_DEF + } +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GetRevocRegDefResultV0 { + pub seq_no: i32, + pub data: RevocationRegistryDefinitionV1, +} diff --git a/indy_ledger_response_parser/src/domain/schema.rs b/indy_ledger_response_parser/src/domain/schema.rs new file mode 100644 index 0000000000..9ad563a438 --- /dev/null +++ b/indy_ledger_response_parser/src/domain/schema.rs @@ -0,0 +1,52 @@ +use indy_vdr::{ledger::identifiers::SchemaId, utils::did::ShortDidValue}; + +use super::{ + constants::GET_SCHEMA, + response::{GetReplyResultV1, ReplyType}, +}; + +use std::collections::HashSet; + +#[derive(Serialize, PartialEq, Debug, Deserialize)] +pub struct SchemaOperationData { + pub name: String, + pub version: String, + pub attr_names: HashSet, +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum GetSchemaReplyResult { + GetSchemaReplyResultV0(GetSchemaResultV0), + GetSchemaReplyResultV1(GetReplyResultV1), +} + +impl ReplyType for GetSchemaReplyResult { + fn get_type<'a>() -> &'a str { + GET_SCHEMA + } +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GetSchemaResultV0 { + pub seq_no: u32, + pub data: SchemaOperationData, + pub dest: ShortDidValue, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GetSchemaResultDataV1 { + pub ver: String, + pub id: SchemaId, + pub schema_name: String, + pub schema_version: String, + pub value: GetSchemaResultDataValueV1, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GetSchemaResultDataValueV1 { + pub attr_names: HashSet, +} diff --git a/indy_ledger_response_parser/src/lib.rs b/indy_ledger_response_parser/src/lib.rs new file mode 100644 index 0000000000..1c804e3dce --- /dev/null +++ b/indy_ledger_response_parser/src/lib.rs @@ -0,0 +1,249 @@ +#[macro_use] +extern crate serde; + +#[macro_use] +extern crate serde_json; + +mod domain; + +pub use indy_api_types::{errors, ErrorCode}; +use indy_api_types::{ + errors::{err_msg, IndyErrorKind, IndyResult, IndyResultExt}, + IndyError, +}; +use indy_vdr::{ + ledger::{ + identifiers::{CredentialDefinitionId, RevocationRegistryId, SchemaId}, + requests::{ + cred_def::{CredentialDefinition, CredentialDefinitionV1}, + rev_reg::{RevocationRegistry, RevocationRegistryDelta, RevocationRegistryDeltaV1}, + rev_reg_def::RevocationRegistryDefinition, + schema::{Schema, SchemaV1}, + }, + }, + utils::did::DidValue, +}; +use serde::de::DeserializeOwned; +// TODO: Can we replace this to get rid of dependency on Ursa +use ursa::cl::RevocationRegistryDelta as UrsaRevocationDelta; + +use crate::domain::{ + cred_def::GetCredDefReplyResult, + did::{GetNymReplyResult, GetNymResultDataV0, NymData}, + response::{Message, Reply, ReplyType}, + rev_reg::{GetRevocRegDeltaReplyResult, GetRevocRegReplyResult}, + rev_reg_def::GetRevocRegDefReplyResult, + schema::GetSchemaReplyResult, +}; + +pub struct RevocationRegistryInfo { + pub revoc_reg: RevocationRegistry, + pub revoc_reg_def_id: RevocationRegistryId, + pub timestamp: u64, +} + +pub struct RevocationRegistryDeltaInfo { + pub revoc_reg_delta: RevocationRegistryDelta, + pub revoc_reg_def_id: RevocationRegistryId, + pub timestamp: u64, +} + +pub struct ResponseParser {} + +impl ResponseParser { + pub fn new() -> Self { + Self {} + } + + pub fn parse_get_nym_response(&self, get_nym_response: &str) -> IndyResult { + let reply: Reply = Self::parse_response(get_nym_response)?; + + let nym_data = match reply.result() { + GetNymReplyResult::GetNymReplyResultV0(res) => { + let data: GetNymResultDataV0 = res + .data + .ok_or(IndyError::from_msg( + IndyErrorKind::LedgerItemNotFound, + format!("Nym not found"), + )) + .and_then(|data| { + serde_json::from_str(&data).map_err(|err| { + IndyError::from_msg( + IndyErrorKind::InvalidState, + format!("Cannot parse GET_NYM response: {}", err), + ) + }) + })?; + + NymData { + did: data.dest, + verkey: data.verkey, + role: data.role, + } + } + GetNymReplyResult::GetNymReplyResultV1(res) => NymData { + did: res.txn.data.did, + verkey: res.txn.data.verkey, + role: res.txn.data.role, + }, + }; + + Ok(nym_data) + } + + pub fn parse_get_schema_response( + &self, + get_schema_response: &str, + method_name: Option<&str>, + ) -> IndyResult { + let reply: Reply = Self::parse_response(get_schema_response)?; + + let schema = match reply.result() { + GetSchemaReplyResult::GetSchemaReplyResultV0(res) => SchemaV1 { + id: SchemaId::new( + &DidValue::new(&res.dest.0, method_name), + &res.data.name, + &res.data.version, + ), + attr_names: res.data.attr_names.into(), + name: res.data.name, + version: res.data.version, + seq_no: Some(res.seq_no), + }, + GetSchemaReplyResult::GetSchemaReplyResultV1(res) => SchemaV1 { + id: SchemaId::new( + &DidValue::new(&res.txn.data.id, method_name), + &res.txn.data.schema_name, + &res.txn.data.schema_version, + ), + attr_names: res.txn.data.value.attr_names.into(), + name: res.txn.data.schema_name, + version: res.txn.data.schema_version, + seq_no: Some(res.txn_metadata.seq_no), + }, + }; + + Ok(Schema::SchemaV1(schema)) + } + + pub fn parse_get_cred_def_response( + &self, + get_cred_def_response: &str, + method_name: Option<&str>, + ) -> IndyResult { + let reply: Reply = Self::parse_response(get_cred_def_response)?; + + let cred_def = match reply.result() { + GetCredDefReplyResult::GetCredDefReplyResultV0(res) => CredentialDefinitionV1 { + schema_id: SchemaId(res.ref_.to_string()), + signature_type: res.signature_type, + tag: res.tag.clone().unwrap_or_default(), + value: res.data, + id: CredentialDefinitionId::new( + &DidValue::new(&res.origin.0, method_name), + &SchemaId(res.ref_.to_string()), + &res.signature_type.to_str(), + &res.tag.clone().unwrap_or_default(), + ), + }, + GetCredDefReplyResult::GetCredDefReplyResultV1(res) => CredentialDefinitionV1 { + id: res.txn.data.id, + schema_id: res.txn.data.schema_ref, + signature_type: res.txn.data.type_, + tag: res.txn.data.tag, + value: res.txn.data.public_keys, + }, + }; + + Ok(CredentialDefinition::CredentialDefinitionV1(cred_def)) + } + + pub fn parse_get_revoc_reg_def_response( + &self, + get_revoc_reg_def_response: &str, + ) -> IndyResult { + let reply: Reply = Self::parse_response(get_revoc_reg_def_response)?; + + let revoc_reg_def = match reply.result() { + GetRevocRegDefReplyResult::GetRevocRegDefReplyResultV0(res) => res.data, + GetRevocRegDefReplyResult::GetRevocRegDefReplyResultV1(res) => res.txn.data, + }; + + Ok(RevocationRegistryDefinition::RevocationRegistryDefinitionV1( + revoc_reg_def, + )) + } + + pub fn parse_get_revoc_reg_response(&self, get_revoc_reg_response: &str) -> IndyResult { + let reply: Reply = Self::parse_response(get_revoc_reg_response)?; + + let (revoc_reg_def_id, revoc_reg, timestamp) = match reply.result() { + GetRevocRegReplyResult::GetRevocRegReplyResultV0(res) => (res.revoc_reg_def_id, res.data, res.txn_time), + GetRevocRegReplyResult::GetRevocRegReplyResultV1(res) => ( + res.txn.data.revoc_reg_def_id, + res.txn.data.value, + res.txn_metadata.creation_time, + ), + }; + + Ok(RevocationRegistryInfo { + revoc_reg: RevocationRegistry::RevocationRegistryV1(revoc_reg), + revoc_reg_def_id, + timestamp, + }) + } + + pub fn parse_get_revoc_reg_delta_response( + &self, + get_revoc_reg_delta_response: &str, + ) -> IndyResult { + let reply: Reply = Self::parse_response(get_revoc_reg_delta_response)?; + + let (revoc_reg_def_id, revoc_reg) = match reply.result() { + GetRevocRegDeltaReplyResult::GetRevocRegDeltaReplyResultV0(res) => (res.revoc_reg_def_id, res.data), + GetRevocRegDeltaReplyResult::GetRevocRegDeltaReplyResultV1(res) => { + (res.txn.data.revoc_reg_def_id, res.txn.data.value) + } + }; + + let revoc_reg_delta = RevocationRegistryDeltaV1 { + value: json!(UrsaRevocationDelta::from_parts( + revoc_reg.value.accum_from.map(|accum| accum.value).as_ref(), + &revoc_reg.value.accum_to.value, + &revoc_reg.value.issued, + &revoc_reg.value.revoked, + )), + }; + + Ok(RevocationRegistryDeltaInfo { + revoc_reg_delta: RevocationRegistryDelta::RevocationRegistryDeltaV1(revoc_reg_delta), + revoc_reg_def_id, + timestamp: revoc_reg.value.accum_to.txn_time, + }) + } + + pub fn parse_response(response: &str) -> IndyResult> + where + T: DeserializeOwned + ReplyType + ::std::fmt::Debug, + { + let message: serde_json::Value = + serde_json::from_str(&response).to_indy(IndyErrorKind::InvalidTransaction, "Response is invalid json")?; + + if message["op"] == json!("REPLY") && message["result"]["type"] != json!(T::get_type()) { + return Err(err_msg(IndyErrorKind::InvalidTransaction, "Invalid response type")); + } + + let message: Message = serde_json::from_value(message).to_indy( + IndyErrorKind::LedgerItemNotFound, + "Structure doesn't correspond to type. Most probably not found", + )?; // FIXME: Review how we handle not found + + match message { + Message::Reject(response) | Message::ReqNACK(response) => Err(err_msg( + IndyErrorKind::InvalidTransaction, + format!("Transaction has been failed: {:?}", response.reason), + )), + Message::Reply(reply) => Ok(reply), + } + } +} diff --git a/libvdrtools/indy-api-types/Cargo.toml b/libvdrtools/indy-api-types/Cargo.toml index 40dec5770a..8747718dd4 100644 --- a/libvdrtools/indy-api-types/Cargo.toml +++ b/libvdrtools/indy-api-types/Cargo.toml @@ -13,7 +13,7 @@ rust-base58 = ["bs58"] [dependencies] failure = "0.1.8" -futures = { version = "0.3", default-features = false } +futures = { version = "0.3", default-features = false, features = ["std"] } log = { version = "0.4.17", features = ["std"] } libc = "0.2.114" openssl = {version = "0.10", optional = true} @@ -21,7 +21,7 @@ bs58 = {version = "0.4.0", optional = true} serde = "1.0.99" serde_json = "1.0.40" serde_derive = "1.0.99" -sqlx = { version = "0.5.8", git = "https://github.com/jovfer/sqlx", branch = "feature/json_no_preserve_order_v5", features = [ "sqlite", "json_no_preserve_order" ], optional = true } +sqlx = { version = "0.5.8", git = "https://github.com/jovfer/sqlx", branch = "feature/json_no_preserve_order_v5", features = [ "sqlite", "json_no_preserve_order", "runtime-tokio-rustls" ], optional = true } zeroize = "~1.3.0" zmq = {version = "0.9.1", optional = true} ursa = { version = "0.3.7", optional = true} From 07e2c8e4ed44678f1e8f7daf3ad9d5dd4996ec53 Mon Sep 17 00:00:00 2001 From: Miroslav Kovar Date: Fri, 19 May 2023 08:17:20 +0200 Subject: [PATCH 2/7] Address CR Signed-off-by: Miroslav Kovar --- .../src/domain/response.rs | 53 +++++++++++++++++++ indy_ledger_response_parser/src/lib.rs | 25 ++++----- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/indy_ledger_response_parser/src/domain/response.rs b/indy_ledger_response_parser/src/domain/response.rs index 85772045a2..03cfdc8efd 100644 --- a/indy_ledger_response_parser/src/domain/response.rs +++ b/indy_ledger_response_parser/src/domain/response.rs @@ -1,3 +1,8 @@ +use indy_api_types::{ + errors::{err_msg, IndyErrorKind}, + IndyError, +}; + #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Response { @@ -74,3 +79,51 @@ pub enum Message { pub trait ReplyType { fn get_type<'a>() -> &'a str; } + +#[derive(Deserialize, Debug)] +#[serde(tag = "op")] +pub enum MessageWithTypedReply<'a, T> { + #[serde(rename = "REQNACK")] + ReqNACK(Response), + #[serde(borrow)] + #[serde(rename = "REPLY")] + Reply(Reply>), + #[serde(rename = "REJECT")] + Reject(Response), +} + +#[derive(Deserialize, Debug)] +pub struct TypedReply<'a, T> { + #[serde(flatten)] + data: T, + #[serde(rename = "type")] + type_: &'a str, +} + +impl<'a, T> TryFrom> for Reply +where + T: ReplyType, +{ + type Error = IndyError; + fn try_from(value: TypedReply<'a, T>) -> Result { + if value.type_ != T::get_type() { + Err(err_msg(IndyErrorKind::InvalidTransaction, "Invalid response type")) + } else { + Ok(Reply::ReplyV0(ReplyV0 { result: value.data })) + } + } +} + +impl<'a, T> TryFrom> for Message +where + T: ReplyType, +{ + type Error = IndyError; + fn try_from(value: MessageWithTypedReply<'a, T>) -> Result { + match value { + MessageWithTypedReply::ReqNACK(r) => Ok(Message::ReqNACK(r)), + MessageWithTypedReply::Reply(r) => Ok(Message::Reply(r.result().try_into()?)), + MessageWithTypedReply::Reject(r) => Ok(Message::Reject(r)), + } + } +} diff --git a/indy_ledger_response_parser/src/lib.rs b/indy_ledger_response_parser/src/lib.rs index 1c804e3dce..4c69eb54eb 100644 --- a/indy_ledger_response_parser/src/lib.rs +++ b/indy_ledger_response_parser/src/lib.rs @@ -6,6 +6,7 @@ extern crate serde_json; mod domain; +use domain::response::MessageWithTypedReply; pub use indy_api_types::{errors, ErrorCode}; use indy_api_types::{ errors::{err_msg, IndyErrorKind, IndyResult, IndyResultExt}, @@ -62,10 +63,7 @@ impl ResponseParser { GetNymReplyResult::GetNymReplyResultV0(res) => { let data: GetNymResultDataV0 = res .data - .ok_or(IndyError::from_msg( - IndyErrorKind::LedgerItemNotFound, - format!("Nym not found"), - )) + .ok_or_else(|| IndyError::from_msg(IndyErrorKind::LedgerItemNotFound, format!("Nym not found"))) .and_then(|data| { serde_json::from_str(&data).map_err(|err| { IndyError::from_msg( @@ -207,12 +205,16 @@ impl ResponseParser { }; let revoc_reg_delta = RevocationRegistryDeltaV1 { - value: json!(UrsaRevocationDelta::from_parts( + value: serde_json::to_value(UrsaRevocationDelta::from_parts( revoc_reg.value.accum_from.map(|accum| accum.value).as_ref(), &revoc_reg.value.accum_to.value, &revoc_reg.value.issued, &revoc_reg.value.revoked, - )), + )) + .to_indy( + IndyErrorKind::InvalidStructure, + "Cannot convert RevocationRegistryDelta to Value", + )?, }; Ok(RevocationRegistryDeltaInfo { @@ -226,19 +228,12 @@ impl ResponseParser { where T: DeserializeOwned + ReplyType + ::std::fmt::Debug, { - let message: serde_json::Value = - serde_json::from_str(&response).to_indy(IndyErrorKind::InvalidTransaction, "Response is invalid json")?; - - if message["op"] == json!("REPLY") && message["result"]["type"] != json!(T::get_type()) { - return Err(err_msg(IndyErrorKind::InvalidTransaction, "Invalid response type")); - } - - let message: Message = serde_json::from_value(message).to_indy( + let message: MessageWithTypedReply = serde_json::from_str(response).to_indy( IndyErrorKind::LedgerItemNotFound, "Structure doesn't correspond to type. Most probably not found", )?; // FIXME: Review how we handle not found - match message { + match message.try_into()? { Message::Reject(response) | Message::ReqNACK(response) => Err(err_msg( IndyErrorKind::InvalidTransaction, format!("Transaction has been failed: {:?}", response.reason), From b1f1a0b6dc34ec9ab3a5e19cb9f0c76b826c3e2f Mon Sep 17 00:00:00 2001 From: Miroslav Kovar Date: Fri, 19 May 2023 12:11:25 +0200 Subject: [PATCH 3/7] Implement TryFrom for both typed reply versions Signed-off-by: Miroslav Kovar --- .../src/domain/response.rs | 63 +++++++++++++++---- indy_ledger_response_parser/src/lib.rs | 5 +- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/indy_ledger_response_parser/src/domain/response.rs b/indy_ledger_response_parser/src/domain/response.rs index 03cfdc8efd..1e1d42dadf 100644 --- a/indy_ledger_response_parser/src/domain/response.rs +++ b/indy_ledger_response_parser/src/domain/response.rs @@ -66,13 +66,15 @@ pub struct TxnMetadata { } #[derive(Deserialize, Debug)] -#[serde(tag = "op")] +#[serde(try_from = "MessageWithTypedReply<'de, T>")] +#[serde(bound(deserialize = " + Self: TryFrom>, + T: serde::Deserialize<'de>, + >>::Error: std::fmt::Display +"))] pub enum Message { - #[serde(rename = "REQNACK")] ReqNACK(Response), - #[serde(rename = "REPLY")] Reply(Reply), - #[serde(rename = "REJECT")] Reject(Response), } @@ -82,7 +84,7 @@ pub trait ReplyType { #[derive(Deserialize, Debug)] #[serde(tag = "op")] -pub enum MessageWithTypedReply<'a, T> { +enum MessageWithTypedReply<'a, T> { #[serde(rename = "REQNACK")] ReqNACK(Response), #[serde(borrow)] @@ -93,36 +95,73 @@ pub enum MessageWithTypedReply<'a, T> { } #[derive(Deserialize, Debug)] -pub struct TypedReply<'a, T> { +struct TypedReply<'a, T> { #[serde(flatten)] - data: T, + reply: T, #[serde(rename = "type")] type_: &'a str, } -impl<'a, T> TryFrom> for Reply +impl<'a, T> TryFrom>> for ReplyV0 where T: ReplyType, { type Error = IndyError; - fn try_from(value: TypedReply<'a, T>) -> Result { - if value.type_ != T::get_type() { + fn try_from(value: ReplyV0>) -> Result { + if value.result.type_ != T::get_type() { Err(err_msg(IndyErrorKind::InvalidTransaction, "Invalid response type")) } else { - Ok(Reply::ReplyV0(ReplyV0 { result: value.data })) + Ok(ReplyV0 { + result: value.result.reply, + }) } } } +impl<'a, T> TryFrom>> for ReplyV1 +where + T: ReplyType, +{ + type Error = IndyError; + + fn try_from(mut value: ReplyV1>) -> Result { + let value = value + .data + .result + .pop() + .ok_or_else(|| err_msg(IndyErrorKind::InvalidTransaction, "Invalid response type"))?; + let data = ReplyDataV1 { + result: vec![value.try_into()?], + }; + Ok(ReplyV1 { data }) + } +} + +impl<'a, T> TryFrom>> for Reply +where + T: ReplyType, +{ + type Error = IndyError; + + fn try_from(value: Reply>) -> Result { + let reply = match value { + Reply::ReplyV0(r) => Reply::ReplyV0(r.try_into()?), + Reply::ReplyV1(r) => Reply::ReplyV1(r.try_into()?), + }; + Ok(reply) + } +} + impl<'a, T> TryFrom> for Message where T: ReplyType, { type Error = IndyError; + fn try_from(value: MessageWithTypedReply<'a, T>) -> Result { match value { MessageWithTypedReply::ReqNACK(r) => Ok(Message::ReqNACK(r)), - MessageWithTypedReply::Reply(r) => Ok(Message::Reply(r.result().try_into()?)), + MessageWithTypedReply::Reply(r) => Ok(Message::Reply(r.try_into()?)), MessageWithTypedReply::Reject(r) => Ok(Message::Reject(r)), } } diff --git a/indy_ledger_response_parser/src/lib.rs b/indy_ledger_response_parser/src/lib.rs index 4c69eb54eb..59e7300da4 100644 --- a/indy_ledger_response_parser/src/lib.rs +++ b/indy_ledger_response_parser/src/lib.rs @@ -6,7 +6,6 @@ extern crate serde_json; mod domain; -use domain::response::MessageWithTypedReply; pub use indy_api_types::{errors, ErrorCode}; use indy_api_types::{ errors::{err_msg, IndyErrorKind, IndyResult, IndyResultExt}, @@ -228,12 +227,12 @@ impl ResponseParser { where T: DeserializeOwned + ReplyType + ::std::fmt::Debug, { - let message: MessageWithTypedReply = serde_json::from_str(response).to_indy( + let message: Message = serde_json::from_str(response).to_indy( IndyErrorKind::LedgerItemNotFound, "Structure doesn't correspond to type. Most probably not found", )?; // FIXME: Review how we handle not found - match message.try_into()? { + match message { Message::Reject(response) | Message::ReqNACK(response) => Err(err_msg( IndyErrorKind::InvalidTransaction, format!("Transaction has been failed: {:?}", response.reason), From dcd220f4652672019c9269618001cd7a2a167648 Mon Sep 17 00:00:00 2001 From: Miroslav Kovar Date: Fri, 19 May 2023 12:54:16 +0200 Subject: [PATCH 4/7] Use array instead of vec for reply Signed-off-by: Miroslav Kovar --- indy_ledger_response_parser/src/domain/response.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/indy_ledger_response_parser/src/domain/response.rs b/indy_ledger_response_parser/src/domain/response.rs index 1e1d42dadf..be291f50c2 100644 --- a/indy_ledger_response_parser/src/domain/response.rs +++ b/indy_ledger_response_parser/src/domain/response.rs @@ -21,7 +21,8 @@ impl Reply { pub fn result(self) -> T { match self { Reply::ReplyV0(reply) => reply.result, - Reply::ReplyV1(mut reply) => reply.data.result.remove(0).result, + // SAFETY: Empty array cannot be instantiated + Reply::ReplyV1(reply) => reply.data.result.into_iter().next().unwrap().result, } } } @@ -38,7 +39,7 @@ pub struct ReplyV1 { #[derive(Debug, Deserialize)] pub struct ReplyDataV1 { - pub result: Vec>, + pub result: [ReplyV0; 1], } #[derive(Debug, Deserialize)] @@ -124,14 +125,15 @@ where { type Error = IndyError; - fn try_from(mut value: ReplyV1>) -> Result { + fn try_from(value: ReplyV1>) -> Result { let value = value .data .result - .pop() + .into_iter() + .next() .ok_or_else(|| err_msg(IndyErrorKind::InvalidTransaction, "Invalid response type"))?; let data = ReplyDataV1 { - result: vec![value.try_into()?], + result: [value.try_into()?], }; Ok(ReplyV1 { data }) } From fb758cda22119fb58e854e0bc4aa12d23f3f5e17 Mon Sep 17 00:00:00 2001 From: Miroslav Kovar Date: Wed, 17 May 2023 21:19:52 +0200 Subject: [PATCH 5/7] Implement transaction endorsing in IndyVdrLedger and enable test Signed-off-by: Miroslav Kovar --- aries_vcx/tests/test_pool.rs | 47 +++++++---- .../src/common/ledger/transactions.rs | 37 +++++++++ .../src/indy/ledger/transactions.rs | 39 +-------- aries_vcx_core/src/ledger/indy_vdr_ledger.rs | 80 +++++++++---------- did_resolver_sov/src/reader/vdr_reader.rs | 14 +++- 5 files changed, 116 insertions(+), 101 deletions(-) diff --git a/aries_vcx/tests/test_pool.rs b/aries_vcx/tests/test_pool.rs index bcd45f23bf..fb0ddcce1b 100644 --- a/aries_vcx/tests/test_pool.rs +++ b/aries_vcx/tests/test_pool.rs @@ -12,8 +12,9 @@ mod integration_tests { use aries_vcx::common::ledger::transactions::{ add_attr, add_new_did, clear_attr, get_attr, get_service, write_endpoint, write_endpoint_legacy, }; + use aries_vcx::common::primitives::credential_schema::{Schema, SchemaData}; use aries_vcx::common::test_utils::create_and_store_nonrevocable_credential_def; - use aries_vcx::utils::constants::DEFAULT_SCHEMA_ATTRS; + use aries_vcx::utils::constants::{DEFAULT_SCHEMA_ATTRS, SCHEMA_DATA}; use aries_vcx::utils::devsetup::{SetupProfile, SetupWalletPool}; use diddoc_legacy::aries::service::AriesService; use std::sync::Arc; @@ -68,22 +69,34 @@ mod integration_tests { .await; } - // TODO - future - bring back after all endorser methods added to baseledger - // #[tokio::test] - // async fn test_endorse_transaction() { - // SetupProfile::run_indy(|setup| async move { - // let ledger = Arc::clone(&setup.profile).inject_ledger(); - // let (author_did, _) = add_new_did(&setup.profile, &setup.institution_did, None).await.unwrap(); - // let (endorser_did, _) = add_new_did(&setup.profile, &setup.institution_did, Some("ENDORSER")).await.unwrap(); - - // let schema_request = ledger.build_schema_request(&author_did, SCHEMA_DATA).await.unwrap(); - // let schema_request = append_request_endorser(&schema_request, &endorser_did).await.unwrap(); - // let schema_request = multisign_request(setup.wallet_handle, &author_did, &schema_request) - // .await - // .unwrap(); - // ledger.endorse_transaction(&endorser_did, &schema_request).await.unwrap(); - // }).await; - // } + #[tokio::test] + #[ignore] + async fn test_endorse_transaction() { + SetupProfile::run_indy(|setup| async move { + let ledger = Arc::clone(&setup.profile).inject_ledger(); + let (author_did, _) = add_new_did(&setup.profile, &setup.institution_did, None).await.unwrap(); + let (endorser_did, _) = add_new_did(&setup.profile, &setup.institution_did, Some("ENDORSER")) + .await + .unwrap(); + + let schema_request = ledger.build_schema_request(&author_did, SCHEMA_DATA).await.unwrap(); + let schema_request = ledger + .set_endorser(&author_did, &schema_request, &endorser_did) + .await + .unwrap(); + ledger + .endorse_transaction(&endorser_did, &schema_request) + .await + .unwrap(); + let schema_data: SchemaData = serde_json::from_str(&SCHEMA_DATA).unwrap(); + let schema_id = format!("{}:2:{}:1.0", author_did, schema_data.name); + thread::sleep(Duration::from_millis(50)); + Schema::create_from_ledger_json(&setup.profile, "source_id", &schema_id) + .await + .unwrap(); + }) + .await; + } #[tokio::test] #[ignore] diff --git a/aries_vcx_core/src/common/ledger/transactions.rs b/aries_vcx_core/src/common/ledger/transactions.rs index c94a36a8a3..11c1485159 100644 --- a/aries_vcx_core/src/common/ledger/transactions.rs +++ b/aries_vcx_core/src/common/ledger/transactions.rs @@ -2,6 +2,8 @@ use std::collections::HashMap; use serde::Deserialize; +use crate::errors::error::{AriesVcxCoreError, AriesVcxCoreErrorKind, VcxCoreResult}; + #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Request { @@ -50,3 +52,38 @@ pub struct ReplyV1 { pub struct ReplyDataV1 { pub result: serde_json::Value, } + +pub fn verify_transaction_can_be_endorsed(transaction_json: &str, did: &str) -> VcxCoreResult<()> { + let transaction: Request = serde_json::from_str(transaction_json) + .map_err(|err| AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::InvalidJson, format!("{err:?}")))?; + + let transaction_endorser = transaction.endorser.ok_or(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::InvalidJson, + "Transaction cannot be endorsed: endorser DID is not set.", + ))?; + + if transaction_endorser != did { + return Err(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::InvalidJson, + format!( + "Transaction cannot be endorsed: transaction endorser DID `{transaction_endorser}` and sender DID `{did}` are different" + ), + )); + } + + let identifier = transaction.identifier.as_str(); + if transaction.signature.is_none() + && !transaction + .signatures + .as_ref() + .map(|signatures| signatures.contains_key(identifier)) + .unwrap_or(false) + { + return Err(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::InvalidJson, + "Transaction cannot be endorsed: the author must sign the transaction.".to_string(), + )); + } + + Ok(()) +} diff --git a/aries_vcx_core/src/indy/ledger/transactions.rs b/aries_vcx_core/src/indy/ledger/transactions.rs index 21fc1be88f..b715718a1a 100644 --- a/aries_vcx_core/src/indy/ledger/transactions.rs +++ b/aries_vcx_core/src/indy/ledger/transactions.rs @@ -1,7 +1,7 @@ use time::OffsetDateTime; use vdrtools::{DidValue, Locator}; -use crate::common::ledger::transactions::{Request, Response}; +use crate::common::ledger::transactions::{verify_transaction_can_be_endorsed, Response}; use crate::errors::error::prelude::*; use crate::global::author_agreement::get_txn_author_agreement; use crate::global::settings; @@ -303,7 +303,7 @@ pub async fn endorse_transaction( return Ok(()); } - _verify_transaction_can_be_endorsed(transaction_json, endorser_did)?; + verify_transaction_can_be_endorsed(transaction_json, endorser_did)?; let transaction = multisign_request(wallet_handle, endorser_did, transaction_json).await?; let response = libindy_submit_request(pool_handle, &transaction).await?; @@ -317,41 +317,6 @@ pub async fn endorse_transaction( } } -fn _verify_transaction_can_be_endorsed(transaction_json: &str, _did: &str) -> VcxCoreResult<()> { - let transaction: Request = serde_json::from_str(transaction_json) - .map_err(|err| AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::InvalidJson, format!("{err:?}")))?; - - let transaction_endorser = transaction.endorser.ok_or(AriesVcxCoreError::from_msg( - AriesVcxCoreErrorKind::InvalidJson, - "Transaction cannot be endorsed: endorser DID is not set.", - ))?; - - if transaction_endorser != _did { - return Err(AriesVcxCoreError::from_msg( - AriesVcxCoreErrorKind::InvalidJson, - format!( - "Transaction cannot be endorsed: transaction endorser DID `{transaction_endorser}` and sender DID `{_did}` are different" - ), - )); - } - - let identifier = transaction.identifier.as_str(); - if transaction.signature.is_none() - && !transaction - .signatures - .as_ref() - .map(|signatures| signatures.contains_key(identifier)) - .unwrap_or(false) - { - return Err(AriesVcxCoreError::from_msg( - AriesVcxCoreErrorKind::InvalidJson, - "Transaction cannot be endorsed: the author must sign the transaction.".to_string(), - )); - } - - Ok(()) -} - pub async fn build_attrib_request( submitter_did: &str, target_did: &str, diff --git a/aries_vcx_core/src/ledger/indy_vdr_ledger.rs b/aries_vcx_core/src/ledger/indy_vdr_ledger.rs index 0723078304..2817053d1c 100644 --- a/aries_vcx_core/src/ledger/indy_vdr_ledger.rs +++ b/aries_vcx_core/src/ledger/indy_vdr_ledger.rs @@ -1,4 +1,3 @@ -use indy_credx::ursa::cl::RevocationRegistryDelta as UrsaRevocationRegistryDelta; use indy_ledger_response_parser::{ResponseParser, RevocationRegistryDeltaInfo, RevocationRegistryInfo}; use indy_vdr as vdr; use std::fmt::{Debug, Formatter}; @@ -7,7 +6,7 @@ use time::OffsetDateTime; use vdr::ledger::requests::cred_def::CredentialDefinitionV1; use vdr::ledger::requests::rev_reg::{RevocationRegistryDelta, RevocationRegistryDeltaV1}; use vdr::ledger::requests::rev_reg_def::{RegistryType, RevocationRegistryDefinition, RevocationRegistryDefinitionV1}; -use vdr::ledger::requests::schema::{AttributeNames, Schema, SchemaV1}; +use vdr::ledger::requests::schema::{Schema, SchemaV1}; use async_trait::async_trait; use serde_json::Value; @@ -18,11 +17,10 @@ use vdr::pool::{LedgerType, PreparedRequest, ProtocolVersion}; use vdr::utils::did::DidValue; use vdr::utils::Qualifiable; +use crate::common::ledger::transactions::verify_transaction_can_be_endorsed; use crate::errors::error::VcxCoreResult; -use crate::errors::error::{AriesVcxCoreError, AriesVcxCoreErrorKind}; use crate::global::author_agreement::get_txn_author_agreement; use crate::global::settings; -use crate::utils::json::{AsTypeOrDeserializationError, TryGetIndex}; use crate::wallet::base_wallet::BaseWallet; use super::base_ledger::BaseLedger; @@ -69,16 +67,17 @@ where self.request_submitter.submit(request).await } - async fn _sign_and_submit_request(&self, submitter_did: &str, request: PreparedRequest) -> VcxCoreResult { - let mut request = request; + async fn _get_request_signature(&self, did: &str, request: &PreparedRequest) -> VcxCoreResult> { let to_sign = request.get_signature_input()?; - - let signer_verkey = self.wallet.key_for_local_did(submitter_did).await?; - + let signer_verkey = self.wallet.key_for_local_did(did).await?; let signature = self.wallet.sign(&signer_verkey, to_sign.as_bytes()).await?; + Ok(signature) + } + async fn _sign_and_submit_request(&self, submitter_did: &str, request: PreparedRequest) -> VcxCoreResult { + let mut request = request; + let signature = self._get_request_signature(submitter_did, &request).await?; request.set_signature(&signature)?; - self._submit_request(request).await } @@ -185,6 +184,13 @@ where .request_builder()? .build_get_txn_request(identifier.as_ref(), LedgerType::DOMAIN.to_id(), seq_no)?) } + + fn _build_taa_request(&self, submitter_did: Option<&str>) -> VcxCoreResult { + let submitter_did = submitter_did.map(DidValue::from_str).transpose()?; + Ok(self + .request_builder()? + .build_get_txn_author_agreement_request(submitter_did.as_ref(), None)?) + } } impl Debug for IndyVdrLedger @@ -213,17 +219,26 @@ where } async fn endorse_transaction(&self, endorser_did: &str, request_json: &str) -> VcxCoreResult<()> { - let _ = (endorser_did, request_json); - Err(unimplemented_method_err("indy_vdr endorse_transaction")) + let mut request = PreparedRequest::from_request_json(&request_json)?; + verify_transaction_can_be_endorsed(request_json, endorser_did)?; + let signature_endorser = self._get_request_signature(endorser_did, &request).await?; + request.set_multi_signature(&DidValue::from_str(endorser_did)?, &signature_endorser)?; + self._submit_request(request).await.map(|_| ()) } async fn set_endorser(&self, submitter_did: &str, request_json: &str, endorser: &str) -> VcxCoreResult { - let _ = (submitter_did, request_json, endorser); - Err(unimplemented_method_err("indy_vdr set_endorser")) + let mut request = PreparedRequest::from_request_json(request_json)?; + request.set_endorser(&DidValue::from_str(endorser)?)?; + let signature_submitter = self._get_request_signature(submitter_did, &request).await?; + request.set_multi_signature(&DidValue::from_str(submitter_did)?, &signature_submitter)?; + Ok(request.req_json.to_string()) } async fn get_txn_author_agreement(&self) -> VcxCoreResult { - Err(unimplemented_method_err("indy_vdr get_txn_author_agreement")) + let request = self + .request_builder()? + .build_get_txn_author_agreement_request(None, None)?; + self._submit_request(request).await } async fn get_nym(&self, did: &str) -> VcxCoreResult { @@ -238,22 +253,16 @@ where submitter_did: &str, target_did: &str, verkey: Option<&str>, - data: Option<&str>, + alias: Option<&str>, role: Option<&str>, ) -> VcxCoreResult { - // TODO - FUTURE: convert data into "alias" for indy vdr. for now throw unimplemented - if data.is_some() { - return Err(unimplemented_method_err("indy_vdr publish_nym with data")); - } - let alias = None; - let identifier = DidValue::from_str(submitter_did)?; let dest = DidValue::from_str(target_did)?; let request = self.request_builder()?.build_nym_request( &identifier, &dest, verkey.map(String::from), - alias, + alias.map(String::from), role.map(String::from), )?; @@ -322,10 +331,6 @@ where timestamp, } = self.response_parser.parse_get_revoc_reg_delta_response(&res)?; - let delta_value = match revoc_reg_delta.clone() { - RevocationRegistryDelta::RevocationRegistryDeltaV1(delta) => delta.value, - }; - Ok(( revoc_reg_def_id.to_string(), serde_json::to_string(&revoc_reg_delta)?, @@ -371,26 +376,23 @@ where &self, schema_json: &str, submitter_did: &str, - endorser_did: Option, + _endorser_did: Option, ) -> VcxCoreResult<()> { let request = self._build_schema_request(submitter_did, schema_json)?; let request = _append_txn_author_agreement_to_request(request).await?; - self._sign_and_submit_request(submitter_did, request).await?; - Ok(()) + self._sign_and_submit_request(submitter_did, request).await.map(|_| ()) } async fn publish_cred_def(&self, cred_def_json: &str, submitter_did: &str) -> VcxCoreResult<()> { let request = self._build_cred_def_request(submitter_did, cred_def_json)?; let request = _append_txn_author_agreement_to_request(request).await?; - self._sign_and_submit_request(submitter_did, request).await?; - Ok(()) + self._sign_and_submit_request(submitter_did, request).await.map(|_| ()) } async fn publish_rev_reg_def(&self, rev_reg_def: &str, submitter_did: &str) -> VcxCoreResult<()> { let request = self._build_rev_reg_def_request(submitter_did, rev_reg_def)?; let request = _append_txn_author_agreement_to_request(request).await?; - self._sign_and_submit_request(submitter_did, request).await?; - Ok(()) + self._sign_and_submit_request(submitter_did, request).await.map(|_| ()) } async fn publish_rev_reg_delta( @@ -401,18 +403,10 @@ where ) -> VcxCoreResult<()> { let request = self._build_rev_reg_delta_request(submitter_did, rev_reg_id, rev_reg_entry_json)?; let request = _append_txn_author_agreement_to_request(request).await?; - self._sign_and_submit_request(submitter_did, request).await?; - Ok(()) + self._sign_and_submit_request(submitter_did, request).await.map(|_| ()) } } -fn unimplemented_method_err(method_name: &str) -> AriesVcxCoreError { - AriesVcxCoreError::from_msg( - AriesVcxCoreErrorKind::UnimplementedFeature, - format!("method called '{}' is not yet implemented in AriesVCX", method_name), - ) -} - fn current_epoch_time() -> i64 { OffsetDateTime::now_utc().unix_timestamp() as i64 } diff --git a/did_resolver_sov/src/reader/vdr_reader.rs b/did_resolver_sov/src/reader/vdr_reader.rs index 64ac7abeea..3ce4033147 100644 --- a/did_resolver_sov/src/reader/vdr_reader.rs +++ b/did_resolver_sov/src/reader/vdr_reader.rs @@ -3,11 +3,11 @@ use std::sync::Arc; use crate::error::DidSovError; use aries_vcx_core::{ ledger::{ - indy_vdr_ledger::IndyVdrLedger, + indy_vdr_ledger::{IndyVdrLedger, IndyVdrLedgerConfig}, request_submitter::vdr_ledger::{IndyVdrLedgerPool, IndyVdrSubmitter, LedgerPoolConfig}, }, wallet::{base_wallet::BaseWallet, indy_wallet::IndySdkWallet}, - INVALID_WALLET_HANDLE, + ResponseParser, INVALID_WALLET_HANDLE, }; use super::ConcreteAttrReader; @@ -18,8 +18,14 @@ impl TryFrom for ConcreteAttrReader { fn try_from(pool_config: LedgerPoolConfig) -> Result { let wallet = Arc::new(IndySdkWallet::new(INVALID_WALLET_HANDLE)) as Arc; let ledger_pool = Arc::new(IndyVdrLedgerPool::new(pool_config)?); - let submitter = Arc::new(IndyVdrSubmitter::new(ledger_pool)); - let ledger = Arc::new(IndyVdrLedger::new(Arc::clone(&wallet), submitter)); + let request_submitter = Arc::new(IndyVdrSubmitter::new(ledger_pool)); + let response_parser = Arc::new(ResponseParser::new()); + let config = IndyVdrLedgerConfig { + wallet: wallet.clone(), + request_submitter, + response_parser, + }; + let ledger = Arc::new(IndyVdrLedger::new(config)); Ok(Self { ledger }) } } From e7e824570fd941fe814aec78fc2928d694ee9f64 Mon Sep 17 00:00:00 2001 From: Miroslav Kovar Date: Fri, 19 May 2023 08:50:55 +0200 Subject: [PATCH 6/7] Address CR Signed-off-by: Miroslav Kovar --- aries_vcx/tests/test_pool.rs | 2 +- aries_vcx_core/src/common/ledger/transactions.rs | 8 ++++---- aries_vcx_core/src/ledger/indy_vdr_ledger.rs | 12 +++++++++--- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/aries_vcx/tests/test_pool.rs b/aries_vcx/tests/test_pool.rs index fb0ddcce1b..ac3e076fb1 100644 --- a/aries_vcx/tests/test_pool.rs +++ b/aries_vcx/tests/test_pool.rs @@ -72,7 +72,7 @@ mod integration_tests { #[tokio::test] #[ignore] async fn test_endorse_transaction() { - SetupProfile::run_indy(|setup| async move { + SetupProfile::run(|setup| async move { let ledger = Arc::clone(&setup.profile).inject_ledger(); let (author_did, _) = add_new_did(&setup.profile, &setup.institution_did, None).await.unwrap(); let (endorser_did, _) = add_new_did(&setup.profile, &setup.institution_did, Some("ENDORSER")) diff --git a/aries_vcx_core/src/common/ledger/transactions.rs b/aries_vcx_core/src/common/ledger/transactions.rs index 11c1485159..19f7a23c74 100644 --- a/aries_vcx_core/src/common/ledger/transactions.rs +++ b/aries_vcx_core/src/common/ledger/transactions.rs @@ -53,20 +53,20 @@ pub struct ReplyDataV1 { pub result: serde_json::Value, } -pub fn verify_transaction_can_be_endorsed(transaction_json: &str, did: &str) -> VcxCoreResult<()> { +pub fn verify_transaction_can_be_endorsed(transaction_json: &str, submitter_did: &str) -> VcxCoreResult<()> { let transaction: Request = serde_json::from_str(transaction_json) .map_err(|err| AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::InvalidJson, format!("{err:?}")))?; - let transaction_endorser = transaction.endorser.ok_or(AriesVcxCoreError::from_msg( + let endorser_did = transaction.endorser.ok_or(AriesVcxCoreError::from_msg( AriesVcxCoreErrorKind::InvalidJson, "Transaction cannot be endorsed: endorser DID is not set.", ))?; - if transaction_endorser != did { + if endorser_did != submitter_did { return Err(AriesVcxCoreError::from_msg( AriesVcxCoreErrorKind::InvalidJson, format!( - "Transaction cannot be endorsed: transaction endorser DID `{transaction_endorser}` and sender DID `{did}` are different" + "Transaction cannot be endorsed: transaction endorser DID `{endorser_did}` and sender DID `{submitter_did}` are different" ), )); } diff --git a/aries_vcx_core/src/ledger/indy_vdr_ledger.rs b/aries_vcx_core/src/ledger/indy_vdr_ledger.rs index 2817053d1c..746ce31c4f 100644 --- a/aries_vcx_core/src/ledger/indy_vdr_ledger.rs +++ b/aries_vcx_core/src/ledger/indy_vdr_ledger.rs @@ -376,10 +376,16 @@ where &self, schema_json: &str, submitter_did: &str, - _endorser_did: Option, + endorser_did: Option, ) -> VcxCoreResult<()> { - let request = self._build_schema_request(submitter_did, schema_json)?; - let request = _append_txn_author_agreement_to_request(request).await?; + let mut request = self._build_schema_request(submitter_did, schema_json)?; + request = _append_txn_author_agreement_to_request(request).await?; + if let Some(endorser_did) = endorser_did { + request = PreparedRequest::from_request_json( + self.set_endorser(submitter_did, &request.req_json.to_string(), &endorser_did) + .await?, + )? + } self._sign_and_submit_request(submitter_did, request).await.map(|_| ()) } From a63c376d9001308e29bdf4b7e913cb61c35e207d Mon Sep 17 00:00:00 2001 From: Miroslav Kovar Date: Thu, 18 May 2023 11:01:57 +0200 Subject: [PATCH 7/7] Remove dependency on BaseWallet from IndyVdrLedger Signed-off-by: Miroslav Kovar --- .../src/core/profile/modular_libs_profile.rs | 4 ++- .../src/core/profile/vdr_proxy_profile.rs | 4 ++- aries_vcx_core/src/ledger/indy_vdr_ledger.rs | 30 ++++++++++--------- aries_vcx_core/src/ledger/mod.rs | 2 ++ .../src/ledger/request_signer/base_wallet.rs | 28 +++++++++++++++++ .../src/ledger/request_signer/mod.rs | 11 +++++++ .../src/ledger/request_submitter/mod.rs | 2 +- did_resolver_sov/src/reader/vdr_reader.rs | 4 ++- 8 files changed, 67 insertions(+), 18 deletions(-) create mode 100644 aries_vcx_core/src/ledger/request_signer/base_wallet.rs create mode 100644 aries_vcx_core/src/ledger/request_signer/mod.rs diff --git a/aries_vcx/src/core/profile/modular_libs_profile.rs b/aries_vcx/src/core/profile/modular_libs_profile.rs index 717385bd60..74179f7810 100644 --- a/aries_vcx/src/core/profile/modular_libs_profile.rs +++ b/aries_vcx/src/core/profile/modular_libs_profile.rs @@ -4,6 +4,7 @@ use aries_vcx_core::anoncreds::base_anoncreds::BaseAnonCreds; use aries_vcx_core::anoncreds::credx_anoncreds::IndyCredxAnonCreds; use aries_vcx_core::ledger::base_ledger::BaseLedger; use aries_vcx_core::ledger::indy_vdr_ledger::{IndyVdrLedger, IndyVdrLedgerConfig}; +use aries_vcx_core::ledger::request_signer::base_wallet::BaseWalletRequestSigner; use aries_vcx_core::ledger::request_submitter::vdr_ledger::{IndyVdrLedgerPool, IndyVdrSubmitter, LedgerPoolConfig}; use aries_vcx_core::wallet::base_wallet::BaseWallet; use aries_vcx_core::ResponseParser; @@ -24,10 +25,11 @@ impl ModularLibsProfile { pub fn new(wallet: Arc, ledger_pool_config: LedgerPoolConfig) -> VcxResult { let anoncreds = Arc::new(IndyCredxAnonCreds::new(Arc::clone(&wallet))); let ledger_pool = Arc::new(IndyVdrLedgerPool::new(ledger_pool_config)?); + let request_signer = Arc::new(BaseWalletRequestSigner::new(wallet.clone())); let request_submitter = Arc::new(IndyVdrSubmitter::new(ledger_pool)); let response_parser = Arc::new(ResponseParser::new()); let config = IndyVdrLedgerConfig { - wallet: wallet.clone(), + request_signer, request_submitter, response_parser, }; diff --git a/aries_vcx/src/core/profile/vdr_proxy_profile.rs b/aries_vcx/src/core/profile/vdr_proxy_profile.rs index 51e9dcaca9..7f00815797 100644 --- a/aries_vcx/src/core/profile/vdr_proxy_profile.rs +++ b/aries_vcx/src/core/profile/vdr_proxy_profile.rs @@ -5,6 +5,7 @@ use aries_vcx_core::{ ledger::{ base_ledger::BaseLedger, indy_vdr_ledger::{IndyVdrLedger, IndyVdrLedgerConfig}, + request_signer::base_wallet::BaseWalletRequestSigner, request_submitter::vdr_proxy::VdrProxySubmitter, }, wallet::{base_wallet::BaseWallet, indy_wallet::IndySdkWallet}, @@ -24,10 +25,11 @@ impl VdrProxyProfile { pub fn new(wallet_handle: WalletHandle, client: VdrProxyClient) -> Self { let wallet = Arc::new(IndySdkWallet::new(wallet_handle)); let anoncreds = Arc::new(IndySdkAnonCreds::new(wallet_handle)); + let request_signer = Arc::new(BaseWalletRequestSigner::new(wallet.clone())); let request_submitter = Arc::new(VdrProxySubmitter::new(Arc::new(client))); let response_parser = Arc::new(ResponseParser::new()); let config = IndyVdrLedgerConfig { - wallet: wallet.clone(), + request_signer, request_submitter, response_parser, }; diff --git a/aries_vcx_core/src/ledger/indy_vdr_ledger.rs b/aries_vcx_core/src/ledger/indy_vdr_ledger.rs index 746ce31c4f..74e2a4ea66 100644 --- a/aries_vcx_core/src/ledger/indy_vdr_ledger.rs +++ b/aries_vcx_core/src/ledger/indy_vdr_ledger.rs @@ -21,36 +21,39 @@ use crate::common::ledger::transactions::verify_transaction_can_be_endorsed; use crate::errors::error::VcxCoreResult; use crate::global::author_agreement::get_txn_author_agreement; use crate::global::settings; -use crate::wallet::base_wallet::BaseWallet; use super::base_ledger::BaseLedger; +use super::request_signer::RequestSigner; use super::request_submitter::RequestSubmitter; -pub struct IndyVdrLedgerConfig +pub struct IndyVdrLedgerConfig where T: RequestSubmitter + Send + Sync, + U: RequestSigner + Send + Sync, { - pub wallet: Arc, + pub request_signer: Arc, pub request_submitter: Arc, pub response_parser: Arc, } -pub struct IndyVdrLedger +pub struct IndyVdrLedger where T: RequestSubmitter + Send + Sync, + U: RequestSigner + Send + Sync, { - wallet: Arc, + request_signer: Arc, request_submitter: Arc, response_parser: Arc, } -impl IndyVdrLedger +impl IndyVdrLedger where T: RequestSubmitter + Send + Sync, + U: RequestSigner + Send + Sync, { - pub fn new(config: IndyVdrLedgerConfig) -> Self { + pub fn new(config: IndyVdrLedgerConfig) -> Self { Self { - wallet: config.wallet, + request_signer: config.request_signer, request_submitter: config.request_submitter, response_parser: config.response_parser, } @@ -68,10 +71,7 @@ where } async fn _get_request_signature(&self, did: &str, request: &PreparedRequest) -> VcxCoreResult> { - let to_sign = request.get_signature_input()?; - let signer_verkey = self.wallet.key_for_local_did(did).await?; - let signature = self.wallet.sign(&signer_verkey, to_sign.as_bytes()).await?; - Ok(signature) + self.request_signer.sign(did, request).await } async fn _sign_and_submit_request(&self, submitter_did: &str, request: PreparedRequest) -> VcxCoreResult { @@ -193,9 +193,10 @@ where } } -impl Debug for IndyVdrLedger +impl Debug for IndyVdrLedger where T: RequestSubmitter + Send + Sync, + U: RequestSigner + Send + Sync, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "IndyVdrLedger instance") @@ -203,9 +204,10 @@ where } #[async_trait] -impl BaseLedger for IndyVdrLedger +impl BaseLedger for IndyVdrLedger where T: RequestSubmitter + Send + Sync, + U: RequestSigner + Send + Sync, { async fn sign_and_submit_request(&self, submitter_did: &str, request_json: &str) -> VcxCoreResult { let request = PreparedRequest::from_request_json(request_json)?; diff --git a/aries_vcx_core/src/ledger/mod.rs b/aries_vcx_core/src/ledger/mod.rs index 3b3296a77a..7bea8c26cc 100644 --- a/aries_vcx_core/src/ledger/mod.rs +++ b/aries_vcx_core/src/ledger/mod.rs @@ -4,4 +4,6 @@ pub mod indy_ledger; #[cfg(any(feature = "modular_libs", feature = "vdr_proxy_ledger"))] pub mod indy_vdr_ledger; #[cfg(any(feature = "modular_libs", feature = "vdr_proxy_ledger"))] +pub mod request_signer; +#[cfg(any(feature = "modular_libs", feature = "vdr_proxy_ledger"))] pub mod request_submitter; diff --git a/aries_vcx_core/src/ledger/request_signer/base_wallet.rs b/aries_vcx_core/src/ledger/request_signer/base_wallet.rs new file mode 100644 index 0000000000..35cc383065 --- /dev/null +++ b/aries_vcx_core/src/ledger/request_signer/base_wallet.rs @@ -0,0 +1,28 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use indy_vdr::pool::PreparedRequest; + +use crate::{errors::error::VcxCoreResult, wallet::base_wallet::BaseWallet}; + +use super::RequestSigner; + +pub struct BaseWalletRequestSigner { + wallet: Arc, +} + +impl BaseWalletRequestSigner { + pub fn new(wallet: Arc) -> Self { + Self { wallet } + } +} + +#[async_trait] +impl RequestSigner for BaseWalletRequestSigner { + async fn sign(&self, did: &str, request: &PreparedRequest) -> VcxCoreResult> { + let to_sign = request.get_signature_input()?; + let signer_verkey = self.wallet.key_for_local_did(did).await?; + let signature = self.wallet.sign(&signer_verkey, to_sign.as_bytes()).await?; + Ok(signature) + } +} diff --git a/aries_vcx_core/src/ledger/request_signer/mod.rs b/aries_vcx_core/src/ledger/request_signer/mod.rs new file mode 100644 index 0000000000..150574427b --- /dev/null +++ b/aries_vcx_core/src/ledger/request_signer/mod.rs @@ -0,0 +1,11 @@ +pub mod base_wallet; + +use async_trait::async_trait; +use indy_vdr::pool::PreparedRequest; + +use crate::errors::error::VcxCoreResult; + +#[async_trait] +pub trait RequestSigner: Send + Sync { + async fn sign(&self, did: &str, request: &PreparedRequest) -> VcxCoreResult>; +} diff --git a/aries_vcx_core/src/ledger/request_submitter/mod.rs b/aries_vcx_core/src/ledger/request_submitter/mod.rs index 8d26b3afc7..aaaac2570e 100644 --- a/aries_vcx_core/src/ledger/request_submitter/mod.rs +++ b/aries_vcx_core/src/ledger/request_submitter/mod.rs @@ -9,6 +9,6 @@ pub mod vdr_ledger; pub mod vdr_proxy; #[async_trait] -pub trait RequestSubmitter { +pub trait RequestSubmitter: Send + Sync { async fn submit(&self, request: PreparedRequest) -> VcxCoreResult; } diff --git a/did_resolver_sov/src/reader/vdr_reader.rs b/did_resolver_sov/src/reader/vdr_reader.rs index 3ce4033147..9a1213f3ee 100644 --- a/did_resolver_sov/src/reader/vdr_reader.rs +++ b/did_resolver_sov/src/reader/vdr_reader.rs @@ -4,6 +4,7 @@ use crate::error::DidSovError; use aries_vcx_core::{ ledger::{ indy_vdr_ledger::{IndyVdrLedger, IndyVdrLedgerConfig}, + request_signer::base_wallet::BaseWalletRequestSigner, request_submitter::vdr_ledger::{IndyVdrLedgerPool, IndyVdrSubmitter, LedgerPoolConfig}, }, wallet::{base_wallet::BaseWallet, indy_wallet::IndySdkWallet}, @@ -19,9 +20,10 @@ impl TryFrom for ConcreteAttrReader { let wallet = Arc::new(IndySdkWallet::new(INVALID_WALLET_HANDLE)) as Arc; let ledger_pool = Arc::new(IndyVdrLedgerPool::new(pool_config)?); let request_submitter = Arc::new(IndyVdrSubmitter::new(ledger_pool)); + let request_signer = Arc::new(BaseWalletRequestSigner::new(wallet.clone())); let response_parser = Arc::new(ResponseParser::new()); let config = IndyVdrLedgerConfig { - wallet: wallet.clone(), + request_signer, request_submitter, response_parser, };