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