.png)
We’ve recently leveled up support for Bitcoin on Turnkey to help customers who are building in this ecosystem. As outlined in our documentation, Turnkey has “always” supported Bitcoin because our signer enclave has supported bare Secp256k1 signing operations since day one (what we call “tier 1” or “curve-level” support1).
We now offer address derivation (“tier 2” or “address-level” support) for different types of addresses on Bitcoin. At first glance this might seem “easy”: aren’t Bitcoin addresses simply derived from Secp256k1 public keys? Turns out supporting Taproot required an entirely new signature scheme and support for cryptographic tweaks. We didn’t see this coming until a mysterious “invalid signature” error surfaced during our testing.
We’ll see why Bitcoin addresses aren’t “simply” derived from public keys: they’re data containers which contain script or public key digests, used to create locking scripts of different types. With this in mind we’ll detail the types of Bitcoin addresses supported on Turnkey: P2PKH, P2SH, P2WPKH, P2WSH, and P2TR. And we’ll take a close look at Taproot locking scripts and the tweaks they require.
Bitcoin addresses are data containers
Bitcoin is an unusual ledger. Instead of tracking balances for each account on the network, Bitcoin keeps track of individual Unspent Transaction Outputs (or “UTXOs”).
UTXOs are locked so that they can only be spent under certain conditions. This is what determines their “owner”. Bitcoin has a scripting language to define these conditions: Bitcoin Script. A UTXO’s “locking script” refers to the conditions under which it can be spent. Scripts are arbitrary, but because some spending conditions are so commonly used, script “patterns” have emerged and been standardized:
- P2PK (for “Pay-to-Public-Key”): requires a signature from a given public key
- P2PKH (for “Pay-to-Public-Key-Hash”): requires a public key matching a given hash, plus a valid signature from that key
- P2SH (for “Pay-to-Script-Hash”): requires another script which matches a given hash, and a successful execution of that script
- And more2
Note that we have not yet talked about addresses! How are Bitcoin addresses used then? In short, they’re a convenient way for a recipient to communicate enough information to a sender so that they can lock UTXOs correctly (which is to say: send them coins!). Taking the 3 examples from above:
- Constructing a P2PK locking script requires the recipient’s bare Secp256k1 public key. This doesn’t require a Bitcoin address! Your public key is your address.
- Constructing a P2PKH locking script requires the recipient’s public key hash. The hash function used by Bitcoin is
HASH1603. - Constructing a P2SH locking script requires a hash of the Bitcoin script. HASH160 is also used to hash scripts.
A recipient could hash their public key or script, and send the raw hash to the sender. That would be sufficient for the sender to lock UTXOs for the recipient. Bitcoin addresses standardize information exchange between recipients and senders, with a few bonuses:
- Bitcoin addresses are checksummed to make simple typos detectable.
- Bitcoin addresses are encoded using a friendly set of characters to avoid confusion between similar-looking glyphs. For example: zero (“0”) and capital o (“O”) look alike in most fonts, so they’re both excluded from the character set.
- Bitcoin addresses are prefixed to avoid confusion about the network and type of data they contain (otherwise, if I send you a hash with no other information, how can you tell if it’s supposed to be a script hash or a public key hash?)
Below is a sample P2PKH Bitcoin address:

