mirror of
https://github.com/ACINQ/eclair.git
synced 2024-11-19 09:54:02 +01:00
merged from wip-bolt3
This commit is contained in:
commit
f213f79821
@ -2,9 +2,9 @@
|
||||
|
||||
**Eclair** is a scala implementation of the Lightning Network. Eclair is french for Lightning.
|
||||
|
||||
This software follows the [BOLT specifications](https://github.com/rustyrussell/lightning-rfc), therefore it is compatible with Blockstream's [lightning-c](https://github.com/ElementsProject/lightning).
|
||||
:construction: This branch implements the [Lightning Network Specifications](https://github.com/lightningnetwork/lightning-rfc), it is a work in progress.
|
||||
|
||||
[![Build Status](https://travis-ci.org/ACINQ/eclair.svg?branch=master)](https://travis-ci.org/ACINQ/eclair)
|
||||
[![Build Status](https://travis-ci.org/ACINQ/eclair.svg?branch=wip-bolts)](https://travis-ci.org/ACINQ/eclair)
|
||||
|
||||
---
|
||||
|
||||
|
@ -79,7 +79,7 @@
|
||||
<dependency>
|
||||
<groupId>fr.acinq</groupId>
|
||||
<artifactId>bitcoin-lib_${scala.version.short}</artifactId>
|
||||
<version>${bitcoinlib.version}</version>
|
||||
<version>0.9.8-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<!-- SERIALIZATION -->
|
||||
<dependency>
|
||||
|
@ -69,7 +69,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
||||
when(WAIT_FOR_INIT_INTERNAL)(handleExceptions {
|
||||
case Event(INPUT_INIT_FUNDER(fundingSatoshis, pushMsat), Nothing) =>
|
||||
val temporaryChannelId = Platform.currentTime
|
||||
val firstPerCommitmentPoint: BinaryData = Generators.perCommitPoint(localParams.shaSeed, 0)
|
||||
val firstPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 0)
|
||||
them ! OpenChannel(temporaryChannelId = temporaryChannelId,
|
||||
fundingSatoshis = fundingSatoshis,
|
||||
pushMsat = pushMsat,
|
||||
@ -97,7 +97,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
||||
// TODO: here we should check if remote parameters suit us
|
||||
// TODO: maybe also check uniqueness of temporary channel id
|
||||
val minimumDepth = Globals.default_mindepth
|
||||
val firstPerCommitmentPoint: BinaryData = Generators.perCommitPoint(localParams.shaSeed, 0)
|
||||
val firstPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 0)
|
||||
them ! AcceptChannel(temporaryChannelId = Platform.currentTime,
|
||||
dustLimitSatoshis = localParams.dustLimitSatoshis,
|
||||
maxHtlcValueInFlightMsat = localParams.maxHtlcValueInFlightMsat,
|
||||
@ -280,7 +280,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
||||
case Event(BITCOIN_FUNDING_DEPTHOK, d@DATA_WAIT_FOR_FUNDING_LOCKED(temporaryChannelId, params, commitments, deferred)) =>
|
||||
val channelId = 0L
|
||||
blockchain ! WatchLost(self, commitments.anchorId, params.minimumDepth, BITCOIN_FUNDING_LOST)
|
||||
val nextPerCommitmentPoint: BinaryData = Generators.perCommitPoint(localParams.shaSeed, 1)
|
||||
val nextPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 1)
|
||||
them ! FundingLocked(channelId, 0L, "00" * 64, "00" * 64, nextPerCommitmentPoint) // TODO: routing announcements disabled
|
||||
deferred.map(self ! _)
|
||||
// TODO: htlcIdx should not be 0 when resuming connection
|
||||
|
@ -42,8 +42,8 @@ object Helpers {
|
||||
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 localPerCommitmentPoint: BinaryData = Generators.perCommitPoint(params.localParams.shaSeed, 0)
|
||||
val localTx = CommitmentSpec.makeLocalTxTemplate(params.localParams, params.remoteParams, commitmentInput :: Nil, localPerCommitmentPoint, localSpec).makeTx
|
||||
val localPerCommitmentPoint = Generators.perCommitPoint(params.localParams.shaSeed, 0)
|
||||
val localTx = CommitmentSpec.makeLocalTxTemplate(params.localParams, params.remoteParams, commitmentInput :: Nil, localPerCommitmentPoint.data, localSpec).makeTx
|
||||
val remoteTx = CommitmentSpec.makeRemoteTxTemplate(params.localParams, params.remoteParams, commitmentInput :: Nil, remoteFirstPerCommitmentPoint, remoteSpec).makeTx
|
||||
|
||||
val localFundingPubkey = Crypto.publicKeyFromPrivateKey(params.localParams.fundingPrivkey)
|
||||
|
@ -1,14 +1,49 @@
|
||||
package fr.acinq.eclair.crypto
|
||||
|
||||
import fr.acinq.bitcoin.BinaryData
|
||||
import java.math.BigInteger
|
||||
|
||||
import fr.acinq.bitcoin.{BinaryData, Crypto}
|
||||
import org.bouncycastle.math.ec.ECPoint
|
||||
|
||||
/**
|
||||
* Created by PM on 07/12/2016.
|
||||
*/
|
||||
object Generators {
|
||||
|
||||
type Point = BinaryData
|
||||
type Scalar = BinaryData
|
||||
def fixSize(data: BinaryData): BinaryData = data.length match {
|
||||
case 32 => data
|
||||
case length if length < 32 => Array.fill(32 - length)(0.toByte) ++ data
|
||||
}
|
||||
|
||||
case class Scalar(data: BinaryData) {
|
||||
require(data.length == 32)
|
||||
|
||||
def point = Point(Crypto.publicKeyFromPrivateKey(data :+ 1.toByte))
|
||||
|
||||
def bigInteger: BigInteger = new BigInteger(1, data)
|
||||
|
||||
def add(scalar: Scalar): Scalar = Scalar(bigInteger.add(scalar.bigInteger))
|
||||
|
||||
def multiply(scalar: Scalar): Scalar = Scalar(bigInteger.multiply(scalar.bigInteger).mod(Crypto.curve.getN))
|
||||
}
|
||||
|
||||
object Scalar {
|
||||
def apply(value: BigInteger): Scalar = new Scalar(fixSize(value.toByteArray.dropWhile(_ == 0)))
|
||||
}
|
||||
|
||||
case class Point(data: BinaryData) {
|
||||
require(data.length == 33)
|
||||
|
||||
def ecPoint: ECPoint = Crypto.curve.getCurve.decodePoint(data)
|
||||
|
||||
def add(point: Point): Point = Point(ecPoint.add(point.ecPoint))
|
||||
|
||||
def multiply(scalar: Scalar): Point = Point(ecPoint.multiply(scalar.bigInteger))
|
||||
}
|
||||
|
||||
object Point {
|
||||
def apply(ecPoint: ECPoint): Point = new Point(ecPoint.getEncoded(true))
|
||||
}
|
||||
|
||||
def perCommitSecret(seed: BinaryData, index: Int): Scalar = ???
|
||||
|
||||
@ -16,16 +51,28 @@ object Generators {
|
||||
|
||||
def perCommitPoint(seed: BinaryData, index: Int): Point = perCommitPoint(perCommitSecret(seed, index))
|
||||
|
||||
def revocationPubKey(revocationBasePoint: Point, perCommitPoint: Point): Point = ???
|
||||
|
||||
def revocationPrivKey(revocationSecret: Scalar, perCommitSecret: Scalar): Scalar = ???
|
||||
def derivePrivKey(secret: Scalar, perCommitPoint: Point): Scalar = {
|
||||
// secretkey = basepoint-secret + SHA256(per-commitment-point || basepoint)
|
||||
secret.add(Scalar(Crypto.sha256(perCommitPoint.data ++ secret.point.data)))
|
||||
}
|
||||
|
||||
def paymentPubKey(paymentBasePoint: Point, perCommitPoint: Point): Point = ???
|
||||
def derivePubKey(basePoint: Point, perCommitPoint: Point): Point = {
|
||||
//pubkey = basepoint + SHA256(per-commitment-point || basepoint)*G
|
||||
val a = Scalar(Crypto.sha256(perCommitPoint.data ++ basePoint.data))
|
||||
Point(basePoint.ecPoint.add(Crypto.curve.getG.multiply(a.bigInteger)))
|
||||
}
|
||||
|
||||
def paymentPrivKey(paymentSecret: Scalar, perCommitSecret: Scalar): Scalar = ???
|
||||
def revocationPubKey(basePoint: Point, perCommitPoint: Point): Point = {
|
||||
val a = Scalar(Crypto.sha256(basePoint.data ++ perCommitPoint.data))
|
||||
val b = Scalar(Crypto.sha256(perCommitPoint.data ++ basePoint.data))
|
||||
basePoint.multiply(a).add(perCommitPoint.multiply(b))
|
||||
}
|
||||
|
||||
def delayedPaymentPubKey(delayedPaymentBasePoint: Point, perCommitPoint: Point): Point = ???
|
||||
|
||||
def delayedPaymentPrivKey(delayedPaymentSecret: Scalar, perCommitSecret: Scalar): Scalar = ???
|
||||
def revocationPrivKey(secret: Scalar, perCommitSecret: Scalar): Scalar = {
|
||||
val a = Scalar(Crypto.sha256(secret.point.data ++ perCommitSecret.point.data))
|
||||
val b = Scalar(Crypto.sha256(perCommitSecret.point.data ++ secret.point.data))
|
||||
secret.multiply(a).add(perCommitSecret.multiply(b))
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import java.math.BigInteger
|
||||
|
||||
import com.google.protobuf.ByteString
|
||||
import fr.acinq.bitcoin._
|
||||
import fr.acinq.eclair.crypto.Generators.{Point, Scalar}
|
||||
import lightning.{bitcoin_pubkey, signature}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
@ -17,6 +18,14 @@ package object eclair {
|
||||
|
||||
implicit def pubkey2bin(in: bitcoin_pubkey): BinaryData = in.key.toByteArray
|
||||
|
||||
implicit def point2bin(in: Point): BinaryData = in.data
|
||||
|
||||
implicit def bin2point(in: BinaryData): Point = Point(in)
|
||||
|
||||
implicit def scalar2bin(in: Scalar): BinaryData = in.data
|
||||
|
||||
implicit def bin2scalar(in: BinaryData): Scalar = Scalar(in)
|
||||
|
||||
private def fixSize(in: Array[Byte]): Array[Byte] = in.size match {
|
||||
case 32 => in
|
||||
case s if s < 32 => Array.fill(32 - s)(0: Byte) ++ in
|
||||
|
@ -1,7 +1,8 @@
|
||||
package fr.acinq.eclair.transactions
|
||||
|
||||
import fr.acinq.bitcoin.{BinaryData, Transaction, TxIn}
|
||||
import fr.acinq.bitcoin.{BinaryData, TxIn}
|
||||
import fr.acinq.eclair.channel.{LocalParams, RemoteParams}
|
||||
import fr.acinq.eclair.crypto.Generators.Point
|
||||
import fr.acinq.eclair.crypto.LightningCrypto.sha256
|
||||
import fr.acinq.eclair.wire.{UpdateAddHtlc, UpdateFailHtlc, UpdateFulfillHtlc, UpdateMessage}
|
||||
|
||||
@ -75,12 +76,12 @@ object CommitmentSpec {
|
||||
spec4
|
||||
}
|
||||
|
||||
def makeLocalTxTemplate(localParams: LocalParams, remoteParams: RemoteParams, inputs: Seq[TxIn], localPerCommitmentPoint: BinaryData, spec: CommitmentSpec): CommitTxTemplate = ???
|
||||
def makeLocalTxTemplate(localParams: LocalParams, remoteParams: RemoteParams, inputs: Seq[TxIn], localPerCommitmentPoint: Point, spec: CommitmentSpec): CommitTxTemplate = ???
|
||||
|
||||
/*def makeLocalTx(localParams: LocalParams, remoteParams: RemoteParams, inputs: Seq[TxIn], localPerCommitmentPoint: BinaryData, spec: CommitmentSpec): Transaction =
|
||||
makeLocalTxTemplate(localParams, remoteParams, inputs, localPerCommitmentPoint, spec).makeTx*/
|
||||
|
||||
def makeRemoteTxTemplate(localParams: LocalParams, remoteParams: RemoteParams, inputs: Seq[TxIn], remotePerCommitmentPoint: BinaryData, spec: CommitmentSpec): CommitTxTemplate = ???
|
||||
def makeRemoteTxTemplate(localParams: LocalParams, remoteParams: RemoteParams, inputs: Seq[TxIn], remotePerCommitmentPoint: Point, spec: CommitmentSpec): CommitTxTemplate = ???
|
||||
|
||||
/*def makeRemoteTx(localParams: LocalParams, remoteParams: RemoteParams, inputs: Seq[TxIn], remotePerCommitmentPoint: BinaryData, spec: CommitmentSpec): Transaction =
|
||||
makeRemoteTxTemplate(localParams, remoteParams, inputs, remotePerCommitmentPoint, spec).makeTx*/
|
||||
|
68
eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala
Normal file
68
eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala
Normal file
@ -0,0 +1,68 @@
|
||||
package fr.acinq.protos
|
||||
|
||||
import fr.acinq.bitcoin._
|
||||
import fr.acinq.eclair.transactions.OldScripts
|
||||
|
||||
object Bolt3 {
|
||||
// TODO: sort tx according to BIP69 (lexicographical ordering)
|
||||
|
||||
def baseSize(tx: Transaction) = Transaction.write(tx, Protocol.PROTOCOL_VERSION | Transaction.SERIALIZE_TRANSACTION_NO_WITNESS).length
|
||||
|
||||
def totalSize(tx: Transaction) = Transaction.write(tx, Protocol.PROTOCOL_VERSION).length
|
||||
|
||||
def weight(tx: Transaction) = 3 * baseSize(tx) + totalSize(tx)
|
||||
|
||||
def fundingScript(pubKey1: BinaryData, pubKey2: BinaryData) = OldScripts.multiSig2of2(pubKey1, pubKey2)
|
||||
|
||||
def toLocal(revocationPubKey: BinaryData, toSelfDelay: Long, localDelayedKey: BinaryData) = {
|
||||
// @formatter:off
|
||||
OP_IF ::
|
||||
OP_PUSHDATA(revocationPubKey) ::
|
||||
OP_ELSE ::
|
||||
OP_PUSHDATA(Script.encodeNumber(toSelfDelay)) :: OP_CHECKSEQUENCEVERIFY :: OP_DROP ::
|
||||
OP_PUSHDATA(localDelayedKey) ::
|
||||
OP_ENDIF ::
|
||||
OP_CHECKSIG :: Nil
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
def toRemote(remoteKey: BinaryData) = remoteKey
|
||||
|
||||
def htlcOffered(localKey: BinaryData, remoteKey: BinaryData, paymentHash: BinaryData) = {
|
||||
// @formatter:off
|
||||
OP_PUSHDATA(remoteKey) :: OP_SWAP ::
|
||||
OP_SIZE :: OP_PUSHDATA(Script.encodeNumber(32)) :: OP_EQUAL ::
|
||||
OP_NOTIF ::
|
||||
OP_DROP :: OP_2 :: OP_SWAP :: OP_PUSHDATA(localKey) :: OP_2 :: OP_CHECKMULTISIG ::
|
||||
OP_ELSE ::
|
||||
OP_HASH160 :: OP_PUSHDATA(paymentHash) :: OP_EQUALVERIFY ::
|
||||
OP_CHECKSIG ::
|
||||
OP_ENDIF :: Nil
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
def htlcReceived(localKey: BinaryData, remoteKey: BinaryData, paymentHash: BinaryData, lockTime: Long) = {
|
||||
// @formatter:off
|
||||
OP_PUSHDATA(remoteKey) :: OP_SWAP ::
|
||||
OP_SIZE :: OP_PUSHDATA(Script.encodeNumber(32)) :: OP_EQUAL ::
|
||||
OP_IF ::
|
||||
OP_HASH160 :: OP_PUSHDATA(paymentHash) :: OP_EQUALVERIFY ::
|
||||
OP_2 :: OP_SWAP :: OP_PUSHDATA(localKey) :: OP_2 :: OP_CHECKMULTISIG ::
|
||||
OP_ELSE ::
|
||||
OP_DROP :: OP_PUSHDATA(Script.encodeNumber(lockTime)) :: OP_CHECKLOCKTIMEVERIFY :: OP_DROP :: OP_CHECKSIG ::
|
||||
OP_ENDIF :: Nil
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
def htlcSuccessOrTimeout(revocationPubKey: BinaryData, toSelfDelay: Long, localDelayedKey: BinaryData) = {
|
||||
// @formatter:off
|
||||
OP_IF ::
|
||||
OP_PUSHDATA(revocationPubKey) ::
|
||||
OP_ELSE ::
|
||||
OP_PUSHDATA(Script.encodeNumber(toSelfDelay)) :: OP_CHECKSEQUENCEVERIFY :: OP_DROP ::
|
||||
OP_PUSHDATA(localDelayedKey) ::
|
||||
OP_ENDIF ::
|
||||
OP_CHECKSIG :: Nil
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
366
eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala
Normal file
366
eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala
Normal file
@ -0,0 +1,366 @@
|
||||
package fr.acinq.protos
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import fr.acinq.bitcoin._
|
||||
import fr.acinq.eclair.blockchain.ExtendedBitcoinClient
|
||||
import fr.acinq.eclair.blockchain.rpc.BitcoinJsonRPCClient
|
||||
import fr.acinq.eclair.crypto.Generators
|
||||
import fr.acinq.eclair.crypto.Generators.Scalar
|
||||
import fr.acinq.eclair.transactions.OldScripts
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.FunSuite
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
import scala.concurrent.Await
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.Try
|
||||
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class Bolt3Spec extends FunSuite {
|
||||
val (Base58.Prefix.SecretKeyTestnet, localPrivKey) = Base58Check.decode("cVuzKWCszfvjkoJyUasvsrRdECriz8hSd1BDinRNzytwnXmX7m1g")
|
||||
val (Base58.Prefix.SecretKeyTestnet, remotePrivKey) = Base58Check.decode("cRUfvpbRtMSqCFD1ADdvgPn5HfRLYuHCFYAr2noWnaRDNger2AoA")
|
||||
val localPubKey: BinaryData = Crypto.publicKeyFromPrivateKey(localPrivKey)
|
||||
val remotePubKey: BinaryData = Crypto.publicKeyFromPrivateKey(remotePrivKey)
|
||||
|
||||
val (Base58.Prefix.SecretKeyTestnet, revocationPrivKey) = Base58Check.decode("cSupnaiBh6jgTcQf9QANCB5fZtXojxkJQczq5kwfSBeULjNd5Ypo")
|
||||
val revocationPubKey = Crypto.publicKeyFromPrivateKey(revocationPrivKey)
|
||||
|
||||
val amount = 40000 + 30000 + 20000 + 15000 satoshi
|
||||
|
||||
val config = ConfigFactory.load()
|
||||
|
||||
// run this test with -Dbolt3-test.use-bitcoind=true to generate publishable tx
|
||||
val useBitcoind = Try(config.getBoolean("bolt3-test.use-bitcoind")).getOrElse(false)
|
||||
val bitcoin: Option[ExtendedBitcoinClient] = if (useBitcoind) {
|
||||
implicit val system = ActorSystem("mySystem")
|
||||
val client = new ExtendedBitcoinClient(new BitcoinJsonRPCClient(
|
||||
user = config.getString("eclair.bitcoind.rpcuser"),
|
||||
password = config.getString("eclair.bitcoind.rpcpassword"),
|
||||
host = config.getString("eclair.bitcoind.host"),
|
||||
port = config.getInt("eclair.bitcoind.rpcport")))
|
||||
Some(client)
|
||||
} else None
|
||||
|
||||
val (fundingTx, fundingPos) = bitcoin match {
|
||||
case Some(client) => Await.result(client.makeAnchorTx(localPubKey, remotePubKey, amount), 5 seconds)
|
||||
case None => (Transaction(version = 2, txIn = Nil, txOut = TxOut(amount, Script.pay2wsh(Bolt3.fundingScript(localPubKey, remotePubKey))) :: Nil, lockTime = 0), 0)
|
||||
}
|
||||
|
||||
def hex(tx: Transaction) = toHexString(Transaction.write(tx))
|
||||
|
||||
println(s"funding tx (use output $fundingPos): ${hex(fundingTx)}")
|
||||
|
||||
val localDelayedKey = localPubKey
|
||||
val paymentPreimage1 = Hash.Zeroes
|
||||
val paymentPreimage2 = Hash.One
|
||||
val paymentHash1 = Crypto.hash160(paymentPreimage1)
|
||||
val paymentHash2 = Crypto.hash160(paymentPreimage2)
|
||||
|
||||
// this is an absolute timeout (i.e. a block height or UNIX timestamp) that will be used with OP_CLTV
|
||||
val htlcTimeout = 10000
|
||||
|
||||
// this is a relative (to the parent tx) timeout, expressed in number of blocks or seconds
|
||||
val selfDelay = 20
|
||||
|
||||
val fee = 5000 satoshi
|
||||
|
||||
// create our local commit tx, with an HTLC that we've offered and a HTLC that we've received
|
||||
val commitTx = {
|
||||
val tx = LexicographicalOrdering.sort(
|
||||
Transaction(
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(fundingTx, fundingPos), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil,
|
||||
txOut = Seq(
|
||||
TxOut(40000.satoshi - fee / 4, Script.pay2wsh(Bolt3.toLocal(revocationPubKey, selfDelay, localDelayedKey))),
|
||||
TxOut(30000.satoshi - fee / 4, Script.pay2wpkh(remotePubKey)),
|
||||
TxOut(20000.satoshi - fee / 4, Script.pay2wsh(Bolt3.htlcOffered(localPubKey, remotePubKey, paymentHash1))),
|
||||
TxOut(15000.satoshi - fee / 4, Script.pay2wsh(Bolt3.htlcReceived(localPubKey, remotePubKey, paymentHash2, htlcTimeout)))
|
||||
),
|
||||
lockTime = 0)
|
||||
)
|
||||
|
||||
val redeemScript: BinaryData = Bolt3.fundingScript(localPubKey, remotePubKey)
|
||||
println(s"size of funding script: ${redeemScript.length}")
|
||||
val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, fundingTx.txOut(fundingPos).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey)
|
||||
println(s"local sig size: ${localSig.length}")
|
||||
val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, fundingTx.txOut(fundingPos).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey)
|
||||
println(s"remote sig size: ${remoteSig.length}")
|
||||
val witness = if (OldScripts.isLess(localPubKey, remotePubKey))
|
||||
ScriptWitness(BinaryData.empty :: localSig :: remoteSig :: redeemScript :: Nil)
|
||||
else
|
||||
ScriptWitness(BinaryData.empty :: remoteSig :: localSig :: redeemScript :: Nil)
|
||||
println(s"witness size: ${ScriptWitness.write(witness).length}")
|
||||
val tx1 = tx.updateWitness(0, witness)
|
||||
println(s"signed commit tx base size: ${Bolt3.baseSize(tx1)} total size: ${Bolt3.totalSize(tx1)} weight: ${Bolt3.weight(tx1)}")
|
||||
tx1
|
||||
}
|
||||
println(s"commit tx: ${hex(commitTx)}")
|
||||
|
||||
def findPubKeyScriptIndex(tx: Transaction, script: BinaryData): Int = tx.txOut.indexWhere(_.publicKeyScript == script)
|
||||
|
||||
def findPubKeyScriptIndex(tx: Transaction, script: Seq[ScriptElt]): Int = findPubKeyScriptIndex(tx, Script.write(script))
|
||||
|
||||
// create our local HTLC timeout tx for the HTLC that we've offered
|
||||
// it is signed by both parties
|
||||
val htlcTimeoutTx = {
|
||||
val redeemScript: BinaryData = Script.write(Bolt3.htlcOffered(localPubKey, remotePubKey, paymentHash1))
|
||||
val index = findPubKeyScriptIndex(commitTx, Script.pay2wsh(redeemScript))
|
||||
val tx = Transaction(
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(commitTx, index), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil,
|
||||
txOut = TxOut(commitTx.txOut(index).amount - fee, Script.pay2wsh(Bolt3.htlcSuccessOrTimeout(revocationPubKey, selfDelay, localDelayedKey))) :: Nil,
|
||||
lockTime = 0)
|
||||
// both parties sign the unsigned tx
|
||||
println(s"size of htlcOffered script: ${redeemScript.length}")
|
||||
val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(index).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey)
|
||||
println(s"local sig size: ${localSig.length}")
|
||||
val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(index).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey)
|
||||
println(s"remote sig size: ${remoteSig.length}")
|
||||
val witness = ScriptWitness(BinaryData.empty :: remoteSig :: localSig :: BinaryData.empty :: redeemScript :: Nil)
|
||||
println(s"witness size: ${ScriptWitness.write(witness).length}")
|
||||
val tx1 = tx.updateWitness(0, witness)
|
||||
println(s"signed htlcTimeoutTx tx: base size: ${Bolt3.baseSize(tx1)} total size: ${Bolt3.totalSize(tx1)} weight: ${Bolt3.weight(tx1)}")
|
||||
tx1
|
||||
}
|
||||
println(s"htlc timeout tx: ${hex(htlcTimeoutTx)}")
|
||||
|
||||
// create our local HTLC success tx for the HTLC that we've received
|
||||
// it is signed by both parties and its witness contains the HTLC payment preimage
|
||||
val htlcSuccessTx = {
|
||||
val redeemScript: BinaryData = Script.write(Bolt3.htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout))
|
||||
val index = findPubKeyScriptIndex(commitTx, Script.pay2wsh(redeemScript))
|
||||
val tx = Transaction(
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(commitTx, index), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil,
|
||||
txOut = TxOut(commitTx.txOut(index).amount - fee, Script.pay2wsh(Bolt3.htlcSuccessOrTimeout(revocationPubKey, selfDelay, localDelayedKey))) :: Nil,
|
||||
lockTime = 0)
|
||||
// both parties sign the unsigned tx
|
||||
println(s"size of htlcReceived script: ${redeemScript.length}")
|
||||
val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(index).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey)
|
||||
println(s"local sig size: ${localSig.length}")
|
||||
val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(index).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey)
|
||||
println(s"remote sig size: ${remoteSig.length}")
|
||||
val witness = ScriptWitness(BinaryData.empty :: remoteSig :: localSig :: paymentPreimage2 :: redeemScript :: Nil)
|
||||
println(s"witness size: ${ScriptWitness.write(witness).length}")
|
||||
val tx1 = tx.updateWitness(0, witness)
|
||||
println(s"signed htlcSuccessTx base size: ${Bolt3.baseSize(tx1)} total size: ${Bolt3.totalSize(tx1)} weight: ${Bolt3.weight(tx1)}")
|
||||
tx1
|
||||
}
|
||||
println(s"htlc success tx: ${hex(htlcSuccessTx)}")
|
||||
|
||||
test("commit tx spends the funding tx") {
|
||||
Transaction.correctlySpends(commitTx, fundingTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
}
|
||||
|
||||
test("HTLC timeout tx spends the commit tx") {
|
||||
Transaction.correctlySpends(htlcTimeoutTx, commitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
}
|
||||
|
||||
test("HTLC success tx spends the commit tx") {
|
||||
Transaction.correctlySpends(htlcSuccessTx, commitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
}
|
||||
|
||||
test("we can spend our commit tx output after a delay") {
|
||||
val spendOurOutput = {
|
||||
val redeemScript: BinaryData = Script.write(Bolt3.toLocal(revocationPubKey, selfDelay, localDelayedKey))
|
||||
val index = findPubKeyScriptIndex(commitTx, Script.pay2wsh(redeemScript))
|
||||
val tx = Transaction(
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(commitTx, index), signatureScript = Nil, sequence = selfDelay + 1) :: Nil,
|
||||
txOut = TxOut(commitTx.txOut(index).amount - fee, Script.pay2wpkh(localPubKey)) :: Nil,
|
||||
lockTime = 0)
|
||||
println(s"size of toLocal script: ${redeemScript.length}")
|
||||
val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(index).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey)
|
||||
println(s"local sig size: ${localSig.length}")
|
||||
val witness = ScriptWitness(localSig :: BinaryData.empty :: redeemScript :: Nil)
|
||||
tx.updateWitness(0, witness)
|
||||
}
|
||||
Transaction.correctlySpends(spendOurOutput, commitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
println(s"we-spend-our-output tx: ${hex(spendOurOutput)}")
|
||||
println(s"you need to publish the commit tx and generate ${selfDelay} blocks before you can publish this tx")
|
||||
}
|
||||
|
||||
test("they can spend their commit tx immediately") {
|
||||
val index = findPubKeyScriptIndex(commitTx, Script.pay2wpkh(remotePubKey))
|
||||
val spendTheirOutput = {
|
||||
val tx = Transaction(
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(commitTx, index), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil,
|
||||
txOut = TxOut(commitTx.txOut(index).amount - fee, Script.pay2wpkh(remotePubKey)) :: Nil,
|
||||
lockTime = 0)
|
||||
val redeemScript: BinaryData = Script.write(Script.pay2pkh(remotePubKey))
|
||||
val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(index).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey)
|
||||
val witness = ScriptWitness(remoteSig :: remotePubKey :: Nil)
|
||||
tx.updateWitness(0, witness)
|
||||
}
|
||||
Transaction.correctlySpends(spendTheirOutput, commitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
println(s"they-spend-their-output tx: ${hex(spendTheirOutput)}")
|
||||
}
|
||||
|
||||
|
||||
test("they can spend our commit tx output immediately if they have the revocation key") {
|
||||
val redeemScript: BinaryData = Script.write(Bolt3.toLocal(revocationPubKey, selfDelay, localDelayedKey))
|
||||
val index = findPubKeyScriptIndex(commitTx, Script.pay2wsh(redeemScript))
|
||||
val penaltyTx = {
|
||||
val tx = Transaction(
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(commitTx, index), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil,
|
||||
txOut = TxOut(commitTx.txOut(index).amount - fee, Script.pay2wpkh(localPubKey)) :: Nil,
|
||||
lockTime = 0)
|
||||
val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(index).amount, SigVersion.SIGVERSION_WITNESS_V0, revocationPrivKey)
|
||||
println(s"remote sig size: ${remoteSig.length}")
|
||||
val witness = ScriptWitness(remoteSig :: BinaryData("01") :: redeemScript :: Nil)
|
||||
tx.updateWitness(0, witness)
|
||||
}
|
||||
Transaction.correctlySpends(penaltyTx, commitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
println(s"they-spend-our-output tx: ${hex(penaltyTx)}")
|
||||
}
|
||||
|
||||
test("we can claim the received HTLC timeout tx after a delay") {
|
||||
val spendHtlcTimeout = {
|
||||
val tx = Transaction(
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(htlcTimeoutTx, 0), signatureScript = Nil, sequence = selfDelay + 1) :: Nil,
|
||||
txOut = TxOut(htlcTimeoutTx.txOut(0).amount - fee, Script.pay2wpkh(localPubKey)) :: Nil,
|
||||
lockTime = 0)
|
||||
val redeemScript: BinaryData = Script.write(Bolt3.htlcSuccessOrTimeout(revocationPubKey, selfDelay, localDelayedKey))
|
||||
println(s"size of htlcSuccessOrTimeout script: ${redeemScript.length}")
|
||||
val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, htlcTimeoutTx.txOut(0).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey)
|
||||
println(s"local sig size: ${localSig.length}")
|
||||
val witness = ScriptWitness(localSig :: BinaryData.empty :: redeemScript :: Nil)
|
||||
tx.updateWitness(0, witness)
|
||||
}
|
||||
Transaction.correctlySpends(spendHtlcTimeout, htlcTimeoutTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
println(s"we-spend-offered-htlc-timeout tx: ${hex(spendHtlcTimeout)}")
|
||||
println(s"you need to publish the htlc timeout tx and generate ${selfDelay} blocks before you can publish this tx")
|
||||
}
|
||||
|
||||
test("we can claim the received HTLC success tx after a delay") {
|
||||
val spendHtlcSuccess = {
|
||||
val tx = Transaction(
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(htlcSuccessTx, 0), signatureScript = Nil, sequence = selfDelay + 1) :: Nil,
|
||||
txOut = TxOut(htlcSuccessTx.txOut(0).amount - fee, Script.pay2wpkh(localPubKey)) :: Nil,
|
||||
lockTime = 0)
|
||||
val redeemScript: BinaryData = Script.write(Bolt3.htlcSuccessOrTimeout(revocationPubKey, selfDelay, localDelayedKey))
|
||||
val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, htlcSuccessTx.txOut(0).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey)
|
||||
println(s"local sig size: ${localSig.length}")
|
||||
val witness = ScriptWitness(localSig :: BinaryData.empty :: redeemScript :: Nil)
|
||||
tx.updateWitness(0, witness)
|
||||
}
|
||||
Transaction.correctlySpends(spendHtlcSuccess, htlcSuccessTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
println(s"we-spend-received-htlc tx: ${hex(spendHtlcSuccess)}")
|
||||
println(s"you need to publish the htlc success tx and generate ${selfDelay} blocks before you can publish this tx")
|
||||
}
|
||||
|
||||
test("they can spend the offered HTLC with the payment preimage") {
|
||||
val spendOfferedHtlc = {
|
||||
val redeemScript: BinaryData = Script.write(Bolt3.htlcOffered(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage1)))
|
||||
val index = findPubKeyScriptIndex(commitTx, Script.pay2wsh(redeemScript))
|
||||
val tx = Transaction(
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(commitTx, index), signatureScript = Nil, sequence = selfDelay + 1) :: Nil,
|
||||
txOut = TxOut(commitTx.txOut(index).amount - fee, Script.pay2wpkh(remotePubKey)) :: Nil,
|
||||
lockTime = 0)
|
||||
val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(index).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey)
|
||||
println(s"remote sig size: ${remoteSig.length}")
|
||||
val witness = ScriptWitness(remoteSig :: paymentPreimage1 :: redeemScript :: Nil)
|
||||
tx.updateWitness(0, witness)
|
||||
}
|
||||
Transaction.correctlySpends(spendOfferedHtlc, commitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
println(s"they-spend-offered-htlc tx: ${hex(spendOfferedHtlc)}")
|
||||
}
|
||||
|
||||
test("they can timeout the received HTLC after a delay") {
|
||||
val redeemScript: BinaryData = Script.write(Bolt3.htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout))
|
||||
val index = findPubKeyScriptIndex(commitTx, Script.pay2wsh(redeemScript))
|
||||
val timeoutReceivedHtlc = {
|
||||
val tx = Transaction(
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(commitTx, index), signatureScript = Nil, sequence = 0) :: Nil,
|
||||
txOut = TxOut(commitTx.txOut(index).amount - fee, Script.pay2wpkh(remotePubKey)) :: Nil,
|
||||
lockTime = htlcTimeout + 1)
|
||||
val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(index).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey)
|
||||
println(s"remote sig size: ${remoteSig.length}")
|
||||
val witness = ScriptWitness(remoteSig :: BinaryData.empty :: redeemScript :: Nil)
|
||||
tx.updateWitness(0, witness)
|
||||
}
|
||||
Transaction.correctlySpends(timeoutReceivedHtlc, commitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
println(s"they-timeout-received-htlc tx: ${hex(timeoutReceivedHtlc)}")
|
||||
}
|
||||
|
||||
test("they can spend our HTLC timeout tx immediately if they know the revocation private key") {
|
||||
val penaltyTx = {
|
||||
val tx = Transaction(
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(htlcTimeoutTx, 0), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil,
|
||||
txOut = TxOut(htlcTimeoutTx.txOut(0).amount - fee, Script.pay2wpkh(remotePubKey)) :: Nil,
|
||||
lockTime = 0)
|
||||
val redeemScript: BinaryData = Script.write(Bolt3.htlcSuccessOrTimeout(revocationPubKey, selfDelay, localDelayedKey))
|
||||
val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, htlcTimeoutTx.txOut(0).amount, SigVersion.SIGVERSION_WITNESS_V0, revocationPrivKey)
|
||||
println(s"remote sig size: ${remoteSig.length}")
|
||||
val witness = ScriptWitness(remoteSig :: BinaryData("01") :: redeemScript :: Nil)
|
||||
tx.updateWitness(0, witness)
|
||||
}
|
||||
Transaction.correctlySpends(penaltyTx, htlcTimeoutTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
println(s"penalty for out htlc timeout tx: ${hex(penaltyTx)}")
|
||||
}
|
||||
|
||||
test("they can spend our HTLC success tx immediately if they know the revocation private key") {
|
||||
val penaltyTx = {
|
||||
val tx = Transaction(
|
||||
version = 2,
|
||||
txIn = TxIn(OutPoint(htlcSuccessTx, 0), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil,
|
||||
txOut = TxOut(htlcSuccessTx.txOut(0).amount - fee, Script.pay2wpkh(remotePubKey)) :: Nil,
|
||||
lockTime = 0)
|
||||
val redeemScript: BinaryData = Script.write(Bolt3.htlcSuccessOrTimeout(revocationPubKey, selfDelay, localDelayedKey))
|
||||
val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, htlcSuccessTx.txOut(0).amount, SigVersion.SIGVERSION_WITNESS_V0, revocationPrivKey)
|
||||
println(s"remote sig size: ${remoteSig.length}")
|
||||
val witness = ScriptWitness(remoteSig :: BinaryData("01") :: redeemScript :: Nil)
|
||||
tx.updateWitness(0, witness)
|
||||
}
|
||||
Transaction.correctlySpends(penaltyTx, htlcSuccessTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
println(s"penalty for out htlc success tx: ${hex(penaltyTx)}")
|
||||
}
|
||||
|
||||
test("derive revocation key") {
|
||||
object Local {
|
||||
val revocationSecret = Scalar(Crypto.sha256("local foo".getBytes()))
|
||||
val revocationBasePoint = revocationSecret.point
|
||||
val perCommitSecret = Scalar(Crypto.sha256("local bar".getBytes()))
|
||||
}
|
||||
object Remote {
|
||||
val revocationSecret = Scalar(Crypto.sha256("remote foo".getBytes()))
|
||||
val perCommitSecret = Scalar(Crypto.sha256("remote bar".getBytes()))
|
||||
val perCommitBasePoint = perCommitSecret.point
|
||||
}
|
||||
|
||||
// I can compute their revocation pubkey
|
||||
val theirRevocationPubKey = Generators.revocationPubKey(Local.revocationBasePoint, Remote.perCommitBasePoint)
|
||||
|
||||
// and if they give me their per-commit secret I can compute their revocation privkey
|
||||
val theirRevocationPrivKey = Generators.revocationPrivKey(Local.revocationSecret, Remote.perCommitSecret)
|
||||
|
||||
assert(theirRevocationPrivKey.point == theirRevocationPubKey)
|
||||
}
|
||||
|
||||
test("derive local/remote/delayed keys") {
|
||||
object Local {
|
||||
val secret = Scalar(Crypto.sha256("local foo".getBytes()))
|
||||
val basePoint = secret.point
|
||||
val perCommitSecret = Scalar(Crypto.sha256("local bar".getBytes()))
|
||||
val perCommitBasePoint = perCommitSecret.point
|
||||
}
|
||||
object Remote {
|
||||
val secret = Scalar(Crypto.sha256("remote foo".getBytes()))
|
||||
val perCommitSecret = Scalar(Crypto.sha256("remote bar".getBytes()))
|
||||
val perCommitBasePoint = perCommitSecret.point
|
||||
}
|
||||
|
||||
val localKey = Generators.derivePrivKey(Local.secret, Local.perCommitBasePoint)
|
||||
val localPubKey = Generators.derivePubKey(Local.basePoint, Local.perCommitBasePoint)
|
||||
assert(localKey.point == localPubKey)
|
||||
}
|
||||
}
|
2
pom.xml
2
pom.xml
@ -48,6 +48,8 @@
|
||||
<akka.version>2.4.9</akka.version>
|
||||
<bitcoinlib.version>0.9.6</bitcoinlib.version>
|
||||
<acinqtools.version>1.2</acinqtools.version>
|
||||
<akka.version>2.4.12</akka.version>
|
||||
<bitcoinlib.version>0.9.7</bitcoinlib.version>
|
||||
<scalapb.version>0.4.21</scalapb.version>
|
||||
</properties>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user