2024-12-20 16:36:37 +01:00
#!/usr/bin/env python3
2025-01-24 11:47:53 +05:30
"""Generate the BIP-0374 test vectors."""
2024-12-20 16:36:37 +01:00
import csv
2025-05-20 21:49:11 +02:00
from pathlib import Path
2024-12-20 16:36:37 +01:00
from reference import (
dleq_generate_proof ,
dleq_verify_proof ,
)
2025-05-20 22:05:28 +02:00
from secp256k1lab.secp256k1 import G as GENERATOR , GE
2026-01-15 00:56:13 +01:00
from secp256k1lab.util import tagged_hash
2024-12-20 16:36:37 +01:00
2025-01-27 14:15:33 +05:30
NUM_SUCCESS_TEST_VECTORS = 8
2024-12-26 12:06:44 -05:00
DLEQ_TAG_TESTVECTORS_RNG = "BIP0374/testvectors_rng"
2024-12-20 16:36:37 +01:00
2025-05-20 21:49:11 +02:00
FILENAME_GENERATE_PROOF_TEST = Path ( __file__ ) . parent / 'test_vectors_generate_proof.csv'
FILENAME_VERIFY_PROOF_TEST = Path ( __file__ ) . parent / 'test_vectors_verify_proof.csv'
2024-12-20 16:36:37 +01:00
def random_scalar_int ( vector_i , purpose ):
2026-01-15 00:56:13 +01:00
rng_out = tagged_hash ( DLEQ_TAG_TESTVECTORS_RNG , purpose . encode () + vector_i . to_bytes ( 4 , 'little' ))
2024-12-20 16:36:37 +01:00
return int . from_bytes ( rng_out , 'big' ) % GE . ORDER
def random_bytes ( vector_i , purpose ):
2026-01-15 00:56:13 +01:00
rng_out = tagged_hash ( DLEQ_TAG_TESTVECTORS_RNG , purpose . encode () + vector_i . to_bytes ( 4 , 'little' ))
2024-12-20 16:36:37 +01:00
return rng_out
def create_test_vector_data ( vector_i ):
2025-01-24 11:47:53 +05:30
g = random_scalar_int ( vector_i , "scalar_g" ) if vector_i < 5 else 1
assert 0 < g < GE . ORDER
2024-12-26 14:17:52 -05:00
G = g * GENERATOR
assert not G . infinity
2024-12-20 16:36:37 +01:00
a = random_scalar_int ( vector_i , "scalar_a" )
A = a * G
b = random_scalar_int ( vector_i , "scalar_b" )
B = b * G
C = a * B # shared secret
assert C . to_bytes_compressed () == ( b * A ) . to_bytes_compressed ()
auxrand = random_bytes ( vector_i , "auxrand" )
2025-01-27 14:15:33 +05:30
msg = random_bytes ( vector_i , "message" ) if vector_i != 5 else None
2024-12-26 14:17:52 -05:00
proof = dleq_generate_proof ( a , B , auxrand , G = G , m = msg )
return ( G , a , A , b , B , C , auxrand , msg , proof )
2024-12-20 16:36:37 +01:00
TEST_VECTOR_DATA = [ create_test_vector_data ( i ) for i in range ( NUM_SUCCESS_TEST_VECTORS )]
def gen_all_generate_proof_vectors ( f ):
writer = csv . writer ( f )
2024-12-26 14:17:52 -05:00
writer . writerow (( "index" , "point_G" , "scalar_a" , "point_B" , "auxrand_r" , "message" , "result_proof" , "comment" ))
2024-12-20 16:36:37 +01:00
# success cases with random values
idx = 0
for i in range ( NUM_SUCCESS_TEST_VECTORS ):
2024-12-26 14:17:52 -05:00
G , a , A , b , B , C , auxrand , msg , proof = TEST_VECTOR_DATA [ i ]
2024-12-20 16:36:37 +01:00
assert proof is not None and len ( proof ) == 64
2025-01-27 14:15:33 +05:30
if msg is None : msg = b ""
2024-12-26 14:17:52 -05:00
writer . writerow (( idx , G . to_bytes_compressed () . hex (), f " { a : 064x } " , B . to_bytes_compressed () . hex (), auxrand . hex (), msg . hex (), proof . hex (), f "Success case { i + 1 } " ))
2024-12-20 16:36:37 +01:00
idx += 1
# failure cases: a is not within group order (a=0, a=N)
a_invalid = 0
2024-12-26 14:17:52 -05:00
assert dleq_generate_proof ( a_invalid , B , auxrand , G = G , m = msg ) is None
writer . writerow (( idx , G . to_bytes_compressed () . hex (), f " { a_invalid : 064x } " , B . to_bytes_compressed () . hex (), auxrand . hex (), msg . hex (), "INVALID" , f "Failure case (a=0)" ))
2024-12-20 16:36:37 +01:00
idx += 1
a_invalid = GE . ORDER
2024-12-26 14:17:52 -05:00
assert dleq_generate_proof ( a_invalid , B , auxrand , G = G , m = msg ) is None
writer . writerow (( idx , G . to_bytes_compressed () . hex (), f " { a_invalid : 064x } " , B . to_bytes_compressed () . hex (), auxrand . hex (), msg . hex (), "INVALID" , f "Failure case (a=N [group order])" ))
2024-12-20 16:36:37 +01:00
idx += 1
# failure case: B is point at infinity
B_infinity = GE ()
B_infinity_str = "INFINITY"
assert dleq_generate_proof ( a , B_infinity , auxrand , m = msg ) is None
2024-12-26 14:17:52 -05:00
writer . writerow (( idx , G . to_bytes_compressed () . hex (), f " { a : 064x } " , B_infinity_str , auxrand . hex (), msg . hex (), "INVALID" , f "Failure case (B is point at infinity)" ))
2024-12-20 16:36:37 +01:00
idx += 1
def gen_all_verify_proof_vectors ( f ):
writer = csv . writer ( f )
2024-12-26 14:17:52 -05:00
writer . writerow (( "index" , "point_G" , "point_A" , "point_B" , "point_C" , "proof" , "message" , "result_success" , "comment" ))
2024-12-20 16:36:37 +01:00
# success cases (same as above)
idx = 0
for i in range ( NUM_SUCCESS_TEST_VECTORS ):
2024-12-26 14:17:52 -05:00
G , _ , A , _ , B , C , _ , msg , proof = TEST_VECTOR_DATA [ i ]
assert dleq_verify_proof ( A , B , C , proof , G = G , m = msg )
2025-01-27 14:15:33 +05:30
if msg is None : msg = b ""
2024-12-26 14:17:52 -05:00
writer . writerow (( idx , G . to_bytes_compressed () . hex (), A . to_bytes_compressed () . hex (), B . to_bytes_compressed () . hex (),
2024-12-20 16:36:37 +01:00
C . to_bytes_compressed () . hex (), proof . hex (), msg . hex (), "TRUE" , f "Success case { i + 1 } " ))
idx += 1
# other permutations of A, B, C should always fail
for i , points in enumerate (([ A , C , B ], [ B , A , C ], [ B , C , A ], [ C , A , B ], [ C , B , A ])):
assert not dleq_verify_proof ( points [ 0 ], points [ 1 ], points [ 2 ], proof , m = msg )
2024-12-26 14:17:52 -05:00
writer . writerow (( idx , G . to_bytes_compressed () . hex (), points [ 0 ] . to_bytes_compressed () . hex (), points [ 1 ] . to_bytes_compressed () . hex (),
2024-12-20 16:36:37 +01:00
points [ 2 ] . to_bytes_compressed () . hex (), proof . hex (), msg . hex (), "FALSE" , f "Swapped points case { i + 1 } " ))
idx += 1
# modifying proof should fail (flip one bit)
proof_damage_pos = random_scalar_int ( idx , "damage_pos" ) % 256
proof_damaged = list ( proof )
proof_damaged [ proof_damage_pos // 8 ] ^= ( 1 << ( proof_damage_pos % 8 ))
proof_damaged = bytes ( proof_damaged )
2024-12-26 14:17:52 -05:00
writer . writerow (( idx , G . to_bytes_compressed () . hex (), A . to_bytes_compressed () . hex (), B . to_bytes_compressed () . hex (),
2024-12-20 16:36:37 +01:00
C . to_bytes_compressed () . hex (), proof_damaged . hex (), msg . hex (), "FALSE" , f "Tampered proof (random bit-flip)" ))
idx += 1
# modifying message should fail (flip one bit)
msg_damage_pos = random_scalar_int ( idx , "damage_pos" ) % 256
msg_damaged = list ( msg )
2025-04-16 16:10:16 +02:00
msg_damaged [ msg_damage_pos // 8 ] ^= ( 1 << ( msg_damage_pos % 8 ))
2024-12-20 16:36:37 +01:00
msg_damaged = bytes ( msg_damaged )
2024-12-26 14:17:52 -05:00
writer . writerow (( idx , G . to_bytes_compressed () . hex (), A . to_bytes_compressed () . hex (), B . to_bytes_compressed () . hex (),
2024-12-20 16:36:37 +01:00
C . to_bytes_compressed () . hex (), proof . hex (), msg_damaged . hex (), "FALSE" , f "Tampered message (random bit-flip)" ))
idx += 1
2026-08-19 15:42:32 +02:00
# out-of-range proof values should fail (e and s must both be valid scalars)
order_bytes = GE . ORDER . to_bytes ( 32 , "big" )
proof_e_out_of_range = order_bytes + proof [ 32 :]
assert not dleq_verify_proof ( A , B , C , proof_e_out_of_range , G = G , m = msg )
writer . writerow (( idx , G . to_bytes_compressed () . hex (), A . to_bytes_compressed () . hex (), B . to_bytes_compressed () . hex (),
C . to_bytes_compressed () . hex (), proof_e_out_of_range . hex (), msg . hex (), "FALSE" , f "Proof with e equal to curve order" ))
idx += 1
proof_s_out_of_range = proof [: 32 ] + order_bytes
assert not dleq_verify_proof ( A , B , C , proof_s_out_of_range , G = G , m = msg )
writer . writerow (( idx , G . to_bytes_compressed () . hex (), A . to_bytes_compressed () . hex (), B . to_bytes_compressed () . hex (),
C . to_bytes_compressed () . hex (), proof_s_out_of_range . hex (), msg . hex (), "FALSE" , f "Proof with s equal to curve order" ))
idx += 1
2024-12-20 16:36:37 +01:00
if __name__ == "__main__" :
print ( f "Generating { FILENAME_GENERATE_PROOF_TEST } ..." )
with open ( FILENAME_GENERATE_PROOF_TEST , "w" , encoding = "utf-8" ) as fil_generate_proof :
gen_all_generate_proof_vectors ( fil_generate_proof )
print ( f "Generating { FILENAME_VERIFY_PROOF_TEST } ..." )
with open ( FILENAME_VERIFY_PROOF_TEST , "w" , encoding = "utf-8" ) as fil_verify_proof :
gen_all_verify_proof_vectors ( fil_verify_proof )