<p>the answer above combined with the comments should be enough to have it working, I wanted also to share it all together in case it may be easier for others to follow through on it.</p>
<p>serialize and Prevouts imports are from <code>bitcoin::psbt</code></p>
<pre><code>fn sign_key_tx(
    secp: &amp;Secp256k1&lt;All&gt;,
    tx_ref: &amp;Transaction,
    prevouts: &amp;Prevouts&lt;TxOut&gt;,
    key_pair_internal: &amp;KeyPair,
    tap_info: &amp;TaprootSpendInfo,
) -&gt; Transaction {
    let mut tx = tx_ref.clone();
    let sighash_sig = SighashCache::new(&amp;mut tx.clone())
        .taproot_key_spend_signature_hash(0, prevouts, SchnorrSighashType::AllPlusAnyoneCanPay) // or All
        .unwrap();

    let tweak_key_pair = key_pair_internal.tap_tweak(secp, tap_info.merkle_root());

    // then sig
    let msg = Message::from_slice(&amp;sighash_sig).unwrap();

    let sig = secp.sign_schnorr(&amp;msg, &amp;tweak_key_pair.to_inner());

    //verify sig
    secp.verify_schnorr(&amp;sig, &amp;msg, &amp;tweak_key_pair.to_inner().x_only_public_key().0)
        .unwrap();

    // then witness
    let schnorr_sig = SchnorrSig {
        sig,
        hash_ty: SchnorrSighashType::AllPlusAnyoneCanPay, // or All
    };

    tx.input[0].witness.push(schnorr_sig.serialize());

    tx
}
</code></pre>
<p>Hope it helps!</p>

CC BY-SA 4.0
2023-05-31T14:44:51.447Z
2023-05-31T14:44:51.447Z
Deployer
<p>It looks like you're using the <a href="https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.Transaction.html#method.signature_hash" rel="noreferrer">Transaction::signature_hash</a> method to compute your signature hash -- this is used to compute legacy (pre-segwit!) signature hashes, and we've been unfortunately very slow in deprecating it. I believe the next release of rust-bitcoin will have better documentation and a deprecation notice.</p>
<p>The new way to compute sighashes, which supports segwit and taproot, is to put your transaction into a <a href="https://docs.rs/bitcoin/latest/bitcoin/util/sighash/struct.SighashCache.html" rel="noreferrer">SighashCache</a> which can then be used to compute sighashes for each input. This structure caches hashed data which is the same for every input -- something both segwit and taproot sighashes were designed for, but legacy sighashes were not -- which is why it has a less convenient API. But on the other hand, it should be much faster.</p>

CC BY-SA 4.0
2023-01-03T01:14:47.903Z
That's helpful. Thanks!
rijndael
CC BY-SA 4.0
2023-01-03T01:28:55.880Z
For any future devs who stumble upon this, the other bit I was missing is you need to make sure the schnorr sig is properly encoded like so:

```
let final_sig = schnorr::SchnorrSig { sig: signature, hash_ty: SchnorrSighashType::All };
```

and then insert it into the witness like:
```
keypath_tx.input[0].witness.push(final_sig.serialize());
```
rijndael
CC BY-SA 4.0
2023-01-01T21:45:38.760Z
2023-01-01T21:45:38.760Z
Andrew Poelstra
<p>I'm trying to make a taproot HTLC using <code>rust-bitcoin</code> for an atomic swap protocol. My plan is that in the &quot;happy case&quot; of my protocol, the coins are spent with the key-path, and then there is a tapleaf for the &quot;refund&quot; case (after <code>n</code> blocks, the original owner can spend), and a tapleaf with a hashlock.</p>
<p>Assuming I have functions to build up the hashlock and timelock scripts, I'm building some <code>TaprootSpendInfo</code> and a receive address as so:</p>
<pre><code>let taproot_spend_info = TaprootBuilder::new()
        .add_leaf(
            1u8,
            build_hashlock_script(alice_hashlock.as_slice(), &amp;bob.hl_keypair.x_only_public_key().0)
        )
        .expect(&quot;couldn't add hashlock leaf&quot;)
        .add_leaf(
            1u8,
            build_timelock_script(2, &amp;alice.refund_keypair.x_only_public_key().0)
        )
        .expect(&quot;Couldn't add timelock leaf&quot;)
        .finalize(
            &amp;secp,
            alice.escrow_keypair.x_only_public_key().0
        )
        .expect(&quot;Could not finalize taproot spend info&quot;);

    let alice2bob_addr = Address::p2tr_tweaked(taproot_spend_info.output_key(), Network::Regtest);
</code></pre>
<p>I go and fund that address. Then when I want to spend from that address with the key-path, I make a transaction and get the sighash as so:</p>
<pre><code>    let mut keypath_tx = Transaction {
        version: 1,
        lock_time: PackedLockTime::ZERO,
        input: vec![ TxIn {
            previous_output: OutPoint {
                txid: first_funding_txid,
                vout: 0
            },
            script_sig: script::Builder::new().into_script(), // this might be wrong
            sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
            witness: Witness::new(),
        }],
        output: vec![ TxOut {
            value: Amount::from_btc(0.99).unwrap().to_sat(),
            script_pubkey: miner_address.script_pubkey()
        }]
    };
    let sighash = keypath_tx.signature_hash(0, &amp;script::Builder::new().into_script(), 0x00);
    let hash_to_be_signed = sighash.as_hash();
</code></pre>
<p>And then I take that sighash and sign it with libsecp:</p>
<pre><code>    let message = secp256k1::Message::from_slice(&amp;hash_to_be_signed).unwrap();
    let signature = secp.sign_schnorr(&amp;message, &amp;alice.escrow_keypair.secret_key().add_tweak(&amp;taproot_spend_info.tap_tweak().to_scalar()).unwrap().keypair(&amp;secp));
    keypath_tx.input[0].witness.push(signature.as_ref());
</code></pre>
<p>When I try to broadcast this transaction, I get an error <code>Invalid Schnorr signature</code>.</p>
<p>I think I'm adding the signature to the witness correctly: BIP341 says that if after removing the annex from the witness (if it's there), if there is one item left, it's interpreted as a key-path spend and the item is interpreted as a signature. So I think pushing the signature into the otherwise empty witness is the right thing to do. I also checked that the signature is valid. That makes me think that I'm either signing the wrong thing or that I'm not tweaking the private key correctly at signing time.</p>
<p>My only taproot experience so far has been with descriptor wallets, so any tips about what I'm doing wrong would be greatly appreciated. Thanks!</p>

<signature><taproot><hash-time-locked-contract><key-tweaking><rust-bitcoin>
Signing taproot key-path transaction from rust-bitcoin
rijndael
# Signing taproot key-path transaction from rust-bitcoin

(Asked by: username_0 on 2023-01-01)

I'm trying to make a taproot HTLC using `rust-bitcoin` for an atomic swap protocol. My plan is that in the "happy case" of my protocol, the coins are spent with the key-path, and then there is a tapleaf for the "refund" case (after `n` blocks, the original owner can spend), and a tapleaf with a hashlock.

Assuming I have functions to build up the hashlock and timelock scripts, I'm building some `TaprootSpendInfo` and a receive address as so:

```
let taproot_spend_info = TaprootBuilder::new()
        .add_leaf(
            1u8,
            build_hashlock_script(alice_hashlock.as_slice(), &bob.hl_keypair.x_only_public_key().0)
        )
        .expect("couldn't add hashlock leaf")
        .add_leaf(
            1u8,
            build_timelock_script(2, &alice.refund_keypair.x_only_public_key().0)
        )
        .expect("Couldn't add timelock leaf")
        .finalize(
            &secp,
            alice.escrow_keypair.x_only_public_key().0
        )
        .expect("Could not finalize taproot spend info");

    let alice2bob_addr = Address::p2tr_tweaked(taproot_spend_info.output_key(), Network::Regtest);

```

I go and fund that address. Then when I want to spend from that address with the key-path, I make a transaction and get the sighash as so:

```
    let mut keypath_tx = Transaction {
        version: 1,
        lock_time: PackedLockTime::ZERO,
        input: vec![ TxIn {
            previous_output: OutPoint {
                txid: first_funding_txid,
                vout: 0
            },
            script_sig: script::Builder::new().into_script(), // this might be wrong
            sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
            witness: Witness::new(),
        }],
        output: vec![ TxOut {
            value: Amount::from_btc(0.99).unwrap().to_sat(),
            script_pubkey: miner_address.script_pubkey()
        }]
    };
    let sighash = keypath_tx.signature_hash(0, &script::Builder::new().into_script(), 0x00);
    let hash_to_be_signed = sighash.as_hash();

```

And then I take that sighash and sign it with libsecp:

```
    let message = secp256k1::Message::from_slice(&hash_to_be_signed).unwrap();
    let signature = secp.sign_schnorr(&message, &alice.escrow_keypair.secret_key().add_tweak(&taproot_spend_info.tap_tweak().to_scalar()).unwrap().keypair(&secp));
    keypath_tx.input[0].witness.push(signature.as_ref());

```

When I try to broadcast this transaction, I get an error `Invalid Schnorr signature`.

I think I'm adding the signature to the witness correctly: BIP341 says that if after removing the annex from the witness (if it's there), if there is one item left, it's interpreted as a key-path spend and the item is interpreted as a signature. So I think pushing the signature into the otherwise empty witness is the right thing to do. I also checked that the signature is valid. That makes me think that I'm either signing the wrong thing or that I'm not tweaking the private key correctly at signing time.

My only taproot experience so far has been with descriptor wallets, so any tips about what I'm doing wrong would be greatly appreciated. Thanks!

**Tags:** signature, taproot, hash-time-locked-contract, key-tweaking, rust-bitcoin


---

## 2 answers

### Answer by username_2 on 2023-01-01. Score: 5 | Accepted answer

It looks like you're using the [Transaction::signature\_hash](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.Transaction.html#method.signature_hash) method to compute your signature hash -- this is used to compute legacy (pre-segwit!) signature hashes, and we've been unfortunately very slow in deprecating it. I believe the next release of rust-bitcoin will have better documentation and a deprecation notice.

The new way to compute sighashes, which supports segwit and taproot, is to put your transaction into a [SighashCache](https://docs.rs/bitcoin/latest/bitcoin/util/sighash/struct.SighashCache.html) which can then be used to compute sighashes for each input. This structure caches hashed data which is the same for every input -- something both segwit and taproot sighashes were designed for, but legacy sighashes were not -- which is why it has a less convenient API. But on the other hand, it should be much faster.

> **Comments:**
> **username_0**: That's helpful. Thanks!  
> **username_0**: For any future devs who stumble upon this, the other bit I was missing is you need to make sure the schnorr sig is properly encoded like so:  ``` let final_sig = schnorr::SchnorrSig { sig: signature, hash_ty: SchnorrSighashType::All }; ```  and then insert it into the witness like: ``` keypath_tx.input[0].witness.push(final_sig.serialize()); ```  

---

### Answer by username_1 on 2023-05-31. Score: 0

the answer above combined with the comments should be enough to have it working, I wanted also to share it all together in case it may be easier for others to follow through on it.

serialize and Prevouts imports are from `bitcoin::psbt`

```
fn sign_key_tx(
    secp: &Secp256k1<All>,
    tx_ref: &Transaction,
    prevouts: &Prevouts<TxOut>,
    key_pair_internal: &KeyPair,
    tap_info: &TaprootSpendInfo,
) -> Transaction {
    let mut tx = tx_ref.clone();
    let sighash_sig = SighashCache::new(&mut tx.clone())
        .taproot_key_spend_signature_hash(0, prevouts, SchnorrSighashType::AllPlusAnyoneCanPay) // or All
        .unwrap();

    let tweak_key_pair = key_pair_internal.tap_tweak(secp, tap_info.merkle_root());

    // then sig
    let msg = Message::from_slice(&sighash_sig).unwrap();

    let sig = secp.sign_schnorr(&msg, &tweak_key_pair.to_inner());

    //verify sig
    secp.verify_schnorr(&sig, &msg, &tweak_key_pair.to_inner().x_only_public_key().0)
        .unwrap();

    // then witness
    let schnorr_sig = SchnorrSig {
        sig,
        hash_ty: SchnorrSighashType::AllPlusAnyoneCanPay, // or All
    };

    tx.input[0].witness.push(schnorr_sig.serialize());

    tx
}

```

Hope it helps!


---

