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.

Make the spec the readme

+202 -268
+202 -41
README.md
··· 1 - ## Foundry 1 + # The did:cow Method Specification v0.1 2 + 3 + **Status:** Draft Specification 4 + **Date:** February 16, 2026 5 + 6 + ## Abstract 7 + 8 + The `did:cow` method (Consensus Ownership Wrapper) provides persistent wrappers around other DID methods, enabling rotation and migration without breaking existing references. 9 + It stores changes of control (done currently done with `rotationKeys` in DID:PLC) on the Ethereum blockchain. 10 + 11 + ## Status of This Document 12 + 13 + This is a draft specification and may be updated, replaced, or obsoleted at any time. It is inappropriate to cite this document as anything other than work in progress. 14 + 15 + ## 1. Introduction 16 + 17 + ### 1.1 Motivation 18 + 19 + Existing DID methods have tradeoffs: 20 + - **did:key** - No rotation or recovery 21 + - **did:web** - Domain dependency, if you lose control of your domain you lose control of your identity 22 + - **did:plc** - Dependency on a centralized sequencer (Bluesky's PLC server) 23 + - **did:ethr** - Gas costs for all updates 24 + 25 + Migrating between methods breaks all existing references. `did:cow` provides a stable wrapper. 26 + 27 + ### 1.2 Design Goals 28 + 29 + 1. **Persistent** - Wrapper DID never changes 30 + 2. **Zero-cost creation** - No blockchain transaction to create 31 + 3. **Method agnostic** - Wraps any DID method 32 + 4. **Decentralized** - No central registry dependency 33 + 5. **Transferable** - Controller can be changed 34 + 6. **Composible Control** - Automatic compatibility with multisig and decentralized organization tooling such as Gnosis Safe. 2 35 3 - **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 36 + ## 2. DID Method Name 4 37 5 - Foundry consists of: 38 + Method name: `cow` (Consensus Ownership Wrapper) 6 39 7 - - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 - - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 - - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 - - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 40 + DID prefix: `did:cow:` (lowercase) 11 41 12 - ## Documentation 42 + ## 3. Method Specific Identifier 13 43 14 - https://book.getfoundry.sh/ 44 + Format: `did:cow:<initial_controller_address>:<initial_wrapped_did>` 15 45 16 - ## Usage 17 46 18 - ### Build 47 + **Parameters:** 48 + - `initial_controller_address` - Ethereum address (20 bytes, no "0x" prefix) 49 + - `initial_wrapped_did` - UTF-8 encoded DID string 50 + 51 + ### 3.1 Example 19 52 20 - ```shell 21 - $ forge build 22 53 ``` 54 + initial_controller_address = "8BC101ABF5BcF8b6209FaaAD4D761C1ED14999Be" (20 bytes, no 0x prefix) 55 + wrapped_did = "did:web:example.com" 23 56 24 - ### Test 57 + DID = did:cow:8BC101ABF5BcF8b6209FaaAD4D761C1ED14999Be:web:example.com 58 + ``` 59 + 60 + ## 5. Blockchain Transaction Model 61 + 62 + State mutations (updates/deactivations) are standard Ethereum transactions from the controller address. 63 + 64 + 1. Controller creates transaction with operation data 65 + 2. Controller signs with Ethereum key 66 + 3. Transaction broadcast to Ethereum 67 + 4. Smart contract validates: `msg.sender == current_controller` 68 + 5. State updated or transaction reverts 69 + 70 + ## 6. CRUD Operations 71 + 72 + ### 6.1 Create 73 + 74 + 1. Create the wrapped DID 75 + 2. Choose your initial controller address 76 + 2. Insert `cow:<initial_controller_address>:` after the initial `did`:. 77 + 78 + ### 6.2 Read (Resolution) 79 + 80 + 1. Query an Ethereum RPC endpoint to find out the wrapped DID 81 + 2. If it returns a value, resolve that as per that DID's standard 82 + 3. If it is unset, use the DID value originally specified in the ID 83 + 84 + Resolved DID document includes wrapped DID's content plus wrapper metadata. 85 + 86 + ### 6.3 Update 87 + 88 + Make an on-chain transaction from the current controller. 89 + 90 + The initial update 91 + 92 + ### 6.4 Deactivate 93 + 94 + Permanent. On-chain transaction from current controller. 95 + 96 + Set the controller address to `0x` and the wrapped DID value to `did::`. 97 + 98 + After deactivation, DID resolves to deactivated status. Cannot be reactivated. 99 + 100 + ## 7. Security Considerations 101 + 102 + ### 7.1 Controller 103 + 104 + The controller address inherits all the security considerations of any other Ethereum address. 105 + 106 + ### 7.2 Wrapped DID Dependence 107 + 108 + The did:cow address inherits all security properties of wrapped DID. 109 + - did:web → DNS hijacking risk 110 + - did:key → no rotation 111 + - did:plc → trust in Bluesky's directory 112 + 113 + However, since users can switch to another wrapped DID they can recover a compromise of the wrapped DID, and also exit in circumstances where the wrapped DID appears unreliable. 114 + 115 + ### 7.3 Blockchain Dependencies 116 + 117 + **Why Ethereum:** 118 + 119 + High security, established ecosystem, established tooling for multisig and organizational control. Strong social consensus on anti-censorship means we can be confident that the main Ethereum chain, or failing that a viable fork of the Ethereum chain, will accept continue accepting updates without censorship for the foreseeable future. 120 + 121 + **Tradeoffs:** Gas costs (~50-100k gas per update), ~12 second confirmation 122 + 123 + ## 8. Privacy Considerations 124 + 125 + ### 8.1 Controller Address Linkability 126 + 127 + `controller_address` is visible as part of the DID and also on-chain once updates are made. Reusing a controller links all DIDs. 128 + 129 + ### 8.2 On-Chain Metadata 130 + 131 + All updates permanently public with timestamps. Creates audit trail of updates, previous/new wrapped DIDs, and controller history. 132 + 133 + ## 9. Reference Implementation 134 + 135 + Available at: [To be provided] 25 136 26 - ```shell 27 - $ forge test 28 - ``` 137 + **Key functions:** 138 + - `createDID(controllerHex, wrappedDid)` - Generate did:cow 139 + - `parseInitialState(stateBytes)` - Parse binary state 140 + - `resolveDID(didCow)` - Resolve to DID document 141 + - `updateDID(didCow, newWrappedDid, newController)` - Build update transaction 142 + - `deactivateDID(didCow)` - Build deactivation transaction 29 143 30 - ### Format 144 + ## 10. Example DID Document 31 145 32 - ```shell 33 - $ forge fmt 146 + Given: 147 + ``` 148 + did:cow:8BC101ABF5BcF8b6209FaaAD4D761C1ED14999Be:web:example.com 34 149 ``` 35 150 36 - ### Gas Snapshots 151 + Wrapping: 152 + ``` 153 + did:web:example.com 154 + ``` 37 155 38 - ```shell 39 - $ forge snapshot 156 + Resolved DID Document: 157 + ```json 158 + { 159 + "@context": [ 160 + "https://www.w3.org/ns/did/v1", 161 + "https://w3id.org/security/suites/jws-2020/v1" 162 + ], 163 + "id": "did:cow:8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52", 164 + "controller": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", 165 + "verificationMethod": [ 166 + { 167 + "id": "did:web:example.com#key-1", 168 + "type": "JsonWebKey2020", 169 + "controller": "did:cow:8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52", 170 + "publicKeyJwk": { 171 + "kty": "EC", 172 + "crv": "secp256k1", 173 + "x": "...", 174 + "y": "..." 175 + } 176 + } 177 + ], 178 + "authentication": [ 179 + "did:web:example.com#key-1" 180 + ], 181 + "service": [ 182 + { 183 + "id": "#wrapper-metadata", 184 + "type": "COWWrapper", 185 + "serviceEndpoint": { 186 + "wrapped_did": "did:web:example.com", 187 + "wrapper_controller": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", 188 + "on_chain_state": true, 189 + "last_updated": "2026-02-16T10:30:00Z" 190 + } 191 + } 192 + ] 193 + } 40 194 ``` 41 195 42 - ### Anvil 196 + ## 11. Comparison 43 197 44 - ```shell 45 - $ anvil 46 - ``` 198 + | Feature | did:cow | did:key | did:web | did:plc | 199 + |---------|---------|---------|---------|---------| 200 + | Rotation Support | ✓ | ✗ | ✓ | ✓ | 201 + | Zero-cost Creation | ✓ | ✓ | ✓ | ✓ | 202 + | Zero-cost Controller Updates | ✗ | ✓ | ✓ | ✓ | 203 + | Decentralized | ✓ | ✓ | ✗ | ✗ | 204 + | Zero-cost Controller Updates | ✓ | ✓ | ✓ | ✓ | 205 + | Decentralized | ✓ | ✓ | ✗ | ✗ | 206 + | Blockchain Required | Ethereum | None | None | None | 207 + | Rotation Authority | Ethereum | N/A | DNS | PLC Directory | 208 + | Censorship Resistant | ✓ | ✓ | ✗ | ✗ | 47 209 48 - ### Deploy 210 + ## 12. Philosophical considerations 49 211 50 - ```shell 51 - $ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key> 52 - ``` 212 + DIDs are intended to be permanent identifiers. Using a wrapper implies that the wrapped DID is not in fact a permanent identifier. 53 213 54 - ### Cast 214 + We consider this to illuminate a problem with the wrapped DIDs, rather than with this proposal. A permanent wrapper is required because users cannot be sufficiently confident in the permanence of their existing options. 55 215 56 - ```shell 57 - $ cast <subcommand> 58 - ``` 216 + ## 13. References 59 217 60 - ### Help 218 + - [DID Core Specification](https://www.w3.org/TR/did-core/) 219 + - [DID Method Rubric](https://w3c.github.io/did-rubric/) 220 + - [did:key Method](https://w3c-ccg.github.io/did-method-key/) 221 + - [did:web Method](https://w3c-ccg.github.io/did-method-web/) 222 + - [did:plc Method](https://github.com/did-method-plc/did-method-plc) 223 + 224 + --- 61 225 62 - ```shell 63 - $ forge --help 64 - $ anvil --help 65 - $ cast --help 66 - ``` 226 + **Version History:** 227 + - v0.1 (2026-02-16) - Initial draft specification
-227
spec.md
··· 1 - # The did:cow Method Specification v0.1 2 - 3 - **Status:** Draft Specification 4 - **Date:** February 16, 2026 5 - 6 - ## Abstract 7 - 8 - The `did:cow` method (Consensus Ownership Wrapper) provides persistent wrappers around other DID methods, enabling rotation and migration without breaking existing references. 9 - It stores changes of control (done currently done with `rotationKeys` in DID:PLC) on the Ethereum blockchain. 10 - 11 - ## Status of This Document 12 - 13 - This is a draft specification and may be updated, replaced, or obsoleted at any time. It is inappropriate to cite this document as anything other than work in progress. 14 - 15 - ## 1. Introduction 16 - 17 - ### 1.1 Motivation 18 - 19 - Existing DID methods have tradeoffs: 20 - - **did:key** - No rotation or recovery 21 - - **did:web** - Domain dependency, if you lose control of your domain you lose control of your identity 22 - - **did:plc** - Dependency on a centralized sequencer (Bluesky's PLC server) 23 - - **did:ethr** - Gas costs for all updates 24 - 25 - Migrating between methods breaks all existing references. `did:cow` provides a stable wrapper. 26 - 27 - ### 1.2 Design Goals 28 - 29 - 1. **Persistent** - Wrapper DID never changes 30 - 2. **Zero-cost creation** - No blockchain transaction to create 31 - 3. **Method agnostic** - Wraps any DID method 32 - 4. **Decentralized** - No central registry dependency 33 - 5. **Transferable** - Controller can be changed 34 - 6. **Composible Control** - Automatic compatibility with multisig and decentralized organization tooling such as Gnosis Safe. 35 - 36 - ## 2. DID Method Name 37 - 38 - Method name: `cow` (Consensus Ownership Wrapper) 39 - 40 - DID prefix: `did:cow:` (lowercase) 41 - 42 - ## 3. Method Specific Identifier 43 - 44 - Format: `did:cow:<initial_controller_address>:<initial_wrapped_did>` 45 - 46 - 47 - **Parameters:** 48 - - `initial_controller_address` - Ethereum address (20 bytes, no "0x" prefix) 49 - - `initial_wrapped_did` - UTF-8 encoded DID string 50 - 51 - ### 3.1 Example 52 - 53 - ``` 54 - initial_controller_address = "8BC101ABF5BcF8b6209FaaAD4D761C1ED14999Be" (20 bytes, no 0x prefix) 55 - wrapped_did = "did:web:example.com" 56 - 57 - DID = did:cow:8BC101ABF5BcF8b6209FaaAD4D761C1ED14999Be:web:example.com 58 - ``` 59 - 60 - ## 5. Blockchain Transaction Model 61 - 62 - State mutations (updates/deactivations) are standard Ethereum transactions from the controller address. 63 - 64 - 1. Controller creates transaction with operation data 65 - 2. Controller signs with Ethereum key 66 - 3. Transaction broadcast to Ethereum 67 - 4. Smart contract validates: `msg.sender == current_controller` 68 - 5. State updated or transaction reverts 69 - 70 - ## 6. CRUD Operations 71 - 72 - ### 6.1 Create 73 - 74 - 1. Create the wrapped DID 75 - 2. Choose your initial controller address 76 - 2. Insert `cow:<initial_controller_address>:` after the initial `did`:. 77 - 78 - ### 6.2 Read (Resolution) 79 - 80 - 1. Query an Ethereum RPC endpoint to find out the wrapped DID 81 - 2. If it returns a value, resolve that as per that DID's standard 82 - 3. If it is unset, use the DID value originally specified in the ID 83 - 84 - Resolved DID document includes wrapped DID's content plus wrapper metadata. 85 - 86 - ### 6.3 Update 87 - 88 - Make an on-chain transaction from the current controller. 89 - 90 - The initial update 91 - 92 - ### 6.4 Deactivate 93 - 94 - Permanent. On-chain transaction from current controller. 95 - 96 - Set the controller address to `0x` and the wrapped DID value to `did::`. 97 - 98 - After deactivation, DID resolves to deactivated status. Cannot be reactivated. 99 - 100 - ## 7. Security Considerations 101 - 102 - ### 7.1 Controller 103 - 104 - The controller address inherits all the security considerations of any other Ethereum address. 105 - 106 - ### 7.2 Wrapped DID Dependence 107 - 108 - The did:cow address inherits all security properties of wrapped DID. 109 - - did:web → DNS hijacking risk 110 - - did:key → no rotation 111 - - did:plc → trust in Bluesky's directory 112 - 113 - However, since users can switch to another wrapped DID they can recover a compromise of the wrapped DID, and also exit in circumstances where the wrapped DID appears unreliable. 114 - 115 - ### 7.3 Blockchain Dependencies 116 - 117 - **Why Ethereum:** 118 - 119 - High security, established ecosystem, established tooling for multisig and organizational control. Strong social consensus on anti-censorship means we can be confident that the main Ethereum chain, or failing that a viable fork of the Ethereum chain, will accept continue accepting updates without censorship for the foreseeable future. 120 - 121 - **Tradeoffs:** Gas costs (~50-100k gas per update), ~12 second confirmation 122 - 123 - ## 8. Privacy Considerations 124 - 125 - ### 8.1 Controller Address Linkability 126 - 127 - `controller_address` is visible as part of the DID and also on-chain once updates are made. Reusing a controller links all DIDs. 128 - 129 - ### 8.2 On-Chain Metadata 130 - 131 - All updates permanently public with timestamps. Creates audit trail of updates, previous/new wrapped DIDs, and controller history. 132 - 133 - ## 9. Reference Implementation 134 - 135 - Available at: [To be provided] 136 - 137 - **Key functions:** 138 - - `createDID(controllerHex, wrappedDid)` - Generate did:cow 139 - - `parseInitialState(stateBytes)` - Parse binary state 140 - - `resolveDID(didCow)` - Resolve to DID document 141 - - `updateDID(didCow, newWrappedDid, newController)` - Build update transaction 142 - - `deactivateDID(didCow)` - Build deactivation transaction 143 - 144 - ## 10. Example DID Document 145 - 146 - Given: 147 - ``` 148 - did:cow:8BC101ABF5BcF8b6209FaaAD4D761C1ED14999Be:web:example.com 149 - ``` 150 - 151 - Wrapping: 152 - ``` 153 - did:web:example.com 154 - ``` 155 - 156 - Resolved DID Document: 157 - ```json 158 - { 159 - "@context": [ 160 - "https://www.w3.org/ns/did/v1", 161 - "https://w3id.org/security/suites/jws-2020/v1" 162 - ], 163 - "id": "did:cow:8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52", 164 - "controller": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", 165 - "verificationMethod": [ 166 - { 167 - "id": "did:web:example.com#key-1", 168 - "type": "JsonWebKey2020", 169 - "controller": "did:cow:8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52", 170 - "publicKeyJwk": { 171 - "kty": "EC", 172 - "crv": "secp256k1", 173 - "x": "...", 174 - "y": "..." 175 - } 176 - } 177 - ], 178 - "authentication": [ 179 - "did:web:example.com#key-1" 180 - ], 181 - "service": [ 182 - { 183 - "id": "#wrapper-metadata", 184 - "type": "COWWrapper", 185 - "serviceEndpoint": { 186 - "wrapped_did": "did:web:example.com", 187 - "wrapper_controller": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", 188 - "on_chain_state": true, 189 - "last_updated": "2026-02-16T10:30:00Z" 190 - } 191 - } 192 - ] 193 - } 194 - ``` 195 - 196 - ## 11. Comparison 197 - 198 - | Feature | did:cow | did:key | did:web | did:plc | 199 - |---------|---------|---------|---------|---------| 200 - | Rotation Support | ✓ | ✗ | ✓ | ✓ | 201 - | Zero-cost Creation | ✓ | ✓ | ✓ | ✓ | 202 - | Zero-cost Controller Updates | ✗ | ✓ | ✓ | ✓ | 203 - | Decentralized | ✓ | ✓ | ✗ | ✗ | 204 - | Zero-cost Controller Updates | ✓ | ✓ | ✓ | ✓ | 205 - | Decentralized | ✓ | ✓ | ✗ | ✗ | 206 - | Blockchain Required | Ethereum | None | None | None | 207 - | Rotation Authority | Ethereum | N/A | DNS | PLC Directory | 208 - | Censorship Resistant | ✓ | ✓ | ✗ | ✗ | 209 - 210 - ## 12. Philosophical considerations 211 - 212 - DIDs are intended to be permanent identifiers. Using a wrapper implies that the wrapped DID is not in fact a permanent identifier. 213 - 214 - We consider this to illuminate a problem with the wrapped DIDs, rather than with this proposal. A permanent wrapper is required because users cannot be sufficiently confident in the permanence of their existing options. 215 - 216 - ## 13. References 217 - 218 - - [DID Core Specification](https://www.w3.org/TR/did-core/) 219 - - [DID Method Rubric](https://w3c.github.io/did-rubric/) 220 - - [did:key Method](https://w3c-ccg.github.io/did-method-key/) 221 - - [did:web Method](https://w3c-ccg.github.io/did-method-web/) 222 - - [did:plc Method](https://github.com/did-method-plc/did-method-plc) 223 - 224 - --- 225 - 226 - **Version History:** 227 - - v0.1 (2026-02-16) - Initial draft specification