/// <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); }