diff --git a/pom.xml b/pom.xml
index 596bdd94..26e49133 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,11 +3,18 @@
fr.acinq
bitcoin-lib_2.13
jar
- 0.45.2-SNAPSHOT
+ 0.46
Simple Scala Bitcoin library
https://github.com/ACINQ/bitcoin-lib
bitcoin-lib
+
+
+ central-snapshots
+ https://central.sonatype.com/repository/maven-snapshots
+
+
+
2020-01-01T00:00:00Z
UTF-8
@@ -40,20 +47,6 @@
-
-
- central-snapshots
- Sonatype Central Portal (snapshots)
- https://central.sonatype.com/repository/maven-snapshots
-
- false
-
-
- true
-
-
-
-
@@ -153,12 +146,12 @@
fr.acinq.bitcoin
bitcoin-kmp-jvm
- 0.28.1
+ 0.29.0
fr.acinq.secp256k1
secp256k1-kmp-jni-jvm
- 0.21.0
+ 0.22.0
org.scodec
diff --git a/src/main/scala/fr/acinq/bitcoin/scalacompat/Crypto.scala b/src/main/scala/fr/acinq/bitcoin/scalacompat/Crypto.scala
index 35db3acb..bfb56357 100644
--- a/src/main/scala/fr/acinq/bitcoin/scalacompat/Crypto.scala
+++ b/src/main/scala/fr/acinq/bitcoin/scalacompat/Crypto.scala
@@ -7,21 +7,14 @@ import scodec.bits.ByteVector
object Crypto {
// @formatter:off
- /** Specify how private keys are tweaked when creating Schnorr signatures. */
- sealed trait SchnorrTweak
- object SchnorrTweak {
- /** The private key is directly used, without any tweaks. */
- case object NoTweak extends SchnorrTweak
- }
-
- sealed trait TaprootTweak extends SchnorrTweak
+ sealed trait TaprootTweak
object TaprootTweak {
/** The private key is tweaked with H_TapTweak(public key) (this is used for key path spending when there is no script tree). */
- case object NoScriptTweak extends TaprootTweak
+ case object KeyPathTweak extends TaprootTweak
/** The private key is tweaked with H_TapTweak(public key || merkle_root) (this is used for key path spending when a script tree exists). */
- case class ScriptTweak(merkleRoot: ByteVector32) extends TaprootTweak
- object ScriptTweak {
- def apply(scriptTree: ScriptTree): ScriptTweak = ScriptTweak(scriptTree.hash())
+ case class ScriptPathTweak(merkleRoot: ByteVector32) extends TaprootTweak
+ object ScriptPathTweak {
+ def apply(scriptTree: ScriptTree): ScriptPathTweak = ScriptPathTweak(scriptTree.hash())
}
}
// @formatter:on
@@ -159,10 +152,10 @@ object Crypto {
}
/** Tweak this key with the merkle root of the given script tree. */
- def outputKey(scriptTree: ScriptTree): (XonlyPublicKey, Boolean) = outputKey(TaprootTweak.ScriptTweak(scriptTree))
+ def outputKey(scriptTree: ScriptTree): (XonlyPublicKey, Boolean) = outputKey(TaprootTweak.ScriptPathTweak(scriptTree))
/** Tweak this key with the merkle root provided. */
- def outputKey(merkleRoot: ByteVector32): (XonlyPublicKey, Boolean) = outputKey(TaprootTweak.ScriptTweak(merkleRoot))
+ def outputKey(merkleRoot: ByteVector32): (XonlyPublicKey, Boolean) = outputKey(TaprootTweak.ScriptPathTweak(merkleRoot))
/**
* add a public key to this x-only key
@@ -295,8 +288,8 @@ object Crypto {
* the key (there is an extra "1" appended to the key)
* @return a signature in compact format (64 bytes)
*/
- def signSchnorr(data: ByteVector32, privateKey: PrivateKey, schnorrTweak: SchnorrTweak = SchnorrTweak.NoTweak, auxrand32: Option[ByteVector32] = None): ByteVector64 = {
- bitcoin.Crypto.signSchnorr(data, privateKey, scala2kmp(schnorrTweak), auxrand32.map(scala2kmp).orNull)
+ def signSchnorr(data: ByteVector32, privateKey: PrivateKey, schnorrTweak_opt: Option[TaprootTweak] = None, auxrand32: Option[ByteVector32] = None): ByteVector64 = {
+ bitcoin.Crypto.signSchnorr(data, privateKey, schnorrTweak_opt.map(scala2kmp).orNull, auxrand32.map(scala2kmp).orNull)
}
/**
diff --git a/src/main/scala/fr/acinq/bitcoin/scalacompat/KotlinUtils.scala b/src/main/scala/fr/acinq/bitcoin/scalacompat/KotlinUtils.scala
index 880ccc67..607c8755 100644
--- a/src/main/scala/fr/acinq/bitcoin/scalacompat/KotlinUtils.scala
+++ b/src/main/scala/fr/acinq/bitcoin/scalacompat/KotlinUtils.scala
@@ -65,25 +65,13 @@ object KotlinUtils {
}
implicit def kmp2scala(input: bitcoin.Crypto.TaprootTweak): TaprootTweak = input match {
- case bitcoin.Crypto.TaprootTweak.NoScriptTweak.INSTANCE => TaprootTweak.NoScriptTweak
- case tweak: bitcoin.Crypto.TaprootTweak.ScriptTweak => TaprootTweak.ScriptTweak(kmp2scala(tweak.getMerkleRoot))
- case _ => ??? // this cannot happen, but the compiler cannot know that there aren't other cases
+ case bitcoin.Crypto.TaprootTweak.KeyPathTweak.INSTANCE => TaprootTweak.KeyPathTweak
+ case tweak: bitcoin.Crypto.TaprootTweak.ScriptPathTweak => TaprootTweak.ScriptPathTweak(kmp2scala(tweak.getMerkleRoot))
}
implicit def scala2kmp(input: TaprootTweak): bitcoin.Crypto.TaprootTweak = input match {
- case TaprootTweak.NoScriptTweak => bitcoin.Crypto.TaprootTweak.NoScriptTweak.INSTANCE
- case tweak: TaprootTweak.ScriptTweak => new bitcoin.Crypto.TaprootTweak.ScriptTweak(scala2kmp(tweak.merkleRoot))
- }
-
- implicit def kmp2scala(input: bitcoin.Crypto.SchnorrTweak): SchnorrTweak = input match {
- case bitcoin.Crypto.SchnorrTweak.NoTweak.INSTANCE => SchnorrTweak.NoTweak
- case tweak: bitcoin.Crypto.TaprootTweak => kmp2scala(tweak)
- case _ => ??? // this cannot happen, but the compiler cannot know that there aren't other cases
- }
-
- implicit def scala2kmp(input: SchnorrTweak): bitcoin.Crypto.SchnorrTweak = input match {
- case SchnorrTweak.NoTweak => bitcoin.Crypto.SchnorrTweak.NoTweak.INSTANCE
- case tweak: TaprootTweak => scala2kmp(tweak)
+ case TaprootTweak.KeyPathTweak => bitcoin.Crypto.TaprootTweak.KeyPathTweak.INSTANCE
+ case tweak: TaprootTweak.ScriptPathTweak => new bitcoin.Crypto.TaprootTweak.ScriptPathTweak(scala2kmp(tweak.merkleRoot))
}
implicit def kmp2scala(input: bitcoin.TxIn): TxIn = TxIn(input.outPoint, input.signatureScript, input.sequence, input.witness)
diff --git a/src/main/scala/fr/acinq/bitcoin/scalacompat/Script.scala b/src/main/scala/fr/acinq/bitcoin/scalacompat/Script.scala
index f28f24aa..41b08bc3 100644
--- a/src/main/scala/fr/acinq/bitcoin/scalacompat/Script.scala
+++ b/src/main/scala/fr/acinq/bitcoin/scalacompat/Script.scala
@@ -166,16 +166,22 @@ object Script {
def witnessPay2wpkh(pubKey: PublicKey, sig: ByteVector): ScriptWitness = bitcoin.Script.witnessPay2wpkh(pubKey, sig)
/**
- * @param outputKey public key exposed by the taproot script (tweaked based on the tapscripts).
- * @return a pay-to-taproot script.
+ * @param internalKey internal public key that will be tweaked with the [scripts] provided.
+ * @param scriptsRoot spending scripts that can be used instead of key-path spending.
*/
- def pay2tr(outputKey: XonlyPublicKey): Seq[ScriptElt] = bitcoin.Script.pay2tr(outputKey.pub).asScala.map(kmp2scala).toList
+ def pay2tr(internalKey: XonlyPublicKey, scriptsRoot: ByteVector32): Seq[ScriptElt] = bitcoin.Script.pay2tr(internalKey.pub, scriptsRoot).asScala.map(kmp2scala).toList
/**
* @param internalKey internal public key that will be tweaked with the [scripts] provided.
- * @param scripts_opt optional spending scripts that can be used instead of key-path spending.
+ * @param scripts spending scripts that can be used instead of key-path spending.
+ */
+ def pay2tr(internalKey: XonlyPublicKey, scripts: ScriptTree): Seq[ScriptElt] = pay2tr(internalKey, scripts.hash())
+
+ /**
+ * @param internalKey internal public key that will be tweaked with the provided [taprootTweak].
+ * @param taprootTweak tweak to apply to [internalKey].
*/
- def pay2tr(internalKey: XonlyPublicKey, scripts_opt: Option[ScriptTree]): Seq[ScriptElt] = bitcoin.Script.pay2tr(internalKey.pub, scripts_opt.map(scala2kmp).orNull).asScala.map(kmp2scala).toList
+ def pay2tr(internalKey: XonlyPublicKey, taprootTweak: Crypto.TaprootTweak): Seq[ScriptElt] = bitcoin.Script.pay2tr(internalKey.pub, taprootTweak).asScala.map(kmp2scala).toList
def isPay2tr(script: Seq[ScriptElt]): Boolean = bitcoin.Script.isPay2tr(script.map(scala2kmp).asJava)
diff --git a/src/test/scala/fr/acinq/bitcoin/scalacompat/Musig2Spec.scala b/src/test/scala/fr/acinq/bitcoin/scalacompat/Musig2Spec.scala
index 55f24e1f..ec7e4a9b 100644
--- a/src/test/scala/fr/acinq/bitcoin/scalacompat/Musig2Spec.scala
+++ b/src/test/scala/fr/acinq/bitcoin/scalacompat/Musig2Spec.scala
@@ -1,6 +1,7 @@
package fr.acinq.bitcoin.scalacompat
import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey
+import fr.acinq.bitcoin.scalacompat.Crypto.TaprootTweak.KeyPathTweak
import fr.acinq.bitcoin.{ScriptFlags, SigHash}
import org.scalatest.FunSuite
import scodec.bits.{ByteVector, HexStringSyntax}
@@ -19,7 +20,7 @@ class Musig2Spec extends FunSuite {
val internalPubKey = Musig2.aggregateKeys(Seq(alicePubKey, bobPubKey))
// This tx sends to a taproot script that doesn't contain any script path.
- val tx = Transaction(2, Nil, Seq(TxOut(10_000 sat, Script.pay2tr(internalPubKey, scripts_opt = None))), 0)
+ val tx = Transaction(2, Nil, Seq(TxOut(10_000 sat, Script.pay2tr(internalPubKey, KeyPathTweak))), 0)
// This tx spends the previous tx with Alice and Bob's signatures.
val spendingTx = Transaction(2, Seq(TxIn(OutPoint(tx, 0), ByteVector.empty, 0)), Seq(TxOut(10_000 sat, Script.pay2wpkh(alicePubKey))), 0)
@@ -59,7 +60,7 @@ class Musig2Spec extends FunSuite {
// The internal pubkey is the musig2 aggregation of the user's and server's public keys: it does not depend upon the user's refund's key.
val aggregatedKey = Musig2.aggregateKeys(Seq(userPublicKey, serverPublicKey))
// It is tweaked with the script's merkle root to get the pubkey that will be exposed.
- val pubkeyScript = Script.pay2tr(aggregatedKey, Some(scriptTree))
+ val pubkeyScript = Script.pay2tr(aggregatedKey, scriptTree)
val swapInTx = Transaction(
version = 2,
diff --git a/src/test/scala/fr/acinq/bitcoin/scalacompat/TaprootSpec.scala b/src/test/scala/fr/acinq/bitcoin/scalacompat/TaprootSpec.scala
index e5b5552b..79d6e3a2 100644
--- a/src/test/scala/fr/acinq/bitcoin/scalacompat/TaprootSpec.scala
+++ b/src/test/scala/fr/acinq/bitcoin/scalacompat/TaprootSpec.scala
@@ -1,5 +1,6 @@
package fr.acinq.bitcoin.scalacompat
+import fr.acinq.bitcoin.scalacompat.Crypto.TaprootTweak.KeyPathTweak
import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey, TaprootTweak}
import fr.acinq.bitcoin.scalacompat.KotlinUtils._
import fr.acinq.bitcoin.{Bech32, ScriptFlags, SigHash, SigVersion}
@@ -14,10 +15,10 @@ class TaprootSpec extends FunSuite {
val (_, master) = DeterministicWallet.ExtendedPrivateKey.decode("tprv8ZgxMBicQKsPeQQADibg4WF7mEasy3piWZUHyThAzJCPNgMHDVYhTCVfev3jFbDhcYm4GimeFMbbi9z1d9rfY1aL5wfJ9mNebQ4thJ62EJb")
val key = DeterministicWallet.derivePrivateKey(master, "86'/1'/0'/0/1")
val internalKey = key.publicKey.xOnly
- val script = Script.pay2tr(internalKey, scripts_opt = None)
- val (outputKey, _) = internalKey.outputKey(TaprootTweak.NoScriptTweak)
+ val script = Script.pay2tr(internalKey, KeyPathTweak)
+ val (outputKey, _) = internalKey.outputKey(TaprootTweak.KeyPathTweak)
assert("tb1phlhs7afhqzkgv0n537xs939s687826vn8l24ldkrckvwsnlj3d7qj6u57c" == Bech32.encodeWitnessAddress("tb", 1, outputKey.pub.value.toByteArray))
- assert(script == Script.pay2tr(outputKey))
+ assert(addressFromPublicKeyScript(Block.Testnet3GenesisBlock.hash, script).contains("tb1phlhs7afhqzkgv0n537xs939s687826vn8l24ldkrckvwsnlj3d7qj6u57c"))
// tx sends to tb1phlhs7afhqzkgv0n537xs939s687826vn8l24ldkrckvwsnlj3d7qj6u57c
val tx = Transaction.read(
@@ -41,23 +42,23 @@ class TaprootSpec extends FunSuite {
assert(Crypto.verifySignatureSchnorr(hash, sig, outputKey))
// re-create signature
- val ourSig = Crypto.signSchnorr(hash, key.privateKey, TaprootTweak.NoScriptTweak)
+ val ourSig = Crypto.signSchnorr(hash, key.privateKey, Some(TaprootTweak.KeyPathTweak))
assert(Crypto.verifySignatureSchnorr(hash, ourSig, outputKey))
assert(Secp256k1.get().verifySchnorr(ourSig.toArray, hash.toArray, outputKey.pub.value.toByteArray))
// setting auxiliary random data to all-zero yields the same result as not setting any auxiliary random data
- val ourSig1 = Crypto.signSchnorr(hash, key.privateKey, TaprootTweak.NoScriptTweak, Some(ByteVector32.Zeroes))
+ val ourSig1 = Crypto.signSchnorr(hash, key.privateKey, Some(TaprootTweak.KeyPathTweak), Some(ByteVector32.Zeroes))
assert(ourSig == ourSig1)
// setting auxiliary random data to a non-zero value yields a different result
- val ourSig2 = Crypto.signSchnorr(hash, key.privateKey, TaprootTweak.NoScriptTweak, Some(ByteVector32.One))
+ val ourSig2 = Crypto.signSchnorr(hash, key.privateKey, Some(TaprootTweak.KeyPathTweak), Some(ByteVector32.One))
assert(ourSig != ourSig2)
}
test("send to and spend from taproot addresses") {
val privateKey = PrivateKey(ByteVector32.fromValidHex("0101010101010101010101010101010101010101010101010101010101010101"))
val internalKey = privateKey.publicKey.xOnly
- val (outputKey, _) = internalKey.outputKey(TaprootTweak.NoScriptTweak)
+ val (outputKey, _) = internalKey.outputKey(TaprootTweak.KeyPathTweak)
val address = Bech32.encodeWitnessAddress("tb", 1, outputKey.pub.value.toByteArray)
assert("tb1p33wm0auhr9kkahzd6l0kqj85af4cswn276hsxg6zpz85xe2r0y8snwrkwy" == address)
@@ -65,7 +66,7 @@ class TaprootSpec extends FunSuite {
val tx = Transaction.read(
"02000000000101bf77ef36f2c0f32e0822cef0514948254997495a34bfba7dd4a73aabfcbb87900000000000fdffffff02c2c2000000000000160014b5c3dbfeb8e7d0c809c3ba3f815fd430777ef4be50c30000000000002251208c5db7f797196d6edc4dd7df6048f4ea6b883a6af6af032342088f436543790f0140583f758bea307216e03c1f54c3c6088e8923c8e1c89d96679fb00de9e808a79d0fba1cc3f9521cb686e8f43fb37cc6429f2e1480c70cc25ecb4ac0dde8921a01f1f70000"
)
- assert(Script.pay2tr(internalKey, scripts_opt = None) == Script.parse(tx.txOut(1).publicKeyScript))
+ assert(Script.pay2tr(internalKey, KeyPathTweak) == Script.parse(tx.txOut(1).publicKeyScript))
// we want to spend
val Right(outputScript) = addressToPublicKeyScript(Block.Testnet3GenesisBlock.hash, "tb1pn3g330w4n5eut7d4vxq0pp303267qc6vg8d2e0ctjuqre06gs3yqnc5yx0")
@@ -157,7 +158,7 @@ class TaprootSpec extends FunSuite {
val internalPubkey = PublicKey.fromBin(ByteVector.fromValidHex("0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0")).xOnly
// funding tx sends to our tapscript
- val fundingTx = Transaction(version = 2, txIn = Nil, txOut = Seq(TxOut(Satoshi(1000000), Script.pay2tr(internalPubkey, Some(scriptTree)))), lockTime = 0)
+ val fundingTx = Transaction(version = 2, txIn = Nil, txOut = Seq(TxOut(Satoshi(1000000), Script.pay2tr(internalPubkey, scriptTree))), lockTime = 0)
// create an unsigned transaction
val tmp = Transaction(
@@ -213,7 +214,7 @@ class TaprootSpec extends FunSuite {
val (tweakedKey, _) = internalPubkey.outputKey(scriptTree)
// this is the tapscript we send funds to
- val script = Script.pay2tr(internalPubkey, Some(scriptTree))
+ val script = Script.pay2tr(internalPubkey, scriptTree)
val bip350Address = Bech32.encodeWitnessAddress(Bech32.hrp(blockchain), 1.toByte, tweakedKey.pub.value.toByteArray)
assert(bip350Address == "tb1p78gx95syx0qz8w5nftk8t7nce78zlpqpsxugcvq5xpfy4tvn6rasd7wk0y")
val Right(sweepPublicKeyScript) = addressToPublicKeyScript(blockchain, "tb1qxy9hhxkw7gt76qrm4yzw4j06gkk4evryh8ayp7")