Skip to content

Commit

Permalink
cron: update bips submodule and build
Browse files Browse the repository at this point in the history
  • Loading branch information
github-actions[bot] committed Dec 28, 2024
1 parent 6f00e5d commit a62d0af
Show file tree
Hide file tree
Showing 412 changed files with 2,537 additions and 318 deletions.
2 changes: 1 addition & 1 deletion bips
126 changes: 126 additions & 0 deletions web/content/374/bip-0374/gen_test_vectors.py
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)
2 changes: 2 additions & 0 deletions web/content/374/bip-0374/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
+++
+++
144 changes: 144 additions & 0 deletions web/content/374/bip-0374/reference.py
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)
77 changes: 77 additions & 0 deletions web/content/374/bip-0374/run_test_vectors.py
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)
Loading

0 comments on commit a62d0af

Please sign in to comment.