1
0
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:
pm47 2016-12-08 15:50:37 +01:00
commit f213f79821
10 changed files with 514 additions and 21 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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
}
}

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

View File

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