1
0
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:
sstone 2016-05-23 17:14:42 +02:00
parent 4bde8bf018
commit 7903e2b53f
20 changed files with 352 additions and 249 deletions

View File

@ -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)

View File

@ -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) =>

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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 = {

View File

@ -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))

View File

@ -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)

View File

@ -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)

View File

@ -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)
// }
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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")))

View File

@ -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

View File

@ -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))

View File

@ -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>