Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
146a462
updated constraints
lorenzogentile404 Oct 28, 2025
e8d0de9
updated precompile scenario fragment
lorenzogentile404 Oct 28, 2025
7413352
added p256_verify precompile to oob
lorenzogentile404 Oct 28, 2025
95f02d2
partially updated ec data
lorenzogentile404 Oct 28, 2025
9b737e2
implemented r1 membership check
lorenzogentile404 Oct 29, 2025
3755be8
updated precompile subsection
lorenzogentile404 Oct 29, 2025
4e8ead3
added test for p256_verify precompile
lorenzogentile404 Oct 31, 2025
8be3ac5
Merge branch 'arith-dev' into 2363-eip-7951-precompile-for-secp256r1-…
lorenzogentile404 Nov 3, 2025
7a30cf4
updated constraints
lorenzogentile404 Nov 3, 2025
b62f5c9
fixed LT ISZERO issue
lorenzogentile404 Nov 3, 2025
c5eec4d
removed computed columns from explicit tracing
lorenzogentile404 Nov 4, 2025
4ff91bd
updated constraints
lorenzogentile404 Nov 5, 2025
835d15a
filled pScenarioPrcP256Verify in the Hub trace
lorenzogentile404 Nov 5, 2025
079446b
Merge branch 'arith-dev' into 2363-eip-7951-precompile-for-secp256r1-…
lorenzogentile404 Nov 6, 2025
fcd2ccd
fixed bugs related to connecting the hub with ecdata wrt P256_VERIFY …
lorenzogentile404 Nov 7, 2025
616fc21
Merge branch 'arith-dev' into 2363-eip-7951-precompile-for-secp256r1-…
lorenzogentile404 Nov 7, 2025
4d142c0
updated constraints
lorenzogentile404 Nov 7, 2025
1c3d80e
updated constraints
lorenzogentile404 Nov 7, 2025
1fdeb76
fixed imports
lorenzogentile404 Nov 7, 2025
3aca610
fixed c1 memership and r1 membership
lorenzogentile404 Nov 7, 2025
702b8e1
cleaned code
lorenzogentile404 Nov 7, 2025
feb91eb
fixed bug related to success bit
lorenzogentile404 Nov 8, 2025
c489dac
fixed bug related to limb of p256_verify
lorenzogentile404 Nov 9, 2025
baed8b2
added several test cases and updated constraints
lorenzogentile404 Nov 10, 2025
6372d9d
Merge branch 'arith-dev' into 2363-eip-7951-precompile-for-secp256r1-…
lorenzogentile404 Nov 14, 2025
c9aa4d2
Merge branch 'arith-dev' into 2363-eip-7951-precompile-for-secp256r1-…
lorenzogentile404 Nov 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ public class ZkCounter implements LineCountingTracer {
new CountingOnlyModule(PRECOMPILE_ECPAIRING_MILLER_LOOPS);
private final IncrementingModule ecPairingFinalExponentiations =
new IncrementingModule(PRECOMPILE_ECPAIRING_FINAL_EXPONENTIATIONS);
private final IncrementingModule p256VerifyEffectiveCalls =
new IncrementingModule(PRECOMPILE_P256_VERIFY_EFFECTIVE_CALLS);

// related to Modexp
private final IncrementAndDetectModule modexpEffectiveCall =
Expand Down Expand Up @@ -338,7 +340,8 @@ public ZkCounter(LineaL1L2BridgeSharedConfiguration bridgeConfiguration) {
ecRecoverEffectiveCall,
ecPairingG2MembershipCalls,
ecPairingMillerLoops,
ecPairingFinalExponentiations);
ecPairingFinalExponentiations,
p256VerifyEffectiveCalls);
blsdata =
new BlsData(
wcp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public enum ModuleName {
PRECOMPILE_ECPAIRING_MILLER_LOOPS,
PRECOMPILE_ECPAIRING_FINAL_EXPONENTIATIONS,
PRECOMPILE_ECPAIRING_G2_MEMBERSHIP_CALLS,
PRECOMPILE_P256_VERIFY_EFFECTIVE_CALLS,

// blakemodexp:
PRECOMPILE_BLAKE_EFFECTIVE_CALLS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ public class EcData implements OperationListModule<EcDataOperation> {
private final CountingOnlyModule ecPairingMillerLoops;
private final IncrementingModule ecPairingFinalExponentiations;

private final IncrementingModule p256VerifyEffectiveCalls;

@Getter private EcDataOperation ecDataOperation;

@Override
Expand Down Expand Up @@ -144,6 +146,9 @@ public void callEcData(
ecDataOperation.circuitSelectorEcPairingCounter()
> 0); // See https://eprint.iacr.org/2008/490.pdf
}
case PRC_P256_VERIFY -> {
p256VerifyEffectiveCalls.updateTally(ecDataOperation.internalChecksPassed());
}
default -> throw new IllegalArgumentException("Operation not supported by EcData");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,24 @@
import static net.consensys.linea.zktracer.Trace.PHASE_ECPAIRING_RESULT;
import static net.consensys.linea.zktracer.Trace.PHASE_ECRECOVER_DATA;
import static net.consensys.linea.zktracer.Trace.PHASE_ECRECOVER_RESULT;
import static net.consensys.linea.zktracer.Trace.PHASE_P256_VERIFY_DATA;
import static net.consensys.linea.zktracer.Trace.PHASE_P256_VERIFY_RESULT;
import static net.consensys.linea.zktracer.TraceOsaka.Ecdata.A_COEFF_R1_HI;
import static net.consensys.linea.zktracer.TraceOsaka.Ecdata.A_COEFF_R1_LO;
import static net.consensys.linea.zktracer.TraceOsaka.Ecdata.B_COEFF_R1_HI;
import static net.consensys.linea.zktracer.TraceOsaka.Ecdata.B_COEFF_R1_LO;
import static net.consensys.linea.zktracer.TraceOsaka.Ecdata.INDEX_MAX_P256_VERIFY_DATA;
import static net.consensys.linea.zktracer.TraceOsaka.Ecdata.INDEX_MAX_P256_VERIFY_RESULT;
import static net.consensys.linea.zktracer.TraceOsaka.Ecdata.P_R1_HI;
import static net.consensys.linea.zktracer.TraceOsaka.Ecdata.P_R1_LO;
import static net.consensys.linea.zktracer.TraceOsaka.Ecdata.SECP256R1N_HI;
import static net.consensys.linea.zktracer.TraceOsaka.Ecdata.SECP256R1N_LO;
import static net.consensys.linea.zktracer.TraceOsaka.Ecdata.TOTAL_SIZE_P256_VERIFY_DATA;
import static net.consensys.linea.zktracer.TraceOsaka.Ecdata.TOTAL_SIZE_P256_VERIFY_RESULT;
import static net.consensys.linea.zktracer.module.Util.rightPaddedSlice;
import static net.consensys.linea.zktracer.module.hub.fragment.scenario.PrecompileScenarioFragment.PrecompileFlag.*;
import static net.consensys.linea.zktracer.types.Containers.repeat;
import static net.consensys.linea.zktracer.types.EWord.ZERO;
import static net.consensys.linea.zktracer.types.Utils.leftPadTo;

import java.util.List;
Expand All @@ -74,6 +89,12 @@ public class EcDataOperation extends ModuleOperation {
public static final EWord SECP256K1N = EWord.of(SECP256K1N_HI, SECP256K1N_LO);
public static final int nBYTES_OF_DELTA_BYTES = 4;

public static final EWord P_R1 = EWord.of(P_R1_HI, P_R1_LO);
public static final EWord SECP256R1N = EWord.of(SECP256R1N_HI, SECP256R1N_LO);

public static final EWord A_COEFF_R1 = EWord.of(A_COEFF_R1_HI, A_COEFF_R1_LO);
public static final EWord B_COEFF_R1 = EWord.of(B_COEFF_R1_HI, B_COEFF_R1_LO);

private final Bytes returnData;

private final Wcp wcp;
Expand Down Expand Up @@ -113,9 +134,6 @@ public class EcDataOperation extends ModuleOperation {
private final List<OpCode> extInst;

@Getter private boolean successBit;
private boolean circuitSelectorEcrecover;
private boolean circuitSelectorEcadd;
private boolean circuitSelectorEcmul;

// pairing-specific
@Getter private final int totalPairings;
Expand Down Expand Up @@ -157,6 +175,7 @@ private EcDataOperation(
callDataSize % TOTAL_SIZE_ECPAIRING_DATA_MIN);
yield callDataSize;
}
case PRC_P256_VERIFY -> TOTAL_SIZE_P256_VERIFY_DATA;
default -> throw new IllegalArgumentException(
"EcDataOperation expects to be called on an elliptic curve precompile, not on "
+ precompileFlag.name());
Expand Down Expand Up @@ -223,6 +242,7 @@ public static EcDataOperation of(
case PRC_ECADD -> ecDataOperation.handleAdd();
case PRC_ECMUL -> ecDataOperation.handleMul();
case PRC_ECPAIRING -> ecDataOperation.handlePairing();
case PRC_P256_VERIFY -> ecDataOperation.handleP256Verify();
}
return ecDataOperation;
}
Expand All @@ -235,6 +255,7 @@ private int getTotalSize(
case PRC_ECADD -> TOTAL_SIZE_ECADD_DATA;
case PRC_ECMUL -> TOTAL_SIZE_ECMUL_DATA;
case PRC_ECPAIRING -> TOTAL_SIZE_ECPAIRING_DATA_MIN * totalPairings;
case PRC_P256_VERIFY -> TOTAL_SIZE_P256_VERIFY_DATA;
default -> throw new IllegalArgumentException("invalid EC type");
};
} else {
Expand All @@ -243,6 +264,7 @@ private int getTotalSize(
case PRC_ECADD -> successBit ? TOTAL_SIZE_ECADD_RESULT : 0;
case PRC_ECMUL -> successBit ? TOTAL_SIZE_ECMUL_RESULT : 0;
case PRC_ECPAIRING -> successBit ? TOTAL_SIZE_ECPAIRING_RESULT : 0;
case PRC_P256_VERIFY -> successBit ? TOTAL_SIZE_P256_VERIFY_RESULT : 0;
default -> throw new IllegalArgumentException("invalid EC type");
};
}
Expand All @@ -256,6 +278,7 @@ private static short getPhase(
case PRC_ECADD -> PHASE_ECADD_DATA;
case PRC_ECMUL -> PHASE_ECMUL_DATA;
case PRC_ECPAIRING -> PHASE_ECPAIRING_DATA;
case PRC_P256_VERIFY -> PHASE_P256_VERIFY_DATA;
default -> throw new IllegalArgumentException("invalid EC type");
};
} else {
Expand All @@ -264,6 +287,7 @@ private static short getPhase(
case PRC_ECADD -> PHASE_ECADD_RESULT;
case PRC_ECMUL -> PHASE_ECMUL_RESULT;
case PRC_ECPAIRING -> PHASE_ECPAIRING_RESULT;
case PRC_P256_VERIFY -> PHASE_P256_VERIFY_RESULT;
default -> throw new IllegalArgumentException("invalid EC type");
};
}
Expand All @@ -277,6 +301,7 @@ private int getIndexMax(
case PRC_ECADD -> INDEX_MAX_ECADD_DATA;
case PRC_ECMUL -> INDEX_MAX_ECMUL_DATA;
case PRC_ECPAIRING -> (INDEX_MAX_ECPAIRING_DATA_MIN + 1) * totalPairings - 1;
case PRC_P256_VERIFY -> INDEX_MAX_P256_VERIFY_DATA;
default -> throw new IllegalArgumentException("invalid EC type");
};
} else {
Expand All @@ -285,16 +310,22 @@ private int getIndexMax(
case PRC_ECADD -> INDEX_MAX_ECADD_RESULT;
case PRC_ECMUL -> INDEX_MAX_ECMUL_RESULT;
case PRC_ECPAIRING -> INDEX_MAX_ECPAIRING_RESULT;
case PRC_P256_VERIFY -> INDEX_MAX_P256_VERIFY_RESULT;
default -> throw new IllegalArgumentException("invalid EC type");
};
}
}

