mirror of
https://github.com/ACINQ/eclair.git
synced 2024-11-19 09:54:02 +01:00
upgrade to segwit
This commit is contained in:
parent
4bde8bf018
commit
7903e2b53f
@ -1,6 +1,6 @@
|
||||
package fr.acinq.eclair.blockchain
|
||||
|
||||
import fr.acinq.bitcoin.{BinaryData, BitcoinJsonRPCClient, JsonRPCError, Transaction, TxIn, TxOut}
|
||||
import fr.acinq.bitcoin.{BinaryData, BitcoinJsonRPCClient, JsonRPCError, Protocol, Satoshi, Transaction, TxIn, TxOut}
|
||||
import fr.acinq.eclair.channel
|
||||
import fr.acinq.eclair.channel.Scripts
|
||||
import org.bouncycastle.util.encoders.Hex
|
||||
@ -16,6 +16,13 @@ class ExtendedBitcoinClient(client: BitcoinJsonRPCClient) {
|
||||
|
||||
implicit val formats = org.json4s.DefaultFormats
|
||||
|
||||
// TODO: this will probably not be needed once segwit is merged into core
|
||||
val protocolVersion = Protocol.PROTOCOL_VERSION | Transaction.SERIALIZE_TRANSACTION_WITNESS
|
||||
|
||||
def tx2Hex(tx: Transaction): String = Hex.toHexString(Transaction.write(tx, protocolVersion))
|
||||
|
||||
def hex2tx(hex: String) : Transaction = Transaction.read(hex, protocolVersion)
|
||||
|
||||
def getTxConfirmations(txId: String)(implicit ec: ExecutionContext): Future[Option[Int]] =
|
||||
client.invoke("getrawtransaction", txId, 1) // we choose verbose output to get the number of confirmations
|
||||
.map(json => Some((json \ "confirmations").extract[Int]))
|
||||
@ -58,7 +65,7 @@ class ExtendedBitcoinClient(client: BitcoinJsonRPCClient) {
|
||||
}
|
||||
|
||||
def fundTransaction(tx: Transaction)(implicit ec: ExecutionContext): Future[FundTransactionResponse] =
|
||||
fundTransaction(Hex.toHexString(Transaction.write(tx)))
|
||||
fundTransaction(tx2Hex(tx))
|
||||
|
||||
case class SignTransactionResponse(tx: Transaction, complete: Boolean)
|
||||
|
||||
@ -70,7 +77,7 @@ class ExtendedBitcoinClient(client: BitcoinJsonRPCClient) {
|
||||
})
|
||||
|
||||
def signTransaction(tx: Transaction)(implicit ec: ExecutionContext): Future[SignTransactionResponse] =
|
||||
signTransaction(Hex.toHexString(Transaction.write(tx)))
|
||||
signTransaction(tx2Hex(tx))
|
||||
|
||||
def publishTransaction(hex: String)(implicit ec: ExecutionContext): Future[String] =
|
||||
client.invoke("sendrawtransaction", hex).map {
|
||||
@ -78,7 +85,7 @@ class ExtendedBitcoinClient(client: BitcoinJsonRPCClient) {
|
||||
}
|
||||
|
||||
def publishTransaction(tx: Transaction)(implicit ec: ExecutionContext): Future[String] =
|
||||
publishTransaction(Hex.toHexString(Transaction.write(tx)))
|
||||
publishTransaction(tx2Hex(tx))
|
||||
|
||||
// TODO : this is very dirty
|
||||
// we only check the memory pool and the last block, and throw an error if tx was not found
|
||||
@ -102,7 +109,7 @@ class ExtendedBitcoinClient(client: BitcoinJsonRPCClient) {
|
||||
|
||||
def makeAnchorTx(ourCommitPub: BinaryData, theirCommitPub: BinaryData, amount: Long)(implicit ec: ExecutionContext): Future[(Transaction, Int)] = {
|
||||
val anchorOutputScript = channel.Scripts.anchorPubkeyScript(ourCommitPub, theirCommitPub)
|
||||
val tx = Transaction(version = 1, txIn = Seq.empty[TxIn], txOut = TxOut(amount, anchorOutputScript) :: Nil, lockTime = 0)
|
||||
val tx = Transaction(version = 2, txIn = Seq.empty[TxIn], txOut = TxOut(Satoshi(amount), anchorOutputScript) :: Nil, lockTime = 0)
|
||||
val future = for {
|
||||
FundTransactionResponse(tx1, changepos, fee) <- fundTransaction(tx)
|
||||
SignTransactionResponse(anchorTx, true) <- signTransaction(tx1)
|
||||
|
@ -56,7 +56,7 @@ class PollingWatcher(client: ExtendedBitcoinClient)(implicit ec: ExecutionContex
|
||||
case Publish(tx) =>
|
||||
log.info(s"publishing tx $tx")
|
||||
client.publishTransaction(tx).onFailure {
|
||||
case t: Throwable => log.error(t, s"cannot publish tx ${Hex.toHexString(Transaction.write(tx))}")
|
||||
case t: Throwable => log.error(t, s"cannot publish tx ${Hex.toHexString(Transaction.write(tx, Protocol.PROTOCOL_VERSION | Transaction.SERIALIZE_TRANSACTION_WITNESS))}")
|
||||
}
|
||||
|
||||
case MakeAnchor(ourCommitPub, theirCommitPub, amount) =>
|
||||
|
@ -62,7 +62,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann
|
||||
|
||||
case Event((anchorTx: Transaction, anchorOutputIndex: Int), DATA_OPEN_WITH_ANCHOR_WAIT_FOR_ANCHOR(ourParams, theirParams, theirRevocationHash, theirNextRevocationHash)) =>
|
||||
log.info(s"anchor txid=${anchorTx.txid}")
|
||||
val amount = anchorTx.txOut(anchorOutputIndex).amount
|
||||
val amount = anchorTx.txOut(anchorOutputIndex).amount.toLong
|
||||
val spec = CommitmentSpec(Set.empty[Htlc], theirParams.initialFeeRate, amount * 1000, 0)
|
||||
val theirTx = makeTheirTx(ourParams, theirParams, TxIn(OutPoint(anchorTx.hash, anchorOutputIndex), Array.emptyByteArray, 0xffffffffL) :: Nil, theirRevocationHash, spec)
|
||||
val ourSig = sign(ourParams, theirParams, theirTx)
|
||||
@ -75,14 +75,16 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann
|
||||
when(OPEN_WAIT_FOR_ANCHOR) {
|
||||
case Event(open_anchor(anchorTxHash, anchorOutputIndex, anchorAmount, theirSig), DATA_OPEN_WAIT_FOR_ANCHOR(ourParams, theirParams, theirRevocationHash, theirNextRevocationHash)) =>
|
||||
val anchorTxid = anchorTxHash.reverse //see https://github.com/ElementsProject/lightning/issues/17
|
||||
val anchorOutput = TxOut(Satoshi(anchorAmount), publicKeyScript = Scripts.anchorPubkeyScript(ourParams.commitPubKey, theirParams.commitPubKey))
|
||||
|
||||
// they fund the channel with their anchor tx, so the money is theirs
|
||||
val spec = CommitmentSpec(Set.empty[Htlc], theirParams.initialFeeRate, 0, anchorAmount * 1000)
|
||||
val spec = CommitmentSpec(Set.empty[Htlc], theirParams.initialFeeRate, 0, anchorAmount * 1000)
|
||||
// we build our commitment tx, sign it and check that it is spendable using the counterparty's sig
|
||||
val ourRevocationHash = Crypto.sha256(ShaChain.shaChainFromSeed(ourParams.shaSeed, 0))
|
||||
val ourTx = makeOurTx(ourParams, theirParams, TxIn(OutPoint(anchorTxHash, anchorOutputIndex), Array.emptyByteArray, 0xffffffffL) :: Nil, ourRevocationHash, spec)
|
||||
val ourSig = sign(ourParams, theirParams, ourTx)
|
||||
val signedTx = addSigs(ourParams, theirParams, ourTx, ourSig, theirSig)
|
||||
checksig(ourParams, theirParams, signedTx) match {
|
||||
checksig(ourParams, theirParams, anchorOutput, signedTx) match {
|
||||
case false =>
|
||||
them ! error(Some("Bad signature"))
|
||||
goto(CLOSED)
|
||||
@ -90,7 +92,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann
|
||||
them ! open_commit_sig(ourSig)
|
||||
blockchain ! WatchConfirmed(self, anchorTxid, ourParams.minDepth, BITCOIN_ANCHOR_DEPTHOK)
|
||||
blockchain ! WatchSpent(self, anchorTxid, anchorOutputIndex, 0, BITCOIN_ANCHOR_SPENT)
|
||||
goto(OPEN_WAITING_THEIRANCHOR) using DATA_OPEN_WAITING(ourParams, theirParams, ShaChain.init, OurCommit(0, spec, signedTx), TheirCommit(0, spec, theirRevocationHash), theirNextRevocationHash, None)
|
||||
goto(OPEN_WAITING_THEIRANCHOR) using DATA_OPEN_WAITING(ourParams, theirParams, ShaChain.init, OurCommit(0, spec, signedTx), TheirCommit(0, spec, theirRevocationHash), theirNextRevocationHash, None, anchorOutput)
|
||||
}
|
||||
|
||||
case Event(CMD_CLOSE(_), _) => goto(CLOSED)
|
||||
@ -104,7 +106,8 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann
|
||||
val ourTx = makeOurTx(ourParams, theirParams, TxIn(OutPoint(anchorTx, anchorOutputIndex), Array.emptyByteArray, 0xffffffffL) :: Nil, ourRevocationHash, spec)
|
||||
val ourSig = sign(ourParams, theirParams, ourTx)
|
||||
val signedTx: Transaction = addSigs(ourParams, theirParams, ourTx, ourSig, theirSig)
|
||||
checksig(ourParams, theirParams, signedTx) match {
|
||||
val anchorOutput = anchorTx.txOut(anchorOutputIndex)
|
||||
checksig(ourParams, theirParams, anchorOutput, signedTx) match {
|
||||
case false =>
|
||||
them ! error(Some("Bad signature"))
|
||||
goto(CLOSED)
|
||||
@ -112,19 +115,19 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann
|
||||
blockchain ! WatchConfirmed(self, anchorTx.txid, ourParams.minDepth, BITCOIN_ANCHOR_DEPTHOK)
|
||||
blockchain ! WatchSpent(self, anchorTx.txid, anchorOutputIndex, 0, BITCOIN_ANCHOR_SPENT)
|
||||
blockchain ! Publish(anchorTx)
|
||||
goto(OPEN_WAITING_OURANCHOR) using DATA_OPEN_WAITING(ourParams, theirParams, ShaChain.init, OurCommit(0, spec, signedTx), commitment, theirNextRevocationHash, None)
|
||||
goto(OPEN_WAITING_OURANCHOR) using DATA_OPEN_WAITING(ourParams, theirParams, ShaChain.init, OurCommit(0, spec, signedTx), commitment, theirNextRevocationHash, None, anchorOutput)
|
||||
}
|
||||
|
||||
case Event(CMD_CLOSE(_), _) => goto(CLOSED)
|
||||
}
|
||||
|
||||
when(OPEN_WAITING_THEIRANCHOR) {
|
||||
case Event(BITCOIN_ANCHOR_DEPTHOK, d@DATA_OPEN_WAITING(ourParams, theirParams, shaChain, ourCommit, theirCommit, theirNextRevocationHash, deferred)) =>
|
||||
case Event(BITCOIN_ANCHOR_DEPTHOK, d@DATA_OPEN_WAITING(ourParams, theirParams, shaChain, ourCommit, theirCommit, theirNextRevocationHash, deferred, anchorOutput)) =>
|
||||
blockchain ! WatchLost(self, d.asInstanceOf[CurrentCommitment].anchorId, ourParams.minDepth, BITCOIN_ANCHOR_LOST)
|
||||
them ! open_complete(None)
|
||||
deferred.map(self ! _)
|
||||
//TODO htlcIdx should not be 0 when resuming connection
|
||||
goto(OPEN_WAIT_FOR_COMPLETE_THEIRANCHOR) using DATA_NORMAL(ourParams, theirParams, shaChain, 0, ourCommit, theirCommit, OurChanges(Nil, Nil), TheirChanges(Nil), Some(theirNextRevocationHash))
|
||||
goto(OPEN_WAIT_FOR_COMPLETE_THEIRANCHOR) using DATA_NORMAL(ourParams, theirParams, shaChain, 0, ourCommit, theirCommit, OurChanges(Nil, Nil), TheirChanges(Nil), Some(theirNextRevocationHash), anchorOutput)
|
||||
|
||||
case Event(msg@open_complete(blockId_opt), d: DATA_OPEN_WAITING) =>
|
||||
log.info(s"received their open_complete, deferring message")
|
||||
@ -157,12 +160,12 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann
|
||||
}
|
||||
|
||||
when(OPEN_WAITING_OURANCHOR) {
|
||||
case Event(BITCOIN_ANCHOR_DEPTHOK, d@DATA_OPEN_WAITING(ourParams, theirParams, shaChain, ourCommit, theirCommit, theirNextRevocationHash, deferred)) =>
|
||||
case Event(BITCOIN_ANCHOR_DEPTHOK, d@DATA_OPEN_WAITING(ourParams, theirParams, shaChain, ourCommit, theirCommit, theirNextRevocationHash, deferred, anchorOutput)) =>
|
||||
blockchain ! WatchLost(self, d.asInstanceOf[CurrentCommitment].anchorId, ourParams.minDepth, BITCOIN_ANCHOR_LOST)
|
||||
them ! open_complete(None)
|
||||
deferred.map(self ! _)
|
||||
//TODO htlcIdx should not be 0 when resuming connection
|
||||
goto(OPEN_WAIT_FOR_COMPLETE_OURANCHOR) using DATA_NORMAL(ourParams, theirParams, shaChain, 0, ourCommit, theirCommit, OurChanges(Nil, Nil), TheirChanges(Nil), Some(theirNextRevocationHash))
|
||||
goto(OPEN_WAIT_FOR_COMPLETE_OURANCHOR) using DATA_NORMAL(ourParams, theirParams, shaChain, 0, ourCommit, theirCommit, OurChanges(Nil, Nil), TheirChanges(Nil), Some(theirNextRevocationHash), anchorOutput)
|
||||
|
||||
case Event(msg@open_complete(blockId_opt), d: DATA_OPEN_WAITING) =>
|
||||
log.info(s"received their open_complete, deferring message")
|
||||
@ -259,7 +262,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann
|
||||
|
||||
when(NORMAL) {
|
||||
|
||||
case Event(CMD_ADD_HTLC(amount, rHash, expiry, nodeIds, origin), d@DATA_NORMAL(_, _, _, htlcIdx, _, _, ourChanges, _, _)) =>
|
||||
case Event(CMD_ADD_HTLC(amount, rHash, expiry, nodeIds, origin), d@DATA_NORMAL(_, _, _, htlcIdx, _, _, ourChanges, _, _, _)) =>
|
||||
// TODO: should we take pending htlcs into account?
|
||||
// TODO: assert(commitment.state.commit_changes(staged).us.pay_msat >= amount, "insufficient funds!")
|
||||
// TODO: nodeIds are ignored
|
||||
@ -267,13 +270,13 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann
|
||||
them ! htlc
|
||||
stay using d.copy(htlcIdx = htlc.id, ourChanges = ourChanges.copy(proposed = ourChanges.proposed :+ htlc))
|
||||
|
||||
case Event(htlc@update_add_htlc(htlcId, amount, rHash, expiry, nodeIds), d@DATA_NORMAL(_, _, _, _, _, _, _, theirChanges, _)) =>
|
||||
case Event(htlc@update_add_htlc(htlcId, amount, rHash, expiry, nodeIds), d@DATA_NORMAL(_, _, _, _, _, _, _, theirChanges, _, _)) =>
|
||||
// TODO: should we take pending htlcs into account?
|
||||
// assert(commitment.state.commit_changes(staged).them.pay_msat >= amount, "insufficient funds!") // TODO : we should fail the channel
|
||||
// TODO: nodeIds are ignored
|
||||
stay using d.copy(theirChanges = theirChanges.copy(proposed = theirChanges.proposed :+ htlc))
|
||||
|
||||
case Event(CMD_FULFILL_HTLC(id, r), d@DATA_NORMAL(_, _, _, _, _, theirCommit, ourChanges, _, _)) =>
|
||||
case Event(CMD_FULFILL_HTLC(id, r), d@DATA_NORMAL(_, _, _, _, _, theirCommit, ourChanges, _, _, _)) =>
|
||||
theirCommit.spec.htlcs.find(h => h.direction == IN && h.id == id) match {
|
||||
case Some(htlc) if htlc.rHash == bin2sha256(sha256(r)) =>
|
||||
val fulfill = update_fulfill_htlc(id, r)
|
||||
@ -283,7 +286,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann
|
||||
case None => throw new RuntimeException(s"unknown htlc id=$id")
|
||||
}
|
||||
|
||||
case Event(fulfill@update_fulfill_htlc(id, r), d@DATA_NORMAL(_, _, _, _, ourCommit, _, _, theirChanges, _)) =>
|
||||
case Event(fulfill@update_fulfill_htlc(id, r), d@DATA_NORMAL(_, _, _, _, ourCommit, _, _, theirChanges, _, _)) =>
|
||||
ourCommit.spec.htlcs.find(h => h.direction == OUT && h.id == id) match {
|
||||
case Some(htlc) if htlc.rHash == bin2sha256(sha256(r)) =>
|
||||
stay using d.copy(theirChanges = theirChanges.copy(proposed = theirChanges.proposed :+ fulfill))
|
||||
@ -291,7 +294,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann
|
||||
case None => throw new RuntimeException(s"unknown htlc id=$id") // TODO : we should fail the channel
|
||||
}
|
||||
|
||||
case Event(CMD_FAIL_HTLC(id, reason), d@DATA_NORMAL(_, _, _, _, _, theirCommit, ourChanges, _, _)) =>
|
||||
case Event(CMD_FAIL_HTLC(id, reason), d@DATA_NORMAL(_, _, _, _, _, theirCommit, ourChanges, _, _, _)) =>
|
||||
theirCommit.spec.htlcs.find(h => h.direction == IN && h.id == id) match {
|
||||
case Some(htlc) =>
|
||||
val fail = update_fail_htlc(id, fail_reason(ByteString.copyFromUtf8(reason)))
|
||||
@ -300,14 +303,14 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann
|
||||
case None => throw new RuntimeException(s"unknown htlc id=$id")
|
||||
}
|
||||
|
||||
case Event(fail@update_fail_htlc(id, reason), d@DATA_NORMAL(_, _, _, _, ourCommit, _, _, theirChanges, _)) =>
|
||||
case Event(fail@update_fail_htlc(id, reason), d@DATA_NORMAL(_, _, _, _, ourCommit, _, _, theirChanges, _, _)) =>
|
||||
ourCommit.spec.htlcs.find(h => h.direction == OUT && h.id == id) match {
|
||||
case Some(htlc) =>
|
||||
stay using d.copy(theirChanges = theirChanges.copy(proposed = theirChanges.proposed :+ fail))
|
||||
case None => throw new RuntimeException(s"unknown htlc id=$id") // TODO : we should fail the channel
|
||||
}
|
||||
|
||||
case Event(CMD_SIGN, d@DATA_NORMAL(ourParams, theirParams, _, _, ourCommit, theirCommit, ourChanges, theirChanges, theirNextRevocationHash_opt)) =>
|
||||
case Event(CMD_SIGN, d@DATA_NORMAL(ourParams, theirParams, _, _, ourCommit, theirCommit, ourChanges, theirChanges, theirNextRevocationHash_opt, anchorOutput)) =>
|
||||
theirNextRevocationHash_opt match {
|
||||
case Some(theirNextRevocationHash) =>
|
||||
val spec = reduce(theirCommit.spec, Nil, ourChanges.proposed)
|
||||
@ -318,14 +321,14 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann
|
||||
case None => throw new RuntimeException(s"cannot send two update_commit in a row (must wait for revocation)")
|
||||
}
|
||||
|
||||
case Event(msg@update_commit(theirSig), d@DATA_NORMAL(ourParams, theirParams, shaChain, _, ourCommit, theirCommit, _, theirChanges, _)) =>
|
||||
case Event(msg@update_commit(theirSig), d@DATA_NORMAL(ourParams, theirParams, shaChain, _, ourCommit, theirCommit, _, theirChanges, _, anchorOutput)) =>
|
||||
val spec = reduce(ourCommit.spec, theirChanges.proposed, Nil)
|
||||
val ourRevocationPreimage = ShaChain.shaChainFromSeed(ourParams.shaSeed, ourCommit.index)
|
||||
val ourRevocationHash = Crypto.sha256(ourRevocationPreimage)
|
||||
val ourTx = makeOurTx(ourParams, theirParams, ourCommit.publishableTx.txIn, ourRevocationHash, spec)
|
||||
val ourSig = sign(ourParams, theirParams, ourTx)
|
||||
val signedTx = addSigs(ourParams, theirParams, ourTx, ourSig, theirSig)
|
||||
checksig(ourParams, theirParams, ourTx) match {
|
||||
checksig(ourParams, theirParams, anchorOutput, ourTx) match {
|
||||
case false =>
|
||||
them ! error(Some("Bad signature"))
|
||||
publish_ourcommit(ourCommit)
|
||||
|
@ -26,7 +26,7 @@ case class ChannelOneSide(pay_msat: Long, fee_msat: Long, htlcs_received: Seq[Ht
|
||||
val funds = pay_msat + fee_msat
|
||||
}
|
||||
|
||||
case class ChannelState(us: ChannelOneSide, them: ChannelOneSide) {
|
||||
case class ChannelState(us: ChannelOneSide, them: ChannelOneSide, feeRate: Long) {
|
||||
/**
|
||||
* Because each party needs to be able to compute the other party's commitment tx
|
||||
*
|
||||
@ -116,6 +116,37 @@ case class ChannelState(us: ChannelOneSide, them: ChannelOneSide) {
|
||||
}
|
||||
|
||||
object ChannelState {
|
||||
/**
|
||||
*
|
||||
* A node MUST use the formula 338 + 32 bytes for every non-dust HTLC as the bytecount for calculating commitment
|
||||
* transaction fees. Note that the fee requirement is unchanged, even if the elimination of dust HTLC outputs
|
||||
* has caused a non-zero fee already.
|
||||
* The fee for a transaction MUST be calculated by multiplying this bytecount by the fee rate, dividing by 1000
|
||||
* and truncating (rounding down) the result to an even number of satoshis.
|
||||
*
|
||||
* @param feeRate fee rate in Satoshi/Kb
|
||||
* @param numberOfHtlcs number of (non-dust) HTLCs to be included in the commit tx
|
||||
* @return the fee in Satoshis for a commit tx with 'numberOfHtlcs' HTLCs
|
||||
*/
|
||||
def computeFee(feeRate: Long, numberOfHtlcs: Int) : Long = {
|
||||
Math.floorDiv((338 + 32 * numberOfHtlcs) * feeRate, 2000) * 2
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param anchorAmount anchor amount in Satoshis
|
||||
* @param feeRate fee rate in Satoshis/Kb
|
||||
* @return a ChannelState instance where 'us' is funder (i.e. provided the anchor).
|
||||
*/
|
||||
def initialFunding(anchorAmount: Long, feeRate: Long) : ChannelState = {
|
||||
val fee = computeFee(feeRate, 0)
|
||||
val pay_msat = (anchorAmount - fee) * 1000
|
||||
val fee_msat = fee * 1000
|
||||
val us = ChannelOneSide(pay_msat, fee_msat, Nil)
|
||||
val them = ChannelOneSide(0, 0, Nil)
|
||||
ChannelState(us, them, feeRate)
|
||||
}
|
||||
|
||||
def adjust_fees(funder: ChannelOneSide, nonfunder: ChannelOneSide, fee: Long): (ChannelOneSide, ChannelOneSide) = {
|
||||
val nonfunder_fee = Math.min(fee - fee / 2, nonfunder.funds)
|
||||
val funder_fee = fee - nonfunder_fee
|
||||
|
@ -1,7 +1,7 @@
|
||||
package fr.acinq.eclair.channel
|
||||
|
||||
import com.trueaccord.scalapb.GeneratedMessage
|
||||
import fr.acinq.bitcoin.{BinaryData, Crypto, Transaction}
|
||||
import fr.acinq.bitcoin.{BinaryData, Crypto, Transaction, TxOut}
|
||||
import fr.acinq.eclair.crypto.ShaChain
|
||||
import lightning.{locktime, open_complete, sha256_hash}
|
||||
|
||||
@ -132,13 +132,14 @@ final case class DATA_OPEN_WAIT_FOR_OPEN (ourParams: OurChannelPara
|
||||
final case class DATA_OPEN_WITH_ANCHOR_WAIT_FOR_ANCHOR(ourParams: OurChannelParams, theirParams: TheirChannelParams, theirRevocationHash: BinaryData, theirNextRevocationHash: sha256_hash) extends Data
|
||||
final case class DATA_OPEN_WAIT_FOR_ANCHOR (ourParams: OurChannelParams, theirParams: TheirChannelParams, theirRevocationHash: sha256_hash, theirNextRevocationHash: sha256_hash) extends Data
|
||||
final case class DATA_OPEN_WAIT_FOR_COMMIT_SIG (ourParams: OurChannelParams, theirParams: TheirChannelParams, anchorTx: Transaction, anchorOutputIndex: Int, initialCommitment: TheirCommit, theirNextRevocationHash: sha256_hash) extends Data
|
||||
final case class DATA_OPEN_WAITING (ourParams: OurChannelParams, theirParams: TheirChannelParams, shaChain: ShaChain, ourCommit: OurCommit, theirCommit: TheirCommit, theirNextRevocationHash: sha256_hash, deferred: Option[open_complete]) extends Data with CurrentCommitment
|
||||
final case class DATA_OPEN_WAITING (ourParams: OurChannelParams, theirParams: TheirChannelParams, shaChain: ShaChain, ourCommit: OurCommit, theirCommit: TheirCommit, theirNextRevocationHash: sha256_hash, deferred: Option[open_complete], anchorOutput: TxOut) extends Data with CurrentCommitment
|
||||
final case class DATA_NORMAL (ourParams: OurChannelParams, theirParams: TheirChannelParams, shaChain: ShaChain, htlcIdx: Long,
|
||||
ourCommit: OurCommit,
|
||||
theirCommit: TheirCommit,
|
||||
ourChanges: OurChanges,
|
||||
theirChanges: TheirChanges,
|
||||
theirNextRevocationHash: Option[sha256_hash]) extends Data with CurrentCommitment
|
||||
theirNextRevocationHash: Option[sha256_hash],
|
||||
anchorOutput: TxOut) extends Data with CurrentCommitment
|
||||
|
||||
object TypeDefs {
|
||||
type Change = GeneratedMessage
|
||||
|
@ -56,8 +56,8 @@ object Helpers {
|
||||
tx.updateSigScript(0, sigScript2of2(theirSig, ourSig, theirParams.commitPubKey, ourParams.commitPubKey))
|
||||
}
|
||||
|
||||
def checksig(ourParams: OurChannelParams, theirParams: TheirChannelParams, tx: Transaction): Boolean =
|
||||
Try(Transaction.correctlySpends(tx, Map(tx.txIn(0).outPoint -> anchorPubkeyScript(ourParams.commitPubKey, theirParams.commitPubKey)), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)).isSuccess
|
||||
def checksig(ourParams: OurChannelParams, theirParams: TheirChannelParams, anchorOutput: TxOut, tx: Transaction): Boolean =
|
||||
Try(Transaction.correctlySpends(tx, Map(tx.txIn(0).outPoint -> anchorOutput), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)).isSuccess
|
||||
|
||||
def isMutualClose(tx: Transaction, ourParams: OurChannelParams, theirParams: TheirChannelParams, commitment: OurCommit): Boolean = {
|
||||
// we rebuild the closing tx as seen by both parties
|
||||
|
@ -13,8 +13,7 @@ object Scripts {
|
||||
|
||||
def locktime2long_csv(in: locktime): Long = in match {
|
||||
case locktime(Blocks(blocks)) => blocks
|
||||
// FIXME : we adopt Element's alpha convention while BIP68 is not enabled
|
||||
case locktime(Seconds(seconds)) => 500000000 + seconds
|
||||
case locktime(Seconds(seconds)) => TxIn.SEQUENCE_LOCKTIME_TYPE_FLAG | (seconds >> TxIn.SEQUENCE_LOCKTIME_GRANULARITY)
|
||||
}
|
||||
|
||||
def locktime2long_cltv(in: locktime): Long = in match {
|
||||
@ -26,7 +25,7 @@ object Scripts {
|
||||
|
||||
def lessThan(output1: TxOut, output2: TxOut): Boolean = (output1, output2) match {
|
||||
case (TxOut(amount1, script1), TxOut(amount2, script2)) if amount1 == amount2 => memcmp(script1.toList, script2.toList) < 0
|
||||
case (TxOut(amount1, _), TxOut(amount2, _)) => amount1 < amount2
|
||||
case (TxOut(amount1, _), TxOut(amount2, _)) => amount1.toLong < amount2.toLong
|
||||
}
|
||||
|
||||
def permuteOutputs(tx: Transaction): Transaction = tx.copy(txOut = tx.txOut.sortWith(lessThan))
|
||||
@ -42,22 +41,66 @@ object Scripts {
|
||||
else
|
||||
Script.write(OP_0 :: OP_PUSHDATA(sig2) :: OP_PUSHDATA(sig1) :: OP_PUSHDATA(multiSig2of2(pubkey1, pubkey2)) :: Nil)
|
||||
|
||||
/**
|
||||
*
|
||||
* @param sig1
|
||||
* @param sig2
|
||||
* @param pubkey1
|
||||
* @param pubkey2
|
||||
* @return a script witness that matches the msig 2-of-2 pubkey script for pubkey1 and pubkey2
|
||||
*/
|
||||
def witness2of2(sig1: BinaryData, sig2: BinaryData, pubkey1: BinaryData, pubkey2: BinaryData): ScriptWitness = {
|
||||
if (isLess(pubkey1, pubkey2))
|
||||
ScriptWitness(Seq(BinaryData.empty, sig1, sig2, multiSig2of2(pubkey1, pubkey2)))
|
||||
else
|
||||
ScriptWitness(Seq(BinaryData.empty, sig2, sig1, multiSig2of2(pubkey1, pubkey2)))
|
||||
|
||||
}
|
||||
|
||||
def pay2sh(script: Seq[ScriptElt]): Seq[ScriptElt] = pay2sh(Script.write(script))
|
||||
|
||||
def pay2sh(script: BinaryData): Seq[ScriptElt] = OP_HASH160 :: OP_PUSHDATA(hash160(script)) :: OP_EQUAL :: Nil
|
||||
|
||||
//TODO : this function does not handle the case where the anchor tx does not spend all previous tx output (meaning there is change)
|
||||
def makeAnchorTx(pubkey1: BinaryData, pubkey2: BinaryData, amount: Long, previousTxOutput: OutPoint, signData: SignData): (Transaction, Int) = {
|
||||
val tx = Transaction(version = 1,
|
||||
txIn = TxIn(outPoint = previousTxOutput, signatureScript = Array.emptyByteArray, sequence = 0xffffffffL) :: Nil,
|
||||
txOut = TxOut(amount, publicKeyScript = pay2sh(multiSig2of2(pubkey1, pubkey2))) :: Nil,
|
||||
def pay2wsh(script: Seq[ScriptElt]): Seq[ScriptElt] = pay2wsh(Script.write(script))
|
||||
|
||||
def pay2wsh(script: BinaryData): Seq[ScriptElt] = OP_0 :: OP_PUSHDATA(sha256(script)) :: Nil
|
||||
|
||||
def pay2wpkh(pubKey: BinaryData): Seq[ScriptElt] = OP_0 :: OP_PUSHDATA(hash160(pubKey)) :: Nil
|
||||
|
||||
/**
|
||||
*
|
||||
* @param pubkey1 public key for A
|
||||
* @param pubkey2 public key for B
|
||||
* @param amount anchor tx amount
|
||||
* @param previousTx tx that will fund the anchor; it * must * be a P2PWPK embedded in a standard P2SH tx: the p2sh
|
||||
* script is just the P2WPK script for the public key that matches our "key" parameter
|
||||
* @param outputIndex index of the output in the funding tx
|
||||
* @param key private key that can redeem the funding tx
|
||||
* @return a signed anchor tx
|
||||
*/
|
||||
def makeAnchorTx(pubkey1: BinaryData, pubkey2: BinaryData, amount: Long, previousTx: Transaction, outputIndex: Int, key: BinaryData): (Transaction, Int) = {
|
||||
val tx = Transaction(version = 2,
|
||||
txIn = TxIn(outPoint = OutPoint(previousTx, outputIndex), signatureScript = Array.emptyByteArray, sequence = 0xffffffffL) :: Nil,
|
||||
txOut = TxOut(Satoshi(amount), publicKeyScript = pay2wsh(multiSig2of2(pubkey1, pubkey2))) :: Nil,
|
||||
lockTime = 0)
|
||||
val signedTx = Transaction.sign(tx, Seq(signData))
|
||||
val pub: BinaryData = Crypto.publicKeyFromPrivateKey(key)
|
||||
val pkh = OP_0 :: OP_PUSHDATA(Crypto.hash160(pub)) :: Nil
|
||||
val p2sh: BinaryData = Script.write(pay2sh(pkh))
|
||||
|
||||
require(p2sh == previousTx.txOut(outputIndex).publicKeyScript)
|
||||
|
||||
val pubKeyScript = Script.write(OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pub)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil)
|
||||
val hash = Transaction.hashForSigning(tx, 0, pubKeyScript, SIGHASH_ALL, tx.txOut(0).amount.toLong, signatureVersion = 1)
|
||||
val sig = Crypto.encodeSignature(Crypto.sign(hash, key.take(32), randomize = false)) :+ SIGHASH_ALL.toByte
|
||||
val witness = ScriptWitness(Seq(sig, pub))
|
||||
val script = Script.write(OP_0 :: OP_PUSHDATA(Crypto.hash160(pub)) :: Nil)
|
||||
val signedTx = tx.updateSigScript(0, OP_PUSHDATA(script) :: Nil).copy(witness = Seq(witness))
|
||||
|
||||
// we don't permute outputs because by convention the multisig output has index = 0
|
||||
(signedTx, 0)
|
||||
}
|
||||
|
||||
def anchorPubkeyScript(pubkey1: BinaryData, pubkey2: BinaryData): BinaryData = Script.write(pay2sh(multiSig2of2(pubkey1, pubkey2)))
|
||||
def anchorPubkeyScript(pubkey1: BinaryData, pubkey2: BinaryData): BinaryData = Script.write(pay2wsh(multiSig2of2(pubkey1, pubkey2)))
|
||||
|
||||
def redeemSecretOrDelay(delayedKey: BinaryData, reltimeout: Long, keyIfSecretKnown: BinaryData, hashOfSecret: BinaryData): Seq[ScriptElt] = {
|
||||
// @formatter:off
|
||||
@ -109,21 +152,23 @@ object Scripts {
|
||||
def makeCommitTx(inputs: Seq[TxIn], ourFinalKey: BinaryData, theirFinalKey: BinaryData, theirDelay: locktime, revocationHash: BinaryData, channelState: ChannelState): Transaction = {
|
||||
val redeemScript = redeemSecretOrDelay(ourFinalKey, locktime2long_csv(theirDelay), theirFinalKey, revocationHash: BinaryData)
|
||||
|
||||
val outputs = Seq(
|
||||
// TODO : is that the correct way to handle sub-satoshi balances ?
|
||||
TxOut(amount = Satoshi(channelState.us.pay_msat / 1000), publicKeyScript = pay2wsh(redeemScript)),
|
||||
TxOut(amount = Satoshi(channelState.them.pay_msat / 1000), publicKeyScript = pay2wpkh(theirFinalKey))
|
||||
).filterNot(_.amount.toLong < 546) // do not add dust
|
||||
|
||||
val tx = Transaction(
|
||||
version = 1,
|
||||
version = 2,
|
||||
txIn = inputs,
|
||||
txOut = Seq(
|
||||
// TODO : is that the correct way to handle sub-satoshi balances ?
|
||||
TxOut(amount = channelState.us.pay_msat / 1000, publicKeyScript = pay2sh(redeemScript)),
|
||||
TxOut(amount = channelState.them.pay_msat / 1000, publicKeyScript = pay2sh(OP_PUSHDATA(theirFinalKey) :: OP_CHECKSIG :: Nil))
|
||||
),
|
||||
txOut = outputs,
|
||||
lockTime = 0)
|
||||
|
||||
val sendOuts = channelState.them.htlcs_received.map(htlc =>
|
||||
TxOut(htlc.amountMsat / 1000, pay2sh(scriptPubKeyHtlcSend(ourFinalKey, theirFinalKey, locktime2long_cltv(htlc.expiry), locktime2long_csv(theirDelay), htlc.rHash, revocationHash)))
|
||||
TxOut(Satoshi(htlc.amountMsat / 1000), pay2wsh(scriptPubKeyHtlcSend(ourFinalKey, theirFinalKey, locktime2long_cltv(htlc.expiry), locktime2long_csv(theirDelay), htlc.rHash, revocationHash)))
|
||||
)
|
||||
val receiveOuts = channelState.us.htlcs_received.map(htlc =>
|
||||
TxOut(htlc.amountMsat / 1000, pay2sh(scriptPubKeyHtlcReceive(ourFinalKey, theirFinalKey, locktime2long_cltv(htlc.expiry), locktime2long_csv(theirDelay), htlc.rHash, revocationHash)))
|
||||
TxOut(Satoshi(htlc.amountMsat / 1000), pay2wsh(scriptPubKeyHtlcReceive(ourFinalKey, theirFinalKey, locktime2long_cltv(htlc.expiry), locktime2long_csv(theirDelay), htlc.rHash, revocationHash)))
|
||||
)
|
||||
val tx1 = tx.copy(txOut = tx.txOut ++ sendOuts ++ receiveOuts)
|
||||
permuteOutputs(tx1)
|
||||
@ -143,23 +188,17 @@ object Scripts {
|
||||
assert(channelState.them.htlcs_received.isEmpty && channelState.us.htlcs_received.isEmpty, s"cannot close a channel with pending htlcs (see rusty's state_types.h line 103)")
|
||||
|
||||
permuteOutputs(Transaction(
|
||||
version = 1,
|
||||
version = 2,
|
||||
txIn = inputs,
|
||||
txOut = Seq(
|
||||
TxOut(amount = channelState.them.pay_msat / 1000, publicKeyScript = pay2sh(OP_PUSHDATA(theirFinalKey) :: OP_CHECKSIG :: Nil)),
|
||||
TxOut(amount = channelState.us.pay_msat / 1000, publicKeyScript = pay2sh(OP_PUSHDATA(ourFinalKey) :: OP_CHECKSIG :: Nil))
|
||||
TxOut(amount = Satoshi(channelState.them.pay_msat / 1000), publicKeyScript = pay2wpkh(theirFinalKey)),
|
||||
TxOut(amount = Satoshi(channelState.us.pay_msat / 1000), publicKeyScript = pay2wpkh(ourFinalKey))
|
||||
),
|
||||
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 = {
|
||||
require(isFunder(a) ^ isFunder(b))
|
||||
val (c1, c2) = ChannelOneSide(pay_msat = anchor.amount - fee, fee_msat = fee, Seq.empty[Htlc2]) -> ChannelOneSide(0, 0, Seq.empty[Htlc2])
|
||||
if (isFunder(a)) ChannelState(c1, c2) else ChannelState(c2, c1)
|
||||
}
|
||||
|
||||
def findPublicKeyScriptIndex(tx: Transaction, publicKeyScript: BinaryData): Option[Int] =
|
||||
tx.txOut.zipWithIndex.find {
|
||||
case (TxOut(_, script), _) => script == publicKeyScript
|
||||
|
@ -75,7 +75,7 @@ object Onion extends App {
|
||||
|
||||
def generate_secrets(ecdh_key: BinaryData): Secrets = {
|
||||
|
||||
val key_hash = Crypto.sha256(ecdh_key)
|
||||
val key_hash: BinaryData = Crypto.sha256(ecdh_key)
|
||||
|
||||
val enckey = tweak_hash(key_hash, 0x00).take(16)
|
||||
val hmac = tweak_hash(key_hash, 0x01)
|
||||
|
@ -20,6 +20,12 @@ package object eclair {
|
||||
sha256_hash(Protocol.uint64(bis), Protocol.uint64(bis), Protocol.uint64(bis), Protocol.uint64(bis))
|
||||
}
|
||||
|
||||
implicit def seq2sha256(in: Seq[Byte]): sha256_hash = {
|
||||
require(in.data.size == 32)
|
||||
val bis = new ByteArrayInputStream(in.toArray)
|
||||
sha256_hash(Protocol.uint64(bis), Protocol.uint64(bis), Protocol.uint64(bis), Protocol.uint64(bis))
|
||||
}
|
||||
|
||||
implicit def array2sha256(in: Array[Byte]): sha256_hash = bin2sha256(in)
|
||||
|
||||
implicit def sha2562bin(in: sha256_hash): BinaryData = {
|
||||
|
@ -27,7 +27,7 @@ object Test extends App {
|
||||
val tmpTx = Transaction(
|
||||
version = 1L,
|
||||
txIn = TxIn(OutPoint(previousTx.hash, 0), sequence = 0L, signatureScript = Array.empty[Byte]) :: Nil,
|
||||
txOut = TxOut(amount = 100, publicKeyScript = scriptPubKey) :: Nil,
|
||||
txOut = TxOut(amount = 100 satoshi, publicKeyScript = scriptPubKey) :: Nil,
|
||||
lockTime = 0
|
||||
)
|
||||
Transaction.sign(tmpTx, Seq(key))
|
||||
@ -44,8 +44,8 @@ object Test extends App {
|
||||
version = 1,
|
||||
txIn = TxIn(OutPoint(openingTx.hash, 0), sequence = 0L, signatureScript = Array.empty[Byte]) :: Nil,
|
||||
txOut = Seq(
|
||||
TxOut(amount = 99, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubAlice)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil),
|
||||
TxOut(amount = 1, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubBob)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil)
|
||||
TxOut(amount = 99 satoshi, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubAlice)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil),
|
||||
TxOut(amount = 1 satoshi, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubBob)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil)
|
||||
),
|
||||
lockTime = 0
|
||||
)
|
||||
@ -75,8 +75,8 @@ object Test extends App {
|
||||
version = 1,
|
||||
txIn = TxIn(OutPoint(openingTx.hash, 0), sequence = 0L, signatureScript = Array.empty[Byte]) :: Nil,
|
||||
txOut = Seq(
|
||||
TxOut(amount = 98, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubAlice)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil),
|
||||
TxOut(amount = 2, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubBob)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil)
|
||||
TxOut(amount = 98 satoshi, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubAlice)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil),
|
||||
TxOut(amount = 2 satoshi, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubBob)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil)
|
||||
),
|
||||
lockTime = 0
|
||||
)
|
||||
@ -114,7 +114,7 @@ object RevokablePaymentChannel extends App {
|
||||
val tmpTx = Transaction(
|
||||
version = 1L,
|
||||
txIn = TxIn(OutPoint(previousTx.hash, 0), sequence = 0L, signatureScript = Array.empty[Byte]) :: Nil,
|
||||
txOut = TxOut(amount = 100, publicKeyScript = scriptPubKey) :: Nil,
|
||||
txOut = TxOut(amount = 100 satoshi, publicKeyScript = scriptPubKey) :: Nil,
|
||||
lockTime = 0
|
||||
)
|
||||
Transaction.sign(tmpTx, Seq(key))
|
||||
|
@ -38,7 +38,7 @@ object Test2 extends App {
|
||||
val tmpTx = Transaction(
|
||||
version = 1,
|
||||
txIn = TxIn(outPoint = OutPoint(previousTx.hash, 0), signatureScript = Array.empty[Byte], sequence = 0xffffffffL) :: Nil,
|
||||
txOut = TxOut(100, OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(Script.write(redeemScript))) :: OP_EQUAL :: Nil) :: Nil,
|
||||
txOut = TxOut(100 satoshi, OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(Script.write(redeemScript))) :: OP_EQUAL :: Nil) :: Nil,
|
||||
lockTime = 0
|
||||
)
|
||||
val sigA = Transaction.signInput(tmpTx, 0, tmpTx.txOut(0).publicKeyScript, SIGHASH_ALL, Alice.commitKey)
|
||||
|
@ -30,7 +30,7 @@ object TestSighashNoInput extends App {
|
||||
val tmpTx = Transaction(
|
||||
version = 1L,
|
||||
txIn = TxIn(OutPoint(previousTx.hash, 0), sequence = 0L, signatureScript = Array.empty[Byte]) :: Nil,
|
||||
txOut = TxOut(amount = 100, publicKeyScript = scriptPubKey) :: Nil,
|
||||
txOut = TxOut(amount = 100 satoshi, publicKeyScript = scriptPubKey) :: Nil,
|
||||
lockTime = 0
|
||||
)
|
||||
Transaction.sign(tmpTx, Seq(key))
|
||||
@ -51,8 +51,8 @@ object TestSighashNoInput extends App {
|
||||
version = 1,
|
||||
txIn = TxIn(OutPoint(openingTx.hash, 0), sequence = 0L, signatureScript = Array.empty[Byte]) :: Nil,
|
||||
txOut = Seq(
|
||||
TxOut(amount = 99, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubAlice)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil),
|
||||
TxOut(amount = 1, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubBob)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil)
|
||||
TxOut(amount = 99 satoshi, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubAlice)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil),
|
||||
TxOut(amount = 1 satoshi, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubBob)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil)
|
||||
),
|
||||
lockTime = 0
|
||||
)
|
||||
@ -83,8 +83,8 @@ object TestSighashNoInput extends App {
|
||||
version = 1,
|
||||
txIn = TxIn(OutPoint(openingTx.hash, 0), sequence = 0L, signatureScript = Array.empty[Byte]) :: Nil,
|
||||
txOut = Seq(
|
||||
TxOut(amount = 98, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubAlice)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil),
|
||||
TxOut(amount = 2, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubBob)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil)
|
||||
TxOut(amount = 98 satoshi, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubAlice)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil),
|
||||
TxOut(amount = 2 satoshi, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubBob)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil)
|
||||
),
|
||||
lockTime = 0
|
||||
)
|
||||
@ -121,8 +121,8 @@ object TestSighashNoInput extends App {
|
||||
version = 1,
|
||||
txIn = TxIn(OutPoint(openingTx.hash, 0), sequence = 0L, signatureScript = Array.empty[Byte]) :: Nil,
|
||||
txOut = Seq(
|
||||
TxOut(amount = 99, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubAlice)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil),
|
||||
TxOut(amount = 1, publicKeyScript = OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(redeemScript1)) :: OP_EQUAL :: Nil)
|
||||
TxOut(amount = 99 satoshi, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubAlice)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil),
|
||||
TxOut(amount = 1 satoshi, publicKeyScript = OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(redeemScript1)) :: OP_EQUAL :: Nil)
|
||||
),
|
||||
lockTime = 0
|
||||
)
|
||||
@ -139,7 +139,7 @@ object TestSighashNoInput extends App {
|
||||
val tmpTx = Transaction(
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(revocablePaymentTx1.hash, 1), sequence = 0L, signatureScript = Array.empty[Byte]) :: Nil,
|
||||
txOut = TxOut(amount = 1, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubBob)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
txOut = TxOut(amount = 1 satoshi, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubBob)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
lockTime = 100
|
||||
)
|
||||
|
||||
@ -160,7 +160,7 @@ object TestSighashNoInput extends App {
|
||||
tmpTx.updateSigScript(0, Script.write(sigScript))
|
||||
}
|
||||
|
||||
Transaction.correctlySpends(revocablePaymentTx1updated, Map(OutPoint(revocablePaymentTx1.hash, 1) -> revocablePaymentTx1.txOut(1).publicKeyScript), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
Transaction.correctlySpends(revocablePaymentTx1updated, Map(OutPoint(revocablePaymentTx1.hash, 1) -> revocablePaymentTx1.txOut(1)), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
|
||||
|
||||
// now we want to update the channel and send 2 BTC to Bob instead of 1
|
||||
@ -175,8 +175,8 @@ object TestSighashNoInput extends App {
|
||||
version = 1,
|
||||
txIn = TxIn(OutPoint(openingTx.hash, 0), sequence = 0L, signatureScript = Array.empty[Byte]) :: Nil,
|
||||
txOut = Seq(
|
||||
TxOut(amount = 98, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubAlice)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil),
|
||||
TxOut(amount = 2, publicKeyScript = OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(redeemScript2)) :: OP_EQUAL :: Nil)
|
||||
TxOut(amount = 98 satoshi, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubAlice)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil),
|
||||
TxOut(amount = 2 satoshi, publicKeyScript = OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(redeemScript2)) :: OP_EQUAL :: Nil)
|
||||
),
|
||||
lockTime = 0
|
||||
)
|
||||
@ -192,7 +192,7 @@ object TestSighashNoInput extends App {
|
||||
val tmpTx = Transaction(
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(revocablePaymentTx2.hash, 1), sequence = 0L, signatureScript = Array.empty[Byte]) :: Nil,
|
||||
txOut = TxOut(amount = 1, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubBob)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
txOut = TxOut(amount = 1 satoshi, publicKeyScript = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubBob)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
lockTime = 100
|
||||
)
|
||||
|
||||
@ -230,7 +230,7 @@ object TestSighashNoInput extends App {
|
||||
val tmpTx = Transaction(
|
||||
version = 1,
|
||||
txIn = TxIn(OutPoint(revocablePaymentTx1Bob.hash, 1), sequence = 0xffffffffL, signatureScript = Array.empty[Byte]) :: Nil,
|
||||
txOut = TxOut(1, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubAlice)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
txOut = TxOut(1 satoshi, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubAlice)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
lockTime = 0
|
||||
)
|
||||
val sigAlice = Transaction.signInput(tmpTx, 0, redeemScript1, SIGHASH_ALL, keyAlice)
|
||||
|
@ -1,6 +1,6 @@
|
||||
package fr.acinq.eclair
|
||||
|
||||
import fr.acinq.bitcoin.Crypto
|
||||
import fr.acinq.bitcoin.{Crypto, Hash}
|
||||
import fr.acinq.eclair.channel._
|
||||
import lightning.locktime.Locktime.Blocks
|
||||
import lightning.{locktime, sha256_hash, update_add_htlc}
|
||||
@ -14,56 +14,73 @@ import org.scalatest.junit.JUnitRunner
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class ChannelStateSpec extends FunSuite {
|
||||
|
||||
test("basic fee computation") {
|
||||
// from https://github.com/rustyrussell/lightning-rfc/blob/master/bolts/02-wire-protocol.md
|
||||
assert(ChannelState.computeFee(1112, 2) === 446)
|
||||
}
|
||||
|
||||
test("initial funding") {
|
||||
val feeRate = 200000
|
||||
val state = ChannelState.initialFunding(995940, feeRate)
|
||||
assert(state.us.pay_msat === 928340000 && state.us.fee_msat === 67600000)
|
||||
}
|
||||
|
||||
test("fee management send") {
|
||||
val state_0 = ChannelState(
|
||||
us = ChannelOneSide(pay_msat = 950000000, fee_msat = 50000000, htlcs_received = Seq()),
|
||||
them = ChannelOneSide(pay_msat = 0, fee_msat = 0, htlcs_received = Seq())
|
||||
)
|
||||
val feeRate = 200000
|
||||
val state = ChannelState.initialFunding(995940, feeRate)
|
||||
|
||||
val r = sha256_hash(7, 7, 7, 7)
|
||||
val rHash = Crypto.sha256(r)
|
||||
val htlc = Htlc2(0, 100000000, rHash, locktime(Blocks(1)), Nil, None)
|
||||
val state_1 = state_0.add_htlc(OUT, htlc)
|
||||
assert(state_1 === ChannelState(
|
||||
us = ChannelOneSide(pay_msat = 850000000, fee_msat = 50000000, htlcs_received = Seq()),
|
||||
them = ChannelOneSide(pay_msat = 0, fee_msat = 0, htlcs_received = Seq(htlc))
|
||||
))
|
||||
|
||||
val state_2 = state_1.fulfill_htlc(IN, htlc.id, r)
|
||||
assert(state_2 === ChannelState(
|
||||
us = ChannelOneSide(pay_msat = 875000000, fee_msat = 25000000, htlcs_received = Seq()),
|
||||
them = ChannelOneSide(pay_msat = 75000000, fee_msat = 25000000, htlcs_received = Seq())
|
||||
))
|
||||
// val state1 = state.add_htlc(OUT, Htlc(0, 1000000, Hash.Zeroes, locktime(Blocks(1)), Nil, None))
|
||||
// assert(state1.us.pay_msat === 920940000 && state1.us.fee_msat === 74000000)
|
||||
//
|
||||
// val state_0 = ChannelState(
|
||||
// us = ChannelOneSide(pay_msat = 950000000, fee_msat = 50000000, htlcs_received = Seq()),
|
||||
// them = ChannelOneSide(pay_msat = 0, fee_msat = 0, htlcs_received = Seq())
|
||||
// )
|
||||
//
|
||||
// val r = sha256_hash(7, 7, 7, 7)
|
||||
// val rHash = Crypto.sha256(r)
|
||||
// val htlc = Htlc2(0, 100000000, rHash, locktime(Blocks(1)), Nil, None)
|
||||
// val state_1 = state_0.add_htlc(OUT, htlc)
|
||||
// assert(state_1 === ChannelState(
|
||||
// us = ChannelOneSide(pay_msat = 850000000, fee_msat = 50000000, htlcs_received = Seq()),
|
||||
// them = ChannelOneSide(pay_msat = 0, fee_msat = 0, htlcs_received = Seq(htlc))
|
||||
// ))
|
||||
//
|
||||
// val state_2 = state_1.fulfill_htlc(IN, htlc.id, r)
|
||||
// assert(state_2 === ChannelState(
|
||||
// us = ChannelOneSide(pay_msat = 875000000, fee_msat = 25000000, htlcs_received = Seq()),
|
||||
// them = ChannelOneSide(pay_msat = 75000000, fee_msat = 25000000, htlcs_received = Seq())
|
||||
// ))
|
||||
}
|
||||
|
||||
test("fee management receive") {
|
||||
val state_0 = ChannelState(
|
||||
us = ChannelOneSide(pay_msat = 0, fee_msat = 0, htlcs_received = Seq()),
|
||||
them = ChannelOneSide(pay_msat = 950000000, fee_msat = 50000000, htlcs_received = Seq())
|
||||
)
|
||||
|
||||
val r = sha256_hash(7, 7, 7, 7)
|
||||
val rHash = Crypto.sha256(r)
|
||||
val htlc = Htlc2(0, 2000000, rHash, locktime(Blocks(1)), Nil, None)
|
||||
val state_1 = state_0.add_htlc(IN, htlc)
|
||||
assert(state_1 === ChannelState(
|
||||
us = ChannelOneSide(pay_msat = 0, fee_msat = 0, htlcs_received = Seq(htlc)),
|
||||
them = ChannelOneSide(pay_msat = 948000000, fee_msat = 50000000, htlcs_received = Seq())
|
||||
))
|
||||
|
||||
val state_2 = state_1.fulfill_htlc(OUT, htlc.id, r)
|
||||
assert(state_2 === ChannelState(
|
||||
us = ChannelOneSide(pay_msat = 0, fee_msat = 2000000, htlcs_received = Seq()),
|
||||
them = ChannelOneSide(pay_msat = 950000000, fee_msat = 48000000, htlcs_received = Seq())
|
||||
))
|
||||
}
|
||||
|
||||
test("adjust fees") {
|
||||
val state_0 = ChannelState(
|
||||
us = ChannelOneSide(pay_msat = 950000*1000, fee_msat = 49900*1000, htlcs_received = Seq()),
|
||||
them = ChannelOneSide(pay_msat = 0, fee_msat = 100*1000, htlcs_received = Seq())
|
||||
)
|
||||
val state_1 = state_0.adjust_fees(100000, true)
|
||||
println(state_1)
|
||||
}
|
||||
// test("fee management receive") {
|
||||
// val state_0 = ChannelState(
|
||||
// us = ChannelOneSide(pay_msat = 0, fee_msat = 0, htlcs_received = Seq()),
|
||||
// them = ChannelOneSide(pay_msat = 950000000, fee_msat = 50000000, htlcs_received = Seq())
|
||||
// )
|
||||
//
|
||||
// val r = sha256_hash(7, 7, 7, 7)
|
||||
// val rHash = Crypto.sha256(r)
|
||||
// val htlc = Htlc2(0, 2000000, rHash, locktime(Blocks(1)), Nil, None)
|
||||
// val state_1 = state_0.add_htlc(IN, htlc)
|
||||
// assert(state_1 === ChannelState(
|
||||
// us = ChannelOneSide(pay_msat = 0, fee_msat = 0, htlcs_received = Seq(htlc)),
|
||||
// them = ChannelOneSide(pay_msat = 948000000, fee_msat = 50000000, htlcs_received = Seq())
|
||||
// ))
|
||||
//
|
||||
// val state_2 = state_1.fulfill_htlc(OUT, htlc.id, r)
|
||||
// assert(state_2 === ChannelState(
|
||||
// us = ChannelOneSide(pay_msat = 0, fee_msat = 2000000, htlcs_received = Seq()),
|
||||
// them = ChannelOneSide(pay_msat = 950000000, fee_msat = 48000000, htlcs_received = Seq())
|
||||
// ))
|
||||
// }
|
||||
//
|
||||
// test("adjust fees") {
|
||||
// val state_0 = ChannelState(
|
||||
// us = ChannelOneSide(pay_msat = 950000*1000, fee_msat = 49900*1000, htlcs_received = Seq()),
|
||||
// them = ChannelOneSide(pay_msat = 0, fee_msat = 100*1000, htlcs_received = Seq())
|
||||
// )
|
||||
// val state_1 = state_0.adjust_fees(100000, true)
|
||||
// println(state_1)
|
||||
// }
|
||||
}
|
||||
|
@ -26,74 +26,65 @@ class ClaimReceivedHtlcSpec extends FunSuite {
|
||||
val (_, finalKey) = Base58Check.decode("cQLk5fMydgVwJjygt9ta8GcUU4GXLumNiXJCQviibs2LE5vyMXey")
|
||||
val commitPubKey = Crypto.publicKeyFromPrivateKey(commitKey)
|
||||
val finalPubKey = Crypto.publicKeyFromPrivateKey(finalKey)
|
||||
val R = "this is Bob's R".getBytes("UTF-8")
|
||||
val Rhash = Crypto.sha256(R)
|
||||
val H = Crypto.hash160(R)
|
||||
val revokeCommit = "Bob foo".getBytes("UTF-8")
|
||||
val revokeCommitHash = Crypto.sha256(revokeCommit)
|
||||
val R: BinaryData = "this is Bob's R".getBytes("UTF-8")
|
||||
val Rhash: BinaryData = Crypto.sha256(R)
|
||||
val H: BinaryData = Crypto.hash160(R)
|
||||
val revokeCommit: BinaryData = Crypto.sha256("Alice revocation R".getBytes("UTF-8"))
|
||||
val revokeCommitRHash: BinaryData = Crypto.sha256(revokeCommit)
|
||||
val revokeCommitH: BinaryData = Crypto.sha256(revokeCommit)
|
||||
}
|
||||
|
||||
val abstimeout = 3000
|
||||
val reltimeout = 2000
|
||||
val htlcScript = scriptPubKeyHtlcReceive(Alice.finalPubKey, Bob.finalPubKey, abstimeout, reltimeout, Bob.Rhash, Bob.revokeCommitHash)
|
||||
val htlcScript = scriptPubKeyHtlcReceive(Alice.finalPubKey, Bob.finalPubKey, abstimeout, reltimeout, Bob.Rhash, Bob.revokeCommitRHash)
|
||||
val redeemScript: BinaryData = Script.write(htlcScript)
|
||||
|
||||
// this tx sends money to our HTLC
|
||||
val tx = Transaction(
|
||||
version = 1,
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(Hash.Zeroes, 0), Array.emptyByteArray, 0xffffffffL) :: Nil,
|
||||
txOut = TxOut(10, Script.write(pay2sh(htlcScript))) :: Nil,
|
||||
txOut = TxOut(10 satoshi, pay2wsh(htlcScript)) :: Nil,
|
||||
lockTime = 0)
|
||||
|
||||
// this tx tries to spend the previous tx
|
||||
val tx1 = Transaction(
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(tx, 0), Array.emptyByteArray, 0xffffffff) :: Nil,
|
||||
txOut = TxOut(10, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(Alice.finalPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
txOut = TxOut(10 satoshi, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(Alice.finalPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
lockTime = 0)
|
||||
|
||||
test("Alice can spend this HTLC after a delay if she knows the payment hash") {
|
||||
val tx2 = Transaction(
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(tx, 0), Array.emptyByteArray, reltimeout + 1) :: Nil,
|
||||
txOut = TxOut(10, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(Alice.finalPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
txOut = TxOut(10 satoshi, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(Alice.finalPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
lockTime = abstimeout + 1)
|
||||
|
||||
val sig = Transaction.signInput(tx2, 0, Script.write(htlcScript), SIGHASH_ALL, Alice.finalKey)
|
||||
val sigScript = OP_PUSHDATA(sig) :: OP_PUSHDATA(Bob.R) :: OP_PUSHDATA(Script.write(htlcScript)) :: Nil
|
||||
val tx3 = tx2.updateSigScript(0, Script.write(sigScript))
|
||||
val sig = Transaction.signInput(tx2, 0, Script.write(htlcScript), SIGHASH_ALL, tx.txOut(0).amount.toLong, 1, Alice.finalKey)
|
||||
val witness = ScriptWitness(sig :: Bob.R :: redeemScript :: Nil)
|
||||
val tx3 = tx2.copy(witness = Seq(witness))
|
||||
|
||||
val runner = new Script.Runner(
|
||||
new Script.Context(tx3, 0),
|
||||
ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS | ScriptFlags.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | ScriptFlags.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY
|
||||
)
|
||||
val result = runner.verifyScripts(Script.write(sigScript), Script.write(pay2sh(htlcScript)))
|
||||
assert(result)
|
||||
Transaction.correctlySpends(tx3, Seq(tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
}
|
||||
|
||||
test("Blob can spend this HTLC after a delay") {
|
||||
val tx2 = Transaction(
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(tx, 0), Array.emptyByteArray, reltimeout + 1) :: Nil,
|
||||
txOut = TxOut(10, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(Alice.finalPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
txOut = TxOut(10 satoshi , OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(Bob.finalPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
lockTime = abstimeout + 1)
|
||||
|
||||
val sig = Transaction.signInput(tx2, 0, Script.write(htlcScript), SIGHASH_ALL, tx.txOut(0).amount.toLong, 1, Bob.finalKey)
|
||||
val witness = ScriptWitness(sig :: Hash.Zeroes :: redeemScript :: Nil)
|
||||
val tx3 = tx2.copy(witness = Seq(witness))
|
||||
|
||||
val sig = Transaction.signInput(tx2, 0, Script.write(htlcScript), SIGHASH_ALL, Bob.finalKey)
|
||||
val sigScript = OP_PUSHDATA(sig) :: OP_PUSHDATA(Array.emptyByteArray) :: OP_PUSHDATA(Script.write(htlcScript)) :: Nil
|
||||
val tx3 = tx2.updateSigScript(0, Script.write(sigScript))
|
||||
|
||||
val runner = new Script.Runner(new Script.Context(tx3, 0), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS | ScriptFlags.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | ScriptFlags.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY)
|
||||
val result = runner.verifyScripts(Script.write(sigScript), Script.write(pay2sh(htlcScript)))
|
||||
assert(result)
|
||||
Transaction.correctlySpends(tx3, Seq(tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
}
|
||||
|
||||
test("Blob can spend this HTLC right away if he knows the revocation hash") {
|
||||
val sig = Transaction.signInput(tx1, 0, Script.write(htlcScript), SIGHASH_ALL, Bob.finalKey)
|
||||
val sigScript = OP_PUSHDATA(sig) :: OP_PUSHDATA(Bob.revokeCommit) :: OP_PUSHDATA(Script.write(htlcScript)) :: Nil
|
||||
val tx2 = tx1.updateSigScript(0, Script.write(sigScript))
|
||||
|
||||
val runner = new Script.Runner(new Script.Context(tx2, 0), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS | ScriptFlags.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | ScriptFlags.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY)
|
||||
val result = runner.verifyScripts(Script.write(sigScript), Script.write(pay2sh(htlcScript)))
|
||||
assert(result)
|
||||
val sig = Transaction.signInput(tx1, 0, Script.write(htlcScript), SIGHASH_ALL, tx.txOut(0).amount.toLong, 1, Bob.finalKey)
|
||||
val witness = ScriptWitness(sig :: Bob.revokeCommit :: redeemScript :: Nil)
|
||||
val tx2 = tx1.copy(witness = Seq(witness))
|
||||
Transaction.correctlySpends(tx2, Seq(tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
}
|
||||
}
|
||||
|
@ -14,11 +14,12 @@ class ClaimSentHtlcSpec extends FunSuite {
|
||||
val (_, finalKey) = Base58Check.decode("cRUfvpbRtMSqCFD1ADdvgPn5HfRLYuHCFYAr2noWnaRDNger2AoA")
|
||||
val commitPubKey = Crypto.publicKeyFromPrivateKey(commitKey)
|
||||
val finalPubKey = Crypto.publicKeyFromPrivateKey(finalKey)
|
||||
val R = "this is Alice's R".getBytes("UTF-8")
|
||||
val Rhash = Crypto.sha256(R)
|
||||
val H = Crypto.hash160(R)
|
||||
val revokeCommit = "Alice foo".getBytes("UTF-8")
|
||||
val revokeCommitHash = Crypto.sha256(revokeCommit)
|
||||
val R: BinaryData = Crypto.sha256("this is Alice's R".getBytes("UTF-8"))
|
||||
val Rhash: BinaryData = Crypto.sha256(R)
|
||||
val H = Crypto.hash160(Rhash)
|
||||
val revokeCommit: BinaryData = Crypto.sha256("Alice revocation R".getBytes("UTF-8"))
|
||||
val revokeCommitRHash: BinaryData = Crypto.sha256(revokeCommit)
|
||||
val revokeCommitH: BinaryData = Crypto.sha256(revokeCommit)
|
||||
}
|
||||
|
||||
object Bob {
|
||||
@ -26,60 +27,60 @@ class ClaimSentHtlcSpec extends FunSuite {
|
||||
val (_, finalKey) = Base58Check.decode("cQLk5fMydgVwJjygt9ta8GcUU4GXLumNiXJCQviibs2LE5vyMXey")
|
||||
val commitPubKey = Crypto.publicKeyFromPrivateKey(commitKey)
|
||||
val finalPubKey = Crypto.publicKeyFromPrivateKey(finalKey)
|
||||
val R = "this is Bob's R".getBytes("UTF-8")
|
||||
val H = Crypto.sha256(R)
|
||||
val revokeCommit = "Bob foo".getBytes("UTF-8")
|
||||
val revokeCommitHash = Crypto.sha256(revokeCommit)
|
||||
val R: BinaryData = Crypto.sha256("this is Bob's R".getBytes("UTF-8"))
|
||||
val Rhash: BinaryData = Crypto.sha256(R)
|
||||
val H = Crypto.hash160(Rhash)
|
||||
val revokeCommit: BinaryData = Crypto.sha256("Bob revocation R".getBytes("UTF-8"))
|
||||
val revokeCommitRHash: BinaryData = Crypto.sha256(revokeCommit)
|
||||
val revokeCommitH: BinaryData = Crypto.sha256(revokeCommit)
|
||||
}
|
||||
|
||||
val abstimeout = 3000
|
||||
val reltimeout = 2000
|
||||
val htlcScript = scriptPubKeyHtlcSend(Alice.finalPubKey, Bob.finalPubKey, abstimeout, reltimeout, Alice.revokeCommitHash, Alice.Rhash)
|
||||
val htlcScript = scriptPubKeyHtlcSend(Alice.finalPubKey, Bob.finalPubKey, abstimeout, reltimeout, Alice.revokeCommitRHash, Alice.Rhash)
|
||||
val redeemScript: BinaryData = Script.write(htlcScript)
|
||||
|
||||
// this tx sends money to our HTLC
|
||||
val tx = Transaction(
|
||||
version = 1,
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(Hash.Zeroes, 0), Array.emptyByteArray, 0xffffffffL) :: Nil,
|
||||
txOut = TxOut(10, Script.write(pay2sh(htlcScript))) :: Nil,
|
||||
txOut = TxOut(10 satoshi, pay2wsh(htlcScript)) :: Nil,
|
||||
lockTime = 0)
|
||||
|
||||
// this tx tries to spend the previous tx
|
||||
val tx1 = Transaction(
|
||||
version = 1,
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(tx, 0), Array.emptyByteArray, 0xffffffff) :: Nil,
|
||||
txOut = TxOut(10, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(Alice.finalPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
txOut = TxOut(10 satoshi, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(Alice.finalPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
lockTime = 0)
|
||||
|
||||
test("Alice can spend this HTLC after a delay") {
|
||||
val tx2 = Transaction(
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(tx, 0), Array.emptyByteArray, sequence = reltimeout + 1) :: Nil,
|
||||
txOut = TxOut(10, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(Alice.finalPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
txOut = TxOut(10 satoshi, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(Alice.finalPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
lockTime = abstimeout + 1)
|
||||
|
||||
val sig = Transaction.signInput(tx2, 0, Script.write(htlcScript), SIGHASH_ALL, Alice.finalKey)
|
||||
val sigScript = OP_PUSHDATA(sig) :: OP_PUSHDATA(Array.emptyByteArray) :: OP_PUSHDATA(Script.write(htlcScript)) :: Nil
|
||||
val tx3 = tx2.updateSigScript(0, Script.write(sigScript))
|
||||
val sig = Transaction.signInput(tx2, 0, redeemScript, SIGHASH_ALL, tx.txOut(0).amount.toLong, 1, Alice.finalKey)
|
||||
val witness = ScriptWitness(sig :: Hash.Zeroes :: redeemScript :: Nil)
|
||||
val tx3 = tx2.copy(witness = Seq(witness))
|
||||
|
||||
val runner = new Script.Runner(new Script.Context(tx3, 0), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS | ScriptFlags.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | ScriptFlags.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY)
|
||||
val result = runner.verifyScripts(Script.write(sigScript), Script.write(pay2sh(htlcScript)))
|
||||
assert(result)
|
||||
Transaction.correctlySpends(tx3, Seq(tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
}
|
||||
|
||||
test("Alice cannot spend this HTLC before its absolute timeout") {
|
||||
val tx2 = Transaction(
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(tx, 0), Array.emptyByteArray, sequence = reltimeout + 1) :: Nil,
|
||||
txOut = TxOut(10, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(Alice.finalPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
txOut = TxOut(10 satoshi, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(Alice.finalPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
lockTime = abstimeout -1)
|
||||
|
||||
val sig = Transaction.signInput(tx2, 0, Script.write(htlcScript), SIGHASH_ALL, Alice.finalKey)
|
||||
val sigScript = OP_PUSHDATA(sig) :: OP_PUSHDATA(Array.emptyByteArray) :: OP_PUSHDATA(Script.write(htlcScript)) :: Nil
|
||||
val tx3 = tx2.updateSigScript(0, Script.write(sigScript))
|
||||
val sig = Transaction.signInput(tx2, 0, redeemScript, SIGHASH_ALL, tx.txOut(0).amount.toLong, 1, Alice.finalKey)
|
||||
val witness = ScriptWitness(sig :: Hash.Zeroes :: redeemScript :: Nil)
|
||||
val tx3 = tx2.copy(witness = Seq(witness))
|
||||
|
||||
val runner = new Script.Runner(new Script.Context(tx3, 0), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS | ScriptFlags.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | ScriptFlags.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY)
|
||||
val e = intercept[RuntimeException] {
|
||||
runner.verifyScripts(Script.write(sigScript), Script.write(pay2sh(htlcScript)))
|
||||
Transaction.correctlySpends(tx3, Seq(tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
}
|
||||
assert(e.getMessage === "unsatisfied CLTV lock time")
|
||||
}
|
||||
@ -88,37 +89,30 @@ class ClaimSentHtlcSpec extends FunSuite {
|
||||
val tx2 = Transaction(
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(tx, 0), Array.emptyByteArray, sequence = reltimeout - 1) :: Nil,
|
||||
txOut = TxOut(10, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(Alice.finalPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
txOut = TxOut(10 satoshi, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(Alice.finalPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
lockTime = abstimeout +1)
|
||||
|
||||
val sig = Transaction.signInput(tx2, 0, Script.write(htlcScript), SIGHASH_ALL, Alice.finalKey)
|
||||
val sigScript = OP_PUSHDATA(sig) :: OP_PUSHDATA(Array.emptyByteArray) :: OP_PUSHDATA(Script.write(htlcScript)) :: Nil
|
||||
val tx3 = tx2.updateSigScript(0, Script.write(sigScript))
|
||||
val sig = Transaction.signInput(tx2, 0, redeemScript, SIGHASH_ALL, tx.txOut(0).amount.toLong, 1, Alice.finalKey)
|
||||
val witness = ScriptWitness(sig :: Hash.Zeroes :: redeemScript :: Nil)
|
||||
val tx3 = tx2.copy(witness = Seq(witness))
|
||||
|
||||
val runner = new Script.Runner(new Script.Context(tx3, 0), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS | ScriptFlags.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | ScriptFlags.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY)
|
||||
val e = intercept[RuntimeException] {
|
||||
runner.verifyScripts(Script.write(sigScript), Script.write(pay2sh(htlcScript)))
|
||||
Transaction.correctlySpends(tx3, Seq(tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
}
|
||||
assert(e.getMessage === "unsatisfied CSV lock time")
|
||||
}
|
||||
|
||||
test("Blob can spend this HTLC if he knows the payment hash") {
|
||||
val sig = Transaction.signInput(tx1, 0, Script.write(htlcScript), SIGHASH_ALL, Bob.finalKey)
|
||||
val sigScript = OP_PUSHDATA(sig) :: OP_PUSHDATA(Alice.R) :: OP_PUSHDATA(Script.write(htlcScript)) :: Nil
|
||||
val tx2 = tx1.updateSigScript(0, Script.write(sigScript))
|
||||
|
||||
val runner = new Script.Runner(new Script.Context(tx2, 0), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS | ScriptFlags.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | ScriptFlags.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY)
|
||||
val result = runner.verifyScripts(Script.write(sigScript), Script.write(pay2sh(htlcScript)))
|
||||
assert(result)
|
||||
val sig = Transaction.signInput(tx1, 0, redeemScript, SIGHASH_ALL, tx.txOut(0).amount.toLong, 1, Bob.finalKey)
|
||||
val witness = ScriptWitness(sig :: Alice.R :: redeemScript :: Nil)
|
||||
val tx2 = tx1.copy(witness = Seq(witness))
|
||||
Transaction.correctlySpends(tx2, Seq(tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
}
|
||||
|
||||
test("Blob can spend this HTLC if he knows the revocation hash") {
|
||||
val sig = Transaction.signInput(tx1, 0, Script.write(htlcScript), SIGHASH_ALL, Bob.finalKey)
|
||||
val sigScript = OP_PUSHDATA(sig) :: OP_PUSHDATA(Alice.revokeCommit) :: OP_PUSHDATA(Script.write(htlcScript)) :: Nil
|
||||
val tx2 = tx1.updateSigScript(0, Script.write(sigScript))
|
||||
|
||||
val runner = new Script.Runner(new Script.Context(tx2, 0), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS | ScriptFlags.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | ScriptFlags.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY)
|
||||
val result = runner.verifyScripts(Script.write(sigScript), Script.write(pay2sh(htlcScript)))
|
||||
assert(result)
|
||||
val sig = Transaction.signInput(tx1, 0, redeemScript, SIGHASH_ALL, tx.txOut(0).amount.toLong, 1, Bob.finalKey)
|
||||
val witness = ScriptWitness(sig :: Alice.revokeCommit :: redeemScript :: Nil)
|
||||
val tx2 = tx1.copy(witness = Seq(witness))
|
||||
Transaction.correctlySpends(tx2, Seq(tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
}
|
||||
}
|
||||
|
@ -14,9 +14,9 @@ class PermuteOutputSpec extends FlatSpec {
|
||||
val pub3: BinaryData = "02C4D72D99CA5AD12C17C9CFE043DC4E777075E8835AF96F46D8E3CCD929FE1926"
|
||||
|
||||
val outputs = Seq(
|
||||
TxOut(5, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pub1)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil),
|
||||
TxOut(7, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pub2)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil),
|
||||
TxOut(11, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pub3)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil)
|
||||
TxOut(5 satoshi, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pub1)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil),
|
||||
TxOut(7 satoshi, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pub2)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil),
|
||||
TxOut(11 satoshi, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pub3)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil)
|
||||
)
|
||||
|
||||
val tx = Transaction(version = 1, txIn = Seq.empty[TxIn], txOut = Seq.empty[TxOut], lockTime = 0)
|
||||
|
@ -2,27 +2,27 @@ package fr.acinq.eclair
|
||||
|
||||
import fr.acinq.bitcoin.Crypto._
|
||||
import fr.acinq.bitcoin._
|
||||
import fr.acinq.eclair.channel.ChannelState
|
||||
import fr.acinq.eclair.channel.Scripts._
|
||||
import lightning._
|
||||
import lightning.locktime.Locktime.Blocks
|
||||
import lightning.open_channel.anchor_offer
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.FlatSpec
|
||||
import org.scalatest.{FlatSpec, FunSuite}
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class ProtocolSpec extends FlatSpec {
|
||||
val previousTx = Transaction.read("0100000001bb4f5a244b29dc733c56f80c0fed7dd395367d9d3b416c01767c5123ef124f82000000006b4830450221009e6ed264343e43dfee2373b925915f7a4468e0bc68216606e40064561e6c097a022030f2a50546a908579d0fab539d5726a1f83cfd48d29b89ab078d649a8e2131a0012103c80b6c289bf0421d010485cec5f02636d18fb4ed0f33bfa6412e20918ebd7a34ffffffff0200093d00000000001976a9145dbf52b8d7af4fb5f9b75b808f0a8284493531b388acf0b0b805000000001976a914807c74c89592e8a260f04b5a3bc63e7bef8c282588ac00000000")
|
||||
// key that can spend this tx
|
||||
val key = SignData(previousTx.txOut(0).publicKeyScript, Base58Check.decode("cV7LGVeY2VPuCyCSarqEqFCUNig2NzwiAEBTTA89vNRQ4Vqjfurs")._2)
|
||||
class ProtocolSpec extends FunSuite {
|
||||
val previousTx = Transaction.read("010000000190b491456fe93621c0576784bca98a2a63a0cb72035a34b4ffdd48a086dfee18000000006a473044022042ccc84c0faa8f3013862eb1e4327f73766c2c8a1a923ecd8b09a0e50b37449e022076476d1ce3240af8636adc5f6fd550fbed6bff61fbc2f530098120af5aba64660121038d847f4ecb4c297457b149485814d6bb8fa52fb86733bcc4d8f1a302437bfc01feffffff0240420f000000000017a914b5494294ea8ec4c4a00906c69187744d924b61de87e8387c44000000001976a914e3e20826a5dc4dfb7bb7b236a7ba62b55ec0ea6a88accf010000")
|
||||
val key: BinaryData = "9a d3 b5 0e fb 03 d9 de 58 7b df 91 8c dd 42 d8 69 03 2d 15 4d 4c 22 1c 88 ac ca f0 d4 a7 8c a0 01".filterNot(_.isSpaceChar)
|
||||
|
||||
object Alice {
|
||||
val (_, commitKey) = Base58Check.decode("cVuzKWCszfvjkoJyUasvsrRdECriz8hSd1BDinRNzytwnXmX7m1g")
|
||||
val (_, finalKey) = Base58Check.decode("cRUfvpbRtMSqCFD1ADdvgPn5HfRLYuHCFYAr2noWnaRDNger2AoA")
|
||||
val commitPubKey = Crypto.publicKeyFromPrivateKey(commitKey)
|
||||
val finalPubKey = Crypto.publicKeyFromPrivateKey(finalKey)
|
||||
val R = "this is Alice's R".getBytes("UTF-8")
|
||||
val H = Crypto.sha256(R)
|
||||
val R: BinaryData = "this is Alice's R".getBytes("UTF-8")
|
||||
val H: BinaryData = Crypto.sha256(R)
|
||||
}
|
||||
|
||||
object Bob {
|
||||
@ -30,33 +30,42 @@ class ProtocolSpec extends FlatSpec {
|
||||
val (_, finalKey) = Base58Check.decode("cQLk5fMydgVwJjygt9ta8GcUU4GXLumNiXJCQviibs2LE5vyMXey")
|
||||
val commitPubKey = Crypto.publicKeyFromPrivateKey(commitKey)
|
||||
val finalPubKey = Crypto.publicKeyFromPrivateKey(finalKey)
|
||||
val R = "this is Bob's R".getBytes("UTF-8")
|
||||
val H = Crypto.sha256(R)
|
||||
val R: BinaryData = "this is Bob's R".getBytes("UTF-8")
|
||||
val H: BinaryData = Crypto.sha256(R)
|
||||
}
|
||||
test("create anchor tx pubscript") {
|
||||
val pubkey1: BinaryData = "02eb1a4be1a738f1808093279c7b055a944acdb573f22748cb262b9e374441dcbc"
|
||||
val pubkey2: BinaryData = "0255952997073d71d0912b140fe43dc13b93889db5223312076efce173b8188a69"
|
||||
assert(anchorPubkeyScript(pubkey1, pubkey2) === BinaryData("0020ee5923cf81831016ae7fe319a8b5b33596b23b25363fbd1e6246e142a5b3d6a8"))
|
||||
}
|
||||
|
||||
"Protocol" should "implement anchor tx" in {
|
||||
|
||||
val (anchor, anchorOutputIndex) = makeAnchorTx(Alice.commitPubKey, Bob.commitPubKey, 10, OutPoint(previousTx, 0), key)
|
||||
test("create and spend anchor tx") {
|
||||
val (anchor, anchorOutputIndex) = makeAnchorTx(Alice.commitPubKey, Bob.commitPubKey, 10 * 1000, previousTx, 0, key)
|
||||
|
||||
val spending = Transaction(version = 1,
|
||||
txIn = TxIn(OutPoint(anchor, anchorOutputIndex), Array.emptyByteArray, 0xffffffffL) :: Nil,
|
||||
txOut = TxOut(10, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(hash160(Alice.commitPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
txOut = TxOut(10 satoshi, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(hash160(Alice.commitPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
lockTime = 0)
|
||||
|
||||
// we only need 2 signatures because this is a 2-on-3 multisig
|
||||
val redeemScript = Script.createMultiSigMofN(2, Seq(Alice.commitPubKey, Bob.commitPubKey))
|
||||
val redeemScript = multiSig2of2(Alice.commitPubKey, Bob.commitPubKey)
|
||||
val sig1 = Transaction.signInput(spending, 0, redeemScript, SIGHASH_ALL, anchor.txOut(anchorOutputIndex).amount.toLong, 1, Alice.commitKey)
|
||||
val sig2 = Transaction.signInput(spending, 0, redeemScript, SIGHASH_ALL, anchor.txOut(anchorOutputIndex).amount.toLong, 1, Bob.commitKey)
|
||||
val witness = if (isLess(Alice.commitPubKey, Bob.commitPubKey))
|
||||
ScriptWitness(Seq(BinaryData.empty, sig1, sig2, redeemScript))
|
||||
else
|
||||
ScriptWitness(Seq(BinaryData.empty, sig2, sig1, redeemScript))
|
||||
|
||||
val sig1 = Transaction.signInput(spending, 0, redeemScript, SIGHASH_ALL, Alice.commitKey, randomize = false)
|
||||
val sig2 = Transaction.signInput(spending, 0, redeemScript, SIGHASH_ALL, Bob.commitKey, randomize = false)
|
||||
val scriptSig = Script.write(OP_0 :: OP_PUSHDATA(sig1) :: OP_PUSHDATA(sig2) :: OP_PUSHDATA(redeemScript) :: Nil)
|
||||
val signedTx = spending.updateSigScript(0, scriptSig)
|
||||
val signedTx = spending.copy(witness = Seq(witness))
|
||||
Transaction.correctlySpends(signedTx, Seq(anchor), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
}
|
||||
it should "implement commit tx" in {
|
||||
val (anchor, anchorOutputIndex) = makeAnchorTx(Alice.commitPubKey, Bob.commitPubKey, 10, OutPoint(previousTx, 0), key)
|
||||
|
||||
test("create and spend commit tx") {
|
||||
val (anchor, anchorOutputIndex) = makeAnchorTx(Alice.commitPubKey, Bob.commitPubKey, 100000, previousTx, 0, key)
|
||||
val ours = open_channel(
|
||||
delay = locktime(Blocks(100)),
|
||||
revocationHash = Alice .H,
|
||||
revocationHash = Alice.H,
|
||||
commitKey = Alice.commitPubKey,
|
||||
finalKey = Alice.finalPubKey,
|
||||
anch = anchor_offer.WILL_CREATE_ANCHOR,
|
||||
@ -72,42 +81,47 @@ class ProtocolSpec extends FlatSpec {
|
||||
initialFeeRate = 1)
|
||||
|
||||
// we assume that Alice knows Bob's H
|
||||
val openAnchor = open_anchor(anchor.hash, anchorOutputIndex, 10, signature.defaultInstance) // commit sig will be computed later
|
||||
val channelState = initialFunding(ours, theirs, openAnchor, fee = 0)
|
||||
val openAnchor = open_anchor(anchor.hash, anchorOutputIndex, 1000*1000, signature.defaultInstance) // commit sig will be computed later
|
||||
val channelState = ChannelState.initialFunding(1000, ours.initialFeeRate) // initialFunding(ours, theirs, openAnchor, fee = 0)
|
||||
val tx = makeCommitTx(ours.finalKey, theirs.finalKey, theirs.delay, openAnchor.txid, openAnchor.outputIndex, Bob.H, channelState)
|
||||
val redeemScript = multiSig2of2(Alice.commitPubKey, Bob.commitPubKey)
|
||||
val sigA = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, Alice.commitKey)
|
||||
val sigA: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, anchor.txOut(anchorOutputIndex).amount.toLong, 1, Alice.commitKey)
|
||||
//val sigA: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, Alice.commitKey)
|
||||
val openAnchor1 = openAnchor.copy(commitSig = sigA)
|
||||
|
||||
// now Bob receives open anchor and wants to check that Alice's commit sig is valid
|
||||
// Bob can sign too and check that he can spend the anchox tx
|
||||
val sigB = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, Bob.commitKey)
|
||||
val scriptSig = sigScript2of2(openAnchor1.commitSig, sigB, Alice.commitPubKey, Bob.commitPubKey)
|
||||
val commitTx = tx.updateSigScript(0, scriptSig)
|
||||
val sigB: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, anchor.txOut(anchorOutputIndex).amount.toLong, 1, Bob.commitKey)
|
||||
//val sigB = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, Bob.commitKey)
|
||||
val witness = witness2of2(openAnchor1.commitSig, sigB, Alice.commitPubKey, Bob.commitPubKey)
|
||||
val commitTx = tx.copy(witness = Seq(witness))
|
||||
Transaction.correctlySpends(commitTx, Seq(anchor), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
// or Bob can just check that Alice's sig is valid
|
||||
val hash = Transaction.hashForSigning(commitTx, 0, redeemScript, SIGHASH_ALL)
|
||||
val hash = Transaction.hashForSigning(commitTx, 0, redeemScript, SIGHASH_ALL, anchor.txOut(anchorOutputIndex).amount.toLong, signatureVersion = 1)
|
||||
assert(Crypto.verifySignature(hash, Crypto.decodeSignature(sigA.dropRight(1)), Alice.commitPubKey))
|
||||
|
||||
// how do we spend our commit tx ?
|
||||
|
||||
// we can spend it by providing Bob's R and his signature
|
||||
val spendingTx = {
|
||||
val tx = Transaction(version = 1,
|
||||
val tx = Transaction(version = 2,
|
||||
txIn = TxIn(OutPoint(commitTx, 0), Array.emptyByteArray, 0xffffffffL) :: Nil,
|
||||
txOut = TxOut(10, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(hash160(Bob.finalPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
txOut = TxOut(10 satoshi, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(hash160(Bob.finalPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
lockTime = 0)
|
||||
val redeemScript = redeemSecretOrDelay(ours.finalKey, locktime2long_csv(theirs.delay), theirs.finalKey, Bob.H)
|
||||
val sig = Transaction.signInput(tx, 0, Script.write(redeemScript), SIGHASH_ALL, Bob.finalKey)
|
||||
val sig: BinaryData = Transaction.signInput(tx, 0, Script.write(redeemScript), SIGHASH_ALL, commitTx.txOut(0).amount.toLong, 1, Bob.finalKey)
|
||||
val witness = ScriptWitness(sig :: Bob.R :: BinaryData(Script.write(redeemScript)) :: Nil)
|
||||
val sigScript = OP_PUSHDATA(sig) :: OP_PUSHDATA(Bob.R) :: OP_PUSHDATA(Script.write(redeemScript)) :: Nil
|
||||
tx.updateSigScript(0, Script.write(sigScript))
|
||||
//tx.updateSigScript(0, Script.write(sigScript))
|
||||
tx.copy(witness = Seq(witness))
|
||||
}
|
||||
|
||||
// or
|
||||
|
||||
Transaction.correctlySpends(spendingTx, Seq(commitTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS | ScriptFlags.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | ScriptFlags.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY)
|
||||
}
|
||||
it should "sort binary data" in {
|
||||
|
||||
test("sort binary data") {
|
||||
assert(!isLess(Array.emptyByteArray, Array.emptyByteArray))
|
||||
assert(isLess(fromHexString("aa"), fromHexString("bb")))
|
||||
assert(isLess(fromHexString("aabbcc"), fromHexString("bbbbcc")))
|
||||
|
@ -56,10 +56,10 @@ class NominalChannelSpec extends BaseChannelTestClass {
|
||||
alice ! CMD_ADD_HTLC(60000000, H, locktime(Blocks(4)))
|
||||
|
||||
alice.stateData match {
|
||||
case DATA_NORMAL(_, _, _, _, _, _, List(Change2(OUT, _, update_add_htlc(_, _, h, _, _))), _, _) if h == bin2sha256(H) => {}
|
||||
case DATA_NORMAL(_, _, _, _, _, _, List(Change2(OUT, _, update_add_htlc(_, _, h, _, _))), _, _, _) if h == bin2sha256(H) => {}
|
||||
}
|
||||
bob.stateData match {
|
||||
case DATA_NORMAL(_, _, _, _, _, _, List(Change2(IN, _, update_add_htlc(_, _, h, _, _))), _, _) if h == bin2sha256(H) => {}
|
||||
case DATA_NORMAL(_, _, _, _, _, _, List(Change2(IN, _, update_add_htlc(_, _, h, _, _))), _, _, _) if h == bin2sha256(H) => {}
|
||||
}
|
||||
|
||||
alice ! CMD_SIGN
|
||||
|
@ -1,6 +1,6 @@
|
||||
package fr.acinq.eclair.channel
|
||||
|
||||
import fr.acinq.bitcoin.{BinaryData, BitcoinJsonRPCClient, Transaction, TxIn, TxOut}
|
||||
import fr.acinq.bitcoin.{BinaryData, BitcoinJsonRPCClient, Satoshi, Transaction, TxIn, TxOut}
|
||||
import fr.acinq.eclair.blockchain.ExtendedBitcoinClient
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
@ -13,7 +13,7 @@ class TestBitcoinClient extends ExtendedBitcoinClient(new BitcoinJsonRPCClient("
|
||||
override def makeAnchorTx(ourCommitPub: BinaryData, theirCommitPub: BinaryData, amount: Long)(implicit ec: ExecutionContext): Future[(Transaction, Int)] = {
|
||||
val anchorTx = Transaction(version = 1,
|
||||
txIn = Seq.empty[TxIn],
|
||||
txOut = TxOut(amount, Scripts.anchorPubkeyScript(ourCommitPub, theirCommitPub)) :: Nil,
|
||||
txOut = TxOut(Satoshi(amount), Scripts.anchorPubkeyScript(ourCommitPub, theirCommitPub)) :: Nil,
|
||||
lockTime = 0
|
||||
)
|
||||
Future.successful((anchorTx, 0))
|
||||
|
2
pom.xml
2
pom.xml
@ -44,7 +44,7 @@
|
||||
<scala.version>2.11.7</scala.version>
|
||||
<scala.version.short>2.11</scala.version.short>
|
||||
<akka.version>2.4.4</akka.version>
|
||||
<bitcoinlib.version>0.9.5</bitcoinlib.version>
|
||||
<bitcoinlib.version>0.9.6-RC1</bitcoinlib.version>
|
||||
<acinqtools.version>1.2</acinqtools.version>
|
||||
</properties>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user