Make the width of the range proof parameterizable.
MODIFY
WalletWasabi.Tests/UnitTests/Crypto/ProofSystemTests.cs
WalletWasabi.Tests/UnitTests/Crypto/ProofSystemTests.cs
@@ -203,15 +203,15 @@ namespace WalletWasabi.Tests.UnitTests.Crypto
var commitment = amountScalar * Generators.Gg + randomness * Generators.Gh;
var maskedScalar = new Scalar(amount & ((1ul << width) - 1));
- var (knowledge, bitCommitments) = ProofSystem.RangeProofKnowledge(maskedScalar, randomness, width, rnd);
+ var (knowledge, bitCommitments) = ProofSystem.RangeProofKnowledge(maskedScalar, randomness, width, rnd, 42);
var rangeProof = ProofSystemHelpers.Prove(knowledge, rnd);
- Assert.Equal(pass, ProofSystemHelpers.Verify(ProofSystem.RangeProofStatement(commitment, bitCommitments), rangeProof));
+ Assert.Equal(pass, ProofSystemHelpers.Verify(ProofSystem.RangeProofStatement(commitment, bitCommitments, 42), rangeProof));
if (!pass)
{
- Assert.Throws<ArgumentException>(() => ProofSystem.RangeProofKnowledge(amountScalar, randomness, width, rnd));
+ Assert.Throws<ArgumentException>(() => ProofSystem.RangeProofKnowledge(amountScalar, randomness, width, rnd, 42));
}
}
@@ -231,16 +231,16 @@ namespace WalletWasabi.Tests.UnitTests.Crypto
var knowledge = new[]
{
- ProofSystem.ZeroProofKnowledge(ma0, r0),
- ProofSystem.ZeroProofKnowledge(ma1, r1)
+ ProofSystem.ZeroProofKnowledge(ma0, r0, 42),
+ ProofSystem.ZeroProofKnowledge(ma1, r1, 42)
};
var proofs = ProofSystem.Prove(new Transcript(Array.Empty<byte>()), knowledge, rnd);
var statements = new[]
{
- ProofSystem.ZeroProofStatement(ma0),
- ProofSystem.ZeroProofStatement(ma1)
+ ProofSystem.ZeroProofStatement(ma0, 42),
+ ProofSystem.ZeroProofStatement(ma1, 42)
};
Assert.True(ProofSystem.Verify(new Transcript(Array.Empty<byte>()), statements, proofs));

