C API for Ethereum BLS signatures (#228)

* [testsuite] Rework parallel test runner to buffer beyond 65536 chars and properly wait for process exit

* [testsuite] improve error reporting

* rework openArray[byte/char] for BLS signature C API

* Prepare for optimized library and bindings

* properly link to constantine

* Compiler fixes, global sanitizers, GCC bug with --opt:size

* workaround/fix #229: don't inline field reduction in Fp2

* fix clang running out of registers with LTO

* [C API] missed length parameters for ctt_eth_bls_fast_aggregate_verify

* double-precision asm is too large for inlining, try to fix Linux and MacOS woes at https://github.com/mratsim/constantine/pull/228#issuecomment-1512773460

* Use FORTIFY_SOURCE for testing

* Fix #230 - gcc miscompiles Fp6 mul with LTO

* disable LTO for now, PR is too long
This commit is contained in:
Mamy Ratsimbazafy 2023-04-18 22:02:23 +02:00 committed by GitHub
parent 93dac2503c
commit 9a7137466e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 1693 additions and 858 deletions

View File

@ -32,7 +32,7 @@ const AvailableCurves = [
]
# const testNumPoints = [10, 100, 1000, 10000, 100000]
const testNumPoints = [8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192,
const testNumPoints = [2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192,
16384, 32768, 65536, 131072, 262144]
proc main() =

View File

@ -44,7 +44,7 @@ proc main() =
separator()
staticFor i, 0, AvailableCurves.len:
const curve = AvailableCurves[i]
const bits = 64 # curve.getCurveOrderBitwidth()
const bits = curve.getCurveOrderBitwidth()
scalarMulUnsafeDoubleAddBench(ECP_ShortW_Prj[Fp[curve], G1], bits, MulIters)
scalarMulUnsafeDoubleAddBench(ECP_ShortW_Jac[Fp[curve], G1], bits, MulIters)
separator()

View File

@ -45,7 +45,7 @@ proc main() =
separator()
staticFor i, 0, AvailableCurves.len:
const curve = AvailableCurves[i]
const bits = 64 # curve.getCurveOrderBitwidth()
const bits = curve.getCurveOrderBitwidth()
scalarMulUnsafeDoubleAddBench(ECP_ShortW_Prj[Fp2[curve], G2], bits, MulIters)
scalarMulUnsafeDoubleAddBench(ECP_ShortW_Jac[Fp2[curve], G2], bits, MulIters)
separator()

View File

@ -9,7 +9,7 @@
import
# Internals
../constantine/[
blssig_pop_on_bls12381_g2,
ethereum_bls_signatures,
ethereum_eip2333_bls12381_key_derivation],
../constantine/math/arithmetic,
# Helpers
@ -33,10 +33,10 @@ template bench(op: string, curve: string, iters: int, body: untyped): untyped =
proc demoKeyGen(): tuple[seckey: SecretKey, pubkey: PublicKey] =
# Don't do this at home, this is for benchmarking purposes
# The RNG is NOT cryptographically secure
# The API for keygen is not ready in blssig_pop_on_bls12381_g2
# The API for keygen is not ready in ethereum_bls_signatures
let ikm = rng.random_byte_seq(32)
doAssert cast[ptr BigInt[255]](result.seckey.addr)[].derive_master_secretKey(ikm)
let ok = result.pubkey.derive_public_key(result.seckey)
let ok = result.pubkey.derive_pubkey(result.seckey)
doAssert ok == cttBLS_Success
proc benchDeserPubkey*(iters: int) =
@ -44,26 +44,26 @@ proc benchDeserPubkey*(iters: int) =
var pk_comp{.noInit.}: array[48, byte]
# Serialize compressed
let ok = pk_comp.serialize_public_key_compressed(pk)
let ok = pk_comp.serialize_pubkey_compressed(pk)
doAssert ok == cttBLS_Success
var pk2{.noInit.}: PublicKey
bench("Pubkey deserialization (full checks)", "BLS12_381 G1", iters):
let status = pk2.deserialize_public_key_compressed(pk_comp)
let status = pk2.deserialize_pubkey_compressed(pk_comp)
proc benchDeserPubkeyUnchecked*(iters: int) =
let (sk, pk) = demoKeyGen()
var pk_comp{.noInit.}: array[48, byte]
# Serialize compressed
let ok = pk_comp.serialize_public_key_compressed(pk)
let ok = pk_comp.serialize_pubkey_compressed(pk)
doAssert ok == cttBLS_Success
var pk2{.noInit.}: PublicKey
bench("Pubkey deserialization (skip checks)", "BLS12_381 G1", iters):
let status = pk2.deserialize_public_key_compressed_unchecked(pk_comp)
let status = pk2.deserialize_pubkey_compressed_unchecked(pk_comp)
proc benchDeserSig*(iters: int) =
let (sk, pk) = demoKeyGen()
@ -139,7 +139,7 @@ proc benchFastAggregateVerify*(numKeys, iters: int) =
let status = sigs[i].sign(sk, msg)
doAssert status == cttBLS_Success
aggSig.aggregate_signatures(sigs)
aggSig.aggregate_signatures_unstable_api(sigs)
bench("BLS agg verif of 1 msg by " & $numKeys & " pubkeys", "BLS12_381", iters):
let valid = validators.fast_aggregate_verify(msg, aggSig)

View File

@ -18,7 +18,10 @@ export curves, curves_primitives
# This files provides template for C bindings generation
template genBindingsField*(Field: untyped) =
when appType == "lib":
{.push cdecl, dynlib, exportc, raises: [].} # No exceptions allowed
else:
{.push cdecl, exportc, raises: [].} # No exceptions allowed
func `ctt _ Field _ unmarshalBE`(dst: var Field, src: openarray[byte]) =
## Deserialize
@ -118,7 +121,10 @@ template genBindingsField*(Field: untyped) =
template genBindingsFieldSqrt*(Field: untyped) =
when appType == "lib":
{.push cdecl, dynlib, exportc, raises: [].} # No exceptions allowed
else:
{.push cdecl, exportc, raises: [].} # No exceptions allowed
func `ctt _ Field _ is_square`(a: Field): SecretBool =
a.isSquare()
@ -148,7 +154,10 @@ template genBindingsFieldSqrt*(Field: untyped) =
template genBindingsExtField*(Field: untyped) =
when appType == "lib":
{.push cdecl, dynlib, exportc, raises: [].} # No exceptions allowed
else:
{.push cdecl, exportc, raises: [].} # No exceptions allowed
# --------------------------------------------------------------------------------------
func `ctt _ Field _ is_eq`(a, b: Field): SecretBool =
@ -248,7 +257,10 @@ template genBindingsExtField*(Field: untyped) =
{.pop.}
template genBindingsExtFieldSqrt*(Field: untyped) =
when appType == "lib":
{.push cdecl, dynlib, exportc, raises: [].} # No exceptions allowed
else:
{.push cdecl, exportc, raises: [].} # No exceptions allowed
func `ctt _ Field _ is_square`(a: Field): SecretBool =
a.isSquare()
@ -262,7 +274,10 @@ template genBindingsExtFieldSqrt*(Field: untyped) =
{.pop}
template genBindings_EC_ShortW_Affine*(ECP, Field: untyped) =
when appType == "lib":
{.push cdecl, dynlib, exportc, raises: [].} # No exceptions allowed
else:
{.push cdecl, exportc, raises: [].} # No exceptions allowed
# --------------------------------------------------------------------------------------
func `ctt _ ECP _ is_eq`(P, Q: ECP): SecretBool =
@ -289,7 +304,10 @@ template genBindings_EC_ShortW_Affine*(ECP, Field: untyped) =
{.pop.}
template genBindings_EC_ShortW_NonAffine*(ECP, ECP_Aff, Field: untyped) =
when appType == "lib":
{.push cdecl, dynlib, exportc, raises: [].} # No exceptions allowed
else:
{.push cdecl, exportc, raises: [].} # No exceptions allowed
# --------------------------------------------------------------------------------------
func `ctt _ ECP _ is_eq`(P, Q: ECP): SecretBool =

View File

@ -16,8 +16,7 @@ import
proc genHeaderLicense*(): string =
"""
/*
* Constantine
/** Constantine
* Copyright (c) 2018-2019 Status Research & Development GmbH
* Copyright (c) 2020-Present Mamy André-Ratsimbazafy
* Licensed and distributed under either of

View File

@ -7,7 +7,185 @@ license = "MIT or Apache License 2.0"
# Dependencies
# ----------------------------------------------------------------
requires "nim >= 1.1.0"
requires "nim >= 1.6.12"
# Nimscript imports
# ----------------------------------------------------------------
import std/strformat
# Library compilation
# ----------------------------------------------------------------
proc releaseBuildOptions: string =
# -d:danger --opt:size
# to avoid boundsCheck and overflowChecks that would trigger exceptions or allocations in a crypto library.
# Those are internally guaranteed at compile-time by fixed-sized array
# and checked at runtime with an appropriate error code if any for user-input.
#
# Furthermore we optimize for size, the performance critical procedures
# either use assembly or are unrolled manually with staticFor,
# Optimizations at -O3 deal with loops and branching
# which we mostly don't have. It's better to optimize
# for instructions cache.
#
# --panics:on -d:noSignalHandler
# Even with `raises: []`, Nim still has an exception path
# for defects, for example array out-of-bound accesses (though deactivated with -d:danger)
# This turns them into panics, removing exceptiosn from the library.
# We also remove signal handlers as it's not our business.
#
# -mm:arc -d:useMalloc
# Constantine stack allocates everything (except for multithreading).
# Inputs are through unmanaged ptr+len. So we don't want any runtime.
# Combined with -d:useMalloc, sanitizers and valgrind work as in C,
# even for test cases that needs to allocate (json inputs).
#
# -fno-semantic-interposition
# https://fedoraproject.org/wiki/Changes/PythonNoSemanticInterpositionSpeedup
# Default in Clang, not default in GCC, prevents optimizations, not portable to non-Linux.
# Also disabling this prevents overriding symbols which might actually be wanted in a cryptographic library
#
# -falign-functions=64
# Reduce instructions cache misses.
# https://lkml.org/lkml/2015/5/21/443
# Our non-inlined functions are large so size cost is minimal.
" -d:danger --opt:size " &
" --panics:on -d:noSignalHandler " &
" --mm:arc -d:useMalloc " &
" --verbosity:0 --hints:off --warnings:off " &
# " --passC:-flto --passL:-flto " &
" --passC:-fno-semantic-interposition " &
" --passC:-falign-functions=64 "
type BindingsKind = enum
kCurve
kProtocol
proc genDynamicBindings(bindingsKind: BindingsKind, bindingsName, prefixNimMain: string) =
proc compile(libName: string, flags = "") =
echo "Compiling dynamic library: lib/" & libName
exec "nim c " &
" --noMain --app:lib " &
flags &
releaseBuildOptions() &
&" --nimMainPrefix:{prefixNimMain} " &
&" --out:{libName} --outdir:lib " &
(block:
case bindingsKind
of kCurve:
&" --nimcache:nimcache/bindings_curves/{bindingsName}" &
&" bindings_generators/{bindingsName}.nim"
of kProtocol:
&" --nimcache:nimcache/bindings_protocols/{bindingsName}" &
&" constantine/{bindingsName}.nim")
let bindingsName = block:
case bindingsKind
of kCurve: bindingsName
of kProtocol: "constantine_" & bindingsName
when defined(windows):
compile bindingsName & ".dll"
elif defined(macosx):
compile "lib" & bindingsName & ".dylib.arm", "--cpu:arm64 -l:'-target arm64-apple-macos11' -t:'-target arm64-apple-macos11'"
compile "lib" & bindingsName & ".dylib.x64", "--cpu:amd64 -l:'-target x86_64-apple-macos10.12' -t:'-target x86_64-apple-macos10.12'"
exec "lipo lib/lib" & bindingsName & ".dylib.arm " &
" lib/lib" & bindingsName & ".dylib.x64 " &
" -output lib/lib" & bindingsName & ".dylib -create"
else:
compile "lib" & bindingsName & ".so"
proc genStaticBindings(bindingsKind: BindingsKind, bindingsName, prefixNimMain: string) =
proc compile(libName: string, flags = "") =
echo "Compiling static library: lib/" & libName
exec "nim c " &
" --noMain --app:staticLib " &
flags &
releaseBuildOptions() &
" --nimMainPrefix:" & prefixNimMain &
" --out:" & libName & " --outdir:lib " &
(block:
case bindingsKind
of kCurve:
" --nimcache:nimcache/bindings_curves/" & bindingsName &
" bindings_generators/" & bindingsName & ".nim"
of kProtocol:
" --nimcache:nimcache/bindings_protocols/" & bindingsName &
" constantine/" & bindingsName & ".nim"
)
let bindingsName = block:
case bindingsKind
of kCurve: bindingsName
of kProtocol: "constantine_" & bindingsName
when defined(windows):
compile bindingsName & ".lib"
elif defined(macosx):
compile "lib" & bindingsName & ".a.arm", "--cpu:arm64 -l:'-target arm64-apple-macos11' -t:'-target arm64-apple-macos11'"
compile "lib" & bindingsName & ".a.x64", "--cpu:amd64 -l:'-target x86_64-apple-macos10.12' -t:'-target x86_64-apple-macos10.12'"
exec "lipo lib/lib" & bindingsName & ".a.arm " &
" lib/lib" & bindingsName & ".a.x64 " &
" -output lib/lib" & bindingsName & ".a -create"
else:
compile "lib" & bindingsName & ".a"
proc genHeaders(bindingsName: string) =
echo "Generating header: include/" & bindingsName & ".h"
exec "nim c -d:CttGenerateHeaders " &
releaseBuildOptions() &
" --out:" & bindingsName & "_gen_header.exe --outdir:build " &
" --nimcache:nimcache/bindings_curves_headers/" & bindingsName & "_header" &
" bindings_generators/" & bindingsName & ".nim"
exec "build/" & bindingsName & "_gen_header.exe include"
task bindings, "Generate Constantine bindings":
# Curve arithmetic
genStaticBindings(kCurve, "constantine_bls12_381", "ctt_bls12381_init_")
genDynamicBindings(kCurve, "constantine_bls12_381", "ctt_bls12381_init_")
genHeaders("constantine_bls12_381")
echo ""
genStaticBindings(kCurve, "constantine_pasta", "ctt_pasta_init_")
genDynamicBindings(kCurve, "constantine_pasta", "ctt_pasta_init_")
genHeaders("constantine_pasta")
echo ""
# Protocols
genStaticBindings(kProtocol, "ethereum_bls_signatures", "ctt_eth_bls_init_")
genDynamicBindings(kProtocol, "ethereum_bls_signatures", "ctt_eth_bls_init_")
proc testLib(path, testName, libName: string, useGMP: bool) =
let dynlibName = if defined(windows): libName & ".dll"
elif defined(macosx): "lib" & libName & ".dylib"
else: "lib" & libName & ".so"
let staticlibName = if defined(windows): libName & ".lib"
else: "lib" & libName & ".a"
echo &"\n[Bindings: {path}/{testName}.c] Testing dynamically linked library {dynlibName}"
exec &"gcc -Iinclude -Llib -o build/testbindings/{testName}_dynlink.exe {path}/{testName}.c -l{libName} " & (if useGMP: "-lgmp" else: "")
when defined(windows):
# Put DLL near the exe as LD_LIBRARY_PATH doesn't work even in a POSIX compatible shell
exec &"./build/testbindings/{testName}_dynlink.exe"
else:
exec &"LD_LIBRARY_PATH=lib ./build/testbindings/{testName}_dynlink.exe"
echo &"\n[Bindings: {path}/{testName}.c] Testing statically linked library: {staticlibName}"
# Beware MacOS annoying linker with regards to static libraries
# The following standard way cannot be used on MacOS
# exec "gcc -Iinclude -Llib -o build/t_libctt_bls12_381_sl.exe examples_c/t_libctt_bls12_381.c -lgmp -Wl,-Bstatic -lconstantine_bls12_381 -Wl,-Bdynamic"
exec &"gcc -Iinclude -o build/testbindings/{testName}_staticlink.exe {path}/{testName}.c lib/{staticlibName} " & (if useGMP: "-lgmp" else: "")
exec &"./build/testbindings/{testName}_staticlink.exe"
task test_bindings, "Test C bindings":
exec "mkdir -p build/testbindings"
testLib("examples_c", "t_libctt_bls12_381", "constantine_bls12_381", useGMP = true)
testLib("examples_c", "ethereum_bls_signatures", "constantine_ethereum_bls_signatures", useGMP = false)
# Test config
# ----------------------------------------------------------------
@ -232,7 +410,7 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[
# Protocols
# ----------------------------------------------------------
("tests/t_ethereum_evm_precompiles.nim", false),
("tests/t_blssig_pop_on_bls12381_g2.nim", false),
("tests/t_ethereum_bls_signatures.nim", false),
("tests/t_ethereum_eip2333_bls12381_key_derivation.nim", false),
]
@ -291,7 +469,7 @@ const benchDesc = [
"bench_poly1305",
"bench_sha256",
"bench_hash_to_curve",
"bench_blssig_on_bls12_381_g2"
"bench_ethereum_bls_signatures"
]
# For temporary (hopefully) investigation that can only be reproduced in CI
@ -300,22 +478,9 @@ const useDebug = [
"tests/math/t_hash_sha256_vs_openssl.nim",
]
# Tests that uses sequences require Nim GC, stack scanning and nil pointer passed to openarray
# In particular the tests that uses the json test vectors, don't sanitize them.
# we do use gc:none to help
# Skip sanitizers for specific tests
const skipSanitizers = [
"tests/math/t_ec_sage_bn254_nogami.nim",
"tests/math/t_ec_sage_bn254_snarks.nim",
"tests/math/t_ec_sage_bls12_377.nim",
"tests/math/t_ec_sage_bls12_381.nim",
"tests/t_blssig_pop_on_bls12381_g2.nim",
"tests/t_hash_to_field.nim",
"tests/t_hash_to_curve.nim",
"tests/t_hash_to_curve_random.nim",
"tests/t_mac_poly1305.nim",
"tests/t_mac_hmac.nim",
"tests/t_kdf_hkdf.nim",
"tests/t_ethereum_eip2333_bls12381_key_derivation.nim"
"tests/t_"
]
when defined(windows):
@ -323,13 +488,19 @@ when defined(windows):
const sanitizers = ""
else:
const sanitizers =
" --passC:-fsanitize=undefined --passL:-fsanitize=undefined" &
" --passC:-fno-sanitize-recover" & # Enforce crash on undefined behaviour
" --gc:none" # The conservative stack scanning of Nim default GC triggers, alignment UB and stack-buffer-overflow check.
# " --passC:-fsanitize=address --passL:-fsanitize=address" & # Requires too much stack for the inline assembly
# Sanitizers are incompatible with nim default GC
# The conservative stack scanning of Nim default GC triggers, alignment UB and stack-buffer-overflow check.
# Address sanitizer requires free registers and needs to be disabled for some inline assembly files.
# Ensure you use --mm:arc -d:useMalloc
#
# Sanitizers are deactivated by default as they slow down CI by at least 6x
# " --passC:-fsanitize=undefined --passL:-fsanitize=undefined" &
# " --passC:-fsanitize=address --passL:-fsanitize=address" &
" --passC:-fno-sanitize-recover" # Enforce crash on undefined behaviour
# Helper functions
# Tests & Benchmarks helper functions
# ----------------------------------------------------------------
proc clearParallelBuild() =
@ -337,7 +508,7 @@ proc clearParallelBuild() =
if fileExists(buildParallel):
rmFile(buildParallel)
template setupCommand(): untyped {.dirty.} =
template setupTestCommand(): untyped {.dirty.} =
var lang = "c"
if existsEnv"TEST_LANG":
lang = getEnv"TEST_LANG"
@ -349,10 +520,12 @@ template setupCommand(): untyped {.dirty.} =
var flags = flags
when not defined(windows):
# Not available in MinGW https://github.com/libressl-portable/portable/issues/54
flags &= " --passC:-fstack-protector-strong"
let command = "nim " & lang & cc & " -d:release " & flags &
" --panics:on " & # Defects are not catchable
" --verbosity:0 --outdir:build/testsuite -r --hints:off --warnings:off " &
flags &= " --passC:-fstack-protector-strong --passC:-D_FORTIFY_SOURCE=2 "
let command = "nim " & lang & cc &
" -r " &
flags &
releaseBuildOptions() &
" --outdir:build/testsuite " &
" --nimcache:nimcache/" & path & " " &
path
@ -363,7 +536,7 @@ proc test(cmd: string) =
exec cmd
proc testBatch(commands: var string, flags, path: string) =
setupCommand()
setupTestCommand()
commands &= command & '\n'
template setupBench(): untyped {.dirty.} =
@ -383,10 +556,10 @@ template setupBench(): untyped {.dirty.} =
if not useAsm:
cc &= " -d:CttASM=false"
let command = "nim " & lang & cc &
" --panics:on " & # Defects are not catchable
" -d:danger --verbosity:0 -o:build/bench/" & benchName & "_" & compiler & "_" & (if useAsm: "useASM" else: "noASM") &
releaseBuildOptions() &
" -o:build/bench/" & benchName & "_" & compiler & "_" & (if useAsm: "useASM" else: "noASM") &
" --nimcache:nimcache/benches/" & benchName & "_" & compiler & "_" & (if useAsm: "useASM" else: "noASM") &
runFlag & "--hints:off --warnings:off benchmarks/" & benchName & ".nim"
runFlag & " benchmarks/" & benchName & ".nim"
proc runBench(benchName: string, compiler = "", useAsm = true) =
if not dirExists "build":
@ -425,8 +598,11 @@ proc addTestSetNvidia(cmdFile: var string) =
mkDir "build"
echo "Found " & $testDescNvidia.len & " tests to run."
for path in testDescNvidia:
cmdFile.testBatch(flags = "", path)
for path in testDescThreadpool:
var flags = ""
if path notin skipSanitizers:
flags &= sanitizers
cmdFile.testBatch(flags, path)
proc addTestSetThreadpool(cmdFile: var string) =
if not dirExists "build":
@ -434,7 +610,10 @@ proc addTestSetThreadpool(cmdFile: var string) =
echo "Found " & $testDescThreadpool.len & " tests to run."
for path in testDescThreadpool:
cmdFile.testBatch(flags = "--threads:on --linetrace:on --debugger:native", path)
var flags = " --threads:on --debugger:native "
if path notin skipSanitizers:
flags &= sanitizers
cmdFile.testBatch(flags, path)
proc addTestSetMultithreadedCrypto(cmdFile: var string, test32bit = false, testASM = true) =
if not dirExists "build":
@ -461,115 +640,12 @@ proc addBenchSet(cmdFile: var string, useAsm = true) =
for bd in benchDesc:
cmdFile.buildBenchBatch(bd, useASM = useASM)
proc genDynamicBindings(bindingsName, prefixNimMain: string) =
proc compile(libName: string, flags = "") =
# -d:danger to avoid boundsCheck, overflowChecks that would trigger exceptions or allocations in a crypto library.
# Those are internally guaranteed at compile-time by fixed-sized array
# and checked at runtime with an appropriate error code if any for user-input.
# -gc:arc Constantine stack allocates everything. Inputs are through unmanaged ptr+len.
# In the future, Constantine might use:
# - heap-allocated sequences and objects manually managed or managed by destructors for multithreading.
# - heap-allocated strings for hex-string or decimal strings
echo "Compiling dynamic library: lib/" & libName
exec "nim c -f " & flags & " --noMain -d:danger --app:lib --gc:arc " &
" --panics:on " & # Defects are not catchable
" --verbosity:0 --hints:off --warnings:off " &
" --nimMainPrefix:" & prefixNimMain &
" --out:" & libName & " --outdir:lib " &
" --nimcache:nimcache/bindings/" & bindingsName &
" bindings/" & bindingsName & ".nim"
when defined(windows):
compile bindingsName & ".dll"
elif defined(macosx):
compile "lib" & bindingsName & ".dylib.arm", "--cpu:arm64 -l:'-target arm64-apple-macos11' -t:'-target arm64-apple-macos11'"
compile "lib" & bindingsName & ".dylib.x64", "--cpu:amd64 -l:'-target x86_64-apple-macos10.12' -t:'-target x86_64-apple-macos10.12'"
exec "lipo lib/lib" & bindingsName & ".dylib.arm " &
" lib/lib" & bindingsName & ".dylib.x64 " &
" -output lib/lib" & bindingsName & ".dylib -create"
else:
compile "lib" & bindingsName & ".so"
proc genStaticBindings(bindingsName, prefixNimMain: string) =
proc compile(libName: string, flags = "") =
# -d:danger to avoid boundsCheck, overflowChecks that would trigger exceptions or allocations in a crypto library.
# Those are internally guaranteed at compile-time by fixed-sized array
# and checked at runtime with an appropriate error code if any for user-input.
# -gc:arc Constantine stack allocates everything. Inputs are through unmanaged ptr+len.
# In the future, Constantine might use:
# - heap-allocated sequences and objects manually managed or managed by destructors for multithreading.
# - heap-allocated strings for hex-string or decimal strings
echo "Compiling static library: lib/" & libName
exec "nim c -f " & flags & " --noMain -d:danger --app:staticLib --gc:arc " &
" --panics:on " & # Defects are not catchable
" --verbosity:0 --hints:off --warnings:off " &
" --nimMainPrefix:" & prefixNimMain &
" --out:" & libName & " --outdir:lib " &
" --nimcache:nimcache/bindings/" & bindingsName &
" bindings/" & bindingsName & ".nim"
when defined(windows):
compile bindingsName & ".lib"
elif defined(macosx):
compile "lib" & bindingsName & ".a.arm", "--cpu:arm64 -l:'-target arm64-apple-macos11' -t:'-target arm64-apple-macos11'"
compile "lib" & bindingsName & ".a.x64", "--cpu:amd64 -l:'-target x86_64-apple-macos10.12' -t:'-target x86_64-apple-macos10.12'"
exec "lipo lib/lib" & bindingsName & ".a.arm " &
" lib/lib" & bindingsName & ".a.x64 " &
" -output lib/lib" & bindingsName & ".a -create"
else:
compile "lib" & bindingsName & ".a"
proc genHeaders(bindingsName: string) =
echo "Generating header: include/" & bindingsName & ".h"
exec "nim c -d:release -d:CttGenerateHeaders " &
" --verbosity:0 --hints:off --warnings:off " &
" --out:" & bindingsName & "_gen_header.exe --outdir:build " &
" --nimcache:nimcache/bindings/" & bindingsName & "_header" &
" bindings/" & bindingsName & ".nim"
exec "build/" & bindingsName & "_gen_header.exe include"
proc genParallelCmdRunner() =
exec "nim c --verbosity:0 --hints:off --warnings:off -d:release --out:build/pararun --nimcache:nimcache/pararun helpers/pararun.nim"
# Tasks
# ----------------------------------------------------------------
task bindings, "Generate Constantine bindings":
genDynamicBindings("constantine_bls12_381", "ctt_bls12381_init_")
genStaticBindings("constantine_bls12_381", "ctt_bls12381_init_")
genHeaders("constantine_bls12_381")
echo ""
genDynamicBindings("constantine_pasta", "ctt_pasta_init_")
genStaticBindings("constantine_pasta", "ctt_pasta_init_")
genHeaders("constantine_pasta")
task test_bindings, "Test C bindings":
exec "mkdir -p build/testsuite"
echo "--> Testing dynamically linked library"
when not defined(windows):
exec "gcc -Iinclude -Llib -o build/testsuite/t_libctt_bls12_381_dl examples_c/t_libctt_bls12_381.c -lgmp -lconstantine_bls12_381"
exec "LD_LIBRARY_PATH=lib ./build/testsuite/t_libctt_bls12_381_dl"
else:
# Put DLL near the exe as LD_LIBRARY_PATH doesn't work even in an POSIX compatible shell
exec "gcc -Iinclude -Llib -o build/testsuite/t_libctt_bls12_381_dl.exe examples_c/t_libctt_bls12_381.c -lgmp -lconstantine_bls12_381"
exec "./build/testsuite/t_libctt_bls12_381_dl.exe"
echo "--> Testing statically linked library"
when not defined(windows):
# Beware MacOS annoying linker with regards to static libraries
# The following standard way cannot be used on MacOS
# exec "gcc -Iinclude -Llib -o build/t_libctt_bls12_381_sl.exe examples_c/t_libctt_bls12_381.c -lgmp -Wl,-Bstatic -lconstantine_bls12_381 -Wl,-Bdynamic"
exec "gcc -Iinclude -o build/testsuite/t_libctt_bls12_381_sl examples_c/t_libctt_bls12_381.c lib/libconstantine_bls12_381.a -lgmp"
exec "./build/testsuite/t_libctt_bls12_381_sl"
else:
exec "gcc -Iinclude -o build/testsuite/t_libctt_bls12_381_sl.exe examples_c/t_libctt_bls12_381.c lib/constantine_bls12_381.lib -lgmp"
exec "./build/testsuite/t_libctt_bls12_381_sl.exe"
task test, "Run all tests":
# -d:testingCurves is configured in a *.nim.cfg for convenience
var cmdFile: string
@ -1123,17 +1199,17 @@ task bench_hash_to_curve_clang_noasm, "Run Hash-to-Curve benchmarks":
# BLS signatures
# ------------------------------------------
task bench_blssig_on_bls12_381_g2, "Run Hash-to-Curve benchmarks":
runBench("bench_blssig_on_bls12_381_g2")
task bench_ethereum_bls_signatures, "Run Ethereum BLS signatures benchmarks":
runBench("bench_ethereum_bls_signatures")
task bench_blssig_on_bls12_381_g2_gcc, "Run Hash-to-Curve benchmarks":
runBench("bench_blssig_on_bls12_381_g2", "gcc")
task bench_ethereum_bls_signatures_gcc, "Run Ethereum BLS signatures benchmarks":
runBench("bench_ethereum_bls_signatures", "gcc")
task bench_blssig_on_bls12_381_g2_clang, "Run Hash-to-Curve benchmarks":
runBench("bench_blssig_on_bls12_381_g2", "clang")
task bench_ethereum_bls_signatures_clang, "Run Ethereum BLS signatures benchmarks":
runBench("bench_ethereum_bls_signatures", "clang")
task bench_blssig_on_bls12_381_g2_gcc_noasm, "Run Hash-to-Curve benchmarks":
runBench("bench_blssig_on_bls12_381_g2", "gcc", useAsm = false)
task bench_ethereum_bls_signatures_gcc_noasm, "Run Ethereum BLS signatures benchmarks":
runBench("bench_ethereum_bls_signatures", "gcc", useAsm = false)
task bench_blssig_on_bls12_381_g2_clang_noasm, "Run Hash-to-Curve benchmarks":
runBench("bench_blssig_on_bls12_381_g2", "clang", useAsm = false)
task bench_ethereum_bls_signatures_clang_noasm, "Run Ethereum BLS signatures benchmarks":
runBench("bench_ethereum_bls_signatures", "clang", useAsm = false)

View File

@ -6,7 +6,7 @@
# * 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 ../platforms/endians
import ../platforms/[endians, views]
# ############################################################
#
@ -86,11 +86,11 @@ func chacha20_block(
for i in 13'u ..< 16:
key_stream.dumpRawInt(state[i] + nonce[i-13], i shl 2, littleEndian)
func chacha20_cipher*[T: byte|char](
func chacha20_cipher*(
key: array[32, byte],
counter: uint32,
nonce: array[12, byte],
data: var openarray[T]): uint32 =
data: var openArray[byte]): uint32 {.genCharAPI.} =
## Encrypt or decrypt `data` using the ChaCha20 cipher
## - `key` is a 256-bit (32 bytes) secret shared encryption/decryption key.
## - `counter`. A monotonically increasing value per encryption.

View File

@ -6,34 +6,15 @@
# * 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
./platforms/abstractions,
./math/config/curves,
./math/[
ec_shortweierstrass,
extension_fields,
arithmetic,
constants/zoo_subgroups
],
./math/io/[io_bigints, io_fields],
hashes,
signatures/bls_signatures
export
abstractions, # generic sandwich on SecretBool and SecretBool in Jacobian sumImpl
curves, # generic sandwich on matchingBigInt
extension_fields, # generic sandwich on extension field access
hashes, # generic sandwich on sha256
ec_shortweierstrass # generic sandwich on affine
## ############################################################
##
## BLS Signatures on BLS12-381 G2
## BLS Signatures on for Ethereum
##
## ############################################################
##
## This module implements BLS Signatures (Boneh-Lynn-Schacham)
## on top of the BLS12-381 curve (Barreto-Lynn-Scott).
## on top of the BLS12-381 curve (Barreto-Lynn-Scott) G2.
## for the Ethereum blockchain.
##
## Ciphersuite:
##
@ -45,33 +26,83 @@ export
## - Domain separation tag: "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"
## - Hash function: SHA256
##
## Currently Constantine does not provide popProve and popVerify
## which are thin wrapper over sign/verify with
## - the message to sign or verify being the compressed or uncompressed public key
## or another application-specific "hash_pubkey_to_point" scheme
## - domain-separation-tag: "BLS_POP_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"
## Specs:
## - https://github.com/ethereum/consensus-specs/blob/v1.2.0/specs/phase0/beacon-chain.md#bls-signatures
## - https://github.com/ethereum/consensus-specs/blob/v1.2.0/specs/altair/bls.md
## - https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html
##
## Constantine currently assumes that proof-of-possessions are handled at the application-level
## Test vectors:
## - https://github.com/ethereum/bls12-381-tests
##
## In proof-of-stake blockchains, being part of the staker/validator sets
## already serve as proof-of-possession.
## The Ethereum blockchain uses the proof-of-possession scheme (PoP).
## Each public key is associated with a deposit proof required to participate
## in the blockchain consensus protocol, hence PopProve and PopVerify
## as defined in the IETF spec are not needed.
const DST = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"
const ffi_prefix {.used.} = "ctt_blssig_pop_on_bls12381_g2_"
const prefix_ffi = "ctt_eth_bls_"
{.push raises: [].} # No exceptions allowed in core cryptographic operations
# {.push cdecl, dynlib, exportc:ffi_prefix & "$1".} # TODO, C API
# Dependencies exports for C FFI
# ------------------------------------------------------------------------------------------------
import ./zoo_exports
static:
# Xxport SHA256 routines with a protocol specific prefix
# This exports sha256.init(), sha256.update(), sha256.finish() and sha256.clear()
prefix_sha256 = prefix_ffi & "_sha256_"
import hashes
export hashes # generic sandwich on sha256
func sha256_hash*(digest: var array[32, byte], message: openArray[byte], clearMem: bool) {.libPrefix: prefix_ffi.} =
## Compute the SHA-256 hash of message
## and store the result in digest.
## Optionally, clear the memory buffer used.
# There is an extra indirect function call as we use a generic `hash` concept but:
# - the indirection saves space (instead of duplicating `hash`)
# - minimal overhead compared to hashing time
# - Can be tail-call optimized into a goto jump instead of call/return
# - Can be LTO-optimized
sha256.hash(digest, message, clearMem)
# Imports
# ------------------------------------------------------------------------------------------------
import
./platforms/[abstractions, views],
./math/config/curves,
./math/[
ec_shortweierstrass,
extension_fields,
arithmetic,
constants/zoo_subgroups
],
./math/io/[io_bigints, io_fields],
signatures/bls_signatures
export
abstractions, # generic sandwich on SecretBool and SecretBool in Jacobian sumImpl
curves, # generic sandwich on matchingBigInt
extension_fields, # generic sandwich on extension field access
ec_shortweierstrass # generic sandwich on affine
# Protocol types
# ------------------------------------------------------------------------------------------------
{.checks: off.} # No exceptions allowed in core cryptographic operations
type
SecretKey* {.byref.} = object
SecretKey* {.byref, exportc: prefix_ffi & "seckey".} = object
## A BLS12_381 secret key
raw: matchingOrderBigInt(BLS12_381)
PublicKey* {.byref.} = object
PublicKey* {.byref, exportc: prefix_ffi & "pubkey".} = object
## A BLS12_381 public key for BLS signature schemes with public keys on G1 and signatures on G2
raw: ECP_ShortW_Aff[Fp[BLS12_381], G1]
Signature* {.byref.} = object
Signature* {.byref, exportc: prefix_ffi & "signature".} = object
## A BLS12_381 signature for BLS signature schemes with public keys on G1 and signatures on G2
raw: ECP_ShortW_Aff[Fp2[BLS12_381], G2]
@ -91,18 +122,26 @@ type
# Comparisons
# ------------------------------------------------------------------------------------------------
func isZero*(elem: PublicKey or Signature): bool =
func pubkey_is_zero*(pubkey: PublicKey): bool {.libPrefix: prefix_ffi.} =
## Returns true if input is 0
bool(elem.raw.isInf())
bool(pubkey.raw.isInf())
func `==`*(a, b: PublicKey or Signature): bool =
func signature_is_zero*(sig: Signature): bool {.libPrefix: prefix_ffi.} =
## Returns true if input is 0
bool(sig.raw.isInf())
func pubkeys_are_equal*(a, b: PublicKey): bool {.libPrefix: prefix_ffi.} =
## Returns true if inputs are equal
bool(a.raw == b.raw)
func signatures_are_equal*(a, b: Signature): bool {.libPrefix: prefix_ffi.} =
## Returns true if inputs are equal
bool(a.raw == b.raw)
# Input validation
# ------------------------------------------------------------------------------------------------
func validate_seckey*(secret_key: SecretKey): CttBLSStatus =
func validate_seckey*(secret_key: SecretKey): CttBLSStatus {.libPrefix: prefix_ffi.} =
## Validate the secret key.
## Regarding timing attacks, this will leak timing information only if the key is invalid.
## Namely, the secret key is 0 or the secret key is too large.
@ -112,7 +151,7 @@ func validate_seckey*(secret_key: SecretKey): CttBLSStatus =
return cttBLS_SecretKeyLargerThanCurveOrder
return cttBLS_Success
func validate_pubkey*(public_key: PublicKey): CttBLSStatus =
func validate_pubkey*(public_key: PublicKey): CttBLSStatus {.libPrefix: prefix_ffi.} =
## Validate the public key.
## This is an expensive operation that can be cached
if public_key.raw.isInf().bool():
@ -122,7 +161,7 @@ func validate_pubkey*(public_key: PublicKey): CttBLSStatus =
if not public_key.raw.isInSubgroup().bool():
return cttBLS_PointNotInSubgroup
func validate_sig*(signature: Signature): CttBLSStatus =
func validate_signature*(signature: Signature): CttBLSStatus {.libPrefix: prefix_ffi.} =
## Validate the signature.
## This is an expensive operation that can be cached
if signature.raw.isInf().bool():
@ -153,17 +192,17 @@ func validate_sig*(signature: Signature): CttBLSStatus =
## The third-most significant bit is set if (and only if) this point is in compressed form
## and it is not the point at infinity and its y-coordinate is the lexicographically largest of the two associated with the encoded x-coordinate.
##
## - https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-04#appendix-A
## - https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05#appendix-A
## - https://docs.rs/bls12_381/latest/bls12_381/notes/serialization/index.html
## - https://github.com/zkcrypto/bls12_381/blob/0.6.0/src/notes/serialization.rs
func serialize_secret_key*(dst: var array[32, byte], secret_key: SecretKey): CttBLSStatus =
func serialize_seckey*(dst: var array[32, byte], secret_key: SecretKey): CttBLSStatus {.libPrefix: prefix_ffi.} =
## Serialize a secret key
## Returns cttBLS_Success if successful
dst.marshal(secret_key.raw, bigEndian)
return cttBLS_Success
func serialize_public_key_compressed*(dst: var array[48, byte], public_key: PublicKey): CttBLSStatus =
func serialize_pubkey_compressed*(dst: var array[48, byte], public_key: PublicKey): CttBLSStatus {.libPrefix: prefix_ffi.} =
## Serialize a public key in compressed (Zcash) format
##
## Returns cttBLS_Success if successful
@ -184,7 +223,7 @@ func serialize_public_key_compressed*(dst: var array[48, byte], public_key: Publ
return cttBLS_Success
func serialize_signature_compressed*(dst: var array[96, byte], signature: Signature): CttBLSStatus =
func serialize_signature_compressed*(dst: var array[96, byte], signature: Signature): CttBLSStatus {.libPrefix: prefix_ffi.} =
## Serialize a signature in compressed (Zcash) format
##
## Returns cttBLS_Success if successful
@ -206,8 +245,9 @@ func serialize_signature_compressed*(dst: var array[96, byte], signature: Signat
return cttBLS_Success
func deserialize_secret_key*(dst: var SecretKey, src: array[32, byte]): CttBLSStatus =
## deserialize a secret key
func deserialize_seckey*(dst: var SecretKey, src: array[32, byte]): CttBLSStatus {.libPrefix: prefix_ffi.} =
## Deserialize a secret key
## This also validates the secret key.
##
## This is protected against side-channel unless your key is invalid.
## In that case it will like whether it's all zeros or larger than the curve order.
@ -218,7 +258,7 @@ func deserialize_secret_key*(dst: var SecretKey, src: array[32, byte]): CttBLSSt
return status
return cttBLS_Success
func deserialize_public_key_compressed_unchecked*(dst: var PublicKey, src: array[48, byte]): CttBLSStatus =
func deserialize_pubkey_compressed_unchecked*(dst: var PublicKey, src: array[48, byte]): CttBLSStatus {.libPrefix: prefix_ffi.} =
## Deserialize a public_key in compressed (Zcash) format.
##
## Warning ⚠:
@ -260,19 +300,20 @@ func deserialize_public_key_compressed_unchecked*(dst: var PublicKey, src: array
let srcIsLargest = SecretBool((src[0] shr 5) and byte 1)
dst.raw.y.cneg(isLexicographicallyLargest xor srcIsLargest)
func deserialize_public_key_compressed*(dst: var PublicKey, src: array[48, byte]): CttBLSStatus =
func deserialize_pubkey_compressed*(dst: var PublicKey, src: array[48, byte]): CttBLSStatus {.libPrefix: prefix_ffi.} =
## Deserialize a public_key in compressed (Zcash) format
## This also validates the public key.
##
## Returns cttBLS_Success if successful
result = deserialize_public_key_compressed_unchecked(dst, src)
result = deserialize_pubkey_compressed_unchecked(dst, src)
if result != cttBLS_Success:
return result
if not(bool dst.raw.isInSubgroup):
if not(bool dst.raw.isInSubgroup()):
return cttBLS_PointNotInSubgroup
func deserialize_signature_compressed_unchecked*(dst: var Signature, src: array[96, byte]): CttBLSStatus =
func deserialize_signature_compressed_unchecked*(dst: var Signature, src: array[96, byte]): CttBLSStatus {.libPrefix: prefix_ffi.} =
## Deserialize a signature in compressed (Zcash) format.
##
## Warning ⚠:
@ -325,7 +366,7 @@ func deserialize_signature_compressed_unchecked*(dst: var Signature, src: array[
let srcIsLargest = SecretBool((src[0] shr 5) and byte 1)
dst.raw.y.cneg(isLexicographicallyLargest xor srcIsLargest)
func deserialize_signature_compressed*(dst: var Signature, src: array[96, byte]): CttBLSStatus =
func deserialize_signature_compressed*(dst: var Signature, src: array[96, byte]): CttBLSStatus {.libPrefix: prefix_ffi.} =
## Deserialize a public_key in compressed (Zcash) format
##
## Returns cttBLS_Success if successful
@ -334,13 +375,13 @@ func deserialize_signature_compressed*(dst: var Signature, src: array[96, byte])
if result != cttBLS_Success:
return result
if not(bool dst.raw.isInSubgroup):
if not(bool dst.raw.isInSubgroup()):
return cttBLS_PointNotInSubgroup
# Signatures
# BLS Signatures
# ------------------------------------------------------------------------------------------------
func derive_public_key*(public_key: var PublicKey, secret_key: SecretKey): CttBLSStatus =
func derive_pubkey*(public_key: var PublicKey, secret_key: SecretKey): CttBLSStatus {.libPrefix: prefix_ffi.} =
## Derive the public key matching with a secret key
##
## Secret protection:
@ -356,7 +397,7 @@ func derive_public_key*(public_key: var PublicKey, secret_key: SecretKey): CttBL
return cttBLS_InvalidEncoding
return cttBLS_Success
func sign*[T: byte|char](signature: var Signature, secret_key: SecretKey, message: openArray[T]): CttBLSStatus =
func sign*(signature: var Signature, secret_key: SecretKey, message: openArray[byte]): CttBLSStatus {.libPrefix: prefix_ffi, genCharAPI.} =
## Produce a signature for the message under the specified secret key
## Signature is on BLS12-381 G2 (and public key on G1)
##
@ -382,7 +423,7 @@ func sign*[T: byte|char](signature: var Signature, secret_key: SecretKey, messag
coreSign(signature.raw, secretKey.raw, message, sha256, 128, augmentation = "", DST)
return cttBLS_Success
func verify*[T: byte|char](public_key: PublicKey, message: openarray[T], signature: Signature): CttBLSStatus =
func verify*(public_key: PublicKey, message: openArray[byte], signature: Signature): CttBLSStatus {.libPrefix: prefix_ffi, genCharAPI.} =
## Check that a signature is valid for a message
## under the provided public key.
## returns `true` if the signature is valid, `false` otherwise.
@ -394,9 +435,13 @@ func verify*[T: byte|char](public_key: PublicKey, message: openarray[T], signatu
## Or validated via validate_pubkey
## - A message
## - A signature initialized by one of the key derivation or deserialization procedure.
## Or validated via validate_pubkey
## Or validated via validate_signature
##
## In particular, the public key and signature are assumed to be on curve subgroup checked.
## Output:
## - a status code with verification success if signature is valid
## or indicating verification failure
##
## In particular, the public key and signature are assumed to be on curve and subgroup-checked.
# Deal with cases were pubkey or signature were mistakenly zero-init, due to a generic aggregation tentative for example
if bool(public_key.raw.isInf() or signature.raw.isInf()):
@ -411,25 +456,29 @@ template unwrap[T: PublicKey|Signature](elems: openArray[T]): auto =
# Unwrap collection of high-level type into collection of low-level type
toOpenArray(cast[ptr UncheckedArray[typeof elems[0].raw]](elems[0].raw.unsafeAddr), elems.low, elems.high)
func aggregate_pubkeys*(aggregate_pubkey: var PublicKey, pubkeys: openArray[PublicKey]) =
func aggregate_pubkeys_unstable_api*(aggregate_pubkey: var PublicKey, pubkeys: openArray[PublicKey]) =
## Aggregate public keys into one
## The individual public keys are assumed to be validated, either during deserialization
## or by validate_pubkeys
#
# TODO: Return a bool or status code or nothing?
if pubkeys.len == 0:
aggregate_pubkey.raw.setInf()
return
aggregate_pubkey.raw.aggregate(pubkeys.unwrap())
func aggregate_signatures*(aggregate_sig: var Signature, signatures: openArray[Signature]) =
func aggregate_signatures_unstable_api*(aggregate_sig: var Signature, signatures: openArray[Signature]) =
## Aggregate signatures into one
## The individual signatures are assumed to be validated, either during deserialization
## or by validate_signature
#
# TODO: Return a bool or status code or nothing?
if signatures.len == 0:
aggregate_sig.raw.setInf()
return
aggregate_sig.raw.aggregate(signatures.unwrap())
func fast_aggregate_verify*[T: byte|char](pubkeys: openArray[PublicKey], message: openarray[T], aggregate_sig: Signature): CttBLSStatus =
func fast_aggregate_verify*(pubkeys: openArray[PublicKey], message: openArray[byte], aggregate_sig: Signature): CttBLSStatus {.libPrefix: prefix_ffi, genCharAPI.} =
## Check that a signature is valid for a message
## under the aggregate of provided public keys.
## returns `true` if the signature is valid, `false` otherwise.
@ -441,7 +490,7 @@ func fast_aggregate_verify*[T: byte|char](pubkeys: openArray[PublicKey], message
## Or validated via validate_pubkey
## - A message
## - A signature initialized by one of the key derivation or deserialization procedure.
## Or validated via validate_sig
## Or validated via validate_signature
##
## In particular, the public keys and signature are assumed to be on curve subgroup checked.
@ -465,7 +514,11 @@ func fast_aggregate_verify*[T: byte|char](pubkeys: openArray[PublicKey], message
return cttBLS_Success
return cttBLS_VerificationFailure
func aggregate_verify*[M](pubkeys: openArray[PublicKey], messages: openarray[M], aggregate_sig: Signature): CttBLSStatus =
# C FFI
func aggregate_verify*(pubkeys: ptr UncheckedArray[PublicKey],
messages: ptr UncheckedArray[View[byte]],
len: int,
aggregate_sig: Signature): CttBLSStatus {.libPrefix: prefix_ffi.} =
## Verify the aggregated signature of multiple (pubkey, message) pairs
## returns `true` if the signature is valid, `false` otherwise.
##
@ -476,12 +529,53 @@ func aggregate_verify*[M](pubkeys: openArray[PublicKey], messages: openarray[M],
## Or validated via validate_pubkey
## - Messages
## - a signature initialized by one of the key derivation or deserialization procedure.
## Or validated via validate_sig
## Or validated via validate_signature
##
## In particular, the public keys and signature are assumed to be on curve subgroup checked.
##
## To avoid splitting zeros and rogue keys attack:
## 1. Public keys signing the same message MUST be aggregated and checked for 0 before calling BLSAggregateSigAccumulator.update()
## 1. Public keys signing the same message MUST be aggregated and checked for 0 before calling this function.
## 2. Augmentation or Proof of possessions must used for each public keys.
if len == 0:
# IETF spec precondition
return cttBLS_ZeroLengthAggregation
# Deal with cases were pubkey or signature were mistakenly zero-init, due to a generic aggregation tentative for example
if aggregate_sig.raw.isInf().bool:
return cttBLS_PointAtInfinity
for i in 0 ..< len:
if pubkeys[i].raw.isInf().bool:
return cttBLS_PointAtInfinity
let verified = aggregateVerify(
pubkeys.toOpenArray(len).unwrap(),
messages.toOpenArray(len),
aggregate_sig.raw,
sha256, 128, DST)
if verified:
return cttBLS_Success
return cttBLS_VerificationFailure
# Nim
func aggregate_verify*[Msg](pubkeys: openArray[PublicKey], messages: openArray[Msg], aggregate_sig: Signature): CttBLSStatus =
## Verify the aggregated signature of multiple (pubkey, message) pairs
## returns `true` if the signature is valid, `false` otherwise.
##
## For message domain separation purpose, the tag is `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_`
##
## Input:
## - Public keys initialized by one of the key derivation or deserialization procedure.
## Or validated via validate_pubkey
## - Messages
## - a signature initialized by one of the key derivation or deserialization procedure.
## Or validated via validate_signature
##
## In particular, the public keys and signature are assumed to be on curve subgroup checked.
##
## To avoid splitting zeros and rogue keys attack:
## 1. Public keys signing the same message MUST be aggregated and checked for 0 before calling this function.
## 2. Augmentation or Proof of possessions must used for each public keys.
if pubkeys.len == 0:
@ -507,7 +601,12 @@ func aggregate_verify*[M](pubkeys: openArray[PublicKey], messages: openarray[M],
return cttBLS_Success
return cttBLS_VerificationFailure
func batch_verify*[M](pubkeys: openArray[PublicKey], messages: openarray[M], signatures: openArray[Signature], secureRandomBytes: array[32, byte]): CttBLSStatus =
# C FFI
func batch_verify*[Msg](pubkeys: ptr UncheckedArray[PublicKey],
messages: ptr UncheckedArray[View[byte]],
signatures: ptr UncheckedArray[Signature],
len: int,
secureRandomBytes: array[32, byte]): CttBLSStatus {.libPrefix: prefix_ffi.} =
## Verify that all (pubkey, message, signature) triplets are valid
## returns `true` if all signatures are valid, `false` if at least one is invalid.
##
@ -518,7 +617,54 @@ func batch_verify*[M](pubkeys: openArray[PublicKey], messages: openarray[M], sig
## Or validated via validate_pubkey
## - Messages
## - Signatures initialized by one of the key derivation or deserialization procedure.
## Or validated via validate_sig
## Or validated via validate_signature
##
## In particular, the public keys and signature are assumed to be on curve subgroup checked.
##
## To avoid splitting zeros and rogue keys attack:
## 1. Cryptographically-secure random bytes must be provided.
## 2. Augmentation or Proof of possessions must used for each public keys.
##
## The secureRandomBytes will serve as input not under the attacker control to foil potential splitting zeros inputs.
## The scheme assumes that the attacker cannot
## resubmit 2^64 times forged (publickey, message, signature) triplets
## against the same `secureRandomBytes`
if len == 0:
# IETF spec precondition
return cttBLS_ZeroLengthAggregation
# Deal with cases were pubkey or signature were mistakenly zero-init, due to a generic aggregation tentative for example
for i in 0 ..< len:
if pubkeys[i].raw.isInf().bool:
return cttBLS_PointAtInfinity
for i in 0 ..< len:
if signatures[i].raw.isInf().bool:
return cttBLS_PointAtInfinity
let verified = batchVerify(
pubkeys.toOpenArray(len).unwrap(),
messages,
signatures.toOpenArray(len).unwrap(),
sha256, 128, DST, secureRandomBytes)
if verified:
return cttBLS_Success
return cttBLS_VerificationFailure
# Nim
func batch_verify*[Msg](pubkeys: openArray[PublicKey], messages: openarray[Msg], signatures: openArray[Signature], secureRandomBytes: array[32, byte]): CttBLSStatus =
## Verify that all (pubkey, message, signature) triplets are valid
## returns `true` if all signatures are valid, `false` if at least one is invalid.
##
## For message domain separation purpose, the tag is `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_`
##
## Input:
## - Public keys initialized by one of the key derivation or deserialization procedure.
## Or validated via validate_pubkey
## - Messages
## - Signatures initialized by one of the key derivation or deserialization procedure.
## Or validated via validate_signature
##
## In particular, the public keys and signature are assumed to be on curve subgroup checked.
##

View File

@ -12,18 +12,18 @@ import
./math/config/[curves, type_ff],
./math/arithmetic/[bigints, limbs_montgomery],
./math/io/io_bigints,
./platforms/endians
./platforms/[primitives, endians]
# EIP2333: BLS12-381 Key Generation
# ------------------------------------------------------------
#
# https://eips.ethereum.org/EIPS/eip-2333
{.push raises: [].} # No exceptions
{.push raises: [], checks: off.} # No exceptions
type SecretKey = matchingOrderBigInt(BLS12_381)
func hkdf_mod_r[T: char|byte](secretKey: var SecretKey, ikm: openArray[byte], key_info: openArray[T]) =
func hkdf_mod_r(secretKey: var SecretKey, ikm: openArray[byte], key_info: openArray[byte]) =
## Ethereum 2 EIP-2333, extracts this from the BLS signature schemes
# 1. salt = "BLS-SIG-KEYGEN-SALT-"
# 2. SK = 0
@ -52,7 +52,7 @@ func hkdf_mod_r[T: char|byte](secretKey: var SecretKey, ikm: openArray[byte], ke
const L = 48
var okm{.noInit.}: array[L, byte]
const L_octetstring = L.uint16.toBytesBE()
ctx.hkdfExpand(okm, prk, key_info, append = L_octetstring)
ctx.hkdfExpand(okm, prk, key_info, append = L_octetstring, clearMem = true)
# 7. x = OS2IP(OKM) mod r
# We reduce mod r via Montgomery reduction, instead of bigint division
# as constant-time division works bits by bits (384 bits) while
@ -90,12 +90,13 @@ iterator ikm_to_lamport_SK(
# 1. OKM = HKDF-Expand(PRK, "" , L)
# with L = K * 255 and K = 32 (sha256 output)
{.push checks: off.} # No OverflowError or IndexError allowed
for i in ctx.hkdfExpandChunk(
lamportSecretKeyChunk,
prk, "",""):
prk, default(array[0, byte]), default(array[0, byte])):
yield i
ctx.clear()
func parent_SK_to_lamport_PK(
lamportPublicKey: var array[32, byte],
parentSecretKey: SecretKey,
@ -119,8 +120,6 @@ func parent_SK_to_lamport_PK(
var tmp{.noInit.}, chunk{.noInit.}: array[32, byte]
{.push checks: off.} # No OverflowError or IndexError allowed
# 2. lamport_0 = IKM_to_lamport_SK(IKM, salt)
# 6. for i = 1, .., 255 (inclusive)
# lamport_PK = lamport_PK | SHA256(lamport_0[i])
@ -152,26 +151,26 @@ func parent_SK_to_lamport_PK(
func derive_child_secretKey*(
childSecretKey: var SecretKey,
parentSecretKey: SecretKey,
index: uint32
): bool =
index: uint32): bool =
## EIP2333 Child Key derivation function
var compressed_lamport_PK{.noInit.}: array[32, byte]
# 0. compressed_lamport_PK = parent_SK_to_lamport_PK(parent_SK, index)
parent_SK_to_lamport_PK(
compressed_lamport_PK,
parentSecretKey,
index,
)
childSecretKey.hkdf_mod_r(compressed_lamport_PK, key_info = "")
index)
childSecretKey.hkdf_mod_r(compressed_lamport_PK, key_info = default(array[0, byte]))
compressed_lamport_PK.setZero()
return true
func derive_master_secretKey*(
masterSecretKey: var SecretKey,
ikm: openArray[byte]
): bool =
ikm: openArray[byte]): bool =
## EIP2333 Master key derivation
## The input keying material SHOULD be cleared after use
## to prevent leakage.
if ikm.len < 32:
return false
masterSecretKey.hkdf_mod_r(ikm, key_info = "")
masterSecretKey.hkdf_mod_r(ikm, key_info = default(array[0, byte]))
return true

View File

@ -113,7 +113,7 @@ func eth_evm_ecadd*(r: var array[64, byte], inputs: openarray[byte]): CttEVMStat
# Auto-pad with zero
var padded: array[128, byte]
padded.copy(0, inputs, 0, min(inputs.len, 128))
padded.rawCopy(0, inputs, 0, min(inputs.len, 128))
var P{.noInit.}, Q{.noInit.}, R{.noInit.}: ECP_ShortW_Prj[Fp[BN254_Snarks], G1]
@ -168,7 +168,7 @@ func eth_evm_ecmul*(r: var array[64, byte], inputs: openarray[byte]): CttEVMStat
# Auto-pad with zero
var padded: array[128, byte]
padded.copy(0, inputs, 0, min(inputs.len, 128))
padded.rawCopy(0, inputs, 0, min(inputs.len, 128))
var P{.noInit.}: ECP_ShortW_Prj[Fp[BN254_Snarks], G1]

View File

@ -8,7 +8,7 @@
import
# Internals
../platforms/[abstractions, endians],
../platforms/[abstractions, endians, views],
../hashes,
../math/io/[io_bigints, io_fields],
../math/config/curves,
@ -37,10 +37,10 @@ template strxor(b_i: var array, b0: array): untyped =
b_i[i] = b_i[i] xor b0[i]
# ----------------------------------------------------------------
func shortDomainSepTag*[DigestSize: static int, B: byte|char](
func shortDomainSepTag*[DigestSize: static int](
H: type CryptoHash,
output: var array[DigestSize, byte],
oversizedDST: openarray[B]) =
oversizedDST: openArray[byte]) {.genCharAPI.} =
## Compute a short Domain Separation Tag
## from a domain separation tag larger than 255 bytes
##
@ -52,13 +52,13 @@ func shortDomainSepTag*[DigestSize: static int, B: byte|char](
ctx.update oversizedDST
ctx.finish(output)
func expandMessageXMD*[B1, B2, B3: byte|char, len_in_bytes: static int](
func expandMessageXMD*[len_in_bytes: static int](
H: type CryptoHash,
output: var array[len_in_bytes, byte],
augmentation: openarray[B1],
message: openarray[B2],
domainSepTag: openarray[B3]
) =
augmentation: openArray[byte],
message: openArray[byte],
domainSepTag: openArray[byte]
) {.genCharAPI.} =
## The expand_message_xmd function produces a uniformly random byte
## string using a cryptographic hash function H that outputs "b" bits,
## with b >= 2*k and k the target security level (for example 128-bit)
@ -77,7 +77,7 @@ func expandMessageXMD*[B1, B2, B3: byte|char, len_in_bytes: static int](
## - `augmentation`, an optional augmentation to the message. This will be prepended,
## prior to hashing.
## This is used for building the "message augmentation" variant of BLS signatures
## https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04#section-3.2
## https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#section-3.2
## which requires `CoreSign(SK, PK || message)`
## and `CoreVerify(PK, PK || message, signature)`
## - `message` is the message to hash
@ -163,14 +163,14 @@ func mulMont(r: var BigInt, a, b: BigInt, FF: type) {.inline.} =
FF.getSpareBits()
)
func hashToField*[Field; B1, B2, B3: byte|char, count: static int](
func hashToField*[Field; count: static int](
H: type CryptoHash,
k: static int,
output: var array[count, Field],
augmentation: openarray[B1],
message: openarray[B2],
domainSepTag: openarray[B3]
) =
augmentation: openArray[byte],
message: openArray[byte],
domainSepTag: openArray[byte]
) {.genCharAPI.} =
## Hash to a field or an extension field
## https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3
##
@ -186,7 +186,7 @@ func hashToField*[Field; B1, B2, B3: byte|char, count: static int](
## - `augmentation`, an optional augmentation to the message. This will be prepended,
## prior to hashing.
## This is used for building the "message augmentation" variant of BLS signatures
## https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04#section-3.2
## https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#section-3.2
## which requires `CoreSign(SK, PK || message)`
## and `CoreVerify(PK, PK || message, signature)`
## - `message` is the message to hash

View File

@ -8,7 +8,7 @@
import
# Internals
../platforms/abstractions,
../platforms/[abstractions, views],
../math/config/curves,
../math/[arithmetic, extension_fields],
../math/constants/[zoo_hash_to_curve, zoo_subgroups],
@ -101,8 +101,7 @@ func mapToIsoCurve_sswuG1_opt3mod4[F](
mapToIsoCurve_sswuG1_opt3mod4(
xn, xd,
yn,
u, xd3
)
u, xd3)
# Convert to Jacobian
r.z = xd # Z = xd
@ -120,8 +119,7 @@ func mapToIsoCurve_sswuG2_opt9mod16[F](
mapToIsoCurve_sswuG2_opt9mod16(
xn, xd,
yn,
u, xd3
)
u, xd3)
# Convert to Jacobian
r.z = xd # Z = xd
@ -191,16 +189,13 @@ func mapToCurve_sswu_fusedAdd[F; G: static Subgroup](
# Hash to curve
# ----------------------------------------------------------------
func hashToCurve_svdw*[
F; G: static Subgroup;
B1, B2, B3: byte|char](
func hashToCurve_svdw*[F; G: static Subgroup](
H: type CryptoHash,
k: static int,
output: var ECP_ShortW_Jac[F, G],
augmentation: openarray[B1],
message: openarray[B2],
domainSepTag: openarray[B3]
) =
augmentation: openArray[byte],
message: openArray[byte],
domainSepTag: openArray[byte]) {.genCharAPI.} =
## Hash a message to an elliptic curve
##
## Arguments:
@ -215,7 +210,7 @@ func hashToCurve_svdw*[
## - `augmentation`, an optional augmentation to the message. This will be prepended,
## prior to hashing.
## This is used for building the "message augmentation" variant of BLS signatures
## https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04#section-3.2
## https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#section-3.2
## which requires `CoreSign(SK, PK || message)`
## and `CoreVerify(PK, PK || message, signature)`
## - `message` is the message to hash
@ -233,16 +228,13 @@ func hashToCurve_svdw*[
output.mapToCurve_svdw_fusedAdd(u[0], u[1])
output.clearCofactor()
func hashToCurve_sswu*[
F; G: static Subgroup;
B1, B2, B3: byte|char](
func hashToCurve_sswu*[F; G: static Subgroup](
H: type CryptoHash,
k: static int,
output: var ECP_ShortW_Jac[F, G],
augmentation: openarray[B1],
message: openarray[B2],
domainSepTag: openarray[B3]
) =
augmentation: openArray[byte],
message: openArray[byte],
domainSepTag: openArray[byte]) {.genCharAPI.} =
## Hash a message to an elliptic curve
##
## Arguments:
@ -257,7 +249,7 @@ func hashToCurve_sswu*[
## - `augmentation`, an optional augmentation to the message. This will be prepended,
## prior to hashing.
## This is used for building the "message augmentation" variant of BLS signatures
## https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04#section-3.2
## https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#section-3.2
## which requires `CoreSign(SK, PK || message)`
## and `CoreVerify(PK, PK || message, signature)`
## - `message` is the message to hash
@ -275,16 +267,13 @@ func hashToCurve_sswu*[
output.mapToCurve_sswu_fusedAdd(u[0], u[1])
output.clearCofactor()
func hashToCurve*[
F; G: static Subgroup;
B1, B2, B3: byte|char](
func hashToCurve*[F; G: static Subgroup](
H: type CryptoHash,
k: static int,
output: var ECP_ShortW_Jac[F, G],
augmentation: openarray[B1],
message: openarray[B2],
domainSepTag: openarray[B3]
) {.inline.} =
augmentation: openArray[byte],
message: openArray[byte],
domainSepTag: openArray[byte]) {.inline, genCharAPI.} =
## Hash a message to an elliptic curve
##
## Arguments:
@ -299,7 +288,7 @@ func hashToCurve*[
## - `augmentation`, an optional augmentation to the message. This will be prepended,
## prior to hashing.
## This is used for building the "message augmentation" variant of BLS signatures
## https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04#section-3.2
## https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#section-3.2
## which requires `CoreSign(SK, PK || message)`
## and `CoreVerify(PK, PK || message, signature)`
## - `message` is the message to hash
@ -313,16 +302,13 @@ func hashToCurve*[
else:
{.error: "Not implemented".}
func hashToCurve*[
F; G: static Subgroup;
B1, B2, B3: byte|char](
func hashToCurve*[F; G: static Subgroup](
H: type CryptoHash,
k: static int,
output: var (ECP_ShortW_Prj[F, G] or ECP_ShortW_Aff[F, G]),
augmentation: openarray[B1],
message: openarray[B2],
domainSepTag: openarray[B3]
) {.inline.} =
augmentation: openArray[byte],
message: openArray[byte],
domainSepTag: openArray[byte]) {.inline, genCharAPI.} =
## Hash a message to an elliptic curve
##
## Arguments:
@ -337,7 +323,7 @@ func hashToCurve*[
## - `augmentation`, an optional augmentation to the message. This will be prepended,
## prior to hashing.
## This is used for building the "message augmentation" variant of BLS signatures
## https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04#section-3.2
## https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#section-3.2
## which requires `CoreSign(SK, PK || message)`
## and `CoreVerify(PK, PK || message, signature)`
## - `message` is the message to hash

View File

@ -6,6 +6,8 @@
# * 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 platforms/views
# ############################################################
#
# Hash Function concept
@ -30,23 +32,19 @@ type
# Context
# -------------------------------------------
# update/finish are not matching properly
# type B = char or byte
ctx.init()
# ctx.update(openarray[B])
# ctx.finish(var array[H.digestSize, byte])
ctx.update(openarray[byte])
ctx.finish(var array[H.digestSize, byte])
ctx.clear()
func hash*[DigestSize: static int, T: char|byte](
func hash*[DigestSize: static int](
HashKind: type CryptoHash,
digest: var array[DigestSize, byte],
message: openarray[T],
clearMem = false) =
message: openArray[byte],
clearMem = false) {.genCharAPI.} =
## Produce a digest from a message
static: doAssert DigestSize == HashKind.type.digestSize
mixin update, finish
var ctx {.noInit.}: HashKind
ctx.init()
ctx.update(message)
@ -55,10 +53,10 @@ func hash*[DigestSize: static int, T: char|byte](
if clearMem:
ctx.clear()
func hash*[T: char|byte](
func hash*(
HashKind: type CryptoHash,
message: openarray[T],
clearmem = false): array[HashKind.digestSize, byte] {.noInit.} =
message: openArray[byte],
clearmem = false): array[HashKind.digestSize, byte] {.noInit, genCharAPI.} =
## Produce a digest from a message
HashKind.hash(result, message, clearMem)

View File

@ -6,8 +6,10 @@
# * 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 ../zoo_exports
import
../platforms/[abstractions, endians],
../platforms/[abstractions, endians, views],
./sha256/sha256_generic
when UseASM_X86_32:
@ -82,7 +84,7 @@ template internalBlockSize*(H: type sha256): int =
## Returns the byte size of the hash function ingested blocks
BlockSize
func init*(ctx: var Sha256Context) =
func init*(ctx: var Sha256Context) {.libPrefix: prefix_sha256.} =
## Initialize or reinitialize a Sha256 context
ctx.msgLen = 0
@ -119,7 +121,7 @@ func initZeroPadded*(ctx: var Sha256Context) =
ctx.s.H[6] = 0xbafef9ea'u32
ctx.s.H[7] = 0x1837a9d8'u32
func update*(ctx: var Sha256Context, message: openarray[byte]) =
func update*(ctx: var Sha256Context, message: openarray[byte]) {.libPrefix: prefix_sha256, genCharAPI.} =
## Append a message to a SHA256 context
## for incremental SHA256 computation
##
@ -141,7 +143,7 @@ func update*(ctx: var Sha256Context, message: openarray[byte]) =
if bufIdx != 0 and bufIdx+bytesLeft >= BlockSize:
# Previous partial update, fill the buffer and do one sha256 hash
let free = BlockSize - bufIdx
ctx.buf.copy(dStart = bufIdx, message, sStart = 0, len = free)
ctx.buf.rawCopy(dStart = bufIdx, message, sStart = 0, len = free)
ctx.hashBuffer()
bufIdx = 0
cur = free
@ -156,26 +158,11 @@ func update*(ctx: var Sha256Context, message: openarray[byte]) =
if bytesLeft != 0:
# Store the tail in buffer
ctx.buf.copy(dStart = bufIdx, message, sStart = cur, len = bytesLeft)
ctx.buf.rawCopy(dStart = bufIdx, message, sStart = cur, len = bytesLeft)
ctx.msgLen += message.len.uint
func update*(ctx: var Sha256Context, message: openarray[char]) {.inline.} =
## Append a message to a SHA256 context
## for incremental SHA256 computation
##
## Security note: the tail of your message might be stored
## in an internal buffer.
## if sensitive content is used, ensure that
## `ctx.finish(...)` and `ctx.clear()` are called as soon as possible.
## Additionally ensure that the message(s) passed were stored
## in memory considered secure for your threat model.
##
## For passwords and secret keys, you MUST NOT use raw SHA-256
## use a Key Derivation Function instead (KDF)
ctx.update(message.toOpenArrayByte(message.low, message.high))
func finish*(ctx: var Sha256Context, digest: var array[32, byte]) =
func finish*(ctx: var Sha256Context, digest: var array[32, byte]) {.libPrefix: prefix_sha256.} =
## Finalize a SHA256 computation and output the
## message digest to the `digest` buffer.
##
@ -205,7 +192,7 @@ func finish*(ctx: var Sha256Context, digest: var array[32, byte]) =
ctx.s.hashMessageBlocks(ctx.buf.asUnchecked(), numBlocks = 1)
digest.dumpHash(ctx.s)
func clear*(ctx: var Sha256Context) =
func clear*(ctx: var Sha256Context) {.libPrefix: prefix_sha256.} =
## Clear the context internal buffers
## Security note:
## For passwords and secret keys, you MUST NOT use raw SHA-256

View File

@ -9,7 +9,7 @@
import
../hashes,
../mac/mac_hmac,
../platforms/primitives
../platforms/[primitives, views]
# HMAC-based Extract-and-Expand Key Derivation Function (HKDF)
# ------------------------------------------------------------
@ -21,15 +21,18 @@ import
type HKDF*[H: CryptoHash] = object
hmac: HMAC[H]
func hkdf_extract_init*[H: CryptoHash, S, I: char|byte](
func clear*(ctx: var HKDF) {.inline.} =
ctx.hmac.clear()
func hkdf_extract_init*[H: CryptoHash](
ctx: var HKDF[H],
salt: openArray[S],
ikm: openArray[I]) {.inline.}=
salt: openArray[byte],
ikm: openArray[byte]) {.inline.}=
ctx.hmac.init(salt)
ctx.hmac.update(ikm)
func hkdf_extract_append_to_IKM*[H: CryptoHash, T: char|byte](
ctx: var HKDF[H], append: openArray[T]) {.inline.} =
func hkdf_extract_append_to_IKM*[H: CryptoHash](
ctx: var HKDF[H], append: openArray[byte]) {.inline.} =
ctx.hmac.update(append)
func hkdf_extract_finish*[H: CryptoHash, N: static int](
@ -38,11 +41,11 @@ func hkdf_extract_finish*[H: CryptoHash, N: static int](
static: doAssert H.digestSize == N
ctx.hmac.finish(prk)
func hkdfExtract*[H: CryptoHash;S,I: char|byte, N: static int](
func hkdfExtract*[H: CryptoHash; N: static int](
ctx: var HKDF[H],
prk: var array[N, byte],
salt: openArray[S],
ikm: openArray[I]) {.inline.} =
salt: openArray[byte],
ikm: openArray[byte]) {.inline.} =
## "Extract" step of HKDF.
## Extract a fixed size pseudom-random key
## from an optional salt value
@ -69,12 +72,12 @@ func hkdfExtract*[H: CryptoHash;S,I: char|byte, N: static int](
ctx.hkdf_extract_init(salt, ikm)
ctx.hkdf_extract_finish(prk)
iterator hkdfExpandChunk*[H: CryptoHash; N: static int; I, A: char|byte](
iterator hkdfExpandChunk*[H: CryptoHash; N: static int](
ctx: var HKDF[H],
chunk: var array[N, byte],
prk: array[N, byte],
info: openArray[I],
append: openArray[A]): int =
info: openArray[byte],
append: openArray[byte]): int =
## "Expand" step of HKDF, with an iterator with up to 255 iterations.
##
## Note: The output MUST be at most 255 iterations as per RFC5869
@ -100,6 +103,9 @@ iterator hkdfExpandChunk*[H: CryptoHash; N: static int; I, A: char|byte](
##
## Temporary:
## - ctx: a HMAC["cryptographic-hash"] context, for example HMAC[sha256].
##
## After iterating, the HKDF context should be cleared
## if secret keying material was used.
const HashLen = H.digestSize()
static: doAssert N == HashLen
@ -117,12 +123,13 @@ iterator hkdfExpandChunk*[H: CryptoHash; N: static int; I, A: char|byte](
yield i
func hkdfExpand*[H: CryptoHash; K: static int; I, A: char|byte](
func hkdfExpand*[H: CryptoHash; K: static int](
ctx: var HKDF[H],
output: var openArray[byte],
prk: array[K, byte],
info: openArray[I],
append: openArray[A]) =
info: openArray[byte],
append: openArray[byte],
clearMem = false) =
## "Expand" step of HKDF
## Expand a fixed size pseudo random-key
## into several pseudo-random keys
@ -153,18 +160,20 @@ func hkdfExpand*[H: CryptoHash; K: static int; I, A: char|byte](
for i in ctx.hkdfExpandChunk(t, prk, info, append):
let iStart = i * HashLen
let size = min(HashLen, output.len - iStart)
copy(output, iStart, t, 0, size)
rawCopy(output, iStart, t, 0, size)
if iStart+HashLen >= output.len:
break
# ctx.clear() - TODO: very expensive
if clearMem:
ctx.clear()
func hkdfExpand*[H: CryptoHash; K: static int; I: char|byte](
func hkdfExpand*[H: CryptoHash; K: static int](
ctx: var HKDF[H],
output: var openArray[byte],
prk: array[K, byte],
info: openArray[I]) {.inline.} =
info: openArray[byte],
clearMem = false) {.inline.} =
## "Expand" step of HKDF
## Expand a fixed size pseudo random-key
## into several pseudo-random keys
@ -178,14 +187,15 @@ func hkdfExpand*[H: CryptoHash; K: static int; I: char|byte](
##
## Temporary:
## - ctx: a HMAC["cryptographic-hash"] context, for example HMAC[sha256].
hkdfExpand(ctx, output, prk, info, default(array[0, byte]))
hkdfExpand(ctx, output, prk, info, default(array[0, byte]), clearMem)
func hkdf*[H: CryptoHash, N: static int, O, S, K, I: char|byte](
func hkdf*[H: CryptoHash, N: static int](
Hash: typedesc[H],
output: var openArray[O],
salt: openArray[S],
ikm: openArray[K],
info: openArray[I]) {.inline.} =
output: var openArray[byte],
salt: openArray[byte],
ikm: openArray[byte],
info: openArray[byte],
clearMem = false) {.inline, genCharAPI.} =
## HKDF
## Inputs:
## - A hash function, with an output digest length HashLen
@ -197,4 +207,4 @@ func hkdf*[H: CryptoHash, N: static int, O, S, K, I: char|byte](
var ctx{.noInit.}: HMAC[H]
var prk{.noInit.}: array[H.digestSize(), byte]
ctx.hkdfExtract(prk, salt, ikm)
ctx.hkdfExpand(output, prk, info)
ctx.hkdfExpand(output, prk, info, clearMem)

View File

@ -8,7 +8,7 @@
import
../hashes,
../platforms/primitives
../platforms/[primitives, views]
# HMAC: Keyed-Hashing for Message Authentication
# ----------------------------------------------
@ -26,7 +26,7 @@ type HMAC*[H: CryptoHash] = object
inner: H
outer: H
func init*[H: CryptoHash, T: char|byte](ctx: var HMAC[H], secretKey: openArray[T]) =
func init*[H: CryptoHash](ctx: var HMAC[H], secretKey: openArray[byte]) {.genCharAPI.} =
## Initialize a HMAC-based Message Authentication Code
## with a pre-shared secret key
## between the parties that want to authenticate messages between each other.
@ -38,7 +38,7 @@ func init*[H: CryptoHash, T: char|byte](ctx: var HMAC[H], secretKey: openArray[T
## refreshed.
var key{.noInit.}: array[H.internalBlockSize(), byte]
if secretKey.len <= key.len:
copy(key, 0, secretKey, 0, secretKey.len)
rawCopy(key, 0, secretKey, 0, secretKey.len)
for i in secretKey.len ..< key.len:
key[i] = byte 0
else:
@ -62,12 +62,12 @@ func init*[H: CryptoHash, T: char|byte](ctx: var HMAC[H], secretKey: openArray[T
ctx.outer.init()
ctx.outer.update(key)
func update*[H: CryptoHash, T: char|byte](ctx: var HMAC[H], message: openArray[T]) =
func update*[H: CryptoHash](ctx: var HMAC[H], message: openArray[byte]) {.genCharAPI.} =
## Append a message to a HMAC authentication context.
## for incremental HMAC computation.
ctx.inner.update(message)
func finish*[H: CryptoHash, T: char|byte, N: static int](ctx: var HMAC[H], tag: var array[N, T]) =
func finish*[H: CryptoHash, N: static int](ctx: var HMAC[H], tag: var array[N, byte]) =
## Finalize a HMAC authentication
## and output an authentication tag to the `tag` buffer
##
@ -85,15 +85,16 @@ func clear*[H: CryptoHash](ctx: var HMAC[H]) =
ctx.inner.clear()
ctx.outer.clear()
func mac*[T: char|byte, H: CryptoHash, N: static int](
func mac*[T0, T1: char|byte, H: CryptoHash, N: static int](
Hash: type HMAC[H],
tag: var array[N, byte],
message: openArray[T],
secretKey: openarray[T],
message: openArray[T0],
secretKey: openArray[T1],
clearMem = false) =
## Produce an authentication tag from a message
## and a preshared unique non-reused secret key
# TODO: we can't use the {.genCharAPI.} macro
# due to 2 openArray[bytes] and the CryptoHash concept
static: doAssert N == H.digestSize()
var ctx {.noInit.}: HMAC[H]

View File

@ -7,7 +7,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../platforms/abstractions,
../platforms/[abstractions, views],
../math/arithmetic/bigints,
../math/arithmetic/[limbs, limbs_extmul],
../math/io/io_bigints
@ -157,10 +157,10 @@ type Poly1305_CTX = object
type poly1305* = Poly1305_CTX
func macMessageBlocks[T: byte|char](
func macMessageBlocks(
acc: var BigInt[130+1],
r: BigInt[124],
message: openArray[T],
message: openArray[byte],
blockSize = BlockSize): uint =
## Authenticate a message block by block
## Poly1305 block size is 16 bytes.
@ -180,16 +180,9 @@ func macMessageBlocks[T: byte|char](
for curBlock in 0 ..< numBlocks:
# range [0, 2¹²⁸-1)
when T is byte:
input.unmarshal(
message.toOpenArray(curBlock*BlockSize, curBlock*BlockSize + BlockSize - 1),
littleEndian
)
else:
input.unmarshal(
message.toOpenArrayByte(curBlock*BlockSize, curBlock*BlockSize + BlockSize - 1),
littleEndian
)
littleEndian)
input.setBit(8*blockSize) # range [2¹²⁸, 2¹²⁸+2¹²⁸-1)
acc += input # range [2¹²⁸, 2¹³⁰-1+2¹²⁸+2¹²⁸-1)
t.prod(acc, r) # range [2²⁵⁶, (2¹²⁴-1)(2¹³⁰+2(2¹²⁸-1)))
@ -224,7 +217,7 @@ func init*(ctx: var Poly1305_CTX, nonReusedKey: array[32, byte]) =
ctx.msgLen = 0
ctx.bufIdx = 0
func update*[T: char|byte](ctx: var Poly1305_CTX, message: openArray[T]) =
func update*(ctx: var Poly1305_CTX, message: openArray[byte]) {.genCharAPI.} =
## Append a message to a Poly1305 authentication context.
## for incremental Poly1305 computation
##
@ -255,12 +248,12 @@ func update*[T: char|byte](ctx: var Poly1305_CTX, message: openArray[T]) =
if free > bytesLeft:
# Enough free space, store in buffer
ctx.buf.copy(dStart = bufIdx, message, sStart = 0, len = bytesLeft)
ctx.buf.rawCopy(dStart = bufIdx, message, sStart = 0, len = bytesLeft)
ctx.bufIdx += bytesLeft.uint8
return
else:
# Fill the buffer and do one Poly1305 MAC
ctx.buf.copy(dStart = bufIdx, message, sStart = 0, len = free)
ctx.buf.rawCopy(dStart = bufIdx, message, sStart = 0, len = free)
ctx.macBuffer(blockSize = BlockSize)
# Update message state for further processing
@ -282,7 +275,7 @@ func update*[T: char|byte](ctx: var Poly1305_CTX, message: openArray[T]) =
doAssert ctx.bufIdx == 0
doAssert cur + bytesLeft == message.len.uint
ctx.buf.copy(dStart = 0'u, message, sStart = cur, len = bytesLeft)
ctx.buf.rawCopy(dStart = 0'u, message, sStart = cur, len = bytesLeft)
ctx.bufIdx = uint8 bytesLeft
func finish*(ctx: var Poly1305_CTX, tag: var array[16, byte]) =
@ -328,12 +321,12 @@ func clear*(ctx: var Poly1305_CTX) =
ctx.msgLen = 0
ctx.bufIdx = 0
func mac*[T: char|byte](
func mac*(
_: type poly1305,
tag: var array[16, byte],
message: openArray[T],
message: openArray[byte],
nonReusedKey: array[32, byte],
clearMem = false) =
clearMem = false) {.genCharAPI.} =
## Produce an authentication tag from a message
## and a preshared unique non-reused secret key
@ -345,11 +338,11 @@ func mac*[T: char|byte](
if clearMem:
ctx.clear()
func mac*[T: char|byte](
func mac*(
_: type poly1305,
message: openArray[T],
message: openArray[byte],
nonReusedKey: array[32, byte],
clearMem = false): array[16, byte]{.noInit.}=
clearMem = false): array[16, byte]{.noInit, genCharAPI.}=
## Produce an authentication tag from a message
## and a preshared unique non-reused secret key
poly1305.mac(result, message, nonReusedKey, clearMem)

View File

@ -26,7 +26,8 @@ import
# and so FpDbl would 768 bits.
static: doAssert UseASM_X86_64
{.localPassC:"-fomit-frame-pointer".} # Needed so that the compiler finds enough registers
# Necessary for the compiler to find enough registers
{.localPassC:"-fomit-frame-pointer".} # (enabled at -O1)
# Double-precision field addition
# ------------------------------------------------------------
@ -93,7 +94,7 @@ macro addmod2x_gen[N: static int](R: var Limbs[N], A, B: Limbs[N], m: Limbs[N di
result.add ctx.generate
func addmod2x_asm*[N: static int](r: var Limbs[N], a, b: Limbs[N], M: Limbs[N div 2]) =
func addmod2x_asm*[N: static int](r: var Limbs[N], a, b: Limbs[N], M: Limbs[N div 2]) {.noInline.} =
## Constant-time double-precision addition
## Output is conditionally reduced by 2ⁿp
## to stay in the [0, 2ⁿp) range
@ -159,7 +160,7 @@ macro submod2x_gen[N: static int](R: var Limbs[N], A, B: Limbs[N], m: Limbs[N di
result.add ctx.generate
func submod2x_asm*[N: static int](r: var Limbs[N], a, b: Limbs[N], M: Limbs[N div 2]) =
func submod2x_asm*[N: static int](r: var Limbs[N], a, b: Limbs[N], M: Limbs[N div 2]) {.noInline.} =
## Constant-time double-precision substraction
## Output is conditionally reduced by 2ⁿp
## to stay in the [0, 2ⁿp) range
@ -233,6 +234,6 @@ macro negmod2x_gen[N: static int](R: var Limbs[N], A: Limbs[N], m: Limbs[N div 2
var `usym`{.noinit, used.}: typeof(`A`)
result.add ctx.generate
func negmod2x_asm*[N: static int](r: var Limbs[N], a: Limbs[N], M: Limbs[N div 2]) =
func negmod2x_asm*[N: static int](r: var Limbs[N], a: Limbs[N], M: Limbs[N div 2]) {.noInline.} =
## Constant-time double-precision negation
negmod2x_gen(r, a, M)

View File

@ -25,13 +25,13 @@ import
static: doAssert UseASM_X86_32
{.localPassC:"-fomit-frame-pointer".} # Needed so that the compiler finds enough registers
# Necessary for the compiler to find enough registers
{.localPassC:"-fomit-frame-pointer".} # (enabled at -O1)
proc finalSubNoOverflowImpl*(
ctx: var Assembler_x86,
r: Operand or OperandArray,
a, M, scratch: OperandArray
) =
a, M, scratch: OperandArray) =
## Reduce `a` into `r` modulo `M`
## To be used when the modulus does not use the full bitwidth of the storing words
## for example a 255-bit modulus in n words of total max size 2^256
@ -58,8 +58,7 @@ proc finalSubMayOverflowImpl*(
ctx: var Assembler_x86,
r: Operand or OperandArray,
a, M, scratch: OperandArray,
scratchReg: Operand or Register or OperandReuse
) =
scratchReg: Operand or Register or OperandReuse) =
## Reduce `a` into `r` modulo `M`
## To be used when the final substraction can
## also overflow the limbs (a 2^256 order of magnitude modulus stored in n words of total max size 2^256)
@ -173,8 +172,9 @@ macro addmod_gen[N: static int](R: var Limbs[N], A, B, m: Limbs[N], spareBits: s
result.add ctx.generate()
func addmod_asm*(r: var Limbs, a, b, m: Limbs, spareBits: static int) =
func addmod_asm*(r: var Limbs, a, b, m: Limbs, spareBits: static int) {.noInline.} =
## Constant-time modular addition
# This MUST be noInline or Clang will run out of registers with LTO
addmod_gen(r, a, b, m, spareBits)
# Field substraction
@ -233,9 +233,10 @@ macro submod_gen[N: static int](R: var Limbs[N], A, B, m: Limbs[N]): untyped =
result.add ctx.generate
func submod_asm*(r: var Limbs, a, b, M: Limbs) =
func submod_asm*(r: var Limbs, a, b, M: Limbs) {.noInline.} =
## Constant-time modular substraction
## Warning, does not handle aliasing of a and b
# This MUST be noInline or Clang will run out of registers with LTO
submod_gen(r, a, b, M)
# Field negation

View File

@ -28,8 +28,9 @@ import
static: doAssert UseASM_X86_64
# Necessary for the compiler to find enough registers (enabled at -O1)
{.localPassC:"-fomit-frame-pointer".}
# Necessary for the compiler to find enough registers
{.localPassC:"-fomit-frame-pointer".} # (enabled at -O1)
{.localPassC:"-fno-sanitize=address".} # need 15 registers out of 16 (1 reserved for stack pointer, none available for Address Sanitizer)
# Montgomery multiplication
# ------------------------------------------------------------
@ -37,8 +38,7 @@ static: doAssert UseASM_X86_64
macro mulMont_CIOS_sparebit_gen[N: static int](
r_PIR: var Limbs[N], a_PIR, b_PIR,
M_PIR: Limbs[N], m0ninv_REG: BaseType,
skipFinalSub: static bool
): untyped =
skipFinalSub: static bool): untyped =
## Generate an optimized Montgomery Multiplication kernel
## using the CIOS method
##
@ -184,26 +184,19 @@ macro mulMont_CIOS_sparebit_gen[N: static int](
)
result.add ctx.generate()
func mulMont_CIOS_sparebit_asm*(r: var Limbs, a, b, M: Limbs, m0ninv: BaseType, skipFinalSub: static bool = false) =
func mulMont_CIOS_sparebit_asm*(r: var Limbs, a, b, M: Limbs, m0ninv: BaseType, skipFinalSub: static bool = false) {.noInline.} =
## Constant-time Montgomery multiplication
## If "skipFinalSub" is set
## the result is in the range [0, 2M)
## otherwise the result is in the range [0, M)
##
## This procedure can only be called if the modulus doesn't use the full bitwidth of its underlying representation
# This MUST be noInline or Clang will run out of registers with LTO
r.mulMont_CIOS_sparebit_gen(a, b, M, m0ninv, skipFinalSub)
# Montgomery Squaring
# ------------------------------------------------------------
func square_asm_inline[rLen, aLen: static int](r: var Limbs[rLen], a: Limbs[aLen]) {.inline.} =
## Multi-precision Squaring
## Assumes r doesn't alias a
## Extra indirection as the generator assumes that
## arrays are pointers, which is true for parameters
## but not for stack variables
sqr_gen(r, a)
func squareMont_CIOS_asm*[N](
r: var Limbs[N],
a, M: Limbs[N],
@ -211,8 +204,8 @@ func squareMont_CIOS_asm*[N](
spareBits: static int, skipFinalSub: static bool) =
## Constant-time modular squaring
var r2x {.noInit.}: Limbs[2*N]
r2x.square_asm_inline(a)
r.redcMont_asm_inline(r2x, M, m0ninv, spareBits, skipFinalSub)
square_asm(r2x, a)
r.redcMont_asm(r2x, M, m0ninv, spareBits, skipFinalSub)
# Montgomery Sum of Products
# ------------------------------------------------------------
@ -220,8 +213,7 @@ func squareMont_CIOS_asm*[N](
macro sumprodMont_CIOS_spare2bits_gen[N, K: static int](
r_PIR: var Limbs[N], a_PIR, b_PIR: array[K, Limbs[N]],
M_PIR: Limbs[N], m0ninv_REG: BaseType,
skipFinalSub: static bool
): untyped =
skipFinalSub: static bool): untyped =
## Generate an optimized Montgomery merged sum of products ⅀aᵢ.bᵢ kernel
## using the CIOS method
##

View File

@ -30,8 +30,9 @@ static: doAssert UseASM_X86_64
# MULX/ADCX/ADOX
{.localPassC:"-madx -mbmi2".}
# Necessary for the compiler to find enough registers (enabled at -O1)
{.localPassC:"-fomit-frame-pointer".}
# Necessary for the compiler to find enough registers
{.localPassC:"-fomit-frame-pointer".} # (enabled at -O1)
{.localPassC:"-fno-sanitize=address".} # need 15 registers out of 16 (1 reserved for stack pointer, none available for Address Sanitizer)
# Montgomery Multiplication
# ------------------------------------------------------------
@ -42,8 +43,7 @@ proc mulx_by_word(
t: OperandArray,
a: Operand, # Pointer in scratchspace
word0: Operand,
lo: Operand
) =
lo: Operand) =
## Multiply the `a[0..<N]` by `word` and store in `t[0..<N]`
## and carry register `C` (t[N])
## `t` and `C` overwritten
@ -89,8 +89,7 @@ proc mulaccx_by_word(
a: Operand, # Pointer in scratchspace
i: int,
word: Operand,
lo: Operand
) =
lo: Operand) =
## Multiply the `a[0..<N]` by `word`
## and accumulate in `t[0..<N]`
## and carry register `C` (t[N])
@ -131,8 +130,7 @@ proc partialRedx(
M: OperandArray,
m0ninv: Operand,
lo: Operand or Register,
S: Operand
) =
S: Operand) =
## Partial Montgomery reduction
## For CIOS method
## `C` the update carry flag (represents t[N])
@ -307,7 +305,7 @@ func squareMont_CIOS_asm_adx*[N](
spareBits: static int, skipFinalSub: static bool) =
## Constant-time modular squaring
var r2x {.noInit.}: Limbs[2*N]
r2x.square_asm_adx_inline(a)
r2x.square_asm_adx(a)
r.redcMont_asm_adx(r2x, M, m0ninv, spareBits, skipFinalSub)
# Montgomery Sum of Products
@ -316,8 +314,7 @@ func squareMont_CIOS_asm_adx*[N](
macro sumprodMont_CIOS_spare2bits_adx_gen[N, K: static int](
r_PIR: var Limbs[N], a_PIR, b_PIR: array[K, Limbs[N]],
M_PIR: Limbs[N], m0ninv_REG: BaseType,
skipFinalSub: static bool
): untyped =
skipFinalSub: static bool): untyped =
## Generate an optimized Montgomery merged sum of products ⅀aᵢ.bᵢ kernel
## using the CIOS method
##

View File

@ -27,8 +27,8 @@ static: doAssert UseASM_X86_64
# MULX/ADCX/ADOX
{.localPassC:"-madx -mbmi2".}
# Necessary for the compiler to find enough registers (enabled at -O1)
# {.localPassC:"-fomit-frame-pointer".}
# Necessary for the compiler to find enough registers
# {.localPassC:"-fomit-frame-pointer".} # (enabled at -O1)
# Multiplication
# ------------------------------------------------------------
@ -36,8 +36,7 @@ proc mulx_by_word(
ctx: var Assembler_x86,
r0: Operand,
a, t: OperandArray,
word0: Operand
) =
word0: Operand) =
## Multiply the `a[0..<N]` by `word`
## and store in `[t[n..1]:r0]`
## with [t[n..1]:r0] = tn, tn-1, ... t1, r0
@ -74,8 +73,7 @@ proc mulaccx_by_word(
r: OperandArray,
i: int,
a, t: OperandArray,
word: Operand
) =
word: Operand) =
## Multiply the `a[0..<N]` by `word`
## and store in `[t[n..0]:r0]`
## with [t[n..0]:r0] = tn, tn-1, ... t1, r0
@ -603,12 +601,7 @@ macro sqrx_gen*[rLen, aLen: static int](r_PIR: var Limbs[rLen], a_PIR: Limbs[aLe
# Codegen
result.add ctx.generate
func square_asm_adx_inline*[rLen, aLen: static int](r: var Limbs[rLen], a: Limbs[aLen]) {.inline.} =
## Multi-precision Squaring
## inline version
sqrx_gen(r, a)
func square_asm_adx*[rLen, aLen: static int](r: var Limbs[rLen], a: Limbs[aLen]) =
## Multi-precision Squaring
## Assumes r doesn't alias a
square_asm_adx_inline(r, a)
sqrx_gen(r, a)

View File

@ -22,8 +22,8 @@ import
static: doAssert UseASM_X86_32
# Necessary for the compiler to find enough registers (enabled at -O1)
{.localPassC:"-fomit-frame-pointer".}
# Necessary for the compiler to find enough registers
{.localPassC:"-fomit-frame-pointer".} # (enabled at -O1)
# Montgomery reduction
# ------------------------------------------------------------
@ -33,9 +33,7 @@ macro redc2xMont_gen*[N: static int](
a_PIR: array[N*2, SecretWord],
M_PIR: array[N, SecretWord],
m0ninv_REG: BaseType,
spareBits: static int, skipFinalSub: static bool
) =
spareBits: static int, skipFinalSub: static bool) =
# No register spilling handling
doAssert N > 2, "The Assembly-optimized montgomery reduction requires a minimum of 2 limbs."
doAssert N <= 6, "The Assembly-optimized montgomery reduction requires at most 6 limbs."
@ -164,29 +162,17 @@ macro redc2xMont_gen*[N: static int](
# Code generation
result.add ctx.generate()
func redcMont_asm_inline*[N: static int](
r: var array[N, SecretWord],
a: array[N*2, SecretWord],
M: array[N, SecretWord],
m0ninv: BaseType,
spareBits: static int,
skipFinalSub: static bool = false
) {.inline.} =
## Constant-time Montgomery reduction
## Inline-version
redc2xMont_gen(r, a, M, m0ninv, spareBits, skipFinalSub)
func redcMont_asm*[N: static int](
r: var array[N, SecretWord],
a: array[N*2, SecretWord],
M: array[N, SecretWord],
m0ninv: BaseType,
spareBits: static int,
skipFinalSub: static bool
) =
skipFinalSub: static bool) {.noInline.} =
## Constant-time Montgomery reduction
# This MUST be noInline or Clang will run out of registers with LTO
static: doAssert UseASM_X86_64, "This requires x86-64."
redcMont_asm_inline(r, a, M, m0ninv, spareBits, skipFinalSub)
redc2xMont_gen(r, a, M, m0ninv, spareBits, skipFinalSub)
# Montgomery conversion
# ----------------------------------------------------------

View File

@ -23,8 +23,8 @@ static: doAssert UseASM_X86_64
# MULX/ADCX/ADOX
{.localPassC:"-madx -mbmi2".}
# Necessary for the compiler to find enough registers (enabled at -O1)
{.localPassC:"-fomit-frame-pointer".}
# Necessary for the compiler to find enough registers
{.localPassC:"-fomit-frame-pointer".} # (enabled at -O1)
# No exceptions allowed
{.push raises: [].}
@ -37,8 +37,7 @@ macro redc2xMont_adx_gen[N: static int](
a_PIR: array[N*2, SecretWord],
M_PIR: array[N, SecretWord],
m0ninv_REG: BaseType,
spareBits: static int, skipFinalSub: static bool
) =
spareBits: static int, skipFinalSub: static bool) =
# No register spilling handling
doAssert N <= 6, "The Assembly-optimized montgomery multiplication requires at most 6 limbs."
@ -141,28 +140,18 @@ macro redc2xMont_adx_gen[N: static int](
# Code generation
result.add ctx.generate()
func redcMont_asm_adx_inline*[N: static int](
r: var array[N, SecretWord],
a: array[N*2, SecretWord],
M: array[N, SecretWord],
m0ninv: BaseType,
spareBits: static int,
skipFinalSub: static bool = false
) {.inline.} =
## Constant-time Montgomery reduction
## Inline-version
redc2xMont_adx_gen(r, a, M, m0ninv, spareBits, skipFinalSub)
func redcMont_asm_adx*[N: static int](
r: var array[N, SecretWord],
a: array[N*2, SecretWord],
M: array[N, SecretWord],
m0ninv: BaseType,
spareBits: static int,
skipFinalSub: static bool = false
) =
skipFinalSub: static bool = false) {.noInline.} =
## Constant-time Montgomery reduction
redcMont_asm_adx_inline(r, a, M, m0ninv, spareBits, skipFinalSub)
# Inlining redcMont_asm_adx twice in mul_fp2_complex_asm_adx
# causes GCC to miscompile with -Os (--opt:size)
# see https://github.com/mratsim/constantine/issues/229
redc2xMont_adx_gen(r, a, M, m0ninv, spareBits, skipFinalSub)
# Montgomery conversion
# ----------------------------------------------------------

View File

@ -249,8 +249,7 @@ func sumprod*[N: static int](r: var FF, a, b: array[N, FF], skipFinalSub: static
r.mres.sumprodMont(
cast[ptr array[N, typeof(a[0].mres)]](a.unsafeAddr)[],
cast[ptr array[N, typeof(b[0].mres)]](b.unsafeAddr)[],
FF.fieldMod(), FF.getNegInvModWord(), FF.getSpareBits(), skipFinalSub
)
FF.fieldMod(), FF.getNegInvModWord(), FF.getSpareBits(), skipFinalSub)
# ############################################################
#

View File

@ -117,7 +117,6 @@ debug:
r = SecretWord r
var a, b: array[2, SecretWord]
var e: array[2, SecretWord]
smul(a[1], a[0], u, r)
smul(b[1], b[0], v, q)
@ -373,8 +372,8 @@ template matVecMul_shr_k_impl(
# First iteration of [u v] [f]
# [q r].[g]
cf.ssumprodAccNoCarry(u, f[0], v, g[0])
cg.ssumprodAccNoCarry(q, f[0], r, g[0])
ssumprodAccNoCarry(cf, u, f[0], v, g[0])
ssumprodAccNoCarry(cg, q, f[0], r, g[0])
# bottom k bits are zero by construction
debug:
doAssert BaseType(cf.lo and Max) == 0, "bottom k bits should be 0, cf.lo: " & $BaseType(cf.lo)
@ -384,8 +383,8 @@ template matVecMul_shr_k_impl(
cg.ashr(k)
for i in 1 ..< numLimbsLeft:
cf.ssumprodAccNoCarry(u, f[i], v, g[i])
cg.ssumprodAccNoCarry(q, f[i], r, g[i])
ssumprodAccNoCarry(cf, u, f[i], v, g[i])
ssumprodAccNoCarry(cg, q, f[i], r, g[i])
f[i-1] = cf.lo and Max
g[i-1] = cg.lo and Max
cf.ashr(k)

View File

@ -543,7 +543,10 @@ func sumprodMont*[N: static int](
r: var Limbs, a, b: array[N, Limbs],
M: Limbs, m0ninv: BaseType,
spareBits: static int,
skipFinalSub: static bool = false) {.inline.} =
skipFinalSub: static bool = false) {.noInline.} =
## Compute r <- ⅀aᵢ.bᵢ (mod M) (sum of products)
# This function must be noInline or GCC miscompiles
# with LTO, see https://github.com/mratsim/constantine/issues/230
when spareBits >= 2:
when UseASM_X86_64 and r.len in {2 .. 6}:
if ({.noSideEffect.}: hasAdx()):

View File

@ -45,10 +45,6 @@ func has_P_3mod4_primeModulus*(C: static Curve): static bool =
## Returns true iff p ≡ 3 (mod 4)
(BaseType(C.Mod.limbs[0]) and 3) == 3
func has_P_3mod8_primeModulus*(C: static Curve): static bool =
## Returns true iff p ≡ 3 (mod 8)
(BaseType(C.Mod.limbs[0]) and 7) == 3
func has_P_5mod8_primeModulus*(C: static Curve): static bool =
## Returns true iff p ≡ 5 (mod 8)
(BaseType(C.Mod.limbs[0]) and 7) == 5

View File

@ -28,8 +28,8 @@ static: doAssert UseASM_X86_64
# MULX/ADCX/ADOX
{.localPassC:"-madx -mbmi2".}
# Necessary for the compiler to find enough registers (enabled at -O1)
{.localPassC:"-fomit-frame-pointer".}
# Necessary for the compiler to find enough registers
{.localPassC:"-fomit-frame-pointer".} # (enabled at -O1)
# No exceptions allowed
{.push raises: [].}
@ -48,8 +48,7 @@ func has1extraBit(F: type Fp): bool =
func sqrx2x_complex_asm_adx*(
r: var array[2, FpDbl],
a: array[2, Fp]
) =
a: array[2, Fp]) =
## Complex squaring on 𝔽p2
# This specialized proc inlines all calls and avoids many ADX support checks.
# and push/pop for paramater passing.
@ -69,8 +68,7 @@ func sqrx2x_complex_asm_adx*(
func sqrx_complex_sparebit_asm_adx*(
r: var array[2, Fp],
a: array[2, Fp]
) =
a: array[2, Fp]) =
## Complex squaring on 𝔽p2
# This specialized proc inlines all calls and avoids many ADX support checks.
# and push/pop for paramater passing.
@ -91,8 +89,7 @@ func sqrx_complex_sparebit_asm_adx*(
func mul2x_fp2_complex_asm_adx*(
r: var array[2, FpDbl],
a, b: array[2, Fp]
) =
a, b: array[2, Fp]) =
## Complex multiplication on 𝔽p2
var D {.noInit.}: typeof(r.c0)
var t0 {.noInit.}, t1 {.noInit.}: typeof(a.c0)
@ -121,15 +118,15 @@ func mul_fp2_complex_asm_adx*(
## Complex multiplication on 𝔽p2
var d {.noInit.}: array[2,doublePrec(Fp)]
d.mul2x_fp2_complex_asm_adx(a, b)
r.c0.mres.limbs.redcMont_asm_adx_inline(
# Inlining redcMont_asm_adx causes GCC to miscompile with -Os (--opt:size)
# see https://github.com/mratsim/constantine/issues/229
r.c0.mres.limbs.redcMont_asm_adx(
d.c0.limbs2x,
Fp.fieldMod().limbs,
Fp.getNegInvModWord(),
Fp.getSpareBits()
)
r.c1.mres.limbs.redcMont_asm_adx_inline(
Fp.getSpareBits())
r.c1.mres.limbs.redcMont_asm_adx(
d.c1.limbs2x,
Fp.fieldMod().limbs,
Fp.getNegInvModWord(),
Fp.getSpareBits()
)
Fp.getSpareBits())

View File

@ -43,7 +43,7 @@ func getWindowLen(bufLen: int): uint =
func powPrologue[F](a: var F, scratchspace: var openarray[F]): uint =
## Setup the scratchspace, then set a to 1.
## Returns the fixed-window size for exponentiation with window optimization
result = scratchspace.len.getWindowLen
result = scratchspace.len.getWindowLen()
# Precompute window content, special case for window = 1
# (i.e scratchspace has only space for 2 temporaries)
# The content scratchspace[2+k] is set at [k]P
@ -62,8 +62,7 @@ func powSquarings[F](
tmp: var F,
window: uint,
acc, acc_len: var uint,
e: var int
): tuple[k, bits: uint] {.inline.}=
e: var int): tuple[k, bits: uint] {.inline.}=
## Squaring step of exponentiation by squaring
## Get the next k bits in range [1, window)
## Square k times
@ -105,8 +104,7 @@ func powSquarings[F](
func powUnsafeExponent[F](
a: var F,
exponent: openArray[byte],
scratchspace: var openArray[F]
) =
scratchspace: var openArray[F]) =
## Extension field exponentiation r = a^exponent (mod p^m)
##
## Warning ⚠️ :

View File

@ -979,12 +979,17 @@ func square2x_disjoint*[Fdbl, F](
# Multiplications (specializations)
# -------------------------------------------------------------------
func prodImpl_fp4o2_p3mod8[C: static Curve](r: var Fp4[C], a, b: Fp4[C]) =
func prodImpl_fp4o2_complex_snr_1pi[C: static Curve](r: var Fp4[C], a, b: Fp4[C]) =
## Returns r = a * b
## For 𝔽p4/𝔽p2 with p ≡ 3 (mod 8),
## hence 𝔽p QNR is 𝑖 = √-1 as p ≡ 3 (mod 8) implies p ≡ 3 (mod 4)
## and 𝔽p SNR is (1 + i)
static: doAssert C.has_P_3mod8_primeModulus()
## For 𝔽p4/𝔽p2 with the following non-residue (NR) constraints:
## * -1 is a quadratic non-residue in 𝔽p hence 𝔽p2 has coordinates a+𝑖b with i = √-1. This implies p ≡ 3 (mod 4)
## * (1 + i) is a quadratic non-residue in 𝔽p hence 𝔽p2 has coordinates a+vb with v = √(1+𝑖).
##
## According to Benger-Scott 2009(https://eprint.iacr.org/2009/556.pdf)
## About 2/3 of the p ≡ 3 (mod 8) primes are in this case
static:
doAssert C.getNonResidueFp() == -1
doAssert C.getNonresidueFp2() == (1, 1)
var
b10_m_b11{.noInit.}, b10_p_b11{.noInit.}: Fp[C]
n_a01{.noInit.}, n_a11{.noInit.}: Fp[C]
@ -1374,8 +1379,8 @@ func prod*(r: var QuadraticExt, a, b: QuadraticExt) =
when QuadraticExt is Fp12 or r.typeof.F.C.has_large_field_elem():
# BW6-761 requires too many registers for Dbl width path
r.prod_generic(a, b)
elif QuadraticExt is Fp4 and QuadraticExt.C.has_P_3mod8_primeModulus():
r.prodImpl_fp4o2_p3mod8(a, b)
elif QuadraticExt is Fp4 and QuadraticExt.C.getNonResidueFp() == -1 and QuadraticExt.C.getNonResidueFp2() == (1, 1):
r.prodImpl_fp4o2_complex_snr_1pi(a, b)
else:
var d {.noInit.}: doublePrec(typeof(r))
d.prod2x_disjoint(a.c0, a.c1, b.c0, b.c1)
@ -1628,13 +1633,18 @@ func square_Chung_Hasan_SQR3(r: var CubicExt, a: CubicExt) =
# Multiplications (specializations)
# -------------------------------------------------------------------
func prodImpl_fp6o2_p3mod8[C: static Curve](r: var Fp6[C], a, b: Fp6[C]) =
func prodImpl_fp6o2_complex_snr_1pi[C: static Curve](r: var Fp6[C], a, b: Fp6[C]) =
## Returns r = a * b
## For 𝔽p6/𝔽p2 with p ≡ 3 (mod 8),
## hence 𝔽p QNR is 𝑖 = √-1 as p ≡ 3 (mod 8) implies p ≡ 3 (mod 4)
## and 𝔽p SNR is (1 + i)
## For 𝔽p4/𝔽p2 with the following non-residue (NR) constraints:
## * -1 is a quadratic non-residue in 𝔽p hence 𝔽p2 has coordinates a+𝑖b with i = √-1. This implies p ≡ 3 (mod 4)
## * (1 + i) is a cubic non-residue in 𝔽p hence 𝔽p2 has coordinates a+vb with v = √(1+𝑖).
##
## According to Benger-Scott 2009 (https://eprint.iacr.org/2009/556.pdf)
## About 2/3 of the p ≡ 3 (mod 8) primes are in this case
# https://eprint.iacr.org/2022/367 - Equation 8
static: doAssert C.has_P_3mod8_primeModulus()
static:
doAssert C.getNonResidueFp() == -1
doAssert C.getNonresidueFp2() == (1, 1)
var
b10_p_b11{.noInit.}, b10_m_b11{.noInit.}: Fp[C]
b20_p_b21{.noInit.}, b20_m_b21{.noInit.}: Fp[C]
@ -2133,8 +2143,8 @@ func prod*(r: var CubicExt, a, b: CubicExt) =
## Out-of-place multiplication
when CubicExt.C.has_large_field_elem():
r.prodImpl(a, b)
elif r is Fp6 and CubicExt.C.has_P_3mod8_primeModulus():
r.prodImpl_fp6o2_p3mod8(a, b)
elif r is Fp6 and CubicExt.C.getNonResidueFp() == -1 and CubicExt.C.getNonResidueFp2() == (1, 1):
r.prodImpl_fp6o2_complex_snr_1pi(a, b)
else:
var d {.noInit.}: doublePrec(typeof(r))
d.prod2x(a, b)

View File

@ -34,9 +34,9 @@ template blobFrom*(dst: var openArray[byte], src: SomeUnsignedInt, startIdx: int
for i in 0 ..< sizeof(src):
dst[startIdx+sizeof(src)-1-i] = toByte(src shr (i * 8))
func parseFromBlob*[T: byte|char](
func parseFromBlob*(
dst: var SomeUnsignedInt,
src: openArray[T],
src: openArray[byte],
cursor: var uint, endian: static Endianness) {.inline.} =
## Read an unsigned integer from a raw binary blob.
## The `cursor` represents the current index in the array and is updated
@ -63,8 +63,8 @@ func parseFromBlob*[T: byte|char](
dst = accum
cursor.inc(L)
func dumpRawInt*[T: byte|char](
dst: var openArray[T],
func dumpRawInt*(
dst: var openArray[byte],
src: SomeUnsignedInt,
cursor: uint, endian: static Endianness) {.inline.} =
## Dump an integer into raw binary form

View File

@ -6,7 +6,7 @@
# * 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 ./utils
import ./c_abi
{.passc: gorge("llvm-config --cflags").}
{.passl: gorge("llvm-config --libs").}

View File

@ -12,7 +12,7 @@
#
# ############################################################
import ./utils
import ./c_abi
# ############################################################
#

View File

@ -8,7 +8,7 @@
import
./bindings/nvidia_abi {.all.},
./bindings/utils,
./bindings/c_abi,
./llvm, ./ir,
./nvidia_inlineasm,
../primitives

View File

@ -86,17 +86,16 @@ func setZero*[N](a: var array[N, SomeNumber]){.inline.} =
for i in 0 ..< a.len:
a[i] = 0
func copy*[T: byte|char](
func rawCopy*(
dst: var openArray[byte],
dStart: SomeInteger,
src: openArray[T],
src: openArray[byte],
sStart: SomeInteger,
len: SomeInteger
) {.inline.} =
## Copy dst[dStart ..< dStart+len] = src[sStart ..< sStart+len]
## Unlike the standard library, this cannot throw
## even a defect.
## It also handles copy of char into byte arrays
debug:
doAssert 0 <= dStart and dStart+len <= dst.len.uint, "dStart: " & $dStart & ", dStart+len: " & $(dStart+len) & ", dst.len: " & $dst.len
doAssert 0 <= sStart and sStart+len <= src.len.uint, "sStart: " & $sStart & ", sStart+len: " & $(sStart+len) & ", src.len: " & $src.len

View File

@ -0,0 +1,181 @@
# 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 std/macros
# OpenArray type
# ---------------------------------------------------------
template toOpenArray*[T](p: ptr UncheckedArray[T], len: int): openArray[T] =
p.toOpenArray(0, len-1)
# View type
# ---------------------------------------------------------
#
# This view type is equivalent to (pointer + length)
# like openArray. Unlike openArray it can be stored in a type
# Or can be used for nested views like openArray[View[byte]]
type View*[T] = object
# TODO, use `lent UncheckedArray[T]` for proper borrow-checking - https://github.com/nim-lang/Nim/issues/21674
data: ptr UncheckedArray[T]
len: int
template toOpenArray*[T](v: View[T]): openArray[T] =
v.data.toOpenArray(0, v.len-1)
# Binary blob API
# ---------------------------------------------------------
#
# High-level API needs to provide functions of the form
# - func verify[T: byte|char](pubkey: PubKey, message: T, signature: Signature)
# - func update[T: byte|char](ctx: var Sha256Context, message: openarray[T])
#
# for all APIs that ingest bytes/strings including:
# - Ciphers
# - Signature protocols
# - Hashing algorithms
# - Message Authentication code
# - Key derivation functions
#
# This causes the following issues:
# - Code explosion due to monomorphization. The code for bytes and char will be duplicated needlessly.
# - Cannot be exported to C. Generic code cannot be exported to C and so will need manual split
# - Longer compile-times. The inner functions can be byte-only instead of using generics.
#
# Instead we create a `genCharAPI` macro that generates the same function as an openArray[byte]
# but with openArray[char] inputs
template toOpenArrayByte[T: byte|char](oa: openArray[T]): openArray[byte] =
when T is byte:
oa
else:
oa.toOpenArrayByte(oa.low, oa.high)
macro genCharAPI*(procAst: untyped): untyped =
## For each openArray[byte] parameter in the input proc
## generate an openArray[char] variation.
procAst.expectKind({nnkProcDef, nnkFuncDef})
result = newStmtList()
result.add procAst
var genericParams = procAst[2].copyNimTree()
var wrapperParams = nnkFormalParams.newTree(procAst.params[0].copyNimTree())
var wrapperBody = newCall(ident($procAst.name))
proc matchBytes(node: NimNode): bool =
node.kind == nnkBracketExpr and
node[0].eqIdent"openArray" and
node[1].eqIdent"byte"
# We do 2 passes:
# If a single params is openArray[byte], we instantiate a non-generic proc.
# - This should make for faster compile-times.
# - It is also necessary for `hash` and `mac`, as it seems like overloading
# a concept function with an argument that matches but the generic and a concrete param
# crashes. i.e. either you use full generic (with genCharAPI) or you instantiate 2 concrete procs
let countBytesParams = block:
var count = 0
for i in 1 ..< procAst.params.len:
if procAst.params[i][^2].matchBytes():
count += 1
elif procAst.params[i][^2].kind == nnkVarTy and procAst.params[i][^2][0].matchBytes():
count += 1
count
if countBytesParams == 0:
error "Using genCharAPI on an input without any openArray[byte] parameter."
if countBytesParams == 1:
for i in 1 ..< procAst.params.len:
# Unfortunately, even in typed macro, .sameType(getType(openArray[byte])) doesn't match
if procAst.params[i][^2].matchBytes():
# Handle "a, b: openArray[byte]"
for j in 0 ..< procAst.params[i].len - 2:
wrapperParams.add newIdentDefs(
procAst.params[i][j].copyNimTree(),
nnkBracketExpr.newTree(ident"openArray", ident"char"))
wrapperBody.add newCall(bindSym"toOpenArrayByte", procAst.params[i][j])
elif procAst.params[i][^2].kind == nnkVarTy and procAst.params[i][^2][0].matchBytes():
# Handle "a, b: openArray[byte]"
for j in 0 ..< procAst.params[i].len - 2:
wrapperParams.add newIdentDefs(
procAst.params[i][j].copyNimTree(),
nnkVarTy.newTree(nnkBracketExpr.newTree(ident"openArray", ident"char")))
wrapperBody.add newCall(bindSym"toOpenArrayByte", procAst.params[i][j])
else:
wrapperParams.add procAst.params[i].copyNimTree()
# Handle "a, b: int"
for j in 0 ..< procAst.params[i].len - 2:
wrapperBody.add ident($procAst.params[i][j])
else:
if genericParams.kind == nnkEmpty:
genericParams = nnkGenericParams.newTree()
for i in 1 ..< procAst.params.len:
# Unfortunately, even in typed macro, .sameType(getType(openArray[byte])) doesn't match
if procAst.params[i][^2].matchBytes():
# Handle "a, b: openArray[byte]"
for j in 0 ..< procAst.params[i].len - 2:
let genericId = ident("API_" & $i & "_" & $j)
wrapperParams.add newIdentDefs(
procAst.params[i][j].copyNimTree(),
nnkBracketExpr.newTree(ident"openArray", genericId))
genericParams.add newIdentDefs(
genericId,
nnkInfix.newTree(ident("|"), ident("byte"), ident("char")))
wrapperBody.add newCall(bindSym"toOpenArrayByte", procAst.params[i][j])
elif procAst.params[i][^2].kind == nnkVarTy and procAst.params[i][^2][0].matchBytes():
for j in 0 ..< procAst.params[i].len - 2:
let genericId = ident("API_" & $i & "_" & $j)
wrapperParams.add newIdentDefs(
procAst.params[i][j].copyNimTree(),
nnkVarTy.newTree(nnkBracketExpr.newTree(bindSym"openArray", genericId)))
genericParams.add newIdentDefs(
genericId,
nnkInfix.newTree(ident("|"), ident("byte"), ident("char")))
wrapperBody.add newCall(bindSym"toOpenArrayByte", procAst.params[i][j])
else:
wrapperParams.add procAst.params[i].copyNimTree()
# Handle "a, b: int"
for j in 0 ..< procAst.params[i].len - 2:
wrapperBody.add ident($procAst.params[i][j])
var pragmas = nnkPragma.newTree(ident"inline")
let skipPragmas = ["inline", "noinline", "noInline", "exportc", "exportcpp", "extern", "cdecl", "stdcall", "dynlib", "libPrefix"]
for i in 0 ..< procAst.pragma.len:
if procAst.pragma[i].kind == nnkIdent:
if $procAst.pragma[i] notin skipPragmas:
pragmas.add procAst.pragma[i].copyNimTree()
else:
procAst.pragma[i].expectKind(nnkExprColonExpr)
if $procAst.pragma[i][0] notin skipPragmas:
pragmas.add procAst.pragma[i].copyNimTree()
let wrapper = newTree(
procAst.kind, # proc or func
procAst[0].copyNimTree(), # name: Keep export marker if any
newEmptyNode(), # term-rewriting macros
genericParams,
wrapperParams,
pragmas,
newEmptyNode(),
wrapperBody)
result.add wrapper
when isMainModule:
expandMacros:
proc foo(x: int, a: openArray[byte]) {.genCharAPI.} =
discard
proc bar(x: int, a: openArray[byte], b: openArray[byte]) {.genCharAPI.} =
discard

View File

@ -14,7 +14,8 @@ import
../math/constants/zoo_generators,
../math/config/curves,
../hash_to_curve/[hash_to_curve, h2c_hash_to_field],
../hashes
../hashes,
../platforms/views
# ############################################################
#
@ -23,7 +24,7 @@ import
# ############################################################
# This module implements generic BLS signatures
# https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-04
# https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html
# https://github.com/cfrg/draft-irtf-cfrg-bls-signature
#
# We use generic shortnames SecKey, PubKey, Sig
@ -56,14 +57,14 @@ func derivePubkey*[Pubkey, SecKey](pubkey: var Pubkey, seckey: SecKey): bool =
pubkey.affine(pk)
return true
func coreSign*[B1, B2, B3: byte|char, Sig, SecKey](
func coreSign*[Sig, SecKey](
signature: var Sig,
secretKey: SecKey,
message: openarray[B1],
message: openArray[byte],
H: type CryptoHash,
k: static int,
augmentation: openarray[B2],
domainSepTag: openarray[B3]) =
augmentation: openArray[byte],
domainSepTag: openArray[byte]) {.genCharAPI.} =
## Computes a signature for the message from the specified secret key.
##
## Output:
@ -81,7 +82,7 @@ func coreSign*[B1, B2, B3: byte|char, Sig, SecKey](
## - `augmentation`, an optional augmentation to the message. This will be prepended,
## prior to hashing.
## This is used for building the "message augmentation" variant of BLS signatures
## https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04#section-3.2
## https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#section-3.2
## which requires `CoreSign(SK, PK || message)`
## and `CoreVerify(PK, PK || message, signature)`
## - `message` is the message to hash
@ -95,14 +96,14 @@ func coreSign*[B1, B2, B3: byte|char, Sig, SecKey](
signature.affine(sig)
func coreVerify*[B1, B2, B3: byte|char, Pubkey, Sig](
func coreVerify*[Pubkey, Sig](
pubkey: Pubkey,
message: openarray[B1],
message: openarray[byte],
signature: Sig,
H: type CryptoHash,
k: static int,
augmentation: openarray[B2],
domainSepTag: openarray[B3]): bool =
augmentation: openarray[byte],
domainSepTag: openarray[byte]): bool {.genCharAPI.} =
## Check that a signature is valid
## for a message under the provided public key
## This assumes that the PublicKey and Signatures
@ -165,8 +166,7 @@ type
domainSepTag{.align: 64.}: array[255, byte] # Alignment to enable SIMD
dst_len: uint8
func init*[T: char|byte](
ctx: var BLSAggregateSigAccumulator, domainSepTag: openArray[T]) =
func init*(ctx: var BLSAggregateSigAccumulator, domainSepTag: openArray[byte]) {.genCharAPI.} =
## Initializes a BLS Aggregate Signature accumulator context.
type H = BLSAggregateSigAccumulator.H
@ -176,22 +176,18 @@ func init*[T: char|byte](
if domainSepTag.len > 255:
var t {.noInit.}: array[H.digestSize(), byte]
H.shortDomainSepTag(output = t, domainSepTag)
copy(ctx.domainSepTag, dStart = 0,
t, sStart = 0,
H.digestSize())
rawCopy(ctx.domainSepTag, dStart = 0, t, sStart = 0, H.digestSize())
ctx.dst_len = uint8 H.digestSize()
else:
copy(ctx.domainSepTag, dStart = 0,
domainSepTag, sStart = 0,
domainSepTag.len)
rawCopy(ctx.domainSepTag, dStart = 0, domainSepTag, sStart = 0, domainSepTag.len)
ctx.dst_len = uint8 domainSepTag.len
for i in ctx.dst_len ..< ctx.domainSepTag.len:
ctx.domainSepTag[i] = byte 0
func update*[T: char|byte, Pubkey: ECP_ShortW_Aff](
func update*[Pubkey: ECP_ShortW_Aff](
ctx: var BLSAggregateSigAccumulator,
pubkey: Pubkey,
message: openArray[T]): bool =
message: openArray[byte]): bool {.genCharAPI.} =
## Add a (public key, message) pair
## to a BLS aggregate signature accumulator
##
@ -224,6 +220,12 @@ func update*[T: char|byte, Pubkey: ECP_ShortW_Aff](
ctx.millerAccum.update(hmsgG1_aff, pubkey)
func update*[Pubkey: ECP_ShortW_Aff](
ctx: var BLSAggregateSigAccumulator,
pubkey: Pubkey,
message: View[byte]): bool {.inline.} =
ctx.update(pubkey, message.toOpenArray())
func merge*(ctxDst: var BLSAggregateSigAccumulator, ctxSrc: BLSAggregateSigAccumulator): bool =
## Merge 2 BLS signature accumulators: ctxDst <- ctxDst + ctxSrc
##
@ -318,8 +320,8 @@ type
# 20*1 (blinding 64-bit) + 50 (Miller) + 50 (final exp) = 120
secureBlinding{.align: 32.}: array[32, byte]
func hash[DigestSize: static int, T0, T1: char|byte](
H: type CryptoHash, digest: var array[DigestSize, byte], input0: openArray[T0], input1: openArray[T1]) =
func hash[DigestSize: static int](
H: type CryptoHash, digest: var array[DigestSize, byte], input0: openArray[byte], input1: openArray[byte]) =
static: doAssert DigestSize == H.digestSize()
@ -329,9 +331,9 @@ func hash[DigestSize: static int, T0, T1: char|byte](
h.update(input1)
h.finish(digest)
func init*[T0, T1: char|byte](
ctx: var BLSBatchSigAccumulator, domainSepTag: openArray[T0],
secureRandomBytes: array[32, byte], accumSepTag: openArray[T1]) =
func init*(
ctx: var BLSBatchSigAccumulator, domainSepTag: openArray[byte],
secureRandomBytes: array[32, byte], accumSepTag: openArray[byte]) {.genCharAPI.} =
## Initializes a Batch BLS Signature accumulator context.
##
## This requires cryptographically secure random bytes
@ -352,25 +354,21 @@ func init*[T0, T1: char|byte](
if domainSepTag.len > 255:
var t {.noInit.}: array[H.digestSize(), byte]
H.shortDomainSepTag(output = t, domainSepTag)
copy(ctx.domainSepTag, dStart = 0,
t, sStart = 0,
H.digestSize())
rawCopy(ctx.domainSepTag, dStart = 0, t, sStart = 0, H.digestSize())
ctx.dst_len = uint8 H.digestSize()
else:
copy(ctx.domainSepTag, dStart = 0,
domainSepTag, sStart = 0,
domainSepTag.len)
rawCopy(ctx.domainSepTag, dStart = 0, domainSepTag, sStart = 0, domainSepTag.len)
ctx.dst_len = uint8 domainSepTag.len
for i in ctx.dst_len ..< ctx.domainSepTag.len:
ctx.domainSepTag[i] = byte 0
H.hash(ctx.secureBlinding, secureRandomBytes, accumSepTag)
func update*[T: char|byte, Pubkey, Sig: ECP_ShortW_Aff](
func update*[Pubkey, Sig: ECP_ShortW_Aff](
ctx: var BLSBatchSigAccumulator,
pubkey: Pubkey,
message: openArray[T],
signature: Sig): bool =
message: openArray[byte],
signature: Sig): bool {.genCharAPI.} =
## Add a (public key, message, signature) triplet
## to a BLS signature accumulator
##
@ -480,6 +478,13 @@ func update*[T: char|byte, Pubkey, Sig: ECP_ShortW_Aff](
hmsgG1_aff.affine(hmsgG1_jac)
ctx.millerAccum.update(hmsgG1_aff, pubkey)
func update*[Pubkey, Sig: ECP_ShortW_Aff](
ctx: var BLSBatchSigAccumulator,
pubkey: Pubkey,
message: View[byte],
signature: Sig): bool {.inline.} =
ctx.update(pubkey, message, signature)
func merge*(ctxDst: var BLSBatchSigAccumulator, ctxSrc: BLSBatchSigAccumulator): bool =
## Merge 2 BLS signature accumulators: ctxDst <- ctxDst + ctxSrc
##
@ -548,13 +553,13 @@ func aggregate*[T: ECP_ShortW_Aff](r: var T, points: openarray[T]) =
accum.sum_reduce_vartime(points)
r.affine(accum)
func fastAggregateVerify*[B1, B2: byte|char, Pubkey, Sig](
func fastAggregateVerify*[Pubkey, Sig](
pubkeys: openArray[Pubkey],
message: openarray[B1],
message: openArray[byte],
aggregateSignature: Sig,
H: type CryptoHash,
k: static int,
domainSepTag: openarray[B2]): bool =
domainSepTag: openArray[byte]): bool {.genCharAPI.} =
## Verify the aggregate of multiple signatures on the same message by multiple pubkeys
## Assumes pubkeys and sig have been checked for non-infinity and group-checked.
@ -563,15 +568,19 @@ func fastAggregateVerify*[B1, B2: byte|char, Pubkey, Sig](
var aggPubkey {.noinit.}: Pubkey
aggPubkey.aggregate(pubkeys)
if bool(aggPubkey.isInf()):
return false
aggPubkey.coreVerify(message, aggregateSignature, H, k, augmentation = "", domainSepTag)
func aggregateVerify*[Msg; B: byte|char, Pubkey, Sig](
func aggregateVerify*[Msg, Pubkey, Sig](
pubkeys: openArray[Pubkey],
messages: openArray[Msg],
aggregateSignature: Sig,
H: type CryptoHash,
k: static int,
domainSepTag: openarray[B]): bool =
domainSepTag: openarray[byte]): bool {.genCharAPI.} =
## Verify the aggregated signature of multiple (pubkey, message) pairs
## Assumes pubkeys and the aggregated signature have been checked for non-infinity and group-checked.
##
@ -598,14 +607,14 @@ func aggregateVerify*[Msg; B: byte|char, Pubkey, Sig](
return accum.finalVerify(aggregateSignature)
func batchVerify*[Msg; B: byte|char, Pubkey, Sig](
func batchVerify*[Msg, Pubkey, Sig](
pubkeys: openArray[Pubkey],
messages: openArray[Msg],
signatures: openArray[Sig],
H: type CryptoHash,
k: static int,
domainSepTag: openarray[B],
secureRandomBytes: array[32, byte]): bool =
domainSepTag: openarray[byte],
secureRandomBytes: array[32, byte]): bool {.genCharAPI.} =
## Verify that all (pubkey, message, signature) triplets are valid
##
## Returns false if there is at least one incorrect signature

View File

@ -0,0 +1,52 @@
# 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.
# This module allows flexible exports of procedures.
# 1. This allows configuring all exported names from the protocol files
# instead of having those in many different places.
# 2. No extra public wrapper proc are needed, reducing function call/return overhead.
# i.e. if we have an inner sha256.hash function
# and we need an exported `ctt_sha256_hash` and we also have a `hash_to_curve` function
# that internally uses `sha256.hash`,
# the ideal outcome is for `sha256.hash to be exported as `ctt_sha256_hash` and
# have `hash_to_curve` directly use that.
# 3. Furthermore while compiling Nim only, no export marker (cdecl, dynlib, exportc) are used.
#
# Each prefix must be modified before importing the module to export
# Exportable functions
# ----------------------------------------------------------------------------------------------
var prefix_sha256* {.compileTime.} = ""
# Conditional exports
# ----------------------------------------------------------------------------------------------
import std/macros
macro libPrefix*(prefix: static string, procAst: untyped): untyped =
if prefix == "":
return procAst
else:
var pragmas = procAst.pragma
if pragmas.kind == nnkEmpty:
pragmas = nnkPragma.newTree()
pragmas.add ident"cdecl"
pragmas.add nnkExprColonExpr.newTree(
ident"exportc",
newLit(prefix & "$1"))
pragmas.add nnkExprColonExpr.newTree(
ident"raises",
nnkBracket.newTree())
if appType == "lib":
pragmas.add ident"dynlib"
result = procAst
result.pragma = pragmas

View File

@ -0,0 +1,63 @@
/** 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.
*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <constantine_ethereum_bls_signatures.h>
int main(){
// Initialize the runtime. For Constantine, it populates the CPU runtime detection dispatch.
ctt_eth_bls_init_NimMain();
ctt_eth_bls_status status;
// Declare an example insecure non-cryptographically random non-secret key. DO NOT USE IN PRODUCTION.
byte raw_seckey[32] = "Security pb becomes key mgmt pb!";
ctt_eth_bls_seckey seckey;
status = ctt_eth_bls_deserialize_seckey(&seckey, raw_seckey);
if (status != cttBLS_Success) {
printf("Secret key deserialization failure: status %d - %s\n", status, ctt_eth_bls_status_to_string(status));
exit(1);
}
// Derive the matching public key
ctt_eth_bls_pubkey pubkey;
status = ctt_eth_bls_derive_pubkey(&pubkey, &seckey);
if (status != cttBLS_Success) {
printf("Public key derivation failure: status %d - %s\n", status, ctt_eth_bls_status_to_string(status));
exit(1);
}
// Sign a message
byte message[32];
ctt_eth_bls_signature sig;
ctt_eth_bls_sha256_hash(message, "Mr F was here", 13, /* clear_memory = */ 0);
status = ctt_eth_bls_sign(&sig, &seckey, message, 32);
if (status != cttBLS_Success) {
printf("Message signing failure: status %d - %s\n", status, ctt_eth_bls_status_to_string(status));
exit(1);
}
// Verify that a signature is valid for a message under the provided public key
status = ctt_eth_bls_verify(&pubkey, message, 32, &sig);
if (status != cttBLS_Success) {
printf("Signature verification failure: status %d - %s\n", status, ctt_eth_bls_status_to_string(status));
exit(1);
}
printf("Example BLS signature/verification protocol completed successfully\n");
return 0;
}

View File

@ -1,21 +1,21 @@
// 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.
/** 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.
*/
// This is a test to ensure Constantine's modular arithmetic is consistent with GMP.
// While not intended as a tutorial, it showcases serialization, deserialization and computation.
#include <assert.h>
#include <gmp.h>
#include <constantine_bls12_381.h>
#include <stdio.h>
#include <stdlib.h>
typedef unsigned char byte;
#include <gmp.h>
#include <constantine_bls12_381.h>
// https://gmplib.org/manual/Integer-Import-and-Export.html
const int GMP_WordLittleEndian = -1;

View File

@ -0,0 +1,4 @@
#!/bin/sh
# Explain size of ELF .o files (does not work with gcc -flto).
nm -oS --defined-only -fposix -td "$@" |
sort -nk5 | awk '{print $1,$2,$3,$5}'

View File

@ -7,7 +7,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
std/[os, strutils, cpuinfo, strformat, deques, terminal],
std/[os, strutils, cpuinfo, strformat, deques],
std/[asyncfutures, asyncdispatch],
asynctools/[asyncproc, asyncpipe, asyncsync]
@ -55,72 +55,90 @@ proc release(s: AsyncSemaphore) =
type WorkQueue = ref object
sem: AsyncSemaphore
cmdQueue: Deque[string]
outputQueue: AsyncQueue[tuple[cmd: string, p: AsyncProcess]]
lineBuf: string
outputQueue: AsyncQueue[tuple[cmd: string, p: AsyncProcess, output: AsyncQueue[string]]]
proc releaseOnProcessExit(sem: AsyncSemaphore, p: AsyncProcess) {.async.} =
# TODO: addProcess callback on exit is cleaner but locks the AsyncPipe "readInto"
#
# p.processID.addProcess do (fd: AsyncFD) -> bool:
# sem.release()
#
# see also: https://forum.nim-lang.org/t/5565
# and https://github.com/cheatfate/asynctools/issues/20
proc monitorProcessLoop(output: AsyncQueue[string], cmd: string, id, total: int, p: AsyncProcess, sem: AsyncSemaphore) {.async.} =
# Ideally we want AsynStreams but that requires chronos, which doesn't support processes/pipes
# Or the nimboost package that hasn't been updated since 2019. So poor man's streams.
template doBuffering: untyped =
while true:
buf.setLen(256)
let charsRead = await p.outputHandle.readInto(buf[0].addr, buf.len)
if charsRead > 0:
buf.setLen(charsRead)
output.putNoWait(buf)
else:
break
var buf = newString(256)
doBuffering()
# Despite the output being empty we might still get STILL_ACTIVE: https://github.com/cheatfate/asynctools/blob/84ced6d/asynctools/asyncproc.nim#L24
# Unfortunately this gives "Resource temporarily unavailable" so we use exponential backoff.
# See also:
# - https://github.com/cheatfate/asynctools/issues/20
# - https://forum.nim-lang.org/t/5565
#
# let exitCode = await p.waitForExit()
var backoff = 8
while p.running():
backoff = min(backoff*2, 1024) # Exponential backoff
await sleepAsync(backoff)
doBuffering()
buf.setLen(0)
let exitCode = p.peekExitCode()
if exitCode != 0:
buf.add("\n" & '='.repeat(26) & " Command exited with code " & $exitCode & " " & '='.repeat(26) & '\n')
buf.add("[FAIL]: '" & cmd & "' (#" & $id & "/" & $total & ")\n")
buf.add("[FAIL]: Command #" & $id & " exited with error " & $exitCode & '\n')
buf.add('='.repeat(80) & '\n')
output.putNoWait(buf)
# close not exported: https://github.com/cheatfate/asynctools/issues/16
p.inputHandle.close()
p.outputHandle.close()
p.errorHandle.close()
output.putNoWait("")
if exitCode == 0:
sem.release()
proc enqueuePendingCommands(wq: WorkQueue) {.async.} =
var id = 0
let total = wq.cmdQueue.len
while wq.cmdQueue.len > 0:
id += 1
await wq.sem.acquire()
let cmd = wq.cmdQueue.popFirst()
let p = cmd.startProcess(
options = {poStdErrToStdOut, poUsePath, poEvalCommand}
)
p.inputHandle.close()
let p = cmd.startProcess(options = {poStdErrToStdOut, poUsePath, poEvalCommand})
asyncCheck wq.sem.releaseOnProcessExit(p)
wq.outputQueue.putNoWait((cmd, p))
let bufOut = newAsyncQueue[string]()
asyncCheck bufOut.monitorProcessLoop(cmd, id, total, p, wq.sem)
proc flushCommandsOutput(wq: WorkQueue) {.async.} =
wq.outputQueue.putNoWait((cmd, p, bufOut))
proc flushCommandsOutput(wq: WorkQueue, total: int) {.async.} =
var id = 0
while true:
let (cmd, p) = await wq.outputQueue.get()
id += 1
let (cmd, p, processOutput) = await wq.outputQueue.get()
echo '\n', '='.repeat(80)
echo "||\n|| Running: ", cmd ,"\n||"
echo "||\n|| Running #", id, "/", total, ": ", cmd ,"\n||"
echo '='.repeat(80)
while true:
let charsRead = await p.outputHandle.readInto(wq.lineBuf[0].addr, wq.lineBuf.len)
if charsRead == 0:
let output = await processOutput.get()
if output == "":
break
let charsWritten = stdout.writeBuffer(wq.lineBuf[0].addr, charsRead)
doAssert charsRead == charsWritten
# close not exported: https://github.com/cheatfate/asynctools/issues/16
p.outputHandle.close()
stdout.write(output)
let exitCode = p.peekExitCode()
if exitCode == 259:
echo "==== Command exited with code 259 ===="
echo "[SKIP]: '", cmd, "' (#", id, ")"
echo "==== Custom stacktrace ===="
writeStackTrace()
echo "==== Custom stacktrace ===="
echo "[SKIP]: Assuming process was unregistered when trying to retrieve its exit code"
elif exitCode != 0:
echo "==== Command exited with code ", exitCode, " ===="
echo "[FAIL]: '", cmd, "' (#", id, ")"
echo "==== Custom stacktrace ===="
writeStackTrace()
echo "==== Custom stacktrace ===="
quit "[FAIL]: Command #" & $id & " exited with error " & $exitCode, exitCode
id += 1
if exitCode != 0:
quit exitCode
if wq.cmdQueue.len == 0 and wq.outputQueue.len == 0:
return
@ -132,9 +150,7 @@ proc runCommands(commandFile: string, numWorkers: int) =
let wq = WorkQueue(
sem: AsyncSemaphore.new(numWorkers),
cmdQueue: initDeque[string](),
outputQueue: newAsyncQueue[tuple[cmd: string, p: AsyncProcess]](),
lineBuf: newString(max(80, terminalWidth()))
)
outputQueue: newAsyncQueue[tuple[cmd: string, p: AsyncProcess, output: AsyncQueue[string]]]())
# Parse the file
# --------------
@ -142,12 +158,13 @@ proc runCommands(commandFile: string, numWorkers: int) =
if cmd.len == 0: continue
wq.cmdQueue.addLast(cmd)
echo "Found ", wq.cmdQueue.len, " commands to run"
let total = wq.cmdQueue.len
echo "Found ", total, " commands to run"
# Run the commands
# ----------------
asyncCheck wq.enqueuePendingCommands()
waitFor wq.flushCommandsOutput()
waitFor wq.flushCommandsOutput(total)
# Main
# ----------------------------------------------------------------

View File

@ -1,5 +1,4 @@
/*
* Constantine
/** Constantine
* Copyright (c) 2018-2019 Status Research & Development GmbH
* Copyright (c) 2020-Present Mamy André-Ratsimbazafy
* Licensed and distributed under either of

View File

@ -0,0 +1,353 @@
/** 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.
*/
#ifndef __CTT_H_ETHEREUM_BLS_SIGNATURES__
#define __CTT_H_ETHEREUM_BLS_SIGNATURES__
#ifdef __cplusplus
extern "C" {
#endif
// Basic Types
// ------------------------------------------------------------------------------------------------
#if defined(__SIZE_TYPE__) && defined(__PTRDIFF_TYPE__)
typedef __SIZE_TYPE__ size_t;
typedef __PTRDIFF_TYPE__ ptrdiff_t;
#else
#include <stddef.h>
#endif
#if defined(__UINT8_TYPE__) && defined(__UINT32_TYPE__) && defined(__UINT64_TYPE__)
typedef __UINT8_TYPE__ uint8_t;
typedef __UINT32_TYPE__ uint32_t;
typedef __UINT64_TYPE__ uint64_t;
#else
#include <stdint.h>
#endif
// https://github.com/nim-lang/Nim/blob/v1.6.12/lib/nimbase.h#L318
#if defined(__STDC_VERSION__) && __STDC_VERSION__>=199901
# define bool _Bool
#else
# define bool unsigned char
#endif
typedef uint8_t byte;
// Attributes
// ------------------------------------------------------------------------------------------------
#if defined(_MSC_VER)
# define ctt_pure __declspec(noalias)
#elif defined(__GNUC__)
# define ctt_pure __attribute__((pure))
#else
# define ctt_pure
#endif
#if defined(_MSC_VER)
# define align(x) __declspec(align(x))
#else
# define align(x) __attribute__((aligned(x)))
#endif
// BLS signature types
// ------------------------------------------------------------------------------------------------
#define FIELD_BITS 381
#define ORDER_BITS 255
#define BYTES(bits) ((int) ((bits) + 8 - 1) / 8)
struct ctt_eth_bls_fp { byte raw[BYTES(FIELD_BITS)]; };
struct ctt_eth_bls_fp2 { struct ctt_eth_bls_fp coords[2]; };
typedef struct { byte raw[BYTES(ORDER_BITS)]; } ctt_eth_bls_seckey;
typedef struct { struct ctt_eth_bls_fp x, y; } ctt_eth_bls_pubkey;
typedef struct { struct ctt_eth_bls_fp2 x, y; } ctt_eth_bls_signature;
typedef enum __attribute__((__packed__)) {
cttBLS_Success,
cttBLS_VerificationFailure,
cttBLS_InvalidEncoding,
cttBLS_CoordinateGreaterOrEqualThanModulus,
cttBLS_PointAtInfinity,
cttBLS_PointNotOnCurve,
cttBLS_PointNotInSubgroup,
cttBLS_ZeroSecretKey,
cttBLS_SecretKeyLargerThanCurveOrder,
cttBLS_ZeroLengthAggregation,
cttBLS_InconsistentLengthsOfInputs,
} ctt_eth_bls_status;
static const char* ctt_eth_bls_status_to_string(ctt_eth_bls_status status) {
static const char* const statuses[] = {
"cttBLS_Success",
"cttBLS_VerificationFailure",
"cttBLS_InvalidEncoding",
"cttBLS_CoordinateGreaterOrEqualThanModulus",
"cttBLS_PointAtInfinity",
"cttBLS_PointNotOnCurve",
"cttBLS_PointNotInSubgroup",
"cttBLS_ZeroSecretKey",
"cttBLS_SecretKeyLargerThanCurveOrder",
"cttBLS_ZeroLengthAggregation",
"cttBLS_InconsistentLengthsOfInputs",
};
size_t length = sizeof statuses / sizeof *statuses;
if (0 <= status && status < length) {
return statuses[status];
}
return "cttBLS_InvalidStatusCode";
}
// Initialization
// ------------------------------------------------------------------------------------------------
/** Initializes the library:
* - detect CPU features like ADX instructions support (MULX, ADCX, ADOX)
*/
void ctt_eth_bls_init_NimMain(void);
// SHA-256
// ------------------------------------------------------------------------------------------------
typedef struct {
align(64) uint32_t message_schedule[16];
align(64) byte buf[64];
uint64_t msgLen;
} ctt_eth_bls_sha256_context;
/** Initialize or reinitialize a Sha256 context.
*/
void ctt_eth_bls_sha256_init(ctt_eth_bls_sha256_context* ctx);
/** Append a message to a SHA256 context
* for incremental SHA256 computation
*
* Security note: the tail of your message might be stored
* in an internal buffer.
* if sensitive content is used, ensure that
* `ctx.finish(...)` and `ctx.clear()` are called as soon as possible.
* Additionally ensure that the message(s) passed were stored
* in memory considered secure for your threat model.
*
* For passwords and secret keys, you MUST NOT use raw SHA-256
* use a Key Derivation Function instead (KDF)
*/
void ctt_eth_bls_sha256_update(ctt_eth_bls_sha256_context* ctx, const byte* message, ptrdiff_t message_len);
/** Finalize a SHA256 computation and output the
* message digest to the `digest` buffer.
*
* Security note: this does not clear the internal buffer.
* if sensitive content is used, use "ctx.clear()"
* and also make sure that the message(s) passed were stored
* in memory considered secure for your threat model.
*
* For passwords and secret keys, you MUST NOT use raw SHA-256
* use a Key Derivation Function instead (KDF)
*/
void ctt_eth_bls_sha256_finish(ctt_eth_bls_sha256_context* ctx, byte digest[32]);
/** Clear the context internal buffers
* Security note:
* For passwords and secret keys, you MUST NOT use raw SHA-256
* use a Key Derivation Function instead (KDF)
*/
void ctt_eth_bls_sha256_clear(ctt_eth_bls_sha256_context* ctx);
/** Compute the SHA-256 hash of message
* and store the result in digest.
* Optionally, clear the memory buffer used.
*/
void ctt_eth_bls_sha256_hash(byte digest[32], const byte* message, ptrdiff_t message_len, bool clear_memory);
// Comparisons
// ------------------------------------------------------------------------------------------------
ctt_pure bool ctt_eth_bls_pubkey_is_zero(const ctt_eth_bls_pubkey* pubkey);
ctt_pure bool ctt_eth_bls_signature_is_zero(const ctt_eth_bls_signature* sig);
ctt_pure bool ctt_eth_bls_pubkeys_are_equal(const ctt_eth_bls_pubkey* a,
const ctt_eth_bls_pubkey* b);
ctt_pure bool ctt_eth_bls_signatures_are_equal(const ctt_eth_bls_signature* a,
const ctt_eth_bls_signature* b);
// Input validation
// ------------------------------------------------------------------------------------------------
/** Validate the secret key.
*
* Regarding timing attacks, this will leak timing information only if the key is invalid.
* Namely, the secret key is 0 or the secret key is too large.
*/
ctt_pure ctt_eth_bls_status ctt_eth_bls_validate_seckey(const ctt_eth_bls_seckey* seckey);
/** Validate the public key.
*
* This is an expensive operation that can be cached.
*/
ctt_pure ctt_eth_bls_status ctt_eth_bls_validate_pubkey(const ctt_eth_bls_pubkey* pubkey);
/** Validate the signature.
*
* This is an expensive operation that can be cached.
*/
ctt_pure ctt_eth_bls_status ctt_eth_bls_validate_signature(const ctt_eth_bls_signature* pubkey);
// Codecs
// ------------------------------------------------------------------------------------------------
/** Serialize a secret key
*
* Returns cttBLS_Success if successful
*/
ctt_eth_bls_status ctt_eth_bls_serialize_seckey(byte dst[32], const ctt_eth_bls_seckey* seckey);
/** Serialize a public key in compressed (Zcash) format
*
* Returns cttBLS_Success if successful
*/
ctt_eth_bls_status ctt_eth_bls_serialize_pubkey_compressed(byte dst[48], const ctt_eth_bls_pubkey* pubkey);
/** Serialize a signature in compressed (Zcash) format
*
* Returns cttBLS_Success if successful
*/
ctt_eth_bls_status ctt_eth_bls_serialize_signature_compressed(byte dst[96], const ctt_eth_bls_signature* sig);
/** Deserialize a secret key
* This also validates the secret key.
*
* This is protected against side-channel unless your key is invalid.
* In that case it will like whether it's all zeros or larger than the curve order.
*/
ctt_eth_bls_status ctt_eth_bls_deserialize_seckey(ctt_eth_bls_seckey* seckey, const byte src[32]);
/** Deserialize a public key in compressed (Zcash) format.
* This does not validate the public key.
* It is intended for cases where public keys are stored in a trusted location
* and validation can be cached.
*
* Warning :
* This procedure skips the very expensive subgroup checks.
* Not checking subgroup exposes a protocol to small subgroup attacks.
*
* Returns cttBLS_Success if successful
*/
ctt_eth_bls_status ctt_eth_bls_deserialize_pubkey_compressed_unchecked(ctt_eth_bls_pubkey* pubkey, const byte src[48]);
/** Deserialize a public_key in compressed (Zcash) format.
* This also validates the public key.
*
* Returns cttBLS_Success if successful
*/
ctt_eth_bls_status ctt_eth_bls_deserialize_pubkey_compressed(ctt_eth_bls_pubkey* pubkey, const byte src[48]);
/** Deserialize a signature in compressed (Zcash) format.
* This does not validate the signature.
* It is intended for cases where public keys are stored in a trusted location
* and validation can be cached.
*
* Warning :
* This procedure skips the very expensive subgroup checks.
* Not checking subgroup exposes a protocol to small subgroup attacks.
*
* Returns cttBLS_Success if successful
*/
ctt_eth_bls_status ctt_eth_bls_deserialize_signature_compressed_unchecked(ctt_eth_bls_signature* sig, const byte src[96]);
/** Deserialize a signature in compressed (Zcash) format.
* This also validates the signature.
*
* Returns cttBLS_Success if successful
*/
ctt_eth_bls_status ctt_eth_bls_deserialize_signature_compressed(ctt_eth_bls_signature* sig, const byte src[96]);
// BLS signatures
// ------------------------------------------------------------------------------------------------
/** Derive the public key matching with a secret key
*
* Secret protection:
* - A valid secret key will only leak that it is valid.
* - An invalid secret key will leak whether it's all zero or larger than the curve order.
*/
ctt_eth_bls_status ctt_eth_bls_derive_pubkey(ctt_eth_bls_pubkey* pubkey, const ctt_eth_bls_seckey* seckey);
/** Produce a signature for the message under the specified secret key
* Signature is on BLS12-381 G2 (and public key on G1)
*
* For message domain separation purpose, the tag is `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_`
*
* Input:
* - A secret key
* - A message
*
* Output:
* - `signature` is overwritten with `message` signed with `secretKey`
* with the scheme
* - A status code indicating success or if the secret key is invalid.
*
* Secret protection:
* - A valid secret key will only leak that it is valid.
* - An invalid secret key will leak whether it's all zero or larger than the curve order.
*/
ctt_eth_bls_status ctt_eth_bls_sign(ctt_eth_bls_signature* sig,
const ctt_eth_bls_seckey* seckey,
const byte* message, ptrdiff_t message_len);
/** Check that a signature is valid for a message
* under the provided public key.
* returns `true` if the signature is valid, `false` otherwise.
*
* For message domain separation purpose, the tag is `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_`
*
* Input:
* - A public key initialized by one of the key derivation or deserialization procedure.
* Or validated via validate_pubkey
* - A message
* - A signature initialized by one of the key derivation or deserialization procedure.
* Or validated via validate_signature
*
* Output:
* - a status code with verification success if signature is valid
* or indicating verification failure
*
* In particular, the public key and signature are assumed to be on curve and subgroup-checked.
*/
ctt_pure ctt_eth_bls_status ctt_eth_bls_verify(const ctt_eth_bls_pubkey* pubkey,
const byte* message, ptrdiff_t message_len,
const ctt_eth_bls_signature* sig);
// TODO: API for pubkeys and signature aggregation. Return a bool or a status code or nothing?
/** Check that a signature is valid for a message
* under the aggregate of provided public keys.
* returns `true` if the signature is valid, `false` otherwise.
*
* For message domain separation purpose, the tag is `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_`
*
* Input:
* - Public keys initialized by one of the key derivation or deserialization procedure.
* Or validated via validate_pubkey
* - A message
* - A signature initialized by one of the key derivation or deserialization procedure.
* Or validated via validate_signature
*
* In particular, the public keys and signature are assumed to be on curve subgroup checked.
*/
ctt_pure ctt_eth_bls_status ctt_eth_bls_fast_aggregate_verify(const ctt_eth_bls_pubkey pubkeys[], ptrdiff_t pubkeys_len,
const byte* message, ptrdiff_t message_len,
const ctt_eth_bls_signature* aggregate_sig);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -1,5 +1,4 @@
/*
* Constantine
/** Constantine
* Copyright (c) 2018-2019 Status Research & Development GmbH
* Copyright (c) 2020-Present Mamy André-Ratsimbazafy
* Licensed and distributed under either of

5
lib/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
# Ignore everything in this directory
*
# Except this file and README
!.gitignore
!README.md

View File

@ -6,7 +6,7 @@
# * 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 ../../constantine/platforms/gpu/[llvm, nvidia, bindings/utils]
import ../../constantine/platforms/gpu/[llvm, nvidia, bindings/c_abi]
# ############################################################
#

View File

@ -85,10 +85,7 @@ func random_point*(rng: var RngState, EC: typedesc, randZ: bool, gen: RandomGen)
proc run_EC_addition_tests*(
ec: typedesc,
Iters: static int,
moduleName: string
) =
moduleName: string) =
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
rng.seed(seed)
@ -274,9 +271,7 @@ proc run_EC_addition_tests*(
proc run_EC_mul_sanity_tests*(
ec: typedesc,
ItersMul: static int,
moduleName: string
) =
moduleName: string) =
# Random seed for reproducibility
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
@ -306,16 +301,16 @@ proc run_EC_mul_sanity_tests*(
bool(reference.isInf())
bool(refMinWeight.isInf())
proc refWNaf(w: static int) = # workaround staticFor symbol visibility
proc refWNaf(bits, w: static int) = # workaround staticFor symbol visibility
var refWNAF = a
refWNAF.scalarMul_minHammingWeight_windowed_vartime(exponent, window = w)
refWNAF.scalarMul_minHammingWeight_windowed_vartime(BigInt[bits](), window = w)
check: bool(refWNAF.isInf())
refWNaf(2)
refWNaf(3)
refWNaf(5)
refWNaf(8)
refWNaf(13)
refWNaf(bits, w = 2)
refWNaf(bits, w = 3)
refWNaf(bits, w = 5)
refWNaf(bits, w = 8)
refWNaf(bits, w = 13)
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false, gen = Uniform)
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true, gen = Uniform)
@ -381,9 +376,7 @@ proc run_EC_mul_sanity_tests*(
proc run_EC_mul_distributive_tests*(
ec: typedesc,
ItersMul: static int,
moduleName: string
) =
moduleName: string) =
# Random seed for reproducibility
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
@ -446,9 +439,7 @@ proc run_EC_mul_distributive_tests*(
proc run_EC_mul_vs_ref_impl*(
ec: typedesc,
ItersMul: static int,
moduleName: string
) =
moduleName: string) =
# Random seed for reproducibility
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
@ -501,9 +492,7 @@ proc run_EC_mul_vs_ref_impl*(
proc run_EC_mixed_add_impl*(
ec: typedesc,
Iters: static int,
moduleName: string
) =
moduleName: string) =
# Random seed for reproducibility
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
@ -634,8 +623,7 @@ proc run_EC_mixed_add_impl*(
proc run_EC_subgroups_cofactors_impl*(
ec: typedesc,
ItersMul: static int,
moduleName: string
) =
moduleName: string) =
# Random seed for reproducibility
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
@ -706,9 +694,7 @@ proc run_EC_subgroups_cofactors_impl*(
proc run_EC_affine_conversion*(
ec: typedesc,
Iters: static int,
moduleName: string
) =
moduleName: string) =
# Random seed for reproducibility
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
@ -869,9 +855,7 @@ proc run_EC_conversion_failures*(
proc run_EC_batch_add_impl*[N: static int](
ec: typedesc,
numPoints: array[N, int],
moduleName: string
) =
moduleName: string) =
# Random seed for reproducibility
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
@ -942,9 +926,7 @@ proc run_EC_batch_add_impl*[N: static int](
proc run_EC_multi_scalar_mul_impl*[N: static int](
ec: typedesc,
numPoints: array[N, int],
moduleName: string
) =
moduleName: string) =
# Random seed for reproducibility
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32

View File

@ -60,8 +60,7 @@ proc runFrobeniusTowerTests*[N](
Iters: static int,
TestCurves: static array[N, Curve],
moduleName: string,
testSuiteDesc: string
) =
testSuiteDesc: string) =
# Random seed for reproducibility
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
@ -75,7 +74,6 @@ proc runFrobeniusTowerTests*[N](
var a = rng.random_elem(Field, gen)
var fa {.noInit.}: typeof(a)
fa.frobenius_map(a, k = 1)
a.powUnsafeExponent(Field.fieldMod(), window = 3)
check: bool(a == fa)

View File

@ -9,7 +9,7 @@
import
std/[os, unittest, strutils],
pkg/jsony,
../constantine/blssig_pop_on_bls12381_g2,
../constantine/ethereum_bls_signatures,
../constantine/platforms/codecs,
../constantine/hashes
@ -115,7 +115,7 @@ template testGen*(name, testData, TestType, body: untyped): untyped =
testGen(deserialization_G1, testVector, DeserG1_test):
var pubkey{.noInit.}: PublicKey
let status = pubkey.deserialize_public_key_compressed(testVector.input.pubkey)
let status = pubkey.deserialize_pubkey_compressed(testVector.input.pubkey)
let success = status == cttBLS_Success or status == cttBLS_PointAtInfinity
doAssert success == testVector.output, block:
@ -126,7 +126,7 @@ testGen(deserialization_G1, testVector, DeserG1_test):
if success: # Roundtrip
var s{.noInit.}: array[48, byte]
let status2 = s.serialize_public_key_compressed(pubkey)
let status2 = s.serialize_pubkey_compressed(pubkey)
doAssert status2 == cttBLS_Success
doAssert s == testVector.input.pubkey, block:
"\nSerialization roundtrip differs from expected \n" &
@ -158,7 +158,7 @@ testGen(sign, testVector, Sign_test):
var seckey{.noInit.}: SecretKey
var sig{.noInit.}: Signature
let status = seckey.deserialize_secret_key(testVector.input.privkey)
let status = seckey.deserialize_seckey(testVector.input.privkey)
if status != cttBLS_Success:
doAssert testVector.output == default(array[96, byte])
let status2 = sig.sign(seckey, testVector.input.message)
@ -171,7 +171,7 @@ testGen(sign, testVector, Sign_test):
var output{.noInit.}: Signature
let status3 = output.deserialize_signature_compressed(testVector.output)
doAssert status3 == cttBLS_Success
doAssert sig == output, block:
doAssert signatures_are_equal(sig, output), block:
var sig_bytes{.noInit.}: array[96, byte]
var roundtrip{.noInit.}: array[96, byte]
let sb_status = sig_bytes.serialize_signature_compressed(sig)
@ -198,7 +198,7 @@ testGen(verify, testVector, Verify_test):
status = cttBLS_VerificationFailure
block testChecks:
status = pubkey.deserialize_public_key_compressed(testVector.input.pubkey)
status = pubkey.deserialize_pubkey_compressed(testVector.input.pubkey)
if status notin {cttBLS_Success, cttBLS_PointAtInfinity}:
# For point at infinity, we want to make sure that "verify" itself handles them.
break testChecks
@ -218,7 +218,7 @@ testGen(verify, testVector, Verify_test):
if success: # Extra codec testing
block:
var output{.noInit.}: array[48, byte]
let s = output.serialize_public_key_compressed(pubkey)
let s = output.serialize_pubkey_compressed(pubkey)
doAssert s == cttBLS_Success
doAssert output == testVector.input.pubkey
@ -236,7 +236,7 @@ testGen(fast_aggregate_verify, testVector, FastAggregateVerify_test):
block testChecks:
for i in 0 ..< testVector.input.pubkeys.len:
status = pubkeys[i].deserialize_public_key_compressed(testVector.input.pubkeys[i])
status = pubkeys[i].deserialize_pubkey_compressed(testVector.input.pubkeys[i])
if status notin {cttBLS_Success, cttBLS_PointAtInfinity}:
# For point at infinity, we want to make sure that "verify" itself handles them.
break testChecks
@ -262,7 +262,7 @@ testGen(aggregate_verify, testVector, AggregateVerify_test):
block testChecks:
for i in 0 ..< testVector.input.pubkeys.len:
status = pubkeys[i].deserialize_public_key_compressed(testVector.input.pubkeys[i])
status = pubkeys[i].deserialize_pubkey_compressed(testVector.input.pubkeys[i])
if status notin {cttBLS_Success, cttBLS_PointAtInfinity}:
# For point at infinity, we want to make sure that "verify" itself handles them.
break testChecks
@ -288,7 +288,7 @@ testGen(batch_verify, testVector, BatchVerify_test):
block testChecks:
for i in 0 ..< testVector.input.pubkeys.len:
status = pubkeys[i].deserialize_public_key_compressed(testVector.input.pubkeys[i])
status = pubkeys[i].deserialize_pubkey_compressed(testVector.input.pubkeys[i])
if status notin {cttBLS_Success, cttBLS_PointAtInfinity}:
# For point at infinity, we want to make sure that "verify" itself handles them.
break testChecks

View File

@ -49,7 +49,7 @@ when not defined(windows):
proc SHA256_OpenSSL[T: byte|char](
digest: var array[32, byte],
s: openarray[T]) =
s: openArray[T]) =
discard SHA256(s, digest.addr)
# discard EVP_Q_digest(nil, "SHA256", nil, s, digest, nil)