Skip to content

Commit

Permalink
feat: BLS Signature Verification Example (#775)
Browse files Browse the repository at this point in the history
* added example contract for verifying a BLS Signature using Keccak hashing algorithm

Added test suite, and added test suite to CI.

Also added some missing tests from the publish results.

Signed-off-by: Alfredo Gutierrez <[email protected]>

* added missing constant change and licence year update to 2024

Signed-off-by: Alfredo Gutierrez <[email protected]>

* address feedback and improved Test

Signed-off-by: Alfredo Gutierrez <[email protected]>

* removing typo

Signed-off-by: Alfredo Gutierrez <[email protected]>

* address feedback

Signed-off-by: Alfredo Gutierrez <[email protected]>

* address feedback

Signed-off-by: Alfredo Gutierrez <[email protected]>

---------

Signed-off-by: Alfredo Gutierrez <[email protected]>
  • Loading branch information
AlfredoG87 authored May 28, 2024
1 parent f434729 commit 27f5c99
Show file tree
Hide file tree
Showing 7 changed files with 366 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ jobs:
uses: ./.github/workflows/test-workflow.yml
with:
testfilter: PrngSystemContract

BLSSignatureVerification:
name: BLS Signature Verification Test Suite
uses: ./.github/workflows/test-workflow.yml
with:
testfilter: BLSSignature

PublishResults:
name: Publish Results
Expand All @@ -121,6 +127,10 @@ jobs:
- HIP583
- Multicall
- HRC
- ShanghaiOpcodes
- PrngSystemContract
- BLSSignatureVerification


runs-on: [self-hosted, Linux, large, ephemeral]
steps:
Expand Down
36 changes: 36 additions & 0 deletions contracts-abi/contracts/bls-signature/BLSTest.sol/BLSTest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "_message",
"type": "bytes"
},
{
"internalType": "uint256",
"name": "_signatureX",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_signatureY",
"type": "uint256"
}
],
"name": "verify",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
]
30 changes: 30 additions & 0 deletions contracts/bls-signature/BLS.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "./Pairing.sol";

/**
* @title BLS
* @dev A library for verifying BLS signatures. Based on the work of https://gist.github.com/BjornvdLaan/ca6dd4e3993e1ef392f363ec27fe74c4, which is licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0).
*/
library BLS {
/*
* Internal functions
*/

/**
* @dev Checks if a BLS signature is valid.
* @param _verificationKey Public verification key associated with the secret key that signed the message.
* @param _message Message that was signed.
* @param _signature Signature over the message.
* @return True if the message was correctly signed.
*/
function verify(
Pairing.G2Point memory _verificationKey,
bytes memory _message,
Pairing.G1Point memory _signature
) internal view returns (bool) {
Pairing.G1Point memory messageHash = Pairing.hashToG1(_message);
return Pairing.pairing2(Pairing.negate(_signature), Pairing.P2(), messageHash, _verificationKey);
}
}
46 changes: 46 additions & 0 deletions contracts/bls-signature/BLSTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "./Pairing.sol";
import "./BLS.sol";

/**
* @title BLSTest
* @dev Testing contract for the BLS library.
*/
contract BLSTest {
/*
* Storage
*/

Pairing.G2Point verificationKey;

/*
* Constructor
*/

constructor() {
verificationKey = Pairing.G2Point({
x: [
18523194229674161632574346342370534213928970227736813349975332190798837787897,
5725452645840548248571879966249653216818629536104756116202892528545334967238
],
y: [
3816656720215352836236372430537606984911914992659540439626020770732736710924,
677280212051826798882467475639465784259337739185938192379192340908771705870
]
});
}

/*
* Public functions
*/

function verify(bytes memory _message, uint _signatureX, uint _signatureY) public view returns (bool) {
Pairing.G1Point memory signature = Pairing.G1Point({
x: _signatureX,
y: _signatureY
});
return BLS.verify(verificationKey, _message, signature);
}
}
156 changes: 156 additions & 0 deletions contracts/bls-signature/Pairing.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

/*
* @title Pairing
* @dev BN128 pairing operations.
* @ref https://github.com/JacobEberhardt/ZoKrates/blob/da5b13f845145cf43d555c7741158727ef0018a2/zokrates_core/src/verification.rs
*/

library Pairing {
/*
* Structs
*/

struct G1Point {
uint x;
uint y;
}

struct G2Point {
uint[2] x;
uint[2] y;
}

/*
* Internal functions
*/

/**
* @return The generator of G1.
*/
function P1() internal pure returns (G1Point memory) {
return G1Point(1, 2);
}

/**
* @return The generator of G2.
*/
function P2() internal pure returns (G2Point memory) {
return G2Point({
x: [
11559732032986387107991004021392285783925812861821192530917403151452391805634,
10857046999023057135944570762232829481370756359578518086990519993285655852781
],
y: [
4082367875863433681332203403145435568316851327593401208105741076214120093531,
8495653923123431417604973247489272438418190587263600148770280649306958101930
]
});
}

/**
* @dev Hashes a message into G1.
* @param _message Message to hash.
* @return Hashed G1 point.
*/
function hashToG1(bytes memory _message) internal view returns (G1Point memory) {
uint256 h = uint256(keccak256(_message));
return curveMul(P1(), h);
}

/**
* @dev Negates a point in G1.
* @param _point Point to negate.
* @return The negated point.
*/
function negate(G1Point memory _point) internal pure returns (G1Point memory) {
uint q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
if (_point.x == 0 && _point.y == 0) {
return G1Point(0, 0);
}
return G1Point(_point.x, q - (_point.y % q));
}

/**
* @dev Computes the pairing check e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1
* @param _g1points List of points in G1.
* @param _g2points List of points in G2.
* @return True if pairing check succeeds.
*/
function pairing(G1Point[] memory _g1points, G2Point[] memory _g2points) private view returns (bool) {
require(_g1points.length == _g2points.length, "Point count mismatch.");

uint elements = _g1points.length;
uint inputSize = elements * 6;
uint[] memory input = new uint[](inputSize);

for (uint i = 0; i < elements; i++) {
input[i * 6 + 0] = _g1points[i].x;
input[i * 6 + 1] = _g1points[i].y;
input[i * 6 + 2] = _g2points[i].x[0];
input[i * 6 + 3] = _g2points[i].x[1];
input[i * 6 + 4] = _g2points[i].y[0];
input[i * 6 + 5] = _g2points[i].y[1];
}

uint[1] memory out;
bool success;

assembly {
success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20)
}
require(success, "Pairing operation failed.");

return out[0] != 0;
}