private boolean callWCPISZERO(int i, EWord arg) {
return callWcp(i, OpCode.ISZERO, arg, ZERO);
}

private boolean callWcp(int i, OpCode wcpInst, EWord arg1, EWord arg2) {
final boolean wcpRes =
switch (wcpInst) {
case LT -> wcp.callLT(arg1, arg2);
case EQ -> wcp.callEQ(arg1, arg2);
case ISZERO -> wcp.callISZERO(arg1);
default -> throw new IllegalStateException("Unexpected value: " + wcpInst);
};

Expand Down Expand Up @@ -346,13 +377,13 @@ void handleRecover() {
boolean rIsInRange = callWcp(0, OpCode.LT, r, SECP256K1N); // r < secp256k1N

// row i + 1
boolean rIsPositive = callWcp(1, OpCode.LT, EWord.ZERO, r); // 0 < r
boolean rIsPositive = !callWCPISZERO(1, r); // 0 < r

// row i + 2
boolean sIsInRange = callWcp(2, OpCode.LT, s, SECP256K1N); // s < secp256k1N

// row i + 3
boolean sIsPositive = callWcp(3, OpCode.LT, EWord.ZERO, s); // 0 < s
boolean sIsPositive = !callWCPISZERO(3, s); // 0 < s

// row i+ 4
boolean vIs27 = callWcp(4, OpCode.EQ, v, EWord.of(27)); // v == 27
Expand All @@ -371,13 +402,8 @@ void handleRecover() {

// Success bit is set in setReturnData

// Set circuitSelectorEcrecover
if (internalChecksPassed) {
circuitSelectorEcrecover = true;
}

// Set result rows
EWord recoveredAddress = EWord.ZERO;
EWord recoveredAddress = ZERO;

// Extract output
if (internalChecksPassed) {
Expand Down Expand Up @@ -422,12 +448,9 @@ void handleAdd() {

// Success bit is set in setReturnData

// set circuitSelectorEcadd
circuitSelectorEcadd = internalChecksPassed;

// Set result rows
EWord resX = EWord.ZERO;
EWord resY = EWord.ZERO;
EWord resX = ZERO;
EWord resY = ZERO;

// Extract output
if (internalChecksPassed && returnData.toArray().length != 0) {
Expand Down Expand Up @@ -473,12 +496,9 @@ void handleMul() {

// Success bit is set in setReturnData

// Set circuitSelectorEcmul
circuitSelectorEcmul = internalChecksPassed;

// Set result rows
EWord resX = EWord.ZERO;
EWord resY = EWord.ZERO;
EWord resX = ZERO;
EWord resY = ZERO;

// Extract output
if (internalChecksPassed && returnData.toArray().length != 0) {
Expand Down Expand Up @@ -631,7 +651,7 @@ void handlePairing() {
}

// Set result rows
EWord pairingResult = EWord.ZERO;
EWord pairingResult = ZERO;

// Extract output
if (internalChecksPassed) {
Expand All @@ -649,8 +669,61 @@ void handlePairing() {
successBit = !notOnG2AccMax;
}

// acceptablePairOfPointsForPairingCircuit, g2MembershipTestRequired, circuitSelectorEcpairing,
// circuitSelectorG2Membership are set in the trace method
// acceptablePairOfPointsForPairingCircuit, g2MembershipTestRequired are set in the trace method
}

private void handleP256Verify() {
// Extract inputs
final EWord h = EWord.of(rightPaddedCallData.slice(0, 32));
final EWord r = EWord.of(rightPaddedCallData.slice(32, 32));
final EWord s = EWord.of(rightPaddedCallData.slice(64, 32));
final EWord qX = EWord.of(rightPaddedCallData.slice(96, 32));
final EWord qY = EWord.of(rightPaddedCallData.slice(128, 32));

// Set input limb
limb.set(0, h.hi());
limb.set(1, h.lo());
limb.set(2, r.hi());
limb.set(3, r.lo());
limb.set(4, s.hi());
limb.set(5, s.lo());
limb.set(6, qX.hi());
limb.set(7, qX.lo());
limb.set(8, qY.hi());
limb.set(9, qY.lo());

// Compute internal checks
// row i
boolean rIsInRange = callWcp(0, OpCode.LT, r, SECP256R1N); // r < secp256r1N

// row i + 1
boolean rIsPositive = !callWCPISZERO(1, r); // 0 < r

// row i + 2
boolean sIsInRange = callWcp(2, OpCode.LT, s, SECP256R1N); // s < secp256r1N

// row i + 3
boolean sIsPositive = !callWCPISZERO(3, s); // 0 < s

// row i+ 4
boolean r1Membership = callToR1Membership(4, qX, qY).getLeft();

// Set hurdle
hurdle.set(0, rIsInRange && rIsPositive);
hurdle.set(1, sIsInRange && sIsPositive);
hurdle.set(2, hurdle.get(0) && hurdle.get(1));
hurdle.set(INDEX_MAX_P256_VERIFY_DATA, r1Membership && hurdle.get(2));

// Set internal checks passed
internalChecksPassed = hurdle.get(INDEX_MAX_P256_VERIFY_DATA);

// Set result rows
EWord recoveredAddress = ZERO;

// Set success bit and output limb
successBit = returnData.bitLength() / 8 == TOTAL_SIZE_P256_VERIFY_RESULT;
limb.set(8, Bytes.EMPTY);
limb.set(9, successBit ? returnData.slice(16, 16) : Bytes.EMPTY);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Flawed P256 Verify Handling: Misread Result, Unused Variable

In handleP256Verify, the successBit is set based on return data size (returnData.bitLength() / 8 == TOTAL_SIZE_P256_VERIFY_RESULT) rather than the actual verification result. The return data extraction and validation logic appears incomplete, and the recoveredAddress variable is declared but never used meaningfully.

Fix in Cursor Fix in Web

}

void trace(Trace.Ecdata trace, final int stamp, final long previousId) {
Expand Down Expand Up @@ -694,13 +767,6 @@ void trace(Trace.Ecdata trace, final int stamp, final long previousId) {
&& !largePointIsAtInfinity
&& !smallPointIsAtInfinity;

boolean circuitSelectorEcPairing = false;
if (isData && precompileFlag == PRC_ECPAIRING) {
circuitSelectorEcPairing = acceptablePairOfPointsForPairingCircuit;
} else if (!isData && precompileFlag == PRC_ECPAIRING) {
circuitSelectorEcPairing = successBit && !isOverallTrivialPairing();
}

if (precompileFlag != PRC_ECPAIRING || !isData) {
checkArgument(
ct == 0,
Expand Down Expand Up @@ -729,7 +795,11 @@ void trace(Trace.Ecdata trace, final int stamp, final long previousId) {
.isEcmulData(precompileFlag == PRC_ECMUL && isData)
.isEcmulResult(precompileFlag == PRC_ECMUL && !isData)
.isEcpairingData(precompileFlag == PRC_ECPAIRING && isData)
.isEcpairingResult(precompileFlag == PRC_ECPAIRING && !isData)
.isEcpairingResult(precompileFlag == PRC_ECPAIRING && !isData);
if (precompileFlag == PRC_P256_VERIFY) {
trace.isP256VerifyData(isData).isP256VerifyResult(!isData);
}
trace
.totalPairings(totalPairings)
.accPairings(
precompileFlag == PRC_ECPAIRING && isData
Expand Down Expand Up @@ -757,11 +827,6 @@ void trace(Trace.Ecdata trace, final int stamp, final long previousId) {
i)) // && conditions necessary because default value is true
.g2MembershipTestRequired(g2MembershipTestRequired)
.acceptablePairOfPointsForPairingCircuit(acceptablePairOfPointsForPairingCircuit)
.circuitSelectorEcrecover(circuitSelectorEcrecover)
.circuitSelectorEcadd(circuitSelectorEcadd)
.circuitSelectorEcmul(circuitSelectorEcmul)
.circuitSelectorEcpairing(circuitSelectorEcPairing)
.circuitSelectorG2Membership(g2MembershipTestRequired) // = circuitSelectorG2Membership
.wcpFlag(wcpFlag.get(i))
.wcpArg1Hi(wcpArg1Hi.get(i))
.wcpArg1Lo(wcpArg1Lo.get(i))
Expand Down Expand Up @@ -805,29 +870,36 @@ protected int computeLineCount() {

private Pair<Boolean, Boolean> callToC1Membership(int k, EWord pX, EWord pY) {
// EXT
EWord pYSquare = callExt(k, OpCode.MULMOD, pY, pY, P_BN);
EWord pXSquare = callExt(k + 1, OpCode.MULMOD, pX, pX, P_BN);
EWord pXCube = callExt(k + 2, OpCode.MULMOD, pXSquare, pX, P_BN);
EWord pXCubePlus3 = callExt(k + 3, OpCode.ADDMOD, pXCube, EWord.of(3), P_BN);
EWord pYSquare = callExt(k, OpCode.MULMOD, pY, pY, P_R1);
EWord pXSquare = callExt(k + 1, OpCode.MULMOD, pX, pX, P_R1);
EWord pXCube = callExt(k + 2, OpCode.MULMOD, pXSquare, pX, P_R1);
EWord aTimesPx = callExt(k + 3, OpCode.MULMOD, A_COEFF_R1, pX, P_R1);
EWord pXCubePlusAPx = callExt(k + 4, OpCode.ADDMOD, pXCube, aTimesPx, P_R1);
EWord pXCubePlusAPxPlusB = callExt(k + 5, OpCode.ADDMOD, pXCubePlusAPx, B_COEFF_R1, P_R1);

// WCP
boolean pXIsInRange = callWcp(k, OpCode.LT, pX, P_BN);
boolean pYIsInRange = callWcp(k + 1, OpCode.LT, pY, P_BN);
boolean pSatisfiesCubic = callWcp(k + 2, OpCode.EQ, pYSquare, pXCubePlus3);
boolean pXIsInRange = callWcp(k, OpCode.LT, pX, P_R1);
boolean pYIsInRange = callWcp(k + 1, OpCode.LT, pY, P_R1);
boolean pSatisfiesCubic = callWcp(k + 2, OpCode.EQ, pYSquare, pXCubePlusAPxPlusB);

// Set hurdle
boolean pIsRange = pXIsInRange && pYIsInRange;
boolean pIsPointAtInfinity = pIsRange && pX.isZero() && pY.isZero();
boolean c1Membership = pIsRange && (pIsPointAtInfinity || pSatisfiesCubic);
boolean r1Membership = pIsRange && !pIsPointAtInfinity && pSatisfiesCubic;
hurdle.set(k + 1, pIsRange);
hurdle.set(k, c1Membership);
hurdle.set(k, r1Membership);

// Set isInfinity
for (int i = 0; i <= CT_MAX_SMALL_POINT; i++) {
isInfinity.set(i + k, pIsPointAtInfinity);
}

return Pair.of(c1Membership, pIsPointAtInfinity);
return Pair.of(r1Membership, pIsPointAtInfinity);
}

private Pair<Boolean, Boolean> callToR1Membership(int k, EWord pX, EWord pY) {
// TODO
return Pair.of(true, true);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Stub Method Bypasses R1 Curve Validation

The callToR1Membership method is a stub that always returns true, bypassing essential R1 curve membership validation for the P256_VERIFY precompile. This allows invalid points to pass validation, creating a critical security vulnerability.

Fix in Cursor Fix in Web

}

private Pair<Boolean, Boolean> callToWellFormedCoordinates(
Expand Down
Loading
Loading