Skip to content

Added Schnorr Signature & EC256 libraries #142

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
May 2, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions contracts/libs/crypto/BN254.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

library BN254 {
uint256 internal constant SCALAR_FIELD_SIZE =
21888242871839275222246405745257275088548364400416034343698204186575808495617;
uint256 internal constant BASE_FIELD_SIZE =
21888242871839275222246405745257275088696311157297823662689037894645226208583;

struct Scalar {
uint256 data;
}

struct G1Affine {
uint256 x;
uint256 y;
}

function g1Basepoint() internal pure returns (G1Affine memory basepoint_) {
return G1Affine(1, 2);
}

function scalarFromUint256(uint256 a_) internal pure returns (Scalar memory scalar_) {
scalar_ = Scalar(a_);

require(validateScalar(scalar_), "BN: invalid scalar");
}

function scalarFromUint256ModScalar(uint256 a_) internal pure returns (Scalar memory scalar_) {
return Scalar(a_ % SCALAR_FIELD_SIZE);
}

function g1PointFromAffine(
uint256 x_,
uint256 y_
) internal pure returns (G1Affine memory point_) {
point_ = G1Affine(x_, y_);

require(validateG1Point(point_), "BN254: invalid point");
}

function pointNeg(G1Affine memory a_) internal pure returns (G1Affine memory r_) {
return G1Affine(a_.x, BASE_FIELD_SIZE - a_.y);

Check warning on line 43 in contracts/libs/crypto/BN254.sol

View check run for this annotation

Codecov / codecov/patch

contracts/libs/crypto/BN254.sol#L42-L43

Added lines #L42 - L43 were not covered by tests
}

function pointEqual(
G1Affine memory a_,
G1Affine memory b_
) internal pure returns (bool isEqual_) {
return a_.x == b_.x && a_.y == b_.y;
}

function pointSub(

Check warning on line 53 in contracts/libs/crypto/BN254.sol

View check run for this annotation

Codecov / codecov/patch

contracts/libs/crypto/BN254.sol#L53

Added line #L53 was not covered by tests
G1Affine memory a_,
G1Affine memory b_
) internal view returns (G1Affine memory r_) {
return pointAdd(a_, pointNeg(b_));

Check warning on line 57 in contracts/libs/crypto/BN254.sol

View check run for this annotation

Codecov / codecov/patch

contracts/libs/crypto/BN254.sol#L57

Added line #L57 was not covered by tests
}

function pointAdd(
G1Affine memory a_,
G1Affine memory b_
) internal view returns (G1Affine memory r_) {
assembly {
let call_ := mload(0x40)
mstore(0x40, add(call_, 0x80))

mstore(call_, mload(a_))
mstore(add(call_, 0x20), mload(add(a_, 0x20)))
mstore(add(call_, 0x40), mload(b_))
mstore(add(call_, 0x60), mload(add(b_, 0x20)))

pop(staticcall(gas(), 0x6, call_, 0x80, r_, 0x40))
}
}

function pointMul(
G1Affine memory p_,
Scalar memory a_
) internal view returns (G1Affine memory r_) {
assembly {
let call_ := mload(0x40)
mstore(0x40, add(call_, 0x60))

mstore(call_, mload(p_))
mstore(add(call_, 0x20), mload(add(p_, 0x20)))
mstore(add(call_, 0x40), mload(a_))

pop(staticcall(gas(), 0x7, call_, 0x60, r_, 0x40))
}
}

function basepointMul(Scalar memory a_) internal view returns (G1Affine memory point_) {
return pointMul(g1Basepoint(), a_);
}

function scalarAdd(

Check warning on line 97 in contracts/libs/crypto/BN254.sol

View check run for this annotation

Codecov / codecov/patch

contracts/libs/crypto/BN254.sol#L97

Added line #L97 was not covered by tests
Scalar memory a_,
Scalar memory b_
) internal pure returns (Scalar memory r_) {
return Scalar(addmod(a_.data, b_.data, SCALAR_FIELD_SIZE));

Check warning on line 101 in contracts/libs/crypto/BN254.sol

View check run for this annotation

Codecov / codecov/patch

contracts/libs/crypto/BN254.sol#L101

Added line #L101 was not covered by tests
}

function scalarSub(

Check warning on line 104 in contracts/libs/crypto/BN254.sol

View check run for this annotation

Codecov / codecov/patch

contracts/libs/crypto/BN254.sol#L104

Added line #L104 was not covered by tests
Scalar memory a_,
Scalar memory b_
) internal pure returns (Scalar memory r_) {
if (a_.data > b_.data) {
return Scalar(a_.data - b_.data);

Check warning on line 109 in contracts/libs/crypto/BN254.sol

View check run for this annotation

Codecov / codecov/patch

contracts/libs/crypto/BN254.sol#L109

Added line #L109 was not covered by tests
}

return Scalar(SCALAR_FIELD_SIZE - (b_.data - a_.data));

Check warning on line 112 in contracts/libs/crypto/BN254.sol

View check run for this annotation

Codecov / codecov/patch

contracts/libs/crypto/BN254.sol#L112

Added line #L112 was not covered by tests
}

function scalarMul(

Check warning on line 115 in contracts/libs/crypto/BN254.sol

View check run for this annotation

Codecov / codecov/patch

contracts/libs/crypto/BN254.sol#L115

Added line #L115 was not covered by tests
Scalar memory a_,
Scalar memory b_
) internal pure returns (Scalar memory r_) {
return Scalar(mulmod(a_.data, b_.data, SCALAR_FIELD_SIZE));

Check warning on line 119 in contracts/libs/crypto/BN254.sol

View check run for this annotation

Codecov / codecov/patch

contracts/libs/crypto/BN254.sol#L119

Added line #L119 was not covered by tests
}

function validateScalar(BN254.Scalar memory scalar_) internal pure returns (bool isValid_) {
return scalar_.data < SCALAR_FIELD_SIZE;
}

function validateG1Point(BN254.G1Affine memory point_) internal pure returns (bool isValid_) {
if (point_.x == 0 && point_.y == 0) {
return true;

Check warning on line 128 in contracts/libs/crypto/BN254.sol

View check run for this annotation

Codecov / codecov/patch

contracts/libs/crypto/BN254.sol#L128

Added line #L128 was not covered by tests
}

if (point_.x > BASE_FIELD_SIZE || point_.y > BASE_FIELD_SIZE) {
return false;

Check warning on line 132 in contracts/libs/crypto/BN254.sol

View check run for this annotation

Codecov / codecov/patch

contracts/libs/crypto/BN254.sol#L132

Added line #L132 was not covered by tests
}

uint256 lhs_ = mulmod(point_.y, point_.y, BASE_FIELD_SIZE);
uint256 rhs_ = mulmod(point_.x, point_.x, BASE_FIELD_SIZE);
rhs_ = mulmod(rhs_, point_.x, BASE_FIELD_SIZE);
rhs_ = (rhs_ + 3) % BASE_FIELD_SIZE;

return lhs_ == rhs_;
}
}
45 changes: 45 additions & 0 deletions contracts/libs/crypto/SchnorrSignature.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