MODIFY
WalletWasabi.Tests/UnitTests/Wabisabi/CredentialTests.cs
WalletWasabi.Tests/UnitTests/Wabisabi/CredentialTests.cs
@@ -22,8 +22,8 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi
var numberOfCredentials = 2;
using var rnd = new SecureRandom();
var sk = new CredentialIssuerSecretKey(rnd);
- var client = new WabiSabiClient(sk.ComputeCredentialIssuerParameters(), numberOfCredentials, rnd);
- var issuer = new CredentialIssuer(sk, numberOfCredentials, rnd);
+ var client = new WabiSabiClient(sk.ComputeCredentialIssuerParameters(), numberOfCredentials, rnd, 4400000000000);
+ var issuer = new CredentialIssuer(sk, numberOfCredentials, rnd, 4400000000000);
// Input Reg
var (zeroCredentialRequest, zeroValidationData) = client.CreateRequestForZeroAmount();
@@ -76,7 +76,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi
using var rnd = new SecureRandom();
var sk = new CredentialIssuerSecretKey(rnd);
- var client = new WabiSabiClient(sk.ComputeCredentialIssuerParameters(), numberOfCredentials, rnd);
+ var client = new WabiSabiClient(sk.ComputeCredentialIssuerParameters(), numberOfCredentials, rnd, 4400000000000);
{
// Null request. This requests `numberOfCredentials` zero-value credentials.
@@ -91,7 +91,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi
Assert.Equal(0, credentialRequest.Delta);
// Issuer part.
- var issuer = new CredentialIssuer(sk, numberOfCredentials, rnd);
+ var issuer = new CredentialIssuer(sk, numberOfCredentials, rnd, 4400000000000);
var credentialResponse = issuer.HandleRequest(credentialRequest);
client.HandleResponse(credentialResponse, validationData);
@@ -112,7 +112,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi
Assert.NotEmpty(credentialRequested[1].BitCommitments);
// Issuer part.
- var issuer = new CredentialIssuer(sk, numberOfCredentials, rnd);
+ var issuer = new CredentialIssuer(sk, numberOfCredentials, rnd, 4400000000000);
var credentialResponse = issuer.HandleRequest(credentialRequest);
client.HandleResponse(credentialResponse, validationData);
@@ -136,7 +136,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi
Assert.Equal(0, credentialRequest.Delta);
// Issuer part.
- var issuer = new CredentialIssuer(sk, numberOfCredentials, rnd);
+ var issuer = new CredentialIssuer(sk, numberOfCredentials, rnd, 4400000000000);
var credentialResponse = issuer.HandleRequest(credentialRequest);
client.HandleResponse(credentialResponse, validationData);
@@ -150,10 +150,10 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi
}
{
- var client0 = new WabiSabiClient(sk.ComputeCredentialIssuerParameters(), numberOfCredentials, rnd);
+ var client0 = new WabiSabiClient(sk.ComputeCredentialIssuerParameters(), numberOfCredentials, rnd, 4400000000000);
var (credentialRequest, validationData) = client0.CreateRequestForZeroAmount();
- var issuer = new CredentialIssuer(sk, numberOfCredentials, rnd);
+ var issuer = new CredentialIssuer(sk, numberOfCredentials, rnd, 4400000000000);
var credentialResponse = issuer.HandleRequest(credentialRequest);
client0.HandleResponse(credentialResponse, validationData);
@@ -179,9 +179,9 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi
using var rnd = new SecureRandom();
var sk = new CredentialIssuerSecretKey(rnd);
- var issuer = new CredentialIssuer(sk, numberOfCredentials, rnd);
+ var issuer = new CredentialIssuer(sk, numberOfCredentials, rnd, 4400000000000);
{
- var client = new WabiSabiClient(sk.ComputeCredentialIssuerParameters(), numberOfCredentials, rnd);
+ var client = new WabiSabiClient(sk.ComputeCredentialIssuerParameters(), numberOfCredentials, rnd, 4400000000000);
// Null request. This requests `numberOfCredentials` zero-value credentials.
var (credentialRequest, validationData) = client.CreateRequestForZeroAmount();
@@ -253,7 +253,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi
}
{
- var client = new WabiSabiClient(sk.ComputeCredentialIssuerParameters(), numberOfCredentials, rnd);
+ var client = new WabiSabiClient(sk.ComputeCredentialIssuerParameters(), numberOfCredentials, rnd, 4400000000000);
var (validCredentialRequest, validationData) = client.CreateRequestForZeroAmount();
// Test invalid proofs.
@@ -268,7 +268,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi
}
{
- var client = new WabiSabiClient(sk.ComputeCredentialIssuerParameters(), numberOfCredentials, rnd);
+ var client = new WabiSabiClient(sk.ComputeCredentialIssuerParameters(), numberOfCredentials, rnd, 4400000000000);
var (validCredentialRequest, validationData) = client.CreateRequestForZeroAmount();
var credentialResponse = issuer.HandleRequest(validCredentialRequest);

MODIFY
WalletWasabi.Tests/UnitTests/Wabisabi/SerializationTests.cs
WalletWasabi.Tests/UnitTests/Wabisabi/SerializationTests.cs
@@ -158,8 +158,8 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi
using var rnd = new SecureRandom();
var sk = new CredentialIssuerSecretKey(rnd);
- var issuer = new CredentialIssuer(sk, numberOfCredentials, rnd);
- var client = new WabiSabiClient(sk.ComputeCredentialIssuerParameters(), numberOfCredentials, rnd);
+ var issuer = new CredentialIssuer(sk, numberOfCredentials, rnd, 4400000000000);
+ var client = new WabiSabiClient(sk.ComputeCredentialIssuerParameters(), numberOfCredentials, rnd, 4400000000000);
var (credentialRequest, validationData) = client.CreateRequestForZeroAmount();
var credentialResponse = issuer.HandleRequest(credentialRequest);
client.HandleResponse(credentialResponse, validationData);

MODIFY
WalletWasabi/Crypto/Groups/Generators.cs
WalletWasabi/Crypto/Groups/Generators.cs
using NBitcoin.Secp256k1;
+using System.Collections.Generic;
using System.Linq;
using System.Text;
using WalletWasabi.Helpers;
@@ -7,8 +8,10 @@ namespace WalletWasabi.Crypto.Groups
{
public static class Generators
{
- private static GroupElement[]? _negateGh2i;
- private static Scalar[]? _powerOfTwo;
+ private static Dictionary<int, GroupElement[]> RangeProofWidthNegateGh2iPairs { get; } = new();
+ private static object RangeProofWidthNegateGh2iPairsLock { get; } = new();
+ private static Dictionary<int, Scalar[]> RangeProofWidthPowersOfTwoPairs { get; } = new();
+ private static object RangeProofWidthPowersOfTwoPairsLock { get; } = new();
/// <summary>
/// Base point defined in the secp256k1 standard used in ECDSA public key derivation.
@@ -60,28 +63,30 @@ namespace WalletWasabi.Crypto.Groups
/// </summary>
public static GroupElement Gs { get; } = FromText("Gs");
- public static GroupElement[] NegateGh2i
+ public static GroupElement[] GetNegateGh2i(int rangeProofWidth)
{
- get
+ lock (RangeProofWidthNegateGh2iPairsLock)
{
- if (_negateGh2i is null)
+ if (!RangeProofWidthNegateGh2iPairs.TryGetValue(rangeProofWidth, out var negateGh2i))
{
var negatedGh = Gh.Negate();
- _negateGh2i = PowersOfTwo.Select(b => b * negatedGh).ToArray();
+ negateGh2i = GetPowersOfTwo(rangeProofWidth).Select(b => b * negatedGh).ToArray();
+ RangeProofWidthNegateGh2iPairs.Add(rangeProofWidth, negateGh2i);
}
- return _negateGh2i;
+ return negateGh2i;
}
}
- public static Scalar[] PowersOfTwo
+ public static Scalar[] GetPowersOfTwo(int rangeProofWidth)
{
- get
+ lock (RangeProofWidthPowersOfTwoPairsLock)
{
- if (_powerOfTwo is null)
+ if (!RangeProofWidthPowersOfTwoPairs.TryGetValue(rangeProofWidth, out var powerOfTwo))
{
- _powerOfTwo = Enumerable.Range(0, Constants.RangeProofWidth).Select(i => Scalar.Zero.CAddBit((uint)i, 1)).ToArray();
+ powerOfTwo = Enumerable.Range(0, rangeProofWidth).Select(i => Scalar.Zero.CAddBit((uint)i, 1)).ToArray();
+ RangeProofWidthPowersOfTwoPairs.Add(rangeProofWidth, powerOfTwo);
}
- return _powerOfTwo;
+ return powerOfTwo;
}
}

MODIFY
WalletWasabi/Crypto/ZeroKnowledge/ProofSystem.cs
WalletWasabi/Crypto/ZeroKnowledge/ProofSystem.cs
@@ -128,15 +128,15 @@ namespace WalletWasabi.Crypto.ZeroKnowledge
// overload for bootstrap credential request proofs.
// this is just a range proof with width=0
// equivalent to proof of representation w/ Gh
- public static Knowledge ZeroProofKnowledge(GroupElement ma, Scalar r)
- => new Knowledge(ZeroProofStatement(ma), new ScalarVector(r));
+ public static Knowledge ZeroProofKnowledge(GroupElement ma, Scalar r, int rangeProofWidth)
+ => new Knowledge(ZeroProofStatement(ma, rangeProofWidth), new ScalarVector(r));
public static GroupElement PedersenCommitment(Scalar s, Scalar b)
=> new GroupElement(ECMultContext.Instance.MultBatch(new[] { s, b }, new[] { Generators.Gg.Ge, Generators.Gh.Ge }));
// TODO swap return value order, remove GroupElement argument
// expect nonce provider instead of WasabiRandom?
- public static (Knowledge knowledge, IEnumerable<GroupElement> bitCommitments) RangeProofKnowledge(Scalar a, Scalar r, int width, WasabiRandom rnd)
+ public static (Knowledge knowledge, IEnumerable<GroupElement> bitCommitments) RangeProofKnowledge(Scalar a, Scalar r, int width, WasabiRandom rnd, int rangeProofWidth)
{
var ma = PedersenCommitment(a, r);
var bits = Enumerable.Range(0, width).Select(i => a.GetBits(i, 1) == 0 ? Scalar.Zero : Scalar.One);
@@ -172,20 +172,20 @@ namespace WalletWasabi.Crypto.ZeroKnowledge
witness[ProductColumn(i)] = r_i * b_i;
}
- return (new Knowledge(RangeProofStatement(ma, bitCommitments), new ScalarVector(witness)), bitCommitments);
+ return (new Knowledge(RangeProofStatement(ma, bitCommitments, rangeProofWidth), new ScalarVector(witness)), bitCommitments);
}
// overload for bootstrap credential request proofs.
// this is just a range proof with width=0
// equivalent to new Statement(ma, Generators.Gh)
- public static Statement ZeroProofStatement(GroupElement ma)
- => RangeProofStatement(ma, Array.Empty<GroupElement>());
+ public static Statement ZeroProofStatement(GroupElement ma, int rangeProofWidth)
+ => RangeProofStatement(ma, Array.Empty<GroupElement>(), rangeProofWidth);
- public static Statement RangeProofStatement(GroupElement ma, IEnumerable<GroupElement> bitCommitments)
+ public static Statement RangeProofStatement(GroupElement ma, IEnumerable<GroupElement> bitCommitments, int rangeProofWidth)
{
var b = bitCommitments.ToArray();
var width = b.Length; // can be 0
- Guard.InRangeAndNotNull(nameof(width), width, 0, Constants.RangeProofWidth);
+ Guard.InRangeAndNotNull(nameof(width), width, 0, rangeProofWidth);
var rows = width * 2 + 1; // two equations per bit, and one for the sum
var columns = width * 3 + 1 + 1; // three witness components per bit and one for the Ma randomness, plus one for the public inputs
@@ -199,7 +199,7 @@ namespace WalletWasabi.Crypto.ZeroKnowledge
// Ma, proven by showing that the public input is a commitment to 0 (only
// Gh term required to represent it). The per-bit witness terms of this
// equation are added in the loop below.
- var powersOfTwo = Generators.PowersOfTwo.AsSpan(0, width);
+ var powersOfTwo = Generators.GetPowersOfTwo(rangeProofWidth).AsSpan(0, width);
var bitsTotal = new GroupElement(ECMultContext.Instance.MultBatch(powersOfTwo, b.Select(x => x.Ge).ToArray()));
equations[0, 0] = ma - bitsTotal;
@@ -222,7 +222,7 @@ namespace WalletWasabi.Crypto.ZeroKnowledge
for (int i = 0; i < width; i++)
{
// Add [ -r_i * 2^i * Gh ] term to first equation.
- equations[0, RndColumn(i)] = Generators.NegateGh2i[i];
+ equations[0, RndColumn(i)] = Generators.GetNegateGh2i(rangeProofWidth)[i];
// Add equation proving B is a Pedersen commitment to b:
// [ B = b*Gg + r*Gh ]

MODIFY
WalletWasabi/Helpers/Constants.cs
WalletWasabi/Helpers/Constants.cs
@@ -92,15 +92,6 @@ namespace WalletWasabi.Helpers
"/Satoshi:0.16.0/",
};
- /// <summary>
- /// This limits the max amount to 44 000.
- /// </summary>
- public static readonly int RangeProofWidth = 42;
-
- public static Money MinimumCredentailAmount = Money.Satoshis(10_000);
-
- public static Money MaximumCredentailAmount = Money.Satoshis(MaximumNumberOfSatoshis);
-
public static string ClientSupportBackendVersionText => ClientSupportBackendVersionMin == ClientSupportBackendVersionMax
? ClientSupportBackendVersionMin
: $"{ClientSupportBackendVersionMin} - {ClientSupportBackendVersionMax}";

MODIFY
WalletWasabi/WabiSabi/Backend/WabiSabiConfig.cs
WalletWasabi/WabiSabi/Backend/WabiSabiConfig.cs
@@ -48,7 +48,7 @@ namespace WalletWasabi.WabiSabi.Backend
public Money MinRegistrableAmount { get; set; } = Money.Coins(0.00005m);
/// <summary>
- /// Constants.RangeProofWidth limits this to 44000, careful!
+ /// The width of the rangeproofs are calculated from this, so don't choose stupid numbers.
/// </summary>
[DefaultValueMoneyBtc("44000")]
[JsonProperty(PropertyName = "MaxRegistrableAmount", DefaultValueHandling = DefaultValueHandling.Populate)]

MODIFY
WalletWasabi/WabiSabi/Crypto/CredentialIssuer.cs
WalletWasabi/WabiSabi/Crypto/CredentialIssuer.cs
@@ -49,14 +49,24 @@ namespace WalletWasabi.WabiSabi.Crypto
/// <param name="credentialIssuerSecretKey">The <see cref="CredentialIssuerSecretKey">coordinator's secret key</see> used to issue the credentials.</param>
/// <param name="numberOfCredentials">The number of credentials that the protocol handles in each request/response.</param>
/// <param name="randomNumberGenerator">The random number generator.</param>
- public CredentialIssuer(CredentialIssuerSecretKey credentialIssuerSecretKey, int numberOfCredentials, WasabiRandom randomNumberGenerator)
- {
+ public CredentialIssuer(
+ CredentialIssuerSecretKey credentialIssuerSecretKey,
+ int numberOfCredentials,
+ WasabiRandom randomNumberGenerator,
+ ulong maxAmount)
+ {
+ MaxAmount = maxAmount;
+ RangeProofWidth = (int)Math.Log2(MaxAmount);
CredentialIssuerSecretKey = Guard.NotNull(nameof(credentialIssuerSecretKey), credentialIssuerSecretKey);
NumberOfCredentials = Guard.InRangeAndNotNull(nameof(numberOfCredentials), numberOfCredentials, 1, 100);
CredentialIssuerParameters = CredentialIssuerSecretKey.ComputeCredentialIssuerParameters();
RandomNumberGenerator = Guard.NotNull(nameof(randomNumberGenerator), randomNumberGenerator);
}
+ public ulong MaxAmount { get; }
+
+ public int RangeProofWidth { get; }
+
// Keeps track of the used serial numbers. This is part of
// the double-spending prevention mechanism.
private HashSet<GroupElement> SerialNumbers { get; } = new HashSet<GroupElement>();
@@ -115,7 +125,7 @@ namespace WalletWasabi.WabiSabi.Crypto
}
// Check that the range proofs are of the appropriate bitwidth
- var rangeProofWidth = registrationRequest.IsNullRequest ? 0 : Constants.RangeProofWidth;
+ var rangeProofWidth = registrationRequest.IsNullRequest ? 0 : RangeProofWidth;
var allRangeProofsAreCorrectSize = requested.All(x => x.BitCommitments.Count() == rangeProofWidth);
if (!allRangeProofsAreCorrectSize)
{
@@ -152,8 +162,8 @@ namespace WalletWasabi.WabiSabi.Crypto
foreach (var credentialRequest in requested)
{
statements.Add(registrationRequest.IsNullRequest
- ? ProofSystem.ZeroProofStatement(credentialRequest.Ma)
- : ProofSystem.RangeProofStatement(credentialRequest.Ma, credentialRequest.BitCommitments));
+ ? ProofSystem.ZeroProofStatement(credentialRequest.Ma, rangeProofWidth)
+ : ProofSystem.RangeProofStatement(credentialRequest.Ma, credentialRequest.BitCommitments, rangeProofWidth));
}
// Balance proof

MODIFY
WalletWasabi/WabiSabi/Crypto/WabiSabiClient.cs
WalletWasabi/WabiSabi/Crypto/WabiSabiClient.cs
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -22,14 +23,21 @@ namespace WalletWasabi.WabiSabi.Crypto
public WabiSabiClient(
CredentialIssuerParameters credentialIssuerParameters,
int numberOfCredentials,
- WasabiRandom randomNumberGenerator)
+ WasabiRandom randomNumberGenerator,
+ ulong maxAmount)
{
+ MaxAmount = maxAmount;
+ RangeProofWidth = (int)Math.Log2(MaxAmount);
RandomNumberGenerator = Guard.NotNull(nameof(randomNumberGenerator), randomNumberGenerator);
NumberOfCredentials = Guard.InRangeAndNotNull(nameof(numberOfCredentials), numberOfCredentials, 1, 100);
CredentialIssuerParameters = Guard.NotNull(nameof(credentialIssuerParameters), credentialIssuerParameters);
Credentials = new CredentialPool();
}
+ public ulong MaxAmount { get; }
+
+ public int RangeProofWidth { get; }
+
private int NumberOfCredentials { get; }
private CredentialIssuerParameters CredentialIssuerParameters { get; }
@@ -61,7 +69,7 @@ namespace WalletWasabi.WabiSabi.Crypto
var randomness = RandomNumberGenerator.GetScalar(allowZero: false);
var ma = randomness * Generators.Gh;
- knowledge[i] = ProofSystem.ZeroProofKnowledge(ma, randomness);
+ knowledge[i] = ProofSystem.ZeroProofKnowledge(ma, randomness, RangeProofWidth);
credentialsToRequest[i] = new IssuanceRequest(ma, Enumerable.Empty<GroupElement>());
validationData[i] = new IssuanceValidationData(0, randomness, ma);
}
@@ -145,7 +153,7 @@ namespace WalletWasabi.WabiSabi.Crypto
var randomness = RandomNumberGenerator.GetScalar(allowZero: false);
var ma = ProofSystem.PedersenCommitment(scalarAmount, randomness);
- var (rangeKnowledge, bitCommitments) = ProofSystem.RangeProofKnowledge(scalarAmount, randomness, Constants.RangeProofWidth, RandomNumberGenerator);
+ var (rangeKnowledge, bitCommitments) = ProofSystem.RangeProofKnowledge(scalarAmount, randomness, RangeProofWidth, RandomNumberGenerator, RangeProofWidth);
knowledgeToProve.Add(rangeKnowledge);
var credentialRequest = new IssuanceRequest(ma, bitCommitments);

Make the width of the range proof parameterizable.