@@ -7,20 +7,179 @@ pragma experimental ABIEncoderV2;
7
7
import "./ReceiverGetters.sol " ;
8
8
import "./ReceiverStructs.sol " ;
9
9
import "../libraries/external/BytesLib.sol " ;
10
+ import "../libraries/external/UnsafeCalldataBytesLib.sol " ;
11
+
12
+ error VmVersionIncompatible ();
13
+ error SignatureIndexesNotAscending ();
10
14
11
15
contract ReceiverMessages is ReceiverGetters {
12
16
using BytesLib for bytes ;
13
17
14
18
/// @dev parseAndVerifyVM serves to parse an encodedVM and wholy validate it for consumption
19
+ /// WARNING: it intentionally sets vm.signatures to an empty array since it is not needed after it is validated in this function
20
+ /// since it not used anywhere. If you need to use vm.signatures, use parseVM and verifyVM separately.
15
21
function parseAndVerifyVM (
16
22
bytes calldata encodedVM
17
23
)
18
24
public
19
25
view
20
26
returns (ReceiverStructs.VM memory vm , bool valid , string memory reason )
21
27
{
22
- vm = parseVM (encodedVM);
23
- (valid, reason) = verifyVM (vm);
28
+ uint index = 0 ;
29
+ unchecked {
30
+ {
31
+ vm.version = UnsafeCalldataBytesLib.toUint8 (encodedVM, index);
32
+ index += 1 ;
33
+ if (vm.version != 1 ) {
34
+ revert VmVersionIncompatible ();
35
+ }
36
+ }
37
+
38
+ ReceiverStructs.GuardianSet memory guardianSet;
39
+ {
40
+ vm.guardianSetIndex = UnsafeCalldataBytesLib.toUint32 (
41
+ encodedVM,
42
+ index
43
+ );
44
+ index += 4 ;
45
+ guardianSet = getGuardianSet (vm.guardianSetIndex);
46
+
47
+ /**
48
+ * @dev Checks whether the guardianSet has zero keys
49
+ * WARNING: This keys check is critical to ensure the guardianSet has keys present AND to ensure
50
+ * that guardianSet key size doesn't fall to zero and negatively impact quorum assessment. If guardianSet
51
+ * key length is 0 and vm.signatures length is 0, this could compromise the integrity of both vm and
52
+ * signature verification.
53
+ */
54
+ if (guardianSet.keys.length == 0 ) {
55
+ return (vm, false , "invalid guardian set " );
56
+ }
57
+
58
+ /// @dev Checks if VM guardian set index matches the current index (unless the current set is expired).
59
+ if (
60
+ vm.guardianSetIndex != getCurrentGuardianSetIndex () &&
61
+ guardianSet.expirationTime < block .timestamp
62
+ ) {
63
+ return (vm, false , "guardian set has expired " );
64
+ }
65
+ }
66
+
67
+ // Parse Signatures
68
+ uint256 signersLen = UnsafeCalldataBytesLib.toUint8 (
69
+ encodedVM,
70
+ index
71
+ );
72
+ index += 1 ;
73
+ {
74
+ // 66 is the length of each signature
75
+ // 1 (guardianIndex) + 32 (r) + 32 (s) + 1 (v)
76
+ uint hashIndex = index + (signersLen * 66 );
77
+ if (hashIndex > encodedVM.length ) {
78
+ return (vm, false , "invalid signature length " );
79
+ }
80
+ // Hash the body
81
+ vm.hash = keccak256 (
82
+ abi.encodePacked (
83
+ keccak256 (
84
+ UnsafeCalldataBytesLib.sliceFrom (
85
+ encodedVM,
86
+ hashIndex
87
+ )
88
+ )
89
+ )
90
+ );
91
+ }
92
+
93
+ {
94
+ uint8 lastIndex = 0 ;
95
+ for (uint i = 0 ; i < signersLen; i++ ) {
96
+ ReceiverStructs.Signature memory sig;
97
+ sig.guardianIndex = UnsafeCalldataBytesLib.toUint8 (
98
+ encodedVM,
99
+ index
100
+ );
101
+ index += 1 ;
102
+
103
+ sig.r = UnsafeCalldataBytesLib.toBytes32 (encodedVM, index);
104
+ index += 32 ;
105
+ sig.s = UnsafeCalldataBytesLib.toBytes32 (encodedVM, index);
106
+ index += 32 ;
107
+ sig.v =
108
+ UnsafeCalldataBytesLib.toUint8 (encodedVM, index) +
109
+ 27 ;
110
+ index += 1 ;
111
+ bool signatureValid;
112
+ string memory invalidReason;
113
+ (signatureValid, invalidReason) = verifySignature (
114
+ i,
115
+ lastIndex,
116
+ vm.hash,
117
+ sig.guardianIndex,
118
+ sig.r,
119
+ sig.s,
120
+ sig.v,
121
+ guardianSet.keys[sig.guardianIndex]
122
+ );
123
+ if (! signatureValid) {
124
+ return (vm, false , invalidReason);
125
+ }
126
+ lastIndex = sig.guardianIndex;
127
+ }
128
+ }
129
+
130
+ /**
131
+ * @dev We're using a fixed point number transformation with 1 decimal to deal with rounding.
132
+ * WARNING: This quorum check is critical to assessing whether we have enough Guardian signatures to validate a VM
133
+ * if making any changes to this, obtain additional peer review. If guardianSet key length is 0 and
134
+ * vm.signatures length is 0, this could compromise the integrity of both vm and signature verification.
135
+ */
136
+
137
+ if (
138
+ (((guardianSet.keys.length * 10 ) / 3 ) * 2 ) / 10 + 1 > signersLen
139
+ ) {
140
+ return (vm, false , "no quorum " );
141
+ }
142
+
143
+ // purposely setting vm.signatures to empty array since we don't need it anymore
144
+ // and we've already verified it above
145
+ vm.signatures = new ReceiverStructs.Signature [](0 );
146
+
147
+ // Parse the body
148
+ vm.timestamp = UnsafeCalldataBytesLib.toUint32 (encodedVM, index);
149
+ index += 4 ;
150
+
151
+ vm.nonce = UnsafeCalldataBytesLib.toUint32 (encodedVM, index);
152
+ index += 4 ;
153
+
154
+ vm.emitterChainId = UnsafeCalldataBytesLib.toUint16 (
155
+ encodedVM,
156
+ index
157
+ );
158
+ index += 2 ;
159
+
160
+ vm.emitterAddress = UnsafeCalldataBytesLib.toBytes32 (
161
+ encodedVM,
162
+ index
163
+ );
164
+ index += 32 ;
165
+
166
+ vm.sequence = UnsafeCalldataBytesLib.toUint64 (encodedVM, index);
167
+ index += 8 ;
168
+
169
+ vm.consistencyLevel = UnsafeCalldataBytesLib.toUint8 (
170
+ encodedVM,
171
+ index
172
+ );
173
+ index += 1 ;
174
+
175
+ if (index > encodedVM.length ) {
176
+ return (vm, false , "invalid payload length " );
177
+ }
178
+
179
+ vm.payload = UnsafeCalldataBytesLib.sliceFrom (encodedVM, index);
180
+
181
+ return (vm, true , "" );
182
+ }
24
183
}
25
184
26
185
/**
@@ -84,6 +243,27 @@ contract ReceiverMessages is ReceiverGetters {
84
243
return (true , "" );
85
244
}
86
245
246
+ function verifySignature (
247
+ uint i ,
248
+ uint8 lastIndex ,
249
+ bytes32 hash ,
250
+ uint8 guardianIndex ,
251
+ bytes32 r ,
252
+ bytes32 s ,
253
+ uint8 v ,
254
+ address guardianSetKey
255
+ ) private pure returns (bool valid , string memory reason ) {
256
+ /// Ensure that provided signature indices are ascending only
257
+ if (i != 0 && guardianIndex <= lastIndex) {
258
+ revert SignatureIndexesNotAscending ();
259
+ }
260
+ /// Check to see if the signer of the signature does not match a specific Guardian key at the provided index
261
+ if (ecrecover (hash, v, r, s) != guardianSetKey) {
262
+ return (false , "VM signature invalid " );
263
+ }
264
+ return (true , "" );
265
+ }
266
+
87
267
/**
88
268
* @dev verifySignatures serves to validate arbitrary sigatures against an arbitrary guardianSet
89
269
* - it intentionally does not solve for expectations within guardianSet (you should use verifyVM if you need these protections)
@@ -98,21 +278,20 @@ contract ReceiverMessages is ReceiverGetters {
98
278
uint8 lastIndex = 0 ;
99
279
for (uint i = 0 ; i < signatures.length ; i++ ) {
100
280
ReceiverStructs.Signature memory sig = signatures[i];
101
-
102
- /// Ensure that provided signature indices are ascending only
103
- require (
104
- i == 0 || sig.guardianIndex > lastIndex,
105
- "signature indices must be ascending "
106
- );
107
- lastIndex = sig.guardianIndex;
108
-
109
- /// Check to see if the signer of the signature does not match a specific Guardian key at the provided index
110
- if (
111
- ecrecover (hash, sig.v, sig.r, sig.s) !=
281
+ (valid, reason) = verifySignature (
282
+ i,
283
+ lastIndex,
284
+ hash,
285
+ sig.guardianIndex,
286
+ sig.r,
287
+ sig.s,
288
+ sig.v,
112
289
guardianSet.keys[sig.guardianIndex]
113
- ) {
114
- return (false , "VM signature invalid " );
290
+ );
291
+ if (! valid) {
292
+ return (false , reason);
115
293
}
294
+ lastIndex = sig.guardianIndex;
116
295
}
117
296
118
297
/// If we are here, we've validated that the provided signatures are valid for the provided guardianSet
@@ -130,7 +309,9 @@ contract ReceiverMessages is ReceiverGetters {
130
309
131
310
vm.version = encodedVM.toUint8 (index);
132
311
index += 1 ;
133
- require (vm.version == 1 , "VM version incompatible " );
312
+ if (vm.version != 1 ) {
313
+ revert VmVersionIncompatible ();
314
+ }
134
315
135
316
vm.guardianSetIndex = encodedVM.toUint32 (index);
136
317
index += 4 ;
0 commit comments