@smartledger/bsv 8.3.1 → 9.1.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.
Files changed (169) hide show
  1. package/CHANGELOG.md +228 -0
  2. package/README.md +152 -1031
  3. package/STABILITY.md +134 -0
  4. package/bsv-gdaf.min.js +58 -53
  5. package/bsv-ltp.min.js +23 -18
  6. package/bsv-script-helper.min.js +1 -1
  7. package/bsv-smartcontract.min.js +21 -21
  8. package/bsv.bundle.js +58 -53
  9. package/bsv.d.ts +0 -4
  10. package/bsv.min.js +58 -53
  11. package/docs/BRC220_BATCH_LEAF_AMENDMENT.md +82 -7
  12. package/docs/MODULE_REFERENCE_COMPLETE.md +27 -27
  13. package/docs/README.md +2 -1
  14. package/docs/THREAT_MODEL.md +10 -2
  15. package/docs/advanced/UTXO_MANAGER_GUIDE.md +1 -1
  16. package/docs/getting-started/INSTALLATION.md +23 -23
  17. package/docs/getting-started/QUICK_START.md +7 -7
  18. package/docs/migration/FROM_BSV_1_5_6.md +5 -5
  19. package/docs/proposals/10.0.0-package-split.md +131 -0
  20. package/index.js +15 -26
  21. package/lib/block/merkleblock.js +19 -5
  22. package/lib/covenant/helpers.js +34 -35
  23. package/lib/covenant/pushtx.js +2 -2
  24. package/lib/custom-script-helper.js +0 -16
  25. package/lib/hdprivatekey.js +15 -0
  26. package/lib/ordinals/ordlock.js +1 -1
  27. package/lib/script/interpreter.js +109 -1
  28. package/lib/smart_contract/builder.js +14 -0
  29. package/lib/smart_contract/debugger.js +2 -2
  30. package/lib/smart_contract/dsl.js +2 -2
  31. package/lib/smart_contract/index.js +0 -3
  32. package/lib/smart_contract/locks.js +1 -41
  33. package/lib/smart_contract/pels.js +1 -1
  34. package/lib/smart_contract/token.js +1 -1
  35. package/lib/util/deprecate.js +159 -0
  36. package/package.json +19 -82
  37. package/tools/gen-brc220-batch-vector.js +205 -0
  38. package/version.js +1 -1
  39. package/.mocharc.json +0 -5
  40. package/test/address.js +0 -629
  41. package/test/block/block.js +0 -239
  42. package/test/block/blockheader.js +0 -270
  43. package/test/block/merkleblock.js +0 -207
  44. package/test/build/bundle_crypto_shim.js +0 -104
  45. package/test/build/bundle_externals.js +0 -125
  46. package/test/build/bundle_smoke.js +0 -55
  47. package/test/build/esbuild_main.js +0 -65
  48. package/test/build/esm_wrapper.js +0 -73
  49. package/test/build/exports_resolution.js +0 -84
  50. package/test/build/version_sync.js +0 -14
  51. package/test/cli/smoke.js +0 -261
  52. package/test/consensus/base58-vectors.js +0 -120
  53. package/test/consensus/sv-script-vectors.js +0 -81
  54. package/test/consensus/sv-sighash-vectors.js +0 -51
  55. package/test/consensus/sv-tx-vectors.js +0 -48
  56. package/test/credentials-test.js +0 -332
  57. package/test/crypto/backend_selection.js +0 -126
  58. package/test/crypto/bn.js +0 -168
  59. package/test/crypto/ecdsa.js +0 -426
  60. package/test/crypto/elliptic-fixed.js +0 -61
  61. package/test/crypto/hash.browser.js +0 -121
  62. package/test/crypto/hash.js +0 -122
  63. package/test/crypto/point.js +0 -193
  64. package/test/crypto/random.js +0 -105
  65. package/test/crypto/security.js +0 -176
  66. package/test/crypto/shamir.js +0 -177
  67. package/test/crypto/shamir_rngcap.js +0 -29
  68. package/test/crypto/signature.js +0 -399
  69. package/test/data/bip69.json +0 -215
  70. package/test/data/bitcoin-sv/README.md +0 -170
  71. package/test/data/bitcoin-sv/base58_encode_decode.json +0 -14
  72. package/test/data/bitcoin-sv/base58_keys_invalid.json +0 -152
  73. package/test/data/bitcoin-sv/base58_keys_valid.json +0 -452
  74. package/test/data/bitcoin-sv/script_tests.json +0 -2591
  75. package/test/data/bitcoin-sv/sighash.json +0 -1003
  76. package/test/data/bitcoin-sv/tx_invalid.json +0 -285
  77. package/test/data/bitcoin-sv/tx_valid.json +0 -367
  78. package/test/data/bitcoind/base58_keys_invalid.json +0 -152
  79. package/test/data/bitcoind/base58_keys_valid.json +0 -452
  80. package/test/data/bitcoind/blocks.json +0 -27
  81. package/test/data/bitcoind/script_tests.json +0 -2244
  82. package/test/data/bitcoind/sig_canonical.json +0 -7
  83. package/test/data/bitcoind/sig_noncanonical.json +0 -22
  84. package/test/data/bitcoind/tx_invalid.json +0 -177
  85. package/test/data/bitcoind/tx_valid.json +0 -224
  86. package/test/data/blk86756-testnet.dat +0 -0
  87. package/test/data/blk86756-testnet.js +0 -12
  88. package/test/data/blk86756-testnet.json +0 -684
  89. package/test/data/ecdsa.json +0 -230
  90. package/test/data/merkleblocks.js +0 -486
  91. package/test/data/messages.json +0 -22
  92. package/test/data/sighash.json +0 -1004
  93. package/test/data/tx_creation.json +0 -85
  94. package/test/didweb/relationships.js +0 -144
  95. package/test/ecies/bitcore-ecies.js +0 -178
  96. package/test/ecies/electrum-ecies.js +0 -206
  97. package/test/encoding/base58.js +0 -131
  98. package/test/encoding/base58check.js +0 -145
  99. package/test/encoding/bufferreader.js +0 -328
  100. package/test/encoding/bufferwriter.js +0 -160
  101. package/test/encoding/varint.js +0 -104
  102. package/test/gdaf/anchor_no_key_leak.js +0 -146
  103. package/test/gdaf/anchor_spv.js +0 -163
  104. package/test/gdaf/canonicalization.js +0 -106
  105. package/test/gdaf/canonicalize.js +0 -140
  106. package/test/gdaf/zk_prover.js +0 -204
  107. package/test/hdkeys.js +0 -365
  108. package/test/hdprivatekey.js +0 -339
  109. package/test/hdpublickey.js +0 -293
  110. package/test/index.js +0 -16
  111. package/test/ltp/ids.js +0 -74
  112. package/test/ltp/right.js +0 -198
  113. package/test/ltp/verify_failclosed.js +0 -134
  114. package/test/message/message.js +0 -190
  115. package/test/mnemonic/data/fixtures.json +0 -300
  116. package/test/mnemonic/mnemonic.js +0 -277
  117. package/test/mnemonic/mocha.opts +0 -1
  118. package/test/mnemonic/pbkdf2.test.js +0 -43
  119. package/test/networks.js +0 -207
  120. package/test/notaryhash/batch_leaf.js +0 -140
  121. package/test/notaryhash/certificate.js +0 -249
  122. package/test/notaryhash/encoding.js +0 -186
  123. package/test/notaryhash/interop.js +0 -112
  124. package/test/notaryhash/merkle.js +0 -181
  125. package/test/notaryhash/script.js +0 -270
  126. package/test/notaryhash/verify.js +0 -342
  127. package/test/opcode.js +0 -186
  128. package/test/ordinals/bsv20.js +0 -337
  129. package/test/ordinals/inscription.js +0 -319
  130. package/test/ordinals/ordlock.js +0 -613
  131. package/test/privatekey.js +0 -540
  132. package/test/publickey.js +0 -411
  133. package/test/regressions.js +0 -215
  134. package/test/script/chronicle.js +0 -543
  135. package/test/script/defaults.js +0 -160
  136. package/test/script/genesis_limits.js +0 -203
  137. package/test/script/interpreter.js +0 -776
  138. package/test/script/script.js +0 -1259
  139. package/test/script/string_ops.js +0 -88
  140. package/test/security/fail_closed_contracts.js +0 -104
  141. package/test/security/threat_model_coverage.js +0 -31
  142. package/test/smart_contract/covenants.js +0 -245
  143. package/test/smart_contract/dsl_debugger.js +0 -101
  144. package/test/smart_contract/extract_field.js +0 -61
  145. package/test/smart_contract/nonenforcing_guard.js +0 -44
  146. package/test/smart_contract/ordinal_transfer.js +0 -81
  147. package/test/smart_contract/preimage.js +0 -98
  148. package/test/smart_contract/sighash_marketplace.js +0 -108
  149. package/test/smart_contract/token_generalized.js +0 -185
  150. package/test/spv/headerchain.js +0 -86
  151. package/test/spv/merkleproof.js +0 -133
  152. package/test/statuslist/failclosed.js +0 -78
  153. package/test/transaction/deserialize.js +0 -33
  154. package/test/transaction/input/input.js +0 -92
  155. package/test/transaction/input/multisig.js +0 -174
  156. package/test/transaction/input/multisigscripthash.js +0 -111
  157. package/test/transaction/input/publickey.js +0 -68
  158. package/test/transaction/input/publickeyhash.js +0 -59
  159. package/test/transaction/output.js +0 -185
  160. package/test/transaction/sighash.js +0 -91
  161. package/test/transaction/signature.js +0 -127
  162. package/test/transaction/transaction.js +0 -1299
  163. package/test/transaction/unspentoutput.js +0 -97
  164. package/test/types/dts_drift.js +0 -124
  165. package/test/types/surface_honesty.js +0 -102
  166. package/test/util/id.js +0 -72
  167. package/test/util/js.js +0 -76
  168. package/test/util/preconditions.js +0 -79
  169. package/test/vcjwt/interop.js +0 -126
