Port unit tests
ADD
WalletWasabi.Tests/UnitTests/Crypto/TestTranscript.cs
+using NBitcoin.Secp256k1;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using WalletWasabi.Crypto.Groups;
+using WalletWasabi.Crypto.StrobeProtocol;
+
+namespace WalletWasabi.Tests.UnitTests.Crypto
+{
+ public class TestTranscript
+ {
+ private Strobe128 _strobe;
+
+ public TestTranscript(byte[] label)
+ {
+ _strobe = new Strobe128("WabiSabi_v1.0");
+
+ var metadata = Enumerable.Concat(Encoding.UTF8.GetBytes("domain-separator"), BitConverter.GetBytes(label.Length)).ToArray();
+ _strobe.AddAssociatedMetaData(metadata, false);
+ _strobe.AddAssociatedData(label, false);
+ }
+
+ public void CommitPublicNonces(IEnumerable<GroupElement> nonces)
+ {
+ var nonceArray = nonces.ToArray();
+
+ var label = Encoding.UTF8.GetBytes("nonce-commitment");
+ var metadata = Enumerable.Concat(label, BitConverter.GetBytes(nonceArray.Length)).ToArray();
+ _strobe.AddAssociatedMetaData(metadata, false);
+
+ for( var i = 0; i < nonceArray.Length; i++)
+ {
+ metadata = Enumerable.Concat(BitConverter.GetBytes(i), BitConverter.GetBytes(33)).ToArray();
+ _strobe.AddAssociatedMetaData(metadata, false);
+ _strobe.AddAssociatedData(nonceArray[i].ToBytes(), false);
+ }
+ }
+
+ public Scalar GenerateChallenge()
+ {
+ _strobe.AddAssociatedMetaData(Encoding.UTF8.GetBytes("challenge"), false);
+ return new Scalar(_strobe.Prf(32, false));
+ }
+ }
+}
\ No newline at end of file

ADD
WalletWasabi.Tests/UnitTests/Crypto/TranscriptTests.cs
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NBitcoin.Secp256k1;
+using WalletWasabi.Crypto.Groups;
+using WalletWasabi.Crypto.Randomness;
+using WalletWasabi.Crypto.ZeroKnowledge;
+using Xunit;
+
+namespace WalletWasabi.Tests.UnitTests.Crypto
+{
+ public class TranscriptTests
+ {
+ [Fact]
+ public void SimpleEquivalenceTest()
+ {
+ var protocol = Encoding.UTF8.GetBytes("test protocol");
+ var realTranscript = new Transcript(protocol);
+ var testTranscript = new TestTranscript(protocol);
+
+ realTranscript.CommitPublicNonces(new[] { Generators.G });
+ testTranscript.CommitPublicNonces(new[] { Generators.G });
+
+ var realChallenge = realTranscript.GenerateChallenge();
+ var testChallenge = testTranscript.GenerateChallenge();
+
+ Assert.Equal(realChallenge, testChallenge);
+ }
+
+ [Fact]
+ public void ComplexEquivalenceTest()
+ {
+ var protocol = Encoding.UTF8.GetBytes("test protocol");
+ var realTranscript = new Transcript(protocol);
+ var testTranscript = new TestTranscript(protocol);
+
+ realTranscript.CommitPublicNonces(new[] { Generators.G });
+ testTranscript.CommitPublicNonces(new[] { Generators.G });
+
+ for(var i = 0; i < 32; i++)
+ {
+ var realChallenge = realTranscript.GenerateChallenge();
+ var testChallenge = testTranscript.GenerateChallenge();
+
+ Assert.Equal(realChallenge, testChallenge);
+
+ realTranscript.CommitPublicNonces(new[] { Generators.G, Generators.GV });
+ testTranscript.CommitPublicNonces(new[] { Generators.G, Generators.GV });
+ }
+ }
+
+ [Fact]
+ public void SyntheticNoncesTest()
+ {
+ var protocol = Encoding.UTF8.GetBytes("test TranscriptRng collisions");
+ var rnd = new SecureRandom();
+
+ var commitment1 = new[] { Generators.Gx0 };
+ var commitment2 = new[] { Generators.Gx1 };
+ var witness1 = new[] { rnd.GetScalar() };
+ var witness2 = new[] { rnd.GetScalar() };
+
+ var transcript1 = new Transcript(protocol);
+ var transcript2 = new Transcript(protocol);
+ var transcript3 = new Transcript(protocol);
+ var transcript4 = new Transcript(protocol);
+
+ transcript1.CommitPublicNonces(commitment1);
+ transcript2.CommitPublicNonces(commitment2);
+ transcript3.CommitPublicNonces(commitment2);
+ transcript4.CommitPublicNonces(commitment2);
+
+ var nonceGenerator1 = transcript1.CreateSyntheticNocesProvider(witness1, rnd);
+ var nonceGenerator2 = transcript2.CreateSyntheticNocesProvider(witness1, rnd);
+ var nonceGenerator3 = transcript3.CreateSyntheticNocesProvider(witness2, rnd);
+ var nonceGenerator4 = transcript4.CreateSyntheticNocesProvider(witness2, rnd);
+
+ var nonce1 = nonceGenerator1().First();
+ var nonce2 = nonceGenerator2().First();
+ var nonce3 = nonceGenerator3().First();
+ var nonce4 = nonceGenerator4().First();
+
+ Assert.NotEqual(nonce1, nonce2);
+ Assert.NotEqual(nonce1, nonce3);
+ Assert.NotEqual(nonce1, nonce4);
+
+ Assert.NotEqual(nonce2, nonce3);
+ Assert.NotEqual(nonce2, nonce4);
+
+ Assert.NotEqual(nonce3, nonce4);
+ }
+ }
+}
\ No newline at end of file

