diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 84dcb16b86..6ef4a9b634 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -289,6 +289,20 @@ jobs: - name: "Run aries-vcx integration tests" run: cargo test --manifest-path="aries/aries_vcx/Cargo.toml" -F vdrtools_wallet,credx -- --ignored; + test-integration-aries-vcx-anoncreds-rs: + needs: workflow-setup + runs-on: ubuntu-20.04 + steps: + - name: "Git checkout" + uses: actions/checkout@v3 + - name: "Setup rust testing environment" + uses: ./.github/actions/setup-testing-rust + with: + rust-toolchain-version: ${{ env.RUST_TOOLCHAIN_VERSION }} + default: true + - name: "Run anoncreds-rs integration tests" + run: cargo test --manifest-path="aries/aries_vcx/Cargo.toml" -F anoncreds --test test_revocations --test test_proof_presentation --test test_anoncreds -- --ignored + test-integration-aries-vcx-mysql: needs: workflow-setup runs-on: ubuntu-20.04 @@ -392,8 +406,6 @@ jobs: - name: "Run integration tests" run: (cd aries/agents/node/vcxagent-core && AGENCY_URL=http://localhost:8080 npm run test:integration) - - ########################################################################################## ############################ NPMJS PUBLISHING ####################################### diff --git a/Cargo.lock b/Cargo.lock index 92d5eb8d19..5be715acd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,6 +295,30 @@ dependencies = [ "libc", ] +[[package]] +name = "anoncreds" +version = "0.2.0-dev.5" +source = "git+https://github.com/mirgee/anoncreds-rs.git?rev=9a7539c#9a7539c62dc0ee65891c2d94b66b7986ef33f210" +dependencies = [ + "anoncreds-clsignatures 0.3.0", + "base64", + "bitvec", + "bs58 0.4.0", + "chrono", + "env_logger 0.9.3", + "ffi-support", + "log", + "once_cell", + "rand 0.8.5", + "regex", + "rmp-serde", + "serde", + "serde_json", + "sha2", + "thiserror", + "zeroize", +] + [[package]] name = "anoncreds-clsignatures" version = "0.2.3" @@ -314,6 +338,25 @@ dependencies = [ "sha2", ] +[[package]] +name = "anoncreds-clsignatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6aaf926b07b4d4067ac9614b73caf126b399f419f5c5d41318dbce97d3ddb86" +dependencies = [ + "amcl", + "glass_pumpkin", + "log", + "num-bigint", + "num-integer", + "num-traits", + "once_cell", + "openssl", + "rand 0.8.5", + "serde", + "sha2", +] + [[package]] name = "anyhow" version = "1.0.75" @@ -394,7 +437,10 @@ name = "aries_vcx_core" version = "0.1.0" dependencies = [ "agency_client", + "anoncreds", "async-trait", + "bitvec", + "did_parser", "futures", "indy-api-types", "indy-credx", @@ -722,9 +768,9 @@ checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" [[package]] name = "base64" -version = "0.21.4" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64-url" @@ -765,6 +811,19 @@ dependencies = [ "serde", ] +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "serde", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -1940,6 +1999,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.28" @@ -2436,7 +2501,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72a8c97ba7f3a0af57c6895f2b7836fc00aa7ed3a56ce28e2367ded9dea3d9be" dependencies = [ - "anoncreds-clsignatures", + "anoncreds-clsignatures 0.2.3", "bs58 0.5.0", "curve25519-dalek", "ed25519-dalek", @@ -2457,7 +2522,7 @@ name = "indy-data-types" version = "0.7.0" source = "git+https://github.com/hyperledger/indy-shared-rs?tag=v1.1.0#0260b93f76573613cedb486bc8836c75c47d4cf4" dependencies = [ - "anoncreds-clsignatures", + "anoncreds-clsignatures 0.2.3", "bs58 0.5.0", "curve25519-dalek", "ed25519-dalek", @@ -2477,7 +2542,7 @@ dependencies = [ name = "indy-ledger-response-parser" version = "0.1.0" dependencies = [ - "anoncreds-clsignatures", + "anoncreds-clsignatures 0.2.3", "indy-vdr", "serde", "serde_json", @@ -3629,6 +3694,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.7.3" @@ -4609,6 +4680,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.8.0" @@ -5548,6 +5625,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x25519-dalek" version = "2.0.0" diff --git a/aries/agents/rust/aries-vcx-agent/src/agent/init.rs b/aries/agents/rust/aries-vcx-agent/src/agent/init.rs index 78f036f87d..c707ae07c3 100644 --- a/aries/agents/rust/aries-vcx-agent/src/agent/init.rs +++ b/aries/agents/rust/aries-vcx-agent/src/agent/init.rs @@ -155,6 +155,7 @@ impl Agent { )); let rev_regs = Arc::new(ServiceRevocationRegistries::new( ledger_write.clone(), + ledger_read.clone(), anoncreds, wallet.clone(), config_issuer.institution_did.clone(), diff --git a/aries/agents/rust/aries-vcx-agent/src/services/revocation_registry.rs b/aries/agents/rust/aries-vcx-agent/src/services/revocation_registry.rs index e58f2eea5f..8da6a808bb 100644 --- a/aries/agents/rust/aries-vcx-agent/src/services/revocation_registry.rs +++ b/aries/agents/rust/aries-vcx-agent/src/services/revocation_registry.rs @@ -6,7 +6,8 @@ use std::{ use aries_vcx::common::primitives::revocation_registry::RevocationRegistry; use aries_vcx_core::{ anoncreds::credx_anoncreds::IndyCredxAnonCreds, - ledger::indy_vdr_ledger::DefaultIndyLedgerWrite, wallet::indy::IndySdkWallet, + ledger::indy_vdr_ledger::{DefaultIndyLedgerRead, DefaultIndyLedgerWrite}, + wallet::indy::IndySdkWallet, }; use crate::{ @@ -16,6 +17,7 @@ use crate::{ pub struct ServiceRevocationRegistries { ledger_write: Arc, + ledger_read: Arc, anoncreds: IndyCredxAnonCreds, wallet: Arc, issuer_did: String, @@ -25,6 +27,7 @@ pub struct ServiceRevocationRegistries { impl ServiceRevocationRegistries { pub fn new( ledger_write: Arc, + ledger_read: Arc, anoncreds: IndyCredxAnonCreds, wallet: Arc, issuer_did: String, @@ -33,6 +36,7 @@ impl ServiceRevocationRegistries { issuer_did, rev_regs: ObjectCache::new("rev-regs"), ledger_write, + ledger_read, anoncreds, wallet, } @@ -91,7 +95,12 @@ impl ServiceRevocationRegistries { pub async fn revoke_credential_locally(&self, id: &str, cred_rev_id: &str) -> AgentResult<()> { let rev_reg = self.rev_regs.get(id)?; rev_reg - .revoke_credential_local(self.wallet.as_ref(), &self.anoncreds, cred_rev_id) + .revoke_credential_local( + self.wallet.as_ref(), + &self.anoncreds, + self.ledger_read.as_ref(), + cred_rev_id, + ) .await?; Ok(()) } diff --git a/aries/aries_vcx/Cargo.toml b/aries/aries_vcx/Cargo.toml index 0a1848e274..a71fb38776 100644 --- a/aries/aries_vcx/Cargo.toml +++ b/aries/aries_vcx/Cargo.toml @@ -17,6 +17,7 @@ credx = [ "test_utils/vdrtools_wallet", "test_utils/credx" ] +anoncreds = ["aries_vcx_core/anoncreds", "test_utils/anoncreds", "test_utils/vdrtools_wallet"] vdr_proxy_ledger = [ "aries_vcx_core/vdr_proxy_ledger", "aries_vcx_core/vdrtools_wallet", diff --git a/aries/aries_vcx/src/common/primitives/credential_definition.rs b/aries/aries_vcx/src/common/primitives/credential_definition.rs index 36564b7724..d0c44acc05 100644 --- a/aries/aries_vcx/src/common/primitives/credential_definition.rs +++ b/aries/aries_vcx/src/common/primitives/credential_definition.rs @@ -119,6 +119,7 @@ impl CredentialDef { wallet, anoncreds, &issuer_did, + &schema_id, &schema_json, &tag, None, @@ -234,10 +235,12 @@ impl CredentialDef { } } +#[allow(clippy::too_many_arguments)] pub async fn generate_cred_def( wallet: &impl BaseWallet, anoncreds: &impl BaseAnonCreds, issuer_did: &str, + schema_id: &str, schema_json: &str, tag: &str, sig_type: Option<&str>, @@ -260,6 +263,7 @@ pub async fn generate_cred_def( .issuer_create_and_store_credential_def( wallet, issuer_did, + schema_id, schema_json, tag, sig_type, diff --git a/aries/aries_vcx/src/common/primitives/revocation_registry.rs b/aries/aries_vcx/src/common/primitives/revocation_registry.rs index 1318947729..9830abd56b 100644 --- a/aries/aries_vcx/src/common/primitives/revocation_registry.rs +++ b/aries/aries_vcx/src/common/primitives/revocation_registry.rs @@ -1,6 +1,8 @@ use aries_vcx_core::{ - anoncreds::base_anoncreds::BaseAnonCreds, errors::error::AriesVcxCoreErrorKind, - ledger::base_ledger::AnoncredsLedgerWrite, wallet::base_wallet::BaseWallet, + anoncreds::base_anoncreds::BaseAnonCreds, + errors::error::{AriesVcxCoreError, AriesVcxCoreErrorKind}, + ledger::base_ledger::{AnoncredsLedgerRead, AnoncredsLedgerWrite}, + wallet::base_wallet::BaseWallet, }; use super::credential_definition::PublicEntityStateType; @@ -115,9 +117,9 @@ impl RevocationRegistry { .publish_rev_reg_def(wallet, &json!(self.rev_reg_def).to_string(), issuer_did) .await .map_err(|err| { - err.map( + AriesVcxCoreError::from_msg( AriesVcxCoreErrorKind::InvalidState, - "Cannot publish revocation registry definition", + format!("Cannot publish revocation registry definition; {err}"), ) })?; self.rev_reg_def_state = PublicEntityStateType::Published; @@ -139,9 +141,9 @@ impl RevocationRegistry { .publish_rev_reg_delta(wallet, &self.rev_reg_id, &self.rev_reg_entry, issuer_did) .await .map_err(|err| { - err.map( + AriesVcxCoreError::from_msg( AriesVcxCoreErrorKind::InvalidRevocationEntry, - "Cannot post RevocationEntry", + format!("Cannot publish revocation entry; {err}"), ) })?; self.rev_reg_delta_state = PublicEntityStateType::Published; @@ -216,10 +218,21 @@ impl RevocationRegistry { &self, wallet: &impl BaseWallet, anoncreds: &impl BaseAnonCreds, + ledger: &impl AnoncredsLedgerRead, cred_rev_id: &str, ) -> VcxResult<()> { + let rev_reg_delta_json = ledger + .get_rev_reg_delta_json(&self.rev_reg_id, None, None) + .await? + .1; anoncreds - .revoke_credential_local(wallet, &self.tails_dir, &self.rev_reg_id, cred_rev_id) + .revoke_credential_local( + wallet, + &self.tails_dir, + &self.rev_reg_id, + &rev_reg_delta_json, + cred_rev_id, + ) .await .map_err(|err| err.into()) } @@ -280,7 +293,7 @@ impl RevocationRegistry { #[derive(Clone, Deserialize, Debug, Serialize, PartialEq, Eq, Default)] #[serde(rename_all = "camelCase")] pub struct RevocationRegistryDefinitionValue { - pub issuance_type: String, + pub issuance_type: String, // FILL IN pub max_cred_num: u32, pub public_keys: serde_json::Value, pub tails_hash: String, @@ -290,12 +303,12 @@ pub struct RevocationRegistryDefinitionValue { #[derive(Clone, Deserialize, Debug, Serialize, PartialEq, Eq, Default)] #[serde(rename_all = "camelCase")] pub struct RevocationRegistryDefinition { - pub id: String, + pub id: String, // FILL IN pub revoc_def_type: String, pub tag: String, pub cred_def_id: String, pub value: RevocationRegistryDefinitionValue, - pub ver: String, + pub ver: String, // FILL IN } pub async fn generate_rev_reg( wallet: &impl BaseWallet, diff --git a/aries/aries_vcx/src/common/proofs/prover/prover_internal.rs b/aries/aries_vcx/src/common/proofs/prover/prover_internal.rs index 72b0e3cc10..5ec9db2654 100644 --- a/aries/aries_vcx/src/common/proofs/prover/prover_internal.rs +++ b/aries/aries_vcx/src/common/proofs/prover/prover_internal.rs @@ -43,7 +43,10 @@ pub async fn build_schemas_json_prover( .get_schema(&cred_info.schema_id, None) .await .map_err(|err| { - err.map(AriesVcxCoreErrorKind::InvalidSchema, "Cannot get schema") + AriesVcxError::from_msg( + AriesVcxErrorKind::InvalidSchema, + format!("Cannot get schema id {}; {}", cred_info.schema_id, err), + ) })?; let schema_json = serde_json::from_str(&schema_json).map_err(|err| { diff --git a/aries/aries_vcx/src/handlers/issuance/issuer.rs b/aries/aries_vcx/src/handlers/issuance/issuer.rs index b7db488cf9..57d7cd8c77 100644 --- a/aries/aries_vcx/src/handlers/issuance/issuer.rs +++ b/aries/aries_vcx/src/handlers/issuance/issuer.rs @@ -228,6 +228,7 @@ impl Issuer { &self, wallet: &impl BaseWallet, anoncreds: &impl BaseAnonCreds, + ledger: &impl AnoncredsLedgerRead, ) -> VcxResult<()> { let revocation_info: RevocationInfoV1 = self.issuer_sm @@ -241,8 +242,18 @@ impl Issuer { revocation_info.rev_reg_id, revocation_info.tails_file, ) { + let rev_reg_delta_json = ledger + .get_rev_reg_delta_json(&rev_reg_id, None, None) + .await? + .1; anoncreds - .revoke_credential_local(wallet, &tails_file, &rev_reg_id, &cred_rev_id) + .revoke_credential_local( + wallet, + &tails_file, + &rev_reg_id, + &cred_rev_id, + &rev_reg_delta_json, + ) .await?; } else { return Err(AriesVcxError::from_msg( diff --git a/aries/aries_vcx/src/protocols/issuance/holder/state_machine.rs b/aries/aries_vcx/src/protocols/issuance/holder/state_machine.rs index 79c1f54862..b6ad0d23c5 100644 --- a/aries/aries_vcx/src/protocols/issuance/holder/state_machine.rs +++ b/aries/aries_vcx/src/protocols/issuance/holder/state_machine.rs @@ -235,7 +235,8 @@ impl HolderSM { let problem_report = build_problem_report_msg(Some(err.to_string()), &self.thread_id); error!( - "Failed to create credential request, generating problem report: {:?}", + "Failed to create credential request with error {err}, generating \ + problem report: {:?}", problem_report ); HolderFullState::Finished(FinishedHolderState::new(problem_report)) @@ -599,9 +600,13 @@ pub async fn create_anoncreds_credential_request( master_secret_id, ) .await - .map_err(|err| err.extend("Cannot create credential request")) + .map_err(|err| { + AriesVcxError::from_msg( + AriesVcxErrorKind::InvalidState, + format!("Cannot create credential request; {}", err), + ) + }) .map(|(s1, s2)| (s1, s2, cred_def_id.to_string(), cred_def_json)) - .map_err(|err| err.into()) } async fn build_credential_request_msg( diff --git a/aries/aries_vcx/tests/test_anoncreds.rs b/aries/aries_vcx/tests/test_anoncreds.rs index f5ff822d1d..6a3f40c202 100644 --- a/aries/aries_vcx/tests/test_anoncreds.rs +++ b/aries/aries_vcx/tests/test_anoncreds.rs @@ -143,7 +143,7 @@ async fn test_pool_revoke_credential() -> Result<(), Box> { .await; let cred_rev_id = get_cred_rev_id(&setup.wallet, &setup.anoncreds, &cred_id).await?; - let ledger = setup.ledger_read; + let ledger = &setup.ledger_read; let (_, first_rev_reg_delta, first_timestamp) = ledger .get_rev_reg_delta_json(&rev_reg.rev_reg_id, None, None) @@ -158,12 +158,18 @@ async fn test_pool_revoke_credential() -> Result<(), Box> { let anoncreds = &setup.anoncreds; + let rev_reg_delta_json = setup + .ledger_read + .get_rev_reg_delta_json(&rev_reg.rev_reg_id, None, None) + .await? + .1; anoncreds .revoke_credential_local( &setup.wallet, get_temp_dir_path().to_str().unwrap(), &rev_reg.rev_reg_id, &cred_rev_id, + &rev_reg_delta_json, ) .await?; diff --git a/aries/aries_vcx/tests/test_credentials.rs b/aries/aries_vcx/tests/test_credentials.rs index 82c8e5ac9b..65c3c916ad 100644 --- a/aries/aries_vcx/tests/test_credentials.rs +++ b/aries/aries_vcx/tests/test_credentials.rs @@ -1,7 +1,9 @@ use std::error::Error; use aries_vcx::common::credentials::{get_cred_rev_id, is_cred_revoked, ProverCredential}; -use aries_vcx_core::anoncreds::base_anoncreds::BaseAnonCreds; +use aries_vcx_core::{ + anoncreds::base_anoncreds::BaseAnonCreds, ledger::base_ledger::AnoncredsLedgerRead, +}; use test_utils::{constants::DEFAULT_SCHEMA_ATTRS, devsetup::build_setup_profile}; use crate::utils::{ @@ -110,6 +112,11 @@ async fn test_pool_is_cred_revoked() -> Result<(), Box> { assert!(!is_cred_revoked(&setup.ledger_read, &rev_reg.rev_reg_id, &cred_rev_id).await?); + let rev_reg_delta_json = setup + .ledger_read + .get_rev_reg_delta_json(&rev_reg.rev_reg_id, None, None) + .await? + .1; setup .anoncreds .revoke_credential_local( @@ -117,6 +124,7 @@ async fn test_pool_is_cred_revoked() -> Result<(), Box> { &rev_reg.get_tails_dir(), &rev_reg.rev_reg_id, &cred_rev_id, + &rev_reg_delta_json, ) .await?; rev_reg diff --git a/aries/aries_vcx/tests/test_primitives.rs b/aries/aries_vcx/tests/test_primitives.rs index 0af7da3b2c..e796c688cb 100644 --- a/aries/aries_vcx/tests/test_primitives.rs +++ b/aries/aries_vcx/tests/test_primitives.rs @@ -35,6 +35,7 @@ async fn test_pool_create_cred_def_real() -> Result<(), Box> { &setup.wallet, &setup.anoncreds, &setup.institution_did, + &schema.schema_id, &schema_json, "tag_1", None, @@ -77,6 +78,7 @@ async fn test_pool_create_rev_reg_def() -> Result<(), Box> { &setup.wallet, &setup.anoncreds, &setup.institution_did, + &schema.schema_id, &schema_json, "tag_1", None, diff --git a/aries/aries_vcx/tests/utils/mod.rs b/aries/aries_vcx/tests/utils/mod.rs index fb9ff8cd11..001b2f0665 100644 --- a/aries/aries_vcx/tests/utils/mod.rs +++ b/aries/aries_vcx/tests/utils/mod.rs @@ -140,7 +140,6 @@ pub async fn create_and_write_credential( } else { (None, None, None) }; - println!("rev_reg_def_json: {:?}", rev_reg_def_json); let (cred, _, _) = anoncreds_issuer .issuer_create_credential( wallet_issuer, diff --git a/aries/aries_vcx/tests/utils/scenarios/proof_presentation.rs b/aries/aries_vcx/tests/utils/scenarios/proof_presentation.rs index 903bee8d65..d4062aef2b 100644 --- a/aries/aries_vcx/tests/utils/scenarios/proof_presentation.rs +++ b/aries/aries_vcx/tests/utils/scenarios/proof_presentation.rs @@ -248,7 +248,6 @@ pub async fn revoke_credential_and_publish_accumulator( rev_reg: &RevocationRegistry, ) { revoke_credential_local(faber, issuer_credential, &rev_reg.rev_reg_id).await; - rev_reg .publish_local_revocations( &faber.wallet, @@ -272,7 +271,7 @@ pub async fn revoke_credential_local( _rev_reg_id: &str, ) { issuer_credential - .revoke_credential_local(&faber.wallet, &faber.anoncreds) + .revoke_credential_local(&faber.wallet, &faber.anoncreds, &faber.ledger_read) .await .unwrap(); } diff --git a/aries/aries_vcx_core/Cargo.toml b/aries/aries_vcx_core/Cargo.toml index 7d9f0efd4c..6f977d219e 100644 --- a/aries/aries_vcx_core/Cargo.toml +++ b/aries/aries_vcx_core/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" vdrtools_wallet = ["dep:libvdrtools", "dep:indy-api-types"] # Feature flag to include the 'modular library' dependencies (vdrtools alternatives; indy-vdr, indy-credx) credx = ["dep:indy-credx"] +anoncreds = ["dep:anoncreds"] vdr_proxy_ledger = ["credx", "dep:indy-vdr-proxy-client"] # Feature flag to allow legacy proof verification legacy_proof = [] @@ -16,8 +17,11 @@ legacy_proof = [] agency_client = { path = "../misc/legacy/agency_client" } indy-vdr = { git = "https://github.com/hyperledger/indy-vdr.git", rev = "c143268", default-features = false, features = ["log"] } indy-credx = { git = "https://github.com/hyperledger/indy-shared-rs", tag = "v1.1.0", optional = true } +# anoncreds = { git = "https://github.com/hyperledger/anoncreds-rs", tag = "v0.2.0-dev.5", optional = true } +anoncreds = { git = "https://github.com/mirgee/anoncreds-rs.git", rev = "9a7539c", optional = true } libvdrtools = { path = "../misc/legacy/libvdrtools", optional = true } indy-api-types = { path = "../misc/legacy/libvdrtools/indy-api-types", optional = true } +did_parser = { path = "../../did_core/did_parser" } async-trait = "0.1.68" futures = { version = "0.3", default-features = false } serde_json = "1.0.95" @@ -34,6 +38,7 @@ indy-vdr-proxy-client = { git = "https://github.com/hyperledger/indy-vdr.git", r indy-ledger-response-parser = { path = "../misc/indy_ledger_response_parser" } lru = { version = "0.12.0" } public_key = { path = "../../did_core/public_key"} +bitvec = "1.0.1" [dev-dependencies] tokio = { version = "1.20", features = ["rt", "macros", "rt-multi-thread"] } diff --git a/aries/aries_vcx_core/src/anoncreds/anoncreds.rs b/aries/aries_vcx_core/src/anoncreds/anoncreds.rs new file mode 100644 index 0000000000..0f52c88030 --- /dev/null +++ b/aries/aries_vcx_core/src/anoncreds/anoncreds.rs @@ -0,0 +1,1778 @@ +use std::collections::{BTreeSet, HashMap, HashSet}; + +use anoncreds::{ + cl::{Accumulator, RevocationRegistry as CryptoRevocationRegistry, RevocationRegistryDelta}, + data_types::{ + cred_def::{CredentialDefinition, CredentialDefinitionId, CL_SIGNATURE_TYPE}, + credential::Credential, + issuer_id::IssuerId, + rev_reg_def::{RevocationRegistryDefinitionId, CL_ACCUM}, + schema::{Schema, SchemaId}, + }, + issuer::{create_revocation_registry_def, create_revocation_status_list}, + tails::TailsFileWriter, + types::{ + CredentialOffer, CredentialRequestMetadata, CredentialRevocationConfig, + CredentialRevocationState, LinkSecret, PresentCredentials, Presentation, + PresentationRequest, RegistryType, RevocationRegistry, RevocationRegistryDefinition, + RevocationStatusList, SignatureType, + }, +}; +use async_trait::async_trait; +use bitvec::bitvec; +use did_parser::Did; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_json::{json, Value}; +use time::OffsetDateTime; +use uuid::Uuid; + +use super::base_anoncreds::BaseAnonCreds; +use crate::{ + errors::error::{AriesVcxCoreError, AriesVcxCoreErrorKind, VcxCoreResult}, + utils::{ + constants::ATTRS, + json::{AsTypeOrDeserializationError, TryGetIndex}, + }, + wallet::base_wallet::{record::Record, search_filter::SearchFilter, BaseWallet}, +}; + +pub const CATEGORY_LINK_SECRET: &str = "VCX_LINK_SECRET"; + +pub const CATEGORY_CREDENTIAL: &str = "VCX_CREDENTIAL"; +pub const CATEGORY_CRED_DEF: &str = "VCX_CRED_DEF"; +pub const CATEGORY_CRED_KEY_CORRECTNESS_PROOF: &str = "VCX_CRED_KEY_CORRECTNESS_PROOF"; +pub const CATEGORY_CRED_DEF_PRIV: &str = "VCX_CRED_DEF_PRIV"; +pub const CATEGORY_CRED_SCHEMA: &str = "VCX_CRED_SCHEMA"; + +pub const CATEGORY_CRED_MAP_SCHEMA_ID: &str = "VCX_CRED_MAP_SCHEMA_ID"; + +pub const CATEGORY_REV_REG: &str = "VCX_REV_REG"; +pub const CATEGORY_REV_REG_DELTA: &str = "VCX_REV_REG_DELTA"; +pub const CATEGORY_REV_REG_INFO: &str = "VCX_REV_REG_INFO"; +pub const CATEGORY_REV_REG_DEF: &str = "VCX_REV_REG_DEF"; +pub const CATEGORY_REV_REG_DEF_PRIV: &str = "VCX_REV_REG_DEF_PRIV"; + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +struct RevRegDeltaSubParts { + prev_accum: Option, + accum: Accumulator, + #[serde(default)] + #[serde(skip_serializing_if = "BTreeSet::is_empty")] + issued: BTreeSet, + #[serde(default)] + #[serde(skip_serializing_if = "BTreeSet::is_empty")] + revoked: BTreeSet, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct RevRegDeltaParts { + value: RevRegDeltaSubParts, +} + +fn from_revocation_registry_delta_to_revocation_status_list( + delta: &RevRegDeltaSubParts, + rev_reg_def: &RevocationRegistryDefinition, + rev_reg_def_id: &RevocationRegistryDefinitionId, + timestamp: Option, + issuance_by_default: bool, +) -> VcxCoreResult { + let default_state = if issuance_by_default { 0 } else { 1 }; + let mut revocation_list = bitvec![default_state; rev_reg_def.value.max_cred_num as usize]; + + for issued in &delta.issued { + revocation_list.insert(*issued as usize, false); + } + + for revoked in &delta.revoked { + revocation_list.insert(*revoked as usize, true); + } + + let accum = delta.accum.into(); + + RevocationStatusList::new( + Some(&rev_reg_def_id.to_string()), + rev_reg_def.issuer_id.clone(), + revocation_list, + Some(accum), + timestamp, + ) + .map_err(Into::into) +} + +fn from_revocation_status_list_to_revocation_registry_delta( + rev_status_list: &RevocationStatusList, + prev_accum: Option, +) -> VcxCoreResult { + let _issued = rev_status_list + .state() + .iter() + .enumerate() + .filter_map( + |(idx, is_revoked)| { + if !is_revoked { + Some(idx as u32) + } else { + None + } + }, + ) + .collect::>(); + let revoked = rev_status_list + .state() + .iter() + .enumerate() + .filter_map( + |(idx, is_revoked)| { + if *is_revoked { + Some(idx as u32) + } else { + None + } + }, + ) + .collect::>(); + + let registry = CryptoRevocationRegistry { + accum: rev_status_list.accum().ok_or_else(|| { + AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::InvalidState, + "Revocation registry delta cannot be created from revocation status list without \ + accumulator", + ) + })?, + }; + + Ok(RevRegDeltaParts { + value: RevRegDeltaSubParts { + prev_accum, + accum: registry.accum, + issued: Default::default(), // TODO + revoked, + }, + }) +} + +#[derive(Debug)] +pub struct Anoncreds; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct RevocationRegistryInfo { + pub id: RevocationRegistryDefinitionId, + pub curr_id: u32, + pub used_ids: HashSet, +} + +impl Anoncreds { + async fn get_wallet_record_value( + &self, + wallet: &impl BaseWallet, + category: &str, + id: &str, + ) -> VcxCoreResult + where + T: DeserializeOwned, + { + let str_record = wallet.get_record(category, id).await?; + serde_json::from_str(str_record.value()).map_err(From::from) + } + + async fn get_link_secret( + &self, + wallet: &impl BaseWallet, + link_secret_id: &str, + ) -> VcxCoreResult { + let ms_decimal = wallet + .get_record(CATEGORY_LINK_SECRET, link_secret_id) + .await?; + + Ok(ms_decimal.value().try_into().unwrap()) + } + + async fn _get_credential( + &self, + wallet: &impl BaseWallet, + credential_id: &str, + ) -> VcxCoreResult { + let cred_record = wallet + .get_record(CATEGORY_CREDENTIAL, credential_id) + .await?; + + let credential: Credential = serde_json::from_str(cred_record.value())?; + + Ok(credential) + } + + async fn _get_credentials( + wallet: &impl BaseWallet, + wql: &str, + ) -> VcxCoreResult> { + let records = wallet + .search_record( + CATEGORY_CREDENTIAL, + Some(SearchFilter::JsonFilter(wql.into())), + ) + .await?; + + let id_cred_tuple_list: VcxCoreResult> = records + .into_iter() + .map(|record| { + let credential: Credential = serde_json::from_str(record.value())?; + + Ok((record.name().into(), credential)) + }) + .collect(); + + id_cred_tuple_list + } + + async fn _get_credentials_for_proof_req_for_attr_name( + &self, + wallet: &impl BaseWallet, + restrictions: Option<&Value>, + attr_names: Vec, + ) -> VcxCoreResult> { + let mut attrs = Vec::new(); + + for name in attr_names { + let attr_marker_tag_name = _format_attribute_as_marker_tag_name(&name); + + let wql_attr_query = json!({ + attr_marker_tag_name: "1" + }); + + attrs.push(wql_attr_query); + } + + let restrictions = restrictions.map(|x| x.to_owned()); + + let wql_query = if let Some(restrictions) = restrictions { + match restrictions { + Value::Array(restrictions) => { + let restrictions_wql = json!({ "$or": restrictions }); + attrs.push(restrictions_wql); + json!({ "$and": attrs }) + } + Value::Object(restriction) => { + attrs.push(Value::Object(restriction)); + json!({ "$and": attrs }) + } + _ => Err(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::InvalidInput, + "Invalid attribute restrictions (must be array or an object)", + ))?, + } + } else { + json!({ "$and": attrs }) + }; + + let wql_query = serde_json::to_string(&wql_query)?; + + Self::_get_credentials(wallet, &wql_query).await + } +} + +#[async_trait] +impl BaseAnonCreds for Anoncreds { + async fn verifier_verify_proof( + &self, + proof_request_json: &str, + proof_json: &str, + schemas_json: &str, + credential_defs_json: &str, + rev_reg_defs_json: &str, + rev_regs_json: &str, + ) -> VcxCoreResult { + let presentation: Presentation = serde_json::from_str(proof_json)?; + let pres_req: PresentationRequest = serde_json::from_str(proof_request_json)?; + + let mut schemas_val: HashMap = serde_json::from_str(schemas_json)?; + let mut schemas: HashMap = HashMap::new(); + for (schema_id, schema_json) in schemas_val.iter_mut() { + if let Some(v) = schema_json.as_object_mut() { + v.insert( + "issuerId".to_owned(), + schema_parts(&schema_id.to_string()).unwrap().1.did().into(), + ); + } + let schema = serde_json::from_value(schema_json.clone())?; + schemas.insert(schema_id.clone(), schema); + } + + let mut cred_defs_val: HashMap = + serde_json::from_str(credential_defs_json)?; + let mut cred_defs: HashMap = HashMap::new(); + for (cred_def_id, cred_def_json) in cred_defs_val.iter_mut() { + if let Some(v) = cred_def_json.as_object_mut() { + v.insert( + "issuerId".to_owned(), + cred_def_parts(&cred_def_id.to_string()) + .unwrap() + .1 + .did() + .into(), + ); + } + let cred_def = serde_json::from_value(cred_def_json.clone())?; + cred_defs.insert(cred_def_id.clone(), cred_def); + } + + let rev_reg_defs_val: Option> = + serde_json::from_str(rev_reg_defs_json)?; + let rev_reg_defs: Option< + HashMap, + > = if let Some(mut rev_defs) = rev_reg_defs_val.clone() { + let mut map = HashMap::new(); + for (rev_reg_def_id, rev_reg_def_json) in rev_defs.iter_mut() { + let v = rev_reg_def_json.as_object_mut().unwrap(); + v.insert( + "issuerId".to_owned(), + rev_reg_def_id.to_string().split(':').next().into(), + ); + v.insert("revocDefType".to_owned(), CL_ACCUM.to_owned().into()); + + map.insert( + rev_reg_def_id.clone(), + serde_json::from_value(rev_reg_def_json.clone())?, + ); + } + Some(map) + } else { + None + }; + + // WTF, anoncreds-rs expects the whole status list just to use the accumulator :| + let rev_reg_deltas: Option< + HashMap>, + > = serde_json::from_str(rev_regs_json)?; + + let rev_status_lists = if let Some(map) = rev_reg_deltas { + let mut lists = Vec::new(); + for (rev_reg_def_id, timestamp_map) in map { + for (timestamp, delta) in timestamp_map { + let issuer_id = IssuerId::new( + rev_reg_def_id + .to_string() + .split(':') + .next() + .unwrap() + .to_string(), + ) + .unwrap(); + let RevRegDeltaSubParts { accum, revoked, .. } = delta.value; + let rev_reg_defs = rev_reg_defs_val.as_ref().unwrap(); + let rev_reg_def = rev_reg_defs.get(&rev_reg_def_id).unwrap(); + let rev_reg_def_id = Some(rev_reg_def_id.0.as_str()); + + let max_cred_num = rev_reg_def["value"]["maxCredNum"].as_u64().unwrap(); + let mut revocation_list = bitvec!(0; max_cred_num as usize); + revoked.into_iter().for_each(|id| { + revocation_list + .get_mut(id as usize) + .map(|mut b| *b = true) + .unwrap_or_default() + }); + + let registry = CryptoRevocationRegistry { accum }; + + let rev_status_list = RevocationStatusList::new( + rev_reg_def_id, + issuer_id, + revocation_list, + Some(registry), + Some(timestamp), + )?; + + lists.push(rev_status_list); + } + } + + Some(lists) + } else { + None + }; + + Ok(anoncreds::verifier::verify_presentation( + &presentation, + &pres_req, + &schemas, + &cred_defs, + rev_reg_defs.as_ref(), + rev_status_lists, + None, // no idea what this is + )?) + } + + async fn issuer_create_and_store_revoc_reg( + &self, + wallet: &impl BaseWallet, + issuer_did: &str, + cred_def_id: &str, + tails_dir: &str, + max_creds: u32, + tag: &str, + ) -> VcxCoreResult<(String, String, String)> { + let mut tails_writer = TailsFileWriter::new(Some(tails_dir.to_owned())); + + let cred_def: CredentialDefinition = self + .get_wallet_record_value(wallet, CATEGORY_CRED_DEF, cred_def_id) + .await?; + let cred_def_id = CredentialDefinitionId::new(cred_def_id).unwrap(); + + let rev_reg_id = + make_revocation_registry_id(issuer_did, &cred_def_id, tag, RegistryType::CL_ACCUM)?; + + let (rev_reg_def, rev_reg_def_priv) = create_revocation_registry_def( + &cred_def, + cred_def_id, + tag, + RegistryType::CL_ACCUM, + max_creds, + &mut tails_writer, + )?; + + let timestamp = OffsetDateTime::now_utc().unix_timestamp() as u64; + + let rev_status_list = create_revocation_status_list( + &cred_def, + rev_reg_id.clone(), + &rev_reg_def, + &rev_reg_def_priv, + true, + Some(timestamp), + )?; + + let opt_rev_reg: Option = (&rev_status_list).try_into().unwrap(); + let rev_reg = opt_rev_reg + .expect("creating a RevocationStatusList always generates a CryptoRevocationRegistry"); + + let rev_reg_info = RevocationRegistryInfo { + id: rev_reg_id.clone(), + curr_id: 0, + used_ids: HashSet::new(), + }; + + let str_rev_reg_info = serde_json::to_string(&rev_reg_info)?; + let record = Record::builder() + .name(rev_reg_id.0.clone()) + .category(CATEGORY_REV_REG_INFO.to_string()) + .value(str_rev_reg_info) + .build(); + wallet.add_record(record).await?; + + let mut rev_reg_def = serde_json::to_value(&rev_reg_def)?; + rev_reg_def + .as_object_mut() + .unwrap() + .insert("id".to_owned(), rev_reg_id.0.clone().into()); + rev_reg_def + .as_object_mut() + .unwrap() + .insert("ver".to_owned(), "1.0".into()); + rev_reg_def["value"] + .as_object_mut() + .unwrap() + .insert("issuanceType".to_string(), "ISSUANCE_BY_DEFAULT".into()); + + let str_rev_reg_def = serde_json::to_string(&rev_reg_def)?; + let record = Record::builder() + .name(rev_reg_id.0.clone()) + .category(CATEGORY_REV_REG_DEF.to_string()) + .value(str_rev_reg_def.clone()) + .build(); + wallet.add_record(record).await?; + + let str_rev_reg_def_priv = serde_json::to_string(&rev_reg_def_priv)?; + let record = Record::builder() + .name(rev_reg_id.0.clone()) + .category(CATEGORY_REV_REG_DEF_PRIV.to_string()) + .value(str_rev_reg_def_priv) + .build(); + wallet.add_record(record).await?; + + let rev_reg = RevocationRegistry { value: rev_reg }; + let str_rev_reg = serde_json::to_string(&rev_reg)?; + let record = Record::builder() + .name(rev_reg_id.0.clone()) + .category(CATEGORY_REV_REG.to_string()) + .value(str_rev_reg.clone()) + .build(); + wallet.add_record(record).await?; + + Ok((rev_reg_id.0, str_rev_reg_def, str_rev_reg)) + } + + async fn issuer_create_and_store_credential_def( + &self, + wallet: &impl BaseWallet, + issuer_did: &str, + schema_id: &str, + schema_json: &str, + tag: &str, + signature_type: Option<&str>, + config_json: &str, + ) -> VcxCoreResult<(String, String)> { + let mut value: Value = serde_json::from_str(schema_json)?; + value + .as_object_mut() + .map(|v| v.insert("issuerId".to_owned(), json!(issuer_did))); + let schema_seq_no = value.get("seqNo").and_then(|v| v.as_u64()); + + let schema = serde_json::from_value(value)?; + + let sig_type = signature_type + .map(serde_json::from_str) + .unwrap_or(Ok(SignatureType::CL))?; + let config = serde_json::from_str(config_json)?; + + let cred_def_id = + make_credential_definition_id(issuer_did, schema_id, schema_seq_no, tag, sig_type); + + // If cred def already exists, return it + if let Ok(cred_def) = self + .get_wallet_record_value(wallet, CATEGORY_CRED_DEF, &cred_def_id.0) + .await + { + return Ok((cred_def_id.0, cred_def)); + } + + // Otherwise, create cred def + let (cred_def, cred_def_priv, cred_key_correctness_proof) = + anoncreds::issuer::create_credential_definition( + // Schema ID must be just the schema seq no for some reason + SchemaId::new_unchecked(schema_seq_no.unwrap().to_string()), + &schema, + schema.issuer_id.clone(), + tag, + sig_type, + config, + )?; + + let mut cred_def = serde_json::to_value(&cred_def)?; + cred_def + .as_object_mut() + .map(|v| v.insert("id".to_owned(), cred_def_id.to_string().into())); + + let str_cred_def = serde_json::to_string(&cred_def)?; + let record = Record::builder() + .name(cred_def_id.0.clone()) + .category(CATEGORY_CRED_DEF.to_string()) + .value(str_cred_def.clone()) + .build(); + wallet.add_record(record).await?; + + let str_cred_def_priv = serde_json::to_string(&cred_def_priv)?; + let record = Record::builder() + .name(cred_def_id.0.clone()) + .category(CATEGORY_CRED_DEF_PRIV.to_string()) + .value(str_cred_def_priv) + .build(); + wallet.add_record(record).await?; + + let str_cred_key_proof = serde_json::to_string(&cred_key_correctness_proof)?; + let record = Record::builder() + .name(cred_def_id.0.clone()) + .category(CATEGORY_CRED_KEY_CORRECTNESS_PROOF.to_string()) + .value(str_cred_key_proof) + .build(); + wallet.add_record(record).await?; + + let record = Record::builder() + .name(schema_id.to_string()) + .category(CATEGORY_CRED_SCHEMA.to_string()) + .value(schema_json.into()) + .build(); + let store_schema_res = wallet.add_record(record).await; + + if let Err(e) = store_schema_res { + warn!("Storing schema {schema_json} failed - {e}. It's possible it is already stored.") + } + + let record = Record::builder() + .name(cred_def_id.0.clone()) + .category(CATEGORY_CRED_MAP_SCHEMA_ID.to_string()) + // .value(schema_id.to_string()) + .value(json!({"schemaId": schema_id}).to_string()) + .build(); + wallet.add_record(record).await?; + + // Return the ID and the cred def + Ok((cred_def_id.0.to_owned(), str_cred_def)) + } + + async fn issuer_create_credential_offer( + &self, + wallet: &impl BaseWallet, + cred_def_id: &str, + ) -> VcxCoreResult { + let correctness_proof = self + .get_wallet_record_value(wallet, CATEGORY_CRED_KEY_CORRECTNESS_PROOF, cred_def_id) + .await?; + + let schema_id_value = self + .get_wallet_record_value::(wallet, CATEGORY_CRED_MAP_SCHEMA_ID, cred_def_id) + .await?; + + let offer = anoncreds::issuer::create_credential_offer( + SchemaId::new(schema_id_value["schemaId"].as_str().unwrap()).unwrap(), + CredentialDefinitionId::new(cred_def_id).unwrap(), + &correctness_proof, + )?; + + serde_json::to_string(&offer).map_err(From::from) + } + + async fn issuer_create_credential( + &self, + wallet: &impl BaseWallet, + cred_offer_json: &str, + cred_req_json: &str, + cred_values_json: &str, + rev_reg_id: Option, + tails_dir: Option, + ) -> VcxCoreResult<(String, Option, Option)> { + let cred_offer: CredentialOffer = serde_json::from_str(cred_offer_json)?; + let cred_request = serde_json::from_str(cred_req_json)?; + let cred_values = serde_json::from_str(cred_values_json)?; + + let cred_def_id = &cred_offer.cred_def_id.0; + + let cred_def = self + .get_wallet_record_value(wallet, CATEGORY_CRED_DEF, cred_def_id) + .await?; + + let cred_def_private = self + .get_wallet_record_value(wallet, CATEGORY_CRED_DEF_PRIV, cred_def_id) + .await?; + + let mut revocation_config_parts = match (tails_dir, &rev_reg_id) { + (Some(tails_dir), Some(rev_reg_def_id)) => { + let rev_reg_def: RevocationRegistryDefinition = self + .get_wallet_record_value(wallet, CATEGORY_REV_REG_DEF, rev_reg_def_id) + .await?; + + let rev_reg_def_priv = self + .get_wallet_record_value(wallet, CATEGORY_REV_REG_DEF_PRIV, rev_reg_def_id) + .await?; + + let rev_reg: RevocationRegistry = self + .get_wallet_record_value(wallet, CATEGORY_REV_REG, rev_reg_def_id) + .await?; + let rev_reg_info: RevocationRegistryInfo = self + .get_wallet_record_value(wallet, CATEGORY_REV_REG_INFO, rev_reg_def_id) + .await?; + + let rev_reg_def_id = RevocationRegistryDefinitionId::new(rev_reg_def_id).unwrap(); + + Some(( + rev_reg_def, + rev_reg_def_id, + rev_reg_def_priv, + rev_reg, + rev_reg_info, + tails_dir, + )) + } + (None, None) => None, + (tails_dir, rev_reg_def_id) => { + warn!( + "Missing revocation config params: tails_dir: {tails_dir:?} - \ + {rev_reg_def_id:?}; Issuing non revokable credential" + ); + None + } + }; + + let rev_status_list = match &revocation_config_parts { + Some((rev_reg_def, rev_reg_def_id, rev_reg_def_priv, _, _, _)) => { + Some(create_revocation_status_list( + &cred_def, + rev_reg_def_id.clone(), + rev_reg_def, + rev_reg_def_priv, + true, + None, + )?) + } + _ => None, + }; + + let revocation_config = match (&mut revocation_config_parts, &rev_status_list) { + ( + Some((rev_reg_def, _, rev_reg_def_priv, _, rev_reg_info, _)), + Some(rev_status_list), + ) => { + rev_reg_info.curr_id += 1; + + if rev_reg_info.curr_id > rev_reg_def.value.max_cred_num { + return Err(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::ActionNotSupported, + "The revocation registry is full", + )); + } + + rev_reg_info.used_ids.insert(rev_reg_info.curr_id); + + let revocation_config = CredentialRevocationConfig { + reg_def: rev_reg_def, + reg_def_private: rev_reg_def_priv, + registry_idx: rev_reg_info.curr_id, + status_list: rev_status_list, + }; + + Some(revocation_config) + } + _ => None, + }; + + let cred = anoncreds::issuer::create_credential( + &cred_def, + &cred_def_private, + &cred_offer, + &cred_request, + cred_values, + revocation_config, + )?; + + let rev_reg = cred.rev_reg.as_ref(); + + let cred_rev_id = + if let (Some(rev_reg_id), Some(rev_reg), Some((_, _, _, _, rev_reg_info, _))) = + (rev_reg_id, rev_reg, revocation_config_parts) + { + let cred_rev_id = rev_reg_info.curr_id.to_string(); + let str_rev_reg_info = serde_json::to_string(&rev_reg_info)?; + + let rev_reg = RevocationRegistry { + value: rev_reg.clone(), + }; + let str_rev_reg = serde_json::to_string(&rev_reg)?; + wallet + .update_record_value(CATEGORY_REV_REG, &rev_reg_id, &str_rev_reg) + .await?; + + wallet + .update_record_value(CATEGORY_REV_REG_INFO, &rev_reg_id, &str_rev_reg_info) + .await?; + + Some(cred_rev_id) + } else { + None + }; + + let str_cred = serde_json::to_string(&cred)?; + + let str_rev_reg_delta = rev_reg + .map(|rev_reg| { + let rev_reg_delta = Into::::into(rev_reg); + serde_json::to_string(&rev_reg_delta) + }) + .transpose()?; + + Ok((str_cred, cred_rev_id, str_rev_reg_delta)) + } + + #[allow(clippy::too_many_arguments)] + async fn prover_create_proof( + &self, + wallet: &impl BaseWallet, + proof_req_json: &str, + requested_credentials_json: &str, + master_secret_id: &str, + schemas_json: &str, + credential_defs_json: &str, + revoc_states_json: Option<&str>, + ) -> VcxCoreResult { + let pres_req: PresentationRequest = serde_json::from_str(proof_req_json)?; + + let requested_credentials: Value = serde_json::from_str(requested_credentials_json)?; + let requested_attributes = (&requested_credentials).try_get("requested_attributes")?; + + let requested_predicates = (&requested_credentials).try_get("requested_predicates")?; + let self_attested_attributes = requested_credentials.get("self_attested_attributes"); + + let rev_states: Option = if let Some(revoc_states_json) = revoc_states_json { + Some(serde_json::from_str(revoc_states_json)?) + } else { + None + }; + + let mut schemas_val: HashMap = serde_json::from_str(schemas_json)?; + let mut schemas: HashMap = HashMap::new(); + for (schema_id, schema_json) in schemas_val.iter_mut() { + schema_json.as_object_mut().map(|v| { + v.insert( + "issuerId".to_owned(), + schema_id.to_string().split(':').next().into(), + ) + }); + let schema = serde_json::from_value(schema_json.clone())?; + schemas.insert(schema_id.clone(), schema); + } + let mut cred_defs_val: HashMap = + serde_json::from_str(credential_defs_json)?; + let mut cred_defs: HashMap = HashMap::new(); + for (cred_def_id, cred_def_json) in cred_defs_val.iter_mut() { + cred_def_json.as_object_mut().map(|v| { + v.insert( + "issuerId".to_owned(), + cred_def_id.to_string().split(':').next().into(), + ) + }); + let cred_def = serde_json::from_value(cred_def_json.clone())?; + cred_defs.insert(cred_def_id.clone(), cred_def); + } + + let mut present_credentials: PresentCredentials = PresentCredentials::default(); + + let mut proof_details_by_cred_id: HashMap< + String, + ( + Credential, + Option, + Option, + Vec<(String, bool)>, + Vec, + ), + > = HashMap::new(); + + // add cred data and referent details for each requested attribute + for (reft, detail) in requested_attributes.try_as_object()?.iter() { + let _cred_id = detail.try_get("cred_id")?; + let cred_id = _cred_id.try_as_str()?; + + let revealed = detail.try_get("revealed")?.try_as_bool()?; + + if let Some((_, _, _, req_attr_refts_revealed, _)) = + proof_details_by_cred_id.get_mut(cred_id) + { + // mapping made for this credential already, add reft and its revealed status + req_attr_refts_revealed.push((reft.to_string(), revealed)); + } else { + let credential = self._get_credential(wallet, cred_id).await?; + + let (timestamp, rev_state) = + get_rev_state(cred_id, &credential, detail, rev_states.as_ref())?; + + proof_details_by_cred_id.insert( + cred_id.to_string(), + ( + credential, + timestamp, + rev_state, + vec![(reft.to_string(), revealed)], + vec![], + ), + ); + } + } + + // add cred data and referent details for each requested predicate + for (reft, detail) in requested_predicates.try_as_object()?.iter() { + let _cred_id = detail.try_get("cred_id")?; + let cred_id = _cred_id.try_as_str()?; + + if let Some((_, _, _, _, req_preds_refts)) = proof_details_by_cred_id.get_mut(cred_id) { + // mapping made for this credential already, add reft + req_preds_refts.push(reft.to_string()); + } else { + let credential = self._get_credential(wallet, cred_id).await?; + + let (timestamp, rev_state) = + get_rev_state(cred_id, &credential, detail, rev_states.as_ref())?; + + proof_details_by_cred_id.insert( + cred_id.to_string(), + ( + credential, + timestamp, + rev_state, + vec![], + vec![reft.to_string()], + ), + ); + } + } + + // add all accumulated requested attributes and requested predicates to credx + // [PresentCredential] object + for ( + _cred_id, + (credential, timestamp, rev_state, req_attr_refts_revealed, req_preds_refts), + ) in proof_details_by_cred_id.iter() + { + let mut add_cred = + present_credentials.add_credential(credential, *timestamp, rev_state.as_ref()); + + for (referent, revealed) in req_attr_refts_revealed { + add_cred.add_requested_attribute(referent, *revealed); + } + + for referent in req_preds_refts { + add_cred.add_requested_predicate(referent); + } + } + + // create self_attested by iterating thru self_attested_value + let self_attested = if let Some(self_attested_value) = self_attested_attributes { + let mut self_attested_map: HashMap = HashMap::new(); + let self_attested_obj = self_attested_value.try_as_object()?.clone(); + let self_attested_iter = self_attested_obj.iter(); + for (k, v) in self_attested_iter { + self_attested_map.insert(k.to_string(), v.try_as_str()?.to_string()); + } + + if self_attested_map.is_empty() { + None + } else { + Some(self_attested_map) + } + } else { + None + }; + + let link_secret = self.get_link_secret(wallet, master_secret_id).await?; + + let presentation = anoncreds::prover::create_presentation( + &pres_req, + present_credentials, + self_attested, + &link_secret, + &schemas, + &cred_defs, + )?; + + Ok(serde_json::to_string(&presentation)?) + } + + async fn prover_get_credential( + &self, + wallet: &impl BaseWallet, + cred_id: &str, + ) -> VcxCoreResult { + let cred = self._get_credential(wallet, cred_id).await?; + let cred_info = _make_cred_info(cred_id, &cred)?; + Ok(serde_json::to_string(&cred_info)?) + } + + async fn prover_get_credentials( + &self, + wallet: &impl BaseWallet, + filter_json: Option<&str>, + ) -> VcxCoreResult { + // filter_json should map to WQL query directly + // TODO - future - may wish to validate the filter_json for more accurate error reporting + + let creds_wql = filter_json.map_or("{}", |x| x); + let creds = Self::_get_credentials(wallet, creds_wql).await?; + + let cred_info_list: VcxCoreResult> = creds + .iter() + .map(|(credential_id, cred)| _make_cred_info(credential_id, cred)) + .collect(); + + let cred_info_list = cred_info_list?; + + Ok(serde_json::to_string(&cred_info_list)?) + } + + async fn prover_get_credentials_for_proof_req( + &self, + wallet: &impl BaseWallet, + proof_request_json: &str, + ) -> VcxCoreResult { + let proof_req_v: Value = serde_json::from_str(proof_request_json).map_err(|e| { + AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::InvalidProofRequest, e) + })?; + + let requested_attributes = proof_req_v.get("requested_attributes"); + let requested_attributes = if let Some(requested_attributes) = requested_attributes { + Some(requested_attributes.try_as_object()?.clone()) + } else { + None + }; + let requested_predicates = proof_req_v.get("requested_predicates"); + let requested_predicates = if let Some(requested_predicates) = requested_predicates { + Some(requested_predicates.try_as_object()?.clone()) + } else { + None + }; + + // handle special case of "empty because json is bad" vs "empty because no attributes + // sepected" + if requested_attributes.is_none() && requested_predicates.is_none() { + return Err(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::InvalidAttributesStructure, + "Invalid Json Parsing of Requested Attributes Retrieved From Libindy", + )); + } + + let mut referents: HashSet = HashSet::new(); + if let Some(requested_attributes) = &requested_attributes { + requested_attributes.iter().for_each(|(k, _)| { + referents.insert(k.to_string()); + }) + }; + if let Some(requested_predicates) = &requested_predicates { + requested_predicates.iter().for_each(|(k, _)| { + referents.insert(k.to_string()); + }); + } + + let mut cred_by_attr: Value = json!({}); + + for reft in referents { + let requested_val = requested_attributes + .as_ref() + .and_then(|req_attrs| req_attrs.get(&reft)) + .or_else(|| { + requested_predicates + .as_ref() + .and_then(|req_preds| req_preds.get(&reft)) + }) + .ok_or(AriesVcxCoreError::from_msg( + // should not happen + AriesVcxCoreErrorKind::InvalidState, + format!("Unknown referent: {}", reft), + ))?; + + let name = requested_val.get("name"); + let names = requested_val.get("names").and_then(|v| v.as_array()); + + let attr_names = match (name, names) { + (Some(name), None) => vec![_normalize_attr_name(name.try_as_str()?)], + (None, Some(names)) => names + .iter() + .map(|v| v.try_as_str().map(_normalize_attr_name)) + .collect::>()?, + _ => Err(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::InvalidInput, + "exactly one of 'name' or 'names' must be present", + ))?, + }; + + let non_revoked = requested_val.get("non_revoked"); // note that aca-py askar fetches from proof_req json + let restrictions = requested_val.get("restrictions"); + + let credx_creds = self + ._get_credentials_for_proof_req_for_attr_name(wallet, restrictions, attr_names) + .await?; + + let mut credentials_json = vec![]; + + for (cred_id, credx_cred) in credx_creds { + credentials_json.push(json!({ + "cred_info": _make_cred_info(&cred_id, &credx_cred)?, + "interval": non_revoked + })) + } + + cred_by_attr[ATTRS][reft] = Value::Array(credentials_json); + } + + Ok(serde_json::to_string(&cred_by_attr)?) + } + + async fn prover_create_credential_req( + &self, + wallet: &impl BaseWallet, + prover_did: &str, + cred_offer_json: &str, + cred_def_json: &str, + master_secret_id: &str, + ) -> VcxCoreResult<(String, String)> { + let prover_did = Did::parse(prover_did.to_owned()) + .map_err(|e| AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::InvalidDid, e))?; + let mut cred_def_json: Value = serde_json::from_str(cred_def_json)?; + let cred_def_id = cred_def_json["id"].as_str().unwrap().to_string(); + cred_def_json.as_object_mut().unwrap().insert( + "issuerId".to_owned(), + cred_def_id.split(':').next().unwrap().into(), + ); + let cred_def: CredentialDefinition = serde_json::from_str(&cred_def_json.to_string())?; + let credential_offer: CredentialOffer = serde_json::from_str(cred_offer_json)?; + let link_secret = self.get_link_secret(wallet, master_secret_id).await?; + + let (cred_req, cred_req_metadata) = anoncreds::prover::create_credential_request( + None, + Some(prover_did.did()), + &cred_def, + &link_secret, + master_secret_id, + &credential_offer, + )?; + + Ok(( + serde_json::to_string(&cred_req)?, + serde_json::to_string(&cred_req_metadata)?, + )) + } + + async fn create_revocation_state( + &self, + tails_dir: &str, + rev_reg_def_json: &str, + rev_reg_delta_json: &str, + timestamp: u64, + cred_rev_id: &str, + ) -> VcxCoreResult { + let mut rev_reg_def_json: Value = serde_json::from_str(rev_reg_def_json)?; + let rev_reg_def_id = rev_reg_def_json["id"].as_str().unwrap().to_string(); + let cred_def_id = rev_reg_def_json["credDefId"].as_str().unwrap(); + + let (_cred_def_method, issuer_did, _signature_type, _schema_num, _tag) = + cred_def_parts(cred_def_id).ok_or(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::InvalidSchema, + format!("Could not process cred_def_id {cred_def_id} as parts."), + ))?; + + rev_reg_def_json + .as_object_mut() + .unwrap() + .insert("issuerId".to_owned(), issuer_did.did().into()); + let revoc_reg_def: RevocationRegistryDefinition = + serde_json::from_str(&rev_reg_def_json.to_string())?; + let tails_file_hash = revoc_reg_def.value.tails_hash.as_str(); + + let mut tails_file_path = std::path::PathBuf::new(); + tails_file_path.push(tails_dir); + tails_file_path.push(tails_file_hash); + + let tails_path = tails_file_path.to_str().ok_or_else(|| { + AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::InvalidOption, + "tails file is not an unicode string", + ) + })?; + + let delta: RevRegDeltaParts = serde_json::from_str(rev_reg_delta_json)?; + + let RevRegDeltaSubParts { accum, revoked, .. } = delta.value; + + let issuer_id = IssuerId::new(issuer_did.did()).unwrap(); + let max_cred_num = rev_reg_def_json["value"]["maxCredNum"].as_u64().unwrap(); + let mut revocation_list = bitvec!(0; max_cred_num as usize); + revoked.into_iter().for_each(|id| { + revocation_list + .get_mut(id as usize) + .map(|mut b| *b = true) + .unwrap_or_default() + }); + let registry = CryptoRevocationRegistry { accum }; + + // let rev_status_list = create_revocation_status_list( + // &cred_def, + // RevocationRegistryDefinitionId::new_unchecked(issuer_id), + // &revoc_reg_def, + // rev_reg_priv, // No way to construct this from revocation registry currently + // true, + // Some(timestamp), + // ); + + // TODO: Made public, should find a better way + let rev_status_list = RevocationStatusList::new( + Some(&rev_reg_def_id), + issuer_id, + revocation_list, + Some(registry), + Some(timestamp), + )?; + + let rev_reg_idx: u32 = cred_rev_id + .parse() + .map_err(|e| AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::ParsingError, e))?; + + let rev_state = anoncreds::prover::create_or_update_revocation_state( + tails_path, + &revoc_reg_def, + &rev_status_list, + rev_reg_idx, + None, + None, + )?; + + Ok(serde_json::to_string(&rev_state)?) + } + + async fn prover_store_credential( + &self, + wallet: &impl BaseWallet, + cred_id: Option<&str>, + cred_req_metadata_json: &str, + cred_json: &str, + cred_def_json: &str, + rev_reg_def_json: Option<&str>, + ) -> VcxCoreResult { + let mut credential: Credential = serde_json::from_str(cred_json)?; + let cred_request_metadata: CredentialRequestMetadata = + serde_json::from_str(cred_req_metadata_json)?; + let link_secret_id = &cred_request_metadata.link_secret_name; + let link_secret = self.get_link_secret(wallet, link_secret_id).await?; + let mut cred_def_json: Value = serde_json::from_str(cred_def_json)?; + let cred_def_id = cred_def_json["id"].as_str().unwrap().to_string(); + let issuer_id = cred_def_id.split(':').next().unwrap().to_string(); + cred_def_json + .as_object_mut() + .unwrap() + .insert("issuerId".to_owned(), issuer_id.clone().into()); + let cred_def: CredentialDefinition = serde_json::from_str(&cred_def_json.to_string())?; + let rev_reg_def: Option = + if let Some(rev_reg_def_json) = rev_reg_def_json { + let mut rev_reg_def_json: Value = serde_json::from_str(rev_reg_def_json)?; + rev_reg_def_json + .as_object_mut() + .unwrap() + .insert("issuerId".to_owned(), issuer_id.into()); + serde_json::from_str(&rev_reg_def_json.to_string())? + } else { + None + }; + + anoncreds::prover::process_credential( + &mut credential, + &cred_request_metadata, + &link_secret, + &cred_def, + rev_reg_def.as_ref(), + )?; + + let schema_id = &credential.schema_id; + + let cred_def_id = &credential.cred_def_id; + let (_cred_def_method, issuer_did, _signature_type, _schema_num, _tag) = + cred_def_parts(cred_def_id.0.as_str()).ok_or(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::InvalidSchema, + "Could not process credential.cred_def_id as parts.", + ))?; + + let (_schema_method, schema_issuer_did, schema_name, schema_version) = + schema_parts(schema_id.0.as_str()).ok_or(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::InvalidSchema, + format!("Could not process credential.schema_id {schema_id} as parts."), + ))?; + + let mut tags = json!({ + "schema_id": schema_id.0, + "schema_issuer_did": schema_issuer_did.did(), + "schema_name": schema_name, + "schema_version": schema_version, + "issuer_did": issuer_did.did(), + "cred_def_id": cred_def_id.0 + }); + + if let Some(rev_reg_id) = &credential.rev_reg_id { + tags["rev_reg_id"] = serde_json::Value::String(rev_reg_id.0.to_string()) + } + + for (raw_attr_name, attr_value) in credential.values.0.iter() { + let attr_name = _normalize_attr_name(raw_attr_name); + // add attribute name and raw value pair + let value_tag_name = _format_attribute_as_value_tag_name(&attr_name); + tags[value_tag_name] = Value::String(attr_value.raw.to_string()); + + // add attribute name and marker (used for checking existent) + let marker_tag_name = _format_attribute_as_marker_tag_name(&attr_name); + tags[marker_tag_name] = Value::String("1".to_string()); + } + + let credential_id = cred_id.map_or(Uuid::new_v4().to_string(), String::from); + + let record_value = serde_json::to_string(&credential)?; + let tags = serde_json::from_value(tags.clone())?; + + let record = Record::builder() + .name(credential_id.clone()) + .category(CATEGORY_CREDENTIAL.into()) + .value(record_value) + .tags(tags) + .build(); + + wallet.add_record(record).await?; + + Ok(credential_id) + } + + async fn prover_delete_credential( + &self, + wallet: &impl BaseWallet, + cred_id: &str, + ) -> VcxCoreResult<()> { + wallet.delete_record(CATEGORY_CREDENTIAL, cred_id).await + } + + async fn prover_create_link_secret( + &self, + wallet: &impl BaseWallet, + link_secret_id: &str, + ) -> VcxCoreResult { + let existing_record = wallet + .get_record(CATEGORY_LINK_SECRET, link_secret_id) + .await + .ok(); // ignore error, as we only care about whether it exists or not + + if existing_record.is_some() { + return Err(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::DuplicationMasterSecret, + format!( + "Master secret id: {} already exists in wallet.", + link_secret_id + ), + )); + } + + let secret = anoncreds::prover::create_link_secret()?; + let ms_decimal = TryInto::::try_into(secret).map_err(|err| { + AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::UrsaError, + format!("Failed convert BigNumber to decimal string: {}", err), + ) + })?; + + let record = Record::builder() + .name(link_secret_id.into()) + .category(CATEGORY_LINK_SECRET.into()) + .value(ms_decimal) + .build(); + wallet.add_record(record).await?; + + return Ok(link_secret_id.to_string()); + } + + async fn issuer_create_schema( + &self, + issuer_did: &str, + name: &str, + version: &str, + attrs: &str, + ) -> VcxCoreResult<(String, String)> { + let attr_names = serde_json::from_str(attrs)?; + + let schema = anoncreds::issuer::create_schema( + name, + version, + IssuerId::new(issuer_did).unwrap(), + attr_names, + )?; + let mut schema_json = serde_json::to_value(schema)?; + let schema_id = make_schema_id(issuer_did, name, version); + schema_json["id"] = schema_id.to_string().into(); + + Ok((schema_id.to_string(), schema_json.to_string())) + } + + async fn revoke_credential_local( + &self, + wallet: &impl BaseWallet, + _tails_dir: &str, + rev_reg_id: &str, + cred_rev_id: &str, + ledger_rev_reg_delta_json: &str, + ) -> VcxCoreResult<()> { + let rev_reg_def: RevocationRegistryDefinition = self + .get_wallet_record_value(wallet, CATEGORY_REV_REG_DEF, rev_reg_id) + .await?; + + let last_rev_reg_delta_stored = self.get_rev_reg_delta(wallet, rev_reg_id).await?; + let last_rev_reg_delta = last_rev_reg_delta_stored + .clone() + .unwrap_or(ledger_rev_reg_delta_json.to_string()); + + let ledger_rev_reg_delta: Option = + serde_json::from_str(ledger_rev_reg_delta_json)?; + let prev_accum = ledger_rev_reg_delta.map(|rev_reg| rev_reg.value.accum); + + let current_time = OffsetDateTime::now_utc().unix_timestamp() as u64; + let rev_reg_delta = serde_json::from_str::(&last_rev_reg_delta)?; + let rev_status_list = from_revocation_registry_delta_to_revocation_status_list( + &rev_reg_delta.value, + &rev_reg_def, + &RevocationRegistryDefinitionId::new(rev_reg_id).unwrap(), + Some(current_time), + true, + )?; + + let cred_def = self + .get_wallet_record_value( + wallet, + CATEGORY_CRED_DEF, + &rev_reg_def.cred_def_id.to_string(), + ) + .await?; + + let rev_reg_def_priv = self + .get_wallet_record_value(wallet, CATEGORY_REV_REG_DEF_PRIV, rev_reg_id) + .await?; + + let cred_rev_id = cred_rev_id.parse().map_err(|e| { + AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::InvalidInput, + format!("Invalid cred_rev_id {cred_rev_id} - {e}"), + ) + })?; + + let updated_rev_status_list = anoncreds::issuer::update_revocation_status_list( + &cred_def, + &rev_reg_def, + &rev_reg_def_priv, + &rev_status_list, + None, + Some(vec![cred_rev_id].into_iter().collect()), + None, + )?; + + let updated_revocation_registry_delta = + from_revocation_status_list_to_revocation_registry_delta( + &updated_rev_status_list, + prev_accum, + )?; + let updated_revocation_registry_delta_str = + serde_json::to_string(&updated_revocation_registry_delta)?; + + if last_rev_reg_delta_stored.is_some() { + wallet + .update_record_value( + CATEGORY_REV_REG_DELTA, + rev_reg_id, + &updated_revocation_registry_delta_str, + ) + .await?; + } else { + let record = Record::builder() + .name(rev_reg_id.into()) + .category(CATEGORY_REV_REG_DELTA.into()) + .value(updated_revocation_registry_delta_str) + .build(); + wallet.add_record(record).await?; + }; + + Ok(()) + } + + async fn get_rev_reg_delta( + &self, + wallet: &impl BaseWallet, + rev_reg_id: &str, + ) -> VcxCoreResult> { + let res_rev_reg_delta = self + .get_wallet_record_value::(wallet, CATEGORY_REV_REG_DELTA, rev_reg_id) + .await; + + if let Err(err) = &res_rev_reg_delta { + warn!( + "get_rev_reg_delta >> Unable to get rev_reg_delta cache for rev_reg_id: {}, \ + error: {}", + rev_reg_id, err + ); + } + + let res_rev_reg_delta = res_rev_reg_delta + .ok() + .as_ref() + .map(serde_json::to_string) + .transpose(); + + if let Err(err) = &res_rev_reg_delta { + warn!( + "get_rev_reg_delta >> Unable to deserialize rev_reg_delta cache for rev_reg_id: \ + {}, error: {}", + rev_reg_id, err + ); + } + + Ok(res_rev_reg_delta.ok().flatten()) + } + + async fn clear_rev_reg_delta( + &self, + wallet: &impl BaseWallet, + rev_reg_id: &str, + ) -> VcxCoreResult<()> { + if self.get_rev_reg_delta(wallet, rev_reg_id).await?.is_some() { + wallet + .delete_record(CATEGORY_REV_REG_DELTA, rev_reg_id) + .await?; + } + + Ok(()) + } + + async fn generate_nonce(&self) -> VcxCoreResult { + Ok(anoncreds::verifier::generate_nonce()?.to_string()) + } +} + +fn get_rev_state( + cred_id: &str, + credential: &Credential, + detail: &Value, + rev_states: Option<&Value>, +) -> VcxCoreResult<(Option, Option)> { + let timestamp = detail + .get("timestamp") + .and_then(|timestamp| timestamp.as_u64()); + let cred_rev_reg_id = credential.rev_reg_id.as_ref().map(|id| id.0.to_string()); + let rev_state = if let (Some(timestamp), Some(cred_rev_reg_id)) = (timestamp, cred_rev_reg_id) { + let rev_state = rev_states + .as_ref() + .and_then(|_rev_states| _rev_states.get(cred_rev_reg_id.to_string())); + let rev_state = rev_state.ok_or(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::InvalidJson, + format!( + "No revocation states provided for credential '{}' with rev_reg_id '{}'", + cred_id, cred_rev_reg_id + ), + ))?; + + let rev_state = rev_state + .get(timestamp.to_string()) + .ok_or(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::InvalidJson, + format!( + "No revocation states provided for credential '{}' with rev_reg_id '{}' at \ + timestamp '{}'", + cred_id, cred_rev_reg_id, timestamp + ), + ))?; + + let rev_state: CredentialRevocationState = serde_json::from_value(rev_state.clone())?; + Some(rev_state) + } else { + None + }; + + Ok((timestamp, rev_state)) +} + +fn _normalize_attr_name(name: &str) -> String { + // "name": string, // attribute name, (case insensitive and ignore spaces) + name.replace(' ', "").to_lowercase() +} + +fn _make_cred_info(credential_id: &str, cred: &Credential) -> VcxCoreResult { + let cred_sig = serde_json::to_value(&cred.signature)?; + + let rev_info = cred_sig.get("r_credential"); + + let schema_id = &cred.schema_id.0; + let cred_def_id = &cred.cred_def_id.0; + let rev_reg_id = cred.rev_reg_id.as_ref().map(|x| x.0.to_string()); + let cred_rev_id = rev_info.and_then(|x| x.get("i")).and_then(|i| { + i.as_str() + .map(|str_i| str_i.to_string()) + .or(i.as_i64().map(|int_i| int_i.to_string())) + }); + + let mut attrs = json!({}); + for (x, y) in cred.values.0.iter() { + attrs[x] = Value::String(y.raw.to_string()); + } + + let val = json!({ + "referent": credential_id, + "schema_id": schema_id, + "cred_def_id": cred_def_id, + "rev_reg_id": rev_reg_id, + "cred_rev_id": cred_rev_id, + "attrs": attrs + }); + + Ok(val) +} + +fn _format_attribute_as_value_tag_name(attribute_name: &str) -> String { + format!("attr::{attribute_name}::value") +} + +fn _format_attribute_as_marker_tag_name(attribute_name: &str) -> String { + format!("attr::{attribute_name}::marker") +} + +pub fn make_schema_id(did: &str, name: &str, version: &str) -> SchemaId { + let prefix = Did::parse(did.to_owned()) + .ok() + .and_then(|did| did.method().map(|method| format!("schema:{}:", method))) + .unwrap_or_default(); + + let id = format!("{}{}:2:{}:{}", prefix, did, name, version); + SchemaId::new(id).unwrap() +} + +pub fn make_credential_definition_id( + origin_did: &str, + schema_id: &str, + schema_seq_no: Option, + tag: &str, + signature_type: SignatureType, +) -> CredentialDefinitionId { + let signature_type = match signature_type { + SignatureType::CL => CL_SIGNATURE_TYPE, + }; + + let tag = if tag.is_empty() { + String::new() + } else { + format!(":{}", tag) + }; + + let prefix = Did::parse(origin_did.to_owned()) + .ok() + .and_then(|did| did.method().map(|method| format!("creddef:{}:", method))) + .unwrap_or_default(); + + let schema_infix_id = schema_seq_no + .map(|n| n.to_string()) + .unwrap_or(schema_id.to_string()); + + let id = format!( + "{}{}:3:{}:{}{}", + prefix, + origin_did, + signature_type, + schema_infix_id, + tag // prefix, origin_did, signature_type, schema_id, tag + ); + + CredentialDefinitionId::new(id).unwrap() +} + +fn make_revocation_registry_id( + origin_did: &str, + cred_def_id: &CredentialDefinitionId, + tag: &str, + rev_reg_type: RegistryType, +) -> VcxCoreResult { + // Must use unchecked as anoncreds doesn't expose validation error + Ok(RevocationRegistryDefinitionId::new(format!( + "{}{}:4:{}:{}:{}", + Did::parse(origin_did.to_owned())? + .method() + .map_or(Default::default(), |method| format!("revreg:{}:", method)), + origin_did, + cred_def_id.0, + match rev_reg_type { + RegistryType::CL_ACCUM => CL_ACCUM, + }, + tag + )) + .unwrap()) +} + +// V4SGRU86Z58d6TV7PBUe6f:4:V4SGRU86Z58d6TV7PBUe6f:3:CL:771:1:CL_ACCUM:tag1 +pub fn rev_reg_def_parts(_id: &str) -> Option<(Option<&str>, Did, String, SchemaId, String)> { + todo!() +} + +pub fn schema_parts(id: &str) -> Option<(Option<&str>, Did, String, String)> { + let parts = id.split_terminator(':').collect::>(); + + if parts.len() == 1 { + // 1 + return None; + } + + if parts.len() == 4 { + // NcYxiDXkpYi6ov5FcYDi1e:2:gvt:1.0 + let did = parts[0].to_string(); + let Ok(did) = Did::parse(did) else { + return None; + }; + let name = parts[2].to_string(); + let version = parts[3].to_string(); + return Some((None, did, name, version)); + } + + if parts.len() == 8 { + // schema:sov:did:sov:NcYxiDXkpYi6ov5FcYDi1e:2:gvt:1.0 + let method = parts[1]; + let did = parts[2..5].join(":"); + let Ok(did) = Did::parse(did) else { + return None; + }; + let name = parts[6].to_string(); + let version = parts[7].to_string(); + return Some((Some(method), did, name, version)); + } + + None +} + +pub fn cred_def_parts(id: &str) -> Option<(Option<&str>, Did, String, SchemaId, String)> { + let parts = id.split_terminator(':').collect::>(); + + if parts.len() == 4 { + // Th7MpTaRZVRYnPiabds81Y:3:CL:1 + let did = parts[0].to_string(); + let Ok(did) = Did::parse(did) else { + return None; + }; + let signature_type = parts[2].to_string(); + let schema_id = parts[3].to_string(); + let tag = String::new(); + return Some((None, did, signature_type, SchemaId(schema_id), tag)); + } + + if parts.len() == 5 { + // Th7MpTaRZVRYnPiabds81Y:3:CL:1:tag + let did = parts[0].to_string(); + let Ok(did) = Did::parse(did) else { + return None; + }; + let signature_type = parts[2].to_string(); + let schema_id = parts[3].to_string(); + let tag = parts[4].to_string(); + return Some((None, did, signature_type, SchemaId(schema_id), tag)); + } + + if parts.len() == 7 { + // NcYxiDXkpYi6ov5FcYDi1e:3:CL:NcYxiDXkpYi6ov5FcYDi1e:2:gvt:1.0 + let did = parts[0].to_string(); + let Ok(did) = Did::parse(did) else { + return None; + }; + let signature_type = parts[2].to_string(); + let schema_id = parts[3..7].join(":"); + let tag = String::new(); + return Some((None, did, signature_type, SchemaId(schema_id), tag)); + } + + if parts.len() == 8 { + // NcYxiDXkpYi6ov5FcYDi1e:3:CL:NcYxiDXkpYi6ov5FcYDi1e:2:gvt:1.0:tag + let did = parts[0].to_string(); + let Ok(did) = Did::parse(did) else { + return None; + }; + let signature_type = parts[2].to_string(); + let schema_id = parts[3..7].join(":"); + let tag = parts[7].to_string(); + return Some((None, did, signature_type, SchemaId(schema_id), tag)); + } + + if parts.len() == 9 { + // creddef:sov:did:sov:NcYxiDXkpYi6ov5FcYDi1e:3:CL:3:tag + let method = parts[1]; + let did = parts[2..5].join(":"); + let Ok(did) = Did::parse(did) else { + return None; + }; + let signature_type = parts[6].to_string(); + let schema_id = parts[7].to_string(); + let tag = parts[8].to_string(); + return Some((Some(method), did, signature_type, SchemaId(schema_id), tag)); + } + + if parts.len() == 16 { + // creddef:sov:did:sov:NcYxiDXkpYi6ov5FcYDi1e:3:CL:schema:sov:did:sov: + // NcYxiDXkpYi6ov5FcYDi1e:2:gvt:1.0:tag + let method = parts[1]; + let did = parts[2..5].join(":"); + let Ok(did) = Did::parse(did) else { + return None; + }; + let signature_type = parts[6].to_string(); + let schema_id = parts[7..15].join(":"); + let tag = parts[15].to_string(); + return Some((Some(method), did, signature_type, SchemaId(schema_id), tag)); + } + + None +} diff --git a/aries/aries_vcx_core/src/anoncreds/base_anoncreds.rs b/aries/aries_vcx_core/src/anoncreds/base_anoncreds.rs index 04bfbbb3b0..f4afe7b66f 100644 --- a/aries/aries_vcx_core/src/anoncreds/base_anoncreds.rs +++ b/aries/aries_vcx_core/src/anoncreds/base_anoncreds.rs @@ -27,11 +27,13 @@ pub trait BaseAnonCreds: std::fmt::Debug + Send + Sync { tag: &str, ) -> VcxCoreResult<(String, String, String)>; + #[allow(clippy::too_many_arguments)] async fn issuer_create_and_store_credential_def( &self, wallet: &impl BaseWallet, issuer_did: &str, schema_json: &str, + schema_id: &str, tag: &str, signature_type: Option<&str>, config_json: &str, @@ -133,12 +135,14 @@ pub trait BaseAnonCreds: std::fmt::Debug + Send + Sync { // TODO - FUTURE - think about moving this to somewhere else, as it aggregates other calls (not // PURE Anoncreds) + // ^ YES async fn revoke_credential_local( &self, wallet: &impl BaseWallet, tails_dir: &str, rev_reg_id: &str, cred_rev_id: &str, + rev_reg_delta_json: &str, ) -> VcxCoreResult<()>; async fn get_rev_reg_delta( diff --git a/aries/aries_vcx_core/src/anoncreds/credx_anoncreds.rs b/aries/aries_vcx_core/src/anoncreds/credx_anoncreds.rs index c844a6f41b..15cc35ce1f 100644 --- a/aries/aries_vcx_core/src/anoncreds/credx_anoncreds.rs +++ b/aries/aries_vcx_core/src/anoncreds/credx_anoncreds.rs @@ -370,6 +370,7 @@ impl BaseAnonCreds for IndyCredxAnonCreds { &self, wallet: &impl BaseWallet, issuer_did: &str, + _schema_id: &str, schema_json: &str, tag: &str, sig_type: Option<&str>, @@ -1129,6 +1130,7 @@ impl BaseAnonCreds for IndyCredxAnonCreds { _tails_dir: &str, rev_reg_id: &str, cred_rev_id: &str, + _rev_reg_delta_json: &str, ) -> VcxCoreResult<()> { let cred_rev_id = cred_rev_id.parse().map_err(|e| { AriesVcxCoreError::from_msg( diff --git a/aries/aries_vcx_core/src/anoncreds/mod.rs b/aries/aries_vcx_core/src/anoncreds/mod.rs index 754b6642d8..c5f139a4da 100644 --- a/aries/aries_vcx_core/src/anoncreds/mod.rs +++ b/aries/aries_vcx_core/src/anoncreds/mod.rs @@ -1,3 +1,6 @@ +#[allow(clippy::module_inception)] +#[cfg(feature = "anoncreds")] +pub mod anoncreds; pub mod base_anoncreds; #[cfg(feature = "credx")] pub mod credx_anoncreds; diff --git a/aries/aries_vcx_core/src/errors/mapping_anoncreds.rs b/aries/aries_vcx_core/src/errors/mapping_anoncreds.rs new file mode 100644 index 0000000000..0353a93c0b --- /dev/null +++ b/aries/aries_vcx_core/src/errors/mapping_anoncreds.rs @@ -0,0 +1,32 @@ +use anoncreds::{Error as AnoncredsError, ErrorKind}; + +use crate::errors::error::{AriesVcxCoreError, AriesVcxCoreErrorKind}; + +impl From for AriesVcxCoreError { + fn from(err: AnoncredsError) -> Self { + match err.kind() { + ErrorKind::Input => { + AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::InvalidInput, err) + } + ErrorKind::IOError => AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::IOError, err), + ErrorKind::InvalidState => { + AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::InvalidState, err) + } + ErrorKind::Unexpected => { + AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::UnknownError, err) + } + ErrorKind::CredentialRevoked => { + AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::InvalidState, err) + } + ErrorKind::InvalidUserRevocId => { + AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::InvalidInput, err) + } + ErrorKind::ProofRejected => { + AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::ProofRejected, err) + } + ErrorKind::RevocationRegistryFull => { + AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::InvalidState, err) + } + } + } +} diff --git a/aries/aries_vcx_core/src/errors/mapping_others.rs b/aries/aries_vcx_core/src/errors/mapping_others.rs index d43aeb7684..97db66f9bf 100644 --- a/aries/aries_vcx_core/src/errors/mapping_others.rs +++ b/aries/aries_vcx_core/src/errors/mapping_others.rs @@ -1,12 +1,14 @@ use std::sync::PoisonError; +use did_parser::ParseError; + use crate::errors::error::{AriesVcxCoreError, AriesVcxCoreErrorKind}; impl From for AriesVcxCoreError { - fn from(_err: serde_json::Error) -> Self { + fn from(err: serde_json::Error) -> Self { AriesVcxCoreError::from_msg( AriesVcxCoreErrorKind::InvalidJson, - "Invalid json".to_string(), + format!("Invalid json: {err}"), ) } } @@ -16,3 +18,9 @@ impl From> for AriesVcxCoreError { AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::InvalidState, err.to_string()) } } + +impl From for AriesVcxCoreError { + fn from(err: ParseError) -> Self { + AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::ParsingError, err.to_string()) + } +} diff --git a/aries/aries_vcx_core/src/errors/mod.rs b/aries/aries_vcx_core/src/errors/mod.rs index 841ab45fe2..3e083082a4 100644 --- a/aries/aries_vcx_core/src/errors/mod.rs +++ b/aries/aries_vcx_core/src/errors/mod.rs @@ -1,5 +1,7 @@ pub mod error; mod mapping_agency_client; +#[cfg(feature = "anoncreds")] +mod mapping_anoncreds; #[cfg(feature = "credx")] mod mapping_credx; #[cfg(feature = "vdrtools_wallet")] diff --git a/aries/misc/legacy/libvcx_core/src/api_vcx/api_handle/issuer_credential.rs b/aries/misc/legacy/libvcx_core/src/api_vcx/api_handle/issuer_credential.rs index 452da878a4..a61d8e93ef 100644 --- a/aries/misc/legacy/libvcx_core/src/api_vcx/api_handle/issuer_credential.rs +++ b/aries/misc/legacy/libvcx_core/src/api_vcx/api_handle/issuer_credential.rs @@ -19,7 +19,7 @@ use serde_json; use super::mediated_connection::send_message; use crate::{ api_vcx::{ - api_global::profile::{get_main_anoncreds, get_main_wallet}, + api_global::profile::{get_main_anoncreds, get_main_ledger_read, get_main_wallet}, api_handle::{ connection, connection::HttpClient, credential_def, mediated_connection, object_cache::ObjectCache, revocation_registry::REV_REG_MAP, ToU32, @@ -383,7 +383,11 @@ pub async fn send_credential_nonmediated(handle: u32, connection_handle: u32) -> pub async fn revoke_credential_local(handle: u32) -> LibvcxResult<()> { let credential = ISSUER_CREDENTIAL_MAP.get_cloned(handle)?; credential - .revoke_credential_local(get_main_wallet()?.as_ref(), get_main_anoncreds()?.as_ref()) + .revoke_credential_local( + get_main_wallet()?.as_ref(), + get_main_anoncreds()?.as_ref(), + get_main_ledger_read()?.as_ref(), + ) .await .map_err(|err| err.into()) } diff --git a/aries/misc/test_utils/Cargo.toml b/aries/misc/test_utils/Cargo.toml index 7c889f7b04..aad2cb09a9 100644 --- a/aries/misc/test_utils/Cargo.toml +++ b/aries/misc/test_utils/Cargo.toml @@ -12,6 +12,7 @@ edition.workspace = true vdrtools_wallet = ["aries_vcx_core/vdrtools_wallet"] vdr_proxy_ledger = ["aries_vcx_core/vdr_proxy_ledger", "credx"] credx = ["aries_vcx_core/credx"] +anoncreds = ["aries_vcx_core/anoncreds"] [dependencies] aries_vcx_core = { path = "../../aries_vcx_core" } diff --git a/aries/misc/test_utils/src/devsetup.rs b/aries/misc/test_utils/src/devsetup.rs index 967be10170..129826a40f 100644 --- a/aries/misc/test_utils/src/devsetup.rs +++ b/aries/misc/test_utils/src/devsetup.rs @@ -192,8 +192,14 @@ pub async fn dev_build_featured_anoncreds() -> impl BaseAnonCreds { return IndyCredxAnonCreds; } - #[cfg(not(feature = "credx"))] - return { + #[cfg(all(not(feature = "credx"), feature = "anoncreds"))] + { + use aries_vcx_core::anoncreds::anoncreds::Anoncreds; + return Anoncreds; + } + + #[cfg(all(not(feature = "credx"), not(feature = "anoncreds")))] + { use crate::mockdata::mock_anoncreds::MockAnoncreds; return MockAnoncreds; }; diff --git a/aries/misc/test_utils/src/mockdata/mock_anoncreds.rs b/aries/misc/test_utils/src/mockdata/mock_anoncreds.rs index 89ac48fd59..25c342422d 100644 --- a/aries/misc/test_utils/src/mockdata/mock_anoncreds.rs +++ b/aries/misc/test_utils/src/mockdata/mock_anoncreds.rs @@ -53,6 +53,7 @@ impl BaseAnonCreds for MockAnoncreds { &self, __wallet: &impl BaseWallet, _issuer_did: &str, + _schema_id: &str, _schema_json: &str, _tag: &str, _signature_type: Option<&str>, @@ -208,6 +209,7 @@ impl BaseAnonCreds for MockAnoncreds { _tails_dir: &str, _rev_reg_id: &str, _cred_rev_id: &str, + _rev_reg_delta_json: &str, ) -> VcxCoreResult<()> { Ok(()) }