···11-## Foundry
11+# The did:cow Method Specification v0.1
22+33+**Status:** Draft Specification
44+**Date:** February 16, 2026
55+66+## Abstract
77+88+The `did:cow` method (Consensus Ownership Wrapper) provides persistent wrappers around other DID methods, enabling rotation and migration without breaking existing references.
99+It stores changes of control (done currently done with `rotationKeys` in DID:PLC) on the Ethereum blockchain.
1010+1111+## Status of This Document
1212+1313+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.
1414+1515+## 1. Introduction
1616+1717+### 1.1 Motivation
1818+1919+Existing DID methods have tradeoffs:
2020+- **did:key** - No rotation or recovery
2121+- **did:web** - Domain dependency, if you lose control of your domain you lose control of your identity
2222+- **did:plc** - Dependency on a centralized sequencer (Bluesky's PLC server)
2323+- **did:ethr** - Gas costs for all updates
2424+2525+Migrating between methods breaks all existing references. `did:cow` provides a stable wrapper.
2626+2727+### 1.2 Design Goals
2828+2929+1. **Persistent** - Wrapper DID never changes
3030+2. **Zero-cost creation** - No blockchain transaction to create
3131+3. **Method agnostic** - Wraps any DID method
3232+4. **Decentralized** - No central registry dependency
3333+5. **Transferable** - Controller can be changed
3434+6. **Composible Control** - Automatic compatibility with multisig and decentralized organization tooling such as Gnosis Safe.
23533-**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**
3636+## 2. DID Method Name
43755-Foundry consists of:
3838+Method name: `cow` (Consensus Ownership Wrapper)
63977-- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
88-- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
99-- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
1010-- **Chisel**: Fast, utilitarian, and verbose solidity REPL.
4040+DID prefix: `did:cow:` (lowercase)
11411212-## Documentation
4242+## 3. Method Specific Identifier
13431414-https://book.getfoundry.sh/
4444+Format: `did:cow:<initial_controller_address>:<initial_wrapped_did>`
15451616-## Usage
17461818-### Build
4747+**Parameters:**
4848+- `initial_controller_address` - Ethereum address (20 bytes, no "0x" prefix)
4949+- `initial_wrapped_did` - UTF-8 encoded DID string
5050+5151+### 3.1 Example
19522020-```shell
2121-$ forge build
2253```
5454+initial_controller_address = "8BC101ABF5BcF8b6209FaaAD4D761C1ED14999Be" (20 bytes, no 0x prefix)
5555+wrapped_did = "did:web:example.com"
23562424-### Test
5757+DID = did:cow:8BC101ABF5BcF8b6209FaaAD4D761C1ED14999Be:web:example.com
5858+```
5959+6060+## 5. Blockchain Transaction Model
6161+6262+State mutations (updates/deactivations) are standard Ethereum transactions from the controller address.
6363+6464+1. Controller creates transaction with operation data
6565+2. Controller signs with Ethereum key
6666+3. Transaction broadcast to Ethereum
6767+4. Smart contract validates: `msg.sender == current_controller`
6868+5. State updated or transaction reverts
6969+7070+## 6. CRUD Operations
7171+7272+### 6.1 Create
7373+7474+1. Create the wrapped DID
7575+2. Choose your initial controller address
7676+2. Insert `cow:<initial_controller_address>:` after the initial `did`:.
7777+7878+### 6.2 Read (Resolution)
7979+8080+1. Query an Ethereum RPC endpoint to find out the wrapped DID
8181+2. If it returns a value, resolve that as per that DID's standard
8282+3. If it is unset, use the DID value originally specified in the ID
8383+8484+Resolved DID document includes wrapped DID's content plus wrapper metadata.
8585+8686+### 6.3 Update
8787+8888+Make an on-chain transaction from the current controller.
8989+9090+The initial update
9191+9292+### 6.4 Deactivate
9393+9494+Permanent. On-chain transaction from current controller.
9595+9696+Set the controller address to `0x` and the wrapped DID value to `did::`.
9797+9898+After deactivation, DID resolves to deactivated status. Cannot be reactivated.
9999+100100+## 7. Security Considerations
101101+102102+### 7.1 Controller
103103+104104+The controller address inherits all the security considerations of any other Ethereum address.
105105+106106+### 7.2 Wrapped DID Dependence
107107+108108+The did:cow address inherits all security properties of wrapped DID.
109109+- did:web → DNS hijacking risk
110110+- did:key → no rotation
111111+- did:plc → trust in Bluesky's directory
112112+113113+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.
114114+115115+### 7.3 Blockchain Dependencies
116116+117117+**Why Ethereum:**
118118+119119+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.
120120+121121+**Tradeoffs:** Gas costs (~50-100k gas per update), ~12 second confirmation
122122+123123+## 8. Privacy Considerations
124124+125125+### 8.1 Controller Address Linkability
126126+127127+`controller_address` is visible as part of the DID and also on-chain once updates are made. Reusing a controller links all DIDs.
128128+129129+### 8.2 On-Chain Metadata
130130+131131+All updates permanently public with timestamps. Creates audit trail of updates, previous/new wrapped DIDs, and controller history.
132132+133133+## 9. Reference Implementation
134134+135135+Available at: [To be provided]
251362626-```shell
2727-$ forge test
2828-```
137137+**Key functions:**
138138+- `createDID(controllerHex, wrappedDid)` - Generate did:cow
139139+- `parseInitialState(stateBytes)` - Parse binary state
140140+- `resolveDID(didCow)` - Resolve to DID document
141141+- `updateDID(didCow, newWrappedDid, newController)` - Build update transaction
142142+- `deactivateDID(didCow)` - Build deactivation transaction
291433030-### Format
144144+## 10. Example DID Document
311453232-```shell
3333-$ forge fmt
146146+Given:
147147+```
148148+did:cow:8BC101ABF5BcF8b6209FaaAD4D761C1ED14999Be:web:example.com
34149```
351503636-### Gas Snapshots
151151+Wrapping:
152152+```
153153+did:web:example.com
154154+```
371553838-```shell
3939-$ forge snapshot
156156+Resolved DID Document:
157157+```json
158158+{
159159+ "@context": [
160160+ "https://www.w3.org/ns/did/v1",
161161+ "https://w3id.org/security/suites/jws-2020/v1"
162162+ ],
163163+ "id": "did:cow:8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52",
164164+ "controller": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
165165+ "verificationMethod": [
166166+ {
167167+ "id": "did:web:example.com#key-1",
168168+ "type": "JsonWebKey2020",
169169+ "controller": "did:cow:8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52",
170170+ "publicKeyJwk": {
171171+ "kty": "EC",
172172+ "crv": "secp256k1",
173173+ "x": "...",
174174+ "y": "..."
175175+ }
176176+ }
177177+ ],
178178+ "authentication": [
179179+ "did:web:example.com#key-1"
180180+ ],
181181+ "service": [
182182+ {
183183+ "id": "#wrapper-metadata",
184184+ "type": "COWWrapper",
185185+ "serviceEndpoint": {
186186+ "wrapped_did": "did:web:example.com",
187187+ "wrapper_controller": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
188188+ "on_chain_state": true,
189189+ "last_updated": "2026-02-16T10:30:00Z"
190190+ }
191191+ }
192192+ ]
193193+}
40194```
411954242-### Anvil
196196+## 11. Comparison
431974444-```shell
4545-$ anvil
4646-```
198198+| Feature | did:cow | did:key | did:web | did:plc |
199199+|---------|---------|---------|---------|---------|
200200+| Rotation Support | ✓ | ✗ | ✓ | ✓ |
201201+| Zero-cost Creation | ✓ | ✓ | ✓ | ✓ |
202202+| Zero-cost Controller Updates | ✗ | ✓ | ✓ | ✓ |
203203+| Decentralized | ✓ | ✓ | ✗ | ✗ |
204204+| Zero-cost Controller Updates | ✓ | ✓ | ✓ | ✓ |
205205+| Decentralized | ✓ | ✓ | ✗ | ✗ |
206206+| Blockchain Required | Ethereum | None | None | None |
207207+| Rotation Authority | Ethereum | N/A | DNS | PLC Directory |
208208+| Censorship Resistant | ✓ | ✓ | ✗ | ✗ |
472094848-### Deploy
210210+## 12. Philosophical considerations
492115050-```shell
5151-$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
5252-```
212212+DIDs are intended to be permanent identifiers. Using a wrapper implies that the wrapped DID is not in fact a permanent identifier.
532135454-### Cast
214214+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.
552155656-```shell
5757-$ cast <subcommand>
5858-```
216216+## 13. References
592176060-### Help
218218+- [DID Core Specification](https://www.w3.org/TR/did-core/)
219219+- [DID Method Rubric](https://w3c.github.io/did-rubric/)
220220+- [did:key Method](https://w3c-ccg.github.io/did-method-key/)
221221+- [did:web Method](https://w3c-ccg.github.io/did-method-web/)
222222+- [did:plc Method](https://github.com/did-method-plc/did-method-plc)
223223+224224+---
612256262-```shell
6363-$ forge --help
6464-$ anvil --help
6565-$ cast --help
6666-```
226226+**Version History:**
227227+- v0.1 (2026-02-16) - Initial draft specification
-227
spec.md
···11-# The did:cow Method Specification v0.1
22-33-**Status:** Draft Specification
44-**Date:** February 16, 2026
55-66-## Abstract
77-88-The `did:cow` method (Consensus Ownership Wrapper) provides persistent wrappers around other DID methods, enabling rotation and migration without breaking existing references.
99-It stores changes of control (done currently done with `rotationKeys` in DID:PLC) on the Ethereum blockchain.
1010-1111-## Status of This Document
1212-1313-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.
1414-1515-## 1. Introduction
1616-1717-### 1.1 Motivation
1818-1919-Existing DID methods have tradeoffs:
2020-- **did:key** - No rotation or recovery
2121-- **did:web** - Domain dependency, if you lose control of your domain you lose control of your identity
2222-- **did:plc** - Dependency on a centralized sequencer (Bluesky's PLC server)
2323-- **did:ethr** - Gas costs for all updates
2424-2525-Migrating between methods breaks all existing references. `did:cow` provides a stable wrapper.
2626-2727-### 1.2 Design Goals
2828-2929-1. **Persistent** - Wrapper DID never changes
3030-2. **Zero-cost creation** - No blockchain transaction to create
3131-3. **Method agnostic** - Wraps any DID method
3232-4. **Decentralized** - No central registry dependency
3333-5. **Transferable** - Controller can be changed
3434-6. **Composible Control** - Automatic compatibility with multisig and decentralized organization tooling such as Gnosis Safe.
3535-3636-## 2. DID Method Name
3737-3838-Method name: `cow` (Consensus Ownership Wrapper)
3939-4040-DID prefix: `did:cow:` (lowercase)
4141-4242-## 3. Method Specific Identifier
4343-4444-Format: `did:cow:<initial_controller_address>:<initial_wrapped_did>`
4545-4646-4747-**Parameters:**
4848-- `initial_controller_address` - Ethereum address (20 bytes, no "0x" prefix)
4949-- `initial_wrapped_did` - UTF-8 encoded DID string
5050-5151-### 3.1 Example
5252-5353-```
5454-initial_controller_address = "8BC101ABF5BcF8b6209FaaAD4D761C1ED14999Be" (20 bytes, no 0x prefix)
5555-wrapped_did = "did:web:example.com"
5656-5757-DID = did:cow:8BC101ABF5BcF8b6209FaaAD4D761C1ED14999Be:web:example.com
5858-```
5959-6060-## 5. Blockchain Transaction Model
6161-6262-State mutations (updates/deactivations) are standard Ethereum transactions from the controller address.
6363-6464-1. Controller creates transaction with operation data
6565-2. Controller signs with Ethereum key
6666-3. Transaction broadcast to Ethereum
6767-4. Smart contract validates: `msg.sender == current_controller`
6868-5. State updated or transaction reverts
6969-7070-## 6. CRUD Operations
7171-7272-### 6.1 Create
7373-7474-1. Create the wrapped DID
7575-2. Choose your initial controller address
7676-2. Insert `cow:<initial_controller_address>:` after the initial `did`:.
7777-7878-### 6.2 Read (Resolution)
7979-8080-1. Query an Ethereum RPC endpoint to find out the wrapped DID
8181-2. If it returns a value, resolve that as per that DID's standard
8282-3. If it is unset, use the DID value originally specified in the ID
8383-8484-Resolved DID document includes wrapped DID's content plus wrapper metadata.
8585-8686-### 6.3 Update
8787-8888-Make an on-chain transaction from the current controller.
8989-9090-The initial update
9191-9292-### 6.4 Deactivate
9393-9494-Permanent. On-chain transaction from current controller.
9595-9696-Set the controller address to `0x` and the wrapped DID value to `did::`.
9797-9898-After deactivation, DID resolves to deactivated status. Cannot be reactivated.
9999-100100-## 7. Security Considerations
101101-102102-### 7.1 Controller
103103-104104-The controller address inherits all the security considerations of any other Ethereum address.
105105-106106-### 7.2 Wrapped DID Dependence
107107-108108-The did:cow address inherits all security properties of wrapped DID.
109109-- did:web → DNS hijacking risk
110110-- did:key → no rotation
111111-- did:plc → trust in Bluesky's directory
112112-113113-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.
114114-115115-### 7.3 Blockchain Dependencies
116116-117117-**Why Ethereum:**
118118-119119-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.
120120-121121-**Tradeoffs:** Gas costs (~50-100k gas per update), ~12 second confirmation
122122-123123-## 8. Privacy Considerations
124124-125125-### 8.1 Controller Address Linkability
126126-127127-`controller_address` is visible as part of the DID and also on-chain once updates are made. Reusing a controller links all DIDs.
128128-129129-### 8.2 On-Chain Metadata
130130-131131-All updates permanently public with timestamps. Creates audit trail of updates, previous/new wrapped DIDs, and controller history.
132132-133133-## 9. Reference Implementation
134134-135135-Available at: [To be provided]
136136-137137-**Key functions:**
138138-- `createDID(controllerHex, wrappedDid)` - Generate did:cow
139139-- `parseInitialState(stateBytes)` - Parse binary state
140140-- `resolveDID(didCow)` - Resolve to DID document
141141-- `updateDID(didCow, newWrappedDid, newController)` - Build update transaction
142142-- `deactivateDID(didCow)` - Build deactivation transaction
143143-144144-## 10. Example DID Document
145145-146146-Given:
147147-```
148148-did:cow:8BC101ABF5BcF8b6209FaaAD4D761C1ED14999Be:web:example.com
149149-```
150150-151151-Wrapping:
152152-```
153153-did:web:example.com
154154-```
155155-156156-Resolved DID Document:
157157-```json
158158-{
159159- "@context": [
160160- "https://www.w3.org/ns/did/v1",
161161- "https://w3id.org/security/suites/jws-2020/v1"
162162- ],
163163- "id": "did:cow:8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52",
164164- "controller": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
165165- "verificationMethod": [
166166- {
167167- "id": "did:web:example.com#key-1",
168168- "type": "JsonWebKey2020",
169169- "controller": "did:cow:8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52",
170170- "publicKeyJwk": {
171171- "kty": "EC",
172172- "crv": "secp256k1",
173173- "x": "...",
174174- "y": "..."
175175- }
176176- }
177177- ],
178178- "authentication": [
179179- "did:web:example.com#key-1"
180180- ],
181181- "service": [
182182- {
183183- "id": "#wrapper-metadata",
184184- "type": "COWWrapper",
185185- "serviceEndpoint": {
186186- "wrapped_did": "did:web:example.com",
187187- "wrapper_controller": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
188188- "on_chain_state": true,
189189- "last_updated": "2026-02-16T10:30:00Z"
190190- }
191191- }
192192- ]
193193-}
194194-```
195195-196196-## 11. Comparison
197197-198198-| Feature | did:cow | did:key | did:web | did:plc |
199199-|---------|---------|---------|---------|---------|
200200-| Rotation Support | ✓ | ✗ | ✓ | ✓ |
201201-| Zero-cost Creation | ✓ | ✓ | ✓ | ✓ |
202202-| Zero-cost Controller Updates | ✗ | ✓ | ✓ | ✓ |
203203-| Decentralized | ✓ | ✓ | ✗ | ✗ |
204204-| Zero-cost Controller Updates | ✓ | ✓ | ✓ | ✓ |
205205-| Decentralized | ✓ | ✓ | ✗ | ✗ |
206206-| Blockchain Required | Ethereum | None | None | None |
207207-| Rotation Authority | Ethereum | N/A | DNS | PLC Directory |
208208-| Censorship Resistant | ✓ | ✓ | ✗ | ✗ |
209209-210210-## 12. Philosophical considerations
211211-212212-DIDs are intended to be permanent identifiers. Using a wrapper implies that the wrapped DID is not in fact a permanent identifier.
213213-214214-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.
215215-216216-## 13. References
217217-218218-- [DID Core Specification](https://www.w3.org/TR/did-core/)
219219-- [DID Method Rubric](https://w3c.github.io/did-rubric/)
220220-- [did:key Method](https://w3c-ccg.github.io/did-method-key/)
221221-- [did:web Method](https://w3c-ccg.github.io/did-method-web/)
222222-- [did:plc Method](https://github.com/did-method-plc/did-method-plc)
223223-224224----
225225-226226-**Version History:**
227227-- v0.1 (2026-02-16) - Initial draft specification