diff --git a/api/consensus/consensus.pb.go b/api/consensus/consensus.pb.go index 4bd07b5d7..90f0ed922 100644 --- a/api/consensus/consensus.pb.go +++ b/api/consensus/consensus.pb.go @@ -23,39 +23,30 @@ const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package type MessageType int32 const ( - MessageType_UNKNOWN MessageType = 0 - MessageType_ANNOUNCE MessageType = 1 - MessageType_COMMIT MessageType = 2 - MessageType_CHALLENGE MessageType = 3 - MessageType_RESPONSE MessageType = 4 - MessageType_COLLECTIVE_SIG MessageType = 5 - MessageType_FINAL_COMMIT MessageType = 6 - MessageType_FINAL_CHALLENGE MessageType = 7 - MessageType_FINAL_RESPONSE MessageType = 8 + MessageType_UNKNOWN MessageType = 0 + MessageType_ANNOUNCE MessageType = 1 + MessageType_PREPARE MessageType = 2 + MessageType_PREPARED MessageType = 3 + MessageType_COMMIT MessageType = 4 + MessageType_COMMITTED MessageType = 5 ) var MessageType_name = map[int32]string{ 0: "UNKNOWN", 1: "ANNOUNCE", - 2: "COMMIT", - 3: "CHALLENGE", - 4: "RESPONSE", - 5: "COLLECTIVE_SIG", - 6: "FINAL_COMMIT", - 7: "FINAL_CHALLENGE", - 8: "FINAL_RESPONSE", + 2: "PREPARE", + 3: "PREPARED", + 4: "COMMIT", + 5: "COMMITTED", } var MessageType_value = map[string]int32{ - "UNKNOWN": 0, - "ANNOUNCE": 1, - "COMMIT": 2, - "CHALLENGE": 3, - "RESPONSE": 4, - "COLLECTIVE_SIG": 5, - "FINAL_COMMIT": 6, - "FINAL_CHALLENGE": 7, - "FINAL_RESPONSE": 8, + "UNKNOWN": 0, + "ANNOUNCE": 1, + "PREPARE": 2, + "PREPARED": 3, + "COMMIT": 4, + "COMMITTED": 5, } func (x MessageType) String() string { @@ -153,24 +144,22 @@ func init() { func init() { proto.RegisterFile("consensus.proto", fileDescriptor_56f0f2c53b3de771) } var fileDescriptor_56f0f2c53b3de771 = []byte{ - // 302 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x91, 0xdf, 0x4e, 0xc2, 0x30, - 0x14, 0xc6, 0x2d, 0x7f, 0x36, 0x76, 0x18, 0xd0, 0x1c, 0x13, 0xd3, 0x44, 0x4d, 0xd0, 0x2b, 0xc2, - 0x05, 0x17, 0xfa, 0x04, 0x64, 0xa9, 0xd0, 0x38, 0x3a, 0x33, 0x40, 0x2f, 0x97, 0xc2, 0x1a, 0x20, - 0x92, 0x6d, 0xa1, 0x70, 0xc1, 0xdb, 0xf8, 0x40, 0x3e, 0x94, 0x61, 0x60, 0xf5, 0xb2, 0xbf, 0xdf, - 0xf7, 0x7d, 0x69, 0x72, 0xa0, 0xb3, 0xcc, 0x33, 0xa3, 0x33, 0x73, 0x30, 0x83, 0x62, 0x97, 0xef, - 0x73, 0xf4, 0x2c, 0x78, 0xfc, 0x26, 0xe0, 0x4e, 0xb4, 0x31, 0x6a, 0xa5, 0xb1, 0x0f, 0xb5, 0xfd, - 0xb1, 0xd0, 0x8c, 0x74, 0x49, 0xaf, 0xfd, 0x74, 0x33, 0xf8, 0xab, 0x5d, 0x12, 0xb3, 0x63, 0xa1, - 0xe3, 0x32, 0x83, 0x0f, 0xe0, 0x5b, 0x9d, 0x6c, 0x52, 0x56, 0xe9, 0x92, 0x5e, 0x2b, 0x6e, 0x5a, - 0x26, 0x52, 0xbc, 0x05, 0xcf, 0xe8, 0x2c, 0xd5, 0xbb, 0x93, 0xaf, 0x96, 0xbe, 0x71, 0x06, 0x22, - 0xc5, 0x7b, 0x80, 0xc5, 0x36, 0x5f, 0x7e, 0x26, 0x6b, 0x65, 0xd6, 0xac, 0xd6, 0x25, 0x3d, 0x3f, - 0xf6, 0x4a, 0x32, 0x56, 0x66, 0x8d, 0x0c, 0xdc, 0x42, 0x1d, 0xb7, 0xb9, 0x4a, 0x59, 0xbd, 0x74, - 0xbf, 0x4f, 0xbc, 0x03, 0xcf, 0x6c, 0x56, 0x99, 0xda, 0x1f, 0x76, 0x9a, 0x39, 0xe7, 0x9e, 0x05, - 0xfd, 0x2f, 0x02, 0xcd, 0x7f, 0x9f, 0xc5, 0x26, 0xb8, 0x73, 0xf9, 0x2a, 0xa3, 0x0f, 0x49, 0xaf, - 0xd0, 0x87, 0xc6, 0x50, 0xca, 0x68, 0x2e, 0x03, 0x4e, 0x09, 0x02, 0x38, 0x41, 0x34, 0x99, 0x88, - 0x19, 0xad, 0x60, 0x0b, 0xbc, 0x60, 0x3c, 0x0c, 0x43, 0x2e, 0x47, 0x9c, 0x56, 0x4f, 0xc1, 0x98, - 0x4f, 0xdf, 0x22, 0x39, 0xe5, 0xb4, 0x86, 0x08, 0xed, 0x20, 0x0a, 0x43, 0x1e, 0xcc, 0xc4, 0x3b, - 0x4f, 0xa6, 0x62, 0x44, 0xeb, 0x48, 0xc1, 0x7f, 0x11, 0x72, 0x18, 0x26, 0x97, 0x09, 0x07, 0xaf, - 0xa1, 0x73, 0x21, 0x76, 0xc8, 0x3d, 0x55, 0xcf, 0xd0, 0xce, 0x35, 0x16, 0x4e, 0x79, 0x83, 0xe7, - 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x86, 0xa2, 0x8a, 0x4f, 0x96, 0x01, 0x00, 0x00, + // 263 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x90, 0xc1, 0x6a, 0xb3, 0x40, + 0x14, 0x85, 0xff, 0x49, 0x8c, 0xc6, 0xab, 0xf9, 0x3b, 0xdc, 0x45, 0x19, 0x68, 0x0b, 0xb6, 0x2b, + 0xc9, 0x22, 0x8b, 0xf6, 0x09, 0x42, 0x22, 0x54, 0x8a, 0x63, 0x10, 0x43, 0x77, 0x0d, 0x93, 0x38, + 0xc4, 0xd0, 0xa0, 0xe2, 0x98, 0x85, 0xef, 0xd8, 0x87, 0x2a, 0x8e, 0xa9, 0xed, 0x6e, 0xce, 0xf7, + 0x9d, 0x03, 0xc3, 0x85, 0x9b, 0x43, 0x59, 0x28, 0x59, 0xa8, 0x8b, 0x5a, 0x54, 0x75, 0xd9, 0x94, + 0x68, 0x0f, 0xe0, 0xe9, 0x8b, 0x80, 0x15, 0x49, 0xa5, 0xc4, 0x51, 0xe2, 0x1c, 0x8c, 0xa6, 0xad, + 0x24, 0x23, 0x1e, 0xf1, 0xff, 0x3f, 0xdf, 0x2e, 0x7e, 0x67, 0xd7, 0x46, 0xda, 0x56, 0x32, 0xd1, + 0x1d, 0x7c, 0x04, 0x77, 0xd0, 0xbb, 0x53, 0xc6, 0x46, 0x1e, 0xf1, 0x67, 0x89, 0x33, 0xb0, 0x30, + 0xc3, 0x3b, 0xb0, 0x95, 0x2c, 0x32, 0x59, 0x77, 0x7e, 0xac, 0xfd, 0xb4, 0x07, 0x61, 0x86, 0x0f, + 0x00, 0xfb, 0x73, 0x79, 0xf8, 0xdc, 0xe5, 0x42, 0xe5, 0xcc, 0xf0, 0x88, 0xef, 0x26, 0xb6, 0x26, + 0xaf, 0x42, 0xe5, 0xc8, 0xc0, 0xaa, 0x44, 0x7b, 0x2e, 0x45, 0xc6, 0x26, 0xda, 0xfd, 0x44, 0xbc, + 0x07, 0x5b, 0x9d, 0x8e, 0x85, 0x68, 0x2e, 0xb5, 0x64, 0x66, 0xbf, 0x1b, 0xc0, 0xfc, 0x03, 0x9c, + 0x3f, 0x7f, 0x45, 0x07, 0xac, 0x2d, 0x7f, 0xe3, 0xf1, 0x3b, 0xa7, 0xff, 0xd0, 0x85, 0xe9, 0x92, + 0xf3, 0x78, 0xcb, 0x57, 0x01, 0x25, 0x9d, 0xda, 0x24, 0xc1, 0x66, 0x99, 0x04, 0x74, 0xd4, 0xa9, + 0x6b, 0x58, 0xd3, 0x31, 0x02, 0x98, 0xab, 0x38, 0x8a, 0xc2, 0x94, 0x1a, 0x38, 0x03, 0xbb, 0x7f, + 0xa7, 0xc1, 0x9a, 0x4e, 0xf6, 0xa6, 0x3e, 0xe0, 0xcb, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0xd5, + 0x30, 0x8a, 0xaf, 0x53, 0x01, 0x00, 0x00, } diff --git a/api/consensus/consensus.proto b/api/consensus/consensus.proto index 2d23f3673..49f7b2992 100644 --- a/api/consensus/consensus.proto +++ b/api/consensus/consensus.proto @@ -5,13 +5,10 @@ package consensus; enum MessageType { UNKNOWN = 0; ANNOUNCE = 1; - COMMIT = 2; - CHALLENGE = 3; - RESPONSE = 4; - COLLECTIVE_SIG = 5; - FINAL_COMMIT = 6; - FINAL_CHALLENGE = 7; - FINAL_RESPONSE = 8; + PREPARE = 2; + PREPARED = 3; + COMMIT = 4; + COMMITTED = 5; } message Message { diff --git a/api/proto/bcconn/bcconn_test.go b/api/proto/bcconn/bcconn_test.go index 57b23f0d7..3f3379c22 100644 --- a/api/proto/bcconn/bcconn_test.go +++ b/api/proto/bcconn/bcconn_test.go @@ -13,11 +13,8 @@ func TestSerializeDeserializeNodeInfo(t *testing.T) { var ip, port string ip = "127.0.0.1" port = "8080" - _, pk := utils.GenKey(ip, port) - pkb, err := pk.MarshalBinary() - if err != nil { - fmt.Println("problem marshalling binary from public key") - } + _, pk := utils.GenKeyBLS(ip, port) + pkb := pk.Serialize() nodeInfo := &node.Info{IP: ip, Port: port, PubKey: pkb} serializedNI := SerializeNodeInfo(nodeInfo) deserializedNI := DeserializeNodeInfo(serializedNI) @@ -32,20 +29,14 @@ func TestSerializeDeserializeRandomInfo(t *testing.T) { ip = "127.0.0.1" port = "8080" - _, pk := utils.GenKey(ip, port) - pkb, err := pk.MarshalBinary() - if err != nil { - fmt.Println("problem marshalling binary from public key") - } + _, pk := utils.GenKeyBLS(ip, port) + pkb := pk.Serialize() nodeInfo1 := &node.Info{IP: ip, Port: port, PubKey: pkb} ip = "127.0.0.1" port = "9080" - _, pk2 := utils.GenKey(ip, port) - pkb2, err := pk2.MarshalBinary() - if err != nil { - fmt.Println("problem marshalling binary from public key") - } + _, pk2 := utils.GenKeyBLS(ip, port) + pkb2 := pk2.Serialize() nodeInfo2 := &node.Info{IP: ip, Port: port, PubKey: pkb2} leaders := make([]*node.Info, 2) diff --git a/api/proto/node/pingpong.go b/api/proto/node/pingpong.go index a461f3d5d..5c5fc669a 100644 --- a/api/proto/node/pingpong.go +++ b/api/proto/node/pingpong.go @@ -16,7 +16,8 @@ import ( "fmt" "log" - "github.com/dedis/kyber" + "github.com/harmony-one/bls/ffi/go/bls" + "github.com/harmony-one/harmony/api/proto" "github.com/harmony-one/harmony/p2p" @@ -84,25 +85,19 @@ func (p PongMessageType) String() string { func NewPingMessage(peer p2p.Peer) *PingMessageType { ping := new(PingMessageType) - var err error ping.Version = ProtocolVersion ping.Node.IP = peer.IP ping.Node.Port = peer.Port ping.Node.PeerID = peer.PeerID ping.Node.ValidatorID = peer.ValidatorID - ping.Node.PubKey, err = peer.PubKey.MarshalBinary() + ping.Node.PubKey = peer.PubKey.Serialize() ping.Node.Role = ValidatorRole - if err != nil { - fmt.Printf("Error Marshal PubKey: %v", err) - return nil - } - return ping } // NewPongMessage creates a new Pong message based on a list of p2p.Peer and a list of publicKeys -func NewPongMessage(peers []p2p.Peer, pubKeys []kyber.Point) *PongMessageType { +func NewPongMessage(peers []p2p.Peer, pubKeys []*bls.PublicKey) *PongMessageType { pong := new(PongMessageType) pong.PubKeys = make([][]byte, 0) @@ -116,7 +111,7 @@ func NewPongMessage(peers []p2p.Peer, pubKeys []kyber.Point) *PongMessageType { n.Port = p.Port n.ValidatorID = p.ValidatorID n.PeerID = p.PeerID - n.PubKey, err = p.PubKey.MarshalBinary() + n.PubKey = p.PubKey.Serialize() if err != nil { fmt.Printf("Error Marshal PubKey: %v", err) continue @@ -125,11 +120,7 @@ func NewPongMessage(peers []p2p.Peer, pubKeys []kyber.Point) *PongMessageType { } for _, p := range pubKeys { - key, err := p.MarshalBinary() - if err != nil { - fmt.Printf("Error Marshal PublicKeys: %v", err) - continue - } + key := p.Serialize() pong.PubKeys = append(pong.PubKeys, key) } diff --git a/api/proto/node/pingpong_test.go b/api/proto/node/pingpong_test.go index 14064f623..943fc2461 100644 --- a/api/proto/node/pingpong_test.go +++ b/api/proto/node/pingpong_test.go @@ -6,27 +6,25 @@ import ( "strings" "testing" - "github.com/dedis/kyber" + "github.com/harmony-one/bls/ffi/go/bls" + "github.com/harmony-one/harmony/api/proto" - "github.com/harmony-one/harmony/crypto" "github.com/harmony-one/harmony/crypto/pki" "github.com/harmony-one/harmony/p2p" ) var ( - priKey1 = crypto.Ed25519Curve.Scalar().SetInt64(int64(333)) - pubKey1 = pki.GetPublicKeyFromScalar(priKey1) + pubKey1 = pki.GetBLSPrivateKeyFromInt(333).GetPublicKey() p1 = p2p.Peer{ IP: "127.0.0.1", Port: "9999", ValidatorID: -1, PubKey: pubKey1, } - e1 = "ping:Validator/1=>127.0.0.1:9999:-1/[90 217 28 68 64 211 160 232 61 244 159 244 160 36 61 161 237 242 236 45 147 118 237 88 234 122 198 188 157 116 90 228]" - e3 = "ping:Client/1=>127.0.0.1:9999:-1/[90 217 28 68 64 211 160 232 61 244 159 244 160 36 61 161 237 242 236 45 147 118 237 88 234 122 198 188 157 116 90 228]" + e1 = "ping:Validator/1=>127.0.0.1:9999:-1/[120 1 130 197 30 202 78 236 84 249 5 230 132 208 242 242 246 63 100 123 96 11 211 228 4 56 64 94 57 133 3 226 254 222 231 160 178 81 252 205 40 28 45 2 90 74 207 15 68 86 138 68 143 176 221 161 108 105 133 6 64 121 92 25 134 255 9 209 156 209 119 187 13 160 23 147 240 24 196 152 100 20 163 51 118 45 100 26 179 227 184 166 147 113 50 139]" + e3 = "ping:Client/1=>127.0.0.1:9999:-1/[120 1 130 197 30 202 78 236 84 249 5 230 132 208 242 242 246 63 100 123 96 11 211 228 4 56 64 94 57 133 3 226 254 222 231 160 178 81 252 205 40 28 45 2 90 74 207 15 68 86 138 68 143 176 221 161 108 105 133 6 64 121 92 25 134 255 9 209 156 209 119 187 13 160 23 147 240 24 196 152 100 20 163 51 118 45 100 26 179 227 184 166 147 113 50 139]" - priKey2 = crypto.Ed25519Curve.Scalar().SetInt64(int64(999)) - pubKey2 = pki.GetPublicKeyFromScalar(priKey2) + pubKey2 = pki.GetBLSPrivateKeyFromInt(999).GetPublicKey() p2 = []p2p.Peer{ { @@ -46,7 +44,7 @@ var ( } e2 = "pong:1=>length:2" - pubKeys = []kyber.Point{pubKey1, pubKey2} + pubKeys = []*bls.PublicKey{pubKey1, pubKey2} buf1 []byte buf2 []byte diff --git a/consensus/consensus.go b/consensus/consensus.go index 7870ce4e3..81193ea51 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -2,20 +2,22 @@ package consensus // consensus import ( + "crypto/sha256" + "encoding/binary" + "encoding/hex" "fmt" "reflect" "strconv" "sync" - "github.com/dedis/kyber" - "github.com/dedis/kyber/sign/schnorr" + "github.com/harmony-one/bls/ffi/go/bls" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/harmony-one/harmony/core/state" "github.com/harmony-one/harmony/core/types" - "github.com/harmony-one/harmony/crypto" - "github.com/harmony-one/harmony/crypto/pki" + bls_cosi "github.com/harmony-one/harmony/crypto/bls" "github.com/harmony-one/harmony/internal/utils" "github.com/harmony-one/harmony/p2p" "github.com/harmony-one/harmony/p2p/host" @@ -30,20 +32,12 @@ type Consensus struct { state State // Commits collected from validators. - commitments *map[uint32]kyber.Point - finalCommitments *map[uint32]kyber.Point - aggregatedCommitment kyber.Point - aggregatedFinalCommitment kyber.Point - bitmap *crypto.Mask - finalBitmap *crypto.Mask - - // Challenges for the validators - challenge [32]byte - finalChallenge [32]byte - - // Responses collected from validators - responses *map[uint32]kyber.Scalar - finalResponses *map[uint32]kyber.Scalar + prepareSigs *map[uint32]*bls.Sign + commitSigs *map[uint32]*bls.Sign + aggregatedPrepareSig *bls.Sign + aggregatedCommitSig *bls.Sign + prepareBitmap *bls_cosi.Mask + commitBitmap *bls_cosi.Mask // map of nodeID to validator Peer object // FIXME: should use PubKey of p2p.Peer as the hashkey @@ -57,12 +51,12 @@ type Consensus struct { leader p2p.Peer // Public keys of the committee including leader and validators - PublicKeys []kyber.Point + PublicKeys []*bls.PublicKey pubKeyLock sync.Mutex // private/public keys of current node - priKey kyber.Scalar - pubKey kyber.Point + priKey *bls.SecretKey + pubKey *bls.PublicKey // Whether I am leader. False means I am validator IsLeader bool @@ -85,8 +79,6 @@ type Consensus struct { // Validator specific fields // Blocks received but not done with consensus yet blocksReceived map[uint32]*BlockConsensusStatus - // Commitment secret - secret map[uint32]kyber.Scalar // Signal channel for starting a new consensus process ReadySignal chan struct{} @@ -136,43 +128,43 @@ func New(host p2p.Host, ShardID string, peers []p2p.Peer, leader p2p.Peer) *Cons consensus.IsLeader = false } - consensus.commitments = &map[uint32]kyber.Point{} - consensus.finalCommitments = &map[uint32]kyber.Point{} - consensus.responses = &map[uint32]kyber.Scalar{} - consensus.finalResponses = &map[uint32]kyber.Scalar{} - consensus.leader = leader for _, peer := range peers { consensus.validators.Store(utils.GetUniqueIDFromPeer(peer), peer) } + consensus.prepareSigs = &map[uint32]*bls.Sign{} + consensus.commitSigs = &map[uint32]*bls.Sign{} + // Initialize cosign bitmap - allPublicKeys := make([]kyber.Point, 0) + allPublicKeys := make([]*bls.PublicKey, 0) for _, validatorPeer := range peers { allPublicKeys = append(allPublicKeys, validatorPeer.PubKey) } allPublicKeys = append(allPublicKeys, leader.PubKey) - mask, err := crypto.NewMask(crypto.Ed25519Curve, allPublicKeys, consensus.leader.PubKey) - if err != nil { - panic("Failed to create mask") - } - finalMask, err := crypto.NewMask(crypto.Ed25519Curve, allPublicKeys, consensus.leader.PubKey) - if err != nil { - panic("Failed to create final mask") - } + consensus.PublicKeys = allPublicKeys - consensus.bitmap = mask - consensus.finalBitmap = finalMask - consensus.secret = map[uint32]kyber.Scalar{} + prepareBitmap, _ := bls_cosi.NewMask(consensus.PublicKeys, consensus.leader.PubKey) + commitBitmap, _ := bls_cosi.NewMask(consensus.PublicKeys, consensus.leader.PubKey) + consensus.prepareBitmap = prepareBitmap + consensus.commitBitmap = commitBitmap + + consensus.aggregatedPrepareSig = nil + consensus.aggregatedCommitSig = nil // For now use socket address as ID // TODO: populate Id derived from address consensus.nodeID = utils.GetUniqueIDFromPeer(selfPeer) // Set private key for myself so that I can sign messages. - consensus.priKey = crypto.Ed25519Curve.Scalar().SetInt64(int64(consensus.nodeID)) - consensus.pubKey = pki.GetPublicKeyFromScalar(consensus.priKey) + nodeIDBytes := make([]byte, 32) + binary.LittleEndian.PutUint32(nodeIDBytes, consensus.nodeID) + privateKey := bls.SecretKey{} + err := privateKey.SetLittleEndian(nodeIDBytes) + consensus.priKey = &privateKey + consensus.pubKey = privateKey.GetPublicKey() + consensus.consensusID = 0 // or view Id in the original pbft paper myShardID, err := strconv.Atoi(ShardID) @@ -207,13 +199,11 @@ func (consensus *Consensus) Author(header *types.Header) (common.Address, error) return common.Address{}, nil } -// TODO: switch to BLS-based signature +// Sign on the hash of the message func (consensus *Consensus) signMessage(message []byte) []byte { - signature, err := schnorr.Sign(crypto.Ed25519Curve, consensus.priKey, message) - if err != nil { - panic("Failed to sign message with Schnorr signature.") - } - return signature + hash := sha256.Sum256(message) + signature := consensus.priKey.SignHash(hash[:]) + return signature.Serialize() } // GetValidatorPeers returns list of validator peers. @@ -231,24 +221,37 @@ func (consensus *Consensus) GetValidatorPeers() []p2p.Peer { return validatorPeers } +// GetPrepareSigsArray returns the signatures for prepare as a array +func (consensus *Consensus) GetPrepareSigsArray() []*bls.Sign { + sigs := []*bls.Sign{} + for _, sig := range *consensus.prepareSigs { + sigs = append(sigs, sig) + } + return sigs +} + +// GetCommitSigsArray returns the signatures for commit as a array +func (consensus *Consensus) GetCommitSigsArray() []*bls.Sign { + sigs := []*bls.Sign{} + for _, sig := range *consensus.commitSigs { + sigs = append(sigs, sig) + } + return sigs +} + // ResetState resets the state of the consensus func (consensus *Consensus) ResetState() { consensus.state = Finished - consensus.commitments = &map[uint32]kyber.Point{} - consensus.finalCommitments = &map[uint32]kyber.Point{} - consensus.responses = &map[uint32]kyber.Scalar{} - consensus.finalResponses = &map[uint32]kyber.Scalar{} - - mask, _ := crypto.NewMask(crypto.Ed25519Curve, consensus.PublicKeys, consensus.leader.PubKey) - finalMask, _ := crypto.NewMask(crypto.Ed25519Curve, consensus.PublicKeys, consensus.leader.PubKey) - consensus.bitmap = mask - consensus.finalBitmap = finalMask - consensus.bitmap.SetMask([]byte{}) - consensus.finalBitmap.SetMask([]byte{}) - - consensus.aggregatedCommitment = nil - consensus.aggregatedFinalCommitment = nil - consensus.secret = map[uint32]kyber.Scalar{} + consensus.prepareSigs = &map[uint32]*bls.Sign{} + consensus.commitSigs = &map[uint32]*bls.Sign{} + + prepareBitmap, _ := bls_cosi.NewMask(consensus.PublicKeys, consensus.leader.PubKey) + commitBitmap, _ := bls_cosi.NewMask(consensus.PublicKeys, consensus.leader.PubKey) + consensus.prepareBitmap = prepareBitmap + consensus.commitBitmap = commitBitmap + + consensus.aggregatedPrepareSig = nil + consensus.aggregatedCommitSig = nil // Clear the OfflinePeersList again consensus.OfflinePeerList = make([]p2p.Peer, 0) @@ -262,8 +265,8 @@ func (consensus *Consensus) String() string { } else { duty = "VLD" // validator } - return fmt.Sprintf("[duty:%s, priKey:%s, ShardID:%v, nodeID:%v, state:%s]", - duty, consensus.priKey.String(), consensus.ShardID, consensus.nodeID, consensus.state) + return fmt.Sprintf("[duty:%s, pubKey:%s, ShardID:%v, nodeID:%v, state:%s]", + duty, hex.EncodeToString(consensus.pubKey.Serialize()), consensus.ShardID, consensus.nodeID, consensus.state) } // AddPeers adds new peers into the validator map of the consensus @@ -343,7 +346,7 @@ func (consensus *Consensus) RemovePeers(peers []p2p.Peer) int { // DebugPrintPublicKeys print all the PublicKeys in string format in Consensus func (consensus *Consensus) DebugPrintPublicKeys() { for _, k := range consensus.PublicKeys { - str := fmt.Sprintf("%s", k) + str := fmt.Sprintf("%s", hex.EncodeToString(k.Serialize())) utils.GetLogInstance().Debug("pk:", "string", str) } @@ -355,7 +358,7 @@ func (consensus *Consensus) DebugPrintValidators() { count := 0 consensus.validators.Range(func(k, v interface{}) bool { if p, ok := v.(p2p.Peer); ok { - str2 := fmt.Sprintf("%s", p.PubKey) + str2 := fmt.Sprintf("%s", p.PubKey.Serialize()) utils.GetLogInstance().Debug("validator:", "IP", p.IP, "Port", p.Port, "VID", p.ValidatorID, "Key", str2) count++ return true @@ -366,7 +369,7 @@ func (consensus *Consensus) DebugPrintValidators() { } // UpdatePublicKeys updates the PublicKeys variable, protected by a mutex -func (consensus *Consensus) UpdatePublicKeys(pubKeys []kyber.Point) int { +func (consensus *Consensus) UpdatePublicKeys(pubKeys []*bls.PublicKey) int { consensus.pubKeyLock.Lock() // consensus.PublicKeys = make([]kyber.Point, len(pubKeys)) consensus.PublicKeys = append(pubKeys[:0:0], pubKeys...) diff --git a/consensus/consensus_leader.go b/consensus/consensus_leader.go index 11907da59..70e6775eb 100644 --- a/consensus/consensus_leader.go +++ b/consensus/consensus_leader.go @@ -3,19 +3,15 @@ package consensus import ( "bytes" "encoding/hex" - "errors" "strconv" "time" - "github.com/dedis/kyber" - "github.com/dedis/kyber/sign/schnorr" "github.com/ethereum/go-ethereum/rlp" protobuf "github.com/golang/protobuf/proto" - + "github.com/harmony-one/bls/ffi/go/bls" consensus_proto "github.com/harmony-one/harmony/api/consensus" "github.com/harmony-one/harmony/api/services/explorer" "github.com/harmony-one/harmony/core/types" - "github.com/harmony-one/harmony/crypto" "github.com/harmony-one/harmony/internal/profiler" "github.com/harmony-one/harmony/internal/utils" "github.com/harmony-one/harmony/p2p" @@ -68,14 +64,10 @@ func (consensus *Consensus) ProcessMessageLeader(payload []byte) { } switch message.Type { + case consensus_proto.MessageType_PREPARE: + consensus.processPrepareMessage(message) case consensus_proto.MessageType_COMMIT: - consensus.processCommitMessage(message, ChallengeDone) - case consensus_proto.MessageType_RESPONSE: - consensus.processResponseMessage(message, CollectiveSigDone) - case consensus_proto.MessageType_FINAL_COMMIT: - consensus.processCommitMessage(message, FinalChallengeDone) - case consensus_proto.MessageType_FINAL_RESPONSE: - consensus.processResponseMessage(message, Finished) + consensus.processCommitMessage(message) default: utils.GetLogInstance().Error("Unexpected message type", "msgType", message.Type, "consensus", consensus) } @@ -101,32 +93,16 @@ func (consensus *Consensus) startConsensus(newBlock *types.Block) { // Set state to AnnounceDone consensus.state = AnnounceDone - consensus.commitByLeader(true) + // TODO: sign for leader itself host.BroadcastMessageFromLeader(consensus.host, consensus.GetValidatorPeers(), msgToSend, consensus.OfflinePeers) } -// commitByLeader commits to the message by leader himself before receiving others commits -func (consensus *Consensus) commitByLeader(firstRound bool) { - // Generate leader's own commitment - secret, commitment := crypto.Commit(crypto.Ed25519Curve) - consensus.secret[consensus.consensusID] = secret - if firstRound { - consensus.mutex.Lock() - defer consensus.mutex.Unlock() - (*consensus.commitments)[consensus.nodeID] = commitment - consensus.bitmap.SetKey(consensus.pubKey, true) - } else { - (*consensus.finalCommitments)[consensus.nodeID] = commitment - consensus.finalBitmap.SetKey(consensus.pubKey, true) - } -} - -// processCommitMessage processes the commit message sent from validators -func (consensus *Consensus) processCommitMessage(message consensus_proto.Message, targetState State) { +// processPrepareMessage processes the prepare message sent from validators +func (consensus *Consensus) processPrepareMessage(message consensus_proto.Message) { consensusID := message.ConsensusId blockHash := message.BlockHash validatorID := message.SenderId - commitment := message.Payload + prepareSig := message.Payload signature := message.Signature // Verify signature @@ -144,12 +120,15 @@ func (consensus *Consensus) processCommitMessage(message consensus_proto.Message message.Signature = nil messageBytes, err := protobuf.Marshal(&message) if err != nil { - utils.GetLogInstance().Warn("Failed to marshal the announce message", "error", err) - } - if schnorr.Verify(crypto.Ed25519Curve, value.PubKey, messageBytes, signature) != nil { - utils.GetLogInstance().Warn("Received message with invalid signature", "validatorKey", consensus.leader.PubKey, "consensus", consensus) - return + utils.GetLogInstance().Warn("Failed to marshal the prepare message", "error", err) } + _ = messageBytes + _ = signature + // TODO: verify message signature + //if schnorr.Verify(crypto.Ed25519Curve, value.PubKey, messageBytes, signature) != nil { + // consensus.Log.Warn("Received message with invalid signature", "validatorKey", consensus.leader.PubKey, "consensus", consensus) + // return + //} // check consensus Id consensus.mutex.Lock() @@ -164,27 +143,28 @@ func (consensus *Consensus) processCommitMessage(message consensus_proto.Message return } - commitments := consensus.commitments // targetState == ChallengeDone - bitmap := consensus.bitmap - if targetState == FinalChallengeDone { - commitments = consensus.finalCommitments - bitmap = consensus.finalBitmap - } + prepareSigs := consensus.prepareSigs + prepareBitmap := consensus.prepareBitmap // proceed only when the message is not received before - _, ok = (*commitments)[validatorID] + _, ok = (*prepareSigs)[validatorID] shouldProcess := !ok - if len((*commitments)) >= ((len(consensus.PublicKeys)*2)/3 + 1) { + if len((*prepareSigs)) >= ((len(consensus.PublicKeys)*2)/3 + 1) { shouldProcess = false } if shouldProcess { - point := crypto.Ed25519Curve.Point() - point.UnmarshalBinary(commitment) - (*commitments)[validatorID] = point - utils.GetLogInstance().Debug("Received new commit message", "num", len(*commitments), "validatorID", validatorID, "PublicKeys", len(consensus.PublicKeys)) + var sign bls.Sign + err := sign.Deserialize(prepareSig) + if err != nil { + utils.GetLogInstance().Error("Failed to deserialize bls signature", "validatorID", validatorID) + } + // TODO: check bls signature + (*prepareSigs)[validatorID] = &sign + utils.GetLogInstance().Debug("Received new prepare signature", "numReceivedSoFar", len(*prepareSigs), "validatorID", validatorID, "PublicKeys", len(consensus.PublicKeys)) + // Set the bitmap indicate this validate signed. - bitmap.SetKey(value.PubKey, true) + prepareBitmap.SetKey(value.PubKey, true) } if !shouldProcess { @@ -192,33 +172,15 @@ func (consensus *Consensus) processCommitMessage(message consensus_proto.Message return } - if len((*commitments)) >= ((len(consensus.PublicKeys)*2)/3+1) && consensus.state < targetState { - utils.GetLogInstance().Debug("Enough commitments received with signatures", "num", len(*commitments), "state", consensus.state) + targetState := PreparedDone + if len((*prepareSigs)) >= ((len(consensus.PublicKeys)*2)/3+1) && consensus.state < targetState { + utils.GetLogInstance().Debug("Enough commitments received with signatures", "num", len(*prepareSigs), "state", consensus.state) - // Broadcast challenge - msgTypeToSend := consensus_proto.MessageType_CHALLENGE // targetState == ChallengeDone - if targetState == FinalChallengeDone { - msgTypeToSend = consensus_proto.MessageType_FINAL_CHALLENGE - } + // Construct prepared message + msgToSend, aggSig := consensus.constructPreparedMessage() + consensus.aggregatedPrepareSig = aggSig - msgToSend, challengeScalar, aggCommitment := consensus.constructChallengeMessage(msgTypeToSend) - bytes, err := challengeScalar.MarshalBinary() - if err != nil { - utils.GetLogInstance().Error("Failed to serialize challenge") - } - - if msgTypeToSend == consensus_proto.MessageType_CHALLENGE { - copy(consensus.challenge[:], bytes) - consensus.aggregatedCommitment = aggCommitment - } else if msgTypeToSend == consensus_proto.MessageType_FINAL_CHALLENGE { - copy(consensus.finalChallenge[:], bytes) - consensus.aggregatedFinalCommitment = aggCommitment - } - - // Add leader's response - consensus.responseByLeader(challengeScalar, targetState == ChallengeDone) - - // Broadcast challenge message + // Broadcast prepared message host.BroadcastMessageFromLeader(consensus.host, consensus.GetValidatorPeers(), msgToSend, consensus.OfflinePeers) // Set state to targetState (ChallengeDone or FinalChallengeDone) @@ -226,29 +188,12 @@ func (consensus *Consensus) processCommitMessage(message consensus_proto.Message } } -// Leader commit to the message itself before receiving others commits -func (consensus *Consensus) responseByLeader(challenge kyber.Scalar, firstRound bool) { - // Generate leader's own commitment - response, err := crypto.Response(crypto.Ed25519Curve, consensus.priKey, consensus.secret[consensus.consensusID], challenge) - if err == nil { - if firstRound { - (*consensus.responses)[consensus.nodeID] = response - consensus.bitmap.SetKey(consensus.pubKey, true) - } else { - (*consensus.finalResponses)[consensus.nodeID] = response - consensus.finalBitmap.SetKey(consensus.pubKey, true) - } - } else { - utils.GetLogInstance().Warn("leader failed to generate response", "err", err) - } -} - -// Processes the response message sent from validators -func (consensus *Consensus) processResponseMessage(message consensus_proto.Message, targetState State) { +// Processes the commit message sent from validators +func (consensus *Consensus) processCommitMessage(message consensus_proto.Message) { consensusID := message.ConsensusId blockHash := message.BlockHash validatorID := message.SenderId - response := message.Payload + commitSig := message.Payload signature := message.Signature shouldProcess := true @@ -280,142 +225,95 @@ func (consensus *Consensus) processResponseMessage(message consensus_proto.Messa message.Signature = nil messageBytes, err := protobuf.Marshal(&message) if err != nil { - utils.GetLogInstance().Warn("Failed to marshal the announce message", "error", err) - } - if schnorr.Verify(crypto.Ed25519Curve, value.PubKey, messageBytes, signature) != nil { - utils.GetLogInstance().Warn("Received message with invalid signature", "validatorKey", consensus.leader.PubKey, "consensus", consensus) - return + utils.GetLogInstance().Warn("Failed to marshal the commit message", "error", err) } + _ = messageBytes + _ = signature + // TODO: verify message signature + //if schnorr.Verify(crypto.Ed25519Curve, value.PubKey, messageBytes, signature) != nil { + // consensus.Log.Warn("Received message with invalid signature", "validatorKey", consensus.leader.PubKey, "consensus", consensus) + // return + //} - commitments := consensus.commitments // targetState == CollectiveSigDone - responses := consensus.responses - bitmap := consensus.bitmap - if targetState == Finished { - commitments = consensus.finalCommitments - responses = consensus.finalResponses - bitmap = consensus.finalBitmap - } + commitSigs := consensus.commitSigs + commitBitmap := consensus.commitBitmap // proceed only when the message is not received before - _, ok = (*responses)[validatorID] + _, ok = (*commitSigs)[validatorID] shouldProcess = shouldProcess && !ok - if len((*responses)) >= ((len(consensus.PublicKeys)*2)/3 + 1) { + if len((*commitSigs)) >= ((len(consensus.PublicKeys)*2)/3 + 1) { shouldProcess = false } if shouldProcess { - // verify the response matches the received commit - responseScalar := crypto.Ed25519Curve.Scalar() - responseScalar.UnmarshalBinary(response) - err := consensus.verifyResponse(commitments, responseScalar, validatorID) + var sign bls.Sign + err := sign.Deserialize(commitSig) if err != nil { - utils.GetLogInstance().Warn("leader failed to verify the response", "error", err, "VID", strconv.Itoa(int(validatorID))) - shouldProcess = false - } else { - (*responses)[validatorID] = responseScalar - utils.GetLogInstance().Debug("Received new response message", "num", len(*responses), "validatorID", strconv.Itoa(int(validatorID))) - // Set the bitmap indicate this validate signed. - bitmap.SetKey(value.PubKey, true) + utils.GetLogInstance().Debug("Failed to deserialize bls signature", "validatorID", validatorID) } + // TODO: check bls signature + (*commitSigs)[validatorID] = &sign + utils.GetLogInstance().Debug("Received new commit message", "numReceivedSoFar", len(*commitSigs), "validatorID", strconv.Itoa(int(validatorID))) + // Set the bitmap indicate this validate signed. + commitBitmap.SetKey(value.PubKey, true) } if !shouldProcess { - utils.GetLogInstance().Debug("Received new response message", "validatorID", strconv.Itoa(int(validatorID))) + utils.GetLogInstance().Debug("Received additional new commit message", "validatorID", strconv.Itoa(int(validatorID))) return } threshold := 2 - if targetState == Finished { - threshold = 1 - } - if len(*responses) >= ((len(consensus.PublicKeys)*threshold)/3+1) && consensus.state != targetState { - if len(*responses) >= ((len(consensus.PublicKeys)*threshold)/3+1) && consensus.state != targetState { - utils.GetLogInstance().Debug("Enough responses received with signatures", "num", len(*responses), "state", consensus.state) - // Aggregate responses - responseScalars := []kyber.Scalar{} - for _, val := range *responses { - responseScalars = append(responseScalars, val) - } - - aggregatedResponse, err := crypto.AggregateResponses(crypto.Ed25519Curve, responseScalars) - if err != nil { - utils.GetLogInstance().Error("Failed to aggregate responses") - return - } - aggregatedCommitment := consensus.aggregatedCommitment - if targetState == Finished { - aggregatedCommitment = consensus.aggregatedFinalCommitment - } - - collectiveSigAndBitmap, err := crypto.Sign(crypto.Ed25519Curve, aggregatedCommitment, aggregatedResponse, bitmap) - - if err != nil { - utils.GetLogInstance().Error("Failed to create collective signature") - return - } - utils.GetLogInstance().Info("CollectiveSig and Bitmap created.", "size", len(collectiveSigAndBitmap)) - - collectiveSig := [64]byte{} - copy(collectiveSig[:], collectiveSigAndBitmap[:64]) - bitmap := collectiveSigAndBitmap[64:] - - // Set state to CollectiveSigDone or Finished - consensus.state = targetState - - if consensus.state != Finished { - // Start the second round of Cosi - msgToSend := consensus.constructCollectiveSigMessage(collectiveSig, bitmap) - - host.BroadcastMessageFromLeader(consensus.host, consensus.GetValidatorPeers(), msgToSend, consensus.OfflinePeers) - consensus.commitByLeader(false) - } else { - var blockObj types.Block - err = rlp.DecodeBytes(consensus.block, &blockObj) - if err != nil { - utils.GetLogInstance().Debug("failed to construct the new block after consensus") - } - - // Sign the block - copy(blockObj.Header().Signature[:], collectiveSig[:]) - copy(blockObj.Header().Bitmap[:], bitmap) - consensus.OnConsensusDone(&blockObj) - - select { - case consensus.VerifiedNewBlock <- &blockObj: - default: - utils.GetLogInstance().Info("[SYNC] consensus verified block send to chan failed", "blockHash", blockObj.Hash()) - } - - consensus.reportMetrics(blockObj) - - // Dump new block into level db. - explorer.GetStorageInstance(consensus.leader.IP, consensus.leader.Port, true).Dump(&blockObj, consensus.consensusID) - - // Reset state to Finished, and clear other data. - consensus.ResetState() - consensus.consensusID++ - - utils.GetLogInstance().Debug("HOORAY!!! CONSENSUS REACHED!!!", "consensusID", consensus.consensusID, "numOfSignatures", len(*responses)) - - // TODO: remove this temporary delay - time.Sleep(500 * time.Millisecond) - // Send signal to Node so the new block can be added and new round of consensus can be triggered - consensus.ReadySignal <- struct{}{} - } + targetState := CommitDone + if len(*commitSigs) >= ((len(consensus.PublicKeys)*threshold)/3+1) && consensus.state != targetState { + utils.GetLogInstance().Info("Enough commits received!", "num", len(*commitSigs), "state", consensus.state) + + // Construct committed message + msgToSend, aggSig := consensus.constructCommittedMessage() + consensus.aggregatedPrepareSig = aggSig + + // Broadcast committed message + host.BroadcastMessageFromLeader(consensus.host, consensus.GetValidatorPeers(), msgToSend, consensus.OfflinePeers) + + // Set state to CollectiveSigDone or Finished + consensus.state = targetState + + var blockObj types.Block + err = rlp.DecodeBytes(consensus.block, &blockObj) + if err != nil { + utils.GetLogInstance().Debug("failed to construct the new block after consensus") } - } -} -func (consensus *Consensus) verifyResponse(commitments *map[uint32]kyber.Point, response kyber.Scalar, validatorID uint32) error { - if response.Equal(crypto.Ed25519Curve.Scalar()) { - return errors.New("response is zero valued") - } - _, ok := (*commitments)[validatorID] - if !ok { - return errors.New("no commit is received for the validator") + // Sign the block + copy(blockObj.Header().Signature[:], aggSig.Serialize()[:]) + copy(blockObj.Header().Bitmap[:], commitBitmap.Bitmap) + consensus.OnConsensusDone(&blockObj) + consensus.state = targetState + + select { + case consensus.VerifiedNewBlock <- &blockObj: + default: + utils.GetLogInstance().Info("[SYNC] consensus verified block send to chan failed", "blockHash", blockObj.Hash()) + } + + consensus.reportMetrics(blockObj) + + // Dump new block into level db. + explorer.GetStorageInstance(consensus.leader.IP, consensus.leader.Port, true).Dump(&blockObj, consensus.consensusID) + + // Reset state to Finished, and clear other data. + consensus.ResetState() + consensus.consensusID++ + + utils.GetLogInstance().Debug("HOORAY!!! CONSENSUS REACHED!!!", "consensusID", consensus.consensusID, "numOfSignatures", len(*commitSigs)) + + // TODO: remove this temporary delay + time.Sleep(500 * time.Millisecond) + // Send signal to Node so the new block can be added and new round of consensus can be triggered + consensus.ReadySignal <- struct{}{} + consensus.state = Finished } - return nil } func (consensus *Consensus) reportMetrics(block types.Block) { @@ -443,7 +341,7 @@ func (consensus *Consensus) reportMetrics(block types.Block) { txHashes = append(txHashes, hex.EncodeToString(txHash[:])) } metrics := map[string]interface{}{ - "key": consensus.pubKey.String(), + "key": hex.EncodeToString(consensus.pubKey.Serialize()), "tps": tps, "txCount": numOfTxs, "nodeCount": len(consensus.PublicKeys) + 1, diff --git a/consensus/consensus_leader_msg.go b/consensus/consensus_leader_msg.go index b0e3d1483..5964ea7d8 100644 --- a/consensus/consensus_leader_msg.go +++ b/consensus/consensus_leader_msg.go @@ -3,12 +3,11 @@ package consensus import ( "bytes" - "github.com/dedis/kyber" - "github.com/ethereum/go-ethereum/log" protobuf "github.com/golang/protobuf/proto" + "github.com/harmony-one/bls/ffi/go/bls" consensus_proto "github.com/harmony-one/harmony/api/consensus" "github.com/harmony-one/harmony/api/proto" - "github.com/harmony-one/harmony/crypto" + bls_cosi "github.com/harmony-one/harmony/crypto/bls" "github.com/harmony-one/harmony/internal/utils" ) @@ -41,14 +40,14 @@ func (consensus *Consensus) constructAnnounceMessage() []byte { if err != nil { utils.GetLogInstance().Debug("Failed to marshal Announce message", "error", err) } - utils.GetLogInstance().Info("New Announce", "NodeID", consensus.nodeID, "bitmap", consensus.bitmap) + utils.GetLogInstance().Info("New Announce", "NodeID", consensus.nodeID) return proto.ConstructConsensusMessage(marshaledMessage) } -// Construct the challenge message, returning challenge message in bytes, challenge scalar and aggregated commmitment point. -func (consensus *Consensus) constructChallengeMessage(msgType consensus_proto.MessageType) ([]byte, kyber.Scalar, kyber.Point) { +// Construct the prepared message, returning prepared message in bytes. +func (consensus *Consensus) constructPreparedMessage() ([]byte, *bls.Sign) { message := consensus_proto.Message{} - message.Type = msgType + message.Type = consensus_proto.MessageType_PREPARED // 4 byte consensus id message.ConsensusId = consensus.consensusID @@ -62,56 +61,37 @@ func (consensus *Consensus) constructChallengeMessage(msgType consensus_proto.Me //// Payload buffer := bytes.NewBuffer([]byte{}) - commitmentsMap := consensus.commitments // msgType == Challenge - bitmap := consensus.bitmap - if msgType == consensus_proto.MessageType_FINAL_CHALLENGE { - commitmentsMap = consensus.finalCommitments - bitmap = consensus.finalBitmap - } - - // 33 byte aggregated commit - commitments := make([]kyber.Point, 0) - for _, val := range *commitmentsMap { - commitments = append(commitments, val) - } - aggCommitment, aggCommitmentBytes := getAggregatedCommit(commitments) - buffer.Write(aggCommitmentBytes) + // 48 bytes aggregated signature + aggSig := bls_cosi.AggregateSig(consensus.GetPrepareSigsArray()) + buffer.Write(aggSig.Serialize()) - // 33 byte aggregated key - buffer.Write(getAggregatedKey(bitmap)) - - // 32 byte challenge - challengeScalar := getChallenge(aggCommitment, bitmap.AggregatePublic, message.BlockHash) - bytes, err := challengeScalar.MarshalBinary() - if err != nil { - log.Error("Failed to serialize challenge") - } - buffer.Write(bytes) + // Bitmap + buffer.Write(consensus.prepareBitmap.Bitmap) message.Payload = buffer.Bytes() //// END Payload + // TODO: use custom serialization method rather than protobuf marshaledMessage, err := protobuf.Marshal(&message) if err != nil { - utils.GetLogInstance().Debug("Failed to marshal Challenge message", "error", err) + utils.GetLogInstance().Debug("Failed to marshal Prepared message", "error", err) } - // 64 byte of signature on previous data + // 48 byte of signature on previous data signature := consensus.signMessage(marshaledMessage) message.Signature = signature marshaledMessage, err = protobuf.Marshal(&message) if err != nil { - utils.GetLogInstance().Debug("Failed to marshal Challenge message", "error", err) + utils.GetLogInstance().Debug("Failed to marshal Prepared message", "error", err) } - utils.GetLogInstance().Info("New Challenge", "NodeID", consensus.nodeID, "bitmap", consensus.bitmap) - return proto.ConstructConsensusMessage(marshaledMessage), challengeScalar, aggCommitment + utils.GetLogInstance().Info("New Prepared Message", "NodeID", consensus.nodeID, "bitmap", consensus.prepareBitmap) + return proto.ConstructConsensusMessage(marshaledMessage), aggSig } -// Construct the collective signature message -func (consensus *Consensus) constructCollectiveSigMessage(collectiveSig [64]byte, bitmap []byte) []byte { +// Construct the committed message, returning committed message in bytes. +func (consensus *Consensus) constructCommittedMessage() ([]byte, *bls.Sign) { message := consensus_proto.Message{} - message.Type = consensus_proto.MessageType_COLLECTIVE_SIG - + message.Type = consensus_proto.MessageType_COMMITTED // 4 byte consensus id message.ConsensusId = consensus.consensusID @@ -124,52 +104,29 @@ func (consensus *Consensus) constructCollectiveSigMessage(collectiveSig [64]byte //// Payload buffer := bytes.NewBuffer([]byte{}) - // 64 byte collective signature - buffer.Write(collectiveSig[:]) + // 48 bytes aggregated signature + aggSig := bls_cosi.AggregateSig(consensus.GetCommitSigsArray()) + buffer.Write(aggSig.Serialize()) - // N byte bitmap - buffer.Write(bitmap) + // Bitmap + buffer.Write(consensus.commitBitmap.Bitmap) message.Payload = buffer.Bytes() //// END Payload + // TODO: use custom serialization method rather than protobuf marshaledMessage, err := protobuf.Marshal(&message) if err != nil { - utils.GetLogInstance().Debug("Failed to marshal Challenge message", "error", err) + utils.GetLogInstance().Debug("Failed to marshal Committed message", "error", err) } - // 64 byte of signature on previous data + // 48 byte of signature on previous data signature := consensus.signMessage(marshaledMessage) message.Signature = signature marshaledMessage, err = protobuf.Marshal(&message) if err != nil { - utils.GetLogInstance().Debug("Failed to marshal Challenge message", "error", err) - } - utils.GetLogInstance().Info("New CollectiveSig", "NodeID", consensus.nodeID, "bitmap", consensus.bitmap) - return proto.ConstructConsensusMessage(marshaledMessage) -} - -func getAggregatedCommit(commitments []kyber.Point) (commitment kyber.Point, bytes []byte) { - aggCommitment := crypto.AggregateCommitmentsOnly(crypto.Ed25519Curve, commitments) - bytes, err := aggCommitment.MarshalBinary() - if err != nil { - panic("Failed to deserialize the aggregated commitment") - } - return aggCommitment, append(bytes[:], byte(0)) -} - -func getAggregatedKey(bitmap *crypto.Mask) []byte { - bytes, err := bitmap.AggregatePublic.MarshalBinary() - if err != nil { - panic("Failed to deserialize the aggregated key") - } - return append(bytes[:], byte(0)) -} - -func getChallenge(aggCommitment, aggKey kyber.Point, message []byte) kyber.Scalar { - challenge, err := crypto.Challenge(crypto.Ed25519Curve, aggCommitment, aggKey, message) - if err != nil { - log.Error("Failed to generate challenge") + utils.GetLogInstance().Debug("Failed to marshal Committed message", "error", err) } - return challenge + utils.GetLogInstance().Info("New Prepared Message", "NodeID", consensus.nodeID, "bitmap", consensus.commitBitmap) + return proto.ConstructConsensusMessage(marshaledMessage), aggSig } diff --git a/consensus/consensus_leader_msg_test.go b/consensus/consensus_leader_msg_test.go index cc33ad357..a5a732dd8 100644 --- a/consensus/consensus_leader_msg_test.go +++ b/consensus/consensus_leader_msg_test.go @@ -3,12 +3,10 @@ package consensus import ( "testing" + "github.com/harmony-one/harmony/internal/utils" + "github.com/harmony-one/harmony/p2p/p2pimpl" - consensus_proto "github.com/harmony-one/harmony/api/consensus" - "github.com/harmony-one/harmony/crypto" - "github.com/harmony-one/harmony/crypto/pki" - "github.com/harmony-one/harmony/internal/utils" "github.com/harmony-one/harmony/p2p" ) @@ -24,22 +22,17 @@ func TestConstructAnnounceMessage(test *testing.T) { consensus.blockHash = [32]byte{} msg := consensus.constructAnnounceMessage() - if len(msg) != 109 { + if len(msg) != 93 { test.Errorf("Annouce message is not constructed in the correct size: %d", len(msg)) } } -func TestConstructChallengeMessage(test *testing.T) { - leaderPriKey := crypto.Ed25519Curve.Scalar() - priKeyInBytes := crypto.HashSha256("12") - leaderPriKey.UnmarshalBinary(priKeyInBytes[:]) - leaderPubKey := pki.GetPublicKeyFromScalar(leaderPriKey) +func TestConstructPreparedMessage(test *testing.T) { + + leaderPriKey, leaderPubKey := utils.GenKeyBLS("127.0.0.1", "6000") leader := p2p.Peer{IP: "127.0.0.1", Port: "6000", PubKey: leaderPubKey} - validatorPriKey := crypto.Ed25519Curve.Scalar() - priKeyInBytes = crypto.HashSha256("12") - validatorPriKey.UnmarshalBinary(priKeyInBytes[:]) - validatorPubKey := pki.GetPublicKeyFromScalar(leaderPriKey) + validatorPriKey, validatorPubKey := utils.GenKeyBLS("127.0.0.1", "5555") validator := p2p.Peer{IP: "127.0.0.1", Port: "5555", PubKey: validatorPubKey} priKey, _, _ := utils.GenKeyP2P("127.0.0.1", "9902") host, err := p2pimpl.NewHost(&leader, priKey) @@ -48,14 +41,16 @@ func TestConstructChallengeMessage(test *testing.T) { } consensus := New(host, "0", []p2p.Peer{leader, validator}, leader) consensus.blockHash = [32]byte{} - (*consensus.commitments)[0] = leaderPubKey - (*consensus.commitments)[1] = validatorPubKey - consensus.bitmap.SetKey(leaderPubKey, true) - consensus.bitmap.SetKey(validatorPubKey, true) - msg, _, _ := consensus.constructChallengeMessage(consensus_proto.MessageType_CHALLENGE) + message := "test string" + (*consensus.prepareSigs)[0] = leaderPriKey.Sign(message) + (*consensus.prepareSigs)[1] = validatorPriKey.Sign(message) + consensus.prepareBitmap.SetKey(leaderPubKey, true) + consensus.prepareBitmap.SetKey(validatorPubKey, true) + + msg, _ := consensus.constructPreparedMessage() - if len(msg) != 209 { + if len(msg) != 144 { test.Errorf("Challenge message is not constructed in the correct size: %d", len(msg)) } } diff --git a/consensus/consensus_leader_test.go b/consensus/consensus_leader_test.go index 26ea49448..8e9f9e2cc 100644 --- a/consensus/consensus_leader_test.go +++ b/consensus/consensus_leader_test.go @@ -6,28 +6,31 @@ import ( "testing" "time" + "crypto/sha256" + + "github.com/ethereum/go-ethereum/rlp" "github.com/golang/mock/gomock" - "github.com/harmony-one/harmony/crypto" + "github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/internal/utils" mock_host "github.com/harmony-one/harmony/p2p/host/mock" "github.com/stretchr/testify/assert" "github.com/harmony-one/harmony/p2p/p2pimpl" - consensus_proto "github.com/harmony-one/harmony/api/consensus" "github.com/harmony-one/harmony/p2p" ) var ( - ip = "127.0.0.1" + ip = "127.0.0.1" + blockHash = sha256.Sum256([]byte("test")) ) -func TestProcessMessageLeaderCommit(test *testing.T) { +func TestProcessMessageLeaderPrepare(test *testing.T) { ctrl := gomock.NewController(test) defer ctrl.Finish() leader := p2p.Peer{IP: ip, Port: "7777"} - _, leader.PubKey = utils.GenKey(leader.IP, leader.Port) + _, leader.PubKey = utils.GenKeyBLS(leader.IP, leader.Port) validators := make([]p2p.Peer, 3) hosts := make([]p2p.Host, 3) @@ -35,7 +38,7 @@ func TestProcessMessageLeaderCommit(test *testing.T) { for i := 0; i < 3; i++ { port := fmt.Sprintf("%d", 7788+i) validators[i] = p2p.Peer{IP: ip, Port: port, ValidatorID: i + 1} - _, validators[i].PubKey = utils.GenKey(validators[i].IP, validators[i].Port) + _, validators[i].PubKey = utils.GenKeyBLS(validators[i].IP, validators[i].Port) } m := mock_host.NewMockHost(ctrl) @@ -45,7 +48,7 @@ func TestProcessMessageLeaderCommit(test *testing.T) { m.EXPECT().SendMessage(gomock.Any(), gomock.Any()).Times(3) consensusLeader := New(m, "0", validators, leader) - consensusLeader.blockHash = [32]byte{} + consensusLeader.blockHash = blockHash consensusValidators := make([]*Consensus, 3) for i := 0; i < 3; i++ { @@ -57,22 +60,22 @@ func TestProcessMessageLeaderCommit(test *testing.T) { hosts[i] = host consensusValidators[i] = New(hosts[i], "0", validators, leader) - consensusValidators[i].blockHash = [32]byte{} - _, msg := consensusValidators[i].constructCommitMessage(consensus_proto.MessageType_COMMIT) + consensusValidators[i].blockHash = blockHash + msg := consensusValidators[i].constructPrepareMessage() consensusLeader.ProcessMessageLeader(msg[1:]) } - assert.Equal(test, ChallengeDone, consensusLeader.state) + assert.Equal(test, PreparedDone, consensusLeader.state) time.Sleep(1 * time.Second) } -func TestProcessMessageLeaderResponse(test *testing.T) { +func TestProcessMessageLeaderCommit(test *testing.T) { ctrl := gomock.NewController(test) defer ctrl.Finish() leader := p2p.Peer{IP: ip, Port: "8889"} - _, leader.PubKey = utils.GenKey(leader.IP, leader.Port) + _, leader.PubKey = utils.GenKeyBLS(leader.IP, leader.Port) validators := make([]p2p.Peer, 3) hosts := make([]p2p.Host, 3) @@ -80,14 +83,14 @@ func TestProcessMessageLeaderResponse(test *testing.T) { for i := 0; i < 3; i++ { port := fmt.Sprintf("%d", 8788+i) validators[i] = p2p.Peer{IP: ip, Port: port, ValidatorID: i + 1} - _, validators[i].PubKey = utils.GenKey(validators[i].IP, validators[i].Port) + _, validators[i].PubKey = utils.GenKeyBLS(validators[i].IP, validators[i].Port) } m := mock_host.NewMockHost(ctrl) // Asserts that the first and only call to Bar() is passed 99. // Anything else will fail. m.EXPECT().GetSelfPeer().Return(leader) - m.EXPECT().SendMessage(gomock.Any(), gomock.Any()).Times(6) + m.EXPECT().SendMessage(gomock.Any(), gomock.Any()).Times(3) for i := 0; i < 3; i++ { priKey, _, _ := utils.GenKeyP2P(validators[i].IP, validators[i].Port) @@ -99,22 +102,25 @@ func TestProcessMessageLeaderResponse(test *testing.T) { } consensusLeader := New(m, "0", validators, leader) - consensusLeader.blockHash = [32]byte{} + consensusLeader.state = PreparedDone + consensusLeader.blockHash = blockHash + consensusLeader.OnConsensusDone = func(newBlock *types.Block) {} + consensusLeader.block, _ = rlp.EncodeToBytes(types.NewBlock(&types.Header{}, nil, nil)) consensusValidators := make([]*Consensus, 3) - for i := 0; i < 3; i++ { - consensusValidators[i] = New(hosts[i], "0", validators, leader) - consensusValidators[i].blockHash = [32]byte{} - _, msg := consensusValidators[i].constructCommitMessage(consensus_proto.MessageType_COMMIT) - consensusLeader.ProcessMessageLeader(msg[1:]) - } + go func() { + <-consensusLeader.ReadySignal + <-consensusLeader.ReadySignal + }() for i := 0; i < 3; i++ { - msg := consensusValidators[i].constructResponseMessage(consensus_proto.MessageType_RESPONSE, crypto.Ed25519Curve.Scalar().One()) + consensusValidators[i] = New(hosts[i], "0", validators, leader) + consensusValidators[i].blockHash = blockHash + msg := consensusValidators[i].constructCommitMessage() consensusLeader.ProcessMessageLeader(msg[1:]) } - assert.Equal(test, CollectiveSigDone, consensusLeader.state) + assert.Equal(test, Finished, consensusLeader.state) time.Sleep(1 * time.Second) } diff --git a/consensus/consensus_state.go b/consensus/consensus_state.go index 37f87fbf4..11b9de041 100644 --- a/consensus/consensus_state.go +++ b/consensus/consensus_state.go @@ -7,13 +7,10 @@ type State int const ( Finished State = iota AnnounceDone + PrepareDone + PreparedDone CommitDone - ChallengeDone - ResponseDone - CollectiveSigDone - FinalCommitDone - FinalChallengeDone - FinalResponseDone + CommittedDone ) // Returns string name for the State enum @@ -21,15 +18,12 @@ func (state State) String() string { names := [...]string{ "Finished", "AnnounceDone", + "PrepareDone", + "PreparedDone", "CommitDone", - "ChallengeDone", - "ResponseDone", - "CollectiveSigDone", - "FinalCommitDone", - "FinalChallengeDone", - "FinalResponseDone"} + "CommittedDone"} - if state < Finished || state > FinalResponseDone { + if state < Finished || state > CommittedDone { return "Unknown" } return names[state] diff --git a/consensus/consensus_test.go b/consensus/consensus_test.go index 41f7f85b0..ef48d5e2d 100644 --- a/consensus/consensus_test.go +++ b/consensus/consensus_test.go @@ -35,11 +35,11 @@ func TestNew(test *testing.T) { } func TestRemovePeers(t *testing.T) { - _, pk1 := utils.GenKey("1", "1") - _, pk2 := utils.GenKey("2", "2") - _, pk3 := utils.GenKey("3", "3") - _, pk4 := utils.GenKey("4", "4") - _, pk5 := utils.GenKey("5", "5") + _, pk1 := utils.GenKeyBLS("1", "1") + _, pk2 := utils.GenKeyBLS("2", "2") + _, pk3 := utils.GenKeyBLS("3", "3") + _, pk4 := utils.GenKeyBLS("4", "4") + _, pk5 := utils.GenKeyBLS("5", "5") p1 := p2p.Peer{IP: "127.0.0.1", Port: "19901", PubKey: pk1} p2 := p2p.Peer{IP: "127.0.0.1", Port: "19902", PubKey: pk2} diff --git a/consensus/consensus_validator.go b/consensus/consensus_validator.go index 2b6c80881..e190ad96e 100644 --- a/consensus/consensus_validator.go +++ b/consensus/consensus_validator.go @@ -3,13 +3,10 @@ package consensus import ( "bytes" - "github.com/dedis/kyber/sign/schnorr" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" protobuf "github.com/golang/protobuf/proto" consensus_proto "github.com/harmony-one/harmony/api/consensus" "github.com/harmony-one/harmony/core/types" - "github.com/harmony-one/harmony/crypto" "github.com/harmony-one/harmony/internal/attack" "github.com/harmony-one/harmony/internal/utils" ) @@ -25,12 +22,10 @@ func (consensus *Consensus) ProcessMessageValidator(payload []byte) { switch message.Type { case consensus_proto.MessageType_ANNOUNCE: consensus.processAnnounceMessage(message) - case consensus_proto.MessageType_CHALLENGE: - consensus.processChallengeMessage(message, ResponseDone) - case consensus_proto.MessageType_FINAL_CHALLENGE: - consensus.processChallengeMessage(message, FinalResponseDone) - case consensus_proto.MessageType_COLLECTIVE_SIG: - consensus.processCollectiveSigMessage(message) + case consensus_proto.MessageType_PREPARED: + consensus.processPreparedMessage(message) + case consensus_proto.MessageType_COMMITTED: + consensus.processCommittedMessage(message) default: utils.GetLogInstance().Error("Unexpected message type", "msgType", message.Type, "consensus", consensus) } @@ -62,10 +57,13 @@ func (consensus *Consensus) processAnnounceMessage(message consensus_proto.Messa if err != nil { utils.GetLogInstance().Warn("Failed to marshal the announce message", "error", err) } - if schnorr.Verify(crypto.Ed25519Curve, consensus.leader.PubKey, messageBytes, signature) != nil { - utils.GetLogInstance().Warn("Received message with invalid signature", "leaderKey", consensus.leader.PubKey, "consensus", consensus) - return - } + _ = signature + _ = messageBytes + // TODO: verify message signature + //if schnorr.Verify(crypto.Ed25519Curve, consensus.leader.PubKey, messageBytes, signature) != nil { + // consensus.Log.Warn("Received message with invalid signature", "leaderKey", consensus.leader.PubKey, "consensus", consensus) + // return + //} // check block header is valid var blockObj types.Block @@ -101,20 +99,19 @@ func (consensus *Consensus) processAnnounceMessage(message consensus_proto.Messa return } - // Commit and store the commit - secret, msgToSend := consensus.constructCommitMessage(consensus_proto.MessageType_COMMIT) - consensus.secret[consensusID] = secret + // Construct and send prepare message + msgToSend := consensus.constructPrepareMessage() consensus.SendMessage(consensus.leader, msgToSend) // utils.GetLogInstance().Warn("Sending Commit to leader", "state", targetState) // Set state to CommitDone - consensus.state = CommitDone + consensus.state = PrepareDone } -// Processes the challenge message sent from the leader -func (consensus *Consensus) processChallengeMessage(message consensus_proto.Message, targetState State) { - utils.GetLogInstance().Info("Received Challenge Message", "nodeID", consensus.nodeID) +// Processes the prepared message sent from the leader +func (consensus *Consensus) processPreparedMessage(message consensus_proto.Message) { + utils.GetLogInstance().Info("Received Prepared Message", "nodeID", consensus.nodeID) consensusID := message.ConsensusId blockHash := message.BlockHash @@ -123,19 +120,13 @@ func (consensus *Consensus) processChallengeMessage(message consensus_proto.Mess signature := message.Signature //#### Read payload data - // TODO: use BLS-based multi-sig offset := 0 - // 33 byte of aggregated commit - aggreCommit := messagePayload[offset : offset+33] - offset += 33 - - // 33 byte of aggregated key - aggreKey := messagePayload[offset : offset+33] - offset += 33 + // 48 byte of multi-sig + multiSig := messagePayload[offset : offset+48] + offset += 48 - // 32 byte of challenge - challenge := messagePayload[offset : offset+32] - offset += 32 + // bitmap + bitmap := messagePayload[offset:] // Update readyByConsensus for attack. attack.GetInstance().UpdateConsensusReady(consensusID) @@ -154,10 +145,13 @@ func (consensus *Consensus) processChallengeMessage(message consensus_proto.Mess if err != nil { utils.GetLogInstance().Warn("Failed to marshal the announce message", "error", err) } - if schnorr.Verify(crypto.Ed25519Curve, consensus.leader.PubKey, messageBytes, signature) != nil { - utils.GetLogInstance().Warn("Received message with invalid signature", "leaderKey", consensus.leader.PubKey, "consensus", consensus) - return - } + _ = signature + _ = messageBytes + // TODO: verify message signature + //if schnorr.Verify(crypto.Ed25519Curve, consensus.leader.PubKey, messageBytes, signature) != nil { + // consensus.Log.Warn("Received message with invalid signature", "leaderKey", consensus.leader.PubKey, "consensus", consensus) + // return + //} // Add attack model of IncorrectResponse. if attack.GetInstance().IncorrectResponse() { @@ -174,94 +168,22 @@ func (consensus *Consensus) processChallengeMessage(message consensus_proto.Mess return } - aggCommitment := crypto.Ed25519Curve.Point() - aggCommitment.UnmarshalBinary(aggreCommit[:32]) - aggKey := crypto.Ed25519Curve.Point() - aggKey.UnmarshalBinary(aggreKey[:32]) - - reconstructedChallenge, err := crypto.Challenge(crypto.Ed25519Curve, aggCommitment, aggKey, blockHash) - - if err != nil { - log.Error("Failed to reconstruct the challenge from commits and keys") - return - } - - // For now, simply return the private key of this node. - receivedChallenge := crypto.Ed25519Curve.Scalar() - err = receivedChallenge.UnmarshalBinary(challenge) - if err != nil { - log.Error("Failed to deserialize challenge", "err", err) - return - } - - if !reconstructedChallenge.Equal(receivedChallenge) { - log.Error("The challenge doesn't match the commitments and keys") - return - } - - response, err := crypto.Response(crypto.Ed25519Curve, consensus.priKey, consensus.secret[consensusID], receivedChallenge) - if err != nil { - log.Warn("validator failed to generate response", "err", err, "priKey", consensus.priKey, "nodeID", consensus.nodeID, "secret", consensus.secret[consensusID]) - return - } + _ = multiSig + _ = bitmap + // TODO: verify multi signature - msgTypeToSend := consensus_proto.MessageType_RESPONSE - if targetState == FinalResponseDone { - msgTypeToSend = consensus_proto.MessageType_FINAL_RESPONSE - } - msgToSend := consensus.constructResponseMessage(msgTypeToSend, response) + // Construct and send the commit message + msgToSend := consensus.constructCommitMessage() consensus.SendMessage(consensus.leader, msgToSend) - // utils.GetLogInstance().Warn("Sending Response to leader", "state", targetState) - // Set state to target state (ResponseDone, FinalResponseDone) - consensus.state = targetState - - if consensus.state == FinalResponseDone { - // TODO: the block catch up logic is a temporary workaround for full failure node catchup. Implement the full node catchup logic - // The logic is to roll up to the latest blocks one by one to try catching up with the leader. - for { - val, ok := consensus.blocksReceived[consensus.consensusID] - if ok { - delete(consensus.blocksReceived, consensus.consensusID) - - consensus.blockHash = [32]byte{} - delete(consensus.secret, consensusID) - consensus.consensusID = consensusID + 1 // roll up one by one, until the next block is not received yet. - - var blockObj types.Block - err := rlp.DecodeBytes(val.block, &blockObj) - if err != nil { - utils.GetLogInstance().Warn("Unparseable block header data", "error", err) - return - } - if err != nil { - utils.GetLogInstance().Debug("failed to construct the new block after consensus") - } - // check block data (transactions - if !consensus.BlockVerifier(&blockObj) { - utils.GetLogInstance().Debug("[WARNING] Block content is not verified successfully", "consensusID", consensus.consensusID) - return - } - utils.GetLogInstance().Info("Finished Response. Adding block to chain", "numTx", len(blockObj.Transactions())) - consensus.OnConsensusDone(&blockObj) - - select { - case consensus.VerifiedNewBlock <- &blockObj: - default: - utils.GetLogInstance().Info("[SYNC] consensus verified block send to chan failed", "blockHash", blockObj.Hash()) - continue - } - - } else { - break - } - } - } + consensus.state = CommitDone } -// Processes the collective signature message sent from the leader -func (consensus *Consensus) processCollectiveSigMessage(message consensus_proto.Message) { +// Processes the committed message sent from the leader +func (consensus *Consensus) processCommittedMessage(message consensus_proto.Message) { + utils.GetLogInstance().Warn("Received Prepared Message", "nodeID", consensus.nodeID) + consensusID := message.ConsensusId blockHash := message.BlockHash leaderID := message.SenderId @@ -269,11 +191,18 @@ func (consensus *Consensus) processCollectiveSigMessage(message consensus_proto. signature := message.Signature //#### Read payload data - collectiveSig := messagePayload[0:64] - bitmap := messagePayload[64:] - //#### END: Read payload data + offset := 0 + // 48 byte of multi-sig + multiSig := messagePayload[offset : offset+48] + offset += 48 - // Verify block data + // bitmap + bitmap := messagePayload[offset:] + + // Update readyByConsensus for attack. + attack.GetInstance().UpdateConsensusReady(consensusID) + + // Verify block data and the aggregated signatures // check leader Id myLeaderID := utils.GetUniqueIDFromPeer(consensus.leader) if uint32(leaderID) != myLeaderID { @@ -287,17 +216,13 @@ func (consensus *Consensus) processCollectiveSigMessage(message consensus_proto. if err != nil { utils.GetLogInstance().Warn("Failed to marshal the announce message", "error", err) } - if schnorr.Verify(crypto.Ed25519Curve, consensus.leader.PubKey, messageBytes, signature) != nil { - utils.GetLogInstance().Warn("Received message with invalid signature", "leaderKey", consensus.leader.PubKey, "consensus", consensus) - return - } - - // Verify collective signature - err = crypto.Verify(crypto.Ed25519Curve, consensus.PublicKeys, blockHash, append(collectiveSig, bitmap...), crypto.NewThresholdPolicy((2*len(consensus.PublicKeys)/3)+1)) - if err != nil { - utils.GetLogInstance().Warn("Failed to verify the collective sig message", "consensusID", consensusID, "err", err, "bitmap", bitmap, "NodeID", consensus.nodeID, "#PK", len(consensus.PublicKeys)) - return - } + _ = signature + _ = messageBytes + // TODO: verify message signature + //if schnorr.Verify(crypto.Ed25519Curve, consensus.leader.PubKey, messageBytes, signature) != nil { + // consensus.Log.Warn("Received message with invalid signature", "leaderKey", consensus.leader.PubKey, "consensus", consensus) + // return + //} // Add attack model of IncorrectResponse. if attack.GetInstance().IncorrectResponse() { @@ -305,6 +230,8 @@ func (consensus *Consensus) processCollectiveSigMessage(message consensus_proto. return } + consensus.mutex.Lock() + defer consensus.mutex.Unlock() // check consensus Id if consensusID != consensus.consensusID { // hack for new node state syncing @@ -319,12 +246,54 @@ func (consensus *Consensus) processCollectiveSigMessage(message consensus_proto. return } - secret, msgToSend := consensus.constructCommitMessage(consensus_proto.MessageType_FINAL_COMMIT) - // Store the commitment secret - consensus.secret[consensusID] = secret + _ = multiSig + _ = bitmap + // TODO: verify multi signature + + // Construct and send the prepare message + msgToSend := consensus.constructPrepareMessage() consensus.SendMessage(consensus.leader, msgToSend) - // Set state to CommitDone - consensus.state = FinalCommitDone + consensus.state = PrepareDone + + // TODO: the block catch up logic is a temporary workaround for full failure node catchup. Implement the full node catchup logic + // The logic is to roll up to the latest blocks one by one to try catching up with the leader. + for { + val, ok := consensus.blocksReceived[consensus.consensusID] + if ok { + delete(consensus.blocksReceived, consensus.consensusID) + + consensus.blockHash = [32]byte{} + consensus.consensusID = consensusID + 1 // roll up one by one, until the next block is not received yet. + + var blockObj types.Block + err := rlp.DecodeBytes(val.block, &blockObj) + if err != nil { + utils.GetLogInstance().Warn("Unparseable block header data", "error", err) + return + } + if err != nil { + utils.GetLogInstance().Debug("failed to construct the new block after consensus") + } + // check block data (transactions + if !consensus.BlockVerifier(&blockObj) { + utils.GetLogInstance().Debug("[WARNING] Block content is not verified successfully", "consensusID", consensus.consensusID) + return + } + utils.GetLogInstance().Info("Finished Response. Adding block to chain", "numTx", len(blockObj.Transactions())) + consensus.OnConsensusDone(&blockObj) + + select { + case consensus.VerifiedNewBlock <- &blockObj: + default: + utils.GetLogInstance().Info("[SYNC] consensus verified block send to chan failed", "blockHash", blockObj.Hash()) + continue + } + + } else { + break + } + + } } diff --git a/consensus/consensus_validator_msg.go b/consensus/consensus_validator_msg.go index 39ac21467..cf626babe 100644 --- a/consensus/consensus_validator_msg.go +++ b/consensus/consensus_validator_msg.go @@ -1,18 +1,16 @@ package consensus import ( - "github.com/dedis/kyber" protobuf "github.com/golang/protobuf/proto" consensus_proto "github.com/harmony-one/harmony/api/consensus" "github.com/harmony-one/harmony/api/proto" - "github.com/harmony-one/harmony/crypto" "github.com/harmony-one/harmony/internal/utils" ) -// Construct the commit message to send to leader (assumption the consensus data is already verified) -func (consensus *Consensus) constructCommitMessage(msgType consensus_proto.MessageType) (secret kyber.Scalar, commitMsg []byte) { +// Construct the prepare message to send to leader (assumption the consensus data is already verified) +func (consensus *Consensus) constructPrepareMessage() []byte { message := consensus_proto.Message{} - message.Type = msgType + message.Type = consensus_proto.MessageType_PREPARE // 4 byte consensus id message.ConsensusId = consensus.consensusID @@ -23,17 +21,15 @@ func (consensus *Consensus) constructCommitMessage(msgType consensus_proto.Messa // 4 byte sender id message.SenderId = uint32(consensus.nodeID) - // 32 byte of commit - secret, commitment := crypto.Commit(crypto.Ed25519Curve) - bytes, err := commitment.MarshalBinary() - if err != nil { - utils.GetLogInstance().Debug("Failed to marshal commit", "error", err) + // 48 byte of bls signature + sign := consensus.priKey.SignHash(message.BlockHash) + if sign != nil { + message.Payload = consensus.priKey.SignHash(message.BlockHash).Serialize() } - message.Payload = bytes marshaledMessage, err := protobuf.Marshal(&message) if err != nil { - utils.GetLogInstance().Debug("Failed to marshal Announce message", "error", err) + utils.GetLogInstance().Debug("Failed to marshal Prepare message", "error", err) } // 64 byte of signature on previous data signature := consensus.signMessage(marshaledMessage) @@ -41,16 +37,16 @@ func (consensus *Consensus) constructCommitMessage(msgType consensus_proto.Messa marshaledMessage, err = protobuf.Marshal(&message) if err != nil { - utils.GetLogInstance().Debug("Failed to marshal Announce message", "error", err) + utils.GetLogInstance().Debug("Failed to marshal Prepare message", "error", err) } - return secret, proto.ConstructConsensusMessage(marshaledMessage) + return proto.ConstructConsensusMessage(marshaledMessage) } -// Construct the response message to send to leader (assumption the consensus data is already verified) -func (consensus *Consensus) constructResponseMessage(msgType consensus_proto.MessageType, response kyber.Scalar) []byte { +// Construct the commit message to send to leader (assumption the consensus data is already verified) +func (consensus *Consensus) constructCommitMessage() []byte { message := consensus_proto.Message{} - message.Type = msgType + message.Type = consensus_proto.MessageType_COMMIT // 4 byte consensus id message.ConsensusId = consensus.consensusID @@ -61,15 +57,16 @@ func (consensus *Consensus) constructResponseMessage(msgType consensus_proto.Mes // 4 byte sender id message.SenderId = uint32(consensus.nodeID) - bytes, err := response.MarshalBinary() - if err != nil { - utils.GetLogInstance().Debug("Failed to marshal response", "error", err) + // 48 byte of bls signature + // TODO: sign on the prepared message hash, rather than the block hash + sign := consensus.priKey.SignHash(message.BlockHash) + if sign != nil { + message.Payload = consensus.priKey.SignHash(message.BlockHash).Serialize() } - message.Payload = bytes marshaledMessage, err := protobuf.Marshal(&message) if err != nil { - utils.GetLogInstance().Debug("Failed to marshal Announce message", "error", err) + utils.GetLogInstance().Debug("Failed to marshal Commit message", "error", err) } // 64 byte of signature on previous data signature := consensus.signMessage(marshaledMessage) @@ -77,7 +74,8 @@ func (consensus *Consensus) constructResponseMessage(msgType consensus_proto.Mes marshaledMessage, err = protobuf.Marshal(&message) if err != nil { - utils.GetLogInstance().Debug("Failed to marshal Announce message", "error", err) + utils.GetLogInstance().Debug("Failed to marshal Commit message", "error", err) } + return proto.ConstructConsensusMessage(marshaledMessage) } diff --git a/consensus/consensus_validator_msg_test.go b/consensus/consensus_validator_msg_test.go index 290736da6..f82520f49 100644 --- a/consensus/consensus_validator_msg_test.go +++ b/consensus/consensus_validator_msg_test.go @@ -5,13 +5,11 @@ import ( "github.com/harmony-one/harmony/p2p/p2pimpl" - consensus_proto "github.com/harmony-one/harmony/api/consensus" - "github.com/harmony-one/harmony/crypto" "github.com/harmony-one/harmony/internal/utils" "github.com/harmony-one/harmony/p2p" ) -func TestConstructCommitMessage(test *testing.T) { +func TestConstructPrepareMessage(test *testing.T) { leader := p2p.Peer{IP: "127.0.0.1", Port: "9992"} validator := p2p.Peer{IP: "127.0.0.1", Port: "9995"} priKey, _, _ := utils.GenKeyP2P("127.0.0.1", "9902") @@ -21,14 +19,14 @@ func TestConstructCommitMessage(test *testing.T) { } consensus := New(host, "0", []p2p.Peer{leader, validator}, leader) consensus.blockHash = [32]byte{} - _, msg := consensus.constructCommitMessage(consensus_proto.MessageType_COMMIT) + msg := consensus.constructPrepareMessage() - if len(msg) != 143 { - test.Errorf("Commit message is not constructed in the correct size: %d", len(msg)) + if len(msg) != 93 { + test.Errorf("Prepare message is not constructed in the correct size: %d", len(msg)) } } -func TestConstructResponseMessage(test *testing.T) { +func TestConstructCommitMessage(test *testing.T) { leader := p2p.Peer{IP: "127.0.0.1", Port: "9902"} validator := p2p.Peer{IP: "127.0.0.1", Port: "9905"} priKey, _, _ := utils.GenKeyP2P("127.0.0.1", "9902") @@ -38,9 +36,9 @@ func TestConstructResponseMessage(test *testing.T) { } consensus := New(host, "0", []p2p.Peer{leader, validator}, leader) consensus.blockHash = [32]byte{} - msg := consensus.constructResponseMessage(consensus_proto.MessageType_RESPONSE, crypto.Ed25519Curve.Scalar()) + msg := consensus.constructCommitMessage() - if len(msg) != 143 { - test.Errorf("Response message is not constructed in the correct size: %d", len(msg)) + if len(msg) != 93 { + test.Errorf("Commit message is not constructed in the correct size: %d", len(msg)) } } diff --git a/consensus/consensus_validator_test.go b/consensus/consensus_validator_test.go index a88597ce8..0fbeb4daa 100644 --- a/consensus/consensus_validator_test.go +++ b/consensus/consensus_validator_test.go @@ -22,14 +22,14 @@ func TestProcessMessageValidatorAnnounce(test *testing.T) { defer ctrl.Finish() leader := p2p.Peer{IP: "127.0.0.1", Port: "9982"} - _, leader.PubKey = utils.GenKey(leader.IP, leader.Port) + _, leader.PubKey = utils.GenKeyBLS(leader.IP, leader.Port) validator1 := p2p.Peer{IP: "127.0.0.1", Port: "9984", ValidatorID: 1} - _, validator1.PubKey = utils.GenKey(validator1.IP, validator1.Port) + _, validator1.PubKey = utils.GenKeyBLS(validator1.IP, validator1.Port) validator2 := p2p.Peer{IP: "127.0.0.1", Port: "9986", ValidatorID: 2} - _, validator2.PubKey = utils.GenKey(validator2.IP, validator2.Port) + _, validator2.PubKey = utils.GenKeyBLS(validator2.IP, validator2.Port) validator3 := p2p.Peer{IP: "127.0.0.1", Port: "9988", ValidatorID: 3} - _, validator3.PubKey = utils.GenKey(validator3.IP, validator3.Port) + _, validator3.PubKey = utils.GenKeyBLS(validator3.IP, validator3.Port) m := mock_host.NewMockHost(ctrl) // Asserts that the first and only call to Bar() is passed 99. @@ -66,24 +66,24 @@ func TestProcessMessageValidatorAnnounce(test *testing.T) { copy(consensusValidator1.blockHash[:], hashBytes[:]) consensusValidator1.processAnnounceMessage(message) - assert.Equal(test, CommitDone, consensusValidator1.state) + assert.Equal(test, PrepareDone, consensusValidator1.state) time.Sleep(1 * time.Second) } -func TestProcessMessageValidatorChallenge(test *testing.T) { +func TestProcessMessageValidatorPrepared(test *testing.T) { ctrl := gomock.NewController(test) defer ctrl.Finish() leader := p2p.Peer{IP: "127.0.0.1", Port: "7782"} - _, leader.PubKey = utils.GenKey(leader.IP, leader.Port) + _, leader.PubKey = utils.GenKeyBLS(leader.IP, leader.Port) validator1 := p2p.Peer{IP: "127.0.0.1", Port: "7784", ValidatorID: 1} - _, validator1.PubKey = utils.GenKey(validator1.IP, validator1.Port) + _, validator1.PubKey = utils.GenKeyBLS(validator1.IP, validator1.Port) validator2 := p2p.Peer{IP: "127.0.0.1", Port: "7786", ValidatorID: 2} - _, validator2.PubKey = utils.GenKey(validator2.IP, validator2.Port) + _, validator2.PubKey = utils.GenKeyBLS(validator2.IP, validator2.Port) validator3 := p2p.Peer{IP: "127.0.0.1", Port: "7788", ValidatorID: 3} - _, validator3.PubKey = utils.GenKey(validator3.IP, validator3.Port) + _, validator3.PubKey = utils.GenKeyBLS(validator3.IP, validator3.Port) m := mock_host.NewMockHost(ctrl) // Asserts that the first and only call to Bar() is passed 99. @@ -103,8 +103,8 @@ func TestProcessMessageValidatorChallenge(test *testing.T) { copy(consensusLeader.blockHash[:], hashBytes[:]) - commitMsg := consensusLeader.constructAnnounceMessage() - challengeMsg, _, _ := consensusLeader.constructChallengeMessage(consensus_proto.MessageType_CHALLENGE) + announceMsg := consensusLeader.constructAnnounceMessage() + preparedMsg, _ := consensusLeader.constructPreparedMessage() if err != nil { test.Errorf("Failed to unmarshal message payload") @@ -116,15 +116,15 @@ func TestProcessMessageValidatorChallenge(test *testing.T) { } message := consensus_proto.Message{} - err = message.XXX_Unmarshal(commitMsg[1:]) + err = message.XXX_Unmarshal(announceMsg[1:]) copy(consensusValidator1.blockHash[:], hashBytes[:]) consensusValidator1.processAnnounceMessage(message) message = consensus_proto.Message{} - err = message.XXX_Unmarshal(challengeMsg[1:]) - consensusValidator1.processChallengeMessage(message, ResponseDone) + err = message.XXX_Unmarshal(preparedMsg[1:]) + consensusValidator1.processPreparedMessage(message) - assert.Equal(test, ResponseDone, consensusValidator1.state) + assert.Equal(test, CommitDone, consensusValidator1.state) time.Sleep(1 * time.Second) } diff --git a/crypto/bls/bls.go b/crypto/bls/bls.go new file mode 100644 index 000000000..a437e2892 --- /dev/null +++ b/crypto/bls/bls.go @@ -0,0 +1,226 @@ +package bls + +import ( + "errors" + "fmt" + + "github.com/harmony-one/bls/ffi/go/bls" +) + +// AggregateSig aggregates all the BLS signature into a single multi-signature. +func AggregateSig(sigs []*bls.Sign) *bls.Sign { + var aggregatedSig bls.Sign + for _, sig := range sigs { + aggregatedSig.Add(sig) + } + return &aggregatedSig +} + +// Mask represents a cosigning participation bitmask. +type Mask struct { + Bitmap []byte + publics []*bls.PublicKey + AggregatePublic *bls.PublicKey +} + +// NewMask returns a new participation bitmask for cosigning where all +// cosigners are disabled by default. If a public key is given it verifies that +// it is present in the list of keys and sets the corresponding index in the +// bitmask to 1 (enabled). +func NewMask(publics []*bls.PublicKey, myKey *bls.PublicKey) (*Mask, error) { + m := &Mask{ + publics: publics, + } + m.Bitmap = make([]byte, m.Len()) + m.AggregatePublic = &bls.PublicKey{} + if myKey != nil { + found := false + for i, key := range publics { + if key.IsEqual(myKey) { + m.SetBit(i, true) + found = true + break + } + } + if !found { + return nil, errors.New("key not found") + } + } + return m, nil +} + +// Mask returns a copy of the participation bitmask. +func (m *Mask) Mask() []byte { + clone := make([]byte, len(m.Bitmap)) + copy(clone[:], m.Bitmap) + return clone +} + +// Len returns the Bitmap length in bytes. +func (m *Mask) Len() int { + return (len(m.publics) + 7) >> 3 +} + +// SetMask sets the participation bitmask according to the given byte slice +// interpreted in little-endian order, i.e., bits 0-7 of byte 0 correspond to +// cosigners 0-7, bits 0-7 of byte 1 correspond to cosigners 8-15, etc. +func (m *Mask) SetMask(mask []byte) error { + if m.Len() != len(mask) { + return fmt.Errorf("mismatching Bitmap lengths") + } + for i := range m.publics { + byt := i >> 3 + msk := byte(1) << uint(i&7) + if ((m.Bitmap[byt] & msk) == 0) && ((mask[byt] & msk) != 0) { + m.Bitmap[byt] ^= msk // flip bit in Bitmap from 0 to 1 + m.AggregatePublic.Add(m.publics[i]) + } + if ((m.Bitmap[byt] & msk) != 0) && ((mask[byt] & msk) == 0) { + m.Bitmap[byt] ^= msk // flip bit in Bitmap from 1 to 0 + m.AggregatePublic.Sub(m.publics[i]) + } + } + return nil +} + +// SetBit enables (enable: true) or disables (enable: false) the bit +// in the participation Bitmap of the given cosigner. +func (m *Mask) SetBit(i int, enable bool) error { + if i >= len(m.publics) { + return errors.New("index out of range") + } + byt := i >> 3 + msk := byte(1) << uint(i&7) + if ((m.Bitmap[byt] & msk) == 0) && enable { + m.Bitmap[byt] ^= msk // flip bit in Bitmap from 0 to 1 + m.AggregatePublic.Add(m.publics[i]) + } + if ((m.Bitmap[byt] & msk) != 0) && !enable { + m.Bitmap[byt] ^= msk // flip bit in Bitmap from 1 to 0 + m.AggregatePublic.Sub(m.publics[i]) + } + return nil +} + +// GetPubKeyFromMask will return pubkeys which masked either zero or one depending on the flag +// it is used to show which signers are signed or not in the cosign message +func (m *Mask) GetPubKeyFromMask(flag bool) []*bls.PublicKey { + pubKeys := []*bls.PublicKey{} + for i := range m.publics { + byt := i >> 3 + msk := byte(1) << uint(i&7) + if flag == true { + if (m.Bitmap[byt] & msk) != 0 { + pubKeys = append(pubKeys, m.publics[i]) + } + } else { + if (m.Bitmap[byt] & msk) == 0 { + pubKeys = append(pubKeys, m.publics[i]) + } + } + } + return pubKeys +} + +// IndexEnabled checks whether the given index is enabled in the Bitmap or not. +func (m *Mask) IndexEnabled(i int) (bool, error) { + if i >= len(m.publics) { + return false, errors.New("index out of range") + } + byt := i >> 3 + msk := byte(1) << uint(i&7) + return ((m.Bitmap[byt] & msk) != 0), nil +} + +// KeyEnabled checks whether the index, corresponding to the given key, is +// enabled in the Bitmap or not. +func (m *Mask) KeyEnabled(public *bls.PublicKey) (bool, error) { + for i, key := range m.publics { + if key.IsEqual(public) { + return m.IndexEnabled(i) + } + } + return false, errors.New("key not found") +} + +// SetKey set the bit in the Bitmap for the given cosigner +func (m *Mask) SetKey(public *bls.PublicKey, enable bool) error { + for i, key := range m.publics { + if key.IsEqual(public) { + return m.SetBit(i, enable) + } + } + return errors.New("key not found") +} + +// CountEnabled returns the number of enabled nodes in the CoSi participation +// Bitmap. +func (m *Mask) CountEnabled() int { + // hw is hamming weight + hw := 0 + for i := range m.publics { + byt := i >> 3 + msk := byte(1) << uint(i&7) + if (m.Bitmap[byt] & msk) != 0 { + hw++ + } + } + return hw +} + +// CountTotal returns the total number of nodes this CoSi instance knows. +func (m *Mask) CountTotal() int { + return len(m.publics) +} + +// AggregateMasks computes the bitwise OR of the two given participation masks. +func AggregateMasks(a, b []byte) ([]byte, error) { + if len(a) != len(b) { + return nil, errors.New("mismatching Bitmap lengths") + } + m := make([]byte, len(a)) + for i := range m { + m[i] = a[i] | b[i] + } + return m, nil +} + +// Policy represents a fully customizable cosigning policy deciding what +// cosigner sets are and aren't sufficient for a collective signature to be +// considered acceptable to a verifier. The Check method may inspect the set of +// participants that cosigned by invoking cosi.Mask and/or cosi.MaskBit, and may +// use any other relevant contextual information (e.g., how security-critical +// the operation relying on the collective signature is) in determining whether +// the collective signature was produced by an acceptable set of cosigners. +type Policy interface { + Check(m *Mask) bool +} + +// CompletePolicy is the default policy requiring that all participants have +// cosigned to make a collective signature valid. +type CompletePolicy struct { +} + +// Check verifies that all participants have contributed to a collective +// signature. +func (p CompletePolicy) Check(m *Mask) bool { + return m.CountEnabled() == m.CountTotal() +} + +// ThresholdPolicy allows to specify a simple t-of-n policy requring that at +// least the given threshold number of participants t have cosigned to make a +// collective signature valid. +type ThresholdPolicy struct { + thold int +} + +// NewThresholdPolicy returns a new ThresholdPolicy with the given threshold. +func NewThresholdPolicy(thold int) *ThresholdPolicy { + return &ThresholdPolicy{thold: thold} +} + +// Check verifies that at least a threshold number of participants have +// contributed to a collective signature. +func (p ThresholdPolicy) Check(m *Mask) bool { + return m.CountEnabled() >= p.thold +} diff --git a/crypto/pki/utils.go b/crypto/pki/utils.go index f9b88d569..fef0c1d1e 100644 --- a/crypto/pki/utils.go +++ b/crypto/pki/utils.go @@ -2,18 +2,17 @@ package pki import ( "crypto/sha256" + "encoding/binary" + + "github.com/harmony-one/bls/ffi/go/bls" "github.com/dedis/kyber" - "github.com/ethereum/go-ethereum/log" "github.com/harmony-one/harmony/crypto" ) // GetAddressFromPublicKey returns address given a public key. -func GetAddressFromPublicKey(pubKey kyber.Point) [20]byte { - bytes, err := pubKey.MarshalBinary() - if err != nil { - log.Error("Failed to serialize challenge") - } +func GetAddressFromPublicKey(pubKey *bls.PublicKey) [20]byte { + bytes := pubKey.Serialize() address := [20]byte{} hash := sha256.Sum256(bytes) copy(address[:], hash[12:]) @@ -21,18 +20,23 @@ func GetAddressFromPublicKey(pubKey kyber.Point) [20]byte { } // GetAddressFromPrivateKey returns address given a private key. -func GetAddressFromPrivateKey(priKey kyber.Scalar) [20]byte { - return GetAddressFromPublicKey(GetPublicKeyFromScalar(priKey)) +func GetAddressFromPrivateKey(priKey *bls.SecretKey) [20]byte { + return GetAddressFromPublicKey(priKey.GetPublicKey()) } // GetAddressFromPrivateKeyBytes returns address from private key in bytes. func GetAddressFromPrivateKeyBytes(priKey [32]byte) [20]byte { - return GetAddressFromPublicKey(GetPublicKeyFromScalar(crypto.Ed25519Curve.Scalar().SetBytes(priKey[:]))) + var privateKey bls.SecretKey + privateKey.SetLittleEndian(priKey[:]) + + return GetAddressFromPublicKey(privateKey.GetPublicKey()) } // GetAddressFromInt is the temporary helper function for benchmark use func GetAddressFromInt(value int) [20]byte { - return GetAddressFromPublicKey(GetPublicKeyFromScalar(GetPrivateKeyScalarFromInt(value))) + priKey := [32]byte{} + binary.LittleEndian.PutUint32(priKey[:], uint32(value)) + return GetAddressFromPrivateKeyBytes(priKey) } // GetPrivateKeyScalarFromInt return private key scalar. @@ -40,6 +44,15 @@ func GetPrivateKeyScalarFromInt(value int) kyber.Scalar { return crypto.Ed25519Curve.Scalar().SetInt64(int64(value)) } +// GetBLSPrivateKeyFromInt returns bls private key +func GetBLSPrivateKeyFromInt(value int) *bls.SecretKey { + priKey := [32]byte{} + binary.LittleEndian.PutUint32(priKey[:], uint32(value)) + var privateKey bls.SecretKey + privateKey.SetLittleEndian(priKey[:]) + return &privateKey +} + // GetPrivateKeyFromInt returns private key in bytes given an interger. func GetPrivateKeyFromInt(value int) [32]byte { priKey, err := crypto.Ed25519Curve.Scalar().SetInt64(int64(value)).MarshalBinary() diff --git a/crypto/pki/utils_test.go b/crypto/pki/utils_test.go index 4b4586525..0d4b16ccd 100644 --- a/crypto/pki/utils_test.go +++ b/crypto/pki/utils_test.go @@ -1,20 +1,25 @@ package pki import ( + "encoding/binary" "reflect" "testing" "time" + "github.com/harmony-one/bls/ffi/go/bls" + "github.com/harmony-one/harmony/crypto" + "github.com/harmony-one/harmony/crypto" ) func TestGetAddressFromPublicKey(test *testing.T) { - suite := crypto.Ed25519Curve t := time.Now().UnixNano() - scalar := suite.Scalar().SetInt64(t) - pubKey := GetPublicKeyFromScalar(scalar) - addr1 := GetAddressFromPublicKey(pubKey) - addr2 := GetAddressFromPrivateKey(scalar) + priKey := [32]byte{} + binary.LittleEndian.PutUint32(priKey[:], uint32(t)) + var privateKey bls.SecretKey + privateKey.SetLittleEndian(priKey[:]) + addr1 := GetAddressFromPublicKey(privateKey.GetPublicKey()) + addr2 := GetAddressFromPrivateKey(&privateKey) if !reflect.DeepEqual(addr1, addr2) { test.Error("two public address should be equal") } diff --git a/internal/beaconchain/libs/beaconchain.go b/internal/beaconchain/libs/beaconchain.go index ec3437b19..7cf4ddb19 100644 --- a/internal/beaconchain/libs/beaconchain.go +++ b/internal/beaconchain/libs/beaconchain.go @@ -6,7 +6,7 @@ import ( "strconv" "sync" - "github.com/dedis/kyber" + "github.com/harmony-one/bls/ffi/go/bls" "github.com/harmony-one/harmony/api/proto/bcconn" proto_identity "github.com/harmony-one/harmony/api/proto/identity" @@ -44,7 +44,7 @@ type BCInfo struct { type BeaconChain struct { BCInfo BCInfo ShardLeaderMap map[int]*node.Info - PubKey kyber.Point + PubKey *bls.PublicKey host p2p.Host state BCState rpcServer *beaconchain.Server @@ -105,10 +105,10 @@ func New(numShards int, ip, port string, key p2p_crypto.PrivKey) *BeaconChain { return &bc } -func generateBCKey() kyber.Point { +func generateBCKey() *bls.PublicKey { r := rand.Intn(1000) - priKey := pki.GetPrivateKeyFromInt(r) - pubkey := pki.GetPublicKeyFromPrivateKey(priKey) + priKey := pki.GetBLSPrivateKeyFromInt(r) + pubkey := priKey.GetPublicKey() return pubkey } diff --git a/internal/newnode/newnode.go b/internal/newnode/newnode.go index 72e5f8687..b10b859b3 100644 --- a/internal/newnode/newnode.go +++ b/internal/newnode/newnode.go @@ -7,13 +7,12 @@ import ( "strconv" "time" - "github.com/dedis/kyber" + "github.com/harmony-one/bls/ffi/go/bls" "github.com/ethereum/go-ethereum/log" "github.com/harmony-one/harmony/api/proto/bcconn" proto_identity "github.com/harmony-one/harmony/api/proto/identity" proto_node "github.com/harmony-one/harmony/api/proto/node" - "github.com/harmony-one/harmony/crypto" "github.com/harmony-one/harmony/internal/utils" "github.com/harmony-one/harmony/p2p" "github.com/harmony-one/harmony/p2p/host" @@ -32,8 +31,8 @@ type NewNode struct { isLeader bool Self p2p.Peer Leaders map[uint32]p2p.Peer - PubK kyber.Point - priK kyber.Scalar + PubK *bls.PublicKey + priK *bls.SecretKey log log.Logger SetInfo chan bool host p2p.Host @@ -41,7 +40,7 @@ type NewNode struct { // New candidatenode initialization func New(ip string, port string, nodePk p2p_crypto.PrivKey) *NewNode { - priKey, pubKey := utils.GenKey(ip, port) + priKey, pubKey := utils.GenKeyBLS(ip, port) var node NewNode var err error node.PubK = pubKey @@ -77,7 +76,7 @@ func (node NewNode) String() string { // RequestBeaconChain requests beacon chain for identity data func (node *NewNode) requestBeaconChain(BCPeer p2p.Peer) (err error) { node.log.Info("connecting to beacon chain now ...") - pubk, err := node.PubK.MarshalBinary() + pubk := node.PubK.Serialize() if err != nil { node.log.Error("Could not Marshall public key into binary") } @@ -130,8 +129,8 @@ func (node *NewNode) processShardInfo(msgPayload []byte) bool { } leaderPeer.Addrs = append(leaderPeer.Addrs, targetAddr) - leaderPeer.PubKey = crypto.Ed25519Curve.Point() - err = leaderPeer.PubKey.UnmarshalBinary(v.PubKey[:]) + leaderPeer.PubKey = &bls.PublicKey{} + err = leaderPeer.PubKey.Deserialize(v.PubKey[:]) if err != nil { node.log.Error("Could not unmarshall leaders public key from binary to kyber.point") } diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 775bf4269..deb739ffc 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -12,6 +12,8 @@ import ( "strconv" "sync" + "github.com/harmony-one/bls/ffi/go/bls" + p2p_crypto "github.com/libp2p/go-libp2p-crypto" "github.com/dedis/kyber" @@ -73,6 +75,21 @@ func GenKey(ip, port string) (kyber.Scalar, kyber.Point) { return priKey, pubKey } +// GenKeyBLS generates a bls key pair given ip and port. +func GenKeyBLS(ip, port string) (*bls.SecretKey, *bls.PublicKey) { + nodeIDBytes := make([]byte, 32) + binary.LittleEndian.PutUint32(nodeIDBytes, GetUniqueIDFromIPPort(ip, port)) + privateKey := bls.SecretKey{} + err := privateKey.SetLittleEndian(nodeIDBytes) + if err != nil { + log.Print("failed to set private key", err) + } + priKey := &privateKey + pubKey := privateKey.GetPublicKey() + + return priKey, pubKey +} + // GenKeyP2P generates a pair of RSA keys used in libp2p host func GenKeyP2P(ip, port string) (p2p_crypto.PrivKey, p2p_crypto.PubKey, error) { r := mrand.New(mrand.NewSource(int64(GetUniqueIDFromIPPort(ip, port)))) diff --git a/node/node_handler.go b/node/node_handler.go index 022216d56..8a3dfcfa2 100644 --- a/node/node_handler.go +++ b/node/node_handler.go @@ -6,15 +6,15 @@ import ( "os" "time" + "github.com/harmony-one/bls/ffi/go/bls" + "github.com/ethereum/go-ethereum/common" - "github.com/dedis/kyber" "github.com/ethereum/go-ethereum/rlp" "github.com/harmony-one/harmony/api/proto" proto_identity "github.com/harmony-one/harmony/api/proto/identity" proto_node "github.com/harmony-one/harmony/api/proto/node" "github.com/harmony-one/harmony/core/types" - hmy_crypto "github.com/harmony-one/harmony/crypto" "github.com/harmony-one/harmony/crypto/pki" "github.com/harmony-one/harmony/internal/utils" "github.com/harmony-one/harmony/p2p" @@ -303,8 +303,8 @@ func (node *Node) pingMessageHandler(msgPayload []byte) int { peer.PeerID = ping.Node.PeerID peer.ValidatorID = ping.Node.ValidatorID - peer.PubKey = hmy_crypto.Ed25519Curve.Point() - err = peer.PubKey.UnmarshalBinary(ping.Node.PubKey[:]) + peer.PubKey = &bls.PublicKey{} + err = peer.PubKey.Deserialize(ping.Node.PubKey[:]) if err != nil { utils.GetLogInstance().Error("UnmarshalBinary Failed", "error", err) return -1 @@ -358,8 +358,8 @@ func (node *Node) pongMessageHandler(msgPayload []byte) int { peer.ValidatorID = p.ValidatorID peer.PeerID = p.PeerID - peer.PubKey = hmy_crypto.Ed25519Curve.Point() - err = peer.PubKey.UnmarshalBinary(p.PubKey[:]) + peer.PubKey = &bls.PublicKey{} + err = peer.PubKey.Deserialize(p.PubKey[:]) if err != nil { utils.GetLogInstance().Error("UnmarshalBinary Failed", "error", err) continue @@ -374,17 +374,17 @@ func (node *Node) pongMessageHandler(msgPayload []byte) int { // Reset Validator PublicKeys every time we receive PONG message from Leader // The PublicKeys has to be idential across the shard on every node // TODO (lc): we need to handle RemovePeer situation - publicKeys := make([]kyber.Point, 0) + publicKeys := make([]*bls.PublicKey, 0) // Create the the PubKey from the []byte sent from leader for _, k := range pong.PubKeys { - key := hmy_crypto.Ed25519Curve.Point() - err = key.UnmarshalBinary(k[:]) + key := bls.PublicKey{} + err = key.Deserialize(k[:]) if err != nil { utils.GetLogInstance().Error("UnmarshalBinary Failed PubKeys", "error", err) continue } - publicKeys = append(publicKeys, key) + publicKeys = append(publicKeys, &key) } if node.State == NodeWaitToJoin { diff --git a/node/node_handler_test.go b/node/node_handler_test.go index f24a0e6cb..9b347bf8f 100644 --- a/node/node_handler_test.go +++ b/node/node_handler_test.go @@ -11,7 +11,7 @@ import ( ) func TestNodeStreamHandler(t *testing.T) { - _, pubKey := utils.GenKey("1", "2") + _, pubKey := utils.GenKeyBLS("1", "2") leader := p2p.Peer{IP: "127.0.0.1", Port: "8882", PubKey: pubKey} validator := p2p.Peer{IP: "127.0.0.1", Port: "8885"} priKey, _, _ := utils.GenKeyP2P("127.0.0.1", "9902") @@ -35,7 +35,7 @@ func TestNodeStreamHandler(t *testing.T) { } func TestAddNewBlock(t *testing.T) { - _, pubKey := utils.GenKey("1", "2") + _, pubKey := utils.GenKeyBLS("1", "2") leader := p2p.Peer{IP: "127.0.0.1", Port: "9882", PubKey: pubKey} validator := p2p.Peer{IP: "127.0.0.1", Port: "9885"} priKey, _, _ := utils.GenKeyP2P("127.0.0.1", "9902") @@ -58,7 +58,7 @@ func TestAddNewBlock(t *testing.T) { } func TestVerifyNewBlock(t *testing.T) { - _, pubKey := utils.GenKey("1", "2") + _, pubKey := utils.GenKeyBLS("1", "2") leader := p2p.Peer{IP: "127.0.0.1", Port: "8882", PubKey: pubKey} validator := p2p.Peer{IP: "127.0.0.1", Port: "8885"} priKey, _, _ := utils.GenKeyP2P("127.0.0.1", "9902") diff --git a/node/node_test.go b/node/node_test.go index 62be0b025..509b6c7fa 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -8,7 +8,6 @@ import ( proto_node "github.com/harmony-one/harmony/api/proto/node" "github.com/harmony-one/harmony/consensus" - "github.com/harmony-one/harmony/crypto" "github.com/harmony-one/harmony/crypto/pki" "github.com/harmony-one/harmony/internal/utils" "github.com/harmony-one/harmony/p2p" @@ -16,7 +15,7 @@ import ( ) func TestNewNode(t *testing.T) { - _, pubKey := utils.GenKey("1", "2") + _, pubKey := utils.GenKeyBLS("1", "2") leader := p2p.Peer{IP: "127.0.0.1", Port: "8882", PubKey: pubKey} validator := p2p.Peer{IP: "127.0.0.1", Port: "8885"} priKey, _, _ := utils.GenKeyP2P("127.0.0.1", "9902") @@ -40,7 +39,7 @@ func TestNewNode(t *testing.T) { } func TestGetSyncingPeers(t *testing.T) { - _, pubKey := utils.GenKey("1", "2") + _, pubKey := utils.GenKeyBLS("1", "2") leader := p2p.Peer{IP: "127.0.0.1", Port: "8882", PubKey: pubKey} validator := p2p.Peer{IP: "127.0.0.1", Port: "8885"} priKey, _, _ := utils.GenKeyP2P("127.0.0.1", "9902") @@ -66,11 +65,8 @@ func TestGetSyncingPeers(t *testing.T) { } func TestAddPeers(t *testing.T) { - priKey1 := crypto.Ed25519Curve.Scalar().SetInt64(int64(333)) - pubKey1 := pki.GetPublicKeyFromScalar(priKey1) - - priKey2 := crypto.Ed25519Curve.Scalar().SetInt64(int64(999)) - pubKey2 := pki.GetPublicKeyFromScalar(priKey2) + pubKey1 := pki.GetBLSPrivateKeyFromInt(333).GetPublicKey() + pubKey2 := pki.GetBLSPrivateKeyFromInt(444).GetPublicKey() peers1 := []*p2p.Peer{ &p2p.Peer{ @@ -88,7 +84,7 @@ func TestAddPeers(t *testing.T) { ValidatorID: 2, }, } - _, pubKey := utils.GenKey("1", "2") + _, pubKey := utils.GenKeyBLS("1", "2") leader := p2p.Peer{IP: "127.0.0.1", Port: "8982", PubKey: pubKey} validator := p2p.Peer{IP: "127.0.0.1", Port: "8985"} priKey, _, _ := utils.GenKeyP2P("127.0.0.1", "9902") @@ -112,8 +108,7 @@ func TestAddPeers(t *testing.T) { } func sendPingMessage(node *Node, leader p2p.Peer) { - priKey1 := crypto.Ed25519Curve.Scalar().SetInt64(int64(333)) - pubKey1 := pki.GetPublicKeyFromScalar(priKey1) + pubKey1 := pki.GetBLSPrivateKeyFromInt(333).GetPublicKey() p1 := p2p.Peer{ IP: "127.0.0.1", @@ -132,15 +127,13 @@ func sendPingMessage(node *Node, leader p2p.Peer) { } func sendPongMessage(node *Node, leader p2p.Peer) { - priKey1 := crypto.Ed25519Curve.Scalar().SetInt64(int64(333)) - pubKey1 := pki.GetPublicKeyFromScalar(priKey1) + pubKey1 := pki.GetBLSPrivateKeyFromInt(333).GetPublicKey() + pubKey2 := pki.GetBLSPrivateKeyFromInt(444).GetPublicKey() p1 := p2p.Peer{ IP: "127.0.0.1", Port: "9998", PubKey: pubKey1, } - priKey2 := crypto.Ed25519Curve.Scalar().SetInt64(int64(999)) - pubKey2 := pki.GetPublicKeyFromScalar(priKey2) p2 := p2p.Peer{ IP: "127.0.0.1", Port: "9999", @@ -165,7 +158,7 @@ func exitServer() { } func TestPingPongHandler(t *testing.T) { - _, pubKey := utils.GenKey("127.0.0.1", "8881") + _, pubKey := utils.GenKeyBLS("127.0.0.1", "8881") leader := p2p.Peer{IP: "127.0.0.1", Port: "8881", PubKey: pubKey} // validator := p2p.Peer{IP: "127.0.0.1", Port: "9991"} priKey, _, _ := utils.GenKeyP2P("127.0.0.1", "9902") diff --git a/p2p/p2p.go b/p2p/p2p.go index cf0e25430..e8adbe836 100644 --- a/p2p/p2p.go +++ b/p2p/p2p.go @@ -4,9 +4,9 @@ import ( "fmt" "net" - "github.com/dedis/kyber" + "github.com/harmony-one/bls/ffi/go/bls" + peer "github.com/libp2p/go-libp2p-peer" - multiaddr "github.com/multiformats/go-multiaddr" ) // StreamHandler handles incoming p2p message. @@ -16,7 +16,7 @@ type StreamHandler func(Stream) type Peer struct { IP string // IP address of the peer Port string // Port number of the peer - PubKey kyber.Point // Public key of the peer, used for consensus signing + PubKey *bls.PublicKey // Public key of the peer, used for consensus signing Ready bool // Ready is true if the peer is ready to join consensus. (FIXME: deprecated) ValidatorID int // -1 is the default value, means not assigned any validator ID in the shard Addrs []multiaddr.Multiaddr // MultiAddress of the peer diff --git a/scripts/go_executable_build.sh b/scripts/go_executable_build.sh index def4c5551..9dd0014e8 100755 --- a/scripts/go_executable_build.sh +++ b/scripts/go_executable_build.sh @@ -13,6 +13,21 @@ GOARCH=amd64 FOLDER=/${WHOAMI:-$USER} RACE= +HMY_PATH=$GOPATH/src/github.com/harmony-one +export CGO_CFLAGS="-I$HMY_PATH/bls/include -I$HMY_PATH/mcl/include" +export CGO_LDFLAGS="-L$HMY_PATH/bls/lib" +export LD_LIBRARY_PATH=$HMY_PATH/bls/lib:$HMY_PATH/mcl/lib + +OS=$(uname -s) +case $OS in + Darwin) + export CGO_CFLAGS="-I$HMY_PATH/bls/include -I$HMY_PATH/mcl/include -I/usr/local/opt/openssl/include" + export CGO_LDFLAGS="-L$HMY_PATH/bls/lib -L/usr/local/opt/openssl/lib" + export LD_LIBRARY_PATH=$HMY_PATH/bls/lib:$HMY_PATH/mcl/lib:/usr/local/opt/openssl/lib + export DYLD_LIBRARY_PATH=$LD_LIBRARY_PATH + ;; +esac + if [ "$(uname -s)" == "Darwin" ]; then MD5='md5 -r' GOOS=darwin diff --git a/scripts/travis_checker.sh b/scripts/travis_checker.sh index 9f6cda555..171b71eb4 100755 --- a/scripts/travis_checker.sh +++ b/scripts/travis_checker.sh @@ -14,6 +14,21 @@ tmpdir= trap 'case "${tmpdir}" in ?*) rm -rf "${tmpdir}";; esac' EXIT tmpdir=$(mktemp -d) +HMY_PATH=$GOPATH/src/github.com/harmony-one +export CGO_CFLAGS="-I$HMY_PATH/bls/include -I$HMY_PATH/mcl/include" +export CGO_LDFLAGS="-L$HMY_PATH/bls/lib" +export LD_LIBRARY_PATH=$HMY_PATH/bls/lib:$HMY_PATH/mcl/lib + +OS=$(uname -s) +case $OS in + Darwin) + export CGO_CFLAGS="-I$HMY_PATH/bls/include -I$HMY_PATH/mcl/include -I/usr/local/opt/openssl/include" + export CGO_LDFLAGS="-L$HMY_PATH/bls/lib -L/usr/local/opt/openssl/lib" + export LD_LIBRARY_PATH=$HMY_PATH/bls/lib:$HMY_PATH/mcl/lib:/usr/local/opt/openssl/lib + export DYLD_LIBRARY_PATH=$LD_LIBRARY_PATH + ;; +esac + echo "Running go test..." if go test -v -count=1 ./... then diff --git a/test/crypto/bls/main.go b/test/crypto/bls/main.go index 1d5611acf..e7e639fda 100644 --- a/test/crypto/bls/main.go +++ b/test/crypto/bls/main.go @@ -1,9 +1,15 @@ package main import ( + "crypto/ecdsa" + "crypto/rand" + "encoding/hex" "log" "time" + "github.com/ethereum/go-ethereum/crypto" + "github.com/harmony-one/bls/ffi/go/bls" + "github.com/harmony-one/bls/ffi/go/bls" ) @@ -14,9 +20,17 @@ func main() { startTime := time.Now() for i := 0; i < 1000; i++ { - bls.Init(bls.BLS12_381) var sec bls.SecretKey sec.SetByCSPRNG() + + if i == 0 { + testECKey, _ := ecdsa.GenerateKey(crypto.S256(), rand.Reader) + log.Printf("Secret Key: 0x%s", sec.GetHexString()) + log.Printf("Secret Key: 0x%s", hex.EncodeToString(sec.GetLittleEndian())) + log.Printf("Secret Key Length: %d", len(sec.GetLittleEndian())) + log.Printf("Secret Key: 0x%s", hex.EncodeToString(testECKey.D.Bytes())) + log.Printf("Secret Key Length: %d", len(testECKey.D.Bytes())) + } if i == 0 { aggSig = sec.Sign(m) aggPub = sec.GetPublicKey() @@ -27,8 +41,8 @@ func main() { } endTime := time.Now() log.Printf("Time required to sign 1000 messages and aggregate 1000 pub keys and signatures: %f seconds", endTime.Sub(startTime).Seconds()) - log.Printf("Aggregate Signature: 0x%x", aggSig.GetHexString()) - log.Printf("Aggregate Public Key: 0x%x", aggPub.GetHexString()) + log.Printf("Aggregate Signature: 0x%s, length: %d", aggSig.GetHexString(), len(aggSig.Serialize())) + log.Printf("Aggregate Public Key: 0x%s, length: %d", aggPub.GetHexString(), len(aggPub.Serialize())) startTime = time.Now() if !aggSig.Verify(aggPub, m) { diff --git a/test/deploy.sh b/test/deploy.sh index 73c2cd784..5c9e594f5 100755 --- a/test/deploy.sh +++ b/test/deploy.sh @@ -3,6 +3,21 @@ ROOT=$(dirname $0)/.. USER=$(whoami) +HMY_PATH=$GOPATH/src/github.com/harmony-one +export CGO_CFLAGS="-I$HMY_PATH/bls/include -I$HMY_PATH/mcl/include" +export CGO_LDFLAGS="-L$HMY_PATH/bls/lib" +export LD_LIBRARY_PATH=$HMY_PATH/bls/lib:$HMY_PATH/mcl/lib + +OS=$(uname -s) +case $OS in + Darwin) + export CGO_CFLAGS="-I$HMY_PATH/bls/include -I$HMY_PATH/mcl/include -I/usr/local/opt/openssl/include" + export CGO_LDFLAGS="-L$HMY_PATH/bls/lib -L/usr/local/opt/openssl/lib" + export LD_LIBRARY_PATH=$HMY_PATH/bls/lib:$HMY_PATH/mcl/lib:/usr/local/opt/openssl/lib + export DYLD_LIBRARY_PATH=$LD_LIBRARY_PATH + ;; +esac + set -x set -eo pipefail