@btc-vision/btc-runtime 1.10.8 → 1.10.11
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/LICENSE +190 -0
- package/README.md +258 -137
- package/SECURITY.md +226 -0
- package/docs/README.md +614 -0
- package/docs/advanced/bitcoin-scripts.md +939 -0
- package/docs/advanced/cross-contract-calls.md +579 -0
- package/docs/advanced/plugins.md +1006 -0
- package/docs/advanced/quantum-resistance.md +660 -0
- package/docs/advanced/signature-verification.md +715 -0
- package/docs/api-reference/blockchain.md +729 -0
- package/docs/api-reference/events.md +642 -0
- package/docs/api-reference/op20.md +902 -0
- package/docs/api-reference/op721.md +819 -0
- package/docs/api-reference/safe-math.md +510 -0
- package/docs/api-reference/storage.md +840 -0
- package/docs/contracts/op-net-base.md +786 -0
- package/docs/contracts/op20-token.md +687 -0
- package/docs/contracts/op20s-signatures.md +614 -0
- package/docs/contracts/op721-nft.md +785 -0
- package/docs/contracts/reentrancy-guard.md +787 -0
- package/docs/core-concepts/blockchain-environment.md +724 -0
- package/docs/core-concepts/decorators.md +466 -0
- package/docs/core-concepts/events.md +652 -0
- package/docs/core-concepts/pointers.md +391 -0
- package/docs/core-concepts/security.md +473 -0
- package/docs/core-concepts/storage-system.md +969 -0
- package/docs/examples/basic-token.md +745 -0
- package/docs/examples/nft-with-reservations.md +1440 -0
- package/docs/examples/oracle-integration.md +1212 -0
- package/docs/examples/stablecoin.md +1180 -0
- package/docs/getting-started/first-contract.md +575 -0
- package/docs/getting-started/installation.md +384 -0
- package/docs/getting-started/project-structure.md +630 -0
- package/docs/storage/memory-maps.md +764 -0
- package/docs/storage/stored-arrays.md +778 -0
- package/docs/storage/stored-maps.md +758 -0
- package/docs/storage/stored-primitives.md +655 -0
- package/docs/types/address.md +773 -0
- package/docs/types/bytes-writer-reader.md +938 -0
- package/docs/types/calldata.md +744 -0
- package/docs/types/safe-math.md +446 -0
- package/package.json +52 -27
- package/runtime/memory/MapOfMap.ts +1 -0
- package/LICENSE.md +0 -21
|
@@ -0,0 +1,939 @@
|
|
|
1
|
+
# Bitcoin Scripts
|
|
2
|
+
|
|
3
|
+
OPNet provides utilities for working with Bitcoin scripts, addresses, and timelocks. This enables contracts to interact with Bitcoin's native scripting capabilities.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import {
|
|
9
|
+
Blockchain,
|
|
10
|
+
BitcoinOpcodes,
|
|
11
|
+
BitcoinScript,
|
|
12
|
+
BitcoinAddresses,
|
|
13
|
+
ScriptNumber,
|
|
14
|
+
ScriptIO,
|
|
15
|
+
Segwit,
|
|
16
|
+
Networks,
|
|
17
|
+
Network,
|
|
18
|
+
} from '@btc-vision/btc-runtime/runtime';
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Networks
|
|
22
|
+
|
|
23
|
+
OPNet supports three Bitcoin networks:
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { Networks, Network } from '@btc-vision/btc-runtime/runtime';
|
|
27
|
+
|
|
28
|
+
// Network enum values
|
|
29
|
+
Networks.Unknown // -1 (uninitialized)
|
|
30
|
+
Networks.Mainnet // 0
|
|
31
|
+
Networks.Testnet // 1
|
|
32
|
+
Networks.Regtest // 2
|
|
33
|
+
|
|
34
|
+
// Get human-readable prefix for addresses
|
|
35
|
+
const hrp = Network.hrp(Networks.Mainnet); // "bc"
|
|
36
|
+
const hrpTestnet = Network.hrp(Networks.Testnet); // "tb"
|
|
37
|
+
const hrpRegtest = Network.hrp(Networks.Regtest); // "bcrt"
|
|
38
|
+
|
|
39
|
+
// Get chain ID (32-byte identifier)
|
|
40
|
+
const chainId = Network.getChainId(Networks.Mainnet);
|
|
41
|
+
|
|
42
|
+
// Convert chain ID back to network enum
|
|
43
|
+
const network = Network.fromChainId(chainId); // Networks.Mainnet
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Address Types
|
|
47
|
+
|
|
48
|
+
### P2PKH (Pay to Public Key Hash)
|
|
49
|
+
|
|
50
|
+
```mermaid
|
|
51
|
+
---
|
|
52
|
+
config:
|
|
53
|
+
theme: dark
|
|
54
|
+
---
|
|
55
|
+
flowchart LR
|
|
56
|
+
PK["Public Key<br/>33 bytes compressed"] --> H1["SHA256"]
|
|
57
|
+
H1 --> H2["RIPEMD160"]
|
|
58
|
+
H2 --> PKH["20-byte hash"]
|
|
59
|
+
PKH --> B58["Base58Check Encoding"]
|
|
60
|
+
B58 --> ADDR["Address: 1..."]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### P2SH (Pay to Script Hash)
|
|
64
|
+
|
|
65
|
+
```mermaid
|
|
66
|
+
---
|
|
67
|
+
config:
|
|
68
|
+
theme: dark
|
|
69
|
+
---
|
|
70
|
+
flowchart LR
|
|
71
|
+
SC["Redeem Script"] --> SH1["SHA256"]
|
|
72
|
+
SH1 --> SH2["RIPEMD160"]
|
|
73
|
+
SH2 --> SHH["20-byte hash"]
|
|
74
|
+
SHH --> B58["Base58Check Encoding"]
|
|
75
|
+
B58 --> ADDR["Address: 3..."]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### P2WPKH (Pay to Witness Public Key Hash)
|
|
79
|
+
|
|
80
|
+
```mermaid
|
|
81
|
+
---
|
|
82
|
+
config:
|
|
83
|
+
theme: dark
|
|
84
|
+
---
|
|
85
|
+
flowchart LR
|
|
86
|
+
PK["Public Key<br/>33 bytes"] --> H1["SHA256"]
|
|
87
|
+
H1 --> H2["RIPEMD160"]
|
|
88
|
+
H2 --> WPH["20-byte hash"]
|
|
89
|
+
WPH --> SEG["Bech32 Encoding"]
|
|
90
|
+
SEG --> ADDR["Address: bc1q..."]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { BitcoinAddresses, Segwit } from '@btc-vision/btc-runtime/runtime';
|
|
95
|
+
|
|
96
|
+
// P2WPKH from compressed public key
|
|
97
|
+
public createP2WPKHAddress(pubkey: Uint8Array, hrp: string): string {
|
|
98
|
+
return BitcoinAddresses.p2wpkh(pubkey, hrp);
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### P2TR (Pay to Taproot)
|
|
103
|
+
|
|
104
|
+
```mermaid
|
|
105
|
+
---
|
|
106
|
+
config:
|
|
107
|
+
theme: dark
|
|
108
|
+
---
|
|
109
|
+
flowchart LR
|
|
110
|
+
PK["x-only Public Key<br/>32 bytes"] --> TR["Tweak key"]
|
|
111
|
+
TR --> TRK["Tweaked key<br/>32 bytes"]
|
|
112
|
+
TRK --> SEG["Bech32m Encoding"]
|
|
113
|
+
SEG --> ADDR["Address: bc1p..."]
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { BitcoinAddresses, Networks, Network } from '@btc-vision/btc-runtime/runtime';
|
|
118
|
+
|
|
119
|
+
// P2TR address from 32-byte x-only public key
|
|
120
|
+
public createP2TRAddress(outputKeyX32: Uint8Array): string {
|
|
121
|
+
const hrp = Network.hrp(Networks.Mainnet);
|
|
122
|
+
return BitcoinAddresses.p2trKeyPathAddress(outputKeyX32, hrp);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Verify a P2TR address
|
|
126
|
+
public verifyP2TRAddress(outputKeyX32: Uint8Array, address: string): bool {
|
|
127
|
+
const hrp = Network.hrp(Networks.Mainnet);
|
|
128
|
+
return BitcoinAddresses.verifyP2trAddress(outputKeyX32, address, hrp);
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### P2WSH (Pay to Witness Script Hash)
|
|
133
|
+
|
|
134
|
+
For complex scripts requiring witness script hash:
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import { BitcoinAddresses, Segwit, sha256 } from '@btc-vision/btc-runtime/runtime';
|
|
138
|
+
|
|
139
|
+
// Create P2WSH from script
|
|
140
|
+
public createP2WSHAddress(script: Uint8Array, hrp: string): string {
|
|
141
|
+
return Segwit.p2wsh(hrp, script);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Create multisig P2WSH address
|
|
145
|
+
public createMultisigP2WSH(
|
|
146
|
+
m: i32,
|
|
147
|
+
pubkeys: Array<Uint8Array>,
|
|
148
|
+
hrp: string
|
|
149
|
+
): MultisigP2wshResult {
|
|
150
|
+
return BitcoinAddresses.multisigP2wshAddress(m, pubkeys, hrp);
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Bitcoin Opcodes
|
|
155
|
+
|
|
156
|
+
The `BitcoinOpcodes` class provides all standard Bitcoin opcodes:
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
import { BitcoinOpcodes } from '@btc-vision/btc-runtime/runtime';
|
|
160
|
+
|
|
161
|
+
// Stack operations
|
|
162
|
+
BitcoinOpcodes.OP_DUP // 118 - Duplicate top stack item
|
|
163
|
+
BitcoinOpcodes.OP_DROP // 117 - Remove top stack item
|
|
164
|
+
BitcoinOpcodes.OP_SWAP // 124 - Swap top two items
|
|
165
|
+
|
|
166
|
+
// Crypto operations
|
|
167
|
+
BitcoinOpcodes.OP_HASH160 // 169 - RIPEMD160(SHA256(x))
|
|
168
|
+
BitcoinOpcodes.OP_SHA256 // 168 - SHA256(x)
|
|
169
|
+
BitcoinOpcodes.OP_CHECKSIG // 172 - Verify signature
|
|
170
|
+
BitcoinOpcodes.OP_CHECKMULTISIG // 174 - Verify multiple signatures
|
|
171
|
+
|
|
172
|
+
// Control flow
|
|
173
|
+
BitcoinOpcodes.OP_IF // 99 - Conditional execution
|
|
174
|
+
BitcoinOpcodes.OP_ELSE // 103
|
|
175
|
+
BitcoinOpcodes.OP_ENDIF // 104
|
|
176
|
+
BitcoinOpcodes.OP_VERIFY // 105 - Verify top is truthy
|
|
177
|
+
BitcoinOpcodes.OP_RETURN // 106 - Mark output as unspendable
|
|
178
|
+
|
|
179
|
+
// Timelocks
|
|
180
|
+
BitcoinOpcodes.OP_CHECKLOCKTIMEVERIFY // 177 - nLockTime check
|
|
181
|
+
BitcoinOpcodes.OP_CHECKSEQUENCEVERIFY // 178 - Relative timelock
|
|
182
|
+
|
|
183
|
+
// Number opcodes (OP_0 through OP_16)
|
|
184
|
+
BitcoinOpcodes.OP_0 // 0 - Push empty/zero
|
|
185
|
+
BitcoinOpcodes.OP_1 // 81 - Push 1
|
|
186
|
+
BitcoinOpcodes.OP_2 // 82 - Push 2
|
|
187
|
+
BitcoinOpcodes.OP_3 // 83 - Push 3
|
|
188
|
+
// ... up to OP_16
|
|
189
|
+
|
|
190
|
+
// Get OP_N dynamically
|
|
191
|
+
BitcoinOpcodes.opN(5); // Returns OP_5 (85)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Script Building
|
|
195
|
+
|
|
196
|
+
### Building Scripts with BytesWriter
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import { BytesWriter, BitcoinOpcodes, ScriptIO, ScriptNumber } from '@btc-vision/btc-runtime/runtime';
|
|
200
|
+
|
|
201
|
+
// Build a simple P2PKH-style script
|
|
202
|
+
public buildP2PKHScript(pubkeyHash: Uint8Array): Uint8Array {
|
|
203
|
+
const script = new BytesWriter(25);
|
|
204
|
+
script.writeU8(BitcoinOpcodes.OP_DUP);
|
|
205
|
+
script.writeU8(BitcoinOpcodes.OP_HASH160);
|
|
206
|
+
script.writeU8(20); // Push 20 bytes
|
|
207
|
+
script.writeBytes(pubkeyHash);
|
|
208
|
+
script.writeU8(BitcoinOpcodes.OP_EQUALVERIFY);
|
|
209
|
+
script.writeU8(BitcoinOpcodes.OP_CHECKSIG);
|
|
210
|
+
return script.getBuffer();
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Building Multisig Scripts
|
|
215
|
+
|
|
216
|
+
Use the built-in `BitcoinScript.multisig()` method:
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
import { BitcoinScript } from '@btc-vision/btc-runtime/runtime';
|
|
220
|
+
|
|
221
|
+
// Build a 2-of-3 multisig script
|
|
222
|
+
public buildMultisigScript(
|
|
223
|
+
pubkey1: Uint8Array,
|
|
224
|
+
pubkey2: Uint8Array,
|
|
225
|
+
pubkey3: Uint8Array
|
|
226
|
+
): Uint8Array {
|
|
227
|
+
const pubkeys = new Array<Uint8Array>(3);
|
|
228
|
+
pubkeys[0] = pubkey1;
|
|
229
|
+
pubkeys[1] = pubkey2;
|
|
230
|
+
pubkeys[2] = pubkey3;
|
|
231
|
+
|
|
232
|
+
// m = 2 (required signatures), n = 3 (total keys)
|
|
233
|
+
return BitcoinScript.multisig(2, pubkeys);
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Manual multisig script building:
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
public buildMultisigManual(
|
|
241
|
+
pubkey1: Uint8Array,
|
|
242
|
+
pubkey2: Uint8Array,
|
|
243
|
+
pubkey3: Uint8Array
|
|
244
|
+
): Uint8Array {
|
|
245
|
+
const script = new BytesWriter(105);
|
|
246
|
+
|
|
247
|
+
// OP_2 - require 2 signatures
|
|
248
|
+
script.writeU8(BitcoinOpcodes.OP_2);
|
|
249
|
+
|
|
250
|
+
// Push pubkey1 (33 bytes compressed)
|
|
251
|
+
script.writeU8(33);
|
|
252
|
+
script.writeBytes(pubkey1);
|
|
253
|
+
|
|
254
|
+
// Push pubkey2
|
|
255
|
+
script.writeU8(33);
|
|
256
|
+
script.writeBytes(pubkey2);
|
|
257
|
+
|
|
258
|
+
// Push pubkey3
|
|
259
|
+
script.writeU8(33);
|
|
260
|
+
script.writeBytes(pubkey3);
|
|
261
|
+
|
|
262
|
+
// OP_3 - 3 total pubkeys
|
|
263
|
+
script.writeU8(BitcoinOpcodes.OP_3);
|
|
264
|
+
|
|
265
|
+
// OP_CHECKMULTISIG
|
|
266
|
+
script.writeU8(BitcoinOpcodes.OP_CHECKMULTISIG);
|
|
267
|
+
|
|
268
|
+
return script.getBuffer();
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Timelock Scripts
|
|
273
|
+
|
|
274
|
+
### CSV (CheckSequenceVerify) - Relative Timelock
|
|
275
|
+
|
|
276
|
+
CSV enables relative timelocks based on block count or time since the UTXO was confirmed:
|
|
277
|
+
|
|
278
|
+
```mermaid
|
|
279
|
+
---
|
|
280
|
+
config:
|
|
281
|
+
theme: dark
|
|
282
|
+
---
|
|
283
|
+
flowchart LR
|
|
284
|
+
CSV1["Transaction Input<br/>nSequence field"] --> CSV2{"Timelock Type?"}
|
|
285
|
+
CSV2 -->|"Block-based"| CSV3["Blocks since confirmation"]
|
|
286
|
+
CSV2 -->|"Time-based"| CSV4["512-second units + FLAG"]
|
|
287
|
+
CSV3 --> CSV5["OP_CSV verifies:<br/>nSequence >= CSV_VALUE"]
|
|
288
|
+
CSV4 --> CSV5
|
|
289
|
+
CSV5 --> CSV6{"Valid?"}
|
|
290
|
+
CSV6 -->|"Yes"| CSV7["Continue execution"]
|
|
291
|
+
CSV6 -->|"No"| CSV8["Transaction invalid"]
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
OPNet provides a built-in method for CSV scripts:
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
import { BitcoinScript, BitcoinAddresses, Network, Networks } from '@btc-vision/btc-runtime/runtime';
|
|
298
|
+
|
|
299
|
+
// Create a CSV timelock script (144 blocks = ~1 day)
|
|
300
|
+
public createCSVScript(pubkey: Uint8Array, lockBlocks: i32): Uint8Array {
|
|
301
|
+
// lockBlocks must be 0-65535
|
|
302
|
+
return BitcoinScript.csvTimelock(pubkey, lockBlocks);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Create a P2WSH address with CSV timelock
|
|
306
|
+
public createCSVAddress(pubkey: Uint8Array, lockBlocks: i32): CsvP2wshResult {
|
|
307
|
+
const hrp = Network.hrp(Networks.Mainnet);
|
|
308
|
+
return BitcoinAddresses.csvP2wshAddress(pubkey, lockBlocks, hrp);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Verify a CSV P2WSH address
|
|
312
|
+
public verifyCSVAddress(
|
|
313
|
+
pubkey: Uint8Array,
|
|
314
|
+
lockBlocks: i32,
|
|
315
|
+
address: string
|
|
316
|
+
): bool {
|
|
317
|
+
const hrp = Network.hrp(Networks.Mainnet);
|
|
318
|
+
return BitcoinAddresses.verifyCsvP2wshAddress(pubkey, lockBlocks, address, hrp);
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Manual CSV Script Building
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
import { BytesWriter, BitcoinOpcodes, ScriptNumber, ScriptIO } from '@btc-vision/btc-runtime/runtime';
|
|
326
|
+
|
|
327
|
+
public buildCSVScriptManual(pubkey: Uint8Array, lockBlocks: i32): Uint8Array {
|
|
328
|
+
const script = new BytesWriter(50);
|
|
329
|
+
|
|
330
|
+
// Push lock value using proper encoding
|
|
331
|
+
if (lockBlocks == 0) {
|
|
332
|
+
script.writeU8(BitcoinOpcodes.OP_0);
|
|
333
|
+
} else if (lockBlocks <= 16) {
|
|
334
|
+
// Use OP_1 through OP_16
|
|
335
|
+
script.writeU8(BitcoinOpcodes.opN(lockBlocks));
|
|
336
|
+
} else {
|
|
337
|
+
// Encode as script number and push
|
|
338
|
+
const encoded = ScriptNumber.encode(lockBlocks);
|
|
339
|
+
ScriptIO.writePush(script, encoded);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// CSV check
|
|
343
|
+
script.writeU8(BitcoinOpcodes.OP_CHECKSEQUENCEVERIFY);
|
|
344
|
+
script.writeU8(BitcoinOpcodes.OP_DROP);
|
|
345
|
+
|
|
346
|
+
// Then normal sig check
|
|
347
|
+
ScriptIO.writePush(script, pubkey);
|
|
348
|
+
script.writeU8(BitcoinOpcodes.OP_CHECKSIG);
|
|
349
|
+
|
|
350
|
+
return script.getBuffer();
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Time-Based Timelock
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
// Lock for ~1 week (in 512-second units)
|
|
358
|
+
// Bit 22 set indicates time-based
|
|
359
|
+
const TIME_FLAG: u32 = 0x400000;
|
|
360
|
+
const SECONDS_PER_UNIT: u32 = 512;
|
|
361
|
+
|
|
362
|
+
public buildTimeCSV(pubkey: Uint8Array, seconds: u32): Uint8Array {
|
|
363
|
+
const units = seconds / SECONDS_PER_UNIT;
|
|
364
|
+
const csvValue = TIME_FLAG | units;
|
|
365
|
+
|
|
366
|
+
const script = new BytesWriter(50);
|
|
367
|
+
|
|
368
|
+
// Encode the time-based CSV value
|
|
369
|
+
const encoded = ScriptNumber.encode(csvValue);
|
|
370
|
+
ScriptIO.writePush(script, encoded);
|
|
371
|
+
|
|
372
|
+
script.writeU8(BitcoinOpcodes.OP_CHECKSEQUENCEVERIFY);
|
|
373
|
+
script.writeU8(BitcoinOpcodes.OP_DROP);
|
|
374
|
+
ScriptIO.writePush(script, pubkey);
|
|
375
|
+
script.writeU8(BitcoinOpcodes.OP_CHECKSIG);
|
|
376
|
+
|
|
377
|
+
return script.getBuffer();
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### CLTV (CheckLockTimeVerify) - Absolute Timelock
|
|
382
|
+
|
|
383
|
+
CLTV enables absolute timelocks based on block height or Unix timestamp:
|
|
384
|
+
|
|
385
|
+
```mermaid
|
|
386
|
+
---
|
|
387
|
+
config:
|
|
388
|
+
theme: dark
|
|
389
|
+
---
|
|
390
|
+
flowchart LR
|
|
391
|
+
CLTV1["Transaction<br/>nLockTime field"] --> CLTV2{"Timelock Type?"}
|
|
392
|
+
CLTV2 -->|"Block-based"| CLTV3["Block height<br/>Value < 500000000"]
|
|
393
|
+
CLTV2 -->|"Time-based"| CLTV4["Unix timestamp<br/>Value >= 500000000"]
|
|
394
|
+
CLTV3 --> CLTV5["OP_CLTV verifies:<br/>nLockTime >= CLTV_VALUE"]
|
|
395
|
+
CLTV4 --> CLTV5
|
|
396
|
+
CLTV5 --> CLTV6{"Valid?"}
|
|
397
|
+
CLTV6 -->|"Yes"| CLTV7["Continue execution"]
|
|
398
|
+
CLTV6 -->|"No"| CLTV8["Transaction invalid"]
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## Script Number Encoding
|
|
402
|
+
|
|
403
|
+
Bitcoin Script uses a unique number encoding format:
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
import { ScriptNumber } from '@btc-vision/btc-runtime/runtime';
|
|
407
|
+
|
|
408
|
+
// Encode a number for script
|
|
409
|
+
const encoded: Uint8Array = ScriptNumber.encode(144);
|
|
410
|
+
|
|
411
|
+
// Get encoded length without encoding
|
|
412
|
+
const len: i32 = ScriptNumber.encodedLen(144);
|
|
413
|
+
|
|
414
|
+
// Decode a script number
|
|
415
|
+
const decoded: i64 = ScriptNumber.decode(encoded);
|
|
416
|
+
|
|
417
|
+
// Safe decoding with result type
|
|
418
|
+
const result = ScriptNumber.decodeResult(encoded, true);
|
|
419
|
+
if (result.success) {
|
|
420
|
+
const value = result.value;
|
|
421
|
+
} else {
|
|
422
|
+
const error = result.error;
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
## Script Recognition
|
|
427
|
+
|
|
428
|
+
OPNet can parse and recognize common script patterns:
|
|
429
|
+
|
|
430
|
+
### Recognize CSV Timelock
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
import { BitcoinScript } from '@btc-vision/btc-runtime/runtime';
|
|
434
|
+
|
|
435
|
+
public parseCSVScript(script: Uint8Array): void {
|
|
436
|
+
const result = BitcoinScript.recognizeCsvTimelock(script);
|
|
437
|
+
|
|
438
|
+
if (result.ok) {
|
|
439
|
+
const csvBlocks: i64 = result.csvBlocks;
|
|
440
|
+
const pubkey: Uint8Array | null = result.pubkey;
|
|
441
|
+
// Process recognized script
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Recognize Multisig
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
public parseMultisigScript(script: Uint8Array): void {
|
|
450
|
+
const result = BitcoinScript.recognizeMultisig(script);
|
|
451
|
+
|
|
452
|
+
if (result.ok) {
|
|
453
|
+
const m: i32 = result.m; // Required signatures
|
|
454
|
+
const n: i32 = result.n; // Total keys
|
|
455
|
+
const keys: Array<Uint8Array> | null = result.pubkeys;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
## Transaction Parsing
|
|
461
|
+
|
|
462
|
+
### Reading Transaction Outputs
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
// Access current transaction outputs
|
|
466
|
+
public analyzeOutputs(): void {
|
|
467
|
+
const outputs = Blockchain.tx.outputs;
|
|
468
|
+
|
|
469
|
+
for (let i = 0; i < outputs.length; i++) {
|
|
470
|
+
const output = outputs[i];
|
|
471
|
+
|
|
472
|
+
// Output value in satoshis
|
|
473
|
+
const value: u64 = output.value;
|
|
474
|
+
|
|
475
|
+
// Output script (may be null)
|
|
476
|
+
const script: Uint8Array | null = output.scriptPublicKey;
|
|
477
|
+
|
|
478
|
+
// Check if output has script before parsing
|
|
479
|
+
if (script !== null) {
|
|
480
|
+
// Parse script type
|
|
481
|
+
if (this.isP2TR(script)) {
|
|
482
|
+
// Taproot output
|
|
483
|
+
} else if (this.isP2WSH(script)) {
|
|
484
|
+
// Witness script hash
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
private isP2TR(script: Uint8Array): bool {
|
|
491
|
+
// P2TR: OP_1 <32-byte key>
|
|
492
|
+
return script.length == 34 && script[0] == 0x51 && script[1] == 0x20;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
private isP2WSH(script: Uint8Array): bool {
|
|
496
|
+
// P2WSH: OP_0 <32-byte hash>
|
|
497
|
+
return script.length == 34 && script[0] == 0x00 && script[1] == 0x20;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
private isP2WPKH(script: Uint8Array): bool {
|
|
501
|
+
// P2WPKH: OP_0 <20-byte hash>
|
|
502
|
+
return script.length == 22 && script[0] == 0x00 && script[1] == 0x14;
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### Parsing Inputs
|
|
507
|
+
|
|
508
|
+
```typescript
|
|
509
|
+
// Access transaction inputs
|
|
510
|
+
public analyzeInputs(): void {
|
|
511
|
+
const inputs = Blockchain.tx.inputs;
|
|
512
|
+
|
|
513
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
514
|
+
const input = inputs[i];
|
|
515
|
+
|
|
516
|
+
// Previous transaction hash
|
|
517
|
+
const txId: Uint8Array = input.txId;
|
|
518
|
+
|
|
519
|
+
// Output index being spent (u16)
|
|
520
|
+
const outputIndex: u16 = input.outputIndex;
|
|
521
|
+
|
|
522
|
+
// Script signature
|
|
523
|
+
const scriptSig: Uint8Array = input.scriptSig;
|
|
524
|
+
|
|
525
|
+
// Witness data (may be null)
|
|
526
|
+
const witnesses: Uint8Array[] | null = input.witnesses;
|
|
527
|
+
|
|
528
|
+
// Check if input has witnesses
|
|
529
|
+
if (input.hasWitnesses && witnesses !== null) {
|
|
530
|
+
// Process witness data
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Check if this is a coinbase input
|
|
534
|
+
if (input.isCoinbase) {
|
|
535
|
+
// This is a coinbase transaction
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
## Common Patterns
|
|
542
|
+
|
|
543
|
+
### Escrow with CSV Timeout
|
|
544
|
+
|
|
545
|
+
```typescript
|
|
546
|
+
// Build escrow script: Either both parties agree, or timeout to sender
|
|
547
|
+
public buildEscrowScript(
|
|
548
|
+
senderPubkey: Uint8Array,
|
|
549
|
+
recipientPubkey: Uint8Array,
|
|
550
|
+
timeoutBlocks: i32
|
|
551
|
+
): Uint8Array {
|
|
552
|
+
const script = new BytesWriter(150);
|
|
553
|
+
|
|
554
|
+
// Path 1: Both signatures
|
|
555
|
+
script.writeU8(BitcoinOpcodes.OP_IF);
|
|
556
|
+
script.writeU8(BitcoinOpcodes.OP_2);
|
|
557
|
+
script.writeU8(33);
|
|
558
|
+
script.writeBytes(senderPubkey);
|
|
559
|
+
script.writeU8(33);
|
|
560
|
+
script.writeBytes(recipientPubkey);
|
|
561
|
+
script.writeU8(BitcoinOpcodes.OP_2);
|
|
562
|
+
script.writeU8(BitcoinOpcodes.OP_CHECKMULTISIG);
|
|
563
|
+
|
|
564
|
+
// Path 2: Timeout to sender
|
|
565
|
+
script.writeU8(BitcoinOpcodes.OP_ELSE);
|
|
566
|
+
|
|
567
|
+
// Push CSV blocks
|
|
568
|
+
if (timeoutBlocks <= 16) {
|
|
569
|
+
script.writeU8(BitcoinOpcodes.opN(timeoutBlocks));
|
|
570
|
+
} else {
|
|
571
|
+
const encoded = ScriptNumber.encode(timeoutBlocks);
|
|
572
|
+
ScriptIO.writePush(script, encoded);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
script.writeU8(BitcoinOpcodes.OP_CHECKSEQUENCEVERIFY);
|
|
576
|
+
script.writeU8(BitcoinOpcodes.OP_DROP);
|
|
577
|
+
script.writeU8(33);
|
|
578
|
+
script.writeBytes(senderPubkey);
|
|
579
|
+
script.writeU8(BitcoinOpcodes.OP_CHECKSIG);
|
|
580
|
+
script.writeU8(BitcoinOpcodes.OP_ENDIF);
|
|
581
|
+
|
|
582
|
+
return script.getBuffer();
|
|
583
|
+
}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### Verifying Output to Contract
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
// Verify transaction sends to this contract
|
|
590
|
+
public verifyPaymentToContract(requiredAmount: u64): bool {
|
|
591
|
+
const contractAddress = Blockchain.contract.address;
|
|
592
|
+
const outputs = Blockchain.tx.outputs;
|
|
593
|
+
|
|
594
|
+
for (let i = 0; i < outputs.length; i++) {
|
|
595
|
+
const output = outputs[i];
|
|
596
|
+
|
|
597
|
+
if (output.value >= requiredAmount) {
|
|
598
|
+
// Check if output is to contract address
|
|
599
|
+
if (this.outputMatchesAddress(output.script, contractAddress)) {
|
|
600
|
+
return true;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
private outputMatchesAddress(script: Uint8Array, address: Address): bool {
|
|
609
|
+
// Implementation depends on script type
|
|
610
|
+
// For P2TR, compare the 32-byte key in script[2..34] with address bytes
|
|
611
|
+
if (script.length == 34 && script[0] == 0x51 && script[1] == 0x20) {
|
|
612
|
+
for (let i: i32 = 0; i < 32; i++) {
|
|
613
|
+
if (script[i + 2] != address[i]) return false;
|
|
614
|
+
}
|
|
615
|
+
return true;
|
|
616
|
+
}
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
### Create OP_RETURN Data
|
|
622
|
+
|
|
623
|
+
```typescript
|
|
624
|
+
// Embed data in an OP_RETURN output
|
|
625
|
+
public buildOpReturnScript(data: Uint8Array): Uint8Array {
|
|
626
|
+
if (data.length > 80) {
|
|
627
|
+
throw new Revert('OP_RETURN data too large');
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const script = new BytesWriter(data.length + 2);
|
|
631
|
+
script.writeU8(BitcoinOpcodes.OP_RETURN);
|
|
632
|
+
script.writeU8(u8(data.length));
|
|
633
|
+
script.writeBytes(data);
|
|
634
|
+
|
|
635
|
+
return script.getBuffer();
|
|
636
|
+
}
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
## Solidity vs OPNet: Bitcoin Scripts Comparison
|
|
640
|
+
|
|
641
|
+
Bitcoin scripting is fundamentally different from Solidity, as it operates on UTXOs rather than account balances. OPNet uniquely bridges smart contract functionality with native Bitcoin scripting capabilities.
|
|
642
|
+
|
|
643
|
+
### Feature Comparison Table
|
|
644
|
+
|
|
645
|
+
| Feature | Solidity/EVM | OPNet | OPNet Advantage |
|
|
646
|
+
|---------|--------------|-------|-----------------|
|
|
647
|
+
| **Bitcoin Script Support** | Not supported | Full support via `BitcoinOpcodes` | Native Bitcoin integration |
|
|
648
|
+
| **Address Types** | Single type (20 bytes) | P2PKH, P2SH, P2WPKH, P2WSH, P2TR | Full Bitcoin address compatibility |
|
|
649
|
+
| **Native Timelocks** | Custom implementation required | OP_CLTV, OP_CSV built-in | Consensus-enforced timelocks |
|
|
650
|
+
| **Multi-signature** | Custom contract logic | Native OP_CHECKMULTISIG | Bitcoin-native security |
|
|
651
|
+
| **Script Execution Model** | Turing-complete EVM | Stack-based Bitcoin Script | Predictable, secure execution |
|
|
652
|
+
| **Data Embedding** | Events, storage (expensive) | OP_RETURN (80 bytes) | Immutable on-chain data |
|
|
653
|
+
| **Taproot Support** | Not applicable | Full P2TR support | Schnorr-based privacy |
|
|
654
|
+
| **Witness Scripts** | Not applicable | P2WSH, SegWit support | Lower transaction fees |
|
|
655
|
+
| **Network Awareness** | Chain ID only | Mainnet/Testnet/Regtest support | Full Bitcoin network support |
|
|
656
|
+
| **UTXO Introspection** | Not possible | Full transaction input/output access | Bitcoin transaction analysis |
|
|
657
|
+
|
|
658
|
+
### Capability Matrix
|
|
659
|
+
|
|
660
|
+
| Capability | Solidity | OPNet |
|
|
661
|
+
|------------|:--------:|:-----:|
|
|
662
|
+
| Create P2PKH addresses | No | Yes |
|
|
663
|
+
| Create P2SH addresses | No | Yes |
|
|
664
|
+
| Create P2WPKH (SegWit) addresses | No | Yes |
|
|
665
|
+
| Create P2WSH (Witness Script) addresses | No | Yes |
|
|
666
|
+
| Create P2TR (Taproot) addresses | No | Yes |
|
|
667
|
+
| Build multisig scripts | No | Yes |
|
|
668
|
+
| Build timelock scripts (CSV) | No | Yes |
|
|
669
|
+
| Build absolute timelock scripts (CLTV) | No | Yes |
|
|
670
|
+
| Parse/recognize Bitcoin scripts | No | Yes |
|
|
671
|
+
| Access transaction inputs | No | Yes |
|
|
672
|
+
| Access transaction outputs | No | Yes |
|
|
673
|
+
| Verify Bitcoin script patterns | No | Yes |
|
|
674
|
+
| Create OP_RETURN data | No | Yes |
|
|
675
|
+
|
|
676
|
+
### Timelock Comparison
|
|
677
|
+
|
|
678
|
+
#### Solidity Approach (Custom Implementation)
|
|
679
|
+
|
|
680
|
+
```solidity
|
|
681
|
+
// Solidity - time-based lock (requires custom implementation)
|
|
682
|
+
contract TimeLock {
|
|
683
|
+
uint256 public unlockTime;
|
|
684
|
+
mapping(address => uint256) public deposits;
|
|
685
|
+
|
|
686
|
+
constructor(uint256 _lockDuration) {
|
|
687
|
+
unlockTime = block.timestamp + _lockDuration;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function deposit() external payable {
|
|
691
|
+
deposits[msg.sender] += msg.value;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function withdraw() external {
|
|
695
|
+
require(block.timestamp >= unlockTime, "Still locked");
|
|
696
|
+
uint256 amount = deposits[msg.sender];
|
|
697
|
+
deposits[msg.sender] = 0;
|
|
698
|
+
payable(msg.sender).transfer(amount);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// Limitations:
|
|
702
|
+
// - Relies on block.timestamp (can be manipulated by miners)
|
|
703
|
+
// - No relative timelocks
|
|
704
|
+
// - Must implement custom logic
|
|
705
|
+
// - No Bitcoin script compatibility
|
|
706
|
+
}
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
#### OPNet Approach (Consensus-Enforced)
|
|
710
|
+
|
|
711
|
+
```typescript
|
|
712
|
+
// OPNet - CSV relative timelock (consensus-enforced)
|
|
713
|
+
import { BitcoinScript, BitcoinAddresses, Network, Networks } from '@btc-vision/btc-runtime/runtime';
|
|
714
|
+
|
|
715
|
+
// Create a timelock script - 144 blocks = ~1 day
|
|
716
|
+
const csvScript = BitcoinScript.csvTimelock(pubkey, 144);
|
|
717
|
+
|
|
718
|
+
// Create P2WSH address with CSV timelock
|
|
719
|
+
const result = BitcoinAddresses.csvP2wshAddress(pubkey, 144, Network.hrp(Networks.Mainnet));
|
|
720
|
+
const address = result.address;
|
|
721
|
+
const witnessScript = result.witnessScript;
|
|
722
|
+
|
|
723
|
+
// Advantages:
|
|
724
|
+
// - Enforced by Bitcoin consensus (not contract logic)
|
|
725
|
+
// - Cannot be manipulated by miners
|
|
726
|
+
// - Supports both relative (CSV) and absolute (CLTV) timelocks
|
|
727
|
+
// - Native to Bitcoin - no custom implementation needed
|
|
728
|
+
// - Works with any Bitcoin wallet
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
### Multisig Comparison
|
|
732
|
+
|
|
733
|
+
#### Solidity Approach (Custom Contract)
|
|
734
|
+
|
|
735
|
+
```solidity
|
|
736
|
+
// Solidity - custom multisig (complex, resource-intensive)
|
|
737
|
+
contract MultiSig {
|
|
738
|
+
mapping(address => bool) public owners;
|
|
739
|
+
uint256 public required;
|
|
740
|
+
uint256 public transactionCount;
|
|
741
|
+
|
|
742
|
+
struct Transaction {
|
|
743
|
+
address to;
|
|
744
|
+
uint256 value;
|
|
745
|
+
bytes data;
|
|
746
|
+
bool executed;
|
|
747
|
+
uint256 confirmations;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
mapping(uint256 => Transaction) public transactions;
|
|
751
|
+
mapping(uint256 => mapping(address => bool)) public confirmations;
|
|
752
|
+
|
|
753
|
+
function submitTransaction(address to, uint256 value, bytes memory data) public returns (uint256) {
|
|
754
|
+
// ... complex submission logic
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function confirmTransaction(uint256 transactionId) public {
|
|
758
|
+
require(owners[msg.sender], "Not owner");
|
|
759
|
+
confirmations[transactionId][msg.sender] = true;
|
|
760
|
+
// ... confirmation logic
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function executeTransaction(uint256 transactionId) public {
|
|
764
|
+
Transaction storage txn = transactions[transactionId];
|
|
765
|
+
require(txn.confirmations >= required, "Not enough confirmations");
|
|
766
|
+
// ... execution logic
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// Limitations:
|
|
770
|
+
// - High storage costs
|
|
771
|
+
// - Complex signature verification
|
|
772
|
+
// - Must manually count confirmations
|
|
773
|
+
// - No native Bitcoin integration
|
|
774
|
+
}
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
#### OPNet Approach (Native Bitcoin Multisig)
|
|
778
|
+
|
|
779
|
+
```typescript
|
|
780
|
+
// OPNet - native Bitcoin multisig (simple, consensus-enforced)
|
|
781
|
+
import { BitcoinScript, BitcoinAddresses, Network, Networks } from '@btc-vision/btc-runtime/runtime';
|
|
782
|
+
|
|
783
|
+
// Build a 2-of-3 multisig script - one line of code!
|
|
784
|
+
const multisigScript = BitcoinScript.multisig(2, [pubkey1, pubkey2, pubkey3]);
|
|
785
|
+
|
|
786
|
+
// Create P2WSH address for the multisig
|
|
787
|
+
const result = BitcoinAddresses.multisigP2wshAddress(2, [pubkey1, pubkey2, pubkey3], Network.hrp(Networks.Mainnet));
|
|
788
|
+
const address = result.address;
|
|
789
|
+
|
|
790
|
+
// Verify and parse existing multisig scripts
|
|
791
|
+
const recognized = BitcoinScript.recognizeMultisig(someScript);
|
|
792
|
+
if (recognized.ok) {
|
|
793
|
+
const requiredSigs = recognized.m; // e.g., 2
|
|
794
|
+
const totalKeys = recognized.n; // e.g., 3
|
|
795
|
+
const publicKeys = recognized.pubkeys;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// Advantages:
|
|
799
|
+
// - No custom signature verification needed
|
|
800
|
+
// - Bitcoin consensus handles signature counting
|
|
801
|
+
// - Works with any Bitcoin wallet supporting multisig
|
|
802
|
+
// - No storage costs for signature storage
|
|
803
|
+
// - Native OP_CHECKMULTISIG opcode
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
### Script Building Comparison
|
|
807
|
+
|
|
808
|
+
| Task | Solidity | OPNet |
|
|
809
|
+
|------|----------|-------|
|
|
810
|
+
| Build P2PKH script | Not possible | `buildP2PKHScript(pubkeyHash)` |
|
|
811
|
+
| Build multisig script | Custom contract | `BitcoinScript.multisig(m, pubkeys)` |
|
|
812
|
+
| Build CSV timelock | Custom logic | `BitcoinScript.csvTimelock(pubkey, blocks)` |
|
|
813
|
+
| Create witness address | Not possible | `Segwit.p2wsh(hrp, script)` |
|
|
814
|
+
| Parse script patterns | Not possible | `BitcoinScript.recognizeCsvTimelock(script)` |
|
|
815
|
+
| Encode script numbers | Not needed | `ScriptNumber.encode(value)` |
|
|
816
|
+
| Access Bitcoin opcodes | Not available | All opcodes via `BitcoinOpcodes` |
|
|
817
|
+
|
|
818
|
+
### Transaction Introspection
|
|
819
|
+
|
|
820
|
+
| Feature | Solidity | OPNet |
|
|
821
|
+
|---------|----------|-------|
|
|
822
|
+
| Access tx outputs | `msg.value` only | `Blockchain.tx.outputs` (full array) |
|
|
823
|
+
| Access tx inputs | Not possible | `Blockchain.tx.inputs` (full array) |
|
|
824
|
+
| Get output value | Limited | `output.value` (satoshis) |
|
|
825
|
+
| Get output script | Not possible | `output.scriptPublicKey` (full script bytes, nullable) |
|
|
826
|
+
| Get input txid | Not possible | `input.txId` |
|
|
827
|
+
| Get input output index | Not possible | `input.outputIndex` (u16) |
|
|
828
|
+
| Get witness data | Not possible | `input.witnesses` (array, nullable) |
|
|
829
|
+
| Get script signature | Not possible | `input.scriptSig` |
|
|
830
|
+
| Check coinbase input | Not possible | `input.isCoinbase` |
|
|
831
|
+
| Parse script type | Not possible | Pattern matching on script bytes |
|
|
832
|
+
|
|
833
|
+
### Data Embedding Comparison
|
|
834
|
+
|
|
835
|
+
```solidity
|
|
836
|
+
// Solidity - Events (expensive, not part of state)
|
|
837
|
+
contract DataEmbed {
|
|
838
|
+
event DataStored(bytes32 indexed hash, bytes data);
|
|
839
|
+
|
|
840
|
+
function storeData(bytes memory data) external {
|
|
841
|
+
emit DataStored(keccak256(data), data);
|
|
842
|
+
// Cost scales with data size
|
|
843
|
+
// Data is not part of UTXO set
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
```typescript
|
|
849
|
+
// OPNet - OP_RETURN (native Bitcoin, permanent)
|
|
850
|
+
import { BytesWriter, BitcoinOpcodes } from '@btc-vision/btc-runtime/runtime';
|
|
851
|
+
|
|
852
|
+
function buildOpReturnScript(data: Uint8Array): Uint8Array {
|
|
853
|
+
if (data.length > 80) {
|
|
854
|
+
throw new Revert('OP_RETURN data too large');
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
const script = new BytesWriter(data.length + 2);
|
|
858
|
+
script.writeU8(BitcoinOpcodes.OP_RETURN);
|
|
859
|
+
script.writeU8(u8(data.length));
|
|
860
|
+
script.writeBytes(data);
|
|
861
|
+
|
|
862
|
+
return script.getBuffer();
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// Advantages:
|
|
866
|
+
// - Standard Bitcoin OP_RETURN
|
|
867
|
+
// - Permanent, immutable storage
|
|
868
|
+
// - Recognized by all Bitcoin explorers
|
|
869
|
+
// - No complex event parsing needed
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
### Why OPNet for Bitcoin Integration?
|
|
873
|
+
|
|
874
|
+
| Solidity Limitation | OPNet Solution |
|
|
875
|
+
|---------------------|----------------|
|
|
876
|
+
| Cannot interact with Bitcoin | Full Bitcoin script support |
|
|
877
|
+
| No UTXO awareness | Complete transaction introspection |
|
|
878
|
+
| Single address format | All Bitcoin address types |
|
|
879
|
+
| No native timelocks | CSV and CLTV support |
|
|
880
|
+
| Custom multisig required | Native OP_CHECKMULTISIG |
|
|
881
|
+
| No Taproot support | Full P2TR integration |
|
|
882
|
+
| EVM-only execution | Bitcoin consensus enforcement |
|
|
883
|
+
|
|
884
|
+
## Best Practices
|
|
885
|
+
|
|
886
|
+
### 1. Validate All Script Inputs
|
|
887
|
+
|
|
888
|
+
```typescript
|
|
889
|
+
public processScript(script: Uint8Array): void {
|
|
890
|
+
// Check minimum length
|
|
891
|
+
if (script.length < 1) {
|
|
892
|
+
throw new Revert('Empty script');
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// Check maximum length
|
|
896
|
+
if (script.length > 10000) {
|
|
897
|
+
throw new Revert('Script too large');
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// Validate script structure
|
|
901
|
+
// ...
|
|
902
|
+
}
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
### 2. Use Named Opcode Constants
|
|
906
|
+
|
|
907
|
+
```typescript
|
|
908
|
+
// Good: Named constants from BitcoinOpcodes
|
|
909
|
+
if (script[0] == BitcoinOpcodes.OP_RETURN) { }
|
|
910
|
+
|
|
911
|
+
// Bad: Magic numbers
|
|
912
|
+
if (script[0] == 0x6a) { }
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
### 3. Use Networks Enum for Network Selection
|
|
916
|
+
|
|
917
|
+
```typescript
|
|
918
|
+
// Good: Use Networks enum
|
|
919
|
+
const hrp = Network.hrp(Networks.Mainnet);
|
|
920
|
+
|
|
921
|
+
// Bad: Hardcoded strings
|
|
922
|
+
const hrp = 'bc';
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
### 4. Use Built-in Script Builders
|
|
926
|
+
|
|
927
|
+
```typescript
|
|
928
|
+
// Good: Use BitcoinScript methods
|
|
929
|
+
const csvScript = BitcoinScript.csvTimelock(pubkey, 144);
|
|
930
|
+
const multisigScript = BitcoinScript.multisig(2, pubkeys);
|
|
931
|
+
|
|
932
|
+
// Only build manually when needed for custom scripts
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
---
|
|
936
|
+
|
|
937
|
+
**Navigation:**
|
|
938
|
+
- Previous: [Quantum Resistance](./quantum-resistance.md)
|
|
939
|
+
- Next: [Plugins](./plugins.md)
|