Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BIP374: Discrete Log Equality Proofs (DLEQ) #1689

Merged
merged 24 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4f5d87a
Bip Draft: DLEQ
andrewtoth Oct 24, 2024
0c7e54d
BIP-DLEQ: add reference implementation for secp256k1
theStack Nov 18, 2024
cc7bb12
Add optional message to DLEQ
andrewtoth Dec 9, 2024
ed98dc7
Add some more commentary
andrewtoth Dec 9, 2024
b5d47df
add theStack as co-author
andrewtoth Dec 9, 2024
597004a
Lowercase secp
andrewtoth Dec 11, 2024
e4f1d7b
Remove cbytes wrapper from m'
andrewtoth Dec 11, 2024
b838696
Remove cbytes wrapper from m'
andrewtoth Dec 11, 2024
dab5571
bugfix: respect message m in DLEQ proof generation/verification
theStack Dec 21, 2024
6b16952
Add test vectors for DLEQ proof generation/verification
theStack Dec 20, 2024
1f875a3
Add note about generating and running test vectors
andrewtoth Dec 21, 2024
687198d
Fail if any point is infinity when verifying
andrewtoth Dec 21, 2024
f5d1c12
Add acknowledgements
andrewtoth Dec 21, 2024
fd60d8e
Add description of proof
andrewtoth Dec 21, 2024
90e7027
Remove changelog
andrewtoth Dec 21, 2024
0b590d0
Add footnote recommending using fresh randomness for each proof
andrewtoth Dec 21, 2024
a0d8aad
Fix typo
andrewtoth Dec 21, 2024
5799659
Update bip-DLEQ.mediawiki
andrewtoth Dec 26, 2024
b533b92
Update bip-DLEQ.mediawiki
andrewtoth Dec 26, 2024
1350bc4
BIP374
andrewtoth Dec 26, 2024
9d6dc6b
Update README table, post-history, and comments-uri
andrewtoth Dec 26, 2024
1842120
Clarify restraints on given points
andrewtoth Dec 26, 2024
cb3afee
Move test vectors to bip-0374 directory, add tests for G
andrewtoth Dec 26, 2024
248540e
fix typo
andrewtoth Dec 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions bip-DLEQ.mediawiki
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Input:
* The public key ''B'': a point on the curve
* The generator point ''G'': a point on the curve
* Auxiliary random data ''r'': a 32-byte array
andrewtoth marked this conversation as resolved.
Show resolved Hide resolved
* An optional message ''m'': a 32-byte array

The algorithm ''GenerateProof(a, B, r)'' is defined as:
* Fail if ''a = 0'' or ''a ≥ n''.
Expand All @@ -50,7 +51,8 @@ The algorithm ''GenerateProof(a, B, r)'' is defined as:
* Fail if ''k = 0''.
* Let ''R<sub>1</sub> = k⋅G''.
* Let ''R<sub>2</sub> = k⋅B''.
* Let ''e = int(hash<sub>BIP0???/challenge</sub>(cbytes(A) || cbytes(B) || cbytes(C) || cbytes(G) || cbytes(R<sub>1</sub>) || cbytes(R<sub>2</sub>)))''.
* Let ''m' = m if m is provided, otherwise an empty byte array''.
* Let ''e = int(hash<sub>BIP0???/challenge</sub>(cbytes(A) || cbytes(B) || cbytes(C) || cbytes(G) || cbytes(R<sub>1</sub>) || cbytes(R<sub>2</sub>) || cbytes(m')))''.
andrewtoth marked this conversation as resolved.
Show resolved Hide resolved
* Let ''s = (k + e⋅a) mod n''.
* Let ''proof = bytes(32, e) || bytes(32, s)''.
* If ''VerifyProof(A, B, C, proof)'' (see below) returns failure, abort.
Expand All @@ -64,6 +66,7 @@ Input:
* The result of multiplying the secret and public keys used in the proof generation ''C'': a point on the curve
* The generator point used in the proof generation ''G'': a point on the curve
* A proof ''proof'': a 64-byte array
* An optional message ''m'': a 32-byte array

The algorithm ''VerifyProof(A, B, C, G, proof)'' is defined as:
* Let ''e = int(proof[0:32])''.
Expand All @@ -72,7 +75,8 @@ The algorithm ''VerifyProof(A, B, C, G, proof)'' is defined as:
* Fail if ''is_infinite(R<sub>1</sub>)''.
* Let ''R<sub>2</sub> = s⋅B - e⋅C''.
* Fail if ''is_infinite(R<sub>2</sub>)''.
* Fail if ''e ≠ int(hash<sub>BIP0???/challenge</sub>(cbytes(A) || cbytes(B) || cbytes(C) || cbytes(G) || cbytes(R<sub>1</sub>) || cbytes(R<sub>2</sub>)))''.
* Let ''m' = m if m is provided, otherwise an empty byte array''.
* Fail if ''e ≠ int(hash<sub>BIP0???/challenge</sub>(cbytes(A) || cbytes(B) || cbytes(C) || cbytes(G) || cbytes(R<sub>1</sub>) || cbytes(R<sub>2</sub>) || cbytes(m')))''.
* Return success iff no failure occurred before reaching this point.

== Test Vectors and Reference Code ==
Expand Down
70 changes: 55 additions & 15 deletions bip-DLEQ/reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,39 +24,60 @@ def xor_bytes(lhs: bytes, rhs: bytes) -> bytes:
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) -> int:
return int.from_bytes(TaggedHash(DLEQ_TAG_CHALLENGE,
A.to_bytes_compressed() + B.to_bytes_compressed() + C.to_bytes_compressed() +
R1.to_bytes_compressed() + R2.to_bytes_compressed()), 'big')


def dleq_generate_proof(a: int, B: GE, r: bytes) -> bytes | None:
def dleq_challenge(
A: GE, B: GE, C: GE, R1: GE, R2: GE, G: GE = G, m: bytes | None = None
andrewtoth marked this conversation as resolved.
Show resolved Hide resolved
) -> int:
if m is not None:
assert len(m) == 32
m = bytes([]) if m is None else m.to_bytes(32, "big")
andrewtoth marked this conversation as resolved.
Show resolved Hide resolved
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
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)
s = (k + e * a) % GE.ORDER
proof = e.to_bytes(32, 'big') + s.to_bytes(32, 'big')
proof = e.to_bytes(32, "big") + s.to_bytes(32, "big")
if not dleq_verify_proof(A, B, C, proof):
return None
return proof


def dleq_verify_proof(A: GE, B: GE, C: GE, proof: bytes) -> bool:
def dleq_verify_proof(
A: GE, B: GE, C: GE, proof: bytes, G: GE = G, m: bytes | None = None
) -> bool:
assert len(proof) == 64
e = int.from_bytes(proof[:32], 'big')
s = int.from_bytes(proof[32:], 'big')
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
Expand Down Expand Up @@ -97,6 +118,25 @@ def test_dleq(self):
# 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)))
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)