-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: BLS Signature Verification Example (#775)
* 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
1 parent
f434729
commit 27f5c99
Showing
7 changed files
with
366 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
contracts-abi/contracts/bls-signature/BLSTest.sol/BLSTest.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.' | ||
); | ||
} | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters