|
| 1 | +#!/usr/bin/env python3 |
| 2 | +"""Generate the BIP-DLEQ test vectors (limited to secp256k1 generator right now).""" |
| 3 | +import csv |
| 4 | +import os |
| 5 | +import sys |
| 6 | +from reference import ( |
| 7 | + TaggedHash, |
| 8 | + dleq_generate_proof, |
| 9 | + dleq_verify_proof, |
| 10 | +) |
| 11 | +from secp256k1 import G as GENERATOR, GE |
| 12 | + |
| 13 | + |
| 14 | +NUM_SUCCESS_TEST_VECTORS = 5 |
| 15 | +DLEQ_TAG_TESTVECTORS_RNG = "BIP0374/testvectors_rng" |
| 16 | + |
| 17 | +FILENAME_GENERATE_PROOF_TEST = os.path.join(sys.path[0], 'test_vectors_generate_proof.csv') |
| 18 | +FILENAME_VERIFY_PROOF_TEST = os.path.join(sys.path[0], 'test_vectors_verify_proof.csv') |
| 19 | + |
| 20 | + |
| 21 | +def random_scalar_int(vector_i, purpose): |
| 22 | + rng_out = TaggedHash(DLEQ_TAG_TESTVECTORS_RNG, purpose.encode() + vector_i.to_bytes(4, 'little')) |
| 23 | + return int.from_bytes(rng_out, 'big') % GE.ORDER |
| 24 | + |
| 25 | + |
| 26 | +def random_bytes(vector_i, purpose): |
| 27 | + rng_out = TaggedHash(DLEQ_TAG_TESTVECTORS_RNG, purpose.encode() + vector_i.to_bytes(4, 'little')) |
| 28 | + return rng_out |
| 29 | + |
| 30 | + |
| 31 | +def create_test_vector_data(vector_i): |
| 32 | + g = random_scalar_int(vector_i, "scalar_g") |
| 33 | + assert g < GE.ORDER |
| 34 | + assert g > 0 |
| 35 | + G = g * GENERATOR |
| 36 | + assert not G.infinity |
| 37 | + a = random_scalar_int(vector_i, "scalar_a") |
| 38 | + A = a * G |
| 39 | + b = random_scalar_int(vector_i, "scalar_b") |
| 40 | + B = b * G |
| 41 | + C = a * B # shared secret |
| 42 | + assert C.to_bytes_compressed() == (b * A).to_bytes_compressed() |
| 43 | + auxrand = random_bytes(vector_i, "auxrand") |
| 44 | + msg = random_bytes(vector_i, "message") |
| 45 | + proof = dleq_generate_proof(a, B, auxrand, G=G, m=msg) |
| 46 | + return (G, a, A, b, B, C, auxrand, msg, proof) |
| 47 | + |
| 48 | +TEST_VECTOR_DATA = [create_test_vector_data(i) for i in range(NUM_SUCCESS_TEST_VECTORS)] |
| 49 | + |
| 50 | + |
| 51 | +def gen_all_generate_proof_vectors(f): |
| 52 | + writer = csv.writer(f) |
| 53 | + writer.writerow(("index", "point_G", "scalar_a", "point_B", "auxrand_r", "message", "result_proof", "comment")) |
| 54 | + |
| 55 | + # success cases with random values |
| 56 | + idx = 0 |
| 57 | + for i in range(NUM_SUCCESS_TEST_VECTORS): |
| 58 | + G, a, A, b, B, C, auxrand, msg, proof = TEST_VECTOR_DATA[i] |
| 59 | + assert proof is not None and len(proof) == 64 |
| 60 | + 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}")) |
| 61 | + idx += 1 |
| 62 | + |
| 63 | + # failure cases: a is not within group order (a=0, a=N) |
| 64 | + a_invalid = 0 |
| 65 | + assert dleq_generate_proof(a_invalid, B, auxrand, G=G, m=msg) is None |
| 66 | + 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)")) |
| 67 | + idx += 1 |
| 68 | + a_invalid = GE.ORDER |
| 69 | + assert dleq_generate_proof(a_invalid, B, auxrand, G=G, m=msg) is None |
| 70 | + 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])")) |
| 71 | + idx += 1 |
| 72 | + |
| 73 | + # failure case: B is point at infinity |
| 74 | + B_infinity = GE() |
| 75 | + B_infinity_str = "INFINITY" |
| 76 | + assert dleq_generate_proof(a, B_infinity, auxrand, m=msg) is None |
| 77 | + 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)")) |
| 78 | + idx += 1 |
| 79 | + |
| 80 | + |
| 81 | +def gen_all_verify_proof_vectors(f): |
| 82 | + writer = csv.writer(f) |
| 83 | + writer.writerow(("index", "point_G", "point_A", "point_B", "point_C", "proof", "message", "result_success", "comment")) |
| 84 | + |
| 85 | + # success cases (same as above) |
| 86 | + idx = 0 |
| 87 | + for i in range(NUM_SUCCESS_TEST_VECTORS): |
| 88 | + G, _, A, _, B, C, _, msg, proof = TEST_VECTOR_DATA[i] |
| 89 | + assert dleq_verify_proof(A, B, C, proof, G=G, m=msg) |
| 90 | + writer.writerow((idx, G.to_bytes_compressed().hex(), A.to_bytes_compressed().hex(), B.to_bytes_compressed().hex(), |
| 91 | + C.to_bytes_compressed().hex(), proof.hex(), msg.hex(), "TRUE", f"Success case {i+1}")) |
| 92 | + idx += 1 |
| 93 | + |
| 94 | + # other permutations of A, B, C should always fail |
| 95 | + for i, points in enumerate(([A, C, B], [B, A, C], [B, C, A], [C, A, B], [C, B, A])): |
| 96 | + assert not dleq_verify_proof(points[0], points[1], points[2], proof, m=msg) |
| 97 | + writer.writerow((idx, G.to_bytes_compressed().hex(), points[0].to_bytes_compressed().hex(), points[1].to_bytes_compressed().hex(), |
| 98 | + points[2].to_bytes_compressed().hex(), proof.hex(), msg.hex(), "FALSE", f"Swapped points case {i+1}")) |
| 99 | + idx += 1 |
| 100 | + |
| 101 | + # modifying proof should fail (flip one bit) |
| 102 | + proof_damage_pos = random_scalar_int(idx, "damage_pos") % 256 |
| 103 | + proof_damaged = list(proof) |
| 104 | + proof_damaged[proof_damage_pos // 8] ^= (1 << (proof_damage_pos % 8)) |
| 105 | + proof_damaged = bytes(proof_damaged) |
| 106 | + writer.writerow((idx, G.to_bytes_compressed().hex(), A.to_bytes_compressed().hex(), B.to_bytes_compressed().hex(), |
| 107 | + C.to_bytes_compressed().hex(), proof_damaged.hex(), msg.hex(), "FALSE", f"Tampered proof (random bit-flip)")) |
| 108 | + idx += 1 |
| 109 | + |
| 110 | + # modifying message should fail (flip one bit) |
| 111 | + msg_damage_pos = random_scalar_int(idx, "damage_pos") % 256 |
| 112 | + msg_damaged = list(msg) |
| 113 | + msg_damaged[proof_damage_pos // 8] ^= (1 << (msg_damage_pos % 8)) |
| 114 | + msg_damaged = bytes(msg_damaged) |
| 115 | + writer.writerow((idx, G.to_bytes_compressed().hex(), A.to_bytes_compressed().hex(), B.to_bytes_compressed().hex(), |
| 116 | + C.to_bytes_compressed().hex(), proof.hex(), msg_damaged.hex(), "FALSE", f"Tampered message (random bit-flip)")) |
| 117 | + idx += 1 |
| 118 | + |
| 119 | + |
| 120 | +if __name__ == "__main__": |
| 121 | + print(f"Generating {FILENAME_GENERATE_PROOF_TEST}...") |
| 122 | + with open(FILENAME_GENERATE_PROOF_TEST, "w", encoding="utf-8") as fil_generate_proof: |
| 123 | + gen_all_generate_proof_vectors(fil_generate_proof) |
| 124 | + print(f"Generating {FILENAME_VERIFY_PROOF_TEST}...") |
| 125 | + with open(FILENAME_VERIFY_PROOF_TEST, "w", encoding="utf-8") as fil_verify_proof: |
| 126 | + gen_all_verify_proof_vectors(fil_verify_proof) |
0 commit comments