diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..9d768b7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "packages/did-eth-registry/lib/forge-std"] + path = packages/did-eth-registry/lib/forge-std + url = https://github.com/foundry-rs/forge-std + branch = v1.6.1 diff --git a/packages/did-eth-registry/.devcontainer/devcontainer.json b/packages/did-eth-registry/.devcontainer/devcontainer.json index 8d96444..13dec7c 100644 --- a/packages/did-eth-registry/.devcontainer/devcontainer.json +++ b/packages/did-eth-registry/.devcontainer/devcontainer.json @@ -7,6 +7,13 @@ "context": "..", // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. "dockerfile": "../Dockerfile" + }, + "customizations": { + "vscode": { + "extensions": [ + "JuanBlanco.solidity" + ] + } } // Features to add to the dev container. More info: https://containers.dev/features. diff --git a/packages/did-eth-registry/.dockerignore b/packages/did-eth-registry/.dockerignore index fce62a7..4a52d8f 100644 --- a/packages/did-eth-registry/.dockerignore +++ b/packages/did-eth-registry/.dockerignore @@ -1,7 +1,5 @@ -.git/ cache/ artifacts/ -lib/ build/ typechain/ typechain-types/ diff --git a/packages/did-eth-registry/.vscode/extensions.json b/packages/did-eth-registry/.vscode/extensions.json new file mode 100644 index 0000000..267d14a --- /dev/null +++ b/packages/did-eth-registry/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "esbenp.prettier-vscode", + "juanblanco.solidity" + ] +} \ No newline at end of file diff --git a/packages/did-eth-registry/.vscode/tasks.json b/packages/did-eth-registry/.vscode/tasks.json new file mode 100644 index 0000000..5b09315 --- /dev/null +++ b/packages/did-eth-registry/.vscode/tasks.json @@ -0,0 +1,106 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "preinstall", + "type": "shell", + "command": "forge install", + "options": { + "cwd": "${workspaceFolder}" + }, + "group": { + "kind": "build" + } + }, + { + "label": "install", + "type": "shell", + "command": "yarn install --frozen-lockfile", + "options": { + "cwd": "${workspaceFolder}" + }, + "dependsOn": "preinstall", + "group": { + "kind": "build" + } + }, + { + "label": "prettier", + "type": "shell", + "command": "yarn prettier:check", + "options": { + "cwd": "${workspaceFolder}" + }, + "dependsOn": "install", + "group": { + "kind": "build" + } + }, + { + "label": "lint", + "type": "shell", + "command": "yarn lint", + "options": { + "cwd": "${workspaceFolder}" + }, + "dependsOn": "prettier", + "group": { + "kind": "build" + } + }, + { + "label": "build", + "type": "shell", + "command": "forge build --sizes", + "options": { + "cwd": "${workspaceFolder}" + }, + "dependsOn": "lint", + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "test", + "type": "shell", + "command": "forge test -vvv", + "options": { + "cwd": "${workspaceFolder}" + }, + "dependsOn": "lint", + "group": { + "kind": "test", + "isDefault": true + } + }, + { + "label": "coverage", + "type": "shell", + "command": "forge coverage", + "options": { + "cwd": "${workspaceFolder}" + }, + "dependsOn": "lint", + "group": { + "kind": "test", + "isDefault": false + } + }, + { + "label": "gas", + "type": "shell", + "command": "forge test --gas-report", + "options": { + "cwd": "${workspaceFolder}" + }, + "dependsOn": "lint", + "group": { + "kind": "test", + "isDefault": false + } + } + ] +} \ No newline at end of file diff --git a/packages/did-eth-registry/Dockerfile b/packages/did-eth-registry/Dockerfile index 50609b9..f6c2118 100644 --- a/packages/did-eth-registry/Dockerfile +++ b/packages/did-eth-registry/Dockerfile @@ -61,7 +61,8 @@ COPY --chown=did:did . . RUN yarn install --frozen-lockfile RUN yarn prettier:check RUN yarn lint -RUN yarn build -RUN yarn test +RUN forge test -v +RUN forge geiger --check contracts/*.sol +RUN forge coverage USER did diff --git a/packages/did-eth-registry/README.md b/packages/did-eth-registry/README.md index db7a795..29f074e 100644 --- a/packages/did-eth-registry/README.md +++ b/packages/did-eth-registry/README.md @@ -304,6 +304,30 @@ needed. The list of delegateTypes to include is still to be determined. Iterate through `DIDAttributeChanged` events for service entries, encrypted public keys, and other public names. The attribute names are still to be determined. +## Quick Start + +### Submodules + +First, init submodules from the project root + +```bash +$ git submodule update --recursive --init -f +``` + +### Registry Development + +This contract supports containerized development. From Visual Studio Code Remote Containers extension + +`Reopen in Container` + +or + +Command line build using docker + +```bash +$ docker build packages/did-eth-registry -t did-eth:1 +``` + ## Deploy contract First run, @@ -324,7 +348,8 @@ Once this funding transaction is confirmed, simply send the `rawTx` to the same ## Testing the Contracts ```bash -yarn install -yarn build -yarn test +$ yarn install --frozen-lockfile +$ yarn prettier:check +$ yarn lint +$ forge test -v ``` diff --git a/packages/did-eth-registry/contracts/EthereumDIDRegistry.sol b/packages/did-eth-registry/contracts/EthereumDIDRegistry.sol index aa86ea1..db6966a 100644 --- a/packages/did-eth-registry/contracts/EthereumDIDRegistry.sol +++ b/packages/did-eth-registry/contracts/EthereumDIDRegistry.sol @@ -44,9 +44,9 @@ contract EthereumDIDRegistry { uint8 sigV, bytes32 sigR, bytes32 sigS, - bytes32 hash + bytes32 digest ) internal returns (address) { - address signer = ecrecover(hash, sigV, sigR, sigS); + address signer = ecrecover(digest, sigV, sigR, sigS); require(signer == identityOwner(identity), "bad_signature"); nonce[signer]++; return signer; @@ -82,7 +82,7 @@ contract EthereumDIDRegistry { bytes32 sigS, address newOwner ) public { - bytes32 hash = keccak256( + bytes32 digest = keccak256( abi.encodePacked( bytes1(0x19), bytes1(0), @@ -93,7 +93,7 @@ contract EthereumDIDRegistry { newOwner ) ); - changeOwner(identity, checkSignature(identity, sigV, sigR, sigS, hash), newOwner); + changeOwner(identity, checkSignature(identity, sigV, sigR, sigS, digest), newOwner); } function addDelegate( @@ -126,7 +126,7 @@ contract EthereumDIDRegistry { address delegate, uint256 validity ) public { - bytes32 hash = keccak256( + bytes32 digest = keccak256( abi.encodePacked( bytes1(0x19), bytes1(0), @@ -139,7 +139,7 @@ contract EthereumDIDRegistry { validity ) ); - addDelegate(identity, checkSignature(identity, sigV, sigR, sigS, hash), delegateType, delegate, validity); + addDelegate(identity, checkSignature(identity, sigV, sigR, sigS, digest), delegateType, delegate, validity); } function revokeDelegate( @@ -169,7 +169,7 @@ contract EthereumDIDRegistry { bytes32 delegateType, address delegate ) public { - bytes32 hash = keccak256( + bytes32 digest = keccak256( abi.encodePacked( bytes1(0x19), bytes1(0), @@ -181,7 +181,7 @@ contract EthereumDIDRegistry { delegate ) ); - revokeDelegate(identity, checkSignature(identity, sigV, sigR, sigS, hash), delegateType, delegate); + revokeDelegate(identity, checkSignature(identity, sigV, sigR, sigS, digest), delegateType, delegate); } function setAttribute( @@ -213,7 +213,7 @@ contract EthereumDIDRegistry { bytes memory value, uint256 validity ) public { - bytes32 hash = keccak256( + bytes32 digest = keccak256( abi.encodePacked( bytes1(0x19), bytes1(0), @@ -226,7 +226,7 @@ contract EthereumDIDRegistry { validity ) ); - setAttribute(identity, checkSignature(identity, sigV, sigR, sigS, hash), name, value, validity); + setAttribute(identity, checkSignature(identity, sigV, sigR, sigS, digest), name, value, validity); } function revokeAttribute( @@ -255,7 +255,7 @@ contract EthereumDIDRegistry { bytes32 name, bytes memory value ) public { - bytes32 hash = keccak256( + bytes32 digest = keccak256( abi.encodePacked( bytes1(0x19), bytes1(0), @@ -267,6 +267,6 @@ contract EthereumDIDRegistry { value ) ); - revokeAttribute(identity, checkSignature(identity, sigV, sigR, sigS, hash), name, value); + revokeAttribute(identity, checkSignature(identity, sigV, sigR, sigS, digest), name, value); } } diff --git a/packages/did-eth-registry/foundry.toml b/packages/did-eth-registry/foundry.toml index 556ed04..07e9aa4 100644 --- a/packages/did-eth-registry/foundry.toml +++ b/packages/did-eth-registry/foundry.toml @@ -1,6 +1,6 @@ [profile.default] src = "contracts" out = "out" -libs = ["node_modules"] +libs = ["node_modules", "lib"] # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/packages/did-eth-registry/lib/forge-std b/packages/did-eth-registry/lib/forge-std new file mode 160000 index 0000000..1d9650e --- /dev/null +++ b/packages/did-eth-registry/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 1d9650e951204a0ddce9ff89c32f1997984cef4d diff --git a/packages/did-eth-registry/scripts/submitDeployTxs.js b/packages/did-eth-registry/scripts/submitDeployTxs.js index c7dbe80..0869c6e 100644 --- a/packages/did-eth-registry/scripts/submitDeployTxs.js +++ b/packages/did-eth-registry/scripts/submitDeployTxs.js @@ -5,14 +5,13 @@ const { Transaction } = require('ethereumjs-tx') const EthUtils = require('ethereumjs-util') const ls = require('ls') const path = require('path') -const ethers = require('ethers') const gasLimits = { EthereumDIDRegistry: 2811144, // If this value needs to be recalculated, it can be done by deploying the rawTx once and looking at gasUsed in the receipt } const generateDeployTx = (code, name) => { - console.log("go gen.") + console.log('go gen.') const rawTx = { nonce: 0, gasPrice: 100000000000, // 100 Gwei @@ -21,7 +20,7 @@ const generateDeployTx = (code, name) => { data: code, v: 27, r: '0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798', - s: '0x0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + s: '0x0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', } const tx = new Transaction(rawTx) const res = { diff --git a/packages/did-eth-registry/test/EthereumDIDRegistry.t.sol b/packages/did-eth-registry/test/EthereumDIDRegistry.t.sol new file mode 100644 index 0000000..810faf3 --- /dev/null +++ b/packages/did-eth-registry/test/EthereumDIDRegistry.t.sol @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.15; + +import { Test } from "forge-std/Test.sol"; +import { VmSafe } from "forge-std/Vm.sol"; + +import { EthereumDIDRegistry } from "../contracts/EthereumDIDRegistry.sol"; +import { VmDigest } from "./VmDigest.sol"; + +contract EthereumDIDRegistryTest is Test { + event DIDOwnerChanged(address indexed identity, address owner, uint256 previousChange); + + EthereumDIDRegistry public registry; + + bytes internal constant PRIVATE_KEY = hex"a285ab66393c5fdda46d6fbad9e27fafd438254ab72ad5acb681a0e9f20f5d7b"; + address internal constant SIGNER = 0x2036C6CD85692F0Fb2C26E6c6B2ECed9e4478Dfd; + + bytes internal constant PRIVATE_KEY2 = hex"a285ab66393c5fdda46d6fbad9e27fafd438254ab72ad5acb681a0e9f20f5d7a"; + address internal constant SIGNER2 = 0xEA91e58E9Fa466786726F0a947e8583c7c5B3185; + + function setUp() public { + registry = new EthereumDIDRegistry(); + } + + function testIdentityOwner() public { + address owner = address(0x123); + address result = registry.identityOwner(owner); + assertEq(result, owner, "should identify address itself"); + } + + function testChangeOwner() public { + address owner = address(0x123); + address newOwner = address(0x456); + vm.prank(owner); + registry.changeOwner(owner, newOwner); + assertEq(registry.identityOwner(owner), newOwner, "should change owner"); + } + + function testChangeOwnerDelegate() public { + address owner = address(0x123); + address newOwner = address(0x456); + address newOwner2 = address(0x789); + vm.prank(owner); + registry.changeOwner(owner, newOwner); + vm.prank(newOwner); + registry.changeOwner(owner, newOwner2); + assertEq(registry.identityOwner(owner), newOwner2, "should change owner twice"); + } + + function testChangeOwnerOriginalAddressIsBadActor() public { + address owner = address(0x123); + address newOwner = address(0x456); + vm.prank(owner); + registry.changeOwner(owner, newOwner); + vm.prank(owner); + vm.expectRevert("bad_actor"); + registry.changeOwner(owner, owner); + } + + function testChangeOwnerAsBadActor() public { + address owner = address(0x123); + address newOwner = address(0x456); + vm.prank(newOwner); + vm.expectRevert("bad_actor"); + registry.changeOwner(owner, newOwner); + } + + function testChangedTransactionBlock() public { + address owner = address(0x123); + address newOwner = address(0x456); + vm.prank(owner); + registry.changeOwner(owner, newOwner); + assertEq(registry.changed(owner), block.number, "should record block number"); + } + + function testExpectDIDOwnerChangedEvent() public { + address owner = address(0x123); + address newOwner = address(0x456); + vm.expectEmit(); + emit DIDOwnerChanged(owner, newOwner, 0); + vm.prank(owner); + registry.changeOwner(owner, newOwner); + } + + function testChangeOwnerSignedMapping() public { + bytes memory message = abi.encodePacked("changeOwner", SIGNER2); + (uint8 v, bytes32 r, bytes32 s) = signData(SIGNER, address(registry), PRIVATE_KEY, message); + registry.changeOwnerSigned(SIGNER, v, r, s, SIGNER2); + + address owner = registry.owners(SIGNER); + assertEq(SIGNER2, owner, "signed change ownership expected"); + } + + function testChangeOwnerSignedIncrementNonce() public { + bytes memory message = abi.encodePacked("changeOwner", SIGNER2); + (uint8 v, bytes32 r, bytes32 s) = signData(SIGNER, address(registry), PRIVATE_KEY, message); + registry.changeOwnerSigned(SIGNER, v, r, s, SIGNER2); + + uint256 nonce = registry.nonce(SIGNER); + assertEq(1, nonce, "nonce should be incremented"); + } + + function testChangeOwnerSignedTransactionBlock() public { + bytes memory message = abi.encodePacked("changeOwner", SIGNER2); + (uint8 v, bytes32 r, bytes32 s) = signData(SIGNER, address(registry), PRIVATE_KEY, message); + registry.changeOwnerSigned(SIGNER, v, r, s, SIGNER2); + + uint256 blockNumber = registry.changed(SIGNER); + assertEq(block.number, blockNumber, "should record block number"); + } + + function testChangeOwnerSignedEventEmit() public { + vm.expectEmit(); + emit DIDOwnerChanged(SIGNER, SIGNER2, 0); + bytes memory message = abi.encodePacked("changeOwner", SIGNER2); + (uint8 v, bytes32 r, bytes32 s) = signData(SIGNER, address(registry), PRIVATE_KEY, message); + registry.changeOwnerSigned(SIGNER, v, r, s, SIGNER2); + } + + function testChangeOwnerSignedOriginalOwnerBadSignature() public { + vm.prank(SIGNER); + registry.changeOwner(SIGNER, SIGNER2); + + bytes memory digestMessage = abi.encodePacked("changeOwner", SIGNER); + (uint8 v, bytes32 r, bytes32 s) = signData(SIGNER, address(registry), PRIVATE_KEY, digestMessage); + vm.expectRevert("bad_signature"); + registry.changeOwnerSigned(SIGNER, v, r, s, SIGNER); + } + + function testChangeOwnerSignedBadSignature() public { + bytes memory digestMessage = abi.encodePacked("changeOwner", SIGNER); + (uint8 v, bytes32 r, bytes32 s) = signData(SIGNER, address(registry), PRIVATE_KEY2, digestMessage); + vm.expectRevert("bad_signature"); + registry.changeOwnerSigned(SIGNER, v, r, s, SIGNER); + } + + function testChangeOwnerSignedWrongSignature() public { + bytes memory digestMessage = abi.encodePacked("changeOwner", SIGNER); + (uint8 v, bytes32 r, bytes32 s) = signData(SIGNER2, address(registry), PRIVATE_KEY, digestMessage); + vm.expectRevert("bad_signature"); + registry.changeOwnerSigned(SIGNER, v, r, s, SIGNER); + } + + function testChangeOwnerSignedOwnerNonceBadSignature() public { + bytes memory digestMessage = abi.encodePacked("changeOwner", SIGNER2); + (uint8 v, bytes32 r, bytes32 s) = VmDigest.signData( + vm, + SIGNER, + address(registry), + PRIVATE_KEY, + digestMessage, + 0x111 + ); + vm.expectRevert("bad_signature"); + registry.changeOwnerSigned(SIGNER, v, r, s, SIGNER2); + } + + /** + * @dev Sign data with private key + * @param _identity Identity address + * @param _registry DID Registry address + * @param _privateKey Private key + * @param _message Digest message + * @return v Signature v + * @return r Signature r + * @return s Signature s + */ + function signData( + address _identity, + address _registry, + bytes memory _privateKey, + bytes memory _message + ) + internal + view + returns ( + uint8 v, + bytes32 r, + bytes32 s + ) + { + address idOwner = registry.identityOwner(_identity); + uint256 ownerNonce = registry.nonce(idOwner); + return VmDigest.signData(vm, _identity, _registry, _privateKey, _message, ownerNonce); + } +} diff --git a/packages/did-eth-registry/test/EthereumDIDRegistryAttribute.t.sol b/packages/did-eth-registry/test/EthereumDIDRegistryAttribute.t.sol new file mode 100644 index 0000000..6d2830c --- /dev/null +++ b/packages/did-eth-registry/test/EthereumDIDRegistryAttribute.t.sol @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.15; + +import { VmSafe } from "forge-std/Vm.sol"; +import { Test } from "forge-std/Test.sol"; + +import { EthereumDIDRegistry } from "../contracts/EthereumDIDRegistry.sol"; + +import { VmDigest } from "./VmDigest.sol"; + +contract EthereumDIDRegistryDelegateTest is Test { + event DIDAttributeChanged( + address indexed identity, + bytes32 name, + bytes value, + uint256 validTo, + uint256 previousChange + ); + + EthereumDIDRegistry public registry; + + bytes internal constant PRIVATE_KEY = hex"a285ab66393c5fdda46d6fbad9e27fafd438254ab72ad5acb681a0e9f20f5d7b"; + address internal constant SIGNER = 0x2036C6CD85692F0Fb2C26E6c6B2ECed9e4478Dfd; + + bytes internal constant PRIVATE_KEY2 = hex"a285ab66393c5fdda46d6fbad9e27fafd438254ab72ad5acb681a0e9f20f5d7a"; + address internal constant SIGNER2 = 0xEA91e58E9Fa466786726F0a947e8583c7c5B3185; + + function setUp() public { + registry = new EthereumDIDRegistry(); + } + + function testSetAttributeChangedTransactionBlock() public { + vm.prank(SIGNER); + registry.setAttribute(SIGNER, "key", "value", 1 weeks); + assertEq(registry.changed(SIGNER), block.number); + } + + function testSetAttributeExpectAttributeChangedEvent() public { + vm.expectEmit(); + emit DIDAttributeChanged(SIGNER, "key", "value", 1 weeks + 1, 0); + vm.prank(SIGNER); + registry.setAttribute(SIGNER, "key", "value", 1 weeks); + } + + function testSetAttributeExpectAttributeChangeOwner() public { + vm.prank(SIGNER); + registry.changeOwner(SIGNER, SIGNER2); + vm.expectEmit(); + emit DIDAttributeChanged(SIGNER, "key", "value", 1 weeks + 1, block.number); + vm.prank(SIGNER2); + registry.setAttribute(SIGNER, "key", "value", 1 weeks); + } + + function testSetAttributeExpectAttributeChangeOwnerAsBadActor() public { + vm.prank(SIGNER); + registry.changeOwner(SIGNER, SIGNER2); + vm.prank(SIGNER); + vm.expectRevert("bad_actor"); + registry.setAttribute(SIGNER, "key", "value", 1 weeks); + } + + function testSetAttributeSpoofingOwnerAsBadActor() public { + vm.prank(SIGNER2); + vm.expectRevert("bad_actor"); + registry.setAttribute(SIGNER, "key", "value", 1 weeks); + } + + function testSetAttributeSignedChangedTransactionBlock() public { + (uint8 v, bytes32 r, bytes32 s) = signAttribute(SIGNER, address(registry), "key", "value", PRIVATE_KEY); + registry.setAttributeSigned(SIGNER, v, r, s, "key", "value", 1 weeks); + assertEq(registry.changed(SIGNER), block.number); + } + + function testSetAttributeSignedChangeOwnerExpectDIDAttributeChanged() public { + vm.prank(SIGNER); + registry.changeOwner(SIGNER, SIGNER2); + vm.expectEmit(); + emit DIDAttributeChanged(SIGNER, "key", "value", 1 weeks + 1, block.number); + (uint8 v, bytes32 r, bytes32 s) = signAttribute(SIGNER, address(registry), "key", "value", PRIVATE_KEY2); + registry.setAttributeSigned(SIGNER, v, r, s, "key", "value", 1 weeks); + } + + function testSetAttributeSignedSpoofingSignature() public { + (uint8 v, bytes32 r, bytes32 s) = signAttribute(SIGNER, address(registry), "key", "value", PRIVATE_KEY2); + vm.expectRevert("bad_signature"); + registry.setAttributeSigned(SIGNER, v, r, s, "key", "value", 1 weeks); + } + + function testSetAttributeSignedWrongSignatureKey() public { + (uint8 v, bytes32 r, bytes32 s) = signAttribute(SIGNER, address(registry), "key1", "value", PRIVATE_KEY); + vm.expectRevert("bad_signature"); + registry.setAttributeSigned(SIGNER, v, r, s, "key", "value", 1 weeks); + } + + function testSetAttributeSignedWrongNonce() public { + bytes memory message = abi.encodePacked(bytes("setAttribute"), "key", "value", uint256(1 weeks)); + address idOwner = registry.identityOwner(SIGNER); + uint256 ownerNonce = registry.nonce(idOwner) + 1; // not expected + (uint8 v, bytes32 r, bytes32 s) = VmDigest.signData( + vm, + SIGNER, + address(registry), + PRIVATE_KEY, + message, + ownerNonce + ); + vm.expectRevert("bad_signature"); + registry.setAttributeSigned(SIGNER, v, r, s, "key", "value", 1 weeks); + } + + function testRevokeAttribute() public { + uint256 startBlock = block.number; + vm.prank(SIGNER); + registry.setAttribute(SIGNER, "key", "value", 1 weeks); + assertEq(registry.changed(SIGNER), block.number); + vm.roll(block.number + 100); + assertNotEq(startBlock, block.number); + vm.prank(SIGNER); + registry.revokeAttribute(SIGNER, "key", "value"); + assertEq(registry.changed(SIGNER), block.number); + } + + function testRevokeAttributeExpectDIDAttributeChanged() public { + vm.prank(SIGNER); + registry.setAttribute(SIGNER, "key", "value", 1 weeks); + vm.expectEmit(); + emit DIDAttributeChanged(SIGNER, "key", "value", 0, block.number); + vm.prank(SIGNER); + registry.revokeAttribute(SIGNER, "key", "value"); + } + + function testRevokeAttributeChangeOwnerExpectDIDAttributeChanged() public { + vm.prank(SIGNER); + registry.setAttribute(SIGNER, "key", "value", 1 weeks); + vm.prank(SIGNER); + registry.changeOwner(SIGNER, SIGNER2); + vm.expectEmit(); + emit DIDAttributeChanged(SIGNER, "key", "value", 0, block.number); + vm.prank(SIGNER2); + registry.revokeAttribute(SIGNER, "key", "value"); + } + + function testRevokeAttributeAsBadActor() public { + vm.prank(SIGNER); + registry.setAttribute(SIGNER, "key", "value", 1 weeks); + vm.expectRevert("bad_actor"); + vm.prank(SIGNER2); + registry.revokeAttribute(SIGNER, "key", "value"); + } + + function testRevokeAttributeChangeOwnerAsBadActor() public { + vm.prank(SIGNER); + registry.setAttribute(SIGNER, "key", "value", 1 weeks); + vm.prank(SIGNER); + registry.changeOwner(SIGNER, SIGNER2); + vm.expectRevert("bad_actor"); + vm.prank(SIGNER); + registry.revokeAttribute(SIGNER, "key", "value"); + } + + function testRevokeAttributeSignedExpectDIDAttributeChanged() public { + vm.prank(SIGNER); + registry.setAttribute(SIGNER, "key", "value", 1 weeks); + (uint8 v, bytes32 r, bytes32 s) = signRevoke(SIGNER, address(registry), "key", "value", PRIVATE_KEY); + vm.expectEmit(); + emit DIDAttributeChanged(SIGNER, "key", "value", 0, block.number); + registry.revokeAttributeSigned(SIGNER, v, r, s, "key", "value"); + } + + function testRevokeAttributeSignedChangedBlockNumber() public { + uint256 startBlock = block.number; + vm.prank(SIGNER); + registry.setAttribute(SIGNER, "key", "value", 1 weeks); + (uint8 v, bytes32 r, bytes32 s) = signRevoke(SIGNER, address(registry), "key", "value", PRIVATE_KEY); + vm.roll(block.number + 100); + assertNotEq(startBlock, block.number); + registry.revokeAttributeSigned(SIGNER, v, r, s, "key", "value"); + assertEq(registry.changed(SIGNER), block.number); + } + + function testRevokeAttributeSignedWithBadSignature() public { + vm.prank(SIGNER); + registry.setAttribute(SIGNER, "key", "value", 1 weeks); + (uint8 v, bytes32 r, bytes32 s) = signRevoke(SIGNER, address(registry), "key", "value", PRIVATE_KEY2); + vm.expectRevert("bad_signature"); + registry.revokeAttributeSigned(SIGNER, v, r, s, "key", "value"); + } + + function testRevokeAttributeSignedWithWrongSignature() public { + vm.prank(SIGNER); + registry.setAttribute(SIGNER, "key", "value", 1 weeks); + (uint8 v, bytes32 r, bytes32 s) = signRevoke(SIGNER, address(registry), "key2", "value", PRIVATE_KEY); + vm.expectRevert("bad_signature"); + registry.revokeAttributeSigned(SIGNER, v, r, s, "key", "value"); + } + + function testRevokeAttributeSignedChangeOriginalOwnerBadSignature() public { + vm.prank(SIGNER); + registry.setAttribute(SIGNER, "key", "value", 1 weeks); + vm.prank(SIGNER); + registry.changeOwner(SIGNER, SIGNER2); + (uint8 v, bytes32 r, bytes32 s) = signRevoke(SIGNER, address(registry), "key2", "value", PRIVATE_KEY); + vm.expectRevert("bad_signature"); + registry.revokeAttributeSigned(SIGNER, v, r, s, "key", "value"); + } + + function testRevokeAttributeSignedChangedOwnerExpectDIDAttributeChanged() public { + vm.prank(SIGNER); + registry.setAttribute(SIGNER, "key", "value", 1 weeks); + vm.prank(SIGNER); + registry.changeOwner(SIGNER, SIGNER2); + (uint8 v, bytes32 r, bytes32 s) = signRevoke(SIGNER, address(registry), "key", "value", PRIVATE_KEY2); + vm.expectEmit(); + emit DIDAttributeChanged(SIGNER, "key", "value", 0, block.number); + registry.revokeAttributeSigned(SIGNER, v, r, s, "key", "value"); + } + + /** + * @dev Sign data with a private key + * @param _identity Identity address + * @param _registry DID Registry address + * @param _privateKey Private key + * @param _message Message to sign + * @return v Signature v + * @return r Signature r + * @return s Signature s + */ + function signData( + address _identity, + address _registry, + bytes memory _privateKey, + bytes memory _message + ) + internal + view + returns ( + uint8 v, + bytes32 r, + bytes32 s + ) + { + address idOwner = registry.identityOwner(_identity); + uint256 ownerNonce = registry.nonce(idOwner); + return VmDigest.signData(vm, _identity, _registry, _privateKey, _message, ownerNonce); + } + + /** + * @dev Sign attribute with a private key + * @param _identity Identity address + * @param _registry DID Registry address + * @param _key Attribute key + * @param _value Attribute value + * @param _privateKey Private key + * @return v Signature v + * @return r Signature r + * @return s Signature s + */ + function signAttribute( + address _identity, + address _registry, + bytes32 _key, + bytes memory _value, + bytes memory _privateKey + ) + internal + view + returns ( + uint8 v, + bytes32 r, + bytes32 s + ) + { + bytes memory message = abi.encodePacked(bytes("setAttribute"), _key, _value, uint256(1 weeks)); + return signData(_identity, _registry, _privateKey, message); + } + + /** + * @dev Sign revoke with a private key + * @param _identity Identity address + * @param _registry DID Registry address + * @param _key Attribute key + * @param _value Attribute value + * @param _privateKey Private key + * @return v Signature v + * @return r Signature r + * @return s Signature s + */ + function signRevoke( + address _identity, + address _registry, + bytes32 _key, + bytes memory _value, + bytes memory _privateKey + ) + internal + view + returns ( + uint8 v, + bytes32 r, + bytes32 s + ) + { + bytes memory message = abi.encodePacked(bytes("revokeAttribute"), _key, _value); + return signData(_identity, _registry, _privateKey, message); + } +} diff --git a/packages/did-eth-registry/test/EthereumDIDRegistryDelegate.t.sol b/packages/did-eth-registry/test/EthereumDIDRegistryDelegate.t.sol new file mode 100644 index 0000000..b96f043 --- /dev/null +++ b/packages/did-eth-registry/test/EthereumDIDRegistryDelegate.t.sol @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.15; + +import { VmSafe } from "forge-std/Vm.sol"; +import { Test } from "forge-std/Test.sol"; + +import { EthereumDIDRegistry } from "../contracts/EthereumDIDRegistry.sol"; + +import { VmDigest } from "./VmDigest.sol"; + +contract EthereumDIDRegistryDelegateTest is Test { + event DIDDelegateChanged( + address indexed identity, + bytes32 delegateType, + address delegate, + uint256 validTo, + uint256 previousChange + ); + + EthereumDIDRegistry public registry; + + bytes internal constant PRIVATE_KEY = hex"a285ab66393c5fdda46d6fbad9e27fafd438254ab72ad5acb681a0e9f20f5d7b"; + address internal constant SIGNER = 0x2036C6CD85692F0Fb2C26E6c6B2ECed9e4478Dfd; + + bytes internal constant PRIVATE_KEY2 = hex"a285ab66393c5fdda46d6fbad9e27fafd438254ab72ad5acb681a0e9f20f5d7a"; + address internal constant SIGNER2 = 0xEA91e58E9Fa466786726F0a947e8583c7c5B3185; + + address internal constant SIGNER3 = address(0x1111); + + function setUp() public { + registry = new EthereumDIDRegistry(); + } + + function testAddDelegate() public { + vm.prank(SIGNER); + registry.addDelegate(SIGNER, "attestor", SIGNER2, 1 days); + bool isDelegate = registry.validDelegate(SIGNER, "attestor", SIGNER2); + assertTrue(isDelegate); + } + + function testAddDelegateExpires() public { + vm.prank(SIGNER); + registry.addDelegate(SIGNER, "attestor", SIGNER2, 1 days); + assertTrue(registry.validDelegate(SIGNER, "attestor", SIGNER2)); + vm.warp(1 days + 1); + assertFalse(registry.validDelegate(SIGNER, "attestor", SIGNER2)); + } + + function testAddDelegateNotADelegateForIdentity() public { + bool isDelegate = registry.validDelegate(SIGNER, "attestor", SIGNER2); + assertFalse(isDelegate, "should not be a delegate"); + vm.prank(SIGNER); + registry.addDelegate(SIGNER, "attestor", SIGNER2, 1 days); + isDelegate = registry.validDelegate(SIGNER, "attestor", SIGNER3); + assertFalse(isDelegate, "should not be a delegate"); + } + + function testAddDelegateExpectTransactionBlock() public { + vm.prank(SIGNER); + registry.addDelegate(SIGNER, "attestor", SIGNER2, 1 days); + uint256 transactionBlock = registry.changed(SIGNER); + assertEq(block.number, transactionBlock, "should record block number"); + } + + function testAddDelegateEventEmit() public { + vm.expectEmit(); + emit DIDDelegateChanged(SIGNER, "attestor", SIGNER2, 1 days + 1, 0); + vm.prank(SIGNER); + registry.addDelegate(SIGNER, "attestor", SIGNER2, 1 days); + } + + function testAddDelegateDoesNotChangeOwner() public { + assertEq(registry.owners(SIGNER), address(0x0)); + vm.prank(SIGNER); + registry.addDelegate(SIGNER, "attestor", SIGNER2, 1 days); + assertEq(registry.owners(SIGNER), address(0x0)); + } + + function testAddDelegateAsBadActor() public { + vm.prank(SIGNER); + vm.expectRevert("bad_actor"); + registry.addDelegate(SIGNER2, "attestor", SIGNER3, 1 days); + } + + function testAddDelegateOwnerChangedBadActor() public { + vm.prank(SIGNER); + registry.changeOwner(SIGNER, SIGNER3); + assertEq(registry.owners(SIGNER), SIGNER3); + vm.expectRevert("bad_actor"); + vm.prank(SIGNER); + registry.addDelegate(SIGNER, "attestor", SIGNER2, 1 days); + } + + function testAddDelegateSigned() public { + (uint8 v, bytes32 r, bytes32 s) = signDelegate(SIGNER, SIGNER2, address(registry), PRIVATE_KEY); + registry.addDelegateSigned(SIGNER, v, r, s, "attestor", SIGNER2, 1 days); + assertTrue(registry.validDelegate(SIGNER, "attestor", SIGNER2)); + } + + function testAddDelegateSignedChangeOwnerBadSignature() public { + vm.prank(SIGNER); + registry.changeOwner(SIGNER, SIGNER3); + assertEq(registry.owners(SIGNER), SIGNER3); + (uint8 v, bytes32 r, bytes32 s) = signDelegate(SIGNER, SIGNER2, address(registry), PRIVATE_KEY); + vm.expectRevert("bad_signature"); + registry.addDelegateSigned(SIGNER, v, r, s, "attestor", SIGNER2, 1 days); + } + + function testAddDelegateSignedExpires() public { + (uint8 v, bytes32 r, bytes32 s) = signDelegate(SIGNER, SIGNER2, address(registry), PRIVATE_KEY); + registry.addDelegateSigned(SIGNER, v, r, s, "attestor", SIGNER2, 1 days); + assertTrue(registry.validDelegate(SIGNER, "attestor", SIGNER2)); + vm.warp(1 days + 1); + assertFalse(registry.validDelegate(SIGNER, "attestor", SIGNER2)); + } + + function testAddDelegateSignedBadSignatureKey() public { + (uint8 v, bytes32 r, bytes32 s) = signDelegate(SIGNER, SIGNER2, address(registry), PRIVATE_KEY); + vm.expectRevert("bad_signature"); + registry.addDelegateSigned(SIGNER2, v, r, s, "attestor", SIGNER2, 1 days); + } + + function testAddDelegateSignedWrongSignature() public { + (uint8 v, bytes32 r, bytes32 s) = signDelegate(SIGNER, SIGNER2, address(registry), PRIVATE_KEY2); + vm.expectRevert("bad_signature"); + registry.addDelegateSigned(SIGNER, v, r, s, "attestor", SIGNER2, 1 days); + } + + function testAddDelegateSignedWrongSignatureData() public { + (uint8 v, bytes32 r, bytes32 s) = signDelegate(SIGNER, SIGNER, address(registry), PRIVATE_KEY); + vm.expectRevert("bad_signature"); + registry.addDelegateSigned(SIGNER, v, r, s, "attestor", SIGNER2, 1 days); + } + + function testAddDelegateSignedChangedBlockNumber() public { + (uint8 v, bytes32 r, bytes32 s) = signDelegate(SIGNER, SIGNER2, address(registry), PRIVATE_KEY); + registry.addDelegateSigned(SIGNER, v, r, s, "attestor", SIGNER2, 1 days); + uint256 transactionBlock = registry.changed(SIGNER); + assertEq(block.number, transactionBlock, "should record block number"); + } + + function testAddDelegateSignedExpectDelegateChangedEvent() public { + (uint8 v, bytes32 r, bytes32 s) = signDelegate(SIGNER, SIGNER2, address(registry), PRIVATE_KEY); + vm.expectEmit(); + emit DIDDelegateChanged(SIGNER, "attestor", SIGNER2, 1 days + 1, 0); + registry.addDelegateSigned(SIGNER, v, r, s, "attestor", SIGNER2, 1 days); + } + + function testAddDelegateSignedExpectNonce() public { + (uint8 v, bytes32 r, bytes32 s) = signDelegate(SIGNER, SIGNER2, address(registry), PRIVATE_KEY); + registry.addDelegateSigned(SIGNER, v, r, s, "attestor", SIGNER2, 1 days); + uint256 nonce = registry.nonce(SIGNER); + assertEq(nonce, 1, "should increment nonce"); + } + + function testAddDelegateSignedNonceIncorrect() public { + bytes memory message = abi.encodePacked(bytes("addDelegate"), bytes32("attestor"), SIGNER2, uint256(1 days)); + address idOwner = registry.identityOwner(SIGNER2); + uint256 ownerNonce = registry.nonce(idOwner) + 0x1; // not expected + (uint8 v, bytes32 r, bytes32 s) = VmDigest.signData( + vm, + SIGNER, + address(registry), + PRIVATE_KEY, + message, + ownerNonce + ); + vm.expectRevert("bad_signature"); + registry.addDelegateSigned(SIGNER, v, r, s, "attestor", SIGNER2, 1 days); + } + + function testRevokeDelegate() public { + vm.prank(SIGNER); + registry.addDelegate(SIGNER, "attestor", SIGNER2, 1 days); + assertTrue(registry.validDelegate(SIGNER, "attestor", SIGNER2)); + vm.prank(SIGNER); + registry.revokeDelegate(SIGNER, "attestor", SIGNER2); + assertFalse(registry.validDelegate(SIGNER, "attestor", SIGNER2)); + } + + function testRevokeDelegateChangeOwner() public { + vm.prank(SIGNER); + registry.changeOwner(SIGNER, SIGNER3); + vm.prank(SIGNER3); + registry.addDelegate(SIGNER, "attestor", SIGNER2, 1 days); + assertTrue(registry.validDelegate(SIGNER, "attestor", SIGNER2)); + vm.prank(SIGNER3); + registry.revokeDelegate(SIGNER, "attestor", SIGNER2); + assertFalse(registry.validDelegate(SIGNER, "attestor", SIGNER2)); + } + + function testRevokeDelegateChangeOwnerAsBadActor() public { + vm.prank(SIGNER); + registry.changeOwner(SIGNER, SIGNER3); + vm.prank(SIGNER3); + registry.addDelegate(SIGNER, "attestor", SIGNER2, 1 days); + assertTrue(registry.validDelegate(SIGNER, "attestor", SIGNER2)); + vm.expectRevert("bad_actor"); + vm.prank(SIGNER); + registry.revokeDelegate(SIGNER, "attestor", SIGNER2); + } + + function testRevokeDelegateChangedTransactionBlock() public { + vm.prank(SIGNER); + registry.addDelegate(SIGNER, "attestor", SIGNER2, 1 days); + uint256 startBlock = block.number; + assertEq(block.number, registry.changed(SIGNER), "should record block number"); + vm.roll(block.number + 100); + vm.prank(SIGNER); + registry.revokeDelegate(SIGNER, "attestor", SIGNER2); + assertEq(block.number, registry.changed(SIGNER), "should record block number"); + assertNotEq(startBlock, block.number, "block number must be different across calls"); + } + + function testRevokeDelegateExpectDelegateChangedEvent() public { + vm.prank(SIGNER); + uint256 startBlock = block.number; + uint256 startTime = block.timestamp; + registry.addDelegate(SIGNER, "attestor", SIGNER2, 1 days); + vm.expectEmit(); + emit DIDDelegateChanged(SIGNER, "attestor", SIGNER2, startTime, startBlock); + vm.prank(SIGNER); + registry.revokeDelegate(SIGNER, "attestor", SIGNER2); + } + + function testRevokeDelegateBadDelegate() public { + vm.prank(SIGNER); + registry.addDelegate(SIGNER, "attestor", SIGNER2, 1 days); + vm.expectRevert("bad_actor"); + vm.prank(SIGNER2); + registry.revokeDelegate(SIGNER, "attestor", SIGNER3); + } + + function testRevokeDelegateSigned() public { + vm.prank(SIGNER); + registry.addDelegate(SIGNER, "attestor", SIGNER2, 1 days); + assertTrue(registry.validDelegate(SIGNER, "attestor", SIGNER2)); + (uint8 v, bytes32 r, bytes32 s) = signRevoke(SIGNER, SIGNER2, address(registry), PRIVATE_KEY); + registry.revokeDelegateSigned(SIGNER, v, r, s, "attestor", SIGNER2); + assertFalse(registry.validDelegate(SIGNER, "attestor", SIGNER2)); + } + + function testRevokeDelegateSignedTransactionBlockIsChanged() public { + uint256 startBlock = block.number; + vm.prank(SIGNER); + registry.addDelegate(SIGNER, "attestor", SIGNER2, 1 days); + assertEq(block.number, registry.changed(SIGNER), "add should record block number"); + vm.roll(block.number + 100); + (uint8 v, bytes32 r, bytes32 s) = signRevoke(SIGNER, SIGNER2, address(registry), PRIVATE_KEY); + registry.revokeDelegateSigned(SIGNER, v, r, s, "attestor", SIGNER2); + assertEq(block.number, registry.changed(SIGNER), "revoke should record block number"); + assertNotEq(block.number, startBlock, "block number must be different across calls"); + } + + function testRevokeDelegateSignedDelegateChangedEvent() public { + uint256 startBlock = block.number; + uint256 startTime = block.timestamp; + vm.prank(SIGNER); + registry.addDelegate(SIGNER, "attestor", SIGNER2, 1 days); + vm.expectEmit(); + emit DIDDelegateChanged(SIGNER, "attestor", SIGNER2, startTime, startBlock); + (uint8 v, bytes32 r, bytes32 s) = signRevoke(SIGNER, SIGNER2, address(registry), PRIVATE_KEY); + registry.revokeDelegateSigned(SIGNER, v, r, s, "attestor", SIGNER2); + } + + function testRevokeDelegateSignedBadSignature() public { + vm.prank(SIGNER); + registry.addDelegate(SIGNER, "attestor", SIGNER2, 1 days); + (uint8 v, bytes32 r, bytes32 s) = signRevoke(SIGNER, SIGNER2, address(registry), PRIVATE_KEY); + vm.expectRevert("bad_signature"); + registry.revokeDelegateSigned(SIGNER, v, r, s, "attestor", SIGNER3); + vm.expectRevert("bad_signature"); + registry.revokeDelegateSigned(SIGNER3, v, r, s, "attestor", SIGNER); + } + + function testRevokeDelegateSignedWrongSignatureKey() public { + vm.prank(SIGNER); + registry.addDelegate(SIGNER, "attestor", SIGNER2, 1 days); + (uint8 v, bytes32 r, bytes32 s) = signRevoke(SIGNER, SIGNER2, address(registry), PRIVATE_KEY2); + vm.expectRevert("bad_signature"); + vm.prank(SIGNER3); + registry.revokeDelegateSigned(SIGNER, v, r, s, "attestor", SIGNER2); + } + + function testRevokeDelegateSignedWrongData() public { + vm.prank(SIGNER); + registry.addDelegate(SIGNER, "attestor", SIGNER2, 1 days); + (uint8 v, bytes32 r, bytes32 s) = signRevoke(SIGNER, SIGNER, address(registry), PRIVATE_KEY); + vm.expectRevert("bad_signature"); + registry.revokeDelegateSigned(SIGNER, v, r, s, "attestor", SIGNER2); + } + + function testRevokeDelegateSignedWithIncorrectNonce() public { + vm.prank(SIGNER); + registry.addDelegate(SIGNER, "attestor", SIGNER2, 1 days); + bytes memory message = abi.encodePacked(bytes("revokeDelegate"), bytes32("attestor"), SIGNER2); + address idOwner = registry.identityOwner(SIGNER2); + uint256 ownerNonce = registry.nonce(idOwner) + 0x1; // not expected + (uint8 v, bytes32 r, bytes32 s) = VmDigest.signData( + vm, + SIGNER, + address(registry), + PRIVATE_KEY, + message, + ownerNonce + ); + vm.expectRevert("bad_signature"); + registry.revokeDelegateSigned(SIGNER, v, r, s, "attestor", SIGNER2); + } + + /** + * @dev Sign data with private key + * @param _identity Identity address + * @param _registry DID Registry address + * @param _privateKey Private key + * @param _message Message to sign + * @return v Signature v + * @return r Signature r + * @return s Signature s + */ + function signData( + address _identity, + address _registry, + bytes memory _privateKey, + bytes memory _message + ) + internal + view + returns ( + uint8 v, + bytes32 r, + bytes32 s + ) + { + address idOwner = registry.identityOwner(_identity); + uint256 ownerNonce = registry.nonce(idOwner); + return VmDigest.signData(vm, _identity, _registry, _privateKey, _message, ownerNonce); + } + + /** + * @dev Sign delegate with private key + * @param _identity Identity address + * @param _delegate Delegate address + * @param _registry DID Registry address + * @param _privateKey Private key + * @return v Signature v + * @return r Signature r + * @return s Signature s + */ + function signDelegate( + address _identity, + address _delegate, + address _registry, + bytes memory _privateKey + ) + internal + view + returns ( + uint8 v, + bytes32 r, + bytes32 s + ) + { + bytes memory message = abi.encodePacked(bytes("addDelegate"), bytes32("attestor"), _delegate, uint256(1 days)); + return signData(_identity, _registry, _privateKey, message); + } + + /** + * @dev Sign revoke with private key + * @param _identity Identity address + * @param _delegate Delegate address + * @param _registry DID Registry address + * @param _privateKey Private key + * @return v Signature v + * @return r Signature r + * @return s Signature s + */ + function signRevoke( + address _identity, + address _delegate, + address _registry, + bytes memory _privateKey + ) + internal + view + returns ( + uint8 v, + bytes32 r, + bytes32 s + ) + { + bytes memory message = abi.encodePacked(bytes("revokeDelegate"), bytes32("attestor"), _delegate); + return signData(_identity, _registry, _privateKey, message); + } +} diff --git a/packages/did-eth-registry/test/VmDigest.sol b/packages/did-eth-registry/test/VmDigest.sol new file mode 100644 index 0000000..24fd231 --- /dev/null +++ b/packages/did-eth-registry/test/VmDigest.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.15; + +import { VmSafe } from "forge-std/Vm.sol"; + +/** + * @title VmDigest + * @dev Helper contract to sign data with VmSafe interface. Not for production use! + */ +library VmDigest { + /** + * @dev Sign data with VmSafe interface. + * @param _vm VmSafe interface + * @param _identity Identity address + * @param _registry DID Registry address + * @param _privateKey Private key + * @param _digestMessage Digest message + * @param _ownerNonce Owner nonce + * @return v Signature v + * @return r Signature r + * @return s Signature s + */ + function signData( + VmSafe _vm, + address _identity, + address _registry, + bytes memory _privateKey, + bytes memory _digestMessage, + uint256 _ownerNonce + ) + internal + pure + returns ( + uint8 v, + bytes32 r, + bytes32 s + ) + { + bytes32 digest = keccak256( + abi.encodePacked(bytes1(0x19), bytes1(0), _registry, _ownerNonce, _identity, _digestMessage) + ); + (v, r, s) = _vm.sign(toUint256(_privateKey), digest); + return (v, r, s); + } + + /** + * @dev Convert bytes to uint256. This is intended for test purposes only. + * @param _bytes Bytes to convert + * @return uint256 + */ + function toUint256(bytes memory _bytes) internal pure returns (uint256) { + require(_bytes.length <= 32, "toUint256_outOfBounds"); + uint256 result; + // solhint-disable-next-line no-inline-assembly + assembly { + result := mload(add(_bytes, 0x20)) + } + return result; + } +} diff --git a/packages/did-eth-registry/test/VmDigest.t.sol b/packages/did-eth-registry/test/VmDigest.t.sol new file mode 100644 index 0000000..f8d2b23 --- /dev/null +++ b/packages/did-eth-registry/test/VmDigest.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.15; + +import { Test } from "forge-std/Test.sol"; +import { VmDigest } from "./VmDigest.sol"; + +contract EthereumDIDRegistryTest is Test { + function testToUint256() public { + bytes memory b1 = hex"10"; + bytes memory b2 = hex"1000"; + bytes memory b3 = hex"100000"; + bytes memory b4 = hex"10000000"; + bytes memory b5 = hex"1000000000"; + bytes memory b6 = hex"100000000000"; + bytes memory b7 = hex"10000000000000"; + bytes memory b8 = hex"1000000000000000"; + bytes memory b9 = hex"100000000000000000"; + bytes memory b10 = hex"10000000000000000000"; + bytes memory b11 = hex"1000000000000000000000"; + bytes memory b12 = hex"100000000000000000000000"; + bytes memory b13 = hex"10000000000000000000000000"; + bytes memory b14 = hex"1000000000000000000000000000"; + + assertEq(VmDigest.toUint256(b1), 0x1 << 252, "should convert bytes to uint256"); + assertEq(VmDigest.toUint256(b2), 0x1 << 252, "should convert bytes to uint256"); + assertEq(VmDigest.toUint256(b3), 0x1 << 252, "should convert bytes to uint256"); + assertEq(VmDigest.toUint256(b4), 0x1 << 252, "should convert bytes to uint256"); + assertEq(VmDigest.toUint256(b5), 0x1 << 252, "should convert bytes to uint256"); + assertEq(VmDigest.toUint256(b6), 0x1 << 252, "should convert bytes to uint256"); + assertEq(VmDigest.toUint256(b7), 0x1 << 252, "should convert bytes to uint256"); + assertEq(VmDigest.toUint256(b8), 0x1 << 252, "should convert bytes to uint256"); + assertEq(VmDigest.toUint256(b9), 0x1 << 252, "should convert bytes to uint256"); + assertEq(VmDigest.toUint256(b10), 0x1 << 252, "should convert bytes to uint256"); + assertEq(VmDigest.toUint256(b11), 0x1 << 252, "should convert bytes to uint256"); + assertEq(VmDigest.toUint256(b12), 0x1 << 252, "should convert bytes to uint256"); + assertEq(VmDigest.toUint256(b13), 0x1 << 252, "should convert bytes to uint256"); + assertEq(VmDigest.toUint256(b14), 0x1 << 252, "should convert bytes to uint256"); + } + + function testToUint256With32ByteValue() public { + bytes memory b32 = hex"1000000000000000000000000000000000000000000000000000000000000000"; + assertEq(VmDigest.toUint256(b32), 0x1 << 252, "should convert bytes to uint256"); + } + + function testToUint256Requires32BytesOrLess() public { + bytes memory b33 = hex"100000000000000000000000000000000000000000000000000000000000000000"; + vm.expectRevert("toUint256_outOfBounds"); + VmDigest.toUint256(b33); + } +} diff --git a/packages/did-eth-registry/test/registry.test.ts b/packages/did-eth-registry/test/registry.test.ts deleted file mode 100644 index b2c5080..0000000 --- a/packages/did-eth-registry/test/registry.test.ts +++ /dev/null @@ -1,864 +0,0 @@ -// noinspection DuplicatedCode - -import chai, { expect } from 'chai' -import chaiAsPromised from 'chai-as-promised' -import { ContractTransaction } from 'ethers' -import { Block, Log } from '@ethersproject/providers' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { - arrayify, - concat, - formatBytes32String, - hexConcat, - hexlify, - hexZeroPad, - keccak256, - parseBytes32String, - SigningKey, - toUtf8Bytes, - zeroPad, -} from 'ethers/lib/utils' -import { - DIDAttributeChangedEvent, - DIDDelegateChangedEvent, - DIDOwnerChangedEvent, - EthereumDIDRegistry, -} from '../typechain-types/EthereumDIDRegistry' - -chai.use(chaiAsPromised) - -// eslint-disable-next-line @typescript-eslint/no-var-requires -const { ethers } = require('hardhat') - -describe('ERC1056', () => { - let didReg: EthereumDIDRegistry - let identity: SignerWithAddress // = accounts[0]; - let identity2: SignerWithAddress // = accounts[1]; - let delegate: SignerWithAddress // = accounts[2]; - let delegate2: SignerWithAddress // = accounts[3]; - let delegate3: SignerWithAddress // = accounts[4]; - let badBoy: SignerWithAddress // = accounts[5]; - - before(async () => { - const Registry = await ethers.getContractFactory('EthereumDIDRegistry') - didReg = await Registry.deploy() - await didReg.deployed() - ;[identity, identity2, delegate, delegate2, delegate3, badBoy] = await ethers.getSigners() - }) - - const privateKey = arrayify('0xa285ab66393c5fdda46d6fbad9e27fafd438254ab72ad5acb681a0e9f20f5d7b') - const signerAddress = '0x2036C6CD85692F0Fb2C26E6c6B2ECed9e4478Dfd' - - const privateKey2 = arrayify('0xa285ab66393c5fdda46d6fbad9e27fafd438254ab72ad5acb681a0e9f20f5d7a') - const signerAddress2 = '0xEA91e58E9Fa466786726F0a947e8583c7c5B3185' - - async function signData( - identity: string, - signerAddress: string, - privateKeyBytes: Uint8Array, - dataBytes: Uint8Array, - nonce?: number - ) { - const _nonce = nonce || (await didReg.nonce(signerAddress)) - const paddedNonce = zeroPad(arrayify(_nonce), 32) - const dataToSign = hexConcat(['0x1900', didReg.address, paddedNonce, identity, dataBytes]) - const hash = keccak256(dataToSign) - return new SigningKey(privateKeyBytes).signDigest(hash) - } - - describe('identityOwner()', () => { - describe('default owner', () => { - it('should return the identity address itself', async () => { - const owner = await didReg.identityOwner(identity2.address) - expect(owner).to.equal(identity2.address) - }) - }) - - describe('changed owner', () => { - before(async () => { - await didReg.connect(identity2).changeOwner(identity2.address, delegate.address) - }) - it('should return the delegate address', async () => { - const owner = await didReg.identityOwner(identity2.address) - expect(owner).to.equal(delegate.address) - }) - }) - }) - - describe('changeOwner()', () => { - describe('using msg.sender', () => { - describe('as current owner', () => { - let tx: ContractTransaction - before(async () => { - tx = await didReg.connect(identity).changeOwner(identity.address, delegate.address) - }) - it('should change owner mapping', async () => { - const owner = await didReg.owners(identity.address) - expect(owner).to.equal(delegate.address) - }) - it('should sets changed to transaction block', async () => { - const latest = await didReg.changed(identity.address) - expect(latest).to.equal(tx.blockNumber) - }) - it('should create DIDDelegateChanged event', async () => { - const event = (await tx.wait()).events?.[0] as DIDOwnerChangedEvent - expect(event.event).to.equal('DIDOwnerChanged') - expect(event.args.identity).to.equal(identity.address) - expect(event.args.owner).to.equal(delegate.address) - expect(event.args.previousChange.toNumber()).to.equal(0) - }) - }) - - describe('as new owner', () => { - let tx: ContractTransaction - let previousChange: number - before(async () => { - previousChange = (await didReg.changed(identity.address)).toNumber() - tx = await didReg.connect(delegate).changeOwner(identity.address, delegate2.address) - }) - it('should change owner mapping', async () => { - const owner = await didReg.owners(identity.address) - expect(owner).to.equal(delegate2.address) - }) - it('should sets changed to transaction block', async () => { - const latest = await didReg.changed(identity.address) - expect(latest).to.equal((await tx.wait()).blockNumber) - }) - it('should create DIDOwnerChanged event', async () => { - const event = (await tx.wait()).events?.[0] as DIDOwnerChangedEvent - expect(event.event).to.equal('DIDOwnerChanged') - expect(event.args.identity).to.equal(identity.address) - expect(event.args.owner).to.equal(delegate2.address) - expect(event.args.previousChange.toNumber()).to.equal(previousChange) - }) - }) - - describe('as original owner', () => { - it('should fail', async () => { - await expect(didReg.connect(identity).changeOwner(identity.address, identity.address)).to.be.rejectedWith( - /bad_actor/ - ) - }) - }) - - describe('as attacker', () => { - it('should fail', async () => { - await expect(didReg.connect(badBoy).changeOwner(identity.address, badBoy.address)).to.be.rejectedWith( - /bad_actor/ - ) - }) - }) - }) - - describe('using signature', () => { - let tx: ContractTransaction - - describe('as current owner', () => { - before(async () => { - const sig = await signData( - signerAddress, - signerAddress, - privateKey, - concat([toUtf8Bytes('changeOwner'), signerAddress2]) - ) - tx = await didReg.connect(badBoy).changeOwnerSigned(signerAddress, sig.v, sig.r, sig.s, signerAddress2) - }) - it('should change owner mapping', async () => { - const owner2: string = await didReg.owners(signerAddress) - expect(owner2).to.equal(signerAddress2) - }) - it('should sets changed to transaction block', async () => { - const latest = await didReg.changed(signerAddress) - expect(latest).to.equal(tx.blockNumber) - }) - it('should create DIDOwnerChanged event', async () => { - const event = (await tx.wait()).events?.[0] as DIDOwnerChangedEvent - expect(event.event).to.equal('DIDOwnerChanged') - expect(event.args.identity).to.equal(signerAddress) - expect(event.args.owner).to.equal(signerAddress2) - expect(event.args.previousChange.toNumber()).to.equal(0) - }) - }) - - describe('as original owner', () => { - it('should fail', async () => { - const sig = await signData( - signerAddress, - signerAddress, - privateKey, - concat([toUtf8Bytes('changeOwner'), signerAddress]) - ) - await expect( - didReg.connect(badBoy).changeOwnerSigned(signerAddress, sig.v, sig.r, sig.s, signerAddress) - ).to.be.rejectedWith(/bad_signature/) - }) - }) - - describe('using wrong nonce', () => { - it('should fail', async () => { - const currentNonce = (await didReg.nonce(signerAddress2)).toNumber() - expect(currentNonce).to.equal(0) - const sig = await signData( - signerAddress, - signerAddress2, - privateKey2, - concat([toUtf8Bytes('changeOwner'), signerAddress2]), - 1 - ) - await expect( - didReg.connect(badBoy).changeOwnerSigned(signerAddress, sig.v, sig.r, sig.s, signerAddress2) - ).to.be.rejectedWith(/bad_signature/) - }) - }) - }) - }) - - describe('addDelegate()', () => { - describe('using msg.sender', () => { - it('validDelegate should be false', async () => { - const valid = await didReg.validDelegate(identity.address, formatBytes32String('attestor'), delegate3.address) - expect(valid).to.equal(false) // we have not yet assigned delegate correctly - }) - - describe('as current owner', () => { - let tx: ContractTransaction - let block: Block - let previousChange: number - before(async () => { - previousChange = (await didReg.changed(identity.address)).toNumber() - tx = await didReg - .connect(delegate2) - .addDelegate(identity.address, formatBytes32String('attestor'), delegate3.address, 86400) - block = await ethers.provider.getBlock((await tx.wait()).blockNumber) - }) - it('validDelegate should be true', async () => { - const valid = await didReg.validDelegate(identity.address, formatBytes32String('attestor'), delegate3.address) - expect(valid).to.equal(true) // assigned delegate correctly - }) - it('should sets changed to transaction block', async () => { - const latest = await didReg.changed(identity.address) - expect(latest).to.equal((await tx.wait()).blockNumber) - }) - it('should create DIDDelegateChanged event', async () => { - const event = (await tx.wait()).events?.[0] as DIDDelegateChangedEvent - expect(event.event).to.equal('DIDDelegateChanged') - expect(event.args.identity).to.equal(identity.address) - expect(parseBytes32String(event.args.delegateType)).to.equal('attestor') - expect(event.args.delegate).to.equal(delegate3.address) - expect(event.args.validTo.toNumber()).to.equal(block.timestamp + 86400) - expect(event.args.previousChange.toNumber()).to.equal(previousChange) - }) - }) - - describe('as original owner', () => { - it('should fail', async () => { - const currentOwnerAddress = await didReg.owners(identity.address) - expect(currentOwnerAddress).not.to.equal(identity.address) - await expect( - didReg - .connect(identity) - .addDelegate(identity.address, formatBytes32String('attestor'), badBoy.address, 86400) - ).to.be.rejectedWith(/bad_actor/) - }) - }) - - describe('as attacker', () => { - it('should fail', async () => { - await expect( - didReg.connect(badBoy).addDelegate(identity.address, formatBytes32String('attestor'), badBoy.address, 86400) - ).to.be.rejectedWith(/bad_actor/) - }) - }) - }) - - describe('using signature', () => { - describe('as current owner', () => { - let tx: ContractTransaction - let block: Block - let previousChange: number - before(async () => { - previousChange = (await didReg.changed(signerAddress)).toNumber() - const sig = await signData( - signerAddress, - signerAddress2, - privateKey2, - concat([ - toUtf8Bytes('addDelegate'), - formatBytes32String('attestor'), - delegate.address, - zeroPad(hexlify(86400), 32), - ]) - ) - tx = await didReg - .connect(badBoy) - .addDelegateSigned( - signerAddress, - sig.v, - sig.r, - sig.s, - formatBytes32String('attestor'), - delegate.address, - 86400 - ) - block = await ethers.provider.getBlock((await tx.wait()).blockNumber) - }) - it('validDelegate should be true', async () => { - const valid = await didReg.validDelegate(signerAddress, formatBytes32String('attestor'), delegate.address) - expect(valid).to.equal(true) // assigned delegate correctly - }) - it('should sets changed to transaction block', async () => { - const latest = await didReg.changed(signerAddress) - expect(latest.toNumber()).to.equal((await tx.wait()).blockNumber) - }) - it('should create DIDDelegateChanged event', async () => { - const event = (await tx.wait()).events?.[0] as DIDDelegateChangedEvent - expect(event.event).to.equal('DIDDelegateChanged') - expect(event.args.identity).to.equal(signerAddress) - expect(parseBytes32String(event.args.delegateType), 'attestor') - expect(event.args.delegate).to.equal(delegate.address) - expect(event.args.validTo.toNumber()).to.equal(block.timestamp + 86400) - expect(event.args.previousChange.toNumber()).to.equal(previousChange) - }) - }) - - describe('as wrong owner', () => { - it('should fail', async () => { - const sig = await signData( - signerAddress, - signerAddress, - privateKey, - concat([ - toUtf8Bytes('addDelegate'), - formatBytes32String('attestor'), - delegate.address, - zeroPad(hexlify(86400), 32), - ]) - ) - await expect( - didReg - .connect(badBoy) - .addDelegateSigned( - signerAddress, - sig.v, - sig.r, - sig.s, - formatBytes32String('attestor'), - delegate.address, - 86400 - ) - ).to.be.rejectedWith(/bad_signature/) - }) - }) - - describe('using wrong nonce', () => { - it('should fail', async () => { - const currentNonce = (await didReg.nonce(signerAddress2)).toNumber() - expect(currentNonce).to.equal(1) - const sig = await signData( - signerAddress, - signerAddress2, - privateKey2, - concat([ - toUtf8Bytes('addDelegate'), - formatBytes32String('attestor'), - delegate.address, - zeroPad(hexlify(86400), 32), - ]), - 2 - ) - await expect( - didReg - .connect(badBoy) - .addDelegateSigned( - signerAddress, - sig.v, - sig.r, - sig.s, - formatBytes32String('attestor'), - delegate.address, - 86400 - ) - ).to.be.rejectedWith(/bad_signature/) - }) - }) - }) - }) - - describe('revokeDelegate()', () => { - describe('using msg.sender', () => { - it('validDelegate should be true', async () => { - const valid = await didReg.validDelegate(identity.address, formatBytes32String('attestor'), delegate3.address) - expect(valid).to.equal(true) // not yet revoked - }) - - describe('as current owner', () => { - let tx: ContractTransaction - let previousChange: number - before(async () => { - previousChange = (await didReg.changed(identity.address)).toNumber() - tx = await didReg - .connect(delegate2) - .revokeDelegate(identity.address, formatBytes32String('attestor'), delegate3.address) - }) - it('validDelegate should be false', async () => { - const valid = await didReg.validDelegate(identity.address, formatBytes32String('attestor'), delegate3.address) - expect(valid).to.equal(false) // revoked correctly - }) - it('should sets changed to transaction block', async () => { - const latest = await didReg.changed(identity.address) - expect(latest).to.equal((await tx.wait()).blockNumber) - }) - it('should create DIDDelegateChanged event', async () => { - const event = (await tx.wait()).events?.[0] as DIDDelegateChangedEvent - expect(event.event).to.equal('DIDDelegateChanged') - expect(event.args.identity).to.equal(identity.address) - expect(parseBytes32String(event.args.delegateType)).to.equal('attestor') - expect(event.args.delegate).to.equal(delegate3.address) - expect(event.args.validTo.toNumber()).to.be.lessThanOrEqual( - (await ethers.provider.getBlock(tx.blockNumber)).timestamp - ) - expect(event.args.previousChange.toNumber()).to.equal(previousChange) - }) - }) - - describe('as original owner', () => { - it('should fail', async () => { - const currentOwnerAddress = await didReg.owners(identity.address) - expect(currentOwnerAddress).not.to.equal(identity.address) - await expect( - didReg.connect(identity).revokeDelegate(identity.address, formatBytes32String('attestor'), badBoy.address) - ).to.be.rejectedWith(/bad_actor/) - }) - }) - - describe('as attacker', () => { - it('should fail', async () => { - await expect( - didReg.connect(badBoy).revokeDelegate(identity.address, formatBytes32String('attestor'), badBoy.address) - ).to.be.revertedWith('bad_actor') - }) - }) - }) - - describe('using signature', () => { - describe('as current owner', () => { - let tx: ContractTransaction - let previousChange: number - before(async () => { - previousChange = (await didReg.changed(signerAddress)).toNumber() - const sig = await signData( - signerAddress, - signerAddress2, - privateKey2, - concat([toUtf8Bytes('revokeDelegate'), formatBytes32String('attestor'), delegate.address]) - ) - tx = await didReg - .connect(badBoy) - .revokeDelegateSigned(signerAddress, sig.v, sig.r, sig.s, formatBytes32String('attestor'), delegate.address) - }) - it('validDelegate should be false', async () => { - const valid = await didReg.validDelegate(signerAddress, formatBytes32String('attestor'), delegate.address) - expect(valid).to.equal(false) // revoked delegate correctly - }) - it('should sets changed to transaction block', async () => { - const latest = await didReg.changed(signerAddress) - expect(latest).to.equal((await tx.wait()).blockNumber) - }) - it('should create DIDDelegateChanged event', async () => { - const event = (await tx.wait()).events?.[0] as DIDDelegateChangedEvent - expect(event.event).to.equal('DIDDelegateChanged') - expect(event.args.identity).to.equal(signerAddress) - expect(parseBytes32String(event.args.delegateType)).to.equal('attestor') - expect(event.args.delegate).to.equal(delegate.address) - expect(event.args.validTo.toNumber()).to.be.lessThanOrEqual( - (await ethers.provider.getBlock(tx.blockNumber)).timestamp - ) - expect(event.args.previousChange.toNumber()).to.equal(previousChange) - }) - }) - - describe('as wrong owner', () => { - it('should fail', async () => { - const sig = await signData( - signerAddress, - signerAddress, - privateKey, - concat([toUtf8Bytes('revokeDelegate'), formatBytes32String('attestor'), delegate.address]) - ) - await expect( - didReg - .connect(badBoy) - .revokeDelegateSigned( - signerAddress, - sig.v, - sig.r, - sig.s, - formatBytes32String('attestor'), - delegate.address - ) - ).to.be.rejectedWith(/bad_signature/) - }) - }) - - describe('using wrong nonce', () => { - it('should fail', async () => { - const currentNonce = (await didReg.nonce(signerAddress2)).toNumber() - expect(currentNonce).to.equal(2) - const sig = await signData( - signerAddress, - signerAddress2, - privateKey2, - concat([toUtf8Bytes('revokeDelegate'), formatBytes32String('attestor'), delegate.address]), - 1 - ) - await expect( - didReg - .connect(badBoy) - .revokeDelegateSigned( - signerAddress, - sig.v, - sig.r, - sig.s, - formatBytes32String('attestor'), - delegate.address - ) - ).to.be.rejectedWith(/bad_signature/) - }) - }) - }) - }) - - describe('setAttribute()', () => { - describe('using msg.sender', () => { - describe('as current owner', () => { - let tx: ContractTransaction - let block: Block - let previousChange: number - before(async () => { - previousChange = (await didReg.changed(identity.address)).toNumber() - const currentOwnerAddress = await didReg.owners(identity.address) - const signer = (await ethers.getSigners()).find( - (signer: SignerWithAddress) => signer.address === currentOwnerAddress - ) - tx = await didReg - .connect(signer) - .setAttribute(identity.address, formatBytes32String('encryptionKey'), toUtf8Bytes('mykey'), 86400) - block = await ethers.provider.getBlock((await tx.wait()).blockNumber) - }) - it('should sets changed to transaction block', async () => { - const latest = await didReg.changed(identity.address) - expect(latest).to.equal((await tx.wait()).blockNumber) - }) - it('should create DIDAttributeChanged event', async () => { - const event = (await tx.wait()).events?.[0] as DIDAttributeChangedEvent - expect(event.event).to.equal('DIDAttributeChanged') - expect(event.args.identity).to.equal(identity.address) - expect(parseBytes32String(event.args.name)).to.equal('encryptionKey') - expect(event.args.value).to.equal('0x6d796b6579') // the hex encoding of the string "mykey" - expect(event.args.validTo.toNumber()).to.equal(block.timestamp + 86400) - expect(event.args.previousChange.toNumber()).to.equal(previousChange) - }) - }) - - describe('as original owner', () => { - it('should fail', async () => { - const currentOwnerAddress = await didReg.owners(identity.address) - expect(currentOwnerAddress).not.to.equal(identity.address) - await expect( - didReg - .connect(identity) - .setAttribute(identity.address, formatBytes32String('encryptionKey'), toUtf8Bytes('mykey'), 86400) - ).to.be.rejectedWith(/bad_actor/) - }) - }) - - describe('as attacker', () => { - it('should fail', async () => { - await expect( - didReg - .connect(badBoy) - .setAttribute(identity.address, formatBytes32String('encryptionKey'), toUtf8Bytes('mykey'), 86400) - ).to.be.rejectedWith(/bad_actor/) - }) - }) - }) - - describe('using signature', () => { - describe('as current owner', () => { - let tx: ContractTransaction - let block: Block - let previousChange: number - before(async () => { - previousChange = (await didReg.changed(signerAddress)).toNumber() - const sig = await signData( - signerAddress, - signerAddress2, - privateKey2, - concat([ - toUtf8Bytes('setAttribute'), - formatBytes32String('encryptionKey'), - toUtf8Bytes('mykey'), - zeroPad(hexlify(86400), 32), - ]) - ) - tx = await didReg - .connect(badBoy) - .setAttributeSigned( - signerAddress, - sig.v, - sig.r, - sig.s, - formatBytes32String('encryptionKey'), - toUtf8Bytes('mykey'), - 86400 - ) - block = await ethers.provider.getBlock((await tx.wait()).blockNumber) - }) - it('should sets changed to transaction block', async () => { - const latest = await didReg.changed(signerAddress) - expect(latest).to.equal((await tx.wait()).blockNumber) - }) - it('should create DIDDelegateChanged event', async () => { - const event = (await tx.wait()).events?.[0] as DIDAttributeChangedEvent - expect(event.event).to.equal('DIDAttributeChanged') - expect(event.args.identity).to.equal(signerAddress) - expect(parseBytes32String(event.args.name)).to.equal('encryptionKey') - expect(event.args.value).to.equal('0x6d796b6579') // the hex encoding of the string "mykey" - expect(event.args.validTo.toNumber()).to.equal(block.timestamp + 86400) - expect(event.args.previousChange.toNumber()).to.equal(previousChange) - }) - }) - - describe('as wrong owner', () => { - it('should fail', async () => { - const sig = await signData( - signerAddress, - signerAddress, - privateKey, - concat([ - toUtf8Bytes('setAttribute'), - formatBytes32String('encryptionKey'), - toUtf8Bytes('mykey'), - zeroPad(hexlify(86400), 32), - ]) - ) - await expect( - didReg - .connect(badBoy) - .setAttributeSigned( - signerAddress, - sig.v, - sig.r, - sig.s, - formatBytes32String('encryptionKey'), - toUtf8Bytes('mykey'), - 86400 - ) - ).to.be.rejectedWith(/bad_signature/) - }) - }) - - describe('using wrong nonce', () => { - it('should fail', async () => { - const currentNonce = (await didReg.nonce(signerAddress2)).toNumber() - expect(currentNonce).to.equal(3) - const sig = await signData( - signerAddress, - signerAddress2, - privateKey2, - concat([ - toUtf8Bytes('setAttribute'), - formatBytes32String('encryptionKey'), - toUtf8Bytes('mykey'), - zeroPad(hexlify(86400), 32), - ]), - 1 - ) - await expect( - didReg - .connect(badBoy) - .setAttributeSigned( - signerAddress, - sig.v, - sig.r, - sig.s, - formatBytes32String('encryptionKey'), - toUtf8Bytes('mykey'), - 86400 - ) - ).to.be.rejectedWith(/bad_signature/) - }) - }) - }) - }) - - describe('revokeAttribute()', () => { - describe('using msg.sender', () => { - describe('as current owner', () => { - let tx: ContractTransaction - let previousChange: number - before(async () => { - previousChange = (await didReg.changed(identity.address)).toNumber() - const currentOwnerAddress = await didReg.owners(identity.address) - const signer = (await ethers.getSigners()).find( - (signer: SignerWithAddress) => signer.address === currentOwnerAddress - ) - tx = await didReg - .connect(signer) - .revokeAttribute(identity.address, formatBytes32String('encryptionKey'), toUtf8Bytes('mykey')) - }) - it('should sets changed to transaction block', async () => { - const latest = await didReg.changed(identity.address) - expect(latest).to.equal((await tx.wait()).blockNumber) - }) - it('should create DIDAttributeChanged event', async () => { - const event = (await tx.wait()).events?.[0] as DIDAttributeChangedEvent - expect(event.event).to.equal('DIDAttributeChanged') - expect(event.args.identity).to.equal(identity.address) - expect(parseBytes32String(event.args.name)).to.equal('encryptionKey') - expect(event.args.value).to.equal('0x6d796b6579') // hex encoding of the string "mykey" - expect(event.args.validTo.toNumber()).to.equal(0) - expect(event.args.previousChange.toNumber()).to.equal(previousChange) - }) - }) - - describe('as original owner', () => { - it('should fail', async () => { - const currentOwnerAddress = await didReg.owners(identity.address) - expect(currentOwnerAddress).not.to.equal(identity.address) - await expect( - didReg - .connect(identity) - .revokeAttribute(identity.address, formatBytes32String('encryptionKey'), toUtf8Bytes('mykey')) - ).to.be.rejectedWith(/bad_actor/) - }) - }) - - describe('as attacker', () => { - it('should fail', async () => { - await expect( - didReg - .connect(badBoy) - .revokeAttribute(identity.address, formatBytes32String('encryptionKey'), toUtf8Bytes('mykey')) - ).to.be.rejectedWith(/bad_actor/) - }) - }) - }) - - describe('using signature', () => { - describe('as current owner', () => { - let tx: ContractTransaction - let previousChange: number - before(async () => { - previousChange = (await didReg.changed(signerAddress)).toNumber() - const sig = await signData( - signerAddress, - signerAddress2, - privateKey2, - concat([toUtf8Bytes('revokeAttribute'), formatBytes32String('encryptionKey'), toUtf8Bytes('mykey')]) - ) - tx = await didReg - .connect(badBoy) - .revokeAttributeSigned( - signerAddress, - sig.v, - sig.r, - sig.s, - formatBytes32String('encryptionKey'), - toUtf8Bytes('mykey') - ) - }) - it('should sets changed to transaction block', async () => { - const latest = await didReg.changed(signerAddress) - expect(latest).to.equal((await tx.wait()).blockNumber) - }) - it('should create DIDDelegateChanged event', async () => { - const event = (await tx.wait()).events?.[0] as DIDAttributeChangedEvent - expect(event.event).to.equal('DIDAttributeChanged') - expect(event.args.identity).to.equal(signerAddress) - expect(parseBytes32String(event.args.name)).to.equal('encryptionKey') - expect(event.args.value).to.equal('0x6d796b6579') // hex encoding of the string "mykey" - expect(event.args.validTo.toNumber()).to.equal(0) - expect(event.args.previousChange.toNumber()).to.equal(previousChange) - }) - }) - - describe('as wrong owner', () => { - it('should fail', async () => { - const sig = await signData( - signerAddress, - signerAddress, - privateKey, - concat([toUtf8Bytes('revokeAttribute'), formatBytes32String('encryptionKey'), toUtf8Bytes('mykey')]) - ) - await expect( - didReg - .connect(badBoy) - .revokeAttributeSigned( - signerAddress, - sig.v, - sig.r, - sig.s, - formatBytes32String('encryptionKey'), - toUtf8Bytes('mykey') - ) - ).to.be.rejectedWith(/bad_signature/) - }) - }) - - describe('using wrong nonce', () => { - it('should fail', async () => { - const currentNonce = (await didReg.nonce(signerAddress2)).toNumber() - expect(currentNonce).to.equal(4) - const sig = await signData( - signerAddress, - signerAddress2, - privateKey2, - concat([toUtf8Bytes('revokeAttribute'), formatBytes32String('encryptionKey'), toUtf8Bytes('mykey')]), - 1 - ) - await expect( - didReg - .connect(badBoy) - .revokeAttributeSigned( - signerAddress, - sig.v, - sig.r, - sig.s, - formatBytes32String('encryptionKey'), - toUtf8Bytes('mykey') - ) - ).to.be.rejectedWith(/bad_signature/) - }) - }) - }) - }) - - describe('Events', () => { - it('can create list', async () => { - const history = [] - let prevChange: number = (await didReg.changed(identity.address)).toNumber() - while (prevChange) { - const logs: Log[] = await ethers.provider.getLogs({ - topics: [null, hexZeroPad(identity.address, 32)], - fromBlock: prevChange, - toBlock: prevChange, - }) - prevChange = 0 - for (const log of logs) { - const logDescription = didReg.interface.parseLog(log) - history.unshift(logDescription.name) - prevChange = logDescription.args.previousChange.toNumber() - } - } - expect(history).to.deep.equal([ - 'DIDOwnerChanged', - 'DIDOwnerChanged', - 'DIDDelegateChanged', - 'DIDDelegateChanged', - 'DIDAttributeChanged', - 'DIDAttributeChanged', - ]) - }) - }) -})