stxer 0.6.1 → 0.8.0
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/CHANGELOG.md +118 -0
- package/README.md +300 -55
- package/dist/ast.d.ts +1 -1
- package/dist/batch-api.d.ts +2 -1
- package/dist/bitcoin.d.ts +121 -0
- package/dist/index.d.ts +3 -1
- package/dist/simulation-api.d.ts +52 -3
- package/dist/simulation.d.ts +70 -3
- package/dist/stxer.cjs.development.js +917 -104
- package/dist/stxer.cjs.development.js.map +1 -1
- package/dist/stxer.cjs.production.min.js +1 -1
- package/dist/stxer.cjs.production.min.js.map +1 -1
- package/dist/stxer.esm.js +897 -107
- package/dist/stxer.esm.js.map +1 -1
- package/dist/transaction.d.ts +71 -0
- package/dist/types.d.ts +547 -54
- package/package.json +17 -10
- package/src/ast.ts +1 -1
- package/src/batch-api.ts +9 -8
- package/src/bitcoin.ts +391 -0
- package/src/index.ts +7 -10
- package/src/simulation-api.ts +110 -13
- package/src/simulation.ts +245 -46
- package/src/transaction.ts +141 -0
- package/src/types.ts +594 -59
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stxer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Kyle Fang",
|
|
6
6
|
"repository": {
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"dist",
|
|
15
15
|
"src",
|
|
16
16
|
"!src/sample",
|
|
17
|
-
"!dist/sample"
|
|
17
|
+
"!dist/sample",
|
|
18
|
+
"CHANGELOG.md"
|
|
18
19
|
],
|
|
19
20
|
"husky": {
|
|
20
21
|
"hooks": {
|
|
@@ -44,21 +45,22 @@
|
|
|
44
45
|
}
|
|
45
46
|
],
|
|
46
47
|
"devDependencies": {
|
|
47
|
-
"@biomejs/biome": "
|
|
48
|
-
"@size-limit/preset-small-lib": "
|
|
48
|
+
"@biomejs/biome": "2.4.14",
|
|
49
|
+
"@size-limit/preset-small-lib": "12.1.0",
|
|
49
50
|
"@tsconfig/recommended": "^1.0.13",
|
|
50
|
-
"@types/node": "
|
|
51
|
+
"@types/node": "25.6.0",
|
|
51
52
|
"dts-cli": "^2.0.5",
|
|
52
53
|
"husky": "^9.1.7",
|
|
53
|
-
"size-limit": "
|
|
54
|
-
"tslib": "^2.8.1",
|
|
54
|
+
"size-limit": "12.1.0",
|
|
55
55
|
"tsx": "^4.21.0",
|
|
56
|
-
"typescript": "
|
|
56
|
+
"typescript": "5.9.3",
|
|
57
|
+
"vitest": "^4.1.5"
|
|
57
58
|
},
|
|
58
59
|
"dependencies": {
|
|
60
|
+
"@noble/hashes": "^2.2.0",
|
|
59
61
|
"@stacks/network": "^7.3.1",
|
|
60
62
|
"@stacks/stacks-blockchain-api-types": "^7.14.1",
|
|
61
|
-
"@stacks/transactions": "
|
|
63
|
+
"@stacks/transactions": "7.4.0",
|
|
62
64
|
"c32check": "^2.0.0",
|
|
63
65
|
"clarity-abi": "^0.1.0",
|
|
64
66
|
"ts-clarity": "^0.1.1"
|
|
@@ -71,6 +73,11 @@
|
|
|
71
73
|
"start": "dts watch",
|
|
72
74
|
"test": "dts test",
|
|
73
75
|
"sample:counter": "tsx src/sample/counter.ts",
|
|
74
|
-
"sample:read": "tsx src/sample/read.ts"
|
|
76
|
+
"sample:read": "tsx src/sample/read.ts",
|
|
77
|
+
"sample:instant": "tsx src/sample/instant.ts",
|
|
78
|
+
"sample:failure-modes": "tsx src/sample/failure-modes.ts",
|
|
79
|
+
"sample:batch-categories": "tsx src/sample/batch-categories.ts",
|
|
80
|
+
"sample:verify-types": "tsx src/sample/verify-types.ts",
|
|
81
|
+
"sample:vitest": "vitest run"
|
|
75
82
|
}
|
|
76
83
|
}
|
package/src/ast.ts
CHANGED
|
@@ -50,7 +50,7 @@ export async function getContractAST(
|
|
|
50
50
|
export interface ParseContractOptions extends AstOptions {
|
|
51
51
|
sourceCode: string;
|
|
52
52
|
contractId: string;
|
|
53
|
-
clarityVersion?: '1' | '2' | '3' | '4';
|
|
53
|
+
clarityVersion?: '1' | '2' | '3' | '4' | '5';
|
|
54
54
|
epoch?: ClarityEpoch;
|
|
55
55
|
}
|
|
56
56
|
|
package/src/batch-api.ts
CHANGED
|
@@ -31,7 +31,8 @@ export interface BatchReads {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export interface BatchReadsResult {
|
|
34
|
-
|
|
34
|
+
/** Index block hash the batch ran against. Matches the wire field name. */
|
|
35
|
+
index_block_hash: string;
|
|
35
36
|
vars: (ClarityValue | Error)[];
|
|
36
37
|
maps: (ClarityValue | Error)[];
|
|
37
38
|
readonly: (ClarityValue | Error)[];
|
|
@@ -44,10 +45,10 @@ export interface BatchApiOptions {
|
|
|
44
45
|
const DEFAULT_STXER_API = 'https://api.stxer.xyz';
|
|
45
46
|
|
|
46
47
|
function convertResults(
|
|
47
|
-
rs: ({ Ok: string } | { Err: string })[],
|
|
48
|
+
rs: ({ Ok: string } | { Err: string })[] | undefined,
|
|
48
49
|
): (ClarityValue | Error)[] {
|
|
49
50
|
const results: (ClarityValue | Error)[] = [];
|
|
50
|
-
for (const v of rs) {
|
|
51
|
+
for (const v of rs ?? []) {
|
|
51
52
|
if ('Ok' in v) {
|
|
52
53
|
results.push(deserializeCV(v.Ok));
|
|
53
54
|
} else {
|
|
@@ -123,14 +124,14 @@ export async function batchRead(
|
|
|
123
124
|
}
|
|
124
125
|
|
|
125
126
|
const rs = JSON.parse(text) as {
|
|
126
|
-
|
|
127
|
-
vars
|
|
128
|
-
maps
|
|
129
|
-
readonly
|
|
127
|
+
index_block_hash: string;
|
|
128
|
+
vars?: ({ Ok: string } | { Err: string })[];
|
|
129
|
+
maps?: ({ Ok: string } | { Err: string })[];
|
|
130
|
+
readonly?: ({ Ok: string } | { Err: string })[];
|
|
130
131
|
};
|
|
131
132
|
|
|
132
133
|
return {
|
|
133
|
-
|
|
134
|
+
index_block_hash: rs.index_block_hash,
|
|
134
135
|
vars: convertResults(rs.vars),
|
|
135
136
|
maps: convertResults(rs.maps),
|
|
136
137
|
readonly: convertResults(rs.readonly),
|
package/src/bitcoin.ts
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bitcoin SPV utilities for cross-chain simulations.
|
|
3
|
+
*
|
|
4
|
+
* Stacks bridge contracts (sBTC, Brotocol, hBTC, …) verify Bitcoin
|
|
5
|
+
* deposit transactions against burn-block headers and Merkle proofs
|
|
6
|
+
* stored in the Stacks chainstate. To exercise these contracts under
|
|
7
|
+
* the simulator you need three things on the wire:
|
|
8
|
+
*
|
|
9
|
+
* 1. A non-witness Bitcoin transaction whose `txid` (= `sha256d` of
|
|
10
|
+
* the bytes) the Stacks contract can recover.
|
|
11
|
+
* 2. An 80-byte block header whose hash matches the
|
|
12
|
+
* `BurnchainHeaderHash` value the simulator stores. The simulator
|
|
13
|
+
* accepts any 32-byte hash via the `burn_header_hashes` override
|
|
14
|
+
* on `AdvanceBlocks`; pair the override with `buildHeader` so the
|
|
15
|
+
* contract's `verify-block-header` resolves to the same value.
|
|
16
|
+
* 3. A Merkle proof linking the txid to the block header's
|
|
17
|
+
* `merkleRoot`. {@link merkleProof} produces the proof; the
|
|
18
|
+
* contract's on-chain verifier follows the same Bitcoin
|
|
19
|
+
* convention this module implements (leaves are NOT pre-hashed;
|
|
20
|
+
* `sha256d` of concatenated children for inner nodes).
|
|
21
|
+
*
|
|
22
|
+
* All wire-byte conventions match `clarity-bitcoin-v1-07` (the
|
|
23
|
+
* current on-chain SPV reference): hash slots inside the header are
|
|
24
|
+
* stored in *internal byte order* (raw `sha256d`), not the
|
|
25
|
+
* display-reversed form Bitcoin block explorers show.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* import {
|
|
30
|
+
* forgeBitcoinTx, p2wpkhScript, opReturnScript,
|
|
31
|
+
* buildBitcoinHeader, singleTxMerkleRoot,
|
|
32
|
+
* hexToBytes, bytesToHex,
|
|
33
|
+
* } from 'stxer';
|
|
34
|
+
*
|
|
35
|
+
* const tx = forgeBitcoinTx({
|
|
36
|
+
* inputs: [{ prevTxid: hexToBytes('00'.repeat(32)), prevVout: 0,
|
|
37
|
+
* scriptSig: new Uint8Array() }],
|
|
38
|
+
* outputs: [
|
|
39
|
+
* { value: 100_000n, scriptPubKey: p2wpkhScript(hexToBytes('aa'.repeat(20))) },
|
|
40
|
+
* { value: 0n, scriptPubKey: opReturnScript(hexToBytes('5832' + '00'.repeat(20))) },
|
|
41
|
+
* ],
|
|
42
|
+
* });
|
|
43
|
+
* const header = buildBitcoinHeader({
|
|
44
|
+
* merkleRoot: singleTxMerkleRoot(tx.txid),
|
|
45
|
+
* });
|
|
46
|
+
* // Pass `bytesToHex(header.hash)` as `burn_header_hashes[0]` on
|
|
47
|
+
* // `addAdvanceBlocks`; pass `bytesToHex(tx.txid)` to the bridge's
|
|
48
|
+
* // `complete-deposit-wrapper` (or equivalent).
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
import { sha256 as nobleSha256 } from '@noble/hashes/sha2.js';
|
|
52
|
+
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// Hashing primitives
|
|
55
|
+
// ============================================================================
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* SHA-256. Pure-JS via `@noble/hashes` so this module works in
|
|
59
|
+
* browsers, Node, and Bun without a polyfill.
|
|
60
|
+
*/
|
|
61
|
+
export function sha256(data: Uint8Array): Uint8Array {
|
|
62
|
+
return nobleSha256(data);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Bitcoin-style double-sha256 used for txids, block hashes, and Merkle nodes. */
|
|
66
|
+
export function sha256d(data: Uint8Array): Uint8Array {
|
|
67
|
+
return sha256(sha256(data));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// Hex encoding
|
|
72
|
+
// ============================================================================
|
|
73
|
+
|
|
74
|
+
/** Decode a hex string (with or without `0x` prefix). */
|
|
75
|
+
export function hexToBytes(s: string): Uint8Array {
|
|
76
|
+
const t = s.toLowerCase().replace(/^0x/, '');
|
|
77
|
+
if (t.length % 2 !== 0) throw new Error('odd-length hex');
|
|
78
|
+
const out = new Uint8Array(t.length / 2);
|
|
79
|
+
for (let i = 0; i < out.length; i++) {
|
|
80
|
+
out[i] = Number.parseInt(t.slice(i * 2, i * 2 + 2), 16);
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Lower-case hex (no `0x` prefix). */
|
|
86
|
+
export function bytesToHex(b: Uint8Array): string {
|
|
87
|
+
return Array.from(b)
|
|
88
|
+
.map((x) => x.toString(16).padStart(2, '0'))
|
|
89
|
+
.join('');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// Bitcoin script builders
|
|
94
|
+
// ============================================================================
|
|
95
|
+
|
|
96
|
+
/** P2WPKH output script: `OP_0 <20-byte pubkey-hash>`. */
|
|
97
|
+
export function p2wpkhScript(pubKeyHash20: Uint8Array): Uint8Array {
|
|
98
|
+
if (pubKeyHash20.length !== 20) {
|
|
99
|
+
throw new Error('pubKeyHash must be 20 bytes');
|
|
100
|
+
}
|
|
101
|
+
const out = new Uint8Array(22);
|
|
102
|
+
out[0] = 0x00; // OP_0
|
|
103
|
+
out[1] = 0x14; // pushdata length 20
|
|
104
|
+
out.set(pubKeyHash20, 2);
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** P2PKH output script: `OP_DUP OP_HASH160 <hash> OP_EQUALVERIFY OP_CHECKSIG`. */
|
|
109
|
+
export function p2pkhScript(pubKeyHash20: Uint8Array): Uint8Array {
|
|
110
|
+
if (pubKeyHash20.length !== 20) {
|
|
111
|
+
throw new Error('pubKeyHash must be 20 bytes');
|
|
112
|
+
}
|
|
113
|
+
const out = new Uint8Array(25);
|
|
114
|
+
out[0] = 0x76; // OP_DUP
|
|
115
|
+
out[1] = 0xa9; // OP_HASH160
|
|
116
|
+
out[2] = 0x14; // pushdata 20
|
|
117
|
+
out.set(pubKeyHash20, 3);
|
|
118
|
+
out[23] = 0x88; // OP_EQUALVERIFY
|
|
119
|
+
out[24] = 0xac; // OP_CHECKSIG
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* `OP_RETURN <data>` output. Used to embed peg-in metadata
|
|
125
|
+
* (recipient principal, marker bytes, etc.). Bitcoin's standard relay
|
|
126
|
+
* limit caps `data` at 80 bytes; this helper rejects > 75 to leave
|
|
127
|
+
* room for the prefix bytes.
|
|
128
|
+
*/
|
|
129
|
+
export function opReturnScript(data: Uint8Array): Uint8Array {
|
|
130
|
+
if (data.length > 75) {
|
|
131
|
+
throw new Error(`OP_RETURN data too long: ${data.length}`);
|
|
132
|
+
}
|
|
133
|
+
const out = new Uint8Array(2 + data.length);
|
|
134
|
+
out[0] = 0x6a; // OP_RETURN
|
|
135
|
+
out[1] = data.length; // direct push
|
|
136
|
+
out.set(data, 2);
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ============================================================================
|
|
141
|
+
// Bitcoin transaction forging
|
|
142
|
+
// ============================================================================
|
|
143
|
+
|
|
144
|
+
export interface BitcoinTxInput {
|
|
145
|
+
/** 32-byte previous txid in *internal byte order* (raw `sha256d`). */
|
|
146
|
+
prevTxid: Uint8Array;
|
|
147
|
+
prevVout: number;
|
|
148
|
+
/** Script signature bytes. Empty for segwit-style inputs. */
|
|
149
|
+
scriptSig: Uint8Array;
|
|
150
|
+
/** Sequence number; default `0xfffffffd`. */
|
|
151
|
+
sequence?: number;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface BitcoinTxOutput {
|
|
155
|
+
/** Satoshi amount. */
|
|
156
|
+
value: bigint;
|
|
157
|
+
/** Locking script — use {@link p2wpkhScript} / {@link p2pkhScript} / {@link opReturnScript}. */
|
|
158
|
+
scriptPubKey: Uint8Array;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface ForgedBitcoinTx {
|
|
162
|
+
/** Wire-encoded non-witness tx bytes (this is what the txid is computed over). */
|
|
163
|
+
bytes: Uint8Array;
|
|
164
|
+
/** Canonical txid: `sha256d(bytes)`. Internal byte order. */
|
|
165
|
+
txid: Uint8Array;
|
|
166
|
+
/** Display-form (reversed) txid — what block explorers render. */
|
|
167
|
+
txidDisplay: Uint8Array;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Build a non-witness Bitcoin transaction (legacy serialization). The
|
|
172
|
+
* txid is `sha256d` of the returned `bytes`. Use this form when you
|
|
173
|
+
* only need to assert SPV inclusion; segwit witness data is irrelevant
|
|
174
|
+
* to the txid.
|
|
175
|
+
*
|
|
176
|
+
* Wire format:
|
|
177
|
+
* ```
|
|
178
|
+
* 4 version (LE)
|
|
179
|
+
* varint in-count
|
|
180
|
+
* per-input: 32 prev_txid + 4 prev_vout + varint script-len + script + 4 sequence
|
|
181
|
+
* varint out-count
|
|
182
|
+
* per-output: 8 value (LE) + varint script-len + script
|
|
183
|
+
* 4 locktime (LE)
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
export function forgeBitcoinTx(opts: {
|
|
187
|
+
version?: number;
|
|
188
|
+
inputs: BitcoinTxInput[];
|
|
189
|
+
outputs: BitcoinTxOutput[];
|
|
190
|
+
locktime?: number;
|
|
191
|
+
}): ForgedBitcoinTx {
|
|
192
|
+
const chunks: Uint8Array[] = [];
|
|
193
|
+
const u32le = (n: number) => {
|
|
194
|
+
const b = new Uint8Array(4);
|
|
195
|
+
new DataView(b.buffer).setUint32(0, n >>> 0, true);
|
|
196
|
+
return b;
|
|
197
|
+
};
|
|
198
|
+
const u64le = (n: bigint) => {
|
|
199
|
+
const b = new Uint8Array(8);
|
|
200
|
+
const view = new DataView(b.buffer);
|
|
201
|
+
view.setUint32(0, Number(n & 0xffffffffn), true);
|
|
202
|
+
view.setUint32(4, Number((n >> 32n) & 0xffffffffn), true);
|
|
203
|
+
return b;
|
|
204
|
+
};
|
|
205
|
+
const varint = (n: number): Uint8Array => {
|
|
206
|
+
if (n < 0xfd) return new Uint8Array([n]);
|
|
207
|
+
if (n <= 0xffff) {
|
|
208
|
+
const b = new Uint8Array(3);
|
|
209
|
+
b[0] = 0xfd;
|
|
210
|
+
new DataView(b.buffer).setUint16(1, n, true);
|
|
211
|
+
return b;
|
|
212
|
+
}
|
|
213
|
+
if (n <= 0xffffffff) {
|
|
214
|
+
const b = new Uint8Array(5);
|
|
215
|
+
b[0] = 0xfe;
|
|
216
|
+
new DataView(b.buffer).setUint32(1, n, true);
|
|
217
|
+
return b;
|
|
218
|
+
}
|
|
219
|
+
throw new Error(`varint too large: ${n}`);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
chunks.push(u32le(opts.version ?? 2));
|
|
223
|
+
chunks.push(varint(opts.inputs.length));
|
|
224
|
+
for (const i of opts.inputs) {
|
|
225
|
+
if (i.prevTxid.length !== 32) {
|
|
226
|
+
throw new Error('prevTxid must be 32 bytes');
|
|
227
|
+
}
|
|
228
|
+
chunks.push(i.prevTxid);
|
|
229
|
+
chunks.push(u32le(i.prevVout));
|
|
230
|
+
chunks.push(varint(i.scriptSig.length));
|
|
231
|
+
chunks.push(i.scriptSig);
|
|
232
|
+
chunks.push(u32le(i.sequence ?? 0xfffffffd));
|
|
233
|
+
}
|
|
234
|
+
chunks.push(varint(opts.outputs.length));
|
|
235
|
+
for (const o of opts.outputs) {
|
|
236
|
+
chunks.push(u64le(o.value));
|
|
237
|
+
chunks.push(varint(o.scriptPubKey.length));
|
|
238
|
+
chunks.push(o.scriptPubKey);
|
|
239
|
+
}
|
|
240
|
+
chunks.push(u32le(opts.locktime ?? 0));
|
|
241
|
+
|
|
242
|
+
const total = chunks.reduce((s, c) => s + c.length, 0);
|
|
243
|
+
const bytes = new Uint8Array(total);
|
|
244
|
+
let off = 0;
|
|
245
|
+
for (const c of chunks) {
|
|
246
|
+
bytes.set(c, off);
|
|
247
|
+
off += c.length;
|
|
248
|
+
}
|
|
249
|
+
const txid = sha256d(bytes);
|
|
250
|
+
const txidDisplay = reverseBytes(txid);
|
|
251
|
+
return { bytes, txid, txidDisplay };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ============================================================================
|
|
255
|
+
// Bitcoin block header
|
|
256
|
+
// ============================================================================
|
|
257
|
+
|
|
258
|
+
export interface BitcoinHeader {
|
|
259
|
+
version: number;
|
|
260
|
+
prevHash: Uint8Array;
|
|
261
|
+
merkleRoot: Uint8Array;
|
|
262
|
+
timestamp: number;
|
|
263
|
+
bits: number;
|
|
264
|
+
nonce: number;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export interface BuiltBitcoinHeader {
|
|
268
|
+
/** Raw 80-byte serialized header. */
|
|
269
|
+
header: Uint8Array;
|
|
270
|
+
/**
|
|
271
|
+
* Display-form hash (`reverse(sha256d(header))`) — what the Stacks
|
|
272
|
+
* chainstate stores as `BurnchainHeaderHash`. Pass this as the
|
|
273
|
+
* `burn_header_hashes[i]` override on `addAdvanceBlocks`.
|
|
274
|
+
*/
|
|
275
|
+
hash: Uint8Array;
|
|
276
|
+
/** Raw `sha256d(header)` (internal byte order). */
|
|
277
|
+
rawHash: Uint8Array;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Build an 80-byte Bitcoin block header. `prevHash` and `merkleRoot`
|
|
282
|
+
* are written in internal byte order (raw `sha256d` output) — matching
|
|
283
|
+
* the on-chain `clarity-bitcoin-v1-07` parser convention.
|
|
284
|
+
*/
|
|
285
|
+
export function buildBitcoinHeader(
|
|
286
|
+
h: Partial<BitcoinHeader> & { merkleRoot: Uint8Array },
|
|
287
|
+
): BuiltBitcoinHeader {
|
|
288
|
+
const out = new Uint8Array(80);
|
|
289
|
+
const view = new DataView(out.buffer);
|
|
290
|
+
view.setUint32(0, h.version ?? 0x20000000, true);
|
|
291
|
+
out.set(h.prevHash ?? new Uint8Array(32), 4);
|
|
292
|
+
out.set(h.merkleRoot, 36);
|
|
293
|
+
view.setUint32(68, h.timestamp ?? Math.floor(Date.now() / 1000), true);
|
|
294
|
+
view.setUint32(72, h.bits ?? 0x1d00ffff, true);
|
|
295
|
+
view.setUint32(76, h.nonce ?? 0, true);
|
|
296
|
+
const rawHash = sha256d(out);
|
|
297
|
+
return { header: out, hash: reverseBytes(rawHash), rawHash };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ============================================================================
|
|
301
|
+
// Merkle proofs (Bitcoin convention)
|
|
302
|
+
// ============================================================================
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Single-tx Merkle root — the txid itself. Bitcoin's Merkle tree
|
|
306
|
+
* convention does NOT pre-hash the leaves; for a one-tx block the
|
|
307
|
+
* root equals the txid.
|
|
308
|
+
*/
|
|
309
|
+
export function singleTxMerkleRoot(txid: Uint8Array): Uint8Array {
|
|
310
|
+
return new Uint8Array(txid);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Compute a Bitcoin Merkle proof for `txid` at `index` in `txids`
|
|
315
|
+
* (transaction order within the block). Returns the sibling hashes
|
|
316
|
+
* from leaf to root.
|
|
317
|
+
*
|
|
318
|
+
* Bitcoin convention:
|
|
319
|
+
* - Leaves are the txids themselves (no pre-hashing).
|
|
320
|
+
* - Inner nodes are `sha256d(left || right)`.
|
|
321
|
+
* - Odd levels duplicate the last entry to pair.
|
|
322
|
+
*/
|
|
323
|
+
export function merkleProof(
|
|
324
|
+
txids: Uint8Array[],
|
|
325
|
+
index: number,
|
|
326
|
+
): { proof: Uint8Array[]; root: Uint8Array } {
|
|
327
|
+
if (index < 0 || index >= txids.length) {
|
|
328
|
+
throw new Error(`merkleProof: index ${index} out of range ${txids.length}`);
|
|
329
|
+
}
|
|
330
|
+
let level: Uint8Array[] = txids.map((t) => new Uint8Array(t));
|
|
331
|
+
const proof: Uint8Array[] = [];
|
|
332
|
+
let i = index;
|
|
333
|
+
while (level.length > 1) {
|
|
334
|
+
if (level.length % 2 === 1) level.push(level[level.length - 1]);
|
|
335
|
+
const sibling = level[i ^ 1];
|
|
336
|
+
proof.push(sibling);
|
|
337
|
+
const next: Uint8Array[] = [];
|
|
338
|
+
for (let j = 0; j < level.length; j += 2) {
|
|
339
|
+
next.push(sha256d(concatBytes(level[j], level[j + 1])));
|
|
340
|
+
}
|
|
341
|
+
level = next;
|
|
342
|
+
i = Math.floor(i / 2);
|
|
343
|
+
}
|
|
344
|
+
return { proof, root: level[0] };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Verify a Bitcoin Merkle proof against `root`. Returns `true` iff the
|
|
349
|
+
* proof reconstructs the root from `txid` at `index`. Matches the
|
|
350
|
+
* on-chain `verify-merkle-proof` semantic.
|
|
351
|
+
*/
|
|
352
|
+
export function verifyMerkleProof(
|
|
353
|
+
txid: Uint8Array,
|
|
354
|
+
index: number,
|
|
355
|
+
proof: Uint8Array[],
|
|
356
|
+
root: Uint8Array,
|
|
357
|
+
): boolean {
|
|
358
|
+
let h: Uint8Array = new Uint8Array(txid);
|
|
359
|
+
let i = index;
|
|
360
|
+
for (const sibling of proof) {
|
|
361
|
+
h =
|
|
362
|
+
(i & 1) === 0
|
|
363
|
+
? sha256d(concatBytes(h, sibling))
|
|
364
|
+
: sha256d(concatBytes(sibling, h));
|
|
365
|
+
i = Math.floor(i / 2);
|
|
366
|
+
}
|
|
367
|
+
return bytesEqual(h, root);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// ============================================================================
|
|
371
|
+
// Internal byte helpers
|
|
372
|
+
// ============================================================================
|
|
373
|
+
|
|
374
|
+
function reverseBytes(a: Uint8Array): Uint8Array {
|
|
375
|
+
const out = new Uint8Array(a.length);
|
|
376
|
+
for (let i = 0; i < a.length; i++) out[i] = a[a.length - 1 - i];
|
|
377
|
+
return out;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function concatBytes(a: Uint8Array, b: Uint8Array): Uint8Array {
|
|
381
|
+
const out = new Uint8Array(a.length + b.length);
|
|
382
|
+
out.set(a, 0);
|
|
383
|
+
out.set(b, a.length);
|
|
384
|
+
return out;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function bytesEqual(a: Uint8Array, b: Uint8Array): boolean {
|
|
388
|
+
if (a.length !== b.length) return false;
|
|
389
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
390
|
+
return true;
|
|
391
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
|
+
// `dts build` (rollup-plugin-typescript2 + babel) does not enable
|
|
2
|
+
// `@babel/preset-typescript` on the entry, so inline `type` specifiers
|
|
3
|
+
// inside `export {}` and standalone `export type {}` blocks both crash
|
|
4
|
+
// the build (babel falls back to flow grammar). Stick with `export *`.
|
|
1
5
|
export * from './ast';
|
|
2
6
|
export * from './batch-api';
|
|
7
|
+
export * from './bitcoin';
|
|
3
8
|
export * from './clarity-api';
|
|
4
9
|
export * from './constants';
|
|
5
10
|
export * from './simulation';
|
|
6
|
-
|
|
7
|
-
export {
|
|
8
|
-
type CreateSessionOptions,
|
|
9
|
-
createSimulationSession,
|
|
10
|
-
getSimulationResult,
|
|
11
|
-
instantSimulation,
|
|
12
|
-
type SimulationApiOptions,
|
|
13
|
-
simulationBatchReads,
|
|
14
|
-
submitSimulationSteps,
|
|
15
|
-
} from './simulation-api';
|
|
11
|
+
export * from './simulation-api';
|
|
16
12
|
export * from './tip';
|
|
13
|
+
export * from './transaction';
|
|
17
14
|
export * from './types';
|