Skip to content

Instantly share code, notes, and snippets.

@mattdesl
Last active April 16, 2024 09:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattdesl/f1554460fb1fceaed76c733778a33453 to your computer and use it in GitHub Desktop.
Save mattdesl/f1554460fb1fceaed76c733778a33453 to your computer and use it in GitHub Desktop.
Verification of bn254 points aka alt_bn128

Verifying a BN254 Private Key in EVM

This is a simple Ethereum contract that verifies that a given public key is exactly derived from a given private key. This uses the curve BN254 aka alt_bn128, as there exists an EVM/Ethereum precompile for this exact elliptic curve multiplication.

There is also some sample JS code that a) generates a secret key and public key, that you can use to test the contract, and b) does the same operation client-side in a faux contract interface, which might be useful for client-side validiation before incurring gas fees.

I wrote this mostly as a learning exercise as I'm getting familiar with elliptic curve cryptography, however, it has some practical applications and I was having trouble finding examples online of a similar application in Solidity. An example of a practical application of this is seen in Fair Data Exchange, which is the original reference for much of my Solidity code here.

import { bn254 } from "@noble/curves/bn254";
// Generate a random private key off-chain
const secretKey = bn254.utils.randomPrivateKey();
// Get the public key and its affine {x, y} points as scalars
const publicKey = bn254.getPublicKey(secretKey);
const publicKeyProjective = bn254.ProjectivePoint.fromHex(publicKey);
const publicKeyAffine = publicKeyProjective.toAffine();
// A contract will receive these scalar points as some data it stores
const fauxContract = FauxContract({
x: publicKeyAffine.x,
y: publicKeyAffine.y,
});
// Now let's assume somebody is putting a new secret key into the contract
// Which may or may not be the underlying secret key we are looking for
const secretKeyInputBytes = secretKey; // valid
// Try using this instead to test invalid input
// const secretKeyInputBytes = bn254.utils.randomPrivateKey(); // invalid
// This is sent as-is to the contract, and it's treated as just a big number (scalar)
const secretKeyInputScalar =
bn254.utils.normPrivateKeyToScalar(secretKeyInputBytes);
console.log("Secret Key:", secretKeyInputScalar);
console.log("Pub Key X:", publicKeyAffine.x);
console.log("Pub Key Y:", publicKeyAffine.y);
// Now, a method on the contract verifies if this input scalar matches the public key
const valid = fauxContract.verify(secretKeyInputScalar);
console.log("Do they match?", valid);
function FauxContract(publicKeyAffine) {
return {
verify(secretKeyScalar) {
// Note the base point is defined by EVM as { x: 1, y: 2 }
// const basePoint = bn254.ProjectivePoint.BASE.toAffine()
// Multiply the projective base point by the scalar
const newPointProjective =
bn254.ProjectivePoint.BASE.multiply(secretKeyScalar);
// If the new point matches the public key point, we are good to go
// Note: both happening in affine coordinates
const newPoint = newPointProjective.toAffine();
return (
newPoint.x === publicKeyAffine.x && newPoint.y === publicKeyAffine.y
);
},
};
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.12 <0.9.0;
contract PointCheck {
struct G1Point {
uint256 x;
uint256 y;
}
event BroadcastSecKey(address indexed sender, uint256 secKey);
/// @return the generator of G1
// solhint-disable-next-line func-name-mixedcase
function P1() internal pure returns (G1Point memory) {
return G1Point(1, 2);
}
/*
* @return The multiplication of a G1 point with a scalar value.
*/
function mul(
G1Point memory _p,
uint256 v
) internal view returns (G1Point memory) {
uint256[3] memory input;
input[0] = _p.x;
input[1] = _p.y;
input[2] = v;
G1Point memory result;
bool success;
// solium-disable-next-line security/no-inline-assembly
assembly {
success := staticcall(sub(gas(), 2000), 7, input, 0x60, result, 0x40)
switch success case 0 { revert(0, 0) }
}
require (success, "BN254: mul failed");
return result;
}
function verifyPrivateKey(
uint256 _secKey,
uint256 _pubKeyX,
uint256 _pubKeyY
) public {
G1Point memory result = mul(P1(), _secKey);
require(result.x == _pubKeyX && result.y == _pubKeyY, "invalid secret key");
// broadcast secret key now
emit BroadcastSecKey(msg.sender, _secKey);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment