|
| 1 | +// SPDX-License-Identifier: MIT OR Apache-2.0 |
| 2 | +pragma solidity 0.8.18; |
| 3 | + |
| 4 | +import "ds-test/test.sol"; |
| 5 | +import "cheats/Vm.sol"; |
| 6 | + |
| 7 | +// https://github.com/foundry-rs/foundry/issues/8383 |
| 8 | +contract Issue8383Test is DSTest { |
| 9 | + Vm constant vm = Vm(HEVM_ADDRESS); |
| 10 | + |
| 11 | + address internal _verifier; |
| 12 | + |
| 13 | + mapping(bytes32 => bool) internal _vectorTested; |
| 14 | + mapping(bytes32 => bool) internal _vectorResult; |
| 15 | + |
| 16 | + function setUp() public { |
| 17 | + _verifier = address(new P256Verifier()); |
| 18 | + } |
| 19 | + |
| 20 | + function _verifyViaVerifier(bytes32 hash, uint256 r, uint256 s, uint256 x, uint256 y) internal returns (bool) { |
| 21 | + return _verifyViaVerifier(hash, bytes32(r), bytes32(s), bytes32(x), bytes32(y)); |
| 22 | + } |
| 23 | + |
| 24 | + function _verifyViaVerifier(bytes32 hash, bytes32 r, bytes32 s, bytes32 x, bytes32 y) internal returns (bool) { |
| 25 | + bytes memory payload = abi.encode(hash, r, s, x, y); |
| 26 | + if (uint256(y) & 0xff == 0) { |
| 27 | + bytes memory truncatedPayload = abi.encodePacked(hash, r, s, x, bytes31(y)); |
| 28 | + _verifierCall(truncatedPayload); |
| 29 | + } |
| 30 | + if (uint256(keccak256(abi.encode(payload, "1"))) & 0x1f == 0) { |
| 31 | + uint256 r = uint256(keccak256(abi.encode(payload, "2"))); |
| 32 | + payload = abi.encodePacked(payload, new bytes(r & 0xff)); |
| 33 | + } |
| 34 | + bytes32 payloadHash = keccak256(payload); |
| 35 | + if (_vectorTested[payloadHash]) return _vectorResult[payloadHash]; |
| 36 | + _vectorTested[payloadHash] = true; |
| 37 | + return (_vectorResult[payloadHash] = _verifierCall(payload)); |
| 38 | + } |
| 39 | + |
| 40 | + function _verifierCall(bytes memory payload) internal returns (bool) { |
| 41 | + (bool success, bytes memory result) = _verifier.call(payload); |
| 42 | + return abi.decode(result, (bool)); |
| 43 | + } |
| 44 | + |
| 45 | + function testP256VerifyOutOfBounds() public { |
| 46 | + vm.pauseGasMetering(); |
| 47 | + uint256 p = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; |
| 48 | + _verifyViaVerifier(bytes32(0), 1, 1, 1, 1); |
| 49 | + _verifyViaVerifier(bytes32(0), 1, 1, 0, 1); |
| 50 | + _verifyViaVerifier(bytes32(0), 1, 1, 1, 0); |
| 51 | + _verifyViaVerifier(bytes32(0), 1, 1, 1, p); |
| 52 | + _verifyViaVerifier(bytes32(0), 1, 1, p, 1); |
| 53 | + _verifyViaVerifier(bytes32(0), 1, 1, p - 1, 1); |
| 54 | + vm.resumeGasMetering(); |
| 55 | + } |
| 56 | +} |
| 57 | + |
| 58 | +contract P256Verifier { |
| 59 | + uint256 private constant GX = 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296; |
| 60 | + uint256 private constant GY = 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5; |
| 61 | + uint256 private constant P = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; // `A = P - 3`. |
| 62 | + uint256 private constant N = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551; |
| 63 | + uint256 private constant B = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B; |
| 64 | + |
| 65 | + fallback() external payable { |
| 66 | + assembly { |
| 67 | + // For this implementation, we will use the memory without caring about |
| 68 | + // the free memory pointer or zero pointer. |
| 69 | + // The slots `0x00`, `0x20`, `0x40`, `0x60`, will not be accessed for the `Points[16]` array, |
| 70 | + // and can be used for storing other variables. |
| 71 | + |
| 72 | + mstore(0x40, P) // Set `0x40` to `P`. |
| 73 | + |
| 74 | + function jAdd(x1, y1, z1, x2, y2, z2) -> x3, y3, z3 { |
| 75 | + if iszero(z1) { |
| 76 | + x3 := x2 |
| 77 | + y3 := y2 |
| 78 | + z3 := z2 |
| 79 | + leave |
| 80 | + } |
| 81 | + if iszero(z2) { |
| 82 | + x3 := x1 |
| 83 | + y3 := y1 |
| 84 | + z3 := z1 |
| 85 | + leave |
| 86 | + } |
| 87 | + let p := mload(0x40) |
| 88 | + let zz1 := mulmod(z1, z1, p) |
| 89 | + let zz2 := mulmod(z2, z2, p) |
| 90 | + let u1 := mulmod(x1, zz2, p) |
| 91 | + let u2 := mulmod(x2, zz1, p) |
| 92 | + let s1 := mulmod(y1, mulmod(zz2, z2, p), p) |
| 93 | + let s2 := mulmod(y2, mulmod(zz1, z1, p), p) |
| 94 | + let h := addmod(u2, sub(p, u1), p) |
| 95 | + let hh := mulmod(h, h, p) |
| 96 | + let hhh := mulmod(h, hh, p) |
| 97 | + let r := addmod(s2, sub(p, s1), p) |
| 98 | + x3 := addmod(addmod(mulmod(r, r, p), sub(p, hhh), p), sub(p, mulmod(2, mulmod(u1, hh, p), p)), p) |
| 99 | + y3 := addmod(mulmod(r, addmod(mulmod(u1, hh, p), sub(p, x3), p), p), sub(p, mulmod(s1, hhh, p)), p) |
| 100 | + z3 := mulmod(h, mulmod(z1, z2, p), p) |
| 101 | + } |
| 102 | + |
| 103 | + function setJPoint(i, x, y, z) { |
| 104 | + // We will multiply by `0x80` (i.e. `shl(7, i)`) instead |
| 105 | + // since the memory expansion costs are cheaper than doing `mul(0x60, i)`. |
| 106 | + // Also help combine the lookup expression for `u1` and `u2` in `jMultShamir`. |
| 107 | + i := shl(7, i) |
| 108 | + mstore(i, x) |
| 109 | + mstore(add(i, returndatasize()), y) |
| 110 | + mstore(add(i, 0x40), z) |
| 111 | + } |
| 112 | + |
| 113 | + function setJPointDouble(i, j) { |
| 114 | + j := shl(7, j) |
| 115 | + let x := mload(j) |
| 116 | + let y := mload(add(j, returndatasize())) |
| 117 | + let z := mload(add(j, 0x40)) |
| 118 | + let p := mload(0x40) |
| 119 | + let yy := mulmod(y, y, p) |
| 120 | + let zz := mulmod(z, z, p) |
| 121 | + let s := mulmod(4, mulmod(x, yy, p), p) |
| 122 | + let m := addmod(mulmod(3, mulmod(x, x, p), p), mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), p) |
| 123 | + let x2 := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) |
| 124 | + let y2 := addmod(mulmod(m, addmod(s, sub(p, x2), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) |
| 125 | + let z2 := mulmod(2, mulmod(y, z, p), p) |
| 126 | + setJPoint(i, x2, y2, z2) |
| 127 | + } |
| 128 | + |
| 129 | + function setJPointAdd(i, j, k) { |
| 130 | + j := shl(7, j) |
| 131 | + k := shl(7, k) |
| 132 | + let x, y, z := |
| 133 | + jAdd( |
| 134 | + mload(j), |
| 135 | + mload(add(j, returndatasize())), |
| 136 | + mload(add(j, 0x40)), |
| 137 | + mload(k), |
| 138 | + mload(add(k, returndatasize())), |
| 139 | + mload(add(k, 0x40)) |
| 140 | + ) |
| 141 | + setJPoint(i, x, y, z) |
| 142 | + } |
| 143 | + |
| 144 | + let r := calldataload(0x20) |
| 145 | + let n := N |
| 146 | + |
| 147 | + { |
| 148 | + let s := calldataload(0x40) |
| 149 | + if lt(shr(1, n), s) { s := sub(n, s) } |
| 150 | + |
| 151 | + // Perform `modExp(s, N - 2, N)`. |
| 152 | + // After which, we can abuse `returndatasize()` to get `0x20`. |
| 153 | + mstore(0x800, 0x20) |
| 154 | + mstore(0x820, 0x20) |
| 155 | + mstore(0x840, 0x20) |
| 156 | + mstore(0x860, s) |
| 157 | + mstore(0x880, sub(n, 2)) |
| 158 | + mstore(0x8a0, n) |
| 159 | + |
| 160 | + let p := mload(0x40) |
| 161 | + mstore(0x20, xor(3, p)) // Set `0x20` to `A`. |
| 162 | + let Qx := calldataload(0x60) |
| 163 | + let Qy := calldataload(0x80) |
| 164 | + |
| 165 | + if iszero( |
| 166 | + and( // The arguments of `and` are evaluated last to first. |
| 167 | + and( |
| 168 | + and(gt(calldatasize(), 0x9f), and(lt(iszero(r), lt(r, n)), lt(iszero(s), lt(s, n)))), |
| 169 | + eq( |
| 170 | + mulmod(Qy, Qy, p), |
| 171 | + addmod(mulmod(addmod(mulmod(Qx, Qx, p), mload(returndatasize()), p), Qx, p), B, p) |
| 172 | + ) |
| 173 | + ), |
| 174 | + and( |
| 175 | + // We need to check that the `returndatasize` is indeed 32, |
| 176 | + // so that we can return false if the chain does not have the modexp precompile. |
| 177 | + eq(returndatasize(), 0x20), |
| 178 | + staticcall(gas(), 0x05, 0x800, 0xc0, returndatasize(), 0x20) |
| 179 | + ) |
| 180 | + ) |
| 181 | + ) { |
| 182 | + // POC Note: |
| 183 | + // Changing this to `return(0x80, 0x20)` fixes it. |
| 184 | + // Alternatively, adding `if mload(0x8c0) { invalid() }` just before the return also fixes it. |
| 185 | + return(0x8c0, 0x20) |
| 186 | + } |
| 187 | + |
| 188 | + setJPoint(0x01, Qx, Qy, 1) |
| 189 | + setJPoint(0x04, GX, GY, 1) |
| 190 | + setJPointDouble(0x02, 0x01) |
| 191 | + setJPointDouble(0x08, 0x04) |
| 192 | + setJPointAdd(0x03, 0x01, 0x02) |
| 193 | + setJPointAdd(0x05, 0x01, 0x04) |
| 194 | + setJPointAdd(0x06, 0x02, 0x04) |
| 195 | + setJPointAdd(0x07, 0x03, 0x04) |
| 196 | + setJPointAdd(0x09, 0x01, 0x08) |
| 197 | + setJPointAdd(0x0a, 0x02, 0x08) |
| 198 | + setJPointAdd(0x0b, 0x03, 0x08) |
| 199 | + setJPointAdd(0x0c, 0x04, 0x08) |
| 200 | + setJPointAdd(0x0d, 0x01, 0x0c) |
| 201 | + setJPointAdd(0x0e, 0x02, 0x0c) |
| 202 | + setJPointAdd(0x0f, 0x03, 0x0c) |
| 203 | + } |
| 204 | + |
| 205 | + let i := 0 |
| 206 | + let u1 := mulmod(calldataload(0x00), mload(0x00), n) |
| 207 | + let u2 := mulmod(r, mload(0x00), n) |
| 208 | + let y := 0 |
| 209 | + let z := 0 |
| 210 | + let x := 0 |
| 211 | + let p := mload(0x40) |
| 212 | + for {} 1 {} { |
| 213 | + if z { |
| 214 | + let yy := mulmod(y, y, p) |
| 215 | + let zz := mulmod(z, z, p) |
| 216 | + let s := mulmod(4, mulmod(x, yy, p), p) |
| 217 | + let m := |
| 218 | + addmod(mulmod(3, mulmod(x, x, p), p), mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), p) |
| 219 | + let x2 := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) |
| 220 | + let y2 := addmod(mulmod(m, addmod(s, sub(p, x2), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) |
| 221 | + let z2 := mulmod(2, mulmod(y, z, p), p) |
| 222 | + yy := mulmod(y2, y2, p) |
| 223 | + zz := mulmod(z2, z2, p) |
| 224 | + s := mulmod(4, mulmod(x2, yy, p), p) |
| 225 | + m := |
| 226 | + addmod(mulmod(3, mulmod(x2, x2, p), p), mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), p) |
| 227 | + x := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) |
| 228 | + z := mulmod(2, mulmod(y2, z2, p), p) |
| 229 | + y := addmod(mulmod(m, addmod(s, sub(p, x), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) |
| 230 | + } |
| 231 | + for { let o := or(and(shr(245, shl(i, u1)), 0x600), and(shr(247, shl(i, u2)), 0x180)) } o {} { |
| 232 | + let z2 := mload(add(o, 0x40)) |
| 233 | + if iszero(z2) { break } |
| 234 | + if iszero(z) { |
| 235 | + x := mload(o) |
| 236 | + y := mload(add(o, returndatasize())) |
| 237 | + z := z2 |
| 238 | + break |
| 239 | + } |
| 240 | + let zz1 := mulmod(z, z, p) |
| 241 | + let zz2 := mulmod(z2, z2, p) |
| 242 | + let u1_ := mulmod(x, zz2, p) |
| 243 | + let s1 := mulmod(y, mulmod(zz2, z2, p), p) |
| 244 | + let h := addmod(mulmod(mload(o), zz1, p), sub(p, u1_), p) |
| 245 | + let hh := mulmod(h, h, p) |
| 246 | + let hhh := mulmod(h, hh, p) |
| 247 | + let r_ := addmod(mulmod(mload(add(o, returndatasize())), mulmod(zz1, z, p), p), sub(p, s1), p) |
| 248 | + x := addmod(addmod(mulmod(r_, r_, p), sub(p, hhh), p), sub(p, mulmod(2, mulmod(u1_, hh, p), p)), p) |
| 249 | + y := addmod(mulmod(r_, addmod(mulmod(u1_, hh, p), sub(p, x), p), p), sub(p, mulmod(s1, hhh, p)), p) |
| 250 | + z := mulmod(h, mulmod(z, z2, p), p) |
| 251 | + break |
| 252 | + } |
| 253 | + // Just unroll twice. Fully unrolling will only save around 1% to 2% gas, but make the |
| 254 | + // bytecode very bloated, which may incur more runtime costs after Verkle. |
| 255 | + // See: https://notes.ethereum.org/%40vbuterin/verkle_tree_eip |
| 256 | + // It's very unlikely that Verkle will come before the P256 precompile. But who knows? |
| 257 | + if z { |
| 258 | + let yy := mulmod(y, y, p) |
| 259 | + let zz := mulmod(z, z, p) |
| 260 | + let s := mulmod(4, mulmod(x, yy, p), p) |
| 261 | + let m := |
| 262 | + addmod(mulmod(3, mulmod(x, x, p), p), mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), p) |
| 263 | + let x2 := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) |
| 264 | + let y2 := addmod(mulmod(m, addmod(s, sub(p, x2), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) |
| 265 | + let z2 := mulmod(2, mulmod(y, z, p), p) |
| 266 | + yy := mulmod(y2, y2, p) |
| 267 | + zz := mulmod(z2, z2, p) |
| 268 | + s := mulmod(4, mulmod(x2, yy, p), p) |
| 269 | + m := |
| 270 | + addmod(mulmod(3, mulmod(x2, x2, p), p), mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), p) |
| 271 | + x := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) |
| 272 | + z := mulmod(2, mulmod(y2, z2, p), p) |
| 273 | + y := addmod(mulmod(m, addmod(s, sub(p, x), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) |
| 274 | + } |
| 275 | + for { let o := or(and(shr(243, shl(i, u1)), 0x600), and(shr(245, shl(i, u2)), 0x180)) } o {} { |
| 276 | + let z2 := mload(add(o, 0x40)) |
| 277 | + if iszero(z2) { break } |
| 278 | + if iszero(z) { |
| 279 | + x := mload(o) |
| 280 | + y := mload(add(o, returndatasize())) |
| 281 | + z := z2 |
| 282 | + break |
| 283 | + } |
| 284 | + let zz1 := mulmod(z, z, p) |
| 285 | + let zz2 := mulmod(z2, z2, p) |
| 286 | + let u1_ := mulmod(x, zz2, p) |
| 287 | + let s1 := mulmod(y, mulmod(zz2, z2, p), p) |
| 288 | + let h := addmod(mulmod(mload(o), zz1, p), sub(p, u1_), p) |
| 289 | + let hh := mulmod(h, h, p) |
| 290 | + let hhh := mulmod(h, hh, p) |
| 291 | + let r_ := addmod(mulmod(mload(add(o, returndatasize())), mulmod(zz1, z, p), p), sub(p, s1), p) |
| 292 | + x := addmod(addmod(mulmod(r_, r_, p), sub(p, hhh), p), sub(p, mulmod(2, mulmod(u1_, hh, p), p)), p) |
| 293 | + y := addmod(mulmod(r_, addmod(mulmod(u1_, hh, p), sub(p, x), p), p), sub(p, mulmod(s1, hhh, p)), p) |
| 294 | + z := mulmod(h, mulmod(z, z2, p), p) |
| 295 | + break |
| 296 | + } |
| 297 | + i := add(i, 4) |
| 298 | + if eq(i, 256) { break } |
| 299 | + } |
| 300 | + |
| 301 | + if iszero(z) { |
| 302 | + mstore(returndatasize(), iszero(r)) |
| 303 | + return(returndatasize(), 0x20) |
| 304 | + } |
| 305 | + |
| 306 | + // Perform `modExp(z, P - 2, P)`. |
| 307 | + // `0x800`, `0x820, `0x840` are still set to `0x20`. |
| 308 | + mstore(0x860, z) |
| 309 | + mstore(0x880, sub(p, 2)) |
| 310 | + mstore(0x8a0, p) |
| 311 | + |
| 312 | + mstore( |
| 313 | + returndatasize(), |
| 314 | + and( // The arguments of `and` are evaluated last to first. |
| 315 | + eq(mod(mulmod(x, mulmod(mload(returndatasize()), mload(returndatasize()), p), p), n), r), |
| 316 | + staticcall(gas(), 0x05, 0x800, 0xc0, returndatasize(), returndatasize()) |
| 317 | + ) |
| 318 | + ) |
| 319 | + return(returndatasize(), returndatasize()) |
| 320 | + } |
| 321 | + } |
| 322 | +} |
0 commit comments