From e57704c688aaf2a756f0a5fc4e8b251dd4066533 Mon Sep 17 00:00:00 2001 From: Pierre-Marie Padiou Date: Wed, 16 Nov 2016 15:14:34 +0100 Subject: [PATCH 01/14] added warning about wip status --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cbf348913..1c4cd9c5d 100644 --- a/README.md +++ b/README.md @@ -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) --- From e8b7e2e780b103a8865476b45245ade3a8154bbd Mon Sep 17 00:00:00 2001 From: sstone Date: Mon, 21 Nov 2016 18:56:43 +0100 Subject: [PATCH 02/14] update pom.xml (use bitcoin-lib 0.9.7) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 16a3b22d3..0853f9f28 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,7 @@ 2.11.8 2.11 2.4.12 - 0.9.6 + 0.9.7 0.4.21 From 6387afe2a7007e31b17a0d183d356db5e965f716 Mon Sep 17 00:00:00 2001 From: sstone Date: Mon, 21 Nov 2016 18:58:28 +0100 Subject: [PATCH 03/14] (very) partial implementation of bolt 3 --- .../main/scala/fr/acinq/protos/Bolt3.scala | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala diff --git a/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala b/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala new file mode 100644 index 000000000..43c68da7c --- /dev/null +++ b/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala @@ -0,0 +1,125 @@ +package fr.acinq.protos + +import fr.acinq.bitcoin._ + +/** + * Created by fabrice on 21/11/16. + */ +object Bolt3 extends App { + // Alice = local, Bob = remote + val (_, localPrivKey) = Base58Check.decode("cVuzKWCszfvjkoJyUasvsrRdECriz8hSd1BDinRNzytwnXmX7m1g") + val (_, remotePrivKey) = Base58Check.decode("cRUfvpbRtMSqCFD1ADdvgPn5HfRLYuHCFYAr2noWnaRDNger2AoA") + val localPubKey = Crypto.publicKeyFromPrivateKey(localPrivKey) + val remotePubKey = Crypto.publicKeyFromPrivateKey(remotePrivKey) + + def fundingScript(pubKey1: BinaryData, pubKey2: BinaryData) = Script.pay2wsh(Script.createMultiSigMofN(2, pubKey1.toArray :: pubKey2.toArray :: Nil)) + + def toLocal(delay: Long, localDelayedKey: BinaryData) = Script.pay2wsh(OP_PUSHDATA(Script.encodeNumber(delay)) :: OP_CHECKSEQUENCEVERIFY :: OP_DROP :: OP_PUSHDATA(localDelayedKey) :: OP_CHECKSIG :: Nil) + + def toRemote(remoteKey: BinaryData) = Script.pay2pkh(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_CHECKMULTISIGVERIFY :: + OP_ELSE :: + OP_HASH160 :: OP_PUSHDATA(paymentHash) :: OP_EQUALVERIFY :: + OP_CHECKSIGVERIFY :: + 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_CHECKMULTISIGVERIFY :: + OP_ELSE :: + OP_DROP :: OP_PUSHDATA(Script.encodeNumber(lockTime)) :: OP_CHECKLOCKTIMEVERIFY :: OP_DROP :: OP_CHECKSIGVERIFY :: + 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 + } + + val amount = 42000 satoshi + val fundingTx = Transaction(version = 2, txIn = Nil, txOut = TxOut(amount, fundingScript(localPubKey, remotePubKey)) :: Nil, lockTime = 0) + + val paymentPreimage = Hash.Zeroes + val paymentHash = Crypto.hash256(paymentPreimage) + + val localDelayedKey = localPubKey + val paymentPreimage1 = Hash.Zeroes + val paymentPreimage2 = Hash.One + + val htlcTimeout = 10000 // we use the same t/o for offered and received htlcs + + val commitTx = Transaction( + version = 2, + txIn = TxIn(OutPoint(fundingTx, 0), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, + txOut = Seq( + TxOut(22000 satoshi, toLocal(15000, localDelayedKey)), + TxOut(10000 satoshi, toRemote(remotePubKey)), + TxOut(6000 satoshi, Script.pay2wsh(htlcOffered(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage1)))), + TxOut(4000 satoshi, Script.pay2wsh(htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout))) + ), + lockTime = 0) + + // Both sides sign the commit tx + val signedCommitTx = { + val redeemScript: BinaryData = Script.createMultiSigMofN(2, localPubKey :: remotePubKey :: Nil) + + val localSig: BinaryData = Transaction.signInput(commitTx, 0, redeemScript, SIGHASH_ALL, fundingTx.txOut(0).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey) + val remoteSig: BinaryData = Transaction.signInput(commitTx, 0, redeemScript, SIGHASH_ALL, fundingTx.txOut(0).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) + val witness = ScriptWitness(BinaryData.empty :: localSig :: remoteSig :: redeemScript :: Nil) + commitTx.updateWitness(0, witness) + } + Transaction.correctlySpends(signedCommitTx, fundingTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + + + val revocationPubKey = remotePubKey + + // offered HTLC + val htlcTimeoutTx = { + val tx = Transaction( + version = 2, + txIn = TxIn(OutPoint(signedCommitTx, 3), signatureScript = Nil, sequence = 0) :: Nil, + txOut = TxOut(4000 satoshi, htlcSuccessOrTimeout(remotePubKey, htlcTimeout, localDelayedKey)) :: Nil, + lockTime = htlcTimeout + 1) + val redeemScript: BinaryData = Script.write(htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout)) + val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, signedCommitTx.txOut(3).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) + val witness = ScriptWitness(BinaryData("01") :: remoteSig :: BinaryData.empty :: redeemScript :: Nil) + tx.updateWitness(0, witness) + } + + // Alice (local) can claim the offered HTLC after a delay + Transaction.correctlySpends(htlcTimeoutTx, signedCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + + // received HTLC + val htlcSuccessTx = { + val tx = Transaction( + version = 2, + txIn = TxIn(OutPoint(signedCommitTx, 2), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, + txOut = TxOut(6000 satoshi, htlcSuccessOrTimeout(remotePubKey, htlcTimeout, localDelayedKey)) :: Nil, + lockTime = 0) + val redeemScript: BinaryData = Script.write(htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout)) + val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, signedCommitTx.txOut(2).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey) + val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, signedCommitTx.txOut(2).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) + val witness = ScriptWitness(BinaryData("01") :: remoteSig :: BinaryData.empty :: redeemScript :: Nil) + tx.updateWitness(0, witness) + } +} From d09286631df962fd1ed1936a67b1d4c05e273a37 Mon Sep 17 00:00:00 2001 From: sstone Date: Tue, 22 Nov 2016 14:47:55 +0100 Subject: [PATCH 04/14] implement commit tx, HTLC success tx and HTLC timeout tx --- .../main/scala/fr/acinq/protos/Bolt3.scala | 84 +--------- .../scala/fr/acinq/protos/Bolt3Spec.scala | 153 ++++++++++++++++++ 2 files changed, 158 insertions(+), 79 deletions(-) create mode 100644 eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala diff --git a/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala b/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala index 43c68da7c..9da810ef2 100644 --- a/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala +++ b/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala @@ -2,21 +2,14 @@ package fr.acinq.protos import fr.acinq.bitcoin._ -/** - * Created by fabrice on 21/11/16. - */ -object Bolt3 extends App { - // Alice = local, Bob = remote - val (_, localPrivKey) = Base58Check.decode("cVuzKWCszfvjkoJyUasvsrRdECriz8hSd1BDinRNzytwnXmX7m1g") - val (_, remotePrivKey) = Base58Check.decode("cRUfvpbRtMSqCFD1ADdvgPn5HfRLYuHCFYAr2noWnaRDNger2AoA") - val localPubKey = Crypto.publicKeyFromPrivateKey(localPrivKey) - val remotePubKey = Crypto.publicKeyFromPrivateKey(remotePrivKey) +object Bolt3 { + // TODO: sort tx according to BIP69 (lexicographical ordering) - def fundingScript(pubKey1: BinaryData, pubKey2: BinaryData) = Script.pay2wsh(Script.createMultiSigMofN(2, pubKey1.toArray :: pubKey2.toArray :: Nil)) + def fundingScript(pubKey1: BinaryData, pubKey2: BinaryData) = Script.createMultiSigMofN(2, pubKey1.toArray :: pubKey2.toArray :: Nil) - def toLocal(delay: Long, localDelayedKey: BinaryData) = Script.pay2wsh(OP_PUSHDATA(Script.encodeNumber(delay)) :: OP_CHECKSEQUENCEVERIFY :: OP_DROP :: OP_PUSHDATA(localDelayedKey) :: OP_CHECKSIG :: Nil) + def toLocal(delay: Long, localDelayedKey: BinaryData) = OP_PUSHDATA(Script.encodeNumber(delay)) :: OP_CHECKSEQUENCEVERIFY :: OP_DROP :: OP_PUSHDATA(localDelayedKey) :: OP_CHECKSIG :: Nil - def toRemote(remoteKey: BinaryData) = Script.pay2pkh(remoteKey) + def toRemote(remoteKey: BinaryData) = remoteKey def htlcOffered(localKey: BinaryData, remoteKey: BinaryData, paymentHash: BinaryData) = { // @formatter:off @@ -55,71 +48,4 @@ object Bolt3 extends App { OP_CHECKSIG :: Nil // @formatter:on } - - val amount = 42000 satoshi - val fundingTx = Transaction(version = 2, txIn = Nil, txOut = TxOut(amount, fundingScript(localPubKey, remotePubKey)) :: Nil, lockTime = 0) - - val paymentPreimage = Hash.Zeroes - val paymentHash = Crypto.hash256(paymentPreimage) - - val localDelayedKey = localPubKey - val paymentPreimage1 = Hash.Zeroes - val paymentPreimage2 = Hash.One - - val htlcTimeout = 10000 // we use the same t/o for offered and received htlcs - - val commitTx = Transaction( - version = 2, - txIn = TxIn(OutPoint(fundingTx, 0), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, - txOut = Seq( - TxOut(22000 satoshi, toLocal(15000, localDelayedKey)), - TxOut(10000 satoshi, toRemote(remotePubKey)), - TxOut(6000 satoshi, Script.pay2wsh(htlcOffered(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage1)))), - TxOut(4000 satoshi, Script.pay2wsh(htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout))) - ), - lockTime = 0) - - // Both sides sign the commit tx - val signedCommitTx = { - val redeemScript: BinaryData = Script.createMultiSigMofN(2, localPubKey :: remotePubKey :: Nil) - - val localSig: BinaryData = Transaction.signInput(commitTx, 0, redeemScript, SIGHASH_ALL, fundingTx.txOut(0).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey) - val remoteSig: BinaryData = Transaction.signInput(commitTx, 0, redeemScript, SIGHASH_ALL, fundingTx.txOut(0).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) - val witness = ScriptWitness(BinaryData.empty :: localSig :: remoteSig :: redeemScript :: Nil) - commitTx.updateWitness(0, witness) - } - Transaction.correctlySpends(signedCommitTx, fundingTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - - - val revocationPubKey = remotePubKey - - // offered HTLC - val htlcTimeoutTx = { - val tx = Transaction( - version = 2, - txIn = TxIn(OutPoint(signedCommitTx, 3), signatureScript = Nil, sequence = 0) :: Nil, - txOut = TxOut(4000 satoshi, htlcSuccessOrTimeout(remotePubKey, htlcTimeout, localDelayedKey)) :: Nil, - lockTime = htlcTimeout + 1) - val redeemScript: BinaryData = Script.write(htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout)) - val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, signedCommitTx.txOut(3).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) - val witness = ScriptWitness(BinaryData("01") :: remoteSig :: BinaryData.empty :: redeemScript :: Nil) - tx.updateWitness(0, witness) - } - - // Alice (local) can claim the offered HTLC after a delay - Transaction.correctlySpends(htlcTimeoutTx, signedCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - - // received HTLC - val htlcSuccessTx = { - val tx = Transaction( - version = 2, - txIn = TxIn(OutPoint(signedCommitTx, 2), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, - txOut = TxOut(6000 satoshi, htlcSuccessOrTimeout(remotePubKey, htlcTimeout, localDelayedKey)) :: Nil, - lockTime = 0) - val redeemScript: BinaryData = Script.write(htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout)) - val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, signedCommitTx.txOut(2).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey) - val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, signedCommitTx.txOut(2).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) - val witness = ScriptWitness(BinaryData("01") :: remoteSig :: BinaryData.empty :: redeemScript :: Nil) - tx.updateWitness(0, witness) - } } diff --git a/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala new file mode 100644 index 000000000..712180a53 --- /dev/null +++ b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala @@ -0,0 +1,153 @@ +package fr.acinq.protos + +import fr.acinq.bitcoin._ +import org.junit.runner.RunWith +import org.scalatest.FunSuite +import org.scalatest.junit.JUnitRunner + +@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 = Crypto.publicKeyFromPrivateKey(localPrivKey) + val remotePubKey = Crypto.publicKeyFromPrivateKey(remotePrivKey) + + val (Base58.Prefix.SecretKeyTestnet, revocationPrivKey) = Base58Check.decode("cSupnaiBh6jgTcQf9QANCB5fZtXojxkJQczq5kwfSBeULjNd5Ypo") + val revocationPubKey = Crypto.publicKeyFromPrivateKey(revocationPrivKey) + + val amount = 42000 satoshi + val fundingTx = Transaction(version = 2, txIn = Nil, txOut = TxOut(amount, Script.pay2wsh(Bolt3.fundingScript(localPubKey, remotePubKey))) :: Nil, lockTime = 0) + + val paymentPreimage = Hash.Zeroes + val paymentHash = Crypto.hash256(paymentPreimage) + + val localDelayedKey = localPubKey + val paymentPreimage1 = Hash.Zeroes + val paymentPreimage2 = Hash.One + + val htlcTimeout = 10000 + val selfDelay = 15000 + + // create our local commit tx, with an HTLC that we've offered and a HTLC that we've received + val commitTx = { + val tx = Transaction( + version = 2, + txIn = TxIn(OutPoint(fundingTx, 0), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, + txOut = Seq( + TxOut(22000 satoshi, Script.pay2wsh(Bolt3.toLocal(selfDelay, localDelayedKey))), + TxOut(10000 satoshi, Script.pay2pkh(Bolt3.toRemote(remotePubKey))), + TxOut(6000 satoshi, Script.pay2wsh(Bolt3.htlcOffered(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage1)))), + TxOut(4000 satoshi, Script.pay2wsh(Bolt3.htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout))) + ), + lockTime = 0) + val redeemScript: BinaryData = Bolt3.fundingScript(localPubKey, remotePubKey) + val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, fundingTx.txOut(0).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey) + val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, fundingTx.txOut(0).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) + val witness = ScriptWitness(BinaryData.empty :: localSig :: remoteSig :: redeemScript :: Nil) + tx.updateWitness(0, witness) + } + + // create our local HTLC timeout tx for the HTLC that we've offered + // it is signed by both parties + val htlcTimeoutTx = { + val tx = Transaction( + version = 2, + txIn = TxIn(OutPoint(commitTx, 2), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, + txOut = TxOut(6000 satoshi, Script.pay2wsh(Bolt3.htlcSuccessOrTimeout(revocationPubKey, selfDelay, localDelayedKey))) :: Nil, + lockTime = 0) + // both parties sign the unsigned tx + val redeemScript: BinaryData = Script.write(Bolt3.htlcOffered(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage1))) + val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(2).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey) + val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(2).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) + val witness = ScriptWitness(BinaryData("01") :: BinaryData.empty :: remoteSig :: localSig :: BinaryData.empty :: redeemScript :: Nil) + tx.updateWitness(0, witness) + } + + // 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 tx = Transaction( + version = 2, + txIn = TxIn(OutPoint(commitTx, 3), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, + txOut = TxOut(4000 satoshi, Script.pay2wsh(Bolt3.htlcSuccessOrTimeout(revocationPubKey, selfDelay, localDelayedKey))) :: Nil, + lockTime = 0) + // both parties sign the unsigned tx + val redeemScript: BinaryData = Script.write(Bolt3.htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout)) + val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(3).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey) + val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(3).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) + val witness = ScriptWitness(BinaryData("01") :: BinaryData.empty :: remoteSig :: localSig :: paymentPreimage2 :: redeemScript :: Nil) + tx.updateWitness(0, witness) + } + + 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 claim the offered HTLC after a delay") { + val spendHtlcTimeout = { + val tx = Transaction( + version = 2, + txIn = TxIn(OutPoint(htlcTimeoutTx, 0), signatureScript = Nil, sequence = selfDelay + 1) :: Nil, + txOut = TxOut(6000 satoshi, 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, htlcTimeoutTx.txOut(0).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey) + val witness = ScriptWitness(localSig :: BinaryData.empty :: redeemScript :: Nil) + tx.updateWitness(0, witness) + } + Transaction.correctlySpends(spendHtlcTimeout, htlcTimeoutTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + } + + test("we can claim the received HTLC after a delay") { + val spendHtlcSuccess = { + val tx = Transaction( + version = 2, + txIn = TxIn(OutPoint(htlcSuccessTx, 0), signatureScript = Nil, sequence = selfDelay + 1) :: Nil, + txOut = TxOut(4000 satoshi, 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) + val witness = ScriptWitness(localSig :: BinaryData.empty :: redeemScript :: Nil) + tx.updateWitness(0, witness) + } + Transaction.correctlySpends(spendHtlcSuccess, htlcSuccessTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + } + + 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(6000 satoshi, 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) + val witness = ScriptWitness(remoteSig :: BinaryData("01") :: redeemScript :: Nil) + tx.updateWitness(0, witness) + } + Transaction.correctlySpends(penaltyTx, htlcTimeoutTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + } + + 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(6000 satoshi, 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) + val witness = ScriptWitness(remoteSig :: BinaryData("01") :: redeemScript :: Nil) + tx.updateWitness(0, witness) + } + Transaction.correctlySpends(penaltyTx, htlcSuccessTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + } +} From 84cf1a9c9446ec9b9b47ae0a258610661e0ad4f8 Mon Sep 17 00:00:00 2001 From: sstone Date: Tue, 22 Nov 2016 17:48:38 +0100 Subject: [PATCH 05/14] dump tx hex (so they can be tested on regtest) --- .../main/scala/fr/acinq/protos/Bolt3.scala | 3 +- .../scala/fr/acinq/protos/Bolt3Spec.scala | 71 ++++++++++++++----- 2 files changed, 57 insertions(+), 17 deletions(-) diff --git a/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala b/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala index 9da810ef2..1f53f1cef 100644 --- a/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala +++ b/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala @@ -1,11 +1,12 @@ package fr.acinq.protos import fr.acinq.bitcoin._ +import fr.acinq.eclair.channel.Scripts object Bolt3 { // TODO: sort tx according to BIP69 (lexicographical ordering) - def fundingScript(pubKey1: BinaryData, pubKey2: BinaryData) = Script.createMultiSigMofN(2, pubKey1.toArray :: pubKey2.toArray :: Nil) + def fundingScript(pubKey1: BinaryData, pubKey2: BinaryData) = Scripts.multiSig2of2(pubKey1, pubKey2) def toLocal(delay: Long, localDelayedKey: BinaryData) = OP_PUSHDATA(Script.encodeNumber(delay)) :: OP_CHECKSEQUENCEVERIFY :: OP_DROP :: OP_PUSHDATA(localDelayedKey) :: OP_CHECKSIG :: Nil diff --git a/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala index 712180a53..d782f7bd9 100644 --- a/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala +++ b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala @@ -1,10 +1,19 @@ 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.channel.Scripts 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._ + @RunWith(classOf[JUnitRunner]) class Bolt3Spec extends FunSuite { val (Base58.Prefix.SecretKeyTestnet, localPrivKey) = Base58Check.decode("cVuzKWCszfvjkoJyUasvsrRdECriz8hSd1BDinRNzytwnXmX7m1g") @@ -15,9 +24,27 @@ class Bolt3Spec extends FunSuite { val (Base58.Prefix.SecretKeyTestnet, revocationPrivKey) = Base58Check.decode("cSupnaiBh6jgTcQf9QANCB5fZtXojxkJQczq5kwfSBeULjNd5Ypo") val revocationPubKey = Crypto.publicKeyFromPrivateKey(revocationPrivKey) - val amount = 42000 satoshi - val fundingTx = Transaction(version = 2, txIn = Nil, txOut = TxOut(amount, Script.pay2wsh(Bolt3.fundingScript(localPubKey, remotePubKey))) :: Nil, lockTime = 0) + val amount = 420000 satoshi + val bitcoin: Option[ExtendedBitcoinClient] = { + // val config = ConfigFactory.load() + // 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) + 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 paymentPreimage = Hash.Zeroes val paymentHash = Crypto.hash256(paymentPreimage) @@ -28,24 +55,30 @@ class Bolt3Spec extends FunSuite { val htlcTimeout = 10000 val selfDelay = 15000 + 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 = Transaction( version = 2, - txIn = TxIn(OutPoint(fundingTx, 0), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, + txIn = TxIn(OutPoint(fundingTx, fundingPos), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, txOut = Seq( - TxOut(22000 satoshi, Script.pay2wsh(Bolt3.toLocal(selfDelay, localDelayedKey))), - TxOut(10000 satoshi, Script.pay2pkh(Bolt3.toRemote(remotePubKey))), - TxOut(6000 satoshi, Script.pay2wsh(Bolt3.htlcOffered(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage1)))), - TxOut(4000 satoshi, Script.pay2wsh(Bolt3.htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout))) + TxOut(210000 satoshi, Script.pay2wsh(Bolt3.toLocal(selfDelay, localDelayedKey))), + TxOut(100000 satoshi, Script.pay2pkh(Bolt3.toRemote(remotePubKey))), + TxOut(60000 satoshi, Script.pay2wsh(Bolt3.htlcOffered(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage1)))), + TxOut(40000 satoshi, Script.pay2wsh(Bolt3.htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout))) ), lockTime = 0) val redeemScript: BinaryData = Bolt3.fundingScript(localPubKey, remotePubKey) - val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, fundingTx.txOut(0).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey) - val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, fundingTx.txOut(0).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) - val witness = ScriptWitness(BinaryData.empty :: localSig :: remoteSig :: redeemScript :: Nil) + val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, fundingTx.txOut(fundingPos).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey) + val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, fundingTx.txOut(fundingPos).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) + val witness = if (Scripts.isLess(localPubKey, remotePubKey)) + ScriptWitness(BinaryData.empty :: localSig :: remoteSig :: redeemScript :: Nil) + else + ScriptWitness(BinaryData.empty :: remoteSig :: localSig :: redeemScript :: Nil) tx.updateWitness(0, witness) } + println(s"commit tx: ${hex(commitTx)}") // create our local HTLC timeout tx for the HTLC that we've offered // it is signed by both parties @@ -53,7 +86,7 @@ class Bolt3Spec extends FunSuite { val tx = Transaction( version = 2, txIn = TxIn(OutPoint(commitTx, 2), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, - txOut = TxOut(6000 satoshi, Script.pay2wsh(Bolt3.htlcSuccessOrTimeout(revocationPubKey, selfDelay, localDelayedKey))) :: Nil, + txOut = TxOut(commitTx.txOut(2).amount - fee, Script.pay2wsh(Bolt3.htlcSuccessOrTimeout(revocationPubKey, selfDelay, localDelayedKey))) :: Nil, lockTime = 0) // both parties sign the unsigned tx val redeemScript: BinaryData = Script.write(Bolt3.htlcOffered(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage1))) @@ -62,6 +95,7 @@ class Bolt3Spec extends FunSuite { val witness = ScriptWitness(BinaryData("01") :: BinaryData.empty :: remoteSig :: localSig :: BinaryData.empty :: redeemScript :: Nil) tx.updateWitness(0, witness) } + 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 @@ -69,7 +103,7 @@ class Bolt3Spec extends FunSuite { val tx = Transaction( version = 2, txIn = TxIn(OutPoint(commitTx, 3), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, - txOut = TxOut(4000 satoshi, Script.pay2wsh(Bolt3.htlcSuccessOrTimeout(revocationPubKey, selfDelay, localDelayedKey))) :: Nil, + txOut = TxOut(commitTx.txOut(3).amount - fee, Script.pay2wsh(Bolt3.htlcSuccessOrTimeout(revocationPubKey, selfDelay, localDelayedKey))) :: Nil, lockTime = 0) // both parties sign the unsigned tx val redeemScript: BinaryData = Script.write(Bolt3.htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout)) @@ -78,6 +112,7 @@ class Bolt3Spec extends FunSuite { val witness = ScriptWitness(BinaryData("01") :: BinaryData.empty :: remoteSig :: localSig :: paymentPreimage2 :: redeemScript :: Nil) tx.updateWitness(0, witness) } + println(s"htlc success tx: ${hex(htlcSuccessTx)}") test("commit tx spends the funding tx") { Transaction.correctlySpends(commitTx, fundingTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) @@ -96,7 +131,7 @@ class Bolt3Spec extends FunSuite { val tx = Transaction( version = 2, txIn = TxIn(OutPoint(htlcTimeoutTx, 0), signatureScript = Nil, sequence = selfDelay + 1) :: Nil, - txOut = TxOut(6000 satoshi, Script.pay2wpkh(localPubKey)) :: Nil, + txOut = TxOut(htlcTimeoutTx.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, htlcTimeoutTx.txOut(0).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey) @@ -104,6 +139,7 @@ class Bolt3Spec extends FunSuite { tx.updateWitness(0, witness) } Transaction.correctlySpends(spendHtlcTimeout, htlcTimeoutTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + println(s"spend-offered-htlc-timeout tx: ${hex(spendHtlcTimeout)}") } test("we can claim the received HTLC after a delay") { @@ -111,7 +147,7 @@ class Bolt3Spec extends FunSuite { val tx = Transaction( version = 2, txIn = TxIn(OutPoint(htlcSuccessTx, 0), signatureScript = Nil, sequence = selfDelay + 1) :: Nil, - txOut = TxOut(4000 satoshi, Script.pay2wpkh(localPubKey)) :: 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) @@ -119,6 +155,7 @@ class Bolt3Spec extends FunSuite { tx.updateWitness(0, witness) } Transaction.correctlySpends(spendHtlcSuccess, htlcSuccessTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + println(s"spend-received-htlc tx: ${hex(spendHtlcSuccess)}") } test("they can spend our HTLC timeout tx immediately if they know the revocation private key") { @@ -126,7 +163,7 @@ class Bolt3Spec extends FunSuite { val tx = Transaction( version = 2, txIn = TxIn(OutPoint(htlcTimeoutTx, 0), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, - txOut = TxOut(6000 satoshi, Script.pay2wpkh(remotePubKey)) :: 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) @@ -134,6 +171,7 @@ class Bolt3Spec extends FunSuite { 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") { @@ -141,7 +179,7 @@ class Bolt3Spec extends FunSuite { val tx = Transaction( version = 2, txIn = TxIn(OutPoint(htlcSuccessTx, 0), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, - txOut = TxOut(6000 satoshi, Script.pay2wpkh(remotePubKey)) :: 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) @@ -149,5 +187,6 @@ class Bolt3Spec extends FunSuite { tx.updateWitness(0, witness) } Transaction.correctlySpends(penaltyTx, htlcSuccessTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + println(s"penalty for out htlc success tx: ${hex(penaltyTx)}") } } From 67e7124b465c38b07fa26c0651570a846e41cfac Mon Sep 17 00:00:00 2001 From: sstone Date: Wed, 23 Nov 2016 18:02:12 +0100 Subject: [PATCH 06/14] bolt 3: implement missing use cases --- .../main/scala/fr/acinq/protos/Bolt3.scala | 12 +- .../scala/fr/acinq/protos/Bolt3Spec.scala | 105 +++++++++++++++--- 2 files changed, 101 insertions(+), 16 deletions(-) diff --git a/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala b/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala index 1f53f1cef..dac665a10 100644 --- a/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala +++ b/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala @@ -8,7 +8,17 @@ object Bolt3 { def fundingScript(pubKey1: BinaryData, pubKey2: BinaryData) = Scripts.multiSig2of2(pubKey1, pubKey2) - def toLocal(delay: Long, localDelayedKey: BinaryData) = OP_PUSHDATA(Script.encodeNumber(delay)) :: OP_CHECKSEQUENCEVERIFY :: OP_DROP :: OP_PUSHDATA(localDelayedKey) :: OP_CHECKSIG :: Nil + 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 diff --git a/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala index d782f7bd9..fd2671f78 100644 --- a/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala +++ b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala @@ -13,6 +13,7 @@ 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 { @@ -26,17 +27,20 @@ class Bolt3Spec extends FunSuite { val amount = 420000 satoshi - val bitcoin: Option[ExtendedBitcoinClient] = { - // val config = ConfigFactory.load() - // 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) - None - } + 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) @@ -52,8 +56,11 @@ class Bolt3Spec extends FunSuite { val paymentPreimage1 = Hash.Zeroes val paymentPreimage2 = Hash.One + // this is an absolute timeout (i.e. a block height or UNIX timestamp) that will be used with OP_CLTV val htlcTimeout = 10000 - val selfDelay = 15000 + + // this is a relative (to the parent tx) timeout, expressed in number of blocks or seconds + val selfDelay = 20 val fee = 5000 satoshi @@ -63,7 +70,7 @@ class Bolt3Spec extends FunSuite { version = 2, txIn = TxIn(OutPoint(fundingTx, fundingPos), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, txOut = Seq( - TxOut(210000 satoshi, Script.pay2wsh(Bolt3.toLocal(selfDelay, localDelayedKey))), + TxOut(210000 satoshi, Script.pay2wsh(Bolt3.toLocal(revocationPubKey, selfDelay, localDelayedKey))), TxOut(100000 satoshi, Script.pay2pkh(Bolt3.toRemote(remotePubKey))), TxOut(60000 satoshi, Script.pay2wsh(Bolt3.htlcOffered(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage1)))), TxOut(40000 satoshi, Script.pay2wsh(Bolt3.htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout))) @@ -126,7 +133,41 @@ class Bolt3Spec extends FunSuite { Transaction.correctlySpends(htlcSuccessTx, commitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } - test("we can claim the offered HTLC after a delay") { + test("we can spend our commit tx output after a delay") { + val spendOurOutput = { + val tx = Transaction( + version = 2, + txIn = TxIn(OutPoint(commitTx, 0), signatureScript = Nil, sequence = selfDelay + 1) :: Nil, + txOut = TxOut(commitTx.txOut(0).amount - fee, Script.pay2wpkh(localPubKey)) :: Nil, + lockTime = 0) + val redeemScript: BinaryData = Script.write(Bolt3.toLocal(revocationPubKey, selfDelay, localDelayedKey)) + val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(0).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey) + val witness = ScriptWitness(localSig :: BinaryData.empty :: redeemScript :: Nil) + tx.updateWitness(0, witness) + } + Transaction.correctlySpends(spendOurOutput, commitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + println(s"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 our commit tx output immediately if they have the revocation key") { + val penaltyTx = { + val tx = Transaction( + version = 2, + txIn = TxIn(OutPoint(commitTx, 0), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, + txOut = TxOut(commitTx.txOut(0).amount - fee, Script.pay2wpkh(localPubKey)) :: Nil, + lockTime = 0) + val redeemScript: BinaryData = Script.write(Bolt3.toLocal(revocationPubKey, selfDelay, localDelayedKey)) + val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(0).amount, SigVersion.SIGVERSION_WITNESS_V0, revocationPrivKey) + val witness = ScriptWitness(remoteSig :: BinaryData("01") :: redeemScript :: Nil) + tx.updateWitness(0, witness) + } + Transaction.correctlySpends(penaltyTx, commitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + println(s"spend-our-output tx: ${hex(penaltyTx)}") + println(s"you need to publish the commit tx and generate ${selfDelay} blocks before you can publish this tx") + } + + test("we can claim the received HTLC timeout tx after a delay") { val spendHtlcTimeout = { val tx = Transaction( version = 2, @@ -140,9 +181,10 @@ class Bolt3Spec extends FunSuite { } Transaction.correctlySpends(spendHtlcTimeout, htlcTimeoutTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) println(s"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 after a delay") { + test("we can claim the received HTLC success tx after a delay") { val spendHtlcSuccess = { val tx = Transaction( version = 2, @@ -156,6 +198,39 @@ class Bolt3Spec extends FunSuite { } Transaction.correctlySpends(spendHtlcSuccess, htlcSuccessTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) println(s"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 tx = Transaction( + version = 2, + txIn = TxIn(OutPoint(commitTx, 2), signatureScript = Nil, sequence = selfDelay + 1) :: Nil, + txOut = TxOut(commitTx.txOut(2).amount - fee, Script.pay2wpkh(remotePubKey)) :: Nil, + lockTime = 0) + val redeemScript: BinaryData = Script.write(Bolt3.htlcOffered(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage1))) + val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(2).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) + val witness = ScriptWitness(BinaryData("01") :: remoteSig :: paymentPreimage1 :: redeemScript :: Nil) + tx.updateWitness(0, witness) + } + Transaction.correctlySpends(spendOfferedHtlc, commitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + println(s"spend-offered-htlc tx: ${hex(spendOfferedHtlc)}") + } + + test("they can timeout the received HTLC after a delay") { + val timeoutReceivedHtlc = { + val tx = Transaction( + version = 2, + txIn = TxIn(OutPoint(commitTx, 3), signatureScript = Nil, sequence = 0) :: Nil, + txOut = TxOut(commitTx.txOut(3).amount - fee, Script.pay2wpkh(remotePubKey)) :: Nil, + lockTime = htlcTimeout + 1) + val redeemScript: BinaryData = Script.write(Bolt3.htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout)) + val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(3).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) + val witness = ScriptWitness(BinaryData("01") :: 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") { From b767edad107c57e53ad451e27cf474c3aac5c1ab Mon Sep 17 00:00:00 2001 From: sstone Date: Wed, 23 Nov 2016 18:11:20 +0100 Subject: [PATCH 07/14] use CHECK(MULTI)SIG instead of CHECK(MULTI)SIGVERIFY it makes the witness scripts nicer --- eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala | 8 ++++---- .../src/test/scala/fr/acinq/protos/Bolt3Spec.scala | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala b/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala index dac665a10..cf12a85ca 100644 --- a/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala +++ b/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala @@ -27,10 +27,10 @@ object Bolt3 { 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_CHECKMULTISIGVERIFY :: + OP_DROP :: OP_2 :: OP_SWAP :: OP_PUSHDATA(localKey) :: OP_2 :: OP_CHECKMULTISIG :: OP_ELSE :: OP_HASH160 :: OP_PUSHDATA(paymentHash) :: OP_EQUALVERIFY :: - OP_CHECKSIGVERIFY :: + OP_CHECKSIG :: OP_ENDIF :: Nil // @formatter:on } @@ -41,9 +41,9 @@ object Bolt3 { 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_CHECKMULTISIGVERIFY :: + 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_CHECKSIGVERIFY :: + OP_DROP :: OP_PUSHDATA(Script.encodeNumber(lockTime)) :: OP_CHECKLOCKTIMEVERIFY :: OP_DROP :: OP_CHECKSIG :: OP_ENDIF :: Nil // @formatter:on } diff --git a/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala index fd2671f78..750092887 100644 --- a/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala +++ b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala @@ -99,7 +99,7 @@ class Bolt3Spec extends FunSuite { val redeemScript: BinaryData = Script.write(Bolt3.htlcOffered(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage1))) val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(2).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey) val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(2).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) - val witness = ScriptWitness(BinaryData("01") :: BinaryData.empty :: remoteSig :: localSig :: BinaryData.empty :: redeemScript :: Nil) + val witness = ScriptWitness(BinaryData.empty :: remoteSig :: localSig :: BinaryData.empty :: redeemScript :: Nil) tx.updateWitness(0, witness) } println(s"htlc timeout tx: ${hex(htlcTimeoutTx)}") @@ -116,7 +116,7 @@ class Bolt3Spec extends FunSuite { val redeemScript: BinaryData = Script.write(Bolt3.htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout)) val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(3).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey) val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(3).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) - val witness = ScriptWitness(BinaryData("01") :: BinaryData.empty :: remoteSig :: localSig :: paymentPreimage2 :: redeemScript :: Nil) + val witness = ScriptWitness(BinaryData.empty :: remoteSig :: localSig :: paymentPreimage2 :: redeemScript :: Nil) tx.updateWitness(0, witness) } println(s"htlc success tx: ${hex(htlcSuccessTx)}") @@ -210,7 +210,7 @@ class Bolt3Spec extends FunSuite { lockTime = 0) val redeemScript: BinaryData = Script.write(Bolt3.htlcOffered(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage1))) val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(2).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) - val witness = ScriptWitness(BinaryData("01") :: remoteSig :: paymentPreimage1 :: redeemScript :: Nil) + val witness = ScriptWitness(remoteSig :: paymentPreimage1 :: redeemScript :: Nil) tx.updateWitness(0, witness) } Transaction.correctlySpends(spendOfferedHtlc, commitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) @@ -226,7 +226,7 @@ class Bolt3Spec extends FunSuite { lockTime = htlcTimeout + 1) val redeemScript: BinaryData = Script.write(Bolt3.htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout)) val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(3).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) - val witness = ScriptWitness(BinaryData("01") :: remoteSig :: BinaryData.empty :: redeemScript :: Nil) + val witness = ScriptWitness(remoteSig :: BinaryData.empty :: redeemScript :: Nil) tx.updateWitness(0, witness) } Transaction.correctlySpends(timeoutReceivedHtlc, commitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) From ae604646635158fe86c758e4b1e99f50d8449d36 Mon Sep 17 00:00:00 2001 From: sstone Date: Thu, 24 Nov 2016 18:03:07 +0100 Subject: [PATCH 08/14] bolt3 tests: print tx size --- .../scala/fr/acinq/protos/Bolt3Spec.scala | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala index 750092887..416420208 100644 --- a/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala +++ b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala @@ -25,7 +25,7 @@ class Bolt3Spec extends FunSuite { val (Base58.Prefix.SecretKeyTestnet, revocationPrivKey) = Base58Check.decode("cSupnaiBh6jgTcQf9QANCB5fZtXojxkJQczq5kwfSBeULjNd5Ypo") val revocationPubKey = Crypto.publicKeyFromPrivateKey(revocationPrivKey) - val amount = 420000 satoshi + val amount = 92000 satoshi val config = ConfigFactory.load() @@ -62,7 +62,7 @@ class Bolt3Spec extends FunSuite { // this is a relative (to the parent tx) timeout, expressed in number of blocks or seconds val selfDelay = 20 - val fee = 5000 satoshi + val fee = 10000 satoshi // create our local commit tx, with an HTLC that we've offered and a HTLC that we've received val commitTx = { @@ -83,7 +83,10 @@ class Bolt3Spec extends FunSuite { ScriptWitness(BinaryData.empty :: localSig :: remoteSig :: redeemScript :: Nil) else ScriptWitness(BinaryData.empty :: remoteSig :: localSig :: redeemScript :: Nil) - tx.updateWitness(0, witness) + println(s"size of unsigned commit tx: ${Transaction.write(tx).length}") + val tx1 = tx.updateWitness(0, witness) + println(s"size of signed commit tx: ${Transaction.write(tx1).length}") + tx1 } println(s"commit tx: ${hex(commitTx)}") @@ -100,7 +103,10 @@ class Bolt3Spec extends FunSuite { val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(2).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey) val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(2).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) val witness = ScriptWitness(BinaryData.empty :: remoteSig :: localSig :: BinaryData.empty :: redeemScript :: Nil) - tx.updateWitness(0, witness) + println(s"size of unsigned htlcTimeoutTx tx: ${Transaction.write(tx).length}") + val tx1 = tx.updateWitness(0, witness) + println(s"size of signed htlcTimeoutTx tx: ${Transaction.write(tx1).length}") + tx1 } println(s"htlc timeout tx: ${hex(htlcTimeoutTx)}") @@ -117,7 +123,10 @@ class Bolt3Spec extends FunSuite { val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(3).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey) val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(3).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) val witness = ScriptWitness(BinaryData.empty :: remoteSig :: localSig :: paymentPreimage2 :: redeemScript :: Nil) - tx.updateWitness(0, witness) + println(s"size of unsigned htlcSuccessTx tx: ${Transaction.write(tx).length}") + val tx1 = tx.updateWitness(0, witness) + println(s"size of signed htlcSuccessTx tx: ${Transaction.write(tx1).length}") + tx1 } println(s"htlc success tx: ${hex(htlcSuccessTx)}") From 7ff9e4cb198d8b18f9883301f3f8d690714fc00e Mon Sep 17 00:00:00 2001 From: sstone Date: Fri, 25 Nov 2016 15:34:58 +0100 Subject: [PATCH 09/14] bolt 3 tests: fix amounts commit tx total output was wrong --- .../src/test/scala/fr/acinq/protos/Bolt3Spec.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala index 416420208..b7fa15672 100644 --- a/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala +++ b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala @@ -62,7 +62,7 @@ class Bolt3Spec extends FunSuite { // this is a relative (to the parent tx) timeout, expressed in number of blocks or seconds val selfDelay = 20 - val fee = 10000 satoshi + 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 = { @@ -70,10 +70,10 @@ class Bolt3Spec extends FunSuite { version = 2, txIn = TxIn(OutPoint(fundingTx, fundingPos), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, txOut = Seq( - TxOut(210000 satoshi, Script.pay2wsh(Bolt3.toLocal(revocationPubKey, selfDelay, localDelayedKey))), - TxOut(100000 satoshi, Script.pay2pkh(Bolt3.toRemote(remotePubKey))), - TxOut(60000 satoshi, Script.pay2wsh(Bolt3.htlcOffered(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage1)))), - TxOut(40000 satoshi, Script.pay2wsh(Bolt3.htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout))) + TxOut((amount - fee) / 4, Script.pay2wsh(Bolt3.toLocal(revocationPubKey, selfDelay, localDelayedKey))), + TxOut((amount - fee) / 4, Script.pay2pkh(Bolt3.toRemote(remotePubKey))), + TxOut((amount - fee) / 4, Script.pay2wsh(Bolt3.htlcOffered(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage1)))), + TxOut((amount - fee) / 4, Script.pay2wsh(Bolt3.htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout))) ), lockTime = 0) val redeemScript: BinaryData = Bolt3.fundingScript(localPubKey, remotePubKey) From 1fe8b8feb23fa4bed452215544fec082f00d3833 Mon Sep 17 00:00:00 2001 From: sstone Date: Mon, 28 Nov 2016 17:32:45 +0100 Subject: [PATCH 10/14] compute tx base size, total size and weight --- .../main/scala/fr/acinq/protos/Bolt3.scala | 6 +++ .../scala/fr/acinq/protos/Bolt3Spec.scala | 54 +++++++++++++++---- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala b/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala index cf12a85ca..06a979aae 100644 --- a/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala +++ b/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala @@ -6,6 +6,12 @@ import fr.acinq.eclair.channel.Scripts 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) = Scripts.multiSig2of2(pubKey1, pubKey2) def toLocal(revocationPubKey: BinaryData, toSelfDelay: Long, localDelayedKey: BinaryData) = { diff --git a/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala index b7fa15672..81de514ba 100644 --- a/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala +++ b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala @@ -19,8 +19,8 @@ import scala.util.Try class Bolt3Spec extends FunSuite { val (Base58.Prefix.SecretKeyTestnet, localPrivKey) = Base58Check.decode("cVuzKWCszfvjkoJyUasvsrRdECriz8hSd1BDinRNzytwnXmX7m1g") val (Base58.Prefix.SecretKeyTestnet, remotePrivKey) = Base58Check.decode("cRUfvpbRtMSqCFD1ADdvgPn5HfRLYuHCFYAr2noWnaRDNger2AoA") - val localPubKey = Crypto.publicKeyFromPrivateKey(localPrivKey) - val remotePubKey = Crypto.publicKeyFromPrivateKey(remotePrivKey) + 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) @@ -71,21 +71,24 @@ class Bolt3Spec extends FunSuite { txIn = TxIn(OutPoint(fundingTx, fundingPos), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, txOut = Seq( TxOut((amount - fee) / 4, Script.pay2wsh(Bolt3.toLocal(revocationPubKey, selfDelay, localDelayedKey))), - TxOut((amount - fee) / 4, Script.pay2pkh(Bolt3.toRemote(remotePubKey))), + TxOut((amount - fee) / 4, Script.pay2wpkh(remotePubKey)), TxOut((amount - fee) / 4, Script.pay2wsh(Bolt3.htlcOffered(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage1)))), TxOut((amount - fee) / 4, Script.pay2wsh(Bolt3.htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), 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 (Scripts.isLess(localPubKey, remotePubKey)) ScriptWitness(BinaryData.empty :: localSig :: remoteSig :: redeemScript :: Nil) else ScriptWitness(BinaryData.empty :: remoteSig :: localSig :: redeemScript :: Nil) - println(s"size of unsigned commit tx: ${Transaction.write(tx).length}") + println(s"witness size: ${ScriptWitness.write(witness).length}") val tx1 = tx.updateWitness(0, witness) - println(s"size of signed commit tx: ${Transaction.write(tx1).length}") + 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)}") @@ -100,12 +103,15 @@ class Bolt3Spec extends FunSuite { lockTime = 0) // both parties sign the unsigned tx val redeemScript: BinaryData = Script.write(Bolt3.htlcOffered(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage1))) + println(s"size of htlcOffered script: ${redeemScript.length}") val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(2).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(2).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"size of unsigned htlcTimeoutTx tx: ${Transaction.write(tx).length}") + println(s"witness size: ${ScriptWitness.write(witness).length}") val tx1 = tx.updateWitness(0, witness) - println(s"size of signed htlcTimeoutTx tx: ${Transaction.write(tx1).length}") + 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)}") @@ -120,12 +126,15 @@ class Bolt3Spec extends FunSuite { lockTime = 0) // both parties sign the unsigned tx val redeemScript: BinaryData = Script.write(Bolt3.htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout)) + println(s"size of htlcReceived script: ${redeemScript.length}") val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(3).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(3).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"size of unsigned htlcSuccessTx tx: ${Transaction.write(tx).length}") + println(s"witness size: ${ScriptWitness.write(witness).length}") val tx1 = tx.updateWitness(0, witness) - println(s"size of signed htlcSuccessTx tx: ${Transaction.write(tx1).length}") + 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)}") @@ -150,7 +159,9 @@ class Bolt3Spec extends FunSuite { txOut = TxOut(commitTx.txOut(0).amount - fee, Script.pay2wpkh(localPubKey)) :: Nil, lockTime = 0) val redeemScript: BinaryData = Script.write(Bolt3.toLocal(revocationPubKey, selfDelay, localDelayedKey)) + println(s"size of toLocal script: ${redeemScript.length}") val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.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) } @@ -159,6 +170,23 @@ class Bolt3Spec extends FunSuite { 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 spendTheirOutput = { + val tx = Transaction( + version = 2, + txIn = TxIn(OutPoint(commitTx, 1), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, + txOut = TxOut(commitTx.txOut(1).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(1).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"spend-their-output tx: ${hex(spendTheirOutput)}") + } + + test("they can spend our commit tx output immediately if they have the revocation key") { val penaltyTx = { val tx = Transaction( @@ -168,6 +196,7 @@ class Bolt3Spec extends FunSuite { lockTime = 0) val redeemScript: BinaryData = Script.write(Bolt3.toLocal(revocationPubKey, selfDelay, localDelayedKey)) val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.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) } @@ -184,7 +213,9 @@ class Bolt3Spec extends FunSuite { 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) } @@ -202,6 +233,7 @@ class Bolt3Spec extends FunSuite { 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) } @@ -219,6 +251,7 @@ class Bolt3Spec extends FunSuite { lockTime = 0) val redeemScript: BinaryData = Script.write(Bolt3.htlcOffered(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage1))) val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(2).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) + println(s"remote sig size: ${remoteSig.length}") val witness = ScriptWitness(remoteSig :: paymentPreimage1 :: redeemScript :: Nil) tx.updateWitness(0, witness) } @@ -235,6 +268,7 @@ class Bolt3Spec extends FunSuite { lockTime = htlcTimeout + 1) val redeemScript: BinaryData = Script.write(Bolt3.htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout)) val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(3).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) } @@ -251,6 +285,7 @@ class Bolt3Spec extends FunSuite { 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) } @@ -267,6 +302,7 @@ class Bolt3Spec extends FunSuite { 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) } From 4e29d2b9fe6ccd51d712fd0157c32be5d8503579 Mon Sep 17 00:00:00 2001 From: sstone Date: Mon, 28 Nov 2016 18:39:13 +0100 Subject: [PATCH 11/14] implement revocation key derivation --- .../main/scala/fr/acinq/protos/Bolt3.scala | 54 +++++++++++++++++++ .../scala/fr/acinq/protos/Bolt3Spec.scala | 22 ++++++++ 2 files changed, 76 insertions(+) diff --git a/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala b/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala index 06a979aae..7dc83960e 100644 --- a/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala +++ b/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala @@ -1,5 +1,7 @@ package fr.acinq.protos +import java.math.BigInteger + import fr.acinq.bitcoin._ import fr.acinq.eclair.channel.Scripts @@ -65,4 +67,56 @@ object Bolt3 { OP_CHECKSIG :: Nil // @formatter:on } + + + 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 basePoint = BasePoint(Crypto.publicKeyFromPrivateKey(data :+ 1.toByte)) + + def add(scalar: Scalar): Scalar = { + val buffer = new BigInteger(1, data).add(new BigInteger(1, scalar.data)).mod(Crypto.curve.getN).toByteArray + val buffer1 = fixSize(buffer.dropWhile(_ == 0)) + Scalar(buffer1) + } + + def multiply(scalar: Scalar): Scalar = { + val buffer = new BigInteger(1, data).multiply(new BigInteger(1, scalar.data)).mod(Crypto.curve.getN).toByteArray + val buffer1 = fixSize(buffer.dropWhile(_ == 0)) + Scalar(buffer1) + } + } + + case class BasePoint(data: BinaryData) { + require(data.length == 33) + + def add(point: BasePoint): BasePoint = { + val local = Crypto.curve.getCurve.decodePoint(data) + val rhs = Crypto.curve.getCurve.decodePoint(point.data) + BasePoint(local.add(rhs).getEncoded(true)) + } + + def multiply(scalar: Scalar): BasePoint = { + val local = Crypto.curve.getCurve.decodePoint(data) + val point = local.multiply(new BigInteger(1, scalar.data)) + BasePoint(point.getEncoded(true)) + } + } + + def revocationPubKey(revocationBasePoint: BasePoint, perCommitPoint: BasePoint): BasePoint = { + val a = Scalar(Crypto.sha256(revocationBasePoint.data ++ perCommitPoint.data)) + val b = Scalar(Crypto.sha256(perCommitPoint.data ++ revocationBasePoint.data)) + revocationBasePoint.multiply(a).add(perCommitPoint.multiply(b)) + } + + def revocationPrivKey(revocationSecret: Scalar, perCommitSecret: Scalar): Scalar = { + val a = Scalar(Crypto.sha256(revocationSecret.basePoint.data ++ perCommitSecret.basePoint.data)) + val b = Scalar(Crypto.sha256(perCommitSecret.basePoint.data ++ revocationSecret.basePoint.data)) + revocationSecret.multiply(a).add(perCommitSecret.multiply(b)) + } } diff --git a/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala index 81de514ba..7816665fb 100644 --- a/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala +++ b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala @@ -6,6 +6,7 @@ import fr.acinq.bitcoin._ import fr.acinq.eclair.blockchain.ExtendedBitcoinClient import fr.acinq.eclair.blockchain.rpc.BitcoinJsonRPCClient import fr.acinq.eclair.channel.Scripts +import fr.acinq.protos.Bolt3.Scalar import org.junit.runner.RunWith import org.scalatest.FunSuite import org.scalatest.junit.JUnitRunner @@ -309,4 +310,25 @@ class Bolt3Spec extends FunSuite { 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.basePoint + 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.basePoint + } + + // I can compute their revocation pubkey + val theirRevocationPubKey = Bolt3.revocationPubKey(Local.revocationBasePoint, Remote.perCommitBasePoint) + + // and if they give me their per-commit secret I can compute their revocation privkey + val theirRevocationPrivKey = Bolt3.revocationPrivKey(Local.revocationSecret, Remote.perCommitSecret) + + assert(theirRevocationPrivKey.basePoint == theirRevocationPubKey) + } } From 21d90905a0daba6d28c1bdcfe599c2e20f838352 Mon Sep 17 00:00:00 2001 From: sstone Date: Tue, 29 Nov 2016 14:11:58 +0100 Subject: [PATCH 12/14] fix labels in bolt3 tests --- eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala | 1 - eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala b/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala index 7dc83960e..001dfee5a 100644 --- a/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala +++ b/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala @@ -68,7 +68,6 @@ object Bolt3 { // @formatter:on } - def fixSize(data: BinaryData): BinaryData = data.length match { case 32 => data case length if length < 32 => Array.fill(32 - length)(0.toByte) ++ data diff --git a/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala index 7816665fb..0ec82dfd9 100644 --- a/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala +++ b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala @@ -167,7 +167,7 @@ class Bolt3Spec extends FunSuite { tx.updateWitness(0, witness) } Transaction.correctlySpends(spendOurOutput, commitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - println(s"spend-our-output tx: ${hex(spendOurOutput)}") + 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") } @@ -202,8 +202,7 @@ class Bolt3Spec extends FunSuite { tx.updateWitness(0, witness) } Transaction.correctlySpends(penaltyTx, commitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - println(s"spend-our-output tx: ${hex(penaltyTx)}") - println(s"you need to publish the commit tx and generate ${selfDelay} blocks before you can publish this tx") + println(s"they-spend-our-output tx: ${hex(penaltyTx)}") } test("we can claim the received HTLC timeout tx after a delay") { From 9225d5a79623eafa8be035d45eaa47145c475c11 Mon Sep 17 00:00:00 2001 From: sstone Date: Wed, 30 Nov 2016 17:21:50 +0100 Subject: [PATCH 13/14] use bip69 ordering for tx inputs/outputs --- eclair-node/pom.xml | 2 +- .../scala/fr/acinq/protos/Bolt3Spec.scala | 106 ++++++++++-------- 2 files changed, 61 insertions(+), 47 deletions(-) diff --git a/eclair-node/pom.xml b/eclair-node/pom.xml index 935ed61b6..2705f02b9 100644 --- a/eclair-node/pom.xml +++ b/eclair-node/pom.xml @@ -79,7 +79,7 @@ fr.acinq bitcoin-lib_${scala.version.short} - ${bitcoinlib.version} + 0.9.8-SNAPSHOT diff --git a/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala index 0ec82dfd9..3657a258f 100644 --- a/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala +++ b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala @@ -26,7 +26,7 @@ class Bolt3Spec extends FunSuite { val (Base58.Prefix.SecretKeyTestnet, revocationPrivKey) = Base58Check.decode("cSupnaiBh6jgTcQf9QANCB5fZtXojxkJQczq5kwfSBeULjNd5Ypo") val revocationPubKey = Crypto.publicKeyFromPrivateKey(revocationPrivKey) - val amount = 92000 satoshi + val amount = 40000 + 30000 + 20000 + 15000 satoshi val config = ConfigFactory.load() @@ -50,12 +50,12 @@ class Bolt3Spec extends FunSuite { def hex(tx: Transaction) = toHexString(Transaction.write(tx)) println(s"funding tx (use output $fundingPos): ${hex(fundingTx)}") - val paymentPreimage = Hash.Zeroes - val paymentHash = Crypto.hash256(paymentPreimage) 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 @@ -67,16 +67,19 @@ class Bolt3Spec extends FunSuite { // create our local commit tx, with an HTLC that we've offered and a HTLC that we've received val commitTx = { - val tx = Transaction( - version = 2, - txIn = TxIn(OutPoint(fundingTx, fundingPos), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, - txOut = Seq( - TxOut((amount - fee) / 4, Script.pay2wsh(Bolt3.toLocal(revocationPubKey, selfDelay, localDelayedKey))), - TxOut((amount - fee) / 4, Script.pay2wpkh(remotePubKey)), - TxOut((amount - fee) / 4, Script.pay2wsh(Bolt3.htlcOffered(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage1)))), - TxOut((amount - fee) / 4, Script.pay2wsh(Bolt3.htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout))) - ), - lockTime = 0) + 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) @@ -94,20 +97,25 @@ class Bolt3Spec extends FunSuite { } 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, 2), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, - txOut = TxOut(commitTx.txOut(2).amount - fee, Script.pay2wsh(Bolt3.htlcSuccessOrTimeout(revocationPubKey, selfDelay, localDelayedKey))) :: Nil, + 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 - val redeemScript: BinaryData = Script.write(Bolt3.htlcOffered(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage1))) println(s"size of htlcOffered script: ${redeemScript.length}") - val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(2).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey) + 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(2).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) + 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}") @@ -120,17 +128,18 @@ class Bolt3Spec extends FunSuite { // 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, 3), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, - txOut = TxOut(commitTx.txOut(3).amount - fee, Script.pay2wsh(Bolt3.htlcSuccessOrTimeout(revocationPubKey, selfDelay, localDelayedKey))) :: Nil, + 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 - val redeemScript: BinaryData = Script.write(Bolt3.htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout)) println(s"size of htlcReceived script: ${redeemScript.length}") - val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(3).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey) + 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(3).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) + 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}") @@ -154,14 +163,15 @@ class Bolt3Spec extends FunSuite { 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, 0), signatureScript = Nil, sequence = selfDelay + 1) :: Nil, - txOut = TxOut(commitTx.txOut(0).amount - fee, Script.pay2wpkh(localPubKey)) :: Nil, + txIn = TxIn(OutPoint(commitTx, index), signatureScript = Nil, sequence = selfDelay + 1) :: Nil, + txOut = TxOut(commitTx.txOut(index).amount - fee, Script.pay2wpkh(localPubKey)) :: Nil, lockTime = 0) - val redeemScript: BinaryData = Script.write(Bolt3.toLocal(revocationPubKey, selfDelay, localDelayedKey)) println(s"size of toLocal script: ${redeemScript.length}") - val localSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(0).amount, SigVersion.SIGVERSION_WITNESS_V0, localPrivKey) + 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) @@ -172,31 +182,33 @@ class Bolt3Spec extends FunSuite { } 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, 1), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, - txOut = TxOut(commitTx.txOut(1).amount - fee, Script.pay2wpkh(remotePubKey)) :: Nil, + 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(1).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) + 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"spend-their-output tx: ${hex(spendTheirOutput)}") + 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, 0), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil, - txOut = TxOut(commitTx.txOut(0).amount - fee, Script.pay2wpkh(localPubKey)) :: Nil, + 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 redeemScript: BinaryData = Script.write(Bolt3.toLocal(revocationPubKey, selfDelay, localDelayedKey)) - val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(0).amount, SigVersion.SIGVERSION_WITNESS_V0, revocationPrivKey) + 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) @@ -220,7 +232,7 @@ class Bolt3Spec extends FunSuite { tx.updateWitness(0, witness) } Transaction.correctlySpends(spendHtlcTimeout, htlcTimeoutTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - println(s"spend-offered-htlc-timeout tx: ${hex(spendHtlcTimeout)}") + 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") } @@ -238,36 +250,38 @@ class Bolt3Spec extends FunSuite { tx.updateWitness(0, witness) } Transaction.correctlySpends(spendHtlcSuccess, htlcSuccessTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - println(s"spend-received-htlc tx: ${hex(spendHtlcSuccess)}") + 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, 2), signatureScript = Nil, sequence = selfDelay + 1) :: Nil, - txOut = TxOut(commitTx.txOut(2).amount - fee, Script.pay2wpkh(remotePubKey)) :: Nil, + 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 redeemScript: BinaryData = Script.write(Bolt3.htlcOffered(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage1))) - val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(2).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) + 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"spend-offered-htlc tx: ${hex(spendOfferedHtlc)}") + 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, 3), signatureScript = Nil, sequence = 0) :: Nil, - txOut = TxOut(commitTx.txOut(3).amount - fee, Script.pay2wpkh(remotePubKey)) :: Nil, + 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 redeemScript: BinaryData = Script.write(Bolt3.htlcReceived(localPubKey, remotePubKey, Crypto.hash160(paymentPreimage2), htlcTimeout)) - val remoteSig: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, commitTx.txOut(3).amount, SigVersion.SIGVERSION_WITNESS_V0, remotePrivKey) + 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) From fa9096655b3a253b8a8097ad07367416531e6a40 Mon Sep 17 00:00:00 2001 From: sstone Date: Wed, 7 Dec 2016 15:39:15 +0100 Subject: [PATCH 14/14] add local/remote/delayed key derivation function --- .../main/scala/fr/acinq/protos/Bolt3.scala | 68 +++++++++++-------- .../scala/fr/acinq/protos/Bolt3Spec.scala | 24 ++++++- 2 files changed, 59 insertions(+), 33 deletions(-) diff --git a/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala b/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala index 001dfee5a..82090fac3 100644 --- a/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala +++ b/eclair-node/src/main/scala/fr/acinq/protos/Bolt3.scala @@ -4,6 +4,7 @@ import java.math.BigInteger import fr.acinq.bitcoin._ import fr.acinq.eclair.channel.Scripts +import org.bouncycastle.math.ec.ECPoint object Bolt3 { // TODO: sort tx according to BIP69 (lexicographical ordering) @@ -76,46 +77,53 @@ object Bolt3 { case class Scalar(data: BinaryData) { require(data.length == 32) - def basePoint = BasePoint(Crypto.publicKeyFromPrivateKey(data :+ 1.toByte)) + def point = Point(Crypto.publicKeyFromPrivateKey(data :+ 1.toByte)) - def add(scalar: Scalar): Scalar = { - val buffer = new BigInteger(1, data).add(new BigInteger(1, scalar.data)).mod(Crypto.curve.getN).toByteArray - val buffer1 = fixSize(buffer.dropWhile(_ == 0)) - Scalar(buffer1) - } + def bigInteger: BigInteger = new BigInteger(1, data) - def multiply(scalar: Scalar): Scalar = { - val buffer = new BigInteger(1, data).multiply(new BigInteger(1, scalar.data)).mod(Crypto.curve.getN).toByteArray - val buffer1 = fixSize(buffer.dropWhile(_ == 0)) - Scalar(buffer1) - } + def add(scalar: Scalar): Scalar = Scalar(bigInteger.add(scalar.bigInteger)) + + def multiply(scalar: Scalar): Scalar = Scalar(bigInteger.multiply(scalar.bigInteger).mod(Crypto.curve.getN)) } - case class BasePoint(data: BinaryData) { + object Scalar { + def apply(value: BigInteger): Scalar = new Scalar(fixSize(value.toByteArray.dropWhile(_ == 0))) + } + + case class Point(data: BinaryData) { require(data.length == 33) - def add(point: BasePoint): BasePoint = { - val local = Crypto.curve.getCurve.decodePoint(data) - val rhs = Crypto.curve.getCurve.decodePoint(point.data) - BasePoint(local.add(rhs).getEncoded(true)) - } + def ecPoint: ECPoint = Crypto.curve.getCurve.decodePoint(data) - def multiply(scalar: Scalar): BasePoint = { - val local = Crypto.curve.getCurve.decodePoint(data) - val point = local.multiply(new BigInteger(1, scalar.data)) - BasePoint(point.getEncoded(true)) - } + def add(point: Point): Point = Point(ecPoint.add(point.ecPoint)) + + def multiply(scalar: Scalar): Point = Point(ecPoint.multiply(scalar.bigInteger)) } - def revocationPubKey(revocationBasePoint: BasePoint, perCommitPoint: BasePoint): BasePoint = { - val a = Scalar(Crypto.sha256(revocationBasePoint.data ++ perCommitPoint.data)) - val b = Scalar(Crypto.sha256(perCommitPoint.data ++ revocationBasePoint.data)) - revocationBasePoint.multiply(a).add(perCommitPoint.multiply(b)) + object Point { + def apply(ecPoint: ECPoint): Point = new Point(ecPoint.getEncoded(true)) } - def revocationPrivKey(revocationSecret: Scalar, perCommitSecret: Scalar): Scalar = { - val a = Scalar(Crypto.sha256(revocationSecret.basePoint.data ++ perCommitSecret.basePoint.data)) - val b = Scalar(Crypto.sha256(perCommitSecret.basePoint.data ++ revocationSecret.basePoint.data)) - revocationSecret.multiply(a).add(perCommitSecret.multiply(b)) + 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 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 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 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)) } } diff --git a/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala index 3657a258f..dcf820b47 100644 --- a/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala +++ b/eclair-node/src/test/scala/fr/acinq/protos/Bolt3Spec.scala @@ -327,13 +327,13 @@ class Bolt3Spec extends FunSuite { test("derive revocation key") { object Local { val revocationSecret = Scalar(Crypto.sha256("local foo".getBytes())) - val revocationBasePoint = revocationSecret.basePoint + 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.basePoint + val perCommitBasePoint = perCommitSecret.point } // I can compute their revocation pubkey @@ -342,6 +342,24 @@ class Bolt3Spec extends FunSuite { // and if they give me their per-commit secret I can compute their revocation privkey val theirRevocationPrivKey = Bolt3.revocationPrivKey(Local.revocationSecret, Remote.perCommitSecret) - assert(theirRevocationPrivKey.basePoint == theirRevocationPubKey) + 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 = Bolt3.derivePrivKey(Local.secret, Local.perCommitBasePoint) + val localPubKey = Bolt3.derivePubKey(Local.basePoint, Local.perCommitBasePoint) + assert(localKey.point == localPubKey) } }