From 63e85f3837345d65960807697b4389c6871eeefa Mon Sep 17 00:00:00 2001 From: HarryR Date: Tue, 18 Jun 2019 20:40:58 +0100 Subject: [PATCH] Initial import of BLS aggregate signature validation --- contracts/BLSValidators.sol | 233 +++++++++++++++++++++ contracts/BN256G2.sol | 394 ++++++++++++++++++++++++++++++++++++ pysolcrypto/bls.py | 125 ++++++++++++ test/test_bls.py | 32 +++ 4 files changed, 784 insertions(+) create mode 100644 contracts/BLSValidators.sol create mode 100644 contracts/BN256G2.sol create mode 100644 pysolcrypto/bls.py create mode 100644 test/test_bls.py diff --git a/contracts/BLSValidators.sol b/contracts/BLSValidators.sol new file mode 100644 index 0000000..83a6282 --- /dev/null +++ b/contracts/BLSValidators.sol @@ -0,0 +1,233 @@ +pragma solidity ^0.5.8; + + +import { BN256G2 } from "./BN256G2.sol"; + + +contract BLSValidators +{ + uint256 internal constant FIELD_ORDER = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; + + // a = (FIELD_ORDER+1) // 4 + uint256 internal constant CURVE_A = 0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52; + + struct Validator { + address owner; + uint256 amount; + uint256[4] pubkey; + } + + uint256 internal aggregate_bitmask; + uint256[4] internal aggregate_pubkey; + + mapping (uint8 => Validator) internal validators; + + event OnNewValidator(uint8 index, address owner, uint256[4] pk); + + event OnValidatorRemoved(uint8 index); + + constructor () public { + aggregate_pubkey = [uint256(0), uint256(0), uint256(0), uint256(0)]; + } + + function HashToG1(uint256 s) + internal view returns (uint256[2] memory) + { + uint256 beta = 0; + uint256 y = 0; + uint256 x = s % FIELD_ORDER; + while( true ) { + (beta, y) = FindYforX(x); + if(beta == mulmod(y, y, FIELD_ORDER)) { + return [x, y]; + } + x = addmod(x, 1, FIELD_ORDER); + } + } + + 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), 3, FIELD_ORDER); + uint256 y = modPow(beta, CURVE_A, FIELD_ORDER); + return (beta, y); + } + + function AddValidator(uint8 index, uint256[4] memory pk, uint256[2] memory sig) + public payable + { + require( msg.value != 0 ); + require( validators[index].owner == address(0) ); + require( ProvePublicKey(pk, sig) ); + + validators[index] = Validator(msg.sender, msg.value, pk); + + // To handle the special case where all validators agree on something + // We pre-accumulate the keys to avoid doing it every time a signature is validated + // Maintain a bitmask of their indices + uint256[4] memory p; + (p[0], p[1], p[2], p[3]) = BN256G2.ECTwistAdd(aggregate_pubkey[0], aggregate_pubkey[1], + aggregate_pubkey[2], aggregate_pubkey[3], + pk[0], pk[1], pk[2], pk[3]); + aggregate_pubkey = p; + aggregate_bitmask = aggregate_bitmask + (uint256(1)<> i) & 1 > 0 ) + { + require( validators[i].owner != address(0) ); + uint256[4] memory p = validators[i].pubkey; + (ap[0], ap[1], ap[2], ap[3]) = BN256G2.ECTwistAdd(ap[0], ap[1], ap[2], ap[3], + p[0], p[1], p[2], p[3]); + } + } + } + } + + function CheckSignature(uint256 message, uint256[4] memory pubkey, uint256[2] memory sig) + public view returns (bool) + { + return pairing2(HashToG1(message), pubkey, sig, NegativeG2()); + } + + function CheckSignature(uint256 bitmask, uint256[2] memory sig, uint256 message) + public view returns (bool) + { + uint256[4] memory ap; + require( bitmask > 0 ); + ap = AggregateKeyForBitmask(bitmask); + return CheckSignature(message, ap, sig); + } + + function CheckSignature(uint256 bitmask, uint256[2] memory sig, bytes memory message_bytes) + public view returns (bool) + { + uint256 message = uint256(keccak256(message_bytes)); + return CheckSignature(bitmask, sig, message); + } + + // neg(G2) + function NegativeG2() + internal pure returns (uint256[4] memory) + { + return [0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed, + 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2, + 0x1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d, + 0x275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec]; + } + + /// Convenience method for a pairing check for two pairs. + function pairing2(uint256[2] memory a1, uint256[4] memory a2, uint256[2] memory b1, uint256[4] memory b2) + internal view returns (bool) + { + // Note that G2 points have reversed coefficients when using the Ethereum pairing precompile + uint256[12] memory input = [ + a1[0], a1[1], // a1 (G1) + a2[1], a2[0], a2[3], a2[2], // a2 (G2) + + b1[0], b1[1], // b1 (G1) + b2[1], b2[0], b2[3], b2[2] // b2 (G2) + ]; + uint[1] memory out; + assembly { + if iszero(staticcall(sub(gas, 2000), 8, input, 0x180, out, 0x20)) { + revert(0, 0) + } + } + return out[0] != 0; + } + + function modPow(uint256 base, uint256 exponent, uint256 modulus) + internal view returns (uint256) + { + uint256[6] memory input = [32, 32, 32, base, exponent, modulus]; + uint256[1] memory result; + assembly { + if iszero(staticcall(not(0), 0x05, input, 0xc0, result, 0x20)) { + revert(0, 0) + } + } + return result[0]; + } + + function negate(uint256 value) + internal pure returns (uint256) + { + return FIELD_ORDER - (value % FIELD_ORDER); + } + + function negate(uint256[4] memory p) + internal pure returns (uint256[4] memory) + { + return [p[0], p[1], negate(p[2]), negate(p[3])]; + } +} diff --git a/contracts/BN256G2.sol b/contracts/BN256G2.sol new file mode 100644 index 0000000..bc43a59 --- /dev/null +++ b/contracts/BN256G2.sol @@ -0,0 +1,394 @@ +pragma solidity ^0.4.24; + +/** + * @title Elliptic curve operations on twist points for alt_bn128 + * @author Mustafa Al-Bassam (mus@musalbas.com) + * @dev Homepage: https://github.com/musalbas/solidity-BN256G2 + */ + +library BN256G2 { + uint256 internal constant FIELD_MODULUS = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; + uint256 internal constant TWISTBX = 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; + uint256 internal constant TWISTBY = 0x9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2; + uint internal constant PTXX = 0; + uint internal constant PTXY = 1; + uint internal constant PTYX = 2; + uint internal constant PTYY = 3; + uint internal constant PTZX = 4; + uint internal constant PTZY = 5; + + /** + * @notice Add two twist points + * @param pt1xx Coefficient 1 of x on point 1 + * @param pt1xy Coefficient 2 of x on point 1 + * @param pt1yx Coefficient 1 of y on point 1 + * @param pt1yy Coefficient 2 of y on point 1 + * @param pt2xx Coefficient 1 of x on point 2 + * @param pt2xy Coefficient 2 of x on point 2 + * @param pt2yx Coefficient 1 of y on point 2 + * @param pt2yy Coefficient 2 of y on point 2 + * @return (pt3xx, pt3xy, pt3yx, pt3yy) + */ + function ECTwistAdd( + uint256 pt1xx, uint256 pt1xy, + uint256 pt1yx, uint256 pt1yy, + uint256 pt2xx, uint256 pt2xy, + uint256 pt2yx, uint256 pt2yy + ) public view returns ( + uint256, uint256, + uint256, uint256 + ) { + if ( + pt1xx == 0 && pt1xy == 0 && + pt1yx == 0 && pt1yy == 0 + ) { + if (!( + pt2xx == 0 && pt2xy == 0 && + pt2yx == 0 && pt2yy == 0 + )) { + assert(_isOnCurve( + pt2xx, pt2xy, + pt2yx, pt2yy + )); + } + return ( + pt2xx, pt2xy, + pt2yx, pt2yy + ); + } else if ( + pt2xx == 0 && pt2xy == 0 && + pt2yx == 0 && pt2yy == 0 + ) { + assert(_isOnCurve( + pt1xx, pt1xy, + pt1yx, pt1yy + )); + return ( + pt1xx, pt1xy, + pt1yx, pt1yy + ); + } + + assert(_isOnCurve( + pt1xx, pt1xy, + pt1yx, pt1yy + )); + assert(_isOnCurve( + pt2xx, pt2xy, + pt2yx, pt2yy + )); + + uint256[6] memory pt3 = _ECTwistAddJacobian( + pt1xx, pt1xy, + pt1yx, pt1yy, + 1, 0, + pt2xx, pt2xy, + pt2yx, pt2yy, + 1, 0 + ); + + return _fromJacobian( + pt3[PTXX], pt3[PTXY], + pt3[PTYX], pt3[PTYY], + pt3[PTZX], pt3[PTZY] + ); + } + + /** + * @notice Multiply a twist point by a scalar + * @param s Scalar to multiply by + * @param pt1xx Coefficient 1 of x + * @param pt1xy Coefficient 2 of x + * @param pt1yx Coefficient 1 of y + * @param pt1yy Coefficient 2 of y + * @return (pt2xx, pt2xy, pt2yx, pt2yy) + */ + function ECTwistMul( + uint256 s, + uint256 pt1xx, uint256 pt1xy, + uint256 pt1yx, uint256 pt1yy + ) public view returns ( + uint256, uint256, + uint256, uint256 + ) { + uint256 pt1zx = 1; + if ( + pt1xx == 0 && pt1xy == 0 && + pt1yx == 0 && pt1yy == 0 + ) { + pt1xx = 1; + pt1yx = 1; + pt1zx = 0; + } else { + assert(_isOnCurve( + pt1xx, pt1xy, + pt1yx, pt1yy + )); + } + + uint256[6] memory pt2 = _ECTwistMulJacobian( + s, + pt1xx, pt1xy, + pt1yx, pt1yy, + pt1zx, 0 + ); + + return _fromJacobian( + pt2[PTXX], pt2[PTXY], + pt2[PTYX], pt2[PTYY], + pt2[PTZX], pt2[PTZY] + ); + } + + /** + * @notice Get the field modulus + * @return The field modulus + */ + function GetFieldModulus() public pure returns (uint256) { + return FIELD_MODULUS; + } + + function submod(uint256 a, uint256 b, uint256 n) internal pure returns (uint256) { + return addmod(a, n - b, n); + } + + function _FQ2Mul( + uint256 xx, uint256 xy, + uint256 yx, uint256 yy + ) internal pure returns (uint256, uint256) { + return ( + submod(mulmod(xx, yx, FIELD_MODULUS), mulmod(xy, yy, FIELD_MODULUS), FIELD_MODULUS), + addmod(mulmod(xx, yy, FIELD_MODULUS), mulmod(xy, yx, FIELD_MODULUS), FIELD_MODULUS) + ); + } + + function _FQ2Muc( + uint256 xx, uint256 xy, + uint256 c + ) internal pure returns (uint256, uint256) { + return ( + mulmod(xx, c, FIELD_MODULUS), + mulmod(xy, c, FIELD_MODULUS) + ); + } + + function _FQ2Add( + uint256 xx, uint256 xy, + uint256 yx, uint256 yy + ) internal pure returns (uint256, uint256) { + return ( + addmod(xx, yx, FIELD_MODULUS), + addmod(xy, yy, FIELD_MODULUS) + ); + } + + function _FQ2Sub( + uint256 xx, uint256 xy, + uint256 yx, uint256 yy + ) internal pure returns (uint256 rx, uint256 ry) { + return ( + submod(xx, yx, FIELD_MODULUS), + submod(xy, yy, FIELD_MODULUS) + ); + } + + function _FQ2Div( + uint256 xx, uint256 xy, + uint256 yx, uint256 yy + ) internal view returns (uint256, uint256) { + (yx, yy) = _FQ2Inv(yx, yy); + return _FQ2Mul(xx, xy, yx, yy); + } + + function _FQ2Inv(uint256 x, uint256 y) internal view returns (uint256, uint256) { + uint256 inv = _modInv(addmod(mulmod(y, y, FIELD_MODULUS), mulmod(x, x, FIELD_MODULUS), FIELD_MODULUS), FIELD_MODULUS); + return ( + mulmod(x, inv, FIELD_MODULUS), + FIELD_MODULUS - mulmod(y, inv, FIELD_MODULUS) + ); + } + + function _isOnCurve( + uint256 xx, uint256 xy, + uint256 yx, uint256 yy + ) internal pure returns (bool) { + uint256 yyx; + uint256 yyy; + uint256 xxxx; + uint256 xxxy; + (yyx, yyy) = _FQ2Mul(yx, yy, yx, yy); + (xxxx, xxxy) = _FQ2Mul(xx, xy, xx, xy); + (xxxx, xxxy) = _FQ2Mul(xxxx, xxxy, xx, xy); + (yyx, yyy) = _FQ2Sub(yyx, yyy, xxxx, xxxy); + (yyx, yyy) = _FQ2Sub(yyx, yyy, TWISTBX, TWISTBY); + return yyx == 0 && yyy == 0; + } + + function _modInv(uint256 a, uint256 n) internal view returns (uint256 result) { + bool success; + assembly { + let freemem := mload(0x40) + mstore(freemem, 0x20) + mstore(add(freemem,0x20), 0x20) + mstore(add(freemem,0x40), 0x20) + mstore(add(freemem,0x60), a) + mstore(add(freemem,0x80), sub(n, 2)) + mstore(add(freemem,0xA0), n) + success := staticcall(sub(gas, 2000), 5, freemem, 0xC0, freemem, 0x20) + result := mload(freemem) + } + require(success); + } + + function _fromJacobian( + uint256 pt1xx, uint256 pt1xy, + uint256 pt1yx, uint256 pt1yy, + uint256 pt1zx, uint256 pt1zy + ) internal view returns ( + uint256 pt2xx, uint256 pt2xy, + uint256 pt2yx, uint256 pt2yy + ) { + uint256 invzx; + uint256 invzy; + (invzx, invzy) = _FQ2Inv(pt1zx, pt1zy); + (pt2xx, pt2xy) = _FQ2Mul(pt1xx, pt1xy, invzx, invzy); + (pt2yx, pt2yy) = _FQ2Mul(pt1yx, pt1yy, invzx, invzy); + } + + function _ECTwistAddJacobian( + uint256 pt1xx, uint256 pt1xy, + uint256 pt1yx, uint256 pt1yy, + uint256 pt1zx, uint256 pt1zy, + uint256 pt2xx, uint256 pt2xy, + uint256 pt2yx, uint256 pt2yy, + uint256 pt2zx, uint256 pt2zy) internal pure returns (uint256[6] memory pt3) { + if (pt1zx == 0 && pt1zy == 0) { + ( + pt3[PTXX], pt3[PTXY], + pt3[PTYX], pt3[PTYY], + pt3[PTZX], pt3[PTZY] + ) = ( + pt2xx, pt2xy, + pt2yx, pt2yy, + pt2zx, pt2zy + ); + return pt3; + } else if (pt2zx == 0 && pt2zy == 0) { + ( + pt3[PTXX], pt3[PTXY], + pt3[PTYX], pt3[PTYY], + pt3[PTZX], pt3[PTZY] + ) = ( + pt1xx, pt1xy, + pt1yx, pt1yy, + pt1zx, pt1zy + ); + return pt3; + } + + (pt2yx, pt2yy) = _FQ2Mul(pt2yx, pt2yy, pt1zx, pt1zy); // U1 = y2 * z1 + (pt3[PTYX], pt3[PTYY]) = _FQ2Mul(pt1yx, pt1yy, pt2zx, pt2zy); // U2 = y1 * z2 + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt1zx, pt1zy); // V1 = x2 * z1 + (pt3[PTZX], pt3[PTZY]) = _FQ2Mul(pt1xx, pt1xy, pt2zx, pt2zy); // V2 = x1 * z2 + + if (pt2xx == pt3[PTZX] && pt2xy == pt3[PTZY]) { + if (pt2yx == pt3[PTYX] && pt2yy == pt3[PTYY]) { + ( + pt3[PTXX], pt3[PTXY], + pt3[PTYX], pt3[PTYY], + pt3[PTZX], pt3[PTZY] + ) = _ECTwistDoubleJacobian(pt1xx, pt1xy, pt1yx, pt1yy, pt1zx, pt1zy); + return pt3; + } + ( + pt3[PTXX], pt3[PTXY], + pt3[PTYX], pt3[PTYY], + pt3[PTZX], pt3[PTZY] + ) = ( + 1, 0, + 1, 0, + 0, 0 + ); + return pt3; + } + + (pt2zx, pt2zy) = _FQ2Mul(pt1zx, pt1zy, pt2zx, pt2zy); // W = z1 * z2 + (pt1xx, pt1xy) = _FQ2Sub(pt2yx, pt2yy, pt3[PTYX], pt3[PTYY]); // U = U1 - U2 + (pt1yx, pt1yy) = _FQ2Sub(pt2xx, pt2xy, pt3[PTZX], pt3[PTZY]); // V = V1 - V2 + (pt1zx, pt1zy) = _FQ2Mul(pt1yx, pt1yy, pt1yx, pt1yy); // V_squared = V * V + (pt2yx, pt2yy) = _FQ2Mul(pt1zx, pt1zy, pt3[PTZX], pt3[PTZY]); // V_squared_times_V2 = V_squared * V2 + (pt1zx, pt1zy) = _FQ2Mul(pt1zx, pt1zy, pt1yx, pt1yy); // V_cubed = V * V_squared + (pt3[PTZX], pt3[PTZY]) = _FQ2Mul(pt1zx, pt1zy, pt2zx, pt2zy); // newz = V_cubed * W + (pt2xx, pt2xy) = _FQ2Mul(pt1xx, pt1xy, pt1xx, pt1xy); // U * U + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt2zx, pt2zy); // U * U * W + (pt2xx, pt2xy) = _FQ2Sub(pt2xx, pt2xy, pt1zx, pt1zy); // U * U * W - V_cubed + (pt2zx, pt2zy) = _FQ2Muc(pt2yx, pt2yy, 2); // 2 * V_squared_times_V2 + (pt2xx, pt2xy) = _FQ2Sub(pt2xx, pt2xy, pt2zx, pt2zy); // A = U * U * W - V_cubed - 2 * V_squared_times_V2 + (pt3[PTXX], pt3[PTXY]) = _FQ2Mul(pt1yx, pt1yy, pt2xx, pt2xy); // newx = V * A + (pt1yx, pt1yy) = _FQ2Sub(pt2yx, pt2yy, pt2xx, pt2xy); // V_squared_times_V2 - A + (pt1yx, pt1yy) = _FQ2Mul(pt1xx, pt1xy, pt1yx, pt1yy); // U * (V_squared_times_V2 - A) + (pt1xx, pt1xy) = _FQ2Mul(pt1zx, pt1zy, pt3[PTYX], pt3[PTYY]); // V_cubed * U2 + (pt3[PTYX], pt3[PTYY]) = _FQ2Sub(pt1yx, pt1yy, pt1xx, pt1xy); // newy = U * (V_squared_times_V2 - A) - V_cubed * U2 + } + + function _ECTwistDoubleJacobian( + uint256 pt1xx, uint256 pt1xy, + uint256 pt1yx, uint256 pt1yy, + uint256 pt1zx, uint256 pt1zy + ) internal pure returns ( + uint256 pt2xx, uint256 pt2xy, + uint256 pt2yx, uint256 pt2yy, + uint256 pt2zx, uint256 pt2zy + ) { + (pt2xx, pt2xy) = _FQ2Muc(pt1xx, pt1xy, 3); // 3 * x + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt1xx, pt1xy); // W = 3 * x * x + (pt1zx, pt1zy) = _FQ2Mul(pt1yx, pt1yy, pt1zx, pt1zy); // S = y * z + (pt2yx, pt2yy) = _FQ2Mul(pt1xx, pt1xy, pt1yx, pt1yy); // x * y + (pt2yx, pt2yy) = _FQ2Mul(pt2yx, pt2yy, pt1zx, pt1zy); // B = x * y * S + (pt1xx, pt1xy) = _FQ2Mul(pt2xx, pt2xy, pt2xx, pt2xy); // W * W + (pt2zx, pt2zy) = _FQ2Muc(pt2yx, pt2yy, 8); // 8 * B + (pt1xx, pt1xy) = _FQ2Sub(pt1xx, pt1xy, pt2zx, pt2zy); // H = W * W - 8 * B + (pt2zx, pt2zy) = _FQ2Mul(pt1zx, pt1zy, pt1zx, pt1zy); // S_squared = S * S + (pt2yx, pt2yy) = _FQ2Muc(pt2yx, pt2yy, 4); // 4 * B + (pt2yx, pt2yy) = _FQ2Sub(pt2yx, pt2yy, pt1xx, pt1xy); // 4 * B - H + (pt2yx, pt2yy) = _FQ2Mul(pt2yx, pt2yy, pt2xx, pt2xy); // W * (4 * B - H) + (pt2xx, pt2xy) = _FQ2Muc(pt1yx, pt1yy, 8); // 8 * y + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt1yx, pt1yy); // 8 * y * y + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt2zx, pt2zy); // 8 * y * y * S_squared + (pt2yx, pt2yy) = _FQ2Sub(pt2yx, pt2yy, pt2xx, pt2xy); // newy = W * (4 * B - H) - 8 * y * y * S_squared + (pt2xx, pt2xy) = _FQ2Muc(pt1xx, pt1xy, 2); // 2 * H + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt1zx, pt1zy); // newx = 2 * H * S + (pt2zx, pt2zy) = _FQ2Mul(pt1zx, pt1zy, pt2zx, pt2zy); // S * S_squared + (pt2zx, pt2zy) = _FQ2Muc(pt2zx, pt2zy, 8); // newz = 8 * S * S_squared + } + + function _ECTwistMulJacobian( + uint256 d, + uint256 pt1xx, uint256 pt1xy, + uint256 pt1yx, uint256 pt1yy, + uint256 pt1zx, uint256 pt1zy + ) internal pure returns (uint256[6] memory pt2) { + while (d != 0) { + if ((d & 1) != 0) { + pt2 = _ECTwistAddJacobian( + pt2[PTXX], pt2[PTXY], + pt2[PTYX], pt2[PTYY], + pt2[PTZX], pt2[PTZY], + pt1xx, pt1xy, + pt1yx, pt1yy, + pt1zx, pt1zy); + } + ( + pt1xx, pt1xy, + pt1yx, pt1yy, + pt1zx, pt1zy + ) = _ECTwistDoubleJacobian( + pt1xx, pt1xy, + pt1yx, pt1yy, + pt1zx, pt1zy + ); + + d = d / 2; + } + } +} diff --git a/pysolcrypto/bls.py b/pysolcrypto/bls.py new file mode 100644 index 0000000..cb962df --- /dev/null +++ b/pysolcrypto/bls.py @@ -0,0 +1,125 @@ +from functools import reduce +import binascii +from os import urandom +from py_ecc.bn128 import * +from sha3 import keccak_256 + +""" +Implements BLS signatture aggregation as described at: + + https://crypto.stanford.edu/~dabo/pubs/papers/BLSmultisig.html + +--------- + +Roughly based on the following code: + + >>> from py_ecc.bn128 import * + >>> from random import randint + + >>> sk1, sk2 = randint(1,curve_order-1), randint(1, curve_ + ... order-1) + + >>> pk1, pk2 = multiply(G2, sk1), multiply(G2, sk2) + + >>> H = multiply(G1, randint(1, curve_order-1)) + + >>> sig1, sig2 = multiply(H, sk1), multiply(H, sk2) + + >>> aggpk = add(pk1, pk2) + + >>> aggsig = add(sig1, sig2) + + >>> pairing(aggpk, H) == pairing(G2, aggsig) + True + + +""" + +addmodp = lambda x, y: (x + y) % field_modulus + +mulmodp = lambda x, y: (x * y) % field_modulus + +safe_ord = lambda x: x if isinstance(x, int) else ord(x) + +bytes_to_int = lambda x: reduce(lambda o, b: (o << 8) + safe_ord(b), [0] + list(x)) + +def int_to_big_endian(lnum): + if lnum == 0: + return b'\0' + s = hex(lnum)[2:].rstrip('L') + if len(s) & 1: + s = '0' + s + return binascii.unhexlify(s) + +zpad = lambda x, l: b'\x00' * max(0, l - len(x)) + x + +tobe256 = lambda v: zpad(int_to_big_endian(v), 32) + +def g2_to_list(point): + return [_.n for _ in point[0].coeffs + point[1].coeffs] + +def g1_to_list(point): + return [_.n for _ in point] + +fmt_list = lambda point: '[' + ', '.join([('"' + hex(_) + '"') for _ in point]) + ']' + +def randn(): + return int.from_bytes(urandom(64), 'big') % curve_order + +def hashs(*x): + data = b''.join(map(tobe256, x)) + return bytes_to_int(keccak_256(data).digest()) + +def evalcurve_g1(x): + beta = addmodp(mulmodp(mulmodp(x, x), x), 3) + assert field_modulus % 4 == 3 + a = (field_modulus+1)//4 # fast square root, using exponentation, assuming (p%4 == 3) + y = pow(beta, a, field_modulus) + return (beta, y) + +def isoncurve_g1(x, y): + return mulmodp(y, y) == addmodp(mulmodp(mulmodp(x, x), x), 3) + +def hash_to_g1(x): + # XXX: todo, re-hash on every round + assert isinstance(x, int) + x = x % field_modulus + while True: + beta, y = evalcurve_g1(x) + if beta == mulmodp(y, y): + assert isoncurve_g1(x, y) + return FQ(x), FQ(y) + x = addmodp(x, 1) + +def hash_g2(x): + xy_ints = [_.n for _ in (x[0].coeffs + x[1].coeffs)] + return hashs(*xy_ints) + +def bls_keygen(): + sk = randn() + pk = multiply(G2, sk) + return sk, pk + +def bls_prove_key(sk): + pk = multiply(G2, sk) + msg = hash_g2(pk) + return bls_sign(sk, msg) + +def bls_verify_key(pk, sig): + msg = hash_g2(pk) + return bls_verify(pk, msg, sig) + +def bls_sign(sk, msg): + H_m = hash_to_g1(msg) + sig = multiply(H_m, sk) + return sig + +def bls_verify(pk, msg, sig) -> bool: + H_m = hash_to_g1(msg) + return pairing(pk, H_m) * pairing(neg(G2), sig) == FQ12.one() + +def bls_agg_verify(pks, msg, sigs) -> bool: + H_m = hash_to_g1(msg) + agg_pk = reduce(add, pks) + agg_sig = reduce(add, sigs) + return pairing(agg_pk, H_m) * pairing(neg(G2), agg_sig) == FQ12.one() diff --git a/test/test_bls.py b/test/test_bls.py new file mode 100644 index 0000000..23aa7a5 --- /dev/null +++ b/test/test_bls.py @@ -0,0 +1,32 @@ +import unittest +import bls + + +class BlsTests(unittest.TestCase): + def test_prove_key(self): + sk, pk = bls.bls_keygen() + sig = bls.bls_prove_key(sk) + self.assertTrue(bls.bls_verify_key(pk, sig)) + + def test_aggregate(self): + "Aggregate signature where the two participants sign the same message" + sk1, pk1 = bls.bls_keygen() + sk2, pk2 = bls.bls_keygen() + msg = bls.randn() + sig1 = bls.bls_sign(sk1, msg) + sig2 = bls.bls_sign(sk2, msg) + self.assertTrue(bls.bls_agg_verify([pk1, pk2], msg, [sig1, sig2])) + + def test_aggregate_bad(self): + "Bad aggregate signature, where the two participants sign different messages" + sk1, pk1 = bls.bls_keygen() + sk2, pk2 = bls.bls_keygen() + msg1 = bls.randn() + msg2 = bls.randn() + sig1 = bls.bls_sign(sk1, msg1) + sig2 = bls.bls_sign(sk2, msg2) + self.assertFalse(bls.bls_agg_verify([pk1, pk2], msg1, [sig1, sig2])) + + +if __name__ == "__main__": + unittest.main()