diff --git a/f3/api.go b/f3/api.go index c5cf6f47..2c61493e 100644 --- a/f3/api.go +++ b/f3/api.go @@ -47,8 +47,15 @@ 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 + // VerifyAggregate verifies an aggregate signature. + VerifyAggregate(payload, aggSig []byte, signers []PubKey) bool } // Participant interface to the host system resources. @@ -56,6 +63,7 @@ type Host interface { Network Clock Signer + Verifier // Logs a message at the "logic" level Log(format string, args ...interface{}) diff --git a/f3/granite.go b/f3/granite.go index ce0e9909..cd17669a 100644 --- a/f3/granite.go +++ b/f3/granite.go @@ -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) { @@ -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. @@ -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 @@ -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 { @@ -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 } @@ -494,6 +497,7 @@ func (i *instance) tryCommit(round uint32) error { } } + i.beginNextRound() } diff --git a/f3/powertable.go b/f3/powertable.go index 62622848..c13d697a 100644 --- a/f3/powertable.go +++ b/f3/powertable.go @@ -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)). @@ -40,7 +40,6 @@ func NewPowerTable(entries []PowerEntry) *PowerTable { if len(entries) != len(lookup) { panic("duplicate power entries") } - return &PowerTable{ Entries: entries, Lookup: lookup, diff --git a/f3/types.go b/f3/types.go index b904d6a6..34a75548 100644 --- a/f3/types.go +++ b/f3/types.go @@ -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) diff --git a/f3/vrf.go b/f3/vrf.go index be7279e5..9454efbe 100644 --- a/f3/vrf.go +++ b/f3/vrf.go @@ -20,17 +20,19 @@ 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, } } @@ -38,8 +40,8 @@ func (f *VRF) MakeTicket(beacon []byte, instance uint32, round uint32, signer Ac 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. diff --git a/go.mod b/go.mod index 3b1320cc..70f19236 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 0b74dc93..2d9c85cb 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/sim/network.go b/sim/network.go index 4b9e49af..7880b380 100644 --- a/sim/network.go +++ b/sim/network.go @@ -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 { @@ -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 { @@ -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 @@ -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 + // (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{}) { diff --git a/sim/sim.go b/sim/sim.go index c46ea8e5..1773c821 100644 --- a/sim/sim.go +++ b/sim/sim.go @@ -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)) } } @@ -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) } } @@ -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() +}