Sign a composition with a trusted key and compose one by hand with mesh-compose (issue 1943, S5)

The node-engine applies only a composition signed by the controller or the operator, and when the controller cannot
compose its own fix the operator composes, signs and writes one at the terminal (ADR 0539 clauses 3 and 10).
This commit is contained in:
2026-10-24 16:36:14 +02:00
parent d506f2196a
commit d8da8f1d36
7 changed files with 700 additions and 5 deletions
+15 -5
View File
@@ -46,6 +46,9 @@ type Composed struct {
Resources []json.RawMessage
Filled []string
Own NodeOwn
// Signature is the controller's or, made by hand, the operator's (ADR 0539 clause 3, SignComposed); it signs the
// digest and is never part of it.
Signature *Signature
}
// composedWire is a composition as it is written.
@@ -58,6 +61,7 @@ type composedWire struct {
Resources []json.RawMessage `json:"resources"`
Filled []string `json:"filled_files,omitempty"`
Own *NodeOwn `json:"own,omitempty"`
Signature *Signature `json:"signature,omitempty"`
}
// canonicalJSON is raw with its object keys sorted and its spacing dropped, its numbers kept as written.
@@ -126,6 +130,11 @@ func (c Composed) check() error {
}
}
}
if c.Signature != nil {
if err := c.Signature.check(); err != nil {
return err
}
}
return nil
}
@@ -135,7 +144,7 @@ func (c Composed) wire() (composedWire, error) {
return composedWire{}, err
}
w := composedWire{V: Version, Node: c.Node, Module: c.Module, FP: c.FP, Data: c.Data, Filled: c.Filled,
Resources: make([]json.RawMessage, len(c.Resources))}
Signature: c.Signature, Resources: make([]json.RawMessage, len(c.Resources))}
for i, r := range c.Resources {
canon, err := canonicalJSON(r)
if err != nil {
@@ -174,7 +183,7 @@ func DecodeComposed(data []byte) (Composed, error) {
return Composed{}, fmt.Errorf("delivery: a composition does not fit its shape: %w", err)
}
c := Composed{V: w.V, Node: w.Node, Module: w.Module, FP: w.FP, Data: w.Data, Resources: w.Resources,
Filled: w.Filled}
Filled: w.Filled, Signature: w.Signature}
if w.Own != nil {
c.Own = *w.Own
}
@@ -184,9 +193,10 @@ func DecodeComposed(data []byte) (Composed, error) {
return c, nil
}
// ComposedDigestOf is a composition's digest, "sha256:<hex>", of its encoding. A want names it (ADR 0539 clause 1),
// and the controller writes it beside the composition.
// ComposedDigestOf is a composition's digest, "sha256:<hex>", of its encoding without its signature. A want names it
// (ADR 0539 clause 1), the controller writes it beside the composition, and the signature signs it.
func ComposedDigestOf(c Composed) (string, error) {
c.Signature = nil
raw, err := EncodeComposed(c)
if err != nil {
return "", err
@@ -195,7 +205,7 @@ func ComposedDigestOf(c Composed) (string, error) {
return "sha256:" + hex.EncodeToString(sum[:]), nil
}
// SameComposed says whether two compositions are the same value, so the controller writes
// SameComposed says whether two compositions are the same value, their signatures included, so the controller writes
// only when they differ.
func SameComposed(a, b Composed) bool {
ra, errA := EncodeComposed(a)