import {BN254} from "./BN254.sol";
import {MemoryUtils} from "../utils/MemoryUtils.sol";

library SchnorrSignature {
using MemoryUtils for *;

function verifySignature(
bytes32 hashedMessage_,
bytes memory signature_,
bytes memory pubKey_
) internal view returns (bool isVerified_) {
(BN254.G1Affine memory r_, BN254.Scalar memory e_) = parseSignature(signature_);
BN254.G1Affine memory a_ = parsePubKey(pubKey_);

BN254.G1Affine memory lhs_ = BN254.basepointMul(e_);

uint256 challenge_ = uint256(keccak256(abi.encodePacked(r_.x, r_.y, hashedMessage_)));
BN254.Scalar memory c_ = BN254.scalarFromUint256ModScalar(challenge_);

BN254.G1Affine memory rhs_ = BN254.pointAdd(r_, BN254.pointMul(a_, c_));

return BN254.pointEqual(lhs_, rhs_);
}

function parseSignature(
bytes memory signature_
) internal pure returns (BN254.G1Affine memory r_, BN254.Scalar memory e_) {
(uint256 pointX_, uint256 pointY_, uint256 scalar_) = abi.decode(
signature_,
(uint256, uint256, uint256)
);

r_ = BN254.g1PointFromAffine(pointX_, pointY_);
e_ = BN254.scalarFromUint256(scalar_);
}

function parsePubKey(bytes memory pubKey_) internal pure returns (BN254.G1Affine memory a_) {
(uint256 pointX_, uint256 pointY_) = abi.decode(pubKey_, (uint256, uint256));

return BN254.g1PointFromAffine(pointX_, pointY_);
}
}
27 changes: 27 additions & 0 deletions contracts/mock/libs/crypto/BN254Mock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

import {BN254} from "../../../libs/crypto/BN254.sol";

contract BN254Mock {
function pointAdd(
BN254.G1Affine memory a_,
BN254.G1Affine memory b_
) external view returns (BN254.G1Affine memory r_) {
return BN254.pointAdd(a_, b_);
}

function pointSub(
BN254.G1Affine memory a_,
BN254.G1Affine memory b_
) external view returns (BN254.G1Affine memory r_) {
return BN254.pointSub(a_, b_);
}

function pointMul(
BN254.G1Affine memory p_,
BN254.Scalar memory a_
) external view returns (BN254.G1Affine memory r_) {
return BN254.pointMul(p_, a_);
}
}
14 changes: 14 additions & 0 deletions contracts/mock/libs/crypto/SchnorrSignatureMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

import {SchnorrSignature} from "../../../libs/crypto/SchnorrSignature.sol";

contract SchnorrSignatureMock {
function verifySignature(
bytes32 hashedMessage_,
bytes memory signature_,
bytes memory pubKey_
) external view returns (bool isVerified_) {
return SchnorrSignature.verifySignature(hashedMessage_, signature_, pubKey_);
}
}
64 changes: 60 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"devDependencies": {
"@iden3/js-crypto": "^1.1.0",
"@iden3/js-merkletree": "^1.3.1",
"@noble/curves": "^1.9.0",
"@nomicfoundation/hardhat-chai-matchers": "^2.0.8",
"@nomicfoundation/hardhat-ethers": "^3.0.8",
"@nomicfoundation/hardhat-network-helpers": "^1.0.12",
Expand Down
Loading