Each square represents a single byte. The prefix is a single byte indicating the network and address type (see a list of prefixes here). The data is a public key hash, and the checksum is added as the last 4 bytes. You can play with this page to see how addresses get generated and encoded in more detail4.
Restating the important ideas:
- Bitcoin addresses are typed, checksummed, data containers. Addresses contain public key hashes or script hashes.
- Bitcoin addresses are used by recipients to communicate to senders how they’d like their Bitcoins locked.
- Senders use Bitcoin addresses to construct locking scripts and lock UTXOs: addresses dictate the type of locking script, as well as their contents.
Address types supported on Turnkey
Turnkey operates secure enclaves to generate key pairs and sign with them. What does it mean for Turnkey to provide “address-level” support for Bitcoin?
- Turnkey must generate Bitcoin addresses from Secp256k1 key pairs. This enables senders to lock UTXOs using the data contained in these addresses.
- Turnkey must be able to unlock the UTXOs by signing with these key pairs! Otherwise, users won’t be able to move the coins sent to them.
Let’s break down address types available on Turnkey5 and the data they hold. All of these addresses are meant to generate locking scripts which can be unlocked by a single key pair: the one that’s held in Turnkey’s secure enclaves and controlled by customers’ authenticators.
Taproot tweaks: a click deeper
Now that we’ve detailed every address type supported on Turnkey, let’s dive into P2TR in particular. What makes Taproot special? Tweaks!
A cryptographic tweak is a straightforward modification to a public key. For a point P and a scalar10 t, the “tweaked” point is Q=P+t∗G, where G is the group generator11 and t is the “tweak”.
In Taproot, tweaked points are used to “commit” to scripts. Taproot scripts are arranged in Merkle trees, which means committing to the root node of the script tree is sufficient (BIP341 explains this in detail if you’re curious). In order to commit to a Merkle root with hash m, the tweaked public key will be:
Q=P+hash(xbytes(P) || m)∗G
(where || is byte concatenation and xbytes(P) is the byte representation of p’s x coordinate)
Turnkey addresses do not use complex Taproot scripts, and thus do not need to commit to a Merkle root. When there is no Merkle root hash m the tweaked public key simplifies to:
Q=P+hash(xbytes(P))∗G
This is how Turnkey handles Taproot (P2TR) outputs. Remember from the previous section:
- Q (not P!) will be used inside of a witness script, and this witness script is hashed and referenced in P2TR locking scripts.
- To unlock P2TR UTXOs we need to present this witness script, the public key Q, and a signature made with Q.
Do you see the issue? If we produce a signature with our original (un-tweaked) key P, the signature is not valid and we cannot unlock P2TR UTXOs. This might come as a surprise and frankly, is a bit of an abstraction violation: locking scripts derived from a key P require a signature by a different key Q to be unlocked! The secret key for Q can be computed from P’s secret key with a trivial addition: q=p+t 12.
However: this addition isn’t possible if the key is stored on a device or environment which doesn’t support extracting and manipulating secret keys arbitrarily!
To implementers out there: proceed very carefully. If you derive P2TR addresses for your users without supporting cryptographic tweaks, they will lock coins irreversibly, because there is no way to unlock P2TR outputs with the original un-tweaked key.
To Turnkey users: we got your back! Turnkey’s signer enclave adds the right tweak to the secret key when signing for a Bitcoin P2TR address13. Everything will just work.
Conclusion
We’ve learned a lot while implementing Bitcoin address derivation at Turnkey, and we didn’t expect to! Classic rabbit-hole story. The most important takeaways are:
- Bitcoin addresses are not merely derived from public keys. Bitcoin addresses are typed, checksummed data containers. They can contain public key digests or script digests.
- Bitcoin addresses are meant to be used by senders to generate locking scripts. It’s the receiver’s responsibility to unlock them (and spend them as they see fit!)
- Turnkey supports all of the most common Bitcoin address types: P2PKH, P2SH, P2WPKH, P2WSH and P2TR addresses.
- Supporting P2TR addresses forced us to modify our core signer enclave: we implemented Schnorr signatures, and had to support tweaking secret keys to unlock P2TR outputs. We didn’t see this coming until a mysterious “invalid signature” error happened during our testnet trials.
- Offering address generation is a promise that cryptocurrency can be received at these addresses and also spent from them. If you’re an implementer: don’t forget to test both sides. Otherwise you put your users’ funds at risk of being stuck or lost.
Additional Resources
- LearnMeABitcoin is a fantastic resource for technical education on Bitcoin. The detailed pages about different types of locking scripts were a really handy reference while writing this blog post. For example: P2PKH locking scripts.
- Bitcoin Improvement Proposals (“BIPs”) are also very well written. They are technical and dense, but do a good job of explaining high-level motivations and outlining the main ideas in the first few sections. For example I’d highly recommend reading the “Design” section of BIP341 to understand Taproot. It’s only a few paragraphs long, well worth your time.
Acknowledgements
Thanks to Dr. Adam Everspaugh, Raheel Ahmed, Zane Kharitonov, Michael Avrukin, Sarah Lu, and Hannah Arnold for reading drafts of this post and providing valuable comments, feedback, and suggestions!
Related articles

Turnkey’s 3 phases of secure software development
Explore Turnkey’s software development process. See how each stage turns source code into a cryptographically verifiable artifact that is built, approved, and attested.

Create sub-orgs with resources and policies using Turnkey
Explore how app builders can use Turnkey to implement an account with multiple wallets and granular policy controls.

