From 311847fb125025ed2a884a9a0cb8fe7d6966dbce Mon Sep 17 00:00:00 2001 From: Guilherme Funchal da Silva Date: Tue, 7 Jan 2025 15:33:41 -0300 Subject: [PATCH] #108 Revocation Registry: fix typos + add new endorsed method Signed-off-by: Guilherme Funchal da Silva --- .../contracts-ts/RevocationRegistry.ts | 40 +++ .../anoncreds/RevocationRegistry.sol | 79 ++--- .../anoncreds/RevocationRegistryInterface.sol | 138 ++++++++- .../anoncreds/RevocationRegistryTypes.sol | 11 +- smart_contracts/demos/flow-with-did-ethr.ts | 6 + .../genesis/contracts/revocationRegistry.ts | 4 +- .../test/anoncreds/RevocationRegistry.spec.ts | 277 +++++++++++------- .../test/utils/contract-helpers.ts | 73 ++++- smart_contracts/utils/entity-factories.ts | 1 - .../anoncreds/revocation_registry.rs | 72 ++++- vdr/wasm/src/contracts/revocation_registry.rs | 15 + 11 files changed, 548 insertions(+), 168 deletions(-) diff --git a/smart_contracts/contracts-ts/RevocationRegistry.ts b/smart_contracts/contracts-ts/RevocationRegistry.ts index c58b17b5..6163e1bc 100644 --- a/smart_contracts/contracts-ts/RevocationRegistry.ts +++ b/smart_contracts/contracts-ts/RevocationRegistry.ts @@ -74,6 +74,25 @@ export class RevocationRegistry extends Contract { return tx.wait() } + public async createRevocationRegistryEntrySigned( + identity: string, + revRegDefId: string, + issuerId: string, + revRegEntry: RevocationRegistryEntryStruct, + signature: Signature, + ) { + const tx = await this.instance.createRevocationRegistryEntrySigned( + identity, + signature.v, + signature.r, + signature.s, + keccak256(toUtf8Bytes(revRegDefId)), + issuerId, + revRegEntry, + ) + return tx.wait() + } + public async resolveRevocationRegistryDefinition(id: string): Promise { const record = await this.instance.resolveRevocationRegistryDefinition(keccak256(toUtf8Bytes(id))) return { @@ -114,4 +133,25 @@ export class RevocationRegistry extends Contract { ]), ) } + + public signCreateRevRegEntryEndorsementData( + identity: string, + privateKey: Uint8Array, + revRegDefId: string, + issuerId: string, + revRegEntry: RevocationRegistryEntryStruct, + ) { + const revRegEntrySolidityStruct = ['tuple(bytes,bytes,uint32[],uint32[],uint64)'] + + return this.signEndorsementData( + privateKey, + concat([ + identity, + toUtf8Bytes('createRevocationRegistryEntry'), + getBytes(keccak256(toUtf8Bytes(revRegDefId)), 'hex'), + toUtf8Bytes(issuerId), + getBytes(new AbiCoder().encode(revRegEntrySolidityStruct, [Object.values(revRegEntry)])), + ]), + ) + } } diff --git a/smart_contracts/contracts/anoncreds/RevocationRegistry.sol b/smart_contracts/contracts/anoncreds/RevocationRegistry.sol index fc928996..c80ce668 100644 --- a/smart_contracts/contracts/anoncreds/RevocationRegistry.sol +++ b/smart_contracts/contracts/anoncreds/RevocationRegistry.sol @@ -11,6 +11,7 @@ import { NotRevocationRegistryDefinitionIssuer, RevocationRegistryDefinitionAlre import { CredentialDefinitionRegistryInterface } from "./CredentialDefinitionRegistryInterface.sol"; import { RoleControlInterface } from "../auth/RoleControl.sol"; import { AnoncredsRegistry } from "./AnoncredsRegistry.sol"; +import { StringUtils } from "../utils/StringUtils.sol"; import { Errors } from "../utils/Errors.sol"; @@ -26,7 +27,7 @@ contract RevocationRegistry is RevocationRegistryInterface, ControlledUpgradeabl mapping(bytes32 id => RevocationRegistryDefinitionRecord revocationRegistryDefinitionRecord) private _revRegDefs; /** - * Checks that the schema exist + * Checks that the Credential Definition exist */ modifier _credentialDefinitionExists(bytes32 id) { _credentialDefinitionRegistry.resolveCredentialDefinition(id); @@ -49,34 +50,21 @@ contract RevocationRegistry is RevocationRegistryInterface, ControlledUpgradeabl _; } - modifier _accumulatorsMatch(bytes32 _revRegDefId, RevocationRegistryEntry calldata initialRevRegEntry) { - if ( - _revRegDefs[_revRegDefId].metadata.currentAccumulator.length != 0 && - keccak256(abi.encodePacked(_revRegDefs[_revRegDefId].metadata.currentAccumulator)) != - keccak256(abi.encodePacked(initialRevRegEntry.prevAccumulator)) - ) revert AccumulatorMismatch(initialRevRegEntry.prevAccumulator); - _; - } - - //TODO: using as modifier caused 'Stack Too Deep' Compile Error - // modifier _isRevRegDefIssuer(bytes32 revRegDefId, string calldata issuerId) { - // if ( - // keccak256(abi.encodePacked(_revRegDefs[revRegDefId].metadata.issuerId)) != - // keccak256(abi.encodePacked(issuerId)) - // ) revert NotRevocationRegistryDefinitionIssuer(issuerId); - // _; - // } - - //TODO: - // Seems odd but keep in mind... - //https://github.com/hyperledger/indy-node/blob/main/design/anoncreds.md#revoc_reg_entry - // Creation of Revocation Registry (Def and Enteries): - // RevocReg Issuer may not be the same as Schema Author and CredDef issuer. /** - * Checks wether Credential Definition Exists + * Checks that the previous accumulator specified by the new Revocation Registry Entry matches + * the current accumulator on chain (only if not the first entry) */ - modifier _credDefExists(bytes32 credDefId) { - _credentialDefinitionRegistry.resolveCredentialDefinition(credDefId); + modifier _accumulatorsMatch(bytes32 _revRegDefId, RevocationRegistryEntry calldata _revRegEntry) { + if (_revRegDefs[_revRegDefId].metadata.currentAccumulator.length != 0) { + if (_revRegDefs[_revRegDefId].metadata.currentAccumulator.length != _revRegEntry.prevAccumulator.length) { + revert AccumulatorMismatch(_revRegEntry.prevAccumulator); + } + for (uint256 i = 0; i < _revRegEntry.prevAccumulator.length; i++) { + if (_revRegDefs[_revRegDefId].metadata.currentAccumulator[i] != _revRegEntry.prevAccumulator[i]) { + revert AccumulatorMismatch(_revRegEntry.prevAccumulator); + } + } + } _; } @@ -137,6 +125,7 @@ contract RevocationRegistry is RevocationRegistryInterface, ControlledUpgradeabl ); } + /// @inheritdoc RevocationRegistryInterface function resolveRevocationRegistryDefinition( bytes32 id ) @@ -149,6 +138,7 @@ contract RevocationRegistry is RevocationRegistryInterface, ControlledUpgradeabl return _revRegDefs[id]; } + /// @inheritdoc RevocationRegistryInterface function createRevocationRegistryEntry( address identity, bytes32 revRegDefId, @@ -158,6 +148,31 @@ contract RevocationRegistry is RevocationRegistryInterface, ControlledUpgradeabl _createRevocationRegistryEntry(identity, msg.sender, revRegDefId, issuerId, revRegEntry); } + /// @inheritdoc RevocationRegistryInterface + function createRevocationRegistryEntrySigned( + address identity, + uint8 sigV, + bytes32 sigR, + bytes32 sigS, + bytes32 revRegDefId, + string calldata issuerId, + RevocationRegistryEntry calldata revRegEntry + ) public virtual { + bytes32 hash = keccak256( + abi.encodePacked( + bytes1(0x19), + bytes1(0), + address(this), + identity, + "createRevocationRegistryEntry", + revRegDefId, + issuerId, + abi.encode(revRegEntry) + ) + ); + _createRevocationRegistryEntry(identity, ecrecover(hash, sigV, sigR, sigS), revRegDefId, issuerId, revRegEntry); + } + function _createRevocationRegistryDefinition( address identity, address actor, @@ -170,7 +185,7 @@ contract RevocationRegistry is RevocationRegistryInterface, ControlledUpgradeabl _senderIsTrusteeOrEndorserOrSteward _uniqueRevRegDefId(id) _validIssuer(issuerId, identity, actor) - _credDefExists(credDefId) + _credentialDefinitionExists(credDefId) { _revRegDefs[id].revRegDef = revRegDef; _revRegDefs[id].metadata.created = block.timestamp; @@ -192,11 +207,9 @@ contract RevocationRegistry is RevocationRegistryInterface, ControlledUpgradeabl _validIssuer(issuerId, identity, actor) _accumulatorsMatch(revRegDefId, revRegEntry) { - //TODO: using as modifier caused 'Stack too deep' compile error - if ( - keccak256(abi.encodePacked(_revRegDefs[revRegDefId].metadata.issuerId)) != - keccak256(abi.encodePacked(issuerId)) - ) revert NotRevocationRegistryDefinitionIssuer(issuerId); + if (!StringUtils.equals(_revRegDefs[revRegDefId].metadata.issuerId, issuerId)) { + revert NotRevocationRegistryDefinitionIssuer(issuerId); + } _revRegDefs[revRegDefId].metadata.currentAccumulator = revRegEntry.currentAccumulator; emit RevocationRegistryEntryCreated(revRegDefId, revRegEntry.timestamp, revRegEntry); diff --git a/smart_contracts/contracts/anoncreds/RevocationRegistryInterface.sol b/smart_contracts/contracts/anoncreds/RevocationRegistryInterface.sol index 4894a18d..c7d7722c 100644 --- a/smart_contracts/contracts/anoncreds/RevocationRegistryInterface.sol +++ b/smart_contracts/contracts/anoncreds/RevocationRegistryInterface.sol @@ -12,12 +12,42 @@ interface RevocationRegistryInterface { */ event RevocationRegistryDefinitionCreated(bytes32 revocationRegistryDefinitionId, address identity); + /** + * @dev Event that is sent when a Revocation Registry Entry (Delta) is created + * + * @param revocationRegistryDefinitionId Keccak hash of created revocation registry definition id + * @param timestamp Timestamp of the created Revocation Registry Entry + * @param revRegEntry Struct containing new accumulator and list of newly issued/revoked credentials + */ event RevocationRegistryEntryCreated( bytes32 indexed revocationRegistryDefinitionId, uint64 indexed timestamp, RevocationRegistryEntry revRegEntry ); + /** + * @dev Creates a new Revocation Registry Definition. + * + * Once the Revocation Registry Definition is created, this function emits a `RevocationRegistryDefinitionCreated` event + * with the new Revocation Registry Definition's ID and issuer address. + * + * Restrictions: + * - Only senders with either TRUSTEE or ENDORSER or STEWARD role are permitted to create new object; + * + * This function can revert with following errors: + * - `RevocationRegistryDefinitionAlreadyExist`: Raised if Revocation Registry Definition with provided ID already exist. + * - `CredentialDefinitionNotFound`: Raised if the associated Credential Definition doesn't exist. + * - `IssuerNotFound`: Raised if the associated issuer doesn't exist. + * - `InvalidIssuerId`: Raised if the provided issuer DID is invalid. + * - `IssuerHasBeenDeactivated`: Raised if the associated issuer is not active. + * - `NotIdentityOwner`: Raised when specified issuer DID is not owned by sender. + * + * @param identity Account address of Revocation Registry Definition issuer. + * @param id Keccak hash of Revocation Registry Id to be created. + * @param credDefId Keccak hash of Credential Definition Id. + * @param issuerId DID of Revocation Registry Definition issuer. + * @param revRegDef AnonCreds Revocation Registry Definition JSON as bytes. + */ function createRevocationRegistryDefinition( address identity, bytes32 id, @@ -26,6 +56,32 @@ interface RevocationRegistryInterface { bytes calldata revRegDef ) external; + /** + * @dev Endorse a new Revocation Registry Definition (off-chain author signature). + * + * Once the Revocation Registry Definition is created, this function emits a `RevocationRegistryDefinitionCreated` event + * with the new Revocation Registry Definition's ID and issuer address. + * + * Restrictions: + * - Only senders with either TRUSTEE or ENDORSER or STEWARD role are permitted to create new object; + * + * This function can revert with following errors: + * - `RevocationRegistryDefinitionAlreadyExist`: Raised if Revocation Registry Definition with provided ID already exist. + * - `CredentialDefinitionNotFound`: Raised if the associated Credential Definition doesn't exist. + * - `IssuerNotFound`: Raised if the associated issuer doesn't exist. + * - `InvalidIssuerId`: Raised if the provided issuer DID is invalid. + * - `IssuerHasBeenDeactivated`: Raised if the associated issuer is not active. + * - `NotIdentityOwner`: Raised when specified issuer DID is not owned by sender. + * + * @param identity Account address of credential definition issuer. + * @param sigR Part of EcDSA signature. + * @param sigV Part of EcDSA signature. + * @param sigS Part of EcDSA signature. + * @param id Keccak hash of Credential Definition id to be created. + * @param issuerId DID of Revocation Registry Definition issuer. + * @param credDefId Keccak hash of Credential Definition id. + * @param revRegDef AnonCreds Revocation Registry Definition JSON as bytes. + */ function createRevocationRegistryDefinitionSigned( address identity, uint8 sigV, @@ -37,10 +93,43 @@ interface RevocationRegistryInterface { bytes calldata revRegDef ) external; + // /** + // * @dev Resolve the Revocation Registry Definition associated with the given ID. + // * + // * If no matching Revocation Registry Definition is found, the function revert with `RevocationRegistryDefinitionNotFound` error + // * + // * @param id Keccak hash of the Revocation Registry Definition to be resolved. + // * + // * @return revocationRegistryDefinitionRecord Returns the Revocation Registry Definition with metadata. + // */ function resolveRevocationRegistryDefinition( bytes32 id ) external returns (RevocationRegistryDefinitionRecord memory revocationRegistryDefinitionRecord); + /** + * @dev Creates a new Revocation Registry Entry (Delta). + * + * Once the Revocation Registry Entry is created, this function emits a `RevocationRegistryEntryCreated` event + * with the Revocation Registry Definition's ID, timestamp and a struct containing the new accumulator and issued/revoked + * credentials + * + * Restrictions: + * - Only senders with either TRUSTEE or ENDORSER or STEWARD role are permitted to create new object; + * - Only the issuer of the associated Revocation Registry Definition is permitted to create new object; + * + * This function can revert with following errors: + * - `RevocationRegistryNotFound`: Raised if the associated Revocation Registry Definition doesn't exist. + * - `IssuerNotFound`: Raised if the associated issuer doesn't exist. + * - `InvalidIssuerId`: Raised if the provided issuer DID is invalid. + * - `IssuerHasBeenDeactivated`: Raised if the associated issuer is not active. + * - `NotIdentityOwner`: Raised when specified issuer DID is not owned by sender. + * - `NotRevocationRegistryDefinitionIssuer`: Raised when trying to create object while not being issuer of associated Revocation Registry Definition. + * + * @param identity Account address of Revocation Registry Definition issuer. + * @param revRegDefId Keccak hash of the associated Revocation Registry Id. + * @param issuerId DID of Revocation Registry Definition issuer. + * @param revRegEntry Struct with new and previous accumulators, list of issued/revoked credentials and timestamp. + */ function createRevocationRegistryEntry( address identity, bytes32 revRegDefId, @@ -48,17 +137,40 @@ interface RevocationRegistryInterface { RevocationRegistryEntry calldata revRegEntry ) external; - //TODO: - // /** - // * @dev Resolve the Revocation Registry Definition associated with the given ID. - // * - // * If no matching Revocation Registry Definition is found, the function revert with `RevocationRegistryDefinitionNotFound` error - // * - // * @param id Keccak hash of the Revocation Registry Definition to be resolved. - // * - // * @return revocationRegistryDefinitionRecord Returns the credential definition with metadata. - // */ - // function resolveRevocationRegistryDefinition( - // bytes32 id - // ) external returns (RevocationRegistryDefinitionRecord memory revocationRegistryDefinitionRecord); + /** + * @dev Endorse a new Revocation Registry Entry (off-chain author signature). + * + * Once the Revocation Registry Entry is created, this function emits a `RevocationRegistryEntryCreated` event + * with the Revocation Registry Definition's ID, timestamp and a struct containing the new accumulator and issued/revoked + * credentials + * + * Restrictions: + * - Only senders with either TRUSTEE or ENDORSER or STEWARD role are permitted to create new object; + * - Only the issuer of the associated Revocation Registry Definition is permitted to create new object; + * + * This function can revert with following errors: + * - `RevocationRegistryNotFound`: Raised if the associated Revocation Registry Definition doesn't exist. + * - `IssuerNotFound`: Raised if the associated issuer doesn't exist. + * - `InvalidIssuerId`: Raised if the provided issuer DID is invalid. + * - `IssuerHasBeenDeactivated`: Raised if the associated issuer is not active. + * - `NotIdentityOwner`: Raised when specified issuer DID is not owned by sender. + * - `NotRevocationRegistryDefinitionIssuer`: Raised when trying to create object while not being issuer of associated Revocation Registry Definition. + * + * @param identity Account address of credential definition issuer. + * @param sigR Part of EcDSA signature. + * @param sigV Part of EcDSA signature. + * @param sigS Part of EcDSA signature. + * @param revRegDefId Keccak hash of the associated Revocation Registry Id. + * @param issuerId DID of Revocation Registry Definition issuer. + * @param revRegEntry Struct with new and previous accumulators, list of issued/revoked credentials and timestamp. + */ + function createRevocationRegistryEntrySigned( + address identity, + uint8 sigV, + bytes32 sigR, + bytes32 sigS, + bytes32 revRegDefId, + string calldata issuerId, + RevocationRegistryEntry calldata revRegEntry + ) external; } diff --git a/smart_contracts/contracts/anoncreds/RevocationRegistryTypes.sol b/smart_contracts/contracts/anoncreds/RevocationRegistryTypes.sol index a616d1f8..ef50dedf 100644 --- a/smart_contracts/contracts/anoncreds/RevocationRegistryTypes.sol +++ b/smart_contracts/contracts/anoncreds/RevocationRegistryTypes.sol @@ -26,9 +26,18 @@ struct RevocationRegistryDefinitionMetadata { uint256 created; string issuerId; bytes currentAccumulator; - //TODO: Add timestamp for on chain control as well? } +/** + * @title RevocationRegistryEntry + * @dev This struct holds the data of a new revocation registry entry (delta). + * + * @param currentAccumulator - New accumulator to be saved on-chain. + * @param prevAccumulator - Previous accumulator for comparison. + * @param issued - list of newly issued credential indexes. + * @param revoked - list of newly revoked credential indexes. + * @param timestamp - timestamp of revocation registry entry (delta). + */ struct RevocationRegistryEntry { bytes currentAccumulator; bytes prevAccumulator; diff --git a/smart_contracts/demos/flow-with-did-ethr.ts b/smart_contracts/demos/flow-with-did-ethr.ts index 7ba58a91..5e88d319 100644 --- a/smart_contracts/demos/flow-with-did-ethr.ts +++ b/smart_contracts/demos/flow-with-did-ethr.ts @@ -108,6 +108,12 @@ async function demo() { ) console.log(`Revocation Registry Entry created for Revocation Registry Definition id ${revocationRegistryId}. Receipt: ${JSON.stringify(receipt)}`) + + console.log("12. Faber fetches Revocation Registry Entries associated with a Test Revocation Registry Definition") + const revRegEntries = await faber.revocationRegistry.fetchAllRevocationEntries(revocationRegistryId); + + console.log(`All Revocation Registry Entries found for Revocation Registry Definition id ${revocationRegistryId}: `) + console.log(revRegEntries) } if (require.main === module) { diff --git a/smart_contracts/scripts/genesis/contracts/revocationRegistry.ts b/smart_contracts/scripts/genesis/contracts/revocationRegistry.ts index a20fa275..923bf342 100644 --- a/smart_contracts/scripts/genesis/contracts/revocationRegistry.ts +++ b/smart_contracts/scripts/genesis/contracts/revocationRegistry.ts @@ -25,9 +25,9 @@ export function revocationRegistry(config: RevocationRegistryConfig) { storage[slots['0']] = padLeft(data.upgradeControlAddress, 64) // address of DID registry contact stored in slot 1 storage[slots['1']] = padLeft(data.universalDidResolverAddress, 64) - // address of Role control contact stored in slot 3 + // address of Role control contact stored in slot 2 storage[slots['2']] = padLeft(data.roleControlContractAddress, 64) - // address of schema registry contact stored in slot 2 + // address of credentialDefinition registry contact stored in slot 3 storage[slots['3']] = padLeft(data.credentialDefinitionRegistryAddress, 64) return buildProxySection(name, address, description, storage) } diff --git a/smart_contracts/test/anoncreds/RevocationRegistry.spec.ts b/smart_contracts/test/anoncreds/RevocationRegistry.spec.ts index e41f952d..87f7a0ec 100644 --- a/smart_contracts/test/anoncreds/RevocationRegistry.spec.ts +++ b/smart_contracts/test/anoncreds/RevocationRegistry.spec.ts @@ -7,17 +7,17 @@ import { expect } from 'chai' import { keccak256, toUtf8Bytes } from 'ethers' import { IndyDidRegistry } from '../../contracts-ts' -import { RevocationRegistryEntryStruct } from '../../typechain-types/contracts/anoncreds/RevocationRegistry' import { createCredentialDefinitionObject, CreateRevocationEntryParams, createRevocationRegistryDefinitionObject, createRevocationRegistryEntryObject, - createSchemaObject, } from '../../utils' import { createCredentialDefinition, - createDidSigned, + createCredentialDefinitionSigned, + createRevocationRegistryDefinition, + createRevocationRegistryDefinitionSigned, createSchema, createSchemaSigned, deployRevocationRegistry, @@ -28,7 +28,7 @@ import { testActorAddress, testActorPrivateKey, } from '../utils/contract-helpers' -import { AuthErrors, ClErrors, DidErrors } from '../utils/errors' +import { ClErrors, DidErrors } from '../utils/errors' import { TestAccounts } from '../utils/test-entities' describe('RevocationRegistry', function () { @@ -42,6 +42,9 @@ describe('RevocationRegistry', function () { let credDefId: string let issuerAddress: string let issuerId: string + let issuerIdSigned: string + let schemaIdSigned: string + let credDefIdSigned: string beforeEach(async function () { const { @@ -71,6 +74,19 @@ describe('RevocationRegistry', function () { schemaId, ) + issuerIdSigned = `did:ethr:${testActorAddress}` + + const { id: createdSchemaIdSigned } = await createSchemaSigned(schemaRegistryInit, testActorAddress, issuerIdSigned) + + schemaIdSigned = createdSchemaIdSigned + + const { id: createdCredDefIdSigned } = await createCredentialDefinitionSigned( + credentialDefinitionRegistryInit, + testActorAddress, + issuerIdSigned, + schemaIdSigned, + ) + didRegistry = didRegistryInit testAccounts = testAccountsInit schemaRegistry = schemaRegistryInit @@ -78,6 +94,7 @@ describe('RevocationRegistry', function () { revocationRegistry = revocationRegistryInit roleControl = roleControlInit credDefId = createdCredDefId + credDefIdSigned = createdCredDefIdSigned }) describe('Add/Resolve Revocation Degistry Definition with did:ethr Issuer', function () { @@ -139,50 +156,25 @@ describe('RevocationRegistry', function () { describe('Endorse/Resolve Revocation Registry Definition with did:ethr Issuer', function () { it('Should endorse and resolve Revocation Registry Definition with did:ethr', async function () { - const authorDid = `did:ethr:${testActorAddress}` - const { id: ethSchemaId } = await createSchemaSigned(schemaRegistry, testActorAddress, authorDid) - - const { id: credentialDefinitionId, credDef } = createCredentialDefinitionObject({ - issuerId: authorDid, - schemaId: ethSchemaId, - }) - const signature = credentialDefinitionRegistry.signCreateCredDefEndorsementData( - testActorAddress, - testActorPrivateKey, - credentialDefinitionId, - authorDid, - ethSchemaId, - credDef, - ) - - await credentialDefinitionRegistry.createCredentialDefinitionSigned( - testActorAddress, - credentialDefinitionId, - authorDid, - ethSchemaId, - credDef, - signature, - ) - const { id, revRegDef } = createRevocationRegistryDefinitionObject({ - issuerId: authorDid, - credDefId: credentialDefinitionId, + issuerId: issuerIdSigned, + credDefId: credDefIdSigned, }) const revRegSig = revocationRegistry.signCreateRevRegDefEndorsementData( testActorAddress, testActorPrivateKey, id, - credentialDefinitionId, - authorDid, + credDefIdSigned, + issuerIdSigned, revRegDef, ) await revocationRegistry.createRevocationRegistryDefinitionSigned( testActorAddress, id, - credentialDefinitionId, - authorDid, + credDefIdSigned, + issuerIdSigned, revRegDef, revRegSig, ) @@ -192,42 +184,17 @@ describe('RevocationRegistry', function () { }) it('Should fail if Revocation Registry Definition is being endorsed with not owned Issuer DID', async function () { - const authorDid = `did:ethr:${testActorAddress}` - const { id: ethSchemaId } = await createSchemaSigned(schemaRegistry, testActorAddress, authorDid) - - const { id: credentialDefinitionId, credDef } = createCredentialDefinitionObject({ - issuerId: authorDid, - schemaId: ethSchemaId, - }) - const signature = credentialDefinitionRegistry.signCreateCredDefEndorsementData( - testActorAddress, - testActorPrivateKey, - credentialDefinitionId, - authorDid, - ethSchemaId, - credDef, - ) - - await credentialDefinitionRegistry.createCredentialDefinitionSigned( - testActorAddress, - credentialDefinitionId, - authorDid, - ethSchemaId, - credDef, - signature, - ) - const { id, revRegDef } = createRevocationRegistryDefinitionObject({ - issuerId: authorDid, - credDefId: credentialDefinitionId, + issuerId: issuerIdSigned, + credDefId: credDefIdSigned, }) const revRegSig = revocationRegistry.signCreateRevRegDefEndorsementData( testAccounts.trustee2.account.address, testActorPrivateKey, id, - credentialDefinitionId, - authorDid, + credDefIdSigned, + issuerIdSigned, revRegDef, ) @@ -235,8 +202,8 @@ describe('RevocationRegistry', function () { revocationRegistry.createRevocationRegistryDefinitionSigned( testAccounts.trustee2.account.address, id, - credentialDefinitionId, - authorDid, + credDefIdSigned, + issuerIdSigned, revRegDef, revRegSig, ), @@ -244,42 +211,17 @@ describe('RevocationRegistry', function () { }) it('Should fail if Revocation Registry Definition is being endorsed with invalid signature', async function () { - const authorDid = `did:ethr:${testActorAddress}` - const { id: ethSchemaId } = await createSchemaSigned(schemaRegistry, testActorAddress, authorDid) - - const { id: credentialDefinitionId, credDef } = createCredentialDefinitionObject({ - issuerId: authorDid, - schemaId: ethSchemaId, - }) - const signature = credentialDefinitionRegistry.signCreateCredDefEndorsementData( - testActorAddress, - testActorPrivateKey, - credentialDefinitionId, - authorDid, - ethSchemaId, - credDef, - ) - - await credentialDefinitionRegistry.createCredentialDefinitionSigned( - testActorAddress, - credentialDefinitionId, - authorDid, - ethSchemaId, - credDef, - signature, - ) - const { id, revRegDef } = createRevocationRegistryDefinitionObject({ - issuerId: authorDid, - credDefId: credentialDefinitionId, + issuerId: issuerIdSigned, + credDefId: credDefIdSigned, }) const revRegSig = revocationRegistry.signCreateRevRegDefEndorsementData( testAccounts.trustee2.account.address, testActorPrivateKey, 'different id passed into signature', - credentialDefinitionId, - authorDid, + credDefIdSigned, + issuerIdSigned, revRegDef, ) @@ -287,8 +229,8 @@ describe('RevocationRegistry', function () { revocationRegistry.createRevocationRegistryDefinitionSigned( testActorAddress, id, - credentialDefinitionId, - authorDid, + credDefIdSigned, + issuerIdSigned, revRegDef, revRegSig, ), @@ -299,9 +241,12 @@ describe('RevocationRegistry', function () { describe('Add/Resolve All Revocation Registry Entry with did:ethr Issuer', function () { it('Should successfully add Revocation Registry Entry', async function () { const ethrIssuerId = `did:ethr:${issuerAddress}` - const { id, revRegDef } = createRevocationRegistryDefinitionObject({ issuerId: ethrIssuerId, credDefId }) - - await revocationRegistry.createRevocationRegistryDefinition(issuerAddress, id, credDefId, ethrIssuerId, revRegDef) + const { id } = await createRevocationRegistryDefinition( + revocationRegistry, + issuerAddress, + ethrIssuerId, + credDefId, + ) const revocationRegistryEntryParams: CreateRevocationEntryParams = { currentAccumulator: '0x20', @@ -325,9 +270,12 @@ describe('RevocationRegistry', function () { it('Should fail to add Revocation Registry Entry with incompatible previous Accumulator', async function () { const ethrIssuerId = `did:ethr:${issuerAddress}` - const { id, revRegDef } = createRevocationRegistryDefinitionObject({ issuerId: ethrIssuerId, credDefId }) - - await revocationRegistry.createRevocationRegistryDefinition(issuerAddress, id, credDefId, ethrIssuerId, revRegDef) + const { id } = await createRevocationRegistryDefinition( + revocationRegistry, + issuerAddress, + ethrIssuerId, + credDefId, + ) let revocationRegistryEntryParams: CreateRevocationEntryParams = { currentAccumulator: '0x20', @@ -373,9 +321,12 @@ describe('RevocationRegistry', function () { it('Should fail if attempting to create Revocation Registry Entry for not owned Revocation Registry Definition', async function () { const ethrIssuerId = `did:ethr:${issuerAddress}` - const { id, revRegDef } = createRevocationRegistryDefinitionObject({ issuerId: ethrIssuerId, credDefId }) - - await revocationRegistry.createRevocationRegistryDefinition(issuerAddress, id, credDefId, ethrIssuerId, revRegDef) + const { id } = await createRevocationRegistryDefinition( + revocationRegistry, + issuerAddress, + ethrIssuerId, + credDefId, + ) const revocationRegistryEntryParams: CreateRevocationEntryParams = { currentAccumulator: '0x20', @@ -403,4 +354,118 @@ describe('RevocationRegistry', function () { .withArgs(notRevRegDefIssuerId) }) }) + + describe('Endorse/Resolve Revocation Registry Entry with did:ethr Issuer', function () { + it('Should endorse and resolve Revocation Registry Entry with did:ethr', async function () { + const { id } = await createRevocationRegistryDefinitionSigned( + revocationRegistry, + testActorAddress, + issuerIdSigned, + credDefIdSigned, + ) + + const revocationRegistryEntryParams: CreateRevocationEntryParams = { + currentAccumulator: '0x20', + prevAccumulator: '0x', + issued: [2, 3], + revoked: [0, 1], + timestamp: 1731067598, + } + + const revocationRegistryEntry = createRevocationRegistryEntryObject(revocationRegistryEntryParams) + + const revRegEntrySignature = revocationRegistry.signCreateRevRegEntryEndorsementData( + testActorAddress, + testActorPrivateKey, + id, + issuerIdSigned, + revocationRegistryEntry, + ) + + await revocationRegistry.createRevocationRegistryEntrySigned( + testActorAddress, + id, + issuerIdSigned, + revocationRegistryEntry, + revRegEntrySignature, + ) + + const entries = await revocationRegistry.fetchAllRevocationEntries(id) + expect(entries[0]).to.be.deep.equal(revocationRegistryEntryParams) + }) + + it('Should fail if Revocation Registry Definition Entry is being endorsed with not owned Issuer DID', async function () { + const { id } = await createRevocationRegistryDefinitionSigned( + revocationRegistry, + testActorAddress, + issuerIdSigned, + credDefIdSigned, + ) + + const revocationRegistryEntryParams: CreateRevocationEntryParams = { + currentAccumulator: '0x20', + prevAccumulator: '0x', + issued: [2, 3], + revoked: [0, 1], + timestamp: 1731067598, + } + + const revocationRegistryEntry = createRevocationRegistryEntryObject(revocationRegistryEntryParams) + + const revRegEntrySignature = revocationRegistry.signCreateRevRegEntryEndorsementData( + testAccounts.trustee2.account.address, + testActorPrivateKey, + id, + issuerIdSigned, + revocationRegistryEntry, + ) + + await expect( + revocationRegistry.createRevocationRegistryEntrySigned( + testAccounts.trustee2.account.address, + id, + issuerIdSigned, + revocationRegistryEntry, + revRegEntrySignature, + ), + ).to.be.revertedWithCustomError(revocationRegistry.baseInstance, DidErrors.NotIdentityOwner) + }) + + it('Should fail if Revocation Registry Entry is being endorsed with invalid signature', async function () { + const { id } = await createRevocationRegistryDefinitionSigned( + revocationRegistry, + testActorAddress, + issuerIdSigned, + credDefIdSigned, + ) + + const revocationRegistryEntryParams: CreateRevocationEntryParams = { + currentAccumulator: '0x20', + prevAccumulator: '0x', + issued: [2, 3], + revoked: [0, 1], + timestamp: 1731067598, + } + + const revocationRegistryEntry = createRevocationRegistryEntryObject(revocationRegistryEntryParams) + + const revRegEntrySignature = revocationRegistry.signCreateRevRegEntryEndorsementData( + testAccounts.trustee2.account.address, + testActorPrivateKey, + 'invalid signature id', + issuerIdSigned, + revocationRegistryEntry, + ) + + await expect( + revocationRegistry.createRevocationRegistryEntrySigned( + testAccounts.trustee2.account.address, + id, + issuerIdSigned, + revocationRegistryEntry, + revRegEntrySignature, + ), + ).to.be.revertedWithCustomError(revocationRegistry.baseInstance, DidErrors.NotIdentityOwner) + }) + }) }) diff --git a/smart_contracts/test/utils/contract-helpers.ts b/smart_contracts/test/utils/contract-helpers.ts index bd985934..fd8d3c82 100644 --- a/smart_contracts/test/utils/contract-helpers.ts +++ b/smart_contracts/test/utils/contract-helpers.ts @@ -17,7 +17,13 @@ import { UpgradeControl, ValidatorControl, } from '../../contracts-ts' -import { Contract, createBaseDidDocument, createCredentialDefinitionObject, createSchemaObject } from '../../utils' +import { + Contract, + createBaseDidDocument, + createCredentialDefinitionObject, + createRevocationRegistryDefinitionObject, + createSchemaObject, +} from '../../utils' import { getTestAccounts, ZERO_ADDRESS } from './test-entities' export const testActorAddress = '0x2036C6CD85692F0Fb2C26E6c6B2ECed9e4478Dfd' @@ -182,6 +188,71 @@ export async function createCredentialDefinition( return { id, credDef } } +export async function createCredentialDefinitionSigned( + credentialDefinitionRegistry: CredentialDefinitionRegistry, + identity: string, + issuerId: string, + schemaId: string, +) { + const { id, credDef } = createCredentialDefinitionObject({ issuerId, schemaId }) + const signature = credentialDefinitionRegistry.signCreateCredDefEndorsementData( + identity, + testActorPrivateKey, + id, + issuerId, + schemaId, + credDef, + ) + + await credentialDefinitionRegistry.createCredentialDefinitionSigned( + testActorAddress, + id, + issuerId, + schemaId, + credDef, + signature, + ) + + return { id, credDef } +} + +export async function createRevocationRegistryDefinition( + revocationRegistry: RevocationRegistry, + identity: string, + issuerId: string, + credDefId: string, +) { + const { id, revRegDef } = createRevocationRegistryDefinitionObject({ issuerId, credDefId }) + await revocationRegistry.createRevocationRegistryDefinition(identity, id, credDefId, issuerId, revRegDef) + return { id, revRegDef } +} + +export async function createRevocationRegistryDefinitionSigned( + revocationRegistry: RevocationRegistry, + identity: string, + issuerId: string, + credDefId: string, +) { + const { id, revRegDef } = createRevocationRegistryDefinitionObject({ issuerId, credDefId }) + const signature = revocationRegistry.signCreateRevRegDefEndorsementData( + identity, + testActorPrivateKey, + id, + credDefId, + issuerId, + revRegDef, + ) + await revocationRegistry.createRevocationRegistryDefinitionSigned( + identity, + id, + credDefId, + issuerId, + revRegDef, + signature, + ) + return { id, revRegDef } +} + function testableContractMixin Contract>(Base: T) { return class extends Base { public get baseInstance() { diff --git a/smart_contracts/utils/entity-factories.ts b/smart_contracts/utils/entity-factories.ts index 976b6eca..8f0f76fd 100644 --- a/smart_contracts/utils/entity-factories.ts +++ b/smart_contracts/utils/entity-factories.ts @@ -4,7 +4,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - import { RevocationRegistryEntryStruct } from '../typechain-types/contracts/anoncreds/RevocationRegistry' export function createBaseDidDocument(did: string, key?: any) { diff --git a/vdr/src/contracts/anoncreds/revocation_registry.rs b/vdr/src/contracts/anoncreds/revocation_registry.rs index de5221c2..f771b5b8 100644 --- a/vdr/src/contracts/anoncreds/revocation_registry.rs +++ b/vdr/src/contracts/anoncreds/revocation_registry.rs @@ -36,6 +36,7 @@ const METHOD_CREATE_REVOCATION_REGISTRY_ENTRY: &str = "createRevocationRegistryE const METHOD_RESOLVE_REVOCATION_REGISTRY_DEFINITION: &str = "resolveRevocationRegistryDefinition"; const METHOD_CREATE_REVOCATION_REGISTRY_DEFINITION_SIGNED: &str = "createRevocationRegistryDefinitionSigned"; +const METHOD_CREATE_REVOCATION_REGISTRY_ENTRY_SIGNED: &str = "createRevocationRegistryEntrySigned"; const EVENT_REV_REG_ENTRY_CREATED: &str = "RevocationRegistryEntryCreated"; @@ -109,7 +110,7 @@ pub async fn build_create_revocation_registry_entry_transaction( } /// Prepared data for endorsing creation of a new Revocation Registry Definition record -/// (RevocationRegistry.createRevocationRegistryDefinitionSigned contract method) +/// (RevocationRegistry.createRevocationRegistryEntrySigned contract method) /// /// #Params /// - `client`: [LedgerClient] - client connected to the network where contract will be executed @@ -143,6 +144,36 @@ pub async fn build_create_revocation_registry_definition_endorsing_data( .await } +/// Prepared data for endorsing creation of a new Revocation Registry Entry record +/// (RevocationRegistry.createRevocationRegistryDefinitionSigned contract method) +/// +/// #Params +/// - `client`: [LedgerClient] - client connected to the network where contract will be executed +/// - `revocation_registry_entry`: [RevocationRegistryEntry] - object matching to the specification - `` +/// +/// #Returns +/// data: [TransactionEndorsingData] - transaction endorsement data to sign +#[logfn(Info)] +#[logfn_inputs(Debug)] +pub async fn build_create_revocation_registry_entry_endorsing_data( + client: &LedgerClient, + revocation_registry_entry: &RevocationRegistryEntry, +) -> VdrResult { + revocation_registry_entry.validate()?; + let identity = Address::try_from(&revocation_registry_entry.issuer_id)?; + + TransactionEndorsingDataBuilder::new() + .set_contract(CONTRACT_NAME) + .set_identity(&identity) + .set_method(METHOD_CREATE_REVOCATION_REGISTRY_ENTRY) + .set_endorsing_method(METHOD_CREATE_REVOCATION_REGISTRY_ENTRY_SIGNED) + .add_param(&revocation_registry_entry.rev_reg_def_id)? + .add_param(&revocation_registry_entry.issuer_id.without_network()?)? + .add_param(&revocation_registry_entry.rev_reg_entry_data)? + .build(client) + .await +} + /// Build a transaction to resolve an existing Revocation Registry Definition record by the given id /// (RevocationRegistry.resolveRevocationRegistryDefinition contract method) /// @@ -258,11 +289,6 @@ pub async fn resolve_revocation_registry_status_list( let mut revocation_list: Vec = vec![0; rev_reg_def.value.max_cred_num.try_into().unwrap()]; - // Set all `issuer` indexes to 0 (not revoked) - for issue in delta.issued { - revocation_list[issue as usize] = RevocationState::Active as u32 - } - // Set all `revoked` indexes to 1 (revoked) for revocation in delta.revoked { revocation_list[revocation as usize] = RevocationState::Revoked as u32 @@ -291,7 +317,19 @@ pub async fn build_latest_revocation_registry_entry_from_status_list( accumulator: String, ) -> VdrResult { let rev_reg_def = resolve_revocation_registry_definition(&client, &id).await?; - if revocation_registry_status_list.len() as u32 > rev_reg_def.value.max_cred_num { + + let status_list_length: u32 = + revocation_registry_status_list + .len() + .try_into() + .map_err(|e| { + VdrError::InvalidRevocationRegistryEntry(format!( + "Status list length too big: {}", + e + )) + })?; + + if status_list_length > rev_reg_def.value.max_cred_num { return Err(VdrError::InvalidRevocationRegistryStatusList(format!( "Revocation Status List has more elements ({}) than Revocation Registry MaxCredNum ({})", revocation_registry_status_list.len(), @@ -337,7 +375,6 @@ pub async fn fetch_revocation_delta( return Ok(None); } - //TODO: sets? vectors? let mut issued: HashSet = HashSet::new(); let mut revoked: HashSet = HashSet::new(); @@ -394,11 +431,23 @@ fn build_latest_revocation_registry_entry_data( let mut issued: Vec = Vec::new(); let mut revoked: Vec = Vec::new(); + let status_list_length: u32 = + revocation_registry_status_list + .len() + .try_into() + .map_err(|e| { + VdrError::InvalidRevocationRegistryEntry(format!( + "Status list length too big: {}", + e + )) + })?; + match previous_delta { Some(previous_delta) => { - previous_delta.validate(revocation_registry_status_list.len() as u32 - 1)?; + previous_delta.validate(status_list_length - 1)?; // Check whether the revocationStatusList entry is not included in the previous delta issued indices - for (index, entry) in (0u32..).zip(revocation_registry_status_list.iter()) { + for (index, entry) in revocation_registry_status_list.iter().enumerate() { + let index = index as u32; if RevocationState::Active == *entry && !previous_delta.issued.contains(&index) { issued.push(index); } @@ -411,7 +460,8 @@ fn build_latest_revocation_registry_entry_data( } None => { // No delta is provided, initial state, so the entire revocation status list is converted to two list of indices - for (index, entry) in (0u32..).zip(revocation_registry_status_list.iter()) { + for (index, entry) in revocation_registry_status_list.iter().enumerate() { + let index = index as u32; match entry { RevocationState::Active => issued.push(index), RevocationState::Revoked => revoked.push(index), diff --git a/vdr/wasm/src/contracts/revocation_registry.rs b/vdr/wasm/src/contracts/revocation_registry.rs index 4d8d288d..56f19569 100644 --- a/vdr/wasm/src/contracts/revocation_registry.rs +++ b/vdr/wasm/src/contracts/revocation_registry.rs @@ -76,6 +76,21 @@ impl RevocationRegistry { .map_err(JsValue::from) } + #[wasm_bindgen(js_name = buildCreateRevocationRegistryEntryEndorsingData)] + pub async fn build_create_revocation_registry_entry_endorsing_data( + client: &LedgerClientWrapper, + rev_reg_entry: RevocationRegistryEntryWrapper, + ) -> Result { + revocation_registry::build_create_revocation_registry_entry_endorsing_data( + &client.0, + &rev_reg_entry.0, + ) + .await + .as_js() + .map(TransactionEndorsingDataWrapper::from) + .map_err(JsValue::from) + } + #[wasm_bindgen(js_name = buildResolveRevocationRegistryDefinitionTransaction)] pub async fn build_resolve_revocation_registry_definition_transaction( client: &LedgerClientWrapper,