From d36fe682b6640f98a57ff34742a6f6a77cec0adc Mon Sep 17 00:00:00 2001 From: chriseth Date: Sat, 19 Dec 2015 00:29:20 +0100 Subject: [PATCH 1/4] Solidity scrypt implementation Partially done. --- scrypt/scrypt.sol | 71 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 scrypt/scrypt.sol diff --git a/scrypt/scrypt.sol b/scrypt/scrypt.sol new file mode 100644 index 0000000..d3176d0 --- /dev/null +++ b/scrypt/scrypt.sol @@ -0,0 +1,71 @@ +contract Scrypt { + uint constant m0 = 0x100000000000000000000000000000000000000000000000000000000; + uint constant m1 = 0x1000000000000000000000000000000000000000000000000; + uint constant m2 = 0x10000000000000000000000000000000000000000; + uint constant m3 = 0x100000000000000000000000000000000; + uint constant m4 = 0x1000000000000000000000000; + uint constant m5 = 0x10000000000000000; + uint constant m6 = 0x100000000; + uint constant m7 = 0x1; + function quarter(uint32 y0, uint32 y1, uint32 y2, uint32 y3) + internal returns (uint32, uint32, uint32, uint32) + { + uint32 t; + t = y0 + y3; + y1 = y1 ^ ((t * 2**7) | (t / 2**(32-7))); + t = y1 + y0; + y2 = y2 ^ ((t * 2**9) | (t / 2**(32-9))); + t = y2 + y1; + y3 = y3 ^ ((t * 2**13) | (t / 2**(32-13))); + t = y3 + y2; + y0 = y0 ^ ((t * 2**18) | (t / 2**(32-18))); + return (y0, y1, y2, y3); + } + function get(uint data, uint word) internal returns (uint32 x) + { + return uint32(data / 2**(256 - word * 32 - 32)); + } + function put(uint x, uint word) internal returns (uint) { + return x * 2**(256 - word * 32 - 32); + } + function rowround(uint first, uint second) internal returns (uint f, uint s) + { + var (a,b,c,d) = quarter(uint32(first / m0), uint32(first / m1), uint32(first / m2), uint32(first / m3)); + f = (((((uint(a) * 2**32) | uint(b)) * 2 ** 32) | uint(c)) * 2**32) | uint(d); + (b,c,d,a) = quarter(uint32(first / m5), uint32(first / m6), uint32(first / m7), uint32(first / m4)); + f = (((((((f * 2**32) | uint(a)) * 2**32) | uint(b)) * 2 ** 32) | uint(c)) * 2**32) | uint(d); + (c,d,a,b) = quarter(uint32(second / m2), uint32(second / m3), uint32(second / m0), uint32(second / m1)); + s = (((((uint(a) * 2**32) | uint(b)) * 2 ** 32) | uint(c)) * 2**32) | uint(d); + (d,a,b,c) = quarter(uint32(second / m7), uint32(second / m4), uint32(second / m5), uint32(second / m6)); + s = (((((((s * 2**32) | uint(a)) * 2**32) | uint(b)) * 2 ** 32) | uint(c)) * 2**32) | uint(d); + } + function columnround(uint first, uint second) internal returns (uint f, uint s) + { + var (a,b,c,d) = quarter(uint32(first / m0), uint32(first / m4), uint32(second / m0), uint32(second / m4)); + f = (uint(a) * m0) | (uint(b) * m4); + s = (uint(c) * m0) | (uint(d) * m4); + (a,b,c,d) = quarter(uint32(first / m5), uint32(second / m1), uint32(second / m5), uint32(first / m1)); + f |= (uint(a) * m5) | (uint(d) * m1); + s |= (uint(b) * m1) | (uint(c) * m5); + (a,b,c,d) = quarter(uint32(second / m2), uint32(second / m6), uint32(first / m2), uint32(first / m6)); + f |= (uint(c) * m2) | (uint(d) * m6); + s |= (uint(a) * m2) | (uint(b) * m6); + (a,b,c,d) = quarter(uint32(second / m7), uint32(first / m3), uint32(first / m7), uint32(second / m3)); + f |= (uint(b) * m3) | (uint(c) * m7); + s |= (uint(a) * m7) | (uint(d) * m3); + } + function salsa8(uint _first, uint _second) constant returns (uint rfirst, uint rsecond) { + uint first = _first; + uint second = _second; + for (uint i = 0; i < 8; i += 2) + { + (first, second) = columnround(first, second); + (first, second) = rowround(first, second); + } + for (i = 0; i < 8; i++) + { + rfirst |= put(get(_first, i) + get(first, i), i); + rsecond |= put(get(_second, i) + get(second, i), i); + } + } +} From db21bd7735d49a9464ed81a4f4c9903422b660a5 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 19 Jan 2016 00:52:17 +0100 Subject: [PATCH 2/4] Added draft for interactive verification. --- scrypt/scrypt.sol | 89 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 13 deletions(-) diff --git a/scrypt/scrypt.sol b/scrypt/scrypt.sol index d3176d0..40804b7 100644 --- a/scrypt/scrypt.sol +++ b/scrypt/scrypt.sol @@ -1,4 +1,62 @@ contract Scrypt { + /// Verifies a salsa step in the first half of the scrypt computation. + function verifyFirstHalf(uint[4] input, uint[4] output) constant returns (bool) { + var (a, b, c, d) = Salsa8.round(input[0], input[1], input[2], input[3]); + return (a == output[0] && b == output[1] && c == output[2] && d == output[3]); + } + /// Verifies a salsa step in the second half of the scrypt computation. + function verifySecondHalf(uint[4] input, uint[4] vinput, uint[4] output) constant returns (bool) { + input[0] ^= vinput[0]; + input[1] ^= vinput[1]; + input[2] ^= vinput[2]; + input[3] ^= vinput[3]; + return verifyFirstHalf(input, output); + } + + event Convicted(); + + uint[] queries; + /// Challenger queries claimant for the inputs and outputs in step `_step`. + function query(uint _step) { + if (queries.length > commits.length) throw; + queries.push(_step); + } + + uint[4][3][] commits; + /// Claimant responds to challenge, commiting to a value. + function respond(uint[4][3] _response) { + if (commits.length >= queries.length) throw; + uint step = queries[queries.length - 1]; + if (step < 1024 && !verifyFirstHalf(_response[0], _response[1])) { + Convicted(); + return; + } else if (step >= 1024 && !verifySecondHalf(_response[0], _response[1], _response[2])) { + Convicted(); + return; + } + + commits.push(_response); + } + + /// Invalid claims in direct computational steps are checked before + /// storing the response. Another source of inconsistency could be + /// different values at wires. + function convictFirstHalfWire(uint q1, uint q2) { + var step1 = queries[q1]; + var step2 = queries[q2]; + if (step2 != step1 + 1 || step1 > 1024) throw; + if (commits[q1][1][0] != commits[q2][0][0] || + commits[q1][1][1] != commits[q2][0][1] || + commits[q1][1][2] != commits[q2][0][2] || + commits[q1][1][3] != commits[q2][0][3] + ) + Convicted(); + } + //@TODO convictSecondHalfWire + //@TODO convictCrossWire: check that in step k with (input, vinput), + // we have vinput = input_step[input % 32] +} +library Salsa8 { uint constant m0 = 0x100000000000000000000000000000000000000000000000000000000; uint constant m1 = 0x1000000000000000000000000000000000000000000000000; uint constant m2 = 0x10000000000000000000000000000000000000000; @@ -54,18 +112,23 @@ contract Scrypt { f |= (uint(b) * m3) | (uint(c) * m7); s |= (uint(a) * m7) | (uint(d) * m3); } - function salsa8(uint _first, uint _second) constant returns (uint rfirst, uint rsecond) { - uint first = _first; - uint second = _second; - for (uint i = 0; i < 8; i += 2) - { - (first, second) = columnround(first, second); - (first, second) = rowround(first, second); - } - for (i = 0; i < 8; i++) - { - rfirst |= put(get(_first, i) + get(first, i), i); - rsecond |= put(get(_second, i) + get(second, i), i); - } + function salsa20_8(uint _first, uint _second) internal returns (uint rfirst, uint rsecond) { + uint first = _first; + uint second = _second; + for (uint i = 0; i < 8; i += 2) + { + (first, second) = columnround(first, second); + (first, second) = rowround(first, second); + } + for (i = 0; i < 8; i++) + { + rfirst |= put(get(_first, i) + get(first, i), i); + rsecond |= put(get(_second, i) + get(second, i), i); + } + } + function round(uint _a, uint _b, uint _c, uint _d) constant returns (uint, uint, uint, uint) { + (_a, _b) = salsa20_8(_a ^ _c, _b ^ _d); + (_c, _d) = salsa20_8(_a ^ _c, _b ^ _d); + return (_a, _b, _c, _d); } } From e9003436e420e8dbb2d364bf3aadeda35ce8ac73 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 19 Jan 2016 15:46:53 +0100 Subject: [PATCH 3/4] Finish interactive protocol. --- scrypt/scrypt.sol | 151 +++++++++++++++++++++++++++++++++------------- 1 file changed, 109 insertions(+), 42 deletions(-) diff --git a/scrypt/scrypt.sol b/scrypt/scrypt.sol index 40804b7..fddb62d 100644 --- a/scrypt/scrypt.sol +++ b/scrypt/scrypt.sol @@ -1,4 +1,112 @@ +/// This contract can be used (with some small additions to create an economic +/// incentive for the claimant to answer) to claim computation results for +/// scrypt (actually only the part where we do not have precompiled contracts) +/// and interactively convict a false claim. +/// Scrypt first applies a certain hash function to the input in 1024 iterations. +/// Then it applies the hash function another 1024 times but looks up an +/// additional input in the table of the first 1024 values where the look +/// up index depends on the first input. +/// Formally: Assume that H computes the salsa8 hash function as defined below. +/// If value[0] is the input and value[2048] is the output, we have the following +/// relation: +/// value[i] = H(value[i-1]) for 0 < i <= 1024 +/// value[i] = H(value[i-1] ^ value[value[i-1] % 1024]) for 1024 < i <= 2048 +/// Strictly, not the lowermost byte in value[i-1] is used for addressing, +/// but that does not matter here. +/// +/// In this contract, a challenger can ask for each of the value[i]. If the +/// claimant was wrong and the challenger asks in a clever way, she can +/// force the claimant to commit to values for a certain step that do not +/// match the computation done in the actual step. +/// +/// The strategy is as follows: +/// We start with a binary search for a point where value[i] is correct but +/// value[i+1] is incorrect (if the response is correct we go up, if it is +/// incorrect we go down). +/// If i <= 1024, the challenger can directly call convict because +/// the commited values do not match the computation. +/// If this ends up at a point i > 1024 it is more complicated because of +/// the second auxiliary input. The challenger has to compute the index +/// of the auxiliary input (value[i] % 1024) and query that. If it is correct, +/// we can again call convict because the output will not match the committed +/// value. If the auxiliary input value is incorrect, it might be that the +/// computation still matches. In this case, we cannot directly call convict +/// but we found an incorrect value at an index smaller than i. This means +/// that we can continue our binary search inside the first half and +/// will end up at a point i' <= 1024 where we can directyl call convict. +/// +/// Overall, this should take not more than 22 rounds. Each round costs approximately +/// 100000 gas and a call to convict is about 60000 gas. +/// This means in total, convicting a false claim should not cost more than +/// around two million gas. +/// Of course, as usual, having to convict a false claimant is only a matter +/// of last resort, because every claimant knows that a false claim can be +/// detected and if this is connected with deposits from both sides that +/// go to the winning party, a false claim will probably not be attempted. +/// +/// Disclaimer: This has not been tested and is only meant as a proof of concept +/// and a way to compute the gas costs. contract Scrypt { + address claimant; + address challenger; + + function Scrypt(uint[4] _input, uint[4] _output) { + claimant = msg.sender; + queries.length = 2; + queries.push(0); + queries.push(2048); + values.push(_input); + values.push(_output); + } + event Convicted(); + + modifier onlyClaimant() { if (msg.sender != claimant) throw; _ } + modifier onlyChallenger() { + if (challenger == 0) challenger = msg.sender; + else if (msg.sender != challenger) throw; + _ + } + + uint16[] public queries; + /// Challenger queries claimant for the value on a wire `_i`. + /// Value 0 is the input, value 1024 is the first input to the second + /// half of the computation, value 2048 is the output. + function query(uint16 _i) onlyChallenger { + if (_i > 2048) throw; + queries.push(_i); + } + + uint[4][] public values; + /// Claimant responds to challenge, committing to a value. + function respond(uint[4] _value) onlyClaimant { + if (values.length >= queries.length) throw; + values.push(_value); + } + + /// Convicts the claimant to have provided inputs and outputs for a single + /// step that do not match the computation of the step. + /// q1, q2 and q3 are query indices providing the relevant values. + /// q1 is the query index of the first input, q2 the query index of + /// the output and q2 is the query index of the auxiliary input only + /// used in the second half of the scrypt computation. + function convict(uint q1, uint q2, uint q3) { + var i = queries[q1]; + if (queries[q2] != i + 1) throw; + var input = values[q1]; + var output = values[q2]; + if (i < 1024) { + if (!verifyFirstHalf(input, output)) + Convicted(); + } else { + var auxIndex = queries[q3]; + if (auxIndex != (input[2] / 0x100000000000000000000000000000000000000000000000000000000) % 1024) + throw; + var auxInput = values[q3]; + if (!verifySecondHalf(input, auxInput, output)) + Convicted(); + } + } + /// Verifies a salsa step in the first half of the scrypt computation. function verifyFirstHalf(uint[4] input, uint[4] output) constant returns (bool) { var (a, b, c, d) = Salsa8.round(input[0], input[1], input[2], input[3]); @@ -13,49 +121,8 @@ contract Scrypt { return verifyFirstHalf(input, output); } - event Convicted(); - - uint[] queries; - /// Challenger queries claimant for the inputs and outputs in step `_step`. - function query(uint _step) { - if (queries.length > commits.length) throw; - queries.push(_step); - } - - uint[4][3][] commits; - /// Claimant responds to challenge, commiting to a value. - function respond(uint[4][3] _response) { - if (commits.length >= queries.length) throw; - uint step = queries[queries.length - 1]; - if (step < 1024 && !verifyFirstHalf(_response[0], _response[1])) { - Convicted(); - return; - } else if (step >= 1024 && !verifySecondHalf(_response[0], _response[1], _response[2])) { - Convicted(); - return; - } - - commits.push(_response); - } - - /// Invalid claims in direct computational steps are checked before - /// storing the response. Another source of inconsistency could be - /// different values at wires. - function convictFirstHalfWire(uint q1, uint q2) { - var step1 = queries[q1]; - var step2 = queries[q2]; - if (step2 != step1 + 1 || step1 > 1024) throw; - if (commits[q1][1][0] != commits[q2][0][0] || - commits[q1][1][1] != commits[q2][0][1] || - commits[q1][1][2] != commits[q2][0][2] || - commits[q1][1][3] != commits[q2][0][3] - ) - Convicted(); - } - //@TODO convictSecondHalfWire - //@TODO convictCrossWire: check that in step k with (input, vinput), - // we have vinput = input_step[input % 32] } + library Salsa8 { uint constant m0 = 0x100000000000000000000000000000000000000000000000000000000; uint constant m1 = 0x1000000000000000000000000000000000000000000000000; From fda68a2551885503c43b17d525b6fc34d959442f Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 19 Jan 2016 15:50:39 +0100 Subject: [PATCH 4/4] Added tx costs. --- scrypt/scrypt.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scrypt/scrypt.sol b/scrypt/scrypt.sol index fddb62d..8166eb0 100644 --- a/scrypt/scrypt.sol +++ b/scrypt/scrypt.sol @@ -36,9 +36,9 @@ /// will end up at a point i' <= 1024 where we can directyl call convict. /// /// Overall, this should take not more than 22 rounds. Each round costs approximately -/// 100000 gas and a call to convict is about 60000 gas. +/// 140000 gas and a call to convict is about 80000 gas. /// This means in total, convicting a false claim should not cost more than -/// around two million gas. +/// around three million gas. /// Of course, as usual, having to convict a false claimant is only a matter /// of last resort, because every claimant knows that a false claim can be /// detected and if this is connected with deposits from both sides that