diff --git a/.pnp.cjs b/.pnp.cjs index c663d0f1304..71340bc9b54 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -8949,7 +8949,7 @@ const RAW_RUNTIME_STATE = ["memory-streams", "npm:0.1.3"],\ ["mocha", "npm:11.1.0"],\ ["mocha-sinon", "virtual:595d7482cc8ddf98ee6aef33fc48b46393554ab5f17f851ef62e6e39315e53666c3e66226b978689aa0bc7f1e83a03081511a21db1c381362fe67614887077f9#npm:2.1.2"],\ - ["node-forge", "npm:1.3.1"],\ + ["node-forge", "npm:1.3.2"],\ ["node-graceful", "npm:3.1.0"],\ ["oclif", "npm:4.0.3"],\ ["pretty-bytes", "npm:5.6.0"],\ @@ -15105,10 +15105,10 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["node-forge", [\ - ["npm:1.3.1", {\ - "packageLocation": "./.yarn/cache/node-forge-npm-1.3.1-f31fd566cc-05bab68686.zip/node_modules/node-forge/",\ + ["npm:1.3.2", {\ + "packageLocation": "./.yarn/cache/node-forge-npm-1.3.2-39b26c0214-dcc54aaffe.zip/node_modules/node-forge/",\ "packageDependencies": [\ - ["node-forge", "npm:1.3.1"]\ + ["node-forge", "npm:1.3.2"]\ ],\ "linkType": "HARD"\ }]\ diff --git a/.yarn/cache/node-forge-npm-1.3.1-f31fd566cc-05bab68686.zip b/.yarn/cache/node-forge-npm-1.3.1-f31fd566cc-05bab68686.zip deleted file mode 100644 index 6b0d0f9b350..00000000000 Binary files a/.yarn/cache/node-forge-npm-1.3.1-f31fd566cc-05bab68686.zip and /dev/null differ diff --git a/.yarn/cache/node-forge-npm-1.3.2-39b26c0214-dcc54aaffe.zip b/.yarn/cache/node-forge-npm-1.3.2-39b26c0214-dcc54aaffe.zip new file mode 100644 index 00000000000..01e1d6efc2e Binary files /dev/null and b/.yarn/cache/node-forge-npm-1.3.2-39b26c0214-dcc54aaffe.zip differ diff --git a/packages/dashmate/package.json b/packages/dashmate/package.json index 6dbcd95927b..54b855f3d11 100644 --- a/packages/dashmate/package.json +++ b/packages/dashmate/package.json @@ -83,7 +83,7 @@ "listr2": "5.0.7", "lodash": "^4.17.21", "memory-streams": "^0.1.3", - "node-forge": "^1.3.1", + "node-forge": "^1.3.2", "node-graceful": "^3.0.1", "pretty-bytes": "^5.3.0", "pretty-ms": "^7.0.0", diff --git a/packages/rs-sdk/src/mock/requests.rs b/packages/rs-sdk/src/mock/requests.rs index 7c79273f159..6ce9954e6c0 100644 --- a/packages/rs-sdk/src/mock/requests.rs +++ b/packages/rs-sdk/src/mock/requests.rs @@ -14,7 +14,7 @@ use dpp::{ dashcore::{hashes::Hash as CoreHash, ProTxHash}, document::{serialization_traits::DocumentCborMethodsV0, Document}, identifier::Identifier, - identity::IdentityPublicKey, + identity::{identities_contract_keys::IdentitiesContractKeys, IdentityPublicKey}, platform_serialization::{platform_encode_to_vec, platform_versioned_decode_from_slice}, prelude::{DataContract, Identity}, serialization::{ @@ -460,6 +460,21 @@ impl MockResponse for GroupActions { } } +impl MockResponse for IdentitiesContractKeys { + fn mock_serialize(&self, _sdk: &MockDashPlatformSdk) -> Vec { + bincode::encode_to_vec(self, BINCODE_CONFIG).expect("encode IdentitiesContractKeys") + } + + fn mock_deserialize(_sdk: &MockDashPlatformSdk, buf: &[u8]) -> Self + where + Self: Sized, + { + bincode::decode_from_slice(buf, BINCODE_CONFIG) + .expect("decode IdentitiesContractKeys") + .0 + } +} + impl_mock_response!(Identity); impl_mock_response!(IdentityPublicKey); impl_mock_response!(Identifier); diff --git a/packages/rs-sdk/src/platform.rs b/packages/rs-sdk/src/platform.rs index e25dd68515a..82e56296e16 100644 --- a/packages/rs-sdk/src/platform.rs +++ b/packages/rs-sdk/src/platform.rs @@ -11,7 +11,7 @@ mod fetch; pub mod fetch_current_no_parameters; mod fetch_many; mod fetch_unproved; -mod identities_contract_keys_query; +pub mod identities_contract_keys_query; pub mod query; pub mod transition; pub mod types; diff --git a/packages/rs-sdk/src/platform/fetch.rs b/packages/rs-sdk/src/platform/fetch.rs index 4339959aaf3..a1be19ce94b 100644 --- a/packages/rs-sdk/src/platform/fetch.rs +++ b/packages/rs-sdk/src/platform/fetch.rs @@ -13,6 +13,7 @@ use crate::sync::retry; use crate::{error::Error, platform::query::Query, Sdk}; use dapi_grpc::platform::v0::{self as platform_proto, Proof, ResponseMetadata}; use dpp::data_contract::associated_token::token_perpetual_distribution::reward_distribution_moment::RewardDistributionMoment; +use dpp::identity::identities_contract_keys::IdentitiesContractKeys; use dpp::voting::votes::Vote; use dpp::{ block::extended_epoch_info::ExtendedEpochInfo, document::Document, platform_value::Identifier, @@ -303,6 +304,11 @@ impl Fetch for RewardDistributionMoment { type Request = platform_proto::GetTokenPerpetualDistributionLastClaimRequest; } +/// Fetch contract-scoped keys for multiple identities. +impl Fetch for IdentitiesContractKeys { + type Request = platform_proto::GetIdentitiesContractKeysRequest; +} + impl Fetch for dpp::tokens::contract_info::TokenContractInfo { type Request = platform_proto::GetTokenContractInfoRequest; } diff --git a/packages/rs-sdk/src/platform/identities_contract_keys_query.rs b/packages/rs-sdk/src/platform/identities_contract_keys_query.rs index e761cecce9b..aaa16b1657b 100644 --- a/packages/rs-sdk/src/platform/identities_contract_keys_query.rs +++ b/packages/rs-sdk/src/platform/identities_contract_keys_query.rs @@ -6,6 +6,7 @@ use rs_dapi_client::transport::{ AppliedRequestSettings, BoxFuture, TransportError, TransportRequest, }; +use crate::platform::query::Query; use crate::platform::Identifier; use crate::Error; @@ -63,6 +64,26 @@ impl TryFrom for GetIdentitiesContractKeysRequest { } } +impl Query for IdentitiesContractKeysQuery { + fn query(self, prove: bool) -> Result { + let IdentitiesContractKeysQuery { + identities_ids, + contract_id, + document_type_name, + purposes, + } = self; + Ok(GetIdentitiesContractKeysRequest { + version: Some(V0(GetIdentitiesContractKeysRequestV0 { + identities_ids: identities_ids.into_iter().map(|a| a.to_vec()).collect(), + contract_id: contract_id.to_vec(), + document_type_name, + purposes: purposes.into_iter().map(|purpose| purpose as i32).collect(), + prove, + })), + }) + } +} + impl TransportRequest for IdentitiesContractKeysQuery { type Client = ::Client; type Response = ::Response; diff --git a/packages/wasm-sdk/src/queries/identity.rs b/packages/wasm-sdk/src/queries/identity.rs index 8dc71227d9d..dda304cf1e7 100644 --- a/packages/wasm-sdk/src/queries/identity.rs +++ b/packages/wasm-sdk/src/queries/identity.rs @@ -2,8 +2,11 @@ use crate::error::WasmSdkError; use crate::queries::utils::deserialize_required_query; use crate::queries::ProofMetadataResponseWasm; use crate::sdk::WasmSdk; +use dash_sdk::dpp::identity::identities_contract_keys::IdentitiesContractKeys; use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; use dash_sdk::dpp::identity::identity_public_key::IdentityPublicKey; +use dash_sdk::dpp::identity::Purpose; +use dash_sdk::platform::identities_contract_keys_query::IdentitiesContractKeysQuery; use dash_sdk::platform::{Fetch, FetchMany, Identifier, Identity, IdentityKeysQuery}; use drive_proof_verifier::types::{IdentityPublicKeys, IndexMap}; use js_sys::{Array, BigInt, Map, Uint8Array}; @@ -82,12 +85,12 @@ impl IdentityKeyInfoWasm { #[wasm_bindgen(js_name = "IdentityContractKeys")] #[derive(Clone)] pub struct IdentityContractKeysWasm { - identity_id: String, + identity_id: IdentifierWasm, keys: Vec, } impl IdentityContractKeysWasm { - fn new(identity_id: String, keys: Vec) -> Self { + fn new(identity_id: IdentifierWasm, keys: Vec) -> Self { IdentityContractKeysWasm { identity_id, keys } } } @@ -95,8 +98,8 @@ impl IdentityContractKeysWasm { #[wasm_bindgen(js_class = IdentityContractKeys)] impl IdentityContractKeysWasm { #[wasm_bindgen(getter = "identityId")] - pub fn identity_id(&self) -> String { - self.identity_id.clone() + pub fn identity_id(&self) -> IdentifierWasm { + self.identity_id } #[wasm_bindgen(getter = "keys")] @@ -238,17 +241,51 @@ struct IdentitiesContractKeysQueryInput { struct IdentitiesContractKeysQueryParsed { identity_ids: Vec, contract_id: Identifier, - purposes: Option>, + purposes: Vec, +} + +impl TryInto for IdentitiesContractKeysQueryParsed { + type Error = WasmSdkError; + + fn try_into(self) -> Result { + IdentitiesContractKeysQuery::new(self.identity_ids, self.contract_id, None, self.purposes) + .map_err(|e| WasmSdkError::generic(format!("Failed to build query: {}", e))) + } } fn parse_identities_contract_keys_query( query: IdentitiesContractKeysQueryJs, ) -> Result { + use dash_sdk::dpp::identity::Purpose; + let input: IdentitiesContractKeysQueryInput = deserialize_required_query( query, "Query object is required", "identities contract keys query", )?; + + let purposes = match input.purposes { + Some(values) => values + .into_iter() + .map(|p| { + let byte: u8 = p.try_into().map_err(|_| { + WasmSdkError::invalid_argument(format!("Invalid purpose value: {}", p)) + })?; + Purpose::try_from(byte).map_err(|e| { + WasmSdkError::invalid_argument(format!("Invalid purpose value {}: {}", p, e)) + }) + }) + .collect::, _>>()?, + None => vec![ + Purpose::AUTHENTICATION, + Purpose::ENCRYPTION, + Purpose::DECRYPTION, + Purpose::TRANSFER, + Purpose::SYSTEM, + Purpose::VOTING, + ], + }; + Ok(IdentitiesContractKeysQueryParsed { identity_ids: input .identity_ids @@ -256,7 +293,7 @@ fn parse_identities_contract_keys_query( .map(Identifier::from) .collect(), contract_id: input.contract_id.into(), - purposes: input.purposes, + purposes, }) } @@ -898,57 +935,32 @@ impl WasmSdk { &self, query: IdentitiesContractKeysQueryJs, ) -> Result { - use dash_sdk::dpp::identity::Purpose; - let params = parse_identities_contract_keys_query(query)?; - let identity_identifiers = params.identity_ids; - let _contract_identifier = params.contract_id; - - // Convert purposes if provided - let purposes_opt = params.purposes.map(|p| { - p.into_iter() - .filter_map(|purpose_int| match purpose_int { - 0 => Some(Purpose::AUTHENTICATION as u32), - 1 => Some(Purpose::ENCRYPTION as u32), - 2 => Some(Purpose::DECRYPTION as u32), - 3 => Some(Purpose::TRANSFER as u32), - 4 => Some(Purpose::SYSTEM as u32), - 5 => Some(Purpose::VOTING as u32), - _ => None, - }) - .collect::>() - }); + use dash_sdk::platform::Fetch; - // For now, we'll implement this by fetching keys for each identity individually - // The SDK doesn't fully expose the batch query yet - let mut responses: Vec = Vec::new(); + let params = parse_identities_contract_keys_query(query)?; - for identity_id in identity_identifiers { - let identity_bytes = *identity_id.as_bytes(); - // Get keys for this identity using the regular identity keys query - let keys_result = IdentityPublicKey::fetch_many(self.as_ref(), identity_id).await?; + let query: IdentitiesContractKeysQuery = params.try_into()?; - let mut identity_keys = Vec::new(); + let keys_result: Option = + IdentitiesContractKeys::fetch(self.as_ref(), query).await?; - // Filter keys by purpose if specified - for (key_id, key_opt) in keys_result { - if let Some(key) = key_opt { - // Check if this key matches the requested purposes - if let Some(ref purposes) = purposes_opt { - if !purposes.contains(&(key.purpose() as u32)) { - continue; - } + let mut responses: Vec = Vec::new(); + if let Some(keys_map) = keys_result { + for (identity_id, purposes_map) in keys_map { + let mut identity_keys = Vec::new(); + for (_, key_opt) in purposes_map { + if let Some(key) = key_opt { + identity_keys.push(IdentityKeyInfoWasm::from_entry(key.id(), &key)); } - - identity_keys.push(IdentityKeyInfoWasm::from_entry(key_id, &key)); } - } - if !identity_keys.is_empty() { - let identity_id_str = IdentifierWasm::from(identity_bytes).get_base58(); - responses.push(IdentityContractKeysWasm::new( - identity_id_str, - identity_keys, - )); + if !identity_keys.is_empty() { + let identity_id_str = IdentifierWasm::from(identity_id); + responses.push(IdentityContractKeysWasm::new( + identity_id_str, + identity_keys, + )); + } } } @@ -1377,70 +1389,30 @@ impl WasmSdk { &self, query: IdentitiesContractKeysQueryJs, ) -> Result { - use dash_sdk::dpp::identity::Purpose; + use dash_sdk::platform::Fetch; let params = parse_identities_contract_keys_query(query)?; - let identity_ids = params.identity_ids; - let _contract_identifier = params.contract_id; - - // Convert purposes if provided - let purposes_opt = params.purposes.map(|p| { - p.into_iter() - .filter_map(|purpose_int| match purpose_int { - 0 => Some(Purpose::AUTHENTICATION as u32), - 1 => Some(Purpose::ENCRYPTION as u32), - 2 => Some(Purpose::DECRYPTION as u32), - 3 => Some(Purpose::TRANSFER as u32), - 4 => Some(Purpose::SYSTEM as u32), - 5 => Some(Purpose::VOTING as u32), - _ => None, - }) - .collect::>() - }); - - // For now, we'll implement this by fetching keys for each identity individually with proof - // The SDK doesn't fully expose the batch query with proof yet - let mut all_responses: Vec = Vec::new(); - let mut combined_metadata: Option = None; - let mut combined_proof: Option = None; + let query: IdentitiesContractKeysQuery = params.try_into()?; - for identity_id in identity_ids { - let identity_bytes = *identity_id.as_bytes(); - // Get keys for this identity using the regular identity keys query with proof - let (keys_result, metadata, proof) = - IdentityPublicKey::fetch_many_with_metadata_and_proof( - self.as_ref(), - identity_id, - None, - ) + let (keys_result, metadata, proof): (Option, _, _) = + IdentitiesContractKeys::fetch_with_metadata_and_proof(self.as_ref(), query, None) .await?; - // Store first metadata and proof - if combined_metadata.is_none() { - combined_metadata = Some(metadata); - combined_proof = Some(proof); - } - - let mut identity_keys = Vec::new(); - - // Filter keys by purpose if specified - for (key_id, key_opt) in keys_result { - if let Some(key) = key_opt { - // Check if this key matches the requested purposes - if let Some(ref purposes) = purposes_opt { - if !purposes.contains(&(key.purpose() as u32)) { - continue; - } + let mut all_responses: Vec = Vec::new(); + if let Some(keys_map) = keys_result { + for (identity_id, purposes_map) in keys_map { + let mut identity_keys = Vec::new(); + for (_, key_opt) in purposes_map { + if let Some(key) = key_opt { + identity_keys.push(IdentityKeyInfoWasm::from_entry(key.id(), &key)); } - - identity_keys.push(IdentityKeyInfoWasm::from_entry(key_id, &key)); } - } - if !identity_keys.is_empty() { - all_responses.push(IdentityContractKeysWasm::new( - IdentifierWasm::from(identity_bytes).get_base58(), - identity_keys, - )); + if !identity_keys.is_empty() { + all_responses.push(IdentityContractKeysWasm::new( + IdentifierWasm::from(identity_id), + identity_keys, + )); + } } } @@ -1451,8 +1423,8 @@ impl WasmSdk { Ok(ProofMetadataResponseWasm::from_sdk_parts( responses_array, - combined_metadata.unwrap_or_default(), - combined_proof.unwrap_or_default(), + metadata, + proof, )) } diff --git a/packages/wasm-sdk/tests/functional/identities.spec.mjs b/packages/wasm-sdk/tests/functional/identities.spec.mjs index 6f9acf0f2a6..02e4c5f93ae 100644 --- a/packages/wasm-sdk/tests/functional/identities.spec.mjs +++ b/packages/wasm-sdk/tests/functional/identities.spec.mjs @@ -59,6 +59,41 @@ describe('Identity queries', function describeBlock() { expect(r).to.be.an('array'); }); + it('contract keys filtered by purpose and with proof', async () => { + const response = await client.getIdentitiesContractKeysWithProofInfo({ + identityIds: [TEST_IDENTITY], + contractId: DPNS_CONTRACT, + purposes: [0], // authentication + }); + + // Basic shape + expect(response).to.be.ok(); + expect(response.metadata).to.be.ok(); + expect(response.proof).to.be.ok(); + + // Data sanity: if keys are returned, all should match the requested purpose set + const keysArray = response.data; + if (Array.isArray(keysArray) && keysArray.length > 0) { + for (const entry of keysArray) { + const { keys } = entry; + for (const key of keys) { + expect(key.purpose).to.match(/Authentication/i); + } + } + } + }); + + it('contract keys batch handles multiple identities', async () => { + const r = await client.getIdentitiesContractKeys({ + identityIds: [TEST_IDENTITY, TEST_IDENTITY], + contractId: DPNS_CONTRACT, + }); + expect(r).to.be.an('array'); + if (r.length > 0) { + expect(r[0].identityId).to.be.instanceOf(sdk.Identifier); // IdentifierWasm + } + }); + it('token balances/infos for identity and batches', async () => { const TOKEN_CONTRACT = 'H7FRpZJqZK933r9CzZMsCuf1BM34NT5P2wSJyjDkprqy'; const tokenId = sdk.WasmSdk.calculateTokenIdFromContract(TOKEN_CONTRACT, 1); diff --git a/yarn.lock b/yarn.lock index c31159136c3..89b3a23545e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6744,7 +6744,7 @@ __metadata: memory-streams: "npm:^0.1.3" mocha: "npm:^11.1.0" mocha-sinon: "npm:^2.1.2" - node-forge: "npm:^1.3.1" + node-forge: "npm:^1.3.2" node-graceful: "npm:^3.0.1" oclif: "npm:^4.0.3" pretty-bytes: "npm:^5.3.0" @@ -11868,10 +11868,10 @@ __metadata: languageName: node linkType: hard -"node-forge@npm:^1.3.1": - version: 1.3.1 - resolution: "node-forge@npm:1.3.1" - checksum: 05bab6868633bf9ad4c3b1dd50ec501c22ffd69f556cdf169a00998ca1d03e8107a6032ba013852f202035372021b845603aeccd7dfcb58cdb7430013b3daa8d +"node-forge@npm:^1.3.2": + version: 1.3.2 + resolution: "node-forge@npm:1.3.2" + checksum: dcc54aaffe0cf52367214a20c0032aa9b209d9095dd14526504f1972d1900a07e96046b3684cb0c8d0cc3d48744dd18e02b7b447ab28fac615ffb850beeabf18 languageName: node linkType: hard