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:
parent
d334fe068e
commit
8447270445
12 changed files with 376 additions and 289 deletions
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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))
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue