DID document key extraction accepts only Multikey, not the legacy verification-method types #98
Labels
No labels
breaking-change
bug
documentation
duplicate
enhancement
good first issue
help wanted
performance
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/ATProto.NET#98
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Both places the SDK pulls a signing key out of a DID document require
type == "Multikey":src/ATProtoNet/Spaces/SpaceAuthority.cs:121(FindMultikey, behindGetSigningKey)src/ATProtoNet/Streaming/FirehoseVerifier.cs:345The reference implementation accepts three types, not one —
packages/identity/src/did/atproto-data.ts:The two encodings differ, so this is not just a string comparison: a
Multikey'spublicKeyMultibaseis base58btc over multicodec-tagged compressed key bytes (0xe7 0x01 || 33 bytesfor secp256k1), while the legacyEcdsaSecp256k1VerificationKey2019form is base58btc over a bare uncompressed point (0x04 || X || Y, 65 bytes) with no multicodec prefix. Converting means compressing by the parity of Y beforeAtProtoCrypto.ImportCompressedPublicKey.Effect. Against a document that publishes the legacy form,
SpaceAuthority.GetSigningKeyreturnsnull, soSpaceSyncer.ResolveSigningKeyAsyncthrowsSpaceRepoVerificationException("'{did}' publishes no AT Protocol signing key")and no permissioned commit can be verified at all; the firehose verifier likewise treats every commit from that account as unverifiable.How it surfaced. While adding the space integration tests (#93). plc.directory serves
Multikeytoday, so productiondid:plcis unaffected — but the referencedev-envnetwork pins an older@did-plc/libthat serves the legacy form, and hand-writtendid:webdocuments may too.SpaceNetworkFixture.ResolveSigningKeyAsyncintests/ATProtoNet.IntegrationTests/works around it with its own resolver so the test network's key format is not what the commit-verification tests end up asserting; that workaround should go away once the SDK handles both.Worth deciding as part of a fix: where the conversion belongs (a shared helper on
DidDocumentor inAtProtoCrypto, rather than duplicated inSpaceAuthorityandFirehoseVerifier), and whetherEcdsaSecp256r1VerificationKey2019is worth accepting alongside the k256 one.Filed by Claude while working on #93 (run).
🤖 On it — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
Pushed
claude/issue-98.What changed
Both key-extraction sites required
type == "Multikey"; both now read all three verification-method types the reference implementation accepts, and the conversion lives in one place rather than duplicated.New shared helpers (
src/ATProtoNet/Identity/PlcClient.cs):VerificationMethod.ToDidKey()—Multikeypasses through (itspublicKeyMultibaseis the did:key encoding);EcdsaSecp256k1VerificationKey2019/EcdsaSecp256r1VerificationKey2019are base58btc-decoded, compressed by the parity of Y, and re-tagged with the multicodec prefix for the curve the type names. Returnsnullfor an absent/unrecognized type, throwsFormatExceptionwhen a recognized entry's material is malformed.DidDocument.GetSigningKey()andDidDocument.GetVerificationKey(fragment)— match both the bare#atprotoand the DID-qualified id.New crypto primitives (
AtProtoCrypto):FormatDidKey(publicKey, curve)andCompressPublicKey(publicKey)(accepts 33-byte compressed or 65-byte uncompressed; both curves have 32-byte coordinates, so compression is curve-independent), plus an internalMultibaseToBytes.Call sites:
SpaceAuthority.GetSigningKey(FindMultikeydeleted —#atproto_spacethen falling back to#atproto, both type-agnostic now) andFirehoseVerifier(its privateGetSigningKeydeleted).I did accept
EcdsaSecp256r1VerificationKey2019alongside the k256 one — it's the same code path with a different multicodec prefix, and the reference implementation accepts it.Workaround removed:
SpaceNetworkFixture.ResolveSigningKeyAsyncis now justSpaceSyncer.ResolveSigningKeyAsync(DidResolver), so the space commit-verification integration tests assert against the production path. Its private base58 decoder is gone.docs/testing-spaces.mdanddocs/did-resolution.mdupdated; CHANGELOG entry underFixed.Verification
dotnet buildon the solution: 0 warnings, 0 errors.dotnet test tests/ATProtoNet.Tests/ --configuration Release: 1549 passed, 0 failed, 5 skipped (the pre-existing Aspire-manifest gates).14 new unit tests —
tests/ATProtoNet.Tests/Identity/DidDocumentKeyTests.csand a new section inAtProtoCryptoTests. The load-bearing ones build a legacy verification method from a real key pair's uncompressed point and assert it resolves to the samedid:keythe key reports for itself, and that a signature made by the private half verifies against the resolved key — looped until both parities of Y have been exercised, since a compression that ignored the parity bit would still produce a well-formed did:key for the wrong point half the time. The k256 case is guarded with the repo's existingPlatformNotSupportedExceptionpattern (it ran here on Linux).The integration tests themselves still need a
dev-envspace network (ATPROTO_TEST_SPACES=true), which this container has no way to stand up, so the fixture change is compile-verified only —tests/ATProtoNet.IntegrationTests/builds clean.⏱ 7m 32s · 41 turns · tokens: 84.8k in / 29.8k out (+2533.7k cache-read)
📜 Full session transcript
→ Opened PR: #103