···30303131 // Start with the PDS-generated credentials (verification methods, services,
3232 // alsoKnownAs). These are always correct regardless of rotation key state.
3333- // CreateDidCredentialsFromPublicKey sets verificationMethods["atproto"] to
3434- // the passkey's did:key (commit signing).
3333+ // CreateDidCredentialsFromPublicKey sets:
3434+ // - verificationMethods["atproto"] to the passkey's did:key (commit signing)
3535+ // - verificationMethods["atproto_service"] to the PDS server key (service-auth)
3536 creds, err := s.plcClient.CreateDidCredentialsFromPublicKey(pubKey, "", repo.Handle)
3637 if err != nil {
3738 logger.Error("error creating did credentials", "error", err)
3839 helpers.ServerError(w, nil)
3940 return
4041 }
4141-4242- // Add the PDS server key as the atproto_service verification method.
4343- // Service-auth JWTs are signed by this key so background requests (feed
4444- // loading, notifications, proxied reads) never require a passkey.
4545- // AppViews that implement the atproto_service RFC will verify tokens against
4646- // this key; others fall back to #atproto (known limitation until spec lands).
4747- pdsDIDKey, err := s.pdsDIDKey()
4848- if err != nil {
4949- logger.Error("error deriving PDS did:key for atproto_service", "error", err)
5050- helpers.ServerError(w, nil)
5151- return
5252- }
5353- creds.VerificationMethods["atproto_service"] = pdsDIDKey
54425543 // If this is a did:plc identity, fetch the actual rotation keys from the
5644 // current PLC document. After supplySigningKey transfers the rotation key
+24-21
server/server.go
···336336 return nil, err
337337 }
338338339339- h := util.RobustHTTPClient()
340340-341341- plcClient, err := plc.NewClient(&plc.ClientArgs{
342342- H: h,
343343- Service: "https://plc.directory",
344344- PdsHostname: args.Hostname,
345345- RotationKey: rkbytes,
346346- })
347347- if err != nil {
348348- return nil, err
349349- }
350350-351339 jwkbytes, err := os.ReadFile(args.JwkPath)
352340 if err != nil {
353341 return nil, err
···363351 return nil, err
364352 }
365353354354+ // Wrap the JWK private key as an atcrypto.PrivateKeyP256 for ATProto-compatible
355355+ // signing. Convert via ecdh to get the raw private key bytes.
356356+ ecdhKey, err := pkey.ECDH()
357357+ if err != nil {
358358+ return nil, fmt.Errorf("converting private key to ecdh: %w", err)
359359+ }
360360+ atpKey, err := atcrypto.ParsePrivateBytesP256(ecdhKey.Bytes())
361361+ if err != nil {
362362+ return nil, fmt.Errorf("wrapping JWK private key for atcrypto: %w", err)
363363+ }
364364+365365+ h := util.RobustHTTPClient()
366366+367367+ plcClient, err := plc.NewClient(&plc.ClientArgs{
368368+ H: h,
369369+ Service: "https://plc.directory",
370370+ PdsHostname: args.Hostname,
371371+ RotationKey: rkbytes,
372372+ ServiceAuthKey: atpKey,
373373+ })
374374+ if err != nil {
375375+ return nil, err
376376+ }
377377+366378 oauthCli := &http.Client{
367379 Timeout: 10 * time.Second,
368380 }
···381393 }
382394383395 cookieStore := sessions.NewCookieStore([]byte(args.SessionSecret))
384384-385385- // Wrap the JWK private key as an atcrypto.PrivateKeyP256 for ATProto-compatible
386386- // signing. atcrypto.ParsePrivateBytesP256 expects the raw 32-byte D scalar.
387387- dBytes := make([]byte, 32)
388388- pkey.D.FillBytes(dBytes)
389389- atpKey, err := atcrypto.ParsePrivateBytesP256(dBytes)
390390- if err != nil {
391391- return nil, fmt.Errorf("wrapping JWK private key for atcrypto: %w", err)
392392- }
393396394397 s := &Server{
395398 http: h,