/// <summary> /// Computes the message that accepts the handshake. /// </summary> /// <remark> /// Here the server computes a signature of the network key, the /// signature of the long term client's public key and a sha 256 of /// the shared ab secret. This is signed with the server's long term /// private key. /// /// This signature is encrypted using a sha 256 of the network key /// and all of the derived secrets. /// </remark> /// <returns> /// A byte array of length 80 consisting of the message. /// </returns> public byte[] Accept() { var detached_signature = PublicKeyAuth.SignDetached( Utils.Concat( _network_key, detached_signature_A, _longterm_client_pk, CryptoHash.Sha256(_shared_ab) ), _longterm_server_keypair.PrivateKey ); // A nonce consisting of 24 zeros var nonce = new byte[NONCE_SIZE]; nonce.Initialize(); var key = CryptoHash.Sha256( Utils.Concat(_network_key, _shared_ab, _shared_aB, _shared_Ab) ); var msg = SecretBox.Create(detached_signature, nonce, key); return(msg); }
/// <summary> /// Validates server acceptance message /// </summary> /// <remark> /// Here the client verifies that the received message length is 80 /// bytes, then opens the encrypted box and verifies that the sent /// message is server's signature to the derived shared secrets. With /// this the handshake concludes. /// </remark> /// <exception cref="ArgumentException"> /// Thrown if the server's Accept <paramref name="msg"/> is not the /// correct size or the signature is not valid. /// </exception> /// <param name="msg"> /// The received message, encrypted server's signature. /// </param> public void VerifyAccept(byte[] msg) { if (msg.Length != 80) { throw new ArgumentException("Incorrect message size"); } var nonce = new byte[NONCE_SIZE]; nonce.Initialize(); // Concatenate the network key and derived secrets to obtain // the message key var key = CryptoHash.Sha256( Utils.Concat(_network_key, _shared_ab, _shared_aB, _shared_Ab) ); var opened_msg = SecretBox.Open(msg, nonce, key); // Compute the message that it is supposed to be signed with the // server's long term key var hashed = CryptoHash.Sha256(_shared_ab); var msg_to_verify = Utils.Concat( _network_key, detached_signature_A, _longterm_client_keypair.PublicKey, hashed ); if (!PublicKeyAuth.VerifyDetached(opened_msg, msg_to_verify, _longterm_server_pk)) { throw new ArgumentException("Invalid signature"); } }
private void DeriveSecrets() { var curve25519Sk = PublicKeyAuth .ConvertEd25519SecretKeyToCurve25519SecretKey( this._longterm_client_keypair.PrivateKey ); var curve25519Pk = PublicKeyAuth .ConvertEd25519PublicKeyToCurve25519PublicKey( _longterm_server_pk ); this._shared_ab = ScalarMult.Mult( this._ephemeral_client_keypair.PrivateKey, this._ephemeral_server_pk ); this._shared_aB = ScalarMult.Mult( this._ephemeral_client_keypair.PrivateKey, curve25519Pk ); this._shared_Ab = ScalarMult.Mult( curve25519Sk, _ephemeral_server_pk ); }
/// <summary> /// Crafts the client Authenticate message /// </summary> /// <remark> /// Consists of a signature of the network identifier, the server's /// long term public key and a sha 256 of the derived secret ab, /// concatenated with the client's long term public key. All /// encrypted using the network identifier and the derived secrets /// ab and aB. /// /// This sets the object's <see cref="detached_signature_A"/> /// </remark> /// <returns> /// The client Authenticate message /// </returns> public byte[] Authenticate() { var hash_ab = CryptoHash.Sha256(this._shared_ab); // Concatenate the network identifier, the server's public key and // the hash of the derived secret. var to_sign = Utils.Concat( _network_key, _longterm_server_pk, hash_ab ); // Sign the first portion of the message and save it in the object // state for later use in the server accept verification. detached_signature_A = PublicKeyAuth.SignDetached( to_sign, _longterm_client_keypair.PrivateKey ); // Create the plaintext message var plaintext = Utils.Concat( detached_signature_A, _longterm_client_keypair.PublicKey ); // Create the key from the network key and the shared secrets var box_key = Utils.Concat( _network_key, _shared_ab, _shared_aB ); // A nonce consisting of 24 zeros var nonce = new byte[NONCE_SIZE]; nonce.Initialize(); var msg = SecretBox.Create(plaintext, nonce, CryptoHash.Sha256(box_key)); return(msg); }
public Key(byte[] privateKey) { var keyPair = PublicKeyAuth.GenerateKeyPair(privateKey); PrivateKey = keyPair.PrivateKey; PublicKey = keyPair.PublicKey; }
public Key(byte[] seed, int index) { var encoder = new ASCIIEncoder(); var secret = encoder.DecodeData(Ed25519Curve); // var seedBytes = HexByteConvertorExtensions.HexToByteArrayInternal(seed); var array = new HMACSHA512(secret).ComputeHash(seed); var key = array.SafeSubarray(0, 32); var chainCode = array.SafeSubarray(32, 32); var derivedPath = unchecked ((uint)index + HardenedOffset); derivedPath = HexByteConvertorExtensions.ReverseBytes(derivedPath); var dpBytes = BitConverter.GetBytes(derivedPath); var data = new byte[] { 0 }.Concat(key, dpBytes); var array2 = new HMACSHA512(chainCode).ComputeHash(data); var key2 = array2.SafeSubarray(0, 32); var keyPair = PublicKeyAuth.GenerateKeyPair(key2); PrivateKey = keyPair.PrivateKey; PublicKey = keyPair.PublicKey; }
/// <summary> /// Constructs the server given the Network key and its keypair /// </summary> /// <param name="network_key"> /// The key that identifies the network /// </param> /// <param name="server_keypair"> /// The server's long term keypair /// </param> public Server(byte[] network_key, Sodium.KeyPair server_keypair) { this._network_key = network_key; this._longterm_server_keypair = server_keypair; _ephemeral_server_keypair = new KeyPair(PublicKeyAuth.GenerateKeyPair()); }
/// <summary> /// Validate a file with a MinisignSignature and a MinisignPublicKey object. /// </summary> /// <param name="message">The message to validate.</param> /// <param name="signature">A valid MinisignSignature object.</param> /// <param name="publicKey">A valid MinisignPublicKey object.</param> /// <returns><c>true</c> if valid; otherwise, <c>false</c>.</returns> /// <exception cref="OverflowException"></exception> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> public static bool ValidateSignature(byte[] message, MinisignSignature signature, MinisignPublicKey publicKey) { if (message == null) { throw new ArgumentException("missing signature input", nameof(message)); } if (signature == null) { throw new ArgumentException("missing signature input", nameof(signature)); } if (publicKey == null) { throw new ArgumentException("missing publicKey input", nameof(publicKey)); } if (!ArrayHelpers.ConstantTimeEquals(signature.KeyId, publicKey.KeyId)) { return(false); } // verify the signature if (PublicKeyAuth.VerifyDetached(signature.Signature, message, publicKey.PublicKey)) { // verify the trusted comment return(PublicKeyAuth.VerifyDetached(signature.GlobalSignature, ArrayHelpers.ConcatArrays(signature.Signature, signature.TrustedComment), publicKey.PublicKey)); } return(false); }
public void ItWorks() { var network_key = new byte[] { 0xd4, 0xa1, 0xcb, 0x88, 0xa6, 0x6f, 0x02, 0xf8, 0xdb, 0x63, 0x5c, 0xe2, 0x64, 0x41, 0xcc, 0x5d, 0xac, 0x1b, 0x08, 0x42, 0x0c, 0xea, 0xac, 0x23, 0x08, 0x39, 0xb7, 0x55, 0x84, 0x5a, 0x9f, 0xfb }; var client_keypair = PublicKeyAuth.GenerateKeyPair(); var server_keypair = PublicKeyAuth.GenerateKeyPair(); var client = new Client(network_key, server_keypair.PublicKey, client_keypair); var server = new Server(network_key, server_keypair); // Client -> Server [1] var client_hello = client.Hello(); server.AcceptHello(client_hello); // Client <- Server [2] var server_hello = server.Hello(); client.AcceptHello(server_hello); Assert.True( Enumerable.SequenceEqual(client.EphemeralDerivedSecret, server.EphemeralDerivedSecret), "The ephemeral derived secrets are not the same" + "client: " + $"{string.Join(" ", client.EphemeralDerivedSecret.Select(x => x.ToString()).ToArray())}\n" + "server: " + $"{string.Join(" ", server.EphemeralDerivedSecret.Select(x => x.ToString()).ToArray())}" ); Assert.True( Enumerable.SequenceEqual(client.ServerDerivedSecret, server.ServerDerivedSecret), "The Server derived secrets are not the same\n" + "client: " + $"{string.Join(" ", client.ServerDerivedSecret.Select(x => x.ToString()).ToArray())}\n" + "server: " + $"{string.Join(" ", server.ServerDerivedSecret.Select(x => x.ToString()).ToArray())}" ); // Client -> Server [3] var client_auth = client.Authenticate(); server.AcceptAuth(client_auth); Assert.True( Enumerable.SequenceEqual(client.ClientDerivedSecret, server.ClientDerivedSecret), "The Client derived secrets are not the same\n" + "client: " + $"{string.Join(" ", client.ClientDerivedSecret.Select(x => x.ToString()).ToArray())}\n" + "server: " + $"{string.Join(" ", server.ClientDerivedSecret.Select(x => x.ToString()).ToArray())}" ); // Client <- Server [4] var server_accept = server.Accept(); client.VerifyAccept(server_accept); }
protected override void OnBeforeRequest(RestClient client, IRestRequest request, Proxy proxy = null) { request.AddHeader("X-Sign-Date", DateTimeOffset.Now.ToUnixTimeSeconds().ToString()); var patchString = request.Resource; var qParamas = request.Parameters .Where(w => w.Type == ParameterType.QueryString) .Select(s => s.ToString()) .ToList(); var bParamas = request.Parameters .Where(w => w.Type == ParameterType.RequestBody) .Select(s => s.ToString()) .ToList(); if (qParamas.Any()) { patchString += $"?{string.Join("&", qParamas.Select(s => s.ToString()))}"; } if (bParamas.Any()) { patchString += JsonConvert.SerializeObject(request.Parameters .Where(w => w.Type == ParameterType.RequestBody) .ToDictionary(d => d.Name, d => d.Value)); } var sigString = request.Method + patchString + DateTimeOffset.Now.ToUnixTimeSeconds(); var sig = Utilities.BinaryToHex(PublicKeyAuth.SignDetached(sigString, Utilities.HexToBinary(_privateKey))); request.AddHeader("X-Request-Sign", $"dmar ed25519 {sig}"); }
public void GenerateKeyPairTest() { var kp = PublicKeyAuth.GenerateKeyPair(); Assert.AreEqual(32, kp.Public.Length); Assert.AreEqual(64, kp.Secret.Length); }
public bool isValidSignature(Interaction interaction) { var signature = Context.Request.Headers["X-Signature-Ed25519"]; signature ??= Context.Request.Headers["X-Signature-Ed25519".ToLower()]; var timestamp = Context.Request.Headers["X-Signature-Timestamp"]; timestamp ??= Context.Request.Headers["X-Signature-Timestamp".ToLower()]; Program.LogMsg($"Verifying {timestamp} with {signature}"); var message = timestamp + Context.Body; var publicKey = Program.Configuration["tokens:publickey"]; #if !DEBUG if (!PublicKeyAuth.VerifyDetached( Sodium.Utilities.HexToBinary(signature), Encoding.UTF8.GetBytes(message), Sodium.Utilities.HexToBinary(publicKey))) { Program.LogMsg($"Failed verification."); return(false); } #endif Program.LogMsg($"Suceeded verification"); return(true); }
private static Certificate ExtractCertificate(byte[] data, byte[] providerKey) { var certificate = new Certificate(); if (data.Length != 116) { return(null); } certificate.MagicQuery = ArrayHelper.SubArray(data, 96, 8); var serial = ArrayHelper.SubArray(data, 104, 4); var tsBegin = ArrayHelper.SubArray(data, 108, 4); var tsEnd = ArrayHelper.SubArray(data, 112, 4); if (BitConverter.IsLittleEndian) { Array.Reverse(serial); Array.Reverse(tsBegin); Array.Reverse(tsEnd); } certificate.Serial = BitConverter.ToInt32(serial, 0); certificate.TsBegin = UnixTimeStampToDateTime(BitConverter.ToInt32(tsBegin, 0)); certificate.TsEnd = UnixTimeStampToDateTime(BitConverter.ToInt32(tsEnd, 0)); try { var m = PublicKeyAuth.Verify(data, providerKey); certificate.Valid = true; return(certificate); } catch (Exception) { } return(null); }
public void PublicKeyAuthConvertToCurve25519() { var keypairSeed = new byte[] { 0x42, 0x11, 0x51, 0xa4, 0x59, 0xfa, 0xea, 0xde, 0x3d, 0x24, 0x71, 0x15, 0xf9, 0x4a, 0xed, 0xae, 0x42, 0x31, 0x81, 0x24, 0x09, 0x5a, 0xfa, 0xbe, 0x4d, 0x14, 0x51, 0xa5, 0x59, 0xfa, 0xed, 0xee }; var keys = PublicKeyAuth.GenerateKeyPair(keypairSeed); var ed25519Pk = keys.PublicKey; var ed25519SkPk = keys.PrivateKey; var curve25519Pk = PublicKeyAuth.ConvertEd25519PublicKeyToCurve25519PublicKey(ed25519Pk); var curve25519Sk = PublicKeyAuth.ConvertEd25519SecretKeyToCurve25519SecretKey(ed25519SkPk); Assert.AreEqual(Utilities.BinaryToHex(curve25519Pk, Utilities.HexFormat.None, Utilities.HexCase.Upper), "F1814F0E8FF1043D8A44D25BABFF3CEDCAE6C22C3EDAA48F857AE70DE2BAAE50"); Assert.AreEqual(Utilities.BinaryToHex(curve25519Sk, Utilities.HexFormat.None, Utilities.HexCase.Upper), "8052030376D47112BE7F73ED7A019293DD12AD910B654455798B4667D73DE166"); for (var i = 0; i < 500; i++) { keys = PublicKeyAuth.GenerateKeyPair(); ed25519Pk = keys.PublicKey; ed25519SkPk = keys.PrivateKey; curve25519Pk = PublicKeyAuth.ConvertEd25519PublicKeyToCurve25519PublicKey(ed25519Pk); curve25519Sk = PublicKeyAuth.ConvertEd25519SecretKeyToCurve25519SecretKey(ed25519SkPk); var curve25519Pk2 = ScalarMult.Base(curve25519Sk); CollectionAssert.AreEqual(curve25519Pk, curve25519Pk2); } }
public void ConvertToCurve25519Test() { // Keypair seed from libsodium-net var keypairSeed = new byte[] { 0x42, 0x11, 0x51, 0xa4, 0x59, 0xfa, 0xea, 0xde, 0x3d, 0x24, 0x71, 0x15, 0xf9, 0x4a, 0xed, 0xae, 0x42, 0x31, 0x81, 0x24, 0x09, 0x5a, 0xfa, 0xbe, 0x4d, 0x14, 0x51, 0xa5, 0x59, 0xfa, 0xed, 0xee }; var kp = PublicKeyAuth.GenerateKeyPair(keypairSeed); var ed25519Pk = kp.Public; var ed25519SkPk = kp.Secret; var curve25519Pk = PublicKeyAuth.ConvertEd25519PublicKeyToCurve25519PublicKey(ed25519Pk); var curve25519Sk = PublicKeyAuth.ConvertEd25519SecretKeyToCurve25519SecretKey(ed25519SkPk); Assert.AreEqual(Convert.ToBase64String(curve25519Pk), "8YFPDo/xBD2KRNJbq/887crmwiw+2qSPhXrnDeK6rlA="); Assert.AreEqual(Convert.ToBase64String(curve25519Sk), "gFIDA3bUcRK+f3PtegGSk90SrZELZURVeYtGZ9c94WY="); for (var i = 0; i < 500; i++) { kp = PublicKeyAuth.GenerateKeyPair(); ed25519Pk = kp.Public; ed25519SkPk = kp.Secret; curve25519Pk = PublicKeyAuth.ConvertEd25519PublicKeyToCurve25519PublicKey(ed25519Pk); curve25519Sk = PublicKeyAuth.ConvertEd25519SecretKeyToCurve25519SecretKey(ed25519SkPk); var curve25519Pk2 = ScalarMult.Base(curve25519Sk); CollectionAssert.AreEqual(curve25519Pk, curve25519Pk2); } }
/// <summary> /// Validate a file with a MinisignSignature and a MinisignPublicKey object. /// </summary> /// <param name="filePath">The full path to the file.</param> /// <param name="signature">A valid MinisignSignature object.</param> /// <param name="publicKey">A valid MinisignPublicKey object.</param> /// <returns><c>true</c> if valid; otherwise, <c>false</c>.</returns> /// <exception cref="FileNotFoundException"></exception> /// <exception cref="OverflowException"></exception> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> public static bool ValidateSignature(string filePath, MinisignSignature signature, MinisignPublicKey publicKey) { if (filePath != null && !File.Exists(filePath)) { throw new FileNotFoundException("could not find filePath"); } if (signature == null) { throw new ArgumentException("missing signature input", nameof(signature)); } if (publicKey == null) { throw new ArgumentException("missing publicKey input", nameof(publicKey)); } if (!ArrayHelpers.ConstantTimeEquals(signature.KeyId, publicKey.KeyId)) { return(false); } // load the file into memory var file = LoadMessageFile(filePath); // verify the signature if (PublicKeyAuth.VerifyDetached(signature.Signature, file, publicKey.PublicKey)) { // verify the trusted comment return(PublicKeyAuth.VerifyDetached(signature.GlobalSignature, ArrayHelpers.ConcatArrays(signature.Signature, signature.TrustedComment), publicKey.PublicKey)); } return(false); }
public static byte[] DecodeMessage(byte[] message, byte[] nonce, byte[] senderPublicKey, byte[] recipientPrivateKey) { var curvePublicKey = PublicKeyAuth.ConvertEd25519PublicKeyToCurve25519PublicKey(senderPublicKey); var curveSecretKey = PublicKeyAuth.ConvertEd25519SecretKeyToCurve25519SecretKey(recipientPrivateKey); return(TweetNaCl.CryptoBoxOpen(message, nonce, curvePublicKey, curveSecretKey)); }
public void VerifyAuthDetachedBadSignature() { PublicKeyAuth.VerifyDetached( Utilities.HexToBinary("5436accbe258a6b252c1140f38d7b8dc6196619945818b72512b6a8019d86dfeeb56f40c4d4b983d97dfeed37948527256c3567d6b253757fcfb32bef56f0b"), Encoding.UTF8.GetBytes("Adam Caudill"), Utilities.HexToBinary("4ffda13c11d61d2b9568e54bec06ea59368e84874883087645e64e5e9653422e")); }
public KeyPair(Sodium.KeyPair a) { this.PublicKey = PublicKeyAuth .ConvertEd25519PublicKeyToCurve25519PublicKey(a.PublicKey); this.PrivateKey = PublicKeyAuth .ConvertEd25519SecretKeyToCurve25519SecretKey(a.PrivateKey); }
public void TestNoiseXx() { var rootKey = PublicKeyAuth.GenerateKeyPair(); var Verifier = DiscoHelper.CreatePublicKeyVerifier(rootKey.PublicKey); var clienPair = Asymmetric.GenerateKeyPair(); var serverPair = Asymmetric.GenerateKeyPair(); // init var clientConfig = new Config { KeyPair = clienPair, HandshakePattern = NoiseHandshakeType.NoiseXX, PublicKeyVerifier = Verifier, StaticPublicKeyProof = DiscoHelper.CreateStaticPublicKeyProof(rootKey.PrivateKey, clienPair.PublicKey) }; var serverConfig = new Config { KeyPair = serverPair, HandshakePattern = NoiseHandshakeType.NoiseXX, PublicKeyVerifier = Verifier, StaticPublicKeyProof = DiscoHelper.CreateStaticPublicKeyProof(rootKey.PrivateKey, serverPair.PublicKey) }; this.RunTwoWayTest(clientConfig, serverConfig, 1802); }
public void ExtractEd25519PublicKeyFromEd25519SecretKeyTest() { var kp = PublicKeyAuth.GenerateKeyPair(); var pub = PublicKeyAuth.ExtractEd25519PublicKeyFromEd25519SecretKey(kp.Secret); Assert.AreEqual(Convert.ToBase64String(kp.Public), Convert.ToBase64String(pub)); }
public void SimpleAuthDetachedTest() { var expected = Utilities.HexToBinary("8d5436accbe258a6b252c1140f38d7b8dc6196619945818b72512b6a8019d86dfeeb56f40c4d4b983d97dfeed37948527256c3567d6b253757fcfb32bef56f0b"); var actual = PublicKeyAuth.SignDetached(Encoding.UTF8.GetBytes("Adam Caudill"), Utilities.HexToBinary("89dff97c131434c11809c3341510ce63c85e851d3ba62e2f810016bbc67d35144ffda13c11d61d2b9568e54bec06ea59368e84874883087645e64e5e9653422e")); CollectionAssert.AreEqual(expected, actual); }
public void SimpleVerifyTest() { var expected = Encoding.UTF8.GetBytes("Adam Caudill"); var actual = PublicKeyAuth.Verify(Utilities.HexToBinary("8d5436accbe258a6b252c1140f38d7b8dc6196619945818b72512b6a8019d86dfeeb56f40c4d4b983d97dfeed37948527256c3567d6b253757fcfb32bef56f0b4164616d2043617564696c6c"), Utilities.HexToBinary("4ffda13c11d61d2b9568e54bec06ea59368e84874883087645e64e5e9653422e")); CollectionAssert.AreEqual(expected, actual); }
public void GenerateKeyTest() { var actual = PublicKeyAuth.GenerateKeyPair(); //need a better test Assert.IsNotNull(actual.PrivateKey); Assert.IsNotNull(actual.PublicKey); }
public void GenerateKeyPairFromSeedTest() { var seed = Core.GetRandomBytes(32); var kp = PublicKeyAuth.GenerateKeyPair(seed); Assert.AreEqual(32, kp.Public.Length); Assert.AreEqual(64, kp.Secret.Length); }
// Decrypts data with receiver’s private key using Libsodium public static string DecryptWithPrivateKey(string data, string privateKey) { var privateKeyConverted = PublicKeyAuth.ConvertEd25519SecretKeyToCurve25519SecretKey(Hex.Decode(privateKey)); var publicKey = Hex.Decode(GetPublicKeyFromPrivate(privateKey)); var publicKeyConverted = PublicKeyAuth.ConvertEd25519PublicKeyToCurve25519PublicKey(publicKey); return(Encoding.UTF8.GetString(SealedPublicKeyBox.Open(Hex.Decode(data), privateKeyConverted, publicKeyConverted))); }
/// <summary> /// Generated a detached signature from the provided data /// </summary> /// <param name="data">String payload to sign</param> /// <returns>byte[] of the detached signature</returns> public byte[] Sign(String data) { try { return(PublicKeyAuth.SignDetached(data, this.signatureSecretKey)); } catch (Exception e) { throw new SigningException("Unable to sign message", e); } }
public void GenerateKeyVerifySignedDataTest() { var actual = PublicKeyAuth.GenerateKeyPair(); byte[] randomArray = SodiumCore.GetRandomBytes(255); var sign = PublicKeyAuth.SignDetached(randomArray, actual.PrivateKey); Assert.IsTrue(PublicKeyAuth.VerifyDetached(sign, randomArray, actual.PublicKey)); }
public void GenerateKeySeedTest() { var expected = new KeyPair(Utilities.HexToBinary("76a1592044a6e4f511265bca73a604d90b0529d1df602be30a19a9257660d1f5"), Utilities.HexToBinary("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff76a1592044a6e4f511265bca73a604d90b0529d1df602be30a19a9257660d1f5")); var actual = PublicKeyAuth.GenerateKeyPair(Utilities.HexToBinary("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); CollectionAssert.AreEqual(expected.PublicKey, actual.PublicKey); CollectionAssert.AreEqual(expected.PrivateKey, actual.PrivateKey); }
public void ExtractEd25519SeedFromEd25519SecretKeyTest() { var kp1 = PublicKeyAuth.GenerateKeyPair(); var seed = PublicKeyAuth.ExtractEd25519SeedFromEd25519SecretKey(kp1.Secret); var kp2 = PublicKeyAuth.GenerateKeyPair(seed); Assert.AreEqual(Convert.ToBase64String(kp1.Public), Convert.ToBase64String(kp2.Public)); Assert.AreEqual(Convert.ToBase64String(kp1.Secret), Convert.ToBase64String(kp2.Secret)); }