Skip to content

Instantly share code, notes, and snippets.

@j-berman
Last active December 3, 2022 19:04
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save j-berman/133d29d09df61ed5083a52e46f085849 to your computer and use it in GitHub Desktop.
Save j-berman/133d29d09df61ed5083a52e46f085849 to your computer and use it in GitHub Desktop.
How pedersen commitments are constructed to hide amounts in Monero's RingCT (simplified)
# The goal with this program is to demonstrate how to construct pedersen
# commitments in similar fashion to how the Monero tx protocol does, but in a hugely
# simplified way (read: not exact, but close), to show how we can prove hidden input
# and output amounts balance.
# Some stuff is left as an exercise to the reader where commented.
# **Corrections are very much so welcome.**
# I encourage anyone who doesn't know much about pedersen commitments to
# go through coinstudent2048's excellent tutorial on elliptic curve
# cryptography [1] first. And anyone interested in this to first read through
# Zero to Monero v2 chapters 5 and 6 [3].
#
# sources:
# [1]: https://github.com/coinstudent2048/ed25519_tutorials
# [2]: https://github.com/SarangNoether/skunkworks
# [3]: https://web.getmonero.org/library/Zero-to-Monero-2-0-0.pdf#chapter.5
# get dumb25519 from Sarang's https://github.com/SarangNoether/skunkworks/blob/rct3/rct3-single/dumb25519.py
from dumb25519 import hash_to_point, Scalar, random_scalar, G
H = hash_to_point("Pedersen")
# "Monero cryptocurrency uses Pedersen commitment to hide amounts in the blockchain.
# implement Pedersen commitment: given a scalar x, it must output a pair (r, rG + xH) where r is
# a random scalar. for Monero, r should never be in the blockchain, only the rG + xH is."
#
# https://github.com/coinstudent2048/ed25519_tutorials/blob/7fa21c7b1dd8d7fea184c552c69f14b6f162f4d1/ed25519_tutorial1.py#L116-L118
def pedersen(amount):
r = random_scalar()
rG = r * G
xH = amount * H
commitment = rG + xH
return r, commitment
# exercise: construct pedersen commitments that show (3 + 5) - (1 + 7) == 0, without revealing the amounts.
# The intent is to demonstrate that (sum of the inputs) - (sum of the outputs) == 0, without
# revealing amounts of inputs and outputs. In mathematical terms, we want to show that:
#
# [(r1 * G + 3 * H) + (r2 * G + 5 * H)] - [(r3 * G + 1 * H) + (r4 * G + 7 * H)] == 0
# or [(c1 + c2) - (c3 + c4)] == 0
# inputs
r1,c1 = pedersen(Scalar(3))
r2,c2 = pedersen(Scalar(5))
# outputs
r3,c3 = pedersen(Scalar(1))
# Recall that pedersen commitments are additively homomorphic (C(a + b) = C(a) + C(b)),
# which means we can re-arrange the above equation to:
#
# [(r1 + r2) - (r3 + r4)] * G - [(3 + 5) - (1 + 7)] * H == 0
# [(r1 + r2) - (r3 + r4)] * G - [(8) - (8)] * H == 0
# [(r1 + r2) - (r3 + r4)] * G - (0) * H == 0
# [(r1 + r2) - (r3 + r4)] * G == 0
# [(r1 + r2) - (r3 + r4)] == 0
# r4 == (r1 + r2) - r3
#
# We re-arrange the equation in the above way to land on a value for r in the final
# output's commitment that ensures the equation balances.
r4 = (r1 + r2) - r3
c4 = (r4 * G) + (Scalar(7) * H)
# Recall only commitments get sent to the blockchain, not secret r values
if c1 + c2 == c3 + c4:
print("We constructed commitments that show the sum of the inputs - sum of the outputs == 0")
else:
print("Something's wrong :(")
# Now, we still need to prove that amounts correspond to commitments (among other things left out of scope).
# Otherwise there is no way for the verifier to know that the prover didn't send over some commitments
# that add up right, but use different amounts, like as follows:
# inputs
r1,c1 = pedersen(Scalar(3))
r2,c2 = pedersen(Scalar(5))
# outputs
r3,c3 = pedersen(Scalar(1))
x4H = Scalar(10000) * H
r4G = (c1 + c2) - c3 - x4H
c4 = r4G + x4H
if c1 + c2 == c3 + c4:
print("We constructed commitments where the final output is a commitment to a coin with amount 10000")
else:
print("Something's wrong :(")
# There is one interesting caveat with the 10000 amount output commitment above:
# no one knows that the value for r4 is.
#
# Looking at the equation:
# r4G = (c1 + c2) - c3 - 10000 * H
# r4G = <some value we can calculate as above>
# r4 = r4G / G ... dividing by G is not solvable because DL problem is hard
#
# Written another way:
# r4G = [(r1 + r2) - r3)] * G + (3 + 5 - 1 - 10000) * H
# r4 = [(r1 + r2) - r3)] + (-9993) * H / G
# r4 = <some value we can calculate> - (<some other value we can calculate> / G) ... dividing by G is not solvable because DL problem is hard
#
# Thus, because the discrete logarithm problem is hard, we are unable to determine what the
# value for r4 would be that gets the above construction with a 10000 coin output to balance.
#
# Thus, if we are able to prove that we have knowledge of the secret r values when
# spending an output, then we have sufficiently proven that we know how to get the
# equation to balance, with correct amounts, without revealing those amounts.
# However, revealing r values, or rG values would allow a passive observer to
# determine output amounts, and the scheme wouldn't work.
# Monero solves for this by using what are referred to in Monero land as "psuedo out commitments".
# For each input in a transaction, there exists a commitment in the chain already,
# and at tx construction time, we construct a brand NEW commitment to the same amount referred to
# as a pseudo out commitment.
# inputs (these already exist in the chain)
r1,c1 = pedersen(Scalar(3))
r2,c2 = pedersen(Scalar(5))
# pseudo output commitments (these are created at tx construction time)
pseudo_r1,pseudo_c1 = pedersen(Scalar(3))
pseudo_r2,pseudo_c2 = pedersen(Scalar(5))
# outputs (created at tx construction time)
r3,c3 = pedersen(Scalar(1))
# We then use the pseudo outs to land on a value for r4, same way as before
r4 = (pseudo_r1 + pseudo_r2) - r3
c4 = (r4 * G) + (Scalar(7) * H)
# The chain verifies the following holds to ensure pseudo out commitments
# balance with output commitments:
if pseudo_c1 + pseudo_c2 == c3 + c4:
print("sum of (pseudo out commitments) inputs - sum of outputs == 0")
else
print("Something's wrong :(")
# Now here is the interesting part: there exists a private key "z" for each input, that
# we can use to show that we have knoweldge of the input r1 and r2 values.
z1 = r1 - pseudo_r1
z2 = r2 - pseudo_r2
# The chain verifies the following holds to ensure pseudo out commitments
# balance with input commitments:
if c1 - pseudo_c1 == z1 * G and c2 - pseudo_c2 == z2 * G:
print("Pseudo out commitments balance with input commitments")
else:
print("Something's wrong :(")
# Then we prove knowledge of z1 and z2, which is accomplished via a ring signature
# as described in Zero to Monero 6.2.2. That is left out of scope here.
# Source: https://web.getmonero.org/library/Zero-to-Monero-2-0-0.pdf#subsection.6.2.2
# We also still have to prove all output amounts are in the range [0, 2^64],
# since negative values would allow commitments to balance while printing more
# coin. This is accomplished via range proofs and described in Zero to Monero 5.5.
# Also left out of scope for the reader.
# Source: https://web.getmonero.org/library/Zero-to-Monero-2-0-0.pdf#section.5.5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment