did:cow, a proposal for an ID resolution method with most of the convenience of did:plc/did:web and the robustness of a public blockchain
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

reorder to be logical

+99 -92
+1 -1
cli/cow.py
··· 213 213 _controller_address(controller_hex), 214 214 initial_wrapped, 215 215 ).call() 216 - registered = contract.functions.cows(cow_hash).call()[2] 216 + registered = contract.functions.cows(cow_hash).call()[1] 217 217 218 218 click.echo(f"status: {'active' if registered else 'not registered on-chain'}") 219 219 click.echo(f"wrapped: {wrapped_did}")
+70 -63
src/CowRegistry.sol
··· 8 8 /// Creation is off-chain and free. This contract only needs to be called when migrating 9 9 /// to a new wrapped DID, transferring control, or deactivating. 10 10 contract CowRegistry { 11 - 12 11 /// @notice On-chain state for a single did:cow identifier. 13 12 /// @dev The cowHash key is derived from the *initial* controller and wrapped DID, 14 13 /// so these fields reflect the *current* state after any updates. 15 14 /// controller and deactivated are packed into a single storage slot. 16 15 struct Cow { 17 16 address controller; 18 - bool deactivated; 19 17 bool initialized; 18 + bool deactivated; 20 19 string wrappedDID; 21 20 } 22 21 ··· 41 40 /// @notice Emitted when a cow's wrapped DID is updated. 42 41 event WrappedDIDUpdated(bytes32 indexed cowHash, string wrappedDID); 43 42 44 - /// @notice Update the wrapped DID for an already-registered cow. 45 - /// @dev Caller must be the current controller. Use updateWrappedDID if the cow 46 - /// may not yet be registered on-chain. 47 - /// @param _cowHash The cow's registry key, as returned by calculateCowHash. 48 - /// @param _wrappedDID The new wrapped DID, without the leading "did:" prefix. 49 - function updateWrappedDIDByHash(bytes32 _cowHash, string memory _wrappedDID) public { 50 - Cow storage cow = cows[_cowHash]; 51 - if (!cow.initialized) revert NotInitialized(); 43 + /// @notice Derive the registry key for a did:cow identifier. 44 + /// @param _controller The initial controller address. 45 + /// @param _wrappedDID The initial wrapped DID, without the leading "did:" prefix. 46 + /// @return The keccak256 hash used as the key in the cows mapping. 47 + function calculateCowHash(address _controller, string memory _wrappedDID) public pure returns (bytes32) { 48 + return keccak256(abi.encodePacked(_controller, _wrappedDID)); 49 + } 50 + 51 + /// @dev Register a cow on-chain if not already present, then return its hash. 52 + /// Reverts if the cow has been deactivated. 53 + /// @param _controller The initial controller address from the did:cow identifier. 54 + /// @param _wrappedDID The initial wrapped DID from the did:cow identifier, without "did:". 55 + /// @return cowHash The registry key for this did:cow identifier. 56 + function _ensureCowInitialized(address _controller, string memory _wrappedDID) internal returns (bytes32 cowHash) { 57 + cowHash = calculateCowHash(_controller, _wrappedDID); 58 + Cow storage cow = cows[cowHash]; 52 59 if (cow.deactivated) revert AlreadyDeactivated(); 53 - if (msg.sender != cow.controller) revert NotController(); 54 - if (bytes(_wrappedDID).length == 0) revert EmptyWrappedDID(); 55 - 56 - cow.wrappedDID = _wrappedDID; 57 - emit WrappedDIDUpdated(_cowHash, _wrappedDID); 60 + if (!cow.initialized) { 61 + cow.initialized = true; 62 + cow.controller = _controller; 63 + cow.wrappedDID = _wrappedDID; 64 + emit CowInitialized(cowHash, _controller, _wrappedDID); 65 + } 66 + return cowHash; 58 67 } 59 68 60 69 /// @notice Transfer control of an already-registered cow to a new address. ··· 74 83 emit ControllerUpdated(_cowHash, _controller); 75 84 } 76 85 86 + /// @notice Update the wrapped DID for an already-registered cow. 87 + /// @dev Caller must be the current controller. Use updateWrappedDID if the cow 88 + /// may not yet be registered on-chain. 89 + /// @param _cowHash The cow's registry key, as returned by calculateCowHash. 90 + /// @param _wrappedDID The new wrapped DID, without the leading "did:" prefix. 91 + function updateWrappedDIDByHash(bytes32 _cowHash, string memory _wrappedDID) public { 92 + Cow storage cow = cows[_cowHash]; 93 + if (!cow.initialized) revert NotInitialized(); 94 + if (cow.deactivated) revert AlreadyDeactivated(); 95 + if (msg.sender != cow.controller) revert NotController(); 96 + if (bytes(_wrappedDID).length == 0) revert EmptyWrappedDID(); 97 + 98 + cow.wrappedDID = _wrappedDID; 99 + emit WrappedDIDUpdated(_cowHash, _wrappedDID); 100 + } 101 + 77 102 /// @notice Permanently deactivate an already-registered cow. 78 103 /// @dev Caller must be the current controller. Deactivation is irreversible. 79 104 /// Use deactivate if the cow may not yet be registered on-chain. ··· 91 116 emit CowDeactivated(_cowHash); 92 117 } 93 118 94 - /// @notice Derive the registry key for a did:cow identifier. 95 - /// @param _controller The initial controller address. 96 - /// @param _wrappedDID The initial wrapped DID, without the leading "did:" prefix. 97 - /// @return The keccak256 hash used as the key in the cows mapping. 98 - function calculateCowHash(address _controller, string memory _wrappedDID) public pure returns (bytes32) { 99 - return keccak256(abi.encodePacked(_controller, _wrappedDID)); 100 - } 101 - 102 - /// @dev Register a cow on-chain if not already present, then return its hash. 103 - function _ensureCowInitialized(address _controller, string memory _wrappedDID) internal returns (bytes32 cowHash) { 104 - cowHash = calculateCowHash(_controller, _wrappedDID); 105 - Cow storage cow = cows[cowHash]; 106 - if (cow.deactivated) revert AlreadyDeactivated(); 107 - if (!cow.initialized) { 108 - cow.initialized = true; 109 - cow.controller = _controller; 110 - cow.wrappedDID = _wrappedDID; 111 - emit CowInitialized(cowHash, _controller, _wrappedDID); 112 - } 113 - return cowHash; 114 - } 115 - 116 - /// @notice Resolve a did:cow identifier to its current wrapped DID and controller. 117 - /// @dev Returns the initial values if the cow has never been registered on-chain. 118 - /// @param _controller The initial controller address from the did:cow identifier. 119 - /// @param _wrappedDID The initial wrapped DID from the did:cow identifier, without "did:". 120 - /// @return wrappedDID The current full wrapped DID (with "did:" prepended), or empty string if deactivated. 121 - /// @return controller The current controller address. 122 - function resolveCow(address _controller, string memory _wrappedDID) external view returns (string memory wrappedDID, address controller) { 123 - bytes32 cowHash = calculateCowHash(_controller, _wrappedDID); 124 - Cow storage cow = cows[cowHash]; 125 - if (cow.deactivated) { 126 - return ("", address(0)); 127 - } 128 - if (!cow.initialized) { 129 - return (string.concat("did:", _wrappedDID), _controller); 130 - } 131 - return (string.concat("did:", cow.wrappedDID), cow.controller); 132 - } 133 - 134 119 /// @notice Optionally pre-register a cow on-chain before its first update. 135 120 /// @dev This is never strictly necessary — updateWrappedDID, updateController, and 136 121 /// deactivate all register the cow automatically if needed. ··· 140 125 _ensureCowInitialized(_controller, _wrappedDID); 141 126 } 142 127 143 - /// @notice Update the wrapped DID, registering the cow on-chain if not already present. 128 + /// @notice Transfer control to a new address, registering the cow on-chain if not already present. 144 129 /// @param _controller The initial controller address from the did:cow identifier. 145 130 /// @param _wrappedDID The initial wrapped DID from the did:cow identifier, without "did:". 146 - /// @param _newWrappedDID The new wrapped DID, without the leading "did:" prefix. 147 - function updateWrappedDID(address _controller, string memory _wrappedDID, string memory _newWrappedDID) external { 131 + /// @param _newController The new controller address. 132 + function updateController(address _controller, string memory _wrappedDID, address _newController) external { 148 133 bytes32 cowHash = _ensureCowInitialized(_controller, _wrappedDID); 149 - updateWrappedDIDByHash(cowHash, _newWrappedDID); 134 + updateControllerByHash(cowHash, _newController); 150 135 } 151 136 152 - /// @notice Transfer control to a new address, registering the cow on-chain if not already present. 137 + /// @notice Update the wrapped DID, registering the cow on-chain if not already present. 153 138 /// @param _controller The initial controller address from the did:cow identifier. 154 139 /// @param _wrappedDID The initial wrapped DID from the did:cow identifier, without "did:". 155 - /// @param _newController The new controller address. 156 - function updateController(address _controller, string memory _wrappedDID, address _newController) external { 140 + /// @param _newWrappedDID The new wrapped DID, without the leading "did:" prefix. 141 + function updateWrappedDID(address _controller, string memory _wrappedDID, string memory _newWrappedDID) external { 157 142 bytes32 cowHash = _ensureCowInitialized(_controller, _wrappedDID); 158 - updateControllerByHash(cowHash, _newController); 143 + updateWrappedDIDByHash(cowHash, _newWrappedDID); 159 144 } 160 145 161 146 /// @notice Permanently deactivate a cow, registering it on-chain if not already present. ··· 165 150 function deactivate(address _controller, string memory _wrappedDID) external { 166 151 bytes32 cowHash = _ensureCowInitialized(_controller, _wrappedDID); 167 152 deactivateByHash(cowHash); 153 + } 154 + 155 + /// @notice Resolve a did:cow identifier to its current wrapped DID and controller. 156 + /// @dev Returns the initial values if the cow has never been registered on-chain. 157 + /// @param _controller The initial controller address from the did:cow identifier. 158 + /// @param _wrappedDID The initial wrapped DID from the did:cow identifier, without "did:". 159 + /// @return wrappedDID The current full wrapped DID (with "did:" prepended), or empty string if deactivated. 160 + /// @return controller The current controller address. 161 + function resolveCow(address _controller, string memory _wrappedDID) 162 + external 163 + view 164 + returns (string memory wrappedDID, address controller) 165 + { 166 + bytes32 cowHash = calculateCowHash(_controller, _wrappedDID); 167 + Cow storage cow = cows[cowHash]; 168 + if (cow.deactivated) { 169 + return ("", address(0)); 170 + } 171 + if (!cow.initialized) { 172 + return (string.concat("did:", _wrappedDID), _controller); 173 + } 174 + return (string.concat("did:", cow.wrappedDID), cow.controller); 168 175 } 169 176 }
+28 -28
test/CowRegistry.t.sol
··· 15 15 string plcDID2 = "plc:abcdefghijklmnopqrstuvwx"; 16 16 17 17 // did:web addresses of varying lengths (did: prefix implied) 18 - string webDIDShort = "web:a.io"; 19 - string webDIDMedium = "web:example.com"; 20 - string webDIDLong = "web:subdomain.very-long-hostname.example.co.uk"; 18 + string webDIDShort = "web:a.io"; 19 + string webDIDMedium = "web:example.com"; 20 + string webDIDLong = "web:subdomain.very-long-hostname.example.co.uk"; 21 21 string webDIDVeryLong = "web:deep.nested.subdomain.with-a-quite-long-hostname.enterprise.example.com"; 22 22 23 23 function setUp() public { ··· 39 39 vm.prank(controller1); 40 40 registry.updateWrappedDIDByHash(cowHash, plcDID2); 41 41 42 - (, , , string memory did) = registry.cows(cowHash); 42 + (,,, string memory did) = registry.cows(cowHash); 43 43 assertEq(did, plcDID2); 44 44 } 45 45 ··· 49 49 vm.prank(controller2); 50 50 registry.updateWrappedDIDByHash(cowHash, plcDID1); 51 51 52 - (, , , string memory did) = registry.cows(cowHash); 52 + (,,, string memory did) = registry.cows(cowHash); 53 53 assertEq(did, plcDID1); 54 54 } 55 55 ··· 59 59 vm.prank(controller1); 60 60 registry.updateWrappedDIDByHash(cowHash, webDIDShort); 61 61 62 - (, , , string memory did) = registry.cows(cowHash); 62 + (,,, string memory did) = registry.cows(cowHash); 63 63 assertEq(did, webDIDShort); 64 64 } 65 65 ··· 69 69 vm.prank(controller1); 70 70 registry.updateWrappedDIDByHash(cowHash, webDIDMedium); 71 71 72 - (, , , string memory did) = registry.cows(cowHash); 72 + (,,, string memory did) = registry.cows(cowHash); 73 73 assertEq(did, webDIDMedium); 74 74 } 75 75 ··· 79 79 vm.prank(controller1); 80 80 registry.updateWrappedDIDByHash(cowHash, webDIDLong); 81 81 82 - (, , , string memory did) = registry.cows(cowHash); 82 + (,,, string memory did) = registry.cows(cowHash); 83 83 assertEq(did, webDIDLong); 84 84 } 85 85 ··· 89 89 vm.prank(controller1); 90 90 registry.updateWrappedDIDByHash(cowHash, webDIDVeryLong); 91 91 92 - (, , , string memory did) = registry.cows(cowHash); 92 + (,,, string memory did) = registry.cows(cowHash); 93 93 assertEq(did, webDIDVeryLong); 94 94 } 95 95 ··· 103 103 vm.prank(controller1); 104 104 registry.updateControllerByHash(cowHash, controller2); 105 105 106 - (address ctrl, , , ) = registry.cows(cowHash); 106 + (address ctrl,,,) = registry.cows(cowHash); 107 107 assertEq(ctrl, controller2); 108 108 } 109 109 ··· 113 113 vm.prank(controller2); 114 114 registry.updateControllerByHash(cowHash, controller1); 115 115 116 - (address ctrl, , , ) = registry.cows(cowHash); 116 + (address ctrl,,,) = registry.cows(cowHash); 117 117 assertEq(ctrl, controller1); 118 118 } 119 119 ··· 123 123 vm.prank(controller1); 124 124 registry.updateControllerByHash(cowHash, controller2); 125 125 126 - (address ctrl, , , ) = registry.cows(cowHash); 126 + (address ctrl,,,) = registry.cows(cowHash); 127 127 assertEq(ctrl, controller2); 128 128 } 129 129 ··· 133 133 vm.prank(controller1); 134 134 registry.updateControllerByHash(cowHash, controller2); 135 135 136 - (address ctrl, , , ) = registry.cows(cowHash); 136 + (address ctrl,,,) = registry.cows(cowHash); 137 137 assertEq(ctrl, controller2); 138 138 } 139 139 ··· 143 143 vm.prank(controller1); 144 144 registry.updateControllerByHash(cowHash, controller2); 145 145 146 - (address ctrl, , , ) = registry.cows(cowHash); 146 + (address ctrl,,,) = registry.cows(cowHash); 147 147 assertEq(ctrl, controller2); 148 148 } 149 149 ··· 153 153 vm.prank(controller1); 154 154 registry.updateControllerByHash(cowHash, controller2); 155 155 156 - (address ctrl, , , ) = registry.cows(cowHash); 156 + (address ctrl,,,) = registry.cows(cowHash); 157 157 assertEq(ctrl, controller2); 158 158 } 159 159 ··· 166 166 registry.updateWrappedDID(controller1, plcDID1, plcDID2); 167 167 168 168 bytes32 cowHash = registry.calculateCowHash(controller1, plcDID1); 169 - (, , , string memory did) = registry.cows(cowHash); 169 + (,,, string memory did) = registry.cows(cowHash); 170 170 assertEq(did, plcDID2); 171 171 } 172 172 ··· 175 175 registry.updateWrappedDID(controller2, plcDID2, plcDID1); 176 176 177 177 bytes32 cowHash = registry.calculateCowHash(controller2, plcDID2); 178 - (, , , string memory did) = registry.cows(cowHash); 178 + (,,, string memory did) = registry.cows(cowHash); 179 179 assertEq(did, plcDID1); 180 180 } 181 181 ··· 184 184 registry.updateWrappedDID(controller1, plcDID1, webDIDShort); 185 185 186 186 bytes32 cowHash = registry.calculateCowHash(controller1, plcDID1); 187 - (, , , string memory did) = registry.cows(cowHash); 187 + (,,, string memory did) = registry.cows(cowHash); 188 188 assertEq(did, webDIDShort); 189 189 } 190 190 ··· 193 193 registry.updateWrappedDID(controller1, plcDID1, webDIDMedium); 194 194 195 195 bytes32 cowHash = registry.calculateCowHash(controller1, plcDID1); 196 - (, , , string memory did) = registry.cows(cowHash); 196 + (,,, string memory did) = registry.cows(cowHash); 197 197 assertEq(did, webDIDMedium); 198 198 } 199 199 ··· 202 202 registry.updateWrappedDID(controller1, plcDID1, webDIDLong); 203 203 204 204 bytes32 cowHash = registry.calculateCowHash(controller1, plcDID1); 205 - (, , , string memory did) = registry.cows(cowHash); 205 + (,,, string memory did) = registry.cows(cowHash); 206 206 assertEq(did, webDIDLong); 207 207 } 208 208 ··· 211 211 registry.updateWrappedDID(controller1, plcDID1, webDIDVeryLong); 212 212 213 213 bytes32 cowHash = registry.calculateCowHash(controller1, plcDID1); 214 - (, , , string memory did) = registry.cows(cowHash); 214 + (,,, string memory did) = registry.cows(cowHash); 215 215 assertEq(did, webDIDVeryLong); 216 216 } 217 217 ··· 224 224 registry.updateController(controller1, plcDID1, controller2); 225 225 226 226 bytes32 cowHash = registry.calculateCowHash(controller1, plcDID1); 227 - (address ctrl, , , ) = registry.cows(cowHash); 227 + (address ctrl,,,) = registry.cows(cowHash); 228 228 assertEq(ctrl, controller2); 229 229 } 230 230 ··· 233 233 registry.updateController(controller2, plcDID2, controller1); 234 234 235 235 bytes32 cowHash = registry.calculateCowHash(controller2, plcDID2); 236 - (address ctrl, , , ) = registry.cows(cowHash); 236 + (address ctrl,,,) = registry.cows(cowHash); 237 237 assertEq(ctrl, controller1); 238 238 } 239 239 ··· 242 242 registry.updateController(controller1, webDIDShort, controller2); 243 243 244 244 bytes32 cowHash = registry.calculateCowHash(controller1, webDIDShort); 245 - (address ctrl, , , ) = registry.cows(cowHash); 245 + (address ctrl,,,) = registry.cows(cowHash); 246 246 assertEq(ctrl, controller2); 247 247 } 248 248 ··· 251 251 registry.updateController(controller1, webDIDMedium, controller2); 252 252 253 253 bytes32 cowHash = registry.calculateCowHash(controller1, webDIDMedium); 254 - (address ctrl, , , ) = registry.cows(cowHash); 254 + (address ctrl,,,) = registry.cows(cowHash); 255 255 assertEq(ctrl, controller2); 256 256 } 257 257 ··· 260 260 registry.updateController(controller1, webDIDLong, controller2); 261 261 262 262 bytes32 cowHash = registry.calculateCowHash(controller1, webDIDLong); 263 - (address ctrl, , , ) = registry.cows(cowHash); 263 + (address ctrl,,,) = registry.cows(cowHash); 264 264 assertEq(ctrl, controller2); 265 265 } 266 266 ··· 269 269 registry.updateController(controller1, webDIDVeryLong, controller2); 270 270 271 271 bytes32 cowHash = registry.calculateCowHash(controller1, webDIDVeryLong); 272 - (address ctrl, , , ) = registry.cows(cowHash); 272 + (address ctrl,,,) = registry.cows(cowHash); 273 273 assertEq(ctrl, controller2); 274 274 } 275 275 ··· 333 333 vm.prank(controller1); 334 334 registry.deactivateByHash(cowHash); 335 335 336 - (, , , string memory did) = registry.cows(cowHash); 336 + (,,, string memory did) = registry.cows(cowHash); 337 337 assertEq(did, ""); 338 338 } 339 339