diff --git a/consensus/consensus_core/consensus_core.go b/consensus/consensus_core/consensus_core.go index 398d67d..f0428c4 100644 --- a/consensus/consensus_core/consensus_core.go +++ b/consensus/consensus_core/consensus_core.go @@ -1,169 +1,224 @@ -// go implementation of consensus_core/src/types/mod.rs -// structs not defined yet: -// LightClientStore,genericUpdate package consensus_core -type BeaconBlock struct { - slot uint64 - proposer_index uint64 - parent_root [32]byte - state_root [32]byte - body BeaconBlockBody -} +import ( + "bytes" + + "github.com/BlocSoc-iitr/selene/consensus/types" + "github.com/ugorji/go/codec" +) + type Bytes32 [32]byte type BLSPubKey [48]byte type Address [20]byte type LogsBloom = [256]byte type SignatureBytes [96]byte + +type BeaconBlock struct { + Slot uint64 + ProposerIndex uint64 + ParentRoot Bytes32 + StateRoot Bytes32 + Body BeaconBlockBody +} + type Eth1Data struct { - deposit_root Bytes32 - deposit_count uint64 - block_hash Bytes32 + DepositRoot Bytes32 + DepositCount uint64 + BlockHash Bytes32 } + type ProposerSlashing struct { - signed_header_1 SignedBeaconBlockHeader - signed_header_2 SignedBeaconBlockHeader + SignedHeader1 SignedBeaconBlockHeader + SignedHeader2 SignedBeaconBlockHeader } + type SignedBeaconBlockHeader struct { - message BeaconBlockHeader - signature SignatureBytes + Message BeaconBlockHeader + Signature SignatureBytes } + type BeaconBlockHeader struct { - slot uint64 - proposer_index uint64 - parent_root Bytes32 - state_root Bytes32 - body_root Bytes32 + Slot uint64 + ProposerIndex uint64 + ParentRoot Bytes32 + StateRoot Bytes32 + BodyRoot Bytes32 } + type AttesterSlashing struct { - attestation_1 IndexedAttestation - attestation_2 IndexedAttestation + Attestation1 IndexedAttestation + Attestation2 IndexedAttestation } + type IndexedAttestation struct { - attesting_indices []uint64 //max length 2048 to be ensured - data AttestationData - signature SignatureBytes + AttestingIndices [2048]uint64 + Data AttestationData + Signature SignatureBytes } + type AttestationData struct { - slot uint64 - index uint64 - beacon_block_root Bytes32 - source Checkpoint - target Checkpoint + Slot uint64 + Index uint64 + BeaconBlockRoot Bytes32 + Source Checkpoint + Target Checkpoint } + type Checkpoint struct { - epoch uint64 - root Bytes32 + Epoch uint64 + Root Bytes32 } + type Bitlist []bool + type Attestation struct { - aggregation_bits Bitlist `ssz-max:"2048"` - data AttestationData - signature SignatureBytes + AggregationBits Bitlist `ssz-max:"2048"` + Data AttestationData + Signature SignatureBytes } + type Deposit struct { - proof [33]Bytes32 //fixed size array - data DepositData + Proof [33]Bytes32 // fixed size array + Data DepositData } + type DepositData struct { - pubkey [48]byte - withdrawal_credentials Bytes32 - amount uint64 - signature SignatureBytes + Pubkey [48]byte + WithdrawalCredentials Bytes32 + Amount uint64 + Signature SignatureBytes } + type SignedVoluntaryExit struct { - message VoluntaryExit - signature SignatureBytes + Message VoluntaryExit + Signature SignatureBytes } + type VoluntaryExit struct { - epoch uint64 - validator_index uint64 + Epoch uint64 + ValidatorIndex uint64 } + type SyncAggregate struct { - sync_committee_bits [64]byte - sync_committee_signature SignatureBytes + SyncCommitteeBits [64]byte + SyncCommitteeSignature SignatureBytes } + type Withdrawal struct { - index uint64 - validator_index uint64 - address Address - amount uint64 -} -type ExecutionPayload struct { //not implemented - parent_hash Bytes32 - fee_recipient Address - state_root Bytes32 - receipts_root Bytes32 - logs_bloom LogsBloom - prev_randao Bytes32 - block_number uint64 - gas_limit uint64 - gas_used uint64 - timestamp uint64 - extra_data [32]byte - base_fee_per_gas uint64 - block_hash Bytes32 - transactions []byte //max length 1073741824 to be implemented - withdrawals []Withdrawal //max length 16 to be implemented - blob_gas_used uint64 - excess_blob_gas uint64 + Index uint64 + ValidatorIndex uint64 + Address Address + Amount uint64 } + +type ExecutionPayload struct { + ParentHash Bytes32 + FeeRecipient Address + StateRoot Bytes32 + ReceiptsRoot Bytes32 + LogsBloom LogsBloom + PrevRandao Bytes32 + BlockNumber uint64 + GasLimit uint64 + GasUsed uint64 + Timestamp uint64 + ExtraData [32]byte + BaseFeePerGas uint64 + BlockHash Bytes32 + Transactions []types.Transaction `ssz-max:"1048576"` + Withdrawals []types.Withdrawal `ssz-max:"16"` + BlobGasUsed *uint64 // Deneb-specific field, use pointer for optionality + ExcessBlobGas *uint64 // Deneb-specific field, use pointer for optionality +} + type SignedBlsToExecutionChange struct { - message BlsToExecutionChange - signature SignatureBytes + Message BlsToExecutionChange + Signature SignatureBytes } + type BlsToExecutionChange struct { - validator_index uint64 - from_bls_pubkey [48]byte -} -type BeaconBlockBody struct { //not implemented - randao_reveal SignatureBytes - eth1_data Eth1Data - graffiti Bytes32 - proposer_slashings []ProposerSlashing //max length 16 to be insured how? - attester_slashings []AttesterSlashing //max length 2 to be ensured - attestations []Attestation //max length 128 to be ensured - deposits []Deposit //max length 16 to be ensured - voluntary_exits SignedVoluntaryExit - sync_aggregate SyncAggregate - execution_payload ExecutionPayload - bls_to_execution_changes []SignedBlsToExecutionChange //max length 16 to be ensured - blob_kzg_commitments [][48]byte //max length 4096 to be ensured + ValidatorIndex uint64 + FromBlsPubkey [48]byte +} + +// BeaconBlockBody represents the body of a beacon block. +type BeaconBlockBody struct { + RandaoReveal SignatureBytes + Eth1Data Eth1Data + Graffiti Bytes32 + ProposerSlashings []ProposerSlashing `ssz-max:"16"` + AttesterSlashings []AttesterSlashing `ssz-max:"2"` + Attestations []Attestation `ssz-max:"128"` + Deposits []Deposit `ssz-max:"16"` + VoluntaryExits []SignedVoluntaryExit `ssz-max:"16"` + SyncAggregate SyncAggregate + ExecutionPayload ExecutionPayload + BlsToExecutionChanges []SignedBlsToExecutionChange `ssz-max:"16"` + BlobKzgCommitments [][48]byte `ssz-max:"4096"` } + type Header struct { - slot uint64 - proposer_index uint64 - parent_root Bytes32 - state_root Bytes32 - body_root Bytes32 + Slot uint64 + ProposerIndex uint64 + ParentRoot Bytes32 + StateRoot Bytes32 + BodyRoot Bytes32 } -type SyncComittee struct { - pubkeys [512]BLSPubKey - aggregate_pubkey BLSPubKey + +type SyncCommittee struct { + Pubkeys [512]BLSPubKey + AggregatePubkey BLSPubKey } + type Update struct { - attested_header Header - next_sync_committee SyncComittee - next_sync_committee_branch []Bytes32 - finalized_header Header - finality_branch []Bytes32 - sync_aggregate SyncAggregate - signature_slot uint64 + AttestedHeader Header + NextSyncCommittee SyncCommittee + NextSyncCommitteeBranch []Bytes32 + FinalizedHeader Header + FinalityBranch []Bytes32 + SyncAggregate SyncAggregate + SignatureSlot uint64 } + type FinalityUpdate struct { - attested_header Header - finalized_header Header - finality_branch []Bytes32 - sync_aggregate SyncAggregate - signature_slot uint64 + AttestedHeader Header + FinalizedHeader Header + FinalityBranch []Bytes32 + SyncAggregate SyncAggregate + SignatureSlot uint64 } + type OptimisticUpdate struct { - attested_header Header - sync_aggregate SyncAggregate - signature_slot uint64 + AttestedHeader Header + SyncAggregate SyncAggregate + SignatureSlot uint64 } + type Bootstrap struct { - header Header - current_sync_committee SyncComittee - current_sync_committee_branch []Bytes32 + Header Header + CurrentSyncCommittee SyncCommittee + CurrentSyncCommitteeBranch []Bytes32 +} + +// ToBytes serializes the Header struct to a byte slice. +func (h *Header) ToBytes() []byte { + var buf bytes.Buffer + enc := codec.NewEncoder(&buf, new(codec.JsonHandle)) + _ = enc.Encode(h) // Ignore error + return buf.Bytes() +} + +func (b *BeaconBlockBody) ToBytes() []byte { + var buf bytes.Buffer + enc := codec.NewEncoder(&buf, new(codec.JsonHandle)) + _ = enc.Encode(b) // Ignore error + return buf.Bytes() +} + +// ToBytes serializes the SyncCommittee struct to a byte slice. +func (sc *SyncCommittee) ToBytes() []byte { + var buf bytes.Buffer + enc := codec.NewEncoder(&buf, new(codec.JsonHandle)) + _ = enc.Encode(sc) // Ignore error + return buf.Bytes() } diff --git a/consensus/rpc/consensus_rpc.go b/consensus/rpc/consensus_rpc.go index 8395c79..b9a7bae 100644 --- a/consensus/rpc/consensus_rpc.go +++ b/consensus/rpc/consensus_rpc.go @@ -6,11 +6,11 @@ import ( // return types not mention and oarameters as well type ConsensusRpc interface { - GetBootstrap(block_root []byte) (consensus_core.Bootstrap, error) + GetBootstrap(block_root [32]byte) (consensus_core.Bootstrap, error) GetUpdates(period uint64, count uint8) ([]consensus_core.Update, error) GetFinalityUpdate() (consensus_core.FinalityUpdate, error) GetOptimisticUpdate() (consensus_core.OptimisticUpdate, error) - GetBlock(slot uint64) (consensus_core.BeaconBlock, error) + GetBlock(slot uint64) (*consensus_core.BeaconBlock, error) ChainId() (uint64, error) } diff --git a/consensus/rpc/nimbus_rpc.go b/consensus/rpc/nimbus_rpc.go index 1f97521..971b2d0 100644 --- a/consensus/rpc/nimbus_rpc.go +++ b/consensus/rpc/nimbus_rpc.go @@ -3,11 +3,12 @@ package rpc import ( "encoding/json" "fmt" - "github.com/BlocSoc-iitr/selene/consensus/consensus_core" "io" "net/http" "strconv" "time" + + "github.com/BlocSoc-iitr/selene/consensus/consensus_core" ) // uses types package @@ -43,15 +44,17 @@ func min(a uint8, b uint8) uint8 { } return b } + type NimbusRpc struct { //ConsensusRpc rpc string } + func NewNimbusRpc(rpc string) *NimbusRpc { return &NimbusRpc{ rpc: rpc} } -func (n *NimbusRpc) GetBootstrap(block_root []byte) (consensus_core.Bootstrap, error) { +func (n *NimbusRpc) GetBootstrap(block_root [32]byte) (consensus_core.Bootstrap, error) { root_hex := fmt.Sprintf("%x", block_root) req := fmt.Sprintf("%s/eth/v1/beacon/light_client/bootstrap/0x%s", n.rpc, root_hex) var res BootstrapResponse @@ -93,14 +96,15 @@ func (n *NimbusRpc) GetOptimisticUpdate() (consensus_core.OptimisticUpdate, erro } return res.data, nil } -func (n *NimbusRpc) GetBlock(slot uint64) (consensus_core.BeaconBlock, error) { +func (n *NimbusRpc) GetBlock(slot uint64) (*consensus_core.BeaconBlock, error) { req := fmt.Sprintf("%s/eth/v2/beacon/blocks/%s", n.rpc, strconv.FormatUint(slot, 10)) var res BeaconBlockResponse err := get(req, &res) if err != nil { - return consensus_core.BeaconBlock{}, fmt.Errorf("block error: %w", err) + return nil, fmt.Errorf("block error: %w", err) } - return res.data.message, nil + + return &res.data.message, nil } func (n *NimbusRpc) ChainId() (uint64, error) { req := fmt.Sprintf("%s/eth/v1/config/spec", n.rpc) @@ -111,6 +115,7 @@ func (n *NimbusRpc) ChainId() (uint64, error) { } return res.data.chain_id, nil } + // BeaconBlock, Update,FinalityUpdate ,OptimisticUpdate,Bootstrap yet to be defined in consensus-core/src/types/mod.go // For now defined in consensus/consensus_core.go type BeaconBlockResponse struct { @@ -138,4 +143,3 @@ type Spec struct { type BootstrapResponse struct { data consensus_core.Bootstrap } - diff --git a/consensus/utils.go b/consensus/utils.go index 4e25578..4b006c5 100644 --- a/consensus/utils.go +++ b/consensus/utils.go @@ -1,15 +1,243 @@ package consensus -// uses types package - -func calculate_sync_period() {} -func is_aggregate_valid() {} -func is_proof_valid() {} -func compute_signing_root() {} -func compute_domian() {} -func compute_fork_data_root() {} -func branch_to_nodes() {} -func bytes32_to_nodes() {} - -type SigningData struct{} -type ForkData struct{} +import ( + "bytes" + "crypto/sha256" + "encoding/json" + "fmt" + "log" + "math" + + "github.com/BlocSoc-iitr/selene/config" + "github.com/BlocSoc-iitr/selene/consensus/consensus_core" + "github.com/BlocSoc-iitr/selene/utils/bls" + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/ethereum/go-ethereum/crypto" +) + +// TreeHashRoot computes the Merkle root from the provided leaves in a flat []byte slice. +func TreeHashRoot(data []byte) ([]byte, error) { + // Convert the input data into a slice of leaves + leaves, err := bytesToLeaves(data) + if err != nil { + return nil, err + } + + nodes := leaves // Start with the leaf nodes + + for len(nodes) > 1 { + var newLevel [][]byte + + // Pair nodes and hash them + for i := 0; i < len(nodes); i += 2 { + if i+1 < len(nodes) { + // Hash pair of nodes + nodeHash := crypto.Keccak256(append(nodes[i], nodes[i+1]...)) + newLevel = append(newLevel, nodeHash) + } else { + // Handle odd number of nodes (carry last node up) + newLevel = append(newLevel, nodes[i]) + } + } + + nodes = newLevel + } + + // Return the root hash + return nodes[0], nil +} + +func bytesToLeaves(data []byte) ([][]byte, error) { + var leaves [][]byte + if err := json.Unmarshal(data, &leaves); err != nil { + return nil, err + } + return leaves, nil +} + +func CalcSyncPeriod(slot uint64) uint64 { + epoch := slot / 32 + return epoch / 256 +} + +// isAggregateValid checks if the provided signature is valid for the given message and public keys. +func isAggregateValid(sigBytes consensus_core.SignatureBytes, msg [32]byte, pks []*bls.G2Point) bool { + var sigInBytes [96]byte + copy(sigInBytes[:], sigBytes[:]) + // Deserialize the signature from bytes + var sig bls12381.G1Affine + if err := sig.Unmarshal(sigInBytes[:]); err != nil { + return false + } + + // Map the message to a point on the curve + msgPoint := bls.MapToCurve(msg) + + // Aggregate the public keys + aggPubKey := bls.AggregatePublicKeys(pks) + + // Prepare the pairing check inputs + P := [2]bls12381.G1Affine{*msgPoint, sig} + Q := [2]bls12381.G2Affine{*aggPubKey.G2Affine, *bls.GetG2Generator()} + + // Perform the pairing check + ok, err := bls12381.PairingCheck(P[:], Q[:]) + if err != nil { + return false + } + return ok +} + +func isProofValid( + attestedHeader *consensus_core.Header, + leafObject []byte, // Single byte slice of the leaf object + branch [][]byte, // Slice of byte slices for the branch + depth, index int, // Depth of the Merkle proof and index of the leaf +) bool { + // If the branch length is not equal to the depth, return false + if len(branch) != depth { + return false + } + + // Compute the root hash of the leaf object + derivedRoot, err := TreeHashRoot(leafObject) + if err != nil { + return false + } + + // Iterate through the branch and compute the Merkle root + for i, node := range branch { + hasher := sha256.New() + + // Check if index / 2^i is odd or even + if (index/int(math.Pow(2, float64(i))))%2 != 0 { + // If odd, hash(node || derived_root) + hasher.Write(node) + hasher.Write(derivedRoot[:]) + } else { + // If even, hash(derived_root || node) + hasher.Write(derivedRoot[:]) + hasher.Write(node) + } + + // Update the derived root + derivedRootNew := sha256.Sum256(hasher.Sum(nil)) + derivedRoot = derivedRootNew[:] + } + + // Compare the final derived root with the attested header's state root + return bytes.Equal(derivedRoot[:], attestedHeader.StateRoot[:]) +} + +func CalculateForkVersion(forks *config.Forks, slot uint64) [4]byte { + epoch := slot / 32 + + switch { + case epoch >= forks.Deneb.Epoch: + return [4]byte(forks.Deneb.ForkVersion) + case epoch >= forks.Capella.Epoch: + return [4]byte(forks.Capella.ForkVersion) + case epoch >= forks.Bellatrix.Epoch: + return [4]byte(forks.Bellatrix.ForkVersion) + case epoch >= forks.Altair.Epoch: + return [4]byte(forks.Altair.ForkVersion) + default: + return [4]byte(forks.Genesis.ForkVersion) + } +} + +func ComputeForkDataRoot(currentVersion [4]byte, genesisValidatorRoot consensus_core.Bytes32) consensus_core.Bytes32 { + forkData := ForkData{ + CurrentVersion: currentVersion, + GenesisValidatorRoot: genesisValidatorRoot, + } + + hash, err := TreeHashRoot(forkData.ToBytes()) + if err != nil { + return consensus_core.Bytes32{} + } + return consensus_core.Bytes32(hash) +} + +// GetParticipatingKeys retrieves the participating public keys from the committee based on the bitfield represented as a byte array. +func GetParticipatingKeys(committee *consensus_core.SyncCommittee, bitfield [64]byte) ([]consensus_core.BLSPubKey, error) { + var pks []consensus_core.BLSPubKey + numBits := len(bitfield) * 8 // Total number of bits + + if len(committee.Pubkeys) > numBits { + return nil, fmt.Errorf("bitfield is too short for the number of public keys") + } + + for i := 0; i < len(bitfield); i++ { + byteVal := bitfield[i] + for bit := 0; bit < 8; bit++ { + if (byteVal & (1 << bit)) != 0 { + index := i*8 + bit + if index >= len(committee.Pubkeys) { + break + } + pks = append(pks, committee.Pubkeys[index]) + } + } + } + + return pks, nil +} + +func ComputeSigningRoot(objectRoot, domain consensus_core.Bytes32) consensus_core.Bytes32 { + signingData := SigningData{ + ObjectRoot: objectRoot, + Domain: domain, + } + hash, err := TreeHashRoot(signingData.ToBytes()) + if err != nil { + return consensus_core.Bytes32{} + } + return consensus_core.Bytes32(hash) +} + +func ComputeDomain(domainType [4]byte, forkDataRoot consensus_core.Bytes32) consensus_core.Bytes32 { + data := append(domainType[:], forkDataRoot[:28]...) + return sha256.Sum256(data) +} + +type SigningData struct { + ObjectRoot consensus_core.Bytes32 + Domain consensus_core.Bytes32 +} + +type ForkData struct { + CurrentVersion [4]byte + GenesisValidatorRoot consensus_core.Bytes32 +} + +func (fd *ForkData) ToBytes() []byte { + data, err := json.Marshal(fd) + if err != nil { + log.Println("Error marshaling ForkData:", err) + return nil // Or return an empty slice, based on your preference + } + return data +} + +func (sd *SigningData) ToBytes() []byte { + data, err := json.Marshal(sd) + if err != nil { + log.Println("Error marshaling SigningData:", err) + return nil // Or return an empty slice, based on your preference + } + return data +} + +func bytes32ToNode(bytes consensus_core.Bytes32) []byte { + return []byte(bytes[:]) +} + +// branchToNodes converts a slice of Bytes32 to a slice of Node +func branchToNodes(branch []consensus_core.Bytes32) ([][]byte, error) { + nodes := make([][]byte, len(branch)) + for i, b32 := range branch { + nodes[i] = bytes32ToNode(b32) + } + return nodes, nil +}