P2SPKH is a native SegWit output type using witness version 3. It encodes HASH256 of a BIP-340 x-only public key as the witness program. Spending reveals the public key and provides a Schnorr signature under BIP-341 sighash rules. It extends the design space by separating key commitment (hashed) from key spend (revealed at spend time), using modern Schnorr signatures without the complexity of MAST or tapscript.
Taproot (BIP-341) bundles three concerns into a single output type: key-path spends, script-path spends via MAST, and the upgrade mechanism itself. For simple monetary transfers, the MAST machinery is dead weight. P2SPKH provides a minimal Schnorr-based single-key output -- the Schnorr equivalent of P2WPKH - at a separate witness version. It uses HASH256 (double SHA256) for familiarity with Limenka's existing hash convention, and BIP-341 sighash for compatibility with existing signing infrastructure.
Separating this into its own witness version (3) keeps the concerns orthogonal: v0 for ECDSA, v1 for full taproot, v3 for bare Schnorr key hashes. V2 remains available for other uses.
A 34-byte witness program:
OP_3 <32-byte HASH256(x-only-pubkey)>
Where HASH256(x) = SHA256(SHA256(x)).
Exactly 2 stack items:
<64-byte BIP-340 Schnorr signature> <32-byte x-only public key>
VerifySchnorrSignature(SIG, PUBKEY, sighash) must pass, where sighashis computed per BIP-341 (tagged hash over prevouts, sequences, amounts, etc., using SigVersion::TAPROOT).
MAST, v2 is unallocated and may serve a different purpose. v3 explicitly signals "bare Schnorr key hash" with no MAST upgrade path -- it is intentionally terminal.
convention (transactions, blocks, merkle trees), avoiding the surprise of a single-SHA256 witness program. Consistency over micro-optimization.
invent a new sighash algorithm for what is functionally the same operation (Schnorr signature over a transaction commitment).
output. Adding unused spending paths increases on-chain footprint and verification cost for no benefit to the simple transfer use case. The upgrade path is to use a different witness version.
version byte disambiguates v3-SPKH from v1-TR. Same address format, different consensus rules -- this is already the design of segwit versioning.
Bech32m with witness version 3. Mainnet addresses begin with bc1p.
spk(<x-only-pubkey-expression>)
spk() accepts any Miniscript-compatible key expression that resolves to an
x-only public key, e.g. spk(tpub...), spk(02feed...), or
spk(xpub.../87h/0h/0h/0/0).
P2SPKH activates via UASF (User Activated Soft Fork) using versionbits
deployment DEPLOYMENT_P2SPKH (bit 2).
| Chain | nStartTime | nTimeout | Threshold | Period |
|---|---|---|---|---|
| mainnet | ALWAYS_ACTIVE | NO_TIMEOUT | 1815 (90%) | 2016 |
| testnet4 | ALWAYS_ACTIVE | NO_TIMEOUT | 1512 (75%) | 2016 |
| testnet3 | ALWAYS_ACTIVE | NO_TIMEOUT | 1512 (75%) | 2016 |
| signet | ALWAYS_ACTIVE | NO_TIMEOUT | 1815 (90%) | 2016 |
| regtest | 0 (genesis) | NO_TIMEOUT | 108 (75%) | 144 |
ALWAYS_ACTIVE (-1) means the deployment is active from genesis on upgraded
nodes. This is a UASF design: when a node runs this software, P2SPKH rules
are enforced on all blocks. Historical blocks are unaffected because they
contain no P2SPKH transactions.
SCRIPT_VERIFY_P2SPKH becomes active at GetBlockScriptFlags() via DeploymentActiveAt(..., DEPLOYMENT_P2SPKH).
segwit versioning).
SCRIPT_VERIFY_P2SPKH is also included in MANDATORY_SCRIPT_VERIFY_FLAGS
(superset of STANDARD_SCRIPT_VERIFY_FLAGS) so the mempool accepts P2SPKH
transactions and wallet operations correctly validate P2SPKH signatures.
Without SCRIPT_VERIFY_P2SPKH, witness version 3 + 32-byte programs return
success (like any undefined witness version). This preserves the standard
segwit soft-fork upgrade path: old nodes accept blocks containing P2SPKH
spends, new nodes enforce the rules. No chain split occurs.
A 24-file change across the codebase.
| # | File | Change |
|---|---|---|
| 1 | src/script/solver.h | Add WITNESS_V3_SPKHASH to TxoutType enum |
| 2 | src/script/solver.cpp | Add string name in GetTxnOutputType(); detection in Solver() for v3 + 32-byte program |
| 3 | src/script/interpreter.h | WITNESS_V3_SPKHASH_SIZE = 32 constant; SCRIPT_VERIFY_P2SPKH flag |
| 4 | src/addresstype.h | WitnessV3SpkHash struct wrapping uint256 (with XOnlyPubKey constructor computing HASH256); add to CTxDestination variant |
| 5 | src/addresstype.cpp | ExtractDestination(), CScriptVisitor (OP_3 <hash>), ValidDestinationVisitor |
| 6 | src/key_io.cpp | DestinationEncoder for v3 (Bech32m); DecodeDestination v3+32 bytes -> WitnessV3SpkHash |
| 7 | src/outputtype.h | Add OutputType::P2SPKH to enum and OUTPUT_TYPES array |
| 8 | src/outputtype.cpp | OutputTypeFromDestination: WitnessV3SpkHash -> P2SPKH; ParseOutputType("p2spkh") |
| # | File | Change |
|---|---|---|
| 9 | src/script/interpreter.cpp | VerifyWitnessProgram(): v3+32-byte branch -- HASH256 pubkey check + CheckSchnorrSignature; ScriptFlagNamesToEnum(): add P2SPKH |
| 10 | src/consensus/params.h | Add DEPLOYMENT_P2SPKH to DeploymentPos enum |
| 11 | src/deploymentinfo.cpp | Add "p2spkh" deployment info |
| 12 | src/kernel/chainparams.cpp | Deployment params for mainnet, testnet4, testnet3, signet, regtest |
| 13 | src/validation.cpp | GetBlockScriptFlags(): add SCRIPT_VERIFY_P2SPKH gated by DEPLOYMENT_P2SPKH |
| # | File | Change |
|---|---|---|
| 14 | src/script/sign.cpp | SignStep() case; ProduceSignature() build witness stack [sig, pubkey] |
| # | File | Change |
|---|---|---|
| 15 | src/script/signingprovider.h | Add GetSpkPubKey() virtual method + spk_keys map |
| 16 | src/script/signingprovider.cpp | Implement FlatSigningProvider::GetSpkPubKey() |
| # | File | Change |
|---|---|---|
| 17 | src/policy/policy.cpp | IsWitnessStandard(): v3 rules (exactly 2 items, sig = 64 bytes, pubkey = 32 bytes) |
| 18 | src/policy/policy.h | Add SCRIPT_VERIFY_P2SPKH to MANDATORY_SCRIPT_VERIFY_FLAGS |
| # | File | Change |
|---|---|---|
| 19 | src/wallet/spend.cpp | GetOutputType(): WITNESS_V3_SPKHASH -> P2SPKH |
| 20 | src/wallet/wallet.cpp | TransactionChangeType(): detect WitnessV3SpkHash recipients, return P2SPKH change |
| 21 | src/wallet/scriptpubkeyman.cpp | Add WITNESS_V3_SPKHASH to legacy wallet handling |
| 22 | src/wallet/rpc/addresses.cpp | DescribeAddressVisitor for WitnessV3SpkHash |
| 23 | src/wallet/walletutil.cpp | GenerateWalletDescriptor: P2SPKH -> spk(...) with 87h derivation |
| # | File | Change |
|---|---|---|
| 24 | src/script/descriptor.cpp | SPKDescriptor class: output type P2SPKH, script size 34, max satisfaction weight 98; ParseScriptContext::P2SPK; spk() function parser; InferScript() recognition |
| # | File | Change |
|---|---|---|
| 25 | src/rpc/rawtransaction.cpp | decodescript RPC: WITNESS_V3_SPKHASH in switch cases |
| 26 | src/rpc/util.cpp | DescribeAddressVisitor for WitnessV3SpkHash (JSON output) |
| # | File | Change |
|---|---|---|
| 27 | src/test/p2spkh_tests.cpp | 13 unit tests: Solver detection, address roundtrip, HASH256, signing roundtrip, wrong hash, wrong stack size, wrong pubkey, invalid sig, descriptor parsing, forward-compat without flag |
| 28 | src/test/transaction_tests.cpp | Updated CTxDestination variant size assert (9 -> 10); skip v3+32 in spendswitnessprog loop |
| 29 | src/test/fuzz/util.cpp | Add WitnessV3SpkHash to ConsumeTxDestination |
| 30 | src/wallet/test/wallet_tests.cpp | BasicOutputTypesTest now includes P2SPKH in OUTPUT_TYPES |
m/87h/0h/0h/0/*
Using purpose 87h following the BIP43/BIP44 convention. Extended key
expressions in spk() descriptors derive x-only public keys from this path.
test/functional/feature_p2spkh.py -- end-to-end functional test: create spk() descriptor wallet, generate address, send, spend.
P2SPKH.