@@ -1425,6 +1425,165 @@ def VerifyPresentationProof(serverPrivateKey, serverPublicKey, requestContext, p
14251425 return verifier.Verify(presentation.proof)
14261426~~~
14271427
1428+ # # Range Proof for Arbitrary Values
1429+
1430+ This section specifies a range proof in the framework of
1431+ {{!SIGMA=I-D.draft-irtf-cfrg-sigma-protocols-00}} to prove a secret value `v` lies
1432+ in an arbitrary interval `[0,upper_bound)`. Before specifying the proof system, we first
1433+ give a brief overview of how it works. For simplicity, assume that `upper_bound` is a
1434+ power of two, that is, `upper_bound == 2^k` for some `k`.
1435+
1436+ To prove a value lies in `[0,(2^k)-1)`, we prove it has a valid `k`-bit representation.
1437+ This is proven by committing to the full value `v`, then all bits of the bit decomposition
1438+ ` b` of the value `v`, and then proving each coefficient of the bit decomposition is
1439+ actually `0` or `1` and that the sum of the bits amounts to the full value `v`. This involves the following steps :
1440+
1441+ 1. Commit to the bits of `v`. That is, for each bit `b[i]` of the bit decomposition of `v`, let
1442+ ` D[i] = b[i] * generatorG + s[i] * generatorH` , where `s[i]` is a blinding scalar.
1443+ 2. Prove that `b[i]` is in `{0,1}` by computing proving the algebraic relation `b[i] *
1444+ (b[i]-1) == 0` holds. This quadratic relation can be linearized by
1445+ adding an auxilary witness `s2[i]` and adding the linear relation
1446+ ` D[i] == b[i] * D[i] + s2[i] * generatorH` to the equation system. A valid witness `s2[i]` can only
1447+ be computed by the prover if `b[i]` is in `{0,1}`. Successfully computing a witness for
1448+ any other value requires the prover to break the discrete logarithm problem.
1449+ 3. Having verified the proof the above relation, the verifier checks the sum by computing
1450+
1451+ ~~~
1452+ C == D[0] * 2^0 + D[1] * 2^1 + D[2] * 2^2 + ... + D[k-1] * 2^{k-1}
1453+ ~~~
1454+
1455+ The third step is verified outside of the proof by adding the commitments
1456+ homomorphically.
1457+
1458+ To support the general case, where `upper_bound` is not necessarily a power of two,
1459+ we extend the range proof for arbitrary ranges by decomposing the range
1460+ up to the second highest power of two and adding an additional, non-binary range that
1461+ covers the remaining range. This is detailed in `ComputeBases` below.
1462+
1463+ ~~~
1464+ def ComputeBases(upper_bound) :
1465+
1466+ Inputs :
1467+
1468+ - upper_bound : the maximum value of the range (exclusive), as integer.
1469+
1470+ Outputs :
1471+
1472+ - bases : an array of Scalar bases to represent elements, sorted in descending order. A base is
1473+ either a power of two or a unique remainder that can be used to represent any integer
1474+ in [0, upper_bound).
1475+
1476+ # compute bases to express the commitment as a linear combination of the bit decomposition
1477+ remainder=upper_bound
1478+ bases=[]
1479+ # Generate all but the last power-of-two base.
1480+ for i in range(ceil(log2(upper_bound)) - 1) :
1481+ base = 2 ** i
1482+ remainder -= base
1483+ bases.append((G.Scalar(base))
1484+
1485+ if remainder != 0 :
1486+ bases.append(remainder - 1)
1487+
1488+ # call sorted on array to ensure the additional base is in correct order
1489+ return sorted(bases, reverse=True)
1490+ ~~~
1491+
1492+ Using the bases from `ComputeBases`, the function `ComputeStatementAndWitnesses`
1493+ represents the secret value `v` as a linear combination of the bases, using the resulting
1494+ bit representation to generate the cryptographic commitments and witness values for the
1495+ range proof.
1496+
1497+ ~~~
1498+ def ComputeStatementAndWitnesses(v, upper_bound) :
1499+
1500+ Inputs :
1501+
1502+ - v : the scalar we want to prove is in range [0, upper_bound)
1503+ - r : randomness for commitment to v
1504+ - upper_bound : the maximum integer value of the range
1505+
1506+ Outputs :
1507+
1508+ - statement : proof statement for the relation
1509+ - [s,s2] : the witness for the equations appended to the statement (the bit
1510+ decomposition, the secret shares of r, and the auxiliary witness s2. Each s2[i] is either zero when
1511+ b[i] is set) or s[i] when b[i] is zero.
1512+ - C : the commitment to v
1513+ - D : the commitments to the bit decomposition of v
1514+
1515+ Parameters :
1516+ - G : group
1517+ - generatorG : Element, equivalent to G.GeneratorG()
1518+ - generatorH : Element, equivalent to G.GeneratorH()
1519+
1520+ Exceptions :
1521+ - NumberTooBigError, raised when v is out of range
1522+
1523+ if G.ScalarToInt(v) >= upper_bound :
1524+ raise NumberTooBigError
1525+
1526+ bases = ComputeBases(upper_bound)
1527+
1528+ # Compute bit decomposition of v.
1529+ b = []
1530+ v_remainder = G.ScalarToInt(v)
1531+ for base in bases :
1532+ # Implementation note: In order to avoid leaking v via a timing channel, this code should be written to be constant time.
1533+ if v_remainder >= base :
1534+ v_remainder -= G.ScalarToInt(base)
1535+ b.append(G.Scalar(1))
1536+ else :
1537+ b.append(G.Scalar(0))
1538+
1539+ # array of group elements where the i-th element corresponds to b[i] * generatorG + s * generatorH
1540+ D = []
1541+ # blinding elements for Pedersen commitment, secret shares of r
1542+ s = []
1543+ # complementing blinders for proof of bit-ness
1544+ s2 = []
1545+ partial_sum = G.Scalar(0)
1546+ for i in range(len(bases) - 1) :
1547+ s.append(G.random_scalar())
1548+ partial_sum += s[i]
1549+ s2.append((G.Scalar(1) - b[i]) * s[i])
1550+ D.append(b[i] * generatorG + s[i] * generatorH)
1551+ idx = len(bases) - 1
1552+ s[idx] = r - partial_sum
1553+ s2.append((G.Scalar(1) - b[idx]) * s[idx])
1554+ D.append(b[idx] * generatorG + s[idx] * generatorH)
1555+
1556+ # Compute the Pedersen commitment to the full value of v, using the provided r.
1557+ C = v * generatorG + r * generatorH
1558+
1559+ # start computing the linear relation
1560+ statement = LinearRelation(G)
1561+
1562+ [var_G, var_H, var_C] = statement.allocate_elements(3)
1563+
1564+ # allocate variables for decomposed statements
1565+ vars_b = statement.allocate_scalars(len(b))
1566+ # allocate blinding elements for Pedersen commitment
1567+ vars_s = statement.allocateScalars(len(b))
1568+ # allocate complementing blinders for proof of bit-ness
1569+ vars_s2 = statement.allocateScalars(len(b))
1570+ # allocate bit commitment values
1571+ vars_D = statement.allocateElements(len(b))
1572+
1573+ # Add equations proving each b[i] is in {0,1}
1574+ # For each base, we prove:
1575+ # D[i] = b[i] * generatorG + s[i] * generatorH (b[i] is committed in D[i])
1576+ # b[i] * (b[i] - 1) = 0 (b[i] is 0 or 1)
1577+ for i in range(len(b)) :
1578+ statement.set_elements([(vars_D[i], D[i])])
1579+ # add Pedersen commitment to the ith bit.
1580+ statement.append_equation(vars_D[i], [(vars_b[i], var_G), (vars_s[i], var_H)])
1581+ # add statement that b[i] is in {0,1}
1582+ statement.append_equation(vars_D[i], [(vars_b[i], vars_D[i]), (vars_s2[i], var_H)])
1583+
1584+ return (statement, [r, v, b, s, s2], [C,D])
1585+ ~~~
1586+
14281587# Ciphersuites {#ciphersuites}
14291588
14301589A ciphersuite (also referred to as 'suite' in this document) for the protocol
0 commit comments