mirror of
https://github.com/luxfi/mpc.git
synced 2026-09-24 00:26:24 +00:00
`mpcd seal` shipped without the one thing it needs: nothing outside a node could read a committee's group key, so the command could not be used at all. reveal.PublicKey has always known it; it was simply unreachable. /group answers it, org and key named explicitly. /keys cannot: a KeyInfo record carries no org, so it cannot name the share to read, and an empty eddsa_key there is silence about the curve rather than a denial of it — 36 records on the live ring report no Ed25519 key and that tells you nothing about whether the shares exist. The value is public in the strict sense. Handing it out lets anyone seal a secret to the committee and lets nobody open one, which is the asymmetry the whole protocol rests on. The answer carries the quorum beside it, because a key that seals but cannot be opened is worse than no key.
63 lines
2.1 KiB
Go
63 lines
2.1 KiB
Go
// Copyright © 2026 Lux Industries Inc. All rights reserved.
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/luxfi/mpc/pkg/kvstore"
|
|
"github.com/luxfi/mpc/pkg/reveal"
|
|
)
|
|
|
|
// groupHandler answers with the group key a caller seals to.
|
|
//
|
|
// It is public in the strict sense — it lets anyone seal a secret to this
|
|
// committee and lets nobody open one — but it is the one thing `mpcd seal`
|
|
// cannot work without, and until now nothing outside a node could read it.
|
|
//
|
|
// /keys cannot answer this. A KeyInfo record carries no org, so it cannot even
|
|
// name the share to read; and an empty eddsa_key there is silence about the
|
|
// curve rather than a denial of it, which is why 36 records reporting no
|
|
// Ed25519 key say nothing about whether the shares exist.
|
|
func groupHandler(store kvstore.KVStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
org := strings.TrimSpace(r.URL.Query().Get("org"))
|
|
keyID := strings.TrimSpace(r.URL.Query().Get("key"))
|
|
if org == "" || keyID == "" {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
json.NewEncoder(w).Encode(map[string]string{"error": "org and key are both required"})
|
|
return
|
|
}
|
|
point, err := reveal.PublicKey(store, org, keyID)
|
|
if err != nil {
|
|
// A key this node holds no share for is a 404: the question is
|
|
// answerable, and the answer is that there is nothing here.
|
|
w.WriteHeader(http.StatusNotFound)
|
|
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
raw, err := point.MarshalBinary()
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
quorum, err := reveal.Quorum(store, org, keyID)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"org": org,
|
|
"key": keyID,
|
|
"group_key": hex.EncodeToString(raw),
|
|
"quorum": quorum,
|
|
})
|
|
}
|
|
}
|