@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,81 +0,0 @@
1
- 'use strict'
2
-
3
- /* global describe, it, before, after */
4
-
5
- // Ordinal-safe transfer (Phase 1). A 1-sat ordinal held in a covenant must be
6
- // transferred WITHOUT burning the inscribed satoshi. The single-output
7
- // ownershipToken sets output = input - fee, so it destroys a 1-sat UTXO; these
8
- // tests prove (a) the single-output path refuses a 1-sat UTXO, and (b)
9
- // transferOrdinal recreates the token at exactly 1 sat among funding outputs and
10
- // the covenant verifies.
11
-
12
- require('chai').should()
13
- var bsv = require('../..')
14
- var SC = bsv.SmartContract
15
- var PrivateKey = bsv.PrivateKey
16
- var Transaction = bsv.Transaction
17
-
18
- var Token = SC.Token
19
- var help = SC.CovenantHelpers
20
- var fundAndSpend = help.fundAndSpend
21
- var p2pkhOutput = help.p2pkhOutput
22
- var verify = help.verify
23
-
24
- describe('Ordinal-safe transfer (1-sat, SIGHASH_ALL)', function () {
25
- this.timeout(20000)
26
- var I = bsv.Script.Interpreter
27
- var saved
28
- var alice = PrivateKey.fromRandom()
29
- var bob = PrivateKey.fromRandom()
30
-
31
- before(function () {
32
- saved = I.getLimits()
33
- SC.enableGenesis()
34
- })
35
- after(function () {
36
- I.setLimits(saved)
37
- })
38
-
39
- it('transferOrdinal recreates the 1-sat token among funding outputs and verifies', function () {
40
- var lock = Token.ordinalLock(Token.ownerId(alice))
41
- var bobHash = Token.ownerId(bob)
42
- var nextToken = Token.ordinalLock(bobHash)
43
-
44
- // Ordinal output stays exactly 1 sat; a change output carries the funding.
45
- var tokenOutput = new Transaction.Output({ script: nextToken, satoshis: 1 })
46
- var change = p2pkhOutput(PrivateKey.fromRandom(), 5000)
47
-
48
- // token output at index 0 (ordinal FIFO), change after.
49
- var spend = fundAndSpend(lock, 1, { outputs: [tokenOutput, change] }).spend
50
- spend.inputs[0].setScript(Token.transferOrdinal(alice, bobHash, spend, 1, lock, { tokenIndex: 0 }))
51
-
52
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: 1 }).ok.should.equal(true)
53
- // The recreated token output is still exactly 1 sat.
54
- spend.outputs[0].satoshis.should.equal(1)
55
- })
56
-
57
- it('guardrail: single-output unlockTransfer refuses a 1-sat UTXO', function () {
58
- var lock = Token.ordinalLock(Token.ownerId(alice))
59
- var bobHash = Token.ownerId(bob)
60
- var next = Token.ownershipToken(500, bobHash)
61
- var spend = fundAndSpend(lock, 1, {
62
- outputs: [new Transaction.Output({ script: next, satoshis: 1 })]
63
- }).spend
64
- ;(function () {
65
- Token.unlockTransfer(alice, bobHash, spend, 1, lock)
66
- }).should.throw(/burns|ordinal/)
67
- })
68
-
69
- it('anti-burn: transferOrdinal rejects a token output not equal to the ordinal value', function () {
70
- var lock = Token.ordinalLock(Token.ownerId(alice))
71
- var bobHash = Token.ownerId(bob)
72
- var nextToken = Token.ordinalLock(bobHash)
73
- // Wrong: token output is 2 sat, not the ordinal's 1 sat.
74
- var tokenOutput = new Transaction.Output({ script: nextToken, satoshis: 2 })
75
- var change = p2pkhOutput(PrivateKey.fromRandom(), 5000)
76
- var spend = fundAndSpend(lock, 1, { outputs: [tokenOutput, change] }).spend
77
- ;(function () {
78
- Token.transferOrdinal(alice, bobHash, spend, 1, lock, { tokenIndex: 0 })
79
- }).should.throw(/equal the ordinal|burn/)
80
- })
81
- })
@@ -1,98 +0,0 @@
1
- 'use strict'
2
-
3
- /* global describe, it */
4
-
5
- // Coverage for SmartContract.Preimage (previously untested public class).
6
- // Focus: a too-short buffer must fail with an HONEST error (it used to trip
7
- // decodeCompactSize into a misleading "8-byte CompactSize" throw plus console
8
- // spam), while real BIP-143 preimages round-trip their fields.
9
-
10
- require('chai').should()
11
- var bsv = require('../..')
12
- var Preimage = bsv.SmartContract.Preimage
13
-
14
- describe('SmartContract.Preimage', function () {
15
- describe('length guard (honest failure)', function () {
16
- it('exposes the derived BIP-143 structural minimum (157)', function () {
17
- Preimage.LEFT_FIXED_LENGTH.should.equal(104)
18
- Preimage.RIGHT_FIXED_LENGTH.should.equal(52)
19
- Preimage.MIN_PREIMAGE_LENGTH.should.equal(157)
20
- })
21
-
22
- it('throws a clear "too short" error at construction, not a CompactSize error', function () {
23
- ;[0, 50, 103, 156].forEach(function (n) {
24
- (function () { return new Preimage(Buffer.alloc(n)) })
25
- .should.throw(/Preimage too short/)
26
- })
27
- })
28
-
29
- it('does NOT mention CompactSize when the real problem is length', function () {
30
- var threw
31
- try { threw = new Preimage(Buffer.alloc(50)) } catch (e) { threw = e.message }
32
- threw.should.match(/Preimage too short/)
33
- threw.should.not.match(/CompactSize/)
34
- })
35
-
36
- it('validate() reports the length gracefully for a deferred preimage (no throw)', function () {
37
- var p = new Preimage(Buffer.alloc(50), { deferExtraction: true })
38
- var res = p.validate()
39
- res.valid.should.equal(false)
40
- res.errors.join(' ').should.match(/Preimage too short: 50 bytes/)
41
- })
42
-
43
- it('accepts a buffer exactly at the minimum length', function () {
44
- // 157 bytes: 104 left + 1-byte scriptLen varint (0x00 => empty script) + 52 right.
45
- var buf = Buffer.alloc(Preimage.MIN_PREIMAGE_LENGTH)
46
- ;(function () { return new Preimage(buf) }).should.not.throw()
47
- })
48
- })
49
-
50
- describe('decodeCompactSize (honest failure)', function () {
51
- it('throws when reading past the end of the buffer', function () {
52
- (function () { Preimage.decodeCompactSize(Buffer.alloc(4), 4) })
53
- .should.throw(/past end of preimage buffer/)
54
- })
55
-
56
- it('rejects the 8-byte (0xff) length prefix as invalid for a script length', function () {
57
- var buf = Buffer.concat([Buffer.from([0xff]), Buffer.alloc(8)])
58
- ;(function () { Preimage.decodeCompactSize(buf, 0) })
59
- .should.throw(/8-byte length prefix/)
60
- })
61
-
62
- it('rejects a truncated 2-byte CompactSize', function () {
63
- (function () { Preimage.decodeCompactSize(Buffer.from([0xfd, 0x01]), 0) })
64
- .should.throw(/Truncated 2-byte CompactSize/)
65
- })
66
-
67
- it('decodes a plain 1-byte length', function () {
68
- var r = Preimage.decodeCompactSize(Buffer.from([0x19]), 0)
69
- r.value.should.equal(0x19)
70
- r.bytes.should.equal(1)
71
- r.nextOffset.should.equal(1)
72
- })
73
- })
74
-
75
- describe('real preimage round-trip', function () {
76
- it('constructs, extracts fields, and validates a genuine preimage', function () {
77
- var ex = Preimage.createExample(0x41) // SIGHASH_ALL | FORKID
78
- ex.preimage.length.should.be.above(Preimage.MIN_PREIMAGE_LENGTH)
79
-
80
- // Fixed-width fields come back at their BIP-143 sizes.
81
- ex.getField('version').length.should.equal(4)
82
- ex.getField('hashPrevouts').length.should.equal(32)
83
- ex.getField('hashOutputs').length.should.equal(32)
84
- ex.getField('sighash').length.should.equal(4)
85
-
86
- var v = ex.validate()
87
- v.valid.should.equal(true)
88
- v.errors.length.should.equal(0)
89
- })
90
-
91
- it('reports SIGHASH flag info for the extracted sighash', function () {
92
- var ex = Preimage.createExample(0x41)
93
- var info = ex.getSighashInfo()
94
- info.flagName.should.match(/ALL/)
95
- info.flagName.should.match(/FORKID/)
96
- })
97
- })
98
- })
@@ -1,108 +0,0 @@
1
- 'use strict'
2
-
3
- /* global describe, it, before, after */
4
-
5
- // Configurable SIGHASH core (Phase 2). The OP_PUSH_TX core was hard-wired to
6
- // SIGHASH_ALL|FORKID (0x41). It can now be built under any flag; these tests prove
7
- // the marketplace / partially-signed pattern with SIGHASH_SINGLE|ANYONECANPAY|FORKID
8
- // (0xc3): a seller commits only to its own input and its own output, and a buyer may
9
- // add funding inputs and change outputs without invalidating the covenant — while
10
- // the seller's OWN output stays bound.
11
-
12
- require('chai').should()
13
- var bsv = require('../..')
14
- var SC = bsv.SmartContract
15
- var PrivateKey = bsv.PrivateKey
16
- var Script = bsv.Script
17
- var Opcode = bsv.Opcode
18
- var Transaction = bsv.Transaction
19
-
20
- var P = SC.PushTx
21
- var help = SC.CovenantHelpers
22
- var fundAndSpend = help.fundAndSpend
23
- var p2pkhOutput = help.p2pkhOutput
24
- var verify = help.verify
25
-
26
- var SATS = 100000
27
-
28
- describe('Configurable SIGHASH — SINGLE|ANYONECANPAY marketplace covenant', function () {
29
- this.timeout(20000)
30
- var I = bsv.Script.Interpreter
31
- var saved
32
- var flag = P.SIGHASH_SINGLE_ANYONECANPAY_FORKID // 0xc3
33
-
34
- before(function () {
35
- saved = I.getLimits()
36
- SC.enableGenesis()
37
- })
38
- after(function () {
39
- I.setLimits(saved)
40
- })
41
-
42
- // Lock: authenticate the pushed preimage under `flag`, assert the flag, succeed.
43
- function makeLock () {
44
- var lock = new Script()
45
- lock.add(Opcode.OP_DUP)
46
- P.pushTxCore(lock, { sighashType: flag })
47
- lock.add(Opcode.OP_VERIFY)
48
- P.assertSighashType(lock, flag)
49
- lock.add(Opcode.OP_DROP)
50
- lock.add(Opcode.OP_TRUE)
51
- return lock
52
- }
53
-
54
- function grindUnlock (spend, lock) {
55
- var g = P.grind(spend, 0, lock, SATS, { sighashType: flag })
56
- spend.inputs[0].setScript(new Script().add(g.preimage))
57
- }
58
-
59
- it('verifies a SINGLE|ANYONECANPAY covenant spend', function () {
60
- var lock = makeLock()
61
- var sellerOut = p2pkhOutput(PrivateKey.fromRandom(), SATS - 500) // committed output (index 0)
62
- var spend = fundAndSpend(lock, SATS, { outputs: [sellerOut] }).spend
63
- grindUnlock(spend, lock)
64
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
65
- })
66
-
67
- it('stays valid when a buyer adds a funding input and a change output', function () {
68
- var lock = makeLock()
69
- var sellerOut = p2pkhOutput(PrivateKey.fromRandom(), SATS - 500)
70
- var spend = fundAndSpend(lock, SATS, { outputs: [sellerOut] }).spend
71
- grindUnlock(spend, lock) // seller signs input0 + output0 only
72
-
73
- // Buyer adds a funding input and their own change output AFTER the seller signed.
74
- spend.addInput(new Transaction.Input({
75
- prevTxId: '11'.repeat(32), outputIndex: 0, script: Script.empty()
76
- }), p2pkhOutput(PrivateKey.fromRandom(), 50000).script, 50000)
77
- spend.addOutput(p2pkhOutput(PrivateKey.fromRandom(), 49000)) // buyer change at index 1
78
-
79
- // The seller's covenant on input 0 is unaffected (ANYONECANPAY ignores the new
80
- // input; SINGLE ignores the new output).
81
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
82
- })
83
-
84
- it('still binds the seller\'s OWN output (SINGLE commits output at the input index)', function () {
85
- var lock = makeLock()
86
- var sellerOut = p2pkhOutput(PrivateKey.fromRandom(), SATS - 500)
87
- var spend = fundAndSpend(lock, SATS, { outputs: [sellerOut] }).spend
88
- grindUnlock(spend, lock)
89
-
90
- // Tamper the committed output (index 0) after signing → must fail.
91
- spend.outputs[0] = p2pkhOutput(PrivateKey.fromRandom(), 1)
92
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
93
- })
94
-
95
- it('back-compat: default pushTxCore is still SIGHASH_ALL|FORKID', function () {
96
- var lock = new Script()
97
- lock.add(Opcode.OP_DUP)
98
- P.pushTxCore(lock) // no opts → 0x41
99
- lock.add(Opcode.OP_VERIFY)
100
- P.assertSighashAll(lock)
101
- lock.add(Opcode.OP_DROP).add(Opcode.OP_TRUE)
102
- var out = p2pkhOutput(PrivateKey.fromRandom(), SATS - 500)
103
- var spend = fundAndSpend(lock, SATS, { outputs: [out] }).spend
104
- var g = P.grind(spend, 0, lock, SATS) // default sighash
105
- spend.inputs[0].setScript(new Script().add(g.preimage))
106
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
107
- })
108
- })
@@ -1,185 +0,0 @@
1
- 'use strict'
2
-
3
- /* global describe, it, before, after */
4
- require('chai').should()
5
- var bsv = require('../..')
6
- var SC = bsv.SmartContract
7
- var PrivateKey = bsv.PrivateKey
8
- var Script = bsv.Script
9
- var Transaction = bsv.Transaction
10
-
11
- var Token = SC.Token
12
- var Auth = SC.Authorizers
13
- var help = SC.CovenantHelpers
14
- var fundAndSpend = help.fundAndSpend
15
- var p2pkhOutput = help.p2pkhOutput
16
- var verify = help.verify
17
-
18
- var SATS = 100000
19
- var FEE = 500
20
-
21
- describe('SmartContract generalized token (pluggable auth + multi-output)', function () {
22
- this.timeout(20000)
23
- var I = bsv.Script.Interpreter
24
- var saved
25
-
26
- before(function () {
27
- saved = I.getLimits()
28
- SC.enableGenesis()
29
- })
30
- after(function () {
31
- I.setLimits(saved)
32
- })
33
-
34
- function spendOf (lock, sats, outputs) {
35
- return fundAndSpend(lock, sats, { outputs: outputs }).spend
36
- }
37
-
38
- describe('single-key authorizer (default)', function () {
39
- var alice = PrivateKey.fromRandom()
40
- var bob = PrivateKey.fromRandom()
41
-
42
- it('transfers and rejects a non-owner', function () {
43
- var lock = Token.ownershipToken(FEE, Token.ownerId(alice))
44
- var bobHash = Token.ownerId(bob)
45
- var next = Token.ownershipToken(FEE, bobHash)
46
- var spend = spendOf(lock, SATS, [new Transaction.Output({ script: next, satoshis: SATS - FEE })])
47
- spend.inputs[0].setScript(Token.unlockTransfer(alice, bobHash, spend, SATS, lock))
48
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
49
-
50
- var bad = spendOf(lock, SATS, [new Transaction.Output({ script: next, satoshis: SATS - FEE })])
51
- bad.inputs[0].setScript(Token.unlockTransfer(bob, bobHash, bad, SATS, lock)) // bob does not own alice's token
52
- verify(bad.inputs[0].script, lock, { tx: bad, satoshis: SATS }).ok.should.equal(false)
53
- })
54
- })
55
-
56
- describe('multisig authorizer (m-of-n)', function () {
57
- var a = PrivateKey.fromRandom()
58
- var b = PrivateKey.fromRandom()
59
- var c = PrivateKey.fromRandom()
60
- var keys = [a.toPublicKey(), b.toPublicKey(), c.toPublicKey()]
61
- var auth23 = Auth.multisig(2, 3)
62
- var commit = auth23.commit(keys)
63
- var nextOwner = Token.ownerId(PrivateKey.fromRandom())
64
-
65
- function transfer23 (signWith) {
66
- var lock = Token.ownershipToken(FEE, commit, auth23)
67
- var next = Token.ownershipToken(FEE, nextOwner, auth23) // covenant recreates the SAME (multisig) code
68
- var spend = spendOf(lock, SATS, [new Transaction.Output({ script: next, satoshis: SATS - FEE })])
69
- spend.inputs[0].setScript(
70
- Token.unlockTransfer({ keys: keys, signWith: signWith }, nextOwner, spend, SATS, lock, { auth: auth23 }))
71
- return { spend: spend, lock: lock }
72
- }
73
-
74
- it('accepts a valid 2-of-3 (keys a,c)', function () {
75
- var r = transfer23([a, c])
76
- verify(r.spend.inputs[0].script, r.lock, { tx: r.spend, satoshis: SATS }).ok.should.equal(true)
77
- })
78
-
79
- it('accepts a different valid pair (keys b,c)', function () {
80
- var r = transfer23([b, c])
81
- verify(r.spend.inputs[0].script, r.lock, { tx: r.spend, satoshis: SATS }).ok.should.equal(true)
82
- })
83
-
84
- it('rejects a single signature (below threshold)', function () {
85
- var run = function () { transfer23([a]) }
86
- run.should.throw(/at least 2/)
87
- })
88
-
89
- it('rejects a signer outside the committed set', function () {
90
- var outsider = PrivateKey.fromRandom()
91
- var run = function () { transfer23([a, outsider]) }
92
- run.should.throw(/not in the committed key set/)
93
- })
94
-
95
- it('rejects a forged set (right threshold, wrong keys)', function () {
96
- // Commit to {a,b,c} but try to spend revealing a different key set {a,b,x}.
97
- var x = PrivateKey.fromRandom()
98
- var wrongKeys = [a.toPublicKey(), b.toPublicKey(), x.toPublicKey()]
99
- var lock = Token.ownershipToken(FEE, commit, auth23)
100
- var next = Token.ownershipToken(FEE, nextOwner, auth23)
101
- var spend = spendOf(lock, SATS, [new Transaction.Output({ script: next, satoshis: SATS - FEE })])
102
- spend.inputs[0].setScript(
103
- Token.unlockTransfer({ keys: wrongKeys, signWith: [a, b] }, nextOwner, spend, SATS, lock, { auth: auth23 }))
104
- // HASH160(forged redeem) != committed redeem hash => fails.
105
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
106
- })
107
- })
108
-
109
- describe('predicate authorizer (custom escape hatch)', function () {
110
- // Re-implement single-key through the generic predicate interface to prove
111
- // the contract is sufficient for custom schemes.
112
- var Opcode = bsv.Opcode
113
- var Hash = bsv.crypto.Hash
114
- var owner = PrivateKey.fromRandom()
115
- var custom = Auth.predicate({
116
- name: 'custom-single-key',
117
- commit: function (key) { return Hash.sha256ripemd160(key.toPublicKey().toBuffer()) },
118
- emit: function (s) {
119
- return s.add(Opcode.OP_OVER).add(Opcode.OP_HASH160).add(Opcode.OP_EQUALVERIFY).add(Opcode.OP_CHECKSIGVERIFY)
120
- },
121
- unlockArgs: function (spend, inputIndex, lock, sats, key) {
122
- return [help.signInput(spend, key, inputIndex, lock, sats), key.toPublicKey().toBuffer()]
123
- }
124
- })
125
-
126
- it('authorizes a transfer via a custom predicate', function () {
127
- var lock = Token.ownershipToken(FEE, custom.commit(owner), custom)
128
- var nextOwner = Token.ownerId(PrivateKey.fromRandom())
129
- var next = Token.ownershipToken(FEE, nextOwner)
130
- var spend = spendOf(lock, SATS, [new Transaction.Output({ script: next, satoshis: SATS - FEE })])
131
- spend.inputs[0].setScript(Token.unlockTransfer(owner, nextOwner, spend, SATS, lock, { auth: custom }))
132
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
133
- })
134
- })
135
-
136
- describe('multi-output token (N-output)', function () {
137
- var alice = PrivateKey.fromRandom()
138
- var bob = PrivateKey.fromRandom()
139
-
140
- function serialize (output) { return output.toBufferWriter().toBuffer() }
141
- function le8 (v) { return new bsv.crypto.BN(v).toBuffer({ endian: 'little', size: 8 }) }
142
-
143
- it('recreates the token among other outputs and binds them all', function () {
144
- var lock = Token.ownershipTokenMulti(Token.ownerId(alice))
145
- var bobHash = Token.ownerId(bob)
146
- var nextToken = Token.ownershipTokenMulti(bobHash)
147
-
148
- var payment = p2pkhOutput(PrivateKey.fromRandom(), 4000)
149
- var tokenValue = 1000
150
- var tokenOutput = new Transaction.Output({ script: nextToken, satoshis: tokenValue })
151
- var dataOutput = new Transaction.Output({
152
- script: new Script().add(bsv.Opcode.OP_FALSE).add(bsv.Opcode.OP_RETURN).add(Buffer.from('hello')),
153
- satoshis: 0
154
- })
155
-
156
- // tx outputs in order: [payment, token, data]
157
- var spend = spendOf(lock, SATS, [payment, tokenOutput, dataOutput])
158
- spend.inputs[0].setScript(Token.unlockTransferMulti(alice, bobHash, spend, SATS, lock, {
159
- before: serialize(payment),
160
- after: serialize(dataOutput),
161
- tokenValue: le8(tokenValue)
162
- }))
163
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
164
- })
165
-
166
- it('rejects a tampered surrounding output', function () {
167
- var lock = Token.ownershipTokenMulti(Token.ownerId(alice))
168
- var bobHash = Token.ownerId(bob)
169
- var nextToken = Token.ownershipTokenMulti(bobHash)
170
- var payment = p2pkhOutput(PrivateKey.fromRandom(), 4000)
171
- var tokenValue = 1000
172
- var tokenOutput = new Transaction.Output({ script: nextToken, satoshis: tokenValue })
173
-
174
- var spend = spendOf(lock, SATS, [payment, tokenOutput])
175
- // Claim a DIFFERENT `before` than the real first output => HASH160 mismatch.
176
- var lyingPayment = p2pkhOutput(PrivateKey.fromRandom(), 4000)
177
- spend.inputs[0].setScript(Token.unlockTransferMulti(alice, bobHash, spend, SATS, lock, {
178
- before: serialize(lyingPayment),
179
- after: Buffer.alloc(0),
180
- tokenValue: le8(tokenValue)
181
- }))
182
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
183
- })
184
- })
185
- })
@@ -1,86 +0,0 @@
1
- 'use strict'
2
-
3
- /* global describe, it */
4
-
5
- // SPV header-chain verification: linkage + per-header proof-of-work + optional
6
- // trusted-hash anchoring. Uses the regtest max target (0x207fffff) so a header's
7
- // PoW passes after trivial nonce grinding (no real mining needed).
8
-
9
- require('chai').should()
10
- var bsv = require('../..')
11
- var SPV = bsv.SPV
12
-
13
- function rand32 () { return bsv.crypto.Random.getRandomBuffer(32) }
14
-
15
- // "Mine" a header at regtest difficulty: grind nonce until PoW passes (~1-2 tries).
16
- function minedHeader (prevHashInternal, merkleInternal) {
17
- for (var nonce = 0; nonce < 100000; nonce++) {
18
- var h = new bsv.BlockHeader({
19
- version: 1,
20
- prevHash: prevHashInternal,
21
- merkleRoot: merkleInternal,
22
- time: 1231006505 + nonce,
23
- bits: 0x207fffff,
24
- nonce: nonce
25
- })
26
- if (h.validProofOfWork()) return h
27
- }
28
- throw new Error('could not mine a regtest header')
29
- }
30
-
31
- function chain (n) {
32
- var hs = []
33
- var prev = Buffer.alloc(32)
34
- for (var i = 0; i < n; i++) {
35
- var h = minedHeader(prev, rand32())
36
- hs.push(h)
37
- prev = h._getHash()
38
- }
39
- return hs
40
- }
41
-
42
- describe('SPV.verifyHeaderChain', function () {
43
- this.timeout(10000)
44
-
45
- it('accepts a linked, PoW-valid chain', function () {
46
- var hs = chain(4)
47
- var res = SPV.verifyHeaderChain(hs)
48
- res.valid.should.equal(true)
49
- res.count.should.equal(4)
50
- res.anchorHash.should.equal(hs[0].id)
51
- res.tipHash.should.equal(hs[3].id)
52
- })
53
-
54
- it('rejects a broken link', function () {
55
- var hs = chain(3)
56
- var res = SPV.verifyHeaderChain([hs[0], hs[2]]) // hs[2] links to hs[1], not hs[0]
57
- res.valid.should.equal(false)
58
- res.reason.should.match(/broken link/)
59
- })
60
-
61
- it('rejects a header that fails proof-of-work', function () {
62
- // Mainnet-difficulty bits with nonce 0 — the hash will not meet the tiny target.
63
- var bad = new bsv.BlockHeader({
64
- version: 1, prevHash: Buffer.alloc(32), merkleRoot: rand32(),
65
- time: 1231006505, bits: 0x1d00ffff, nonce: 0
66
- })
67
- SPV.verifyHeaderChain([bad]).valid.should.equal(false)
68
- // ...but skipping PoW, a single header is trivially "valid".
69
- SPV.verifyHeaderChain([bad], { requirePow: false }).valid.should.equal(true)
70
- })
71
-
72
- it('honours a trusted-hash anchor (tip or anchor)', function () {
73
- var hs = chain(3)
74
- SPV.verifyHeaderChain(hs, { trustedHash: hs[2].id }).valid.should.equal(true)
75
- SPV.verifyHeaderChain(hs, { trustedHash: hs[0].id }).valid.should.equal(true)
76
- var res = SPV.verifyHeaderChain(hs, { trustedHash: 'ff'.repeat(32) })
77
- res.valid.should.equal(false)
78
- res.reason.should.match(/trusted hash/)
79
- })
80
-
81
- it('accepts hex / buffer headers', function () {
82
- var hs = chain(2)
83
- var asHex = hs.map(function (h) { return h.toBuffer().toString('hex') })
84
- SPV.verifyHeaderChain(asHex).valid.should.equal(true)
85
- })
86
- })
@@ -1,133 +0,0 @@
1
- 'use strict'
2
-
3
- /* global describe, it */
4
-
5
- // SPV Merkle inclusion proofs, cross-checked against the codebase's own Merkle
6
- // tree algorithm (lib/block/block.js getMerkleTree). For random trees of many
7
- // shapes (even, odd, duplicate-last), extract the branch for each leaf and assert
8
- // the SPV verifier recomputes the exact same root — then assert tampering fails.
9
-
10
- require('chai').should()
11
- var bsv = require('../..')
12
- var SPV = bsv.SPV
13
- var Hash = bsv.crypto.Hash
14
-
15
- function rev (b) { return Buffer.from(b).reverse() }
16
-
17
- // Build the flat merkle tree exactly like Block.getMerkleTree (leaves = internal-LE).
18
- function buildTree (leaves) {
19
- var tree = leaves.slice()
20
- var j = 0
21
- for (var size = leaves.length; size > 1; size = Math.floor((size + 1) / 2)) {
22
- for (var i = 0; i < size; i += 2) {
23
- var i2 = Math.min(i + 1, size - 1)
24
- tree.push(Hash.sha256sha256(Buffer.concat([tree[j + i], tree[j + i2]])))
25
- }
26
- j += size
27
- }
28
- return tree
29
- }
30
- function rootOf (leaves) { var t = buildTree(leaves); return t[t.length - 1] }
31
-
32
- // Extract the sibling branch (display-order hex) for a leaf, mirroring the tree layout.
33
- function branchFor (leaves, index) {
34
- var tree = buildTree(leaves)
35
- var branch = []
36
- var j = 0
37
- var idx = index
38
- for (var size = leaves.length; size > 1; size = Math.floor((size + 1) / 2)) {
39
- var sib = idx ^ 1
40
- if (sib >= size) sib = idx // odd end -> duplicate self
41
- branch.push(rev(tree[j + sib]).toString('hex'))
42
- j += size
43
- idx = Math.floor(idx / 2)
44
- }
45
- return branch
46
- }
47
-
48
- function randLeaves (n) {
49
- var a = []
50
- for (var i = 0; i < n; i++) a.push(bsv.crypto.Random.getRandomBuffer(32))
51
- return a
52
- }
53
-
54
- describe('SPV.verifyMerkleProof (cross-checked vs Block.getMerkleTree)', function () {
55
- var sizes = [1, 2, 3, 4, 5, 7, 8, 9, 16, 21]
56
-
57
- it('recomputes the root for every leaf across many tree shapes', function () {
58
- sizes.forEach(function (n) {
59
- var leaves = randLeaves(n)
60
- var root = rev(rootOf(leaves)).toString('hex') // display order
61
- for (var idx = 0; idx < n; idx++) {
62
- var txid = rev(leaves[idx]).toString('hex')
63
- var nodes = branchFor(leaves, idx)
64
- SPV.merkleRootFromBranch(txid, idx, nodes).should.equal(root,
65
- 'size ' + n + ' index ' + idx)
66
- SPV.verifyMerkleProof({ txid: txid, index: idx, nodes: nodes, merkleRoot: root })
67
- .should.equal(true)
68
- }
69
- })
70
- })
71
-
72
- it('rejects a tampered branch node', function () {
73
- var leaves = randLeaves(6)
74
- var root = rev(rootOf(leaves)).toString('hex')
75
- var nodes = branchFor(leaves, 2)
76
- nodes[0] = rev(bsv.crypto.Random.getRandomBuffer(32)).toString('hex') // wrong sibling
77
- SPV.verifyMerkleProof({ txid: rev(leaves[2]).toString('hex'), index: 2, nodes: nodes, merkleRoot: root })
78
- .should.equal(false)
79
- })
80
-
81
- it('rejects the right proof against the wrong txid', function () {
82
- var leaves = randLeaves(8)
83
- var root = rev(rootOf(leaves)).toString('hex')
84
- var nodes = branchFor(leaves, 3)
85
- var wrongTxid = rev(bsv.crypto.Random.getRandomBuffer(32)).toString('hex')
86
- SPV.verifyMerkleProof({ txid: wrongTxid, index: 3, nodes: nodes, merkleRoot: root })
87
- .should.equal(false)
88
- })
89
-
90
- it("accepts '*' as the odd-node duplicate marker", function () {
91
- // 3 leaves: index 2 is the odd one; its level-0 sibling is itself.
92
- var leaves = randLeaves(3)
93
- var root = rev(rootOf(leaves)).toString('hex')
94
- var nodes = branchFor(leaves, 2)
95
- nodes[0] = '*' // replace the explicit self-hash with the duplicate marker
96
- SPV.verifyMerkleProof({ txid: rev(leaves[2]).toString('hex'), index: 2, nodes: nodes, merkleRoot: root })
97
- .should.equal(true)
98
- })
99
-
100
- it('single-tx block: empty branch, root == txid', function () {
101
- var leaves = randLeaves(1)
102
- var txid = rev(leaves[0]).toString('hex')
103
- SPV.merkleRootFromBranch(txid, 0, []).should.equal(txid)
104
- })
105
-
106
- it('verifyTxInclusion binds the branch to a header merkle root', function () {
107
- var leaves = randLeaves(5)
108
- var rootInternal = rootOf(leaves)
109
- var header = new bsv.BlockHeader({
110
- version: 1,
111
- prevHash: Buffer.alloc(32),
112
- merkleRoot: rootInternal, // internal-LE buffer
113
- time: 1231006505,
114
- bits: 0x1d00ffff,
115
- nonce: 0
116
- })
117
- var res = SPV.verifyTxInclusion({
118
- txid: rev(leaves[3]).toString('hex'),
119
- index: 3,
120
- nodes: branchFor(leaves, 3),
121
- header: header,
122
- requirePow: false // synthetic header has no real PoW
123
- })
124
- res.rootMatches.should.equal(true)
125
- res.valid.should.equal(true)
126
-
127
- // Wrong index -> root mismatch.
128
- SPV.verifyTxInclusion({
129
- txid: rev(leaves[3]).toString('hex'), index: 1,
130
- nodes: branchFor(leaves, 3), header: header, requirePow: false
131
- }).valid.should.equal(false)
132
- })
133
- })