···139139 return &creds, nil
140140}
141141142142-// RotationDIDKey returns the PDS rotation key as a did:key string. This is
143143-// used to check whether the PDS still has authority over a DID by comparing
144144-// against the rotationKeys list in the current PLC document.
145145-func (c *Client) RotationDIDKey() string {
146146- pub, err := c.rotationKey.PublicKey()
147147- if err != nil {
148148- return ""
149149- }
150150- return pub.DIDKey()
151151-}
152152-153142// RotationKeyBytes returns the raw bytes of the PDS rotation key. This allows
154143// callers to use the rotation key to sign genesis commits instead of
155144// generating a throwaway ephemeral key.
+4-12
server/handle_identity_update_handle.go
···55 "encoding/base64"
66 "encoding/json"
77 "net/http"
88- "slices"
98 "strings"
109 "time"
1110···7372 Prev: &latest.Cid,
7473 }
75747676- // Determine whether the PDS rotation key still has authority over
7777- // this DID. After supplySigningKey transfers the rotation key to the
7878- // user's passkey, the PDS key is no longer in the rotation key list
7979- // and cannot sign PLC operations.
8080- pdsRotationDIDKey := s.plcClient.RotationDIDKey()
8181- pdsCanSign := slices.Contains(latest.Operation.RotationKeys, pdsRotationDIDKey)
8282-8383- if pdsCanSign {
7575+ // If no passkey is registered yet, PDS signs. Otherwise, the user's
7676+ // passkey signs (it is the rotation key in Vow's model).
7777+ if len(repo.PublicKey) == 0 {
8478 // PDS still holds authority — sign directly.
8579 if err := s.plcClient.SignOp(&op); err != nil {
8680 logger.Error("error signing PLC operation with rotation key", "error", err)
···8882 return
8983 }
9084 } else {
9191- // Rotation key belongs to the user's passkey. Delegate the
9292- // signing to the signer over WebSocket, same as
9393- // handleSignPlcOperation does for other PLC operations.
8585+ // User has registered a passkey. Request signature via SignerHub.
9486 opCBOR, err := op.MarshalCBOR()
9587 if err != nil {
9688 logger.Error("error marshalling PLC op to CBOR", "error", err)
+17-31
server/handle_server_supply_signing_key.go
···66 "encoding/json"
77 "maps"
88 "net/http"
99- "slices"
109 "strings"
1110 "time"
1211···3029 // AttestationObject is the base64url-encoded attestationObject CBOR from
3130 // the AuthenticatorAttestationResponse.
3231 AttestationObject string `json:"attestationObject" validate:"required"`
3333- // RotationKeys, if provided, sets the rotation keys for the PLC operation.
3434- // When recreating a passkey, the client must include the current rotation
3535- // keys from the signed PLC operation returned by signPlcOperation.
3636- RotationKeys []string `json:"rotationKeys"`
3732}
38333934type SupplySigningKeyResponse struct {
4040- Did string `json:"did"`
4141- PublicKey string `json:"publicKey"` // did:key for atproto (commit signing, passkey)
4242- ServiceKey string `json:"serviceKey"` // did:key for atproto_service (service-auth, PDS server key)
4343- CredentialID string `json:"credentialId"` // base64url credential ID
4444- RotationKeys []string `json:"rotationKeys"` // new rotation keys after the operation
3535+ Did string `json:"did"`
3636+ PublicKey string `json:"publicKey"` // did:key for atproto (commit signing, passkey)
3737+ ServiceKey string `json:"serviceKey"` // did:key for atproto_service (service-auth, PDS server key)
3838+ CredentialID string `json:"credentialId"` // base64url credential ID
3939+ RotationKeys []string `json:"rotationKeys"` // new rotation keys after the operation
4540 SignedOperation *plc.Operation `json:"signedOperation,omitempty"` // signed PLC operation (when passkey signs)
4641}
4742···141136 // signed by this key; others fall back to #atproto (known limitation).
142137 newVerificationMethods["atproto_service"] = pdsDIDKey
143138144144- // Determine whether the PDS rotation key still has authority over
145145- // this DID. After the first passkey registration, the rotation key
146146- // belongs to the user's passkey and the PDS can no longer sign.
147147- pdsRotationDIDKey := s.plcClient.RotationDIDKey()
148148- pdsCanSign := slices.Contains(latest.Operation.RotationKeys, pdsRotationDIDKey)
149149-150150- // Use client-provided rotation keys when recreating passkey (PDS can't sign),
151151- // otherwise replace the PDS rotation key with the passkey's did:key.
152152- if !pdsCanSign && len(req.RotationKeys) > 0 {
153153- newRotationKeys = req.RotationKeys
154154- } else {
155155- newRotationKeys = []string{pubDIDKey}
156156- }
139139+ // The passkey becomes the rotation key. In Vow's model, the signing key
140140+ // and rotation key are the same - the user's passkey controls both.
141141+ newRotationKeys = []string{pubDIDKey}
157142158143 op := plc.Operation{
159144 Type: "plc_operation",
···164149 Prev: &latest.Cid,
165150 }
166151167167- if pdsCanSign {
152152+ // If no passkey is registered yet, PDS signs (initial registration).
153153+ // Otherwise, the existing passkey signs (passkey rotation).
154154+ if len(repo.PublicKey) == 0 {
168155 // PDS still holds authority — sign directly. This is the last
169156 // PLC operation the PDS will ever be able to sign on behalf of the
170157 // user. It is voluntarily handing over control to the passkey.
···174161 return
175162 }
176163 } else {
177177- // Rotation key belongs to the user's existing passkey. Request
178178- // signature via SignerHub, same as handleIdentityUpdateHandle.
164164+ // Passkey already registered. Request signature via SignerHub.
179165 opCBOR, err := op.MarshalCBOR()
180166 if err != nil {
181167 logger.Error("error marshalling PLC op to CBOR", "error", err)
···246232247233 s.writeJSON(w, 200, SupplySigningKeyResponse{
248234 Did: repo.Repo.Did,
249249- PublicKey: pubDIDKey,
250250- ServiceKey: pdsDIDKey,
251251- CredentialID: base64.RawURLEncoding.EncodeToString(credentialID),
252252- RotationKeys: newRotationKeys,
253253- SignedOperation: signedOp,
235235+ PublicKey: pubDIDKey,
236236+ ServiceKey: pdsDIDKey,
237237+ CredentialID: base64.RawURLEncoding.EncodeToString(credentialID),
238238+ RotationKeys: newRotationKeys,
239239+ SignedOperation: signedOp,
254240 })
255241}