1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-22 22:25:26 +01:00

added a Boot

This commit is contained in:
pm47 2016-01-25 19:53:09 +01:00
parent 60029d6275
commit 11f482779b
5 changed files with 132 additions and 98 deletions

View file

@ -0,0 +1,26 @@
package fr.acinq.eclair
import akka.actor.{Props, ActorSystem}
import akka.util.Timeout
import fr.acinq.eclair.blockchain.PollingWatcher
import grizzled.slf4j.Logging
import scala.concurrent.{ExecutionContext, Await}
import scala.concurrent.duration._
import Globals._
/**
* Created by PM on 25/01/2016.
*/
object Boot extends App with Logging {
implicit val system = ActorSystem()
implicit val timeout = Timeout(30 seconds)
implicit val formats = org.json4s.DefaultFormats
implicit val ec = ExecutionContext.Implicits.global
val testnet = Await.result(bitcoin_client.invoke("getinfo").map(json => (json \ "testnet").extract[Boolean]), 10 seconds)
assert(testnet, "you should be on testnet")
val blockchain = system.actorOf(Props(new PollingWatcher(bitcoin_client)), name = "blockchain")
}

View file

@ -0,0 +1,26 @@
package fr.acinq.eclair
import fr.acinq.bitcoin.{BitcoinJsonRPCClient, Base58Check}
import fr.acinq.eclair.crypto.LightningCrypto._
import lightning.locktime
import lightning.locktime.Locktime.Seconds
/**
* Created by PM on 25/01/2016.
*/
object Globals {
val node_id = KeyPair("0277863c1e40a2d4934ccf18e6679ea949d36bb0d1333fb098e99180df60d0195a","0623a602c7b0c96df445b999de31ca31682f0117ca2bf2fb149b9e09287d5d47")
val commit_priv = Base58Check.decode("cQPmcNr6pwBQPyGfab3SksE9nTCtx9ism9T4dkS9dETNU2KKtJHk")._2
val final_priv = Base58Check.decode("cUrAtLtV7GGddqdkhUxnbZVDWGJBTducpPoon3eKp9Vnr1zxs6BG")._2
val default_locktime = locktime(Seconds(86400))
val default_mindepth = 3
val default_commitfee = 50000
val default_anchor_amount = 1000000
val bitcoin_client = new BitcoinJsonRPCClient("foo", "bar")
}

View file

