326 lines
12 KiB
Nim
326 lines
12 KiB
Nim
# 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/[os, unittest, strutils],
|
|
pkg/jsony,
|
|
../constantine/blssig_pop_on_bls12381_g2,
|
|
../constantine/platforms/codecs,
|
|
../constantine/hashes
|
|
|
|
type
|
|
# https://github.com/ethereum/bls12-381-tests/blob/master/formats/
|
|
|
|
PubkeyField = object
|
|
pubkey: array[48, byte]
|
|
DeserG1_test = object
|
|
input: PubkeyField
|
|
output: bool
|
|
|
|
SignatureField =object
|
|
signature: array[96, byte]
|
|
DeserG2_test = object
|
|
input: SignatureField
|
|
output: bool
|
|
|
|
InputSign = object
|
|
privkey: array[32, byte]
|
|
message: array[32, byte]
|
|
Sign_test = object
|
|
input: InputSign
|
|
output: array[96, byte]
|
|
|
|
InputVerify = object
|
|
pubkey: array[48, byte]
|
|
message: array[32, byte]
|
|
signature: array[96, byte]
|
|
Verify_test = object
|
|
input: InputVerify
|
|
output: bool
|
|
|
|
InputFastAggregateVerify = object
|
|
pubkeys: seq[array[48, byte]]
|
|
message: array[32, byte]
|
|
signature: array[96, byte]
|
|
FastAggregateVerify_test = object
|
|
input: InputFastAggregateVerify
|
|
output: bool
|
|
|
|
InputAggregateVerify = object
|
|
pubkeys: seq[array[48, byte]]
|
|
messages: seq[array[32, byte]]
|
|
signature: array[96, byte]
|
|
AggregateVerify_test = object
|
|
input: InputAggregateVerify
|
|
output: bool
|
|
|
|
InputBatchVerify = object
|
|
pubkeys: seq[array[48, byte]]
|
|
messages: seq[array[32, byte]]
|
|
signatures: seq[array[96, byte]]
|
|
BatchVerify_test = object
|
|
input: InputBatchVerify
|
|
output: bool
|
|
|
|
proc parseHook*[N: static int](src: string, pos: var int, value: var array[N, byte]) =
|
|
var str: string
|
|
parseHook(src, pos, str)
|
|
value.paddedFromHex(str, bigEndian)
|
|
|
|
const SkippedTests = [
|
|
# By construction, input MUST be 48 bytes, which is enforced at the type-system level.
|
|
"deserialization_fails_too_many_bytes.json"
|
|
]
|
|
|
|
const TestDir = currentSourcePath.rsplit(DirSep, 1)[0] / "protocol_blssig_pop_on_bls12381_g2_test_vectors_v0.1.1"
|
|
|
|
iterator walkTests*(category: string, skipped: var int): (string, string) =
|
|
let testDir = TestDir/category
|
|
|
|
for file in walkDirRec(testDir, relative = true):
|
|
if file in SkippedTests:
|
|
echo "[WARNING] Skipping - ", file
|
|
inc skipped
|
|
continue
|
|
|
|
yield (testDir, file)
|
|
|
|
template testGen*(name, testData, TestType, body: untyped): untyped =
|
|
## Generates a test proc
|
|
## with identifier "test_name"
|
|
## The test vector data is available as JsonNode under the
|
|
## the variable passed as `testData`
|
|
proc `test _ name`() =
|
|
var count = 0 # Need to fail if walkDir doesn't return anything
|
|
var skipped = 0
|
|
for dir, file in walkTests(astToStr(name), skipped):
|
|
stdout.write(" " & astToStr(name) & " test: " & alignLeft(file, 70))
|
|
let testFile = readFile(dir/file)
|
|
let testData = testFile.fromJson(TestType)
|
|
|
|
body
|
|
|
|
stdout.write "[" & $status & "]\n"
|
|
inc count
|
|
|
|
doAssert count > 0, "Empty or inexisting test folder: " & astToStr(name)
|
|
if skipped > 0:
|
|
echo "[Warning]: ", skipped, " tests skipped."
|
|
|
|
testGen(deserialization_G1, testVector, DeserG1_test):
|
|
var pubkey{.noInit.}: PublicKey
|
|
|
|
let status = pubkey.deserialize_public_key_compressed(testVector.input.pubkey)
|
|
let success = status == cttBLS_Success or status == cttBLS_PointAtInfinity
|
|
|
|
doAssert success == testVector.output, block:
|
|
"\nDeserialization differs from expected \n" &
|
|
" deserializable? " & $success & " (" & $status & ")\n" &
|
|
" expected: " & $testVector.output
|
|
|
|
if success: # Roundtrip
|
|
var s{.noInit.}: array[48, byte]
|
|
|
|
let status2 = s.serialize_public_key_compressed(pubkey)
|
|
doAssert status2 == cttBLS_Success
|
|
doAssert s == testVector.input.pubkey, block:
|
|
"\nSerialization roundtrip differs from expected \n" &
|
|
" serialized: 0x" & $s.toHex() & " (" & $status2 & ")\n" &
|
|
" expected: 0x" & $testVector.input.pubkey.toHex()
|
|
|
|
testGen(deserialization_G2, testVector, DeserG2_test):
|
|
var sig{.noInit.}: Signature
|
|
|
|
let status = sig.deserialize_signature_compressed(testVector.input.signature)
|
|
let success = status == cttBLS_Success or status == cttBLS_PointAtInfinity
|
|
|
|
doAssert success == testVector.output, block:
|
|
"\nDeserialization differs from expected \n" &
|
|
" deserializable? " & $success & " (" & $status & ")\n" &
|
|
" expected: " & $testVector.output
|
|
|
|
if success: # Roundtrip
|
|
var s{.noInit.}: array[96, byte]
|
|
|
|
let status2 = s.serialize_signature_compressed(sig)
|
|
doAssert status2 == cttBLS_Success
|
|
doAssert s == testVector.input.signature, block:
|
|
"\nSerialization roundtrip differs from expected \n" &
|
|
" serialized: 0x" & $s.toHex() & " (" & $status2 & ")\n" &
|
|
" expected: 0x" & $testVector.input.signature.toHex()
|
|
|
|
testGen(sign, testVector, Sign_test):
|
|
var seckey{.noInit.}: SecretKey
|
|
var sig{.noInit.}: Signature
|
|
|
|
let status = seckey.deserialize_secret_key(testVector.input.privkey)
|
|
if status != cttBLS_Success:
|
|
doAssert testVector.output == default(array[96, byte])
|
|
let status2 = sig.sign(seckey, testVector.input.message)
|
|
doAssert status2 != cttBLS_Success
|
|
else:
|
|
let status2 = sig.sign(seckey, testVector.input.message)
|
|
doAssert status2 == cttBLS_Success
|
|
|
|
block: # deserialize the output for extra codec testing
|
|
var output{.noInit.}: Signature
|
|
let status3 = output.deserialize_signature_compressed(testVector.output)
|
|
doAssert status3 == cttBLS_Success
|
|
doAssert 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)
|
|
let rt_status = roundtrip.serialize_signature_compressed(output)
|
|
|
|
"\nResult signature differs from expected \n" &
|
|
" computed: 0x" & $sig_bytes.toHex() & " (" & $sb_status & ")\n" &
|
|
" roundtrip: 0x" & $roundtrip.toHex() & " (" & $rt_status & ")\n" &
|
|
" expected: 0x" & $testVector.output.toHex()
|
|
|
|
block: # serialize the result for extra codec testing
|
|
var sig_bytes{.noInit.}: array[96, byte]
|
|
let status3 = sig_bytes.serialize_signature_compressed(sig)
|
|
doAssert status3 == cttBLS_Success
|
|
doAssert sig_bytes == testVector.output, block:
|
|
"\nResult signature differs from expected \n" &
|
|
" computed: 0x" & $sig_bytes.toHex() & " (" & $status3 & ")\n" &
|
|
" expected: 0x" & $testVector.output.toHex()
|
|
|
|
testGen(verify, testVector, Verify_test):
|
|
var
|
|
pubkey{.noInit.}: PublicKey
|
|
signature{.noInit.}: Signature
|
|
status = cttBLS_VerificationFailure
|
|
|
|
block testChecks:
|
|
status = pubkey.deserialize_public_key_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
|
|
status = signature.deserialize_signature_compressed(testVector.input.signature)
|
|
if status notin {cttBLS_Success, cttBLS_PointAtInfinity}:
|
|
# For point at infinity, we want to make sure that "verify" itself handles them.
|
|
break testChecks
|
|
|
|
status = pubkey.verify(testVector.input.message, signature)
|
|
|
|
let success = status == cttBLS_Success
|
|
doAssert success == testVector.output, block:
|
|
"Verification differs from expected \n" &
|
|
" valid sig? " & $success & " (" & $status & ")\n" &
|
|
" expected: " & $testVector.output
|
|
|
|
if success: # Extra codec testing
|
|
block:
|
|
var output{.noInit.}: array[48, byte]
|
|
let s = output.serialize_public_key_compressed(pubkey)
|
|
doAssert s == cttBLS_Success
|
|
doAssert output == testVector.input.pubkey
|
|
|
|
block:
|
|
var output{.noInit.}: array[96, byte]
|
|
let s = output.serialize_signature_compressed(signature)
|
|
doAssert s == cttBLS_Success
|
|
doAssert output == testVector.input.signature
|
|
|
|
testGen(fast_aggregate_verify, testVector, FastAggregateVerify_test):
|
|
var
|
|
pubkeys = newSeq[PublicKey](testVector.input.pubkeys.len)
|
|
signature{.noInit.}: Signature
|
|
status = cttBLS_VerificationFailure
|
|
|
|
block testChecks:
|
|
for i in 0 ..< testVector.input.pubkeys.len:
|
|
status = pubkeys[i].deserialize_public_key_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
|
|
|
|
status = signature.deserialize_signature_compressed(testVector.input.signature)
|
|
if status notin {cttBLS_Success, cttBLS_PointAtInfinity}:
|
|
# For point at infinity, we want to make sure that "verify" itself handles them.
|
|
break testChecks
|
|
|
|
status = pubkeys.fast_aggregate_verify(testVector.input.message, signature)
|
|
|
|
let success = status == cttBLS_Success
|
|
doAssert success == testVector.output, block:
|
|
"Verification differs from expected \n" &
|
|
" valid sig? " & $success & " (" & $status & ")\n" &
|
|
" expected: " & $testVector.output
|
|
|
|
testGen(aggregate_verify, testVector, AggregateVerify_test):
|
|
var
|
|
pubkeys = newSeq[PublicKey](testVector.input.pubkeys.len)
|
|
signature{.noInit.}: Signature
|
|
status = cttBLS_VerificationFailure
|
|
|
|
block testChecks:
|
|
for i in 0 ..< testVector.input.pubkeys.len:
|
|
status = pubkeys[i].deserialize_public_key_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
|
|
|
|
status = signature.deserialize_signature_compressed(testVector.input.signature)
|
|
if status notin {cttBLS_Success, cttBLS_PointAtInfinity}:
|
|
# For point at infinity, we want to make sure that "verify" itself handles them.
|
|
break testChecks
|
|
|
|
status = pubkeys.aggregate_verify(testVector.input.messages, signature)
|
|
|
|
let success = status == cttBLS_Success
|
|
doAssert success == testVector.output, block:
|
|
"Verification differs from expected \n" &
|
|
" valid sig? " & $success & " (" & $status & ")\n" &
|
|
" expected: " & $testVector.output
|
|
|
|
testGen(batch_verify, testVector, BatchVerify_test):
|
|
var
|
|
pubkeys = newSeq[PublicKey](testVector.input.pubkeys.len)
|
|
signatures = newSeq[Signature](testVector.input.signatures.len)
|
|
status = cttBLS_VerificationFailure
|
|
|
|
block testChecks:
|
|
for i in 0 ..< testVector.input.pubkeys.len:
|
|
status = pubkeys[i].deserialize_public_key_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
|
|
|
|
for i in 0 ..< testVector.input.signatures.len:
|
|
status = signatures[i].deserialize_signature_compressed(testVector.input.signatures[i])
|
|
if status notin {cttBLS_Success, cttBLS_PointAtInfinity}:
|
|
# For point at infinity, we want to make sure that "verify" itself handles them.
|
|
break testChecks
|
|
|
|
let randomBytes = sha256.hash("totally non-secure source of entropy")
|
|
|
|
status = pubkeys.batch_verify(testVector.input.messages, signatures, randomBytes)
|
|
|
|
let success = status == cttBLS_Success
|
|
doAssert success == testVector.output, block:
|
|
"Verification differs from expected \n" &
|
|
" valid sig? " & $success & " (" & $status & ")\n" &
|
|
" expected: " & $testVector.output
|
|
|
|
suite "BLS signature on BLS12381G3 - ETH 2.0 test vectors":
|
|
test "Deserialization_G1(PublicKey) -> bool":
|
|
test_deserialization_G1()
|
|
test "Deserialization_G2(Signature) -> bool":
|
|
test_deserialization_G2()
|
|
test "sign(SecretKey, message) -> Signature":
|
|
test_sign()
|
|
test "verify(PublicKey, message, Signature) -> bool":
|
|
test_verify()
|
|
test "fast_aggregate_verify(seq[PublicKey], message, Signature) -> bool":
|
|
test_fast_aggregate_verify()
|
|
test "aggregate_verify(seq[PublicKey], seq[message], Signature) -> bool":
|
|
test_aggregate_verify()
|
|
test "batch_verify(seq[PublicKey], seq[message], seq[Signature]) -> bool":
|
|
test_batch_verify() |