Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create did state and resolve did #5

Merged
merged 14 commits into from
Aug 16, 2023
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ jobs:

- name: Run Forge tests
run: |
forge test -vvv
forge test -vvv --gas-report
id: test
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
src = "src"
out = "out"
libs = ["lib"]
solc = '0.8.19'

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
164 changes: 61 additions & 103 deletions src/DidRegistry.sol
Original file line number Diff line number Diff line change
@@ -1,134 +1,92 @@
/* SPDX-License-Identifier: MIT */

pragma solidity ^0.8.13;

contract DIDRegistryEvents {
event DIDOwnerChanged(
address indexed identity,
address owner,
uint previousChange
);

event DIDDelegateChanged(
address indexed identity,
bytes32 delegateType,
address delegate,
uint validTo,
uint previousChange
);

event DIDAttributeChanged(
address indexed identity,
bytes32 name,
bytes value,
uint validTo,
uint previousChange
);
}

contract DIDRegistry is DIDRegistryEvents {

mapping(address => address) public owners;
mapping(address => mapping(bytes32 => mapping(address => uint))) public delegates;
mapping(address => uint) public changed;
mapping(address => uint) public nonce;

modifier onlyOwner(address identity, address actor) {
require (actor == identityOwner(identity), "bad_actor");
_;
}
pragma solidity ^0.8.19;

function identityOwner(address identity) public view returns(address) {
address owner = owners[identity];
if (owner != address(0x00)) {
return owner;
}
return identity;
}
import "./IDidRegistry.sol";

function checkSignature(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, bytes32 hash) internal returns(address) {
address signer = ecrecover(hash, sigV, sigR, sigS);
require(signer == identityOwner(identity), "bad_signature");
nonce[signer]++;
return signer;
}
contract DIDRegistry is IDidRegistry {

function validDelegate(address identity, bytes32 delegateType, address delegate) public view returns(bool) {
uint validity = delegates[identity][keccak256(abi.encode(delegateType))][delegate];
return (validity > block.timestamp);
enum VerificationMethodType {
EcdsaSecp256k1RecoveryMethod // Verification Method for For 20-bytes Ethereum Keys
}

function changeOwner(address identity, address actor, address newOwner) internal onlyOwner(identity, actor) {
owners[identity] = newOwner;
emit DIDOwnerChanged(identity, newOwner, changed[identity]);
changed[identity] = block.number;
// Each flag is represented by a specific bit. This enum specifies what flag corresponds to which bit.
enum VerificationMethodFlagBitMask {
NONE, // bit 0
AUTHENTICATION, // bit 1
ASSERTION, // bit 2
CAPABILITY_INVOCATION, // bit 3
CAPABILITY_DELEGATION, // bit 4
OWNERSHIP_PROOF, // bit 5
PROTECTED // bit 6
}

function changeOwner(address identity, address newOwner) public {
changeOwner(identity, msg.sender, newOwner);
struct VerificationMethod {
string fragment;
uint16 flags; // The permissions this key has where each bit corresponds to a configuration flag
VerificationMethodType methodType;
bytes keyData; // Key data to match the given verification type. Each verification method type has differentlly formatted keyData
}

function changeOwnerSigned(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, address newOwner) public {
bytes32 hash = keccak256(abi.encodePacked(bytes1(0x19), bytes1(0), this, nonce[identityOwner(identity)], identity, "changeOwner", newOwner));
changeOwner(identity, checkSignature(identity, sigV, sigR, sigS, hash), newOwner);
struct Service {
string fragment; //TODO: Are fragments globally unique? Ie can a service and a verification method have the same fragment?
string service_type;
string service_endpoint;
}

function addDelegate(address identity, address actor, bytes32 delegateType, address delegate, uint validity) internal onlyOwner(identity, actor) {
delegates[identity][keccak256(abi.encode(delegateType))][delegate] = block.timestamp + validity;
emit DIDDelegateChanged(identity, delegateType, delegate, block.timestamp + validity, changed[identity]);
changed[identity] = block.number;
struct DidState {
VerificationMethod[] verificationMethods;
Service[] services;
address[] nativeControllers;
string[] externalControllers;
}

function addDelegate(address identity, bytes32 delegateType, address delegate, uint validity) public {
addDelegate(identity, msg.sender, delegateType, delegate, validity);
}
mapping(address => DidState) private didStates; // Mapping from didId to the state

function addDelegateSigned(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, bytes32 delegateType, address delegate, uint validity) public {
bytes32 hash = keccak256(abi.encodePacked(bytes1(0x19), bytes1(0), this, nonce[identityOwner(identity)], identity, "addDelegate", delegateType, delegate, validity));
addDelegate(identity, checkSignature(identity, sigV, sigR, sigS, hash), delegateType, delegate, validity);
}
uint16 private DEFAULT_VERIFICATION_METHOD_FLAGS = uint16(1) << uint16(VerificationMethodFlagBitMask.OWNERSHIP_PROOF) | uint16(1) << uint16(VerificationMethodFlagBitMask.CAPABILITY_INVOCATION);

bytes16 private constant _HEX_DIGITS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;

//////// Fetching/Resolving Did /////////////
function resolveDidState(address didIdentifier) external view returns(DidState memory) {
if(isGenerativeDidState(didIdentifier)) {
return _getDefaultDidState(didIdentifier);
}

function revokeDelegate(address identity, address actor, bytes32 delegateType, address delegate) internal onlyOwner(identity, actor) {
delegates[identity][keccak256(abi.encode(delegateType))][delegate] = block.timestamp;
emit DIDDelegateChanged(identity, delegateType, delegate, block.timestamp, changed[identity]);
changed[identity] = block.number;
return didStates[didIdentifier];
}

function revokeDelegate(address identity, bytes32 delegateType, address delegate) public {
revokeDelegate(identity, msg.sender, delegateType, delegate);
}
function initializeDidState(address didIdentifier) external {
require(isGenerativeDidState(didIdentifier), "Did state already exist");

function revokeDelegateSigned(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, bytes32 delegateType, address delegate) public {
bytes32 hash = keccak256(abi.encodePacked(bytes1(0x19), bytes1(0), this, nonce[identityOwner(identity)], identity, "revokeDelegate", delegateType, delegate));
revokeDelegate(identity, checkSignature(identity, sigV, sigR, sigS, hash), delegateType, delegate);
}
DidState memory defaultDidState = _getDefaultDidState(didIdentifier);

function setAttribute(address identity, address actor, bytes32 name, bytes memory value, uint validity ) internal onlyOwner(identity, actor) {
emit DIDAttributeChanged(identity, name, value, block.timestamp + validity, changed[identity]);
changed[identity] = block.number;
didStates[didIdentifier].verificationMethods.push(defaultDidState.verificationMethods[0]);
}

function setAttribute(address identity, bytes32 name, bytes memory value, uint validity) public {
setAttribute(identity, msg.sender, name, value, validity);
function isGenerativeDidState(address didIdentifier) public view returns(bool) {
DidState memory didState = didStates[didIdentifier];
return didState.verificationMethods.length == 0;
}

function setAttributeSigned(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, bytes32 name, bytes memory value, uint validity) public {
bytes32 hash = keccak256(abi.encodePacked(bytes1(0x19), bytes1(0), this, nonce[identityOwner(identity)], identity, "setAttribute", name, value, validity));
setAttribute(identity, checkSignature(identity, sigV, sigR, sigS, hash), name, value, validity);
function _getDefaultVerificationMethod(address authorityKey) internal view returns(VerificationMethod memory verificationMethod) {
return VerificationMethod({
fragment: 'default',
flags: DEFAULT_VERIFICATION_METHOD_FLAGS,
methodType: VerificationMethodType.EcdsaSecp256k1RecoveryMethod,
keyData: abi.encodePacked(authorityKey)
});
}

function revokeAttribute(address identity, address actor, bytes32 name, bytes memory value ) internal onlyOwner(identity, actor) {
emit DIDAttributeChanged(identity, name, value, 0, changed[identity]);
changed[identity] = block.number;
}
function _getDefaultDidState(address didIdentifier) internal view returns(DidState memory) {

function revokeAttribute(address identity, bytes32 name, bytes memory value) public {
revokeAttribute(identity, msg.sender, name, value);
}
DidState memory defaultDidState;

defaultDidState.verificationMethods = new VerificationMethod[](1);
defaultDidState.verificationMethods[0] = _getDefaultVerificationMethod(didIdentifier);

function revokeAttributeSigned(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, bytes32 name, bytes memory value) public {
bytes32 hash = keccak256(abi.encodePacked(bytes1(0x19), bytes1(0), this, nonce[identityOwner(identity)], identity, "revokeAttribute", name, value));
revokeAttribute(identity, checkSignature(identity, sigV, sigR, sigS, hash), name, value);
return defaultDidState;
}

}
}
8 changes: 8 additions & 0 deletions src/IDidRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* SPDX-License-Identifier: MIT */

pragma solidity ^0.8.19;
interface IDidRegistry {
/**
- Events from Update and Delete operations will live here
*/
}
51 changes: 31 additions & 20 deletions test/DidRegistry.t.sol
Original file line number Diff line number Diff line change
@@ -1,39 +1,50 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
pragma solidity ^0.8.19;

import "forge-std/Test.sol";
import {DIDRegistry, DIDRegistryEvents} from "../src/DidRegistry.sol";
import {DIDRegistry} from "../src/DidRegistry.sol";

contract CounterTest is Test, DIDRegistryEvents {
DIDRegistry public didRegistry;
uint public testValidity = 86400;
contract CounterTest is Test {

DIDRegistry public didRegistry;

function setUp() public {
didRegistry = new DIDRegistry();
}

function test_unknownIdentityOwnerIsIdentity(address identity) public {
assertEq(identity, didRegistry.identityOwner(identity));
}

function test_changeOwner(address newOwner) public {
address owner = address(1);
vm.prank(owner);
didRegistry.changeOwner(owner, newOwner);
assertEq(newOwner, didRegistry.identityOwner(owner));
function test_fuzz_should_resolve_did_state(address user) public {
vm.assume(user > address(0));

DIDRegistry.DidState memory defaultState = didRegistry.resolveDidState(user);

//Default the didState should be Invocation and have an ownership proof
assertEq(
defaultState.verificationMethods[0].flags,
uint16(1) << uint16(DIDRegistry.VerificationMethodFlagBitMask.OWNERSHIP_PROOF) | uint16(1) << uint16(DIDRegistry.VerificationMethodFlagBitMask.CAPABILITY_INVOCATION)
);
assertEq(defaultState.verificationMethods[0].fragment,"default");

// Verify the key on the default verification method matches the address in the did
assertEq(address(bytes20(defaultState.verificationMethods[0].keyData)), user);
}

function test_addDelegate(address newDelegate) public {
address owner = address(2);
function test_should_initialize_did_state() public {
address user = vm.addr(3);

assertEq(didRegistry.isGenerativeDidState(user), true);

// setup event expectations
vm.expectEmit(true, false, false, true, address(didRegistry));
emit DIDDelegateChanged(owner, 0, newDelegate, block.timestamp + testValidity, 0);
didRegistry.initializeDidState(user);

vm.prank(owner);
didRegistry.addDelegate(owner, 0, newDelegate, testValidity);
assertEq(didRegistry.isGenerativeDidState(user), false);
}

function testFail_should_fail_to_initialize_did_that_exist() public {
address user = vm.addr(3);

// Initialize
didRegistry.initializeDidState(user);
// Try to initialize an existing didState
didRegistry.initializeDidState(user);
}
}