1
0
mirror of https://github.com/bitcoin/bips.git synced 2026-06-01 17:15:27 +00:00

BIP449: OP_TWEAKADD (#1944)

* BIP: OP_TWEAKADD

* BIP TweakAdd: note on commutativity of tweaking and add test cases

* BIP TweakAdd: Invert Argument Order

* BIP Tweakadd: fix typo & add note on even-y tweaking

* BIP TweakAdd -- add mailing list discussion

* BIP TweakAdd: Add Alpen and MATT mentions

* BIP TweakAdd Formatting Edits

* BIP TWEAKADD remove conventions section

* BIP TWEAKADD formatting fix

* BIP TWEAKADD Move Vectors to end

* BIP TweakAdd: Condense compatibility section

* [BIP-0449] Updates post assignemnt

* [BIP-0449] Normalize Metadata

* Update bip-0449.md Link Text to point to OP of ML Thread
This commit is contained in:
Jeremy Rubin
2026-05-23 10:03:53 -04:00
committed by GitHub
parent 2d12e67b00
commit 76d434e5fc
4 changed files with 646 additions and 0 deletions

View File

@@ -1486,6 +1486,13 @@ users (see also: [https://en.bitcoin.it/wiki/Economic_majority economic majority
| Specification | Specification
| Draft | Draft
|- |-
| [[bip-0449.md|449]]
| Consensus (soft fork)
| OP_TWEAKADD - x-only key tweak addition
| Jeremy Rubin
| Specification
| Draft
|-
| [[bip-0450.mediawiki|450]] | [[bip-0450.mediawiki|450]]
| Applications | Applications
| Formosa—Seed encoding by themed mnemonic stories | Formosa—Seed encoding by themed mnemonic stories

458
bip-0449.md Normal file
View File

@@ -0,0 +1,458 @@
```
BIP: 449
Layer: Consensus (soft fork)
Title: OP_TWEAKADD - x-only key tweak addition
Authors: Jeremy Rubin <jeremy@char.network>
Status: Draft
Type: Specification
Assigned: 2026-03-05
License: BSD-3-Clause
```
## Abstract
This proposal defines a new tapscript opcode, `OP_TWEAKADD`, that takes an x-only public key and a 32-byte integer `t` on the stack and pushes the x-only public key corresponding to `P + t*G`, where `P` is the lifted point for the input x-coordinate and `G` is the secp256k1 generator. The operation mirrors the Taproot tweak used by BIP340 signers and enables simple, verifiable key modifications inside script without revealing private keys or relying on hash locks.
## Motivation
Bitcoin already leverages x-only key tweaking when deriving Taproot output keys, but tapscript currently has no way to perform the same group operation inside script and continue using the derived key. Without such a primitive, protocols that depend on relations between keys must instead reveal preimages, commit to multiple full public keys, or perform additional signature checks at each step.
Making key tweaking available in script enables several concrete constructions. These include tweak-reveal scripts built from `OP_TWEAKADD` together with proposed opcodes such as BIP 348 `OP_CHECKSIGFROMSTACK` and BIP 349 `OP_INTERNALKEY`, proof-of-signing-order constructions in which one signature commits to another signer acting later, delegation schemes in which one participant adjusts another participant's signing key, and target-tweak or key-reveal contracts that prove knowledge of a discrete logarithm by matching one tweaked key against another.
In these constructions, the script commonly commits to the base key while the witness provides the scalar used to derive the spending key. `OP_TWEAKADD` therefore exposes only the elliptic-curve addition primitive and adopts an operand order that fits that pattern directly, while leaving hashing, Taproot-specific tweak construction, and higher-level protocol logic to script composition or future opcodes.
## Specification
### Applicability and opcode number
- Context: Only valid in tapscript (witness version 1, leaf version 0xc0). In legacy or segwit v0 script, `OP_TWEAKADD` is disabled and causes script failure.
- Opcode: OP_TWEAKADD (0xBE, or TBD, any unused OP_SUCCESSx, preferably one which might never be restored in the future).
### Stack semantics
```
... [tweak32] [pubkey32] OP_TWEAKADD -> ... [pubkey32_out]
```
Input:
- `pubkey32`: 32-byte x-only public key (big-endian x coordinate).
- `tweak32`: 32-byte big-endian unsigned integer encoding scalar `t`.
Output:
- `pubkey32_out`: 32-byte x-only public key for `Q = P + t*G`.
#### Operation and failure conditions
Let `n` be the secp256k1 curve order.
1. If `tweak32` or `pubkey32` are not 32 bytes, fail.
2. Parse `tweak32` as big-endian integer `t`. If `t >= n`, fail.
3. Interpret `pubkey32` as an x-coordinate and attempt the BIP340 even-Y lift:
- If no curve point exists with that x, fail.
- Otherwise, obtain `P` with even Y.
4. Compute `Q = P + t*G`. If `Q` is the point at infinity, fail.
5. Push `x(Q)` as a 32-byte big-endian value.
Note: `t = 0` may fail if `pubkey32` is not valid.
#### Script evaluation rules
1. If less than 2 stack elements, fail.
2. Pop `pubkey32` and then `tweak32`.
3. If either length is not 32, fail.
4. Run `tweak_add` as above.
5. Push the 32-byte x-only result.
### Resource usage
- Performs one fixed-base EC scalar multiplication (`t*G`) plus one EC point addition (`P + t*G`).
- Costs should be aligned with `OP_CHECKSIG` operation, budget is decremented by 50.
## Rationale
### X-only encoding and failure conditions
Using the BIP340 even-Y lift keeps the opcode aligned with the x-only key model already used by Taproot. Rejecting invalid lifts and the point at infinity ensures that `OP_TWEAKADD` never produces a value that cannot be interpreted as a usable x-only public key.
The opcode also rejects scalars greater than or equal to the curve order instead of reducing them modulo the curve order. Modular reduction would permit distinct 32-byte inputs to encode the same effective tweak, introducing an unnecessary source of malleability. In constructions where the tweak is derived from witness data, for example `<data> OP_SHA256 <P> OP_TWEAKADD`, this would allow different witness elements to produce the same derived key while changing transaction weight and fee characteristics. More generally, protocols should not be able to infer equality of tweak encodings from equality of derived keys when two distinct tweak values could be congruent modulo the curve order. Rejecting non-canonical scalars avoids these ambiguities. Applications that derive scalars from 32-byte hashes already need to handle the negligible-probability case in which the resulting value is not a valid secp256k1 scalar.
### Push semantics
`OP_TWEAKADD` pushes a derived key instead of directly verifying a signature. This keeps the opcode narrowly scoped and reusable. The derived key can feed into existing or proposed signature opcodes, equality checks, or other script logic, rather than forcing all uses through a single verification flow.
### Raw tweak input
The opcode accepts a raw 32-byte scalar instead of internally hashing the input or computing a Taproot-specific tweak. This keeps the consensus rule minimal. Constructions that require a hashed tweak can apply `OP_SHA256` before `OP_TWEAKADD`. Constructions that require a BIP341 tweak to be computed entirely on-chain would require additional general-purpose opcodes that expose tapleaf hashes or taptree commitments to script.
### Argument order
The chosen stack order puts the public key on top of the stack when `OP_TWEAKADD` executes. In the common pattern described above, the script pushes a fixed key and the witness supplies the scalar. This permits tweak-reveal, delegation, and proof-of-signing-order constructions to invoke `OP_TWEAKADD` without first inserting an `OP_SWAP`.
## Compatibility
This is a soft-fork change which is tapscript-only. Un-upgraded nodes will continue
to treat unknown tapscript opcode as OP_SUCCESSx.
A future upgrade, such as `OP_CAT` or an opcode that exposes tapleaf hashes or taptree commitments to script, could prepare the 32-byte scalar required to derive BIP341-compatible Taproot output keys entirely on-chain.
## Deployment
TBD
## Security considerations
- Scalar range check prevents overflow, ambiguity, and alternate encodings of the same effective tweak.
- Infinity guard ensures valid outputs only.
- Scripts must control `t` derivation securely, which in many applications is trivial.
- No new witness malleability is introduced by alternate tweak encodings because tweaks must be exactly 32 bytes, must encode scalars less than the curve order, and x-only keys admit a unique even-Y lift.
## Reference semantics (pseudocode)
```python
SECP256K1_ORDER = n # 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
def tweak_add(pubkey32: bytes, tweak32: bytes) -> bytes:
if len(pubkey32) != 32 or len(tweak32) != 32:
raise ValueError
t = int.from_bytes(tweak32, 'big')
if t >= SECP256K1_ORDER:
raise ValueError
P = lift_x_even_y(pubkey32) # BIP340 lift of x to the point with even Y
if P is None:
raise ValueError
Q = point_add(P, scalar_mul_G(t)) # Q = P + t*G
if Q is None: # point at infinity
raise ValueError
return Q.x.to_bytes(32, 'big')
```
## Reference implementation notes
* Reuse BIP340 lift/encode helpers from Taproot verification.
* Implement `t*G` via fixed-base multiplication, then combine with `P` using point addition.
* Serialize the result as 32-byte x-only.
* Charge EC op budget as 50, like `OP_CHECKSIGADD`.
## Protocol Design Note: Scalar Adjustment
When working with x-only keys, it is important to remember that each 32-byte value encodes the equivalence class `{P, P}`.
BIP340 defines the canonical lift as **the point with even Y**. As a result:
- If an off-chain protocol describes an x-only key as "the point `s·G`," then in consensus terms the actual key is `adj(s)·G`, where:
```
adj(s) = s if y(s·G) is even
= n s if y(s·G) is odd
```
- Consequently, `OP_TWEAKADD(x(s·G), t)` always computes:
```
result = x(adj(s)·G + t·G)
```
not simply `x(s·G + t·G)`.
This distinction is invisible when signing or verifying against BIP340 keys, because both `s` and `n s` yield the same x-only key.
But it matters when a protocol tries to relate "a tweak applied at the base" (`x(G), t = s`) to "a tweak applied at a derived key" (`x(s·G), t = 1`). In general those will differ unless the original point already had even Y.
- If you want consistent algebraic relations across different ways of composing tweaks, **normalize scalars off-chain** before pushing them into script.
- That is: replace every candidate tweak `s` with `adj(s)`, so that `adj(s)·G` has even Y.
- A simple library function can perform this parity check and adjustment using libsecp256k1 without a consensus modification or opcode.
If the tweak is derived from inflexible state, such as a transaction hash or a signature, it may be infeasible to depend on commutativity of tweaking.
Protocols such as LN-Symmetry may simply grind the tx if even-y of tweak is required.
## Protocol Design Note: Algebraic Limits
`OP_TWEAKADD` implements only:
```text
x(lift_x(X) + t*G)
```
If `X = x(G)`, then because the secp256k1 generator has even Y, this becomes:
```text
x(G + t*G) = x((t + 1)*G)
```
This may suggest that repeated applications of `OP_TWEAKADD` can emulate an x-only scalar-multiplication opcode. In general this is not correct. After each application, the 32-byte x-only output is interpreted again using the BIP340 even-Y lift before the next tweak is added. Consequently:
```text
OP_TWEAKADD(OP_TWEAKADD(x(G), t), n - 1)
= x(adj(t + 1)*G - G)
```
which equals `x(t*G)` only when `(t + 1)*G` already has even Y.
More generally, `OP_TWEAKADD` does not provide a generic `OP_ECMUL_XONLY` or `OP_ECADD` for hidden points. The opcode only adds multiples of the fixed generator `G`; it does not multiply an arbitrary point by a scalar, and x-only encodings do not retain the sign information needed to compose arbitrary point additions without additional data. As a result, `OP_TWEAKADD` is useful for expressing relations to a known base key or to generator-derived tweaks, but it does not subsume generic elliptic-curve arithmetic or signature verification primitives. For example, checking a Schnorr relation `s*G = R + e*P` requires scalar multiplication of an arbitrary public key `P` by `e` and point addition with `R`, neither of which can be implemented from `OP_TWEAKADD` alone without revealing additional information outside the x-only model.
## Illustrative Constructions
The following sketches are non-normative examples. Some of them compose `OP_TWEAKADD` with other proposed opcodes, notably BIP 348 `OP_CHECKSIGFROMSTACK` and BIP 349 `OP_INTERNALKEY`.
### Tweak-reveal scripts
A tapscript may commit to a base key and accept a witness-provided scalar that must be revealed to derive the signing key. One such construction composes `OP_TWEAKADD` with BIP 348 `OP_CHECKSIGFROMSTACK` and BIP 349 `OP_INTERNALKEY`. When the construction requires the scalar to be hashed before use, the script can express that explicitly:
```text
witness: <sig> <msg> <tweak>
script: OP_SHA256 OP_INTERNALKEY OP_TWEAKADD OP_CHECKSIGFROMSTACK
```
Alternatively, with `OP_CHECKSIG`:
```text
witness: <sig> <tweak>
script: OP_SHA256 OP_INTERNALKEY OP_TWEAKADD OP_CHECKSIG
```
Applying `OP_SHA256` to the witness-provided scalar in these constructions prevents key cancellation.
### Proof of signing order and transaction refinement
`OP_TWEAKADD` can bind one signer's authorization to another signer's signature by tweaking key `A` with `SHA256(sig_B)`. The second signature then proves that `A` signed only after seeing `B`'s complete signature, so `B` can fix transaction details first and `A` can refine them later:
```text
witness: <sig_A> <sig_B>
script: DUP TOALT <B> CHECKSIGVERIFY FROMALT SHA256 <A> OP_TWEAKADD OP_CHECKSIG
```
In this construction, `A` is bound to `B`'s complete signature. `B` may use any sighash flag combination, so `A` refines `B`'s signature rather than the reverse.
For example, `B` may sign with `SIGHASH_SINGLE|SIGHASH_ANYONECANPAY` to commit only to one particular output paying `B`, while leaving input selection, fee payment, and change outputs unspecified. `A` may then sign the completed transaction with `SIGHASH_ALL`, selecting the concrete inputs, fee arrangement, and any additional outputs while remaining bound to that exact authorization from `B`. In this sense, `A` refines `B`'s partial authorization into authorization for one fully specified transaction.
### Delegation
A delegation protocol may allow `A` to delegate broad authority to key `B`. The predicate enforced by this script is that `B` produced a valid signature and that `A` authorized delegation to `B` by signing for the key derived from base key `A` and scalar `SHA256(B)`. The script verifies `B`'s signature first and then checks `A`'s delegation authorization. This verification order is chosen to avoid `OP_SWAP`; it does not imply that `B` must have signed before `A` authorized delegation.
```text
witness: <sig_A> <sig_B> <B>
script: DUP TOALT CHECKSIGVERIFY FROMALT SHA256 <A> OP_TWEAKADD OP_CHECKSIG
```
If `A` uses a weak sighash mode such as `SIGHASH_NONE`, `A` need only authorize delegation to `B` and does not materially constrain the final transaction. `B` may then choose the concrete inputs, outputs, and fees, and provide the transaction signature that refines `A`'s permissive authorization into authorization for one fully specified transaction.
Unlike the signing-order construction above, this construction does not require `OP_CHECKSIGFROMSTACK`, because `A` need only be bound to `B`'s public key rather than to `B`'s eventual signature bytes.
### Target-tweak and key-reveal contracts
In its simplest form, `OP_TWEAKADD` can prove knowledge of a scalar `t` such that `tG + k1G = k2G`:
```text
witness: <t>
script: <k1G> OP_TWEAKADD <k2G> OP_EQUAL
```
If the construction requires the scalar to be hashed first, the script can express that directly:
```text
witness: <t>
script: OP_SHA256 <k1G> OP_TWEAKADD <k2G> OP_EQUAL
```
If `k2G` is used as a Taproot output key, this construction can force disclosure of the scalar needed to account for the corresponding Taproot tweak.
A key-reveal contract is a special case of the target-tweak construction. Given `T = tG`, the script can require disclosure of `t` by checking that `G + tG = T + G`:
```text
witness: <t>
script: <G> OP_TWEAKADD <T_plus_G> OP_EQUAL
```
### Merkleized commitments
`OP_TWEAKADD` can also be used to commit to the root of a Merkleized data structure by treating the root hash as a tweak scalar. Let `K_root = tweak(P, H(root))`, where `root` is the Merkle root of some application state or set of valid transitions and `H(root)` is interpreted as a secp256k1 scalar when valid. Revealing the root can then be checked with:
```text
witness: <root>
script: OP_SHA256 <P> OP_TWEAKADD <K_root> OP_EQUAL
```
This permits a script to commit to a large data structure using one derived key while deferring disclosure of the underlying tree to the witness. If the script also has a way to compute parent hashes from child hashes, for example `OP_CAT OP_SHA256`, then a Merkle branch can be verified by iteratively recomputing `root` from a revealed leaf and its sibling path, and then checking the resulting root against the committed key above. In that setting, `OP_TWEAKADD` provides the bridge from a Merkle root to a key that can be consumed by the rest of the script, allowing Merkleized state or transition commitments in the style of constructions such as MATT.
Even without arbitrary concatenation opcodes, `OP_TWEAKADD` can be used to build a Merkle-like authenticated tree over 32-byte node commitments. Let `B_leaf = x(2G)` and `B_node = x(3G)` be distinct fixed x-only public keys, and define:
```text
Leaf(x) = tweak(B_leaf, SHA256(x))
Node(L,R) = tweak(tweak(B_node, HASH256(L)), SHA256(HASH256(R)))
```
Here the point input is always either a fixed valid x-only key or the output of a prior `OP_TWEAKADD`, so no hash-to-point grinding is required. The different hash constructions separate the left-child and right-child roles, while the distinct base keys separate leaf commitments from internal-node commitments.
One parent reduction can be expressed directly in script:
```text
witness: <L> <R>
script: OP_TOALTSTACK OP_HASH256 <B_node> OP_TWEAKADD
OP_FROMALTSTACK OP_HASH256 OP_SHA256 OP_SWAP OP_TWEAKADD
```
A branch proof starts from `Leaf(x)` for the revealed leaf value `x`, then iteratively combines it with each sibling commitment using the appropriate left or right parent rule until the committed root key is reconstructed.
This construction should be understood as an ordered binary commitment tree rather than as a general Merkle compression opcode. The recursive use of `SHA256`, `HASH256`, and `SHA256(HASH256(.))` separates roles, but it does not by itself define a canonical tree shape. A protocol using this pattern should therefore also fix the tree arity, proof length, and any empty-subtree or append rules so that the same application state cannot be represented by multiple tree layouts.
For comparison, BIP 442 `OP_PAIRCOMMIT` is purpose-built for pair commitments and yields simpler and cheaper Merkleized constructions in practice. The construction above is therefore best understood as a potentially useful tree-commitment technique if `OP_TWEAKADD` were deployed on its own, rather than as a primary reason to add `OP_TWEAKADD` when a dedicated pair-commitment opcode is available.
## Test vectors (Generated)
Curve order n = fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
### Passing cases
1) Identity tweak (t = 0)
```
pubkey32 = 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
tweak32 = 0000000000000000000000000000000000000000000000000000000000000000
expect = 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
script = <0000000000000000000000000000000000000000000000000000000000000000> <79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798> OP_TWEAKADD <79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798> OP_EQUAL
```
2) Increment by 1
```
pubkey32 = 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
tweak32 = 0000000000000000000000000000000000000000000000000000000000000001
expect = c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
script = <0000000000000000000000000000000000000000000000000000000000000001> <79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798> OP_TWEAKADD <c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5> OP_EQUAL
```
3) Increment by 2
```
pubkey32 = 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
tweak32 = 0000000000000000000000000000000000000000000000000000000000000002
expect = f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9
script = <0000000000000000000000000000000000000000000000000000000000000002> <79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798> OP_TWEAKADD <f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9> OP_EQUAL
```
4) Increment by 5
```
pubkey32 = 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
tweak32 = 0000000000000000000000000000000000000000000000000000000000000005
expect = fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556
script = <0000000000000000000000000000000000000000000000000000000000000005> <79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798> OP_TWEAKADD <fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556> OP_EQUAL
```
5) Input x(2G), t = 3
```
pubkey32 = c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
tweak32 = 0000000000000000000000000000000000000000000000000000000000000003
expect = 2f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4
script = <0000000000000000000000000000000000000000000000000000000000000003> <c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5> OP_TWEAKADD <2f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4> OP_EQUAL
```
6) Input x(7G), t = 9
```
pubkey32 = 5cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc
tweak32 = 0000000000000000000000000000000000000000000000000000000000000009
expect = e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a
script = <0000000000000000000000000000000000000000000000000000000000000009> <5cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc> OP_TWEAKADD <e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a> OP_EQUAL
```
7) Input x(h(1) G), t = 1
```
pubkey32 = d415b187c6e7ce9da46ac888d20df20737d6f16a41639e68ea055311e1535dd9
tweak32 = 0000000000000000000000000000000000000000000000000000000000000001
expect = c6713b2ac2495d1a879dc136abc06129a7bf355da486cd25f757e0a5f6f40f74
script = <0000000000000000000000000000000000000000000000000000000000000001> <d415b187c6e7ce9da46ac888d20df20737d6f16a41639e68ea055311e1535dd9> OP_TWEAKADD <c6713b2ac2495d1a879dc136abc06129a7bf355da486cd25f757e0a5f6f40f74> OP_EQUAL
```
8) Input x(h(2) G), t = 1
```
pubkey32 = d27cd27dbff481bc6fc4aa39dd19405eb6010237784ecba13bab130a4a62df5d
tweak32 = 0000000000000000000000000000000000000000000000000000000000000001
expect = 136f23e6c2efcaa13b37f0c22cd6cfb0d4e6e9eddccefe17e747f5cf440bb785
script = <0000000000000000000000000000000000000000000000000000000000000001> <d27cd27dbff481bc6fc4aa39dd19405eb6010237784ecba13bab130a4a62df5d> OP_TWEAKADD <136f23e6c2efcaa13b37f0c22cd6cfb0d4e6e9eddccefe17e747f5cf440bb785> OP_EQUAL
```
9) Input x(h(7) G), t = 1
```
pubkey32 = ddc399701a78edd5ea56429b2b7b6cd11f7d1e4015e7830b4f5e07eb25058768
tweak32 = 0000000000000000000000000000000000000000000000000000000000000001
expect = 0e27b02714b3f2344f2bfa6d821654f2bd9f0ef497ec541b653b8dcb3a915faf
script = <0000000000000000000000000000000000000000000000000000000000000001> <ddc399701a78edd5ea56429b2b7b6cd11f7d1e4015e7830b4f5e07eb25058768> OP_TWEAKADD <0e27b02714b3f2344f2bfa6d821654f2bd9f0ef497ec541b653b8dcb3a915faf> OP_EQUAL
```
10) Input x(G), t = 1
```
pubkey32 = 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
tweak32 = 4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a
expect = c6713b2ac2495d1a879dc136abc06129a7bf355da486cd25f757e0a5f6f40f74
script = <4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a> <79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798> OP_TWEAKADD <c6713b2ac2495d1a879dc136abc06129a7bf355da486cd25f757e0a5f6f40f74> OP_EQUAL
```
11) Input x(G), t = h(2)
```
pubkey32 = 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
tweak32 = dbc1b4c900ffe48d575b5da5c638040125f65db0fe3e24494b76ea986457d986
expect = 136f23e6c2efcaa13b37f0c22cd6cfb0d4e6e9eddccefe17e747f5cf440bb785
script = <dbc1b4c900ffe48d575b5da5c638040125f65db0fe3e24494b76ea986457d986> <79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798> OP_TWEAKADD <136f23e6c2efcaa13b37f0c22cd6cfb0d4e6e9eddccefe17e747f5cf440bb785> OP_EQUAL
```
12) Input x(G), t = h(7) (Note: differs from 9)
```
pubkey32 = 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
tweak32 = ca358758f6d27e6cf45272937977a748fd88391db679ceda7dc7bf1f005ee879
expect = 00b152fb17d249541e3b2f51455269e02d76507ad7857aaa98e3c51ee5da5b1d
script = <ca358758f6d27e6cf45272937977a748fd88391db679ceda7dc7bf1f005ee879> <79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798> OP_TWEAKADD <00b152fb17d249541e3b2f51455269e02d76507ad7857aaa98e3c51ee5da5b1d> OP_EQUAL
```
### Failing cases
A) Scalar out of range (t = n)
```
pubkey32 = 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
tweak32 = fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
expect = fail
script = <fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141> <79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798> OP_TWEAKADD OP_DROP OP_1
```
B) Invalid x (x = 0), t = 1
```
pubkey32 = 0000000000000000000000000000000000000000000000000000000000000000
tweak32 = 0000000000000000000000000000000000000000000000000000000000000001
expect = fail
script = <0000000000000000000000000000000000000000000000000000000000000001> <0000000000000000000000000000000000000000000000000000000000000000> OP_TWEAKADD OP_DROP OP_1
```
C) Infinity result (x(G), t = n-1)
```
pubkey32 = 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
tweak32 = fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140
expect = fail
script = <fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140> <79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798> OP_TWEAKADD OP_DROP OP_1
```
## Acknowledgements
This proposal extends the Taproot tweak mechanism (BIP340/341) into script, inspired by prior work on scriptless scripts and key-evolution constructions. There has been various discussion of OP_TWEAKADD over the years, including by Russell O'Connor and Steven Roose.
## References
- [Bitcoin Dev Mailing List Discussion](https://groups.google.com/g/bitcoindev/c/-_geIB25zrg/m/bDpv822yAAAJ)
- [CATT: Thoughts about an alternative covenant softfork proposal](https://delvingbitcoin.org/t/catt-thoughts-about-an-alternative-covenant-softfork-proposal/125)
- [Draft BIP: OP_TXHASH and OP_CHECKTXHASHVERIFY](https://gnusha.org/pi/bitcoindev/e98d76f2-6f2c-9c3a-6a31-bccb34578c31@roose.io/)
- [Advent 8: Scriptless Scripts and Key Tweaks](https://rubin.io/bitcoin/2021/12/05/advent-8/)
- [Re: [bitcoin-dev] Unlimited covenants, was Re: CHECKSIGFROMSTACK/{Verify} BIP for Bitcoin](https://gnusha.org/pi/bitcoindev/CAMZUoKnVLRLgL1rcq8DYHRjM--8VEUC5kjUbzbY5S860QSbk5w@mail.gmail.com/)
- [Re: [bitcoin-dev] Unlimited covenants, was Re: CHECKSIGFROMSTACK/{Verify} BIP for Bitcoin](https://gnusha.org/pi/bitcoindev/CAMZUoKkAUodCT+2aQG71xwHYD8KXeTAdQq4NmXZ4GBe0pcD=9A@mail.gmail.com/)
- [ElementsProject: Tapscript opcodes documentation](https://github.com/ElementsProject/elements/blob/master/doc/tapscript_opcodes.md#new-opcodes-for-additional-functionality)
- [[bitcoin-dev] Merkleize All The Things](https://gnusha.org/pi/bitcoindev/CAMhCMoH9uZPeAE_2tWH6rf0RndqV+ypjbNzazpFwFnLUpPsZ7g@mail.gmail.com/)
- [Alpen Labs Technical-Whitepaper](https://github.com/alpenlabs/Technical-Whitepaper/tree/76d5279e62fe3f157ae94ffc0514ad2a95c6dbcf)
## Copyright
This BIP is licensed under the [BSD-3-Clause License](https://opensource.org/licenses/BSD-3-Clause).

View File

@@ -0,0 +1,9 @@
[package]
name = "test-vectors"
version = "0.1.0"
edition = "2024"
[dependencies]
secp256k1 = "0.29"
hex = "0.4"
bitcoin_hashes = "0.16.0"

View File

@@ -0,0 +1,172 @@
use secp256k1::{constants::CURVE_ORDER, PublicKey, Secp256k1, SecretKey, XOnlyPublicKey};
fn hex32(b: &[u8; 32]) -> String {
b.iter().map(|x| format!("{:02x}", x)).collect()
}
/// Implements OP_TWEAKADD semantics.
/// Returns None if invalid scalar, invalid x, or infinity.
fn tweak_add_xonly(pubkey32: [u8; 32], h32: [u8; 32]) -> Option<[u8; 32]> {
let secp = Secp256k1::new();
// Reject if t >= n
let scalar = secp256k1::Scalar::from_be_bytes(h32).ok()?;
// Lift pubkey from x-only
let xpk = XOnlyPublicKey::from_slice(&pubkey32).ok()?;
let (xonly, _) = xpk.add_tweak(&secp, &scalar).ok()?;
Some(xonly.serialize())
}
fn case(name: &str, pubkey_hex: &str, t_hex: &str, check_res: Option<&str>) {
let pk_bytes = hex::decode(pubkey_hex).unwrap();
let t_bytes = hex::decode(t_hex).unwrap();
let mut pk32 = [0u8; 32];
pk32.copy_from_slice(&pk_bytes);
let mut t32 = [0u8; 32];
t32.copy_from_slice(&t_bytes);
match tweak_add_xonly(pk32, t32) {
Some(out) => {
let out_hex = hex32(&out);
if let Some(check) = check_res {
assert_eq!(out_hex, check);
}
let script = format!("<{t_hex}> <{pubkey_hex}> OP_TWEAKADD <{out_hex}> OP_EQUAL");
println!("{name}\n```\n pubkey32 = {pubkey_hex}\n h32 = {t_hex}\n expect = {out_hex}\n\n script = {script}\n```")
}
None => {
let script = format!("<{t_hex}> <{pubkey_hex}> OP_TWEAKADD OP_DROP OP_1");
println!("{name}\n```\n pubkey32 = {pubkey_hex}\n h32 = {t_hex}\n expect = fail\n script = {script}\n```")
}
}
}
/// Helper: compute x-only for scalar*k*G.
fn xonly_of_scalar(k: u8) -> String {
let secp = Secp256k1::new();
let mut buf = [0u8; 32];
buf[31] = k;
let sk = SecretKey::from_slice(&buf).unwrap();
let pk = PublicKey::from_secret_key(&secp, &sk);
let (xonly, _) = pk.x_only_public_key();
hex32(&xonly.serialize())
}
fn hash_scalar(k: u8) -> [u8; 32] {
bitcoin_hashes::Sha256::hash(&[k]).to_byte_array()
}
/// Helper: compute x-only for scalar*k*G.
fn xonly_of_scalar_hash(k: [u8; 32]) -> String {
let secp = Secp256k1::new();
let sk = SecretKey::from_slice(&k).unwrap();
let pk = PublicKey::from_secret_key(&secp, &sk);
let (xonly, _) = pk.x_only_public_key();
hex32(&xonly.serialize())
}
fn main() {
println!("Curve order n = {}", hex::encode(CURVE_ORDER));
println!();
let x_g = xonly_of_scalar(1);
let x_2g = xonly_of_scalar(2);
let x_3g = xonly_of_scalar(3);
let x_5g = xonly_of_scalar(5);
let x_6g = xonly_of_scalar(6);
let x_7g = xonly_of_scalar(7);
let x_16g = xonly_of_scalar(16);
let h1 = hash_scalar(1);
let h2 = hash_scalar(2);
let h7 = hash_scalar(7);
let x_h1g = xonly_of_scalar_hash(h1);
let x_h2g = xonly_of_scalar_hash(h2);
let x_h7g = xonly_of_scalar_hash(h7);
println!("\n### Passing cases\n");
case(
"1) Identity tweak (t = 0)",
&x_g,
"0000000000000000000000000000000000000000000000000000000000000000",
Some(&x_g),
);
case(
"2) Increment by 1",
&x_g,
"0000000000000000000000000000000000000000000000000000000000000001",
Some(&x_2g),
);
case(
"3) Increment by 2",
&x_g,
"0000000000000000000000000000000000000000000000000000000000000002",
Some(&x_3g),
);
case(
"4) Increment by 5",
&x_g,
"0000000000000000000000000000000000000000000000000000000000000005",
Some(&x_6g),
);
case(
"5) Input x(2G), t = 3",
&x_2g,
"0000000000000000000000000000000000000000000000000000000000000003",
Some(&x_5g),
);
case(
"6) Input x(7G), t = 9",
&x_7g,
"0000000000000000000000000000000000000000000000000000000000000009",
Some(&x_16g),
);
case(
"7) Input x(h(1) G), t = 1",
&x_h1g,
"0000000000000000000000000000000000000000000000000000000000000001",
None,
);
case(
"8) Input x(h(2) G), t = 1",
&x_h2g,
"0000000000000000000000000000000000000000000000000000000000000001",
None,
);
case(
"9) Input x(h(7) G), t = 1",
&x_h7g,
"0000000000000000000000000000000000000000000000000000000000000001",
None,
);
case("10) Input x(G), t = 1", &x_g, &hex32(&h1), None);
case("11) Input x(G), t = h(2)", &x_g, &hex32(&h2), None);
case(
"12) Input x(G), t = h(7) (Note: differs from 9)",
&x_g,
&hex32(&h7),
None,
);
println!("\n### Failing cases\n");
case(
"A) Scalar out of range (t = n)",
&x_g,
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141",
None,
);
case(
"B) Invalid x (x = 0), t = 1",
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000001",
None,
);
case(
"C) Infinity result (x(G), t = n-1)",
&x_g,
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
None,
);
}