diff --git a/constantine.nimble b/constantine.nimble index f664d7f..7a257a8 100644 --- a/constantine.nimble +++ b/constantine.nimble @@ -58,6 +58,7 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[ ("tests/t_fp12_bls12_377.nim", false), ("tests/t_fp12_bls12_381.nim", false), ("tests/t_fp12_exponentiation.nim", false), + ("tests/t_fp12_anti_regression.nim", false), # ("tests/t_fp4_frobenius.nim", false), # ("tests/t_fp6_frobenius.nim", false), @@ -285,6 +286,17 @@ task test, "Run all tests": if not defined(windows) or not (existsEnv"UCPU" or getEnv"UCPU" == "i686"): buildAllBenches() +task test_no_assembler, "Run all tests": + # -d:testingCurves is configured in a *.nim.cfg for convenience + runTests(requireGMP = true, testASM = false) + + # if sizeof(int) == 8: # 32-bit tests on 64-bit arch + # runTests(requireGMP = true, test32bit = true) + + # Ensure benchmarks stay relevant. Ignore Windows 32-bit at the moment + if not defined(windows) or not (existsEnv"UCPU" or getEnv"UCPU" == "i686"): + buildAllBenches() + task test_no_gmp, "Run tests that don't require GMP": # -d:testingCurves is configured in a *.nim.cfg for convenience runTests(requireGMP = false) diff --git a/constantine/arithmetic/assembly/limbs_asm_modular_x86.nim b/constantine/arithmetic/assembly/limbs_asm_modular_x86.nim index 149b9db..e4b3197 100644 --- a/constantine/arithmetic/assembly/limbs_asm_modular_x86.nim +++ b/constantine/arithmetic/assembly/limbs_asm_modular_x86.nim @@ -128,7 +128,7 @@ macro submod_gen[N: static int](a: var Limbs[N], b, M: Limbs[N]): untyped = # Interleaved copy the modulus to hide SBB latencies ctx.mov arrTadd[i], arrM[i] - # Mask: undeflowed contains 0xFFFF or 0x0000 + # Mask: underflowed contains 0xFFFF or 0x0000 let underflowed = arrB.reuseRegister() ctx.sbb underflowed, underflowed @@ -166,21 +166,37 @@ macro negmod_gen[N: static int](r: var Limbs[N], a, M: Limbs[N]): untyped = var ctx = init(Assembler_x86, BaseType) let arrA = init(OperandArray, nimSymbol = a, N, PointerInReg, Input) - arrR = init(OperandArray, nimSymbol = r, N, ElemsInReg, InputOutput) + arrR = init(OperandArray, nimSymbol = r, N, PointerInReg, InputOutput) + arrT = init(OperandArray, nimSymbol = ident"t", N, ElemsInReg, Output_EarlyClobber) # We could force M as immediate by specializing per moduli - arrM = init(OperandArray, nimSymbol = M, N, PointerInReg, Input) + # We reuse the reg used for M for overflow detection + arrM = init(OperandArray, nimSymbol = M, N, PointerInReg, InputOutput) - # Addition + # Substraction M - a for i in 0 ..< N: - ctx.mov arrR[i], arrM[i] + ctx.mov arrT[i], arrM[i] if i == 0: - ctx.sub arrR[0], arrA[0] + ctx.sub arrT[0], arrA[0] else: - ctx.sbb arrR[i], arrA[i] + ctx.sbb arrT[i], arrA[i] + # Deal with a == 0 + let isZero = arrM.reuseRegister() + ctx.mov isZero, arrA[0] + for i in 1 ..< N: + ctx.`or` isZero, arrA[i] + + # Zero result if a == 0 + for i in 0 ..< N: + ctx.cmovz arrT[i], isZero + ctx.mov arrR[i], arrT[i] + + let t = arrT.nimSymbol + result.add quote do: + var `t`{.noinit.}: typeof(`a`) result.add ctx.generate -func negmod_asm*(r: var Limbs, a, M: Limbs) {.inline.} = +func negmod_asm*(r: var Limbs, a, M: Limbs) = ## Constant-time modular negation negmod_gen(r, a, M) diff --git a/constantine/arithmetic/assembly/limbs_asm_montred_x86_adx_bmi2.nim b/constantine/arithmetic/assembly/limbs_asm_montred_x86_adx_bmi2.nim index f8b467a..9108d4d 100644 --- a/constantine/arithmetic/assembly/limbs_asm_montred_x86_adx_bmi2.nim +++ b/constantine/arithmetic/assembly/limbs_asm_montred_x86_adx_bmi2.nim @@ -29,6 +29,9 @@ static: doAssert UseASM_X86_64 # Necessary for the compiler to find enough registers (enabled at -O1) {.localPassC:"-fomit-frame-pointer".} +# No exceptions allowed +{.push raises: [].} + # Montgomery reduction # ------------------------------------------------------------ diff --git a/constantine/arithmetic/bigints.nim b/constantine/arithmetic/bigints.nim index 53ec303..b6f565c 100644 --- a/constantine/arithmetic/bigints.nim +++ b/constantine/arithmetic/bigints.nim @@ -74,6 +74,10 @@ func setOne*(a: var BigInt) = ## Set a BigInt to 1 a.limbs.setOne() +func czero*(a: var BigInt, ctl: SecretBool) = + ## Set ``a`` to 0 if ``ctl`` is true + a.limbs.czero(ctl) + # Copy # ------------------------------------------------------------ diff --git a/constantine/arithmetic/finite_fields.nim b/constantine/arithmetic/finite_fields.nim index 2933064..fdd5961 100644 --- a/constantine/arithmetic/finite_fields.nim +++ b/constantine/arithmetic/finite_fields.nim @@ -229,7 +229,15 @@ func neg*(r: var FF, a: FF) {.inline.} = # especially on FF2 negmod_asm(r.mres.limbs, a.mres.limbs, FF.fieldMod().limbs) else: - discard r.mres.diff(FF.fieldMod(), a.mres) + # If a = 0 we need r = 0 and not r = M + # as comparison operator assume unicity + # of the modular representation. + # Also make sure to handle aliasing where r.addr = a.addr + var t {.noInit.}: FF + let isZero = a.isZero() + discard t.mres.diff(FF.fieldMod(), a.mres) + t.mres.czero(isZero) + r = t func neg*(a: var FF) {.inline.} = ## Negate modulo p diff --git a/constantine/arithmetic/limbs.nim b/constantine/arithmetic/limbs.nim index 633f2cd..a7e1027 100644 --- a/constantine/arithmetic/limbs.nim +++ b/constantine/arithmetic/limbs.nim @@ -71,6 +71,13 @@ func setOne*(a: var Limbs) = when a.len > 1: zeroMem(a[1].addr, (a.len - 1) * sizeof(SecretWord)) +func czero*(a: var Limbs, ctl: SecretBool) = + ## Set ``a`` to 0 if ``ctl`` is true + # Only used for FF neg in pure Nim fallback + # so no need for assembly + for i in 0 ..< a.len: + ctl.ccopy(a[i], SecretWord 0) + # Copy # ------------------------------------------------------------ diff --git a/constantine/io/io_towers.nim b/constantine/io/io_towers.nim index fcf2034..dcc375c 100644 --- a/constantine/io/io_towers.nim +++ b/constantine/io/io_towers.nim @@ -92,7 +92,7 @@ func fromHex*(dst: var Fp12, c4, c5, c6, c7: string, c8, c9, c10, c11: string) {.raises: [ValueError].}= ## Convert 12 coordinates to an element of 𝔽p12 - when Fp12.c0 is Fp6: + when dst.c0 is Fp6: dst.c0.fromHex(c0, c1, c2, c3, c4, c5) dst.c1.fromHex(c6, c7, c8, c9, c10, c11) else: diff --git a/constantine/primitives/macro_assembler_x86.nim b/constantine/primitives/macro_assembler_x86.nim index 70a79cc..ae17ba9 100644 --- a/constantine/primitives/macro_assembler_x86.nim +++ b/constantine/primitives/macro_assembler_x86.nim @@ -10,6 +10,9 @@ import std/[macros, strutils, sets, hashes, algorithm] # A compile-time inline assembler +# No exceptions allowed +{.push raises: [].} + type RM* = enum ## Register or Memory operand @@ -43,7 +46,6 @@ type ## GCC extended assembly modifier Input = "" Input_Commutative = "%" - Input_EarlyClobber = "&" Output_Overwrite = "=" Output_EarlyClobber = "=&" InputOutput = "+" @@ -94,7 +96,10 @@ const OutputReg = {Output_EarlyClobber, InputOutput, InputOutput_EnsureClobber, func hash(od: OperandDesc): Hash = {.noSideEffect.}: - hash($od.nimSymbol) + try: # Why does this raise a generic exception? + hash($od.nimSymbol) + except: + raise newException(Defect, "Broke Nim") # TODO: remove the need of OperandArray @@ -138,7 +143,10 @@ func init*(T: type OperandArray, nimSymbol: NimNode, len: int, rm: RM, constrain let nimSymbol = if isHiddenDeref: nimSymbol[0] else: nimSymbol {.noSideEffect.}: - let symStr = $nimSymbol + let symStr = try: # Why does this raise a generic exception? + $nimSymbol + except: + raise newException(Defect, "Broke Nim!") result.nimSymbol = nimSymbol @@ -197,7 +205,10 @@ func setToCarryFlag*(a: var Assembler_x86, carry: NimNode) = let nimSymbol = if isHiddenDeref: carry[0] else: carry {.noSideEffect.}: - let symStr = $nimSymbol + let symStr = try: # Why does this raise a generic exception? + $nimSymbol + except: + raise newException(Defect, "Broke Nim!") let desc = OperandDesc( asmId: "", @@ -403,13 +414,29 @@ func codeFragment(a: var Assembler_x86, instr: string, reg0: OperandReuse, reg1: # ⚠️ Warning: # The caller should deal with destination/source operand # so that it fits GNU Assembly + let off1 = a.getStrOffset(reg1) + if a.wordBitWidth == 64: - a.code &= instr & "q %" & $reg0.asmId & ", %" & $reg1.desc.asmId & '\n' + a.code &= instr & "q %" & $reg0.asmId & ", " & off1 & '\n' else: - a.code &= instr & "l %" & $reg0.asmId & ", %" & $reg1.desc.asmId & '\n' + a.code &= instr & "l %" & $reg0.asmId & ", " & off1 & '\n' a.operands.incl reg1.desc +func codeFragment(a: var Assembler_x86, instr: string, reg0: Operand, reg1: OperandReuse) = + # Generate a code fragment + # ⚠️ Warning: + # The caller should deal with destination/source operand + # so that it fits GNU Assembly + let off0 = a.getStrOffset(reg0) + + if a.wordBitWidth == 64: + a.code &= instr & "q " & off0 & ", %" & $reg1.asmId & '\n' + else: + a.code &= instr & "l " & off0 & ", %" & $reg1.asmId & '\n' + + a.operands.incl reg0.desc + func reuseRegister*(reg: OperandArray): OperandReuse = # TODO: disable the reg input doAssert reg.buf[0].desc.constraint == InputOutput @@ -526,10 +553,28 @@ func test*(a: var Assembler_x86, x, y: Operand) = a.codeFragment("test", x, y) a.areFlagsClobbered = true -func `xor`*(a: var Assembler_x86, x, y: Operand) = +func test*(a: var Assembler_x86, x, y: OperandReuse) = + ## Compute the bitwise AND of x and y and + ## set the Sign, Zero and Parity flags + a.codeFragment("test", x, y) + a.areFlagsClobbered = true + +func `or`*(a: var Assembler_x86, dst, src: Operand) = + ## Compute the bitwise or of x and y and + ## reset all flags + a.codeFragment("or", src, dst) + a.areFlagsClobbered = true + +func `or`*(a: var Assembler_x86, dst: OperandReuse, src: Operand) = + ## Compute the bitwise or of x and y and + ## reset all flags + a.codeFragment("or", src, dst) + a.areFlagsClobbered = true + +func `xor`*(a: var Assembler_x86, dst, src: Operand) = ## Compute the bitwise xor of x and y and ## reset all flags - a.codeFragment("xor", x, y) + a.codeFragment("xor", src, dst) a.areFlagsClobbered = true func mov*(a: var Assembler_x86, dst, src: Operand) = @@ -539,6 +584,20 @@ func mov*(a: var Assembler_x86, dst, src: Operand) = a.codeFragment("mov", src, dst) # No clobber +func mov*(a: var Assembler_x86, dst: Operand, src: OperandReuse) = + ## Does: dst <- src + doAssert dst.desc.constraint in OutputReg, $dst.repr + + a.codeFragment("mov", src, dst) + # No clobber + +func mov*(a: var Assembler_x86, dst: OperandReuse, src: Operand) = + ## Does: dst <- src + # doAssert dst.desc.constraint in OutputReg, $dst.repr + + a.codeFragment("mov", src, dst) + # No clobber + func mov*(a: var Assembler_x86, dst: Operand, imm: int) = ## Does: dst <- imm doAssert dst.desc.constraint in OutputReg, $dst.repr @@ -570,6 +629,14 @@ func cmovz*(a: var Assembler_x86, dst, src: Operand) = a.codeFragment("cmovz", src, dst) # No clobber +func cmovz*(a: var Assembler_x86, dst: Operand, src: OperandReuse) = + ## Does: dst <- src if the zero flag is not set + doAssert dst.desc.rm in {Reg, ElemsInReg}, "The destination operand must be a register: " & $dst.repr + doAssert dst.desc.constraint in OutputReg, $dst.repr + + a.codeFragment("cmovz", src, dst) + # No clobber + func cmovnz*(a: var Assembler_x86, dst, src: Operand) = ## Does: dst <- src if the zero flag is not set doAssert dst.desc.rm in {Reg, ElemsInReg}, "The destination operand must be a register: " & $dst.repr @@ -578,6 +645,14 @@ func cmovnz*(a: var Assembler_x86, dst, src: Operand) = a.codeFragment("cmovnz", src, dst) # No clobber +func cmovnz*(a: var Assembler_x86, dst: Operand, src: OperandReuse) = + ## Does: dst <- src if the zero flag is not set + doAssert dst.desc.rm in {Reg, ElemsInReg}, "The destination operand must be a register: " & $dst.repr + doAssert dst.desc.constraint in OutputReg, $dst.repr + + a.codeFragment("cmovnz", src, dst) + # No clobber + func cmovs*(a: var Assembler_x86, dst, src: Operand) = ## Does: dst <- src if the sign flag doAssert dst.desc.rm in {Reg, ElemsInReg}, "The destination operand must be a register: " & $dst.repr diff --git a/tests/t_finite_fields.nim b/tests/t_finite_fields.nim index a06d247..37b5362 100644 --- a/tests/t_finite_fields.nim +++ b/tests/t_finite_fields.nim @@ -314,3 +314,19 @@ proc main() = main() + +proc largeField() = + suite "Large field": + test "Negate 0 returns 0 (unique Montgomery repr)": + # https://github.com/mratsim/constantine/issues/136 + # and https://github.com/mratsim/constantine/issues/114 + # The assembly implementation of neg didn't check + # after M-a if a was zero and so while in mod M + # M ≡ 0 (mod M), the `==` doesn't support unreduced representation. + var a: Fp[BN254_Snarks] + var r {.noInit.}: Fp[BN254_Snarks] + r.neg(a) + + check: bool r.isZero() + +largeField() diff --git a/tests/t_fp12_anti_regression.nim b/tests/t_fp12_anti_regression.nim new file mode 100644 index 0000000..6783a06 --- /dev/null +++ b/tests/t_fp12_anti_regression.nim @@ -0,0 +1,187 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy André-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +import + # stdlib + std/unittest, + # Internals + ../constantine/config/[common, type_ff], + ../constantine/towers, + ../constantine/config/curves, + ../constantine/io/io_towers, + ../constantine/towers + +# ############################################################### +# +# Edge cases highlighted by property-based testing or fuzzing +# +# ############################################################### + +# Fuzzing failure #114: Fp12 BN254 Mul and add/sub are consistent +# Highlighted by the Long01Seq skewed RNG +# with random seeds +# - 1611183150 +# - 1611267611 +# - 1611393788 +# - 1611420927 +# - 1611402369 + +proc test114(factor: int, a: Fp12[BN254_Snarks]): bool = + var sum{.noInit.}, one{.noInit.}, f{.noInit.}: Fp12[BN254_Snarks] + one.setOne() + + if factor < 0: + sum.neg(a) + f.neg(one) + for i in 1 ..< -factor: + sum -= a + f -= one + else: + sum = a + f = one + for i in 1 ..< factor: + sum += a + f += one + + var r{.noInit.}: Fp12[BN254_Snarks] + + r.prod(a, f) + + result = bool(r == sum) + + if not result: + echo "Failure for" + echo "===================" + echo "r: ", r.toHex() + echo "-------------------" + echo "sum: ", sum.toHex() + echo "-------------------" + debug: + echo "r (raw montgomery): ", $r + echo "-------------------" + echo "sum (raw montgomery):", $sum + echo "-------------------" + echo "\n\n" + +# Requires a Fp -> Fp2 -> Fp4 -> Fp12 towering +var t114_cases: seq[tuple[factor: int, a: Fp12[BN254_Snarks]]] + +t114_cases.add ( + # seed 1611183150 + -13, + Fp12[BN254_Snarks].fromHex( + "0x0000000000ffffffffffffffff3f00c00100000000fcffff0700000000000000", + "0x0000000000ffffffffffff7f000000e0ffff03000000fcff07e0ffffff9fffff", + "0x0080ffffffffff1f00f00080ffffffffffffffffffffffffffffffffffffffff", + "0x0c0a77c19a07df2f666ea36f7899461c0a78ec28b5d70b3dd35d430dc58f0d9d", + "0x000007fc00000000000000000000003ffffffffffff1ffffff8000000001ffff", + "0x000000c0ffffffdfffffffff0100feffff03c0ffffffffffffffff3f00000000", + "0x000000000000000000000080ffffffffff3f0000f0dfff0f80ffffffffff0700", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0e0a77c199c7df2f666ee36f7879422c0a78ed28f5c70b3dd2dd448dc58eed9d", + "0x0e0a77a19a07df2f866ea36f7839462c0a78eb28f5d70b3dd3dd438dc58f0d9c", + "0x000000000000000000000000003fc0000003f80000000000000007ffffffffff", + "0x0000001fff0000000000000000038000003ffffffffffff800000000000ff000" + ) +) + +var x = Fp12[BN254_Snarks].fromHex( + "0x30644e72d431a029b85045b68b4e4e9d8a816a915b98ca99e1208c16d87cfd47", + "0x30644e72d431a029b8504c4381814cf0978e43916864f199d5b38c16dd5cfd54", + "0x29d74e72e131ab96ac203f298181585d97816a916871ca8d3c208c16d87cfd54", + "0x250924f6b2602b3eada2ca30e63cd209d5e1ac3465db981134c5c8a859b04423", + "0x3063e6a6e131a029b85045b68181551d97816a916927ca8d42a08c16d862fd54", + "0x306444a5e131a1c9b85045c37474655da4509d916871ca8d3c2095e3d87cfd47", + "0x30644e72e131a029b8503f298181585da14e6a852d11d6c3af208c16d889a247", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0b0924f6b5a02b3ead9f8a30e7dd0539d5e19f3126ab98113b45b52859b1e423", + "0x0b092696b2602b3d0da2ca30eb1cd139d5e1b93125db98112e45c22859b04430", + "0x30644e72e131a029b85045b67e44985d974dd2916871ca8d3c202416d87cfd54", + "0x30644cd2ee31a029b85045b68153d85d94416a916871caf53c208c16d7adcd47" + ) + +t114_cases.add ( + # seed 1611267611 + -7, + Fp12[BN254_Snarks].fromHex( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0e0477c19a07de6e666ea46eb77947290a786a28f5c70b3dd35d4486c58f0cdc", + "0x00fffffffffffffffffffffff80000000003ffffffffffffc0000000007fffff", + "0x00ffff00000080ffffffffffffffffffffff1f00000000000000000000c03f00", + "0x00000000c0ff00c0ff07000000000000000000000000000000feffffffffffff", + "0x000000000007ffffffffff000000e003f83fffffe0000000001ffff803ffc000", + "0x0000003fffffffffffffffffffffffffffff801fffffffc01f00000007ffffff", + "0x00000000003fffffffe00000000000ffffffe08003fff800007fffffffffffff", + "0x0e0a57c19a47dfaf666ea36f787945ac8a78eb28f5c70b3dd2dd438dc58f0d9d", + "0x0000000000feffffffffff1f0000000000000000000080ffff03f8ffffffffff", + "0x000000f87f0000c0ffffffffffffffffffffffffffffffffffffff07fcffffff", + "0x01fffffffe0000000001fcffffffffffffffffc003ffffff8001ffffffffffff" + ) +) + +t114_cases.add ( + # seed 1611393788 + -15, + Fp12[BN254_Snarks].fromHex( + "0x0e0a77c192085f2f666e63777879462c0a78eb08f5c70b3dd35d438dd58f0d9c", + "0x0fffe03ffe0000000000000000001fffffff0000000fffffe0000fffffffffff", + "0x000000000003ffffffffffff00000000000000000000000000000ffffffeffff", + "0x00f0ffffffff3f0000f0ffffffffff0700000000000000000000600000001f00", + "0x0f9bb18c1ece5fd647afba4d7e7ea7a0687ebd6a978e3572c3df73e9278306b8", + "0x00e0ff3f00f0ffffffffff010000000080ffffffffffffffffffff000000ffff", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0dca77c19a07e02f6666a56f7878462c0a792b28f5c6cb3dd35d438dcd8f0d80", + "0x0e0a76c19a07df2f6e6ea36f7879462c0a78eb28f5c70b3dd359438dc59f0d7d", + "0x0e0a77c11a07df2f666ea36f8075462c0a78eb28f5c70b3dd35d538dc58f0dac", + "0x0e0a77819a083f2f766e9b6f7879462c0a78eb28f5c70b3dd35d438dc592119c", + "0x000000000ffffffffffffe000000003ffffc0000000000000000000000000000" + ) +) + +t114_cases.add ( + # seed 1611420927 + -25, + Fp12[BN254_Snarks].fromHex( + "0x0000000000ffffc00000000000000fffffffffffffffffffff00007fffe003ff", + "0x00000000ffff1fc0ffffff1ff8ffffffffffff00fc010000feffffffff0300f0", + "0x000000000000001800000000e00300feffffffffffff1f00f0ffffffffffffff", + "0x0e0a75c1da07df2f666ea36f7879461c0a78ec28f5c70b35d35d438dc590ed9d", + "0x0e09f6c19a085f30666f846f7780c72c097feb29f5c70b3cd65dc48d44900cbc", + "0x0000000001ffffe7e0000000000000003fffffffffffff000000000001fff800", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0e09b6c28b07df2f666ea36f7879462c0a780ae9f5c70b3dd35c638cc48f0da3", + "0x0e0a77c19a07df2f666da46f7879462c0a78eb2976c60a7cd35d438eb68f0c9d", + "0x0007f00007fffff00000000000000003ffffffff8000000fffffc001ffffffff", + "0x0e0a77c19a07df2f666ea36f7879462c0a68ec28f5c70b3dd35c438dcd4f0e1c", + "0x1ffffffffffffffffffffffffffffff000000000000000000fc000ffffffffff" + ) +) + +t114_cases.add ( + # seed 1611402369 + -10, + Fp12[BN254_Snarks].fromHex( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000020000007fffff800000000000001ffffffffffffffffffffe000", + "0x0000000000000000000000f8fffffffffffffff7ffffffffffff1f0000000200", + "0x0000030000000003fffc00000000003ffffffe000000000000ffc00000000000", + "0x0e0a76e09a07df2f666ea3705881432c0a78e828f5c70b3d125e348d058f0cbc", + "0x0000000f01fffc7fffffffffffffffffffffffe000000000000fffffc0000000", + "0x0e0a77c0b907e02c666ea36f77f8462c0a78eb28f5c70b3e545d438dc58f0d9c", + "0x0e0a77a19a07df2f668ea36f78793a2c0a78eb2875c74b3dd355438dc59f0d9c", + "0x0e0a75c19a07df31662ea36f7879462c0a78eb28f5c70c1dd361438dc58f0d9c", + "0x00000000000000000000000000feffff00001c000007e0ffffffffff07000000", + "0x00001ffffffff000007fffffffff0000007f000000000000ffffffffffffffff", + "0x0e0996c19a08d02e756ea36f7879462c0a78eb28f5c70b3dd43dc28dc58f0d9d" + ) +) + +suite "Fuzzing failure #114: Fp12 BN254 Mul and add/sub are consistent": + test $t114_cases.len & " failure cases are now successful": + for i in 0..