storage.gno
3.99 Kb · 162 lines
1package storage
2
3import (
4 "bytes"
5 "crypto/keccak256"
6 "encoding/binary"
7 "errors"
8
9 "gno.land/p/onbloc/encoding/rlp"
10 "gno.land/p/onbloc/verifier/evm/mpt"
11)
12
13var (
14 ErrInvalidRootLength = errors.New("storage: root must be 32 bytes")
15 ErrInvalidKeyLength = errors.New("storage: key must be 32 bytes")
16 ErrInvalidPathLength = errors.New("storage: path must be 32 bytes")
17 ErrInvalidValueLength = errors.New("storage: value must be 32 bytes")
18 ErrValueMismatch = errors.New("storage: value mismatch")
19)
20
21// Proof is the storage proof shape returned by eth_getProof for one slot,
22// normalized to bytes. Key and Value are 32-byte EVM words. Proof contains the
23// raw RLP-encoded trie nodes from root to leaf.
24//
25// It is also the normalized form of Union's
26// ethereum_light_client_types::StorageProof wire type:
27// https://github.com/unionlabs/union/blob/main/lib/ethereum-light-client-types/src/storage_proof.rs
28type Proof struct {
29 Key []byte
30 Value []byte
31 Proof [][]byte
32}
33
34// VerifyProof verifies that proof.Key stores proof.Value under storageRoot.
35// Ethereum storage tries are keyed by keccak256(slot), and the leaf payload is
36// the RLP encoding of the storage value with leading zero bytes removed.
37func VerifyProof(storageRoot []byte, proof Proof) error {
38 err := validateWord(storageRoot, ErrInvalidRootLength)
39 if err != nil {
40 return err
41 }
42
43 err = validateWord(proof.Key, ErrInvalidKeyLength)
44 if err != nil {
45 return err
46 }
47
48 err = validateWord(proof.Value, ErrInvalidValueLength)
49 if err != nil {
50 return err
51 }
52
53 path := keccak256.Sum256(proof.Key)
54
55 return mpt.Verify(storageRoot, path[:], encodeStorageValue(proof.Value), proof.Proof)
56}
57
58// VerifyAbsence verifies that proof.Key is absent under storageRoot. The Value
59// field is intentionally ignored for absence proofs because eth_getProof returns
60// zero for missing slots.
61func VerifyAbsence(storageRoot []byte, proof Proof) error {
62 err := validateWord(storageRoot, ErrInvalidRootLength)
63 if err != nil {
64 return err
65 }
66
67 err = validateWord(proof.Key, ErrInvalidKeyLength)
68 if err != nil {
69 return err
70 }
71
72 path := keccak256.Sum256(proof.Key)
73
74 return mpt.VerifyAbsence(storageRoot, path[:], proof.Proof)
75}
76
77// CheckValue verifies only the value reported by the proof object. This is
78// useful when a caller receives an expected 32-byte commitment separately.
79func CheckValue(proof Proof, expected []byte) error {
80 err := validateWord(proof.Value, ErrInvalidValueLength)
81 if err != nil {
82 return err
83 }
84
85 err = validateWord(expected, ErrInvalidValueLength)
86 if err != nil {
87 return err
88 }
89
90 if !bytes.Equal(proof.Value, expected) {
91 return ErrValueMismatch
92 }
93
94 return nil
95}
96
97// StoragePath returns the MPT path used for an EVM storage slot.
98func StoragePath(slot []byte) ([32]byte, error) {
99 err := validateWord(slot, ErrInvalidKeyLength)
100 if err != nil {
101 return [32]byte{}, err
102 }
103
104 return keccak256.Sum256(slot), nil
105}
106
107// IBCCommitmentKey mirrors Union's ethereum::ibc_commitment_key for
108// mapping(bytes32 => bytes32) commitments at slot 0.
109//
110// Union reference:
111// https://github.com/unionlabs/union/blob/main/lib/unionlabs/src/ethereum.rs
112func IBCCommitmentKey(path []byte) ([32]byte, error) {
113 err := validateWord(path, ErrInvalidPathLength)
114 if err != nil {
115 return [32]byte{}, err
116 }
117
118 var input [64]byte
119
120 copy(input[0:32], path)
121
122 return keccak256.Sum256(input[:]), nil
123}
124
125// EncodeStorageValue returns the leaf payload expected by the storage trie.
126func EncodeStorageValue(word []byte) ([]byte, error) {
127 err := validateWord(word, ErrInvalidValueLength)
128 if err != nil {
129 return nil, err
130 }
131
132 return encodeStorageValue(word), nil
133}
134
135func validateWord(word []byte, err error) error {
136 if len(word) != 32 {
137 return err
138 }
139
140 return nil
141}
142
143func encodeStorageValue(word []byte) []byte {
144 return rlp.EncodeBytes(trimLeadingZeroes(word))
145}
146
147func trimLeadingZeroes(word []byte) []byte {
148 i := 0
149
150 for i < len(word) && word[i] == 0 {
151 i++
152 }
153
154 return word[i:]
155}
156
157func WordFromUint64(v uint64) []byte {
158 out := make([]byte, 32)
159 binary.BigEndian.PutUint64(out[24:], v)
160
161 return out
162}