Skip to content
Merged
8 changes: 4 additions & 4 deletions README.mediawiki
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
People wishing to submit a BIP should first describe their idea to the [https://groups.google.com/g/bitcoindev
bitcoindev@googlegroups.com] mailing list to gather feedback on viability and community interest before working on a
People wishing to submit a BIP should first describe their idea to the [https://groups.google.com/g/bitcoindev bitcoindev@googlegroups.com]
mailing list to gather feedback on viability and community interest before working on a
formal description. Please open a pull request to this repository only when substantial progress on the draft has been
made, preferably when the draft is nearing completion. Authors do <em>not</em> assign a number to their own proposal.
After a proposal meets the editorial criteria, a BIP Editor will assign a number to it and publish the proposal by
Expand Down Expand Up @@ -1198,13 +1198,13 @@ users (see also: [https://en.bitcoin.it/wiki/Economic_majority economic majority
| Steven Roose, Brandon Black
| Specification
| Draft
|-
|- style="background-color: #ffffcf"
| [[bip-0347.mediawiki|347]]
| Consensus (soft fork)
| OP_CAT in Tapscript
| Ethan Heilman, Armin Sabouri
| Specification
| Draft
| Complete
|-
| [[bip-0348.md|348]]
| Consensus (soft fork)
Expand Down
71 changes: 70 additions & 1 deletion bip-0347.mediawiki
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
Title: OP_CAT in Tapscript
Authors: Ethan Heilman <ethan.r.heilman@gmail.com>
Armin Sabouri <arminsdev@gmail.com>
Status: Draft
Status: Complete
Type: Specification
Assigned: 2023-12-11
License: BSD-3-Clause
Discussion: 2023-10-21: https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2023-October/022049.html [bitcoin-dev] Proposed BIP for OP_CAT
Version: 1.0.0
Requires: 340, 341, 342
</pre>

==Abstract==
Expand Down Expand Up @@ -42,6 +44,8 @@ OP_CAT aims to expand the toolbox of the tapscript developer with a simple, modu
* Non-equivocation contracts <ref>T. Ruffing, A. Kate, D. Schröder, "Liar, Liar, Coins on Fire: Penalizing Equivocation by Loss of Bitcoins", 2015, https://web.archive.org/web/20221023121048/https://publications.cispa.saarland/565/1/penalizing.pdf</ref> in tapscript provide a mechanism to punish equivocation/double spending in Bitcoin payment channels. OP_CAT enables this by enforcing rules on the spending transaction's nonce. The capability is a useful building block for payment channels and other Bitcoin protocols.
* Vaults <ref>M. Moser, I. Eyal, and E. G. Sirer, Bitcoin Covenants, https://web.archive.org/web/20220203124718/https://fc16.ifca.ai/bitcoin/papers/MES16.pdf</ref> which are a specialized covenant that allows a user to block a malicious party who has compromised the user's secret key from stealing the funds in that output. As shown in <ref>A. Poelstra, "CAT and Schnorr Tricks II", 2021, https://www.wpsoftware.net/andrew/blog/cat-and-schnorr-tricks-ii.html</ref> OP_CAT is sufficient to build vaults in Bitcoin.
* Replicating CheckSigFromStack <ref>A. Poelstra, "CAT and Schnorr Tricks I", 2021, https://www.wpsoftware.net/andrew/blog/cat-and-schnorr-tricks-i.html</ref> which would allow the creation of simple covenants and other advanced contracts without having to presign spending transactions, possibly reducing complexity and the amount of data that needs to be stored. Originally shown to work with Schnorr signatures, this result has been extended to ECDSA signatures <ref>R. Linus, "Covenants with CAT and ECDSA", 2023, https://gist.github.com/RobinLinus/9a69f5552be94d13170ec79bf34d5e85#file-covenants_cat_ecdsa-md</ref>.
* OP_CAT would allow tapscript to perform arbitrary computation on stack elements larger than 32-bits such as signatures and public keys. While Collider Script <ref>E. Heilman, V. I. Kolobov, A. M. Levy, A. Poelstra, "ColliderScript: Covenants in Bitcoin via 160-bit hash collisions", 2024, https://eprint.iacr.org/2024/1802</ref> showed that even without OP_CAT tapscript can perform arbitrary computation on stack elements larger than 32-bits, this approach is extremely computationally expensive. In practice, today's tapscript can only perform arbitrary computation on 32-bit stack elements. OP_CAT was not designed with this usecase in mind and OP_CAT an inefficient way to perform this task.
* BitVM<ref>R. Linus, L. Aumayr, A. Zamyatin, A. Pelosi, Z. Avarikioti, M. Maffei "BitVM2: Bridging Bitcoin to Second Layers", 2025, https://bitvm.org/bitvm_bridge.pdf</ref> improvements. OP_CAT would enable BitVM2 to eliminating the trusted setup from its proof system allow BitVM2 to reduce the size of transactions it uses.

OP_CAT was available in early versions of Bitcoin.
In 2010, a single commit disabled OP_CAT, along with another 15 opcodes.
Expand Down Expand Up @@ -102,10 +106,75 @@ break;

An alternative implementation of OP_CAT can be found in Elements <ref>Roose S., Elements Project, "Re-enable several disabled opcodes", 2019, https://github.com/ElementsProject/elements/commit/13e1103abe3e328c5a4e2039b51a546f8be6c60a#diff-a0337ffd7259e8c7c9a7786d6dbd420c80abfa1afdb34ebae3261109d9ae3c19R740-R759</ref>.


==Test Vectors==

The following test vectors use Bitcoin-core JSON script-test format.

<pre>
[
[
"78a11a1260c1101260",
"78a11a1260",
"c1101260",
"#SCRIPT# CAT EQUAL",
"#CONTROLBLOCK#",
0.00000001
],
"",
"0x51 0x20 #TAPROOTOUTPUT#",
"P2SH,WITNESS,TAPROOT,OP_CAT",
"OK",
"TAPSCRIPT CATs 78a11a1260 and c1101260 together and checks it is EQUAL to stack element 78a11a1260c1101260"
],
[
[
"51",
"bbbb",
"01",
"#SCRIPT# IF CAT ELSE DROP ENDIF",
"#CONTROLBLOCK#",
0.00000001
],
"",
"0x51 0x20 #TAPROOTOUTPUT#",
"P2SH,WITNESS,TAPROOT,OP_CAT",
"OK",
"TAPSCRIPT Tests CAT inside of an IF ELSE conditional (true IF)"
],
[
[
"",
"09ca7009ca7009ca7009ca7009ca70",
"#SCRIPT# CAT",
"#CONTROLBLOCK#",
0.00000001
],
"",
"0x51 0x20 #TAPROOTOUTPUT#",
"P2SH,WITNESS,TAPROOT,OP_CAT",
"OK",
"TAPSCRIPT (['', 09ca7009ca7009ca7009ca7009ca70], CAT) Tests CAT succeeds when one of the two values to concatenate is of size zero"
],
</pre>

A full test suite with additional vectors can be found at the Bitcoin [https://github.com/bitcoin/bitcoin/pull/29247 OP_CAT PR].

==References==

<references/>

==Changelog==

* __1.0.0__ (2026-03-01) - Marked as complete.
* __0.3.1__ (2026-01-23) - Made compliant with BIP 003, use cases added.
* __0.3.0__ (2024-05-06) - Merged to BIP repo
* __0.2.0__ (2024-04-24) - Assigned BIP number
* __0.1.0__ (2023-12-11) - Initial draft posted


May 6, 2024

==Acknowledgements==

We wish to acknowledge Dan Gould for encouraging and helping review this effort. We also want to thank Madars Virza, Jeremy Rubin, Andrew Poelstra, Bob Summerwill,
Expand Down
19 changes: 16 additions & 3 deletions bip-0352.mediawiki
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,8 @@ After the inputs have been selected, the sender can create one or more outputs f
* Let ''input_hash = hash<sub>BIP0352/Inputs</sub>(outpoint<sub>L</sub> || A)'', where ''outpoint<sub>L</sub>'' is the smallest ''outpoint'' lexicographically used in the transaction<ref name="why_smallest_outpoint"></ref> and ''A = a·G''
** If ''input_hash'' is not a valid scalar, i.e., if ''input_hash = 0'' or ''input_hash'' is larger or equal to the secp256k1 group order, fail
* Group receiver silent payment addresses by ''B<sub>scan</sub>'' (e.g. each group consists of one ''B<sub>scan</sub>'' and one or more ''B<sub>m</sub>'')
* If any of the groups exceed the limit of ''K<sub>max</sub>'' (=2323) silent payment addresses, fail.<ref name="why_limit_k">'''Why is the size of groups (i.e. silent payment addresses sharing the same scan public key) limited by ''K<sub>max</sub>''?''' An adversary could construct a block filled with a single transaction consisting of N=23255 outputs (that's the theoretical maximum under current consensus rules, w.r.t. the block weight limit) that all target the same entity, consisting of one large group. Without a limit on the group size, scanning such a block with the algorithm described in this document would have a complexity of ''O(N<sup>2</sup>)'' for that entity, taking several minutes on modern systems. By capping the group size at ''K<sub>max</sub>'', we reduce the inner loop iterations to ''K<sub>max</sub>'', thereby decreasing the worst-case block scanning complexity to ''O(N·K<sub>max</sub>)''. This cuts down the scanning cost to the order of tens of seconds. The chosen value of ''K<sub>max</sub>'' = 2323 represents the maximum number of P2TR outputs that can fit into a 100kvB transaction, meaning a transaction that adheres to the current standardness rules is guaranteed to be within the limit. This ensures flexibility and also mitigates potential fingerprinting issues.</ref>

* For each group:
** Let ''ecdh_shared_secret = input_hash·a·B<sub>scan</sub>''
** Let ''k = 0''
Expand Down Expand Up @@ -340,6 +342,7 @@ If each of the checks in ''[[#scanning-silent-payment-eligible-transactions|Scan
* Check for outputs:
** Let ''outputs_to_check'' be the taproot output keys from all taproot outputs in the transaction (spent and unspent).
** Starting with ''k = 0'':
*** If ''k == K<sub>max</sub>'' (=2323), stop scanning.<ref name="why_limit_k"></ref>
*** Let ''t<sub>k</sub> = hash<sub>BIP0352/SharedSecret</sub>(ser<sub>P</sub>(ecdh_shared_secret) || ser<sub>32</sub>(k))''
**** If ''t<sub>k</sub>'' is not a valid scalar, i.e., if ''t<sub>k</sub> = 0'' or ''t<sub>k</sub>'' is larger or equal to the secp256k1 group order, fail
*** Compute ''P<sub>k</sub> = B<sub>spend</sub> + t<sub>k</sub>·G''
Expand Down Expand Up @@ -390,7 +393,15 @@ A [[bip-0352/send_and_receive_test_vectors.json|collection of test vectors in JS
{
"given": {
"vin": [<array of vin objects with an added field for the private key. These objects are structured to match the `vin` output field from `getrawtransaction verbosity=2`>],
"recipients": [<array of strings, where each string is a bech32m encoding representing a silent payment address>]
"recipients": [<array of recipient objects, consisting of the address and its contained scan/spend public keys each>
{
"address": <bech32m encoding representing a silent payment address>,
"scan_pub_key": <hex encoded scan public key>,
"spend_pub_key": <hex encoded spend public key>,
"count": <optional integer for specifying the same recipient repeatedly (1 by default)>
},
...
]
},
"expected": {
"outputs": [<array of strings, where each string is a hex encoding of 32-byte X-only public key; contains all possible output sets, test must match a subset of size `n_outputs`>],
Expand All @@ -411,15 +422,15 @@ A [[bip-0352/send_and_receive_test_vectors.json|collection of test vectors in JS
},
"expected": {
"addresses": [<array of bech32m strings, one for the silent payment address and each labeled address (if used)>],
"outputs": [<array of outputs with tweak and signature; contains all possible output sets, tester must match a subset of size `n_outputs`>
"outputs": [<optional array of outputs with tweak and signature, tester must match this set (alternatively, "n_outputs" can be specified)>
{
"priv_key_tweak": <hex encoded private key tweak data>,
"pub_key": <hex encoded X-only public key>,
"signature": <hex encoded signature for the output (produced with spend_priv_key + priv_key_tweak)>
},
...
],
"n_outputs": <integer for the exact number of expected outputs>
"n_outputs": <optional integer for the number of expected found outputs (alternative to "outputs")>
}
}

Expand Down Expand Up @@ -489,6 +500,8 @@ The <code>MAJOR</code> version is incremented if changes to the BIP are introduc
The <code>MINOR</code> version is incremented whenever the inputs or the output of an algorithm changes in a backward-compatible way or new backward-compatible functionality is added.
The <code>PATCH</code> version is incremented for other changes that are noteworthy (bug fixes, test vectors, important clarifications, etc.).

* '''1.1.0''' (2026-03-02):
** Introduce per-group recipient limit ''K<sub>max</sub>'' to mitigate quadratic scanning behavior for adversarial transactions.<ref name="why_limit_k"></ref>
* '''1.0.2''' (2025-07-25):
** Clarify how to handle the improbable corner case where the output of SHA256 is equal to 0 or greater than or equal to the secp256k1 curve order.
* '''1.0.1''' (2024-06-22):
Expand Down
26 changes: 22 additions & 4 deletions bip-0352/reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
)


K_max = 2323 # per-group recipient limit


def get_pubkey_from_input(vin: VinInfo) -> ECPubKey:
if is_p2pkh(vin.prevout):
# skip the first 3 op_codes and grab the 20 byte hash
Expand Down Expand Up @@ -144,6 +147,10 @@ def create_outputs(input_priv_keys: List[Tuple[ECKey, bool]], outpoints: List[CO
else:
silent_payment_groups[B_scan] = [B_m]

# Fail if per-group recipient limit (K_max) is exceeded
if any([len(group) > K_max for group in silent_payment_groups.values()]):
return []

outputs = []
for B_scan, B_m_values in silent_payment_groups.items():
ecdh_shared_secret = input_hash * a_sum * B_scan
Expand Down Expand Up @@ -175,6 +182,8 @@ def scanning(b_scan: ECKey, B_spend: ECPubKey, A_sum: ECPubKey, input_hash: byte
k = 0
wallet = []
while True:
if k == K_max: # Don't look further than the per-group recipient limit (K_max)
break
t_k = TaggedHash("BIP0352/SharedSecret", ecdh_shared_secret.get_bytes(False) + ser_uint32(k))
P_k = B_spend + t_k * G
for output in outputs_to_check:
Expand Down Expand Up @@ -259,7 +268,11 @@ def scanning(b_scan: ECKey, B_spend: ECPubKey, A_sum: ECPubKey, input_hash: byte
sending_outputs = []
if (len(input_pub_keys) > 0):
outpoints = [vin.outpoint for vin in vins]
sending_outputs = create_outputs(input_priv_keys, outpoints, given["recipients"], expected=expected, hrp="sp")
recipients = [] # expand given recipient entries to full list
for recipient_entry in given["recipients"]:
count = recipient_entry.get("count", 1)
recipients.extend([recipient_entry] * count)
sending_outputs = create_outputs(input_priv_keys, outpoints, recipients, expected=expected, hrp="sp")

# Note: order doesn't matter for creating/finding the outputs. However, different orderings of the recipient addresses
# will produce different generated outputs if sending to multiple silent payment addresses belonging to the
Expand Down Expand Up @@ -354,9 +367,14 @@ def scanning(b_scan: ECKey, B_spend: ECPubKey, A_sum: ECPubKey, input_hash: byte
# same sender but with different labels. Because of this, expected["outputs"] contains all possible valid output sets,
# based on all possible permutations of recipient address orderings. Must match exactly one of the possible found output
# sets in expected["outputs"]
generated_set = {frozenset(d.items()) for d in add_to_wallet}
expected_set = {frozenset(d.items()) for d in expected["outputs"]}
assert generated_set == expected_set, "Receive test failed"
if "outputs" in expected: # detailed check against expected outputs
generated_set = {frozenset(d.items()) for d in add_to_wallet}
expected_set = {frozenset(d.items()) for d in expected["outputs"]}
assert generated_set == expected_set, "Receive test failed"
elif "n_outputs" in expected: # only check the number of found outputs
assert len(add_to_wallet) == expected["n_outputs"], "Receive test failed"
else:
assert False, "either 'outputs' or 'n_outputs' must be specified in 'expected' field of receiving test vector"


print("All tests passed")
Loading