internal void CheckSolution(ScalarVector witness) { if (PublicPoint != witness * Generators) { throw new ArgumentException($"{nameof(witness)} is not solution of the equation"); } }
// Given a witness and secret nonces, respond to a challenge proving the equation holds w.r.t the witness internal ScalarVector Respond(ScalarVector witness, ScalarVector secretNonces, Scalar challenge) { // By canceling G on both sides of the verification equation above we can // obtain a formula for the response s given k, e and x: // s = k + ex return(secretNonces + challenge * witness); }
// Simulate a public nonce given a challenge and arbitrary responses (should be random) internal GroupElement Simulate(Scalar challenge, ScalarVector givenResponses) { // The verification equation above can be rearranged as a formula for R // given e, P and s by subtracting eP from both sides: // R = sG - eP return(givenResponses * Generators - challenge * PublicPoint); }
internal Proof(GroupElementVector publicNonces, ScalarVector responses) { Guard.NotNullOrInfinity(nameof(publicNonces), publicNonces); Guard.NotNullOrEmpty(nameof(responses), responses); PublicNonces = publicNonces; Responses = responses; }
// Evaluate the verification equation corresponding to the one in the statement internal bool Verify(GroupElement publicNonce, Scalar challenge, ScalarVector responses) { // the verification equation (for 1 generator case) is: // sG =? R + eP // where: // - R = kG is the public nonce, k is the secret nonce // - P = xG is the public input, x is the secret // - e is the challenge // - s is the response return(responses * Generators == (publicNonce + challenge * PublicPoint)); }
public void VerifyResponses(uint scalarSeed1, uint scalarSeed2) { var witness = new ScalarVector(new Scalar(scalarSeed1), new Scalar(scalarSeed2)); var generators = new GroupElementVector(Generators.G, Generators.Ga); var publicPoint = witness * generators; var equation = new Equation(publicPoint, generators); // First, demonstrate proving knowledge with the witness var secretNonces = new ScalarVector(new Scalar(23), new Scalar(42)); var publicNonce = Enumerable.Zip(secretNonces, generators, (s, g) => s * g).Sum(); var challenge = new Scalar(101); var response = Equation.Respond(witness, secretNonces, challenge); Assert.True(equation.Verify(publicNonce, challenge, response)); var otherChallenge = new Scalar(103); // The verifier should reject invalid transcripts. This requires // an exception for when the public input is the point at infinity, // because with a different challenge the nonce should be different // but if the secret is 0, due to the absorption property the response // will be the same with the same nonce. if (scalarSeed1 != 0 && scalarSeed2 != 0) { Assert.False(equation.Verify(publicNonce, otherChallenge, response)); } // Modifying the response should invalidate the equation var modifiedResponse = new ScalarVector(response.Select((x, i) => i == 0 ? x + Scalar.One : x)); Assert.False(equation.Verify(publicNonce, challenge, modifiedResponse)); Assert.True(equation.Verify(publicNonce + Generators.G, challenge, modifiedResponse)); // A proof made with a different witness must be rejected. If the // challenge is 0 (negligible probability with Fiat-Shamir under ROM) in // which case any witness satisfies the equation, but the verifier should // still reject. var badWitness = new ScalarVector(new Scalar(scalarSeed1 + 1), new Scalar(scalarSeed2)); Assert.False(equation.Verify(publicNonce, challenge, Equation.Respond(badWitness, secretNonces, challenge))); Assert.False(equation.Verify(publicNonce, Scalar.Zero, Equation.Respond(badWitness, secretNonces, Scalar.Zero))); // A proof made with different secret nonces must also be rejected, unless // the public nonce is tweaked (not possible with Fiat-Shamir without a // 2nd pre-image attack on the hash function). var modifiedSecretNonces = new ScalarVector(secretNonces.Select((x, i) => i == 0 ? x + Scalar.One : x)); var modifiedNonceResponse = Equation.Respond(witness, modifiedSecretNonces, challenge); Assert.False(equation.Verify(publicNonce, challenge, modifiedNonceResponse)); Assert.True(equation.Verify(publicNonce + Generators.G, challenge, modifiedNonceResponse)); }
public void VerifyResponsesAndSimulations(uint scalarSeed1, uint scalarSeed2) { var witness = new ScalarVector(new Scalar(scalarSeed1), new Scalar(scalarSeed2)); var generators = new GroupElementVector(Generators.G, Generators.Ga); var publicPoint = witness * generators; var equation = new Equation(publicPoint, generators); // First, demonstrate proving knowledge with the witness var secretNonces = new ScalarVector(new Scalar(23), new Scalar(42)); var publicNonce = Enumerable.Zip(secretNonces, generators, (s, g) => s * g).Sum(); var challenge = new Scalar(101); var response = equation.Respond(witness, secretNonces, challenge); Assert.True(equation.Verify(publicNonce, challenge, response)); // Even without a witness, simulated proofs with the same response should still verify var simulatedNonce = equation.Simulate(challenge, response); Assert.True(equation.Verify(simulatedNonce, challenge, response)); // And the simulated prover commitment should be the same as the real one // even if its discrete log w.r.t. the generators is not known Assert.True(simulatedNonce == publicNonce); // With a different challenge the nonce should be different // unless the secret is 0, due to the absorption property var otherChallenge = new Scalar(103); var otherSimulatedNonce = equation.Simulate(otherChallenge, response); Assert.True(equation.Verify(otherSimulatedNonce, otherChallenge, response)); if (scalarSeed1 != 0 && scalarSeed2 != 0) { Assert.True(otherSimulatedNonce != publicNonce); } // And with a different response the verifier should still accept var otherResponse = new ScalarVector(new Scalar(2), new Scalar(3)); var thirdSimulatedNonce = equation.Simulate(challenge, otherResponse); Assert.True(equation.Verify(thirdSimulatedNonce, challenge, otherResponse)); Assert.True(thirdSimulatedNonce != otherSimulatedNonce); Assert.True(thirdSimulatedNonce != publicNonce); // The verifying should reject invalid transcripts, and this also requires // an exception for when the public input is the point at infinity if (scalarSeed1 != 0 && scalarSeed2 != 0) { Assert.False(equation.Verify(simulatedNonce, otherChallenge, response)); Assert.False(equation.Verify(publicNonce, otherChallenge, response)); } }
internal Knowledge(Statement statement, ScalarVector witness) { Guard.True(nameof(witness), witness.Count() == statement.Equations.First().Generators.Count(), $"{nameof(witness)} size does not match {nameof(statement)}.{nameof(statement.Equations)}"); // don't try to prove something which isn't true foreach (var equation in statement.Equations) { equation.CheckSolution(witness); } Statement = statement; Witness = witness; }
public void End2EndVerificationSimple(uint scalarSeed1, uint scalarSeed2) { var secrets = new ScalarVector(new Scalar(scalarSeed1), new Scalar(scalarSeed2)); var generators = new GroupElementVector(Generators.G, Generators.Ga); var publicPoint = secrets * generators; var statement = new Statement(publicPoint, generators); var mockRandom = new Mock <WasabiRandom>(MockBehavior.Strict); mockRandom.Setup(rnd => rnd.GetBytes(32)).Returns(new byte[32]); var proof = ProofSystemHelpers.Prove(statement, secrets, mockRandom.Object); Assert.True(ProofSystemHelpers.Verify(statement, proof)); }
public void End2EndVerificationSimple(uint scalarSeed1, uint scalarSeed2) { var secrets = new ScalarVector(new Scalar(scalarSeed1), new Scalar(scalarSeed2)); var generators = new GroupElementVector(Generators.G, Generators.Ga); var publicPoint = secrets * generators; var statement = new Statement(publicPoint, generators); var random = new MockRandom(); random.GetBytesResults.Add(new byte[32]); var proof = ProofSystemHelpers.Prove(statement, secrets, random); Assert.True(ProofSystemHelpers.Verify(statement, proof)); }
public Knowledge(Statement statement, ScalarVector witness) { Guard.NotNull(nameof(statement), statement); Guard.NotNullOrEmpty(nameof(witness), witness); Guard.True($"{nameof(witness)} size does not match {nameof(statement)}.{nameof(statement.Equations)}", witness.Count() == statement.Equations.First().Generators.Count()); // don't try to prove something which isn't true foreach (var equation in statement.Equations) { Guard.True($"{nameof(witness)} is not solution of the {nameof(equation)}", equation.VerifySolution(witness)); } Statement = statement; Witness = witness; }
// Given a witness and secret nonces, respond to a challenge proving the equation holds w.r.t the witness internal static ScalarVector Respond(ScalarVector witness, ScalarVector secretNonces, Scalar challenge) { // blinding terms are required in order to protect the witness (unless the // challenge is 0), so only respond if that is the case foreach (var secretNonce in secretNonces) { Guard.NotZero(nameof(secretNonce), secretNonce); } // Taking the discrete logarithms of both sides of the verification // equation with respect to G results in a formula for the response s // given k, e and x: // s = k + ex return(secretNonces + challenge * witness); }
// Evaluate the verification equation corresponding to the one in the statement internal bool Verify(GroupElement publicNonce, Scalar challenge, ScalarVector responses) { // A challenge of 0 does not place any constraint on the witness if (challenge.IsZero) { return(false); } // the verification equation (for 1 generator case) is: // sG =? R + eP // where: // - R = kG is the public nonce, k is the secret nonce // - P = xG is the public input, x is the secret // - e is the challenge // - s is the response return(responses * Generators == (publicNonce + challenge * PublicPoint)); }
public void End2EndVerification() { var goodScalars = CryptoHelpers.GetScalars(x => !x.IsOverflow && !x.IsZero); foreach (var secret1 in goodScalars) { foreach (var secret2 in goodScalars.Where(x => x != secret1)) { var secrets = new ScalarVector(secret1, secret2); var generators = new GroupElementVector(Generators.G, Generators.Ga); var publicPoint = secrets * generators; var statement = new Statement(publicPoint, generators); var proof = ProofSystemHelpers.Prove(statement, secrets, new SecureRandom()); Assert.True(ProofSystemHelpers.Verify(statement, proof)); } } }
public void CanProveAndVerifyMAC() { // The coordinator generates a composed private key called CoordinatorSecretKey // and derives from that the coordinator's public parameters called CoordinatorParameters. 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.G + 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); var coordinatorKnowledge = ProofSystem.IssuerParameters(mac, Ma, coordinatorKey); var proofOfMac = ProofSystem.Prove(coordinatorKnowledge, rnd); // The client receives the MAC and the proofOfMac which let the client know that the MAC // was generated with the coordinator's secret key. var clientStatement = ProofSystem.IssuerParameters(coordinatorParameters, mac, Ma); var isValidProof = ProofSystem.Verify(clientStatement, proofOfMac); Assert.True(isValidProof); var corruptedResponses = new ScalarVector(proofOfMac.Responses.Reverse()); var invalidProofOfMac = new Proof(proofOfMac.PublicNonces, corruptedResponses); isValidProof = ProofSystem.Verify(clientStatement, invalidProofOfMac); Assert.False(isValidProof); var corruptedPublicNonces = new GroupElementVector(proofOfMac.PublicNonces.Reverse()); invalidProofOfMac = new Proof(corruptedPublicNonces, proofOfMac.Responses); isValidProof = ProofSystem.Verify(clientStatement, invalidProofOfMac); Assert.False(isValidProof); }
public static IEnumerable <Proof> Prove(Transcript transcript, IEnumerable <Knowledge> knowledge, WasabiRandom random) { // Before anything else all components in a compound proof commit to the // individual sub-statement that will be proven, ensuring that the // challenges and therefore the responses depend on the statement as a // whole. foreach (var k in knowledge) { transcript.CommitStatement(k.Statement); } var deferredResponds = new List <DeferredProofCreator>(); foreach (var k in knowledge) { // With all the statements committed, generate a vector of random secret // nonces for every equation in underlying proof system. In order to // ensure that nonces are never reused (e.g. due to an insecure RNG) with // different challenges which would leak the witness, these are generated // as synthetic nonces that also depend on the witness data. var secretNonceProvider = transcript.CreateSyntheticSecretNonceProvider(k.Witness, random); ScalarVector secretNonces = secretNonceProvider.GetScalarVector(); // The prover then commits to these, adding the corresponding public // points to the transcript. var equations = k.Statement.Equations; var publicNonces = new GroupElementVector(equations.Select(equation => secretNonces * equation.Generators)); transcript.CommitPublicNonces(publicNonces); deferredResponds.Add((challenge) => new Proof(publicNonces, k.RespondToChallenge(challenge, secretNonces))); } // With the public nonces committed to the transcript the prover can then // derive a challenge that depend on the transcript state without needing // to interact with the verifier, but ensuring that they can't know the // challenge before the prover commitments are generated. Scalar challenge = transcript.GenerateChallenge(); return(deferredResponds.Select(createProof => createProof(challenge))); }
public void IgnoredWitnessComponents() { // Sometimes an equation uses the point at infinity as a generator, // effectively canceling out the corresponding component of the witness var generators = new GroupElementVector(Generators.G, GroupElement.Infinity); var publicPoint = new Scalar(42) * Generators.G; var equation = new Equation(publicPoint, generators); var witness1 = new ScalarVector(new Scalar(42), new Scalar(23)); var witness2 = new ScalarVector(new Scalar(42), new Scalar(100)); // Generate a single nonce to be shared by both proofs. // note that in normal circumstances this is catastrophic because nonce // reuse with different challenges allows recovery of the witness. // in this case this is intentional, so that the test can compare the // responses which would otherwise be different. var secretNonces = new ScalarVector(new Scalar(7), new Scalar(11)); var publicNonce = Enumerable.Zip(secretNonces, generators, (s, g) => s * g).Sum(); var challenge = new Scalar(13); // Derive two responses with the two different witnesses for the same // point, and ensure that both are valid, implying that the second // component in the witness is ignored. var response1 = Equation.Respond(witness1, secretNonces, challenge); Assert.True(equation.Verify(publicNonce, challenge, response1)); var response2 = Equation.Respond(witness2, secretNonces, challenge); Assert.True(equation.Verify(publicNonce, challenge, response2)); // With different witnesses the responses should be different even if the // nonces are the same, but since the first part of the witness is the // same that sub-response should be the same for the same nonce Assert.False(response1 == response2); Assert.True(response1.First() == response2.First()); }
public void StatementAndKnowledge() { var x = new Scalar(42); var a = x * Generators.Gg; var b = x * Generators.Gh; // Discrete log equality (Chaum-Pedersen proof) var statement = new Statement(new GroupElement[, ] { { a, Generators.Gg }, { b, Generators.Gh }, }); var challenge = new Scalar(13); // Create transcripts using a witness to the relation var knowledge = new Knowledge(statement, new ScalarVector(x)); var secretNonces = new ScalarVector(new Scalar(7)); var publicNonces = new GroupElementVector(statement.Equations.Select(equation => secretNonces * equation.Generators)); var responses = knowledge.RespondToChallenge(challenge, secretNonces); Assert.Single(responses); Assert.True(statement.CheckVerificationEquation(publicNonces, challenge, responses)); // Ensure that verifier rejects invalid transcripts Assert.False(statement.CheckVerificationEquation(publicNonces, new Scalar(17), responses)); // Ensure that verifier rejects when proofs have been altered but not // when they are valid (which are prevented using Fiat-Shamir transform) var modifiedNonces = new GroupElementVector(publicNonces.First() + Generators.Gg, publicNonces.Last() + Generators.Gh); var modifiedResponses = new ScalarVector(responses.Select(x => x + Scalar.One)); Assert.True(statement.CheckVerificationEquation(modifiedNonces, challenge, modifiedResponses)); Assert.False(statement.CheckVerificationEquation(modifiedNonces, challenge, responses)); Assert.False(statement.CheckVerificationEquation(publicNonces, challenge, modifiedResponses)); }
internal ScalarVector RespondToChallenge(Scalar challenge, ScalarVector secretNonces) => Equation.Respond(Witness, secretNonces, challenge);
internal bool VerifySolution(ScalarVector witness) { return(PublicPoint == witness * Generators); }
public void FiatShamirComposition() { var rnd = new MockRandom(); rnd.GetBytesResults.Add(new byte[32]); rnd.GetBytesResults.Add(new byte[32]); var witness1 = new ScalarVector(Scalar.One); var witness2 = new ScalarVector(Scalar.One + Scalar.One); var g = new GroupElementVector(Generators.G); var publicPoint1 = witness1 * g; var publicPoint2 = witness2 * g; var statement1 = new Statement(new Equation(publicPoint1, g)); var statement2 = new Statement(new Equation(publicPoint2, g)); var prover1 = new Prover(new Knowledge(statement1, witness1)); var prover2 = new Prover(new Knowledge(statement2, witness2)); var proverTranscript = new WalletWasabi.Crypto.ZeroKnowledge.Transcript(new byte[0]); var verifierTranscript = proverTranscript.MakeCopy(); var prover1Nonces = prover1.CommitToStatements(proverTranscript); var prover2Nonces = prover2.CommitToStatements(proverTranscript); var prover1Respond = prover1Nonces(rnd); var prover2Respond = prover2Nonces(rnd); var proof1 = prover1Respond(); var proof2 = prover2Respond(); var verifier1 = new Verifier(statement1); var verifier2 = new Verifier(statement2); // First, verify as a compound proof var correctVerifierTranscript = verifierTranscript.MakeCopy(); var correctVerifier1Nonces = verifier1.CommitToStatements(correctVerifierTranscript); var correctVerifier2Nonces = verifier2.CommitToStatements(correctVerifierTranscript); var correctVerifier1Verify = correctVerifier1Nonces(proof1); var correctVerifier2Verify = correctVerifier2Nonces(proof2); Assert.True(correctVerifier1Verify()); Assert.True(correctVerifier2Verify()); // If the verifiers are not run interleaved, they should reject. var notInterleavedVerifierTranscript = verifierTranscript.MakeCopy(); var notInterleavedVerifier1Nonces = verifier1.CommitToStatements(correctVerifierTranscript); var notInterleavedVerifier1Verify = notInterleavedVerifier1Nonces(proof1); Assert.False(notInterleavedVerifier1Verify()); var notInterleavedVerifier2Nonces = verifier2.CommitToStatements(correctVerifierTranscript); var notInterleavedVerifier2Verify = notInterleavedVerifier2Nonces(proof2); Assert.False(notInterleavedVerifier2Verify()); // If the verifiers are run independently (without sharing a transcript), // they should reject. var incompleteTranscript1 = verifierTranscript.MakeCopy(); var incompleteTranscript2 = verifierTranscript.MakeCopy(); var incompleteTranscriptVerifier1Nonces = verifier1.CommitToStatements(incompleteTranscript1); var incompleteTranscriptVerifier2Nonces = verifier2.CommitToStatements(incompleteTranscript2); var incompleteTranscriptVerifier1Verify = incompleteTranscriptVerifier1Nonces(proof1); var incompleteTranscriptVerifier2Verify = incompleteTranscriptVerifier2Nonces(proof2); Assert.False(incompleteTranscriptVerifier1Verify()); Assert.False(incompleteTranscriptVerifier2Verify()); // If the sub-proofs are swapped between the verifiers, they should reject. var incorrectProofVerifierTranscript = verifierTranscript.MakeCopy(); var incorrectProofVerifier1Nonces = verifier1.CommitToStatements(correctVerifierTranscript); var incorrectProofVerifier2Nonces = verifier2.CommitToStatements(correctVerifierTranscript); var incorrectProofVerifier1Verify = incorrectProofVerifier1Nonces(proof2); var incorrectProofVerifier2Verify = incorrectProofVerifier2Nonces(proof1); Assert.False(incorrectProofVerifier1Verify()); Assert.False(incorrectProofVerifier2Verify()); // If the order of the verifiers is changed, they should reject. var incorrectOrderVerifierTranscript = verifierTranscript.MakeCopy(); var incorrectOrderVerifier1Nonces = verifier1.CommitToStatements(correctVerifierTranscript); var incorrectOrderVerifier2Nonces = verifier2.CommitToStatements(correctVerifierTranscript); var incorrectOrderVerifier2Verify = incorrectOrderVerifier2Nonces(proof2); var incorrectOrderVerifier1Verify = incorrectOrderVerifier1Nonces(proof1); Assert.False(incorrectOrderVerifier1Verify()); Assert.False(incorrectOrderVerifier2Verify()); // If the proofs are committed to the transcript in the right order but // with the wrong verifier (combination of previous two cases) they should // reject. var incorrectOrderAndProofVerifierTranscript = verifierTranscript.MakeCopy(); var incorrectOrderAndProofVerifier1Nonces = verifier1.CommitToStatements(correctVerifierTranscript); var incorrectOrderAndProofVerifier2Nonces = verifier2.CommitToStatements(correctVerifierTranscript); var incorrectOrderAndProofVerifier2Verify = incorrectOrderAndProofVerifier2Nonces(proof1); var incorrectOrderAndProofVerifier1Verify = incorrectOrderAndProofVerifier1Nonces(proof2); Assert.False(incorrectOrderAndProofVerifier2Verify()); Assert.False(incorrectOrderAndProofVerifier1Verify()); }
internal bool VerifySolution(ScalarVector witness) => PublicPoint == witness * Generators;
public static Proof Prove(Statement statement, ScalarVector witness, WasabiRandom random) { return(Prove(new Knowledge(statement, witness), random)); }
public bool CheckVerificationEquation(GroupElementVector publicNonces, Scalar challenge, ScalarVector responses) { // The responses matrix should match the generators in the equations and // there should be once nonce per equation. Guard.True(nameof(publicNonces), Equations.Count() == publicNonces.Count()); return(Equations.Zip(publicNonces, (equation, r) => equation.Verify(r, challenge, responses)).All(x => x)); }
public Knowledge ToKnowledge(ScalarVector witness) => new Knowledge(this, witness);