mirror of
https://github.com/luxfi/threshold.git
synced 2026-10-30 15:15:53 +00:00
The estate could hold a secret no single party could sign away, and had nowhere to put a secret no single party could READ. tfhe's committee is the shape of that but not the substance — its PartialDecrypt says so in its own comment, returns an HMAC tag rather than a partial, and its combine calls a single-party decrypt with the whole key. It panics outside tests, deliberately, so nothing was ever misled by it. What was missing was the primitive underneath. This is hashed ElGamal over the group the DKG already produced. A party answers a ciphertext with its share acting on the ephemeral point — one point, not the share — and the requester interpolates the points. The secret exists only as the thing the shares would sum to, which is the property a threshold is bought for. Every answer carries a Chaum-Pedersen proof that the point really is that party's share acting on R, and combining verifies each one. Without it a single wrong answer yields a key that is merely wrong, and a wrong key is indistinguishable from a wrong ciphertext: the AEAD fails and nothing says which party was at fault. The proof also binds the party's name, so one valid answer cannot be replayed under another's — otherwise t+1 answers could come from fewer than t+1 parties. Answers are counted by party and not by arrival, because a set that counted one party three times would open a 2-of-5 with one share. The tests state the properties: any t+1 reach the same message, t reach nothing, a wrong answer is named, a stolen answer is refused, another group's shares do not open it, and a body moved onto another ephemeral does not open. The last one runs the real FROST keygen and opens with what it hands back — the algebra agreeing with itself proves nothing about whether it agrees with the DKG's index convention. The two go.sum lines are hashes for modules already required, without which this package cannot build.
96 lines
2.6 KiB
Go
96 lines
2.6 KiB
Go
package reveal_test
|
|
|
|
// The primitive against a REAL share set.
|
|
//
|
|
// Everything in reveal_test.go deals its own shares, which proves the algebra
|
|
// but not that the algebra matches what the DKG actually produces. A share set
|
|
// is a polynomial evaluated at party indices, and the one thing that has to
|
|
// agree between the DKG, the Lagrange helper and this protocol is WHICH index —
|
|
// disagree and every open silently yields the wrong point.
|
|
//
|
|
// So this runs the keygen the fleet runs, and opens with what it hands back.
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"testing"
|
|
|
|
"github.com/luxfi/threshold/internal/test"
|
|
"github.com/luxfi/threshold/pkg/math/curve"
|
|
"github.com/luxfi/threshold/pkg/party"
|
|
"github.com/luxfi/threshold/pkg/protocol"
|
|
"github.com/luxfi/threshold/protocols/frost"
|
|
"github.com/luxfi/threshold/protocols/reveal"
|
|
)
|
|
|
|
func TestARealKeygensSharesOpenIt(t *testing.T) {
|
|
const n, threshold = 3, 2
|
|
|
|
var configs map[party.ID]*frost.Config
|
|
pt := &test.ProtocolTest{
|
|
Name: "reveal-over-frost",
|
|
PartyCount: n,
|
|
Threshold: threshold,
|
|
SessionID: []byte("reveal-over-frost"),
|
|
CreateStart: func(id party.ID, ids []party.ID, threshold int) protocol.StartFunc {
|
|
return frost.Keygen(curve.Secp256k1{}, id, ids, threshold)
|
|
},
|
|
Validate: func(t *testing.T, results map[party.ID]interface{}) {
|
|
configs = make(map[party.ID]*frost.Config, len(results))
|
|
for id, r := range results {
|
|
c, ok := r.(*frost.Config)
|
|
if !ok {
|
|
t.Fatalf("party %s produced %T", id, r)
|
|
}
|
|
configs[id] = c
|
|
}
|
|
},
|
|
}
|
|
pt.Run(t)
|
|
|
|
if len(configs) != n {
|
|
t.Fatalf("keygen produced %d configs, want %d", len(configs), n)
|
|
}
|
|
|
|
var any *frost.Config
|
|
for _, c := range configs {
|
|
any = c
|
|
break
|
|
}
|
|
|
|
root := make([]byte, 32)
|
|
if _, err := rand.Read(root); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ct, err := reveal.Encrypt(rand.Reader, any.PublicKey, root)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// threshold+1 of the real parties answer with their real shares.
|
|
var answers []*reveal.Answer
|
|
for id, c := range configs {
|
|
if len(answers) == threshold+1 {
|
|
break
|
|
}
|
|
a, err := ct.Answer(rand.Reader, id, c.PrivateShare)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
answers = append(answers, a)
|
|
}
|
|
|
|
got, err := reveal.Open(ct, threshold, any.VerificationShares, answers)
|
|
if err != nil {
|
|
t.Fatalf("open with real shares: %v", err)
|
|
}
|
|
if !bytes.Equal(got, root) {
|
|
t.Fatal("the root did not survive a real share set")
|
|
}
|
|
|
|
// And threshold alone still does not.
|
|
if _, err := reveal.Open(ct, threshold, any.VerificationShares, answers[:threshold]); err == nil {
|
|
t.Fatal("threshold parties opened it")
|
|
}
|
|
}
|