From 4d69bb8bb0063b4b1f5533f4816091a21f1e7e4b Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Thu, 24 Apr 2025 21:30:58 +0300 Subject: [PATCH 01/20] init bn254 & schnorr --- contracts/libs/crypto/BN254.sol | 142 ++++++++++++++++++ contracts/libs/crypto/SchnorrSignature.sol | 45 ++++++ contracts/mock/libs/crypto/BN254Mock.sol | 27 ++++ .../mock/libs/crypto/SchnorrSignatureMock.sol | 14 ++ package-lock.json | 64 +++++++- package.json | 1 + 6 files changed, 289 insertions(+), 4 deletions(-) create mode 100644 contracts/libs/crypto/BN254.sol create mode 100644 contracts/libs/crypto/SchnorrSignature.sol create mode 100644 contracts/mock/libs/crypto/BN254Mock.sol create mode 100644 contracts/mock/libs/crypto/SchnorrSignatureMock.sol diff --git a/contracts/libs/crypto/BN254.sol b/contracts/libs/crypto/BN254.sol new file mode 100644 index 00000000..afd4da98 --- /dev/null +++ b/contracts/libs/crypto/BN254.sol @@ -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); + } + + function pointEqual( + G1Affine memory a_, + G1Affine memory b_ + ) internal pure returns (bool isEqual_) { + return a_.x == b_.x && a_.y == b_.y; + } + + function pointSub( + G1Affine memory a_, + G1Affine memory b_ + ) internal view returns (G1Affine memory r_) { + return pointAdd(a_, pointNeg(b_)); + } + + 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( + Scalar memory a_, + Scalar memory b_ + ) internal pure returns (Scalar memory r_) { + return Scalar(addmod(a_.data, b_.data, SCALAR_FIELD_SIZE)); + } + + function scalarSub( + Scalar memory a_, + Scalar memory b_ + ) internal pure returns (Scalar memory r_) { + if (a_.data > b_.data) { + return Scalar(a_.data - b_.data); + } + + return Scalar(SCALAR_FIELD_SIZE - (b_.data - a_.data)); + } + + function scalarMul( + Scalar memory a_, + Scalar memory b_ + ) internal pure returns (Scalar memory r_) { + return Scalar(mulmod(a_.data, b_.data, SCALAR_FIELD_SIZE)); + } + + 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; + } + + if (point_.x > BASE_FIELD_SIZE || point_.y > BASE_FIELD_SIZE) { + return false; + } + + uint256 lhs_ = mulmod(point_.y, point_.y, BASE_FIELD_SIZE); + uint256 rhs_ = mulmod(point_.x, point_.x, BASE_FIELD_SIZE); + rhs_ = mulmod(point_.x, point_.x, BASE_FIELD_SIZE); + rhs_ = (point_.x + 3) % BASE_FIELD_SIZE; + + return lhs_ == rhs_; + } +} diff --git a/contracts/libs/crypto/SchnorrSignature.sol b/contracts/libs/crypto/SchnorrSignature.sol new file mode 100644 index 00000000..cd117f22 --- /dev/null +++ b/contracts/libs/crypto/SchnorrSignature.sol @@ -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_); + } +} diff --git a/contracts/mock/libs/crypto/BN254Mock.sol b/contracts/mock/libs/crypto/BN254Mock.sol new file mode 100644 index 00000000..921c6257 --- /dev/null +++ b/contracts/mock/libs/crypto/BN254Mock.sol @@ -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_); + } +} diff --git a/contracts/mock/libs/crypto/SchnorrSignatureMock.sol b/contracts/mock/libs/crypto/SchnorrSignatureMock.sol new file mode 100644 index 00000000..30dc183c --- /dev/null +++ b/contracts/mock/libs/crypto/SchnorrSignatureMock.sol @@ -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_); + } +} diff --git a/package-lock.json b/package-lock.json index 686e27c3..945085dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,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", @@ -1172,13 +1173,29 @@ } }, "node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.0.tgz", + "integrity": "sha512-7YDlXiNMdO1YZeH6t/kvopHHbIZzlxrCV9WLqCY6QhcXOoXiNCMDqJIglZ9Yjx5+w7Dz30TITFrlTjnRg7sKEg==", "dev": true, "license": "MIT", "dependencies": { - "@noble/hashes": "1.3.2" + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -1658,6 +1675,19 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@scure/bip32/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@scure/bip39": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", @@ -3837,6 +3867,19 @@ "node": ">=14.0.0" } }, + "node_modules/ethers/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/ethers/node_modules/@types/node": { "version": "22.7.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", @@ -8314,6 +8357,19 @@ "dev": true, "license": "MIT" }, + "node_modules/viem/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/viem/node_modules/ws": { "version": "8.13.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", diff --git a/package.json b/package.json index 30844e7b..38f54826 100644 --- a/package.json +++ b/package.json @@ -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", From 99953b826768bf2dc14f74945cdb2e1ff1ad48a4 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Fri, 25 Apr 2025 11:23:20 +0300 Subject: [PATCH 02/20] fixes & added basic test for schnorr --- contracts/libs/crypto/BN254.sol | 4 +- test/libs/crypto/SchnorrSignature.test.ts | 63 +++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 test/libs/crypto/SchnorrSignature.test.ts diff --git a/contracts/libs/crypto/BN254.sol b/contracts/libs/crypto/BN254.sol index afd4da98..81380f8f 100644 --- a/contracts/libs/crypto/BN254.sol +++ b/contracts/libs/crypto/BN254.sol @@ -134,8 +134,8 @@ library BN254 { uint256 lhs_ = mulmod(point_.y, point_.y, BASE_FIELD_SIZE); uint256 rhs_ = mulmod(point_.x, point_.x, BASE_FIELD_SIZE); - rhs_ = mulmod(point_.x, point_.x, BASE_FIELD_SIZE); - rhs_ = (point_.x + 3) % BASE_FIELD_SIZE; + rhs_ = mulmod(rhs_, point_.x, BASE_FIELD_SIZE); + rhs_ = (rhs_ + 3) % BASE_FIELD_SIZE; return lhs_ == rhs_; } diff --git a/test/libs/crypto/SchnorrSignature.test.ts b/test/libs/crypto/SchnorrSignature.test.ts new file mode 100644 index 00000000..13f17e35 --- /dev/null +++ b/test/libs/crypto/SchnorrSignature.test.ts @@ -0,0 +1,63 @@ +import { ethers } from "hardhat"; +import { expect } from "chai"; +import { Reverter } from "@/test/helpers/reverter"; + +import { SchnorrSignatureMock } from "@ethers-v6"; + +import { bn254 } from "@noble/curves/bn254"; +import { bytesToNumberBE } from "@noble/curves/abstract/utils"; +import { AffinePoint } from "@noble/curves/abstract/curve"; +import { hash } from "crypto"; + +describe.only("SchnorrSignature", () => { + const schnorrKeyPair = () => { + const privKey = bytesToNumberBE(bn254.utils.randomPrivateKey()); + const pubKey = bn254.G1.ProjectivePoint.BASE.multiply(privKey).toAffine(); + + return { privKey, pubKey }; + }; + + const serializePoint = (p: AffinePoint) => { + return ethers.solidityPacked(["uint256", "uint256"], [p.x, p.y]); + }; + + const schnorrSign = function (hashedMessage: string, privKey: bigint) { + const randomness = schnorrKeyPair(); + const k = randomness.privKey; + const R = randomness.pubKey; + + const c = BigInt(ethers.solidityPackedKeccak256(["uint256", "uint256", "bytes32"], [R.x, R.y, hashedMessage])); + + const e = bn254.fields.Fr.create(k + c * privKey); + + return ethers.solidityPacked(["uint256", "uint256", "uint256"], [R.x, R.y, e]); + }; + + const reverter = new Reverter(); + + let schnorr: SchnorrSignatureMock; + + before(async () => { + const SchnorrSignature = await ethers.getContractFactory("SchnorrSignatureMock"); + + schnorr = await SchnorrSignature.deploy(); + + await reverter.snapshot(); + }); + + afterEach(reverter.revert); + + describe.only("verify", () => { + it.only("should verify the signature", async () => { + const { privKey, pubKey } = schnorrKeyPair(); + + const message = "0x1337"; + const hashedMessage = ethers.keccak256(message); + + const signature = schnorrSign(hashedMessage, privKey); + const pubKeyBytes = serializePoint(pubKey); + + console.log(await schnorr.verifySignature(hashedMessage, signature, pubKeyBytes)); + }); + }); +}); From 1325d23db0100b1e51179731ed37c00c83d7a8a7 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Fri, 25 Apr 2025 11:57:14 +0300 Subject: [PATCH 03/20] small adjustments --- test/libs/crypto/SchnorrSignature.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/libs/crypto/SchnorrSignature.test.ts b/test/libs/crypto/SchnorrSignature.test.ts index 13f17e35..46ef4ea3 100644 --- a/test/libs/crypto/SchnorrSignature.test.ts +++ b/test/libs/crypto/SchnorrSignature.test.ts @@ -1,5 +1,4 @@ import { ethers } from "hardhat"; -import { expect } from "chai"; import { Reverter } from "@/test/helpers/reverter"; import { SchnorrSignatureMock } from "@ethers-v6"; @@ -7,7 +6,6 @@ import { SchnorrSignatureMock } from "@ethers-v6"; import { bn254 } from "@noble/curves/bn254"; import { bytesToNumberBE } from "@noble/curves/abstract/utils"; import { AffinePoint } from "@noble/curves/abstract/curve"; -import { hash } from "crypto"; describe.only("SchnorrSignature", () => { const schnorrKeyPair = () => { From afc9ad91bb3078158ca6db15ecc9fdd4686a4f86 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Wed, 30 Apr 2025 17:51:56 +0300 Subject: [PATCH 04/20] rewrote schnorr for any curve --- contracts/libs/crypto/EC.sol | 395 ++++++++++++++++++ contracts/libs/crypto/SchnorrSignature.sol | 98 ++++- .../mock/libs/crypto/SchnorrSignatureMock.sol | 14 +- test/libs/crypto/SchnorrSignature.test.ts | 18 +- 4 files changed, 495 insertions(+), 30 deletions(-) create mode 100644 contracts/libs/crypto/EC.sol diff --git a/contracts/libs/crypto/EC.sol b/contracts/libs/crypto/EC.sol new file mode 100644 index 00000000..a27401df --- /dev/null +++ b/contracts/libs/crypto/EC.sol @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; + +library EC { + struct Jpoint { + uint256 x; + uint256 y; + uint256 z; + } + + function isOnCurve( + uint256 x_, + uint256 y_, + uint256 a_, + uint256 b_, + uint256 p_ + ) internal pure returns (bool result_) { + assembly ("memory-safe") { + let lhs_ := mulmod(y_, y_, p_) + let rhs_ := addmod(mulmod(addmod(mulmod(x_, x_, p_), a_, p_), x_, p_), b_, p_) + + result_ := and(and(lt(x_, p_), lt(y_, p_)), eq(lhs_, rhs_)) // Should conform with the Weierstrass equation + } + } + + function isValidScalar(uint256 u_, uint256 n_) internal pure returns (bool result_) { + return u_ < n_; + } + + function jEqual( + Jpoint memory point1_, + Jpoint memory point2_, + uint256 p_ + ) internal view returns (bool result_) { + (uint256 point1X_, uint256 point1Y_) = affineFromJacobian(point1_, p_); + (uint256 point2X_, uint256 point2Y_) = affineFromJacobian(point2_, p_); + + return point1X_ == point2X_ && point1Y_ == point2Y_; + } + + /** + * @dev Reduce from jacobian to affine coordinates + * @param point_ - point with jacobian coordinate x, y and z + * @return ax_ - affine coordinate x + * @return ay_ - affine coordinate y + */ + function affineFromJacobian( + Jpoint memory point_, + uint256 p_ + ) internal view returns (uint256 ax_, uint256 ay_) { + if (point_.z == 0) return (0, 0); + + uint256 zInverse_ = Math.invModPrime(point_.z, p_); + + assembly ("memory-safe") { + let zzInverse_ := mulmod(zInverse_, zInverse_, p_) + + ax_ := mulmod(mload(point_), zzInverse_, p_) + ay_ := mulmod(mload(add(point_, 0x20)), mulmod(zzInverse_, zInverse_, p_), p_) + } + } + + function jacobianFromAffine( + uint256 x_, + uint256 y_ + ) internal pure returns (Jpoint memory res_) { + return Jpoint(x_, y_, 1); + } + + function jacobianInfinity() internal pure returns (Jpoint memory res_) { + return Jpoint(0, 0, 0); + } + + function jMultShamir( + Jpoint[16] memory points_, + uint256 u_, + uint256 p_, + uint256 a_ + ) internal pure returns (Jpoint memory point_) { + unchecked { + for (uint256 i = 0; i < 64; ++i) { + if (point_.z > 0) { + (point_.x, point_.y, point_.z) = _jDouble( + point_.x, + point_.y, + point_.z, + p_, + a_ + ); + (point_.x, point_.y, point_.z) = _jDouble( + point_.x, + point_.y, + point_.z, + p_, + a_ + ); + (point_.x, point_.y, point_.z) = _jDouble( + point_.x, + point_.y, + point_.z, + p_, + a_ + ); + (point_.x, point_.y, point_.z) = _jDouble( + point_.x, + point_.y, + point_.z, + p_, + a_ + ); + } + // Read 4 bits of u1 which corresponds to the lookup index in the table. + uint256 pos_ = u_ >> 252; + + // Points that have z = 0 are points at infinity. They are the additive 0 of the group + // - if the lookup point is a 0, we can skip it + // - otherwise: + // - if the current point (x, y, z) is 0, we use the lookup point as our new value (0+P=P) + // - if the current point (x, y, z) is not 0, both points are valid and we can use `_jAdd` + if (points_[pos_].z != 0) { + if (point_.z == 0) { + (point_.x, point_.y, point_.z) = ( + points_[pos_].x, + points_[pos_].y, + points_[pos_].z + ); + } else { + (point_.x, point_.y, point_.z) = _jAdd(points_[pos_], point_, p_, a_); + } + } + + u_ <<= 4; + } + } + } + + /** + * @dev Compute G·u1 + P·u2 using the precomputed points for G and P (see {_preComputeJacobianPoints}). + * + * Uses Strauss Shamir trick for EC multiplication + * https://stackoverflow.com/questions/50993471/ec-scalar-multiplication-with-strauss-shamir-method + */ + function jMultShamir2( + Jpoint[16] memory points_, + uint256 u1_, + uint256 u2_, + uint256 p_, + uint256 a_ + ) internal view returns (Jpoint memory point_) { + unchecked { + for (uint256 i = 0; i < 128; ++i) { + if (point_.z > 0) { + (point_.x, point_.y, point_.z) = _jDouble( + point_.x, + point_.y, + point_.z, + p_, + a_ + ); + (point_.x, point_.y, point_.z) = _jDouble( + point_.x, + point_.y, + point_.z, + p_, + a_ + ); + } + // Read 2 bits of u1, and 2 bits of u2. Combining the two gives the lookup index in the table. + uint256 pos_ = ((u1_ >> 252) & 0xc) | ((u2_ >> 254) & 0x3); + // Points that have z = 0 are points at infinity. They are the additive 0 of the group + // - if the lookup point is a 0, we can skip it + // - otherwise: + // - if the current point (x, y, z) is 0, we use the lookup point as our new value (0+P=P) + // - if the current point (x, y, z) is not 0, both points are valid and we can use `_jAdd` + if (points_[pos_].z != 0) { + if (point_.z == 0) { + (point_.x, point_.y, point_.z) = ( + points_[pos_].x, + points_[pos_].y, + points_[pos_].z + ); + } else { + (point_.x, point_.y, point_.z) = _jAdd(points_[pos_], point_, p_, a_); + } + } + u1_ <<= 2; + u2_ <<= 2; + } + } + } + + /** + * @dev Precompute a matrice of useful jacobian points associated with a given P. This can be seen as a 4x4 matrix + * that contains combination of P and G (generator) up to 3 times each + */ + function preComputeJacobianPoints2( + uint256 x_, + uint256 y_, + uint256 gx_, + uint256 gy_, + uint256 p_, + uint256 a_ + ) internal pure returns (Jpoint[16] memory points_) { + points_[0x00] = jacobianInfinity(); + points_[0x01] = jacobianFromAffine(x_, y_); + points_[0x04] = jacobianFromAffine(gx_, gy_); + points_[0x02] = jDoublePoint(points_[0x01], p_, a_); + points_[0x08] = jDoublePoint(points_[0x04], p_, a_); + points_[0x03] = jAddPoint(points_[0x01], points_[0x02], p_, a_); + points_[0x05] = jAddPoint(points_[0x01], points_[0x04], p_, a_); + points_[0x06] = jAddPoint(points_[0x02], points_[0x04], p_, a_); + points_[0x07] = jAddPoint(points_[0x03], points_[0x04], p_, a_); + points_[0x09] = jAddPoint(points_[0x01], points_[0x08], p_, a_); + points_[0x0a] = jAddPoint(points_[0x02], points_[0x08], p_, a_); + points_[0x0b] = jAddPoint(points_[0x03], points_[0x08], p_, a_); + points_[0x0c] = jAddPoint(points_[0x04], points_[0x08], p_, a_); + points_[0x0d] = jAddPoint(points_[0x01], points_[0x0c], p_, a_); + points_[0x0e] = jAddPoint(points_[0x02], points_[0x0c], p_, a_); + points_[0x0f] = jAddPoint(points_[0x03], points_[0x0c], p_, a_); + } + + function preComputeJacobianPoints( + uint256 x_, + uint256 y_, + uint256 p_, + uint256 a_ + ) internal pure returns (Jpoint[16] memory points_) { + points_[0x00] = jacobianInfinity(); + points_[0x01] = jacobianFromAffine(x_, y_); + points_[0x02] = jDoublePoint(points_[0x01], p_, a_); + points_[0x03] = jAddPoint(points_[0x01], points_[0x02], p_, a_); + points_[0x04] = jAddPoint(points_[0x01], points_[0x03], p_, a_); + points_[0x05] = jAddPoint(points_[0x01], points_[0x04], p_, a_); + points_[0x06] = jAddPoint(points_[0x01], points_[0x05], p_, a_); + points_[0x07] = jAddPoint(points_[0x01], points_[0x06], p_, a_); + points_[0x08] = jAddPoint(points_[0x01], points_[0x07], p_, a_); + points_[0x09] = jAddPoint(points_[0x01], points_[0x08], p_, a_); + points_[0x0a] = jAddPoint(points_[0x01], points_[0x09], p_, a_); + points_[0x0b] = jAddPoint(points_[0x01], points_[0x0a], p_, a_); + points_[0x0c] = jAddPoint(points_[0x01], points_[0x0b], p_, a_); + points_[0x0d] = jAddPoint(points_[0x01], points_[0x0c], p_, a_); + points_[0x0e] = jAddPoint(points_[0x01], points_[0x0d], p_, a_); + points_[0x0f] = jAddPoint(points_[0x01], points_[0x0e], p_, a_); + + // points_[0x00] = jacobianFromAffine(0, 0); + // points_[0x01] = jacobianFromAffine(x_, y_); + // points_[0x02] = jDoublePoint(points_[0x01], p_, a_); + // points_[0x04] = jDoublePoint(points_[0x02], p_, a_); + // points_[0x08] = jDoublePoint(points_[0x04], p_, a_); + // points_[0x03] = jAddPoint(points_[0x01], points_[0x02], p_, a_); + // points_[0x06] = jDoublePoint(points_[0x03], p_, a_); + // points_[0x0c] = jDoublePoint(points_[0x06], p_, a_); + // points_[0x05] = jAddPoint(points_[0x01], points_[0x04], p_, a_); + // points_[0x0a] = jDoublePoint(points_[0x05], p_, a_); + // points_[0x07] = jAddPoint(points_[0x01], points_[0x06], p_, a_); + // points_[0x0e] = jDoublePoint(points_[0x07], p_, a_); + // points_[0x09] = jAddPoint(points_[0x01], points_[0x08], p_, a_); + // points_[0x0b] = jAddPoint(points_[0x01], points_[0x0a], p_, a_); + // points_[0x0d] = jAddPoint(points_[0x01], points_[0x0c], p_, a_); + // points_[0x0f] = jAddPoint(points_[0x01], points_[0x0e], p_, a_); + } + + function jAddPoint( + Jpoint memory point1_, + Jpoint memory point2_, + uint256 p_, + uint256 a_ + ) internal pure returns (Jpoint memory) { + (uint256 x_, uint256 y_, uint256 z_) = _jAdd(point1_, point2_, p_, a_); + + return Jpoint(x_, y_, z_); + } + + function jDoublePoint( + Jpoint memory point_, + uint256 p_, + uint256 a_ + ) internal pure returns (Jpoint memory) { + (uint256 x_, uint256 y_, uint256 z_) = _jDouble(point_.x, point_.y, point_.z, p_, a_); + + return Jpoint(x_, y_, z_); + } + + /** + * @dev Point addition on the jacobian coordinates + * Reference: https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-add-1998-cmo-2 + */ + function _jAdd( + Jpoint memory point1_, + Jpoint memory point2_, + uint256 p_, + uint256 a_ + ) private pure returns (uint256 resX_, uint256 resY_, uint256 resZ_) { + assembly ("memory-safe") { + let zz1_ := mulmod(mload(add(point1_, 0x40)), mload(add(point1_, 0x40)), p_) + let s1_ := mulmod( + mload(add(point1_, 0x20)), + mulmod( + mulmod(mload(add(point2_, 0x40)), mload(add(point2_, 0x40)), p_), + mload(add(point2_, 0x40)), + p_ + ), + p_ + ) + let r_ := addmod( + mulmod(mload(add(point2_, 0x20)), mulmod(zz1_, mload(add(point1_, 0x40)), p_), p_), + sub(p_, s1_), + p_ + ) + let u1_ := mulmod( + mload(point1_), + mulmod(mload(add(point2_, 0x40)), mload(add(point2_, 0x40)), p_), + p_ + ) + let h_ := addmod(mulmod(mload(point2_), zz1_, p_), sub(p_, u1_), p_) + + // detect edge cases where inputs are identical + switch and(iszero(r_), iszero(h_)) + // case 0: points are different + case 0 { + let hh_ := mulmod(h_, h_, p_) + + resX_ := addmod( + addmod(mulmod(r_, r_, p_), sub(p_, mulmod(h_, hh_, p_)), p_), + sub(p_, mulmod(2, mulmod(u1_, hh_, p_), p_)), + p_ + ) + resY_ := addmod( + mulmod(r_, addmod(mulmod(u1_, hh_, p_), sub(p_, resX_), p_), p_), + sub(p_, mulmod(s1_, mulmod(h_, hh_, p_), p_)), + p_ + ) + resZ_ := mulmod( + h_, + mulmod(mload(add(point1_, 0x40)), mload(add(point2_, 0x40)), p_), + p_ + ) + } + // case 1: points are equal + case 1 { + let yy_ := mulmod(mload(add(point2_, 0x20)), mload(add(point2_, 0x20)), p_) + let zz_ := mulmod(mload(add(point2_, 0x40)), mload(add(point2_, 0x40)), p_) + let xx_ := mulmod(mload(point2_), mload(point2_), p_) + let m_ := addmod(mulmod(3, xx_, p_), mulmod(a_, mulmod(zz_, zz_, p_), p_), p_) + let s_ := mulmod(4, mulmod(mload(point2_), yy_, p_), p_) + + resX_ := addmod(mulmod(m_, m_, p_), sub(p_, mulmod(2, s_, p_)), p_) + + // cut the computation to avoid stack too deep + let rytmp1_ := sub(p_, mulmod(8, mulmod(yy_, yy_, p_), p_)) + let rytmp2_ := addmod(s_, sub(p_, resX_), p_) + resY_ := addmod(mulmod(m_, rytmp2_, p_), rytmp1_, p_) + + resZ_ := mulmod( + 2, + mulmod(mload(add(point2_, 0x20)), mload(add(point2_, 0x40)), p_), + p_ + ) + } + } + } + + /** + * @dev Point doubling on the jacobian coordinates + * Reference: https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-1998-cmo-2 + */ + function _jDouble( + uint256 x_, + uint256 y_, + uint256 z_, + uint256 p_, + uint256 a_ + ) private pure returns (uint256 resX_, uint256 resY_, uint256 resZ_) { + assembly ("memory-safe") { + let yy_ := mulmod(y_, y_, p_) + let zz_ := mulmod(z_, z_, p_) + let m_ := addmod( + mulmod(3, mulmod(x_, x_, p_), p_), + mulmod(a_, mulmod(zz_, zz_, p_), p_), + p_ + ) + let s_ := mulmod(4, mulmod(x_, yy_, p_), p_) + + resX_ := addmod(mulmod(m_, m_, p_), sub(p_, mulmod(2, s_, p_)), p_) + resY_ := addmod( + mulmod(m_, addmod(s_, sub(p_, resX_), p_), p_), + sub(p_, mulmod(8, mulmod(yy_, yy_, p_), p_)), + p_ + ) + resZ_ := mulmod(2, mulmod(y_, z_, p_), p_) + } + } +} diff --git a/contracts/libs/crypto/SchnorrSignature.sol b/contracts/libs/crypto/SchnorrSignature.sol index cd117f22..e39a5a2b 100644 --- a/contracts/libs/crypto/SchnorrSignature.sol +++ b/contracts/libs/crypto/SchnorrSignature.sol @@ -1,45 +1,101 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.21; -import {BN254} from "./BN254.sol"; +import {EC} from "./EC.sol"; import {MemoryUtils} from "../utils/MemoryUtils.sol"; library SchnorrSignature { using MemoryUtils for *; - function verifySignature( + struct Parameters { + uint256 a; + uint256 b; + uint256 gx; + uint256 gy; + uint256 p; + uint256 n; + } + + struct _Inputs { + uint256 rx; + uint256 ry; + uint256 e; + uint256 px; + uint256 py; + } + + function verify( + Parameters memory curveParams_, 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_); + ) internal view returns (bool) { + _Inputs memory inputs_; - BN254.G1Affine memory lhs_ = BN254.basepointMul(e_); + (inputs_.rx, inputs_.ry, inputs_.e) = _parseSignature(signature_); + (inputs_.px, inputs_.py) = _parsePubKey(pubKey_); - uint256 challenge_ = uint256(keccak256(abi.encodePacked(r_.x, r_.y, hashedMessage_))); - BN254.Scalar memory c_ = BN254.scalarFromUint256ModScalar(challenge_); + require( + EC.isOnCurve(inputs_.rx, inputs_.ry, curveParams_.a, curveParams_.b, curveParams_.p) + ); + require( + EC.isOnCurve(inputs_.px, inputs_.py, curveParams_.a, curveParams_.b, curveParams_.p) + ); + require(EC.isValidScalar(inputs_.e, curveParams_.n)); - BN254.G1Affine memory rhs_ = BN254.pointAdd(r_, BN254.pointMul(a_, c_)); + EC.Jpoint[16] memory baseShamir_ = EC.preComputeJacobianPoints( + curveParams_.gx, + curveParams_.gy, + curveParams_.p, + curveParams_.a + ); + EC.Jpoint memory lhs_ = EC.jMultShamir( + baseShamir_, + inputs_.e, + curveParams_.p, + curveParams_.a + ); + + uint256 c_ = uint256( + keccak256( + abi.encodePacked( + curveParams_.gx, + curveParams_.gy, + inputs_.rx, + inputs_.ry, + hashedMessage_ + ) + ) + ) % curveParams_.n; + + EC.Jpoint[16] memory pubKeyShamir_ = EC.preComputeJacobianPoints( + inputs_.px, + inputs_.py, + curveParams_.p, + curveParams_.a + ); + EC.Jpoint memory rhs_ = EC.jMultShamir(pubKeyShamir_, c_, curveParams_.p, curveParams_.a); + rhs_ = EC.jAddPoint( + EC.jacobianFromAffine(inputs_.rx, inputs_.ry), + rhs_, + curveParams_.p, + curveParams_.a + ); - return BN254.pointEqual(lhs_, rhs_); + return EC.jEqual(lhs_, rhs_, curveParams_.p); } - function parseSignature( + 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) - ); + ) private pure returns (uint256 rx_, uint256 ry_, uint256 e_) { + require(signature_.length == 96); - r_ = BN254.g1PointFromAffine(pointX_, pointY_); - e_ = BN254.scalarFromUint256(scalar_); + (rx_, ry_, e_) = abi.decode(signature_, (uint256, uint256, uint256)); } - function parsePubKey(bytes memory pubKey_) internal pure returns (BN254.G1Affine memory a_) { - (uint256 pointX_, uint256 pointY_) = abi.decode(pubKey_, (uint256, uint256)); + function _parsePubKey(bytes memory pubKey_) private pure returns (uint256 px_, uint256 py_) { + require(pubKey_.length == 64); - return BN254.g1PointFromAffine(pointX_, pointY_); + (px_, py_) = abi.decode(pubKey_, (uint256, uint256)); } } diff --git a/contracts/mock/libs/crypto/SchnorrSignatureMock.sol b/contracts/mock/libs/crypto/SchnorrSignatureMock.sol index 30dc183c..749da5fe 100644 --- a/contracts/mock/libs/crypto/SchnorrSignatureMock.sol +++ b/contracts/mock/libs/crypto/SchnorrSignatureMock.sol @@ -4,11 +4,21 @@ pragma solidity ^0.8.21; import {SchnorrSignature} from "../../../libs/crypto/SchnorrSignature.sol"; contract SchnorrSignatureMock { - function verifySignature( + SchnorrSignature.Parameters private _secp256k1CurveParams = + SchnorrSignature.Parameters({ + a: 0x0000000000000000000000000000000000000000000000000000000000000000, + b: 0x0000000000000000000000000000000000000000000000000000000000000007, + gx: 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798, + gy: 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8, + p: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f, + n: 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 + }); + + function verifySECP256k1( bytes32 hashedMessage_, bytes memory signature_, bytes memory pubKey_ ) external view returns (bool isVerified_) { - return SchnorrSignature.verifySignature(hashedMessage_, signature_, pubKey_); + return SchnorrSignature.verify(_secp256k1CurveParams, hashedMessage_, signature_, pubKey_); } } diff --git a/test/libs/crypto/SchnorrSignature.test.ts b/test/libs/crypto/SchnorrSignature.test.ts index 46ef4ea3..e51b1bb1 100644 --- a/test/libs/crypto/SchnorrSignature.test.ts +++ b/test/libs/crypto/SchnorrSignature.test.ts @@ -3,14 +3,14 @@ import { Reverter } from "@/test/helpers/reverter"; import { SchnorrSignatureMock } from "@ethers-v6"; -import { bn254 } from "@noble/curves/bn254"; +import { secp256k1 } from "@noble/curves/secp256k1"; import { bytesToNumberBE } from "@noble/curves/abstract/utils"; import { AffinePoint } from "@noble/curves/abstract/curve"; describe.only("SchnorrSignature", () => { const schnorrKeyPair = () => { - const privKey = bytesToNumberBE(bn254.utils.randomPrivateKey()); - const pubKey = bn254.G1.ProjectivePoint.BASE.multiply(privKey).toAffine(); + const privKey = bytesToNumberBE(secp256k1.utils.randomPrivateKey()); + const pubKey = secp256k1.ProjectivePoint.BASE.multiply(privKey).toAffine(); return { privKey, pubKey }; }; @@ -24,9 +24,13 @@ describe.only("SchnorrSignature", () => { const k = randomness.privKey; const R = randomness.pubKey; - const c = BigInt(ethers.solidityPackedKeccak256(["uint256", "uint256", "bytes32"], [R.x, R.y, hashedMessage])); - - const e = bn254.fields.Fr.create(k + c * privKey); + const c = BigInt( + ethers.solidityPackedKeccak256( + ["uint256", "uint256", "uint256", "uint256", "bytes32"], + [secp256k1.CURVE.Gx, secp256k1.CURVE.Gy, R.x, R.y, hashedMessage], + ), + ); + const e = (k + c * privKey) % secp256k1.CURVE.n; return ethers.solidityPacked(["uint256", "uint256", "uint256"], [R.x, R.y, e]); }; @@ -55,7 +59,7 @@ describe.only("SchnorrSignature", () => { const signature = schnorrSign(hashedMessage, privKey); const pubKeyBytes = serializePoint(pubKey); - console.log(await schnorr.verifySignature(hashedMessage, signature, pubKeyBytes)); + console.log(await schnorr.verifySECP256k1(hashedMessage, signature, pubKeyBytes)); }); }); }); From c77589555aaee6fc7234c1eb6ac87da48f8f5743 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Wed, 30 Apr 2025 17:59:11 +0300 Subject: [PATCH 05/20] adjustments --- contracts/libs/crypto/EC.sol | 65 ++++++++-------------- contracts/libs/crypto/SchnorrSignature.sol | 37 +++++++++--- 2 files changed, 52 insertions(+), 50 deletions(-) diff --git a/contracts/libs/crypto/EC.sol b/contracts/libs/crypto/EC.sol index a27401df..caffec86 100644 --- a/contracts/libs/crypto/EC.sol +++ b/contracts/libs/crypto/EC.sol @@ -191,6 +191,30 @@ library EC { } } + function preComputeJacobianPoints( + uint256 x_, + uint256 y_, + uint256 p_, + uint256 a_ + ) internal pure returns (Jpoint[16] memory points_) { + points_[0x00] = jacobianInfinity(); + points_[0x01] = jacobianFromAffine(x_, y_); + points_[0x02] = jDoublePoint(points_[0x01], p_, a_); + points_[0x04] = jDoublePoint(points_[0x02], p_, a_); + points_[0x08] = jDoublePoint(points_[0x04], p_, a_); + points_[0x03] = jAddPoint(points_[0x01], points_[0x02], p_, a_); + points_[0x06] = jDoublePoint(points_[0x03], p_, a_); + points_[0x0c] = jDoublePoint(points_[0x06], p_, a_); + points_[0x05] = jAddPoint(points_[0x01], points_[0x04], p_, a_); + points_[0x0a] = jDoublePoint(points_[0x05], p_, a_); + points_[0x07] = jAddPoint(points_[0x01], points_[0x06], p_, a_); + points_[0x0e] = jDoublePoint(points_[0x07], p_, a_); + points_[0x09] = jAddPoint(points_[0x01], points_[0x08], p_, a_); + points_[0x0b] = jAddPoint(points_[0x01], points_[0x0a], p_, a_); + points_[0x0d] = jAddPoint(points_[0x01], points_[0x0c], p_, a_); + points_[0x0f] = jAddPoint(points_[0x01], points_[0x0e], p_, a_); + } + /** * @dev Precompute a matrice of useful jacobian points associated with a given P. This can be seen as a 4x4 matrix * that contains combination of P and G (generator) up to 3 times each @@ -221,47 +245,6 @@ library EC { points_[0x0f] = jAddPoint(points_[0x03], points_[0x0c], p_, a_); } - function preComputeJacobianPoints( - uint256 x_, - uint256 y_, - uint256 p_, - uint256 a_ - ) internal pure returns (Jpoint[16] memory points_) { - points_[0x00] = jacobianInfinity(); - points_[0x01] = jacobianFromAffine(x_, y_); - points_[0x02] = jDoublePoint(points_[0x01], p_, a_); - points_[0x03] = jAddPoint(points_[0x01], points_[0x02], p_, a_); - points_[0x04] = jAddPoint(points_[0x01], points_[0x03], p_, a_); - points_[0x05] = jAddPoint(points_[0x01], points_[0x04], p_, a_); - points_[0x06] = jAddPoint(points_[0x01], points_[0x05], p_, a_); - points_[0x07] = jAddPoint(points_[0x01], points_[0x06], p_, a_); - points_[0x08] = jAddPoint(points_[0x01], points_[0x07], p_, a_); - points_[0x09] = jAddPoint(points_[0x01], points_[0x08], p_, a_); - points_[0x0a] = jAddPoint(points_[0x01], points_[0x09], p_, a_); - points_[0x0b] = jAddPoint(points_[0x01], points_[0x0a], p_, a_); - points_[0x0c] = jAddPoint(points_[0x01], points_[0x0b], p_, a_); - points_[0x0d] = jAddPoint(points_[0x01], points_[0x0c], p_, a_); - points_[0x0e] = jAddPoint(points_[0x01], points_[0x0d], p_, a_); - points_[0x0f] = jAddPoint(points_[0x01], points_[0x0e], p_, a_); - - // points_[0x00] = jacobianFromAffine(0, 0); - // points_[0x01] = jacobianFromAffine(x_, y_); - // points_[0x02] = jDoublePoint(points_[0x01], p_, a_); - // points_[0x04] = jDoublePoint(points_[0x02], p_, a_); - // points_[0x08] = jDoublePoint(points_[0x04], p_, a_); - // points_[0x03] = jAddPoint(points_[0x01], points_[0x02], p_, a_); - // points_[0x06] = jDoublePoint(points_[0x03], p_, a_); - // points_[0x0c] = jDoublePoint(points_[0x06], p_, a_); - // points_[0x05] = jAddPoint(points_[0x01], points_[0x04], p_, a_); - // points_[0x0a] = jDoublePoint(points_[0x05], p_, a_); - // points_[0x07] = jAddPoint(points_[0x01], points_[0x06], p_, a_); - // points_[0x0e] = jDoublePoint(points_[0x07], p_, a_); - // points_[0x09] = jAddPoint(points_[0x01], points_[0x08], p_, a_); - // points_[0x0b] = jAddPoint(points_[0x01], points_[0x0a], p_, a_); - // points_[0x0d] = jAddPoint(points_[0x01], points_[0x0c], p_, a_); - // points_[0x0f] = jAddPoint(points_[0x01], points_[0x0e], p_, a_); - } - function jAddPoint( Jpoint memory point1_, Jpoint memory point2_, diff --git a/contracts/libs/crypto/SchnorrSignature.sol b/contracts/libs/crypto/SchnorrSignature.sol index e39a5a2b..dc0f3187 100644 --- a/contracts/libs/crypto/SchnorrSignature.sol +++ b/contracts/libs/crypto/SchnorrSignature.sol @@ -24,6 +24,11 @@ library SchnorrSignature { uint256 py; } + error LengthIsNot64(); + error LengthIsNot96(); + error InvalidPubKey(); + error InvalidSignature(); + function verify( Parameters memory curveParams_, bytes32 hashedMessage_, @@ -35,13 +40,23 @@ library SchnorrSignature { (inputs_.rx, inputs_.ry, inputs_.e) = _parseSignature(signature_); (inputs_.px, inputs_.py) = _parsePubKey(pubKey_); - require( - EC.isOnCurve(inputs_.rx, inputs_.ry, curveParams_.a, curveParams_.b, curveParams_.p) - ); - require( - EC.isOnCurve(inputs_.px, inputs_.py, curveParams_.a, curveParams_.b, curveParams_.p) - ); - require(EC.isValidScalar(inputs_.e, curveParams_.n)); + if ( + !EC.isOnCurve( + inputs_.rx, + inputs_.ry, + curveParams_.a, + curveParams_.b, + curveParams_.p + ) || !EC.isValidScalar(inputs_.e, curveParams_.n) + ) { + revert InvalidSignature(); + } + + if ( + !EC.isOnCurve(inputs_.px, inputs_.py, curveParams_.a, curveParams_.b, curveParams_.p) + ) { + revert InvalidPubKey(); + } EC.Jpoint[16] memory baseShamir_ = EC.preComputeJacobianPoints( curveParams_.gx, @@ -88,13 +103,17 @@ library SchnorrSignature { function _parseSignature( bytes memory signature_ ) private pure returns (uint256 rx_, uint256 ry_, uint256 e_) { - require(signature_.length == 96); + if (signature_.length != 96) { + revert LengthIsNot96(); + } (rx_, ry_, e_) = abi.decode(signature_, (uint256, uint256, uint256)); } function _parsePubKey(bytes memory pubKey_) private pure returns (uint256 px_, uint256 py_) { - require(pubKey_.length == 64); + if (pubKey_.length != 64) { + revert LengthIsNot64(); + } (px_, py_) = abi.decode(pubKey_, (uint256, uint256)); } From d006fbda9431f11ccc5519c208d10bbb199fe2c8 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Thu, 1 May 2025 14:41:05 +0300 Subject: [PATCH 06/20] refactored --- contracts/libs/crypto/BN254.sol | 142 -------- contracts/libs/crypto/{EC.sol => EC256.sol} | 331 ++++++++---------- contracts/libs/crypto/SchnorrSignature.sol | 96 ++--- contracts/mock/libs/crypto/BN254Mock.sol | 27 -- .../mock/libs/crypto/SchnorrSignatureMock.sol | 5 +- 5 files changed, 176 insertions(+), 425 deletions(-) delete mode 100644 contracts/libs/crypto/BN254.sol rename contracts/libs/crypto/{EC.sol => EC256.sol} (52%) delete mode 100644 contracts/mock/libs/crypto/BN254Mock.sol diff --git a/contracts/libs/crypto/BN254.sol b/contracts/libs/crypto/BN254.sol deleted file mode 100644 index 81380f8f..00000000 --- a/contracts/libs/crypto/BN254.sol +++ /dev/null @@ -1,142 +0,0 @@ -// 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); - } - - function pointEqual( - G1Affine memory a_, - G1Affine memory b_ - ) internal pure returns (bool isEqual_) { - return a_.x == b_.x && a_.y == b_.y; - } - - function pointSub( - G1Affine memory a_, - G1Affine memory b_ - ) internal view returns (G1Affine memory r_) { - return pointAdd(a_, pointNeg(b_)); - } - - 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( - Scalar memory a_, - Scalar memory b_ - ) internal pure returns (Scalar memory r_) { - return Scalar(addmod(a_.data, b_.data, SCALAR_FIELD_SIZE)); - } - - function scalarSub( - Scalar memory a_, - Scalar memory b_ - ) internal pure returns (Scalar memory r_) { - if (a_.data > b_.data) { - return Scalar(a_.data - b_.data); - } - - return Scalar(SCALAR_FIELD_SIZE - (b_.data - a_.data)); - } - - function scalarMul( - Scalar memory a_, - Scalar memory b_ - ) internal pure returns (Scalar memory r_) { - return Scalar(mulmod(a_.data, b_.data, SCALAR_FIELD_SIZE)); - } - - 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; - } - - if (point_.x > BASE_FIELD_SIZE || point_.y > BASE_FIELD_SIZE) { - return false; - } - - 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_; - } -} diff --git a/contracts/libs/crypto/EC.sol b/contracts/libs/crypto/EC256.sol similarity index 52% rename from contracts/libs/crypto/EC.sol rename to contracts/libs/crypto/EC256.sol index caffec86..74ab2985 100644 --- a/contracts/libs/crypto/EC.sol +++ b/contracts/libs/crypto/EC256.sol @@ -3,70 +3,78 @@ pragma solidity ^0.8.21; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -library EC { +library EC256 { + struct Curve { + uint256 a; + uint256 b; + uint256 p; + uint256 n; + uint256 gx; + uint256 gy; + } + + struct Apoint { + uint256 x; + uint256 y; + } + struct Jpoint { uint256 x; uint256 y; uint256 z; } + function basepoint(Curve memory ec) internal pure returns (Apoint memory point_) { + return Apoint(ec.gx, ec.gy); + } + + function scalarFromU256( + Curve memory ec, + uint256 u256_ + ) internal pure returns (uint256 scalar_) { + return u256_ % ec.n; + } + function isOnCurve( - uint256 x_, - uint256 y_, - uint256 a_, - uint256 b_, - uint256 p_ + Curve memory ec, + Apoint memory point_ ) internal pure returns (bool result_) { - assembly ("memory-safe") { - let lhs_ := mulmod(y_, y_, p_) - let rhs_ := addmod(mulmod(addmod(mulmod(x_, x_, p_), a_, p_), x_, p_), b_, p_) - - result_ := and(and(lt(x_, p_), lt(y_, p_)), eq(lhs_, rhs_)) // Should conform with the Weierstrass equation - } + return _isOnCurve(point_.x, point_.y, ec.a, ec.b, ec.p); } - function isValidScalar(uint256 u_, uint256 n_) internal pure returns (bool result_) { - return u_ < n_; + function isValidScalar(Curve memory ec, uint256 scalar_) internal pure returns (bool result_) { + return scalar_ < ec.n; } function jEqual( - Jpoint memory point1_, - Jpoint memory point2_, - uint256 p_ + Curve memory ec, + Jpoint memory jPoint1_, + Jpoint memory jPoint2_ ) internal view returns (bool result_) { - (uint256 point1X_, uint256 point1Y_) = affineFromJacobian(point1_, p_); - (uint256 point2X_, uint256 point2Y_) = affineFromJacobian(point2_, p_); + Apoint memory aPoint1_ = affineFromJacobian(ec, jPoint1_); + Apoint memory aPoint2_ = affineFromJacobian(ec, jPoint2_); - return point1X_ == point2X_ && point1Y_ == point2Y_; + return aPoint1_.x == aPoint2_.x && aPoint1_.y == aPoint2_.y; } /** * @dev Reduce from jacobian to affine coordinates - * @param point_ - point with jacobian coordinate x, y and z - * @return ax_ - affine coordinate x - * @return ay_ - affine coordinate y + * @param jPoint_ point with jacobian coordinate x, y and z + * @return aPoint_ point with affine coordinate x and y */ function affineFromJacobian( - Jpoint memory point_, - uint256 p_ - ) internal view returns (uint256 ax_, uint256 ay_) { - if (point_.z == 0) return (0, 0); - - uint256 zInverse_ = Math.invModPrime(point_.z, p_); - - assembly ("memory-safe") { - let zzInverse_ := mulmod(zInverse_, zInverse_, p_) + Curve memory ec, + Jpoint memory jPoint_ + ) internal view returns (Apoint memory aPoint_) { + (aPoint_.x, aPoint_.y) = _affineFromJacobian(jPoint_, ec.p); + } - ax_ := mulmod(mload(point_), zzInverse_, p_) - ay_ := mulmod(mload(add(point_, 0x20)), mulmod(zzInverse_, zInverse_, p_), p_) - } + function jacobianFromAffine(Apoint memory aPoint_) internal pure returns (Jpoint memory res_) { + return Jpoint(aPoint_.x, aPoint_.y, 1); } - function jacobianFromAffine( - uint256 x_, - uint256 y_ - ) internal pure returns (Jpoint memory res_) { - return Jpoint(x_, y_, 1); + function isJacobianInfinity(Jpoint memory point_) internal pure returns (bool res_) { + return point_.z == 0; } function jacobianInfinity() internal pure returns (Jpoint memory res_) { @@ -74,64 +82,22 @@ library EC { } function jMultShamir( + Curve memory ec, Jpoint[16] memory points_, - uint256 u_, - uint256 p_, - uint256 a_ + uint256 u_ ) internal pure returns (Jpoint memory point_) { unchecked { for (uint256 i = 0; i < 64; ++i) { - if (point_.z > 0) { - (point_.x, point_.y, point_.z) = _jDouble( - point_.x, - point_.y, - point_.z, - p_, - a_ - ); - (point_.x, point_.y, point_.z) = _jDouble( - point_.x, - point_.y, - point_.z, - p_, - a_ - ); - (point_.x, point_.y, point_.z) = _jDouble( - point_.x, - point_.y, - point_.z, - p_, - a_ - ); - (point_.x, point_.y, point_.z) = _jDouble( - point_.x, - point_.y, - point_.z, - p_, - a_ - ); - } + point_ = jDoublePoint(ec, point_); + point_ = jDoublePoint(ec, point_); + point_ = jDoublePoint(ec, point_); + point_ = jDoublePoint(ec, point_); + // Read 4 bits of u1 which corresponds to the lookup index in the table. uint256 pos_ = u_ >> 252; - - // Points that have z = 0 are points at infinity. They are the additive 0 of the group - // - if the lookup point is a 0, we can skip it - // - otherwise: - // - if the current point (x, y, z) is 0, we use the lookup point as our new value (0+P=P) - // - if the current point (x, y, z) is not 0, both points are valid and we can use `_jAdd` - if (points_[pos_].z != 0) { - if (point_.z == 0) { - (point_.x, point_.y, point_.z) = ( - points_[pos_].x, - points_[pos_].y, - points_[pos_].z - ); - } else { - (point_.x, point_.y, point_.z) = _jAdd(points_[pos_], point_, p_, a_); - } - } - u_ <<= 4; + + point_ = jAddPoint(ec, points_[pos_], point_); } } } @@ -143,76 +109,46 @@ library EC { * https://stackoverflow.com/questions/50993471/ec-scalar-multiplication-with-strauss-shamir-method */ function jMultShamir2( + Curve memory ec, Jpoint[16] memory points_, - uint256 u1_, - uint256 u2_, - uint256 p_, - uint256 a_ + uint256 scalar1_, + uint256 scalar2_ ) internal view returns (Jpoint memory point_) { unchecked { for (uint256 i = 0; i < 128; ++i) { - if (point_.z > 0) { - (point_.x, point_.y, point_.z) = _jDouble( - point_.x, - point_.y, - point_.z, - p_, - a_ - ); - (point_.x, point_.y, point_.z) = _jDouble( - point_.x, - point_.y, - point_.z, - p_, - a_ - ); - } + point_ = jDoublePoint(ec, point_); + point_ = jDoublePoint(ec, point_); + // Read 2 bits of u1, and 2 bits of u2. Combining the two gives the lookup index in the table. - uint256 pos_ = ((u1_ >> 252) & 0xc) | ((u2_ >> 254) & 0x3); - // Points that have z = 0 are points at infinity. They are the additive 0 of the group - // - if the lookup point is a 0, we can skip it - // - otherwise: - // - if the current point (x, y, z) is 0, we use the lookup point as our new value (0+P=P) - // - if the current point (x, y, z) is not 0, both points are valid and we can use `_jAdd` - if (points_[pos_].z != 0) { - if (point_.z == 0) { - (point_.x, point_.y, point_.z) = ( - points_[pos_].x, - points_[pos_].y, - points_[pos_].z - ); - } else { - (point_.x, point_.y, point_.z) = _jAdd(points_[pos_], point_, p_, a_); - } - } - u1_ <<= 2; - u2_ <<= 2; + uint256 pos_ = ((scalar1_ >> 252) & 0xc) | ((scalar2_ >> 254) & 0x3); + scalar1_ <<= 2; + scalar2_ <<= 2; + + point_ = jAddPoint(ec, points_[pos_], point_); } } } function preComputeJacobianPoints( - uint256 x_, - uint256 y_, - uint256 p_, - uint256 a_ + Curve memory ec, + Apoint memory point_ ) internal pure returns (Jpoint[16] memory points_) { points_[0x00] = jacobianInfinity(); - points_[0x01] = jacobianFromAffine(x_, y_); - points_[0x02] = jDoublePoint(points_[0x01], p_, a_); - points_[0x04] = jDoublePoint(points_[0x02], p_, a_); - points_[0x08] = jDoublePoint(points_[0x04], p_, a_); - points_[0x03] = jAddPoint(points_[0x01], points_[0x02], p_, a_); - points_[0x06] = jDoublePoint(points_[0x03], p_, a_); - points_[0x0c] = jDoublePoint(points_[0x06], p_, a_); - points_[0x05] = jAddPoint(points_[0x01], points_[0x04], p_, a_); - points_[0x0a] = jDoublePoint(points_[0x05], p_, a_); - points_[0x07] = jAddPoint(points_[0x01], points_[0x06], p_, a_); - points_[0x0e] = jDoublePoint(points_[0x07], p_, a_); - points_[0x09] = jAddPoint(points_[0x01], points_[0x08], p_, a_); - points_[0x0b] = jAddPoint(points_[0x01], points_[0x0a], p_, a_); - points_[0x0d] = jAddPoint(points_[0x01], points_[0x0c], p_, a_); - points_[0x0f] = jAddPoint(points_[0x01], points_[0x0e], p_, a_); + points_[0x01] = jacobianFromAffine(point_); + points_[0x02] = jDoublePoint(ec, points_[0x01]); + points_[0x04] = jDoublePoint(ec, points_[0x02]); + points_[0x08] = jDoublePoint(ec, points_[0x04]); + points_[0x03] = jAddPoint(ec, points_[0x01], points_[0x02]); + points_[0x06] = jDoublePoint(ec, points_[0x03]); + points_[0x0c] = jDoublePoint(ec, points_[0x06]); + points_[0x05] = jAddPoint(ec, points_[0x01], points_[0x04]); + points_[0x0a] = jDoublePoint(ec, points_[0x05]); + points_[0x07] = jAddPoint(ec, points_[0x01], points_[0x06]); + points_[0x0e] = jDoublePoint(ec, points_[0x07]); + points_[0x09] = jAddPoint(ec, points_[0x01], points_[0x08]); + points_[0x0b] = jAddPoint(ec, points_[0x01], points_[0x0a]); + points_[0x0d] = jAddPoint(ec, points_[0x01], points_[0x0c]); + points_[0x0f] = jAddPoint(ec, points_[0x01], points_[0x0e]); } /** @@ -220,48 +156,55 @@ library EC { * that contains combination of P and G (generator) up to 3 times each */ function preComputeJacobianPoints2( - uint256 x_, - uint256 y_, - uint256 gx_, - uint256 gy_, - uint256 p_, - uint256 a_ + Curve memory ec, + Apoint memory point1_, + Apoint memory point2_ ) internal pure returns (Jpoint[16] memory points_) { points_[0x00] = jacobianInfinity(); - points_[0x01] = jacobianFromAffine(x_, y_); - points_[0x04] = jacobianFromAffine(gx_, gy_); - points_[0x02] = jDoublePoint(points_[0x01], p_, a_); - points_[0x08] = jDoublePoint(points_[0x04], p_, a_); - points_[0x03] = jAddPoint(points_[0x01], points_[0x02], p_, a_); - points_[0x05] = jAddPoint(points_[0x01], points_[0x04], p_, a_); - points_[0x06] = jAddPoint(points_[0x02], points_[0x04], p_, a_); - points_[0x07] = jAddPoint(points_[0x03], points_[0x04], p_, a_); - points_[0x09] = jAddPoint(points_[0x01], points_[0x08], p_, a_); - points_[0x0a] = jAddPoint(points_[0x02], points_[0x08], p_, a_); - points_[0x0b] = jAddPoint(points_[0x03], points_[0x08], p_, a_); - points_[0x0c] = jAddPoint(points_[0x04], points_[0x08], p_, a_); - points_[0x0d] = jAddPoint(points_[0x01], points_[0x0c], p_, a_); - points_[0x0e] = jAddPoint(points_[0x02], points_[0x0c], p_, a_); - points_[0x0f] = jAddPoint(points_[0x03], points_[0x0c], p_, a_); + points_[0x01] = jacobianFromAffine(point1_); + points_[0x04] = jacobianFromAffine(point2_); + points_[0x02] = jDoublePoint(ec, points_[0x01]); + points_[0x08] = jDoublePoint(ec, points_[0x04]); + points_[0x03] = jAddPoint(ec, points_[0x01], points_[0x02]); + points_[0x05] = jAddPoint(ec, points_[0x01], points_[0x04]); + points_[0x06] = jAddPoint(ec, points_[0x02], points_[0x04]); + points_[0x07] = jAddPoint(ec, points_[0x03], points_[0x04]); + points_[0x09] = jAddPoint(ec, points_[0x01], points_[0x08]); + points_[0x0a] = jAddPoint(ec, points_[0x02], points_[0x08]); + points_[0x0b] = jAddPoint(ec, points_[0x03], points_[0x08]); + points_[0x0c] = jAddPoint(ec, points_[0x04], points_[0x08]); + points_[0x0d] = jAddPoint(ec, points_[0x01], points_[0x0c]); + points_[0x0e] = jAddPoint(ec, points_[0x02], points_[0x0c]); + points_[0x0f] = jAddPoint(ec, points_[0x03], points_[0x0c]); } function jAddPoint( + Curve memory ec, Jpoint memory point1_, - Jpoint memory point2_, - uint256 p_, - uint256 a_ + Jpoint memory point2_ ) internal pure returns (Jpoint memory) { - (uint256 x_, uint256 y_, uint256 z_) = _jAdd(point1_, point2_, p_, a_); + if (isJacobianInfinity(point1_)) { + return Jpoint(point2_.x, point2_.y, point2_.z); + } + + if (isJacobianInfinity(point2_)) { + return Jpoint(point1_.x, point1_.y, point1_.z); + } + + (uint256 x_, uint256 y_, uint256 z_) = _jAdd(point1_, point2_, ec.p, ec.a); return Jpoint(x_, y_, z_); } function jDoublePoint( - Jpoint memory point_, - uint256 p_, - uint256 a_ + Curve memory ec, + Jpoint memory point_ ) internal pure returns (Jpoint memory) { - (uint256 x_, uint256 y_, uint256 z_) = _jDouble(point_.x, point_.y, point_.z, p_, a_); + if (isJacobianInfinity(point_)) { + return Jpoint(point_.x, point_.y, point_.z); + } + + (uint256 x_, uint256 y_, uint256 z_) = _jDouble(point_.x, point_.y, point_.z, ec.p, ec.a); return Jpoint(x_, y_, z_); } @@ -375,4 +318,36 @@ library EC { resZ_ := mulmod(2, mulmod(y_, z_, p_), p_) } } + + function _affineFromJacobian( + Jpoint memory point_, + uint256 p_ + ) internal view returns (uint256 ax_, uint256 ay_) { + if (point_.z == 0) return (0, 0); + + uint256 zInverse_ = Math.invModPrime(point_.z, p_); + + assembly ("memory-safe") { + let zzInverse_ := mulmod(zInverse_, zInverse_, p_) + + ax_ := mulmod(mload(point_), zzInverse_, p_) + ay_ := mulmod(mload(add(point_, 0x20)), mulmod(zzInverse_, zInverse_, p_), p_) + } + } + + function _isOnCurve( + uint256 x_, + uint256 y_, + uint256 a_, + uint256 b_, + uint256 p_ + ) private pure returns (bool result_) { + assembly ("memory-safe") { + let lhs_ := mulmod(y_, y_, p_) + let rhs_ := addmod(mulmod(addmod(mulmod(x_, x_, p_), a_, p_), x_, p_), b_, p_) + + // Should conform with the Weierstrass equation + result_ := and(and(lt(x_, p_), lt(y_, p_)), eq(lhs_, rhs_)) + } + } } diff --git a/contracts/libs/crypto/SchnorrSignature.sol b/contracts/libs/crypto/SchnorrSignature.sol index dc0f3187..4754a2f2 100644 --- a/contracts/libs/crypto/SchnorrSignature.sol +++ b/contracts/libs/crypto/SchnorrSignature.sol @@ -1,28 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.21; -import {EC} from "./EC.sol"; +import {EC256} from "./EC256.sol"; import {MemoryUtils} from "../utils/MemoryUtils.sol"; library SchnorrSignature { using MemoryUtils for *; - - struct Parameters { - uint256 a; - uint256 b; - uint256 gx; - uint256 gy; - uint256 p; - uint256 n; - } - - struct _Inputs { - uint256 rx; - uint256 ry; - uint256 e; - uint256 px; - uint256 py; - } + using EC256 for EC256.Curve; error LengthIsNot64(); error LengthIsNot96(); @@ -30,91 +14,51 @@ library SchnorrSignature { error InvalidSignature(); function verify( - Parameters memory curveParams_, + EC256.Curve memory ec, bytes32 hashedMessage_, bytes memory signature_, bytes memory pubKey_ ) internal view returns (bool) { - _Inputs memory inputs_; - - (inputs_.rx, inputs_.ry, inputs_.e) = _parseSignature(signature_); - (inputs_.px, inputs_.py) = _parsePubKey(pubKey_); + (EC256.Apoint memory r_, uint256 e_) = _parseSignature(signature_); + EC256.Apoint memory p_ = _parsePubKey(pubKey_); - if ( - !EC.isOnCurve( - inputs_.rx, - inputs_.ry, - curveParams_.a, - curveParams_.b, - curveParams_.p - ) || !EC.isValidScalar(inputs_.e, curveParams_.n) - ) { + if (!ec.isOnCurve(r_) || !ec.isValidScalar(e_)) { revert InvalidSignature(); } - if ( - !EC.isOnCurve(inputs_.px, inputs_.py, curveParams_.a, curveParams_.b, curveParams_.p) - ) { + if (!ec.isOnCurve(p_)) { revert InvalidPubKey(); } - EC.Jpoint[16] memory baseShamir_ = EC.preComputeJacobianPoints( - curveParams_.gx, - curveParams_.gy, - curveParams_.p, - curveParams_.a - ); - EC.Jpoint memory lhs_ = EC.jMultShamir( - baseShamir_, - inputs_.e, - curveParams_.p, - curveParams_.a - ); - - uint256 c_ = uint256( - keccak256( - abi.encodePacked( - curveParams_.gx, - curveParams_.gy, - inputs_.rx, - inputs_.ry, - hashedMessage_ - ) - ) - ) % curveParams_.n; + EC256.Jpoint[16] memory baseShamir_ = EC256.preComputeJacobianPoints(ec, ec.basepoint()); + EC256.Jpoint memory lhs_ = EC256.jMultShamir(ec, baseShamir_, e_); - EC.Jpoint[16] memory pubKeyShamir_ = EC.preComputeJacobianPoints( - inputs_.px, - inputs_.py, - curveParams_.p, - curveParams_.a - ); - EC.Jpoint memory rhs_ = EC.jMultShamir(pubKeyShamir_, c_, curveParams_.p, curveParams_.a); - rhs_ = EC.jAddPoint( - EC.jacobianFromAffine(inputs_.rx, inputs_.ry), - rhs_, - curveParams_.p, - curveParams_.a + uint256 c_ = ec.scalarFromU256( + uint256(keccak256(abi.encodePacked(ec.gx, ec.gy, r_.x, r_.y, hashedMessage_))) ); - return EC.jEqual(lhs_, rhs_, curveParams_.p); + EC256.Jpoint[16] memory pubKeyShamir_ = EC256.preComputeJacobianPoints(ec, p_); + EC256.Jpoint memory rhs_ = EC256.jMultShamir(ec, pubKeyShamir_, c_); + rhs_ = EC256.jAddPoint(ec, rhs_, EC256.jacobianFromAffine(r_)); + + return EC256.jEqual(ec, lhs_, rhs_); } function _parseSignature( bytes memory signature_ - ) private pure returns (uint256 rx_, uint256 ry_, uint256 e_) { + ) private pure returns (EC256.Apoint memory r_, uint256 e_) { if (signature_.length != 96) { revert LengthIsNot96(); } - (rx_, ry_, e_) = abi.decode(signature_, (uint256, uint256, uint256)); + (r_.x, r_.y, e_) = abi.decode(signature_, (uint256, uint256, uint256)); } - function _parsePubKey(bytes memory pubKey_) private pure returns (uint256 px_, uint256 py_) { + function _parsePubKey(bytes memory pubKey_) private pure returns (EC256.Apoint memory p_) { if (pubKey_.length != 64) { revert LengthIsNot64(); } - (px_, py_) = abi.decode(pubKey_, (uint256, uint256)); + (p_.x, p_.y) = abi.decode(pubKey_, (uint256, uint256)); } } diff --git a/contracts/mock/libs/crypto/BN254Mock.sol b/contracts/mock/libs/crypto/BN254Mock.sol deleted file mode 100644 index 921c6257..00000000 --- a/contracts/mock/libs/crypto/BN254Mock.sol +++ /dev/null @@ -1,27 +0,0 @@ -// 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_); - } -} diff --git a/contracts/mock/libs/crypto/SchnorrSignatureMock.sol b/contracts/mock/libs/crypto/SchnorrSignatureMock.sol index 749da5fe..02a771c8 100644 --- a/contracts/mock/libs/crypto/SchnorrSignatureMock.sol +++ b/contracts/mock/libs/crypto/SchnorrSignatureMock.sol @@ -2,10 +2,11 @@ pragma solidity ^0.8.21; import {SchnorrSignature} from "../../../libs/crypto/SchnorrSignature.sol"; +import {EC256} from "../../../libs/crypto/EC256.sol"; contract SchnorrSignatureMock { - SchnorrSignature.Parameters private _secp256k1CurveParams = - SchnorrSignature.Parameters({ + EC256.Curve private _secp256k1CurveParams = + EC256.Curve({ a: 0x0000000000000000000000000000000000000000000000000000000000000000, b: 0x0000000000000000000000000000000000000000000000000000000000000007, gx: 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798, From bd67b7482ebf8daa8d34e9d73db6c46849348573 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Thu, 1 May 2025 15:06:00 +0300 Subject: [PATCH 07/20] refactored --- contracts/libs/crypto/EC256.sol | 275 +++++++++++---------- contracts/libs/crypto/SchnorrSignature.sol | 10 +- 2 files changed, 150 insertions(+), 135 deletions(-) diff --git a/contracts/libs/crypto/EC256.sol b/contracts/libs/crypto/EC256.sol index 74ab2985..1e303f8e 100644 --- a/contracts/libs/crypto/EC256.sol +++ b/contracts/libs/crypto/EC256.sol @@ -24,7 +24,7 @@ library EC256 { uint256 z; } - function basepoint(Curve memory ec) internal pure returns (Apoint memory point_) { + function basepoint(Curve memory ec) internal pure returns (Apoint memory aPoint_) { return Apoint(ec.gx, ec.gy); } @@ -37,9 +37,9 @@ library EC256 { function isOnCurve( Curve memory ec, - Apoint memory point_ + Apoint memory aPoint_ ) internal pure returns (bool result_) { - return _isOnCurve(point_.x, point_.y, ec.a, ec.b, ec.p); + return _isOnCurve(aPoint_.x, aPoint_.y, ec.a, ec.b, ec.p); } function isValidScalar(Curve memory ec, uint256 scalar_) internal pure returns (bool result_) { @@ -69,35 +69,39 @@ library EC256 { (aPoint_.x, aPoint_.y) = _affineFromJacobian(jPoint_, ec.p); } - function jacobianFromAffine(Apoint memory aPoint_) internal pure returns (Jpoint memory res_) { + function jacobianFromAffine( + Apoint memory aPoint_ + ) internal pure returns (Jpoint memory jPoint_) { return Jpoint(aPoint_.x, aPoint_.y, 1); } - function isJacobianInfinity(Jpoint memory point_) internal pure returns (bool res_) { - return point_.z == 0; + function isJacobianInfinity(Jpoint memory jPoint_) internal pure returns (bool result_) { + return jPoint_.z == 0; } - function jacobianInfinity() internal pure returns (Jpoint memory res_) { + function jacobianInfinity() internal pure returns (Jpoint memory jPoint_) { return Jpoint(0, 0, 0); } function jMultShamir( Curve memory ec, - Jpoint[16] memory points_, + Jpoint memory jPoint_, uint256 u_ - ) internal pure returns (Jpoint memory point_) { + ) internal pure returns (Jpoint memory jPoint2_) { unchecked { + Jpoint[16] memory jPoints_ = _preComputeJacobianPoints(ec, jPoint_); + for (uint256 i = 0; i < 64; ++i) { - point_ = jDoublePoint(ec, point_); - point_ = jDoublePoint(ec, point_); - point_ = jDoublePoint(ec, point_); - point_ = jDoublePoint(ec, point_); + jPoint2_ = jDoublePoint(ec, jPoint2_); + jPoint2_ = jDoublePoint(ec, jPoint2_); + jPoint2_ = jDoublePoint(ec, jPoint2_); + jPoint2_ = jDoublePoint(ec, jPoint2_); // Read 4 bits of u1 which corresponds to the lookup index in the table. uint256 pos_ = u_ >> 252; u_ <<= 4; - point_ = jAddPoint(ec, points_[pos_], point_); + jPoint2_ = jAddPoint(ec, jPoints_[pos_], jPoint2_); } } } @@ -110,101 +114,61 @@ library EC256 { */ function jMultShamir2( Curve memory ec, - Jpoint[16] memory points_, + Jpoint memory jPoint1_, + Jpoint memory jPoint2_, uint256 scalar1_, uint256 scalar2_ - ) internal view returns (Jpoint memory point_) { + ) internal pure returns (Jpoint memory jPoint3_) { unchecked { + Jpoint[16] memory jPoints_ = _preComputeJacobianPoints2(ec, jPoint1_, jPoint2_); + for (uint256 i = 0; i < 128; ++i) { - point_ = jDoublePoint(ec, point_); - point_ = jDoublePoint(ec, point_); + jPoint3_ = jDoublePoint(ec, jPoint3_); + jPoint3_ = jDoublePoint(ec, jPoint3_); // Read 2 bits of u1, and 2 bits of u2. Combining the two gives the lookup index in the table. uint256 pos_ = ((scalar1_ >> 252) & 0xc) | ((scalar2_ >> 254) & 0x3); scalar1_ <<= 2; scalar2_ <<= 2; - point_ = jAddPoint(ec, points_[pos_], point_); + jPoint3_ = jAddPoint(ec, jPoints_[pos_], jPoint3_); } } } - function preComputeJacobianPoints( - Curve memory ec, - Apoint memory point_ - ) internal pure returns (Jpoint[16] memory points_) { - points_[0x00] = jacobianInfinity(); - points_[0x01] = jacobianFromAffine(point_); - points_[0x02] = jDoublePoint(ec, points_[0x01]); - points_[0x04] = jDoublePoint(ec, points_[0x02]); - points_[0x08] = jDoublePoint(ec, points_[0x04]); - points_[0x03] = jAddPoint(ec, points_[0x01], points_[0x02]); - points_[0x06] = jDoublePoint(ec, points_[0x03]); - points_[0x0c] = jDoublePoint(ec, points_[0x06]); - points_[0x05] = jAddPoint(ec, points_[0x01], points_[0x04]); - points_[0x0a] = jDoublePoint(ec, points_[0x05]); - points_[0x07] = jAddPoint(ec, points_[0x01], points_[0x06]); - points_[0x0e] = jDoublePoint(ec, points_[0x07]); - points_[0x09] = jAddPoint(ec, points_[0x01], points_[0x08]); - points_[0x0b] = jAddPoint(ec, points_[0x01], points_[0x0a]); - points_[0x0d] = jAddPoint(ec, points_[0x01], points_[0x0c]); - points_[0x0f] = jAddPoint(ec, points_[0x01], points_[0x0e]); - } - - /** - * @dev Precompute a matrice of useful jacobian points associated with a given P. This can be seen as a 4x4 matrix - * that contains combination of P and G (generator) up to 3 times each - */ - function preComputeJacobianPoints2( - Curve memory ec, - Apoint memory point1_, - Apoint memory point2_ - ) internal pure returns (Jpoint[16] memory points_) { - points_[0x00] = jacobianInfinity(); - points_[0x01] = jacobianFromAffine(point1_); - points_[0x04] = jacobianFromAffine(point2_); - points_[0x02] = jDoublePoint(ec, points_[0x01]); - points_[0x08] = jDoublePoint(ec, points_[0x04]); - points_[0x03] = jAddPoint(ec, points_[0x01], points_[0x02]); - points_[0x05] = jAddPoint(ec, points_[0x01], points_[0x04]); - points_[0x06] = jAddPoint(ec, points_[0x02], points_[0x04]); - points_[0x07] = jAddPoint(ec, points_[0x03], points_[0x04]); - points_[0x09] = jAddPoint(ec, points_[0x01], points_[0x08]); - points_[0x0a] = jAddPoint(ec, points_[0x02], points_[0x08]); - points_[0x0b] = jAddPoint(ec, points_[0x03], points_[0x08]); - points_[0x0c] = jAddPoint(ec, points_[0x04], points_[0x08]); - points_[0x0d] = jAddPoint(ec, points_[0x01], points_[0x0c]); - points_[0x0e] = jAddPoint(ec, points_[0x02], points_[0x0c]); - points_[0x0f] = jAddPoint(ec, points_[0x03], points_[0x0c]); - } - function jAddPoint( Curve memory ec, - Jpoint memory point1_, - Jpoint memory point2_ - ) internal pure returns (Jpoint memory) { - if (isJacobianInfinity(point1_)) { - return Jpoint(point2_.x, point2_.y, point2_.z); + Jpoint memory jPoint1_, + Jpoint memory jPoint2_ + ) internal pure returns (Jpoint memory jPoint3_) { + if (isJacobianInfinity(jPoint1_)) { + return Jpoint(jPoint2_.x, jPoint2_.y, jPoint2_.z); } - if (isJacobianInfinity(point2_)) { - return Jpoint(point1_.x, point1_.y, point1_.z); + if (isJacobianInfinity(jPoint2_)) { + return Jpoint(jPoint1_.x, jPoint1_.y, jPoint1_.z); } - (uint256 x_, uint256 y_, uint256 z_) = _jAdd(point1_, point2_, ec.p, ec.a); + (uint256 x_, uint256 y_, uint256 z_) = _jAdd(jPoint1_, jPoint2_, ec.p, ec.a); return Jpoint(x_, y_, z_); } function jDoublePoint( Curve memory ec, - Jpoint memory point_ - ) internal pure returns (Jpoint memory) { - if (isJacobianInfinity(point_)) { - return Jpoint(point_.x, point_.y, point_.z); + Jpoint memory jPoint1_ + ) internal pure returns (Jpoint memory jPoint2_) { + if (isJacobianInfinity(jPoint1_)) { + return Jpoint(jPoint1_.x, jPoint1_.y, jPoint1_.z); } - (uint256 x_, uint256 y_, uint256 z_) = _jDouble(point_.x, point_.y, point_.z, ec.p, ec.a); + (uint256 x_, uint256 y_, uint256 z_) = _jDouble( + jPoint1_.x, + jPoint1_.y, + jPoint1_.z, + ec.p, + ec.a + ); return Jpoint(x_, y_, z_); } @@ -214,33 +178,37 @@ library EC256 { * Reference: https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-add-1998-cmo-2 */ function _jAdd( - Jpoint memory point1_, - Jpoint memory point2_, + Jpoint memory jPoint1_, + Jpoint memory jPoint2_, uint256 p_, uint256 a_ - ) private pure returns (uint256 resX_, uint256 resY_, uint256 resZ_) { + ) private pure returns (uint256 jx3_, uint256 jy3_, uint256 jz3_) { assembly ("memory-safe") { - let zz1_ := mulmod(mload(add(point1_, 0x40)), mload(add(point1_, 0x40)), p_) + let zz1_ := mulmod(mload(add(jPoint1_, 0x40)), mload(add(jPoint1_, 0x40)), p_) let s1_ := mulmod( - mload(add(point1_, 0x20)), + mload(add(jPoint1_, 0x20)), mulmod( - mulmod(mload(add(point2_, 0x40)), mload(add(point2_, 0x40)), p_), - mload(add(point2_, 0x40)), + mulmod(mload(add(jPoint2_, 0x40)), mload(add(jPoint2_, 0x40)), p_), + mload(add(jPoint2_, 0x40)), p_ ), p_ ) let r_ := addmod( - mulmod(mload(add(point2_, 0x20)), mulmod(zz1_, mload(add(point1_, 0x40)), p_), p_), + mulmod( + mload(add(jPoint2_, 0x20)), + mulmod(zz1_, mload(add(jPoint1_, 0x40)), p_), + p_ + ), sub(p_, s1_), p_ ) let u1_ := mulmod( - mload(point1_), - mulmod(mload(add(point2_, 0x40)), mload(add(point2_, 0x40)), p_), + mload(jPoint1_), + mulmod(mload(add(jPoint2_, 0x40)), mload(add(jPoint2_, 0x40)), p_), p_ ) - let h_ := addmod(mulmod(mload(point2_), zz1_, p_), sub(p_, u1_), p_) + let h_ := addmod(mulmod(mload(jPoint2_), zz1_, p_), sub(p_, u1_), p_) // detect edge cases where inputs are identical switch and(iszero(r_), iszero(h_)) @@ -248,40 +216,40 @@ library EC256 { case 0 { let hh_ := mulmod(h_, h_, p_) - resX_ := addmod( + jx3_ := addmod( addmod(mulmod(r_, r_, p_), sub(p_, mulmod(h_, hh_, p_)), p_), sub(p_, mulmod(2, mulmod(u1_, hh_, p_), p_)), p_ ) - resY_ := addmod( - mulmod(r_, addmod(mulmod(u1_, hh_, p_), sub(p_, resX_), p_), p_), + jy3_ := addmod( + mulmod(r_, addmod(mulmod(u1_, hh_, p_), sub(p_, jx3_), p_), p_), sub(p_, mulmod(s1_, mulmod(h_, hh_, p_), p_)), p_ ) - resZ_ := mulmod( + jz3_ := mulmod( h_, - mulmod(mload(add(point1_, 0x40)), mload(add(point2_, 0x40)), p_), + mulmod(mload(add(jPoint1_, 0x40)), mload(add(jPoint2_, 0x40)), p_), p_ ) } // case 1: points are equal case 1 { - let yy_ := mulmod(mload(add(point2_, 0x20)), mload(add(point2_, 0x20)), p_) - let zz_ := mulmod(mload(add(point2_, 0x40)), mload(add(point2_, 0x40)), p_) - let xx_ := mulmod(mload(point2_), mload(point2_), p_) + let yy_ := mulmod(mload(add(jPoint2_, 0x20)), mload(add(jPoint2_, 0x20)), p_) + let zz_ := mulmod(mload(add(jPoint2_, 0x40)), mload(add(jPoint2_, 0x40)), p_) + let xx_ := mulmod(mload(jPoint2_), mload(jPoint2_), p_) let m_ := addmod(mulmod(3, xx_, p_), mulmod(a_, mulmod(zz_, zz_, p_), p_), p_) - let s_ := mulmod(4, mulmod(mload(point2_), yy_, p_), p_) + let s_ := mulmod(4, mulmod(mload(jPoint2_), yy_, p_), p_) - resX_ := addmod(mulmod(m_, m_, p_), sub(p_, mulmod(2, s_, p_)), p_) + jx3_ := addmod(mulmod(m_, m_, p_), sub(p_, mulmod(2, s_, p_)), p_) // cut the computation to avoid stack too deep let rytmp1_ := sub(p_, mulmod(8, mulmod(yy_, yy_, p_), p_)) - let rytmp2_ := addmod(s_, sub(p_, resX_), p_) - resY_ := addmod(mulmod(m_, rytmp2_, p_), rytmp1_, p_) + let rytmp2_ := addmod(s_, sub(p_, jx3_), p_) + jy3_ := addmod(mulmod(m_, rytmp2_, p_), rytmp1_, p_) - resZ_ := mulmod( + jz3_ := mulmod( 2, - mulmod(mload(add(point2_, 0x20)), mload(add(point2_, 0x40)), p_), + mulmod(mload(add(jPoint2_, 0x20)), mload(add(jPoint2_, 0x40)), p_), p_ ) } @@ -293,61 +261,110 @@ library EC256 { * Reference: https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-1998-cmo-2 */ function _jDouble( - uint256 x_, - uint256 y_, - uint256 z_, + uint256 jx1_, + uint256 jy1_, + uint256 jz1_, uint256 p_, uint256 a_ - ) private pure returns (uint256 resX_, uint256 resY_, uint256 resZ_) { + ) private pure returns (uint256 jx2_, uint256 jy2_, uint256 jz2_) { assembly ("memory-safe") { - let yy_ := mulmod(y_, y_, p_) - let zz_ := mulmod(z_, z_, p_) + let yy_ := mulmod(jy1_, jy1_, p_) + let zz_ := mulmod(jz1_, jz1_, p_) let m_ := addmod( - mulmod(3, mulmod(x_, x_, p_), p_), + mulmod(3, mulmod(jx1_, jx1_, p_), p_), mulmod(a_, mulmod(zz_, zz_, p_), p_), p_ ) - let s_ := mulmod(4, mulmod(x_, yy_, p_), p_) + let s_ := mulmod(4, mulmod(jx1_, yy_, p_), p_) - resX_ := addmod(mulmod(m_, m_, p_), sub(p_, mulmod(2, s_, p_)), p_) - resY_ := addmod( - mulmod(m_, addmod(s_, sub(p_, resX_), p_), p_), + jx2_ := addmod(mulmod(m_, m_, p_), sub(p_, mulmod(2, s_, p_)), p_) + jy2_ := addmod( + mulmod(m_, addmod(s_, sub(p_, jx2_), p_), p_), sub(p_, mulmod(8, mulmod(yy_, yy_, p_), p_)), p_ ) - resZ_ := mulmod(2, mulmod(y_, z_, p_), p_) + jz2_ := mulmod(2, mulmod(jy1_, jz1_, p_), p_) } } function _affineFromJacobian( - Jpoint memory point_, + Jpoint memory jPoint_, uint256 p_ - ) internal view returns (uint256 ax_, uint256 ay_) { - if (point_.z == 0) return (0, 0); + ) private view returns (uint256 ax_, uint256 ay_) { + if (jPoint_.z == 0) return (0, 0); - uint256 zInverse_ = Math.invModPrime(point_.z, p_); + uint256 zInverse_ = Math.invModPrime(jPoint_.z, p_); assembly ("memory-safe") { let zzInverse_ := mulmod(zInverse_, zInverse_, p_) - ax_ := mulmod(mload(point_), zzInverse_, p_) - ay_ := mulmod(mload(add(point_, 0x20)), mulmod(zzInverse_, zInverse_, p_), p_) + ax_ := mulmod(mload(jPoint_), zzInverse_, p_) + ay_ := mulmod(mload(add(jPoint_, 0x20)), mulmod(zzInverse_, zInverse_, p_), p_) } } function _isOnCurve( - uint256 x_, - uint256 y_, + uint256 ax_, + uint256 ay_, uint256 a_, uint256 b_, uint256 p_ ) private pure returns (bool result_) { assembly ("memory-safe") { - let lhs_ := mulmod(y_, y_, p_) - let rhs_ := addmod(mulmod(addmod(mulmod(x_, x_, p_), a_, p_), x_, p_), b_, p_) + let lhs_ := mulmod(ay_, ay_, p_) + let rhs_ := addmod(mulmod(addmod(mulmod(ax_, ax_, p_), a_, p_), ax_, p_), b_, p_) // Should conform with the Weierstrass equation - result_ := and(and(lt(x_, p_), lt(y_, p_)), eq(lhs_, rhs_)) + result_ := and(and(lt(ax_, p_), lt(ay_, p_)), eq(lhs_, rhs_)) } } + + function _preComputeJacobianPoints( + Curve memory ec, + Jpoint memory jPoint_ + ) private pure returns (Jpoint[16] memory jPoints_) { + jPoints_[0x00] = jacobianInfinity(); + jPoints_[0x01] = Jpoint(jPoint_.x, jPoint_.y, jPoint_.z); + jPoints_[0x02] = jDoublePoint(ec, jPoints_[0x01]); + jPoints_[0x04] = jDoublePoint(ec, jPoints_[0x02]); + jPoints_[0x08] = jDoublePoint(ec, jPoints_[0x04]); + jPoints_[0x03] = jAddPoint(ec, jPoints_[0x01], jPoints_[0x02]); + jPoints_[0x06] = jDoublePoint(ec, jPoints_[0x03]); + jPoints_[0x0c] = jDoublePoint(ec, jPoints_[0x06]); + jPoints_[0x05] = jAddPoint(ec, jPoints_[0x01], jPoints_[0x04]); + jPoints_[0x0a] = jDoublePoint(ec, jPoints_[0x05]); + jPoints_[0x07] = jAddPoint(ec, jPoints_[0x01], jPoints_[0x06]); + jPoints_[0x0e] = jDoublePoint(ec, jPoints_[0x07]); + jPoints_[0x09] = jAddPoint(ec, jPoints_[0x01], jPoints_[0x08]); + jPoints_[0x0b] = jAddPoint(ec, jPoints_[0x01], jPoints_[0x0a]); + jPoints_[0x0d] = jAddPoint(ec, jPoints_[0x01], jPoints_[0x0c]); + jPoints_[0x0f] = jAddPoint(ec, jPoints_[0x01], jPoints_[0x0e]); + } + + /** + * @dev Precompute a matrice of useful jacobian points associated with a given P. This can be seen as a 4x4 matrix + * that contains combination of P and G (generator) up to 3 times each + */ + function _preComputeJacobianPoints2( + Curve memory ec, + Jpoint memory jPoint1_, + Jpoint memory jPoint2_ + ) private pure returns (Jpoint[16] memory jPoints_) { + jPoints_[0x00] = jacobianInfinity(); + jPoints_[0x01] = Jpoint(jPoint1_.x, jPoint1_.y, jPoint1_.z); + jPoints_[0x04] = Jpoint(jPoint2_.x, jPoint2_.y, jPoint2_.z); + jPoints_[0x02] = jDoublePoint(ec, jPoints_[0x01]); + jPoints_[0x08] = jDoublePoint(ec, jPoints_[0x04]); + jPoints_[0x03] = jAddPoint(ec, jPoints_[0x01], jPoints_[0x02]); + jPoints_[0x05] = jAddPoint(ec, jPoints_[0x01], jPoints_[0x04]); + jPoints_[0x06] = jAddPoint(ec, jPoints_[0x02], jPoints_[0x04]); + jPoints_[0x07] = jAddPoint(ec, jPoints_[0x03], jPoints_[0x04]); + jPoints_[0x09] = jAddPoint(ec, jPoints_[0x01], jPoints_[0x08]); + jPoints_[0x0a] = jAddPoint(ec, jPoints_[0x02], jPoints_[0x08]); + jPoints_[0x0b] = jAddPoint(ec, jPoints_[0x03], jPoints_[0x08]); + jPoints_[0x0c] = jAddPoint(ec, jPoints_[0x04], jPoints_[0x08]); + jPoints_[0x0d] = jAddPoint(ec, jPoints_[0x01], jPoints_[0x0c]); + jPoints_[0x0e] = jAddPoint(ec, jPoints_[0x02], jPoints_[0x0c]); + jPoints_[0x0f] = jAddPoint(ec, jPoints_[0x03], jPoints_[0x0c]); + } } diff --git a/contracts/libs/crypto/SchnorrSignature.sol b/contracts/libs/crypto/SchnorrSignature.sol index 4754a2f2..48d70bda 100644 --- a/contracts/libs/crypto/SchnorrSignature.sol +++ b/contracts/libs/crypto/SchnorrSignature.sol @@ -6,7 +6,7 @@ import {MemoryUtils} from "../utils/MemoryUtils.sol"; library SchnorrSignature { using MemoryUtils for *; - using EC256 for EC256.Curve; + using EC256 for *; error LengthIsNot64(); error LengthIsNot96(); @@ -30,16 +30,14 @@ library SchnorrSignature { revert InvalidPubKey(); } - EC256.Jpoint[16] memory baseShamir_ = EC256.preComputeJacobianPoints(ec, ec.basepoint()); - EC256.Jpoint memory lhs_ = EC256.jMultShamir(ec, baseShamir_, e_); + EC256.Jpoint memory lhs_ = EC256.jMultShamir(ec, ec.basepoint().jacobianFromAffine(), e_); uint256 c_ = ec.scalarFromU256( uint256(keccak256(abi.encodePacked(ec.gx, ec.gy, r_.x, r_.y, hashedMessage_))) ); - EC256.Jpoint[16] memory pubKeyShamir_ = EC256.preComputeJacobianPoints(ec, p_); - EC256.Jpoint memory rhs_ = EC256.jMultShamir(ec, pubKeyShamir_, c_); - rhs_ = EC256.jAddPoint(ec, rhs_, EC256.jacobianFromAffine(r_)); + EC256.Jpoint memory rhs_ = EC256.jMultShamir(ec, p_.jacobianFromAffine(), c_); + rhs_ = EC256.jAddPoint(ec, rhs_, r_.jacobianFromAffine()); return EC256.jEqual(ec, lhs_, rhs_); } From 2f9084a7775d0cb32ad182bcbdfd27093b18425a Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Thu, 1 May 2025 15:33:21 +0300 Subject: [PATCH 08/20] refactored ecdsa --- contracts/libs/crypto/ECDSA256.sol | 324 ++------------------ contracts/libs/crypto/SchnorrSignature.sol | 10 +- contracts/mock/libs/crypto/ECDSA256Mock.sol | 15 +- 3 files changed, 33 insertions(+), 316 deletions(-) diff --git a/contracts/libs/crypto/ECDSA256.sol b/contracts/libs/crypto/ECDSA256.sol index e3b729c3..17616762 100644 --- a/contracts/libs/crypto/ECDSA256.sol +++ b/contracts/libs/crypto/ECDSA256.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.21; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {EC256} from "./EC256.sol"; + /** * @notice Cryptography module * @@ -12,39 +14,13 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; * For more information, please refer to the OpenZeppelin documentation. */ library ECDSA256 { - /** - * @notice 256-bit curve parameters. - */ - struct Parameters { - uint256 a; - uint256 b; - uint256 gx; - uint256 gy; - uint256 p; - uint256 n; - uint256 lowSmax; - } - - // solhint-disable-next-line contract-name-capwords - struct _JPoint { - uint256 x; - uint256 y; - uint256 z; - } - - // solhint-disable-next-line contract-name-capwords - struct _Inputs { - uint256 r; - uint256 s; - uint256 x; - uint256 y; - } + using EC256 for *; error LengthIsNot64(); /** * @notice The function to verify the ECDSA signature - * @param curveParams_ the 256-bit curve parameters. `lowSmax` is `n / 2`. + * @param ec the 256-bit curve parameters. * @param hashedMessage_ the already hashed message to be verified. * @param signature_ the ECDSA signature. Equals to `bytes(r) + bytes(s)`. * @param pubKey_ the full public key of a signer. Equals to `bytes(x) + bytes(y)`. @@ -53,21 +29,19 @@ library ECDSA256 { * If your `s > n / 2`, change it to `s = n - s`. */ function verify( - Parameters memory curveParams_, + EC256.Curve memory ec, bytes32 hashedMessage_, bytes memory signature_, bytes memory pubKey_ ) internal view returns (bool) { unchecked { - _Inputs memory inputs_; - - (inputs_.r, inputs_.s) = _split(signature_); - (inputs_.x, inputs_.y) = _split(pubKey_); + uint256 r_; + uint256 s_; + EC256.Apoint memory p_; + (r_, s_) = _split(signature_); + (p_.x, p_.y) = _split(pubKey_); - if ( - !_isProperSignature(inputs_.r, inputs_.s, curveParams_.n, curveParams_.lowSmax) || - !_isOnCurve(inputs_.x, inputs_.y, curveParams_.a, curveParams_.b, curveParams_.p) - ) { + if (!_isProperSignature(ec, r_, s_) || !ec.isOnCurve(p_)) { return false; } @@ -75,27 +49,19 @@ library ECDSA256 { uint256 u2_; { - uint256 w_ = Math.invModPrime(inputs_.s, curveParams_.n); - u1_ = mulmod(uint256(hashedMessage_), w_, curveParams_.n); - u2_ = mulmod(inputs_.r, w_, curveParams_.n); + uint256 w_ = Math.invModPrime(s_, ec.n); + u1_ = mulmod(uint256(hashedMessage_), w_, ec.n); + u2_ = mulmod(r_, w_, ec.n); } - uint256 x_; + EC256.Jpoint memory point_ = ec.jMultShamir2( + p_.jacobianFromAffine(), + ec.basepoint().jacobianFromAffine(), + u1_, + u2_ + ); - { - _JPoint[16] memory points_ = _preComputeJacobianPoints( - inputs_.x, - inputs_.y, - curveParams_.gx, - curveParams_.gy, - curveParams_.p, - curveParams_.a - ); - - (x_, ) = _jMultShamir(points_, u1_, u2_, curveParams_.p, curveParams_.a); - } - - return x_ % curveParams_.n == inputs_.r; + return ec.affineFromJacobian(point_).x % ec.n == r_; } } @@ -123,253 +89,11 @@ library ECDSA256 { * In particular, this checks that `s` is in the "lower-range", making the signature non-malleable */ function _isProperSignature( + EC256.Curve memory ec, uint256 r_, - uint256 s_, - uint256 n_, - uint256 lowSmax_ + uint256 s_ ) private pure returns (bool) { - return r_ > 0 && r_ < n_ && s_ > 0 && s_ <= lowSmax_; - } - - /** - * @dev Reduce from jacobian to affine coordinates - * @param point_ - point with jacobian coordinate x, y and z - * @return ax_ - affine coordinate x - * @return ay_ - affine coordinate y - */ - function _affineFromJacobian( - _JPoint memory point_, - uint256 p_ - ) private view returns (uint256 ax_, uint256 ay_) { - if (point_.z == 0) return (0, 0); - - uint256 zInverse_ = Math.invModPrime(point_.z, p_); - - assembly ("memory-safe") { - let zzInverse_ := mulmod(zInverse_, zInverse_, p_) - - ax_ := mulmod(mload(point_), zzInverse_, p_) - ay_ := mulmod(mload(add(point_, 0x20)), mulmod(zzInverse_, zInverse_, p_), p_) - } - } - - /** - * @dev Point addition on the jacobian coordinates - * Reference: https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-add-1998-cmo-2 - */ - function _jAdd( - _JPoint memory point1_, - _JPoint memory point2_, - uint256 p_, - uint256 a_ - ) private pure returns (uint256 resX_, uint256 resY_, uint256 resZ_) { - assembly ("memory-safe") { - let zz1_ := mulmod(mload(add(point1_, 0x40)), mload(add(point1_, 0x40)), p_) - let s1_ := mulmod( - mload(add(point1_, 0x20)), - mulmod( - mulmod(mload(add(point2_, 0x40)), mload(add(point2_, 0x40)), p_), - mload(add(point2_, 0x40)), - p_ - ), - p_ - ) - let r_ := addmod( - mulmod(mload(add(point2_, 0x20)), mulmod(zz1_, mload(add(point1_, 0x40)), p_), p_), - sub(p_, s1_), - p_ - ) - let u1_ := mulmod( - mload(point1_), - mulmod(mload(add(point2_, 0x40)), mload(add(point2_, 0x40)), p_), - p_ - ) - let h_ := addmod(mulmod(mload(point2_), zz1_, p_), sub(p_, u1_), p_) - - // detect edge cases where inputs are identical - switch and(iszero(r_), iszero(h_)) - // case 0: points are different - case 0 { - let hh_ := mulmod(h_, h_, p_) - - resX_ := addmod( - addmod(mulmod(r_, r_, p_), sub(p_, mulmod(h_, hh_, p_)), p_), - sub(p_, mulmod(2, mulmod(u1_, hh_, p_), p_)), - p_ - ) - resY_ := addmod( - mulmod(r_, addmod(mulmod(u1_, hh_, p_), sub(p_, resX_), p_), p_), - sub(p_, mulmod(s1_, mulmod(h_, hh_, p_), p_)), - p_ - ) - resZ_ := mulmod( - h_, - mulmod(mload(add(point1_, 0x40)), mload(add(point2_, 0x40)), p_), - p_ - ) - } - // case 1: points are equal - case 1 { - let yy_ := mulmod(mload(add(point2_, 0x20)), mload(add(point2_, 0x20)), p_) - let zz_ := mulmod(mload(add(point2_, 0x40)), mload(add(point2_, 0x40)), p_) - let xx_ := mulmod(mload(point2_), mload(point2_), p_) - let m_ := addmod(mulmod(3, xx_, p_), mulmod(a_, mulmod(zz_, zz_, p_), p_), p_) - let s_ := mulmod(4, mulmod(mload(point2_), yy_, p_), p_) - - resX_ := addmod(mulmod(m_, m_, p_), sub(p_, mulmod(2, s_, p_)), p_) - - // cut the computation to avoid stack too deep - let rytmp1_ := sub(p_, mulmod(8, mulmod(yy_, yy_, p_), p_)) - let rytmp2_ := addmod(s_, sub(p_, resX_), p_) - resY_ := addmod(mulmod(m_, rytmp2_, p_), rytmp1_, p_) - - resZ_ := mulmod( - 2, - mulmod(mload(add(point2_, 0x20)), mload(add(point2_, 0x40)), p_), - p_ - ) - } - } - } - - /** - * @dev Point doubling on the jacobian coordinates - * Reference: https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-1998-cmo-2 - */ - function _jDouble( - uint256 x_, - uint256 y_, - uint256 z_, - uint256 p_, - uint256 a_ - ) private pure returns (uint256 resX_, uint256 resY_, uint256 resZ_) { - assembly ("memory-safe") { - let yy_ := mulmod(y_, y_, p_) - let zz_ := mulmod(z_, z_, p_) - let m_ := addmod( - mulmod(3, mulmod(x_, x_, p_), p_), - mulmod(a_, mulmod(zz_, zz_, p_), p_), - p_ - ) - let s_ := mulmod(4, mulmod(x_, yy_, p_), p_) - - resX_ := addmod(mulmod(m_, m_, p_), sub(p_, mulmod(2, s_, p_)), p_) - resY_ := addmod( - mulmod(m_, addmod(s_, sub(p_, resX_), p_), p_), - sub(p_, mulmod(8, mulmod(yy_, yy_, p_), p_)), - p_ - ) - resZ_ := mulmod(2, mulmod(y_, z_, p_), p_) - } - } - - /** - * @dev Compute G·u1 + P·u2 using the precomputed points for G and P (see {_preComputeJacobianPoints}). - * - * Uses Strauss Shamir trick for EC multiplication - * https://stackoverflow.com/questions/50993471/ec-scalar-multiplication-with-strauss-shamir-method - */ - function _jMultShamir( - _JPoint[16] memory points_, - uint256 u1_, - uint256 u2_, - uint256 p_, - uint256 a_ - ) private view returns (uint256 resX_, uint256 resY_) { - _JPoint memory point_; - - unchecked { - for (uint256 i = 0; i < 128; ++i) { - if (point_.z > 0) { - (point_.x, point_.y, point_.z) = _jDouble( - point_.x, - point_.y, - point_.z, - p_, - a_ - ); - (point_.x, point_.y, point_.z) = _jDouble( - point_.x, - point_.y, - point_.z, - p_, - a_ - ); - } - // Read 2 bits of u1, and 2 bits of u2. Combining the two gives the lookup index in the table. - uint256 pos_ = ((u1_ >> 252) & 0xc) | ((u2_ >> 254) & 0x3); - // Points that have z = 0 are points at infinity. They are the additive 0 of the group - // - if the lookup point is a 0, we can skip it - // - otherwise: - // - if the current point (x, y, z) is 0, we use the lookup point as our new value (0+P=P) - // - if the current point (x, y, z) is not 0, both points are valid and we can use `_jAdd` - if (points_[pos_].z != 0) { - if (point_.z == 0) { - (point_.x, point_.y, point_.z) = ( - points_[pos_].x, - points_[pos_].y, - points_[pos_].z - ); - } else { - (point_.x, point_.y, point_.z) = _jAdd(points_[pos_], point_, p_, a_); - } - } - u1_ <<= 2; - u2_ <<= 2; - } - } - return _affineFromJacobian(point_, p_); - } - - /** - * @dev Precompute a matrice of useful jacobian points associated with a given P. This can be seen as a 4x4 matrix - * that contains combination of P and G (generator) up to 3 times each - */ - function _preComputeJacobianPoints( - uint256 x_, - uint256 y_, - uint256 gx_, - uint256 gy_, - uint256 p_, - uint256 a_ - ) private pure returns (_JPoint[16] memory points_) { - points_[0x00] = _JPoint(0, 0, 0); - points_[0x01] = _JPoint(x_, y_, 1); - points_[0x04] = _JPoint(gx_, gy_, 1); - points_[0x02] = _jDoublePoint(points_[0x01], p_, a_); - points_[0x08] = _jDoublePoint(points_[0x04], p_, a_); - points_[0x03] = _jAddPoint(points_[0x01], points_[0x02], p_, a_); - points_[0x05] = _jAddPoint(points_[0x01], points_[0x04], p_, a_); - points_[0x06] = _jAddPoint(points_[0x02], points_[0x04], p_, a_); - points_[0x07] = _jAddPoint(points_[0x03], points_[0x04], p_, a_); - points_[0x09] = _jAddPoint(points_[0x01], points_[0x08], p_, a_); - points_[0x0a] = _jAddPoint(points_[0x02], points_[0x08], p_, a_); - points_[0x0b] = _jAddPoint(points_[0x03], points_[0x08], p_, a_); - points_[0x0c] = _jAddPoint(points_[0x04], points_[0x08], p_, a_); - points_[0x0d] = _jAddPoint(points_[0x01], points_[0x0c], p_, a_); - points_[0x0e] = _jAddPoint(points_[0x02], points_[0x0c], p_, a_); - points_[0x0f] = _jAddPoint(points_[0x03], points_[0x0c], p_, a_); - } - - function _jAddPoint( - _JPoint memory point1_, - _JPoint memory point2_, - uint256 p_, - uint256 a_ - ) private pure returns (_JPoint memory) { - (uint256 x_, uint256 y_, uint256 z_) = _jAdd(point1_, point2_, p_, a_); - - return _JPoint(x_, y_, z_); - } - - function _jDoublePoint( - _JPoint memory point_, - uint256 p_, - uint256 a_ - ) private pure returns (_JPoint memory) { - (uint256 x_, uint256 y_, uint256 z_) = _jDouble(point_.x, point_.y, point_.z, p_, a_); - - return _JPoint(x_, y_, z_); + return r_ > 0 && r_ < ec.n && s_ > 0 && s_ <= (ec.n >> 1); } /** diff --git a/contracts/libs/crypto/SchnorrSignature.sol b/contracts/libs/crypto/SchnorrSignature.sol index 48d70bda..233e0314 100644 --- a/contracts/libs/crypto/SchnorrSignature.sol +++ b/contracts/libs/crypto/SchnorrSignature.sol @@ -10,8 +10,6 @@ library SchnorrSignature { error LengthIsNot64(); error LengthIsNot96(); - error InvalidPubKey(); - error InvalidSignature(); function verify( EC256.Curve memory ec, @@ -22,12 +20,8 @@ library SchnorrSignature { (EC256.Apoint memory r_, uint256 e_) = _parseSignature(signature_); EC256.Apoint memory p_ = _parsePubKey(pubKey_); - if (!ec.isOnCurve(r_) || !ec.isValidScalar(e_)) { - revert InvalidSignature(); - } - - if (!ec.isOnCurve(p_)) { - revert InvalidPubKey(); + if (!ec.isOnCurve(r_) || !ec.isOnCurve(p_) || !ec.isValidScalar(e_)) { + return false; } EC256.Jpoint memory lhs_ = EC256.jMultShamir(ec, ec.basepoint().jacobianFromAffine(), e_); diff --git a/contracts/mock/libs/crypto/ECDSA256Mock.sol b/contracts/mock/libs/crypto/ECDSA256Mock.sol index 3b0d0519..c34bdde2 100644 --- a/contracts/mock/libs/crypto/ECDSA256Mock.sol +++ b/contracts/mock/libs/crypto/ECDSA256Mock.sol @@ -2,30 +2,29 @@ pragma solidity ^0.8.21; import {ECDSA256} from "../../../libs/crypto/ECDSA256.sol"; +import {EC256} from "../../../libs/crypto/EC256.sol"; contract ECDSA256Mock { using ECDSA256 for *; - ECDSA256.Parameters private _secp256r1CurveParams = - ECDSA256.Parameters({ + EC256.Curve private _secp256r1CurveParams = + EC256.Curve({ a: 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC, b: 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B, gx: 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296, gy: 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5, p: 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF, - n: 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551, - lowSmax: 0x7fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a8 + n: 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 }); - ECDSA256.Parameters private _brainpoolP256r1CurveParams = - ECDSA256.Parameters({ + EC256.Curve private _brainpoolP256r1CurveParams = + EC256.Curve({ a: 0x7D5A0975FC2C3057EEF67530417AFFE7FB8055C126DC5C6CE94A4B44F330B5D9, b: 0x26DC5C6CE94A4B44F330B5D9BBD77CBF958416295CF7E1CE6BCCDC18FF8C07B6, gx: 0x8BD2AEB9CB7E57CB2C4B482FFC81B7AFB9DE27E1E3BD23C23A4453BD9ACE3262, gy: 0x547EF835C3DAC4FD97F8461A14611DC9C27745132DED8E545C1D54C72F046997, p: 0xA9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377, - n: 0xA9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7, - lowSmax: 0x54fdabedd0f754de1f3305484ec1c6b9371dfb11ea9310141009a40e8fb729bb + n: 0xA9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7 }); function verifySECP256r1( From bb7278f4c6c7626d8af9a1e370768dab55fd8a46 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Thu, 1 May 2025 15:34:47 +0300 Subject: [PATCH 09/20] renamed --- contracts/libs/crypto/EC256.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/libs/crypto/EC256.sol b/contracts/libs/crypto/EC256.sol index 1e303f8e..fe6351c2 100644 --- a/contracts/libs/crypto/EC256.sol +++ b/contracts/libs/crypto/EC256.sol @@ -86,7 +86,7 @@ library EC256 { function jMultShamir( Curve memory ec, Jpoint memory jPoint_, - uint256 u_ + uint256 scalar_ ) internal pure returns (Jpoint memory jPoint2_) { unchecked { Jpoint[16] memory jPoints_ = _preComputeJacobianPoints(ec, jPoint_); @@ -98,8 +98,8 @@ library EC256 { jPoint2_ = jDoublePoint(ec, jPoint2_); // Read 4 bits of u1 which corresponds to the lookup index in the table. - uint256 pos_ = u_ >> 252; - u_ <<= 4; + uint256 pos_ = scalar_ >> 252; + scalar_ <<= 4; jPoint2_ = jAddPoint(ec, jPoints_[pos_], jPoint2_); } From 39d04e57ebc2e8f7c35ca54bd85c73b1abcec68b Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Thu, 1 May 2025 15:37:39 +0300 Subject: [PATCH 10/20] rm unused fn --- contracts/libs/crypto/ECDSA256.sol | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/contracts/libs/crypto/ECDSA256.sol b/contracts/libs/crypto/ECDSA256.sol index 17616762..6e02ae6b 100644 --- a/contracts/libs/crypto/ECDSA256.sol +++ b/contracts/libs/crypto/ECDSA256.sol @@ -65,25 +65,6 @@ library ECDSA256 { } } - /** - * @dev Checks if (x, y) are valid coordinates of a point on the curve. - * In particular this function checks that x < P and y < P. - */ - function _isOnCurve( - uint256 x_, - uint256 y_, - uint256 a_, - uint256 b_, - uint256 p_ - ) private pure returns (bool result_) { - assembly ("memory-safe") { - let lhs_ := mulmod(y_, y_, p_) - let rhs_ := addmod(mulmod(addmod(mulmod(x_, x_, p_), a_, p_), x_, p_), b_, p_) - - result_ := and(and(lt(x_, p_), lt(y_, p_)), eq(lhs_, rhs_)) // Should conform with the Weierstrass equation - } - } - /** * @dev Checks if (r, s) is a proper signature. * In particular, this checks that `s` is in the "lower-range", making the signature non-malleable From 0a18ae7b7a110552d598fd9bb12ff0740aa46152 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Thu, 1 May 2025 16:08:32 +0300 Subject: [PATCH 11/20] added docs --- contracts/libs/crypto/EC256.sol | 119 +++++++++++++++++++-- contracts/libs/crypto/SchnorrSignature.sol | 18 ++++ 2 files changed, 128 insertions(+), 9 deletions(-) diff --git a/contracts/libs/crypto/EC256.sol b/contracts/libs/crypto/EC256.sol index fe6351c2..4a143d11 100644 --- a/contracts/libs/crypto/EC256.sol +++ b/contracts/libs/crypto/EC256.sol @@ -3,7 +3,21 @@ pragma solidity ^0.8.21; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +/** + * @notice Cryptography module + * + * Elliptic curve arithmetic over a 256-bit prime field (Weierstrass curve y^2 = x^3 + ax + b). + */ library EC256 { + /** + * @notice 256-bit curve parameters. + * @param a The curve coefficient a. + * @param b The curve coefficient b. + * @param p The base field size. + * @param n The scalar field size. + * @param gx The x-coordinate of the basepoint G. + * @param gy The y-coordinate of the basepoint G. + */ struct Curve { uint256 a; uint256 b; @@ -13,21 +27,43 @@ library EC256 { uint256 gy; } + /** + * @notice Affine representation of a curve point. + * @param x The x-coordinate. + * @param y The y-coordinate. + */ struct Apoint { uint256 x; uint256 y; } + /** + * @notice Jacobian representation of a curve point. + * @param x The Jacobian X coordinate. + * @param y The Jacobian Y coordinate. + * @param z The Jacobian Z coordinate. + */ struct Jpoint { uint256 x; uint256 y; uint256 z; } + /** + * @notice Returns the generator (base) point of the curve in affine form. + * @param ec The curve parameters. + * @return aPoint_ The basepoint (gx, gy). + */ function basepoint(Curve memory ec) internal pure returns (Apoint memory aPoint_) { return Apoint(ec.gx, ec.gy); } + /** + * @notice Reduces an arbitrary uint256 into the scalar field [0, n). + * @param ec The curve parameters. + * @param u256_ The integer to reduce. + * @return scalar_ The result of u256_ mod n. + */ function scalarFromU256( Curve memory ec, uint256 u256_ @@ -35,6 +71,12 @@ library EC256 { return u256_ % ec.n; } + /** + * @notice Checks whether an affine point lies on the curve. + * @param ec The curve parameters. + * @param aPoint_ The affine point to test. + * @return result_ True if `aPoint_` satisfies y^2 = x^3 + ax + b (mod p). + */ function isOnCurve( Curve memory ec, Apoint memory aPoint_ @@ -42,10 +84,23 @@ library EC256 { return _isOnCurve(aPoint_.x, aPoint_.y, ec.a, ec.b, ec.p); } + /** + * @notice Checks whether a scalar is in the valid range [0, n). + * @param ec The curve parameters. + * @param scalar_ The scalar to test. + * @return result_ True if scalar < n. + */ function isValidScalar(Curve memory ec, uint256 scalar_) internal pure returns (bool result_) { return scalar_ < ec.n; } + /** + * @notice Compares two Jacobian points for equality in affine coordinates. + * @param ec The curve parameters. + * @param jPoint1_ The first Jacobian point. + * @param jPoint2_ The second Jacobian point. + * @return result_ True if their affine representations match. + */ function jEqual( Curve memory ec, Jpoint memory jPoint1_, @@ -58,9 +113,10 @@ library EC256 { } /** - * @dev Reduce from jacobian to affine coordinates - * @param jPoint_ point with jacobian coordinate x, y and z - * @return aPoint_ point with affine coordinate x and y + * @notice Converts a point from Jacobian to affine coordinates. + * @param ec The curve parameters. + * @param jPoint_ The Jacobian point (X, Y, Z). + * @return aPoint_ The equivalent affine point (x, y). */ function affineFromJacobian( Curve memory ec, @@ -69,20 +125,41 @@ library EC256 { (aPoint_.x, aPoint_.y) = _affineFromJacobian(jPoint_, ec.p); } + /** + * @notice Converts an affine point to Jacobian coordinates. + * @param aPoint_ The affine point (x, y). + * @return jPoint_ The Jacobian representation (x, y, 1). + */ function jacobianFromAffine( Apoint memory aPoint_ ) internal pure returns (Jpoint memory jPoint_) { return Jpoint(aPoint_.x, aPoint_.y, 1); } + /** + * @notice Checks whether a Jacobian point is the point at infinity. + * @param jPoint_ The Jacobian point to test. + * @return result_ True if Z == 0. + */ function isJacobianInfinity(Jpoint memory jPoint_) internal pure returns (bool result_) { return jPoint_.z == 0; } + /** + * @notice Returns the Jacobian representation of the point at infinity. + * @return jPoint_ The point at infinity (0, 0, 0). + */ function jacobianInfinity() internal pure returns (Jpoint memory jPoint_) { return Jpoint(0, 0, 0); } + /** + * @notice Point multiplication: R = u*P using 4-bit windowed method. + * @param ec The curve parameters. + * @param jPoint_ The Jacobian point P. + * @param scalar_ The scalar u. + * @return jPoint2_ The Jacobian representation of result point R. + */ function jMultShamir( Curve memory ec, Jpoint memory jPoint_, @@ -107,10 +184,13 @@ library EC256 { } /** - * @dev Compute G·u1 + P·u2 using the precomputed points for G and P (see {_preComputeJacobianPoints}). - * - * Uses Strauss Shamir trick for EC multiplication - * https://stackoverflow.com/questions/50993471/ec-scalar-multiplication-with-strauss-shamir-method + * @notice Simultaneous double-scalar multiplication: R = u1*P1 + u2*P2 via Strauss–Shamir. + * @param ec The curve parameters. + * @param jPoint1_ The first Jacobian point P1. + * @param jPoint2_ The second Jacobian point P2. + * @param scalar1_ The first scalar u1. + * @param scalar2_ The second scalar u2. + * @return jPoint3_ The Jacobian representation of result point R. */ function jMultShamir2( Curve memory ec, @@ -136,6 +216,13 @@ library EC256 { } } + /** + * @notice Adds two Jacobian points: R = P1 + P2. + * @param ec The curve parameters. + * @param jPoint1_ The first Jacobian point P1. + * @param jPoint2_ The second Jacobian point P2. + * @return jPoint3_ The Jacobian representation of result point R. + */ function jAddPoint( Curve memory ec, Jpoint memory jPoint1_, @@ -154,6 +241,12 @@ library EC256 { return Jpoint(x_, y_, z_); } + /** + * @notice Doubles a Jacobian point: R = 2*P. + * @param ec The curve parameters. + * @param jPoint1_ The Jacobian point P to double. + * @return jPoint2_ The Jacobian representation of result point R. + */ function jDoublePoint( Curve memory ec, Jpoint memory jPoint1_ @@ -287,6 +380,9 @@ library EC256 { } } + /** + * @dev Internal conversion from Jacobian to affine coordinates. + */ function _affineFromJacobian( Jpoint memory jPoint_, uint256 p_ @@ -303,6 +399,9 @@ library EC256 { } } + /** + * @dev Internal curve equation check in affine coordinates. + */ function _isOnCurve( uint256 ax_, uint256 ay_, @@ -319,6 +418,9 @@ library EC256 { } } + /** + * @dev Precomputes 4-bit window lookup table for one point (Shamir's trick) + */ function _preComputeJacobianPoints( Curve memory ec, Jpoint memory jPoint_ @@ -342,8 +444,7 @@ library EC256 { } /** - * @dev Precompute a matrice of useful jacobian points associated with a given P. This can be seen as a 4x4 matrix - * that contains combination of P and G (generator) up to 3 times each + * @dev Precomputes 2-bit window lookup table for two points (Shamir's trick) */ function _preComputeJacobianPoints2( Curve memory ec, diff --git a/contracts/libs/crypto/SchnorrSignature.sol b/contracts/libs/crypto/SchnorrSignature.sol index 233e0314..6d1ad982 100644 --- a/contracts/libs/crypto/SchnorrSignature.sol +++ b/contracts/libs/crypto/SchnorrSignature.sol @@ -4,6 +4,11 @@ pragma solidity ^0.8.21; import {EC256} from "./EC256.sol"; import {MemoryUtils} from "../utils/MemoryUtils.sol"; +/** + * @notice Cryptography module + * + * This library provides functionality for Schnorr signature verification over any 256-bit curve. + */ library SchnorrSignature { using MemoryUtils for *; using EC256 for *; @@ -11,6 +16,13 @@ library SchnorrSignature { error LengthIsNot64(); error LengthIsNot96(); + /** + * @notice The function to verify the Schnorr signature + * @param ec the 256-bit curve parameters. + * @param hashedMessage_ the already hashed message to be verified. + * @param signature_ the Schnorr signature. Equals to `bytes(R) + bytes(e)`. + * @param pubKey_ the full public key of a signer. Equals to `bytes(x) + bytes(y)`. + */ function verify( EC256.Curve memory ec, bytes32 hashedMessage_, @@ -36,6 +48,9 @@ library SchnorrSignature { return EC256.jEqual(ec, lhs_, rhs_); } + /** + * @dev Helper function for splitting 96-byte signature into R (affine point) and e (scalar) components. + */ function _parseSignature( bytes memory signature_ ) private pure returns (EC256.Apoint memory r_, uint256 e_) { @@ -46,6 +61,9 @@ library SchnorrSignature { (r_.x, r_.y, e_) = abi.decode(signature_, (uint256, uint256, uint256)); } + /** + * @dev Helper function for converting 64-byte pub key into affine point. + */ function _parsePubKey(bytes memory pubKey_) private pure returns (EC256.Apoint memory p_) { if (pubKey_.length != 64) { revert LengthIsNot64(); From aef3904c66177468a8d3397a66acf53c0a5bc04b Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Thu, 1 May 2025 16:09:00 +0300 Subject: [PATCH 12/20] upd comment --- contracts/libs/crypto/EC256.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/libs/crypto/EC256.sol b/contracts/libs/crypto/EC256.sol index 4a143d11..6e7afe5e 100644 --- a/contracts/libs/crypto/EC256.sol +++ b/contracts/libs/crypto/EC256.sol @@ -6,7 +6,7 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; /** * @notice Cryptography module * - * Elliptic curve arithmetic over a 256-bit prime field (Weierstrass curve y^2 = x^3 + ax + b). + * Elliptic curve arithmetic over a 256-bit prime field (Weierstrass curve y^2 = x^3 + ax + b (mod p)). */ library EC256 { /** From 8b0ba21ee2fa8f44c184f1174cf2c1e92e45513c Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Thu, 1 May 2025 16:43:07 +0300 Subject: [PATCH 13/20] added tests --- contracts/libs/crypto/EC256.sol | 4 +- test/libs/crypto/SchnorrSignature.test.ts | 61 +++++++++++++++++++---- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/contracts/libs/crypto/EC256.sol b/contracts/libs/crypto/EC256.sol index 6e7afe5e..e148bffc 100644 --- a/contracts/libs/crypto/EC256.sol +++ b/contracts/libs/crypto/EC256.sol @@ -387,7 +387,9 @@ library EC256 { Jpoint memory jPoint_, uint256 p_ ) private view returns (uint256 ax_, uint256 ay_) { - if (jPoint_.z == 0) return (0, 0); + if (isJacobianInfinity(jPoint_)) { + return (0, 0); + } uint256 zInverse_ = Math.invModPrime(jPoint_.z, p_); diff --git a/test/libs/crypto/SchnorrSignature.test.ts b/test/libs/crypto/SchnorrSignature.test.ts index e51b1bb1..750ee916 100644 --- a/test/libs/crypto/SchnorrSignature.test.ts +++ b/test/libs/crypto/SchnorrSignature.test.ts @@ -1,4 +1,5 @@ import { ethers } from "hardhat"; +import { expect } from "chai"; import { Reverter } from "@/test/helpers/reverter"; import { SchnorrSignatureMock } from "@ethers-v6"; @@ -7,7 +8,7 @@ import { secp256k1 } from "@noble/curves/secp256k1"; import { bytesToNumberBE } from "@noble/curves/abstract/utils"; import { AffinePoint } from "@noble/curves/abstract/curve"; -describe.only("SchnorrSignature", () => { +describe("SchnorrSignature", () => { const schnorrKeyPair = () => { const privKey = bytesToNumberBE(secp256k1.utils.randomPrivateKey()); const pubKey = secp256k1.ProjectivePoint.BASE.multiply(privKey).toAffine(); @@ -35,6 +36,20 @@ describe.only("SchnorrSignature", () => { return ethers.solidityPacked(["uint256", "uint256", "uint256"], [R.x, R.y, e]); }; + const prepareParameters = function (message: string) { + const { privKey, pubKey } = schnorrKeyPair(); + + const hashedMessage = ethers.keccak256(message); + + const signature = schnorrSign(hashedMessage, privKey); + + return { + hashedMessage, + signature, + pubKey: serializePoint(pubKey), + }; + }; + const reverter = new Reverter(); let schnorr: SchnorrSignatureMock; @@ -49,17 +64,45 @@ describe.only("SchnorrSignature", () => { afterEach(reverter.revert); - describe.only("verify", () => { - it.only("should verify the signature", async () => { - const { privKey, pubKey } = schnorrKeyPair(); + describe("verify", () => { + it("should verify the signature", async () => { + const { hashedMessage, signature, pubKey } = prepareParameters("0x1337"); + + expect(await schnorr.verifySECP256k1(hashedMessage, signature, pubKey)).to.be.true; + }); + + it("should revert if signature or public key has an invalid length", async () => { + const { hashedMessage, signature, pubKey } = prepareParameters("0x1337"); + + const wrongSig = "0x0101"; + + await expect(schnorr.verifySECP256k1(hashedMessage, wrongSig, pubKey)).to.be.revertedWithCustomError( + schnorr, + "LengthIsNot96", + ); + + const wrongPubKey = "0x0101"; + + await expect(schnorr.verifySECP256k1(hashedMessage, signature, wrongPubKey)).to.be.revertedWithCustomError( + schnorr, + "LengthIsNot64", + ); + }); + + it("should not verify if signature or public key is invalid", async () => { + const { hashedMessage, signature, pubKey } = prepareParameters("0x1337"); + + const r = signature.slice(2, 130); + const e = signature.slice(130); - const message = "0x1337"; - const hashedMessage = ethers.keccak256(message); + const wrongSig1 = "0x" + ethers.toBeHex(0, 0x40).slice(2) + e; + expect(await schnorr.verifySECP256k1(hashedMessage, wrongSig1, pubKey)).to.be.false; - const signature = schnorrSign(hashedMessage, privKey); - const pubKeyBytes = serializePoint(pubKey); + const wrongSig2 = "0x" + r + ethers.toBeHex((1n << 256n) - 1n, 0x20).slice(2); + expect(await schnorr.verifySECP256k1(hashedMessage, wrongSig2, pubKey)).to.be.false; - console.log(await schnorr.verifySECP256k1(hashedMessage, signature, pubKeyBytes)); + const wrongPubKey = "0x" + ethers.toBeHex(0, 0x40).slice(2); + expect(await schnorr.verifySECP256k1(hashedMessage, signature, wrongPubKey)).to.be.false; }); }); }); From 56f56116fa3cbe6cc7a63faf779d1d581f40753c Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Thu, 1 May 2025 18:10:18 +0300 Subject: [PATCH 14/20] basic fixes --- contracts/libs/crypto/EC256.sol | 138 +++++++++--------- contracts/libs/crypto/ECDSA256.sol | 11 +- .../{SchnorrSignature.sol => Schnorr256.sol} | 20 +-- ...rrSignatureMock.sol => Schnorr256Mock.sol} | 6 +- test/libs/crypto/ECDSA256.test.ts | 2 +- ...rrSignature.test.ts => Schnorr256.test.ts} | 8 +- 6 files changed, 95 insertions(+), 90 deletions(-) rename contracts/libs/crypto/{SchnorrSignature.sol => Schnorr256.sol} (76%) rename contracts/mock/libs/crypto/{SchnorrSignatureMock.sol => Schnorr256Mock.sol} (80%) rename test/libs/crypto/{SchnorrSignature.test.ts => Schnorr256.test.ts} (95%) diff --git a/contracts/libs/crypto/EC256.sol b/contracts/libs/crypto/EC256.sol index e148bffc..05d947b0 100644 --- a/contracts/libs/crypto/EC256.sol +++ b/contracts/libs/crypto/EC256.sol @@ -32,7 +32,7 @@ library EC256 { * @param x The x-coordinate. * @param y The y-coordinate. */ - struct Apoint { + struct APoint { uint256 x; uint256 y; } @@ -43,7 +43,7 @@ library EC256 { * @param y The Jacobian Y coordinate. * @param z The Jacobian Z coordinate. */ - struct Jpoint { + struct JPoint { uint256 x; uint256 y; uint256 z; @@ -54,8 +54,17 @@ library EC256 { * @param ec The curve parameters. * @return aPoint_ The basepoint (gx, gy). */ - function basepoint(Curve memory ec) internal pure returns (Apoint memory aPoint_) { - return Apoint(ec.gx, ec.gy); + function basepoint(Curve memory ec) internal pure returns (APoint memory aPoint_) { + return APoint(ec.gx, ec.gy); + } + + /** + * @notice Returns the generator (base) point of the curve in jacobian form. + * @param ec The curve parameters. + * @return jPoint_ The basepoint (gx, gy, 1). + */ + function jbasepoint(Curve memory ec) internal pure returns (JPoint memory jPoint_) { + return JPoint(ec.gx, ec.gy, 1); } /** @@ -64,10 +73,7 @@ library EC256 { * @param u256_ The integer to reduce. * @return scalar_ The result of u256_ mod n. */ - function scalarFromU256( - Curve memory ec, - uint256 u256_ - ) internal pure returns (uint256 scalar_) { + function toScalar(Curve memory ec, uint256 u256_) internal pure returns (uint256 scalar_) { return u256_ % ec.n; } @@ -79,7 +85,7 @@ library EC256 { */ function isOnCurve( Curve memory ec, - Apoint memory aPoint_ + APoint memory aPoint_ ) internal pure returns (bool result_) { return _isOnCurve(aPoint_.x, aPoint_.y, ec.a, ec.b, ec.p); } @@ -94,34 +100,16 @@ library EC256 { return scalar_ < ec.n; } - /** - * @notice Compares two Jacobian points for equality in affine coordinates. - * @param ec The curve parameters. - * @param jPoint1_ The first Jacobian point. - * @param jPoint2_ The second Jacobian point. - * @return result_ True if their affine representations match. - */ - function jEqual( - Curve memory ec, - Jpoint memory jPoint1_, - Jpoint memory jPoint2_ - ) internal view returns (bool result_) { - Apoint memory aPoint1_ = affineFromJacobian(ec, jPoint1_); - Apoint memory aPoint2_ = affineFromJacobian(ec, jPoint2_); - - return aPoint1_.x == aPoint2_.x && aPoint1_.y == aPoint2_.y; - } - /** * @notice Converts a point from Jacobian to affine coordinates. * @param ec The curve parameters. * @param jPoint_ The Jacobian point (X, Y, Z). * @return aPoint_ The equivalent affine point (x, y). */ - function affineFromJacobian( + function toAffine( Curve memory ec, - Jpoint memory jPoint_ - ) internal view returns (Apoint memory aPoint_) { + JPoint memory jPoint_ + ) internal view returns (APoint memory aPoint_) { (aPoint_.x, aPoint_.y) = _affineFromJacobian(jPoint_, ec.p); } @@ -130,10 +118,8 @@ library EC256 { * @param aPoint_ The affine point (x, y). * @return jPoint_ The Jacobian representation (x, y, 1). */ - function jacobianFromAffine( - Apoint memory aPoint_ - ) internal pure returns (Jpoint memory jPoint_) { - return Jpoint(aPoint_.x, aPoint_.y, 1); + function toJacobian(APoint memory aPoint_) internal pure returns (JPoint memory jPoint_) { + return JPoint(aPoint_.x, aPoint_.y, 1); } /** @@ -141,7 +127,7 @@ library EC256 { * @param jPoint_ The Jacobian point to test. * @return result_ True if Z == 0. */ - function isJacobianInfinity(Jpoint memory jPoint_) internal pure returns (bool result_) { + function isJacobianInfinity(JPoint memory jPoint_) internal pure returns (bool result_) { return jPoint_.z == 0; } @@ -149,8 +135,26 @@ library EC256 { * @notice Returns the Jacobian representation of the point at infinity. * @return jPoint_ The point at infinity (0, 0, 0). */ - function jacobianInfinity() internal pure returns (Jpoint memory jPoint_) { - return Jpoint(0, 0, 0); + function jinfinity() internal pure returns (JPoint memory jPoint_) { + return JPoint(0, 0, 0); + } + + /** + * @notice Compares two Jacobian points for equality in affine coordinates. + * @param ec The curve parameters. + * @param jPoint1_ The first Jacobian point. + * @param jPoint2_ The second Jacobian point. + * @return result_ True if their affine representations match. + */ + function jEqual( + Curve memory ec, + JPoint memory jPoint1_, + JPoint memory jPoint2_ + ) internal view returns (bool result_) { + APoint memory aPoint1_ = toAffine(ec, jPoint1_); + APoint memory aPoint2_ = toAffine(ec, jPoint2_); + + return aPoint1_.x == aPoint2_.x && aPoint1_.y == aPoint2_.y; } /** @@ -162,11 +166,11 @@ library EC256 { */ function jMultShamir( Curve memory ec, - Jpoint memory jPoint_, + JPoint memory jPoint_, uint256 scalar_ - ) internal pure returns (Jpoint memory jPoint2_) { + ) internal pure returns (JPoint memory jPoint2_) { unchecked { - Jpoint[16] memory jPoints_ = _preComputeJacobianPoints(ec, jPoint_); + JPoint[16] memory jPoints_ = _preComputeJacobianPoints(ec, jPoint_); for (uint256 i = 0; i < 64; ++i) { jPoint2_ = jDoublePoint(ec, jPoint2_); @@ -194,13 +198,13 @@ library EC256 { */ function jMultShamir2( Curve memory ec, - Jpoint memory jPoint1_, - Jpoint memory jPoint2_, + JPoint memory jPoint1_, + JPoint memory jPoint2_, uint256 scalar1_, uint256 scalar2_ - ) internal pure returns (Jpoint memory jPoint3_) { + ) internal pure returns (JPoint memory jPoint3_) { unchecked { - Jpoint[16] memory jPoints_ = _preComputeJacobianPoints2(ec, jPoint1_, jPoint2_); + JPoint[16] memory jPoints_ = _preComputeJacobianPoints2(ec, jPoint1_, jPoint2_); for (uint256 i = 0; i < 128; ++i) { jPoint3_ = jDoublePoint(ec, jPoint3_); @@ -225,20 +229,20 @@ library EC256 { */ function jAddPoint( Curve memory ec, - Jpoint memory jPoint1_, - Jpoint memory jPoint2_ - ) internal pure returns (Jpoint memory jPoint3_) { + JPoint memory jPoint1_, + JPoint memory jPoint2_ + ) internal pure returns (JPoint memory jPoint3_) { if (isJacobianInfinity(jPoint1_)) { - return Jpoint(jPoint2_.x, jPoint2_.y, jPoint2_.z); + return JPoint(jPoint2_.x, jPoint2_.y, jPoint2_.z); } if (isJacobianInfinity(jPoint2_)) { - return Jpoint(jPoint1_.x, jPoint1_.y, jPoint1_.z); + return JPoint(jPoint1_.x, jPoint1_.y, jPoint1_.z); } (uint256 x_, uint256 y_, uint256 z_) = _jAdd(jPoint1_, jPoint2_, ec.p, ec.a); - return Jpoint(x_, y_, z_); + return JPoint(x_, y_, z_); } /** @@ -249,10 +253,10 @@ library EC256 { */ function jDoublePoint( Curve memory ec, - Jpoint memory jPoint1_ - ) internal pure returns (Jpoint memory jPoint2_) { + JPoint memory jPoint1_ + ) internal pure returns (JPoint memory jPoint2_) { if (isJacobianInfinity(jPoint1_)) { - return Jpoint(jPoint1_.x, jPoint1_.y, jPoint1_.z); + return JPoint(jPoint1_.x, jPoint1_.y, jPoint1_.z); } (uint256 x_, uint256 y_, uint256 z_) = _jDouble( @@ -263,7 +267,7 @@ library EC256 { ec.a ); - return Jpoint(x_, y_, z_); + return JPoint(x_, y_, z_); } /** @@ -271,8 +275,8 @@ library EC256 { * Reference: https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-add-1998-cmo-2 */ function _jAdd( - Jpoint memory jPoint1_, - Jpoint memory jPoint2_, + JPoint memory jPoint1_, + JPoint memory jPoint2_, uint256 p_, uint256 a_ ) private pure returns (uint256 jx3_, uint256 jy3_, uint256 jz3_) { @@ -384,7 +388,7 @@ library EC256 { * @dev Internal conversion from Jacobian to affine coordinates. */ function _affineFromJacobian( - Jpoint memory jPoint_, + JPoint memory jPoint_, uint256 p_ ) private view returns (uint256 ax_, uint256 ay_) { if (isJacobianInfinity(jPoint_)) { @@ -425,10 +429,10 @@ library EC256 { */ function _preComputeJacobianPoints( Curve memory ec, - Jpoint memory jPoint_ - ) private pure returns (Jpoint[16] memory jPoints_) { - jPoints_[0x00] = jacobianInfinity(); - jPoints_[0x01] = Jpoint(jPoint_.x, jPoint_.y, jPoint_.z); + JPoint memory jPoint_ + ) private pure returns (JPoint[16] memory jPoints_) { + jPoints_[0x00] = jinfinity(); + jPoints_[0x01] = JPoint(jPoint_.x, jPoint_.y, jPoint_.z); jPoints_[0x02] = jDoublePoint(ec, jPoints_[0x01]); jPoints_[0x04] = jDoublePoint(ec, jPoints_[0x02]); jPoints_[0x08] = jDoublePoint(ec, jPoints_[0x04]); @@ -450,12 +454,12 @@ library EC256 { */ function _preComputeJacobianPoints2( Curve memory ec, - Jpoint memory jPoint1_, - Jpoint memory jPoint2_ - ) private pure returns (Jpoint[16] memory jPoints_) { - jPoints_[0x00] = jacobianInfinity(); - jPoints_[0x01] = Jpoint(jPoint1_.x, jPoint1_.y, jPoint1_.z); - jPoints_[0x04] = Jpoint(jPoint2_.x, jPoint2_.y, jPoint2_.z); + JPoint memory jPoint1_, + JPoint memory jPoint2_ + ) private pure returns (JPoint[16] memory jPoints_) { + jPoints_[0x00] = jinfinity(); + jPoints_[0x01] = JPoint(jPoint1_.x, jPoint1_.y, jPoint1_.z); + jPoints_[0x04] = JPoint(jPoint2_.x, jPoint2_.y, jPoint2_.z); jPoints_[0x02] = jDoublePoint(ec, jPoints_[0x01]); jPoints_[0x08] = jDoublePoint(ec, jPoints_[0x04]); jPoints_[0x03] = jAddPoint(ec, jPoints_[0x01], jPoints_[0x02]); diff --git a/contracts/libs/crypto/ECDSA256.sol b/contracts/libs/crypto/ECDSA256.sol index 6e02ae6b..712ce277 100644 --- a/contracts/libs/crypto/ECDSA256.sol +++ b/contracts/libs/crypto/ECDSA256.sol @@ -37,7 +37,8 @@ library ECDSA256 { unchecked { uint256 r_; uint256 s_; - EC256.Apoint memory p_; + EC256.APoint memory p_; + (r_, s_) = _split(signature_); (p_.x, p_.y) = _split(pubKey_); @@ -54,14 +55,14 @@ library ECDSA256 { u2_ = mulmod(r_, w_, ec.n); } - EC256.Jpoint memory point_ = ec.jMultShamir2( - p_.jacobianFromAffine(), - ec.basepoint().jacobianFromAffine(), + EC256.JPoint memory point_ = ec.jMultShamir2( + p_.toJacobian(), + ec.basepoint().toJacobian(), u1_, u2_ ); - return ec.affineFromJacobian(point_).x % ec.n == r_; + return ec.toAffine(point_).x % ec.n == r_; } } diff --git a/contracts/libs/crypto/SchnorrSignature.sol b/contracts/libs/crypto/Schnorr256.sol similarity index 76% rename from contracts/libs/crypto/SchnorrSignature.sol rename to contracts/libs/crypto/Schnorr256.sol index 6d1ad982..a3eebff8 100644 --- a/contracts/libs/crypto/SchnorrSignature.sol +++ b/contracts/libs/crypto/Schnorr256.sol @@ -9,7 +9,7 @@ import {MemoryUtils} from "../utils/MemoryUtils.sol"; * * This library provides functionality for Schnorr signature verification over any 256-bit curve. */ -library SchnorrSignature { +library Schnorr256 { using MemoryUtils for *; using EC256 for *; @@ -29,23 +29,23 @@ library SchnorrSignature { bytes memory signature_, bytes memory pubKey_ ) internal view returns (bool) { - (EC256.Apoint memory r_, uint256 e_) = _parseSignature(signature_); - EC256.Apoint memory p_ = _parsePubKey(pubKey_); + (EC256.APoint memory r_, uint256 e_) = _parseSignature(signature_); + EC256.APoint memory p_ = _parsePubKey(pubKey_); if (!ec.isOnCurve(r_) || !ec.isOnCurve(p_) || !ec.isValidScalar(e_)) { return false; } - EC256.Jpoint memory lhs_ = EC256.jMultShamir(ec, ec.basepoint().jacobianFromAffine(), e_); + EC256.JPoint memory lhs_ = ec.jMultShamir(ec.jbasepoint(), e_); - uint256 c_ = ec.scalarFromU256( + uint256 c_ = ec.toScalar( uint256(keccak256(abi.encodePacked(ec.gx, ec.gy, r_.x, r_.y, hashedMessage_))) ); - EC256.Jpoint memory rhs_ = EC256.jMultShamir(ec, p_.jacobianFromAffine(), c_); - rhs_ = EC256.jAddPoint(ec, rhs_, r_.jacobianFromAffine()); + EC256.JPoint memory rhs_ = ec.jMultShamir(p_.toJacobian(), c_); + rhs_ = ec.jAddPoint(rhs_, r_.toJacobian()); - return EC256.jEqual(ec, lhs_, rhs_); + return ec.jEqual(lhs_, rhs_); } /** @@ -53,7 +53,7 @@ library SchnorrSignature { */ function _parseSignature( bytes memory signature_ - ) private pure returns (EC256.Apoint memory r_, uint256 e_) { + ) private pure returns (EC256.APoint memory r_, uint256 e_) { if (signature_.length != 96) { revert LengthIsNot96(); } @@ -64,7 +64,7 @@ library SchnorrSignature { /** * @dev Helper function for converting 64-byte pub key into affine point. */ - function _parsePubKey(bytes memory pubKey_) private pure returns (EC256.Apoint memory p_) { + function _parsePubKey(bytes memory pubKey_) private pure returns (EC256.APoint memory p_) { if (pubKey_.length != 64) { revert LengthIsNot64(); } diff --git a/contracts/mock/libs/crypto/SchnorrSignatureMock.sol b/contracts/mock/libs/crypto/Schnorr256Mock.sol similarity index 80% rename from contracts/mock/libs/crypto/SchnorrSignatureMock.sol rename to contracts/mock/libs/crypto/Schnorr256Mock.sol index 02a771c8..2b8e2426 100644 --- a/contracts/mock/libs/crypto/SchnorrSignatureMock.sol +++ b/contracts/mock/libs/crypto/Schnorr256Mock.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.21; -import {SchnorrSignature} from "../../../libs/crypto/SchnorrSignature.sol"; +import {Schnorr256} from "../../../libs/crypto/Schnorr256.sol"; import {EC256} from "../../../libs/crypto/EC256.sol"; -contract SchnorrSignatureMock { +contract Schnorr256Mock { EC256.Curve private _secp256k1CurveParams = EC256.Curve({ a: 0x0000000000000000000000000000000000000000000000000000000000000000, @@ -20,6 +20,6 @@ contract SchnorrSignatureMock { bytes memory signature_, bytes memory pubKey_ ) external view returns (bool isVerified_) { - return SchnorrSignature.verify(_secp256k1CurveParams, hashedMessage_, signature_, pubKey_); + return Schnorr256.verify(_secp256k1CurveParams, hashedMessage_, signature_, pubKey_); } } diff --git a/test/libs/crypto/ECDSA256.test.ts b/test/libs/crypto/ECDSA256.test.ts index c38ff1d0..179b3fd5 100644 --- a/test/libs/crypto/ECDSA256.test.ts +++ b/test/libs/crypto/ECDSA256.test.ts @@ -4,7 +4,7 @@ import { Reverter } from "@/test/helpers/reverter"; import { ECDSA256Mock } from "@ethers-v6"; -describe("ECDSA256", () => { +describe.only("ECDSA256", () => { const reverter = new Reverter(); let ecdsa256: ECDSA256Mock; diff --git a/test/libs/crypto/SchnorrSignature.test.ts b/test/libs/crypto/Schnorr256.test.ts similarity index 95% rename from test/libs/crypto/SchnorrSignature.test.ts rename to test/libs/crypto/Schnorr256.test.ts index 750ee916..58658484 100644 --- a/test/libs/crypto/SchnorrSignature.test.ts +++ b/test/libs/crypto/Schnorr256.test.ts @@ -2,13 +2,13 @@ import { ethers } from "hardhat"; import { expect } from "chai"; import { Reverter } from "@/test/helpers/reverter"; -import { SchnorrSignatureMock } from "@ethers-v6"; +import { Schnorr256Mock } from "@ethers-v6"; import { secp256k1 } from "@noble/curves/secp256k1"; import { bytesToNumberBE } from "@noble/curves/abstract/utils"; import { AffinePoint } from "@noble/curves/abstract/curve"; -describe("SchnorrSignature", () => { +describe.only("Schnorr256", () => { const schnorrKeyPair = () => { const privKey = bytesToNumberBE(secp256k1.utils.randomPrivateKey()); const pubKey = secp256k1.ProjectivePoint.BASE.multiply(privKey).toAffine(); @@ -52,10 +52,10 @@ describe("SchnorrSignature", () => { const reverter = new Reverter(); - let schnorr: SchnorrSignatureMock; + let schnorr: Schnorr256Mock; before(async () => { - const SchnorrSignature = await ethers.getContractFactory("SchnorrSignatureMock"); + const SchnorrSignature = await ethers.getContractFactory("Schnorr256Mock"); schnorr = await SchnorrSignature.deploy(); From 17228c68c20dec4ae5dfabcabd91fee8cd4506c8 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Thu, 1 May 2025 18:11:42 +0300 Subject: [PATCH 15/20] fix --- contracts/libs/crypto/ECDSA256.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/libs/crypto/ECDSA256.sol b/contracts/libs/crypto/ECDSA256.sol index 712ce277..88b36e84 100644 --- a/contracts/libs/crypto/ECDSA256.sol +++ b/contracts/libs/crypto/ECDSA256.sol @@ -57,7 +57,7 @@ library ECDSA256 { EC256.JPoint memory point_ = ec.jMultShamir2( p_.toJacobian(), - ec.basepoint().toJacobian(), + ec.jbasepoint(), u1_, u2_ ); From af5b6dfff4ef4b55992573d6365f98b08c0bb603 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Thu, 1 May 2025 18:20:01 +0300 Subject: [PATCH 16/20] fixes --- contracts/libs/crypto/ECDSA384.sol | 9 +++------ contracts/libs/crypto/ECDSA512.sol | 9 +++------ contracts/mock/libs/crypto/ECDSA384Mock.sol | 6 ++---- contracts/mock/libs/crypto/ECDSA512Mock.sol | 3 +-- test/libs/crypto/ECDSA256.test.ts | 2 +- test/libs/crypto/Schnorr256.test.ts | 6 +++--- 6 files changed, 13 insertions(+), 22 deletions(-) diff --git a/contracts/libs/crypto/ECDSA384.sol b/contracts/libs/crypto/ECDSA384.sol index 4e492705..03d3b293 100644 --- a/contracts/libs/crypto/ECDSA384.sol +++ b/contracts/libs/crypto/ECDSA384.sol @@ -28,7 +28,6 @@ library ECDSA384 { bytes gy; bytes p; bytes n; - bytes lowSmax; } // solhint-disable-next-line contract-name-capwords @@ -39,7 +38,6 @@ library ECDSA384 { uint512 gy; uint512 p; uint512 n; - uint512 lowSmax; } // solhint-disable-next-line contract-name-capwords @@ -52,7 +50,7 @@ library ECDSA384 { /** * @notice The function to verify the ECDSA signature - * @param curveParams_ the 384-bit curve parameters. `lowSmax` is `n / 2`. + * @param curveParams_ the 384-bit curve parameters. * @param hashedMessage_ the already hashed message to be verified. * @param signature_ the ECDSA signature. Equals to `bytes(r) + bytes(s)`. * @param pubKey_ the full public key of a signer. Equals to `bytes(x) + bytes(y)`. @@ -78,8 +76,7 @@ library ECDSA384 { gx: U512.fromBytes(curveParams_.gx), gy: U512.fromBytes(curveParams_.gy), p: U512.fromBytes(curveParams_.p), - n: U512.fromBytes(curveParams_.n), - lowSmax: U512.fromBytes(curveParams_.lowSmax) + n: U512.fromBytes(curveParams_.n) }); call512 call_ = U512.initCall(); @@ -89,7 +86,7 @@ library ECDSA384 { U512.eqU256(inputs_.r, 0) || U512.cmp(inputs_.r, params_.n) >= 0 || U512.eqU256(inputs_.s, 0) || - U512.cmp(inputs_.s, params_.lowSmax) > 0 + U512.cmp(inputs_.s, U512.shr(params_.n, 1)) > 0 ) { return false; } diff --git a/contracts/libs/crypto/ECDSA512.sol b/contracts/libs/crypto/ECDSA512.sol index 3f62c359..827783b7 100644 --- a/contracts/libs/crypto/ECDSA512.sol +++ b/contracts/libs/crypto/ECDSA512.sol @@ -28,7 +28,6 @@ library ECDSA512 { bytes gy; bytes p; bytes n; - bytes lowSmax; } // solhint-disable-next-line contract-name-capwords @@ -39,7 +38,6 @@ library ECDSA512 { uint512 gy; uint512 p; uint512 n; - uint512 lowSmax; } // solhint-disable-next-line contract-name-capwords @@ -52,7 +50,7 @@ library ECDSA512 { /** * @notice The function to verify the ECDSA signature - * @param curveParams_ the 512-bit curve parameters. `lowSmax` is `n / 2`. + * @param curveParams_ the 512-bit curve parameters. * @param hashedMessage_ the already hashed message to be verified. * @param signature_ the ECDSA signature. Equals to `bytes(r) + bytes(s)`. * @param pubKey_ the full public key of a signer. Equals to `bytes(x) + bytes(y)`. @@ -78,8 +76,7 @@ library ECDSA512 { gx: U512.fromBytes(curveParams_.gx), gy: U512.fromBytes(curveParams_.gy), p: U512.fromBytes(curveParams_.p), - n: U512.fromBytes(curveParams_.n), - lowSmax: U512.fromBytes(curveParams_.lowSmax) + n: U512.fromBytes(curveParams_.n) }); call512 call_ = U512.initCall(); @@ -89,7 +86,7 @@ library ECDSA512 { U512.eqU256(inputs_.r, 0) || U512.cmp(inputs_.r, params_.n) >= 0 || U512.eqU256(inputs_.s, 0) || - U512.cmp(inputs_.s, params_.lowSmax) > 0 + U512.cmp(inputs_.s, U512.shr(params_.n, 1)) > 0 ) { return false; } diff --git a/contracts/mock/libs/crypto/ECDSA384Mock.sol b/contracts/mock/libs/crypto/ECDSA384Mock.sol index b29c035f..c9e7cceb 100644 --- a/contracts/mock/libs/crypto/ECDSA384Mock.sol +++ b/contracts/mock/libs/crypto/ECDSA384Mock.sol @@ -17,8 +17,7 @@ contract ECDSA384Mock { gx: hex"aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7", gy: hex"3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f", p: hex"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff", - n: hex"ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973", - lowSmax: hex"7fffffffffffffffffffffffffffffffffffffffffffffffe3b1a6c0fa1b96efac0d06d9245853bd76760cb5666294b9" + n: hex"ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973" }); ECDSA384.Parameters private _brainpoolP384r1CurveParams = @@ -28,8 +27,7 @@ contract ECDSA384Mock { gx: hex"1d1c64f068cf45ffa2a63a81b7c13f6b8847a3e77ef14fe3db7fcafe0cbd10e8e826e03436d646aaef87b2e247d4af1e", gy: hex"8abe1d7520f9c2a45cb1eb8e95cfd55262b70b29feec5864e19c054ff99129280e4646217791811142820341263c5315", p: hex"8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b412b1da197fb71123acd3a729901d1a71874700133107ec53", - n: hex"8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b31f166e6cac0425a7cf3ab6af6b7fc3103b883202e9046565", - lowSmax: hex"465c8f41519c369407aeb7bf287320ef8a97b884f6aa2b598f8b3736560212d3e79d5b57b5bfe1881dc41901748232b2" + n: hex"8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b31f166e6cac0425a7cf3ab6af6b7fc3103b883202e9046565" }); function verifySECP384r1( diff --git a/contracts/mock/libs/crypto/ECDSA512Mock.sol b/contracts/mock/libs/crypto/ECDSA512Mock.sol index c8ca9ec7..97e58189 100644 --- a/contracts/mock/libs/crypto/ECDSA512Mock.sol +++ b/contracts/mock/libs/crypto/ECDSA512Mock.sol @@ -13,8 +13,7 @@ contract ECDSA512Mock { gx: hex"81aee4bdd82ed9645a21322e9c4c6a9385ed9f70b5d916c1b43b62eef4d0098eff3b1f78e2d0d48d50d1687b93b97d5f7c6d5047406a5e688b352209bcb9f822", gy: hex"7dde385d566332ecc0eabfa9cf7822fdf209f70024a57b1aa000c55b881f8111b2dcde494a5f485e5bca4bd88a2763aed1ca2b2fa8f0540678cd1e0f3ad80892", p: hex"aadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca703308717d4d9b009bc66842aecda12ae6a380e62881ff2f2d82c68528aa6056583a48f3", - n: hex"aadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca70330870553e5c414ca92619418661197fac10471db1d381085ddaddb58796829ca90069", - lowSmax: hex"556ecedc6df4e2459fea735719e4fe03e59846d9d9e4e9076b31ce65381984382a9f2e20a654930ca0c3308cbfd608238ed8e9c0842eed6edac3cb414e548034" + n: hex"aadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca70330870553e5c414ca92619418661197fac10471db1d381085ddaddb58796829ca90069" }); function verifyBrainpoolP512r1WithoutHashing( diff --git a/test/libs/crypto/ECDSA256.test.ts b/test/libs/crypto/ECDSA256.test.ts index 179b3fd5..c38ff1d0 100644 --- a/test/libs/crypto/ECDSA256.test.ts +++ b/test/libs/crypto/ECDSA256.test.ts @@ -4,7 +4,7 @@ import { Reverter } from "@/test/helpers/reverter"; import { ECDSA256Mock } from "@ethers-v6"; -describe.only("ECDSA256", () => { +describe("ECDSA256", () => { const reverter = new Reverter(); let ecdsa256: ECDSA256Mock; diff --git a/test/libs/crypto/Schnorr256.test.ts b/test/libs/crypto/Schnorr256.test.ts index 58658484..2dc7ef5b 100644 --- a/test/libs/crypto/Schnorr256.test.ts +++ b/test/libs/crypto/Schnorr256.test.ts @@ -8,7 +8,7 @@ import { secp256k1 } from "@noble/curves/secp256k1"; import { bytesToNumberBE } from "@noble/curves/abstract/utils"; import { AffinePoint } from "@noble/curves/abstract/curve"; -describe.only("Schnorr256", () => { +describe("Schnorr256", () => { const schnorrKeyPair = () => { const privKey = bytesToNumberBE(secp256k1.utils.randomPrivateKey()); const pubKey = secp256k1.ProjectivePoint.BASE.multiply(privKey).toAffine(); @@ -55,9 +55,9 @@ describe.only("Schnorr256", () => { let schnorr: Schnorr256Mock; before(async () => { - const SchnorrSignature = await ethers.getContractFactory("Schnorr256Mock"); + const Schnorr256 = await ethers.getContractFactory("Schnorr256Mock"); - schnorr = await SchnorrSignature.deploy(); + schnorr = await Schnorr256.deploy(); await reverter.snapshot(); }); From 14e74aa272699e51bedfad55ae1fd64c2f3bdff1 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Thu, 1 May 2025 18:22:33 +0300 Subject: [PATCH 17/20] bumped version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 945085dc..ecd44310 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@solarity/solidity-lib", - "version": "3.0.2", + "version": "3.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@solarity/solidity-lib", - "version": "3.0.2", + "version": "3.1.0", "license": "MIT", "dependencies": { "@openzeppelin/contracts": "5.2.0", diff --git a/package.json b/package.json index 38f54826..da12a42d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@solarity/solidity-lib", - "version": "3.0.2", + "version": "3.1.0", "license": "MIT", "author": "Distributed Lab", "readme": "README.md", From 1d6cff5f7f5fd1b988464009ed9a0197dc316498 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Fri, 2 May 2025 12:19:38 +0300 Subject: [PATCH 18/20] cov 100 --- contracts/mock/libs/crypto/EC256Mock.sol | 26 ++++++++++++++++++ test/libs/crypto/EC256.test.ts | 35 ++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 contracts/mock/libs/crypto/EC256Mock.sol create mode 100644 test/libs/crypto/EC256.test.ts diff --git a/contracts/mock/libs/crypto/EC256Mock.sol b/contracts/mock/libs/crypto/EC256Mock.sol new file mode 100644 index 00000000..9c7c8f3d --- /dev/null +++ b/contracts/mock/libs/crypto/EC256Mock.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import {EC256} from "../../../libs/crypto/EC256.sol"; + +contract EC256Mock { + using EC256 for *; + + EC256.Curve public secp256k1CurveParams = + EC256.Curve({ + a: 0x0000000000000000000000000000000000000000000000000000000000000000, + b: 0x0000000000000000000000000000000000000000000000000000000000000007, + gx: 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798, + gy: 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8, + p: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f, + n: 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 + }); + + function affineInfinity() external view returns (EC256.APoint memory) { + return secp256k1CurveParams.toAffine(EC256.jinfinity()); + } + + function basepoint() external view returns (EC256.APoint memory) { + return secp256k1CurveParams.basepoint(); + } +} diff --git a/test/libs/crypto/EC256.test.ts b/test/libs/crypto/EC256.test.ts new file mode 100644 index 00000000..28977e88 --- /dev/null +++ b/test/libs/crypto/EC256.test.ts @@ -0,0 +1,35 @@ +import { ethers } from "hardhat"; +import { expect } from "chai"; +import { Reverter } from "@/test/helpers/reverter"; + +import { EC256Mock } from "@ethers-v6"; + +describe("EC256", () => { + const reverter = new Reverter(); + + let ec256: EC256Mock; + + before(async () => { + const EC256Mock = await ethers.getContractFactory("EC256Mock"); + + ec256 = await EC256Mock.deploy(); + + await reverter.snapshot(); + }); + + afterEach(reverter.revert); + + describe("toAffine", () => { + it("should return zero affine", async () => { + expect(await ec256.affineInfinity()).to.deep.equal([0, 0]); + }); + }); + + describe("basepoint", () => { + it("should return correct basepoint", async () => { + const secp256k1 = await ec256.secp256k1CurveParams(); + + expect(await ec256.basepoint()).to.deep.equal([secp256k1.gx, secp256k1.gy]); + }); + }); +}); From cc17f8a07cf27ea234e6b33eee9e4b2f8d1176e0 Mon Sep 17 00:00:00 2001 From: dovgopoly Date: Fri, 2 May 2025 12:22:07 +0300 Subject: [PATCH 19/20] upd readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9fead4a7..c42e95ad 100644 --- a/README.md +++ b/README.md @@ -46,9 +46,11 @@ contracts │ ├── bn │ │ └── U512 — "A hyperoptimized uint512 implementation" │ ├── crypto +│ │ ├── EC256 — "Elliptic curve arithmetic over a 256-bit prime field" │ │ ├── ECDSA256 — "ECDSA verification over any 256-bit curves" │ │ ├── ECDSA384 — "ECDSA verification over any 384-bit curves" │ │ ├── ECDSA512 — "ECDSA verification over any 512-bit curves" +│ │ ├── Schnorr256 — "Schnorr signature verification over any 256-bit curves" │ │ └── RSASSAPSS — "RSASSA-PSS verification with MGF1" │ ├── data—structures │ │ ├── AvlTree — "AVL tree implementation with an iterator traversal" From eb6bceb3a640653b96c2ccc66dc3dbdfa32636c5 Mon Sep 17 00:00:00 2001 From: Artem Chystiakov Date: Fri, 2 May 2025 12:50:24 +0300 Subject: [PATCH 20/20] quick readme fix --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c42e95ad..8ecf6102 100644 --- a/README.md +++ b/README.md @@ -47,11 +47,11 @@ contracts │ │ └── U512 — "A hyperoptimized uint512 implementation" │ ├── crypto │ │ ├── EC256 — "Elliptic curve arithmetic over a 256-bit prime field" -│ │ ├── ECDSA256 — "ECDSA verification over any 256-bit curves" -│ │ ├── ECDSA384 — "ECDSA verification over any 384-bit curves" -│ │ ├── ECDSA512 — "ECDSA verification over any 512-bit curves" -│ │ ├── Schnorr256 — "Schnorr signature verification over any 256-bit curves" -│ │ └── RSASSAPSS — "RSASSA-PSS verification with MGF1" +│ │ ├── ECDSA256 — "ECDSA verification over any 256-bit curve" +│ │ ├── ECDSA384 — "ECDSA verification over any 384-bit curve" +│ │ ├── ECDSA512 — "ECDSA verification over any 512-bit curve" +│ │ ├── Schnorr256 — "Schnorr signature verification over any 256-bit curve" +│ │ └── RSASSAPSS — "RSASSA-PSS signature verification with MGF1" │ ├── data—structures │ │ ├── AvlTree — "AVL tree implementation with an iterator traversal" │ │ ├── CartesianMerkleTree — "CMT reference implementation"