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:
parent
60029d6275
commit
11f482779b
5 changed files with 132 additions and 98 deletions
26
eclair-demo/src/main/scala/fr/acinq/eclair/Boot.scala
Normal file
26
eclair-demo/src/main/scala/fr/acinq/eclair/Boot.scala
Normal 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")
|
||||
|
||||
}
|
26
eclair-demo/src/main/scala/fr/acinq/eclair/Globals.scala
Normal file
26
eclair-demo/src/main/scala/fr/acinq/eclair/Globals.scala
Normal 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")
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue