Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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 = {
Expand All @@ -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()
Expand Down Expand Up @@ -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")
Comment thread
araspitzu marked this conversation as resolved.
Resolved

/**
*
* @param key 32 bytes encryption key
Expand All @@ -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)
}

Expand All @@ -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
}

Expand Down
12 changes: 6 additions & 6 deletions eclair-core/src/main/scala/fr/acinq/eclair/crypto/Noise.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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")
Expand Down
17 changes: 17 additions & 0 deletions eclair-node/src/main/resources/logback.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@
</encoder>
</appender>

<appender name="KEYLOG" class="ch.qos.logback.core.FileAppender">
<file>${eclair.datadir:-${user.home}/.eclair}/keys.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>

<if condition='isDefined("eclair.printToConsole")'>
<then>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
Expand All @@ -52,6 +59,16 @@
</then>
</if>

<!--
This logger can be used to dump encryption keys for traffic analysis.
It is used by the wireshark lightning dissector to troubleshoot issues between nodes.
To enable it, set the log level to "DEBUG" for this logger (no need to set it for the root logger).
See https://github.com/nayutaco/lightning-dissector for details.
-->
<logger level="OFF" name="keylog" additivity="false">
<appender-ref ref="KEYLOG"/>
</logger>

<root level="INFO">
<appender-ref ref="ROLLING"/>
</root>
Expand Down