public void AuthValidSignAndRecoverEip8() { // Generate all needed keypairs. EthereumEcdsa localPrivateKey = EthereumEcdsa.Generate(); EthereumEcdsa ephemeralPrivateKey = EthereumEcdsa.Generate(); EthereumEcdsa receiverPrivateKey = EthereumEcdsa.Generate(); // Create an RLPx auth packet and sign it. RLPxAuthEIP8 authPacket = new RLPxAuthEIP8(); authPacket.Sign(localPrivateKey, ephemeralPrivateKey, receiverPrivateKey, null); // Serialize and deserialize it. byte[] serializedData = authPacket.Serialize(); RLPxAuthEIP8 deserializedPacket = new RLPxAuthEIP8(serializedData); // Verify all matches between our serialized/deserialized object data. Assert.Equal(authPacket.Nonce.ToHexString(), deserializedPacket.Nonce.ToHexString()); Assert.Equal(authPacket.PublicKey.ToHexString(), deserializedPacket.PublicKey.ToHexString()); Assert.Equal(authPacket.R.ToHexString(), deserializedPacket.R.ToHexString()); Assert.Equal(authPacket.S.ToHexString(), deserializedPacket.S.ToHexString()); Assert.Equal(authPacket.V, deserializedPacket.V); // Try to recover the public key EthereumEcdsa recoveredEphemeralPublicKey = deserializedPacket.RecoverDataFromSignature(receiverPrivateKey).remoteEphemeralPublicKey; // Verify our public key hashes match Assert.Equal(ephemeralPrivateKey.GetPublicKeyHash().ToHexString(), recoveredEphemeralPublicKey.GetPublicKeyHash().ToHexString()); }
/// <summary> /// Verifies the provided encrypted serialized authentication data received from the initiator. /// If verification fails, an appropriate exception will be thrown. If no exception is thrown, /// the verification has succeeded. /// </summary> /// <param name="authData">The encrypted serialized authentication data to verify.</param> public void VerifyAuthentication(byte[] authData) { // Verify this is the responder role. if (Role != RLPxSessionRole.Responder) { throw new Exception("RLPx auth data should only be verified by the responder."); } // Verify the session state is correct. if (SessionState != RLPxSessionState.Initial) { throw new Exception("RLPx auth verification should only be performed on a new session."); } // Try to deserialize the data as a standard authentication packet. // The data is currently encrypted serialized data, so it will also need to be decrypted. RLPxAuthBase authenticationMessage = null; try { // Set the auth data AuthData = authData.Slice(0, AUTH_STANDARD_ENCRYPTED_SIZE); // The authentication data can simply be decrypted without any shared mac. byte[] decryptedAuthData = Ecies.Decrypt(LocalPrivateKey, AuthData, null); // Try to parse the decrypted authentication data. authenticationMessage = new RLPxAuthStandard(decryptedAuthData); UsingEIP8Authentication = false; // Set the remote version RemoteVersion = MAX_SUPPORTED_VERSION; } catch (Exception authStandardEx) { try { // EIP8 has a uint16 denoting encrypted data size, and the data to decrypt follows. Memory <byte> authDataMem = authData; Memory <byte> sharedMacDataMem = authDataMem.Slice(0, 2); ushort encryptedSize = (ushort)BigIntegerConverter.GetBigInteger(sharedMacDataMem.Span, false, sharedMacDataMem.Length); Memory <byte> encryptedData = authDataMem.Slice(2, encryptedSize); // Set our auth data AuthData = authDataMem.Slice(0, sharedMacDataMem.Length + encryptedSize).ToArray(); // Split the shared mac from the authentication data byte[] decryptedAuthData = Ecies.Decrypt(LocalPrivateKey, encryptedData, sharedMacDataMem); // Try to parse the decrypted EIP8 authentication data. RLPxAuthEIP8 authEip8Message = new RLPxAuthEIP8(decryptedAuthData); UsingEIP8Authentication = true; // Set the generic authentication data object. authenticationMessage = authEip8Message; // Set the remote version RemoteVersion = authEip8Message.Version; } catch (Exception authEip8Ex) { string exceptionMessage = "Could not deserialize RLPx auth data in either standard or EIP8 format.\r\n"; exceptionMessage += $"Standard Auth Error: {authStandardEx.Message}\r\n"; exceptionMessage += $"EIP8 Auth Error: {authEip8Ex.Message}"; throw new Exception(exceptionMessage); } } // Set the initiator nonce InitiatorNonce = authenticationMessage.Nonce; // Set the remote public key. RemotePublicKey = EthereumEcdsa.Create(authenticationMessage.PublicKey, EthereumEcdsaKeyType.Public); // Try to recover the public key (with the "receiver" private key, which in this case is our local private key, since our role is responder). // If this fails, it will throw an exception as we expect this method to if any failure occurs. (EthereumEcdsa remoteEphermalPublicKey, uint?chainId) = authenticationMessage.RecoverDataFromSignature(LocalPrivateKey); RemoteEphermalPublicKey = remoteEphermalPublicKey; // TODO: Verify the chain id. // Set the session state SessionState = RLPxSessionState.AuthenticationCompleted; }
/// <summary> /// Creates authentication data to send to the responder. /// </summary> /// <param name="receiverPublicKey">The responder/receiver's public key.</param> /// <param name="nonce">The nonce to use for the authentication data. If null, new data is generated.</param> /// <returns>Returns the encrypted serialized authentication data.</returns> public byte[] CreateAuthentiation(EthereumEcdsa receiverPublicKey, byte[] nonce = null) { // Verify this is the initiator role. if (Role != RLPxSessionRole.Initiator) { throw new Exception("RLPx auth data should only be created by the initiator."); } // Verify the session state is correct. if (SessionState != RLPxSessionState.Initial) { throw new Exception("RLPx auth creation should only be performed on a new session."); } // If the nonce is null, generate a new one. nonce = nonce ?? GenerateNonce(); // Set our initiator nonce InitiatorNonce = nonce; // Set the remote public key RemotePublicKey = receiverPublicKey; // Create a new authentication message based off of our configured setting. RLPxAuthBase authMessage = null; if (UsingEIP8Authentication) { // Create our EIP8 authentication message. authMessage = new RLPxAuthEIP8() { Nonce = InitiatorNonce, Version = MAX_SUPPORTED_VERSION, }; } else { // Create our standard authentication message. authMessage = new RLPxAuthStandard() { Nonce = InitiatorNonce, }; } // Sign the authentication message authMessage.Sign(LocalPrivateKey, LocalEphemeralPrivateKey, RemotePublicKey, ChainId); // Serialize the authentication data byte[] serializedData = authMessage.Serialize(); // Encrypt the data accordingly (standard uses no shared mac data, EIP8 has 2 bytes which prefix the resulting encrypted data). if (UsingEIP8Authentication) { // Obtain two bytes of mac data by EIP8 standards (big endian 16-bit unsigned integer equal to the size of the resulting ECIES encrypted data). // We can calculate this as the amount of overhead data ECIES adds is static in size. byte[] sharedMacData = BigIntegerConverter.GetBytes(serializedData.Length + Ecies.ECIES_ADDITIONAL_OVERHEAD, 2); // Encrypt the serialized data with the shared mac data, and prefix the result with it. byte[] encryptedSerializedData = Ecies.Encrypt(RemotePublicKey, serializedData, sharedMacData); AuthData = sharedMacData.Concat(encryptedSerializedData); } else { // Encrypt it using ECIES without any shared mac data. AuthData = Ecies.Encrypt(RemotePublicKey, serializedData, null); } // Set the session state SessionState = RLPxSessionState.AuthenticationCompleted; // Return the auth data return(AuthData); }