Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Aggregator interface and implement fake aggregator #35

Merged
merged 16 commits into from
Jan 24, 2024
Merged
10 changes: 9 additions & 1 deletion f3/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,23 @@ type Clock interface {
type Signer interface {
// Signs a message for the given sender ID.
Sign(sender ActorID, msg []byte) []byte
}

type Verifier interface {
// Verifies a signature for the given sender ID.
Verify(sender ActorID, msg, sig []byte) bool
Verify(pubKey PubKey, msg, sig []byte) bool
// Aggregates signatures from a participant to an existing signature.
Aggregate(sig [][]byte, aggSignature []byte) []byte
Copy link
Contributor

@Kubuxu Kubuxu Jan 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that you are assuming that aggregate signatures can be combined. I think the conclusion is that it is not true, but we can address that later.

// VerifyAggregate verifies an aggregate signature.
VerifyAggregate(payload, aggSig []byte, signers []PubKey) bool
}

// Participant interface to the host system resources.
type Host interface {
Network
Clock
Signer
Verifier

// Logs a message at the "logic" level
Log(format string, args ...interface{})
Expand Down
10 changes: 7 additions & 3 deletions f3/granite.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ func (i *instance) receiveOne(msg *GMessage) error {
if i.phase == TERMINATED_PHASE {
return nil // No-op
}
round := i.roundState(msg.Round)

// Drop any messages that can never be valid.
if !i.isValid(msg) {
Expand All @@ -262,7 +263,6 @@ func (i *instance) receiveOne(msg *GMessage) error {
return nil
}

round := i.roundState(msg.Round)
switch msg.Step {
case QUALITY_PHASE:
// Receive each prefix of the proposal independently.
Expand Down Expand Up @@ -321,6 +321,9 @@ func (i *instance) isValid(msg *GMessage) bool {
i.log("sender with zero power or not in power table")
return false
}

_, pubKey := i.powerTable.Get(msg.Sender)

if !(msg.Value.IsZero() || msg.Value.HasBase(i.input.Base())) {
i.log("unexpected base %s", &msg.Value)
return false
Expand All @@ -330,7 +333,7 @@ func (i *instance) isValid(msg *GMessage) bool {
} else if msg.Step == CONVERGE_PHASE {
if msg.Round == 0 ||
msg.Value.IsZero() ||
!i.vrf.VerifyTicket(i.beacon, i.instanceID, msg.Round, msg.Sender, msg.Ticket) {
!i.vrf.VerifyTicket(i.beacon, i.instanceID, msg.Round, pubKey, msg.Ticket) {
return false
}
} else if msg.Step == DECIDE_PHASE {
Expand All @@ -339,7 +342,7 @@ func (i *instance) isValid(msg *GMessage) bool {
}

sigPayload := SignaturePayload(msg.Instance, msg.Round, msg.Step, msg.Value)
if !i.host.Verify(msg.Sender, sigPayload, msg.Signature) {
if !i.host.Verify(pubKey, sigPayload, msg.Signature) {
i.log("invalid signature on %v", msg)
return false
}
Expand Down Expand Up @@ -494,6 +497,7 @@ func (i *instance) tryCommit(round uint32) error {
}

}

i.beginNextRound()
}

Expand Down
3 changes: 1 addition & 2 deletions f3/powertable.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
type PowerEntry struct {
ID ActorID
Power *StoragePower
PubKey []byte
PubKey PubKey
}

// PowerTable maps ActorID to a unique index in the range [0, len(powerTable.Entries)).
Expand Down Expand Up @@ -40,7 +40,6 @@ func NewPowerTable(entries []PowerEntry) *PowerTable {
if len(entries) != len(lookup) {
panic("duplicate power entries")
}

return &PowerTable{
Entries: entries,
Lookup: lookup,
Expand Down
2 changes: 2 additions & 0 deletions f3/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ type ActorID uint64

type StoragePower = big.Int

type PubKey []byte

// Creates a new StoragePower struct with a specific value and returns the result
func NewStoragePower(value int64) *StoragePower {
return new(big.Int).SetInt64(value)
Expand Down
14 changes: 8 additions & 6 deletions f3/vrf.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,28 @@ type VRFTicketSource interface {
}

type VRFTicketVerifier interface {
VerifyTicket(beacon []byte, instance uint32, round uint32, signer ActorID, ticket Ticket) bool
VerifyTicket(beacon []byte, instance uint32, round uint32, signer PubKey, ticket Ticket) bool
}

// VRF used for the CONVERGE step of GossiPBFT.
type VRF struct {
signer Signer
signer Signer
verifier Verifier
}

func NewVRF(signer Signer) *VRF {
func NewVRF(signer Signer, verifier Verifier) *VRF {
return &VRF{
signer: signer,
signer: signer,
verifier: verifier,
}
}

func (f *VRF) MakeTicket(beacon []byte, instance uint32, round uint32, signer ActorID) Ticket {
return f.signer.Sign(signer, f.serializeSigInput(beacon, instance, round))
}

func (f *VRF) VerifyTicket(beacon []byte, instance uint32, round uint32, signer ActorID, ticket Ticket) bool {
return f.signer.Verify(signer, f.serializeSigInput(beacon, instance, round), ticket)
func (f *VRF) VerifyTicket(beacon []byte, instance uint32, round uint32, signer PubKey, ticket Ticket) bool {
return f.verifier.Verify(signer, f.serializeSigInput(beacon, instance, round), ticket)
}

// Serializes the input to the VRF signature for the CONVERGE step of GossiPBFT.
Expand Down
14 changes: 14 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ require github.com/stretchr/testify v1.8.4

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/filecoin-project/go-bitfield v0.2.4 // indirect
github.com/ipfs/go-cid v0.0.5 // indirect
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771 // indirect
github.com/mr-tron/base58 v1.1.3 // indirect
github.com/multiformats/go-base32 v0.0.3 // indirect
github.com/multiformats/go-multibase v0.0.1 // indirect
github.com/multiformats/go-multihash v0.0.13 // indirect
github.com/multiformats/go-varint v0.0.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/whyrusleeping/cbor-gen v0.0.0-20200414195334-429a0b5e922e // indirect
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 // indirect
golang.org/x/sys v0.0.0-20190412213103-97732733099d // indirect
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,21 @@ github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/whyrusleeping/cbor-gen v0.0.0-20200414195334-429a0b5e922e h1:JY8o/ebUUrCYetWmjRCNghxC59cOEaili83rxPRQCLw=
github.com/whyrusleeping/cbor-gen v0.0.0-20200414195334-429a0b5e922e/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
110 changes: 88 additions & 22 deletions sim/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ package sim

import (
"bytes"
"encoding/binary"
"fmt"
"github.com/filecoin-project/go-f3/f3"
"io"
"sort"
"strings"

"github.com/filecoin-project/go-f3/f3"
)

type AdversaryReceiver interface {
Expand Down Expand Up @@ -48,6 +47,8 @@ type Network struct {
globalStabilisationElapsed bool
// Trace level.
traceLevel int

actor2PubKey map[f3.ActorID]f3.PubKey
}

func NewNetwork(latency LatencyModel, traceLevel int) *Network {
Expand All @@ -59,15 +60,17 @@ func NewNetwork(latency LatencyModel, traceLevel int) *Network {
latency: latency,
globalStabilisationElapsed: false,
traceLevel: traceLevel,
actor2PubKey: map[f3.ActorID]f3.PubKey{},
}
}

func (n *Network) AddParticipant(p f3.Receiver) {
func (n *Network) AddParticipant(p f3.Receiver, pubKey f3.PubKey) {
if n.participants[p.ID()] != nil {
panic("duplicate participant ID")
}
n.participantIDs = append(n.participantIDs, p.ID())
n.participants[p.ID()] = p
n.actor2PubKey[p.ID()] = pubKey
}

////// Network interface
Expand Down Expand Up @@ -107,31 +110,94 @@ func (n *Network) SetAlarm(sender f3.ActorID, payload string, at float64) {

func (n *Network) Sign(sender f3.ActorID, msg []byte) []byte {
// Fake implementation.
// Just prepends 8-byte sender ID to message.
buf := new(bytes.Buffer)
if err := binary.Write(buf, binary.BigEndian, sender); err != nil {
panic(err)
}
return append(buf.Bytes(), msg...)
// Just prepends the pubkey associated with the sender ID to message.
aux := append([]byte{}, n.actor2PubKey[sender]...)
return append(aux, msg...)
}

func (n *Network) Verify(sender f3.ActorID, msg, sig []byte) bool {
func (n *Network) Verify(pubKey f3.PubKey, msg, sig []byte) bool {
// Fake implementation.
// Just checks that first 8 bytes of signature match sender ID,
// Just checks that first bytes of the signature match sender ID,
// and remaining bytes match message.
buf := bytes.NewReader(sig)
var recoveredSender uint64
if err := binary.Read(buf, binary.BigEndian, &recoveredSender); err != nil {
return false
aux := append([]byte{}, pubKey...)
aux = append(aux, msg...)
return bytes.Equal(aux, sig)
}

func (n *Network) Aggregate(sigs [][]byte, aggSignature []byte) []byte {
// Fake implementation.
// Just appends signature to aggregate signature.
// This fake aggregation is not commutative (order matters)
// But determinism is preserved by sorting by weight both here and in VerifyAggregate
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO: the deterministic sorting should happen outside the interface to match real signature schemes which can also have this behaviour.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But that would mean forcing outside the interface a sorting that may not be required by all signature schemes. Would it not be better to have it inside the signature schemes that need it only?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I get optimizing if the real BLS is not commutative, and coupling in that particular instantiation of the interface, but I also mean in particular in the case of the sorting required by this general-purpose (or so-intending) fake aggregation and verification scheme, that just happens to need to sort)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point, and I don't know which approach is better at this moment. I mentioned it because this requirement was unclear to me, and I immediately highlighted it as a possible difference between test/sim and reality.

We can keep order independence internal, but then we should highlight it in the interface documentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can keep order independence internal, but then we should highlight it in the interface documentation.

Sounds good, I suggest going back to this when we discuss interfaces in #42 not to duplicate discussions.

// (That contains the sender ID in the signature)

// Sort the pubKeys based on descending order of actorID
// take any pubkey from the actor2pubkey
var pubKeyLen int
for _, pubKey := range n.actor2PubKey {
pubKeyLen = len(pubKey)
break
}
remainingBytes := sig[8:]
if recoveredSender != uint64(sender) {
return false

msg := sigs[0][pubKeyLen:]
msgLen := len(msg)

// Extract existing pubKeys along with their actorIDs
pubKeys := [][]byte{}
if len(aggSignature) > 0 {
buf := bytes.NewReader(aggSignature[msgLen:])
for {
existingPubKey := make([]byte, pubKeyLen)
if _, err := io.ReadFull(buf, existingPubKey); err != nil {
if err == io.EOF {
break // End of the aggregate signature.
} else if err != nil {
panic(err) // Error in reading the signature.
}
}
pubKeys = append(pubKeys, existingPubKey)
}
}
if !bytes.Equal(remainingBytes, msg) {
return false

for i := 0; i < len(sigs); i++ {
if !bytes.Equal(msg, sigs[i][pubKeyLen:]) {
panic("Payload mismatch")
}
pubKeys = append(pubKeys, sigs[i][:pubKeyLen])
}
return true

sort.Slice(pubKeys, func(i, j int) bool {
return bytes.Compare(pubKeys[i], pubKeys[j]) > 0
})

for i := 0; i < len(pubKeys)-1; i++ {
if bytes.Equal(pubKeys[i], pubKeys[i+1]) {
panic("Duplicate pubkeys")
}
}

// Reconstruct the aggregated signature in sorted order
updatedAggSignature := append([]byte{}, msg...)
for _, s := range pubKeys {
updatedAggSignature = append(updatedAggSignature, s...)
}

return updatedAggSignature
}

func (n *Network) VerifyAggregate(payload, aggSig []byte, signers []f3.PubKey) bool {
sort.Slice(signers, func(i, j int) bool {
return bytes.Compare(signers[i], signers[j]) > 0
})

signersConcat := make([]byte, 0)
for _, signer := range signers {
signersConcat = append(signersConcat, signer...)
}

aux := append([]byte{}, payload...)
aux = append(aux, signersConcat...)
return bytes.Equal(aux, aggSig)
}

func (n *Network) Log(format string, args ...interface{}) {
Expand Down
20 changes: 12 additions & 8 deletions sim/sim.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,15 @@ func NewSimulation(simConfig Config, graniteConfig f3.GraniteConfig, traceLevel
// Create a network to deliver messages.
lat := NewLogNormal(simConfig.LatencySeed, simConfig.LatencyMean)
ntwk := NewNetwork(lat, traceLevel)
vrf := f3.NewVRF(ntwk)
vrf := f3.NewVRF(ntwk, ntwk)

// Create participants.
genesisPower := f3.NewPowerTable(make([]f3.PowerEntry, 0))
participants := make([]*f3.Participant, simConfig.HonestCount)
for i := 0; i < len(participants); i++ {
participants[i] = f3.NewParticipant(f3.ActorID(i), graniteConfig, ntwk, vrf)
ntwk.AddParticipant(participants[i])
var buf bytes.Buffer
buf.WriteString("PUBKEY:")
_ = binary.Write(&buf, binary.BigEndian, participants[i].ID())
if err := genesisPower.Add(participants[i].ID(), f3.NewStoragePower(1), buf.Bytes()); err != nil {
ntwk.AddParticipant(participants[i], getFakePubKey(participants[i].ID()))
if err := genesisPower.Add(participants[i].ID(), f3.NewStoragePower(1), getFakePubKey(participants[i].ID())); err != nil {
panic(fmt.Errorf("failed adding participant to power table: %w", err))
}
}
Expand All @@ -69,8 +66,8 @@ func NewSimulation(simConfig Config, graniteConfig f3.GraniteConfig, traceLevel

func (s *Simulation) SetAdversary(adv AdversaryReceiver, power uint) {
s.Adversary = adv
s.Network.AddParticipant(adv)
if err := s.PowerTable.Add(adv.ID(), f3.NewStoragePower(int64(power)), make([]byte, 0)); err != nil {
s.Network.AddParticipant(adv, getFakePubKey(adv.ID()))
if err := s.PowerTable.Add(adv.ID(), f3.NewStoragePower(int64(power)), getFakePubKey(adv.ID())); err != nil {
panic(err)
}
}
Expand Down Expand Up @@ -202,3 +199,10 @@ func (c *CIDGen) next() uint64 {
}

var alphanum = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")

func getFakePubKey(id f3.ActorID) []byte {
var buf bytes.Buffer
buf.WriteString("PUBKEY:")
_ = binary.Write(&buf, binary.BigEndian, id)
return buf.Bytes()
}
Loading