diff --git a/demo/src/dedi/registry-entries-tx.ts b/demo/src/dedi/registry-entries-tx.ts index bf41d8c3..1401aadd 100644 --- a/demo/src/dedi/registry-entries-tx.ts +++ b/demo/src/dedi/registry-entries-tx.ts @@ -227,6 +227,21 @@ async function main() { ); console.log('\nāœ… Registry Entry reinstated!', registryEntryReinstate); + + console.log(`\nā„ļø Registry Entry verification `) + + const verificationResult = await Cord.Entries.verifyAgainstInputProperties( + registryEntry, + updateEntryDigest, + `did:cord:3${authorIdentity.address}`, + registry.uri + ) + + if (verificationResult.isValid) { + console.log(`āœ… Verification successful! "${registryEntry}" šŸŽ‰`) + } else { + console.log(`šŸš« Verification failed! - "${verificationResult.message}" šŸš«`) + } } main() diff --git a/packages/entries/src/Entries.chain.ts b/packages/entries/src/Entries.chain.ts index 611ad0e4..5c53db69 100644 --- a/packages/entries/src/Entries.chain.ts +++ b/packages/entries/src/Entries.chain.ts @@ -40,6 +40,7 @@ */ import { SDKErrors, + DecoderUtils, } from '@cord.network/utils'; @@ -49,10 +50,15 @@ import { CordKeyringPair, Option, RegistryAuthorizationUri, + IRegistryEntryChainStorage, + RegistryUri, + DidUri } from '@cord.network/types'; import { Chain } from '@cord.network/network'; +import { encodeAddress } from '@polkadot/util-crypto'; + import { ConfigService } from '@cord.network/config' import type { @@ -62,6 +68,7 @@ import type { import { uriToIdentifier, uriToEntryIdAndDigest, + identifierToUri, } from '@cord.network/identifier' export async function isRegistryEntryStored( @@ -340,3 +347,151 @@ export async function dispatchReinstateEntryToChain( ) } } + + +/** + * Decodes the registry entry details from the blockchain state. + * This function takes an optional encoded entry and an identifier, + * then extracts and formats the relevant properties into a structured object. + * + * @param {Option} encoded - + * The optional encoded data from the blockchain representing the registry entry details. + * It may contain the entry details or be `None`. + * + * @param {string} identifier - + * The identifier used to generate the URI for the registry entry. + * + * @returns {IRegistryEntryChainStorage | null} + * - Returns an object containing the decoded registry entry details structured as `IRegistryEntryChainStorage`. + * - If the encoded data is `None`, returns `null`. + * + * @example + * // Example Usage: + * const encodedEntryDetails = ... // fetched from the blockchain + * const identifier = "someIdentifier"; + * + * const registryEntry = decodeRegistryEntryDetailsFromChain(encodedEntryDetails, identifier); + * console.log(registryEntry); // Outputs the decoded registry entry details. + * + */ +export function decodeRegistryEntryDetailsFromChain( + encoded: Option, + identifier: string +): IRegistryEntryChainStorage | null { + if (encoded.isNone) { + return null; + } + + const chainRegistryEntry = encoded.unwrap(); + + /* + * Below code block encodes the data from the chain present in raw + * to its respective formats. + */ + const registryEntry: IRegistryEntryChainStorage = { + uri: identifierToUri(identifier) as EntryUri, + digest: chainRegistryEntry.digest.toHex(), + revoked: chainRegistryEntry.revoked.valueOf(), + creatorUri: `did:cord:3${encodeAddress(chainRegistryEntry.creator, 29)}` as DidUri, + registryUri: identifierToUri( + DecoderUtils.hexToString(chainRegistryEntry.registryId.toString()) + ) as RegistryUri + }; + + console.log("chainRegistryEntry after", registryEntry); + + return registryEntry; +} + + +/** + * Retrieves the details of a registry entry from the blockchain using the provided identifier. + * This asynchronous function queries the blockchain for the registry entry associated with + * the specified identifier and decodes the details into a structured format. + * + * @param {string} identifier - + * The identifier used to query the registry entry from the blockchain. + * + * @returns {Promise} + * - Returns a promise that resolves to an object containing the decoded registry entry details + * structured as `IRegistryEntryChainStorage`. + * - If no entry is found, it throws an error. + * + * @throws {SDKErrors.CordFetchError} + * Throws an error if there is no registry entry associated with the provided identifier. + * + * @example + * // Example Usage: + * const identifier = "someIdentifier"; + * + * try { + * const entryDetails = await getDetailsfromChain(identifier); + * console.log(entryDetails); // Outputs the registry entry details. + * } catch (error) { + * console.error(error.message); // Handle the error accordingly. + * } + * + */ +export async function getDetailsfromChain( + identifier: string +): Promise { + const api = ConfigService.get('api'); + const registryEntryId = uriToIdentifier(identifier); + + const registryEntry = await api.query.entries.registryEntries(registryEntryId); + + const decodedDetails = decodeRegistryEntryDetailsFromChain(registryEntry, identifier); + + if (!decodedDetails) { + throw new SDKErrors.CordFetchError( + `There is no registry entry with the provided ID "${registryEntryId}" present on the chain.` + ); + } + + return decodedDetails; +} + + +/** + * Fetches the registry entry details from the blockchain using the specified entry URI. + * This asynchronous function converts the entry URI into its corresponding identifier, + * retrieves the details of the registry entry from the blockchain, and returns them in a + * structured format. + * + * @param {EntryUri} registryEntryUri - + * The URI of the registry entry for which details are to be fetched. + * + * @returns {Promise} + * - Returns a promise that resolves to an object containing the decoded registry entry details + * structured as `IRegistryEntryChainStorage`. + * + * @throws {SDKErrors.CordFetchError} + * Throws an error if no registry entry is found associated with the provided URI. + * + * @example + * // Example Usage: + * const registryEntryUri = "someEntryUri"; + * + * try { + * const entryDetails = await fetchRegistryEntryDetailsFromChain(registryEntryUri); + * console.log(entryDetails); // Outputs the registry entry details. + * } catch (error) { + * console.error(error.message); // Handle the error accordingly. + * } + * + */ +export async function fetchRegistryEntryDetailsFromChain( + registryEntryUri: EntryUri +): Promise { + const registryEntryObj = uriToEntryIdAndDigest(registryEntryUri); + + const entryDetails = await getDetailsfromChain(registryEntryObj.identifier); + + if (!entryDetails) { + throw new SDKErrors.CordFetchError( + `There is no registry entry with the provided ID "${registryEntryObj.identifier}" present on the chain.` + ); + } + + return entryDetails; +} diff --git a/packages/entries/src/Entries.ts b/packages/entries/src/Entries.ts index 70fae928..d76958c4 100644 --- a/packages/entries/src/Entries.ts +++ b/packages/entries/src/Entries.ts @@ -47,21 +47,25 @@ import { blake2AsHex, } from "@cord.network/types"; +import { SDKErrors } from '@cord.network/utils'; + +import { DataUtils } from '@cord.network/utils' + import { - SDKErrors, - DataUtils, - } from '@cord.network/utils'; + fetchRegistryEntryDetailsFromChain +} from "./Entries.chain"; import { uriToIdentifier, updateRegistryEntryUri, buildRegistryEntryUri, checkIdentifier, + identifierToUri, + uriToEntryIdAndDigest } from '@cord.network/identifier'; import { ConfigService } from '@cord.network/config'; - /** * Generates a URI for a registry entry based on its digest, registry URI, and creator's address. * @@ -375,3 +379,118 @@ export async function updateEntriesProperties( return registryEntryObj } + + +/** + * Verifies the input properties of a registry entry URI against its corresponding chain state details. + * This function ensures that the provided digest, creator URI, and registry URI match the data retrieved + * from the blockchain. It also checks if the entry is revoked or if the URIs are mismatched. + * + * @param {EntryUri} registryEntryUri - The element's URI to be verified against the chain's registry entry. + * It should be of the form `entry:cord:IdDigest:Digest`. + * @param {HexString} digest - The expected digest (hash) associated with the registry entry. + * @param {DidUri} [creatorUri] - (Optional) The expected DID of the creator of the registry entry. + * @param {RegistryUri} [registryUri] - (Optional) The expected registry URI associated with the registry entry. + * + * @returns {Promise<{ isValid: boolean; message: string }>} + * - A result object containing: + * - `isValid`: A boolean indicating whether the verification was successful. + * - `message`: A message describing the outcome of the verification. + * + * @throws {Error} - If an unexpected error occurs during the verification process. + * + * @example + * // Example Usage: + * const registryEntryUri = "cord:3xyz123" as EntryUri; + * const digest = "0xabcdef..." as HexString; + * const creatorUri = "did:cord:3F7HsGqJPyz..." as DidUri; + * const registryUri = "cord:registry:xyz" as RegistryUri; + * + * const result = await verifyAgainstInputProperties( + * registryEntryUri, + * digest, + * creatorUri, + * registryUri + * ); + * + * console.log(result.isValid); // true or false + * console.log(result.message); // 'Digest properties provided are valid and match...' + * + */ +export async function verifyAgainstInputProperties( + registryEntryUri: EntryUri, + digest: HexString, + creatorUri?: DidUri, + registryUri?: RegistryUri, +): Promise<{ isValid: boolean; message: string }> { + try { + const registryEntryStatus = await fetchRegistryEntryDetailsFromChain(registryEntryUri); + const registryEntryObj = uriToEntryIdAndDigest(registryEntryUri); + const entryUri = identifierToUri(registryEntryObj.identifier); + + if (!registryEntryStatus) { + return { + isValid: false, + message: `Registry Entry details for "${digest}" not found.`, + } + } + + console.log("uri", entryUri, "digest", digest, "creatorUri", creatorUri, "registryUri", registryUri); + + if (digest !== registryEntryStatus.digest) { + return { + isValid: false, + message: 'Digest does not match with Registry Entry Digest.', + } + } + + if (registryEntryStatus?.revoked) { + return { + isValid: false, + message: `Registry Entry "${registryEntryUri}" Revoked.`, + } + } + + if (entryUri !== registryEntryStatus.uri) { + return { + isValid: false, + message: 'Registry Entry and Chain Entry URI details does not match.', + } + } + + if (creatorUri) { + if (creatorUri !== registryEntryStatus.creatorUri) { + return { + isValid: false, + message: 'Registry Entry and Digest creator does not match.', + } + } + } + + if (registryUri) { + if (registryUri !== registryEntryStatus.registryUri) { + return { + isValid: false, + message: 'Registry Entry and Chain Registry URI does not match.', + } + } + } + + return { + isValid: true, + message: + 'Digest properties provided are valid and matches the registry entry details.', + } + } catch (error) { + if (error instanceof Error) { + return { + isValid: false, + message: `Error verifying properties: ${error}`, + } + } + return { + isValid: false, + message: 'An unknown error occurred while verifying the properties.', + } + } +}