Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
85 changes: 83 additions & 2 deletions arbutil/espresso_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
"time"

espressoTypes "github.com/EspressoSystems/espresso-network/sdks/go/types"
Expand All @@ -16,6 +18,8 @@ import (
const MAX_ATTESTATION_QUOTE_SIZE int = 4 * 1024
const LEN_SIZE int = 8
const INDEX_SIZE int = 8
const HEADER_SIZE = 1
const HEADER_LEN = 4

type SubmittedEspressoTx struct {
Hash string
Expand All @@ -24,6 +28,26 @@ type SubmittedEspressoTx struct {
SubmittedAt time.Time `rlp:"optional"`
}

type Header struct {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you also add comments on what each of this fields signifies?

Version TransactionVersion
TransactionType TransactionType
Reserved uint16
Comment thread
lukeiannucci marked this conversation as resolved.
Outdated
}

type TransactionType uint8

const (
Legacy TransactionType = 0
EphemeralKey TransactionType = 1
Timeboost TransactionType = 2
)

type TransactionVersion uint8

const (
V0 TransactionVersion = 0
)

func BuildRawHotShotPayload(
msgPositions []MessageIndex,
msgFetcher func(MessageIndex) ([]byte, error),
Expand Down Expand Up @@ -67,10 +91,32 @@ func SignHotShotPayload(
return nil, err
}

header := Header{
Version: V0,
TransactionType: EphemeralKey,
Reserved: 0,
}

encoded := []byte{
uint8(header.Version),
uint8(header.TransactionType),
byte(header.Reserved >> 8),
byte(header.Reserved),
}

headerBuf := make([]byte, HEADER_SIZE)
size := len(encoded)
if size > math.MaxUint8 {
return nil, fmt.Errorf("encoded data too large: %d bytes (max %d)", len(encoded), math.MaxUint8)
}
headerBuf[0] = uint8(size)
result := headerBuf
result = append(result, encoded...)

quoteSizeBuf := make([]byte, LEN_SIZE)
binary.BigEndian.PutUint64(quoteSizeBuf, uint64(len(quote)))
// Put the signature first. That would help easier parsing.
result := quoteSizeBuf
result = append(result, quoteSizeBuf...)
result = append(result, quote...)
result = append(result, unsigned...)

Expand All @@ -88,7 +134,42 @@ func ValidateIfPayloadIsInBlock(p []byte, payloads []espressoTypes.Bytes) bool {
return validated
}

func ParseHotShotPayload(payload []byte) (signature []byte, userDataHash []byte, indices []uint64, messages [][]byte, err error) {
func ParseHotshotPayloadForHeader(tx []byte) *TransactionType {
if len(tx) < HEADER_SIZE+HEADER_LEN {
log.Warn("hotshot transaction is too small for a header")
return nil
}
// Try and see if there is a header
size := tx[0]
var transactionType TransactionType
if size == HEADER_LEN {
encoded := tx[HEADER_SIZE : HEADER_SIZE+HEADER_LEN]
header := Header{
Version: TransactionVersion(encoded[0]),
TransactionType: TransactionType(encoded[1]),
Reserved: binary.BigEndian.Uint16(encoded[2:4]),
}

if header.Version == V0 && header.Reserved == 0 {
switch header.TransactionType {
case Legacy:
transactionType = Legacy
case EphemeralKey:
transactionType = EphemeralKey
case Timeboost:
transactionType = Timeboost
default:
return nil
}
}
}
return &transactionType
}

func ParseHotShotPayload(payload []byte, txType *TransactionType) (signature []byte, userDataHash []byte, indices []uint64, messages [][]byte, err error) {
if txType != nil {
payload = payload[HEADER_SIZE+HEADER_LEN:]
}
if len(payload) < LEN_SIZE {
return nil, nil, nil, nil, errors.New("payload too short to parse signature size")
}
Expand Down
5 changes: 3 additions & 2 deletions arbutil/espresso_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ func TestParsePayload(t *testing.T) {
}

// Parse the signed payload
signature, userDataHash, indices, messages, err := ParseHotShotPayload(signedPayload)
txType := ParseHotshotPayloadForHeader(signedPayload)
signature, userDataHash, indices, messages, err := ParseHotShotPayload(signedPayload, txType)
if err != nil {
t.Fatalf("failed to parse payload: %v", err)
}
Expand Down Expand Up @@ -123,7 +124,7 @@ func TestParsePayloadInvalidCases(t *testing.T) {

for _, tc := range invalidPayloads {
t.Run(tc.description, func(t *testing.T) {
_, _, _, _, err := ParseHotShotPayload(tc.payload)
_, _, _, _, err := ParseHotShotPayload(tc.payload, nil)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a test to show that it will work with new transaction format as well as legacy?

if err == nil {
t.Errorf("expected error for case '%s', but got none", tc.description)
}
Expand Down
88 changes: 67 additions & 21 deletions espressostreamer/espresso_streamer.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,11 +246,73 @@ func (s *EspressoStreamer) verifyLegacy(attestation []byte, signature [32]byte)
return err
}

func (s *EspressoStreamer) verify(data []byte, userDataHashArr [32]byte, l1Height uint64, transactionType *arbutil.TransactionType) error {
if transactionType != nil {
txType := *transactionType
switch txType {
case arbutil.Legacy:
if s.espressoSGXVerifier == nil {
return fmt.Errorf("failed to verify attestation quote, legacy header found but sgx verifier is nil")
}
err := s.verifyLegacy(data, userDataHashArr)
if err != nil {
log.Warn("failed to verify attestation quote", "err", err)
return err
}
case arbutil.EphemeralKey:
err := s.verifyBatchPosterSignature(data, userDataHashArr, l1Height)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we create functions for each case instead of all this in one verify function?

if err == nil {
return nil
} else if strings.Contains(err.Error(), ErrRetryParsingHotShotPayload.Error()) {
log.Warn("retrying to verify batch poster signature", "err", err)
return err
} else {
log.Warn("failed to verify batch poster signature", "err", err)
return err
}
default:
return fmt.Errorf("failed to verify transaction, received unexpected transaction type: %d", txType)
}
} else {
var success bool
err := s.verifyBatchPosterSignature(data, userDataHashArr, l1Height)
if err == nil {
success = true
} else if strings.Contains(err.Error(), ErrRetryParsingHotShotPayload.Error()) {
log.Warn("retrying to verify batch poster signature", "err", err)
return err
} else {
log.Warn("failed to verify batch poster signature", "err", err)
}

if !success {
if s.espressoSGXVerifier == nil {
return fmt.Errorf("failed to verify attestation quote, legacy header found but sgx verifier is nil. %w", err)
}
err = s.verifyLegacy(data, userDataHashArr)
if err != nil {
log.Warn("failed to verify attestation quote", "err", err)
return err
}
}
}
return nil
}

func (s *EspressoStreamer) parseEspressoTransaction(tx espressoTypes.Bytes, l1Height uint64) ([]*MessageWithMetadataAndPos, error) {
signature, userDataHash, indices, messages, err := arbutil.ParseHotShotPayload(tx)
transactionType := arbutil.ParseHotshotPayloadForHeader(tx)
signature, userDataHash, indices, messages, err := arbutil.ParseHotShotPayload(tx, transactionType)
if err != nil {
log.Warn("failed to parse hotshot payload", "err", err)
return nil, err
if transactionType != nil {
// in case somehow we parsed a header and there wasnt one, try again
transactionType = nil
signature, userDataHash, indices, messages, err = arbutil.ParseHotShotPayload(tx, transactionType)
if err != nil {
log.Warn("failed to parse hotshot payload", "err", err)
return nil, err
}
}

}
if len(messages) == 0 {
return nil, ErrPayloadHadNoMessages
Expand All @@ -260,25 +322,9 @@ func (s *EspressoStreamer) parseEspressoTransaction(tx espressoTypes.Bytes, l1He
return nil, ErrUserDataHashNot32Bytes
}

userDataHashArr := [32]byte(userDataHash)

var success bool
err = s.verifyBatchPosterSignature(signature, userDataHashArr, l1Height)
if err == nil {
success = true
} else if strings.Contains(err.Error(), ErrRetryParsingHotShotPayload.Error()) {
log.Warn("retrying to verify batch poster signature", "err", err)
err = s.verify(signature, [32]byte(userDataHash), l1Height, transactionType)
if err != nil {
return nil, err
} else {
log.Warn("failed to verify batch poster signature", "err", err)
}

if !success && s.espressoSGXVerifier != nil {
err = s.verifyLegacy(signature, userDataHashArr)
if err != nil {
log.Warn("failed to verify attestation quote", "err", err)
return nil, err
}
}

result := []*MessageWithMetadataAndPos{}
Expand Down
3 changes: 2 additions & 1 deletion system_tests/espresso/generate/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,8 @@ func ConvertEspressoTransactionsInBlockToMessages(
// We can parse the transactions to get the messages
// This is a mock function that simulates the parsing of the transaction
// In a real scenario, this would be replaced with the actual parsing logic
_, _, _, messages, err := arbutil.ParseHotShotPayload(tx)
transactionType := arbutil.ParseHotshotPayloadForHeader(tx)
_, _, _, messages, err := arbutil.ParseHotShotPayload(tx, transactionType)
if err != nil {
return nil, fmt.Errorf("encountered error while parsing transaction: %w", err)
}
Expand Down
Loading