From 36c84f58b64a66e68cd5e86883a372f96ae2bb2b Mon Sep 17 00:00:00 2001 From: Eric Mastro Date: Mon, 11 Apr 2022 22:59:59 +1000 Subject: [PATCH] WIP: Swap over to AltBn254 solidity lib Still need to figure out how to stringify TauZero and verify the signature. --- contracts/Proofs.sol | 165 +++++++++++--------- contracts/ecc/AltBn254.sol | 309 +++++++++++++++++++++++++++++++++++++ 2 files changed, 405 insertions(+), 69 deletions(-) create mode 100644 contracts/ecc/AltBn254.sol diff --git a/contracts/Proofs.sol b/contracts/Proofs.sol index 6272597..e2d60a3 100644 --- a/contracts/Proofs.sol +++ b/contracts/Proofs.sol @@ -2,6 +2,7 @@ pragma solidity >=0.6.0 <=0.8.13; import "bls-solidity/contracts/BN256G1.sol"; +import "./ecc/AltBn254.sol"; import "elliptic-curve-solidity/contracts/EllipticCurve.sol"; contract Proofs { @@ -285,21 +286,24 @@ contract Proofs { struct TauZero { // bytes[512] name; // array[512, byte], byte is alias for uint8 - bytes name; - int64 n; - BnP1[] u; // seq[blst_p1] + bytes name; + int64 n; + Curve.G1Point[] u; // seq[blst_p1] } struct Tau { - TauZero t; - bytes32[96] signature; + TauZero t; + // bytes32[96] signature; + Curve.G1Point signature; } // x', y' affine coordinates, result of EllipticCurve.ecMul // e.g. https://github.com/witnet/elliptic-curve-solidity/blob/master/examples/Secp256k1.sol struct PublicKey { - uint256 x; - uint256 y; + Curve.G1Point signkey; + Curve.G2Point key; + // uint256 x; + // uint256 y; } struct QElement { @@ -314,21 +318,21 @@ contract Proofs { } return false; } - function toBnP1(uint x, uint y) internal pure returns(BnP1 memory p1) { - p1 = BnP1( - { - x: BnFp({ls: x}), - y: BnFp({ls: y}) - }); - } + // function toBnP1(uint x, uint y) internal pure returns(BnP1 memory p1) { + // p1 = BnP1( + // { + // x: BnFp({ls: x}), + // y: BnFp({ls: y}) + // }); + // } - function toBnP2(uint x, uint y) internal pure returns(BnP2 memory p2) { - p2 = BnP2( - { - x: BnFp2({ls: x}), - y: BnFp2({ls: y}) - }); - } + // function toBnP2(uint x, uint y) internal pure returns(BnP2 memory p2) { + // p2 = BnP2( + // { + // x: BnFp2({ls: x}), + // y: BnFp2({ls: y}) + // }); + // } // proc pairing(a: blst_p1, b: blst_p2): blst_fp12 = // ## Calculate pairing G_1,G_2 -> G_T @@ -339,39 +343,72 @@ contract Proofs { // var l: blst_fp12 // blst_miller_loop(l, bb, aa) // blst_final_exp(result, l) - function _pairing (BnP1 memory a, BnP2 memory b) internal returns (BnFp12 memory fp12) { - (uint aax, uint aay) = EllipticCurve.toAffine(a.x, a.y, _z, BN256G1.PP); - (uint bbx, uint bby) = EllipticCurve.toAffine(b.x, b.y, _z, BN256G1.PP); + // function _pairing (BnP1 memory a, BnP2 memory b) internal returns (BnFp12 memory fp12) { + // (uint aax, uint aay) = EllipticCurve.toAffine(a.x, a.y, _z, BN256G1.PP); + // (uint bbx, uint bby) = EllipticCurve.toAffine(b.x, b.y, _z, BN256G1.PP); + // } + + // function _verifyPairings ( + // BnP1 memory a1, + // BnP2 memory a2, + // BnP1 memory b1, + // BnP2 memory b2) internal returns (bool) { + + // // let e1 = pairing(a1, a2) + // // let e2 = pairing(b1, b2) + // // return e1 == e2 + // BnFp12 memory e1 = _pairing(a1, a2); + // BnFp12 memory e2 = _pairing(b1, b2); + // return e1 == e2; + + // } + + // Example of BLS signature verification + // Taken from: https://ethereum.stackexchange.com/a/59315 + function _verifySignature( + Curve.G1Point memory sig, + Curve.G2Point memory signkey, + uint hashedMsg) internal view returns (bool) + { + + // bytes memory message = hex"7b0a2020226f70656e223a207b0a20202020227072696365223a2039353931372c0a202020202274696d65223a207b0a20202020202022756e6978223a20313438333134323430302c0a2020202020202269736f223a2022323031362d31322d33315430303a30303a30302e3030305a220a202020207d0a20207d2c0a202022636c6f7365223a207b0a20202020227072696365223a2039363736302c0a202020202274696d65223a207b0a20202020202022756e6978223a20313438333232383830302c0a2020202020202269736f223a2022323031372d30312d30315430303a30303a30302e3030305a220a202020207d0a20207d2c0a2020226c6f6f6b7570223a207b0a20202020227072696365223a2039363736302c0a20202020226b223a20312c0a202020202274696d65223a207b0a20202020202022756e6978223a20313438333232383830302c0a2020202020202269736f223a2022323031372d30312d30315430303a30303a30302e3030305a220a202020207d0a20207d0a7d0a6578616d706c652e636f6d2f6170692f31"; + + // Curve.G1Point memory signature = Curve.G1Point(11181692345848957662074290878138344227085597134981019040735323471731897153462, 6479746447046570360435714249272776082787932146211764251347798668447381926167); + + // Curve.G2Point memory v = Curve.G2Point( + // [18523194229674161632574346342370534213928970227736813349975332190798837787897, 5725452645840548248571879966249653216818629536104756116202892528545334967238], + // [3816656720215352836236372430537606984911914992659540439626020770732736710924, 677280212051826798882467475639465784259337739185938192379192340908771705870] + // ); + + Curve.G1Point memory h = Curve.HashToPoint(hashedMsg); + + return Curve.pairingProd2(Curve.g1neg(sig), Curve.P2(), h, signkey); } - function _verifyPairings ( - BnP1 memory a1, - BnP2 memory a2, - BnP1 memory b1, - BnP2 memory b2) internal returns (bool) { - - // let e1 = pairing(a1, a2) - // let e2 = pairing(b1, b2) - // return e1 == e2 - BnFp12 memory e1 = _pairing(a1, a2); - BnFp12 memory e2 = _pairing(b1, b2); - return e1 == e2; - + function _isOnCurve(Curve.G1Point memory g1) internal view returns (bool) { + return EllipticCurve.isOnCurve( + g1.X, + g1.Y, + Curve.A(), + Curve.B(), + Curve.P()); } function _verifyProof( Tau memory tau, QElement[] memory q, BnFr[10] memory mus, // Possibly 48 bytes long, csaba? - BnP1 memory sigma, + // If only 48 bytes, how can it be a G1Point? (x, y both only 24 bytes?) + Curve.G1Point memory sigma, PublicKey memory spk) internal returns (bool) { - // is this really needed? - require(!isEmpty(tau.signature), "Signature cannot be empty"); + // is this needed in solidity? + // require(!isEmpty(tau.signature), "Signature cannot be empty"); - // TODO: add verification - // if not verify(spk.signkey, $tau.t, signature): - // return false + // $tau.t -- how to do this in solidity? + uint hashedMsg = uint(sha256(abi.encodePacked(tau.t))); + require(_verifySignature(tau.signature, spk.signkey, hashedMsg), + "invalid signature"); // var first: blst_p1 // for qelem in q : @@ -379,21 +416,19 @@ contract Proofs { // prod.blst_p1_mult(hashNameI(tau.t.name, qelem.I), qelem.V, 255) // first.blst_p1_add_or_double(first, prod) // doAssert(blst_p1_on_curve(first).bool) - // BnP1 memory first; - uint firstX; - uint firstY; + Curve.G1Point memory first; for (uint i = 0; i=0.6.0 <=0.8.13; + +library Curve { + // p = p(u) = 36u^4 + 36u^3 + 24u^2 + 6u + 1 + uint256 internal constant FIELD_ORDER = + 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; + + // Number of elements in the field (often called `q`) + // n = n(u) = 36u^4 + 36u^3 + 18u^2 + 6u + 1 + uint256 internal constant GEN_ORDER = + 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001; + + uint256 internal constant CURVE_B = 3; + + // a = (p+1) / 4 + uint256 internal constant CURVE_A = + 0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52; + + struct G1Point { + uint256 X; + uint256 Y; + } + + // Encoding of field elements is: X[0] * z + X[1] + struct G2Point { + uint256[2] X; + uint256[2] Y; + } + + // (P+1) / 4 + function A() internal pure returns (uint256) { + return CURVE_A; + } + + function B() internal pure returns (uint256) { + return CURVE_B; + } + + function P() internal pure returns (uint256) { + return FIELD_ORDER; + } + + function N() internal pure returns (uint256) { + return GEN_ORDER; + } + + /// @return the generator of G1 + function P1() internal pure returns (G1Point memory) { + return G1Point(1, 2); + } + + function HashToPoint(uint256 s) internal view returns (G1Point memory) { + uint256 beta = 0; + uint256 y = 0; + + // XXX: Gen Order (n) or Field Order (p) ? + uint256 x = s % GEN_ORDER; + + while (true) { + (beta, y) = FindYforX(x); + + // y^2 == beta + if (beta == mulmod(y, y, FIELD_ORDER)) { + return G1Point(x, y); + } + + x = addmod(x, 1, FIELD_ORDER); + } + } + + /** + * Given X, find Y + * + * where y = sqrt(x^3 + b) + * + * Returns: (x^3 + b), y + */ + function FindYforX(uint256 x) internal view returns (uint256, uint256) { + // beta = (x^3 + b) % p + uint256 beta = addmod( + mulmod(mulmod(x, x, FIELD_ORDER), x, FIELD_ORDER), + CURVE_B, + FIELD_ORDER + ); + + // y^2 = x^3 + b + // this acts like: y = sqrt(beta) + uint256 y = expMod(beta, CURVE_A, FIELD_ORDER); + + return (beta, y); + } + + // a - b = c; + function submod(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 a_nn; + + if (a > b) { + a_nn = a; + } else { + a_nn = a + GEN_ORDER; + } + + return addmod(a_nn - b, 0, GEN_ORDER); + } + + function expMod( + uint256 _base, + uint256 _exponent, + uint256 _modulus + ) internal view returns (uint256 retval) { + bool success; + uint256[1] memory output; + uint256[6] memory input; + input[0] = 0x20; // baseLen = new(big.Int).SetBytes(getData(input, 0, 32)) + input[1] = 0x20; // expLen = new(big.Int).SetBytes(getData(input, 32, 32)) + input[2] = 0x20; // modLen = new(big.Int).SetBytes(getData(input, 64, 32)) + input[3] = _base; + input[4] = _exponent; + input[5] = _modulus; + assembly { + success := staticcall(sub(gas(), 2000), 5, input, 0xc0, output, 0x20) + // Use "invalid" to make gas estimation work + switch success + case 0 { + invalid() + } + } + require(success); + return output[0]; + } + + /// @return the generator of G2 + function P2() internal pure returns (G2Point memory) { + return + G2Point( + [ + 11559732032986387107991004021392285783925812861821192530917403151452391805634, + 10857046999023057135944570762232829481370756359578518086990519993285655852781 + ], + [ + 4082367875863433681332203403145435568316851327593401208105741076214120093531, + 8495653923123431417604973247489272438418190587263600148770280649306958101930 + ] + ); + } + + /// @return the negation of p, i.e. p.add(p.negate()) should be zero. + function g1neg(G1Point memory p) + internal pure returns (G1Point memory) + { + // The prime q in the base field F_q for G1 + uint256 q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + if (p.X == 0 && p.Y == 0) return G1Point(0, 0); + return G1Point(p.X, q - (p.Y % q)); + } + + /// @return r the sum of two points of G1 + function g1add(G1Point memory p1, G1Point memory p2) + internal view returns (G1Point memory r) + { + uint256[4] memory input; + input[0] = p1.X; + input[1] = p1.Y; + input[2] = p2.X; + input[3] = p2.Y; + bool success; + assembly { + success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60) + // Use "invalid" to make gas estimation work + switch success + case 0 { + invalid() + } + } + require(success); + } + + /// @return r the product of a point on G1 and a scalar, i.e. + /// p == p.mul(1) and p.add(p) == p.mul(2) for all points p. + function g1mul(G1Point memory p, uint256 s) + internal view returns (G1Point memory r) + { + uint256[3] memory input; + input[0] = p.X; + input[1] = p.Y; + input[2] = s; + bool success; + assembly { + success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60) + // Use "invalid" to make gas estimation work + switch success + case 0 { + invalid() + } + } + require(success); + } + + /// @return the result of computing the pairing check + /// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 + /// For example pairing([P1(), P1().negate()], [P2(), P2()]) should + /// return true. + function pairing(G1Point[] memory p1, G2Point[] memory p2) + internal + view + returns (bool) + { + require(p1.length == p2.length); + uint256 elements = p1.length; + uint256 inputSize = elements * 6; + uint256[] memory input = new uint256[](inputSize); + for (uint256 i = 0; i < elements; i++) { + input[i * 6 + 0] = p1[i].X; + input[i * 6 + 1] = p1[i].Y; + input[i * 6 + 2] = p2[i].X[0]; + input[i * 6 + 3] = p2[i].X[1]; + input[i * 6 + 4] = p2[i].Y[0]; + input[i * 6 + 5] = p2[i].Y[1]; + } + uint256[1] memory out; + bool success; + assembly { + success := staticcall( + sub(gas(), 2000), + 8, + add(input, 0x20), + mul(inputSize, 0x20), + out, + 0x20 + ) + // Use "invalid" to make gas estimation work + switch success + case 0 { + invalid() + } + } + require(success); + return out[0] != 0; + } + + /// Convenience method for a pairing check for two pairs. + function pairingProd2( + G1Point memory a1, + G2Point memory a2, + G1Point memory b1, + G2Point memory b2 + ) internal view returns (bool) { + G1Point[] memory p1 = new G1Point[](2); + G2Point[] memory p2 = new G2Point[](2); + p1[0] = a1; + p1[1] = b1; + p2[0] = a2; + p2[1] = b2; + return pairing(p1, p2); + } + + /// Convenience method for a pairing check for three pairs. + function pairingProd3( + G1Point memory a1, + G2Point memory a2, + G1Point memory b1, + G2Point memory b2, + G1Point memory c1, + G2Point memory c2 + ) internal view returns (bool) { + G1Point[] memory p1 = new G1Point[](3); + G2Point[] memory p2 = new G2Point[](3); + p1[0] = a1; + p1[1] = b1; + p1[2] = c1; + p2[0] = a2; + p2[1] = b2; + p2[2] = c2; + return pairing(p1, p2); + } + + /// Convenience method for a pairing check for four pairs. + function pairingProd4( + G1Point memory a1, + G2Point memory a2, + G1Point memory b1, + G2Point memory b2, + G1Point memory c1, + G2Point memory c2, + G1Point memory d1, + G2Point memory d2 + ) internal view returns (bool) { + G1Point[] memory p1 = new G1Point[](4); + G2Point[] memory p2 = new G2Point[](4); + p1[0] = a1; + p1[1] = b1; + p1[2] = c1; + p1[3] = d1; + p2[0] = a2; + p2[1] = b2; + p2[2] = c2; + p2[3] = d2; + return pairing(p1, p2); + } +}