From 49281f7f004cdc6419faf490de5cb0d853a623d4 Mon Sep 17 00:00:00 2001 From: Miroslav Kovar Date: Mon, 15 May 2023 16:12:58 +0200 Subject: [PATCH] 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 | 194 ++++---------- 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(+), 180 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..0d1c4a1759 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)?; - - data["ver"] = Value::String("1.0".to_string()); + let rev_reg_def = self.response_parser.parse_get_revoc_reg_def_response(&res)?; - 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 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 } - }); + let RevocationRegistryDeltaInfo { + revoc_reg_def_id, + revoc_reg_delta, + timestamp, + } = self.response_parser.parse_get_revoc_reg_delta_response(&res)?; - 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 { 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}