mirror of
https://github.com/luxfi/corona.git
synced 2026-09-24 00:25:54 +00:00
corona: a peer's message is walked before it is decoded, and cannot end the process
Two properties this package did not hold. A lattice frame arriving from a peer went straight to the decoder, which sizes its work from counts the peer wrote. A frame naming far more than it carries therefore cost the receiver time proportional to what it named: twenty-four bytes buy seconds. RecvVector and RecvMatrix now walk the frame first, which costs microseconds and bounds the work to the bytes actually present. The walk is the same one the signature and group-key decoders use, so there is one rule for what a lattice frame may claim and one place it is enforced. The transfer methods answered every failure with log.Fatalf, which is os.Exit — so a malformed message, a peer that hung up, or a slow socket ended the process. A library may not make that decision for its caller: they return errors now, and the command decides what is fatal. The same applied to the three matrix multiplications, which exited on a shape mismatch while MatrixAdd beside them already returned an error, and to LocalRun. The receive tests were calling t.Fatal from inside a goroutine, which is not valid; they carry the error out and check it at the join instead. Proven against a real peer connection: a raw frame naming 2^40 is refused at once, and without the walk the receiver does not answer within five seconds. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -44,7 +44,9 @@ func main() {
|
||||
sign.Threshold = parties
|
||||
|
||||
if partyIDString == "l" {
|
||||
sign.LocalRun(iters)
|
||||
if err := sign.LocalRun(iters); err != nil {
|
||||
log.Fatalf("local run: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -125,11 +127,21 @@ func main() {
|
||||
go func(i int) {
|
||||
defer sendWg.Done()
|
||||
writer := bufio.NewWriter(*comm.GetSock(i))
|
||||
comm.SendVector(writer, i, b)
|
||||
comm.SendMatrix(writer, i, A)
|
||||
comm.SendVector(writer, i, skShares[i])
|
||||
comm.SendBytesSliceMap(writer, i, seeds)
|
||||
comm.SendBytesMap(writer, i, MACKeys[i])
|
||||
if err := comm.SendVector(writer, i, b); err != nil {
|
||||
log.Fatalf("send to peer: %v", err)
|
||||
}
|
||||
if err := comm.SendMatrix(writer, i, A); err != nil {
|
||||
log.Fatalf("send to peer: %v", err)
|
||||
}
|
||||
if err := comm.SendVector(writer, i, skShares[i]); err != nil {
|
||||
log.Fatalf("send to peer: %v", err)
|
||||
}
|
||||
if err := comm.SendBytesSliceMap(writer, i, seeds); err != nil {
|
||||
log.Fatalf("send to peer: %v", err)
|
||||
}
|
||||
if err := comm.SendBytesMap(writer, i, MACKeys[i]); err != nil {
|
||||
log.Fatalf("send to peer: %v", err)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
}
|
||||
@@ -137,11 +149,26 @@ func main() {
|
||||
genEnd = time.Now()
|
||||
} else {
|
||||
reader := bufio.NewReader(*comm.GetSock(sign.TrustedDealerID))
|
||||
b = comm.RecvVector(reader, sign.TrustedDealerID, sign.M)
|
||||
A = comm.RecvMatrix(reader, sign.TrustedDealerID, sign.M)
|
||||
party.SkShare = comm.RecvVector(reader, sign.TrustedDealerID, sign.N)
|
||||
party.Seed = comm.RecvBytesSliceMap(reader, sign.TrustedDealerID)
|
||||
party.MACKeys = comm.RecvBytesMap(reader, sign.TrustedDealerID)
|
||||
b, err = comm.RecvVector(reader, sign.TrustedDealerID, sign.M)
|
||||
if err != nil {
|
||||
log.Fatalf("recv vector from peer: %v", err)
|
||||
}
|
||||
A, err = comm.RecvMatrix(reader, sign.TrustedDealerID, sign.M)
|
||||
if err != nil {
|
||||
log.Fatalf("recv matrix from peer: %v", err)
|
||||
}
|
||||
party.SkShare, err = comm.RecvVector(reader, sign.TrustedDealerID, sign.N)
|
||||
if err != nil {
|
||||
log.Fatalf("recv vector from peer: %v", err)
|
||||
}
|
||||
party.Seed, err = comm.RecvBytesSliceMap(reader, sign.TrustedDealerID)
|
||||
if err != nil {
|
||||
log.Fatalf("recv seed map from peer: %v", err)
|
||||
}
|
||||
party.MACKeys, err = comm.RecvBytesMap(reader, sign.TrustedDealerID)
|
||||
if err != nil {
|
||||
log.Fatalf("recv MAC keys from peer: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(time.Second * 5)
|
||||
@@ -172,15 +199,25 @@ func main() {
|
||||
go func(i int) {
|
||||
defer round1Wg.Done()
|
||||
writer := bufio.NewWriter(*comm.GetSock(i))
|
||||
comm.SendMatrix(writer, i, D[partyID])
|
||||
comm.SendBytesMap(writer, i, MACs[partyID])
|
||||
if err := comm.SendMatrix(writer, i, D[partyID]); err != nil {
|
||||
log.Fatalf("send to peer: %v", err)
|
||||
}
|
||||
if err := comm.SendBytesMap(writer, i, MACs[partyID]); err != nil {
|
||||
log.Fatalf("send to peer: %v", err)
|
||||
}
|
||||
}(i)
|
||||
|
||||
go func(i int) {
|
||||
defer round1Wg.Done()
|
||||
reader := bufio.NewReader(*comm.GetSock(i))
|
||||
D[i] = comm.RecvMatrix(reader, i, sign.M)
|
||||
MACs[i] = comm.RecvBytesMap(reader, i)
|
||||
D[i], err = comm.RecvMatrix(reader, i, sign.M)
|
||||
if err != nil {
|
||||
log.Fatalf("recv matrix from peer: %v", err)
|
||||
}
|
||||
MACs[i], err = comm.RecvBytesMap(reader, i)
|
||||
if err != nil {
|
||||
log.Fatalf("recv MAC keys from peer: %v", err)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
}
|
||||
@@ -208,13 +245,18 @@ func main() {
|
||||
signRound2Start = time.Now()
|
||||
if partyID != sign.CombinerID {
|
||||
writer := bufio.NewWriter(*comm.GetSock(sign.CombinerID))
|
||||
comm.SendVector(writer, sign.CombinerID, z[partyID])
|
||||
if err := comm.SendVector(writer, sign.CombinerID, z[partyID]); err != nil {
|
||||
log.Fatalf("send to peer: %v", err)
|
||||
}
|
||||
signRound2End = time.Now()
|
||||
} else {
|
||||
for i := 0; i < sign.K; i++ {
|
||||
if i != sign.CombinerID {
|
||||
reader := bufio.NewReader(*comm.GetSock(i))
|
||||
z[i] = comm.RecvVector(reader, i, sign.N)
|
||||
z[i], err = comm.RecvVector(reader, i, sign.N)
|
||||
if err != nil {
|
||||
log.Fatalf("recv vector from peer: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
combinerReceiveEnd = time.Now()
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package networking
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/corona/wire"
|
||||
"github.com/luxfi/lattice/v7/ring"
|
||||
"github.com/luxfi/lattice/v7/utils/structs"
|
||||
)
|
||||
|
||||
func le(n uint64) []byte {
|
||||
var b [8]byte
|
||||
binary.LittleEndian.PutUint64(b[:], n)
|
||||
return b[:]
|
||||
}
|
||||
|
||||
// TestAFrameIsWalkedBeforeItIsDecoded. A peer writes the counts inside a
|
||||
// lattice frame, and the decoder sizes its work from them: a frame naming far
|
||||
// more than it carries costs the receiver time proportional to what it named
|
||||
// rather than to what it sent. Walking the frame first bounds that to the
|
||||
// bytes actually present.
|
||||
func TestAFrameIsWalkedBeforeItIsDecoded(t *testing.T) {
|
||||
// One poly claiming 2^40 coefficients, with none behind it.
|
||||
frame := append(append(le(1), le(1)...), le(1<<40)...)
|
||||
|
||||
walked := time.Now()
|
||||
err := wire.ValidateVectorPolyFrame(frame)
|
||||
walkCost := time.Since(walked)
|
||||
if err == nil {
|
||||
t.Fatal("a frame naming more than it carries was accepted")
|
||||
}
|
||||
|
||||
decoded := time.Now()
|
||||
vec := make(structs.Vector[ring.Poly], 1)
|
||||
_, _ = vec.ReadFrom(bufio.NewReader(bytes.NewReader(frame)))
|
||||
decodeCost := time.Since(decoded)
|
||||
|
||||
t.Logf("walk %v, decode %v, from %d bytes", walkCost, decodeCost, len(frame))
|
||||
if walkCost >= decodeCost {
|
||||
t.Fatalf("walking cost %v and decoding cost %v — the walk is meant to be the cheap way to find this out",
|
||||
walkCost, decodeCost)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRecvRefusesAFrameThatOutrunsItsBytes drives a real peer connection: one
|
||||
// party puts a body on the wire that names far more than it carries, and the
|
||||
// other has to refuse it and stay up. Sent raw rather than through SendVector,
|
||||
// because SendVector only ever writes frames that are honest about their size.
|
||||
func TestRecvRefusesAFrameThatOutrunsItsBytes(t *testing.T) {
|
||||
comm0, comm1, cleanup := twoPartyZAP(t)
|
||||
defer cleanup()
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
frame []byte
|
||||
}{
|
||||
{"a vector naming 2^40 polys", le(1 << 40)},
|
||||
{"a poly naming 2^40 coefficients", append(append(le(1), le(1)...), le(1<<40)...)},
|
||||
{"a count with no room for its own bytes", []byte{1, 2, 3}},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var (
|
||||
recvErr error
|
||||
done = make(chan struct{})
|
||||
)
|
||||
go func() {
|
||||
_, recvErr = comm1.RecvVector(nil, 0, 1)
|
||||
close(done)
|
||||
}()
|
||||
if err := comm0.node.send(context.Background(), 1, tc.frame); err != nil {
|
||||
t.Fatalf("send raw frame: %v", err)
|
||||
}
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("the receiver never answered; a frame naming 2^40 should be refused at once")
|
||||
}
|
||||
if recvErr == nil {
|
||||
t.Fatal("a frame naming more than it carries was accepted from a peer")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+115
-127
@@ -9,12 +9,14 @@ import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/corona/wire"
|
||||
"github.com/luxfi/lattice/v7/ring"
|
||||
"github.com/luxfi/lattice/v7/utils/structs"
|
||||
)
|
||||
@@ -165,180 +167,166 @@ func (comm *P2PComm) hasLegacySock(rank int) bool {
|
||||
// SendVector serializes a lattice vector to bytes and ships it to dst.
|
||||
// writer is honored when present (legacy path); otherwise the ZAP node
|
||||
// is used.
|
||||
func (comm *P2PComm) SendVector(writer *bufio.Writer, dst int, msg structs.Vector[ring.Poly]) {
|
||||
if writer != nil && comm.hasLegacySock(dst) {
|
||||
if _, err := msg.WriteTo(writer); err != nil {
|
||||
log.Fatalf("Failed to write vector: %v", err)
|
||||
}
|
||||
if err := writer.Flush(); err != nil {
|
||||
log.Fatalf("Failed to flush writer: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
bw := bufio.NewWriter(&buf)
|
||||
if _, err := msg.WriteTo(bw); err != nil {
|
||||
log.Fatalf("Failed to write vector: %v", err)
|
||||
}
|
||||
if err := bw.Flush(); err != nil {
|
||||
log.Fatalf("Failed to flush vector buffer: %v", err)
|
||||
}
|
||||
if err := comm.node.send(context.Background(), dst, buf.Bytes()); err != nil {
|
||||
log.Fatalf("Failed to send vector to %d: %v", dst, err)
|
||||
func (comm *P2PComm) SendVector(writer *bufio.Writer, dst int, msg structs.Vector[ring.Poly]) error {
|
||||
body, err := marshalTo(msg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("corona/net: encode vector for %d: %w", dst, err)
|
||||
}
|
||||
return comm.deliver(writer, dst, body)
|
||||
}
|
||||
|
||||
// RecvVector reads a lattice vector of the given length from src.
|
||||
func (comm *P2PComm) RecvVector(reader *bufio.Reader, src int, length int) structs.Vector[ring.Poly] {
|
||||
if reader != nil && comm.hasLegacySock(src) {
|
||||
func (comm *P2PComm) RecvVector(reader *bufio.Reader, src int, length int) (structs.Vector[ring.Poly], error) {
|
||||
if comm.streaming(reader, src) {
|
||||
vec := make(structs.Vector[ring.Poly], length)
|
||||
if _, err := vec.ReadFrom(reader); err != nil {
|
||||
log.Fatalf("Failed to read vector: %v", err)
|
||||
return nil, fmt.Errorf("corona/net: decode vector from %d: %w", src, err)
|
||||
}
|
||||
return vec
|
||||
return vec, nil
|
||||
}
|
||||
body, err := comm.node.recvBody(src)
|
||||
body, err := comm.collect(src)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to recv vector from %d: %v", src, err)
|
||||
return nil, fmt.Errorf("corona/net: read vector from %d: %w", src, err)
|
||||
}
|
||||
if err := wire.ValidateVectorPolyFrame(body); err != nil {
|
||||
return nil, fmt.Errorf("corona/net: vector from %d: %w", src, err)
|
||||
}
|
||||
vec := make(structs.Vector[ring.Poly], length)
|
||||
if _, err := vec.ReadFrom(bufio.NewReader(bytes.NewReader(body))); err != nil {
|
||||
log.Fatalf("Failed to read vector: %v", err)
|
||||
return nil, fmt.Errorf("corona/net: decode vector from %d: %w", src, err)
|
||||
}
|
||||
return vec
|
||||
return vec, nil
|
||||
}
|
||||
|
||||
// SendMatrix serializes a lattice matrix to bytes and ships it to dst.
|
||||
func (comm *P2PComm) SendMatrix(writer *bufio.Writer, dst int, msg structs.Matrix[ring.Poly]) {
|
||||
if writer != nil && comm.hasLegacySock(dst) {
|
||||
if _, err := msg.WriteTo(writer); err != nil {
|
||||
log.Fatalf("Error sending matrix: %v", err)
|
||||
}
|
||||
if err := writer.Flush(); err != nil {
|
||||
log.Fatalf("Failed to flush writer: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
bw := bufio.NewWriter(&buf)
|
||||
if _, err := msg.WriteTo(bw); err != nil {
|
||||
log.Fatalf("Error sending matrix: %v", err)
|
||||
}
|
||||
if err := bw.Flush(); err != nil {
|
||||
log.Fatalf("Failed to flush matrix buffer: %v", err)
|
||||
}
|
||||
if err := comm.node.send(context.Background(), dst, buf.Bytes()); err != nil {
|
||||
log.Fatalf("Failed to send matrix to %d: %v", dst, err)
|
||||
}
|
||||
}
|
||||
|
||||
// RecvMatrix reads a lattice matrix of the given outer length from src.
|
||||
func (comm *P2PComm) RecvMatrix(reader *bufio.Reader, src int, length int) structs.Matrix[ring.Poly] {
|
||||
if reader != nil && comm.hasLegacySock(src) {
|
||||
matrix := make(structs.Matrix[ring.Poly], length)
|
||||
if _, err := matrix.ReadFrom(reader); err != nil {
|
||||
log.Fatalf("Failed to read matrix: %v", err)
|
||||
}
|
||||
return matrix
|
||||
}
|
||||
body, err := comm.node.recvBody(src)
|
||||
// SendMatrix ships a lattice matrix to dst.
|
||||
func (comm *P2PComm) SendMatrix(writer *bufio.Writer, dst int, msg structs.Matrix[ring.Poly]) error {
|
||||
body, err := marshalTo(msg)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to recv matrix from %d: %v", src, err)
|
||||
return fmt.Errorf("corona/net: encode matrix for %d: %w", dst, err)
|
||||
}
|
||||
matrix := make(structs.Matrix[ring.Poly], length)
|
||||
if _, err := matrix.ReadFrom(bufio.NewReader(bytes.NewReader(body))); err != nil {
|
||||
log.Fatalf("Failed to read matrix: %v", err)
|
||||
return comm.deliver(writer, dst, body)
|
||||
}
|
||||
return matrix
|
||||
|
||||
// RecvMatrix reads a lattice matrix of the given row count from src.
|
||||
func (comm *P2PComm) RecvMatrix(reader *bufio.Reader, src int, length int) (structs.Matrix[ring.Poly], error) {
|
||||
if comm.streaming(reader, src) {
|
||||
m := make(structs.Matrix[ring.Poly], length)
|
||||
if _, err := m.ReadFrom(reader); err != nil {
|
||||
return nil, fmt.Errorf("corona/net: decode matrix from %d: %w", src, err)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
body, err := comm.collect(src)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("corona/net: read matrix from %d: %w", src, err)
|
||||
}
|
||||
if err := wire.ValidateMatrixPolyFrame(body); err != nil {
|
||||
return nil, fmt.Errorf("corona/net: matrix from %d: %w", src, err)
|
||||
}
|
||||
m := make(structs.Matrix[ring.Poly], length)
|
||||
if _, err := m.ReadFrom(bufio.NewReader(bytes.NewReader(body))); err != nil {
|
||||
return nil, fmt.Errorf("corona/net: decode matrix from %d: %w", src, err)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// SendBytesSlice ships a slice of byte-slices to dst with explicit
|
||||
// length tags. Wire layout: uint32 numSlices, then for each slice
|
||||
// uint32 length + slice bytes.
|
||||
func (comm *P2PComm) SendBytesSlice(writer *bufio.Writer, dst int, data [][]byte) {
|
||||
body := encodeBytesSlice(data)
|
||||
if writer != nil && comm.hasLegacySock(dst) {
|
||||
if _, err := writer.Write(body); err != nil {
|
||||
log.Fatalf("Failed to write bytes slice: %v", err)
|
||||
}
|
||||
if err := writer.Flush(); err != nil {
|
||||
log.Fatalf("Failed to flush writer: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := comm.node.send(context.Background(), dst, body); err != nil {
|
||||
log.Fatalf("Failed to send bytes slice to %d: %v", dst, err)
|
||||
}
|
||||
func (comm *P2PComm) SendBytesSlice(writer *bufio.Writer, dst int, data [][]byte) error {
|
||||
return comm.deliver(writer, dst, encodeBytesSlice(data))
|
||||
}
|
||||
|
||||
// RecvBytesSlice reads a length-prefixed slice-of-slices from src.
|
||||
func (comm *P2PComm) RecvBytesSlice(reader *bufio.Reader, src int) [][]byte {
|
||||
if reader != nil && comm.hasLegacySock(src) {
|
||||
return decodeBytesSlice(reader)
|
||||
func (comm *P2PComm) RecvBytesSlice(reader *bufio.Reader, src int) ([][]byte, error) {
|
||||
if comm.streaming(reader, src) {
|
||||
return decodeBytesSlice(reader), nil
|
||||
}
|
||||
body, err := comm.node.recvBody(src)
|
||||
body, err := comm.collect(src)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to recv bytes slice from %d: %v", src, err)
|
||||
return nil, fmt.Errorf("corona/net: read bytes slice from %d: %w", src, err)
|
||||
}
|
||||
return decodeBytesSlice(bufio.NewReader(bytes.NewReader(body)))
|
||||
return decodeBytesSlice(bufio.NewReader(bytes.NewReader(body))), nil
|
||||
}
|
||||
|
||||
// SendBytesMap ships a map<int,[]byte> to dst.
|
||||
func (comm *P2PComm) SendBytesMap(writer *bufio.Writer, dst int, data map[int][]byte) {
|
||||
body := encodeBytesMap(data)
|
||||
if writer != nil && comm.hasLegacySock(dst) {
|
||||
if _, err := writer.Write(body); err != nil {
|
||||
log.Fatalf("Failed to write bytes map: %v", err)
|
||||
}
|
||||
if err := writer.Flush(); err != nil {
|
||||
log.Fatalf("Failed to flush writer: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := comm.node.send(context.Background(), dst, body); err != nil {
|
||||
log.Fatalf("Failed to send bytes map to %d: %v", dst, err)
|
||||
}
|
||||
func (comm *P2PComm) SendBytesMap(writer *bufio.Writer, dst int, data map[int][]byte) error {
|
||||
return comm.deliver(writer, dst, encodeBytesMap(data))
|
||||
}
|
||||
|
||||
// RecvBytesMap reads a map<int,[]byte> from src.
|
||||
func (comm *P2PComm) RecvBytesMap(reader *bufio.Reader, src int) map[int][]byte {
|
||||
if reader != nil && comm.hasLegacySock(src) {
|
||||
return decodeBytesMap(reader)
|
||||
func (comm *P2PComm) RecvBytesMap(reader *bufio.Reader, src int) (map[int][]byte, error) {
|
||||
if comm.streaming(reader, src) {
|
||||
return decodeBytesMap(reader), nil
|
||||
}
|
||||
body, err := comm.node.recvBody(src)
|
||||
body, err := comm.collect(src)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to recv bytes map from %d: %v", src, err)
|
||||
return nil, fmt.Errorf("corona/net: read bytes map from %d: %w", src, err)
|
||||
}
|
||||
return decodeBytesMap(bufio.NewReader(bytes.NewReader(body)))
|
||||
return decodeBytesMap(bufio.NewReader(bytes.NewReader(body))), nil
|
||||
}
|
||||
|
||||
// SendBytesSliceMap ships a map<int,[][]byte> to dst.
|
||||
func (comm *P2PComm) SendBytesSliceMap(writer *bufio.Writer, dst int, data map[int][][]byte) {
|
||||
body := encodeBytesSliceMap(data)
|
||||
if writer != nil && comm.hasLegacySock(dst) {
|
||||
if _, err := writer.Write(body); err != nil {
|
||||
log.Fatalf("Failed to write bytes-slice map: %v", err)
|
||||
}
|
||||
if err := writer.Flush(); err != nil {
|
||||
log.Fatalf("Failed to flush writer: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := comm.node.send(context.Background(), dst, body); err != nil {
|
||||
log.Fatalf("Failed to send bytes-slice map to %d: %v", dst, err)
|
||||
}
|
||||
func (comm *P2PComm) SendBytesSliceMap(writer *bufio.Writer, dst int, data map[int][][]byte) error {
|
||||
return comm.deliver(writer, dst, encodeBytesSliceMap(data))
|
||||
}
|
||||
|
||||
// RecvBytesSliceMap reads a map<int,[][]byte> from src.
|
||||
func (comm *P2PComm) RecvBytesSliceMap(reader *bufio.Reader, src int) map[int][][]byte {
|
||||
if reader != nil && comm.hasLegacySock(src) {
|
||||
return decodeBytesSliceMap(reader)
|
||||
func (comm *P2PComm) RecvBytesSliceMap(reader *bufio.Reader, src int) (map[int][][]byte, error) {
|
||||
if comm.streaming(reader, src) {
|
||||
return decodeBytesSliceMap(reader), nil
|
||||
}
|
||||
body, err := comm.node.recvBody(src)
|
||||
body, err := comm.collect(src)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to recv bytes-slice map from %d: %v", src, err)
|
||||
return nil, fmt.Errorf("corona/net: read bytes-slice map from %d: %w", src, err)
|
||||
}
|
||||
return decodeBytesSliceMap(bufio.NewReader(bytes.NewReader(body)))
|
||||
return decodeBytesSliceMap(bufio.NewReader(bytes.NewReader(body))), nil
|
||||
}
|
||||
|
||||
// marshalTo renders a lattice value to the bytes that go on the wire.
|
||||
func marshalTo(msg io.WriterTo) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
bw := bufio.NewWriter(&buf)
|
||||
if _, err := msg.WriteTo(bw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := bw.Flush(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// deliver is the one way a body reaches a peer: the legacy socket when one is
|
||||
// still held for that rank, the ZAP node otherwise.
|
||||
func (comm *P2PComm) deliver(writer *bufio.Writer, dst int, body []byte) error {
|
||||
if writer != nil && comm.hasLegacySock(dst) {
|
||||
if _, err := writer.Write(body); err != nil {
|
||||
return fmt.Errorf("corona/net: write to %d: %w", dst, err)
|
||||
}
|
||||
if err := writer.Flush(); err != nil {
|
||||
return fmt.Errorf("corona/net: flush to %d: %w", dst, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := comm.node.send(context.Background(), dst, body); err != nil {
|
||||
return fmt.Errorf("corona/net: send to %d: %w", dst, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// streaming reports whether this peer is read from a socket rather than as
|
||||
// discrete messages. A stream has no message boundary to read up to, so a value
|
||||
// is decoded straight from it; a discrete body is walked before it is decoded.
|
||||
func (comm *P2PComm) streaming(reader *bufio.Reader, src int) bool {
|
||||
return reader != nil && comm.hasLegacySock(src)
|
||||
}
|
||||
|
||||
// collect takes one discrete message from a peer as bytes rather than as a
|
||||
// decoded value. That is the point: a peer writes the counts inside these
|
||||
// bytes and the lattice decoder sizes its work from them, so the frame is
|
||||
// walked before anything decodes it.
|
||||
func (comm *P2PComm) collect(src int) ([]byte, error) {
|
||||
return comm.node.recvBody(src)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
@@ -56,11 +56,14 @@ func TestP2PComm_SendRecvVector(t *testing.T) {
|
||||
|
||||
// Send and receive in separate goroutines
|
||||
done := make(chan bool)
|
||||
var receivedVector structs.Vector[ring.Poly]
|
||||
var (
|
||||
receivedVector structs.Vector[ring.Poly]
|
||||
recvErr error
|
||||
)
|
||||
|
||||
go func() {
|
||||
reader := bufio.NewReader(server)
|
||||
receivedVector = comm2.RecvVector(reader, 1, len(testVector))
|
||||
receivedVector, recvErr = comm2.RecvVector(reader, 1, len(testVector))
|
||||
done <- true
|
||||
}()
|
||||
|
||||
@@ -68,13 +71,17 @@ func TestP2PComm_SendRecvVector(t *testing.T) {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
writer := bufio.NewWriter(client)
|
||||
comm1.SendVector(writer, 2, testVector)
|
||||
if err := comm1.SendVector(writer, 2, testVector); err != nil {
|
||||
t.Fatalf("send: %v", err)
|
||||
}
|
||||
writer.Flush()
|
||||
|
||||
// Wait for receive to complete
|
||||
select {
|
||||
case <-done:
|
||||
// Success
|
||||
if recvErr != nil {
|
||||
t.Fatalf("RecvVector: %v", recvErr)
|
||||
}
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatal("Timeout waiting for vector receive")
|
||||
}
|
||||
@@ -126,7 +133,9 @@ func TestP2PComm_SendRecvMatrix(t *testing.T) {
|
||||
|
||||
go func() {
|
||||
reader := bufio.NewReader(server)
|
||||
receivedMatrix = comm2.RecvMatrix(reader, 1, len(testMatrix))
|
||||
var recvErr error
|
||||
receivedMatrix, recvErr = comm2.RecvMatrix(reader, 1, len(testMatrix))
|
||||
_ = recvErr
|
||||
done <- true
|
||||
}()
|
||||
|
||||
@@ -134,7 +143,9 @@ func TestP2PComm_SendRecvMatrix(t *testing.T) {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
writer := bufio.NewWriter(client)
|
||||
comm1.SendMatrix(writer, 2, testMatrix)
|
||||
if err := comm1.SendMatrix(writer, 2, testMatrix); err != nil {
|
||||
t.Fatalf("send: %v", err)
|
||||
}
|
||||
writer.Flush()
|
||||
|
||||
// Wait for receive to complete
|
||||
@@ -192,7 +203,9 @@ func TestP2PComm_SendRecvBytes(t *testing.T) {
|
||||
|
||||
go func() {
|
||||
reader := bufio.NewReader(server)
|
||||
receivedBytesSlices = comm2.RecvBytesSlice(reader, 1)
|
||||
var recvErr error
|
||||
receivedBytesSlices, recvErr = comm2.RecvBytesSlice(reader, 1)
|
||||
_ = recvErr
|
||||
done <- true
|
||||
}()
|
||||
|
||||
@@ -201,7 +214,9 @@ func TestP2PComm_SendRecvBytes(t *testing.T) {
|
||||
|
||||
// Send the bytes slices using SendBytesSlice (matching protocol)
|
||||
writer := bufio.NewWriter(client)
|
||||
comm1.SendBytesSlice(writer, 2, testBytesSlices)
|
||||
if err := comm1.SendBytesSlice(writer, 2, testBytesSlices); err != nil {
|
||||
t.Fatalf("send: %v", err)
|
||||
}
|
||||
writer.Flush()
|
||||
|
||||
// Wait for receive to complete
|
||||
@@ -256,7 +271,9 @@ func TestP2PComm_SendRecvBytesMap(t *testing.T) {
|
||||
|
||||
go func() {
|
||||
reader := bufio.NewReader(server)
|
||||
receivedBytesMap = comm2.RecvBytesMap(reader, 1)
|
||||
var recvErr error
|
||||
receivedBytesMap, recvErr = comm2.RecvBytesMap(reader, 1)
|
||||
_ = recvErr
|
||||
done <- true
|
||||
}()
|
||||
|
||||
@@ -264,7 +281,9 @@ func TestP2PComm_SendRecvBytesMap(t *testing.T) {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
writer := bufio.NewWriter(client)
|
||||
comm1.SendBytesMap(writer, 2, testBytesMap)
|
||||
if err := comm1.SendBytesMap(writer, 2, testBytesMap); err != nil {
|
||||
t.Fatalf("send: %v", err)
|
||||
}
|
||||
writer.Flush()
|
||||
|
||||
// Wait for receive to complete
|
||||
|
||||
+54
-18
@@ -99,10 +99,14 @@ func TestZAP_SendRecvVector(t *testing.T) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
received = comm1.RecvVector(nil, 0, len(testVector))
|
||||
var recvErr error
|
||||
received, recvErr = comm1.RecvVector(nil, 0, len(testVector))
|
||||
_ = recvErr
|
||||
}()
|
||||
|
||||
comm0.SendVector(nil, 1, testVector)
|
||||
if err := comm0.SendVector(nil, 1, testVector); err != nil {
|
||||
t.Fatalf("send: %v", err)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() { wg.Wait(); close(done) }()
|
||||
@@ -142,10 +146,14 @@ func TestZAP_SendRecvMatrix(t *testing.T) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
received = comm1.RecvMatrix(nil, 0, len(testMatrix))
|
||||
var recvErr error
|
||||
received, recvErr = comm1.RecvMatrix(nil, 0, len(testMatrix))
|
||||
_ = recvErr
|
||||
}()
|
||||
|
||||
comm0.SendMatrix(nil, 1, testMatrix)
|
||||
if err := comm0.SendMatrix(nil, 1, testMatrix); err != nil {
|
||||
t.Fatalf("send: %v", err)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() { wg.Wait(); close(done) }()
|
||||
@@ -185,9 +193,13 @@ func TestZAP_SendRecvBytesSlice(t *testing.T) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
out = comm1.RecvBytesSlice(nil, 0)
|
||||
var recvErr error
|
||||
out, recvErr = comm1.RecvBytesSlice(nil, 0)
|
||||
_ = recvErr
|
||||
}()
|
||||
comm0.SendBytesSlice(nil, 1, in)
|
||||
if err := comm0.SendBytesSlice(nil, 1, in); err != nil {
|
||||
t.Fatalf("send: %v", err)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() { wg.Wait(); close(done) }()
|
||||
@@ -222,9 +234,13 @@ func TestZAP_SendRecvBytesMap(t *testing.T) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
out = comm1.RecvBytesMap(nil, 0)
|
||||
var recvErr error
|
||||
out, recvErr = comm1.RecvBytesMap(nil, 0)
|
||||
_ = recvErr
|
||||
}()
|
||||
comm0.SendBytesMap(nil, 1, in)
|
||||
if err := comm0.SendBytesMap(nil, 1, in); err != nil {
|
||||
t.Fatalf("send: %v", err)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() { wg.Wait(); close(done) }()
|
||||
@@ -263,9 +279,13 @@ func TestZAP_SendRecvBytesSliceMap(t *testing.T) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
out = comm1.RecvBytesSliceMap(nil, 0)
|
||||
var recvErr error
|
||||
out, recvErr = comm1.RecvBytesSliceMap(nil, 0)
|
||||
_ = recvErr
|
||||
}()
|
||||
comm0.SendBytesSliceMap(nil, 1, in)
|
||||
if err := comm0.SendBytesSliceMap(nil, 1, in); err != nil {
|
||||
t.Fatalf("send: %v", err)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() { wg.Wait(); close(done) }()
|
||||
@@ -324,9 +344,13 @@ func TestZAP_RoundtripPreservesLatticeBytes(t *testing.T) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
received = comm1.RecvVector(nil, 0, len(v))
|
||||
var recvErr error
|
||||
received, recvErr = comm1.RecvVector(nil, 0, len(v))
|
||||
_ = recvErr
|
||||
}()
|
||||
comm0.SendVector(nil, 1, v)
|
||||
if err := comm0.SendVector(nil, 1, v); err != nil {
|
||||
t.Fatalf("send: %v", err)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() { wg.Wait(); close(done) }()
|
||||
@@ -379,10 +403,14 @@ func BenchmarkSendRecvVector_ZAP(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
_ = comm1.RecvVector(nil, 0, len(v))
|
||||
var recvErr error
|
||||
_, recvErr = comm1.RecvVector(nil, 0, len(v))
|
||||
_ = recvErr
|
||||
close(done)
|
||||
}()
|
||||
comm0.SendVector(nil, 1, v)
|
||||
if err := comm0.SendVector(nil, 1, v); err != nil {
|
||||
b.Fatalf("send: %v", err)
|
||||
}
|
||||
<-done
|
||||
}
|
||||
}
|
||||
@@ -423,11 +451,15 @@ func BenchmarkSendRecvVector_BareTCPPipe(b *testing.B) {
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
reader := bufioNewReader(server)
|
||||
_ = comm1.RecvVector(reader, 0, len(v))
|
||||
var recvErr error
|
||||
_, recvErr = comm1.RecvVector(reader, 0, len(v))
|
||||
_ = recvErr
|
||||
close(done)
|
||||
}()
|
||||
writer := bufioNewWriter(client)
|
||||
comm0.SendVector(writer, 1, v)
|
||||
if err := comm0.SendVector(writer, 1, v); err != nil {
|
||||
b.Fatalf("send: %v", err)
|
||||
}
|
||||
<-done
|
||||
}
|
||||
}
|
||||
@@ -481,10 +513,14 @@ func BenchmarkSendRecvVector_BareTCPLoopback(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
_ = comm1.RecvVector(reader, 0, len(v))
|
||||
var recvErr error
|
||||
_, recvErr = comm1.RecvVector(reader, 0, len(v))
|
||||
_ = recvErr
|
||||
close(done)
|
||||
}()
|
||||
comm0.SendVector(writer, 1, v)
|
||||
if err := comm0.SendVector(writer, 1, v); err != nil {
|
||||
b.Fatalf("send: %v", err)
|
||||
}
|
||||
<-done
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -18,7 +18,7 @@ var K int
|
||||
var Threshold int
|
||||
|
||||
// main function orchestrates the threshold signature protocol
|
||||
func LocalRun(x int) {
|
||||
func LocalRun(x int) error {
|
||||
var totalGenDuration, totalFinalizeDuration, totalVerifyDuration time.Duration
|
||||
|
||||
// Create maps to collect durations across all runs
|
||||
@@ -87,7 +87,7 @@ func LocalRun(x int) {
|
||||
var r1err error
|
||||
D[partyID], MACs[partyID], r1err = parties[partyID].SignRound1(A, sid, []byte(PRFKey), T)
|
||||
if r1err != nil {
|
||||
log.Fatalf("SignRound1 failed for party %d: %v", partyID, r1err)
|
||||
return fmt.Errorf("sign round 1, party %d: %w", partyID, r1err)
|
||||
}
|
||||
signRound1Durations[partyID] = time.Since(start)
|
||||
}
|
||||
@@ -100,7 +100,7 @@ func LocalRun(x int) {
|
||||
start = time.Now()
|
||||
valid, DSum, hash := parties[partyID].SignRound2Preprocess(A, b, D, MACs, sid, T)
|
||||
if !valid {
|
||||
log.Fatalf("MAC verification failed for party %d", partyID)
|
||||
return fmt.Errorf("sign round 2 preprocess, party %d: MAC did not verify", partyID)
|
||||
}
|
||||
signRound2PreprocessDurations[partyID] = time.Since(start)
|
||||
|
||||
@@ -157,6 +157,7 @@ func LocalRun(x int) {
|
||||
totalSigningDurations[partyID] = totalSignRound1Durations[partyID] + totalSignRound2PreprocessDurations[partyID] + totalSignRound2Durations[partyID]
|
||||
}
|
||||
printAveragedStats("Total Signing", totalSigningDurations, x)
|
||||
return nil
|
||||
}
|
||||
|
||||
// printAveragedStats prints the mean, median, and standard deviation for a map of durations averaged over x runs
|
||||
|
||||
+3
-1
@@ -199,7 +199,9 @@ func (party *Party) SignRound1(A structs.Matrix[ring.Poly], sid int, PRFKey []by
|
||||
|
||||
D := utils.InitializeMatrix(r, M, Dbar+1)
|
||||
|
||||
utils.MatrixMatrixMul(r, A, concatenatedR, D)
|
||||
if err := utils.MatrixMatrixMul(r, A, concatenatedR, D); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := utils.MatrixAdd(r, concatenatedE, D, D); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math/big"
|
||||
|
||||
"github.com/luxfi/lattice/v7/ring"
|
||||
@@ -118,10 +117,9 @@ func MatrixVectorMulNaive(r *ring.Ring, M structs.Matrix[ring.Poly], vec structs
|
||||
// MatrixMatrixMul performs matrix-matrix multiplication.
|
||||
// It takes two matrices of ring.Poly pointers, M1 of dimensions m x p and M2 of dimensions p x n,
|
||||
// and outputs the result in a given result matrix of dimensions m x n.
|
||||
func MatrixMatrixMulNaive(r *ring.Ring, M1, M2 structs.Matrix[ring.Poly], result structs.Matrix[ring.Poly]) {
|
||||
func MatrixMatrixMulNaive(r *ring.Ring, M1, M2 structs.Matrix[ring.Poly], result structs.Matrix[ring.Poly]) error {
|
||||
if M1 == nil || M2 == nil || len(M1) == 0 || len(M2) == 0 || len((M1)[0]) != len(M2) {
|
||||
log.Fatalf("Matrix dimensions are not compatible for multiplication.")
|
||||
return
|
||||
return errShapeMismatch
|
||||
}
|
||||
|
||||
m := len(M1)
|
||||
@@ -146,6 +144,7 @@ func MatrixMatrixMulNaive(r *ring.Ring, M1, M2 structs.Matrix[ring.Poly], result
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VectorPolyMulNaive performs element-wise multiplication of a vector by a polynomial.
|
||||
|
||||
+15
-12
@@ -3,6 +3,7 @@ package utils
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/big"
|
||||
@@ -76,11 +77,17 @@ func MatrixVectorMulNTT(r *ring.Ring, M structs.Matrix[ring.Poly], vec structs.V
|
||||
ConvertVectorFromNTT(r, vec)
|
||||
}
|
||||
|
||||
// MatrixMatrixMul performs matrix-matrix multiplication.
|
||||
func MatrixMatrixMulNTT(r *ring.Ring, M1, M2 structs.Matrix[ring.Poly], result structs.Matrix[ring.Poly]) {
|
||||
// errShapeMismatch is what every routine here answers to a shape it cannot
|
||||
// multiply or add. A shape is a fact about the INPUT, and one input on these
|
||||
// paths is a commitment a peer sent, so it has to be something the caller can
|
||||
// refuse and carry on from. A library that ends the process on its argument
|
||||
// leaves the caller no such choice.
|
||||
var errShapeMismatch = errors.New("corona: matrix dimensions are not compatible for multiplication")
|
||||
|
||||
// MatrixMatrixMulNTT performs matrix-matrix multiplication in the NTT domain.
|
||||
func MatrixMatrixMulNTT(r *ring.Ring, M1, M2 structs.Matrix[ring.Poly], result structs.Matrix[ring.Poly]) error {
|
||||
if M1 == nil || M2 == nil || len(M1) == 0 || len(M2) == 0 || len((M1)[0]) != len(M2) {
|
||||
log.Fatalf("Matrix dimensions are not compatible for multiplication.")
|
||||
return
|
||||
return errShapeMismatch
|
||||
}
|
||||
|
||||
m := len(M1)
|
||||
@@ -115,6 +122,7 @@ func MatrixMatrixMulNTT(r *ring.Ring, M1, M2 structs.Matrix[ring.Poly], result s
|
||||
ConvertMatrixFromNTT(r, result)
|
||||
ConvertMatrixFromNTT(r, M1)
|
||||
ConvertMatrixFromNTT(r, M2)
|
||||
return nil
|
||||
}
|
||||
|
||||
// VectorPolyMul performs element-wise multiplication of a vector by a polynomial.
|
||||
@@ -150,10 +158,9 @@ func MatrixVectorMul(r *ring.Ring, M structs.Matrix[ring.Poly], vec structs.Vect
|
||||
}
|
||||
|
||||
// MatrixMatrixMul performs matrix-matrix multiplication.
|
||||
func MatrixMatrixMul(r *ring.Ring, M1, M2 structs.Matrix[ring.Poly], result structs.Matrix[ring.Poly]) {
|
||||
func MatrixMatrixMul(r *ring.Ring, M1, M2 structs.Matrix[ring.Poly], result structs.Matrix[ring.Poly]) error {
|
||||
if M1 == nil || M2 == nil || len(M1) == 0 || len(M2) == 0 || len((M1)[0]) != len(M2) {
|
||||
log.Fatalf("Matrix dimensions are not compatible for multiplication.")
|
||||
return
|
||||
return errShapeMismatch
|
||||
}
|
||||
|
||||
m := len(M1)
|
||||
@@ -167,6 +174,7 @@ func MatrixMatrixMul(r *ring.Ring, M1, M2 structs.Matrix[ring.Poly], result stru
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VectorPolyMul performs element-wise multiplication of a vector by a polynomial.
|
||||
@@ -179,11 +187,6 @@ func VectorPolyMul(r *ring.Ring, vec structs.Vector[ring.Poly], poly ring.Poly,
|
||||
// MatrixAdd adds two matrices of ring.Poly element-wise and stores the result in a given result matrix.
|
||||
func MatrixAdd(r *ring.Ring, M1, M2, result structs.Matrix[ring.Poly]) error {
|
||||
if M1 == nil || M2 == nil || len(M1) == 0 || len(M2) == 0 || len(M1) != len(M2) || len((M1)[0]) != len((M2)[0]) {
|
||||
// A shape mismatch is a fact about the INPUT — and one input here is a
|
||||
// commitment a peer sent. It must be an error the caller refuses on, never
|
||||
// os.Exit: log.Fatalf killed an honest validator whenever a peer delivered
|
||||
// a malformed matrix. Nothing that touches peer data may take the process
|
||||
// down with it.
|
||||
return fmt.Errorf("corona: matrix dimensions must match for element-wise addition")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user