Review of 083: finishing an enrolment whose token was spent takes proof of the key's private half, a live lease and a first delivery — a public key alone cannot replay a spent token; shutdown leaves held messages for the broker; identical builds supersede; what is held leaves room in the prefetch

This commit is contained in:
2026-09-22 14:33:13 +02:00
parent a3b7e830c8
commit 4567fa666c
7 changed files with 214 additions and 25 deletions
+21
View File
@@ -6,6 +6,8 @@
// traffic between them, each receiving half of what it expects. That has happened here before.
package link
import "encoding/base64"
// Exchange is where nodes publish everything they have to say.
const Exchange = "mesh"
@@ -58,6 +60,18 @@ type EnrolRequest struct {
// Profile is what this machine can be asked to do. The control plane cannot decide what a
// node should run without it, so it arrives with enrolment rather than being asked for after.
Profile map[string]any `json:"profile,omitempty"`
// Proof is the node's identity key signing EnrolProof over this request: that the presenter
// holds the private half of PublicKey, not only knows the public one. Required to finish an
// enrolment whose token this key already spent — the case of an answer lost after the spend —
// because a public key is no secret, and without it anyone holding a leaked token and a
// node's public key could replay the spent token (novox/hq issue 083, on review).
Proof []byte `json:"proof,omitempty"`
// Redelivered is set by the control plane, never sent: the broker handed this request over a
// second time. Such a request does not finish an enrolment already spent — the first time may
// have answered, and the node holds what it was told.
Redelivered bool `json:"-"`
}
// Signed is a declaration and the signature over it.
@@ -137,3 +151,10 @@ type EnrolReply struct {
// token can fail — unknown, spent, expired — so that guessing learns nothing.
Refusal string `json:"refusal,omitempty"`
}
// EnrolProof is what a node signs with its identity key when it enrols: the token and every key it
// presents, so a proof cannot be moved to another request.
func EnrolProof(secret string, public []byte, overlay, sealing, serving string) []byte {
return []byte("novox-mesh-enrol\x00" + secret + "\x00" + base64.StdEncoding.EncodeToString(public) +
"\x00" + overlay + "\x00" + sealing + "\x00" + serving)
}