Skip to content

feat: derive self-certifying Willow DIDs#3

Merged
pauldelucia merged 1 commit into
masterfrom
feat/self-certifying-did
Jul 2, 2026
Merged

feat: derive self-certifying Willow DIDs#3
pauldelucia merged 1 commit into
masterfrom
feat/self-certifying-did

Conversation

@pauldelucia

Copy link
Copy Markdown
Contributor

Why

The chain's RegisterDid check now requires new DID ids to be self-certifying (bound to the public key). This SDK still emitted the old formats — did:willow:<alg>:<pubkey_prefix> — so SDK-driven registration was being rejected on-chain.

What

Replace the chosen/prefix id with the exact derivation the chain enforces:

did = "did:willow:z" + base58btc( SHA3-256( multicodec_prefix || public_key ) )
  • FIPS-202 SHA3-256 (hashlib.sha3_256) — not Keccak-256 (these differ; using keccak would be a bug).
  • multicodec prefix: 0xED 0x01 (Ed25519) / 0xE7 0x01 (secp256k1). secp256k1 hashes the 33-byte compressed key; the SDK's 64-byte uncompressed wire key is normalised to compressed before hashing.
  • base58btc (Bitcoin alphabet), each leading 0x00 byte encoded as 1, with the multibase z marker.
  • public_key_id stays "{did}#key-1".

API

  • generate_did(...) keeps its return shape; the did is just derived now (the DID document, public_key, signing/verification are all unchanged).
  • New derive_did(public_key, algorithm) (hex or bytes) computes the id from a public key alone. This is what makes the two-step bootstrap possible: because the id is known before registration, a funded account pre-funds the derived id (>= the registration fee), then the holder registers (fee paid from that balance). Documented on both register_did paths and in the README. No chosen-id API was added.

Acceptance vector (mandatory)

tests/test_auth.py::TestSelfCertifyingDid::test_ed25519_acceptance_vector asserts:

ed25519 pubkey a003201e65e47d578ad9bb17cb1d3590e9f504f55eac6ee40002e3ab9517c49c
=> did:willow:zDZ1Qqspppayjd9LF3Pkebq64Fa2PuK8zFQDDc11citB2

Ran locally and passed (full suite: 289 passed, 13 skipped/network). No new runtime dependencies — SHA3-256 is stdlib and base58btc is a small inline encoder.

Known limitation / follow-up (out of scope here)

The self-certifying id no longer embeds the algorithm, so detect_algorithm_from_did() can no longer recover it from the bare DID string and defaults to Ed25519. For secp256k1 per-request auth, sign_request() would therefore pick the wrong algorithm. This SDK is Ed25519-first (the acceptance vector is Ed25519), so this doesn't affect the default path, but a follow-up should thread the algorithm through set_identity/sign_request (or read it from the DID document's key_type) rather than parsing it from the id.

🤖 Generated with Claude Code

The chain's RegisterDid check now requires new DID ids to be
self-certifying, so the old formats this SDK emitted
(did:willow:<alg>:<pubkey_prefix>) are rejected on-chain and
SDK-driven registration fails.

Derive the id from the public key instead:

    did = "did:willow:z" + base58btc(SHA3-256(multicodec_prefix || pubkey))

- FIPS-202 SHA3-256 (hashlib.sha3_256), not Keccak-256.
- multicodec prefix 0xED01 (Ed25519) / 0xE701 (secp256k1); secp256k1
  hashes the 33-byte compressed key (uncompressed input is normalised).
- base58btc (Bitcoin alphabet), leading 0x00 bytes -> '1', with the
  multibase 'z' marker.

Add derive_did() so a DID can be computed from a public key alone,
which enables the pre-fund step of onboarding, and keep generate_did()'s
return shape unchanged (the id is just derived now). Document the
two-step bootstrap (pre-fund the derived id, then register) on the
register_did paths and in the README.

Add a unit test asserting the mandatory Ed25519 acceptance vector.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pauldelucia pauldelucia merged commit d5dc035 into master Jul 2, 2026
1 check passed
@pauldelucia pauldelucia deleted the feat/self-certifying-did branch July 2, 2026 11:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant