Skip to content

Commit

Permalink
Use musig2 versions of bitcoin-kmp and secp256k1-kmp
Browse files Browse the repository at this point in the history
  • Loading branch information
sstone committed Jan 18, 2024
1 parent 185abcb commit 78fa967
Show file tree
Hide file tree
Showing 5 changed files with 30 additions and 37 deletions.
8 changes: 4 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<groupId>fr.acinq</groupId>
<artifactId>bitcoin-lib_2.13</artifactId>
<packaging>jar</packaging>
<version>0.32-SNAPSHOT</version>
<version>0.32-MUSIG2-SNAPSHOT</version>
<description>Simple Scala Bitcoin library</description>
<url>https://github.com/ACINQ/bitcoin-lib</url>
<name>bitcoin-lib</name>
Expand Down Expand Up @@ -171,17 +171,17 @@
<dependency>
<groupId>fr.acinq.bitcoin</groupId>
<artifactId>bitcoin-kmp-jvm</artifactId>
<version>0.15.0</version>
<version>0.16.0-MUSIG2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>fr.acinq.secp256k1</groupId>
<artifactId>secp256k1-kmp-jni-jvm</artifactId>
<version>0.12.0</version>
<version>0.13.0-MUSIG2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>1.8.21</version>
<version>1.9.22</version>
</dependency>
<dependency>
<groupId>org.scodec</groupId>
Expand Down
5 changes: 0 additions & 5 deletions src/main/scala/fr/acinq/bitcoin/scalacompat/KotlinUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,6 @@ object KotlinUtils {

implicit def scala2kmp(input: DeterministicWallet.KeyPath): bitcoin.KeyPath = input.keyPath

implicit def scala2kmp(input: Script.ExecutionData): bitcoin.Script.ExecutionData =
new bitcoin.Script.ExecutionData(input.annex.map(scala2kmp).orNull, input.tapleafHash.map(scala2kmp).orNull, input.validationWeightLeft.map(i => Integer.valueOf(i)).orNull, input.codeSeparatorPos)

implicit def kmp2scala(input: bitcoin.Script.ExecutionData): Script.ExecutionData = Script.ExecutionData(Option(input.getAnnex), Option(input.getTapleafHash), Option(input.getValidationWeightLeft), input.getCodeSeparatorPos)

case class InputStreamWrapper(is: InputStream) extends bitcoin.io.Input {
// NB: on the JVM we will use a ByteArrayInputStream, which guarantees that the result will be correct.
override def getAvailableBytes: Int = is.available()
Expand Down
6 changes: 0 additions & 6 deletions src/main/scala/fr/acinq/bitcoin/scalacompat/Script.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,6 @@ object Script {
require(inputIndex >= 0 && inputIndex < tx.txIn.length, "invalid input index")
}

case class ExecutionData(annex: Option[ByteVector], tapleafHash: Option[ByteVector32], validationWeightLeft: Option[Int] = None, codeSeparatorPos: Long = 0xFFFFFFFFL)

object ExecutionData {
val empty: ExecutionData = ExecutionData(None, None)
}

/**
* Bitcoin script runner
*
Expand Down
20 changes: 12 additions & 8 deletions src/main/scala/fr/acinq/bitcoin/scalacompat/Transaction.scala
Original file line number Diff line number Diff line change
Expand Up @@ -248,15 +248,19 @@ object Transaction extends BtcSerializer[Transaction] {
hashForSigning(tx, inputIndex, Script.write(previousOutputScript), sighashType, amount, signatureVersion)

/**
* @param tx transaction to sign
* @param inputIndex index of the transaction input being signed
* @param inputs UTXOs spent by this transaction
* @param sighashType signature hash type
* @param sigVersion signature version
* @param executionData execution context of a transaction script
*
* @param tx transaction to sign
* @param inputIndex index of the transaction input being signed
* @param inputs UTXOs spent by this transaction
* @param sighashType signature hash type
* @param sigVersion signature version
* @param tapleaf optional tapleaf hash
* @param annex optional taproot annex
* @param codeSeparatorPos code separartor position
* @return
*/
def hashForSigningSchnorr(tx: Transaction, inputIndex: Int, inputs: Seq[TxOut], sighashType: Int, sigVersion: Int, executionData: Script.ExecutionData = Script.ExecutionData.empty): ByteVector32 =
bitcoin.Transaction.hashForSigningSchnorr(tx, inputIndex, inputs.map(scala2kmp).asJava, sighashType, sigVersion, executionData)
def hashForSigningSchnorr(tx: Transaction, inputIndex: Int, inputs: Seq[TxOut], sighashType: Int, sigVersion: Int, tapleaf: Option[ByteVector32] = None, annex: Option[ByteVector] = None, codeSeparatorPos: Long = 0xFFFFFFFFL): ByteVector32 =
bitcoin.Transaction.hashForSigningSchnorr(tx, inputIndex, inputs.map(scala2kmp).asJava, sighashType, sigVersion, tapleaf.map(scala2kmp).orNull, annex.map(scala2kmp).orNull, codeSeparatorPos)

/**
* sign a tx input
Expand Down
28 changes: 14 additions & 14 deletions src/test/scala/fr/acinq/bitcoin/scalacompat/TaprootSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import fr.acinq.bitcoin.Crypto.TaprootTweak
import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey, XonlyPublicKey}
import fr.acinq.bitcoin.scalacompat.KotlinUtils._
import fr.acinq.bitcoin.scalacompat.Transaction.hashForSigningSchnorr
import fr.acinq.bitcoin.{Bech32, ScriptFlags, ScriptLeaf, ScriptTree, SigHash, SigVersion}
import fr.acinq.bitcoin.{Bech32, ScriptFlags, ScriptTree, SigHash, SigVersion}
import fr.acinq.secp256k1.Secp256k1
import org.scalatest.FunSuite
import scodec.bits.ByteVector
Expand Down Expand Up @@ -151,8 +151,8 @@ class TaprootSpec extends FunSuite {
)

// simple script tree with a single element
val scriptTree = new ScriptTree.Leaf(new ScriptLeaf(0, Script.write(script), fr.acinq.bitcoin.Script.TAPROOT_LEAF_TAPSCRIPT))
val merkleRoot = ScriptTree.hash(scriptTree)
val scriptTree = new ScriptTree.Leaf(0, Script.write(script), fr.acinq.bitcoin.Script.TAPROOT_LEAF_TAPSCRIPT)
val merkleRoot = scriptTree.hash()

// we choose a pubkey that does not have a corresponding private key: our funding tx can only be spent through the script path, not the key path
val internalPubkey = XonlyPublicKey(PublicKey.fromBin(ByteVector.fromValidHex("0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0")))
Expand All @@ -168,7 +168,7 @@ class TaprootSpec extends FunSuite {
txOut = TxOut(fundingTx.txOut.head.amount - Satoshi(5000), addressToPublicKeyScript(Block.RegtestGenesisBlock.hash, "bcrt1qdtu5cwyngza8hw8s5uk2erlrkh8ceh3msp768v").toOption.get) :: Nil,
lockTime = 0
)
val hash = hashForSigningSchnorr(tmp, 0, Seq(fundingTx.txOut.head), SigHash.SIGHASH_DEFAULT, SigVersion.SIGVERSION_TAPSCRIPT, Script.ExecutionData(annex = None, tapleafHash = Some(merkleRoot)))
val hash = hashForSigningSchnorr(tmp, 0, Seq(fundingTx.txOut.head), SigHash.SIGHASH_DEFAULT, SigVersion.SIGVERSION_TAPSCRIPT, Some(merkleRoot))

// compute all 3 signatures
val sigs = privs.map { p => Crypto.signSchnorr(hash, p, fr.acinq.bitcoin.Crypto.SchnorrTweak.NoTweak.INSTANCE) }
Expand Down Expand Up @@ -204,7 +204,7 @@ class TaprootSpec extends FunSuite {
)
val scripts: Seq[Seq[ScriptElt]] = privs.map { p => Seq(OP_PUSHDATA(XonlyPublicKey(p.publicKey())), OP_CHECKSIG) }

val leaves = scripts.zipWithIndex.map { case (script, idx) => new ScriptTree.Leaf(new ScriptLeaf(idx, Script.write(script), fr.acinq.bitcoin.Script.TAPROOT_LEAF_TAPSCRIPT)) }
val leaves = scripts.zipWithIndex.map { case (script, idx) => new ScriptTree.Leaf(idx, Script.write(script), fr.acinq.bitcoin.Script.TAPROOT_LEAF_TAPSCRIPT) }
// root
// / \
// / \ #3
Expand All @@ -213,7 +213,7 @@ class TaprootSpec extends FunSuite {
new ScriptTree.Branch(leaves.head, leaves(1)),
leaves(2)
)
val merkleRoot = ScriptTree.hash(scriptTree)
val merkleRoot = scriptTree.hash()
val blockchain = Block.SignetGenesisBlock.hash

// we use key #1 as our internal key
Expand Down Expand Up @@ -266,10 +266,10 @@ class TaprootSpec extends FunSuite {
// to re-compute the merkle root we need to provide leaves #2 and #3
val controlBlock = ByteVector.view(Array((fr.acinq.bitcoin.Script.TAPROOT_LEAF_TAPSCRIPT + (if (parity) 1 else 0)).toByte) ++
internalPubkey.pub.value.toByteArray ++
ScriptTree.hash(leaves(1)).toByteArray ++
ScriptTree.hash(leaves(2)).toByteArray)
leaves(1).hash().toByteArray ++
leaves(2).hash().toByteArray)

val hash = hashForSigningSchnorr(tmp, 0, Seq(fundingTx.txOut.head), SigHash.SIGHASH_DEFAULT, SigVersion.SIGVERSION_TAPSCRIPT, Script.ExecutionData(None, Some(ScriptTree.hash(leaves.head))))
val hash = hashForSigningSchnorr(tmp, 0, Seq(fundingTx.txOut.head), SigHash.SIGHASH_DEFAULT, SigVersion.SIGVERSION_TAPSCRIPT, Some(leaves.head.hash()))
val sig = Crypto.signSchnorr(hash, privs.head, fr.acinq.bitcoin.Crypto.SchnorrTweak.NoTweak.INSTANCE)
tmp.updateWitness(0, ScriptWitness(Seq(sig, Script.write(scripts.head), controlBlock)))
}
Expand All @@ -294,9 +294,9 @@ class TaprootSpec extends FunSuite {
// to re-compute the merkle root we need to provide leaves #1 and #3
val controlBlock = ByteVector.view(Array((fr.acinq.bitcoin.Script.TAPROOT_LEAF_TAPSCRIPT + (if (parity) 1 else 0)).toByte) ++
internalPubkey.pub.value.toByteArray ++
ScriptTree.hash(leaves.head).toByteArray ++
ScriptTree.hash(leaves(2)).toByteArray)
val hash = hashForSigningSchnorr(tmp, 0, Seq(fundingTx2.txOut.head), SigHash.SIGHASH_DEFAULT, SigVersion.SIGVERSION_TAPSCRIPT, Script.ExecutionData(None, Some(ScriptTree.hash(leaves(1)))))
leaves.head.hash().toByteArray ++
leaves(2).hash().toByteArray)
val hash = hashForSigningSchnorr(tmp, 0, Seq(fundingTx2.txOut.head), SigHash.SIGHASH_DEFAULT, SigVersion.SIGVERSION_TAPSCRIPT, Some(leaves(1).hash()))
val sig = Crypto.signSchnorr(hash, privs(1), fr.acinq.bitcoin.Crypto.SchnorrTweak.NoTweak.INSTANCE) // signature for script spend of leaf #2
tmp.updateWitness(0, ScriptWitness(Seq(sig, Script.write(scripts(1)), controlBlock)))
}
Expand All @@ -320,8 +320,8 @@ class TaprootSpec extends FunSuite {
// to re-compute the merkle root we need to provide branch(#1, #2)
val controlBlock = ByteVector.view(Array((fr.acinq.bitcoin.Script.TAPROOT_LEAF_TAPSCRIPT + (if (parity) 1 else 0)).toByte) ++
internalPubkey.pub.value.toByteArray ++
ScriptTree.hash(new ScriptTree.Branch(leaves.head, leaves(1))).toByteArray)
val hash = hashForSigningSchnorr(tmp, 0, Seq(fundingTx3.txOut(1)), SigHash.SIGHASH_DEFAULT, SigVersion.SIGVERSION_TAPSCRIPT, Script.ExecutionData(None, Some(ScriptTree.hash(leaves(2)))))
new ScriptTree.Branch(leaves.head, leaves(1)).hash().toByteArray)
val hash = hashForSigningSchnorr(tmp, 0, Seq(fundingTx3.txOut(1)), SigHash.SIGHASH_DEFAULT, SigVersion.SIGVERSION_TAPSCRIPT, Some(leaves(2).hash()))
val sig = Crypto.signSchnorr(hash, privs(2), fr.acinq.bitcoin.Crypto.SchnorrTweak.NoTweak.INSTANCE) // signature for script spend of leaf #3
tmp.updateWitness(0, ScriptWitness(Seq(sig, Script.write(scripts(2)), controlBlock)))
}
Expand Down

0 comments on commit 78fa967

Please sign in to comment.