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

replaced TxTemplate by TransactionWithInputInfo

This commit is contained in:
pm47 2016-12-16 19:03:41 +01:00
parent d334fe068e
commit 8447270445
12 changed files with 376 additions and 289 deletions

View file

@ -2,7 +2,7 @@ package fr.acinq.eclair.blockchain
import fr.acinq.bitcoin._
import fr.acinq.eclair.blockchain.rpc.{BitcoinJsonRPCClient, JsonRPCError}
import fr.acinq.eclair.{channel, transactions}
import fr.acinq.eclair.transactions
import fr.acinq.eclair.transactions.Common
import org.bouncycastle.util.encoders.Hex
import org.json4s.JsonAST._
@ -93,7 +93,7 @@ class ExtendedBitcoinClient(val client: BitcoinJsonRPCClient) {
publishTransaction(tx2Hex(tx))
def makeAnchorTx(ourCommitPub: BinaryData, theirCommitPub: BinaryData, amount: Satoshi)(implicit ec: ExecutionContext): Future[(Transaction, Int)] = {
val anchorOutputScript = transactions.Common.anchorPubkeyScript(ourCommitPub, theirCommitPub)
val anchorOutputScript = Script.write(Common.pay2wsh(Common.multiSig2of2(ourCommitPub, theirCommitPub)))
val tx = Transaction(version = 2, txIn = Seq.empty[TxIn], txOut = TxOut(amount, anchorOutputScript) :: Nil, lockTime = 0)
val future = for {
FundTransactionResponse(tx1, changepos, fee) <- fundTransaction(tx)
@ -113,7 +113,7 @@ class ExtendedBitcoinClient(val client: BitcoinJsonRPCClient) {
tx <- getTransaction(id)
Some(pos) = Common.findPublicKeyScriptIndex(tx, script)
output = tx.txOut(pos)
anchorOutputScript = transactions.Common.anchorPubkeyScript(ourCommitPub, theirCommitPub)
anchorOutputScript = Script.write(Common.pay2wsh(Common.multiSig2of2(ourCommitPub, theirCommitPub)))
tx1 = Transaction(version = 2, txIn = TxIn(OutPoint(tx, pos), Nil, 0xffffffffL) :: Nil, txOut = TxOut(amount, anchorOutputScript) :: Nil, lockTime = 0)
pubKeyScript = Script.write(OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pub)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil)
sig = Transaction.signInput(tx1, 0, pubKeyScript, SIGHASH_ALL, output.amount, 1, fundingPriv)

View file

@ -7,7 +7,6 @@ import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel.Helpers.Closing._
import fr.acinq.eclair.channel.Helpers.Funding
import fr.acinq.eclair.crypto.{Generators, ShaChain}
import fr.acinq.eclair.transactions.Signature._
import fr.acinq.eclair.transactions._
import fr.acinq.eclair.wire._
@ -180,16 +179,16 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
log.info(s"funding tx txid=${fundingTx.txid}")
// let's create the first commitment tx that spends the yet uncommitted funding tx
val (localSpec, localTx, remoteSpec, remoteTx, fundingTxOutput) = Funding.makeFirstCommitmentTx(funder = true, params, pushMsat, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint)
val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(funder = true, params, pushMsat, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint)
val localSigOfRemoteTx = Signature.sign(params.localParams, params.remoteParams, Satoshi(params.fundingSatoshis), remoteTx) // signature of their initial commitment tx that pays them pushMsat
val localSigOfRemoteTx = Transactions.sign(remoteCommitTx, localParams.fundingPrivkey) // signature of their initial commitment tx that pays them pushMsat
them ! FundingCreated(
temporaryChannelId = temporaryChannelId,
txid = fundingTx.hash,
outputIndex = fundingTxOutputIndex,
signature = localSigOfRemoteTx
)
goto(WAIT_FOR_FUNDING_SIGNED) using DATA_WAIT_FOR_FUNDING_SIGNED(temporaryChannelId, params, fundingTx, fundingTxOutputIndex, fundingTxOutput, localSpec, localTx, RemoteCommit(0, remoteSpec, remoteTx.txid, remoteFirstPerCommitmentPoint))
goto(WAIT_FOR_FUNDING_SIGNED) using DATA_WAIT_FOR_FUNDING_SIGNED(temporaryChannelId, params, fundingTx, localSpec, localCommitTx, RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, remoteFirstPerCommitmentPoint))
case Event(CMD_CLOSE(_), _) => goto(CLOSED)
@ -201,35 +200,36 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
when(WAIT_FOR_FUNDING_CREATED)(handleExceptions {
case Event(FundingCreated(_, fundingTxHash, fundingTxOutputIndex, remoteSig), DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId, params, pushMsat, remoteFirstPerCommitmentPoint)) =>
// they fund the channel with their funding tx, so the money is theirs (but we are paid pushMsat)
val (localSpec, localTx, remoteSpec, remoteTx, fundingTxOutput) = Funding.makeFirstCommitmentTx(funder = false, params, pushMsat, fundingTxHash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint)
val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(funder = false, params, pushMsat, fundingTxHash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint)
// check remote signature validity
val firstPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 0)
signAndCheckSig(params.localParams, params.remoteParams, fundingTxOutput, firstPerCommitmentPoint, localTx, remoteSig) match {
val localSigOfLocalTx = Transactions.sign(localCommitTx, localParams.fundingPrivkey)
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, params.localParams.fundingPrivkey.point, params.remoteParams.fundingPubkey, localSigOfLocalTx, remoteSig)
Transactions.checkSig(signedLocalCommitTx) match {
case Failure(cause) =>
log.error(cause, "their FundingCreated message contains an invalid signature")
them ! Error(temporaryChannelId, cause.getMessage.getBytes)
// we haven't anything at stake yet, we can just stop
goto(CLOSED)
case Success(signedTx) =>
log.info(s"signing remote tx: $remoteTx")
val localSigOfRemoteTx = Signature.sign(params.localParams, params.remoteParams, Satoshi(params.fundingSatoshis), remoteTx) // signature of their initial commitment tx that pays them pushMsat
case Success(_) =>
log.info(s"signing remote tx: $remoteCommitTx")
val localSigOfRemoteTx = Transactions.sign(remoteCommitTx, localParams.fundingPrivkey)
them ! FundingSigned(
temporaryChannelId = temporaryChannelId,
signature = localSigOfRemoteTx
)
// watch the funding tx transaction
val fundingTxid = fundingTxHash.reverse //see https://github.com/ElementsProject/lightning/issues/17
blockchain ! WatchSpent(self, fundingTxid, fundingTxOutputIndex, 0, BITCOIN_FUNDING_SPENT) // TODO: should we wait for an acknowledgment from the watcher?
blockchain ! WatchConfirmed(self, fundingTxid, params.minimumDepth.toInt, BITCOIN_FUNDING_DEPTHOK)
val commitInput = localCommitTx.input
blockchain ! WatchSpent(self, commitInput.outPoint.txid, commitInput.outPoint.index.toInt, minDepth = 0, BITCOIN_FUNDING_SPENT) // TODO: should we wait for an acknowledgment from the watcher?
blockchain ! WatchConfirmed(self, commitInput.outPoint.txid, params.minimumDepth.toInt, BITCOIN_FUNDING_DEPTHOK)
val commitments = Commitments(params.localParams, params.remoteParams,
LocalCommit(0, localSpec, signedTx), RemoteCommit(0, remoteSpec, remoteTx.txid, remoteFirstPerCommitmentPoint),
LocalCommit(0, localSpec, signedLocalCommitTx.tx), RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, remoteFirstPerCommitmentPoint),
LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil),
localCurrentHtlcId = 0L,
remoteNextCommitInfo = Right(BinaryData("")), // we will receive their next per-commitment point in the next message, so we temporarily put an empty byte array
fundingTxOutput, ShaChain.init, new BasicTxDb, 0)
commitInput, ShaChain.init, new BasicTxDb, 0)
context.system.eventStream.publish(ChannelIdAssigned(self, commitments.anchorId, Satoshi(params.fundingSatoshis)))
goto(WAIT_FOR_FUNDING_LOCKED_INTERNAL) using DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL(temporaryChannelId, params, commitments, None)
}
@ -242,26 +242,27 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
})
when(WAIT_FOR_FUNDING_SIGNED)(handleExceptions {
case Event(FundingSigned(_, remoteSig), DATA_WAIT_FOR_FUNDING_SIGNED(temporaryChannelId, params, fundingTx, fundingTxOutputIndex, fundingTxOutput, localSpec, localTx, remoteCommit)) =>
case Event(FundingSigned(_, remoteSig), DATA_WAIT_FOR_FUNDING_SIGNED(temporaryChannelId, params, fundingTx, localSpec, localCommitTx, remoteCommit)) =>
// we make sure that their sig checks out and that our first commit tx is spendable
log.info(s"checking our tx: $localTx")
val firstPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 0)
signAndCheckSig(params.localParams, params.remoteParams, fundingTxOutput, firstPerCommitmentPoint, localTx, remoteSig) match {
val localSigOfLocalTx = Transactions.sign(localCommitTx, localParams.fundingPrivkey)
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, params.localParams.fundingPrivkey.point, params.remoteParams.fundingPubkey, localSigOfLocalTx, remoteSig)
Transactions.checkSig(signedLocalCommitTx) match {
case Failure(cause) =>
log.error(cause, "their FundingSigned message contains an invalid signature")
them ! Error(temporaryChannelId, cause.getMessage.getBytes)
// we haven't published anything yet, we can just stop
goto(CLOSED)
case Success(signedTx) =>
blockchain ! WatchSpent(self, fundingTx.txid, fundingTxOutputIndex, 0, BITCOIN_FUNDING_SPENT) // TODO: should we wait for an acknowledgment from the watcher?
blockchain ! WatchConfirmed(self, fundingTx.txid, params.minimumDepth, BITCOIN_FUNDING_DEPTHOK)
case Success(_) =>
val commitInput = localCommitTx.input
blockchain ! WatchSpent(self, commitInput.outPoint.txid, commitInput.outPoint.index.toInt, minDepth = 0, BITCOIN_FUNDING_SPENT) // TODO: should we wait for an acknowledgment from the watcher?
blockchain ! WatchConfirmed(self, commitInput.outPoint.txid, params.minimumDepth, BITCOIN_FUNDING_DEPTHOK)
blockchain ! Publish(fundingTx)
val commitments = Commitments(params.localParams, params.remoteParams,
LocalCommit(0, localSpec, signedTx), remoteCommit,
LocalCommit(0, localSpec, signedLocalCommitTx.tx), remoteCommit,
LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil),
localCurrentHtlcId = 0L,
remoteNextCommitInfo = Right(BinaryData("")), // we will receive their next per-commitment point in the next message, so we temporarily put an empty byte array
fundingTxOutput, ShaChain.init, new BasicTxDb, 0)
commitInput, ShaChain.init, new BasicTxDb, 0)
context.system.eventStream.publish(ChannelIdAssigned(self, commitments.anchorId, Satoshi(params.fundingSatoshis)))
context.system.eventStream.publish(ChannelSignatureReceived(self, commitments))
goto(WAIT_FOR_FUNDING_LOCKED_INTERNAL) using DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL(temporaryChannelId, params, commitments, None)
@ -591,7 +592,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
log.info(s"finalTxId=${finalTx.txid}")
them ! closingSigned
if (closeFee == theirCloseFee) {
val signedTx = addSigs(finalTx, d.commitments.localParams.fundingPrivkey.point, d.commitments.remoteParams.fundingPubkey, localSig, remoteSig)
val signedTx: Transaction = ??? //Signature.addSigs(finalTx, d.commitments.localParams.fundingPrivkey.point, d.commitments.remoteParams.fundingPubkey, localSig, remoteSig)
blockchain ! Publish(signedTx)
blockchain ! WatchConfirmed(self, signedTx.txid, 3, BITCOIN_CLOSE_DONE) // hardcoded mindepth
goto(CLOSING) using DATA_CLOSING(d.commitments, ourSignature = Some(closingSigned), mutualClosePublished = Some(signedTx))

View file

@ -1,8 +1,9 @@
package fr.acinq.eclair.channel
import fr.acinq.bitcoin.{BinaryData, Transaction, TxOut}
import fr.acinq.bitcoin.{BinaryData, Transaction}
import fr.acinq.eclair.crypto.Generators.{Point, Scalar}
import fr.acinq.eclair.transactions.CommitmentSpec
import fr.acinq.eclair.transactions.Transactions.CommitTx
import fr.acinq.eclair.wire.{ClosingSigned, FundingLocked, Shutdown}
import lightning.{route, route_step}
@ -124,7 +125,7 @@ final case class DATA_WAIT_FOR_OPEN_CHANNEL(localParams: LocalParams, autoSignIn
final case class DATA_WAIT_FOR_ACCEPT_CHANNEL(temporaryChannelId: Long, localParams: LocalParams, fundingSatoshis: Long, pushMsat: Long, autoSignInterval: Option[FiniteDuration]) extends Data
final case class DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId: Long, params: ChannelParams, pushMsat: Long, remoteFirstPerCommitmentPoint: BinaryData) extends Data
final case class DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId: Long, params: ChannelParams, pushMsat: Long, remoteFirstPerCommitmentPoint: BinaryData) extends Data
final case class DATA_WAIT_FOR_FUNDING_SIGNED(temporaryChannelId: Long, params: ChannelParams, fundingTx: Transaction, fundingTxOutputIndex: Int, fundingTxOutput: TxOut, localSpec: CommitmentSpec, localTx: Transaction, remoteCommit: RemoteCommit) extends Data
final case class DATA_WAIT_FOR_FUNDING_SIGNED(temporaryChannelId: Long, params: ChannelParams, fundingTx: Transaction, localSpec: CommitmentSpec, localCommitTx: CommitTx, remoteCommit: RemoteCommit) extends Data
final case class DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL(temporaryChannelId: Long, params: ChannelParams, commitments: Commitments, deferred: Option[FundingLocked]) extends Data with HasCommitments
final case class DATA_NORMAL(channelId: Long, params: ChannelParams, commitments: Commitments, ourShutdown: Option[Shutdown], downstreams: Map[Long, Option[Origin]]) extends Data with HasCommitments
final case class DATA_SHUTDOWN(channelId: Long, params: ChannelParams, commitments: Commitments,

View file

@ -1,8 +1,9 @@
package fr.acinq.eclair.channel
import fr.acinq.bitcoin.{BinaryData, Crypto, Satoshi, ScriptFlags, Transaction, TxOut}
import fr.acinq.bitcoin.{BinaryData, Crypto, ScriptFlags, Transaction}
import fr.acinq.eclair.crypto.LightningCrypto.sha256
import fr.acinq.eclair.crypto.ShaChain
import fr.acinq.eclair.transactions.Transactions.{CommitTx, InputInfo}
import fr.acinq.eclair.transactions._
import fr.acinq.eclair.wire._
@ -29,7 +30,7 @@ case class Commitments(localParams: LocalParams, remoteParams: RemoteParams,
localChanges: LocalChanges, remoteChanges: RemoteChanges,
localCurrentHtlcId: Long,
remoteNextCommitInfo: Either[RemoteCommit, BinaryData],
fundingTxOutput: TxOut,
commitInput: InputInfo,
remotePerCommitmentSecrets: ShaChain, txDb: TxDb, channelId: Long) {
def anchorId: BinaryData = {
assert(localCommit.publishableTx.txIn.size == 1, "commitment tx should only have one input")
@ -139,13 +140,11 @@ object Commitments {
case Right(remoteNextPerCommitmentPoint) =>
// remote commitment will includes all local changes + remote acked changes
val spec = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed)
val (remoteTxTemplate, htlcTimeoutTxTemplates, htlcSuccessTxTemplates) = CommitmentSpec.makeRemoteTxTemplates(localParams, remoteParams, localCommit.publishableTx.txIn, remoteNextPerCommitmentPoint, spec)
val remoteTx = remoteTxTemplate.makeTx
val sig = Signature.sign(localParams, remoteParams, fundingTxOutput.amount, remoteTx)
val (remoteCommitTx, htlcTimeoutTx, htlcSuccessTx) = CommitmentSpec.makeRemoteTxs(localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, spec)
val sig = Transactions.sign(remoteCommitTx, localParams.fundingPrivkey)
// TODO: sigs!
val htlcTimeoutSigs: List[BinaryData] = ??? //htlcTimeoutTxTemplates.map(tpl => Signature.sign(localParams, remoteParams, fundingTxOutput.amount, tpl.makeTx))
val htlcSuccessSigs: List[BinaryData] = ??? //htlcTimeoutTxTemplates.map(tpl => Signature.sign(localParams, remoteParams, fundingTxOutput.amount, tpl.makeTx))
val htlcTimeoutSigs: List[BinaryData] = ??? //htlcTimeoutTx.map(tpl => Signature.sign(localParams, remoteParams, fundingTxOutput.amount, tpl.makeTx))
val htlcSuccessSigs: List[BinaryData] = ??? //htlcTimeoutTx.map(tpl => Signature.sign(localParams, remoteParams, fundingTxOutput.amount, tpl.makeTx))
// don't sign if they don't get paid
val commitSig = CommitSig(
@ -154,7 +153,7 @@ object Commitments {
htlcSignatures = htlcTimeoutSigs ++ htlcSuccessSigs // TODO: ordering!!
)
val commitments1 = commitments.copy(
remoteNextCommitInfo = Left(RemoteCommit(remoteCommit.index + 1, spec, remoteTx.txid, remoteNextPerCommitmentPoint)),
remoteNextCommitInfo = Left(RemoteCommit(remoteCommit.index + 1, spec, remoteCommitTx.tx.txid, remoteNextPerCommitmentPoint)),
localChanges = localChanges.copy(proposed = Nil, signed = localChanges.proposed),
remoteChanges = remoteChanges.copy(acked = Nil))
(commitments1, commitSig)
@ -183,7 +182,7 @@ object Commitments {
val spec = CommitmentSpec.reduce(localCommit.spec, localChanges.acked, remoteChanges.proposed)
val ourNextRevocationHash = revocationHash(localParams.shaSeed, localCommit.index + 1)
val ourTxTemplate = CommitmentSpec.makeLocalTxTemplate(localParams, remoteParams, localCommit.publishableTx.txIn, ourNextRevocationHash, spec)
val ourTxTemplate = CommitmentSpec.makeLocalTxs(localParams, remoteParams, commitInput, ourNextRevocationHash, spec)
// this tx will NOT be signed if our output is empty
val ourCommitTx: Transaction = ???
@ -221,11 +220,10 @@ object Commitments {
throw new RuntimeException("invalid preimage")
case Left(theirNextCommit) =>
// this is their revoked commit tx
val (remoteTxTemplate, htlcTimeoutTxTemplates, htlcSuccessTxTemplates) = CommitmentSpec.makeRemoteTxTemplates(localParams, remoteParams, localCommit.publishableTx.txIn, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec)
val theirTx = remoteTxTemplate.makeTx
val (remoteCommitTx, htlcTimeoutCommitTx, htlcSuccessCommitTx) = CommitmentSpec.makeRemoteTxs(localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec)
val punishTx: Transaction = ??? //Helpers.claimRevokedCommitTx(theirTxTemplate, revocation.revocationPreimage, localParams.finalPrivKey)
Transaction.correctlySpends(punishTx, Seq(theirTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
txDb.add(theirTx.txid, punishTx)
//Transaction.correctlySpends(punishTx, Seq(theirTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
//txDb.add(theirTx.txid, punishTx)
commitments.copy(
localChanges = localChanges.copy(signed = Nil, acked = localChanges.acked ++ localChanges.signed),
@ -237,8 +235,8 @@ object Commitments {
}
}
def makeLocalTxTemplate(commitments: Commitments): CommitTxTemplate = {
CommitmentSpec.makeLocalTxTemplate(commitments.localParams, commitments.remoteParams, commitments.localCommit.publishableTx.txIn,
def makeLocalTxTemplate(commitments: Commitments): CommitTx = {
CommitmentSpec.makeLocalTxs(commitments.localParams, commitments.remoteParams, commitments.commitInput,
revocationHash(commitments.localParams.shaSeed, commitments.localCommit.index), commitments.localCommit.spec)
}
}

View file

@ -3,8 +3,8 @@ package fr.acinq.eclair.channel
import fr.acinq.bitcoin.{OutPoint, _}
import fr.acinq.eclair.crypto.Generators
import fr.acinq.eclair.transactions.Common._
import fr.acinq.eclair.transactions.Transactions.{CommitTx, InputInfo}
import fr.acinq.eclair.transactions._
import fr.acinq.eclair.wire.UpdateFulfillHtlc
import scala.util.Try
@ -16,15 +16,15 @@ object Helpers {
object Funding {
/**
* Extracts a [TxIn] from a funding tx id and an output index
* @param fundingTxId
* @param fundingTxOutputIndex
*/
def inputFromFundingTx(fundingTxId: BinaryData, fundingTxOutputIndex: Int): TxIn = TxIn(OutPoint(fundingTxId, fundingTxOutputIndex), Array.emptyByteArray, 0xffffffffL)
def makeFundingInputInfo(fundingTxId: BinaryData, fundingTxOutputIndex: Int, fundingSatoshis: Satoshi, fundingPubkey1: BinaryData, fundingPubkey2: BinaryData): InputInfo = {
val fundingScript = multiSig2of2(fundingPubkey1, fundingPubkey2)
val fundingTxOut = TxOut(fundingSatoshis, pay2wsh(fundingScript))
InputInfo(OutPoint(fundingTxId, fundingTxOutputIndex), fundingTxOut, fundingScript)
}
/**
* Creates both sides's first commitment transaction
*
* @param funder
* @param params
* @param pushMsat
@ -33,7 +33,7 @@ object Helpers {
* @param remoteFirstPerCommitmentPoint
* @return (localSpec, localTx, remoteSpec, remoteTx, fundingTxOutput)
*/
def makeFirstCommitmentTx(funder: Boolean, params: ChannelParams, pushMsat: Long, fundingTxHash: BinaryData, fundingTxOutputIndex: Int, remoteFirstPerCommitmentPoint: BinaryData): (CommitmentSpec, Transaction, CommitmentSpec, Transaction, TxOut) = {
def makeFirstCommitTxs(funder: Boolean, params: ChannelParams, pushMsat: Long, fundingTxHash: BinaryData, fundingTxOutputIndex: Int, remoteFirstPerCommitmentPoint: BinaryData): (CommitmentSpec, CommitTx, CommitmentSpec, CommitTx) = {
val toLocalMsat = if (funder) params.fundingSatoshis * 1000 - pushMsat else pushMsat
val toRemoteMsat = if (funder) pushMsat else params.fundingSatoshis * 1000 - pushMsat
@ -41,16 +41,12 @@ object Helpers {
val localSpec = CommitmentSpec(Set.empty[Htlc], feeRate = params.localParams.feeratePerKw, to_local_msat = toLocalMsat, to_remote_msat = toRemoteMsat)
val remoteSpec = CommitmentSpec(Set.empty[Htlc], feeRate = params.remoteParams.feeratePerKw, to_local_msat = toRemoteMsat, to_remote_msat = toLocalMsat)
val commitmentInput = Funding.inputFromFundingTx(fundingTxHash, fundingTxOutputIndex)
val commitmentInput = makeFundingInputInfo(fundingTxHash, fundingTxOutputIndex, Satoshi(params.fundingSatoshis), params.localParams.fundingPrivkey.point, params.remoteParams.fundingPubkey)
val localPerCommitmentPoint = Generators.perCommitPoint(params.localParams.shaSeed, 0)
val localTx = CommitmentSpec.makeLocalTxTemplate(params.localParams, params.remoteParams, commitmentInput :: Nil, localPerCommitmentPoint.data, localSpec).makeTx
val (remoteTxTemplate, _, _) = CommitmentSpec.makeRemoteTxTemplates(params.localParams, params.remoteParams, commitmentInput :: Nil, remoteFirstPerCommitmentPoint, remoteSpec)
val remoteTx = remoteTxTemplate.makeTx
val localTxTemplate = CommitmentSpec.makeLocalTxs(params.localParams, params.remoteParams, commitmentInput, localPerCommitmentPoint.data, localSpec)
val (remoteTxTemplate, _, _) = CommitmentSpec.makeRemoteTxs(params.localParams, params.remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, remoteSpec)
val localFundingPubkey = params.localParams.fundingPrivkey.point
val fundingTxOutput = TxOut(Satoshi(params.fundingSatoshis), publicKeyScript = Common.anchorPubkeyScript(localFundingPubkey, params.remoteParams.fundingPubkey))
(localSpec, localTx, remoteSpec, remoteTx, fundingTxOutput)
(localSpec, localTxTemplate, remoteSpec, remoteTxTemplate)
}
}
@ -92,7 +88,7 @@ object Helpers {
* last commit tx
*/
def makeFinalTx(commitments: Commitments, ourScriptPubKey: BinaryData, theirScriptPubKey: BinaryData): (Transaction, Long, BinaryData) = {
val commitFee = commitments.fundingTxOutput.amount.toLong - commitments.localCommit.publishableTx.txOut.map(_.amount.toLong).sum
val commitFee = commitments.commitInput.txOut.amount.toLong - commitments.localCommit.publishableTx.txOut.map(_.amount.toLong).sum
val closeFee = Satoshi(2 * (commitFee / 4))
makeFinalTx(commitments, ourScriptPubKey, theirScriptPubKey, closeFee)
}
@ -106,7 +102,9 @@ object Helpers {
* @param privateKey private key to send the claimed funds to (the returned tx will include a single P2WPKH output)
* @return a signed transaction that spends the revoked commit tx
*/
def claimRevokedCommitTx(theirTxTemplate: CommitTxTemplate, revocationPreimage: BinaryData, privateKey: BinaryData): Transaction = {
def claimRevokedCommitTx(theirTxTemplate: CommitTx, revocationPreimage: BinaryData, privateKey: BinaryData): Transaction = ???
/*{
val theirTx = theirTxTemplate.makeTx
val outputs = collection.mutable.ListBuffer.empty[TxOut]
@ -136,7 +134,7 @@ object Helpers {
}
tx1.updateWitnesses(witnesses)
}
}*/
/**
* claim an HTLC that we received using its payment preimage. This is used only when the other party publishes its
@ -150,7 +148,7 @@ object Helpers {
* This tx is not spendable right away: it has both an absolute CLTV time-out and a relative CSV time-out
* before which it can be published
*/
def claimReceivedHtlc(tx: Transaction, htlcTemplate: ReceivedHTLCOutputTemplate, paymentPreimage: BinaryData, privateKey: BinaryData): Transaction = ???
//def claimReceivedHtlc(tx: Transaction, htlcTemplate: ReceivedHTLC, paymentPreimage: BinaryData, privateKey: BinaryData): Transaction = ???
/*{
require(htlcTemplate.htlc.add.paymentHash == BinaryData(Crypto.sha256(paymentPreimage)), "invalid payment preimage")
// find its index in their tx
@ -174,7 +172,7 @@ object Helpers {
* @param commitments our commitment data, which include payment preimages
* @return a list of transactions (one per HTLC that we can claim)
*/
def claimReceivedHtlcs(tx: Transaction, txTemplate: CommitTxTemplate, commitments: Commitments): Seq[Transaction] = ???
//def claimReceivedHtlcs(tx: Transaction, txTemplate: CommitTxTemplate, commitments: Commitments): Seq[Transaction] = ???
/*{
val preImages = commitments.localChanges.all.collect { case UpdateFulfillHtlc(_, id, paymentPreimage) => paymentPreimage }
// TODO: FIXME !!!
@ -197,7 +195,7 @@ object Helpers {
loop(htlcTemplates)
}*/
def claimSentHtlc(tx: Transaction, htlcTemplate: OfferedHTLCOutputTemplate, privateKey: BinaryData): Transaction = ???
//def claimSentHtlc(tx: Transaction, htlcTemplate: OfferedHTLCOutputTemplate, privateKey: BinaryData): Transaction = ???
/*{
val index = tx.txOut.indexOf(htlcTemplate.txOut)
val tx1 = Transaction(
@ -212,7 +210,7 @@ object Helpers {
}*/
// TODO: fix this!
def claimSentHtlcs(tx: Transaction, txTemplate: CommitTxTemplate, commitments: Commitments): Seq[Transaction] = Nil
//def claimSentHtlcs(tx: Transaction, txTemplate: CommitTxTemplate, commitments: Commitments): Seq[Transaction] = Nil
/*{
// txTemplate could be our template (we published our commit tx) or their template (they published their commit tx)

View file

@ -1,12 +1,13 @@
package fr.acinq.eclair.transactions
import fr.acinq.bitcoin.{BinaryData, TxIn}
import fr.acinq.bitcoin.BinaryData
import fr.acinq.eclair._
import fr.acinq.eclair.channel.{LocalParams, RemoteParams}
import fr.acinq.eclair.crypto.Generators
import fr.acinq.eclair.crypto.Generators.Point
import fr.acinq.eclair.crypto.LightningCrypto.sha256
import fr.acinq.eclair.transactions.Transactions.{CommitTx, HtlcSuccessTx, HtlcTimeoutTx, InputInfo}
import fr.acinq.eclair.wire.{UpdateAddHtlc, UpdateFailHtlc, UpdateFulfillHtlc, UpdateMessage}
import fr.acinq.eclair._
/**
* Created by PM on 07/12/2016.
@ -78,19 +79,19 @@ object CommitmentSpec {
spec4
}
def makeLocalTxTemplate(localParams: LocalParams, remoteParams: RemoteParams, inputs: Seq[TxIn], localPerCommitmentPoint: Point, spec: CommitmentSpec): CommitTxTemplate = {
def makeLocalTxs(localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: Point, spec: CommitmentSpec): CommitTx = {
val localPubkey = Generators.derivePubKey(localParams.delayedPaymentKey.point, localPerCommitmentPoint)
val remotePubkey = Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint)
val localRevocationPubkey = Generators.revocationPubKey(localParams.revocationSecret.point, localPerCommitmentPoint)
CommitTxTemplate.makeCommitTxTemplate(inputs, localRevocationPubkey, localParams.toSelfDelay, localPubkey, remotePubkey, spec)
Transactions.makeCommitTx(commitmentInput, localRevocationPubkey, localParams.toSelfDelay, localPubkey, remotePubkey, spec)
}
def makeRemoteTxTemplates(localParams: LocalParams, remoteParams: RemoteParams, inputs: Seq[TxIn], remotePerCommitmentPoint: Point, spec: CommitmentSpec): (CommitTxTemplate, Seq[HTLCTimeoutTxTemplate], Seq[HTLCSuccessTxTemplate]) = {
def makeRemoteTxs(localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: Point, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = {
val localPubkey = Generators.derivePubKey(localParams.paymentSecret.point, remotePerCommitmentPoint)
val remotePubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint)
val remoteRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, remotePerCommitmentPoint)
val commitTxTemplate = CommitTxTemplate.makeCommitTxTemplate(inputs, remoteRevocationPubkey, remoteParams.toSelfDelay, remotePubkey, localPubkey, spec)
val (htlcTimeoutTxTemplates, htlcSuccessTxTemplates) = CommitTxTemplate.makeHtlcTxTemplates(commitTxTemplate.makeTx, remoteRevocationPubkey, remoteParams.toSelfDelay, remotePubkey, localPubkey, spec)
(commitTxTemplate, htlcTimeoutTxTemplates, htlcSuccessTxTemplates)
val commitTx = Transactions.makeCommitTx(commitmentInput, remoteRevocationPubkey, remoteParams.toSelfDelay, remotePubkey, localPubkey, spec)
val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, remoteRevocationPubkey, remoteParams.toSelfDelay, remotePubkey, localPubkey, spec)
(commitTx, htlcTimeoutTxs, htlcSuccessTxs)
}
}

View file

@ -37,12 +37,6 @@ object Common {
else
Script.createMultiSigMofN(2, Seq(pubkey2, pubkey1))
def sigScript2of2(sig1: BinaryData, sig2: BinaryData, pubkey1: BinaryData, pubkey2: BinaryData): BinaryData = if (isLess(pubkey1, pubkey2))
Script.write(OP_0 :: OP_PUSHDATA(sig1) :: OP_PUSHDATA(sig2) :: OP_PUSHDATA(multiSig2of2(pubkey1, pubkey2)) :: Nil)
else
Script.write(OP_0 :: OP_PUSHDATA(sig2) :: OP_PUSHDATA(sig1) :: OP_PUSHDATA(multiSig2of2(pubkey1, pubkey2)) :: Nil)
/**
*
* @param sig1
@ -82,7 +76,7 @@ object Common {
* @param key private key that can redeem the funding tx
* @return a signed funding tx
*/
def makeAnchorTx(pubkey1: BinaryData, pubkey2: BinaryData, amount: Long, previousTx: Transaction, outputIndex: Int, key: BinaryData): (Transaction, Int) = {
def makeFundingTx(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,
@ -104,8 +98,6 @@ object Common {
(signedTx, 0)
}
def anchorPubkeyScript(pubkey1: BinaryData, pubkey2: BinaryData): BinaryData = Script.write(pay2wsh(multiSig2of2(pubkey1, pubkey2)))
def encodeNumber(n: Long): BinaryData = {
// TODO: added for compatibility with lightningd => check (it's either a bug in lighningd or bitcoin-lib)
if (n < 0xff) Protocol.writeUInt8(n.toInt) else Script.encodeNumber(n)
@ -287,4 +279,19 @@ object OutputScripts {
object InputScripts {
def witnessHtlcReceived(localSig: BinaryData, paymentPreimage: BinaryData, htlcOfferedScript: BinaryData) =
ScriptWitness(localSig :: paymentPreimage :: htlcOfferedScript :: Nil)
def witnessHtlcSuccess(localSig: BinaryData, remoteSig: BinaryData, paymentPreimage: BinaryData, htlcOfferedScript: BinaryData) =
ScriptWitness(BinaryData.empty :: remoteSig :: localSig :: paymentPreimage :: htlcOfferedScript :: Nil)
def witnessHtlcOffered(localSig: BinaryData, htlcReceivedScript: BinaryData) =
ScriptWitness(localSig :: BinaryData.empty :: htlcReceivedScript :: Nil)
def witnessHtlcTimeout(localSig: BinaryData, remoteSig: BinaryData, htlcReceivedScript: BinaryData) =
ScriptWitness(BinaryData.empty :: remoteSig :: localSig :: BinaryData.empty :: htlcReceivedScript :: Nil)
def witnessHtlcDelayed(localSig: BinaryData, htlcDelayedScript: BinaryData) =
ScriptWitness(localSig :: BinaryData.empty :: htlcDelayedScript :: Nil)
}

View file

@ -1,34 +0,0 @@
package fr.acinq.eclair.transactions
import fr.acinq.bitcoin.{BinaryData, SIGHASH_ALL, Satoshi, ScriptFlags, Transaction, TxOut}
import fr.acinq.eclair.channel.{LocalParams, RemoteParams}
import fr.acinq.eclair.crypto.Generators.{Point, Scalar}
import scala.util.Try
/**
* Created by PM on 07/12/2016.
*/
object Signature {
def sign(localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Satoshi, tx: Transaction): BinaryData = {
// this is because by convention in bitcoin-core-speak 32B keys are 'uncompressed' and 33B keys (ending by 0x01) are 'compressed'
val localCompressedFundingPrivkey: Seq[Byte] = localParams.fundingPrivkey.data.toSeq :+ 1.toByte
Transaction.signInput(tx, 0, Common.multiSig2of2(localParams.fundingPrivkey.point, remoteParams.fundingPubkey), SIGHASH_ALL, fundingSatoshis, 1, localCompressedFundingPrivkey)
}
def addSigs(tx: Transaction, localFundingPubkey: Point, remoteFundingPubkey: Point, localSig: BinaryData, remoteSig: BinaryData): Transaction = {
val witness = Common.witness2of2(localSig, remoteSig, localFundingPubkey, remoteFundingPubkey)
tx.updateWitness(0, witness)
}
def checksig(anchorOutput: TxOut, tx: Transaction): Try[Unit] =
Try(Transaction.correctlySpends(tx, Map(tx.txIn(0).outPoint -> anchorOutput), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS))
def signAndCheckSig(localParams: LocalParams, remoteParams: RemoteParams, anchorOutput: TxOut, localPerCommitmentPoint: Point, tx: Transaction, remoteSig: BinaryData): Try[Transaction] = {
val localSig = sign(localParams: LocalParams, remoteParams: RemoteParams, anchorOutput.amount, tx)
val signedTx = addSigs(tx, localParams.fundingPrivkey.point, remoteParams.fundingPubkey, localSig, remoteSig)
checksig(anchorOutput, signedTx).map(_ => signedTx)
}
}

View file

@ -0,0 +1,176 @@
package fr.acinq.eclair.transactions
import fr.acinq.bitcoin.Crypto.ripemd160
import fr.acinq.bitcoin.SigVersion.SIGVERSION_WITNESS_V0
import fr.acinq.bitcoin.{BinaryData, OutPoint, SIGHASH_ALL, Satoshi, Script, ScriptElt, ScriptFlags, Transaction, TxIn, TxOut}
import fr.acinq.eclair.crypto.Generators.Point
import fr.acinq.eclair.transactions.Common.{pay2wsh, _}
import fr.acinq.eclair.transactions.OutputScripts.{htlcSuccessOrTimeout, _}
import fr.acinq.eclair.wire.UpdateAddHtlc
import scala.util.Try
/**
* Created by PM on 15/12/2016.
*/
object Transactions {
// @formatter:off
case class InputInfo(outPoint: OutPoint, txOut: TxOut, redeemScript: BinaryData)
sealed trait TransactionWithInputInfo {
def input: InputInfo
def tx: Transaction
}
case class CommitTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo
case class HtlcSuccessTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo
case class HtlcTimeoutTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo
case class ClaimHtlcSuccessTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo
case class ClaimHtlcTimeoutTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo
case class ClaimHtlcDelayed(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo
// @formatter:on
def makeCommitTx(commitTxInput: InputInfo, localRevocationPubkey: BinaryData, toLocalDelay: Int, localPubkey: BinaryData, remotePubkey: BinaryData, spec: CommitmentSpec): CommitTx = {
// TODO: no fees!!!
val toLocalDelayedOutput_opt = if (spec.to_local_msat >= 546000) Some(TxOut(Satoshi(spec.to_local_msat / 1000), pay2wsh(toLocal(localRevocationPubkey, toLocalDelay, localPubkey)))) else None
val toRemoteOutput_opt = if (spec.to_remote_msat >= 546000) Some(TxOut(Satoshi(spec.to_remote_msat / 1000), pay2wpkh(toRemote(remotePubkey)))) else None
val htlcOfferedOutputs = spec.htlcs.
filter(_.direction == OUT)
.map(htlc => TxOut(Satoshi(htlc.add.amountMsat / 1000), pay2wsh(htlcOffered(localPubkey, remotePubkey, ripemd160(htlc.add.paymentHash)))))
val htlcReceivedOutputs = spec.htlcs.
filter(_.direction == IN)
.map(htlc => TxOut(Satoshi(htlc.add.amountMsat / 1000), pay2wsh(htlcReceived(localPubkey, remotePubkey, ripemd160(htlc.add.paymentHash), htlc.add.expiry))))
val tx = Transaction(
version = 2,
txIn = TxIn(commitTxInput.outPoint, Array.emptyByteArray, 0xffffffffL) :: Nil,
txOut = toLocalDelayedOutput_opt.toSeq ++ toRemoteOutput_opt.toSeq ++ htlcOfferedOutputs.toSeq ++ htlcReceivedOutputs.toSeq,
lockTime = 0)
CommitTx(commitTxInput, permuteOutputs(tx))
}
def makeHtlcTimeoutTx(commitTx: Transaction, localRevocationPubkey: BinaryData, toLocalDelay: Int, localPubkey: BinaryData, remotePubkey: BinaryData, htlc: UpdateAddHtlc): HtlcTimeoutTx = {
val redeemScript = htlcOffered(localPubkey, remotePubkey, ripemd160(htlc.paymentHash))
val pubkeyScript = Script.write(pay2wsh(redeemScript))
val outputIndex = findPubKeyScriptIndex(commitTx, pubkeyScript)
require(outputIndex >= 0, "output not found")
val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), Script.write(redeemScript))
HtlcTimeoutTx(input, Transaction(
version = 2,
txIn = TxIn(input.outPoint, Array.emptyByteArray, 0xffffffffL) :: Nil,
txOut = TxOut(Satoshi(htlc.amountMsat / 1000), pay2wsh(htlcSuccessOrTimeout(localRevocationPubkey, toLocalDelay, localPubkey))) :: Nil,
lockTime = htlc.expiry))
}
def makeHtlcSuccessTx(commitTx: Transaction, localRevocationPubkey: BinaryData, toLocalDelay: Int, localPubkey: BinaryData, remotePubkey: BinaryData, htlc: UpdateAddHtlc): HtlcSuccessTx = {
val redeemScript = htlcReceived(localPubkey, remotePubkey, ripemd160(htlc.paymentHash), htlc.expiry)
val pubkeyScript = Script.write(pay2wsh(redeemScript))
val outputIndex = findPubKeyScriptIndex(commitTx, pubkeyScript)
require(outputIndex >= 0, "output not found")
val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), Script.write(redeemScript))
HtlcSuccessTx(input, Transaction(
version = 2,
txIn = TxIn(input.outPoint, Array.emptyByteArray, 0xffffffffL) :: Nil,
txOut = TxOut(Satoshi(htlc.amountMsat / 1000), pay2wsh(htlcSuccessOrTimeout(localRevocationPubkey, toLocalDelay, localPubkey))) :: Nil,
lockTime = 0))
}
def makeHtlcTxs(commitTx: Transaction, localRevocationPubkey: BinaryData, toLocalDelay: Int, localPubkey: BinaryData, remotePubkey: BinaryData, spec: CommitmentSpec): (Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = {
// TODO: no fees!!!
val htlcTimeoutTxs = spec.htlcs
.filter(_.direction == OUT)
.map(htlc => makeHtlcTimeoutTx(commitTx, localRevocationPubkey, toLocalDelay, localPubkey, remotePubkey, htlc.add))
.toSeq
val htlcSuccessTxs = spec.htlcs
.filter(_.direction == IN)
.map(htlc => makeHtlcSuccessTx(commitTx, localRevocationPubkey, toLocalDelay, localPubkey, remotePubkey, htlc.add))
.toSeq
(htlcTimeoutTxs, htlcSuccessTxs)
}
def makeClaimHtlcSuccessTx(commitTx: Transaction, localPubkey: BinaryData, remotePubkey: BinaryData, finalLocalPubkey: BinaryData, htlc: UpdateAddHtlc): ClaimHtlcSuccessTx = {
val redeemScript = htlcOffered(remotePubkey, localPubkey, ripemd160(htlc.paymentHash))
val pubkeyScript = Script.write(pay2wsh(redeemScript))
val outputIndex = findPubKeyScriptIndex(commitTx, pubkeyScript)
require(outputIndex >= 0, "output not found")
val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), Script.write(redeemScript))
ClaimHtlcSuccessTx(input, Transaction(
version = 2,
txIn = TxIn(input.outPoint, Array.emptyByteArray, 0xffffffffL) :: Nil,
txOut = TxOut(Satoshi(htlc.amountMsat / 1000), pay2wpkh(finalLocalPubkey)) :: Nil,
lockTime = 0))
}
def makeClaimHtlcTimeoutTx(commitTx: Transaction, localPubkey: BinaryData, remotePubkey: BinaryData, finalLocalPubkey: BinaryData, htlc: UpdateAddHtlc): ClaimHtlcTimeoutTx = {
val redeemScript = htlcReceived(remotePubkey, localPubkey, ripemd160(htlc.paymentHash), htlc.expiry)
val pubkeyScript = Script.write(pay2wsh(redeemScript))
val outputIndex = findPubKeyScriptIndex(commitTx, pubkeyScript)
require(outputIndex >= 0, "output not found")
val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), Script.write(redeemScript))
ClaimHtlcTimeoutTx(input, Transaction(
version = 2,
txIn = TxIn(input.outPoint, Array.emptyByteArray, 0x00000000L) :: Nil,
txOut = TxOut(Satoshi(htlc.amountMsat / 1000), pay2wpkh(finalLocalPubkey)) :: Nil,
lockTime = htlc.expiry))
}
def makeClaimHtlcDelayed(htlcSuccessOrTimeoutTx: Transaction, localRevocationPubkey: BinaryData, toLocalDelay: Int, localPubkey: BinaryData, finalLocalPubkey: BinaryData, htlc: UpdateAddHtlc): ClaimHtlcDelayed = {
val redeemScript = htlcSuccessOrTimeout(localRevocationPubkey, toLocalDelay, localPubkey)
val pubkeyScript = Script.write(pay2wsh(redeemScript))
val outputIndex = findPubKeyScriptIndex(htlcSuccessOrTimeoutTx, pubkeyScript)
require(outputIndex >= 0, "output not found")
val input = InputInfo(OutPoint(htlcSuccessOrTimeoutTx, outputIndex), htlcSuccessOrTimeoutTx.txOut(outputIndex), Script.write(redeemScript))
ClaimHtlcDelayed(input, Transaction(
version = 2,
txIn = TxIn(input.outPoint, Array.emptyByteArray, toLocalDelay) :: Nil,
txOut = TxOut(Satoshi(htlc.amountMsat / 1000), pay2wpkh(finalLocalPubkey)) :: Nil,
lockTime = 0))
}
def findPubKeyScriptIndex(tx: Transaction, pubkeyScript: BinaryData): Int = tx.txOut.indexWhere(_.publicKeyScript == pubkeyScript)
def findPubKeyScriptIndex(tx: Transaction, pubkeyScript: Seq[ScriptElt]): Int = findPubKeyScriptIndex(tx, Script.write(pubkeyScript))
def sign(tx: Transaction, inputIndex: Int, redeemScript: BinaryData, amount: Satoshi, key: BinaryData): BinaryData = {
// this is because by convention in bitcoin-core-speak 32B keys are 'uncompressed' and 33B keys (ending by 0x01) are 'compressed'
val compressedKey: Seq[Byte] = key.data :+ 1.toByte
Transaction.signInput(tx, inputIndex, redeemScript, SIGHASH_ALL, amount, SIGVERSION_WITNESS_V0, compressedKey)
}
def sign(txinfo: TransactionWithInputInfo, key: BinaryData): BinaryData = {
require(txinfo.tx.txIn.size == 1, "only one input allowed")
sign(txinfo.tx, inputIndex = 0, txinfo.input.redeemScript, txinfo.input.txOut.amount, key)
}
def addSigs(commitTx: CommitTx, localFundingPubkey: Point, remoteFundingPubkey: Point, localSig: BinaryData, remoteSig: BinaryData): CommitTx = {
val witness = Common.witness2of2(localSig, remoteSig, localFundingPubkey, remoteFundingPubkey)
commitTx.copy(tx = commitTx.tx.updateWitness(0, witness))
}
def addSigs(htlcSuccessTx: HtlcSuccessTx, localSig: BinaryData, remoteSig: BinaryData, paymentPreimage: BinaryData): HtlcSuccessTx = {
val witness = InputScripts.witnessHtlcSuccess(localSig, remoteSig, paymentPreimage, htlcSuccessTx.input.redeemScript)
htlcSuccessTx.copy(tx = htlcSuccessTx.tx.updateWitness(0, witness))
}
def addSigs(htlcTimeoutTx: HtlcTimeoutTx, localSig: BinaryData, remoteSig: BinaryData): HtlcTimeoutTx = {
val witness = InputScripts.witnessHtlcTimeout(localSig, remoteSig, htlcTimeoutTx.input.redeemScript)
htlcTimeoutTx.copy(tx = htlcTimeoutTx.tx.updateWitness(0, witness))
}
def addSigs(claimHtlcSuccessTx: ClaimHtlcSuccessTx, localSig: BinaryData, paymentPreimage: BinaryData): ClaimHtlcSuccessTx = {
val witness = InputScripts.witnessHtlcReceived(localSig, paymentPreimage, claimHtlcSuccessTx.input.redeemScript)
claimHtlcSuccessTx.copy(tx = claimHtlcSuccessTx.tx.updateWitness(0, witness))
}
def addSigs(claimHtlcTimeoutTx: ClaimHtlcTimeoutTx, localSig: BinaryData): ClaimHtlcTimeoutTx = {
val witness = InputScripts.witnessHtlcOffered(localSig, claimHtlcTimeoutTx.input.redeemScript)
claimHtlcTimeoutTx.copy(tx = claimHtlcTimeoutTx.tx.updateWitness(0, witness))
}
def addSigs(claimHtlcDelayed: ClaimHtlcDelayed, localSig: BinaryData): ClaimHtlcDelayed = {
val witness = InputScripts.witnessHtlcDelayed(localSig, claimHtlcDelayed.input.redeemScript)
claimHtlcDelayed.copy(tx = claimHtlcDelayed.tx.updateWitness(0, witness))
}
def checkSig(txinfo: TransactionWithInputInfo): Try[Unit] =
Try(Transaction.correctlySpends(txinfo.tx, Map(txinfo.tx.txIn(0).outPoint -> txinfo.input.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS))
}

View file

@ -1,167 +0,0 @@
package fr.acinq.eclair.transactions
import fr.acinq.bitcoin.{BinaryData, Crypto, OP_CHECKSIG, OP_DUP, OP_EQUALVERIFY, OP_HASH160, OP_PUSHDATA, OutPoint, Satoshi, Script, ScriptElt, Transaction, TxIn, TxOut}
import fr.acinq.eclair.transactions.Common._
import fr.acinq.eclair.transactions.OutputScripts._
import fr.acinq.eclair.wire.UpdateAddHtlc
/**
* Created by PM on 06/12/2016.
*/
sealed trait TxTemplate {
def makeTx: Transaction
}
case class CommitTxTemplate(inputs: Seq[TxIn], localOutput: Option[DelayedPaymentOutputTemplate], remoteOutput: Option[P2WPKHOutputTemplate], htlcOfferedOutputs: Seq[OfferedHTLCOutputTemplate], htlcReceivedOutputs: Seq[ReceivedHTLCOutputTemplate]) extends TxTemplate {
def makeTx = {
val outputs = localOutput.toSeq ++ remoteOutput.toSeq ++ htlcOfferedOutputs ++ htlcReceivedOutputs
val tx = Transaction(
version = 2,
txIn = inputs,
txOut = outputs.map(_.txOut),
lockTime = 0
)
permuteOutputs(tx)
}
}
case class HTLCSuccessTxTemplate(inputs: Seq[TxIn], successOutputTemplate: SuccessOrTimeoutOutputTemplate, locktime: Long) extends TxTemplate {
def makeTx = Transaction(
version = 2,
txIn = inputs,
txOut = successOutputTemplate.txOut :: Nil,
lockTime = locktime)
}
case class HTLCTimeoutTxTemplate(inputs: Seq[TxIn], successOutputTemplate: SuccessOrTimeoutOutputTemplate) extends TxTemplate {
def makeTx = Transaction(
version = 2,
txIn = inputs,
txOut = successOutputTemplate.txOut :: Nil,
lockTime = 0)
}
object CommitTxTemplate {
/**
* Creates a commitment publishable by 'Local' (meaning that main output to local is delayed)
*
* @param inputs
* @param localRevocationPubkey
* @param toLocalDelay
* @param localPubkey
* @param remotePubkey
* @param spec
* @return
*/
def makeCommitTxTemplate(inputs: Seq[TxIn], localRevocationPubkey: BinaryData, toLocalDelay: Int, localPubkey: BinaryData, remotePubkey: BinaryData, spec: CommitmentSpec): CommitTxTemplate = {
// TODO: no fees!!!
val (toLocalAmount: Satoshi, toRemoteAmount: Satoshi) = (Satoshi(spec.to_local_msat / 1000), Satoshi(spec.to_remote_msat / 1000))
val toLocalDelayedOutput_opt = if (spec.to_local_msat >= 546000) Some(DelayedPaymentOutputTemplate(toLocalAmount, localRevocationPubkey, toLocalDelay, localPubkey)) else None
val toRemoteOutput_opt = if (spec.to_remote_msat >= 546000) Some(P2WPKHOutputTemplate(toRemoteAmount, toRemote(remotePubkey))) else None
val htlcSentOutputs = spec.htlcs.
filter(_.direction == OUT)
.map(htlc => OfferedHTLCOutputTemplate(htlc.add, localPubkey, remotePubkey)).toSeq
val htlcReceivedOutputs = spec.htlcs
.filter(_.direction == IN)
.map(htlc => ReceivedHTLCOutputTemplate(htlc.add, localPubkey, remotePubkey, htlc.add.paymentHash)).toSeq
CommitTxTemplate(inputs, toLocalDelayedOutput_opt, toRemoteOutput_opt, htlcSentOutputs, htlcReceivedOutputs)
}
def makeHtlcTxTemplates(commitTx: Transaction, localRevocationPubkey: BinaryData, toLocalDelay: Int, localPubkey: BinaryData, remotePubkey: BinaryData, spec: CommitmentSpec): (Seq[HTLCTimeoutTxTemplate], Seq[HTLCSuccessTxTemplate]) = {
// TODO: no fees!!!
val commitTxId = commitTx.txid
val indexed = commitTx.txOut.zipWithIndex.toMap
val htlcTimeoutTxTemplates = spec.htlcs
.filter(_.direction == OUT)
.map(htlc => {
val outputIndex = indexed(OfferedHTLCOutputTemplate(htlc.add, localPubkey, remotePubkey).txOut)
val inputs = TxIn(OutPoint(commitTxId, outputIndex), Array.emptyByteArray, 0xffffffffL) :: Nil
HTLCTimeoutTxTemplate(inputs, SuccessOrTimeoutOutputTemplate(htlc.add, localRevocationPubkey, toLocalDelay, localPubkey))
}).toSeq
val htlcSuccessTxTemplates = spec.htlcs
.filter(_.direction == OUT)
.map(htlc => {
val outputIndex = indexed(OfferedHTLCOutputTemplate(htlc.add, localPubkey, remotePubkey).txOut)
val inputs = TxIn(OutPoint(commitTxId, outputIndex), Array.emptyByteArray, 0xffffffffL) :: Nil
HTLCSuccessTxTemplate(inputs, SuccessOrTimeoutOutputTemplate(htlc.add, localRevocationPubkey, toLocalDelay, localPubkey), htlc.add.expiry)
}).toSeq
(htlcTimeoutTxTemplates, htlcSuccessTxTemplates)
}
/*def makeCommitTxTemplate(inputs: Seq[TxIn], ourFinalKey: BinaryData, theirFinalKey: BinaryData, theirDelay: Int, revocationHash: BinaryData, commitmentSpec: CommitmentSpec): CommitTxTemplate = {
val redeemScript = redeemSecretOrDelay(ourFinalKey, toSelfDelay2csv(theirDelay), theirFinalKey, revocationHash: BinaryData)
val htlcs = commitmentSpec.htlcs.filter(_.add.amountMsat >= 546000).toSeq
val fee_msat = computeFee(commitmentSpec.feeRate, htlcs.size) * 1000
val (amount_us_msat: Long, amount_them_msat: Long) = (commitmentSpec.to_local_msat, commitmentSpec.to_remote_msat) match {
case (us, them) if us >= fee_msat / 2 && them >= fee_msat / 2 => (us - fee_msat / 2, them - fee_msat / 2)
case (us, them) if us < fee_msat / 2 => (0L, Math.max(0L, them - fee_msat + us))
case (us, them) if them < fee_msat / 2 => (Math.max(us - fee_msat + them, 0L), 0L)
}
// our output is a pay2wsh output than can be claimed by them if they know the preimage, or by us after a delay
// when * they * publish a revoked commit tx, we use the preimage that they sent us to claim it
val ourOutput = if (amount_us_msat >= 546000) Some(P2WSHTemplate(Satoshi(amount_us_msat / 1000), redeemScript)) else None
// their output is a simple pay2pkh output that sends money to their final key and can only be claimed by them
// when * they * publish a revoked commit tx we don't have anything special to do about it
val theirOutput = if (amount_them_msat >= 546000) Some(P2WPKHTemplate(Satoshi(amount_them_msat / 1000), theirFinalKey)) else None
val sendOuts: Seq[HTLCTemplate] = htlcs.filter(_.direction == OUT).map(htlc => {
HTLCTemplate(htlc, ourFinalKey, theirFinalKey, theirDelay, revocationHash)
})
val receiveOuts: Seq[HTLCTemplate] = htlcs.filter(_.direction == IN).map(htlc => {
HTLCTemplate(htlc, ourFinalKey, theirFinalKey, theirDelay, revocationHash)
})
CommitTxTemplate(inputs, ourOutput, theirOutput, sendOuts, receiveOuts)
}*/
}
sealed trait OutputTemplate {
def amount: Satoshi
def txOut: TxOut
// this is the actual script that must be used to claim this output
def redeemScript: BinaryData
}
class P2WSHOutputTemplate(val amount: Satoshi, val script: BinaryData) extends OutputTemplate {
def this(amount: Satoshi, script: Seq[ScriptElt]) = this(amount, Script.write(script))
override def txOut: TxOut = TxOut(amount, pay2wsh(script))
override def redeemScript = script
}
case class DelayedPaymentOutputTemplate(override val amount: Satoshi, localRevocationPubkey: BinaryData, toLocalDelay: Int, localPubkey: BinaryData)
extends P2WSHOutputTemplate(amount, toLocal(localRevocationPubkey, toLocalDelay, localPubkey))
case class OfferedHTLCOutputTemplate(htlc: UpdateAddHtlc, localPubkey: BinaryData, remotePubKey: BinaryData)
extends P2WSHOutputTemplate(Satoshi(htlc.amountMsat / 1000), htlcOffered(localPubkey, remotePubKey, htlc.paymentHash))
case class ReceivedHTLCOutputTemplate(htlc: UpdateAddHtlc, localPubkey: BinaryData, remotePubKey: BinaryData, paymentHash: BinaryData)
extends P2WSHOutputTemplate(Satoshi(htlc.amountMsat / 1000), htlcReceived(localPubkey, remotePubKey, paymentHash, htlc.expiry))
case class SuccessOrTimeoutOutputTemplate(htlc: UpdateAddHtlc, revocationPubkey: BinaryData, toSelfDelay: Long, localDelayedPubkey: BinaryData)
extends P2WSHOutputTemplate(Satoshi(htlc.amountMsat / 1000), htlcSuccessOrTimeout(revocationPubkey, toSelfDelay, localDelayedPubkey))
case class P2WPKHOutputTemplate(amount: Satoshi, pubKey: BinaryData) extends OutputTemplate {
override def txOut: TxOut = TxOut(amount, pay2wpkh(pubKey))
override def redeemScript = Script.write(OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil)
}

View file

@ -1,7 +1,7 @@
package fr.acinq.eclair
import akka.actor.ActorSystem
import fr.acinq.bitcoin.{BinaryData, Satoshi, Transaction, TxIn, TxOut}
import fr.acinq.bitcoin.{BinaryData, Satoshi, Script, Transaction, TxIn, TxOut}
import fr.acinq.eclair.blockchain.ExtendedBitcoinClient
import fr.acinq.eclair.blockchain.peer.{NewBlock, NewTransaction}
import fr.acinq.eclair.blockchain.rpc.BitcoinJsonRPCClient
@ -24,7 +24,7 @@ class TestBitcoinClient()(implicit system: ActorSystem) extends ExtendedBitcoinC
override def makeAnchorTx(ourCommitPub: BinaryData, theirCommitPub: BinaryData, amount: Satoshi)(implicit ec: ExecutionContext): Future[(Transaction, Int)] = {
val anchorTx = Transaction(version = 1,
txIn = Seq.empty[TxIn],
txOut = TxOut(amount, Common.anchorPubkeyScript(ourCommitPub, theirCommitPub)) :: Nil,
txOut = TxOut(amount, Common.pay2wsh(Common.multiSig2of2(ourCommitPub, theirCommitPub))) :: Nil,
lockTime = 0
)
Future.successful((anchorTx, 0))

View file

@ -0,0 +1,106 @@
package fr.acinq.eclair.transactions
import fr.acinq.bitcoin.Crypto.sha256
import fr.acinq.bitcoin.{BinaryData, Btc, MilliBtc, millibtc2satoshi}
import fr.acinq.eclair.channel.Helpers.Funding
import fr.acinq.eclair.crypto.Generators.Scalar
import fr.acinq.eclair.transactions.Transactions._
import fr.acinq.eclair.wire.UpdateAddHtlc
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
/**
* Created by PM on 16/12/2016.
*/
@RunWith(classOf[JUnitRunner])
class TransactionsSpec extends FunSuite {
val fundingLocalPriv = Scalar(BinaryData("aa" * 32))
val fundingRemotePriv = Scalar(BinaryData("bb" * 32))
val commitInput = Funding.makeFundingInputInfo(BinaryData("12" * 32), 0, Btc(1), fundingLocalPriv.point, fundingRemotePriv.point)
val localRevocationPriv = Scalar(BinaryData("cc" * 32))
val localPaymentPriv = Scalar(BinaryData("dd" * 32))
val remotePaymentPriv = Scalar(BinaryData("ee" * 32))
val toLocalDelay = 144
test("generate valid commitment and htlc transactions") {
val paymentPreimage1 = BinaryData("11" * 32)
val htlc1 = UpdateAddHtlc(0, 0, millibtc2satoshi(MilliBtc(100)).amount * 1000, 300, sha256(paymentPreimage1), BinaryData(""))
val paymentPreimage2 = BinaryData("22" * 32)
val htlc2 = UpdateAddHtlc(0, 1, millibtc2satoshi(MilliBtc(200)).amount * 1000, 300, sha256(paymentPreimage2), BinaryData(""))
val spec = CommitmentSpec(
htlcs = Set(
Htlc(OUT, htlc1, None),
Htlc(IN, htlc2, None)
),
feeRate = 0,
to_local_msat = millibtc2satoshi(MilliBtc(400)).amount * 1000,
to_remote_msat = millibtc2satoshi(MilliBtc(300)).amount * 1000)
val commitTx = makeCommitTx(commitInput, localRevocationPriv.point, toLocalDelay, localPaymentPriv.point, remotePaymentPriv.point, spec)
val (htlcTimeoutTxs, htlcSuccessTxs) = makeHtlcTxs(commitTx.tx, localRevocationPriv.point, toLocalDelay, localPaymentPriv.point, remotePaymentPriv.point, spec)
assert(htlcTimeoutTxs.size == 1)
assert(htlcSuccessTxs.size == 1)
{
// either party spends local->remote htlc output with htlc timeout tx
val htlcTimeoutTx = htlcTimeoutTxs(0)
val localSig = sign(htlcTimeoutTx, localPaymentPriv)
val remoteSig = sign(htlcTimeoutTx, remotePaymentPriv)
val signed = addSigs(htlcTimeoutTx, localSig, remoteSig)
assert(checkSig(signed).isSuccess)
}
{
// local spends delayed output of htlc timeout tx
val htlcTimeoutTx = htlcTimeoutTxs(0)
val localFinalPriv = Scalar(BinaryData("ff" * 32))
val claimHtlcDelayed = makeClaimHtlcDelayed(htlcTimeoutTx.tx, localRevocationPriv.point, toLocalDelay, localPaymentPriv.point, localFinalPriv.point, htlc1)
val localSig = sign(claimHtlcDelayed, localPaymentPriv)
val signedTx = addSigs(claimHtlcDelayed, localSig)
assert(checkSig(signedTx).isSuccess)
}
{
// remote spends local->remote htlc output directly in case of success
val remoteFinalPriv = Scalar(BinaryData("ff" * 32))
val claimHtlcSuccessTx = makeClaimHtlcSuccessTx(commitTx.tx, remotePaymentPriv.point, localPaymentPriv.point, remoteFinalPriv.point, htlc1)
val localSig = sign(claimHtlcSuccessTx, remotePaymentPriv)
val signed = addSigs(claimHtlcSuccessTx, localSig, paymentPreimage1)
assert(checkSig(signed).isSuccess)
}
{
// local spends remote->local htlc output with htlc success tx using payment preimage
val htlcSuccessTx = htlcSuccessTxs(0)
val localSig = sign(htlcSuccessTx, localPaymentPriv)
val remoteSig = sign(htlcSuccessTx, remotePaymentPriv)
val signedTx = addSigs(htlcSuccessTx, localSig, remoteSig, paymentPreimage2)
assert(checkSig(signedTx).isSuccess)
}
{
// local spends delayed output of htlc success tx
val htlcSuccessTx = htlcSuccessTxs(0)
val localFinalPriv = Scalar(BinaryData("ff" * 32))
val claimHtlcDelayed = makeClaimHtlcDelayed(htlcSuccessTx.tx, localRevocationPriv.point, toLocalDelay, localPaymentPriv.point, localFinalPriv.point, htlc1)
val localSig = sign(claimHtlcDelayed, localPaymentPriv)
val signedTx = addSigs(claimHtlcDelayed, localSig)
assert(checkSig(signedTx).isSuccess)
}
{
// remote spends remote->local htlc output directly in case of timeout
val remoteFinalPriv = Scalar(BinaryData("ff" * 32))
val claimHtlcTimeoutTx = makeClaimHtlcTimeoutTx(commitTx.tx, remotePaymentPriv.point, localPaymentPriv.point, remoteFinalPriv.point, htlc2)
val localSig = sign(claimHtlcTimeoutTx, remotePaymentPriv)
val signed = addSigs(claimHtlcTimeoutTx, localSig)
assert(checkSig(signed).isSuccess)
}
}
}