hash-to-curve BLS12-381 perf (#163)

* fp square noasm split from non-4 non-6 limbs fallback (40% speedup)

* optimized cofactor clearing for BLS12-381 G2

* Support jacobian isogenies and point_add on isogenies

* fuse addition and isogeny map

* {.noInit.} and sparseMul

* poly_eval_horner init

* dedicated invsqrt + cleanup square root file

* hash to field: reduce copy overhead and don't return arrays

* h2c isogeny jacobian reuse pow 3 precomputed value

* Fix sqrt bench
This commit is contained in:
Mamy Ratsimbazafy 2021-08-14 21:01:50 +02:00 committed by GitHub
parent 499f9605b2
commit 0bc228126a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 608 additions and 304 deletions

View File

@ -124,28 +124,32 @@ proc sqrtBench*(T: typedesc, iters: int) =
discard r.sqrt_if_square()
proc sqrtP3mod4Bench*(T: typedesc, iters: int) =
var r: T
let x = rng.random_unsafe(T)
bench("SquareRoot + isSquare (p ≡ 3 (mod 4) exponentiation)", T, iters):
var r = x
discard r.sqrt_if_square_p3mod4()
bench("SquareRoot (p ≡ 3 (mod 4) exponentiation)", T, iters):
r.invsqrt_p3mod4(x)
r *= x
proc sqrtAddChainBench*(T: typedesc, iters: int) =
var r: T
let x = rng.random_unsafe(T)
bench("SquareRoot + isSquare (addition chain)", T, iters):
var r = x
discard r.sqrt_if_square_addchain()
bench("SquareRoot (addition chain)", T, iters):
r.invsqrt_addchain(x)
r *= x
proc sqrtTonelliBench*(T: typedesc, iters: int) =
var r: T
let x = rng.random_unsafe(T)
bench("SquareRoot + isSquare (constant-time Tonelli-Shanks exponentiation)", T, iters):
var r = x
discard r.sqrt_if_square_tonelli_shanks(useAddChain = false)
bench("SquareRoot (constant-time Tonelli-Shanks exponentiation)", T, iters):
r.invsqrt_tonelli_shanks(x, useAddChain = false)
r *= x
proc sqrtTonelliAddChainBench*(T: typedesc, iters: int) =
var r: T
let x = rng.random_unsafe(T)
bench("SquareRoot + isSquare (constant-time Tonelli-Shanks addchain)", T, iters):
var r = x
discard r.sqrt_if_square_tonelli_shanks(useAddChain = true)
bench("SquareRoot (constant-time Tonelli-Shanks addchain)", T, iters):
r.invsqrt_tonelli_shanks(x, useAddChain = true)
r *= x
proc powBench*(T: typedesc, iters: int) =
let x = rng.random_unsafe(T)

View File

@ -263,26 +263,26 @@ proc runTests(requireGMP: bool, dumpCmdFile = false, test32bit = false, testASM
flags &= sanitizers
test flags, td.path, dumpCmdFile
proc buildAllBenches() =
proc buildAllBenches(useAsm = true) =
echo "\n\n------------------------------------------------------\n"
echo "Building benchmarks to ensure they stay relevant ..."
buildBench("bench_fp")
buildBench("bench_fp_double_precision")
buildBench("bench_fp2")
buildBench("bench_fp6")
buildBench("bench_fp12")
buildBench("bench_ec_g1")
buildBench("bench_ec_g2")
buildBench("bench_pairing_bls12_377")
buildBench("bench_pairing_bls12_381")
buildBench("bench_pairing_bn254_nogami")
buildBench("bench_pairing_bn254_snarks")
buildBench("bench_summary_bls12_377")
buildBench("bench_summary_bls12_381")
buildBench("bench_summary_bn254_nogami")
buildBench("bench_summary_bn254_snarks")
buildBench("bench_sha256")
buildBench("bench_hash_to_curve")
buildBench("bench_fp", useAsm = useAsm)
buildBench("bench_fp_double_precision", useAsm = useAsm)
buildBench("bench_fp2", useAsm = useAsm)
buildBench("bench_fp6", useAsm = useAsm)
buildBench("bench_fp12", useAsm = useAsm)
buildBench("bench_ec_g1", useAsm = useAsm)
buildBench("bench_ec_g2", useAsm = useAsm)
buildBench("bench_pairing_bls12_377", useAsm = useAsm)
buildBench("bench_pairing_bls12_381", useAsm = useAsm)
buildBench("bench_pairing_bn254_nogami", useAsm = useAsm)
buildBench("bench_pairing_bn254_snarks", useAsm = useAsm)
buildBench("bench_summary_bls12_377", useAsm = useAsm)
buildBench("bench_summary_bls12_381", useAsm = useAsm)
buildBench("bench_summary_bn254_nogami", useAsm = useAsm)
buildBench("bench_summary_bn254_snarks", useAsm = useAsm)
buildBench("bench_sha256", useAsm = useAsm)
buildBench("bench_hash_to_curve", useAsm = useAsm)
echo "All benchmarks compile successfully."
# Tasks
@ -308,7 +308,7 @@ task test_no_assembler, "Run all tests":
# Ensure benchmarks stay relevant. Ignore Windows 32-bit at the moment
if not defined(windows) or not (existsEnv"UCPU" or getEnv"UCPU" == "i686"):
buildAllBenches()
buildAllBenches(useASM = false)
task test_no_gmp, "Run tests that don't require GMP":
# -d:testingCurves is configured in a *.nim.cfg for convenience
@ -357,7 +357,7 @@ task test_parallel_no_assembler, "Run all tests (without macro assembler) in par
# ignore Windows 32-bit for the moment
# Ensure benchmarks stay relevant. Ignore Windows 32-bit at the moment
if not defined(windows) or not (existsEnv"UCPU" or getEnv"UCPU" == "i686"):
buildAllBenches()
buildAllBenches(useASM = false)
task test_parallel_no_gmp, "Run all tests in parallel (via GNU parallel)":
# -d:testingCurves is configured in a *.nim.cfg for convenience
@ -395,7 +395,7 @@ task test_parallel_no_gmp_no_assembler, "Run all tests in parallel (via GNU para
# ignore Windows 32-bit for the moment
# Ensure benchmarks stay relevant. Ignore Windows 32-bit at the moment
if not defined(windows) or not (existsEnv"UCPU" or getEnv"UCPU" == "i686"):
buildAllBenches()
buildAllBenches(useASM = false)
# Finite field 𝔽p
# ------------------------------------------

View File

@ -54,8 +54,8 @@ func hasP3mod4_primeModulus(C: static Curve): static bool =
## Returns true iff p ≡ 3 (mod 4)
(BaseType(C.Mod.limbs[0]) and 3) == 3
func sqrt_p3mod4(a: var Fp) =
## Compute the square root of ``a``
func invsqrt_p3mod4*(r: var Fp, a: Fp) =
## Compute the inverse square root of ``a``
##
## This requires ``a`` to be a square
## and the prime field modulus ``p``: p ≡ 3 (mod 4)
@ -65,14 +65,6 @@ func sqrt_p3mod4(a: var Fp) =
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
static: doAssert BaseType(Fp.C.Mod.limbs[0]) mod 4 == 3
a.powUnsafeExponent(Fp.getPrimePlus1div4_BE())
func sqrt_invsqrt_p3mod4(sqrt, invsqrt: var Fp, a: Fp) =
## If ``a`` is a square, compute the square root of ``a`` in sqrt
## and the inverse square root of a in invsqrt
##
## This assumes that the prime field modulus ``p``: p ≡ 3 (mod 4)
# TODO: deterministic sign
#
# Algorithm
@ -83,82 +75,12 @@ func sqrt_invsqrt_p3mod4(sqrt, invsqrt: var Fp, a: Fp) =
# a^((p-3)/2)) ≡ 1/a (mod p)
# a^((p-3)/4)) ≡ 1/√a (mod p) # Requires p ≡ 3 (mod 4)
static: doAssert BaseType(Fp.C.Mod.limbs[0]) mod 4 == 3
invsqrt = a
invsqrt.powUnsafeExponent(Fp.getPrimeMinus3div4_BE())
# √a ≡ a * 1/√a ≡ a^((p+1)/4) (mod p)
sqrt.prod(invsqrt, a)
func sqrt_invsqrt_if_square_p3mod4(sqrt, invsqrt: var Fp, a: Fp): SecretBool =
## If ``a`` is a square, compute the square root of ``a`` in sqrt
## and the inverse square root of a in invsqrt
##
## If a is not square, sqrt and invsqrt are undefined
##
## This assumes that the prime field modulus ``p``: p ≡ 3 (mod 4)
sqrt_invsqrt_p3mod4(sqrt, invsqrt, a)
var test {.noInit.}: Fp
test.square(sqrt)
result = test == a
func sqrt_if_square_p3mod4*(a: var Fp): SecretBool =
## If ``a`` is a square, compute the square root of ``a``
## if not, ``a`` is unmodified.
##
## This assumes that the prime field modulus ``p``: p ≡ 3 (mod 4)
##
## The result is undefined otherwise
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
var sqrt {.noInit.}, invsqrt {.noInit.}: Fp
result = sqrt_invsqrt_if_square_p3mod4(sqrt, invsqrt, a)
a.ccopy(sqrt, result)
r = a
r.powUnsafeExponent(Fp.getPrimeMinus3div4_BE())
# Specialized routines for addchain-based square roots
# ------------------------------------------------------------
func sqrt_addchain(a: var Fp) =
## Compute the square root of ``a``
##
## This requires ``a`` to be a square
## The result is undefined otherwise
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
var invsqrt {.noInit.}: Fp
invsqrt.invsqrt_addchain(a)
a *= invsqrt
func sqrt_invsqrt_addchain(sqrt, invsqrt: var Fp, a: Fp) =
## If ``a`` is a square, compute the square root of ``a`` in sqrt
## and the inverse square root of a in invsqrt
invsqrt.invsqrt_addchain(a)
sqrt.prod(invsqrt, a)
func sqrt_invsqrt_if_square_addchain(sqrt, invsqrt: var Fp, a: Fp): SecretBool =
## If ``a`` is a square, compute the square root of ``a`` in sqrt
## and the inverse square root of a in invsqrt
##
## If a is not square, sqrt and invsqrt are undefined
sqrt_invsqrt_addchain(sqrt, invsqrt, a)
var test {.noInit.}: Fp
test.square(sqrt)
result = test == a
func sqrt_if_square_addchain*(a: var Fp): SecretBool =
## If ``a`` is a square, compute the square root of ``a``
## if not, ``a`` is unmodified.
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
var sqrt {.noInit.}, invsqrt {.noInit.}: Fp
result = sqrt_invsqrt_if_square_addchain(sqrt, invsqrt, a)
a.ccopy(sqrt, result)
{.pop.} # inline
# Tonelli Shanks for any prime
@ -174,12 +96,15 @@ func precompute_tonelli_shanks(
a_pre_exp.powUnsafeExponent(Fp.C.tonelliShanks(exponent))
func isSquare_tonelli_shanks(
a, a_pre_exp: Fp): SecretBool =
a, a_pre_exp: Fp): SecretBool {.used.} =
## Returns if `a` is a quadratic residue
## This uses common precomputation for
## Tonelli-Shanks based square root and inverse square root
##
## a^((p-1-2^e)/(2*2^e))
##
## Note: if we need to compute a candidate square root anyway
## it's faster to square it to check if we get ``a``
const e = Fp.C.tonelliShanks(twoAdicity)
var r {.noInit.}: Fp
r.square(a_pre_exp) # a^(2(q-1-2^e)/(2*2^e)) = a^((q-1)/2^e - 1)
@ -198,10 +123,10 @@ func isSquare_tonelli_shanks(
r.isMinusOne()
)
func sqrt_invsqrt_tonelli_shanks_pre(
sqrt, invsqrt: var Fp,
func invsqrt_tonelli_shanks_pre(
invsqrt: var Fp,
a, a_pre_exp: Fp) =
## Compute the square_root and inverse_square_root
## Compute the inverse_square_root
## of `a` via constant-time Tonelli-Shanks
##
## a_pre_exp is a precomputation a^((p-1-2^e)/(2*2^e))
@ -230,12 +155,8 @@ func sqrt_invsqrt_tonelli_shanks_pre(
t.ccopy(buf, bNotOne)
b = t
sqrt.prod(invsqrt, a)
# ----------------------------------------------
func sqrt_tonelli_shanks(a: var Fp, useAddChain: static bool) =
## Compute the square root of ``a``
func invsqrt_tonelli_shanks*(r: var Fp, a: Fp, useAddChain: static bool) =
## Compute the inverse square root of ``a``
##
## This requires ``a`` to be a square
##
@ -245,54 +166,9 @@ func sqrt_tonelli_shanks(a: var Fp, useAddChain: static bool) =
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
## This procedure is constant-time
var a_pre_exp{.noInit.}, sqrt{.noInit.}, invsqrt{.noInit.}: Fp
a_pre_exp.precompute_tonelli_shanks(a, useAddChain)
sqrt_invsqrt_tonelli_shanks_pre(sqrt, invsqrt, a, a_pre_exp)
a = sqrt
func sqrt_invsqrt_tonelli_shanks(sqrt, invsqrt: var Fp, a: Fp, useAddChain: static bool) =
## Compute the square root and inverse square root of ``a``
##
## This requires ``a`` to be a square
##
## The result is undefined otherwise
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
var a_pre_exp{.noInit.}: Fp
a_pre_exp.precompute_tonelli_shanks(a, useAddChain)
sqrt_invsqrt_tonelli_shanks_pre(sqrt, invsqrt, a, a_pre_exp)
func sqrt_invsqrt_if_square_tonelli_shanks(sqrt, invsqrt: var Fp, a: Fp, useAddChain: static bool): SecretBool =
## Compute the square root and ivnerse square root of ``a``
##
## This returns true if ``a`` is square and sqrt/invsqrt contains the square root/inverse square root
##
## The result is undefined otherwise
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
var a_pre_exp{.noInit.}: Fp
a_pre_exp.precompute_tonelli_shanks(a, useAddChain)
result = isSquare_tonelli_shanks(a, a_pre_exp)
sqrt_invsqrt_tonelli_shanks_pre(sqrt, invsqrt, a, a_pre_exp)
a = sqrt
func sqrt_if_square_tonelli_shanks*(a: var Fp, useAddChain: static bool): SecretBool =
## If ``a`` is a square, compute the square root of ``a``
## if not, ``a`` is unmodified.
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
## This procedure is constant-time
var a_pre_exp{.noInit.}, sqrt{.noInit.}, invsqrt{.noInit.}: Fp
a_pre_exp.precompute_tonelli_shanks(a, useAddChain)
result = isSquare_tonelli_shanks(a, a_pre_exp)
sqrt_invsqrt_tonelli_shanks_pre(sqrt, invsqrt, a, a_pre_exp)
a = sqrt
invsqrt_tonelli_shanks_pre(r, a, a_pre_exp)
# Public routines
# ------------------------------------------------------------
@ -301,6 +177,24 @@ func sqrt_if_square_tonelli_shanks*(a: var Fp, useAddChain: static bool): Secret
{.push inline.}
func invsqrt*[C](r: var Fp[C], a: Fp[C]) =
## Compute the inverse square root of ``a``
##
## This requires ``a`` to be a square
##
## The result is undefined otherwise
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
## This procedure is constant-time
when C.hasSqrtAddchain():
r.invsqrt_addchain(a)
elif C.hasP3mod4_primeModulus():
r.invsqrt_p3mod4(a)
else:
r.invsqrt_tonelli_shanks(a, useAddChain = C.hasTonelliShanksAddchain())
func sqrt*[C](a: var Fp[C]) =
## Compute the square root of ``a``
##
@ -312,12 +206,9 @@ func sqrt*[C](a: var Fp[C]) =
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
## This procedure is constant-time
when C.hasSqrtAddchain():
sqrt_addchain(a)
elif C.hasP3mod4_primeModulus():
sqrt_p3mod4(a)
else:
sqrt_tonelli_shanks(a, useAddChain = C.hasTonelliShanksAddchain())
var t {.noInit.}: Fp[C]
t.invsqrt(a)
a *= t
func sqrt_invsqrt*[C](sqrt, invsqrt: var Fp[C], a: Fp[C]) =
## Compute the square root and inverse square root of ``a``
@ -329,12 +220,8 @@ func sqrt_invsqrt*[C](sqrt, invsqrt: var Fp[C], a: Fp[C]) =
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
when C.hasSqrtAddchain():
sqrt_invsqrt_addchain(sqrt, invsqrt, a)
elif C.hasP3mod4_primeModulus():
sqrt_invsqrt_p3mod4(sqrt, invsqrt, a)
else:
sqrt_invsqrt_tonelli_shanks(sqrt, invsqrt, a, useAddChain = C.hasTonelliShanksAddchain())
invsqrt.invsqrt(a)
sqrt.prod(invsqrt, a)
func sqrt_invsqrt_if_square*[C](sqrt, invsqrt: var Fp[C], a: Fp[C]): SecretBool =
## Compute the square root and ivnerse square root of ``a``
@ -346,27 +233,33 @@ func sqrt_invsqrt_if_square*[C](sqrt, invsqrt: var Fp[C], a: Fp[C]): SecretBool
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
when C.hasSqrtAddchain():
result = sqrt_invsqrt_if_square_addchain(sqrt, invsqrt, a)
elif C.hasP3mod4_primeModulus():
result = sqrt_invsqrt_if_square_p3mod4(sqrt, invsqrt, a)
else:
result = sqrt_invsqrt_if_square_tonelli_shanks(sqrt, invsqrt, a, useAddChain = C.hasTonelliShanksAddchain())
sqrt_invsqrt(sqrt, invsqrt, a)
var test {.noInit.}: Fp[C]
test.square(sqrt)
result = test == a
func sqrt_if_square*[C](a: var Fp[C]): SecretBool =
## If ``a`` is a square, compute the square root of ``a``
## if not, ``a`` is unmodified.
## if not, ``a`` is undefined.
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
## This procedure is constant-time
when C.hasSqrtAddchain():
result = sqrt_if_square_addchain(a)
elif C.hasP3mod4_primeModulus():
result = sqrt_if_square_p3mod4(a)
else:
result = sqrt_if_square_tonelli_shanks(a, useAddChain = C.hasTonelliShanksAddchain())
var sqrt{.noInit.}, invsqrt{.noInit.}: Fp[C]
result = sqrt_invsqrt_if_square(sqrt, invsqrt, a)
a = sqrt
func invsqrt_if_square*[C](r: var Fp[C], a: Fp[C]): SecretBool =
## If ``a`` is a square, compute the inverse square root of ``a``
## if not, ``a`` is undefined.
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
## This procedure is constant-time
var sqrt{.noInit.}: Fp[C]
result = sqrt_invsqrt_if_square(sqrt, r, a)
{.pop.} # inline
{.pop.} # raises no exceptions

View File

@ -398,10 +398,12 @@ func montySquare*[N](r: var Limbs[N], a, M: Limbs[N],
montSquare_CIOS_asm_adx_bmi2(r, a, M, m0ninv, spareBits >= 1)
else:
montSquare_CIOS_asm(r, a, M, m0ninv, spareBits >= 1)
else:
elif UseASM_X86_64:
var r2x {.noInit.}: Limbs[2*N]
r2x.square(a)
r.montyRedc2x(r2x, M, m0ninv, spareBits)
else:
montyMul(r, a, a, M, m0ninv, spareBits)
func redc*(r: var Limbs, a, one, M: Limbs,
m0ninv: static BaseType, spareBits: static int) =

View File

@ -31,7 +31,7 @@ const BLS12_381_h2c_G2_Z* = Fp2[BLS12_381].fromHex( # -(2 + 𝑖)
"0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaa9",
"0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaa"
)
const BLS12_381_h2c_G2_minusA* = Fp2[BLS12_381].fromHex( # -240𝑖
const BLS12_381_h2c_G2_minus_A* = Fp2[BLS12_381].fromHex( # -240𝑖
"0x0",
"0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffa9bb"
)
@ -60,22 +60,22 @@ const BLS12_381_h2c_G2_3_isogeny_map_xnum* = [
# The polynomial is stored as an array of coefficients ordered from k₀ to kₙ
# 1
Fp2[BLS12_381].fromHex(
Fp2[BLS12_381].fromHex(
"0x5c759507e8e333ebb5b7a9a47d7ed8532c52d39fd3a042a88b58423c50ae15d5c2638e343d9c71c6238aaaaaaaa97d6",
"0x5c759507e8e333ebb5b7a9a47d7ed8532c52d39fd3a042a88b58423c50ae15d5c2638e343d9c71c6238aaaaaaaa97d6"
),
# x
Fp2[BLS12_381].fromHex(
Fp2[BLS12_381].fromHex(
"0x0",
"0x11560bf17baa99bc32126fced787c88f984f87adf7ae0c7f9a208c6b4f20a4181472aaa9cb8d555526a9ffffffffc71a"
),
# x^2
Fp2[BLS12_381].fromHex(
Fp2[BLS12_381].fromHex(
"0x11560bf17baa99bc32126fced787c88f984f87adf7ae0c7f9a208c6b4f20a4181472aaa9cb8d555526a9ffffffffc71e",
"0x8ab05f8bdd54cde190937e76bc3e447cc27c3d6fbd7063fcd104635a790520c0a395554e5c6aaaa9354ffffffffe38d"
),
# x^3
Fp2[BLS12_381].fromHex(
Fp2[BLS12_381].fromHex(
"0x171d6541fa38ccfaed6dea691f5fb614cb14b4e7f4e810aa22d6108f142b85757098e38d0f671c7188e2aaaaaaaa5ed1",
"0x0"
)
@ -85,17 +85,17 @@ const BLS12_381_h2c_G2_3_isogeny_map_xden* = [
# The polynomial is stored as an array of coefficients ordered from k₀ to kₙ
# 1
Fp2[BLS12_381].fromHex(
Fp2[BLS12_381].fromHex(
"0x0",
"0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaa63"
),
# x
Fp2[BLS12_381].fromHex(
Fp2[BLS12_381].fromHex(
"0xc",
"0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaa9f"
),
# x^2
Fp2[BLS12_381].fromHex(
Fp2[BLS12_381].fromHex(
"0x1",
"0x0"
)
@ -105,22 +105,22 @@ const BLS12_381_h2c_G2_3_isogeny_map_ynum* = [
# The polynomial is stored as an array of coefficients ordered from k₀ to kₙ
# y
Fp2[BLS12_381].fromHex(
Fp2[BLS12_381].fromHex(
"0x1530477c7ab4113b59a4c18b076d11930f7da5d4a07f649bf54439d87d27e500fc8c25ebf8c92f6812cfc71c71c6d706",
"0x1530477c7ab4113b59a4c18b076d11930f7da5d4a07f649bf54439d87d27e500fc8c25ebf8c92f6812cfc71c71c6d706"
),
# x*y
Fp2[BLS12_381].fromHex(
Fp2[BLS12_381].fromHex(
"0x0",
"0x5c759507e8e333ebb5b7a9a47d7ed8532c52d39fd3a042a88b58423c50ae15d5c2638e343d9c71c6238aaaaaaaa97be"
),
# x^2*y
Fp2[BLS12_381].fromHex(
Fp2[BLS12_381].fromHex(
"0x11560bf17baa99bc32126fced787c88f984f87adf7ae0c7f9a208c6b4f20a4181472aaa9cb8d555526a9ffffffffc71c",
"0x8ab05f8bdd54cde190937e76bc3e447cc27c3d6fbd7063fcd104635a790520c0a395554e5c6aaaa9354ffffffffe38f"
),
# x^3*y
Fp2[BLS12_381].fromHex(
Fp2[BLS12_381].fromHex(
"0x124c9ad43b6cf79bfbf7043de3811ad0761b0f37a1e26286b0e977c69aa274524e79097a56dc4bd9e1b371c71c718b10",
"0x0"
)
@ -130,23 +130,23 @@ const BLS12_381_h2c_G2_3_isogeny_map_yden* = [
# The polynomial is stored as an array of coefficients ordered from k₀ to kₙ
# 1
Fp2[BLS12_381].fromHex(
Fp2[BLS12_381].fromHex(
"0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffa8fb",
"0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffa8fb"
),
# x
Fp2[BLS12_381].fromHex(
Fp2[BLS12_381].fromHex(
"0x0",
"0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffa9d3"
),
# x^2
Fp2[BLS12_381].fromHex(
Fp2[BLS12_381].fromHex(
"0x12",
"0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaa99"
),
# x^3
Fp2[BLS12_381].fromHex(
Fp2[BLS12_381].fromHex(
"0x1",
"0x0"
)
]
]

View File

@ -0,0 +1,31 @@
# 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.
# ############################################################
#
# Short Weierstrass Elliptic Curves
#
# ############################################################
import
elliptic/[
ec_shortweierstrass_affine,
ec_shortweierstrass_jacobian,
ec_shortweierstrass_projective
]
export ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_shortweierstrass_projective
func projectiveFromJacobian*[F; Tw](
prj: var ECP_ShortW_Prj[F, Tw],
jac: ECP_ShortW_Jac[F, Tw]) {.inline.} =
prj.x.prod(jac.x, jac.z)
prj.y = jac.y
prj.z.square(jac.z)
prj.z *= jac.z

View File

@ -34,6 +34,11 @@ type
SexticNonResidue* = NonResidue
func `==`*(P, Q: ECP_ShortW_Aff): SecretBool =
## Constant-time equality check
result = P.x == Q.x
result = result and (P.y == Q.y)
func isInf*(P: ECP_ShortW_Aff): SecretBool =
## Returns true if P is an infinity point
## and false otherwise

View File

@ -134,11 +134,13 @@ func cneg*(P: var ECP_ShortW_Jac, ctl: CTBool) {.inline.} =
## Negate if ``ctl`` is true
P.y.cneg(ctl)
func sum*[F; Tw: static Twisted](
template sumImpl[F; Tw: static Twisted](
r: var ECP_ShortW_Jac[F, Tw],
P, Q: ECP_ShortW_Jac[F, Tw]
P, Q: ECP_ShortW_Jac[F, Tw],
CoefA: untyped
) =
## Elliptic curve point addition for Short Weierstrass curves in Jacobian coordinates
## with the curve ``a`` being a parameter for summing on isogenous curves.
##
## R = P + Q
##
@ -148,6 +150,8 @@ func sum*[F; Tw: static Twisted](
## y² = x³ + a x + b
##
## ``r`` is initialized/overwritten with the sum
## ``CoefA`` allows fast path for curve with a == 0 or a == -3
## and also allows summing on curve isogenies.
##
## Implementation is constant-time, in particular it will not expose
## that P == Q or P == -Q or P or Q are the infinity points
@ -218,7 +222,16 @@ func sum*[F; Tw: static Twisted](
V_or_S *= HH_or_YY # V = U₁*HH (add) or S = X₁*YY (dbl)
block: # Compute M for doubling
when F.C.getCoefA() == 0:
# "when" static evaluation doesn't shortcut booleans :/
# which causes issues when CoefA isn't an int but Fp or Fp2
when CoefA is int:
const CoefA_eq_zero = CoefA == 0
const CoefA_eq_minus3 = CoefA == -3
else:
const CoefA_eq_zero = false
const CoefA_eq_minus3 = false
when CoefA_eq_zero:
var a = H
var b = HH_or_YY
a.ccopy(P.x, isDbl) # H or X₁
@ -230,7 +243,7 @@ func sum*[F; Tw: static Twisted](
M += HHH_or_Mpre # 3X₁²/2
R_or_M.ccopy(M, isDbl)
elif F.C.getCoefA() == -3:
elif CoefA_eq_minus3:
var a{.noInit.}, b{.noInit.}: F
a.sum(P.x, Z1Z1)
b.diff(P.z, Z1Z1)
@ -247,7 +260,7 @@ func sum*[F; Tw: static Twisted](
# TODO: Costly `a` coefficients can be computed
# by merging their computation with Z₃ = Z₁*Z₂*H (add) or Z₃ = Y₁*Z₁ (dbl)
var a = H
var b = HH
var b = HH_or_YY
a.ccopy(P.x, isDbl)
b.ccopy(P.x, isDbl)
HHH_or_Mpre.prod(a, b) # HHH or X₁²
@ -256,7 +269,8 @@ func sum*[F; Tw: static Twisted](
a.square(HHH_or_Mpre)
a *= HHH_or_Mpre # a = 3X₁²
b.square(Z1Z1)
b *= F.C.getCoefA() # b = αZZ, with α the "a" coefficient of the curve
# b.mulCheckSparse(CoefA) # TODO: broken static compile-time type inference
b *= CoefA # b = αZZ, with α the "a" coefficient of the curve
a += b
a.div2()
@ -292,6 +306,52 @@ func sum*[F; Tw: static Twisted](
r.ccopy(Q, P.isInf())
r.ccopy(P, Q.isInf())
func sum*[F; Tw: static Twisted](
r: var ECP_ShortW_Jac[F, Tw],
P, Q: ECP_ShortW_Jac[F, Tw],
CoefA: static F
) =
## Elliptic curve point addition for Short Weierstrass curves in Jacobian coordinates
## with the curve ``a`` being a parameter for summing on isogenous curves.
##
## R = P + Q
##
## Short Weierstrass curves have the following equation in Jacobian coordinates
## Y² = X³ + aXZ⁴ + bZ⁶
## from the affine equation
## y² = x³ + a x + b
##
## ``r`` is initialized/overwritten with the sum
## ``CoefA`` allows fast path for curve with a == 0 or a == -3
## and also allows summing on curve isogenies.
##
## Implementation is constant-time, in particular it will not expose
## that P == Q or P == -Q or P or Q are the infinity points
## to simple side-channel attacks (SCA)
## This is done by using a "complete" or "exception-free" addition law.
r.sumImpl(P, Q, CoefA)
func sum*[F; Tw: static Twisted](
r: var ECP_ShortW_Jac[F, Tw],
P, Q: ECP_ShortW_Jac[F, Tw]
) =
## Elliptic curve point addition for Short Weierstrass curves in Jacobian coordinates
##
## R = P + Q
##
## Short Weierstrass curves have the following equation in Jacobian coordinates
## Y² = X³ + aXZ⁴ + bZ⁶
## from the affine equation
## y² = x³ + a x + b
##
## ``r`` is initialized/overwritten with the sum
##
## Implementation is constant-time, in particular it will not expose
## that P == Q or P == -Q or P or Q are the infinity points
## to simple side-channel attacks (SCA)
## This is done by using a "complete" or "exception-free" addition law.
r.sumImpl(P, Q, F.C.getCoefA())
func madd*[F; Tw: static Twisted](
r: var ECP_ShortW_Jac[F, Tw],
P: ECP_ShortW_Jac[F, Tw],

View File

@ -14,7 +14,8 @@ import
../towers,
../config/curves,
../io/io_bigints,
../elliptic/[ec_shortweierstrass_projective, ec_scalar_mul]
../elliptic/[ec_shortweierstrass_projective, ec_scalar_mul],
../isogeny/frobenius
# ############################################################
#
@ -96,3 +97,97 @@ func clearCofactorReference*(P: var ECP_ShortW_Prj[Fp[BW6_761], OnTwist]) {.inli
## Clear the cofactor of BW6_761 G2
# Endomorphism acceleration cannot be used if cofactor is not cleared
P.scalarMulGeneric(Cofactor_Eff_BW6_761_G2)
# ############################################################
#
# Clear Cofactor - Optimized
#
# ############################################################
# BLS12 G2
# ------------------------------------------------------------
# From any point on the elliptic curve E2 of a BLS12 curve
# Obtain a point in the G2 prime-order subgroup
#
# Described in https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-G.4
#
# Implementations, multiple implementations are possible in increasing order of speed:
#
# - The default, canonical, implementation is h_eff * P
# - Scott et al, "Fast Hashing to G2 on Pairing-Friendly Curves", https://doi.org/10.1007/978-3-642-03298-1_8
# - Fuentes-Castaneda et al, "Fast Hashing to G2 on Pairing-Friendly Curves", https://doi.org/10.1007/978-3-642-28496-0_25
# - Budroni et al, "Hashing to G2 on BLS pairing-friendly curves", https://doi.org/10.1145/3313880.3313884
# - Wahby et al "Fast and simple constant-time hashing to the BLS12-381 elliptic curve", https://eprint.iacr.org/2019/403
# - IETF "Hashing to Elliptic Curves", https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-G.4
#
# In summary, the elliptic curve point multiplication is very expensive,
# the fast methods uses endomorphism acceleration instead.
#
# The method described in Wahby et al is implemented by Riad Wahby
# in C at: https://github.com/kwantam/bls12-381_hash/blob/23c1930039f58606138459557677668fabc8ce39/src/curve2/ops2.c#L106-L204
# following Budroni et al, "Efficient hash maps to G2 on BLS curves"
# https://eprint.iacr.org/2017/419
#
# "P -> [x² - x - 1] P + [x - 1] ψ(P) + ψ(ψ([2]P))"
#
# with Psi (ψ) - untwist-Frobenius-Twist function
# and x the curve BLS parameter
func double_repeated*[EC](P: var EC, num: int) {.inline.} =
## Repeated doublings
for _ in 0 ..< num:
P.double()
func pow_x(
r{.noalias.}: var ECP_ShortW_Prj[Fp2[BLS12_381], OnTwist],
P{.noalias.}: ECP_ShortW_Prj[Fp2[BLS12_381], OnTwist],
) =
## Does the scalar multiplication [x]P
## with x the BLS12 curve parameter
## For BLS12_381 [-0xd201000000010000]P
## Requires r and P to not alias
# In binary
# 0b11
r.double(P)
r += P
# 0b1101
r.double_repeated(2)
r += P
# 0b1101001
r.double_repeated(3)
r += P
# 0b1101001000000001
r.double_repeated(9)
r += P
# 0b110100100000000100000000000000000000000000000001
r.double_repeated(32)
r += P
# 0b1101001000000001000000000000000000000000000000010000000000000000
r.double_repeated(16)
# Negative, x = -0xd201000000010000
r.neg(r)
func clearCofactorFast*(P: var ECP_ShortW_Prj[Fp2[BLS12_381], OnTwist]) =
## Clear the cofactor of BLS12_381 G2
## Optimized using endomorphisms
## P -> [x²-x-1]P + [x-1] ψ(P) + ψ²([2]P)
var xP{.noInit.}, x2P{.noInit.}: typeof(P)
xP.pow_x(P) # 1. xP = [x]P
x2P.pow_x(xP) # 2. x2P = [x²]P
x2P.diff(x2P, xP) # 3. x2P = [x²-x]P
x2P.diff(x2P, P) # 4. x2P = [x²-x-1]P
xP.diff(xP, P) # 5. xP = [x-1]P
xP.frobenius_psi(xP) # 6. xP = ψ([x-1]P) = [x-1] ψ(P)
P.double(P) # 7. P = [2]P
P.frobenius_psi(P, k=2) # 8. P = ψ²([2]P)
P.sum(P, x2P) # 9. P = [x²-x-1]P + ψ²([2]P)
P.sum(P, xP) # 10. P = [x²-x-1]P + [x-1] ψ(P) + ψ²([2]P)

View File

@ -30,12 +30,11 @@ func ceilDiv(a, b: uint): uint =
## ceil(a / b)
(a + b - 1) div b
proc copyFrom[N](output: var openarray[byte], bi: array[N, byte], cur: var uint) =
var b_index = 0'u
while b_index < bi.len.uint and cur < output.len.uint:
output[cur] = bi[b_index]
inc cur
inc b_index
proc copyFrom[M, N: static int](output: var array[M, byte], bi: array[N, byte], cur: var uint) =
static: doAssert M mod N == 0
for i in 0'u ..< N:
output[cur+i] = bi[i]
cur += N.uint
template strxor(b_i: var array, b0: array): untyped =
for i in 0 ..< b_i.len:
@ -57,9 +56,9 @@ func shortDomainSepTag[DigestSize: static int, B: byte|char](
ctx.update oversizedDST
ctx.finish(output)
func expandMessageXMD*[B1, B2, B3: byte|char](
func expandMessageXMD*[B1, B2, B3: byte|char, len_in_bytes: static int](
H: type CryptoHash,
output: var openarray[byte],
output: var array[len_in_bytes, byte],
augmentation: openarray[B1],
message: openarray[B2],
domainSepTag: openarray[B3]
@ -110,23 +109,23 @@ func expandMessageXMD*[B1, B2, B3: byte|char](
const DigestSize = Hash.digestSize()
const BlockSize = Hash.internalBlockSize()
assert output.len mod 8 == 0
static:
doAssert output.len mod 8 == 0 # By spec
doAssert output.len mod 32 == 0 # Assumed by copy optimization
let ell = ceilDiv(output.len.uint, DigestSize.uint)
const zPad = default(array[BlockSize, byte])
let l_i_b_str = output.len.uint16.toBytesBE()
var l_i_b_str0 {.noInit.}: array[3, byte]
l_i_b_str0.asBytesBE(output.len.uint16, pos = 0)
l_i_b_str0[2] = 0
var b0 {.noinit, align: DigestSize.}: array[DigestSize, byte]
func ctZpad(): Hash =
# Compile-time precompute
# TODO upstream: `toOpenArray` throws "cannot generate code for: mSlice"
result.init()
result.update zPad
var ctx = ctZpad() # static(ctZpad())
var ctx {.noInit.}: Hash
ctx.initZeroPadded()
ctx.update augmentation
ctx.update message
ctx.update l_i_b_str
ctx.update [byte 0]
ctx.update l_i_b_str0
# ctx.update [byte 0] # already appended to l_i_b_str
ctx.update domainSepTag
ctx.update [byte domainSepTag.len] # DST_prime
ctx.finish(b0)

View File

@ -109,8 +109,7 @@ func invsqrt_if_square[C: static Curve](
t2 *= NonResidue
t1 -= t2
# TODO: implement invsqrt alone
result = sqrt_invsqrt_if_square(sqrt = r.c1, invsqrt = t3, t1) # 1/sqrt(a0² - β a1²)
result = t3.invsqrt_if_square(t1) # 1/sqrt(a0² - β a1²)
# If input is not a square in Fp2, multiply by 1/Z³
inp.prod(a, h2cConst(C, G2, inv_Z3)) # inp = a / Z³
@ -131,8 +130,7 @@ func invsqrt_if_square[C: static Curve](
t1.ccopy(t2, t1.isZero())
t1.div2() # (a0 ± sqrt(a0² - βa1²))/2
# TODO: implement invsqrt alone
sqrt_invsqrt(sqrt = r.c1, invsqrt = r.c0, t1)
r.c0.invsqrt(t1)
r.c1 = inp.c1
r.c1.div2()
@ -152,19 +150,22 @@ func invsqrt_if_square[C: static Curve](
func mapToIsoCurve_sswuG2_opt9mod16*[C: static Curve](
xn, xd, yn: var Fp2[C],
u: Fp2[C]) =
u: Fp2[C], xd3: var Fp2[C]) =
## Given G2, the target prime order subgroup of E2 we want to hash to,
## this function maps any field element of Fp2 to E'2
## a curve isogenous to E2 using the Simplified Shallue-van de Woestijne method.
##
## This requires p² ≡ 9 (mod 16).
#
# Input:
# - u, an Fp2 element
# Output:
# - (xn, xd, yn, yd) such that (x', y') = (xn/xd, yn/yd)
# is a point of E'2
# - yd is implied to be 1
##
## Input:
## - u, an Fp2 element
## Output:
## - (xn, xd, yn, yd) such that (x', y') = (xn/xd, yn/yd)
## is a point of E'2
## - yd is implied to be 1
## Scratchspace:
## - xd3 is temporary scratchspace that will hold xd³
## after execution (which might be useful for Jacobian coordinate conversion)
#
# Paper: https://eprint.iacr.org/2019/403
# Spec: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-G.2.3
@ -175,7 +176,6 @@ func mapToIsoCurve_sswuG2_opt9mod16*[C: static Curve](
var
uu {.noInit.}, tv2 {.noInit.}: Fp2[C]
tv4 {.noInit.}, x2n {.noInit.}, gx1 {.noInit.}: Fp2[C]
gxd {.noInit.}: Fp2[C]
y2 {.noInit.}: Fp2[C]
e1, e2: SecretBool
@ -184,6 +184,7 @@ func mapToIsoCurve_sswuG2_opt9mod16*[C: static Curve](
template x1n: untyped = xn
template y1: untyped = yn
template Zuu: untyped = x2n
template gxd: untyped = xd3
# x numerators
uu.square(u) # uu = u²
@ -203,7 +204,7 @@ func mapToIsoCurve_sswuG2_opt9mod16*[C: static Curve](
# y numerators
tv2.square(xd)
gxd.prod(xd, tv2) # gxd = xd³
tv2 *= h2CConst(C, G2, Aprime_E2)
tv2.mulCheckSparse(h2CConst(C, G2, Aprime_E2))
gx1.square(x1n)
gx1 += tv2 # x1n² + A * xd²
gx1 *= x1n # x1n³ + A * x1n * xd²

View File

@ -11,7 +11,7 @@ import
../config/[common, curves],
../primitives, ../arithmetic, ../towers,
../curves/zoo_hash_to_curve,
../elliptic/ec_shortweierstrass_projective,
../ec_shortweierstrass,
./h2c_hash_to_field,
./h2c_map_to_isocurve_swu,
./cofactors,
@ -34,8 +34,28 @@ import
# Map to curve
# ----------------------------------------------------------------
func mapToIsoCurve_sswuG2_opt9mod16[F; Tw: static Twisted](
r: var ECP_ShortW_Jac[F, Tw],
u: F) =
var
xn{.noInit.}, xd{.noInit.}: F
yn{.noInit.}: F
xd3{.noInit.}: F
mapToIsoCurve_sswuG2_opt9mod16(
xn, xd,
yn,
u, xd3
)
# Convert to Jacobian
r.z = xd # Z = xd
r.x.prod(xn, xd) # X = xZ² = xn/xd * xd² = xn*xd
r.y.prod(yn, xd3) # Y = yZ³ = yn * xd³
func mapToCurve[F; Tw: static Twisted](
r: var ECP_ShortW_Prj[F, Tw], u: F) =
r: var (ECP_ShortW_Prj[F, Tw] or ECP_ShortW_Jac[F, Tw]),
u: F) =
## Map an element of the
## finite or extension field F
## to an elliptic curve E
@ -48,11 +68,12 @@ func mapToCurve[F; Tw: static Twisted](
var
xn{.noInit.}, xd{.noInit.}: F
yn{.noInit.}: F
xd3{.noInit.}: F
mapToIsoCurve_sswuG2_opt9mod16(
xn, xd,
yn,
u
u, xd3
)
# 2. Map from E'2 to E2
@ -64,6 +85,39 @@ func mapToCurve[F; Tw: static Twisted](
else:
{.error: "Not implemented".}
func mapToCurve_fusedAdd[F; Tw: static Twisted](
r: var ECP_ShortW_Jac[F, Tw],
u0, u1: F) =
## Map 2 elements of the
## finite or extension field F
## to an elliptic curve E
## and add them
# Optimization suggested in https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-6.6.3
# Note that iso_map is a group homomorphism, meaning that point
# addition commutes with iso_map. Thus, when using this mapping in the
# hash_to_curve construction of Section 3, one can effect a small
# optimization by first mapping u0 and u1 to E', adding the resulting
# points on E', and then applying iso_map to the sum. This gives the
# same result while requiring only one evaluation of iso_map.
# Jacobian formulae are independent of the curve equation B'
# y² = x³ + A'*x + B'
# unlike the complete projective formulae which heavily depends on it
# So we use jacobian coordinates for computation on isogenies.
var P0{.noInit.}, P1{.noInit.}: ECP_ShortW_Jac[F, Tw]
when F.C == BLS12_381 and F is Fp2:
# 1. Map to E'2 isogenous to E2
P0.mapToIsoCurve_sswuG2_opt9mod16(u0)
P1.mapToIsoCurve_sswuG2_opt9mod16(u1)
P0.sum(P0, P1, h2CConst(F.C, G2, Aprime_E2))
# 2. Map from E'2 to E2
r.h2c_isogeny_map(P0, isodegree = 3)
else:
{.error: "Not implemented".}
# Hash to curve
# ----------------------------------------------------------------
@ -102,9 +156,14 @@ func hashToCurve*[
var u{.noInit.}: array[2, F]
H.hashToField(k, u, augmentation, message, domainSepTag)
var P{.noInit.}: array[2, ECP_ShortW_Prj[F, Tw]]
P[0].mapToCurve(u[0])
P[1].mapToCurve(u[1])
when false:
var P{.noInit.}: array[2, ECP_ShortW_Prj[F, Tw]]
P[0].mapToCurve(u[0])
P[1].mapToCurve(u[1])
output.sum(P[0], P[1])
else:
var Pjac{.noInit.}: ECP_ShortW_Jac[F, Tw]
Pjac.mapToCurve_fusedAdd(u[0], u[1])
output.projectiveFromJacobian(Pjac)
output.sum(P[0], P[1])
output.clearCofactorReference() # TODO - fast cofactor clear
output.clearCofactorFast()

View File

@ -315,14 +315,37 @@ func init*(ctx: var Sha256Context) =
ctx.buf.setZero()
ctx.bufIdx = 0
ctx.H[0] = 0x6a09e667'u32;
ctx.H[1] = 0xbb67ae85'u32;
ctx.H[2] = 0x3c6ef372'u32;
ctx.H[3] = 0xa54ff53a'u32;
ctx.H[4] = 0x510e527f'u32;
ctx.H[5] = 0x9b05688c'u32;
ctx.H[6] = 0x1f83d9ab'u32;
ctx.H[7] = 0x5be0cd19'u32;
ctx.H[0] = 0x6a09e667'u32
ctx.H[1] = 0xbb67ae85'u32
ctx.H[2] = 0x3c6ef372'u32
ctx.H[3] = 0xa54ff53a'u32
ctx.H[4] = 0x510e527f'u32
ctx.H[5] = 0x9b05688c'u32
ctx.H[6] = 0x1f83d9ab'u32
ctx.H[7] = 0x5be0cd19'u32
func initZeroPadded*(ctx: var Sha256Context) =
## Initialize a Sha256 context
## with the result of
## ctx.init()
## ctx.update default(array[BlockSize, byte])
#
# This work arounds `toOpenArray`
# not working in the Nim VM, preventing `sha256.update`
# at compile-time
ctx.msgLen = 64
ctx.buf.setZero()
ctx.bufIdx = 0
ctx.H[0] = 0xda5698be'u32
ctx.H[1] = 0x17b9b469'u32
ctx.H[2] = 0x62335799'u32
ctx.H[3] = 0x779fbeca'u32
ctx.H[4] = 0x8ce5d491'u32
ctx.H[5] = 0xc0d26243'u32
ctx.H[6] = 0xbafef9ea'u32
ctx.H[7] = 0x1837a9d8'u32
func update*[T: char|byte](ctx: var Sha256Context, message: openarray[T]) =
## Append a message to a SHA256 context

View File

@ -77,8 +77,13 @@ func dumpRawInt*[T: byte|char](
for i in 0'u ..< L:
dst[cursor+i] = toByte(src shr ((L-i-1) * 8))
func toBytesBE*(num: SomeUnsignedInt): array[sizeof(num), byte] {.inline.}=
## Convert an integer to an array of bytes
func asBytesBE*[N: static int](
r: var array[N, byte],
num: SomeUnsignedInt,
pos: static int) {.inline.}=
## Store an integer into an array of bytes
## in big endian representation
const L = sizeof(num)
static: doAssert N - (pos + L) >= 0
for i in 0 ..< L:
result[i] = toByte(num shr ((L-1-i) * 8))
r[i] = toByte(num shr ((L-1-i) * 8))

View File

@ -10,7 +10,10 @@ import
# Internals
../primitives, ../arithmetic, ../towers,
../curves/zoo_hash_to_curve,
../elliptic/ec_shortweierstrass_projective
../elliptic/[
ec_shortweierstrass_projective,
ec_shortweierstrass_jacobian,
]
# ############################################################
#
@ -78,6 +81,51 @@ func poly_eval_horner_scaled[F; D, N: static int](
# Missing scaling factor
r *= xd_pow[isodegree - poly_degree - 1]
func h2c_isogeny_map[F](
rxn, rxd, ryn, ryd: var F,
xn, xd, yn: F, isodegree: static int) =
## Given G2, the target prime order subgroup of E2,
## this function maps an element of
## E'2 a curve isogenous to E2
## to E2.
##
## The E'2 input is represented as
## (x', y') with x' = xn/xd and y' = yn/yd
##
## yd is assumed to be 1 hence y' == yn
##
## The E2 output is represented as
## (rx, ry) with rx = rxn/rxd and ry = ryn/ryd
# xd^e with e in [1, N], for example [xd, xd², xd³]
static: doAssert isodegree >= 2
var xd_pow{.noInit.}: array[isodegree, F]
xd_pow[0] = xd
xd_pow[1].square(xd_pow[0])
for i in 2 ..< xd_pow.len:
xd_pow[i].prod(xd_pow[i-1], xd_pow[0])
rxn.poly_eval_horner_scaled(
xn, xd_pow,
h2cIsomapPoly(F.C, G2, isodegree, xnum)
)
rxd.poly_eval_horner_scaled(
xn, xd_pow,
h2cIsomapPoly(F.C, G2, isodegree, xden)
)
ryn.poly_eval_horner_scaled(
xn, xd_pow,
h2cIsomapPoly(F.C, G2, isodegree, ynum)
)
ryd.poly_eval_horner_scaled(
xn, xd_pow,
h2cIsomapPoly(F.C, G2, isodegree, yden)
)
# y coordinate is y' * poly_yNum(x)
ryn *= yn
func h2c_isogeny_map*[F; Tw: static Twisted](
r: var ECP_ShortW_Prj[F, Tw],
xn, xd, yn: F, isodegree: static int) =
@ -95,33 +143,15 @@ func h2c_isogeny_map*[F; Tw: static Twisted](
## - https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-6.6.3
## - https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve
# xd^e with e in [1, N], for example [xd, xd², xd³]
var xd_pow: array[isodegree, F]
xd_pow[0] = xd
for i in 1 ..< xd_pow.len:
xd_pow[i].prod(xd_pow[i-1], xd)
var t{.noInit.}: F
r.x.poly_eval_horner_scaled(
xn, xd_pow,
h2cIsomapPoly(F.C, G2, isodegree, xnum)
h2c_isogeny_map(
rxn = r.x,
rxd = r.z,
ryn = r.y,
ryd = t,
xn, xd, yn, isodegree
)
r.z.poly_eval_horner_scaled(
xn, xd_pow,
h2cIsomapPoly(F.C, G2, isodegree, xden)
)
r.y.poly_eval_horner_scaled(
xn, xd_pow,
h2cIsomapPoly(F.C, G2, isodegree, ynum)
)
var t {.noInit.}: F
t.poly_eval_horner_scaled(
xn, xd_pow,
h2cIsomapPoly(F.C, G2, isodegree, yden)
)
# y coordinate is y' * poly_yNum(x)
r.y *= yn
# Now convert to projective coordinates
# (x, y) => (xnum/xden, ynum/yden)
@ -129,3 +159,103 @@ func h2c_isogeny_map*[F; Tw: static Twisted](
r.y *= r.z
r.x *= t
r.z *= t
func h2c_isogeny_map*[F; Tw: static Twisted](
r: var ECP_ShortW_Jac[F, Tw],
xn, xd, yn: F, isodegree: static int) =
## Given G2, the target prime order subgroup of E2,
## this function maps an element of
## E'2 a curve isogenous to E2
## to E2.
##
## The E'2 input is represented as
## (x', y') with x' = xn/xd and y' = yn/yd
##
## yd is assumed to be 1 hence y' == yn
##
## Reference:
## - https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-6.6.3
## - https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve
var rxn{.noInit.}, rxd{.noInit.}: F
var ryn{.noInit.}, ryd{.noInit.}: F
h2c_isogeny_map(
rxn, rxd,
ryn, ryd,
xn, xd, yn, isodegree
)
# Now convert to jacobian coordinates
# (x, y) => (xnum/xden, ynum/yden)
# <=> (xZ², yZ³, Z)
# <=> (xnum*xden*yden², ynum*yden²*xden³, xden*yden)
r.z.prod(rxd, ryd) # Z = xn * xd
r.x.prod(rxn, ryd) # X = xn * yd
r.x *= r.z # X = xn * xd * yd²
r.y.square(r.z) # Y = xd² * yd²
r.y *= rdx # Y = yd² * xd³
r.y *= ryn # Y = yn * yd² * xd³
func h2c_isogeny_map*[F; Tw: static Twisted](
r: var ECP_ShortW_Jac[F, Tw],
P: ECP_ShortW_Jac[F, Tw],
isodegree: static int) =
## Map P in isogenous curve E'2
## to r in E2
##
## r and P are NOT on the same curve.
#
# We have in affine <=> jacobian representation
# (x, y) <=> (xn/xd, yn/yd)
# <=> (xZ², yZ³, Z)
#
# We scale the isogeny coefficients by powers
# of Z²
var xn{.noInit.}, xd{.noInit.}: F
var yn{.noInit.}, yd{.noInit.}: F
# Z²^e with e in [1, N], for example [Z², Z⁴, Z⁶]
static: doAssert isodegree >= 2
var ZZpow{.noInit.}: array[isodegree, F]
ZZpow[0].square(P.z)
ZZpow[1].square(ZZpow[0])
for i in 2 ..< ZZpow.len:
ZZpow[i].prod(ZZpow[i-1], ZZpow[0])
xn.poly_eval_horner_scaled(
P.x, ZZpow,
h2cIsomapPoly(F.C, G2, isodegree, xnum)
)
xd.poly_eval_horner_scaled(
P.x, ZZpow,
h2cIsomapPoly(F.C, G2, isodegree, xden)
)
yn.poly_eval_horner_scaled(
P.x, ZZpow,
h2cIsomapPoly(F.C, G2, isodegree, ynum)
)
yd.poly_eval_horner_scaled(
P.x, ZZpow,
h2cIsomapPoly(F.C, G2, isodegree, yden)
)
# yn = y' * poly_yNum(x) = yZ³ * poly_yNum(x)
yn *= P.y
# Scale yd by Z³
yd *= P.z
yd *= ZZpow[0]
# Now convert to jacobian coordinates
# (x, y) => (xnum/xden, ynum/yden)
# <=> (xZ², yZ³, Z)
# <=> (xnum*xden*yden², ynum*yden²*xden³, xden*yden)
r.z.prod(xd, yd) # Z = xn * xd
r.x.prod(xn, yd) # X = xn * yd
r.x *= r.z # X = xn * xd * yd²
r.y.square(r.z) # Y = xd² * yd²
r.y *= xd # Y = yd² * xd³
r.y *= yn # Y = yn * yd² * xd³

View File

@ -133,10 +133,8 @@ func sqrt_if_square_opt(a: var Fp2): SecretBool =
t1.ccopy(t2, t1.isZero())
t1.div2() # (a0 ± sqrt(a0² - β a1²))/2
# TODO: implement invsqrt alone
# t1 being an actual sqrt will be tested in sqrt_rotate_extension
# 1/sqrt((a0 ± sqrt(a0² - β b²))/2)
sqrt_invsqrt(sqrt = cand.c1, invsqrt = cand.c0, t1) # we only want invsqrt = cand.c0
cand.c0.invsqrt(t1) # 1/sqrt((a0 ± sqrt(a0² - β b²))/2)
cand.c1 = a.c1
cand.c1.div2()

View File

@ -181,7 +181,7 @@ def genBLS12381G2_H2C_constants(curve_config):
buf += field_to_nim(Z, 'Fp2', curve_name, comment_right = "-(2 + 𝑖)")
buf += '\n'
buf += f'const {curve_name}_h2c_G2_minusA* = '
buf += f'const {curve_name}_h2c_G2_minus_A* = '
buf += field_to_nim(minus_A, 'Fp2', curve_name, comment_right = "-240𝑖")
buf += '\n'

View File

@ -80,7 +80,6 @@ proc exhaustiveCheck(C: static Curve, modulus: static int) =
check:
bool not a.isSquare()
bool not a.sqrt_if_square()
bool (a == a2) # a shouldn't be modified
template testImpl(a: untyped): untyped {.dirty.} =
var na{.noInit.}: typeof(a)

View File

@ -61,7 +61,7 @@ proc runFrobeniusTowerTests*[N](
) =
# Random seed for reproducibility
var rng: RngState
let seed = 0 # uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
rng.seed(seed)
echo moduleName, " xoshiro512** seed: ", seed