示例#1
0
        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));
            }
        }
示例#2
0
    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));
    }
示例#3
0
        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));
        }
示例#7
0
        /// <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());
        }
示例#8
0
        /// <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));
        }
示例#9
0
        /// <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);
        }
示例#10
0
        /// <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)));
        }
示例#11
0
 public static Proof Prove(Knowledge knowledge, WasabiRandom random)
 {
     return(ProofSystem.Prove(new Transcript(Array.Empty <byte>()), new[] { knowledge }, random).First());
 }
示例#12
0
 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 }));
 }