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.

Use a bit flag for activation instead of trying to read the wrapped did value

+83 -43
+1 -1
README.md
··· 163 163 164 164 ## 9. Reference Implementation 165 165 166 - Deployed on Sepolia testnet: [`0x3D9Dbf9b1Ed2c1E0Cc9995f38d50d94230A63E9A`](https://sepolia.etherscan.io/address/0x3D9Dbf9b1Ed2c1E0Cc9995f38d50d94230A63E9A) 166 + Deployed on Sepolia testnet: [`0x40CEB9a624E79e5bA117775D176F0059E70C92FB`](https://sepolia.etherscan.io/address/0x40CEB9a624E79e5bA117775D176F0059E70C92FB) 167 167 168 168 **Contract functions (`CowRegistry.sol`):** 169 169 - `calculateCowHash(controller, wrappedDID)` — derive the registry key for a did:cow ID
+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()[1] != "" 216 + registered = contract.functions.cows(cow_hash).call()[2] 217 217 218 218 click.echo(f"status: {'active' if registered else 'not registered on-chain'}") 219 219 click.echo(f"wrapped: {wrapped_did}")
+25 -17
src/CowRegistry.sol
··· 12 12 /// @notice On-chain state for a single did:cow identifier. 13 13 /// @dev The cowHash key is derived from the *initial* controller and wrapped DID, 14 14 /// so these fields reflect the *current* state after any updates. 15 + /// controller and deactivated are packed into a single storage slot. 15 16 struct Cow { 16 17 address controller; 18 + bool deactivated; 19 + bool initialized; 17 20 string wrappedDID; 18 21 } 19 - 20 - /// @dev Sentinel value stored in wrappedDID to mark a deactivated cow. 21 - string constant DEACTIVATED = ":"; 22 22 23 23 /// @notice Mapping from cow hash to current on-chain state. 24 24 /// @dev Returns zero values if the cow has never been registered on-chain. ··· 42 42 /// @param _cowHash The cow's registry key, as returned by calculateCowHash. 43 43 /// @param _wrappedDID The new wrapped DID, without the leading "did:" prefix. 44 44 function updateWrappedDIDByHash(bytes32 _cowHash, string memory _wrappedDID) public { 45 - require(msg.sender == cows[_cowHash].controller); 46 - require(bytes(_wrappedDID).length > 1, "Use deactivate() to deactivate"); 45 + Cow storage cow = cows[_cowHash]; 46 + require(msg.sender == cow.controller && !cow.deactivated); 47 + require(bytes(_wrappedDID).length > 0); 47 48 48 - cows[_cowHash].wrappedDID = _wrappedDID; 49 + cow.wrappedDID = _wrappedDID; 49 50 emit WrappedDIDUpdated(_cowHash, _wrappedDID); 50 51 } 51 52 ··· 57 58 /// @param _cowHash The cow's registry key, as returned by calculateCowHash. 58 59 /// @param _controller The new controller address. 59 60 function updateControllerByHash(bytes32 _cowHash, address _controller) public { 60 - require(msg.sender == cows[_cowHash].controller); 61 + Cow storage cow = cows[_cowHash]; 62 + require(msg.sender == cow.controller && !cow.deactivated); 61 63 62 - cows[_cowHash].controller = _controller; 64 + cow.controller = _controller; 63 65 emit ControllerUpdated(_cowHash, _controller); 64 66 } 65 67 ··· 68 70 /// Use deactivate if the cow may not yet be registered on-chain. 69 71 /// @param _cowHash The cow's registry key, as returned by calculateCowHash. 70 72 function deactivateByHash(bytes32 _cowHash) public { 71 - require(msg.sender == cows[_cowHash].controller); 73 + Cow storage cow = cows[_cowHash]; 74 + require(msg.sender == cow.controller && !cow.deactivated); 72 75 73 - cows[_cowHash].wrappedDID = DEACTIVATED; 74 - cows[_cowHash].controller = address(0); 76 + cow.deactivated = true; 77 + cow.controller = address(0); 78 + delete cow.wrappedDID; 75 79 76 80 emit CowDeactivated(_cowHash); 77 81 } ··· 87 91 /// @dev Register a cow on-chain if not already present, then return its hash. 88 92 function _ensureCowInitialized(address _controller, string memory _wrappedDID) internal returns (bytes32 cowHash) { 89 93 cowHash = calculateCowHash(_controller, _wrappedDID); 90 - if (bytes(cows[cowHash].wrappedDID).length == 0) { 91 - cows[cowHash] = Cow(_controller, _wrappedDID); 94 + Cow storage cow = cows[cowHash]; 95 + require(!cow.deactivated); 96 + if (!cow.initialized) { 97 + cow.initialized = true; 98 + cow.controller = _controller; 99 + cow.wrappedDID = _wrappedDID; 92 100 emit CowInitialized(cowHash, _controller, _wrappedDID); 93 101 } 94 102 return cowHash; ··· 103 111 function resolveCow(address _controller, string memory _wrappedDID) external view returns (string memory wrappedDID, address controller) { 104 112 bytes32 cowHash = calculateCowHash(_controller, _wrappedDID); 105 113 Cow storage cow = cows[cowHash]; 106 - if (bytes(cow.wrappedDID).length == 0) { 107 - return (string.concat("did:", _wrappedDID), _controller); 108 - } 109 - if (keccak256(bytes(cow.wrappedDID)) == keccak256(bytes(DEACTIVATED))) { 114 + if (cow.deactivated) { 110 115 return ("", address(0)); 116 + } 117 + if (!cow.initialized) { 118 + return (string.concat("did:", _wrappedDID), _controller); 111 119 } 112 120 return (string.concat("did:", cow.wrappedDID), cow.controller); 113 121 }
+56 -24
test/CowRegistry.t.sol
··· 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 ··· 303 303 vm.prank(controller1); 304 304 vm.expectRevert(); 305 305 registry.updateWrappedDIDByHash(cowHash, plcDID2); 306 + } 307 + 308 + function test_updateController_rejectsAfterDeactivation() public { 309 + bytes32 cowHash = _init(controller1, plcDID1); 310 + 311 + vm.prank(controller1); 312 + registry.deactivateByHash(cowHash); 313 + 314 + vm.prank(controller1); 315 + vm.expectRevert(); 316 + registry.updateControllerByHash(cowHash, controller2); 317 + } 318 + 319 + function test_deactivate_rejectsAlreadyDeactivated() public { 320 + bytes32 cowHash = _init(controller1, plcDID1); 321 + 322 + vm.prank(controller1); 323 + registry.deactivateByHash(cowHash); 324 + 325 + vm.prank(controller1); 326 + vm.expectRevert(); 327 + registry.deactivateByHash(cowHash); 328 + } 329 + 330 + function test_deactivate_clearsWrappedDID() public { 331 + bytes32 cowHash = _init(controller1, plcDID1); 332 + 333 + vm.prank(controller1); 334 + registry.deactivateByHash(cowHash); 335 + 336 + (, , , string memory did) = registry.cows(cowHash); 337 + assertEq(did, ""); 306 338 } 307 339 }