package drand import ( "bytes" "time" "github.com/harmony-one/harmony/crypto/bls" protobuf "github.com/golang/protobuf/proto" msg_pb "github.com/harmony-one/harmony/api/proto/message" "github.com/harmony-one/harmony/core" "github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/crypto/vdf" "github.com/harmony-one/harmony/crypto/vrf/p256" "github.com/harmony-one/harmony/internal/utils" "github.com/harmony-one/harmony/p2p" "github.com/harmony-one/harmony/p2p/host" ) const ( vdfDifficulty = 5000000 // This takes about 20s to finish the vdf ) // WaitForEpochBlock waits for the first epoch block to run DRG on func (dRand *DRand) WaitForEpochBlock(blockChannel chan *types.Block, stopChan chan struct{}, stoppedChan chan struct{}) { go func() { defer close(stoppedChan) for { select { default: // keep waiting for epoch block newBlock := <-blockChannel if core.IsEpochLastBlock(newBlock) { dRand.init(newBlock) } pRnd := newBlock.Header().Vrf zeros := [32]byte{} if core.IsEpochBlock(newBlock) && !bytes.Equal(pRnd[:], zeros[:]) { // The epoch block should contain the randomness preimage pRnd go func() { vdf := vdf.New(vdfDifficulty, pRnd) outputChannel := vdf.GetOutputChannel() start := time.Now() vdf.Execute() duration := time.Now().Sub(start) utils.GetLogInstance().Info("VDF computation finished", "time spent", duration.String()) output := <-outputChannel rndBytes := [64]byte{} // The first 32 bytes are the randomness and the last 32 bytes are the hash of the block where the corresponding pRnd was generated copy(rndBytes[:32], output[:]) blockHash := newBlock.Hash() copy(rndBytes[32:], blockHash[:]) dRand.RndChannel <- rndBytes }() } case <-stopChan: return } } }() } func (dRand *DRand) init(epochBlock *types.Block) { utils.GetLogInstance().Debug("INITING DRAND") dRand.ResetState() // Copy over block hash and block header data blockHash := epochBlock.Hash() copy(dRand.blockHash[:], blockHash[:]) msgToSend := dRand.constructInitMessage() // Leader commit vrf itself rand, proof := dRand.vrf(dRand.blockHash) (*dRand.vrfs)[dRand.SelfAddress] = append(rand[:], proof...) utils.GetLogInstance().Info("[DRG] sent init", "msg", msgToSend, "leader.PubKey", dRand.leader.ConsensusPubKey) dRand.host.SendMessageToGroups([]p2p.GroupID{p2p.NewGroupIDByShardID(p2p.ShardID(dRand.ShardID))}, host.ConstructP2pMessage(byte(17), msgToSend)) } // ProcessMessageLeader dispatches messages for the leader to corresponding processors. func (dRand *DRand) ProcessMessageLeader(payload []byte) { message := &msg_pb.Message{} err := protobuf.Unmarshal(payload, message) if err != nil { utils.GetLogInstance().Error("Failed to unmarshal message payload.", "err", err, "dRand", dRand) } if message.GetDrand().ShardId != dRand.ShardID { utils.GetLogInstance().Warn("Received drand message from different shard", "myShardId", dRand.ShardID, "receivedShardId", message.GetDrand().ShardId) return } switch message.Type { case msg_pb.MessageType_DRAND_COMMIT: dRand.processCommitMessage(message) default: utils.GetLogInstance().Error("Unexpected message type", "msgType", message.Type, "dRand", dRand) } } // ProcessMessageValidator dispatches validator's consensus message. func (dRand *DRand) processCommitMessage(message *msg_pb.Message) { utils.GetLogInstance().Info("[DRG] Leader received commit") if message.Type != msg_pb.MessageType_DRAND_COMMIT { utils.GetLogInstance().Error("Wrong message type received", "expected", msg_pb.MessageType_DRAND_COMMIT, "got", message.Type) return } dRand.mutex.Lock() defer dRand.mutex.Unlock() drandMsg := message.GetDrand() senderPubKey, err := bls.BytesToBlsPublicKey(drandMsg.SenderPubkey) if err != nil { utils.GetLogInstance().Debug("Failed to deserialize BLS public key", "error", err) return } validatorAddress := utils.GetBlsAddress(senderPubKey) if !dRand.IsValidatorInCommittee(validatorAddress) { utils.GetLogInstance().Error("Invalid validator", "validatorAddress", validatorAddress) return } vrfs := dRand.vrfs if len((*vrfs)) >= ((len(dRand.PublicKeys))/3 + 1) { utils.GetLogInstance().Debug("Received additional randomness commit message", "validatorAddress", validatorAddress) return } // Verify message signature err = verifyMessageSig(senderPubKey, message) if err != nil { utils.GetLogInstance().Warn("[DRAND] failed to verify the message signature", "Error", err, "PubKey", senderPubKey) return } rand := drandMsg.Payload[:32] proof := drandMsg.Payload[32 : len(drandMsg.Payload)-64] pubKeyBytes := drandMsg.Payload[len(drandMsg.Payload)-64:] _, vrfPubKey := p256.GenerateKey() vrfPubKey.Deserialize(pubKeyBytes) expectedRand, err := vrfPubKey.ProofToHash(dRand.blockHash[:], proof) if err != nil || !bytes.Equal(expectedRand[:], rand) { utils.GetLogInstance().Error("[DRAND] Failed to verify the VRF", "error", err, "validatorAddress", validatorAddress, "expectedRand", expectedRand, "receivedRand", rand) return } utils.GetLogInstance().Debug("Received new VRF commit", "numReceivedSoFar", len((*vrfs)), "validatorAddress", validatorAddress, "PublicKeys", len(dRand.PublicKeys)) (*vrfs)[validatorAddress] = drandMsg.Payload dRand.bitmap.SetKey(senderPubKey, true) // Set the bitmap indicating that this validator signed. if len((*vrfs)) >= ((len(dRand.PublicKeys))/3 + 1) { // Construct pRand and initiate consensus on it utils.GetLogInstance().Debug("[DRAND] {BINGO} Received enough randomness commit", "numReceivedSoFar", len((*vrfs)), "validatorAddress", validatorAddress, "PublicKeys", len(dRand.PublicKeys)) pRnd := [32]byte{} // Bitwise XOR on all the submitted vrfs for _, vrf := range *vrfs { for i := 0; i < len(pRnd); i++ { pRnd[i] = pRnd[i] ^ vrf[i] } } dRand.PRndChannel <- append(pRnd[:], dRand.bitmap.Bitmap...) } }