MODIFY
WalletWasabi/Crypto/StrobeProtocol/Strobe.cs
WalletWasabi/Crypto/StrobeProtocol/Strobe.cs
@@ -133,6 +133,7 @@ namespace WalletWasabi.Crypto.StrobeProtocol
throw new InvalidOperationException($"Attempt to continue operation '{_currentFlags}' with new flags '{flags}' is not allowed.");
}
_currentFlags = flags;
+ return;
}
// Skip adjusting direction information (we just use AD, PRF)

MODIFY
WalletWasabi/Crypto/Transcript.cs
WalletWasabi/Crypto/Transcript.cs
@@ -15,9 +15,10 @@ namespace WalletWasabi.Crypto.ZeroKnowledge
// implements synthetic nonces and Fiat-Shamir challenges.
public sealed class Transcript
{
+ private const int KeySizeInBytes = 32;
+
private Strobe128 _strobe;
- private const int KeySizeInBytes = 32;
private static readonly byte[] StatementTag = Encoding.UTF8.GetBytes("statement");
private static readonly byte[] ChallengeTag = Encoding.UTF8.GetBytes("challenge");
private static readonly byte[] NonceTag = Encoding.UTF8.GetBytes("nonce-commitment");
@@ -50,9 +51,8 @@ namespace WalletWasabi.Crypto.ZeroKnowledge
public Transcript MakeCopy() =>
new Transcript(_strobe.MakeCopy());
-
// Generate synthetic nonce using current state combined with additional randomness.
- public /*Func<IEnumerable<Scalar>>*/ NoncesSequence CreateSyntheticNocesProvider(IEnumerable<Scalar> secrets, WasabiRandom random)
+ public NoncesSequence CreateSyntheticNocesProvider(IEnumerable<Scalar> secrets, WasabiRandom random)
{
// To integrate prior inputs for deterministic component of nonce
// generation, first clone the state at the current point in the
@@ -75,7 +75,7 @@ namespace WalletWasabi.Crypto.ZeroKnowledge
{
yield return new Scalar(forked.Prf(KeySizeInBytes, false));
}
- };
+ }
// Generate a new scalar for each secret using this updated state as a seed.
return NoncesGenerator;
@@ -91,7 +91,6 @@ namespace WalletWasabi.Crypto.ZeroKnowledge
public Scalar GenerateChallenge()
{
_strobe.AddAssociatedMetaData(ChallengeTag, false);
- _strobe.AddAssociatedMetaData(BitConverter.GetBytes(KeySizeInBytes), true); // TODO: does this make sense?
return new Scalar(_strobe.Prf(KeySizeInBytes, false));
}

Port unit tests