diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala
index b71fe8d1ee..da93d4cd7a 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala
@@ -19,6 +19,7 @@ package fr.acinq.eclair.crypto
import java.nio.ByteOrder
import fr.acinq.bitcoin.{ByteVector32, Protocol}
+import grizzled.slf4j.Logger
import grizzled.slf4j.Logging
import org.spongycastle.crypto.engines.ChaCha7539Engine
import org.spongycastle.crypto.params.{KeyParameter, ParametersWithIV}
@@ -31,8 +32,8 @@ import scodec.bits.ByteVector
object Poly1305 {
/**
*
- * @param key input key
- * @param data input data
+ * @param key input key
+ * @param datas input data
* @return a 16 byte authentication tag
*/
def mac(key: ByteVector, datas: ByteVector*): ByteVector = {
@@ -50,6 +51,8 @@ object Poly1305 {
* see https://tools.ietf.org/html/rfc7539#section-2.5
*/
object ChaCha20 {
+ // Whenever key rotation happens, we start with a nonce value of 0 and increment it for each message.
+ val ZeroNonce = ByteVector.fill(12)(0.byteValue)
def encrypt(plaintext: ByteVector, key: ByteVector, nonce: ByteVector, counter: Int = 0): ByteVector = {
val engine = new ChaCha7539Engine()
@@ -94,6 +97,11 @@ object ChaCha20 {
*/
object ChaCha20Poly1305 extends Logging {
+ // This logger is used to dump encryption keys to enable traffic analysis by the lightning-dissector.
+ // See https://github.com/nayutaco/lightning-dissector for more details.
+ // It is disabled by default (in the logback.xml configuration file).
+ val keyLogger = Logger("keylog")
+
/**
*
* @param key 32 bytes encryption key
@@ -106,7 +114,12 @@ object ChaCha20Poly1305 extends Logging {
val polykey = ChaCha20.encrypt(ByteVector32.Zeroes, key, nonce)
val ciphertext = ChaCha20.encrypt(plaintext, key, nonce, 1)
val tag = Poly1305.mac(polykey, aad, pad16(aad), ciphertext, pad16(ciphertext), Protocol.writeUInt64(aad.length, ByteOrder.LITTLE_ENDIAN), Protocol.writeUInt64(ciphertext.length, ByteOrder.LITTLE_ENDIAN))
+
logger.debug(s"encrypt($key, $nonce, $aad, $plaintext) = ($ciphertext, $tag)")
+ if (nonce === ChaCha20.ZeroNonce) {
+ keyLogger.debug(s"${tag.toHex} ${key.toHex}")
+ }
+
(ciphertext, tag)
}
@@ -124,7 +137,12 @@ object ChaCha20Poly1305 extends Logging {
val tag = Poly1305.mac(polykey, aad, pad16(aad), ciphertext, pad16(ciphertext), Protocol.writeUInt64(aad.length, ByteOrder.LITTLE_ENDIAN), Protocol.writeUInt64(ciphertext.length, ByteOrder.LITTLE_ENDIAN))
require(tag == mac, "invalid mac")
val plaintext = ChaCha20.decrypt(ciphertext, key, nonce, 1)
+
logger.debug(s"decrypt($key, $nonce, $aad, $ciphertext, $mac) = $plaintext")
+ if (nonce === ChaCha20.ZeroNonce) {
+ keyLogger.debug(s"${mac.toHex} ${key.toHex}")
+ }
+
plaintext
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Noise.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Noise.scala
index fb85cf0216..10470c062b 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Noise.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Noise.scala
@@ -82,7 +82,7 @@ object Noise {
trait CipherFunctions {
def name: String
- //Encrypts plaintext using the cipher key k of 32 bytes and an 8-byte unsigned integer nonce n which must be unique
+ // Encrypts plaintext using the cipher key k of 32 bytes and an 8-byte unsigned integer nonce n which must be unique
// for the key k. Returns the ciphertext. Encryption must be done with an "AEAD" encryption mode with the associated
// data ad (using the terminology from [1]) and returns a ciphertext that is the same size as the plaintext
// plus 16 bytes for authentication data. The entire ciphertext must be indistinguishable from random if the key is secret.
@@ -99,7 +99,7 @@ object Noise {
// as specified in BOLT #8
def nonce(n: Long): ByteVector = ByteVector.fill(4)(0) ++ Protocol.writeUInt64(n, ByteOrder.LITTLE_ENDIAN)
- //Encrypts plaintext using the cipher key k of 32 bytes and an 8-byte unsigned integer nonce n which must be unique
+ // Encrypts plaintext using the cipher key k of 32 bytes and an 8-byte unsigned integer nonce n which must be unique.
override def encrypt(k: ByteVector, n: Long, ad: ByteVector, plaintext: ByteVector): ByteVector = {
val (ciphertext, mac) = ChaCha20Poly1305.encrypt(k, nonce(n), plaintext, ad)
ciphertext ++ mac
@@ -329,7 +329,7 @@ object Noise {
* contain 2 cipherstates than can be used to encrypt/decrypt further communication
*/
def write(payload: ByteVector): (HandshakeStateReader, ByteVector, Option[(CipherState, CipherState, ByteVector)]) = {
- require(!messages.isEmpty)
+ require(messages.nonEmpty)
logger.debug(s"write($payload)")
val (writer1, buffer1) = messages.head.foldLeft(this -> ByteVector.empty) {
@@ -377,7 +377,7 @@ object Noise {
*
* @param message input message
* @return a (writer, payload, Option[(cipherstate, cipherstate)] tuple.
- * The payload contains the original payload used be the sender and a writer that will be used to create the
+ * The payload contains the original payload used by the sender and a writer that will be used to create the
* next message. When the handshake is over (i.e. there are no more handshake patterns to process) the last item will
* contain 2 cipherstates than can be used to encrypt/decrypt further communication
*/
@@ -433,12 +433,12 @@ object Noise {
def initializeWriter(handshakePattern: HandshakePattern, prologue: ByteVector, s: KeyPair, e: KeyPair, rs: ByteVector, re: ByteVector, dh: DHFunctions, cipher: CipherFunctions, hash: HashFunctions, byteStream: ByteStream = RandomBytes): HandshakeStateWriter = {
val symmetricState = makeSymmetricState(handshakePattern, prologue, dh, cipher, hash)
- val symmetricState1 = (handshakePattern.initiatorPreMessages).foldLeft(symmetricState) {
+ val symmetricState1 = handshakePattern.initiatorPreMessages.foldLeft(symmetricState) {
case (state, E) => state.mixHash(e.pub)
case (state, S) => state.mixHash(s.pub)
case _ => throw new RuntimeException("invalid pre-message")
}
- val symmetricState2 = (handshakePattern.responderPreMessages).foldLeft(symmetricState1) {
+ val symmetricState2 = handshakePattern.responderPreMessages.foldLeft(symmetricState1) {
case (state, E) => state.mixHash(re)
case (state, S) => state.mixHash(rs)
case _ => throw new RuntimeException("invalid pre-message")
diff --git a/eclair-node/src/main/resources/logback.xml b/eclair-node/src/main/resources/logback.xml
index 59f3871af9..ae7ccbb8a7 100644
--- a/eclair-node/src/main/resources/logback.xml
+++ b/eclair-node/src/main/resources/logback.xml
@@ -39,6 +39,13 @@
+
+ ${eclair.datadir:-${user.home}/.eclair}/keys.log
+
+ %msg%n
+
+
+
@@ -52,6 +59,16 @@
+
+
+
+
+