diff --git a/bindings/src/conversion.rs b/bindings/src/conversion.rs index dc3e33d6..86dc935c 100644 --- a/bindings/src/conversion.rs +++ b/bindings/src/conversion.rs @@ -7,7 +7,7 @@ use arm_risc0::logic_instance::{AppData, ExpirableBlob}; use arm_risc0::logic_proof::LogicVerifierInputs; use arm_risc0::proving_system::encode_seal; -use arm_risc0::transaction::{Delta, Transaction}; +use arm_risc0::transaction::{Delta as ArmDelta, Transaction}; use arm_risc0::utils::words_to_bytes; sol!( @@ -120,8 +120,8 @@ impl From for ProtocolAdapter::Action { impl From for ProtocolAdapter::Transaction { fn from(tx: Transaction) -> Self { let delta_proof = match &tx.delta_proof { - Delta::Witness(_) => panic!("Unbalanced Transactions cannot be converted"), - Delta::Proof(proof) => proof.to_bytes().to_vec(), + ArmDelta::Witness(_) => panic!("Unbalanced Transactions cannot be converted"), + ArmDelta::Proof(proof) => proof.to_bytes().to_vec(), }; Self { diff --git a/contracts/src/ProtocolAdapter.sol b/contracts/src/ProtocolAdapter.sol index 7d41b313..63ce4bf7 100644 --- a/contracts/src/ProtocolAdapter.sol +++ b/contracts/src/ProtocolAdapter.sol @@ -35,7 +35,7 @@ contract ProtocolAdapter is CommitmentTree, NullifierSet { - using Delta for Delta.CurvePoint; + using Delta for Delta.Point; using MerkleTree for bytes32[]; using Logic for Logic.VerifierInput[]; using Logic for Logic.VerifierInput; @@ -61,7 +61,7 @@ contract ProtocolAdapter is bytes32[] tags; bytes32[] logicRefs; bytes32 latestCommitmentTreeRoot; - Delta.CurvePoint transactionDelta; + Delta.Point transactionDelta; uint256 tagCounter; /* Proof aggregation-related variables */ bool isProofAggregated; @@ -146,7 +146,7 @@ contract ProtocolAdapter is // Add the unit delta to the transaction delta. vars.transactionDelta = vars.transactionDelta .add( - Delta.CurvePoint({ + Delta.Point({ x: uint256(complianceVerifierInput.instance.unitDeltaX), y: uint256(complianceVerifierInput.instance.unitDeltaY) }) diff --git a/contracts/src/libs/proving/Delta.sol b/contracts/src/libs/proving/Delta.sol index bf8083b3..55cb12ab 100644 --- a/contracts/src/libs/proving/Delta.sol +++ b/contracts/src/libs/proving/Delta.sol @@ -10,43 +10,62 @@ import {EfficientHashLib} from "@solady/utils/EfficientHashLib.sol"; /// @notice A library containing methods of the delta proving system. /// @custom:security-contact security@anoma.foundation library Delta { - using Delta for CurvePoint; + using Delta for Point; /// @notice An elliptic curve point representing a delta value. /// @param x The x component of the point. /// @param y The y component of the point. - struct CurvePoint { + struct Point { uint256 x; uint256 y; } - /// @notice The constant of the secp256k1 (K-256) elliptic curve. + /// @notice The x-coordinate of the curve generator point. + uint256 internal constant _GX = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798; + + /// @notice The y-coordinate of the curve generator point. + uint256 internal constant _GY = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8; + + // @notice The coefficient a of th secp256k1 (K-256) elliptic curve (y² = x³ + ax + b). uint256 internal constant _AA = 0; - /// @notice The modulus of the secp256k1 (K-256) elliptic curve. + // @notice The coefficient b of th secp256k1 (K-256) elliptic curve (y² = x³ + ax + b). + uint256 internal constant _BB = 7; + + /// @notice The field prime modulus (2^256 - 2^32 - 977) of the secp256k1 (K-256) elliptic curve (y² = x³ + ax + b). uint256 internal constant _PP = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F; /// @notice Thrown if the recovered delta public key doesn't match the delta instance. error DeltaMismatch(address expected, address actual); + /// @notice Thrown when a provided point is not on the curve. + error PointNotOnCurve(Point point); + /// @notice Returns the elliptic curve point representing the zero delta. /// @return zeroDelta The zero delta. - function zero() internal pure returns (CurvePoint memory zeroDelta) { - zeroDelta = CurvePoint({x: 0, y: 0}); + function zero() internal pure returns (Point memory zeroDelta) { + zeroDelta = Point({x: 0, y: 0}); } - /// @notice Adds two elliptic curve points and returns the resulting value. - /// @param p1 The first curve point. - /// @param p2 The second curve point. + /// @notice Adds two delta points and returns the sum. + /// @param lhs The left-hand side point that can also be the zero delta. + /// @param rhs The right-hand side point that must be a curve point. /// @return sum The resulting curve point. - function add(CurvePoint memory p1, CurvePoint memory p2) internal pure returns (CurvePoint memory sum) { - (sum.x, sum.y) = EllipticCurve.ecAdd({_x1: p1.x, _y1: p1.y, _x2: p2.x, _y2: p2.y, _aa: _AA, _pp: _PP}); + /// @dev Note that only the right-hand side point is checked to allow adding the zero delta from the left. This is + /// done due to the delta points being added sequentially starting from the zero delta in the + /// `ProtocolAdapter.execute()` function. + function add(Point memory lhs, Point memory rhs) internal pure returns (Point memory sum) { + if (!EllipticCurve.isOnCurve({_x: rhs.x, _y: rhs.y, _aa: _AA, _bb: _BB, _pp: _PP})) { + revert PointNotOnCurve(rhs); + } + + (sum.x, sum.y) = EllipticCurve.ecAdd({_x1: lhs.x, _y1: lhs.y, _x2: rhs.x, _y2: rhs.y, _aa: _AA, _pp: _PP}); } /// @notice Converts an elliptic curve point to an Ethereum account address. /// @param delta The elliptic curve point. /// @return account The associated account. - function toAccount(CurvePoint memory delta) internal pure returns (address account) { + function toAccount(Point memory delta) internal pure returns (address account) { // Hash the public key with Keccak-256. bytes32 hashedKey = EfficientHashLib.hash(delta.x, delta.y); @@ -66,7 +85,7 @@ library Delta { /// @param proof The delta proof. /// @param instance The transaction delta. /// @param verifyingKey The Keccak-256 hash of all nullifiers and commitments as ordered in the compliance units. - function verify(bytes memory proof, CurvePoint memory instance, bytes32 verifyingKey) internal pure { + function verify(bytes memory proof, Point memory instance, bytes32 verifyingKey) internal pure { // Verify the delta proof using the ECDSA.recover API to obtain the address address recovered = ECDSA.recover({hash: verifyingKey, signature: proof}); diff --git a/contracts/test/libs/DeltaGen.sol b/contracts/test/libs/DeltaGen.sol index da3fc414..fc1f02fc 100644 --- a/contracts/test/libs/DeltaGen.sol +++ b/contracts/test/libs/DeltaGen.sol @@ -49,7 +49,7 @@ library DeltaGen { /// @return instance The delta instance corresponding to the parameters function generateInstance(VmSafe vm, InstanceInputs memory deltaInputs) internal - returns (Delta.CurvePoint memory instance) + returns (Delta.Point memory instance) { deltaInputs.valueCommitmentRandomness = deltaInputs.valueCommitmentRandomness.modOrder(); if (deltaInputs.valueCommitmentRandomness == 0) { @@ -69,7 +69,7 @@ library DeltaGen { VmSafe.Wallet memory valueWallet = vm.createWallet(preDelta); // Extract the transaction delta from the wallet - instance = Delta.CurvePoint({x: valueWallet.publicKeyX, y: valueWallet.publicKeyY}); + instance = Delta.Point({x: valueWallet.publicKeyX, y: valueWallet.publicKeyY}); } /// @notice Generates a transaction delta proof by signing verifyingKey with diff --git a/contracts/test/libs/EllipticCurve.t.sol b/contracts/test/libs/EllipticCurve.t.sol new file mode 100644 index 00000000..e176eced --- /dev/null +++ b/contracts/test/libs/EllipticCurve.t.sol @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.30; + +import {EllipticCurve} from "@elliptic-curve-solidity/contracts/EllipticCurve.sol"; +import {Test} from "forge-std/Test.sol"; + +import {Delta} from "../../src/libs/proving/Delta.sol"; + +/** + * @title EllipticCurvePropertiesTest + * @author Informal Systems + * @dev Property-based tests for EllipticCurve.ecAdd using Foundry fuzzing. + * + * These tests verify GROUP PROPERTIES with points VERIFIED to be on the curve. + * Points are generated via scalar multiplication: P = k × G + * This guarantees all test points satisfy the curve equation y² = x³ + ax + b + * + * Run with: forge test -vv + * Run with more fuzz runs: forge test --fuzz-runs 10000 -vv + */ +contract EllipticCurvePropertiesTest is Test { + using EllipticCurve for *; + + /* GROUP PROPERTY TESTS (Points Verified On Curve) */ + + /// @notice GROUP PROPERTY: Commutativity - P + Q = Q + P + /// @dev Uses secp256k1 with points VERIFIED to be on the curve + function testFuzz_group_property_commutativity_secp256k1(uint256 scalar1, uint256 scalar2) public pure { + // Generate two points on secp256k1 by scalar multiplication of G + scalar1 = bound(scalar1, 1, 20); // Keep small for performance + scalar2 = bound(scalar2, 1, 20); + + // P1 = scalar1 * G + (uint256 x1, uint256 y1) = + EllipticCurve.ecMul({_k: scalar1, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP}); + + // P2 = scalar2 * G + (uint256 x2, uint256 y2) = + EllipticCurve.ecMul({_k: scalar2, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP}); + + // Verify both points are on curve + assertTrue( + EllipticCurve.isOnCurve({_x: x1, _y: y1, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}), + "P1 must be on curve" + ); + assertTrue( + EllipticCurve.isOnCurve({_x: x2, _y: y2, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}), + "P2 must be on curve" + ); + + // Test commutativity: P1 + P2 = P2 + P1 + (uint256 resultX1, uint256 resultY1) = + EllipticCurve.ecAdd({_x1: x1, _y1: y1, _x2: x2, _y2: y2, _aa: Delta._AA, _pp: Delta._PP}); + (uint256 resultX2, uint256 resultY2) = + EllipticCurve.ecAdd({_x1: x2, _y1: y2, _x2: x1, _y2: y1, _aa: Delta._AA, _pp: Delta._PP}); + + assertEq(resultX1, resultX2, "GROUP PROPERTY: P + Q must equal Q + P"); + assertEq(resultY1, resultY2, "GROUP PROPERTY: P + Q must equal Q + P"); + } + + /// @notice GROUP PROPERTY: Identity - P + O = P + function testFuzz_group_property_identity_secp256k1(uint256 scalar) public pure { + scalar = bound(scalar, 1, 20); + + // P = scalar * G (guaranteed on curve) + (uint256 x, uint256 y) = + EllipticCurve.ecMul({_k: scalar, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP}); + + // Verify P is on curve + assertTrue( + EllipticCurve.isOnCurve({_x: x, _y: y, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}), + "P must be on curve" + ); + + // P + O should equal P + (uint256 resultX, uint256 resultY) = + EllipticCurve.ecAdd({_x1: x, _y1: y, _x2: 0, _y2: 0, _aa: Delta._AA, _pp: Delta._PP}); + + assertEq(resultX, x, "GROUP PROPERTY: P + O must equal P"); + assertEq(resultY, y, "GROUP PROPERTY: P + O must equal P"); + } + + /// @notice GROUP PROPERTY: Inverse - P + (-P) = O + function testFuzz_group_property_inverse_secp256k1(uint256 scalar) public pure { + scalar = bound(scalar, 1, 20); + + // P = scalar * G (guaranteed on curve) + (uint256 x, uint256 y) = + EllipticCurve.ecMul({_k: scalar, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP}); + + // Verify P is on curve + assertTrue( + EllipticCurve.isOnCurve({_x: x, _y: y, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}), + "P must be on curve" + ); + + // Get -P + (uint256 invX, uint256 invY) = EllipticCurve.ecInv({_x: x, _y: y, _pp: Delta._PP}); + + // Verify -P is on curve + assertTrue( + EllipticCurve.isOnCurve({_x: invX, _y: invY, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}), + "-P must be on curve" + ); + + // P + (-P) should equal O + (uint256 resultX, uint256 resultY) = + EllipticCurve.ecAdd({_x1: x, _y1: y, _x2: invX, _y2: invY, _aa: Delta._AA, _pp: Delta._PP}); + + assertTrue( + _isPointAtInfinity(Delta.Point(resultX, resultY)), "GROUP PROPERTY: P + (-P) must equal point at infinity" + ); + } + + /// @notice GROUP PROPERTY: Associativity - (P + Q) + R = P + (Q + R) + function testFuzz_group_property_associativity_secp256k1(uint256 scalar1, uint256 scalar2, uint256 scalar3) + public + pure + { + scalar1 = bound(scalar1, 1, 10); // Keep small for performance + scalar2 = bound(scalar2, 1, 10); + scalar3 = bound(scalar3, 1, 10); + + // Generate three points on curve + (uint256 x1, uint256 y1) = + EllipticCurve.ecMul({_k: scalar1, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP}); + (uint256 x2, uint256 y2) = + EllipticCurve.ecMul({_k: scalar2, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP}); + (uint256 x3, uint256 y3) = + EllipticCurve.ecMul({_k: scalar3, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP}); + + // Verify all points are on curve + assertTrue(EllipticCurve.isOnCurve({_x: x1, _y: y1, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP})); + assertTrue(EllipticCurve.isOnCurve({_x: x2, _y: y2, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP})); + assertTrue(EllipticCurve.isOnCurve({_x: x3, _y: y3, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP})); + + // Compute (P + Q) + R + (uint256 pqX, uint256 pqY) = + EllipticCurve.ecAdd({_x1: x1, _y1: y1, _x2: x2, _y2: y2, _aa: Delta._AA, _pp: Delta._PP}); + (uint256 resultX1, uint256 resultY1) = + EllipticCurve.ecAdd({_x1: pqX, _y1: pqY, _x2: x3, _y2: y3, _aa: Delta._AA, _pp: Delta._PP}); + + // Compute P + (Q + R) + (uint256 qrX, uint256 qrY) = + EllipticCurve.ecAdd({_x1: x2, _y1: y2, _x2: x3, _y2: y3, _aa: Delta._AA, _pp: Delta._PP}); + + (uint256 resultX2, uint256 resultY2) = + EllipticCurve.ecAdd({_x1: x1, _y1: y1, _x2: qrX, _y2: qrY, _aa: Delta._AA, _pp: Delta._PP}); + + assertEq(resultX1, resultX2, "GROUP PROPERTY: Associativity must hold"); + assertEq(resultY1, resultY2, "GROUP PROPERTY: Associativity must hold"); + } + + /// @notice GROUP PROPERTY: Closure - If P, Q on curve, then P+Q on curve or at infinity + function testFuzz_group_property_Closure_secp256k1(uint256 scalar1, uint256 scalar2) public pure { + scalar1 = bound(scalar1, 1, 20); + scalar2 = bound(scalar2, 1, 20); + + // Generate two points on curve + (uint256 x1, uint256 y1) = + EllipticCurve.ecMul({_k: scalar1, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP}); + (uint256 x2, uint256 y2) = + EllipticCurve.ecMul({_k: scalar2, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP}); + + // Verify inputs are on curve + assertTrue(EllipticCurve.isOnCurve({_x: x1, _y: y1, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP})); + assertTrue(EllipticCurve.isOnCurve({_x: x2, _y: y2, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP})); + + // Add points + (uint256 resultX, uint256 resultY) = + EllipticCurve.ecAdd({_x1: x1, _y1: y1, _x2: x2, _y2: y2, _aa: Delta._AA, _pp: Delta._PP}); + + // Result must be on curve or at infinity + bool atInfinity = _isPointAtInfinity(Delta.Point(resultX, resultY)); + bool onCurve = + EllipticCurve.isOnCurve({_x: resultX, _y: resultY, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}); + + assertTrue(atInfinity || onCurve, "GROUP PROPERTY: Closure - result must be on curve or at infinity"); + } + + /* PRECONDITION TESTS (UNREDUCED COORDINATES) */ + + /// @notice PRECONDITION TEST: Demonstrates correct behavior with reduced coordinates + function test_precondition_reduced_coordinates() public pure { + // Use a small prime: p = 7, curve parameter a = 2 + uint256 pp = 7; + uint256 aa = 2; + + // Point P: (3, 5), Point Q: (3, 2) where Q is inverse of P since 2 + 5 = 7 ≡ 0 (mod 7) + (uint256 rx, uint256 ry) = EllipticCurve.ecAdd({_x1: 3, _y1: 5, _x2: 3, _y2: 2, _aa: aa, _pp: pp}); + + // P + (-P) should equal point at infinity + assertTrue( + _isPointAtInfinity(Delta.Point(rx, ry)), + "With REDUCED coordinates: (3,5) + (3,2) correctly gives point at infinity" + ); + } + + /* CONCRETE TESTS (SECP256K1) */ + + /// @notice Concrete: G + G = 2G + function test_doubling_secp256k1() public pure { + (uint256 rx, uint256 ry) = EllipticCurve.ecAdd({ + _x1: Delta._GX, _y1: Delta._GY, _x2: Delta._GX, _y2: Delta._GY, _aa: Delta._AA, _pp: Delta._PP + }); + + // Expected 2G + assertEq(rx, 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5); + assertEq(ry, 0x1ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a); + + // Verify result is on curve + assertTrue(EllipticCurve.isOnCurve({_x: rx, _y: ry, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP})); + } + + /// @notice Concrete: G + O = G + function test_group_property_identity_secp256k1() public pure { + (uint256 rx, uint256 ry) = + EllipticCurve.ecAdd({_x1: Delta._GX, _y1: Delta._GY, _x2: 0, _y2: 0, _aa: Delta._AA, _pp: Delta._PP}); + + assertEq(rx, Delta._GX); + assertEq(ry, Delta._GY); + } + + /// @notice Concrete: G + (-G) = O + function test_inverse_secp256k1() public pure { + (uint256 invX, uint256 invY) = EllipticCurve.ecInv({_x: Delta._GX, _y: Delta._GY, _pp: Delta._PP}); + + (uint256 rx, uint256 ry) = + EllipticCurve.ecAdd({_x1: Delta._GX, _y1: Delta._GY, _x2: invX, _y2: invY, _aa: Delta._AA, _pp: Delta._PP}); + + assertTrue(_isPointAtInfinity(Delta.Point(rx, ry)), "G + (-G) should be point at infinity"); + } + + /// @notice Concrete: (G + 2G) + 3G = G + (2G + 3G) = 6G + function test_associativity_secp256k1() public pure { + // Compute 2G, 3G + + (uint256 p2gX, uint256 p2gY) = + EllipticCurve.ecMul({_k: 2, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP}); + (uint256 p3gX, uint256 p3gY) = + EllipticCurve.ecMul({_k: 3, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP}); + + // Compute (G + 2G) + 3G + (uint256 pg2gX, uint256 pg2gY) = + EllipticCurve.ecAdd({_x1: Delta._GX, _y1: Delta._GY, _x2: p2gX, _y2: p2gY, _aa: Delta._AA, _pp: Delta._PP}); + (uint256 result1X, uint256 result1Y) = + EllipticCurve.ecAdd({_x1: pg2gX, _y1: pg2gY, _x2: p3gX, _y2: p3gY, _aa: Delta._AA, _pp: Delta._PP}); + + // Compute G + (2G + 3G) + (uint256 p2g3gX, uint256 p2g3gY) = + EllipticCurve.ecAdd({_x1: p2gX, _y1: p2gY, _x2: p3gX, _y2: p3gY, _aa: Delta._AA, _pp: Delta._PP}); + (uint256 result2X, uint256 result2Y) = EllipticCurve.ecAdd({ + _x1: Delta._GX, _y1: Delta._GY, _x2: p2g3gX, _y2: p2g3gY, _aa: Delta._AA, _pp: Delta._PP + }); + + // Both should equal 6G + (uint256 p6gx, uint256 p6gy) = + EllipticCurve.ecMul({_k: 6, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP}); + + assertEq(result1X, result2X, "Associativity: (G+2G)+3G = G+(2G+3G)"); + + assertEq(result1Y, result2Y, "Associativity: (G+2G)+3G = G+(2G+3G)"); + assertEq(result1X, p6gx, "Result should be 6G"); + assertEq(result1Y, p6gy, "Result should be 6G"); + } + + /// @notice Returns whether a point is at infinity or not.. + /// @param p The point to check. + /// @return isAtInfinity Whether the point is at infinity or not. + function _isPointAtInfinity(Delta.Point memory p) internal pure returns (bool isAtInfinity) { + isAtInfinity = p.x == 0 && p.y == 0; + } +} diff --git a/contracts/test/libs/TxGen.sol b/contracts/test/libs/TxGen.sol index 0569c160..86c78622 100644 --- a/contracts/test/libs/TxGen.sol +++ b/contracts/test/libs/TxGen.sol @@ -50,7 +50,7 @@ library TxGen { bytes32 cm = commitment(created); // Construct the delta for consumption based on kind and quantity - Delta.CurvePoint memory unitDelta = DeltaGen.generateInstance( + Delta.Point memory unitDelta = DeltaGen.generateInstance( vm, DeltaGen.InstanceInputs({ kind: kind(consumed), quantity: consumed.quantity, consumed: true, valueCommitmentRandomness: 1 diff --git a/contracts/test/proofs/DeltaProof.t.sol b/contracts/test/proofs/DeltaProof.t.sol index 316ce573..e07c899a 100644 --- a/contracts/test/proofs/DeltaProof.t.sol +++ b/contracts/test/proofs/DeltaProof.t.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.30; +import {EllipticCurve} from "@elliptic-curve-solidity/contracts/EllipticCurve.sol"; import {Test} from "forge-std/Test.sol"; import {Delta} from "../../src/libs/proving/Delta.sol"; @@ -20,14 +21,14 @@ library DeltaFuzzing { } /// @dev This function exposes `Delta.verify` for the fuzzer. - function verify(bytes memory proof, Delta.CurvePoint memory instance, bytes32 verifyingKey) public pure { + function verify(bytes memory proof, Delta.Point memory instance, bytes32 verifyingKey) public pure { Delta.verify({proof: proof, instance: instance, verifyingKey: verifyingKey}); } } contract DeltaProofTest is Test { using SignMagnitude for SignMagnitude.Number; - using Delta for Delta.CurvePoint; + using Delta for Delta.Point; using DeltaGen for DeltaGen.InstanceInputs[]; using DeltaGen for DeltaGen.InstanceInputs; using DeltaGen for uint256; @@ -52,7 +53,7 @@ contract DeltaProofTest is Test { DeltaGen.ProofInputs({valueCommitmentRandomness: valueCommitmentRandomness, verifyingKey: verifyingKey}); // Generate a delta instance from the above inputs - Delta.CurvePoint memory instance = DeltaGen.generateInstance(vm, deltaInstanceInputs); + Delta.Point memory instance = DeltaGen.generateInstance(vm, deltaInstanceInputs); // Generate a delta proof from the above inputs bytes memory proof = DeltaGen.generateProof(vm, deltaProofInputs); @@ -110,12 +111,12 @@ contract DeltaProofTest is Test { vm.assume(summedDeltaInputs.computePreDelta() != 0); // Generate a delta proof and instance from the above tags and preimage - Delta.CurvePoint memory instance1 = DeltaGen.generateInstance(vm, deltaInputs1); - Delta.CurvePoint memory instance2 = DeltaGen.generateInstance(vm, deltaInputs2); - Delta.CurvePoint memory expectedDelta = DeltaGen.generateInstance(vm, summedDeltaInputs); + Delta.Point memory instance1 = DeltaGen.generateInstance(vm, deltaInputs1); + Delta.Point memory instance2 = DeltaGen.generateInstance(vm, deltaInputs2); + Delta.Point memory expectedDelta = DeltaGen.generateInstance(vm, summedDeltaInputs); // Verify that the deltas add correctly - Delta.CurvePoint memory computedDelta = Delta.add(instance1, instance2); + Delta.Point memory computedDelta = Delta.add(instance1, instance2); assertEq(computedDelta.x, expectedDelta.x); assertEq(computedDelta.y, expectedDelta.y); @@ -140,7 +141,7 @@ contract DeltaProofTest is Test { }); // Generate a delta proof and instance from the above tags and preimage - Delta.CurvePoint memory instance = DeltaGen.generateInstance(vm, deltaInstanceInputs); + Delta.Point memory instance = DeltaGen.generateInstance(vm, deltaInstanceInputs); bytes memory proof = DeltaGen.generateProof(vm, deltaProofInputs); vm.expectPartialRevert(Delta.DeltaMismatch.selector); DeltaFuzzing.verify({proof: proof, instance: instance, verifyingKey: deltaProofInputs.verifyingKey}); @@ -165,7 +166,7 @@ contract DeltaProofTest is Test { DeltaGen.ProofInputs({valueCommitmentRandomness: valueCommitmentRandomness1, verifyingKey: verifyingKey}) ); - Delta.CurvePoint memory instanceRcv2; + Delta.Point memory instanceRcv2; { DeltaGen.InstanceInputs memory deltaInputs2 = DeltaGen.InstanceInputs({ kind: kind, quantity: 0, consumed: consumed, valueCommitmentRandomness: valueCommitmentRandomness2 @@ -195,7 +196,7 @@ contract DeltaProofTest is Test { DeltaGen.ProofInputs({valueCommitmentRandomness: valueCommitmentRandomness, verifyingKey: verifyingKey1}) ); - Delta.CurvePoint memory instance; + Delta.Point memory instance; { DeltaGen.InstanceInputs memory deltaInputs2 = DeltaGen.InstanceInputs({ kind: kind, quantity: 0, consumed: consumed, valueCommitmentRandomness: valueCommitmentRandomness @@ -218,7 +219,7 @@ contract DeltaProofTest is Test { kind = bound(kind, 1, DeltaGen.SECP256K1_ORDER - 1); DeltaGen.InstanceInputs[] memory deltaInputs = _getBoundedDeltaInstances(kind, fuzzerInputs); - Delta.CurvePoint memory deltaAcc = Delta.zero(); + Delta.Point memory deltaAcc = Delta.zero(); // Make sure that the delta quantities balance out ( @@ -248,7 +249,7 @@ contract DeltaProofTest is Test { vm.assume(wrappedDeltaInputs[i].valueCommitmentRandomness != 0); vm.assume(wrappedDeltaInputs[i].computePreDelta() != 0); - Delta.CurvePoint memory instance = DeltaGen.generateInstance(vm, wrappedDeltaInputs[i]); + Delta.Point memory instance = DeltaGen.generateInstance(vm, wrappedDeltaInputs[i]); deltaAcc = deltaAcc.add(instance); } @@ -270,7 +271,7 @@ contract DeltaProofTest is Test { kind = bound(kind, 1, DeltaGen.SECP256K1_ORDER - 1); DeltaGen.InstanceInputs[] memory deltaInputs = _getBoundedDeltaInstances(kind, fuzzerInputs); - Delta.CurvePoint memory deltaAcc = Delta.zero(); + Delta.Point memory deltaAcc = Delta.zero(); // Accumulate the total quantity and randomness commitment ( @@ -289,7 +290,7 @@ contract DeltaProofTest is Test { vm.assume(wrappedDeltaInputs[i].valueCommitmentRandomness != 0); vm.assume(wrappedDeltaInputs[i].computePreDelta() != 0); - Delta.CurvePoint memory instance = DeltaGen.generateInstance(vm, wrappedDeltaInputs[i]); + Delta.Point memory instance = DeltaGen.generateInstance(vm, wrappedDeltaInputs[i]); deltaAcc = deltaAcc.add(instance); } // Compute the proof for the balanced transaction @@ -302,12 +303,104 @@ contract DeltaProofTest is Test { DeltaFuzzing.verify({proof: proof, instance: deltaAcc, verifyingKey: verifyingKey}); } + function testFuzz_add_reverts_when_adding_a_non_curve_from_the_right(uint32 k, Delta.Point memory rhs) public { + // Ensure that `rhs` is not on the curve. + vm.assume(!EllipticCurve.isOnCurve({_x: rhs.x, _y: rhs.y, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP})); + + // Generate a random point on the curve. + Delta.Point memory lhs = _mul({p: Delta.Point({x: Delta._GX, y: Delta._GY}), k: k}); + assertTrue( + EllipticCurve.isOnCurve({_x: lhs.x, _y: lhs.y, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}), + "Left-hand side point must be on the curve." + ); + + // Attempt to add a point being not on the curve. + vm.expectRevert(abi.encodeWithSelector(Delta.PointNotOnCurve.selector, rhs), address(this)); + lhs.add(rhs); + } + + function testFuzz_add_reverts_when_adding_zero_from_the_right(uint32 k) public { + // Generate a random point on the curve. + Delta.Point memory lhs = _mul({p: Delta.Point({x: Delta._GX, y: Delta._GY}), k: k}); + assertTrue( + EllipticCurve.isOnCurve({_x: lhs.x, _y: lhs.y, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}), + "Left-hand side point must be on the curve." + ); + + Delta.Point memory zero = Delta.zero(); + + // Add the two points. + vm.expectRevert(abi.encodeWithSelector(Delta.PointNotOnCurve.selector, zero), address(this)); + lhs.add(zero); + } + + function testFuzz_add_adding_zero_from_the_left_produces_a_curve_point(uint32 k) public pure { + Delta.Point memory zero = Delta.zero(); + + // Generate a random point on the curve. + Delta.Point memory rhs = _mul({p: Delta.Point({x: Delta._GX, y: Delta._GY}), k: k}); + assertTrue( + EllipticCurve.isOnCurve({_x: rhs.x, _y: rhs.y, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}), + "Right-hand side point must be on the curve." + ); + + // Add the two points and check that the sum is a curve point. + Delta.Point memory sum = zero.add(rhs); + assertTrue( + EllipticCurve.isOnCurve({_x: sum.x, _y: sum.y, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}), + "Sum must be on the curve." + ); + } + + function testFuzz_add_adding_zero_from_the_left_is_the_identity_operation(uint32 k) public pure { + Delta.Point memory zero = Delta.zero(); + + // Generate a random point on the curve. + Delta.Point memory rhs = _mul({p: Delta.Point({x: Delta._GX, y: Delta._GY}), k: k}); + assertTrue( + EllipticCurve.isOnCurve({_x: rhs.x, _y: rhs.y, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}), + "Right-hand side point must be on the curve." + ); + + // Add the two points and check that the sum is a curve point. + Delta.Point memory sum = zero.add(rhs); + assertEq(sum.x, rhs.x); + assertEq(sum.y, rhs.y); + } + + function testFuzz_add_adding_two_curve_points_produces_a_curve_point(uint32 k1, uint32 k2) public pure { + // Generate two random points on the curve. + Delta.Point memory lhs = _mul({p: Delta.Point({x: Delta._GX, y: Delta._GY}), k: k1}); + assertTrue( + EllipticCurve.isOnCurve({_x: lhs.x, _y: lhs.y, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}), + "Left-hand side point must be on the curve." + ); + + Delta.Point memory rhs = _mul({p: Delta.Point({x: Delta._GX, y: Delta._GY}), k: k2}); + assertTrue( + EllipticCurve.isOnCurve({_x: rhs.x, _y: rhs.y, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}), + "Right-hand side must be on the curve." + ); + + // Add the two points and check that the sum is a curve point. + Delta.Point memory sum = lhs.add(rhs); + assertTrue( + EllipticCurve.isOnCurve({_x: sum.x, _y: sum.y, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}), + "Sum must be on the curve." + ); + } + + function test_zero_is_not_on_the_curve() public pure { + Delta.Point memory p2 = Delta.zero(); + assertFalse(EllipticCurve.isOnCurve({_x: p2.x, _y: p2.y, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP})); + } + function test_verify_example_delta_proof() public pure { Transaction memory txn = TransactionExample.transaction(); DeltaFuzzing.verify({ proof: txn.deltaProof, - instance: Delta.CurvePoint({ + instance: Delta.Point({ x: uint256(txn.actions[0].complianceVerifierInputs[0].instance.unitDeltaX), y: uint256(txn.actions[0].complianceVerifierInputs[0].instance.unitDeltaY) }), @@ -338,4 +431,8 @@ contract DeltaProofTest is Test { }); } } + + function _mul(Delta.Point memory p, uint256 k) internal pure returns (Delta.Point memory product) { + (product.x, product.y) = EllipticCurve.ecMul({_k: k, _x: p.x, _y: p.y, _aa: Delta._AA, _pp: Delta._PP}); + } }