1
1
/// This contract can be used (with some small additions to create an economic
2
2
/// incentive for the claimant to answer) to claim computation results for
3
- /// scrypt (actually only the part where we do not have precompiled contracts)
4
- /// and interactively convict a false claim.
5
- /// Scrypt first applies a certain hash function to the input in 1024 iterations.
3
+ /// scrypt and interactively convict a false claim.
4
+ /// Scrypt starts and ends with a certain key derivation function that can be
5
+ /// more or less directly computed by the EVM. The computation in the middle
6
+ /// is much more complicated and needs interactive verification.
7
+ /// Here, scrypt applies a certain hash function to the input in 1024 iterations.
6
8
/// Then it applies the hash function another 1024 times but looks up an
7
9
/// additional input in the table of the first 1024 values where the look
8
10
/// up index depends on the first input.
46
48
///
47
49
/// Disclaimer: This has not been tested and is only meant as a proof of concept
48
50
/// and a way to compute the gas costs.
49
- contract Scrypt {
50
- address claimant;
51
- address challenger;
51
+ contract ScryptVerifier {
52
+
53
+ struct VerificationSession {
54
+ address claimant;
55
+ address challenger;
56
+ bytes data;
57
+ bytes32 hash;
58
+ uint16 [] queries;
59
+ uint [4 ][] values;
60
+ }
61
+ VerificationSession[] sessions;
52
62
53
- function Scrypt (uint [4 ] _input , uint [4 ] _output ) {
54
- claimant = msg .sender ;
55
- queries.length = 2 ;
56
- queries.push (0 );
57
- queries.push (2048 );
58
- values.push (_input);
59
- values.push (_output);
60
- }
61
- event Convicted ();
63
+ /// Claim that scrypt(data) == hash. With reference to above,
64
+ /// values[0] = pbkdf2(data) (the "input") and
65
+ /// pbkdf2(values[2048]) should be equal to hash, values[2048] is called "output".
66
+ function claimComputation (bytes _data , bytes32 _hash ) {
67
+ sessions.push (VerificationSession ({
68
+ claimant: msg .sender ,
69
+ challenger: address (0 ),
70
+ data: _data,
71
+ hash: _hash,
72
+ queries: new uint16 [](0 ),
73
+ values: new uint [4 ][](0 )
74
+ }));
75
+ NewClaim (sessions.length - 1 );
76
+ }
77
+ event NewClaim (uint sessionId );
78
+ event Convicted (uint sessionId );
79
+ event NewQuery (uint sessionId );
80
+ event NewResponse (uint sessionId );
62
81
63
- modifier onlyClaimant () { if (msg .sender != claimant) throw ; _ }
64
- modifier onlyChallenger () {
65
- if (challenger == 0 ) challenger = msg .sender ;
66
- else if (msg .sender != challenger) throw ;
82
+ modifier onlyClaimant (uint id ) { if (msg .sender != sessions[id].claimant) throw ; _ }
83
+ modifier onlyChallenger (uint id ) {
84
+ var session = sessions[id];
85
+ if (session.challenger == 0 ) session.challenger = msg .sender ;
86
+ else if (msg .sender != session.challenger) throw ;
67
87
_
68
88
}
69
89
70
- uint16 [] public queries;
71
90
/// Challenger queries claimant for the value on a wire `_i`.
72
91
/// Value 0 is the input, value 1024 is the first input to the second
73
92
/// half of the computation, value 2048 is the output.
74
- function query (uint16 _i ) onlyChallenger {
93
+ function query (uint session , uint16 _i ) onlyChallenger (session) {
75
94
if (_i > 2048 ) throw ;
76
- queries.push (_i);
95
+ sessions[session].queries.push (_i);
96
+ NewQuery (session);
77
97
}
78
98
79
- uint [4 ][] public values;
80
99
/// Claimant responds to challenge, committing to a value.
81
- function respond (uint [4 ] _value ) onlyClaimant {
82
- if (values.length >= queries.length ) throw ;
83
- values.push (_value);
100
+ function respond (uint session , uint [4 ] _value ) onlyClaimant (session) {
101
+ var s = sessions[session];
102
+ if (s.values.length >= s.queries.length ) throw ;
103
+ s.values.push (_value);
104
+ NewResponse (session);
84
105
}
85
106
86
107
/// Convicts the claimant to have provided inputs and outputs for a single
@@ -89,24 +110,48 @@ contract Scrypt {
89
110
/// q1 is the query index of the first input, q2 the query index of
90
111
/// the output and q2 is the query index of the auxiliary input only
91
112
/// used in the second half of the scrypt computation.
92
- function convict (uint q1 , uint q2 , uint q3 ) {
93
- var i = queries[q1];
94
- if (queries[q2] != i + 1 ) throw ;
95
- var input = values[q1];
96
- var output = values[q2];
113
+ function convict (uint session , uint q1 , uint q2 , uint q3 ) onlyChallenger (session) {
114
+ var s = sessions[session];
115
+ var i = s.queries[q1];
116
+ if (s.queries[q2] != i + 1 ) throw ;
117
+ var input = s.values[q1];
118
+ var output = s.values[q2];
97
119
if (i < 1024 ) {
98
120
if (! verifyFirstHalf (input, output))
99
- Convicted ();
121
+ Convicted (session );
100
122
} else {
101
- var auxIndex = queries[q3];
123
+ var auxIndex = s. queries[q3];
102
124
if (auxIndex != (input[2 ] / 0x100000000000000000000000000000000000000000000000000000000 ) % 1024 )
103
125
throw ;
104
- var auxInput = values[q3];
126
+ var auxInput = s. values[q3];
105
127
if (! verifySecondHalf (input, auxInput, output))
106
- Convicted ();
128
+ Convicted (session );
107
129
}
108
130
}
109
131
132
+ /// Convicts the claimant to have provided an incorrect value for value[0].
133
+ function convictInitial (uint session , uint q ) onlyChallenger (session) {
134
+ var s = sessions[session];
135
+ if (s.queries[q] != 0 ) throw ;
136
+ var v = s.values[q];
137
+ var h = KeyDeriv.pbkdf2 (s.data, s.data, 128 );
138
+ if (uint (h[0 ]) != v[0 ] || uint (h[1 ]) != v[1 ] || uint (h[2 ]) != v[2 ] || uint (h[3 ]) != v[3 ])
139
+ Convicted (session);
140
+ }
141
+
142
+ /// Convicts the claimant to have provided an incorrect value for value[2048].
143
+ function convictFinal (uint session , uint q ) onlyChallenger (session) {
144
+ var s = sessions[session];
145
+ if (s.queries[q] != 2048 ) throw ;
146
+ var v = s.values[q];
147
+ bytes memory val = new bytes (128 );
148
+ for (uint i = 0 ; i < 128 ; i ++ )
149
+ val[i] = byte (uint8 (v[i / 32 ] / 2 ** ((32 - (i % 32 )) * 8 )));
150
+ var h = KeyDeriv.pbkdf2 (val, val, 32 );
151
+ if (h[0 ] != s.hash)
152
+ Convicted (session);
153
+ }
154
+
110
155
/// Verifies a salsa step in the first half of the scrypt computation.
111
156
function verifyFirstHalf (uint [4 ] input , uint [4 ] output ) constant returns (bool ) {
112
157
var (a, b, c, d) = Salsa8.round (input[0 ], input[1 ], input[2 ], input[3 ]);
@@ -121,6 +166,48 @@ contract Scrypt {
121
166
return verifyFirstHalf (input, output);
122
167
}
123
168
169
+ // /// This function can be used to compute the correct response to a
170
+ // /// challenge. It is only intended to be called without a transaction
171
+ // /// because it consumes tremendous amounts of gas.
172
+ // function computeResponse(uint16 _i) constant returns (uint[4]) {
173
+ // if (_i <= 1024)
174
+ // return computeResponseFirstHalf(_i);
175
+ // else
176
+ // return computeResponseSecondHalf(_i);
177
+ // }
178
+ // function computeResponseSecondHalf(uint16 _i) constant returns (uint[4]) {
179
+ // uint[4 * 1024] memory lookup;
180
+ // uint a = values[0][0];
181
+ // uint b = values[0][1];
182
+ // uint c = values[0][2];
183
+ // uint d = values[0][3];
184
+ // uint l = 0;
185
+ // lookup[l++] = a;
186
+ // lookup[l++] = b;
187
+ // lookup[l++] = c;
188
+ // lookup[l++] = d;
189
+ // for (uint16 i = 1; i <= 1024; i++) {
190
+ // (a, b, c, d) = Salsa8.round(a, b, c, d);
191
+ // lookup[l++] = a;
192
+ // lookup[l++] = b;
193
+ // lookup[l++] = c;
194
+ // lookup[l++] = d;
195
+ // }
196
+ // for (; i <= _i; i++) {
197
+ // uint auxIndex = ((c / 0x100000000000000000000000000000000000000000000000000000000) % 1024) * 4;
198
+ // (a, b, c, d) = Salsa8.round(a ^ lookup[auxIndex], b ^ lookup[auxIndex + 1], c ^ lookup[auxIndex + 2], d ^ lookup[auxIndex + 3]);
199
+ // }
200
+ // return [a, b, c, d];
201
+ // }
202
+ // function computeResponseFirstHalf(uint16 _i) constant internal returns (uint[4]) {
203
+ // uint a = values[0][0];
204
+ // uint b = values[0][1];
205
+ // uint c = values[0][2];
206
+ // uint d = values[0][3];
207
+ // for (uint16 i = 1; i <= _i; i++)
208
+ // (a, b, c, d) = Salsa8.round(a, b, c, d);
209
+ // return [a, b, c, d];
210
+ // }
124
211
}
125
212
126
213
library Salsa8 {
@@ -199,3 +286,31 @@ library Salsa8 {
199
286
return (_a, _b, _c, _d);
200
287
}
201
288
}
289
+ library KeyDeriv {
290
+ function hmacsha256 (bytes key , bytes message ) constant returns (bytes32 ) {
291
+ bytes32 keyl;
292
+ bytes32 keyr;
293
+ uint i;
294
+ if (key.length > 64 ) {
295
+ keyl = sha256 (key);
296
+ } else {
297
+ for (i = 0 ; i < key.length && i < 32 ; i++ )
298
+ keyl |= bytes32 (uint (key[i]) * 2 ** (8 * (31 - i)));
299
+ for (i = 32 ; i < key.length && i < 64 ; i++ )
300
+ keyr |= bytes32 (uint (key[i]) * 2 ** (8 * (63 - i)));
301
+ }
302
+ bytes32 threesix = 0x3636363636363636363636363636363636363636363636363636363636363636 ;
303
+ bytes32 fivec = 0x5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c ;
304
+ return sha256 (fivec ^ keyl, fivec ^ keyr, sha256 (threesix ^ keyl, threesix ^ keyr, message));
305
+ }
306
+ /// PBKDF2 restricted to c=1, hash = hmacsha256 and dklen being a multiple of 32 not larger than 128
307
+ function pbkdf2 (bytes key , bytes salt , uint dklen ) constant returns (bytes32 [4 ] r ) {
308
+ var msg = new bytes (salt.length + 4 );
309
+ for (uint i = 0 ; i < salt.length ; i++ )
310
+ msg[i] = salt[i];
311
+ for (i = 0 ; i * 32 < dklen; i++ ) {
312
+ msg[msg .length - 1 ] = bytes1 (uint8 (i + 1 ));
313
+ r[i] = hmacsha256 (key, msg);
314
+ }
315
+ }
316
+ }
0 commit comments