1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-03-27 02:37:06 +01:00

completed normal workflow, functional still unfunctional

This commit is contained in:
pm47 2015-08-26 17:41:55 +02:00
parent db75d8dd1b
commit 2c168a188e
3 changed files with 175 additions and 90 deletions

View file

@ -2,7 +2,9 @@ package fr.acinq.eclair
import akka.actor.{Props, ActorSystem}
import fr.acinq.bitcoin._
import lightning.sha256_hash
import fr.acinq.lightning._
import lightning.locktime.Locktime.Blocks
import lightning.{locktime, sha256_hash}
import org.bouncycastle.util.encoders.Hex
/**
@ -20,16 +22,25 @@ object Boot extends App {
bob.tell(INPUT_NONE, alice)
alice.tell(INPUT_NONE, bob)
Thread.sleep(3000)
Thread.sleep(500)
alice ! TxConfirmed(sha256_hash(1, 2, 3, 4), 1)
bob ! TxConfirmed(sha256_hash(1, 2, 3, 4), 1)
Thread.sleep(2000)
Thread.sleep(500)
alice ! TxConfirmed(sha256_hash(1, 2, 3, 4), 2)
bob ! TxConfirmed(sha256_hash(1, 2, 3, 4), 2)
Thread.sleep(2000)
Thread.sleep(1000)
val r = sha256_hash(7, 7, 7, 7)
val rHash = Crypto.sha256(r)
alice ! CMD_SEND_HTLC_UPDATE(100000, rHash, locktime(Blocks(4)))
Thread.sleep(1000)
alice ! CMD_SEND_HTLC_COMPLETE(r)
Thread.sleep(1000)
alice ! CMD_SEND_UPDATE
}

View file

@ -42,11 +42,13 @@ case object CLOSED extends State
case object INPUT_NONE
sealed trait BlockchainEvent
final case class TxConfirmed(blockId: sha256_hash, confirmations: Int)
final case class TxConfirmed(blockId: sha256_hash, confirmations: Int) extends BlockchainEvent
final case class BITCOIN_CLOSE_DONE()
sealed trait Commands
case object CMD_SEND_UPDATE
sealed trait Command
case object CMD_SEND_UPDATE extends Command
final case class CMD_SEND_HTLC_UPDATE(amount: Long, rHash: sha256_hash, expiry: locktime) extends Command
final case class CMD_SEND_HTLC_COMPLETE(r: sha256_hash) extends Command
// DATA
@ -54,15 +56,18 @@ sealed trait Data
case object Nothing extends Data
final case class AnchorInput(amount: Long, previousTxOutput: OutPoint, signData: SignData) extends Data
final case class ChannelParams(delay: locktime, commitKey: bitcoin_pubkey, finalKey: bitcoin_pubkey, minDepth: Int, commitmentFee: Long)
final case class CommitmentTx(tx: Transaction, ourRevocationPreimage: sha256_hash)
final case class CommitmentTx(tx: Transaction, state: ChannelState, ourRevocationPreimage: sha256_hash, theirRevocationHash: sha256_hash)
final case class UpdateProposal(state: ChannelState, ourRevocationPreimage: sha256_hash)
final case class DATA_OPEN_WAIT_FOR_OPEN_NOANCHOR(ourParams: ChannelParams, ourRevocationPreimage: sha256_hash) extends Data
final case class DATA_OPEN_WAIT_FOR_OPEN_WITHANCHOR(ourParams: ChannelParams, anchorInput: AnchorInput, ourRevocationPreimage: sha256_hash) extends Data
final case class DATA_OPEN_WAIT_FOR_ANCHOR(ourParams: ChannelParams, theirParams: ChannelParams, ourRevocationPreimage: sha256_hash, theirRevocationHash: sha256_hash) extends Data
final case class DATA_OPEN_WAIT_FOR_COMMIT_SIG(ourParams: ChannelParams, theirParams: ChannelParams, anchorTx: Transaction, anchorOutputIndex: Int, ourRevocationPreimage: sha256_hash, theirRevocationHash: sha256_hash) extends Data
final case class DATA_OPEN_WAIT_FOR_COMMIT_SIG(ourParams: ChannelParams, theirParams: ChannelParams, anchorTx: Transaction, anchorOutputIndex: Int, newCommitmentTxUnsigned: CommitmentTx) extends Data
final case class DATA_OPEN_WAITING(ourParams: ChannelParams, theirParams: ChannelParams, commitmentTx: CommitmentTx, otherPartyOpen: Boolean = false) extends Data
final case class DATA_NORMAL(ourParams: ChannelParams, theirParams: ChannelParams, commitmentTx: CommitmentTx) extends Data
//TODO : create SignedTransaction
final case class DATA_WAIT_FOR_UPDATE_ACCEPT(ourParams: ChannelParams, theirParams: ChannelParams, previousCommitmentTxSigned: CommitmentTx, newCommitmentTxUnsigned: CommitmentTx) extends Data
final case class DATA_WAIT_FOR_UPDATE_ACCEPT(ourParams: ChannelParams, theirParams: ChannelParams, previousCommitmentTxSigned: CommitmentTx, updateProposal: UpdateProposal) extends Data
final case class DATA_WAIT_FOR_HTLC_ACCEPT(ourParams: ChannelParams, theirParams: ChannelParams, previousCommitmentTxSigned: CommitmentTx, updateProposal: UpdateProposal) extends Data
final case class DATA_WAIT_FOR_UPDATE_SIG(ourParams: ChannelParams, theirParams: ChannelParams, previousCommitmentTxSigned: CommitmentTx, newCommitmentTxUnsigned: CommitmentTx) extends Data
final case class DATA_WAIT_FOR_UPDATE_COMPLETE(ourParams: ChannelParams, theirParams: ChannelParams, previousCommitmentTxSigned: CommitmentTx, newCommitmentTxUnsigned: CommitmentTx) extends Data
@ -89,8 +94,7 @@ class Node(val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val anch
case Event(INPUT_NONE, _) =>
them = sender
val ourParams = ChannelParams(DEFAULT_delay, commitPubKey, finalPubKey, DEFAULT_minDepth, DEFAULT_commitmentFee)
//TODO : should be random
val ourRevocationHashPreimage = sha256_hash(1, 2, 3, 4)
val ourRevocationHashPreimage = randomsha256()
val ourRevocationHash = Crypto.sha256(ourRevocationHashPreimage)
them ! open_channel(ourParams.delay, ourRevocationHash, ourParams.commitKey, ourParams.finalKey, WONT_CREATE_ANCHOR, Some(ourParams.minDepth), ourParams.commitmentFee)
goto(OPEN_WAIT_FOR_OPEN_NOANCHOR) using DATA_OPEN_WAIT_FOR_OPEN_NOANCHOR(ourParams, ourRevocationHashPreimage)
@ -100,36 +104,39 @@ class Node(val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val anch
case Event(INPUT_NONE, anchorInput: AnchorInput) =>
them = sender
val ourParams = ChannelParams(DEFAULT_delay, commitPubKey, finalPubKey, DEFAULT_minDepth, DEFAULT_commitmentFee)
//TODO : should be random
val ourRevocationHashPreimage = sha256_hash(1, 2, 3, 4)
val ourRevocationHashPreimage = randomsha256()
val ourRevocationHash = Crypto.sha256(ourRevocationHashPreimage)
them ! open_channel(ourParams.delay, ourRevocationHash, ourParams.commitKey, ourParams.finalKey, WILL_CREATE_ANCHOR, Some(ourParams.minDepth), ourParams.commitmentFee)
goto(OPEN_WAIT_FOR_OPEN_WITHANCHOR) using DATA_OPEN_WAIT_FOR_OPEN_WITHANCHOR(ourParams, anchorInput, ourRevocationHashPreimage)
}
when(OPEN_WAIT_FOR_OPEN_NOANCHOR) {
case Event(open_channel(delay, revocationHash, commitKey, finalKey, WILL_CREATE_ANCHOR, minDepth, commitmentFee), DATA_OPEN_WAIT_FOR_OPEN_NOANCHOR(ourParams, ourRevocationPreimage)) =>
case Event(open_channel(delay, theirRevocationHash, commitKey, finalKey, WILL_CREATE_ANCHOR, minDepth, commitmentFee), DATA_OPEN_WAIT_FOR_OPEN_NOANCHOR(ourParams, ourRevocationPreimage)) =>
val theirParams = ChannelParams(delay, commitKey, finalKey, minDepth.get, commitmentFee)
goto(OPEN_WAIT_FOR_ANCHOR) using DATA_OPEN_WAIT_FOR_ANCHOR(ourParams, theirParams, ourRevocationPreimage, revocationHash)
goto(OPEN_WAIT_FOR_ANCHOR) using DATA_OPEN_WAIT_FOR_ANCHOR(ourParams, theirParams, ourRevocationPreimage, theirRevocationHash)
}
when(OPEN_WAIT_FOR_OPEN_WITHANCHOR) {
case Event(open_channel(delay, revocationHash, commitKey, finalKey, WONT_CREATE_ANCHOR, minDepth, commitmentFee), DATA_OPEN_WAIT_FOR_OPEN_WITHANCHOR(ourParams, anchorInput, ourRevocationHashPreimage)) =>
case Event(open_channel(delay, theirRevocationHash, commitKey, finalKey, WONT_CREATE_ANCHOR, minDepth, commitmentFee), DATA_OPEN_WAIT_FOR_OPEN_WITHANCHOR(ourParams, anchorInput, ourRevocationHashPreimage)) =>
val theirParams = ChannelParams(delay, commitKey, finalKey, minDepth.get, commitmentFee)
val anchorTx = makeAnchorTx(ourParams.commitKey, theirParams.commitKey, anchorInput.amount, anchorInput.previousTxOutput, anchorInput.signData)
//TODO : anchorOutputIndex might not always be zero if there are multiple outputs
val anchorOutputIndex = 0
// we fund the channel with the anchor tx, so the money is ours
val state = ChannelState(them = ChannelOneSide(0, 0, Seq()), us = ChannelOneSide(anchorInput.amount, 0, Seq()))
// we build our commitment tx, leaving it unsigned
val ourCommitTx = makeCommitTx(ourParams.finalKey, theirParams.finalKey, theirParams.delay, anchorTx.hash, anchorOutputIndex, theirRevocationHash, state)
// then we build their commitment tx and sign it
val state = ChannelState(ChannelOneSide(anchorInput.amount, 0, Seq()), ChannelOneSide(0, 0, Seq()))
val theirCommitTx = makeCommitTx(theirParams.finalKey, ourParams.finalKey, ourParams.delay, anchorTx.hash, anchorOutputIndex, Crypto.sha256(ourRevocationHashPreimage), state.copy(a = state.b, b = state.a))
val theirCommitTx = makeCommitTx(theirParams.finalKey, ourParams.finalKey, ourParams.delay, anchorTx.hash, anchorOutputIndex, Crypto.sha256(ourRevocationHashPreimage), state.reverse)
val ourSigForThem = bin2signature(Transaction.signInput(theirCommitTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey)))
them ! open_anchor(anchorTx.hash, anchorOutputIndex, anchorInput.amount, ourSigForThem)
goto(OPEN_WAIT_FOR_COMMIT_SIG) using DATA_OPEN_WAIT_FOR_COMMIT_SIG(ourParams, theirParams, anchorTx, anchorOutputIndex, ourRevocationHashPreimage, revocationHash)
goto(OPEN_WAIT_FOR_COMMIT_SIG) using DATA_OPEN_WAIT_FOR_COMMIT_SIG(ourParams, theirParams, anchorTx, anchorOutputIndex, CommitmentTx(ourCommitTx, state, ourRevocationHashPreimage, theirRevocationHash))
}
when(OPEN_WAIT_FOR_ANCHOR) {
case Event(open_anchor(anchorTxid, anchorOutputIndex, anchorAmount, theirSig), DATA_OPEN_WAIT_FOR_ANCHOR(ourParams, theirParams, ourRevocationHashPreimage, theirRevocationHash)) =>
val state = ChannelState(ChannelOneSide(anchorAmount, 0, Seq()), ChannelOneSide(0, 0, Seq()))
// they fund the channel with their anchor tx, so the money is theirs
val state = ChannelState(them = ChannelOneSide(anchorAmount, 0, Seq()), us = ChannelOneSide(0, 0, Seq()))
// we build our commitment tx, sign it and check that it is spendable using the counterparty's sig
val ourCommitTx = makeCommitTx(ourParams.finalKey, theirParams.finalKey, theirParams.delay, anchorTxid, anchorOutputIndex, Crypto.sha256(ourRevocationHashPreimage), state)
// TODO : Transaction.sign(...) should handle multisig
@ -138,24 +145,22 @@ class Node(val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val anch
val ok = Try(Transaction.correctlySpends(signedCommitTx, Map(OutPoint(anchorTxid, anchorOutputIndex) -> multiSig2of2(ourParams.commitKey, theirParams.commitKey)), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)).isSuccess
// TODO : return Error and close channel if !ok
// then we build their commitment tx and sign it
val theirCommitTx = makeCommitTx(theirParams.finalKey, ourParams.finalKey, ourParams.delay, anchorTxid, anchorOutputIndex, theirRevocationHash, state.copy(a = state.b, b = state.a))
val theirCommitTx = makeCommitTx(theirParams.finalKey, ourParams.finalKey, ourParams.delay, anchorTxid, anchorOutputIndex, theirRevocationHash, state.reverse)
val ourSigForThem = bin2signature(Transaction.signInput(theirCommitTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey)))
them ! open_commit_sig(ourSigForThem)
// TODO : register for confirmations of anchor tx on the bitcoin network
goto(OPEN_WAITING_THEIRANCHOR) using DATA_OPEN_WAITING(ourParams, theirParams, CommitmentTx(signedCommitTx, ourRevocationHashPreimage), false)
goto(OPEN_WAITING_THEIRANCHOR) using DATA_OPEN_WAITING(ourParams, theirParams, CommitmentTx(signedCommitTx, state, ourRevocationHashPreimage, theirRevocationHash), false)
}
when(OPEN_WAIT_FOR_COMMIT_SIG) {
case Event(open_commit_sig(theirSig), DATA_OPEN_WAIT_FOR_COMMIT_SIG(ourParams, theirParams, anchorTx, anchorOutputIndex, ourRevocationHashPreimage, theirRevocationHash)) =>
val state = ChannelState(ChannelOneSide(anchorTx.txOut(0).amount, 0, Seq()), ChannelOneSide(0, 0, Seq()))
case Event(open_commit_sig(theirSig), DATA_OPEN_WAIT_FOR_COMMIT_SIG(ourParams, theirParams, anchorTx, anchorOutputIndex, newCommitTx)) =>
// we build our commitment tx, sign it and check that it is spendable using the counterparty's sig
val ourCommitTx = makeCommitTx(ourParams.finalKey, theirParams.finalKey, theirParams.delay, anchorTx.hash, anchorOutputIndex, Crypto.sha256(ourRevocationHashPreimage), state)
val ourSig = Transaction.signInput(ourCommitTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey))
val signedCommitTx = ourCommitTx.updateSigScript(0, sigScript2of2(theirSig, ourSig, theirParams.commitKey, ourParams.commitKey))
val ourSig = Transaction.signInput(newCommitTx.tx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey))
val signedCommitTx = newCommitTx.tx.updateSigScript(0, sigScript2of2(theirSig, ourSig, theirParams.commitKey, ourParams.commitKey))
val ok = Try(Transaction.correctlySpends(signedCommitTx, Map(OutPoint(anchorTx.hash, anchorOutputIndex) -> multiSig2of2(ourParams.commitKey, theirParams.commitKey)), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)).isSuccess
// TODO : return Error and close channel if !ok
log.info(s"publishing anchor tx ${new BinaryData(Transaction.write(anchorTx))}")
goto(OPEN_WAITING_OURANCHOR) using DATA_OPEN_WAITING(ourParams, theirParams, CommitmentTx(ourCommitTx, ourRevocationHashPreimage), false)
goto(OPEN_WAITING_OURANCHOR) using DATA_OPEN_WAITING(ourParams, theirParams, newCommitTx.copy(tx = signedCommitTx), false)
}
when(OPEN_WAITING_THEIRANCHOR) {
@ -204,86 +209,108 @@ class Node(val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val anch
}
when(NORMAL) {
case Event(CMD_SEND_UPDATE, DATA_NORMAL(ourParams, theirParams, previousCommitmentTx)) =>
// TODO : should be random
val ourRevocationHashPreimage = sha256_hash(1, 2, 3, 4)
case Event(CMD_SEND_UPDATE, DATA_NORMAL(ourParams, theirParams, p@CommitmentTx(previousCommitmentTx, previousState, _, _))) =>
val (newState, delta) = previousState.fold
val ourRevocationHashPreimage = randomsha256()
val ourRevocationHash = Crypto.sha256(ourRevocationHashPreimage)
// TODO : delta should be computed
val delta = 5000000
// TODO : state is wrong
val state = ChannelState(ChannelOneSide(delta, 0, Seq()), ChannelOneSide(0, 0, Seq()))
// we build our side of the new commitment tx
val ourCommitTx = makeCommitTx(previousCommitmentTx.tx.txIn, ourParams.finalKey, theirParams.finalKey, theirParams.delay, Crypto.sha256(ourRevocationHashPreimage), state)
them ! update(ourRevocationHash, delta)
goto(WAIT_FOR_UPDATE_ACCEPT) using DATA_WAIT_FOR_UPDATE_ACCEPT(ourParams, theirParams, previousCommitmentTx, CommitmentTx(ourCommitTx, ourRevocationHashPreimage))
goto(WAIT_FOR_UPDATE_ACCEPT) using DATA_WAIT_FOR_UPDATE_ACCEPT(ourParams, theirParams, p, UpdateProposal(newState, ourRevocationHashPreimage))
case Event(update(theirRevocationHash, delta), DATA_NORMAL(ourParams, theirParams, previousCommitmentTx)) =>
// TODO : we should check the delta
// TODO : should be random
val ourRevocationHashPreimage = sha256_hash(1, 2, 3, 4)
case Event(update(theirRevocationHash, theirDelta), DATA_NORMAL(ourParams, theirParams, p@CommitmentTx(previousCommitmentTx, previousState, _, _))) =>
val (newState, delta) = previousState.fold
assert(delta == -theirDelta, s"delta mismatch, ours=$delta theirs=$theirDelta")
val ourRevocationHashPreimage = randomsha256()
val ourRevocationHash = Crypto.sha256(ourRevocationHashPreimage)
// TODO : state is wrong
val state = ChannelState(ChannelOneSide(delta, 0, Seq()), ChannelOneSide(0, 0, Seq()))
// we build our side of the new commitment tx
val ourCommitTx = makeCommitTx(previousCommitmentTx.tx.txIn, ourParams.finalKey, theirParams.finalKey, theirParams.delay, Crypto.sha256(ourRevocationHashPreimage), state)
val ourCommitTx = makeCommitTx(previousCommitmentTx.txIn, ourParams.finalKey, theirParams.finalKey, theirParams.delay, Crypto.sha256(ourRevocationHashPreimage), newState)
// we build their commitment tx and sign it
val theirCommitTx = makeCommitTx(previousCommitmentTx.tx.txIn, theirParams.finalKey, ourParams.finalKey, ourParams.delay, theirRevocationHash, state.copy(a = state.b, b = state.a))
val theirCommitTx = makeCommitTx(previousCommitmentTx.txIn, theirParams.finalKey, ourParams.finalKey, ourParams.delay, theirRevocationHash, newState.reverse)
val ourSigForThem = bin2signature(Transaction.signInput(theirCommitTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey)))
them ! update_accept(ourSigForThem, ourRevocationHash)
goto(WAIT_FOR_UPDATE_SIG) using DATA_WAIT_FOR_UPDATE_SIG(ourParams, theirParams, previousCommitmentTx, CommitmentTx(ourCommitTx, ourRevocationHashPreimage))
goto(WAIT_FOR_UPDATE_SIG) using DATA_WAIT_FOR_UPDATE_SIG(ourParams, theirParams, p, CommitmentTx(ourCommitTx, newState, ourRevocationHashPreimage, theirRevocationHash))
case Event(update_add_htlc(revocationHash, amount, rHash, expiry), d: DATA_NORMAL) =>
// TODO : we should probably check that we can reach the next node (which can be the final payee) using routing info that will be provided in the msg
// TODO : should be random
val ourRevocationHashPreimage = sha256_hash(1, 2, 3, 4)
case Event(CMD_SEND_HTLC_UPDATE(amount, rHash, expiry), DATA_NORMAL(ourParams, theirParams, p@CommitmentTx(previousCommitmentTx, previousState, _, _))) =>
val ourRevocationHashPreimage = randomsha256()
val ourRevocationHash = Crypto.sha256(ourRevocationHashPreimage)
// TODO : build htlc output
val ourCommitTx = null
// TODO : build their commitment tx and sign it
val theirCommitTx = null
val ourSigForThem = null
// TODO : reply with send update_accept(sig, revocationHash) ; note that this tx increases our balance so there is no risk in signing it
val htlc = update_add_htlc(ourRevocationHash, amount, rHash, expiry)
val newState = previousState.copy(them = previousState.them.copy(htlcs = previousState.them.htlcs :+ htlc))
them ! htlc
goto(WAIT_FOR_HTLC_ACCEPT) using DATA_WAIT_FOR_HTLC_ACCEPT(ourParams, theirParams, p, UpdateProposal(newState, ourRevocationHashPreimage))
case Event(m@update_add_htlc(theirRevocationHash, amount, rHash, expiry), DATA_NORMAL(ourParams, theirParams, p@CommitmentTx(previousCommitmentTx, previousState, _, _))) =>
// TODO : we should probably check that we can reach the next node (which can be the final payee) using routing info that will be provided in the msg
// the receiver of this message will have its balance increased : it is the receiver of the htlc
val newState = previousState.copy(us = previousState.us.copy(htlcs = previousState.us.htlcs :+ m))
val ourRevocationHashPreimage = randomsha256()
val ourRevocationHash = Crypto.sha256(ourRevocationHashPreimage)
// we build our side of the new commitment tx
val ourCommitTx = makeCommitTx(previousCommitmentTx.txIn, ourParams.finalKey, theirParams.finalKey, theirParams.delay, Crypto.sha256(ourRevocationHashPreimage), newState)
// we build their commitment tx and sign it
val theirCommitTx = makeCommitTx(previousCommitmentTx.txIn, theirParams.finalKey, ourParams.finalKey, ourParams.delay, theirRevocationHash, newState.reverse)
val ourSigForThem = bin2signature(Transaction.signInput(theirCommitTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey)))
// note that this tx increases our balance so there is no risk in signing it
them ! update_accept(ourSigForThem, ourRevocationHash)
// TODO : send update_add_htlc(revocationHash, amount, rHash, expiry - 1) *to the next node*
goto(WAIT_FOR_UPDATE_SIG)
goto(WAIT_FOR_UPDATE_SIG) using DATA_WAIT_FOR_UPDATE_SIG(ourParams, theirParams, p, CommitmentTx(ourCommitTx, newState, ourRevocationHashPreimage, theirRevocationHash))
case Event(update_complete_htlc(revocationHash, r), d: DATA_NORMAL) =>
// TODO : check that r hashes to the rHash we have
// TODO : should be random
val ourRevocationHashPreimage = sha256_hash(1, 2, 3, 4)
case Event(CMD_SEND_HTLC_COMPLETE(r), DATA_NORMAL(ourParams, theirParams, p@CommitmentTx(previousCommitmentTx, previousState, _, _))) =>
val htlc = previousState.them.htlcs.find(_.rHash == bin2sha256(Crypto.sha256(r))).getOrElse(throw new RuntimeException(s"could not find corresponding htlc (r=$r)"))
val newState = previousState.copy(them = previousState.them.copy(htlcs = previousState.them.htlcs.filterNot(_ == htlc)))
val ourRevocationHashPreimage = randomsha256()
val ourRevocationHash = Crypto.sha256(ourRevocationHashPreimage)
// TODO : remove htlc and update balance in the channel
val ourCommitTx = null
// TODO : build their commitment tx and sign it
val theirCommitTx = null
val ourSigForThem = null
them ! update_accept(ourSigForThem, ourRevocationHash)
goto(WAIT_FOR_UPDATE_SIG)
them ! update_complete_htlc(ourRevocationHash, r)
goto(WAIT_FOR_HTLC_ACCEPT) using DATA_WAIT_FOR_HTLC_ACCEPT(ourParams, theirParams, p, UpdateProposal(newState, ourRevocationHashPreimage))
case Event(close_channel(theirSig, closeFee), d: DATA_NORMAL) =>
// TODO generate a standard multisig tx with one output to their final key and one output to our final key
val ourSig = signature(1, 2, 3, 4, 5, 6, 7, 8)
them ! close_channel_complete(ourSig)
case Event(update_complete_htlc(theirRevocationHash, r), DATA_NORMAL(ourParams, theirParams, p@CommitmentTx(previousCommitmentTx, previousState, _, _))) =>
val htlc = previousState.us.htlcs.find(_.rHash == bin2sha256(Crypto.sha256(r))).getOrElse(throw new RuntimeException(s"could not find corresponding htlc (r=$r)"))
val newState = previousState.copy(us = previousState.us.copy(htlcs = previousState.us.htlcs.filterNot(_ == htlc)))
val ourRevocationHashPreimage = randomsha256()
val ourRevocationHash = Crypto.sha256(ourRevocationHashPreimage)
// we build our side of the new commitment tx
val ourCommitTx = makeCommitTx(previousCommitmentTx.txIn, ourParams.finalKey, theirParams.finalKey, theirParams.delay, Crypto.sha256(ourRevocationHashPreimage), newState)
// we build their commitment tx and sign it
val theirCommitTx = makeCommitTx(previousCommitmentTx.txIn, theirParams.finalKey, ourParams.finalKey, ourParams.delay, theirRevocationHash, newState.reverse)
val ourSigForThem = bin2signature(Transaction.signInput(theirCommitTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey)))
them ! update_accept(ourSigForThem, ourRevocationHash)
goto(WAIT_FOR_UPDATE_SIG) using DATA_WAIT_FOR_UPDATE_SIG(ourParams, theirParams, p, CommitmentTx(ourCommitTx, newState, ourRevocationHashPreimage, theirRevocationHash))
case Event(close_channel(theirSig, closeFee), DATA_NORMAL(ourParams, theirParams, CommitmentTx(commitmentTx, state, _, _))) =>
val finalTx = makeFinalTx(commitmentTx.txIn, ourParams.finalKey, theirParams.finalKey, state)
val ourSigForThem = bin2signature(Transaction.signInput(finalTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey)))
them ! close_channel_complete(ourSigForThem)
goto(WAIT_FOR_CLOSE_ACK)
}
when(WAIT_FOR_UPDATE_ACCEPT) {
case Event(update_accept(theirSig, theirRevocationHash), DATA_WAIT_FOR_UPDATE_ACCEPT(ourParams, theirParams, previousCommitmentTx, newCommitmentTx)) =>
// counterparty replied with the signature for its new commitment tx, and revocationPreimage
// TODO : check that revocationPreimage indeed hashes to the revocationHash they gave us previously
case Event(update_accept(theirSig, theirRevocationHash), DATA_WAIT_FOR_UPDATE_ACCEPT(ourParams, theirParams, previous, UpdateProposal(newState, ourRevocationHashPreimage))) =>
// counterparty replied with the signature for the new commitment tx
// we build our commitment tx, sign it and check that it is spendable using the counterparty's sig
val ourSig = Transaction.signInput(newCommitmentTx.tx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey))
val signedCommitTx = newCommitmentTx.tx.updateSigScript(0, sigScript2of2(theirSig, ourSig, theirParams.commitKey, ourParams.commitKey))
val ok = Try(Transaction.correctlySpends(signedCommitTx, Map(previousCommitmentTx.tx.txIn(0).outPoint -> multiSig2of2(ourParams.commitKey, theirParams.commitKey)), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)).isSuccess
val newCommitmentTx = makeCommitTx(previous.tx.txIn, ourParams.finalKey, theirParams.finalKey, theirParams.delay, Crypto.sha256(ourRevocationHashPreimage), newState)
val ourSig = Transaction.signInput(newCommitmentTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey))
val signedCommitTx = newCommitmentTx.updateSigScript(0, sigScript2of2(theirSig, ourSig, theirParams.commitKey, ourParams.commitKey))
val ok = Try(Transaction.correctlySpends(signedCommitTx, Map(previous.tx.txIn(0).outPoint -> multiSig2of2(ourParams.commitKey, theirParams.commitKey)), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)).isSuccess
// TODO : return Error and close channel if !ok
them ! update_signature(ourSig, previousCommitmentTx.ourRevocationPreimage)
goto(WAIT_FOR_UPDATE_COMPLETE) using DATA_WAIT_FOR_UPDATE_COMPLETE(ourParams, theirParams, previousCommitmentTx, newCommitmentTx.copy(tx = signedCommitTx))
them ! update_signature(ourSig, previous.ourRevocationPreimage)
goto(WAIT_FOR_UPDATE_COMPLETE) using DATA_WAIT_FOR_UPDATE_COMPLETE(ourParams, theirParams, previous, CommitmentTx(signedCommitTx, newState, ourRevocationHashPreimage, theirRevocationHash))
}
when(WAIT_FOR_HTLC_ACCEPT) {
case Event(update_accept(theirSig, theirRevocationHash), DATA_WAIT_FOR_HTLC_ACCEPT(ourParams, theirParams, previous, UpdateProposal(newState, ourRevocationHashPreimage))) =>
// counterparty replied with the signature for the new commitment tx
// we build our commitment tx, sign it and check that it is spendable using the counterparty's sig
val newCommitmentTx = makeCommitTx(previous.tx.txIn, ourParams.finalKey, theirParams.finalKey, theirParams.delay, Crypto.sha256(ourRevocationHashPreimage), newState)
val ourSig = Transaction.signInput(newCommitmentTx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey))
val signedCommitTx = newCommitmentTx.updateSigScript(0, sigScript2of2(theirSig, ourSig, theirParams.commitKey, ourParams.commitKey))
val ok = Try(Transaction.correctlySpends(signedCommitTx, Map(previous.tx.txIn(0).outPoint -> multiSig2of2(ourParams.commitKey, theirParams.commitKey)), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)).isSuccess
// TODO : return Error and close channel if !ok
them ! update_signature(ourSig, previous.ourRevocationPreimage)
goto(WAIT_FOR_UPDATE_COMPLETE) using DATA_WAIT_FOR_UPDATE_COMPLETE(ourParams, theirParams, previous, CommitmentTx(signedCommitTx, newState, ourRevocationHashPreimage, theirRevocationHash))
}
when(WAIT_FOR_UPDATE_SIG) {
case Event(update_signature(theirSig, revocationPreimage), DATA_WAIT_FOR_UPDATE_SIG(ourParams, theirParams, previousCommitmentTx, newCommitmentTx)) =>
case Event(update_signature(theirSig, theirRevocationPreimage), DATA_WAIT_FOR_UPDATE_SIG(ourParams, theirParams, previousCommitmentTx, newCommitmentTx)) =>
// counterparty replied with the signature for its new commitment tx, and revocationPreimage
// TODO : check that revocationPreimage indeed hashes to the revocationHash they gave us previously
assert(new BinaryData(previousCommitmentTx.theirRevocationHash) == new BinaryData(Crypto.sha256(theirRevocationPreimage)), s"the revocation preimage they gave us is wrong! hash=${previousCommitmentTx.theirRevocationHash} preimage=$theirRevocationPreimage")
// we build our commitment tx, sign it and check that it is spendable using the counterparty's sig
val ourSig = Transaction.signInput(newCommitmentTx.tx, 0, multiSig2of2(ourParams.commitKey, theirParams.commitKey), SIGHASH_ALL, pubkey2bin(commitPrivKey))
val signedCommitTx = newCommitmentTx.tx.updateSigScript(0, sigScript2of2(theirSig, ourSig, theirParams.commitKey, ourParams.commitKey))
@ -295,7 +322,7 @@ class Node(val commitPrivKey: BinaryData, val finalPrivKey: BinaryData, val anch
when(WAIT_FOR_UPDATE_COMPLETE) {
case Event(update_complete(theirRevocationPreimage), DATA_WAIT_FOR_UPDATE_COMPLETE(ourParams, theirParams, previousCommitmentTx, newCommitmentTx)) => {
// TODO : check that revocationPreimage indeed hashes to the revocationHash they gave us previously
assert(new BinaryData(previousCommitmentTx.theirRevocationHash) == new BinaryData(Crypto.sha256(theirRevocationPreimage)), s"the revocation preimage they gave us is wrong! hash=${previousCommitmentTx.theirRevocationHash} preimage=$theirRevocationPreimage")
goto(NORMAL) using DATA_NORMAL(ourParams, theirParams, newCommitmentTx)
}
}

View file

@ -2,6 +2,7 @@ package fr.acinq
import java.io.{ByteArrayInputStream, ByteArrayOutputStream}
import java.math.BigInteger
import java.security.SecureRandom
import _root_.lightning._
import _root_.lightning.locktime.Locktime.{Blocks, Seconds}
@ -15,11 +16,37 @@ package lightning {
case class ChannelOneSide(pay: Long, fee: Long, htlcs: Seq[update_add_htlc])
case class ChannelState(a: ChannelOneSide, b: ChannelOneSide)
case class ChannelState(them: ChannelOneSide, us: ChannelOneSide) {
/**
* Because each party needs to be able to compute the other party's commitment tx
* @return the channel state as seen by the other party
*/
def reverse: ChannelState = this.copy(them = us, us = them)
/**
* Remove all pending htlc and compute a delta as seen by us
* @return the new channel state and the delta
*/
def fold: (ChannelState, Long) = {
val delta = us.htlcs.map(_.amount).sum - them.htlcs.map(_.amount).sum
val newState = this.copy(them = them.copy(pay = them.pay - delta, htlcs = Seq()), us = us.copy(pay = us.pay, htlcs = Seq()))
(newState, delta)
}
}
}
package object lightning {
val random = new SecureRandom()
// TODO : should generate them in a deterministic fashion ?
def randomsha256(): sha256_hash = {
val bytes = new Array[Byte](32)
random.nextBytes(bytes)
bin2sha256(bytes)
}
implicit def bin2sha256(in: BinaryData): sha256_hash = {
require(in.size == 32)
val bis = new ByteArrayInputStream(in)
@ -185,21 +212,41 @@ package object lightning {
version = 1,
txIn = inputs,
txOut = Seq(
TxOut(amount = channelState.a.pay, publicKeyScript = pay2sh(redeemScript)),
TxOut(amount = channelState.b.pay, publicKeyScript = pay2sh(OP_PUSHDATA(theirFinalKey) :: OP_CHECKSIG :: Nil))
TxOut(amount = channelState.them.pay, publicKeyScript = pay2sh(redeemScript)),
TxOut(amount = channelState.us.pay, publicKeyScript = pay2sh(OP_PUSHDATA(theirFinalKey) :: OP_CHECKSIG :: Nil))
),
lockTime = 0)
val sendOuts = channelState.a.htlcs.map(htlc => {
val sendOuts = channelState.them.htlcs.map(htlc => {
TxOut(htlc.amount, pay2sh(scriptPubKeyHtlcSend(ourFinalKey, theirFinalKey, htlc.amount, htlc.expiry, theirDelay, rhash, htlc.revocationHash)))
})
val receiveOuts = channelState.b.htlcs.map(htlc => {
val receiveOuts = channelState.us.htlcs.map(htlc => {
TxOut(htlc.amount, pay2sh(scriptPubKeyHtlcReceive(ourFinalKey, theirFinalKey, htlc.amount, htlc.expiry, theirDelay, rhash, htlc.revocationHash)))
})
val tx1 = tx.copy(txOut = tx.txOut ++ sendOuts ++ receiveOuts)
tx1
}
/**
* This is a simple tx with a multisig input and two pay2pk output
* @param inputs
* @param ourFinalKey
* @param theirFinalKey
* @param channelState
* @return
*/
def makeFinalTx(inputs: Seq[TxIn], ourFinalKey: BinaryData, theirFinalKey: BinaryData, channelState: ChannelState): Transaction = {
// TODO : is this the proper behaviour ?
assert(channelState.them.htlcs.isEmpty && channelState.us.htlcs.isEmpty, s"cannot close a channel with pending htlcs (not sure this is in the specs)")
Transaction(
version = 1,
txIn = inputs,
txOut = Seq(
TxOut(amount = channelState.them.pay, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(theirFinalKey) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil),
TxOut(amount = channelState.them.pay, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(ourFinalKey) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil)),
lockTime = 0)
}
def isFunder(o: open_channel): Boolean = o.anch == open_channel.anchor_offer.WILL_CREATE_ANCHOR
def initialFunding(a: open_channel, b: open_channel, anchor: open_anchor, fee: Long): ChannelState = {