@btc-vision/transaction 1.6.4 → 1.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/browser/_version.d.ts +1 -1
- package/browser/generators/builders/P2WDAGenerator.d.ts +13 -0
- package/browser/index.js +1 -1
- package/browser/keypair/Address.d.ts +3 -2
- package/browser/keypair/AddressVerificator.d.ts +12 -1
- package/browser/keypair/Wallet.d.ts +3 -0
- package/browser/opnet.d.ts +4 -0
- package/browser/p2wda/P2WDADetector.d.ts +16 -0
- package/browser/transaction/TransactionFactory.d.ts +3 -1
- package/browser/transaction/builders/DeploymentTransaction.d.ts +3 -3
- package/browser/transaction/builders/InteractionTransactionP2WDA.d.ts +37 -0
- package/browser/transaction/builders/SharedInteractionTransaction.d.ts +4 -4
- package/browser/transaction/mineable/IP2WSHAddress.d.ts +4 -0
- package/browser/transaction/mineable/TimelockGenerator.d.ts +2 -5
- package/browser/transaction/shared/TweakedTransaction.d.ts +5 -0
- package/build/_version.d.ts +1 -1
- package/build/_version.js +1 -1
- package/build/generators/builders/P2WDAGenerator.d.ts +13 -0
- package/build/generators/builders/P2WDAGenerator.js +62 -0
- package/build/keypair/Address.d.ts +3 -2
- package/build/keypair/Address.js +28 -2
- package/build/keypair/AddressVerificator.d.ts +12 -1
- package/build/keypair/AddressVerificator.js +78 -1
- package/build/keypair/Wallet.d.ts +3 -0
- package/build/keypair/Wallet.js +4 -0
- package/build/opnet.d.ts +4 -0
- package/build/opnet.js +4 -0
- package/build/p2wda/P2WDADetector.d.ts +16 -0
- package/build/p2wda/P2WDADetector.js +97 -0
- package/build/transaction/TransactionFactory.d.ts +3 -1
- package/build/transaction/TransactionFactory.js +35 -4
- package/build/transaction/builders/DeploymentTransaction.d.ts +3 -3
- package/build/transaction/builders/DeploymentTransaction.js +1 -1
- package/build/transaction/builders/InteractionTransactionP2WDA.d.ts +37 -0
- package/build/transaction/builders/InteractionTransactionP2WDA.js +205 -0
- package/build/transaction/builders/SharedInteractionTransaction.d.ts +4 -4
- package/build/transaction/builders/SharedInteractionTransaction.js +3 -3
- package/build/transaction/mineable/IP2WSHAddress.d.ts +4 -0
- package/build/transaction/mineable/IP2WSHAddress.js +1 -0
- package/build/transaction/mineable/TimelockGenerator.d.ts +2 -5
- package/build/transaction/shared/TweakedTransaction.d.ts +5 -0
- package/build/transaction/shared/TweakedTransaction.js +19 -0
- package/doc/README.md +0 -0
- package/doc/addresses/P2OP.md +1 -0
- package/doc/addresses/P2WDA.md +240 -0
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/generators/builders/P2WDAGenerator.ts +174 -0
- package/src/keypair/Address.ts +58 -3
- package/src/keypair/AddressVerificator.ts +140 -1
- package/src/keypair/Wallet.ts +16 -0
- package/src/opnet.ts +4 -0
- package/src/p2wda/P2WDADetector.ts +218 -0
- package/src/transaction/TransactionFactory.ts +79 -5
- package/src/transaction/builders/DeploymentTransaction.ts +4 -3
- package/src/transaction/builders/InteractionTransactionP2WDA.ts +376 -0
- package/src/transaction/builders/SharedInteractionTransaction.ts +7 -6
- package/src/transaction/mineable/IP2WSHAddress.ts +4 -0
- package/src/transaction/mineable/TimelockGenerator.ts +2 -6
- package/src/transaction/shared/TweakedTransaction.ts +36 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# Pay-to-Witness-Data-Authentication (P2WDA)
|
|
2
|
+
|
|
3
|
+
## Executive Summary
|
|
4
|
+
|
|
5
|
+
Bitcoin makes arbitrary authenticated data inclusion expensive unless you exploit the SegWit witness discount, where
|
|
6
|
+
every witness byte weighs 1 unit instead of 4. P2WDA is a spend template that deliberately carries authenticated
|
|
7
|
+
application data in witness stack items and then drops them before a standard signature check. This preserves standard
|
|
8
|
+
Bitcoin validation while letting applications verify the attached data out-of-band.
|
|
9
|
+
|
|
10
|
+
In this implementation, the witness stack reserves **10 data slots** per P2WDA input, each **≤80 bytes** to respect
|
|
11
|
+
default relay policy. The data (after compression) is prefixed by a **BIP340 Schnorr** signature over
|
|
12
|
+
`(tx_signature || uncompressed_data)`, then split across those slots. Applications reassemble and verify the signed
|
|
13
|
+
data, making any miner tampering detectable. On-chain validation remains standard and cheap thanks to the witness
|
|
14
|
+
discount.
|
|
15
|
+
|
|
16
|
+
## 1. Problem Space
|
|
17
|
+
|
|
18
|
+
### 1.1 The Traditional Dilemma
|
|
19
|
+
|
|
20
|
+
OP_RETURN is simple but tiny: **80 bytes max** per output under standard policy. Anything larger forces many outputs and
|
|
21
|
+
multiple transactions, compounding base (non-witness) bytes that weigh 4× witness bytes. For realistic payloads (
|
|
22
|
+
airdrops, rich metadata), this is cost-prohibitive.
|
|
23
|
+
|
|
24
|
+
Importantly, even if OP_RETURN size limits were completely removed, it would not solve the fundamental economic problem.
|
|
25
|
+
OP_RETURN data is stored in the transaction's output section, which means every byte counts as non-witness data and
|
|
26
|
+
incurs the full 4× weight penalty. A hypothetical uncapped OP_RETURN storing 800 bytes would cost 3,200 weight units,
|
|
27
|
+
while P2WDA achieves the same data storage for only 800 weight units in the witness section. The economic disadvantage
|
|
28
|
+
of OP_RETURN is architectural, not merely a policy limitation.
|
|
29
|
+
|
|
30
|
+
Commit-reveal styles fix integrity but double touches the chain (commit tx, reveal tx) and fragment UX.
|
|
31
|
+
|
|
32
|
+
### 1.2 The Witness Discount Opportunity
|
|
33
|
+
|
|
34
|
+
SegWit introduced **weight**: non-witness bytes weigh 4 units each; witness bytes weigh **1**. Fees are proportional to
|
|
35
|
+
weight (vbytes ≈ weight/4). Packing authenticated data into witness can be ~75% cheaper than encoding the same data in
|
|
36
|
+
base tx bytes.
|
|
37
|
+
|
|
38
|
+
## 2. Technical Architecture
|
|
39
|
+
|
|
40
|
+
### 2.1 Spend Template
|
|
41
|
+
|
|
42
|
+
P2WDA uses a **P2WSH** spend whose script pre-drops a fixed number of stack items (the data slots), then performs a
|
|
43
|
+
standard single-sig check.
|
|
44
|
+
|
|
45
|
+
The witness script consists of:
|
|
46
|
+
|
|
47
|
+
- 5 consecutive `OP_2DROP` operations (dropping 10 items total)
|
|
48
|
+
- A 33-byte compressed public key
|
|
49
|
+
- An `OP_CHECKSIG` operation
|
|
50
|
+
|
|
51
|
+
This creates a script of approximately 40 bytes that validates like any standard single-signature P2WSH spend, but with
|
|
52
|
+
space for our data payload in the witness stack.
|
|
53
|
+
|
|
54
|
+
### 2.2 Authentication & Packing
|
|
55
|
+
|
|
56
|
+
The authentication and packing process ensures data integrity while maximizing compression efficiency. Here's how it
|
|
57
|
+
works:
|
|
58
|
+
|
|
59
|
+
**Step 1: Prepare the data**
|
|
60
|
+
|
|
61
|
+
- Start with your uncompressed payload data (application bytes)
|
|
62
|
+
- Get the transaction signature (the DER signature that authorizes spending this input)
|
|
63
|
+
|
|
64
|
+
**Step 2: Create authentication signature**
|
|
65
|
+
|
|
66
|
+
- Compute a BIP340 Schnorr signature over the hash of (transaction_signature || payload_data)
|
|
67
|
+
- This signature proves authorship and binds the data to this specific spend
|
|
68
|
+
|
|
69
|
+
**Step 3: Combine and compress**
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
// Combine the authentication signature with the payload
|
|
73
|
+
const combined_bytes = data_signature + payload_data;
|
|
74
|
+
|
|
75
|
+
// Compress everything using DEFLATE or similar
|
|
76
|
+
const compressed_bytes = COMPRESS(combined_bytes);
|
|
77
|
+
|
|
78
|
+
// Split into chunks of max 80 bytes each
|
|
79
|
+
const chunks = SPLIT_INTO_80_BYTE_CHUNKS(compressed_bytes);
|
|
80
|
+
|
|
81
|
+
// Ensure we don't exceed 10 chunks
|
|
82
|
+
if (chunks.length > 10) {
|
|
83
|
+
throw Error("Payload too large")
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Step 4: Build the witness stack**
|
|
88
|
+
The witness stack must contain exactly 12 items in this order:
|
|
89
|
+
|
|
90
|
+
1. Data slot 0 (up to 80 bytes, or empty)
|
|
91
|
+
2. Data slot 1 (up to 80 bytes, or empty)
|
|
92
|
+
3. ... through Data slot 9
|
|
93
|
+
4. Transaction signature (DER encoded, ~72 bytes)
|
|
94
|
+
5. Witness script (~40 bytes)
|
|
95
|
+
|
|
96
|
+
Any unused data slots are filled with empty byte arrays (length 0) to maintain the expected stack structure.
|
|
97
|
+
|
|
98
|
+
**Verification Process for Indexers:**
|
|
99
|
+
|
|
100
|
+
When an indexer encounters a P2WDA spend, it:
|
|
101
|
+
|
|
102
|
+
1. Extracts and concatenates the first 10 witness items
|
|
103
|
+
2. Decompresses the result to get (data_signature || original_data)
|
|
104
|
+
3. Verifies the Schnorr signature against hash(tx_signature || original_data)
|
|
105
|
+
4. If valid, passes the original_data to the application layer
|
|
106
|
+
|
|
107
|
+
This design ensures that while Bitcoin consensus never inspects the data, any tampering is cryptographically detectable
|
|
108
|
+
by applications.
|
|
109
|
+
|
|
110
|
+
### 2.3 Input Placement Rules
|
|
111
|
+
|
|
112
|
+
To simplify parsing and minimize duplication:
|
|
113
|
+
|
|
114
|
+
* **All application data must be injected in the first P2WDA input by index** (the lowest-index input spending a P2WDA
|
|
115
|
+
UTXO)
|
|
116
|
+
* Any **additional P2WDA inputs** in the same tx must supply **10 empty data items** (length=0) in their witness
|
|
117
|
+
* Non-P2WDA inputs are unaffected
|
|
118
|
+
* If a transaction flagged as `InteractionTransactionP2WDA` spends **no** P2WDA UTXOs, **throw an error**
|
|
119
|
+
|
|
120
|
+
## 3. Economics
|
|
121
|
+
|
|
122
|
+
### 3.1 OP_RETURN Baseline
|
|
123
|
+
|
|
124
|
+
Standard OP_RETURN script is ≤83 bytes total, allowing ≤80 bytes data. All bytes are non-witness, costing **1 vbyte per
|
|
125
|
+
byte**. For 80 bytes of data you typically consume ~91 vbytes including script overhead and output framing.
|
|
126
|
+
|
|
127
|
+
### 3.2 P2WDA Spend
|
|
128
|
+
|
|
129
|
+
The witness bytes calculation for a P2WDA input includes:
|
|
130
|
+
|
|
131
|
+
- 1 byte for item count
|
|
132
|
+
- 10 bytes for length prefixes (one per data slot)
|
|
133
|
+
- The actual compressed data bytes
|
|
134
|
+
- ~73 bytes for the transaction signature (including length prefix)
|
|
135
|
+
- ~41 bytes for the witness script (including length prefix)
|
|
136
|
+
|
|
137
|
+
Since all of this is witness data, it costs only 1/4 the weight of equivalent non-witness bytes.
|
|
138
|
+
|
|
139
|
+
**Example with 512 bytes of incompressible data:**
|
|
140
|
+
|
|
141
|
+
- Data + signature = 576 bytes total
|
|
142
|
+
- Split across 8 slots (72 bytes each)
|
|
143
|
+
- Total witness bytes ≈ 701
|
|
144
|
+
- Cost in vbytes ≈ 175
|
|
145
|
+
|
|
146
|
+
Compare to OP_RETURN which would need 7 outputs at ~91 vbytes each ≈ 637 vbytes.
|
|
147
|
+
**Result: ~72% savings** while keeping everything in a single transaction.
|
|
148
|
+
|
|
149
|
+
## 4. Security Model
|
|
150
|
+
|
|
151
|
+
The security model separates on-chain authorization from off-chain data authentication:
|
|
152
|
+
|
|
153
|
+
* **On-chain authorization**: The transaction signature proves spend authorization exactly like any P2WSH single-sig
|
|
154
|
+
spend
|
|
155
|
+
* **Off-chain authorship**: The Schnorr signature authenticates the data and binds it to this spend via the transaction
|
|
156
|
+
signature
|
|
157
|
+
* **Malleability handling**: SegWit allows witness data modification without changing the txid. The Schnorr signature
|
|
158
|
+
ensures any tampering is detectable
|
|
159
|
+
|
|
160
|
+
Practical outcomes:
|
|
161
|
+
|
|
162
|
+
- Funds cannot be redirected (protected by transaction signature)
|
|
163
|
+
- Data forgery is detectable at the application layer (Schnorr signature fails)
|
|
164
|
+
- Miners could theoretically replace data with garbage, but applications will reject it
|
|
165
|
+
|
|
166
|
+
## 5. Operational Capabilities
|
|
167
|
+
|
|
168
|
+
P2WDA works well for:
|
|
169
|
+
|
|
170
|
+
* Mints
|
|
171
|
+
* Airdrops
|
|
172
|
+
* NFTs
|
|
173
|
+
* Batch updates and state checkpoints
|
|
174
|
+
* Governance votes and attestations
|
|
175
|
+
* Swap listings (like nativeswap) but **NOT TRADES**, a miner could cancel your transaction!
|
|
176
|
+
* etc.
|
|
177
|
+
|
|
178
|
+
The 10 slots serve as a transport layer; keep application formats versioned and compact.
|
|
179
|
+
|
|
180
|
+
## 6. Advanced Considerations
|
|
181
|
+
|
|
182
|
+
### 6.1 Why 10 Slots of ≤80 Bytes?
|
|
183
|
+
|
|
184
|
+
Default relay policy limits:
|
|
185
|
+
|
|
186
|
+
- Maximum 100 stack items for P2WSH
|
|
187
|
+
- Maximum 80 bytes per non-script witness item
|
|
188
|
+
- Maximum 3600 bytes for the witness script itself
|
|
189
|
+
|
|
190
|
+
Ten slots provides headroom while keeping scripts simple (5 * OP_2DROP). You can scale by using multiple P2WDA inputs,
|
|
191
|
+
keeping data only in the first P2WDA input and zeros in the rest.
|
|
192
|
+
|
|
193
|
+
### 6.2 Domain Separation (Recommended)
|
|
194
|
+
|
|
195
|
+
To harden against cross-protocol attacks, consider using domain separation:
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
message = Hash("P2WDA/v1" || txid || input_index || tx_signature || data)
|
|
199
|
+
data_signature = Schnorr.Sign(auth_private_key, message)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
This prevents signatures from being reused across different protocols or transactions.
|
|
203
|
+
|
|
204
|
+
### 6.3 Compression
|
|
205
|
+
|
|
206
|
+
Use a standard compression algorithm like DEFLATE to maximize data packing efficiency. Ensure your application layer
|
|
207
|
+
can handle decompression and verify the integrity of the decompressed data.
|
|
208
|
+
|
|
209
|
+
### 6.4 Why SegWit Instead of Taproot?
|
|
210
|
+
|
|
211
|
+
A common question is why P2WDA uses SegWit's P2WSH instead of the newer Taproot technology. This decision is deliberate
|
|
212
|
+
and based on fundamental economics of block space usage. Understanding this choice illuminates the elegant design of
|
|
213
|
+
P2WDA.
|
|
214
|
+
|
|
215
|
+
#### The Block Space Reality
|
|
216
|
+
|
|
217
|
+
When people first hear about P2WDA, they might assume it should use Taproot since it's Bitcoin's newest upgrade.
|
|
218
|
+
However, Taproot would actually consume more block space for our use case, making it less efficient. This
|
|
219
|
+
counterintuitive reality stems from how these technologies were designed for different purposes.
|
|
220
|
+
|
|
221
|
+
Taproot offers two spending paths. The key path is remarkably efficient, requiring only a 64-byte Schnorr signature, but
|
|
222
|
+
it cannot carry arbitrary data. To include data, you must use the script path, which requires a control block containing
|
|
223
|
+
the internal public key, parity information, and Merkle proof of your script's inclusion in the taproot tree. Even with
|
|
224
|
+
the simplest possible tree structure, this control block adds approximately 65 bytes of pure overhead.
|
|
225
|
+
|
|
226
|
+
Let's examine the concrete numbers for storing 500 bytes of authenticated data:
|
|
227
|
+
|
|
228
|
+
With P2WSH (what P2WDA uses), the witness contains the transaction signature (~72 bytes), your data (500 bytes), and the
|
|
229
|
+
witness script (~40 bytes), totaling approximately 612 bytes, which equals 612 weight units.
|
|
230
|
+
|
|
231
|
+
With Taproot's script path, you would need a Schnorr signature (64 bytes), your data (500 bytes), the tapscript (~40
|
|
232
|
+
bytes), and the control block (~65 bytes), totaling approximately 669 bytes, which equals 669 weight units.
|
|
233
|
+
|
|
234
|
+
This represents about 10% more block space consumption for identical functionality. When your goal is cost-efficient
|
|
235
|
+
data storage, every byte matters, and this overhead directly translates to higher fees for users.
|
|
236
|
+
|
|
237
|
+
### 6.4 Future Extensions
|
|
238
|
+
|
|
239
|
+
- Use annexes (BIP490) to carry larger payloads if needed and stop requiring another signature in the witness stack
|
|
240
|
+
- Explore multi-signature variants for collaborative data signing
|
package/package.json
CHANGED
package/src/_version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '1.6.
|
|
1
|
+
export const version = '1.6.5';
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { Network, networks } from '@btc-vision/bitcoin';
|
|
2
|
+
import { BinaryWriter } from '../../buffer/BinaryWriter.js';
|
|
3
|
+
import { Feature, Features } from '../Features.js';
|
|
4
|
+
import { Generator } from '../Generator.js';
|
|
5
|
+
import { ChallengeSolution } from '../../epoch/ChallengeSolution.js';
|
|
6
|
+
|
|
7
|
+
export class P2WDAGenerator extends Generator {
|
|
8
|
+
private static readonly P2WDA_VERSION = 0x01;
|
|
9
|
+
|
|
10
|
+
constructor(
|
|
11
|
+
senderPubKey: Buffer,
|
|
12
|
+
contractSaltPubKey: Buffer,
|
|
13
|
+
network: Network = networks.bitcoin,
|
|
14
|
+
) {
|
|
15
|
+
super(senderPubKey, contractSaltPubKey, network);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Validate that operation data will fit in P2WDA witness fields
|
|
20
|
+
*
|
|
21
|
+
* @param dataSize Size of the operation data
|
|
22
|
+
* @param maxWitnessFields Maximum number of witness fields (default 10)
|
|
23
|
+
* @param maxBytesPerWitness Maximum bytes per witness field (default 80)
|
|
24
|
+
* @returns true if data will fit, false otherwise
|
|
25
|
+
*/
|
|
26
|
+
public static validateWitnessSize(
|
|
27
|
+
dataSize: number,
|
|
28
|
+
maxWitnessFields: number = 10,
|
|
29
|
+
maxBytesPerWitness: number = 80,
|
|
30
|
+
): boolean {
|
|
31
|
+
// Account for Schnorr signature (64 bytes) and compression
|
|
32
|
+
// Assume 30% compression ratio (conservative estimate)
|
|
33
|
+
|
|
34
|
+
const signatureSize = 64;
|
|
35
|
+
const compressionRatio = 0.7;
|
|
36
|
+
|
|
37
|
+
const totalSize = dataSize + signatureSize;
|
|
38
|
+
const compressedSize = Math.ceil(totalSize * compressionRatio);
|
|
39
|
+
|
|
40
|
+
const requiredFields = Math.ceil(compressedSize / maxBytesPerWitness);
|
|
41
|
+
|
|
42
|
+
return requiredFields <= maxWitnessFields;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Compile operation data for P2WDA witness embedding
|
|
47
|
+
*
|
|
48
|
+
* This creates a binary structure containing all operation information
|
|
49
|
+
* without Bitcoin script opcodes. The structure is:
|
|
50
|
+
*
|
|
51
|
+
* [version(1)] [header(12)] [contract(32)] [challenge_pubkey(33)] [challenge_solution(32)]
|
|
52
|
+
* [calldata_length(4)] [calldata] [features_length(2)] [features_data]
|
|
53
|
+
*
|
|
54
|
+
* @param calldata The compressed calldata for the contract interaction
|
|
55
|
+
* @param contractSecret The 32-byte contract secret
|
|
56
|
+
* @param challenge The challenge solution for epoch rewards
|
|
57
|
+
* @param maxPriority Maximum priority fee in satoshis
|
|
58
|
+
* @param features Optional features like access lists
|
|
59
|
+
* @returns Raw operation data ready for signing and compression
|
|
60
|
+
*/
|
|
61
|
+
public compile(
|
|
62
|
+
calldata: Buffer,
|
|
63
|
+
contractSecret: Buffer,
|
|
64
|
+
challenge: ChallengeSolution,
|
|
65
|
+
maxPriority: bigint,
|
|
66
|
+
features: Feature<Features>[] = [],
|
|
67
|
+
): Buffer {
|
|
68
|
+
if (!this.contractSaltPubKey) {
|
|
69
|
+
throw new Error('Contract salt public key not set');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (contractSecret.length !== 32) {
|
|
73
|
+
throw new Error('Contract secret must be exactly 32 bytes');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const writer = new BinaryWriter();
|
|
77
|
+
|
|
78
|
+
// Version byte
|
|
79
|
+
writer.writeU8(P2WDAGenerator.P2WDA_VERSION);
|
|
80
|
+
|
|
81
|
+
// Header
|
|
82
|
+
writer.writeBytes(
|
|
83
|
+
this.getHeader(
|
|
84
|
+
maxPriority,
|
|
85
|
+
features.map((f) => f.opcode),
|
|
86
|
+
),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Contract secret
|
|
90
|
+
writer.writeBytes(contractSecret);
|
|
91
|
+
|
|
92
|
+
// Challenge components for epoch rewards
|
|
93
|
+
writer.writeBytes(challenge.publicKey.originalPublicKeyBuffer());
|
|
94
|
+
writer.writeBytes(challenge.solution);
|
|
95
|
+
|
|
96
|
+
// Calldata with length prefix
|
|
97
|
+
writer.writeU32(calldata.length);
|
|
98
|
+
writer.writeBytes(calldata);
|
|
99
|
+
|
|
100
|
+
// Features
|
|
101
|
+
this.writeFeatures(writer, features);
|
|
102
|
+
|
|
103
|
+
return Buffer.from(writer.getBuffer());
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Create a minimal header for P2WDA operations
|
|
108
|
+
*
|
|
109
|
+
* The header contains essential transaction metadata in a compact format:
|
|
110
|
+
* [sender_pubkey_prefix(1)] [feature_flags(3)] [max_priority(8)]
|
|
111
|
+
*
|
|
112
|
+
* @param maxPriority Maximum priority fee
|
|
113
|
+
* @param features Feature opcodes to set in flags
|
|
114
|
+
* @returns 12-byte header
|
|
115
|
+
*/
|
|
116
|
+
public override getHeader(maxPriority: bigint, features: Features[] = []): Buffer {
|
|
117
|
+
return super.getHeader(maxPriority, features);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Write features section to the operation data
|
|
122
|
+
*
|
|
123
|
+
* Features are encoded as:
|
|
124
|
+
* [feature_count(2)] [feature1_opcode(1)] [feature1_length(4)] [feature1_data] ...
|
|
125
|
+
*
|
|
126
|
+
* @param writer Binary writer to write to
|
|
127
|
+
* @param features Array of features to encode
|
|
128
|
+
*/
|
|
129
|
+
private writeFeatures(writer: BinaryWriter, features: Feature<Features>[]): void {
|
|
130
|
+
// Write feature count
|
|
131
|
+
writer.writeU16(features.length);
|
|
132
|
+
|
|
133
|
+
for (const feature of features) {
|
|
134
|
+
// Write feature opcode
|
|
135
|
+
writer.writeU8(feature.opcode);
|
|
136
|
+
|
|
137
|
+
// Encode feature data
|
|
138
|
+
const encodedData = this.encodeFeatureData(feature);
|
|
139
|
+
|
|
140
|
+
// Write feature data with length prefix
|
|
141
|
+
writer.writeU32(encodedData.length);
|
|
142
|
+
writer.writeBytes(encodedData);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Encode a single feature's data
|
|
148
|
+
*
|
|
149
|
+
* Unlike the base Generator class, we don't split into chunks here
|
|
150
|
+
* since P2WDA handles chunking at the witness level
|
|
151
|
+
*
|
|
152
|
+
* @param feature The feature to encode
|
|
153
|
+
* @returns Encoded feature data
|
|
154
|
+
*/
|
|
155
|
+
private encodeFeatureData(feature: Feature<Features>): Buffer {
|
|
156
|
+
switch (feature.opcode) {
|
|
157
|
+
case Features.ACCESS_LIST: {
|
|
158
|
+
// Access lists are already encoded efficiently by the parent class
|
|
159
|
+
const chunks = this.encodeFeature(feature);
|
|
160
|
+
// Flatten chunks since P2WDA doesn't need script-level chunking
|
|
161
|
+
return Buffer.concat(chunks.flat());
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
case Features.EPOCH_SUBMISSION: {
|
|
165
|
+
// Epoch submissions are also handled by parent
|
|
166
|
+
const chunks = this.encodeFeature(feature);
|
|
167
|
+
return Buffer.concat(chunks.flat());
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
default:
|
|
171
|
+
throw new Error(`Unknown feature type: ${feature.opcode}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
package/src/keypair/Address.ts
CHANGED
|
@@ -5,7 +5,9 @@ import { AddressVerificator } from './AddressVerificator.js';
|
|
|
5
5
|
import { EcKeyPair } from './EcKeyPair.js';
|
|
6
6
|
import { ContractAddress } from '../transaction/ContractAddress.js';
|
|
7
7
|
import { BitcoinUtils } from '../utils/BitcoinUtils.js';
|
|
8
|
-
import {
|
|
8
|
+
import { TimeLockGenerator } from '../transaction/mineable/TimelockGenerator.js';
|
|
9
|
+
import { IP2WSHAddress } from '../transaction/mineable/IP2WSHAddress.js';
|
|
10
|
+
import { P2WDADetector } from '../p2wda/P2WDADetector.js';
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Objects of type "Address" are the representation of tweaked public keys. They can be converted to different address formats.
|
|
@@ -19,6 +21,7 @@ export class Address extends Uint8Array {
|
|
|
19
21
|
#keyPair: ECPairInterface | undefined;
|
|
20
22
|
#uncompressed: UncompressedPublicKey | undefined;
|
|
21
23
|
#tweakedUncompressed: Buffer | undefined;
|
|
24
|
+
#p2wda: IP2WSHAddress | undefined;
|
|
22
25
|
|
|
23
26
|
public constructor(bytes?: ArrayLike<number>) {
|
|
24
27
|
super(ADDRESS_BYTE_LENGTH);
|
|
@@ -318,6 +321,58 @@ export class Address extends Uint8Array {
|
|
|
318
321
|
throw new Error('Public key not set');
|
|
319
322
|
}
|
|
320
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Generate a P2WDA (Pay-to-Witness-Data-Authentication) address
|
|
326
|
+
*
|
|
327
|
+
* P2WDA addresses are a special type of P2WSH address that allows embedding
|
|
328
|
+
* authenticated data directly in the witness field, achieving 75% cost reduction
|
|
329
|
+
* through Bitcoin's witness discount.
|
|
330
|
+
*
|
|
331
|
+
* The witness script pattern is: (OP_2DROP * 5) <pubkey> OP_CHECKSIG
|
|
332
|
+
* This allows up to 10 witness data fields (5 * 2 = 10), where each field
|
|
333
|
+
* can hold up to 80 bytes of data due to relay rules.
|
|
334
|
+
*
|
|
335
|
+
* @param {Network} network - The Bitcoin network to use
|
|
336
|
+
* @returns {IP2WSHAddress} The P2WDA address
|
|
337
|
+
* @throws {Error} If the public key is not set or address generation fails
|
|
338
|
+
*
|
|
339
|
+
* @example
|
|
340
|
+
* ```typescript
|
|
341
|
+
* const address = Address.fromString('0x02...');
|
|
342
|
+
* const p2wdaAddress = address.p2wda(networks.bitcoin);
|
|
343
|
+
* console.log(p2wdaAddress); // bc1q...
|
|
344
|
+
* ```
|
|
345
|
+
*/
|
|
346
|
+
public p2wda(network: Network): IP2WSHAddress {
|
|
347
|
+
if (this.#p2wda && this.#network === network) {
|
|
348
|
+
return this.#p2wda;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (!this.#originalPublicKey) {
|
|
352
|
+
throw new Error('Cannot create P2WDA address: public key not set');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const publicKeyBuffer = Buffer.from(this.#originalPublicKey);
|
|
356
|
+
|
|
357
|
+
if (publicKeyBuffer.length !== 33) {
|
|
358
|
+
throw new Error('P2WDA requires a compressed public key (33 bytes)');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
const p2wdaInfo = P2WDADetector.generateP2WDAAddress(publicKeyBuffer, network);
|
|
363
|
+
|
|
364
|
+
this.#network = network;
|
|
365
|
+
this.#p2wda = p2wdaInfo;
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
address: p2wdaInfo.address,
|
|
369
|
+
witnessScript: p2wdaInfo.witnessScript,
|
|
370
|
+
};
|
|
371
|
+
} catch (error) {
|
|
372
|
+
throw new Error(`Failed to generate P2WDA address: ${(error as Error).message}`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
321
376
|
/**
|
|
322
377
|
* Generate a P2WSH address with CSV (CheckSequenceVerify) timelock
|
|
323
378
|
* The resulting address can only be spent after the specified number of blocks
|
|
@@ -325,10 +380,10 @@ export class Address extends Uint8Array {
|
|
|
325
380
|
*
|
|
326
381
|
* @param {bigint | number | string} duration - The number of blocks that must pass before spending (1-65535)
|
|
327
382
|
* @param {Network} network - The Bitcoin network to use
|
|
328
|
-
* @returns {
|
|
383
|
+
* @returns {IP2WSHAddress} The timelocked address and its witness script
|
|
329
384
|
* @throws {Error} If the block number is out of range or public key is not available
|
|
330
385
|
*/
|
|
331
|
-
public toCSV(duration: bigint | number | string, network: Network):
|
|
386
|
+
public toCSV(duration: bigint | number | string, network: Network): IP2WSHAddress {
|
|
332
387
|
const n = Number(duration);
|
|
333
388
|
|
|
334
389
|
// First, let's validate the block number to ensure it's within the valid range
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { address, initEccLib, Network } from '@btc-vision/bitcoin';
|
|
1
|
+
import { address, initEccLib, Network, payments } from '@btc-vision/bitcoin';
|
|
2
2
|
import * as ecc from '@bitcoinerlab/secp256k1';
|
|
3
3
|
import { EcKeyPair } from './EcKeyPair.js';
|
|
4
4
|
import { BitcoinUtils } from '../utils/BitcoinUtils.js';
|
|
5
|
+
import { P2WDADetector } from '../p2wda/P2WDADetector.js';
|
|
5
6
|
|
|
6
7
|
initEccLib(ecc);
|
|
7
8
|
|
|
@@ -13,6 +14,15 @@ export enum AddressTypes {
|
|
|
13
14
|
P2TR = 'P2TR',
|
|
14
15
|
P2WPKH = 'P2WPKH',
|
|
15
16
|
P2WSH = 'P2WSH',
|
|
17
|
+
P2WDA = 'P2WDA',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ValidatedP2WDAAddress {
|
|
21
|
+
readonly isValid: boolean;
|
|
22
|
+
readonly isPotentiallyP2WDA: boolean;
|
|
23
|
+
readonly isDefinitelyP2WDA: boolean;
|
|
24
|
+
readonly publicKey?: Buffer;
|
|
25
|
+
readonly error?: string;
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
export class AddressVerificator {
|
|
@@ -63,6 +73,22 @@ export class AddressVerificator {
|
|
|
63
73
|
return isValidSegWitAddress;
|
|
64
74
|
}
|
|
65
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Check if a given witness script is a P2WDA witness script
|
|
78
|
+
*
|
|
79
|
+
* P2WDA witness scripts have a specific pattern:
|
|
80
|
+
* (OP_2DROP * 5) <pubkey> OP_CHECKSIG
|
|
81
|
+
*
|
|
82
|
+
* This pattern allows for 10 witness data fields (5 * 2 = 10),
|
|
83
|
+
* which can be used to embed authenticated operation data.
|
|
84
|
+
*
|
|
85
|
+
* @param witnessScript The witness script to check
|
|
86
|
+
* @returns true if this is a valid P2WDA witness script
|
|
87
|
+
*/
|
|
88
|
+
public static isP2WDAWitnessScript(witnessScript: Buffer): boolean {
|
|
89
|
+
return P2WDADetector.isP2WDAWitnessScript(witnessScript);
|
|
90
|
+
}
|
|
91
|
+
|
|
66
92
|
/**
|
|
67
93
|
* Checks if the given address is a valid P2PKH or P2SH address.
|
|
68
94
|
* @param addy - The address to check.
|
|
@@ -213,4 +239,117 @@ export class AddressVerificator {
|
|
|
213
239
|
|
|
214
240
|
return null; // Not a valid or recognized Bitcoin address type
|
|
215
241
|
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Enhanced detectAddressType that provides hints about P2WDA
|
|
245
|
+
*
|
|
246
|
+
* Note: P2WDA addresses cannot be distinguished from regular P2WSH
|
|
247
|
+
* addresses without the witness script. When a P2WSH address is detected,
|
|
248
|
+
* it could potentially be P2WDA if it has the correct witness script.
|
|
249
|
+
*
|
|
250
|
+
* @param addy The address to analyze
|
|
251
|
+
* @param network The Bitcoin network
|
|
252
|
+
* @param witnessScript Optional witness script for P2WSH addresses
|
|
253
|
+
* @returns The address type, with P2WDA detection if witness script provided
|
|
254
|
+
*/
|
|
255
|
+
public static detectAddressTypeWithWitnessScript(
|
|
256
|
+
addy: string,
|
|
257
|
+
network: Network,
|
|
258
|
+
witnessScript?: Buffer,
|
|
259
|
+
): AddressTypes | null {
|
|
260
|
+
const baseType = AddressVerificator.detectAddressType(addy, network);
|
|
261
|
+
|
|
262
|
+
if (baseType === AddressTypes.P2WSH && witnessScript) {
|
|
263
|
+
if (AddressVerificator.isP2WDAWitnessScript(witnessScript)) {
|
|
264
|
+
return AddressTypes.P2WDA;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return baseType;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Validate a P2WDA address and extract information
|
|
273
|
+
*
|
|
274
|
+
* This method validates that an address is a properly formatted P2WSH
|
|
275
|
+
* address and, if a witness script is provided, verifies it matches
|
|
276
|
+
* the P2WDA pattern and corresponds to the address.
|
|
277
|
+
*
|
|
278
|
+
* @param address The address to validate
|
|
279
|
+
* @param network The Bitcoin network
|
|
280
|
+
* @param witnessScript Optional witness script to verify
|
|
281
|
+
* @returns Validation result with extracted information
|
|
282
|
+
*/
|
|
283
|
+
public static validateP2WDAAddress(
|
|
284
|
+
address: string,
|
|
285
|
+
network: Network,
|
|
286
|
+
witnessScript?: Buffer,
|
|
287
|
+
): ValidatedP2WDAAddress {
|
|
288
|
+
try {
|
|
289
|
+
const addressType = AddressVerificator.detectAddressType(address, network);
|
|
290
|
+
if (addressType !== AddressTypes.P2WSH) {
|
|
291
|
+
return {
|
|
292
|
+
isValid: false,
|
|
293
|
+
isPotentiallyP2WDA: false,
|
|
294
|
+
isDefinitelyP2WDA: false,
|
|
295
|
+
error: 'Not a P2WSH address',
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!witnessScript) {
|
|
300
|
+
return {
|
|
301
|
+
isValid: true,
|
|
302
|
+
isPotentiallyP2WDA: true,
|
|
303
|
+
isDefinitelyP2WDA: false,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (!AddressVerificator.isP2WDAWitnessScript(witnessScript)) {
|
|
308
|
+
return {
|
|
309
|
+
isValid: true,
|
|
310
|
+
isPotentiallyP2WDA: true,
|
|
311
|
+
isDefinitelyP2WDA: false,
|
|
312
|
+
error: 'Witness script does not match P2WDA pattern',
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const p2wsh = payments.p2wsh({
|
|
317
|
+
redeem: { output: witnessScript },
|
|
318
|
+
network,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
if (p2wsh.address !== address) {
|
|
322
|
+
return {
|
|
323
|
+
isValid: false,
|
|
324
|
+
isPotentiallyP2WDA: false,
|
|
325
|
+
isDefinitelyP2WDA: false,
|
|
326
|
+
error: 'Witness script does not match address',
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const publicKey = P2WDADetector.extractPublicKeyFromP2WDA(witnessScript);
|
|
331
|
+
if (!publicKey) {
|
|
332
|
+
return {
|
|
333
|
+
isValid: false,
|
|
334
|
+
isPotentiallyP2WDA: false,
|
|
335
|
+
isDefinitelyP2WDA: false,
|
|
336
|
+
error: 'Failed to extract public key from witness script',
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
isValid: true,
|
|
342
|
+
isPotentiallyP2WDA: true,
|
|
343
|
+
isDefinitelyP2WDA: true,
|
|
344
|
+
publicKey,
|
|
345
|
+
};
|
|
346
|
+
} catch (error) {
|
|
347
|
+
return {
|
|
348
|
+
isValid: false,
|
|
349
|
+
isPotentiallyP2WDA: false,
|
|
350
|
+
isDefinitelyP2WDA: false,
|
|
351
|
+
error: (error as Error).message,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
}
|
|
216
355
|
}
|