/**
* @dev Convenience method for pairing check on two pairs.
* @param _g1point1 First point in G1.
* @param _g2point1 First point in G2.
* @param _g1point2 Second point in G1.
* @param _g2point2 Second point in G2.
* @return True if the pairing check succeeds.
*/
function pairing2(
G1Point memory _g1point1,
G2Point memory _g2point1,
G1Point memory _g1point2,
G2Point memory _g2point2
) internal view returns (bool) {
G1Point[] memory g1points = new G1Point[](2);
G2Point[] memory g2points = new G2Point[](2);
g1points[0] = _g1point1;
g1points[1] = _g1point2;
g2points[0] = _g2point1;
g2points[1] = _g2point2;
return pairing(g1points, g2points);
}

/*
* Private functions
*/

/**
* @dev Multiplies a point in G1 by a scalar.
* @param _point G1 point to multiply.
* @param _scalar Scalar to multiply.
* @return The resulting G1 point.
*/
function curveMul(G1Point memory _point, uint _scalar) private view returns (G1Point memory) {
uint[3] memory input;
input[0] = _point.x;
input[1] = _point.y;
input[2] = _scalar;

bool success;
G1Point memory result;
assembly {
success := staticcall(sub(gas(), 2000), 7, input, 0x80, result, 0x60)
}
require(success, "Point multiplication failed.");

return result;
}
}
87 changes: 87 additions & 0 deletions test/bls-signature/bls-signature-verification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*-
*
* Hedera Smart Contracts
*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

const { assert } = require('chai');
const { ethers } = require('hardhat');

const Constants = require('../constants');

describe('BLSSignature Test Suite', function () {
const message =
'0x7b0a2020226f70656e223a207b0a20202020227072696365223a2039353931372c0a202020202274696d65223a207b0a20202020202022756e6978223a20313438333134323430302c0a2020202020202269736f223a2022323031362d31322d33315430303a30303a30302e3030305a220a202020207d0a20207d2c0a202022636c6f7365223a207b0a20202020227072696365223a2039363736302c0a202020202274696d65223a207b0a20202020202022756e6978223a20313438333232383830302c0a2020202020202269736f223a2022323031372d30312d30315430303a30303a30302e3030305a220a202020207d0a20207d2c0a2020226c6f6f6b7570223a207b0a20202020227072696365223a2039363736302c0a20202020226b223a20312c0a202020202274696d65223a207b0a20202020202022756e6978223a20313438333232383830302c0a2020202020202269736f223a2022323031372d30312d30315430303a30303a30302e3030305a220a202020207d0a20207d0a7d0a6578616d706c652e636f6d2f6170692f31';
let BLS;
let signers;

before(async function () {
signers = await ethers.getSigners();
const BLSTestFactory = await ethers.getContractFactory(
Constants.Contract.BLSTest
);

BLS = await BLSTestFactory.deploy();

console.log(
`${Constants.Contract.BLSTest} deployed: ${await BLS.getAddress()}`
);
});

it('should verify a valid signature', async () => {
let signatureX =
'11181692345848957662074290878138344227085597134981019040735323471731897153462';
let signatureY =
'6479746447046570360435714249272776082787932146211764251347798668447381926167';
let result = await BLS.verify(message, signatureX, signatureY);
assert.equal(result, true, 'Verification failed.');
});

it('should not verify a invalid signature', async () => {
try {
let signatureX =
'11181692345848957662074290878138344227085597134981019040735323471731897153462';
let signatureY = '12345';
let result = await BLS.verify(message, signatureX, signatureY);
assert.equal(result, false, 'Verification succeded when should have failed.');
} catch (err) {
assert.include(
err.message,
'Pairing operation failed',
'Verification failed.'
);
}
});

it('should not verify a invalid message', async () => {
try {
let signatureX =
'11181692345848957662074290878138344227085597134981019040735323471731897153462';
let signatureY =
'6479746447046570360435714249272776082787932146211764251347798668447381926167';
const result = await BLS.verify('0x123456', signatureX, signatureY);
assert.equal(result, false, 'Verification succeded when should have failed.');

} catch (err) {
assert.include(
err.message,
'Pairing operation failed',
'Verification failed.'
);
}
});
});
1 change: 1 addition & 0 deletions test/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ const Contract = {
InternalCaller: 'InternalCaller',
EthNativePrecompileCaller: 'EthNativePrecompileCaller',
AtomicHTS: 'AtomicHTS',
BLSTest: 'BLSTest',
};

const CALL_EXCEPTION = 'CALL_EXCEPTION';
Expand Down

0 comments on commit 27f5c99

Please sign in to comment.