Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Add metadata field on the certificate #151

Merged
merged 3 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion agglayer/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ type Certificate struct {
NewLocalExitRoot [32]byte `json:"new_local_exit_root"`
BridgeExits []*BridgeExit `json:"bridge_exits"`
ImportedBridgeExits []*ImportedBridgeExit `json:"imported_bridge_exits"`
Metadata common.Hash `json:"metadata"`
}

// Hash returns a hash that uniquely identifies the certificate
Expand Down Expand Up @@ -110,6 +111,20 @@ func (c *Certificate) Hash() common.Hash {
)
}

// HashToSign is the actual hash that needs to be signed by the aggsender
// as expected by the agglayer
func (c *Certificate) HashToSign() common.Hash {
globalIndexHashes := make([][]byte, len(c.ImportedBridgeExits))
for i, importedBridgeExit := range c.ImportedBridgeExits {
globalIndexHashes[i] = importedBridgeExit.GlobalIndex.Hash().Bytes()
}

return crypto.Keccak256Hash(
c.NewLocalExitRoot[:],
crypto.Keccak256Hash(globalIndexHashes...).Bytes(),
)
}

// SignedCertificate is the struct that contains the certificate and the signature of the signer
type SignedCertificate struct {
*Certificate
Expand Down Expand Up @@ -138,7 +153,10 @@ type GlobalIndex struct {

func (g *GlobalIndex) Hash() common.Hash {
return crypto.Keccak256Hash(
bridgesync.GenerateGlobalIndex(g.MainnetFlag, g.RollupIndex, g.LeafIndex).Bytes())
cdkcommon.BigIntToLittleEndianBytes(
bridgesync.GenerateGlobalIndex(g.MainnetFlag, g.RollupIndex, g.LeafIndex),
),
)
}

// BridgeExit represents a token bridge exit
Expand Down Expand Up @@ -379,6 +397,7 @@ type CertificateHeader struct {
CertificateID common.Hash `json:"certificate_id"`
NewLocalExitRoot common.Hash `json:"new_local_exit_root"`
Status CertificateStatus `json:"status"`
Metadata common.Hash `json:"metadata"`
}

func (c CertificateHeader) String() string {
Expand Down
4 changes: 2 additions & 2 deletions agglayer/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import (
)

const (
expectedSignedCertificateEmptyMetadataJSON = `{"network_id":1,"height":1,"prev_local_exit_root":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"new_local_exit_root":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"bridge_exits":[{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":[]}],"imported_bridge_exits":[{"bridge_exit":{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":[]},"claim_data":null,"global_index":{"mainnet_flag":false,"rollup_index":1,"leaf_index":1}}],"signature":{"r":"0x0000000000000000000000000000000000000000000000000000000000000000","s":"0x0000000000000000000000000000000000000000000000000000000000000000","odd_y_parity":false}}`
expectedSignedCertificateyMetadataJSON = `{"network_id":1,"height":1,"prev_local_exit_root":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"new_local_exit_root":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"bridge_exits":[{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":[1,2,3]}],"imported_bridge_exits":[{"bridge_exit":{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":[]},"claim_data":null,"global_index":{"mainnet_flag":false,"rollup_index":1,"leaf_index":1}}],"signature":{"r":"0x0000000000000000000000000000000000000000000000000000000000000000","s":"0x0000000000000000000000000000000000000000000000000000000000000000","odd_y_parity":false}}`
expectedSignedCertificateEmptyMetadataJSON = `{"network_id":1,"height":1,"prev_local_exit_root":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"new_local_exit_root":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"bridge_exits":[{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":[]}],"imported_bridge_exits":[{"bridge_exit":{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":[]},"claim_data":null,"global_index":{"mainnet_flag":false,"rollup_index":1,"leaf_index":1}}],"metadata":"0x0000000000000000000000000000000000000000000000000000000000000000","signature":{"r":"0x0000000000000000000000000000000000000000000000000000000000000000","s":"0x0000000000000000000000000000000000000000000000000000000000000000","odd_y_parity":false}}`
expectedSignedCertificateyMetadataJSON = `{"network_id":1,"height":1,"prev_local_exit_root":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"new_local_exit_root":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"bridge_exits":[{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":[1,2,3]}],"imported_bridge_exits":[{"bridge_exit":{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":[]},"claim_data":null,"global_index":{"mainnet_flag":false,"rollup_index":1,"leaf_index":1}}],"metadata":"0x0000000000000000000000000000000000000000000000000000000000000000","signature":{"r":"0x0000000000000000000000000000000000000000000000000000000000000000","s":"0x0000000000000000000000000000000000000000000000000000000000000000","odd_y_parity":false}}`
)

func TestMarshalJSON(t *testing.T) {
Expand Down
20 changes: 17 additions & 3 deletions aggsender/aggsender.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"math/big"
"os"
"time"

Expand Down Expand Up @@ -153,7 +154,7 @@ func (a *AggSender) sendCertificate(ctx context.Context) error {

a.log.Infof("building certificate for block: %d to block: %d", fromBlock, toBlock)

certificate, err := a.buildCertificate(ctx, bridges, claims, lastSentCertificateInfo)
certificate, err := a.buildCertificate(ctx, bridges, claims, lastSentCertificateInfo, toBlock)
if err != nil {
return fmt.Errorf("error building certificate: %w", err)
}
Expand Down Expand Up @@ -209,7 +210,8 @@ func (a *AggSender) saveCertificateToFile(signedCertificate *agglayer.SignedCert
func (a *AggSender) buildCertificate(ctx context.Context,
bridges []bridgesync.Bridge,
claims []bridgesync.Claim,
lastSentCertificateInfo aggsendertypes.CertificateInfo) (*agglayer.Certificate, error) {
lastSentCertificateInfo aggsendertypes.CertificateInfo,
toBlock uint64) (*agglayer.Certificate, error) {
if len(bridges) == 0 && len(claims) == 0 {
return nil, errNoBridgesAndClaims
}
Expand Down Expand Up @@ -245,6 +247,7 @@ func (a *AggSender) buildCertificate(ctx context.Context,
BridgeExits: bridgeExits,
ImportedBridgeExits: importedBridgeExits,
Height: height,
Metadata: createCertificateMetadata(toBlock),
}, nil
}

Expand Down Expand Up @@ -412,13 +415,19 @@ func (a *AggSender) getImportedBridgeExits(

// signCertificate signs a certificate with the sequencer key
func (a *AggSender) signCertificate(certificate *agglayer.Certificate) (*agglayer.SignedCertificate, error) {
hashToSign := certificate.Hash()
hashToSign := certificate.HashToSign()

sig, err := crypto.Sign(hashToSign.Bytes(), a.sequencerKey)
if err != nil {
return nil, err
}

a.log.Infof("Signed certificate. sequencer address: %s. New local exit root: %s Hash signed: %s",
crypto.PubkeyToAddress(a.sequencerKey.PublicKey).String(),
common.BytesToHash(certificate.NewLocalExitRoot[:]).String(),
hashToSign.String(),
)

r, s, isOddParity, err := extractSignatureData(sig)
if err != nil {
return nil, err
Expand Down Expand Up @@ -500,3 +509,8 @@ func extractSignatureData(signature []byte) (r, s common.Hash, isOddParity bool,

return
}

// createCertificateMetadata creates a certificate metadata from given input
func createCertificateMetadata(toBlock uint64) common.Hash {
return common.BigToHash(new(big.Int).SetUint64(toBlock))
}
5 changes: 4 additions & 1 deletion aggsender/aggsender_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@ func TestBuildCertificate(t *testing.T) {
bridges []bridgesync.Bridge
claims []bridgesync.Claim
lastSentCertificateInfo aggsendertypes.CertificateInfo
toBlock uint64
mockFn func()
expectedCert *agglayer.Certificate
expectedError bool
Expand Down Expand Up @@ -532,10 +533,12 @@ func TestBuildCertificate(t *testing.T) {
NewLocalExitRoot: common.HexToHash("0x123"),
Height: 1,
},
toBlock: 10,
expectedCert: &agglayer.Certificate{
NetworkID: 1,
PrevLocalExitRoot: common.HexToHash("0x123"),
NewLocalExitRoot: common.HexToHash("0x789"),
Metadata: createCertificateMetadata(10),
BridgeExits: []*agglayer.BridgeExit{
{
LeafType: agglayer.LeafTypeAsset,
Expand Down Expand Up @@ -686,7 +689,7 @@ func TestBuildCertificate(t *testing.T) {
l1infoTreeSyncer: mockL1InfoTreeSyncer,
log: log.WithFields("test", "unittest"),
}
cert, err := aggSender.buildCertificate(context.Background(), tt.bridges, tt.claims, tt.lastSentCertificateInfo)
cert, err := aggSender.buildCertificate(context.Background(), tt.bridges, tt.claims, tt.lastSentCertificateInfo, tt.toBlock)

if tt.expectedError {
require.Error(t, err)
Expand Down
17 changes: 17 additions & 0 deletions common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,20 @@ func NewKeyFromKeystore(cfg types.KeystoreFileConfig) (*ecdsa.PrivateKey, error)
}
return key.PrivateKey, nil
}

// BigIntToLittleEndianBytes converts a big.Int to a 32-byte little-endian representation.
// big.Int is capped to 32 bytes
func BigIntToLittleEndianBytes(n *big.Int) []byte {
// Get the absolute value in big-endian byte slice
beBytes := n.Bytes()

// Initialize a 32-byte array for the result
leBytes := make([]byte, common.HashLength)

// Fill the array in reverse order to convert to little-endian
for i := 0; i < len(beBytes) && i < common.HashLength; i++ {
leBytes[i] = beBytes[len(beBytes)-1-i]
}

return leBytes
}
61 changes: 61 additions & 0 deletions common/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package common

import (
"fmt"
"math/big"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)

func TestAsLittleEndianSlice(t *testing.T) {
t.Parallel()

tests := []struct {
name string
input *big.Int
expected []byte
}{
{
name: "Zero value",
input: big.NewInt(0),
expected: make([]byte, 32),
},
{
name: "Positive value",
input: big.NewInt(123456789),
expected: append([]byte{21, 205, 91, 7}, make([]byte, 28)...),
},
{
name: "Negative value",
input: big.NewInt(-123456789),
expected: append([]byte{21, 205, 91, 7}, make([]byte, 28)...),
},
{
name: "Large positive value",
input: new(big.Int).SetBytes([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}),
expected: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
},
}

for _, tt := range tests {
tt := tt

t.Run(tt.name, func(t *testing.T) {
t.Parallel()

result := BigIntToLittleEndianBytes(tt.input)
require.Len(t, result, common.HashLength)

for i := range result {
require.Equal(t, tt.expected[i], result[i],
fmt.Sprintf("expected byte at index %d to be %x, got %x", i, tt.expected[i], result[i]))
}
})
}
}
Loading