···8585 UsedAt time.Time
8686}
87878888-// PendingWrite represents a write (or PLC operation) that has been prepared by
8989-// the PDS and is waiting for the user's client to sign it. Once the signature
9090-// is submitted via handleSubmitSignature the stored CommitData is used to
9191-// finalise the commit without the PDS ever having held the private key.
9292-type PendingWrite struct {
9393- ID string `gorm:"primaryKey"`
9494- Did string `gorm:"index"`
9595- PayloadHash string
9696- // Bytes to sign.
9797- Payload []byte
9898- // Original request.
9999- Data []byte
100100- // Serialized commit state.
101101- CommitData []byte
102102- CreatedAt time.Time
103103- ExpiresAt time.Time `gorm:"index"`
104104-}
105105-10688type Token struct {
10789 Token string `gorm:"primaryKey"`
10890 Did string `gorm:"index"`
+14
plc/client.go
···140140 return pub.DIDKey()
141141}
142142143143+// RotationKeyBytes returns the raw bytes of the PDS rotation key. This allows
144144+// callers to use the rotation key to sign genesis commits instead of
145145+// generating a throwaway ephemeral key.
146146+func (c *Client) RotationKeyBytes() []byte {
147147+ return c.rotationKey.Bytes()
148148+}
149149+150150+// RotationPrivateKey returns the PDS rotation key as a typed *atcrypto.PrivateKeyK256.
151151+// This allows callers to pass it directly to functions that expect a signing key,
152152+// such as CreateDID, without generating a throwaway ephemeral key.
153153+func (c *Client) RotationPrivateKey() *atcrypto.PrivateKeyK256 {
154154+ return c.rotationKey
155155+}
156156+143157// SignOp signs a PLC operation with the PDS rotation key. This is the only
144158// key that can authorise changes to a did:plc document (until the rotation
145159// key is transferred to the user via supplySigningKey).
···77 "slices"
88 "strings"
991010- "github.com/bluesky-social/indigo/atproto/atcrypto"
1110 "github.com/bluesky-social/indigo/atproto/syntax"
1211 blocks "github.com/ipfs/go-block-format"
1312 "github.com/ipfs/go-cid"
···108107109108 tx.Commit()
110109111111- // The PDS never holds the user's private key. We generate an ephemeral key
112112- // solely to produce a valid commit block for the imported repo. The user
113113- // must call supplySigningKey via the account page after import so that
114114- // subsequent writes can be signed correctly.
115115- ephemeralKey, err := atcrypto.GeneratePrivateKeyK256()
116116- if err != nil {
117117- logger.Error("error generating ephemeral key for import commit", "error", err)
118118- helpers.ServerError(w, nil)
119119- return
120120- }
121121-122122- root, rev, err := commitRepo(ctx, bs, atRepo, ephemeralKey.Bytes())
110110+ // Sign the import commit with the PDS rotation key instead of a throwaway
111111+ // ephemeral key. The user can supply their own signing key later via the
112112+ // account page; subsequent writes require a registered public key and a
113113+ // connected signer.
114114+ root, rev, err := commitRepo(ctx, bs, atRepo, s.plcClient.RotationKeyBytes())
123115 if err != nil {
124116 logger.Error("error committing", "error", err)
125117 helpers.ServerError(w, nil)
+6-41
server/handle_server_create_account.go
···99 "time"
10101111 "github.com/bluesky-social/indigo/api/atproto"
1212- "github.com/bluesky-social/indigo/atproto/atcrypto"
1212+1313 atp "github.com/bluesky-social/indigo/atproto/repo"
1414 "github.com/bluesky-social/indigo/atproto/repo/mst"
1515 "github.com/bluesky-social/indigo/atproto/syntax"
···159159 return
160160 }
161161162162- // For the genesis commit we use a temporary ephemeral key. The PDS never
163163- // stores it — as soon as the commit is written the key is discarded. The
164164- // user must call supplySigningKey (via the account page) before any
165165- // subsequent write will succeed, because applyWrites requires a registered
166166- // public key and a connected signer.
167167- var k *atcrypto.PrivateKeyK256
168168-169169- if signupDid != "" {
170170- reservedKey, err := s.getReservedKey(ctx, signupDid)
171171- if err != nil {
172172- logger.Error("error looking up reserved key", "error", err)
173173- }
174174- if reservedKey != nil {
175175- k, err = atcrypto.ParsePrivateBytesK256(reservedKey.PrivateKey)
176176- if err != nil {
177177- logger.Error("error parsing reserved key", "error", err)
178178- k = nil
179179- } else {
180180- defer func() {
181181- if delErr := s.deleteReservedKey(ctx, reservedKey.KeyDid, reservedKey.Did); delErr != nil {
182182- logger.Error("error deleting reserved key", "error", delErr)
183183- }
184184- }()
185185- }
186186- }
187187- }
188188-189189- if k == nil {
190190- // Generate an ephemeral key for the genesis commit only.
191191- k, err = atcrypto.GeneratePrivateKeyK256()
192192- if err != nil {
193193- logger.Error("error generating ephemeral key", "endpoint", "com.atproto.server.createAccount", "error", err)
194194- helpers.ServerError(w, nil)
195195- return
196196- }
197197- }
162162+ // Use the PDS rotation key to sign the genesis commit. The user can supply their own signing key later via
163163+ // supplySigningKey; subsequent writes require a registered public key and a connected signer.
198164199165 if signupDid == "" {
200200- did, op, err := s.plcClient.CreateDID(k, "", request.Handle)
166166+ did, op, err := s.plcClient.CreateDID(s.plcClient.RotationPrivateKey(), "", request.Handle)
201167 if err != nil {
202168 logger.Error("error creating operation", "endpoint", "com.atproto.server.createAccount", "error", err)
203169 helpers.ServerError(w, nil)
···266232 RecordStore: bs,
267233 }
268234269269- // Sign the genesis commit with the ephemeral key. This key is never
270270- // persisted; it is only used to produce a valid initial commit block.
271271- root, rev, err := commitRepo(ctx, bs, r, k.Bytes())
235235+ // Sign the genesis commit with the PDS rotation key.
236236+ root, rev, err := commitRepo(ctx, bs, r, s.plcClient.RotationKeyBytes())
272237 if err != nil {
273238 logger.Error("error committing", "error", err)
274239 helpers.ServerError(w, nil)
+2-3
server/repo.go
···305305// TODO make use of swap commit
306306// pendingCommitState captures everything produced by the MST-building phase of
307307// applyWrites that is needed to finalise the commit once a signature arrives.
308308-// It is JSON-serialised into models.PendingWrite.CommitData so that
309309-// finaliseWriteFromSignature can reconstruct it without re-running the MST
310310-// logic.
308308+// It is held in memory and passed directly to finaliseWriteFromState after the
309309+// SignerHub WebSocket round-trip completes.
311310//
312311// NOTE: block data is stored as raw bytes slices (base64 in JSON) because CIDs
313312// and block objects are not JSON-serialisable out of the box with the standard