@smartledger/bsv 7.8.1 → 7.10.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 +117 -6
- package/README.md +38 -38
- package/bsv-covenant.min.js +1 -1
- package/bsv-ecies.min.js +1 -1
- package/bsv-gdaf.min.js +54 -54
- package/bsv-ltp.min.js +58 -85
- package/bsv-message.min.js +2 -2
- package/bsv-mnemonic.min.js +12 -14
- package/bsv-script-helper.min.js +1 -1
- package/bsv-shamir.min.js +14 -16
- package/bsv-smartcontract.min.js +23 -25
- package/bsv.bundle.js +54 -54
- package/bsv.min.js +54 -54
- package/build/esbuild.js +84 -4
- package/docs/MODULE_REFERENCE_COMPLETE.md +27 -27
- package/docs/advanced/UTXO_MANAGER_GUIDE.md +1 -1
- package/docs/getting-started/INSTALLATION.md +23 -23
- package/docs/getting-started/QUICK_START.md +7 -7
- package/docs/migration/FROM_BSV_1_5_6.md +5 -5
- package/index.js +9 -1
- package/lib/covenant/helpers.js +146 -0
- package/lib/covenant/index.js +21 -0
- package/lib/covenant/pushtx.js +243 -0
- package/lib/covenant-interface.js +408 -397
- package/lib/custom-script-helper.js +44 -38
- package/lib/ecies/bitcore-ecies.js +3 -4
- package/lib/ecies/electrum-ecies.js +4 -6
- package/lib/ecies/errors.js +1 -1
- package/lib/gdaf/attestation-signer.js +7 -7
- package/lib/gdaf/attestation-verifier.js +5 -6
- package/lib/gdaf/did-resolver.js +10 -9
- package/lib/gdaf/smartledger-anchor.js +13 -12
- package/lib/gdaf/zk-prover.js +8 -8
- package/lib/ltp/anchor.js +2 -3
- package/lib/ltp/claim.js +2 -3
- package/lib/ltp/index.js +2 -3
- package/lib/ltp/obligation.js +9 -9
- package/lib/ltp/proof.js +7 -7
- package/lib/ltp/registry.js +4 -5
- package/lib/ltp/right.js +11 -11
- package/lib/message/index.js +5 -4
- package/lib/message/message.js +11 -11
- package/lib/mnemonic/errors.js +1 -1
- package/lib/mnemonic/index.js +5 -4
- package/lib/mnemonic/mnemonic.js +22 -10
- package/lib/ordinals/bsv20.js +4 -4
- package/lib/ordinals/inscription.js +8 -12
- package/lib/ordinals/ordlock.js +26 -21
- package/lib/script/interpreter.js +96 -31
- package/lib/smart_contract/authorizers.js +8 -6
- package/lib/smart_contract/builder.js +32 -26
- package/lib/smart_contract/covenant.js +32 -25
- package/lib/smart_contract/covenant_helpers.js +8 -128
- package/lib/smart_contract/debugger.js +9 -6
- package/lib/smart_contract/dsl.js +5 -6
- package/lib/smart_contract/index.js +11 -8
- package/lib/smart_contract/locks.js +3 -4
- package/lib/smart_contract/opcode_list.js +2 -3
- package/lib/smart_contract/pels.js +4 -5
- package/lib/smart_contract/preimage.js +11 -7
- package/lib/smart_contract/pushtx.js +8 -263
- package/lib/smart_contract/script_interpreter.js +112 -105
- package/lib/smart_contract/script_tester.js +24 -18
- package/lib/smart_contract/script_utils.js +15 -14
- package/lib/smart_contract/sighash.js +23 -15
- package/lib/smart_contract/stack_examiner.js +70 -63
- package/lib/smart_contract/token.js +6 -6
- package/lib/smart_contract/utxo_generator.js +21 -15
- package/lib/smartutxo.js +27 -21
- package/lib/spv/headerchain.js +3 -3
- package/lib/spv/merkleproof.js +3 -3
- package/package.json +4 -2
- package/test/script/chronicle.js +53 -10
- package/test/script/interpreter.js +9 -6
- package/test/script/string_ops.js +26 -10
- package/version.js +1 -1
|
@@ -1,264 +1,9 @@
|
|
|
1
1
|
'use strict'
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* proves the pushed preimage IS this transaction, letting a script read and
|
|
11
|
-
* constrain its own spending transaction.
|
|
12
|
-
*
|
|
13
|
-
* Optimal parameters: private key a = 1, ephemeral k = 1 => r = Gx,
|
|
14
|
-
* s = (e + Gx) mod n, pubkey P = 02||Gx, e = HASH256(preimage).
|
|
15
|
-
*
|
|
16
|
-
* Requires post-Genesis limits — call SmartContract.enableGenesis() (a.k.a
|
|
17
|
-
* Interpreter.useGenesisLimits()) before verifying these scripts.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
var bsv = require('../..')
|
|
21
|
-
var BN = require('../crypto/bn')
|
|
22
|
-
var Hash = require('../crypto/hash')
|
|
23
|
-
var H = require('./covenant_helpers')
|
|
24
|
-
|
|
25
|
-
var Script = bsv.Script
|
|
26
|
-
var Opcode = bsv.Opcode
|
|
27
|
-
var SIGHASH = H.SIGHASH
|
|
28
|
-
var scriptNum = H.scriptNum
|
|
29
|
-
|
|
30
|
-
// secp256k1 constants (big-endian)
|
|
31
|
-
var Gx = Buffer.from('79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', 'hex')
|
|
32
|
-
var N = new BN('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 16)
|
|
33
|
-
var PUBKEY = Buffer.concat([Buffer.from([0x02]), Gx]) // compressed P = G (y even)
|
|
34
|
-
|
|
35
|
-
// script-number (little-endian) forms
|
|
36
|
-
var gxLe = Buffer.from(Gx).reverse() // 32B, top 0x79 => positive
|
|
37
|
-
var N_LE = Buffer.concat([Buffer.from(N.toBuffer()).reverse(), Buffer.from([0x00])]) // 33B positive
|
|
38
|
-
|
|
39
|
-
// fixed DER prefix: SEQUENCE(0x44) INTEGER(0x20) r=Gx INTEGER(0x20)
|
|
40
|
-
var DER_PREFIX = Buffer.concat([Buffer.from([0x30, 0x44, 0x02, 0x20]), Gx, Buffer.from([0x02, 0x20])])
|
|
41
|
-
|
|
42
|
-
/** Reverse a fixed n-byte buffer on top of the stack (big-endian <-> little-endian). */
|
|
43
|
-
function reverseBytes (script, n) {
|
|
44
|
-
var i
|
|
45
|
-
for (i = 0; i < n - 1; i++) script.add(Opcode.OP_1).add(Opcode.OP_SPLIT)
|
|
46
|
-
for (i = 0; i < n - 1; i++) script.add(Opcode.OP_SWAP).add(Opcode.OP_CAT)
|
|
47
|
-
return script
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Common SIGHASH flag combinations (all include FORKID, required on BSV). The
|
|
51
|
-
// marketplace/partially-signed pattern uses SINGLE|ANYONECANPAY: the signer commits
|
|
52
|
-
// only to its own input and its own output (the output at the same index), leaving
|
|
53
|
-
// a counterparty free to add funding inputs and change outputs without invalidating
|
|
54
|
-
// the signature.
|
|
55
|
-
var Signature = bsv.crypto.Signature
|
|
56
|
-
var SIGHASH_ALL_FORKID = Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID // 0x41
|
|
57
|
-
var SIGHASH_SINGLE_FORKID = Signature.SIGHASH_SINGLE | Signature.SIGHASH_FORKID // 0x43
|
|
58
|
-
var SIGHASH_SINGLE_ANYONECANPAY_FORKID =
|
|
59
|
-
Signature.SIGHASH_SINGLE | Signature.SIGHASH_ANYONECANPAY | Signature.SIGHASH_FORKID // 0xc3
|
|
60
|
-
// ALL|ANYONECANPAY commits to ALL outputs (so the covenant can pin the FULL output
|
|
61
|
-
// set) while leaving inputs open for a counterparty to add funding. This is the flag
|
|
62
|
-
// the 1Sat Ordinals OrdLock uses: the buyer supplies the surrounding outputs and the
|
|
63
|
-
// covenant binds the seller's payment output into the committed hashOutputs.
|
|
64
|
-
var SIGHASH_ALL_ANYONECANPAY_FORKID =
|
|
65
|
-
Signature.SIGHASH_ALL | Signature.SIGHASH_ANYONECANPAY | Signature.SIGHASH_FORKID // 0xc1
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Append the in-script signature generator + verifier ("PUSH_TX core").
|
|
69
|
-
* Pre: top of stack = preimage. Post: top = OP_CHECKSIG result.
|
|
70
|
-
*
|
|
71
|
-
* @param {Script} script
|
|
72
|
-
* @param {object} [opts]
|
|
73
|
-
* @param {number} [opts.sighashType=0x41] the SIGHASH flag byte baked into the
|
|
74
|
-
* synthetic signature. OP_CHECKSIG derives the spend's sighash using THIS flag,
|
|
75
|
-
* so the spender must push the matching BIP-143 preimage (grind with the same
|
|
76
|
-
* sighashType). Defaults to SIGHASH_ALL|FORKID — existing covenants are unchanged.
|
|
77
|
-
*/
|
|
78
|
-
function pushTxCore (script, opts) {
|
|
79
|
-
var sighashType = (opts && opts.sighashType) || SIGHASH
|
|
80
|
-
script.add(Opcode.OP_HASH256) // z = HASH256(preimage), 32B BE
|
|
81
|
-
reverseBytes(script, 32) // -> little-endian = e. The grind guarantees e is
|
|
82
|
-
// already positive and minimally encoded (z[0] in 0x01..0x7f), so NO 0x00 sign
|
|
83
|
-
// byte is appended — keeping the script MINIMALDATA-clean (mainnet-relayable).
|
|
84
|
-
script.add(gxLe).add(Opcode.OP_ADD) // e + Gx
|
|
85
|
-
script.add(N_LE).add(Opcode.OP_MOD) // s = (e + Gx) mod n
|
|
86
|
-
script.add(scriptNum(32)).add(Opcode.OP_NUM2BIN) // s -> 32-byte LE
|
|
87
|
-
reverseBytes(script, 32) // -> big-endian (DER INTEGER body) ; stack: [s_be]
|
|
88
|
-
// Build the DER signature and the pubkey from a SINGLE Gx push (Gx is both the
|
|
89
|
-
// r-value inside the DER prefix and the body of the 02||Gx pubkey). Sharing it
|
|
90
|
-
// via the altstack saves a 32-byte constant vs. embedding Gx twice.
|
|
91
|
-
script.add(Gx).add(Opcode.OP_DUP) // [s_be, Gx, Gx]
|
|
92
|
-
script.add(Opcode.OP_2).add(Opcode.OP_SWAP).add(Opcode.OP_CAT) // pubkey = 02||Gx (OP_2 = minimal push of 0x02)
|
|
93
|
-
script.add(Opcode.OP_TOALTSTACK) // park pubkey ; [s_be, Gx]
|
|
94
|
-
script.add(Buffer.from([0x30, 0x44, 0x02, 0x20])).add(Opcode.OP_SWAP).add(Opcode.OP_CAT) // 30440220||Gx
|
|
95
|
-
script.add(Buffer.from([0x02, 0x20])).add(Opcode.OP_CAT) // ||0220 => DER prefix
|
|
96
|
-
script.add(Opcode.OP_SWAP).add(Opcode.OP_CAT) // DER prefix || s_be
|
|
97
|
-
script.add(Buffer.from([sighashType])).add(Opcode.OP_CAT) // || sighash flag => full DER sig
|
|
98
|
-
script.add(Opcode.OP_FROMALTSTACK).add(Opcode.OP_CHECKSIG) // pubkey ; verify against P = G
|
|
99
|
-
return script
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/** Bare authenticator script: unlock with the (grindable) preimage. */
|
|
103
|
-
function authenticator () {
|
|
104
|
-
return pushTxCore(new Script())
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Extract the committed hashOutputs (item 9, offsetFromEnd 40, len 32) from a preimage on-stack. */
|
|
108
|
-
function extractHashOutputs (script) {
|
|
109
|
-
// last 40 bytes, then the first 32 of those = hashOutputs. (BSV string opcodes.)
|
|
110
|
-
script.add(scriptNum(40)).add(Opcode.OP_RIGHT).add(scriptNum(32)).add(Opcode.OP_LEFT)
|
|
111
|
-
return script
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// The BIP-143 preimage ends with nHashType as a 4-byte LE word; for SIGHASH_ALL|
|
|
115
|
-
// FORKID that word is 0x41 00 00 00. assertSighashType builds this word per flag.
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Assert the spend is SIGHASH_ALL|FORKID (0x41). Pre/Post: top of stack = preimage
|
|
119
|
-
* (left untouched). These covenants read fixed offsets (104 / 52 / 40) that are
|
|
120
|
-
* meaningful only for the 0x41 layout, and the OP_PUSH_TX core already pins the
|
|
121
|
-
* flag implicitly (its synthetic sig carries 0x41, so OP_CHECKSIG only accepts a
|
|
122
|
-
* preimage hashing to the 0x41 sighash). Note the offsets themselves are invariant
|
|
123
|
-
* in the number of INPUTS — hashPrevouts/hashSequence are digests of all inputs,
|
|
124
|
-
* so the preimage is the same length for 1 or N inputs; the covenant simply does
|
|
125
|
-
* not constrain sibling inputs, by design. What it does assume is a single
|
|
126
|
-
* recreated OUTPUT (HASH256(nextOutput) == hashOutputs). This guard makes the
|
|
127
|
-
* flag assumption explicit and fails fast — and survives any refactor of the core.
|
|
128
|
-
*/
|
|
129
|
-
function assertSighashAll (script) {
|
|
130
|
-
return assertSighashType(script, SIGHASH)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Assert the spend uses a specific SIGHASH flag. Pre/Post: top of stack = preimage
|
|
135
|
-
* (left untouched). The BIP-143 preimage's trailing 4-byte LE nHashType word must
|
|
136
|
-
* equal `sighashType`. Use with pushTxCore({ sighashType }) + grind({ sighashType })
|
|
137
|
-
* to build covenants under SIGHASH_SINGLE|ANYONECANPAY (marketplace) etc.
|
|
138
|
-
*/
|
|
139
|
-
function assertSighashType (script, sighashType) {
|
|
140
|
-
var word = Buffer.from([sighashType & 0xff, 0x00, 0x00, 0x00])
|
|
141
|
-
script.add(Opcode.OP_DUP).add(scriptNum(4)).add(Opcode.OP_RIGHT)
|
|
142
|
-
.add(word).add(Opcode.OP_EQUALVERIFY)
|
|
143
|
-
return script
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/** BIP-143 hashOutputs (SIGHASH_ALL) for a set of Transaction.Output objects. */
|
|
147
|
-
function hashOutputs (outputs) {
|
|
148
|
-
var ser = Buffer.concat(outputs.map(function (o) { return o.toBufferWriter().toBuffer() }))
|
|
149
|
-
return Hash.sha256sha256(ser)
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Value/output covenant: the spend is valid only if its outputs hash to
|
|
154
|
-
* `expectedHashOutputs` — coins can only go where the covenant says.
|
|
155
|
-
*/
|
|
156
|
-
function valueCovenant (expectedHashOutputs) {
|
|
157
|
-
var script = new Script().add(Opcode.OP_DUP)
|
|
158
|
-
pushTxCore(script)
|
|
159
|
-
script.add(Opcode.OP_VERIFY)
|
|
160
|
-
extractHashOutputs(script)
|
|
161
|
-
script.add(Buffer.from(expectedHashOutputs)).add(Opcode.OP_EQUAL)
|
|
162
|
-
return script
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// floor(n/2) — the canonical low-S boundary (matches Signature.hasLowS()).
|
|
166
|
-
var HALF_N = new BN('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0', 'hex')
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Compute s = (HASH256(preimage)+Gx) mod n; return its 32-byte BE form, or null
|
|
170
|
-
* if the resulting signature would not be a clean, CANONICAL (low-S) DER.
|
|
171
|
-
* Requiring s <= n/2 makes the in-script signature non-malleable and standard,
|
|
172
|
-
* so it passes nodes enforcing SCRIPT_VERIFY_LOW_S — at zero script-size cost
|
|
173
|
-
* (the burden is on the spender's grind, not extra opcodes).
|
|
174
|
-
*/
|
|
175
|
-
function sFromPreimage (preimage) {
|
|
176
|
-
var z = Hash.sha256sha256(preimage)
|
|
177
|
-
// Script no longer sign-extends z, so e (= reverse(z) as a number) must be
|
|
178
|
-
// positive AND minimally encoded: its little-endian MSB (= z[0]) in 0x01..0x7f.
|
|
179
|
-
// This keeps the locking script MINIMALDATA-clean (mainnet-relayable).
|
|
180
|
-
if (z[0] < 0x01 || z[0] > 0x7f) return null
|
|
181
|
-
var s = new BN(z).add(new BN(Gx)).mod(N)
|
|
182
|
-
if (s.gt(HALF_N)) return null // enforce low-S (canonical / non-malleable)
|
|
183
|
-
// NOT s.toBuffer('be', 32): lib/crypto/bn.js REPLACES toBuffer with an
|
|
184
|
-
// options-object form, so 'be' would land in `opts`, opts.size would be
|
|
185
|
-
// undefined and the 32 ignored — yielding a natural-length buffer.
|
|
186
|
-
//
|
|
187
|
-
// That truncation is currently UNREACHABLE, and the reason is worth writing
|
|
188
|
-
// down because it is not obvious: z is filtered to a leading byte of
|
|
189
|
-
// 0x01..0x7f above, so z is in [2**248, 2**255); adding Gx (~0.476 * 2**256)
|
|
190
|
-
// never wraps; and the low-S check caps the result at n/2. s therefore lands
|
|
191
|
-
// in a narrow band with a leading byte of 0x7a..0x7f and is always 32 bytes.
|
|
192
|
-
//
|
|
193
|
-
// The call is still wrong, and worth fixing, because that safety is an
|
|
194
|
-
// accident of two filters that have nothing to do with buffer length. Widen
|
|
195
|
-
// the MINIMALDATA range or drop low-S and the truncation becomes reachable —
|
|
196
|
-
// silently, inside a covenant, where DER_PREFIX declares s to be exactly 32
|
|
197
|
-
// bytes and a short one makes the spend invalid.
|
|
198
|
-
var sBE = s.toBuffer({ size: 32 })
|
|
199
|
-
return (sBE[0] >= 0x01) ? sBE : null // s <= n/2 already guarantees sBE[0] <= 0x7f
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Grind a malleable field of the spend until the in-script OP_PUSH_TX signature
|
|
204
|
-
* is a clean, canonical (low-S) fixed-length DER. Returns { preimage, tries,
|
|
205
|
-
* field, nonce }; mutates the chosen field on `spend`.
|
|
206
|
-
*
|
|
207
|
-
* @param {object} [opts] grind options (a bare number is accepted as legacy maxTries):
|
|
208
|
-
* - maxTries {number} attempts before giving up (default 5000).
|
|
209
|
-
* - start {number} first nonce value to try (default 0).
|
|
210
|
-
* - field {'nLockTime'|'sequence'} which field carries the grind nonce
|
|
211
|
-
* (default 'nLockTime'). Use 'sequence' when the covenant pins nLockTime for
|
|
212
|
-
* a real CLTV absolute timelock: the input's sequence is swept down from
|
|
213
|
-
* 0xfffffffe (kept non-final, so CLTV still triggers) instead of clobbering
|
|
214
|
-
* nLockTime. (Not for CSV/relative-locktime covenants, which encode meaning
|
|
215
|
-
* in the sequence number itself.)
|
|
216
|
-
*/
|
|
217
|
-
function grind (spend, inputIndex, lockingScript, satoshis, opts) {
|
|
218
|
-
if (typeof opts === 'number') opts = { maxTries: opts } // back-compat: legacy maxTries arg
|
|
219
|
-
opts = opts || {}
|
|
220
|
-
var maxTries = opts.maxTries || 5000
|
|
221
|
-
var start = opts.start || 0
|
|
222
|
-
var field = opts.field || 'nLockTime'
|
|
223
|
-
var sighashType = opts.sighashType || SIGHASH
|
|
224
|
-
if (field !== 'nLockTime' && field !== 'sequence') {
|
|
225
|
-
throw new Error("grind field must be 'nLockTime' or 'sequence'")
|
|
226
|
-
}
|
|
227
|
-
var input = spend.inputs[inputIndex]
|
|
228
|
-
for (var i = 0; i < maxTries; i++) {
|
|
229
|
-
var nonce = start + i
|
|
230
|
-
if (field === 'nLockTime') spend.nLockTime = nonce
|
|
231
|
-
else input.sequenceNumber = 0xfffffffe - nonce // stay < 0xffffffff (non-final)
|
|
232
|
-
var preimage = H.rawPreimage(spend, inputIndex, lockingScript, satoshis, sighashType)
|
|
233
|
-
// Length-checked, not truthy: grind's contract is a FIXED-length DER
|
|
234
|
-
// signature, and a short s would be truthy. Belt-and-braces alongside the
|
|
235
|
-
// toBuffer fix above — this is the check that would catch it if the
|
|
236
|
-
// upstream filters that currently make a short s impossible ever change.
|
|
237
|
-
var sBE = sFromPreimage(preimage)
|
|
238
|
-
if (sBE && sBE.length === 32) return { preimage: preimage, tries: i + 1, field: field, nonce: nonce }
|
|
239
|
-
}
|
|
240
|
-
throw new Error('OP_PUSH_TX grind failed after ' + maxTries + ' tries')
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
module.exports = {
|
|
244
|
-
Gx: Gx,
|
|
245
|
-
N: N,
|
|
246
|
-
PUBKEY: PUBKEY,
|
|
247
|
-
gxLe: gxLe,
|
|
248
|
-
N_LE: N_LE,
|
|
249
|
-
DER_PREFIX: DER_PREFIX,
|
|
250
|
-
reverseBytes: reverseBytes,
|
|
251
|
-
pushTxCore: pushTxCore,
|
|
252
|
-
authenticator: authenticator,
|
|
253
|
-
extractHashOutputs: extractHashOutputs,
|
|
254
|
-
assertSighashAll: assertSighashAll,
|
|
255
|
-
assertSighashType: assertSighashType,
|
|
256
|
-
SIGHASH_ALL_FORKID: SIGHASH_ALL_FORKID,
|
|
257
|
-
SIGHASH_SINGLE_FORKID: SIGHASH_SINGLE_FORKID,
|
|
258
|
-
SIGHASH_SINGLE_ANYONECANPAY_FORKID: SIGHASH_SINGLE_ANYONECANPAY_FORKID,
|
|
259
|
-
SIGHASH_ALL_ANYONECANPAY_FORKID: SIGHASH_ALL_ANYONECANPAY_FORKID,
|
|
260
|
-
hashOutputs: hashOutputs,
|
|
261
|
-
valueCovenant: valueCovenant,
|
|
262
|
-
sFromPreimage: sFromPreimage,
|
|
263
|
-
grind: grind
|
|
264
|
-
}
|
|
2
|
+
// Moved to lib/covenant/pushtx.js.
|
|
3
|
+
//
|
|
4
|
+
// lib/ordinals needs these primitives too, and importing them from
|
|
5
|
+
// lib/smart_contract was the only dependency from core code into the
|
|
6
|
+
// application layer. This path is retained because deep imports
|
|
7
|
+
// (require('@smartledger/bsv/lib/smart_contract/pushtx')) are public API via
|
|
8
|
+
// the package.json exports map.
|
|
9
|
+
module.exports = require('../covenant/pushtx')
|
|
@@ -20,204 +20,211 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
// Browser-safe requires
|
|
23
|
-
let readline = null
|
|
23
|
+
let readline = null
|
|
24
24
|
try {
|
|
25
25
|
// Only require readline in Node.js environment
|
|
26
26
|
if (typeof window === 'undefined' && typeof require !== 'undefined') {
|
|
27
|
-
readline = require(
|
|
27
|
+
readline = require('readline')
|
|
28
28
|
}
|
|
29
29
|
} catch (err) {
|
|
30
30
|
// Gracefully handle missing readline in browser
|
|
31
|
-
readline = null
|
|
31
|
+
readline = null
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
const
|
|
34
|
+
const _global = (typeof window !== 'undefined' && window.bsv) ? window.bsv : null
|
|
35
|
+
const Script = _global ? _global.Script : require('../script')
|
|
36
|
+
const Interpreter = _global ? _global.Script.Interpreter : require('../script/interpreter')
|
|
37
|
+
const Opcode = _global ? _global.Opcode : require('../opcode')
|
|
38
|
+
const BN = _global ? _global.crypto.BN : require('../crypto/bn')
|
|
39
|
+
const Transaction = _global ? _global.Transaction : require('../transaction')
|
|
40
|
+
const Input = _global ? _global.Transaction.Input : require('../transaction/input')
|
|
41
|
+
const Output = _global ? _global.Transaction.Output : require('../transaction/output')
|
|
35
42
|
|
|
36
43
|
// Debug: Show Script Interpreter module structure (uncomment to inspect)
|
|
37
|
-
// const ScriptInterpreter =
|
|
44
|
+
// const ScriptInterpreter = Interpreter;
|
|
38
45
|
// console.log("Script Interpreter Module:", ScriptInterpreter);
|
|
39
46
|
/**
|
|
40
47
|
* Parse input as either ASM or HEX.
|
|
41
48
|
*/
|
|
42
|
-
function parseScript(input) {
|
|
43
|
-
if (!input) return new
|
|
44
|
-
const trimmed = input.trim()
|
|
45
|
-
const isHex = /^[0-9a-fA-F]+$/.test(trimmed.replace(/\s+/g,
|
|
49
|
+
function parseScript (input) {
|
|
50
|
+
if (!input) return new Script()
|
|
51
|
+
const trimmed = input.trim()
|
|
52
|
+
const isHex = /^[0-9a-fA-F]+$/.test(trimmed.replace(/\s+/g, ''))
|
|
46
53
|
try {
|
|
47
54
|
return isHex
|
|
48
|
-
?
|
|
49
|
-
:
|
|
55
|
+
? Script.fromHex(trimmed)
|
|
56
|
+
: Script.fromASM(trimmed)
|
|
50
57
|
} catch (err) {
|
|
51
|
-
console.error(
|
|
52
|
-
process.exit(1)
|
|
58
|
+
console.error('❌ Error parsing script:', err.message)
|
|
59
|
+
process.exit(1)
|
|
53
60
|
}
|
|
54
61
|
}
|
|
55
62
|
|
|
56
63
|
/**
|
|
57
64
|
* Display stack contents in readable format.
|
|
58
65
|
*/
|
|
59
|
-
function printStack(stack, altstack = []) {
|
|
60
|
-
const fmt = (b) => (b.length ? b.toString(
|
|
61
|
-
const top = stack.map(fmt)
|
|
62
|
-
console.log(
|
|
63
|
-
if (altstack.length) console.log(
|
|
66
|
+
function printStack (stack, altstack = []) {
|
|
67
|
+
const fmt = (b) => (b.length ? b.toString('hex') : '(empty)')
|
|
68
|
+
const top = stack.map(fmt)
|
|
69
|
+
console.log('Stack:', top.length ? top : ['(empty)'])
|
|
70
|
+
if (altstack.length) console.log('AltStack:', altstack.map(fmt))
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
/**
|
|
67
74
|
* Execute script step-by-step with optional interactivity.
|
|
68
75
|
*/
|
|
69
|
-
async function stepThroughScript(combinedScript, tx, verbose = true) {
|
|
70
|
-
const interpreter = new
|
|
71
|
-
interpreter.script = combinedScript
|
|
72
|
-
interpreter.tx = tx
|
|
73
|
-
interpreter.nIn = 0
|
|
74
|
-
interpreter.flags =
|
|
75
|
-
|
|
76
|
-
const chunks = combinedScript.chunks
|
|
77
|
-
|
|
76
|
+
async function stepThroughScript (combinedScript, tx, verbose = true) {
|
|
77
|
+
const interpreter = new Interpreter()
|
|
78
|
+
interpreter.script = combinedScript
|
|
79
|
+
interpreter.tx = tx
|
|
80
|
+
interpreter.nIn = 0
|
|
81
|
+
interpreter.flags = Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID
|
|
82
|
+
|
|
83
|
+
const chunks = combinedScript.chunks
|
|
84
|
+
|
|
78
85
|
// Browser compatibility check for readline
|
|
79
|
-
let rl = null
|
|
86
|
+
let rl = null
|
|
80
87
|
if (readline && typeof process !== 'undefined' && process.stdin) {
|
|
81
88
|
rl = readline.createInterface({
|
|
82
89
|
input: process.stdin,
|
|
83
|
-
output: process.stdout
|
|
84
|
-
})
|
|
90
|
+
output: process.stdout
|
|
91
|
+
})
|
|
85
92
|
}
|
|
86
93
|
|
|
87
|
-
console.log(
|
|
88
|
-
console.log(
|
|
89
|
-
console.log(
|
|
90
|
-
console.log(
|
|
91
|
-
console.log(
|
|
94
|
+
console.log('===========================================')
|
|
95
|
+
console.log('🔍 SCRIPT INTERPRETER DEBUGGER (STEP MODE)')
|
|
96
|
+
console.log('===========================================\n')
|
|
97
|
+
console.log('Script:', combinedScript.toASM())
|
|
98
|
+
console.log('-------------------------------------------')
|
|
92
99
|
|
|
93
100
|
for (let i = 0; i < chunks.length; i++) {
|
|
94
|
-
const chunk = chunks[i]
|
|
95
|
-
const opname =
|
|
101
|
+
const chunk = chunks[i]
|
|
102
|
+
const opname = Opcode.reverseMap[chunk.opcodenum] || 'PUSH'
|
|
96
103
|
|
|
97
104
|
if (verbose && rl) {
|
|
98
105
|
await new Promise((resolve) =>
|
|
99
106
|
rl.question(`\n[${i + 1}/${chunks.length}] Step (${opname}) — press Enter to execute...`, resolve)
|
|
100
|
-
)
|
|
107
|
+
)
|
|
101
108
|
} else if (verbose) {
|
|
102
|
-
console.log(`\n[${i + 1}/${chunks.length}] Step (${opname})`)
|
|
109
|
+
console.log(`\n[${i + 1}/${chunks.length}] Step (${opname})`)
|
|
103
110
|
}
|
|
104
111
|
|
|
105
112
|
try {
|
|
106
113
|
// Check if step method exists (compatibility check)
|
|
107
114
|
if (typeof interpreter.step === 'function') {
|
|
108
|
-
interpreter.step()
|
|
115
|
+
interpreter.step()
|
|
109
116
|
} else {
|
|
110
|
-
console.log(
|
|
111
|
-
console.log(
|
|
112
|
-
break
|
|
117
|
+
console.log('⚠️ Step-by-step execution not supported in this BSV version')
|
|
118
|
+
console.log('💡 Consider using runFullEvaluation instead')
|
|
119
|
+
break
|
|
113
120
|
}
|
|
114
|
-
|
|
115
|
-
console.log(`🧩 Executed: ${opname}`)
|
|
116
|
-
printStack(interpreter.stack, interpreter.altstack)
|
|
121
|
+
|
|
122
|
+
console.log(`🧩 Executed: ${opname}`)
|
|
123
|
+
printStack(interpreter.stack, interpreter.altstack)
|
|
117
124
|
} catch (err) {
|
|
118
|
-
console.log(`⚠️ Error executing ${opname}: ${err.message}`)
|
|
119
|
-
break
|
|
125
|
+
console.log(`⚠️ Error executing ${opname}: ${err.message}`)
|
|
126
|
+
break
|
|
120
127
|
}
|
|
121
128
|
}
|
|
122
129
|
|
|
123
|
-
if (rl) rl.close()
|
|
124
|
-
console.log(
|
|
125
|
-
console.log(
|
|
126
|
-
printStack(interpreter.stack || [], interpreter.altstack || [])
|
|
127
|
-
console.log(
|
|
130
|
+
if (rl) rl.close()
|
|
131
|
+
console.log('\n===========================================')
|
|
132
|
+
console.log('✅ Final Stack State:')
|
|
133
|
+
printStack(interpreter.stack || [], interpreter.altstack || [])
|
|
134
|
+
console.log('===========================================')
|
|
128
135
|
}
|
|
129
136
|
|
|
130
137
|
/**
|
|
131
138
|
* Run complete truth evaluation (non-interactive)
|
|
132
139
|
*/
|
|
133
|
-
function runFullEvaluation(unlockingScript, lockingScript, tx) {
|
|
134
|
-
console.log(
|
|
135
|
-
console.log(
|
|
136
|
-
console.log(
|
|
140
|
+
function runFullEvaluation (unlockingScript, lockingScript, tx) {
|
|
141
|
+
console.log('===========================================')
|
|
142
|
+
console.log('🔍 SCRIPT INTERPRETER DEBUGGER (FULL RUN)')
|
|
143
|
+
console.log('===========================================\n')
|
|
144
|
+
|
|
145
|
+
const interpreter = new Interpreter()
|
|
137
146
|
|
|
138
|
-
const interpreter = new bsv.Script.Interpreter();
|
|
139
|
-
|
|
140
147
|
// For smartledger-bsv, need to provide satoshisBN parameter
|
|
141
|
-
const satoshisBN = new
|
|
142
|
-
|
|
148
|
+
const satoshisBN = new BN(100000) // 100,000 satoshis
|
|
149
|
+
|
|
143
150
|
const verified = interpreter.verify(
|
|
144
151
|
unlockingScript,
|
|
145
152
|
lockingScript,
|
|
146
153
|
tx,
|
|
147
154
|
0,
|
|
148
|
-
|
|
155
|
+
Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID,
|
|
149
156
|
satoshisBN
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
console.log(
|
|
153
|
-
console.log(
|
|
154
|
-
console.log(
|
|
155
|
-
console.log(
|
|
156
|
-
console.log(
|
|
157
|
-
console.log(
|
|
158
|
-
console.log(
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
console.log('🔐 Locking Script:', lockingScript.toASM())
|
|
160
|
+
console.log('🔓 Unlocking Script:', unlockingScript.toASM())
|
|
161
|
+
console.log('-------------------------------------------')
|
|
162
|
+
console.log('✅ Result:', verified ? 'TRUE (Success)' : '❌ FALSE (Failure)')
|
|
163
|
+
console.log('Final Stack:', interpreter.stack.map((b) => b.toString('hex')))
|
|
164
|
+
console.log('AltStack:', interpreter.altstack.map((b) => b.toString('hex')))
|
|
165
|
+
console.log('===========================================')
|
|
159
166
|
}
|
|
160
167
|
|
|
161
168
|
/**
|
|
162
169
|
* Main entrypoint
|
|
163
170
|
*/
|
|
164
|
-
async function main() {
|
|
171
|
+
async function main () {
|
|
165
172
|
// Browser compatibility check
|
|
166
173
|
if (typeof process === 'undefined' || !process.argv) {
|
|
167
|
-
console.log(
|
|
168
|
-
return
|
|
174
|
+
console.log('⚙️ CLI mode not available in browser environment')
|
|
175
|
+
return
|
|
169
176
|
}
|
|
170
177
|
|
|
171
|
-
const args = process.argv.slice(2)
|
|
178
|
+
const args = process.argv.slice(2)
|
|
172
179
|
const getArg = (name) => {
|
|
173
|
-
const idx = args.indexOf(name)
|
|
174
|
-
return idx !== -1 && args[idx + 1] ? args[idx + 1] : null
|
|
175
|
-
}
|
|
180
|
+
const idx = args.indexOf(name)
|
|
181
|
+
return idx !== -1 && args[idx + 1] ? args[idx + 1] : null
|
|
182
|
+
}
|
|
176
183
|
|
|
177
|
-
const unlockingInput = getArg(
|
|
178
|
-
const lockingInput = getArg(
|
|
179
|
-
const combinedInput = getArg(
|
|
180
|
-
const stepMode = args.includes(
|
|
181
|
-
const truthMode = args.includes(
|
|
184
|
+
const unlockingInput = getArg('--unlocking')
|
|
185
|
+
const lockingInput = getArg('--locking')
|
|
186
|
+
const combinedInput = getArg('--combined')
|
|
187
|
+
const stepMode = args.includes('--step')
|
|
188
|
+
const truthMode = args.includes('--truth')
|
|
182
189
|
|
|
183
190
|
// Build scripts
|
|
184
|
-
let unlockingScript, lockingScript, combinedScript
|
|
191
|
+
let unlockingScript, lockingScript, combinedScript
|
|
185
192
|
if (combinedInput) {
|
|
186
|
-
combinedScript = parseScript(combinedInput)
|
|
193
|
+
combinedScript = parseScript(combinedInput)
|
|
187
194
|
} else {
|
|
188
|
-
unlockingScript = parseScript(unlockingInput)
|
|
189
|
-
lockingScript = parseScript(lockingInput)
|
|
195
|
+
unlockingScript = parseScript(unlockingInput)
|
|
196
|
+
lockingScript = parseScript(lockingInput)
|
|
190
197
|
const combinedBuf = Buffer.concat([
|
|
191
198
|
unlockingScript.toBuffer(),
|
|
192
|
-
lockingScript.toBuffer()
|
|
193
|
-
])
|
|
194
|
-
combinedScript =
|
|
199
|
+
lockingScript.toBuffer()
|
|
200
|
+
])
|
|
201
|
+
combinedScript = Script.fromBuffer(combinedBuf)
|
|
195
202
|
}
|
|
196
203
|
|
|
197
204
|
// Dummy TX context for interpreter
|
|
198
|
-
const tx = new
|
|
199
|
-
|
|
205
|
+
const tx = new Transaction()
|
|
206
|
+
|
|
200
207
|
// Create proper input with UTXO information
|
|
201
|
-
const dummyInput = new
|
|
208
|
+
const dummyInput = new Input({
|
|
202
209
|
prevTxId: '0'.repeat(64),
|
|
203
210
|
outputIndex: 0,
|
|
204
|
-
script:
|
|
211
|
+
script: Script.empty(),
|
|
205
212
|
satoshis: 100000,
|
|
206
|
-
output: new
|
|
213
|
+
output: new Output({
|
|
207
214
|
satoshis: 100000,
|
|
208
|
-
script:
|
|
215
|
+
script: Script.empty()
|
|
209
216
|
})
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
tx.addInput(dummyInput)
|
|
213
|
-
tx.addOutput(new
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
tx.addInput(dummyInput)
|
|
220
|
+
tx.addOutput(new Output({ satoshis: 100000, script: Script.empty() }))
|
|
214
221
|
|
|
215
222
|
if (stepMode) {
|
|
216
|
-
await stepThroughScript(combinedScript, tx, true)
|
|
223
|
+
await stepThroughScript(combinedScript, tx, true)
|
|
217
224
|
} else if (truthMode) {
|
|
218
|
-
runFullEvaluation(unlockingScript ||
|
|
225
|
+
runFullEvaluation(unlockingScript || Script.empty(), lockingScript || Script.empty(), tx)
|
|
219
226
|
} else {
|
|
220
|
-
console.log(
|
|
227
|
+
console.log('⚙️ No mode specified. Use --step or --truth')
|
|
221
228
|
}
|
|
222
229
|
}
|
|
223
230
|
|
|
@@ -228,9 +235,9 @@ module.exports = {
|
|
|
228
235
|
stepThroughScript,
|
|
229
236
|
runFullEvaluation,
|
|
230
237
|
main
|
|
231
|
-
}
|
|
238
|
+
}
|
|
232
239
|
|
|
233
240
|
// CLI execution
|
|
234
241
|
if (typeof require !== 'undefined' && require.main === module) {
|
|
235
|
-
main()
|
|
242
|
+
main()
|
|
236
243
|
}
|