@@ -1,613 +0,0 @@
1
- 'use strict'
2
-
3
- /* global describe, it, before, after */
4
-
5
- // OrdLock marketplace covenant — list a 1Sat ordinal for sale behind a trustless
6
- // "pay the seller or cancel" covenant. Every locking/unlocking pair below is driven
7
- // through the consensus interpreter (the "if it emits Script, it's interpreter-
8
- // verified" operating principle), including adversarial spends that MUST be rejected.
9
-
10
- require('chai').should()
11
- var bsv = require('../..')
12
- var Ord = bsv.Ordinals
13
- var SC = bsv.SmartContract
14
- var Script = bsv.Script
15
- var PrivateKey = bsv.PrivateKey
16
- var Transaction = bsv.Transaction
17
-
18
- var help = SC.CovenantHelpers
19
- var verify = help.verify
20
- var p2pkhOutput = help.p2pkhOutput
21
-
22
- var ORD_SATS = 1
23
- var PRICE = 100000
24
-
25
- describe('Ordinals OrdLock marketplace', function () {
26
- this.timeout(20000)
27
- var I = bsv.Script.Interpreter
28
- var saved
29
-
30
- before(function () {
31
- saved = I.getLimits()
32
- SC.enableGenesis()
33
- })
34
- after(function () {
35
- I.setLimits(saved)
36
- })
37
-
38
- var seller = PrivateKey.fromRandom()
39
- var buyer = PrivateKey.fromRandom()
40
-
41
- // A funding tx that pays the listing UTXO, plus a spend consuming it at input 0.
42
- function fundListing (lock) {
43
- return help.fundAndSpend(lock, ORD_SATS, {}).spend
44
- }
45
-
46
- // Build the buyer's outputs: [ ordinal->buyer (index 0), payment->seller (index 1) ].
47
- function buyerOutputs (payTo, price) {
48
- return [
49
- p2pkhOutput(buyer, ORD_SATS), // the ordinal goes to the buyer at output 0
50
- Ord.payOutputFor(payTo || seller, price == null ? PRICE : price) // seller payment at 1
51
- ]
52
- }
53
-
54
- it('verifies a purchase that pays the seller', function () {
55
- var lock = Ord.buildOrdLock({ seller: seller.toAddress(), price: PRICE })
56
- var spend = fundListing(lock)
57
- buyerOutputs().forEach(function (o) { spend.addOutput(o) })
58
- Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS })
59
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(true)
60
- })
61
-
62
- it('stays valid when the buyer adds a funding input and a change output', function () {
63
- var lock = Ord.buildOrdLock({ seller: seller.toAddress(), price: PRICE })
64
- var spend = fundListing(lock)
65
- // outputs: [ordinal->buyer, payment->seller, change->buyer]
66
- spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
67
- spend.addOutput(Ord.payOutputFor(seller, PRICE))
68
- spend.addOutput(p2pkhOutput(buyer, 40000)) // buyer change, trailing
69
-
70
- // Grind + build the unlock BEFORE adding the (ANYONECANPAY) funding input.
71
- Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS })
72
- spend.addInput(new Transaction.Input({
73
- prevTxId: '22'.repeat(32), outputIndex: 0, script: Script.empty()
74
- }), p2pkhOutput(buyer, PRICE + 50000).script, PRICE + 50000)
75
-
76
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(true)
77
- })
78
-
79
- it('rejects a purchase that underpays the seller', function () {
80
- var lock = Ord.buildOrdLock({ seller: seller.toAddress(), price: PRICE })
81
- var spend = fundListing(lock)
82
- spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
83
- spend.addOutput(Ord.payOutputFor(seller, PRICE))
84
- Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS })
85
-
86
- // Tamper: shave the seller's payment after grinding → committed outputs change.
87
- spend.outputs[1] = Ord.payOutputFor(seller, PRICE - 1)
88
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(false)
89
- })
90
-
91
- it('rejects a purchase that redirects the payment to someone else', function () {
92
- var lock = Ord.buildOrdLock({ seller: seller.toAddress(), price: PRICE })
93
- var spend = fundListing(lock)
94
- spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
95
- spend.addOutput(Ord.payOutputFor(seller, PRICE))
96
- Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS })
97
-
98
- spend.outputs[1] = Ord.payOutputFor(PrivateKey.fromRandom(), PRICE) // thief's address
99
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(false)
100
- })
101
-
102
- it('pays a distinct payTo recipient while the seller keeps cancel rights', function () {
103
- var payee = PrivateKey.fromRandom()
104
- var lock = Ord.buildOrdLock({ seller: seller.toAddress(), payTo: payee.toAddress(), price: PRICE })
105
- var spend = fundListing(lock)
106
- spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
107
- spend.addOutput(Ord.payOutputFor(payee, PRICE))
108
- Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS })
109
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(true)
110
- })
111
-
112
- it('purchase() fail-fasts (throws) when the payout output is wrong', function () {
113
- var lock = Ord.buildOrdLock({ seller: seller.toAddress(), price: PRICE })
114
- var spend = fundListing(lock)
115
- spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
116
- spend.addOutput(Ord.payOutputFor(seller, PRICE - 1)) // underpay
117
- var pinned = Ord.payOutputFor(seller, PRICE)
118
- ;(function () {
119
- Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS, payOutput: pinned })
120
- }).should.throw(/would not be paid/)
121
- })
122
-
123
- it('lets the seller CANCEL the listing with their key', function () {
124
- var lock = Ord.buildOrdLock({ seller: seller.toAddress(), price: PRICE })
125
- var spend = fundListing(lock)
126
- spend.addOutput(p2pkhOutput(seller, ORD_SATS)) // seller reclaims the ordinal
127
- Ord.cancelOrdLock({ privateKey: seller, spend: spend, lockingScript: lock, satoshis: ORD_SATS })
128
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(true)
129
- })
130
-
131
- it('rejects a CANCEL signed by the wrong key', function () {
132
- var lock = Ord.buildOrdLock({ seller: seller.toAddress(), price: PRICE })
133
- var spend = fundListing(lock)
134
- spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
135
- Ord.cancelOrdLock({ privateKey: buyer, spend: spend, lockingScript: lock, satoshis: ORD_SATS })
136
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(false)
137
- })
138
-
139
- it('listInscriptionOutput is a 1-sat output whose script cancels + purchases', function () {
140
- var out = Ord.listInscriptionOutput({ seller: seller.toAddress(), price: PRICE })
141
- out.satoshis.should.equal(1)
142
- // Purchase path round-trips through the interpreter.
143
- var lock = out.script
144
- var spend = fundListing(lock)
145
- spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
146
- spend.addOutput(Ord.payOutputFor(seller, PRICE))
147
- Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS })
148
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(true)
149
- })
150
-
151
- it('can inscribe + list in one output; the envelope is inert and parseable', function () {
152
- var lock = Ord.buildOrdLock({
153
- seller: seller.toAddress(),
154
- price: PRICE,
155
- inscription: { contentType: 'text/plain', content: 'for sale' }
156
- })
157
- // The inscription rides along and is recoverable.
158
- Ord.isInscription(lock).should.equal(true)
159
- Ord.parseInscription(lock).contentText.should.equal('for sale')
160
- // ...and the covenant still enforces payment.
161
- var spend = fundListing(lock)
162
- spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
163
- spend.addOutput(Ord.payOutputFor(seller, PRICE))
164
- Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS })
165
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(true)
166
- })
167
- })
168
-
169
- describe('Ordinals OrdLock — multi-output payments (royalties + marketplace fee)', function () {
170
- this.timeout(20000)
171
- var I = bsv.Script.Interpreter
172
- var saved
173
- before(function () {
174
- saved = I.getLimits()
175
- SC.enableGenesis()
176
- })
177
- after(function () {
178
- I.setLimits(saved)
179
- })
180
-
181
- var seller = PrivateKey.fromRandom()
182
- var royalty = PrivateKey.fromRandom()
183
- var market = PrivateKey.fromRandom()
184
- var buyer = PrivateKey.fromRandom()
185
- var ROY = 5000
186
- var FEE = 2000
187
-
188
- function multiLock () {
189
- return Ord.buildOrdLock({
190
- seller: seller.toAddress(),
191
- price: PRICE,
192
- royalties: [ { address: royalty.toAddress(), satoshis: ROY }, { address: market.toAddress(), satoshis: FEE } ]
193
- })
194
- }
195
-
196
- // The three pinned outputs, in order, that a purchase must recreate.
197
- function pinnedOuts () {
198
- return [ Ord.payOutputFor(seller, PRICE), Ord.payOutputFor(royalty, ROY), Ord.payOutputFor(market, FEE) ]
199
- }
200
-
201
- it('pays seller + royalty + marketplace fee atomically (auto-derived payoutCount)', function () {
202
- var lock = multiLock()
203
- var spend = help.fundAndSpend(lock, ORD_SATS, {}).spend
204
- spend.addOutput(p2pkhOutput(buyer, ORD_SATS)) // ordinal -> buyer
205
- pinnedOuts().forEach(function (o) { spend.addOutput(o) }) // the 3 pinned payments
206
- spend.addOutput(p2pkhOutput(buyer, 10000)) // buyer change (trailing)
207
- // No payoutCount passed — purchaseOrdLock reads it from the listing script.
208
- Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS })
209
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(true)
210
- })
211
-
212
- it('rejects a purchase that skips the royalty payment', function () {
213
- var lock = multiLock()
214
- var spend = help.fundAndSpend(lock, ORD_SATS, {}).spend
215
- spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
216
- pinnedOuts().forEach(function (o) { spend.addOutput(o) })
217
- Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS, validate: false })
218
- // Zero out the royalty (output index 2) after signing → covenant must reject.
219
- spend.outputs[2] = Ord.payOutputFor(royalty, 1)
220
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(false)
221
- })
222
-
223
- it('rejects a purchase that redirects the marketplace fee', function () {
224
- var lock = multiLock()
225
- var spend = help.fundAndSpend(lock, ORD_SATS, {}).spend
226
- spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
227
- pinnedOuts().forEach(function (o) { spend.addOutput(o) })
228
- Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS, validate: false })
229
- spend.outputs[3] = Ord.payOutputFor(PrivateKey.fromRandom(), FEE) // thief keeps the fee
230
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(false)
231
- })
232
-
233
- it('purchaseOrdLock fail-fasts if a pinned payment is missing before broadcast', function () {
234
- var lock = multiLock()
235
- var spend = help.fundAndSpend(lock, ORD_SATS, {}).spend
236
- spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
237
- spend.addOutput(Ord.payOutputFor(seller, PRICE))
238
- spend.addOutput(Ord.payOutputFor(royalty, ROY))
239
- spend.addOutput(Ord.payOutputFor(market, 1)) // underpays the fee
240
- ;(function () {
241
- Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS })
242
- }).should.throw(/would not be paid/)
243
- })
244
- })
245
-
246
- describe('Ordinals OrdLock — parseOrdLock (self-describing listings)', function () {
247
- var seller = PrivateKey.fromRandom()
248
- var royalty = PrivateKey.fromRandom()
249
-
250
- it('recovers seller, payments, total price, and inscription', function () {
251
- var lock = Ord.buildOrdLock({
252
- seller: seller.toAddress(),
253
- price: PRICE,
254
- royalties: [ { address: royalty.toAddress(), satoshis: 7500 } ],
255
- inscription: { contentType: 'image/png', content: 'PNGDATA' }
256
- })
257
- var p = Ord.parseOrdLock(lock)
258
- p.should.be.an('object')
259
- p.seller.address.should.equal(seller.toAddress().toString())
260
- p.payOutputs.length.should.equal(2)
261
- p.payOutputs[0].satoshis.should.equal(PRICE)
262
- p.payOutputs[0].address.should.equal(seller.toAddress().toString())
263
- p.payOutputs[1].satoshis.should.equal(7500)
264
- p.payOutputs[1].address.should.equal(royalty.toAddress().toString())
265
- p.totalPrice.should.equal(PRICE + 7500)
266
- p.inscription.contentType.should.equal('image/png')
267
- p.inscription.contentText.should.equal('PNGDATA')
268
- })
269
-
270
- it('isOrdLock distinguishes a listing from a plain P2PKH and an inscription', function () {
271
- var lock = Ord.buildOrdLock({ seller: seller.toAddress(), price: PRICE })
272
- Ord.isOrdLock(lock).should.equal(true)
273
- Ord.isOrdLock(lock.toHex()).should.equal(true) // accepts hex too
274
- Ord.isOrdLock(bsv.Script.buildPublicKeyHashOut(seller.toAddress())).should.equal(false)
275
- Ord.isOrdLock(Ord.buildInscription({ address: seller.toAddress(), content: 'x' })).should.equal(false)
276
- })
277
-
278
- it('parseOrdLock returns null for a non-listing script', function () {
279
- var isNull = Ord.parseOrdLock(bsv.Script.buildPublicKeyHashOut(seller.toAddress())) === null
280
- isNull.should.equal(true)
281
- })
282
- })
283
-
284
- describe('Ordinals OrdLock — buildPurchaseTx (end-to-end)', function () {
285
- this.timeout(20000)
286
- var I = bsv.Script.Interpreter
287
- var saved
288
- before(function () {
289
- saved = I.getLimits()
290
- SC.enableGenesis()
291
- })
292
- after(function () {
293
- I.setLimits(saved)
294
- })
295
-
296
- var seller = PrivateKey.fromRandom()
297
- var royalty = PrivateKey.fromRandom()
298
- var buyer = PrivateKey.fromRandom()
299
- var fundKey = PrivateKey.fromRandom()
300
- var fundScript = bsv.Script.buildPublicKeyHashOut(fundKey.toAddress())
301
-
302
- function listing () {
303
- var lock = Ord.buildOrdLock({
304
- seller: seller.toAddress(), price: PRICE,
305
- royalties: [ { address: royalty.toAddress(), satoshis: 5000 } ]
306
- })
307
- return { txid: 'a1'.repeat(32), outputIndex: 0, script: lock, satoshis: ORD_SATS }
308
- }
309
-
310
- function fundingCoins (amounts) {
311
- return amounts.map(function (sats, i) {
312
- return { txid: 'f' + i + '0'.repeat(62), outputIndex: i, script: fundScript, satoshis: sats, privateKey: fundKey }
313
- })
314
- }
315
-
316
- // Verify every input of a built tx through the consensus interpreter.
317
- function verifyAllInputs (tx, l, funding) {
318
- var r0 = verify(tx.inputs[0].script, l.script, { tx: tx, satoshis: l.satoshis, inputIndex: 0 })
319
- r0.ok.should.equal(true)
320
- funding.forEach(function (f, i) {
321
- var r = verify(tx.inputs[i + 1].script, fundScript, { tx: tx, satoshis: f.satoshis, inputIndex: i + 1 })
322
- r.ok.should.equal(true)
323
- })
324
- }
325
-
326
- it('assembles a fully-signed purchase from just the listing UTXO and buyer coins', function () {
327
- var l = listing()
328
- var funding = fundingCoins([80000, 60000])
329
- var tx = Ord.buildPurchaseTx({
330
- listing: l, ordinalDestination: buyer.toAddress(), funding: funding, fee: 500
331
- })
332
- // Layout: [ordinal->buyer, seller PRICE, royalty 5000, change].
333
- tx.inputs.length.should.equal(3)
334
- tx.outputs.length.should.equal(4)
335
- tx.outputs[0].satoshis.should.equal(ORD_SATS)
336
- tx.outputs[1].satoshis.should.equal(PRICE)
337
- tx.outputs[2].satoshis.should.equal(5000)
338
- // Value conservation: in 1+80000+60000 = 140001; out 1+100000+5000+change; fee 500.
339
- tx.outputs[3].satoshis.should.equal(140001 - ORD_SATS - PRICE - 5000 - 500)
340
- verifyAllInputs(tx, l, funding)
341
- })
342
-
343
- it('reads the required payments straight off the listing (buyer supplies no terms)', function () {
344
- var l = listing()
345
- var funding = fundingCoins([200000])
346
- var tx = Ord.buildPurchaseTx({ listing: l, ordinalDestination: buyer.toAddress(), funding: funding, fee: 250 })
347
- // The pinned payments came from parseOrdLock, and the covenant still validates.
348
- verifyAllInputs(tx, l, funding)
349
- tx.outputs[1].satoshis.should.equal(PRICE) // seller
350
- tx.outputs[2].satoshis.should.equal(5000) // royalty
351
- })
352
-
353
- it('omits the change output when funding is exact', function () {
354
- var l = listing()
355
- var funding = fundingCoins([PRICE + 5000 + 500]) // exactly payments + fee
356
- var tx = Ord.buildPurchaseTx({ listing: l, ordinalDestination: buyer.toAddress(), funding: funding, fee: 500 })
357
- tx.outputs.length.should.equal(3) // ordinal + 2 payments, no change
358
- verifyAllInputs(tx, l, funding)
359
- })
360
-
361
- it('throws on insufficient funding', function () {
362
- var l = listing()
363
- var funding = fundingCoins([1000]) // nowhere near PRICE + royalty + fee
364
- ;(function () {
365
- Ord.buildPurchaseTx({ listing: l, ordinalDestination: buyer.toAddress(), funding: funding, fee: 500 })
366
- }).should.throw(/insufficient funding/)
367
- })
368
-
369
- it('the covenant still binds — tampering any output after build breaks input 0', function () {
370
- var l = listing()
371
- var funding = fundingCoins([200000])
372
- var tx = Ord.buildPurchaseTx({ listing: l, ordinalDestination: buyer.toAddress(), funding: funding, fee: 500 })
373
- tx.outputs[1] = Ord.payOutputFor(PrivateKey.fromRandom(), PRICE) // steal the seller payment
374
- verify(tx.inputs[0].script, l.script, { tx: tx, satoshis: l.satoshis, inputIndex: 0 }).ok.should.equal(false)
375
- })
376
- })
377
-
378
- describe('Ordinals OrdLock — full lifecycle (list -> buy / cancel)', function () {
379
- this.timeout(20000)
380
- var I = bsv.Script.Interpreter
381
- var saved
382
- before(function () {
383
- saved = I.getLimits()
384
- SC.enableGenesis()
385
- })
386
- after(function () {
387
- I.setLimits(saved)
388
- })
389
-
390
- var owner = PrivateKey.fromRandom() // current ordinal owner / seller
391
- var royalty = PrivateKey.fromRandom()
392
- var buyer = PrivateKey.fromRandom()
393
- var fundKey = PrivateKey.fromRandom()
394
- var fundScript = bsv.Script.buildPublicKeyHashOut(fundKey.toAddress())
395
- var ownerScript = bsv.Script.buildPublicKeyHashOut(owner.toAddress())
396
-
397
- // The ordinal UTXO currently held by the owner under P2PKH.
398
- function ordinalUtxo () {
399
- return { txid: '0e'.repeat(32), outputIndex: 0, script: ownerScript, satoshis: ORD_SATS, privateKey: owner }
400
- }
401
- function fundCoin (sats, i) {
402
- return { txid: 'f' + (i || 0) + '0'.repeat(62), outputIndex: 0, script: fundScript, satoshis: sats, privateKey: fundKey }
403
- }
404
-
405
- it('buildListingTx signs the ordinal + fee and preserves the sat into the listing', function () {
406
- var funding = [fundCoin(20000)]
407
- var out = Ord.buildListingTx({
408
- ordinal: ordinalUtxo(), seller: owner.toAddress(), price: PRICE,
409
- royalties: [{ address: royalty.toAddress(), satoshis: 4000 }],
410
- funding: funding, fee: 500
411
- })
412
- // Output 0 is the listing (ordinal's sat), and it's a parseable OrdLock.
413
- out.tx.outputs[0].satoshis.should.equal(ORD_SATS)
414
- out.listingOutpoint.outputIndex.should.equal(0)
415
- Ord.isOrdLock(out.tx.outputs[0].script).should.equal(true)
416
- Ord.parseOrdLock(out.listingScript).totalPrice.should.equal(PRICE + 4000)
417
- // Change = funding - fee.
418
- out.tx.outputs[1].satoshis.should.equal(20000 - 500)
419
- // Both inputs (ordinal P2PKH + funding P2PKH) verify.
420
- verify(out.tx.inputs[0].script, ownerScript, { tx: out.tx, satoshis: ORD_SATS, inputIndex: 0 }).ok.should.equal(true)
421
- verify(out.tx.inputs[1].script, fundScript, { tx: out.tx, satoshis: 20000, inputIndex: 1 }).ok.should.equal(true)
422
- })
423
-
424
- it('a listing created by buildListingTx can then be PURCHASED end-to-end', function () {
425
- // 1) Seller lists.
426
- var listed = Ord.buildListingTx({
427
- ordinal: ordinalUtxo(), seller: owner.toAddress(), price: PRICE,
428
- royalties: [{ address: royalty.toAddress(), satoshis: 4000 }],
429
- funding: [fundCoin(20000)], fee: 500
430
- })
431
- // 2) Buyer purchases the freshly-created listing UTXO (terms read off its script).
432
- var listing = {
433
- txid: listed.listingOutpoint.txid,
434
- outputIndex: listed.listingOutpoint.outputIndex,
435
- script: listed.listingScript,
436
- satoshis: ORD_SATS
437
- }
438
- var buyFunding = [fundCoin(200000, 1)]
439
- var buyTx = Ord.buildPurchaseTx({
440
- listing: listing, ordinalDestination: buyer.toAddress(), funding: buyFunding, fee: 500
441
- })
442
- // Ordinal -> buyer, seller + royalty paid, change back to buyer.
443
- buyTx.outputs[0].satoshis.should.equal(ORD_SATS)
444
- buyTx.outputs[1].satoshis.should.equal(PRICE)
445
- buyTx.outputs[2].satoshis.should.equal(4000)
446
- // Covenant input + funding input both verify through the interpreter.
447
- verify(buyTx.inputs[0].script, listing.script, { tx: buyTx, satoshis: ORD_SATS, inputIndex: 0 }).ok.should.equal(true)
448
- verify(buyTx.inputs[1].script, fundScript, { tx: buyTx, satoshis: 200000, inputIndex: 1 }).ok.should.equal(true)
449
- })
450
-
451
- it('a listing created by buildListingTx can instead be CANCELLED by the seller', function () {
452
- var listed = Ord.buildListingTx({
453
- ordinal: ordinalUtxo(), seller: owner.toAddress(), price: PRICE,
454
- funding: [fundCoin(20000)], fee: 500
455
- })
456
- // Seller reclaims the ordinal from the listing UTXO.
457
- var reclaim = new Transaction()
458
- reclaim.addInput(new Transaction.Input({
459
- prevTxId: listed.listingOutpoint.txid, outputIndex: 0, script: Script.empty()
460
- }), listed.listingScript, ORD_SATS)
461
- reclaim.addOutput(p2pkhOutput(owner, ORD_SATS))
462
- Ord.cancelOrdLock({ privateKey: owner, spend: reclaim, lockingScript: listed.listingScript, satoshis: ORD_SATS })
463
- verify(reclaim.inputs[0].script, listed.listingScript, { tx: reclaim, satoshis: ORD_SATS }).ok.should.equal(true)
464
- })
465
-
466
- it('buildListingTx throws when funding cannot cover the fee', function () {
467
- ;(function () {
468
- Ord.buildListingTx({
469
- ordinal: ordinalUtxo(), seller: owner.toAddress(), price: PRICE,
470
- funding: [fundCoin(300)], fee: 500
471
- })
472
- }).should.throw(/insufficient funding/)
473
- })
474
- })
475
-
476
- describe('Ordinals OrdLock — code-review regressions', function () {
477
- this.timeout(20000)
478
- var I = bsv.Script.Interpreter
479
- var saved
480
- before(function () {
481
- saved = I.getLimits()
482
- SC.enableGenesis()
483
- })
484
- after(function () {
485
- I.setLimits(saved)
486
- })
487
-
488
- var owner = PrivateKey.fromRandom()
489
- var fundKey = PrivateKey.fromRandom()
490
- var ownerScript = bsv.Script.buildPublicKeyHashOut(owner.toAddress())
491
- var fundScript = bsv.Script.buildPublicKeyHashOut(fundKey.toAddress())
492
-
493
- // Finding #1: buildListingTx must sign the ordinal input with the resolved value even
494
- // when the caller omits ordinal.satoshis (documented to default to 1).
495
- it('buildListingTx signs a valid ordinal input when ordinal.satoshis is omitted', function () {
496
- var out = Ord.buildListingTx({
497
- ordinal: { txid: '0e'.repeat(32), outputIndex: 0, script: ownerScript, privateKey: owner }, // no satoshis
498
- seller: owner.toAddress(),
499
- price: PRICE,
500
- funding: [{ txid: 'f0'.repeat(32), outputIndex: 0, script: fundScript, satoshis: 20000, privateKey: fundKey }],
501
- fee: 500
502
- })
503
- verify(out.tx.inputs[0].script, ownerScript, { tx: out.tx, satoshis: 1, inputIndex: 0 }).ok.should.equal(true)
504
- })
505
-
506
- // Finding #2: parsed addresses honor a network option instead of always being livenet.
507
- it('parseOrdLock formats addresses for the requested network', function () {
508
- var lock = Ord.buildOrdLock({ seller: owner.toAddress(), price: PRICE })
509
- var live = Ord.parseOrdLock(lock).seller.address
510
- var test = Ord.parseOrdLock(lock, { network: 'testnet' }).seller.address
511
- live.should.not.equal(test)
512
- bsv.Address.fromString(test).network.name.should.equal('testnet')
513
- bsv.Address.fromString(live).network.name.should.equal('livenet')
514
- })
515
-
516
- // Finding #3: a funding coin missing satoshis fails fast (was a silent wrong-amount signature).
517
- it('buildPurchaseTx throws when a funding coin has no satoshis', function () {
518
- var lock = Ord.buildOrdLock({ seller: owner.toAddress(), price: PRICE })
519
- ;(function () {
520
- Ord.buildPurchaseTx({
521
- listing: { txid: 'a1'.repeat(32), outputIndex: 0, script: lock, satoshis: 1 },
522
- ordinalDestination: fundKey.toAddress(),
523
- funding: [{ txid: 'b1'.repeat(32), outputIndex: 0, script: fundScript, privateKey: fundKey }], // no satoshis
524
- fee: 500
525
- })
526
- }).should.throw(/satoshis/)
527
- })
528
-
529
- // Finding #5: payOutputFor never silently ignores price when handed a full Output.
530
- it('payOutputFor rejects a Transaction.Output (price would be ignored)', function () {
531
- var o = new bsv.Transaction.Output({ script: ownerScript, satoshis: 5 })
532
- ;(function () { Ord.payOutputFor(o, PRICE) }).should.throw()
533
- })
534
-
535
- it('buildOrdLock still accepts a pre-built Output as payTo (uses its own value)', function () {
536
- var lock = Ord.buildOrdLock({
537
- seller: owner.toAddress(), price: 1,
538
- payTo: new bsv.Transaction.Output({ script: ownerScript, satoshis: 777 })
539
- })
540
- Ord.parseOrdLock(lock).totalPrice.should.equal(777)
541
- })
542
-
543
- // parseOrdLock recovers terms from the script's shape. Shape alone proves nothing: a
544
- // script wearing the same opcode arrangement without the OP_PUSH_TX covenant enforces no
545
- // payment, and reporting a seller and a price for it invents a listing that does not
546
- // exist. The terms are therefore verified by rebuilding the listing and comparing bytes.
547
- describe('is a verifier, not a shape matcher', function () {
548
- var attacker = bsv.PrivateKey.fromBuffer(Buffer.alloc(32, 9))
549
-
550
- // Wears the fingerprint parseOrdLock keys off: a top-level OP_IF with a 20-byte push,
551
- // and OP_TOALTSTACK <blob> OP_CAT OP_SWAP in the OP_ELSE branch. Enforces nothing.
552
- function fabricatedListing () {
553
- var payout = new bsv.Transaction.Output({
554
- script: bsv.Script.buildPublicKeyHashOut(attacker.toAddress()),
555
- satoshis: 100000000
556
- })
557
- var s = new bsv.Script()
558
- s.add(bsv.Opcode.OP_IF)
559
- s.add(attacker.toAddress().hashBuffer)
560
- s.add(bsv.Opcode.OP_DROP)
561
- s.add(bsv.Opcode.OP_ELSE)
562
- s.add(bsv.Opcode.OP_TOALTSTACK)
563
- s.add(payout.toBufferWriter().toBuffer())
564
- s.add(bsv.Opcode.OP_CAT)
565
- s.add(bsv.Opcode.OP_SWAP)
566
- s.add(bsv.Opcode.OP_ENDIF)
567
- s.add(bsv.Opcode.OP_TRUE)
568
- return s
569
- }
570
-
571
- it('rejects a fabricated listing that enforces no payment', function () {
572
- var fake = fabricatedListing()
573
- Ord.isOrdLock(fake).should.equal(false)
574
- ;(Ord.parseOrdLock(fake) === null).should.equal(true)
575
- })
576
-
577
- it('proves the fabricated listing really is spendable for free', function () {
578
- // Without this, the test above only shows we reject *something*.
579
- var interp = new bsv.Script.Interpreter()
580
- var unlock = new bsv.Script().add(bsv.Opcode.OP_1)
581
- var spent = interp.verify(unlock, fabricatedListing(), new bsv.Transaction(), 0, 0)
582
- spent.should.equal(true)
583
- })
584
-
585
- it('rejects a genuine listing with anything appended', function () {
586
- var real = Ord.buildOrdLock({ seller: owner.toAddress(), price: 50000 })
587
- var tampered = bsv.Script.fromHex(real.toHex()).add(bsv.Opcode.OP_NOP)
588
- Ord.isOrdLock(tampered).should.equal(false)
589
- })
590
-
591
- it('still accepts every genuine listing shape', function () {
592
- var simple = Ord.buildOrdLock({ seller: owner.toAddress(), price: 50000 })
593
- Ord.parseOrdLock(simple).totalPrice.should.equal(50000)
594
-
595
- var withInsc = Ord.buildOrdLock({
596
- seller: owner.toAddress(),
597
- price: 777,
598
- inscription: { contentType: 'text/plain', content: 'for sale' }
599
- })
600
- var parsed = Ord.parseOrdLock(withInsc)
601
- parsed.totalPrice.should.equal(777)
602
- parsed.inscription.contentText.should.equal('for sale')
603
-
604
- var multi = Ord.buildOrdLock({
605
- seller: owner.toAddress(),
606
- price: 1000,
607
- royalties: [{ address: attacker.toAddress(), satoshis: 50 }]
608
- })
609
- Ord.parseOrdLock(multi).payOutputs.length.should.equal(2)
610
- Ord.parseOrdLock(multi).totalPrice.should.equal(1050)
611
- })
612
- })
613
- })