-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
cron: update bips submodule and build
- Loading branch information
1 parent
6f00e5d
commit a62d0af
Showing
412 changed files
with
2,537 additions
and
318 deletions.
There are no files selected for viewing
Submodule bips
updated
7 files
+7 −0 | README.mediawiki | |
+19 −0 | bip-0349.md | |
+128 −0 | bip-0374.mediawiki | |
+126 −0 | bip-0374/gen_test_vectors.py | |
+144 −0 | bip-0374/reference.py | |
+77 −0 | bip-0374/run_test_vectors.py | |
+354 −0 | bip-0374/secp256k1.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
#!/usr/bin/env python3 | ||
"""Generate the BIP-DLEQ test vectors (limited to secp256k1 generator right now).""" | ||
import csv | ||
import os | ||
import sys | ||
from reference import ( | ||
TaggedHash, | ||
dleq_generate_proof, | ||
dleq_verify_proof, | ||
) | ||
from secp256k1 import G as GENERATOR, GE | ||
|
||
|
||
NUM_SUCCESS_TEST_VECTORS = 5 | ||
DLEQ_TAG_TESTVECTORS_RNG = "BIP0374/testvectors_rng" | ||
|
||
FILENAME_GENERATE_PROOF_TEST = os.path.join(sys.path[0], 'test_vectors_generate_proof.csv') | ||
FILENAME_VERIFY_PROOF_TEST = os.path.join(sys.path[0], 'test_vectors_verify_proof.csv') | ||
|
||
|
||
def random_scalar_int(vector_i, purpose): | ||
rng_out = TaggedHash(DLEQ_TAG_TESTVECTORS_RNG, purpose.encode() + vector_i.to_bytes(4, 'little')) | ||
return int.from_bytes(rng_out, 'big') % GE.ORDER | ||
|
||
|
||
def random_bytes(vector_i, purpose): | ||
rng_out = TaggedHash(DLEQ_TAG_TESTVECTORS_RNG, purpose.encode() + vector_i.to_bytes(4, 'little')) | ||
return rng_out | ||
|
||
|
||
def create_test_vector_data(vector_i): | ||
g = random_scalar_int(vector_i, "scalar_g") | ||
assert g < GE.ORDER | ||
assert g > 0 | ||
G = g * GENERATOR | ||
assert not G.infinity | ||
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") | ||
msg = random_bytes(vector_i, "message") | ||
proof = dleq_generate_proof(a, B, auxrand, G=G, m=msg) | ||
return (G, a, A, b, B, C, auxrand, msg, proof) | ||
|
||
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) | ||
writer.writerow(("index", "point_G", "scalar_a", "point_B", "auxrand_r", "message", "result_proof", "comment")) | ||
|
||
# success cases with random values | ||
idx = 0 | ||
for i in range(NUM_SUCCESS_TEST_VECTORS): | ||
G, a, A, b, B, C, auxrand, msg, proof = TEST_VECTOR_DATA[i] | ||
assert proof is not None and len(proof) == 64 | ||
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}")) | ||
idx += 1 | ||
|
||
# failure cases: a is not within group order (a=0, a=N) | ||
a_invalid = 0 | ||
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)")) | ||
idx += 1 | ||
a_invalid = GE.ORDER | ||
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])")) | ||
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 | ||
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)")) | ||
idx += 1 | ||
|
||
|
||
def gen_all_verify_proof_vectors(f): | ||
writer = csv.writer(f) | ||
writer.writerow(("index", "point_G", "point_A", "point_B", "point_C", "proof", "message", "result_success", "comment")) | ||
|
||
# success cases (same as above) | ||
idx = 0 | ||
for i in range(NUM_SUCCESS_TEST_VECTORS): | ||
G, _, A, _, B, C, _, msg, proof = TEST_VECTOR_DATA[i] | ||
assert dleq_verify_proof(A, B, C, proof, 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.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) | ||
writer.writerow((idx, G.to_bytes_compressed().hex(), points[0].to_bytes_compressed().hex(), points[1].to_bytes_compressed().hex(), | ||
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) | ||
writer.writerow((idx, G.to_bytes_compressed().hex(), A.to_bytes_compressed().hex(), B.to_bytes_compressed().hex(), | ||
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) | ||
msg_damaged[proof_damage_pos // 8] ^= (1 << (msg_damage_pos % 8)) | ||
msg_damaged = bytes(msg_damaged) | ||
writer.writerow((idx, G.to_bytes_compressed().hex(), A.to_bytes_compressed().hex(), B.to_bytes_compressed().hex(), | ||
C.to_bytes_compressed().hex(), proof.hex(), msg_damaged.hex(), "FALSE", f"Tampered message (random bit-flip)")) | ||
idx += 1 | ||
|
||
|
||
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
+++ | ||
+++ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
"""Reference implementation of DLEQ BIP for secp256k1 with unit tests.""" | ||
|
||
from hashlib import sha256 | ||
import random | ||
from secp256k1 import G, GE | ||
import sys | ||
import unittest | ||
|
||
|
||
DLEQ_TAG_AUX = "BIP0374/aux" | ||
DLEQ_TAG_NONCE = "BIP0374/nonce" | ||
DLEQ_TAG_CHALLENGE = "BIP0374/challenge" | ||
|
||
|
||
def TaggedHash(tag: str, data: bytes) -> bytes: | ||
ss = sha256(tag.encode()).digest() | ||
ss += ss | ||
ss += data | ||
return sha256(ss).digest() | ||
|
||
|
||
def xor_bytes(lhs: bytes, rhs: bytes) -> bytes: | ||
assert len(lhs) == len(rhs) | ||
return bytes([lhs[i] ^ rhs[i] for i in range(len(lhs))]) | ||
|
||
|
||
def dleq_challenge( | ||
A: GE, B: GE, C: GE, R1: GE, R2: GE, m: bytes | None, G: GE = G, | ||
) -> int: | ||
if m is not None: | ||
assert len(m) == 32 | ||
m = bytes([]) if m is None else m | ||
return int.from_bytes( | ||
TaggedHash( | ||
DLEQ_TAG_CHALLENGE, | ||
A.to_bytes_compressed() | ||
+ B.to_bytes_compressed() | ||
+ C.to_bytes_compressed() | ||
+ G.to_bytes_compressed() | ||
+ R1.to_bytes_compressed() | ||
+ R2.to_bytes_compressed() | ||
+ m, | ||
), | ||
"big", | ||
) | ||
|
||
|
||
def dleq_generate_proof( | ||
a: int, B: GE, r: bytes, G: GE = G, m: bytes | None = None | ||
) -> bytes | None: | ||
assert len(r) == 32 | ||
if not (0 < a < GE.ORDER): | ||
return None | ||
if B.infinity: | ||
return None | ||
A = a * G | ||
C = a * B | ||
t = xor_bytes(a.to_bytes(32, "big"), TaggedHash(DLEQ_TAG_AUX, r)) | ||
rand = TaggedHash( | ||
DLEQ_TAG_NONCE, t + A.to_bytes_compressed() + C.to_bytes_compressed() | ||
) | ||
k = int.from_bytes(rand, "big") % GE.ORDER | ||
if k == 0: | ||
return None | ||
R1 = k * G | ||
R2 = k * B | ||
e = dleq_challenge(A, B, C, R1, R2, m) | ||
s = (k + e * a) % GE.ORDER | ||
proof = e.to_bytes(32, "big") + s.to_bytes(32, "big") | ||
if not dleq_verify_proof(A, B, C, proof, G=G, m=m): | ||
return None | ||
return proof | ||
|
||
|
||
def dleq_verify_proof( | ||
A: GE, B: GE, C: GE, proof: bytes, G: GE = G, m: bytes | None = None | ||
) -> bool: | ||
if A.infinity or B.infinity or C.infinity or G.infinity: | ||
return False | ||
assert len(proof) == 64 | ||
e = int.from_bytes(proof[:32], "big") | ||
s = int.from_bytes(proof[32:], "big") | ||
if s >= GE.ORDER: | ||
return False | ||
# TODO: implement subtraction operator (__sub__) for GE class to simplify these terms | ||
R1 = s * G + (-e * A) | ||
if R1.infinity: | ||
return False | ||
R2 = s * B + (-e * C) | ||
if R2.infinity: | ||
return False | ||
if e != dleq_challenge(A, B, C, R1, R2, m): | ||
return False | ||
return True | ||
|
||
|
||
class DLEQTests(unittest.TestCase): | ||
def test_dleq(self): | ||
seed = random.randrange(sys.maxsize) | ||
random.seed(seed) | ||
print(f"PRNG seed is: {seed}") | ||
for _ in range(10): | ||
# generate random keypairs for both parties | ||
a = random.randrange(1, GE.ORDER) | ||
A = a * G | ||
b = random.randrange(1, GE.ORDER) | ||
B = b * G | ||
|
||
# create shared secret | ||
C = a * B | ||
|
||
# create dleq proof | ||
rand_aux = random.randbytes(32) | ||
proof = dleq_generate_proof(a, B, rand_aux) | ||
self.assertTrue(proof is not None) | ||
# verify dleq proof | ||
success = dleq_verify_proof(A, B, C, proof) | ||
self.assertTrue(success) | ||
|
||
# flip a random bit in the dleq proof and check that verification fails | ||
for _ in range(5): | ||
proof_damaged = list(proof) | ||
proof_damaged[random.randrange(len(proof))] ^= 1 << ( | ||
random.randrange(8) | ||
) | ||
success = dleq_verify_proof(A, B, C, bytes(proof_damaged)) | ||
self.assertFalse(success) | ||
|
||
# create the same dleq proof with a message | ||
message = random.randbytes(32) | ||
proof = dleq_generate_proof(a, B, rand_aux, m=message) | ||
self.assertTrue(proof is not None) | ||
# verify dleq proof with a message | ||
success = dleq_verify_proof(A, B, C, proof, m=message) | ||
self.assertTrue(success) | ||
|
||
# flip a random bit in the dleq proof and check that verification fails | ||
for _ in range(5): | ||
proof_damaged = list(proof) | ||
proof_damaged[random.randrange(len(proof))] ^= 1 << ( | ||
random.randrange(8) | ||
) | ||
success = dleq_verify_proof(A, B, C, bytes(proof_damaged)) | ||
self.assertFalse(success) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
#!/usr/bin/env python3 | ||
"""Run the BIP-DLEQ test vectors.""" | ||
import csv | ||
import os | ||
import sys | ||
from reference import ( | ||
dleq_generate_proof, | ||
dleq_verify_proof, | ||
) | ||
from secp256k1 import GE | ||
|
||
|
||
FILENAME_GENERATE_PROOF_TEST = os.path.join(sys.path[0], 'test_vectors_generate_proof.csv') | ||
FILENAME_VERIFY_PROOF_TEST = os.path.join(sys.path[0], 'test_vectors_verify_proof.csv') | ||
|
||
|
||
all_passed = True | ||
print("-----------------------------------------") | ||
print("----- Proof generation test vectors -----") | ||
print("-----------------------------------------") | ||
with open(FILENAME_GENERATE_PROOF_TEST, newline='') as csvfile: | ||
reader = csv.reader(csvfile) | ||
reader.__next__() | ||
for row in reader: | ||
(index, point_G_hex, seckey_a_hex, point_B_hex, aux_rand_hex, msg_hex, result_str, comment) = row | ||
print(seckey_a_hex) | ||
G = GE() if point_G_hex == 'INFINITY' else GE.from_bytes(bytes.fromhex(point_G_hex)) | ||
a = int.from_bytes(bytes.fromhex(seckey_a_hex), 'big') | ||
B = GE() if point_B_hex == 'INFINITY' else GE.from_bytes(bytes.fromhex(point_B_hex)) | ||
aux_rand = bytes.fromhex(aux_rand_hex) | ||
msg = bytes.fromhex(msg_hex) | ||
print('Test vector', ('#' + index).rjust(3, ' ') + ':' + f' ({comment})') | ||
expected_result = None if result_str == 'INVALID' else bytes.fromhex(result_str) | ||
actual_result = dleq_generate_proof(a, B, aux_rand, G=G, m=msg) | ||
if expected_result == actual_result: | ||
print(' * Passed proof generation test.') | ||
else: | ||
print(' * Failed proof generation test.') | ||
print(' Expected proof: ', expected_result.hex() if expected_result is not None else 'INVALID') | ||
print(' Actual proof: ', actual_result.hex() if actual_result is not None else 'INVALID') | ||
all_passed = False | ||
print() | ||
|
||
|
||
print("-------------------------------------------") | ||
print("----- Proof verification test vectors -----") | ||
print("-------------------------------------------") | ||
with open(FILENAME_VERIFY_PROOF_TEST, newline='') as csvfile: | ||
reader = csv.reader(csvfile) | ||
reader.__next__() | ||
for row in reader: | ||
(index, point_G_hex, point_A_hex, point_B_hex, point_C_hex, proof_hex, msg_hex, result_success, comment) = row | ||
G = GE() if point_G_hex == 'INFINITY' else GE.from_bytes(bytes.fromhex(point_G_hex)) | ||
A = GE() if point_A_hex == 'INFINITY' else GE.from_bytes(bytes.fromhex(point_A_hex)) | ||
B = GE() if point_B_hex == 'INFINITY' else GE.from_bytes(bytes.fromhex(point_B_hex)) | ||
C = GE() if point_C_hex == 'INFINITY' else GE.from_bytes(bytes.fromhex(point_C_hex)) | ||
proof = bytes.fromhex(proof_hex) | ||
msg = bytes.fromhex(msg_hex) | ||
print('Test vector', ('#' + index).rjust(3, ' ') + ':' + f' ({comment})') | ||
expected_result = result_success == 'TRUE' | ||
actual_result = dleq_verify_proof(A, B, C, proof, G=G, m=msg) | ||
if expected_result == actual_result: | ||
print(' * Passed proof verification test.') | ||
else: | ||
print(' * Failed proof verification test.') | ||
print(' Expected verification result: ', expected_result) | ||
print(' Actual verification result: ', actual_result) | ||
all_passed = False | ||
|
||
|
||
print() | ||
if all_passed: | ||
print('All test vectors passed.') | ||
sys.exit(0) | ||
else: | ||
print('Some test vectors failed.') | ||
sys.exit(1) |
Oops, something went wrong.