@ -8,90 +8,25 @@ import akka.util.ByteString
import com.trueaccord.scalapb.GeneratedMessage
import fr.acinq.bitcoin._
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.PollingWatcher
import fr.acinq.eclair.channel.{INPUT_NONE, Channel, OurChannelParams, AnchorInput}
import fr.acinq.eclair.crypto.LightningCrypto._
import fr.acinq.eclair.io.AuthHandler.Secrets
import lightning._
import lightning.locktime.Locktime.{Seconds, Blocks}
import lightning.pkt.Pkt._
import org.bouncycastle.util.encoders.Hex
import org.json4s.JsonAST.{JInt, JObject, JValue}
import scala.annotation.tailrec
import scala.concurrent.Future
/**
* Created by PM on 27/10/2015.
*/
object AuthHandler {
case class Secrets(aes_key: BinaryData, hmac_key: BinaryData, aes_iv: BinaryData)
def generate_secrets(ecdh_key: BinaryData, pub: BinaryData): Secrets = {
val aes_key = Crypto.sha256(ecdh_key ++ pub :+ 0x00.toByte).take(16)
val hmac_key = Crypto.sha256(ecdh_key ++ pub :+ 0x01.toByte)
val aes_iv = Crypto.sha256(ecdh_key ++ pub :+ 0x02.toByte).take(16)
Secrets(aes_key, hmac_key, aes_iv)
}
/**
* Rounds up to a factor of 16
*
* @param l
* @return
*/
def round16(l: Int) = l % 16 match {
case 0 => l
case x => l + 16 - x
}
def writeMsg(msg: pkt, secrets: Secrets, cipher: Cipher, totlen_prev: Long): (BinaryData, Long) = {
val buf = pkt.toByteArray(msg)
val enclen = round16(buf.length)
val enc = cipher.update(buf.padTo(enclen, 0x00: Byte))
val totlen = totlen_prev + buf.length
val totlen_bin = Protocol.writeUInt64(totlen)
(hmac256(secrets.hmac_key, totlen_bin ++ enc) ++ totlen_bin ++ enc, totlen)
}
/**
* splits buffer in separate msg
*
* @param data raw data received (possibly one, multiple or fractions of messages)
* @param totlen_prev
* @param f will be applied to full messages
* @return rest of the buffer (incomplete msg)
*/
@tailrec
def split(data: BinaryData, secrets: Secrets, cipher: Cipher, totlen_prev: Long, f: BinaryData => Unit): (BinaryData, Long) = {
if (data.length < 32 + 8) (data, totlen_prev)
else {
val totlen = Protocol.uint64(data.slice(32, 32 + 8))
val len = (totlen - totlen_prev).asInstanceOf[Int]
val enclen = round16(len)
if (data.length < 32 + 8 + enclen) (data, totlen_prev)
else {
val splitted = data.splitAt(32 + 8 + enclen)
val refsig = BinaryData(data.take(32))
val payload = BinaryData(data.slice(32, 32 + 8 + enclen))
val sig = hmac256(secrets.hmac_key, payload)
assert(sig.data.sameElements(refsig), "sig mismatch!")
val dec = cipher.update(payload.drop(8).toArray)
f(dec.take(len))
split(splitted._2, secrets, cipher, totlen_prev + len, f)
}
}
}
}
// @formatter:off
sealed trait Data
case object Nothing extends Data
case class SessionData(theirpub: BinaryData, secrets_in: Secrets, secrets_out: Secrets, cipher_in: Cipher, cipher_out: Cipher, totlen_in: Long, totlen_out: Long, acc_in: BinaryData) extends Data
case class SessionData(their_session_key: BinaryData, secrets_in: Secrets, secrets_out: Secrets, cipher_in: Cipher, cipher_out: Cipher, totlen_in: Long, totlen_out: Long, acc_in: BinaryData) extends Data
case class Normal(channel: ActorRef, sessionData: SessionData) extends Data
sealed trait State
@ -101,11 +36,10 @@ case object IO_NORMAL extends State
// @formatter:on
class AuthHandler(them: ActorRef) extends LoggingFSM[State, Data] with Stash {
class AuthHandler(them: ActorRef, blockchain: ActorRef, our_anchor: Boolean) extends LoggingFSM[State, Data] with Stash {
import AuthHandler._
val node_id = randomKeyPair()
val session_key = randomKeyPair()
them ! Write(ByteString.fromArray(session_key.pub))
@ -113,17 +47,17 @@ class AuthHandler(them: ActorRef) extends LoggingFSM[State, Data] with Stash {
when(IO_WAITING_FOR_SESSION_KEY) {
case Event(Received(data), _) =>
val theirpub = BinaryData(data)
log.info(s"received pubkey $theirpub")
val secrets_in = generate_secrets(ecdh(theirpub, session_key.priv), theirpub)
val secrets_out = generate_secrets(ecdh(theirpub, session_key.priv), session_key.pub)
val their_session_key = BinaryData(data)
log.info(s"their_session_key=$their_session_key")
val secrets_in = generate_secrets(ecdh(their_session_key, session_key.priv), their_session_key)
val secrets_out = generate_secrets(ecdh(their_session_key, session_key.priv), session_key.pub)
log.info(s"generated secrets_in=$secrets_in secrets_out=$secrets_out")
val cipher_in = aesDecryptCipher(secrets_in.aes_key, secrets_in.aes_iv)
val cipher_out = aesEncryptCipher(secrets_out.aes_key, secrets_out.aes_iv)
val our_auth = pkt(Auth(lightning.authenticate(node_id.pub, bin2signature(Crypto.encodeSignature(Crypto.sign(Crypto.hash256(theirpub), node_id.priv))))))
val our_auth = pkt(Auth(lightning.authenticate(Globals.node_id.pub, bin2signature(Crypto.encodeSignature(Crypto.sign(Crypto.hash256(their_session_key), Globals.node_id.priv))))))
val (d, new_totlen_out) = writeMsg(our_auth, secrets_out, cipher_out, 0)
them ! Write(ByteString.fromArray(d))
goto(IO_WAITING_FOR_AUTH) using SessionData(theirpub, secrets_in, secrets_out, cipher_in, cipher_out, 0, new_totlen_out, BinaryData(Seq()))
goto(IO_WAITING_FOR_AUTH) using SessionData(their_session_key, secrets_in, secrets_out, cipher_in, cipher_out, 0, new_totlen_out, BinaryData(Seq()))
}
when(IO_WAITING_FOR_AUTH) {
@ -133,24 +67,12 @@ class AuthHandler(them: ActorRef) extends LoggingFSM[State, Data] with Stash {
stay using s.copy(totlen_in = new_totlen_in, acc_in = rest)
case Event(pkt(Auth(auth)), s: SessionData) =>
log.info(s"received authenticate: $auth")
log.info(s"nodeid: ${BinaryData(auth.nodeId.key.toByteArray)}")
log.info(s"sig: ${BinaryData(signature2bin(auth.sessionSig))}")
log.info(s"their_nodeid: ${BinaryData(auth.nodeId.key.toByteArray)}")
assert(Crypto.verifySignature(Crypto.hash256(session_key.pub), signature2bin(auth.sessionSig), pubkey2bin(auth.nodeId)), "auth failed")
log.info(s"initializing channel actor")
val anchorInput = AnchorInput(1000000L, OutPoint(Hex.decode("7727730d21428276a4d6b0e16f3a3e6f3a07a07dc67151e6a88d4a8c3e8edb24").reverse, 1), SignData("76a914e093fbc19866b98e0fbc25d79d0ad0f0375170af88ac", Base58Check.decode("cU1YgK56oUKAtV6XXHZeJQjEx1KGXkZS1pGiKpyW4mUyKYFJwWFg")._2))
val alice_commit_priv = Base58Check.decode("cQPmcNr6pwBQPyGfab3SksE9nTCtx9ism9T4dkS9dETNU2KKtJHk")._2
val alice_final_priv = Base58Check.decode("cUrAtLtV7GGddqdkhUxnbZVDWGJBTducpPoon3eKp9Vnr1zxs6BG")._2
val alice_params = OurChannelParams(locktime(Seconds(86400)), alice_commit_priv, alice_final_priv, 1, 50000, "alice-seed".getBytes())
val mockCoreClient = new BitcoinJsonRPCClient("foo", "bar") {
override def invoke(method: String, params: Any*): Future[JValue] = method match {
case "getrawtransaction" => Future.successful(JObject(("confirmations", JInt(100))))
case "gettxout" => Future.successful(JObject())
case _ => ???
}
}
val blockchain = context.system.actorOf(Props(new PollingWatcher(mockCoreClient)), name = "blockchain")
val channel = context.system.actorOf(Props(new Channel(blockchain, alice_params, Some(anchorInput))), name = "alice")
val anchorInput_opt = if (our_anchor) Some(AnchorInput(1000000L, OutPoint(Hex.decode("7727730d21428276a4d6b0e16f3a3e6f3a07a07dc67151e6a88d4a8c3e8edb24").reverse, 1), SignData("76a914e093fbc19866b98e0fbc25d79d0ad0f0375170af88ac", Base58Check.decode("cU1YgK56oUKAtV6XXHZeJQjEx1KGXkZS1pGiKpyW4mUyKYFJwWFg")._2))) else None
val channel_params = OurChannelParams(Globals.default_locktime, Globals.commit_priv, Globals.final_priv, Globals.default_mindepth, Globals.default_commitfee, "sha-seed".getBytes())
val channel = context.system.actorOf(Props(new Channel(blockchain, channel_params, anchorInput_opt)), name = "alice")
channel ! INPUT_NONE
goto(IO_NORMAL) using Normal(channel, s)
}
@ -211,4 +133,65 @@ class AuthHandler(them: ActorRef) extends LoggingFSM[State, Data] with Stash {
}
initialize()
}
object AuthHandler {
case class Secrets(aes_key: BinaryData, hmac_key: BinaryData, aes_iv: BinaryData)
def generate_secrets(ecdh_key: BinaryData, pub: BinaryData): Secrets = {
val aes_key = Crypto.sha256(ecdh_key ++ pub :+ 0x00.toByte).take(16)
val hmac_key = Crypto.sha256(ecdh_key ++ pub :+ 0x01.toByte)
val aes_iv = Crypto.sha256(ecdh_key ++ pub :+ 0x02.toByte).take(16)
Secrets(aes_key, hmac_key, aes_iv)
}
/**
* Rounds up to a factor of 16
*
* @param l
* @return
*/
def round16(l: Int) = l % 16 match {
case 0 => l
case x => l + 16 - x
}
def writeMsg(msg: pkt, secrets: Secrets, cipher: Cipher, totlen_prev: Long): (BinaryData, Long) = {
val buf = pkt.toByteArray(msg)
val enclen = round16(buf.length)
val enc = cipher.update(buf.padTo(enclen, 0x00: Byte))
val totlen = totlen_prev + buf.length
val totlen_bin = Protocol.writeUInt64(totlen)
(hmac256(secrets.hmac_key, totlen_bin ++ enc) ++ totlen_bin ++ enc, totlen)
}
/**
* splits buffer in separate msg
*
* @param data raw data received (possibly one, multiple or fractions of messages)
* @param totlen_prev
* @param f will be applied to full messages
* @return rest of the buffer (incomplete msg)
*/
@tailrec
def split(data: BinaryData, secrets: Secrets, cipher: Cipher, totlen_prev: Long, f: BinaryData => Unit): (BinaryData, Long) = {
if (data.length < 32 + 8) (data, totlen_prev)
else {
val totlen = Protocol.uint64(data.slice(32, 32 + 8))
val len = (totlen - totlen_prev).asInstanceOf[Int]
val enclen = round16(len)
if (data.length < 32 + 8 + enclen) (data, totlen_prev)
else {
val splitted = data.splitAt(32 + 8 + enclen)
val refsig = BinaryData(data.take(32))
val payload = BinaryData(data.slice(32, 32 + 8 + enclen))
val sig = hmac256(secrets.hmac_key, payload)
assert(sig.data.sameElements(refsig), "sig mismatch!")
val dec = cipher.update(payload.drop(8).toArray)
f(dec.take(len))
split(splitted._2, secrets, cipher, totlen_prev + len, f)
}
}
}
}

View file

@ -1,10 +1,10 @@
package fr.acinq.protos
package fr.acinq.eclair.io
import java.net.InetSocketAddress
import akka.actor._
import akka.io.{IO, Tcp}
import fr.acinq.eclair.io.AuthHandler
import fr.acinq.eclair.Boot
/**
* Created by PM on 27/10/2015.
@ -23,7 +23,7 @@ class Client(remote: InetSocketAddress) extends Actor with ActorLogging {
case c@Connected(remote, local) =>
log.info(s"connected to $remote")
val connection = sender()
val handler = context.actorOf(Props(classOf[AuthHandler], connection))
val handler = context.actorOf(Props(classOf[AuthHandler], Boot.blockchain, connection))
connection ! Register(handler)
}
}

View file

@ -1,10 +1,10 @@
package fr.acinq.protos
package fr.acinq.eclair.io
import java.net.InetSocketAddress
import akka.actor.{ActorSystem, Props, ActorLogging, Actor}
import akka.actor.{Actor, ActorLogging, ActorSystem, Props}
import akka.io.{IO, Tcp}
import fr.acinq.eclair.io.AuthHandler
import fr.acinq.eclair.Boot
/**
* Created by PM on 27/10/2015.
@ -25,9 +25,8 @@ class Server extends Actor with ActorLogging {
case c @ Connected(remote, local) =>
log.info(s"connected to $remote")
val connection = sender()
val handler = context.actorOf(Props(classOf[AuthHandler], connection))
val handler = context.actorOf(Props(classOf[AuthHandler], connection, Boot.blockchain, false))
connection ! Register(handler)
handler ! 'init
}
}