Fuzz fix - non-unique modular representation after Assembly negate (#137)

* Fix #114 - Negating 0 left the prime modulus, which is working most of the time for everything except for comparison. (also somehow triggers and workaround weird compiler bug where exceptions tracking is activated in macros and all the curve enums were stringified as their ordinal value)

* https://github.com/mratsim/constantine/issues/136 was also fixed, add to anti-regression

* add comment in test

* Fix the pure Nim fallback as well
This commit is contained in:
Mamy Ratsimbazafy 2021-01-24 12:35:27 +01:00 committed by GitHub
parent 5b1d280486
commit 7e97cd4ac5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 378 additions and 19 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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
# ------------------------------------------------------------

View File

@ -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
# ------------------------------------------------------------

View File

@ -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

View File

@ -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
# ------------------------------------------------------------

View File

@ -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:

View File

@ -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

View File

@ -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()

View File

@ -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..<t114_cases.len:
check: test114(t114_cases[i].factor, t114_cases[i].a)

View File

@ -34,7 +34,7 @@ type
Long01Sequence
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
let seed = 1611432811 # uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
rng.seed(seed)
echo "\n------------------------------------------------------\n"
echo "test_fp2_sqrt xoshiro512** seed: ", seed
@ -98,4 +98,35 @@ proc main() =
bool not a.isSquare()
bool not a.sqrt_if_square()
suite "Modular square root - Assembly bugs highlighted by property-based testing " & " [" & $WordBitwidth & "-bit mode]":
test "Don't set Neg(Zero) fields to modulus (non-unique Montgomery repr) - #136":
# 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.
# Seed: 1611432811
let a = Fp2[BN254_Snarks].fromHex(
"0x0e097bc0990edfae676ba36f7879462c09b7eb28f6450b6dd3de438dc58f0d9c",
"0x0000000000000000000000000000000000000000000000000000000000000000"
)
var na{.noInit.}: Fp2[BN254_Snarks]
na.neg(a)
var a2 = a
var na2 = na
a2.square()
na2.square()
check:
bool a2 == na2
bool a2.isSquare()
var r, s = a2
# r.sqrt()
let ok = s.sqrt_if_square()
check:
bool ok
# bool(r == s)
bool(s == a or s == na)
main()