public void CanProveAndVerifyCommitmentRange(ulong amount, int width, bool pass) { using var rnd = new SecureRandom(); var amountScalar = new Scalar(amount); var randomness = rnd.GetScalar(); 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 rangeProof = ProofSystemHelpers.Prove(knowledge, rnd); Assert.Equal(pass, ProofSystemHelpers.Verify(ProofSystem.RangeProofStatement(commitment, bitCommitments), rangeProof)); if (!pass) { Assert.Throws <ArgumentException>(() => ProofSystem.RangeProofKnowledge(amountScalar, randomness, width, rnd)); } }
public void CanProveAndVerifyPresentedBalance() { var a = new Scalar(10_000u); var r = Rnd.GetScalar(); var z = Rnd.GetScalar(); var ca = z * Generators.Ga + a * Generators.Gg + r * Generators.Gh; var knowledge = ProofSystem.BalanceProofKnowledge(z, r); var proofOfBalance = ProofSystemHelpers.Prove(knowledge, Rnd); var statement = ProofSystem.BalanceProofStatement(ca - a * Generators.Gg); Assert.True(ProofSystemHelpers.Verify(statement, proofOfBalance)); var badStatement = ProofSystem.BalanceProofStatement(ca + Generators.Gg - a * Generators.Gg); Assert.False(ProofSystemHelpers.Verify(badStatement, proofOfBalance)); badStatement = ProofSystem.BalanceProofStatement(ca); Assert.False(ProofSystemHelpers.Verify(badStatement, proofOfBalance)); }
public void CanProveAndVerifyCommitmentRange(ulong amount, int width, bool pass) { using var rnd = new SecureRandom(); var amountScalar = new Scalar(amount); var randomness = rnd.GetScalar(); var commitment = amountScalar * Generators.Gg + randomness * Generators.Gh; // First, generate a proof for the given statement. This proof may // be invalid (verification equation fails to hold) if the statement // is in fact wrong, in which case the verifier should reject. var(knowledge, bitCommitments) = ProofSystem.RangeProofKnowledge(amountScalar, randomness, width, rnd); var rangeProof = ProofSystemHelpers.Prove(knowledge, rnd); Assert.Equal(pass, ProofSystemHelpers.Verify(ProofSystem.RangeProofStatement(commitment, bitCommitments, width), rangeProof)); if (!pass) { Assert.Throws <ArgumentException>(() => knowledge.AssertSoundness()); // When the statement is unprovable, modify the secret input by // clearing the high bits to make sure that the proof is always // formally valid, but when the original statement is false this // will be a valid proof of a different statement. The verifier // should still reject. var maskedScalar = new Scalar(amount & ((1ul << width) - 1)); var(knowledgeOfSomethingElse, incompleteBitCommitments) = ProofSystem.RangeProofKnowledge(maskedScalar, randomness, width, rnd); var incorrectRangeProof = ProofSystemHelpers.Prove(knowledgeOfSomethingElse, rnd); Assert.False(ProofSystemHelpers.Verify(ProofSystem.RangeProofStatement(commitment, incompleteBitCommitments, width), incorrectRangeProof)); // For completeness, make sure other corrupted statements are also rejected Assert.False(ProofSystemHelpers.Verify(ProofSystem.RangeProofStatement(commitment, bitCommitments, width), incorrectRangeProof)); Assert.False(ProofSystemHelpers.Verify(ProofSystem.RangeProofStatement(commitment, incompleteBitCommitments, width), rangeProof)); } }
public void CanProveAndVerifyPresentedBalance() { var rnd = new SecureRandom(); var a = new Scalar(10_000u); var r = rnd.GetScalar(); var z = rnd.GetScalar(); var Ca = z * Generators.Ga + a * Generators.Gg + r * Generators.Gh; var knowledge = ProofSystem.BalanceProof(z, r); var proofOfBalance = ProofSystem.Prove(knowledge, rnd); var statement = ProofSystem.BalanceProof(Ca - a * Generators.Gg); Assert.True(ProofSystem.Verify(statement, proofOfBalance)); var badStatement = ProofSystem.BalanceProof(Ca + Generators.Gg - a * Generators.Gg); Assert.False(ProofSystem.Verify(badStatement, proofOfBalance)); badStatement = ProofSystem.BalanceProof(Ca); Assert.False(ProofSystem.Verify(badStatement, proofOfBalance)); }
public void CanProveAndVerifyMacShow() { var rnd = new SecureRandom(); var coordinatorKey = new CoordinatorSecretKey(rnd); var coordinatorParameters = coordinatorKey.ComputeCoordinatorParameters(); // A blinded amount is known as an `attribute`. In this case the attribute Ma is the // value 10000 blinded with a random `blindingFactor`. This attribute is sent to // the coordinator. var amount = new Scalar(10_000); var r = rnd.GetScalar(); var Ma = amount * Generators.Gg + r * Generators.Gh; // The coordinator generates a MAC and a proof that the MAC was generated using the // coordinator's secret key. The coordinator sends the pair (MAC, proofOfMac) back // to the client. var t = rnd.GetScalar(); var mac = MAC.ComputeMAC(coordinatorKey, Ma, t); // The client randomizes the commitments before presenting them to the coordinator proving to // the coordinator that a credential is valid (prover knows a valid MAC on non-randomized attribute) var credential = new Credential(amount, r, mac); var z = rnd.GetScalar(); var randomizedCredential = credential.Present(z); var knowledge = ProofSystem.ShowCredential(randomizedCredential, z, credential, coordinatorParameters); var proofOfMacShow = ProofSystem.Prove(knowledge, rnd); // The coordinator must verify the received randomized credential is valid. var Z = randomizedCredential.ComputeZ(coordinatorKey); Assert.Equal(Z, z * coordinatorParameters.I); var statement = ProofSystem.ShowCredential(randomizedCredential, Z, coordinatorParameters); var isValidProof = ProofSystem.Verify(statement, proofOfMacShow); Assert.True(isValidProof); }
public void End2EndVerificationLargeScalar() { var random = new SecureRandom(); uint val = int.MaxValue; var gen = new Scalar(4) * Generators.G; var secret = new Scalar(val, val, val, val, val, val, val, val); var p = secret * gen; var statement = new Statement(p, gen); var proof = ProofSystem.Prove(statement, secret, random); Assert.True(ProofSystem.Verify(statement, proof)); secret = EC.N + Scalar.One.Negate(); p = secret * gen; statement = new Statement(p, gen); proof = ProofSystem.Prove(statement, secret, random); Assert.True(ProofSystem.Verify(statement, proof)); secret = EC.NC; p = secret * gen; statement = new Statement(p, gen); proof = ProofSystem.Prove(statement, secret, random); Assert.True(ProofSystem.Verify(statement, proof)); secret = EC.NC + Scalar.One; p = secret * gen; statement = new Statement(p, gen); proof = ProofSystem.Prove(statement, secret, random); Assert.True(ProofSystem.Verify(statement, proof)); secret = EC.NC + Scalar.One.Negate(); p = secret * gen; statement = new Statement(p, gen); proof = ProofSystem.Prove(statement, secret, random); Assert.True(ProofSystem.Verify(statement, proof)); }
/// <summary> /// Handles the registration response received from the coordinator. /// </summary> /// <remarks> /// Verifies the registration response message proofs, creates the credentials based on the issued MACs and /// finally updates the credentials pool by removing those credentials that were presented and by adding /// those that were issued. /// </remarks> /// <param name="registrationResponse">The registration response message received from the coordinator.</param> /// <param name="registrationValidationData">The state data required to validate the issued credentials and the proofs.</param> public Credential[] HandleResponse( CredentialsResponse registrationResponse, CredentialsResponseValidation registrationValidationData) { Guard.NotNull(nameof(registrationResponse), registrationResponse); Guard.NotNull(nameof(registrationValidationData), registrationValidationData); var issuedCredentialCount = registrationResponse.IssuedCredentials.Count(); var requestedCredentialCount = registrationValidationData.Requested.Count(); if (issuedCredentialCount != NumberOfCredentials) { throw new WabiSabiCryptoException( WabiSabiCryptoErrorCode.IssuedCredentialNumberMismatch, $"{issuedCredentialCount} issued but {requestedCredentialCount} were requested."); } var credentials = registrationValidationData.Requested.Zip(registrationResponse.IssuedCredentials) .Select(x => (Requested: x.First, Issued: x.Second)) .ToArray(); var statements = credentials .Select(x => ProofSystem.IssuerParametersStatement(CredentialIssuerParameters, x.Issued, x.Requested.Ma)); var areCorrectlyIssued = ProofSystem.Verify(registrationValidationData.Transcript, statements, registrationResponse.Proofs); if (!areCorrectlyIssued) { throw new WabiSabiCryptoException(WabiSabiCryptoErrorCode.ClientReceivedInvalidProofs); } var credentialReceived = credentials.Select(x => new Credential(new Scalar((ulong)x.Requested.Amount), x.Requested.Randomness, x.Issued)); return(ZeroCredentialPool.ProcessAndGetValuableCredentials(credentialReceived).ToArray()); }
/// <summary> /// Creates a <see cref="RegistrationRequestMessage">credential registration request messages</see> /// for requesting `k` non-zero-value credentials. /// </summary> /// <param name="amountsToRequest">List of amounts requested in credentials.</param> /// <param name="credentialsToPresent">List of credentials to be presented to the coordinator.</param> /// <returns> /// A tuple containing the registration request message instance and the registration validation data /// to be used to validate the coordinator response message (the issued credentials). /// </returns> public (RegistrationRequestMessage, RegistrationValidationData) CreateRequest( IEnumerable <Money> amountsToRequest, IEnumerable <Credential> credentialsToPresent) { // Make sure we request always the same number of credentials var credentialAmountsToRequest = amountsToRequest.ToList(); var missingCredentialRequests = NumberOfCredentials - amountsToRequest.Count(); for (var i = 0; i < missingCredentialRequests; i++) { credentialAmountsToRequest.Add(Money.Zero); } // Make sure we present always the same number of credentials (except for Null requests) var missingCredentialPresent = NumberOfCredentials - credentialsToPresent.Count(); var alreadyPresentedZeroCredentials = credentialsToPresent.Where(x => x.Amount.IsZero); var availableZeroCredentials = Credentials.ZeroValue.Except(alreadyPresentedZeroCredentials); // This should not be possible var availableZeroCredentialCount = availableZeroCredentials.Count(); if (availableZeroCredentialCount < missingCredentialPresent) { throw new WabiSabiException( WabiSabiErrorCode.NotEnoughZeroCredentialToFillTheRequest, $"{missingCredentialPresent} credentials are missing but there are only {availableZeroCredentialCount} zero-value credentials available."); } credentialsToPresent = credentialsToPresent.Concat(availableZeroCredentials.Take(missingCredentialPresent)).ToList(); var macsToPresent = credentialsToPresent.Select(x => x.Mac); if (macsToPresent.Distinct().Count() < macsToPresent.Count()) { throw new WabiSabiException(WabiSabiErrorCode.CredentialToPresentDuplicated); } var zs = new List <Scalar>(); var knowledgeToProve = new List <Knowledge>(); var presentations = new List <CredentialPresentation>(); foreach (var credential in credentialsToPresent) { var z = RandomNumberGenerator.GetScalar(); var presentation = credential.Present(z); presentations.Add(presentation); knowledgeToProve.Add(ProofSystem.ShowCredentialKnowledge(presentation, z, credential, CoordinatorParameters)); zs.Add(z); } // Generate RangeProofs (or ZeroProof) for each requested credential var credentialsToRequest = new IssuanceRequest[NumberOfCredentials]; var validationData = new IssuanceValidationData[NumberOfCredentials]; for (var i = 0; i < NumberOfCredentials; i++) { var amount = credentialAmountsToRequest[i]; var scalarAmount = new Scalar((ulong)amount.Satoshi); var randomness = RandomNumberGenerator.GetScalar(allowZero: false); var Ma = scalarAmount * Generators.Gg + randomness * Generators.Gh; var(rangeKnowledge, bitCommitments) = ProofSystem.RangeProofKnowledge(scalarAmount, randomness, Constants.RangeProofWidth, RandomNumberGenerator); knowledgeToProve.Add(rangeKnowledge); var credentialRequest = new IssuanceRequest(Ma, bitCommitments); credentialsToRequest[i] = credentialRequest; validationData[i] = new IssuanceValidationData(amount, randomness, Ma); } // Generate Balance Proof var sumOfZ = zs.Sum(); var cr = credentialsToPresent.Select(x => x.Randomness).Sum(); var r = validationData.Select(x => x.Randomness).Sum(); var deltaR = cr + r.Negate(); var balanceKnowledge = ProofSystem.BalanceProofKnowledge(sumOfZ, deltaR); knowledgeToProve.Add(balanceKnowledge); var transcript = BuildTransnscript(isNullRequest: false); return( new RegistrationRequestMessage( amountsToRequest.Sum() - credentialsToPresent.Sum(x => x.Amount.ToMoney()), presentations, credentialsToRequest, ProofSystem.Prove(transcript, knowledgeToProve, RandomNumberGenerator)), new RegistrationValidationData( transcript, credentialsToPresent, validationData)); }
/// <summary> /// Process the <see cref="RegistrationRequestMessage">credentials registration requests</see> and /// issues the credentials. /// </summary> /// <param name="registrationRequest">The request containing the credentials presentations, credential requests and the proofs.</param> /// <returns>The <see cref="RegistrationResponseMessage">registration response</see> containing the requested credentials and the proofs.</returns> /// <exception cref="WabiSabiException">Error code: <see cref="WabiSabiErrorCode">WabiSabiErrorCode</see></exception> public RegistrationResponseMessage HandleRequest(RegistrationRequestMessage registrationRequest) { Guard.NotNull(nameof(registrationRequest), registrationRequest); var requested = registrationRequest.Requested ?? Enumerable.Empty <IssuanceRequest>(); var presented = registrationRequest.Presented ?? Enumerable.Empty <CredentialPresentation>(); var requestedCount = requested.Count(); if (requestedCount != NumberOfCredentials) { throw new WabiSabiException( WabiSabiErrorCode.InvalidNumberOfRequestedCredentials, $"{NumberOfCredentials} credential requests were expected but {requestedCount} were received."); } var presentedCount = presented.Count(); var requiredNumberOfPresentations = registrationRequest.IsNullRequest ? 0 : NumberOfCredentials; if (presentedCount != requiredNumberOfPresentations) { throw new WabiSabiException( WabiSabiErrorCode.InvalidNumberOfPresentedCredentials, $"{requiredNumberOfPresentations} credential presentations were expected but {presentedCount} were received."); } // Don't allow balance to go negative. In case this goes below zero // then there is a problem somewhere because this should not be possible. if (Balance + registrationRequest.DeltaAmount < Money.Zero) { throw new WabiSabiException(WabiSabiErrorCode.NegativeBalance); } // Check that the range proofs are of the appropriate bitwidth var rangeProofWidth = registrationRequest.IsNullRequest ? 0 : Constants.RangeProofWidth; var allRangeProofsAreCorrectSize = requested.All(x => x.BitCommitments.Count() == rangeProofWidth); if (!allRangeProofsAreCorrectSize) { throw new WabiSabiException(WabiSabiErrorCode.InvalidBitCommitment); } // Check all the serial numbers are unique. Note that this is checked separately from // ensuring that they haven't been used before, because even presenting a previously // unused credential more than once in the same request is still a double spend. if (registrationRequest.AreThereDuplicatedSerialNumbers) { throw new WabiSabiException(WabiSabiErrorCode.SerialNumberDuplicated); } var statements = new List <Statement>(); foreach (var presentation in presented) { // Calculate Z using coordinator secret. var Z = presentation.ComputeZ(CoordinatorSecretKey); // Add the credential presentation to the statements to be verified. statements.Add(ProofSystem.ShowCredentialStmt(presentation, Z, CoordinatorParameters)); // Check if the serial numbers have been used before. Note that // the serial numbers have not yet been verified at this point, but a // request with an invalid proof and a used serial number should also be // rejected. if (SerialNumbers.Contains(presentation.S)) { throw new WabiSabiException(WabiSabiErrorCode.SerialNumberAlreadyUsed, $"Serial number reused {presentation.S}"); } } foreach (var credentialRequest in requested) { statements.Add(registrationRequest.IsNullRequest ? ProofSystem.ZeroProofStmt(credentialRequest.Ma) : ProofSystem.RangeProofStmt(credentialRequest.Ma, credentialRequest.BitCommitments)); } // Balance proof if (!registrationRequest.IsNullRequest) { var sumCa = presented.Select(x => x.Ca).Sum(); var sumMa = requested.Select(x => x.Ma).Sum(); // A positive Delta_a means the requested credential amounts are larger // than the presented ones (i.e. input registration, and a negative // balance correspond to output registration). The equation requires a // commitment to 0, so the sum of the presented attributes and the // negated requested attributes are tweaked by delta_a. var absAmountDelta = new Scalar(registrationRequest.DeltaAmount.Abs()); var deltaA = registrationRequest.DeltaAmount < Money.Zero ? absAmountDelta.Negate() : absAmountDelta; var balanceTweak = deltaA * Generators.Gg; statements.Add(ProofSystem.BalanceProofStmt(balanceTweak + sumCa - sumMa)); } var transcript = BuildTransnscript(registrationRequest.IsNullRequest); // Verify all statements. var areProofsValid = ProofSystem.Verify(transcript, statements, registrationRequest.Proofs); if (!areProofsValid) { throw new WabiSabiException(WabiSabiErrorCode.CoordinatorReceivedInvalidProofs); } // Issue credentials. var credentials = requested.Select(x => IssueCredential(x.Ma, RandomNumberGenerator.GetScalar())).ToArray(); // Construct response. var proofs = ProofSystem.Prove(transcript, credentials.Select(x => x.Knowledge), RandomNumberGenerator); var macs = credentials.Select(x => x.Mac); var response = new RegistrationResponseMessage(macs, proofs); // Register the serial numbers to prevent credential reuse. foreach (var presentation in presented) { SerialNumbers.Add(presentation.S); } Balance += registrationRequest.DeltaAmount; return(response); }
/// <summary> /// Creates a <see cref="RealCredentialsRequest">credential registration request messages</see> /// for requesting `k` non-zero-value credentials. /// </summary> /// <param name="amountsToRequest">List of amounts requested in credentials.</param> /// <param name="credentialsToPresent">List of credentials to be presented to the coordinator.</param> /// <param name="cancellationToken">The cancellation token to be used in case shut down is in progress..</param> /// <returns> /// A tuple containing the registration request message instance and the registration validation data /// to be used to validate the coordinator response message (the issued credentials). /// </returns> private RealCredentialsRequestData InternalCreateRequest( IEnumerable <long> amountsToRequest, IEnumerable <Credential> credentialsToPresent, CancellationToken cancellationToken) { // Make sure we request always the same number of credentials var credentialAmountsToRequest = amountsToRequest.ToList(); var macsToPresent = credentialsToPresent.Select(x => x.Mac); if (macsToPresent.Distinct().Count() < macsToPresent.Count()) { throw new WabiSabiCryptoException(WabiSabiCryptoErrorCode.CredentialToPresentDuplicated); } var zs = new List <Scalar>(); var knowledgeToProve = new List <Knowledge>(); var presentations = new List <CredentialPresentation>(); foreach (var credential in credentialsToPresent) { var z = RandomNumberGenerator.GetScalar(); var presentation = credential.Present(z); presentations.Add(presentation); knowledgeToProve.Add(ProofSystem.ShowCredentialKnowledge(presentation, z, credential, CredentialIssuerParameters)); zs.Add(z); } // Generate RangeProofs (or ZeroProof) for each requested credential var expectedNumberOfCredentials = credentialAmountsToRequest.Count; var credentialsToRequest = new IssuanceRequest[expectedNumberOfCredentials]; var validationData = new IssuanceValidationData[expectedNumberOfCredentials]; for (var i = 0; i < expectedNumberOfCredentials; i++) { var value = credentialAmountsToRequest[i]; var scalar = new Scalar((ulong)value); var randomness = RandomNumberGenerator.GetScalar(allowZero: false); var ma = ProofSystem.PedersenCommitment(scalar, randomness); var(rangeKnowledge, bitCommitments) = ProofSystem.RangeProofKnowledge(scalar, randomness, RangeProofWidth, RandomNumberGenerator); knowledgeToProve.Add(rangeKnowledge); var credentialRequest = new IssuanceRequest(ma, bitCommitments); credentialsToRequest[i] = credentialRequest; validationData[i] = new IssuanceValidationData(value, randomness, ma); } // Generate Balance Proof var sumOfZ = zs.Sum(); var cr = credentialsToPresent.Select(x => x.Randomness).Sum(); var r = validationData.Select(x => x.Randomness).Sum(); var deltaR = cr + r.Negate(); var balanceKnowledge = ProofSystem.BalanceProofKnowledge(sumOfZ, deltaR); knowledgeToProve.Add(balanceKnowledge); var transcript = BuildTransnscript(isNullRequest: false); return(new( new RealCredentialsRequest( amountsToRequest.Sum() - credentialsToPresent.Sum(x => x.Value), presentations, credentialsToRequest, ProofSystem.Prove(transcript, knowledgeToProve, RandomNumberGenerator)), new CredentialsResponseValidation( transcript, credentialsToPresent, validationData))); }
public static Proof Prove(Knowledge knowledge, WasabiRandom random) { return(ProofSystem.Prove(new Transcript(Array.Empty <byte>()), new[] { knowledge }, random).First()); }
public static bool Verify(Statement statement, Proof proof) { return(ProofSystem.Verify(new Transcript(Array.Empty <byte>()), new[] { statement }, new[] { proof })); }
public static bool Verify(Statement statement, Proof proof) { return(ProofSystem.Verify(new Transcript(new byte[0]), new[] { statement }, new[] { proof })); }