The did:permara DID method
Version: 1.0.0 (draft) · Date: 2026-09-13 · Maintainer: Permara
Abstract
did:permara identifies a business or person that can be paid through Permara. The identifier is generated, not chosen, and is never derived from a name, a key or a chain. Its state lives in a small registry contract on an EVM network; its DID document lives at an HTTPS location the registry names and is accepted only when the document's canonical hash equals the hash the registry holds. This document specifies the method as required by the W3C Decentralized Identifiers (DIDs) v1.0 Recommendation and the method requirements of DIDs v1.1.
Status of this document
Draft of the first method version. It conforms to DID Core v1.0 and is written to satisfy the method requirements of DID v1.1. Nothing here is endorsed by the W3C; registration in the DID Extensions repository is a discoverability step.
Conformance and terminology
The key words MUST, MUST NOT, SHOULD and MAY are to be interpreted as described in RFC 2119. DID, DID document, DID controller, DID subject, verification method, verification relationship, service and verifiable data registry have their DID Core meanings. "Registry" means the PermaraDidRegistry contract named in the registry table. "Operator" means Permara, the party that holds the registrar role.
Method name
The method name is permara. A DID that uses this method MUST begin with did:permara:.
Method-specific identifier
Syntax
permara-did = "did:permara:" suffix
suffix = 64lowerhex
lowerhex = DIGIT / %x61-66 ; 0-9 a-f
Example:
did:permara:7f5df4d16e3c8a2e4b1c9d0a3f6e8b7c2d5a4f1e9c8b7a6d5e4f3c2b1a0f9e8d
Generation
The suffix is the lowercase hexadecimal encoding of 32 bytes drawn from a cryptographically secure random source. The suffix is used directly as the registry's 32-byte record key. No hashing, encoding prefix or checksum is applied.
Normalization
There is no normalization. A resolver MUST reject any input whose suffix is not exactly 64 characters from 0-9a-f, including uppercase hexadecimal, a 0x prefix, or any additional path segment. DIDs are compared byte for byte after trimming surrounding whitespace at the transport boundary.
Uniqueness
Global uniqueness follows from the random source. The registry rejects a create for a suffix that already has a record, and the operator's database enforces uniqueness on the suffix, so a collision cannot produce two records for one DID.
Networks
The identifier carries no network segment. Which network and contract hold the records is a property of the method version, published in the registry table below. A DID minted while the registry lived on one network keeps resolving after the operator publishes a new registry on another network, because the operator re-creates every live record there before retiring the old one.
Data model
Registry record
Each record is keyed by the suffix and holds:
| Field | Type | Meaning |
|---|---|---|
controller | address | May publish a new document version and hand control on. |
recovery | address | May replace both authorities and may deactivate. |
documentHash | bytes32 | sha256 over the canonical DID document. |
documentUri | string | Where the document is served. 1 to 256 bytes. |
version | uint32 | Document version. 1 at create, incremented by each update. |
createdAt, updatedAt | uint40 | Block timestamps. |
deactivated | bool | Absorbing. |
DID document
A resolved document MUST contain id equal to the DID. It SHOULD contain the verification methods that prove control on behalf of the subject, the verification relationships that use them, an alsoKnownAs entry for the subject's credential identifier where one exists, and a single service of type PermaraPaymentProfile whose endpoint is an authenticated API. The document MUST NOT contain names, email addresses, phone numbers, payment endpoints or any other personal data. Verification methods are of type EcdsaSecp256k1RecoveryMethod2020 with a CAIP-10 blockchainAccountId. Fragment identifiers are #signer-<n> in ascending address order so the canonical form is deterministic.
{
"@context": ["https://www.w3.org/ns/did/v1"],
"id": "did:permara:7f5df4d16e3c8a2e4b1c9d0a3f6e8b7c2d5a4f1e9c8b7a6d5e4f3c2b1a0f9e8d",
"verificationMethod": [
{
"id": "did:permara:7f5d…9e8d#signer-1",
"type": "EcdsaSecp256k1RecoveryMethod2020",
"controller": "did:permara:7f5d…9e8d",
"blockchainAccountId": "eip155:1:0x1111111111111111111111111111111111111111"
}
],
"authentication": ["did:permara:7f5d…9e8d#signer-1"],
"capabilityInvocation": ["did:permara:7f5d…9e8d#signer-1"],
"service": [
{
"id": "did:permara:7f5d…9e8d#payment-profile",
"type": "PermaraPaymentProfile",
"serviceEndpoint": "https://api.permara.com/v1/payment-profile"
}
]
}
Canonicalization and hash
documentHash is sha256 over the UTF-8 bytes of the document serialized with the JSON Canonicalization Scheme (RFC 8785). Two conforming implementations serializing the same document MUST produce the same bytes and therefore the same hash.
Operations
Create
- The operator generates the suffix and builds the initial document.
- The operator serves the document at
documentUriand computesdocumentHash. - An address holding the registrar role calls
create(suffix, documentHash, documentUri, controller, recovery). - The record is final once the transaction is included and the network's finality rule (below) is met.
The registry reverts on a zero suffix, a zero hash, a zero address, an empty or over-long URI, and a suffix that already has a record.
Resolve
Input: a DID and resolution options. Output: a DID resolution result with didResolutionMetadata, didDocument and didDocumentMetadata.
- Validate syntax. Failure:
didResolutionMetadata.error = "invalidDid". A different method:"methodNotSupported". - Read
getRecord(suffix)from the current registry. No record (createdAt == 0):"notFound". - If
deactivated: returndidDocumentcontaining onlyid, anddidDocumentMetadata.deactivated = truewithversionId,createdandupdated. - Fetch the document from
documentUriwith bounded size and time. - Verify
document.idequals the requested DID andsha256(JCS(document))equalsdocumentHash. Failure:"internalError"; a resolver MUST NOT return a document that failed either check. - Return the document with
didDocumentMetadata.created,updated,versionId(<chainId>:<transaction hash>:<version>) anddeactivated = false.
A conforming resolver only needs a JSON-RPC endpoint for the registry's network and an HTTPS client; it does not need to trust the operator's API for the result to be authentic.
Update
The controller publishes a new document version by calling update(suffix, documentHash, documentUri). The registry increments version. Until the update is final, the previously anchored document remains the served document so that bytes and hash always agree.
Rotate controller
The controller calls rotateController(suffix, newController). The document version is unchanged.
Recover
The recovery authority calls recover(suffix, newController, newRecovery). This works while the registry is paused so that an incident can never lock out recovery. The previous controller and the previous recovery authority lose all rights.
Deactivate
The controller or the recovery authority calls deactivate(suffix). Deactivation is absorbing: every later write to the record reverts, the suffix can never be created again, and resolution reports deactivated: true. This also works while paused.
DID URL dereferencing
Only fragment identifiers are supported in this version. Dereferencing did:permara:<suffix>#<fragment> resolves the DID and returns the verification method or service whose id matches. Paths and query parameters are not defined and MUST be rejected.
Versioning and finality
versionId is the registry's version qualified by the chain id and the transaction hash that set it. Historical versions are not resolvable in this method version. A record change is final once the transaction is included and the confirmation depth in the registry table is reached; resolvers SHOULD NOT treat an unconfirmed change and a final one as equivalent. After a chain reorganization the operator reconciles its mirror from the registry, never the other way round.
Authorization
This method version is operator-registered. Only addresses holding the registrar role may create records; the operator holds controller and recovery for every record it creates. Subjects hold the verification keys listed in their documents and use them to authorize actions in Permara; those keys never write to the registry. rotateController exists so a subject can take control of its own record in a later method version.
Cryptographic algorithms
- Random source: 256 bits from a cryptographically secure generator.
- Hash: SHA-256 over RFC 8785 canonical JSON.
- Verification method type:
EcdsaSecp256k1RecoveryMethod2020(secp256k1, CAIP-10 account identifiers). - Registry authorization: EVM transaction signatures (secp256k1).
Registry table
| Method version | Network | Chain id | Contract | Confirmations |
|---|---|---|---|---|
| 1.0 | Base Sepolia | 84532 | published in contracts/deployments/84532.json after deployment | 1 |
| 1.0 | Base | 8453 | not yet deployed | 2 |
The contract is not upgradeable. A new contract is a new row and a new method minor version; the operator re-creates live records on the new contract before retiring the old row.
Security considerations
- Registrar key compromise. An attacker can create records and, as controller, publish documents for them. The recovery authority, held separately on a multi-signature account, can recover every affected record and the operator can pause creates. Recovery and deactivation work while paused.
- Recovery key compromise. The attacker can replace authorities on any record. Mitigation is the multi-signature threshold and the pause; the contract has no admin key that can rewrite records, so the blast radius is the records the attacker touches, each of which emits an event.
- Replay. Registry writes are EVM transactions bound to chain id, nonce and contract; a transaction for one network cannot be replayed on another.
- Document substitution. A document served from
documentUrithat does not hash todocumentHashMUST be rejected. A resolver that trusts the operator's HTTPS endpoint without checking the hash is not a conforming resolver. - Storage disappearance. The registry stores the hash and the location, not the document. The operator keeps every anchored version and serves the last anchored one; a resolver may cache documents keyed by hash.
- Reorganizations and equivocating RPC providers. Resolvers SHOULD read from more than one provider or wait for the table's confirmation depth before treating a change as final.
- Denial of service.
documentUriis bounded to 256 bytes on chain. Resolvers MUST bound document size, fetch time, redirects and decompression. - Man in the middle on resolution. Resolution output is authenticated by the hash check, not by transport; TLS protects confidentiality of what is being resolved.
Privacy considerations
- No personal data is written to the registry or into any document: no names, contact details or payment endpoints. The service endpoint is an authenticated API.
- A DID is a stable identifier and can be correlated across payments. The method does not offer pairwise DIDs in this version; subjects who need relationship separation must use separate accounts.
- Verification methods expose signer addresses. Those addresses are already public on the networks they transact on; the document adds a link between an address and a DID, which is the purpose of the method.
- Records created on a test network carry no production assurance and are published in the registry table as such.
Governance
Permara, as operator, publishes changes to the registry table, the confirmation depths, the supported cryptographic suites and the resolver behaviour through new versions of this document, with a sunset notice of at least ninety days before any row of the registry table is retired. The contract has no upgrade path; changes to contract behaviour are new deployments.
Test vectors
Valid:
did:permara:0000000000000000000000000000000000000000000000000000000000000001
did:permara:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
Invalid, with the expected resolution error:
| Input | Error |
|---|---|
did:permara:FFFF… (uppercase) | invalidDid |
did:permara:0xff… (prefix) | invalidDid |
did:permara:abc (wrong length) | invalidDid |
did:permara:<suffix>/path | invalidDid |
did:example:1234 | methodNotSupported |
| syntactically valid, no record | notFound |
record with deactivated = true | didDocumentMetadata.deactivated = true |
Canonicalization: the document {"b":1,"a":[2,{"d":true,"c":"x"}]} canonicalizes to {"a":[2,{"c":"x","d":true}],"b":1} and its SHA-256 is the hash a registry would hold for it.