mirror of
https://github.com/luxfi/mpc.git
synced 2026-09-24 00:26:24 +00:00
Two halves of opening a secret were missing, and both showed up the first time this ran against the real ring rather than an in-process one. The request reached no party. Parties subscribed to "mpc.reveal_request.*" and the requester published to "mpc.reveal_request.<session>". This transport delivers by exact map lookup on the topic — it has no wildcards — so the subscription matched nothing, every node stayed silent, and the opening failed as "0 of 2 answered" with all three shares present and nobody having been asked. That reads like a quorum fault and is a routing one. Both sides name one literal now; the session rides in the body, where a reader wants it anyway. The pattern came from the NATS-era topics next door, where wildcards do work. And nothing could seal. mpcrek requires a sealed root to bring and no tool in the estate produced one, so the protocol had no beginning. reveal.Seal takes the published group key and a secret — no share, no node, no round, because writing to the committee needs none of that — and `mpcd seal` is that from a shell, with the secret on stdin so it never becomes an argument other processes can read. Sealing to the identity is refused. It decodes as a valid point, so the decoder lets it through, and every answer verifies against it: a secret sealed there is readable by anyone. A test asked for that and the first version did not do it.
116 lines
3.0 KiB
Go
116 lines
3.0 KiB
Go
// Copyright © 2026 Lux Industries Inc. All rights reserved.
|
|
|
|
package reveal
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/luxfi/threshold/pkg/math/curve"
|
|
)
|
|
|
|
// The point of Seal is that it needs nothing but the published key. This holds
|
|
// it to that: the sealer is given 32 bytes and no share, no store and no node,
|
|
// and the committee opens what it wrote.
|
|
func TestASealerWithOnlyThePublishedKeyWritesWhatTheCommitteeOpens(t *testing.T) {
|
|
stores, cfg := committee(t, 2)
|
|
|
|
published, err := cfg.PublicKey.MarshalBinary()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(published) != PublicKeySize {
|
|
t.Fatalf("a published group key is %d bytes, want %d", len(published), PublicKeySize)
|
|
}
|
|
|
|
secret := []byte("the root key, which no one node may hold")
|
|
sealed, err := Seal(published, secret)
|
|
if err != nil {
|
|
t.Fatalf("seal: %v", err)
|
|
}
|
|
if bytes.Contains(sealed, secret) {
|
|
t.Fatal("the ciphertext carries the secret in the clear")
|
|
}
|
|
|
|
var answers [][]byte
|
|
var combiner *shelf
|
|
for _, s := range stores {
|
|
a, err := Answer(s, testOrg, testWallet, sealed)
|
|
if err != nil {
|
|
t.Fatalf("answer: %v", err)
|
|
}
|
|
answers = append(answers, a)
|
|
combiner = s
|
|
}
|
|
|
|
got, err := Combine(combiner, testOrg, testWallet, sealed, answers)
|
|
if err != nil {
|
|
t.Fatalf("combine: %v", err)
|
|
}
|
|
if !bytes.Equal(got, secret) {
|
|
t.Fatalf("opened %q, want %q", got, secret)
|
|
}
|
|
}
|
|
|
|
// Two seals of one secret share no bytes. Encrypt draws a fresh scalar each
|
|
// time, so a repeated enrolment does not announce that it repeated.
|
|
func TestSealingTwiceWritesTwoDifferentThings(t *testing.T) {
|
|
_, cfg := committee(t, 2)
|
|
published, err := cfg.PublicKey.MarshalBinary()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
secret := []byte("same secret, twice")
|
|
a, err := Seal(published, secret)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b, err := Seal(published, secret)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if bytes.Equal(a, b) {
|
|
t.Fatal("two seals of one secret are identical")
|
|
}
|
|
}
|
|
|
|
// A key that is not a point never becomes one. The identity matters most: every
|
|
// answer verifies against it, so a secret sealed there is readable by anyone,
|
|
// and the decoder is where that has to stop.
|
|
func TestSealRefusesAnythingThatIsNotAGroupKey(t *testing.T) {
|
|
identity, err := curve.Ed25519{}.NewPoint().MarshalBinary()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, c := range []struct {
|
|
name string
|
|
key []byte
|
|
}{
|
|
{"identity", identity},
|
|
{"off the curve", bytes.Repeat([]byte{0xff}, PublicKeySize)},
|
|
{"too short", make([]byte, PublicKeySize-1)},
|
|
{"too long", make([]byte, PublicKeySize+1)},
|
|
{"nothing", nil},
|
|
} {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
if _, err := Seal(c.key, []byte("secret")); !errors.Is(err, ErrBadPublicKey) {
|
|
t.Fatalf("err = %v, want ErrBadPublicKey", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSealRefusesAnEmptySecret(t *testing.T) {
|
|
_, cfg := committee(t, 2)
|
|
published, err := cfg.PublicKey.MarshalBinary()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := Seal(published, nil); err == nil {
|
|
t.Fatal("sealed nothing without complaint")
|
|
}
|
|
}
|