@smartledger/bsv 9.0.0 → 9.1.1

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 +164 -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 +53 -9
  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,71 +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 alice = PrivateKey.fromRandom()
27
- var bob = PrivateKey.fromRandom()
28
-
29
- it('transferOrdinal recreates the 1-sat token among funding outputs and verifies', function () {
30
- var lock = Token.ordinalLock(Token.ownerId(alice))
31
- var bobHash = Token.ownerId(bob)
32
- var nextToken = Token.ordinalLock(bobHash)
33
-
34
- // Ordinal output stays exactly 1 sat; a change output carries the funding.
35
- var tokenOutput = new Transaction.Output({ script: nextToken, satoshis: 1 })
36
- var change = p2pkhOutput(PrivateKey.fromRandom(), 5000)
37
-
38
- // token output at index 0 (ordinal FIFO), change after.
39
- var spend = fundAndSpend(lock, 1, { outputs: [tokenOutput, change] }).spend
40
- spend.inputs[0].setScript(Token.transferOrdinal(alice, bobHash, spend, 1, lock, { tokenIndex: 0 }))
41
-
42
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: 1 }).ok.should.equal(true)
43
- // The recreated token output is still exactly 1 sat.
44
- spend.outputs[0].satoshis.should.equal(1)
45
- })
46
-
47
- it('guardrail: single-output unlockTransfer refuses a 1-sat UTXO', function () {
48
- var lock = Token.ordinalLock(Token.ownerId(alice))
49
- var bobHash = Token.ownerId(bob)
50
- var next = Token.ownershipToken(500, bobHash)
51
- var spend = fundAndSpend(lock, 1, {
52
- outputs: [new Transaction.Output({ script: next, satoshis: 1 })]
53
- }).spend
54
- ;(function () {
55
- Token.unlockTransfer(alice, bobHash, spend, 1, lock)
56
- }).should.throw(/burns|ordinal/)
57
- })
58
-
59
- it('anti-burn: transferOrdinal rejects a token output not equal to the ordinal value', function () {
60
- var lock = Token.ordinalLock(Token.ownerId(alice))
61
- var bobHash = Token.ownerId(bob)
62
- var nextToken = Token.ordinalLock(bobHash)
63
- // Wrong: token output is 2 sat, not the ordinal's 1 sat.
64
- var tokenOutput = new Transaction.Output({ script: nextToken, satoshis: 2 })
65
- var change = p2pkhOutput(PrivateKey.fromRandom(), 5000)
66
- var spend = fundAndSpend(lock, 1, { outputs: [tokenOutput, change] }).spend
67
- ;(function () {
68
- Token.transferOrdinal(alice, bobHash, spend, 1, lock, { tokenIndex: 0 })
69
- }).should.throw(/equal the ordinal|burn/)
70
- })
71
- })
@@ -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,98 +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 flag = P.SIGHASH_SINGLE_ANYONECANPAY_FORKID // 0xc3
31
-
32
- // Lock: authenticate the pushed preimage under `flag`, assert the flag, succeed.
33
- function makeLock () {
34
- var lock = new Script()
35
- lock.add(Opcode.OP_DUP)
36
- P.pushTxCore(lock, { sighashType: flag })
37
- lock.add(Opcode.OP_VERIFY)
38
- P.assertSighashType(lock, flag)
39
- lock.add(Opcode.OP_DROP)
40
- lock.add(Opcode.OP_TRUE)
41
- return lock
42
- }
43
-
44
- function grindUnlock (spend, lock) {
45
- var g = P.grind(spend, 0, lock, SATS, { sighashType: flag })
46
- spend.inputs[0].setScript(new Script().add(g.preimage))
47
- }
48
-
49
- it('verifies a SINGLE|ANYONECANPAY covenant spend', function () {
50
- var lock = makeLock()
51
- var sellerOut = p2pkhOutput(PrivateKey.fromRandom(), SATS - 500) // committed output (index 0)
52
- var spend = fundAndSpend(lock, SATS, { outputs: [sellerOut] }).spend
53
- grindUnlock(spend, lock)
54
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
55
- })
56
-
57
- it('stays valid when a buyer adds a funding input and a change output', function () {
58
- var lock = makeLock()
59
- var sellerOut = p2pkhOutput(PrivateKey.fromRandom(), SATS - 500)
60
- var spend = fundAndSpend(lock, SATS, { outputs: [sellerOut] }).spend
61
- grindUnlock(spend, lock) // seller signs input0 + output0 only
62
-
63
- // Buyer adds a funding input and their own change output AFTER the seller signed.
64
- spend.addInput(new Transaction.Input({
65
- prevTxId: '11'.repeat(32), outputIndex: 0, script: Script.empty()
66
- }), p2pkhOutput(PrivateKey.fromRandom(), 50000).script, 50000)
67
- spend.addOutput(p2pkhOutput(PrivateKey.fromRandom(), 49000)) // buyer change at index 1
68
-
69
- // The seller's covenant on input 0 is unaffected (ANYONECANPAY ignores the new
70
- // input; SINGLE ignores the new output).
71
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
72
- })
73
-
74
- it('still binds the seller\'s OWN output (SINGLE commits output at the input index)', function () {
75
- var lock = makeLock()
76
- var sellerOut = p2pkhOutput(PrivateKey.fromRandom(), SATS - 500)
77
- var spend = fundAndSpend(lock, SATS, { outputs: [sellerOut] }).spend
78
- grindUnlock(spend, lock)
79
-
80
- // Tamper the committed output (index 0) after signing → must fail.
81
- spend.outputs[0] = p2pkhOutput(PrivateKey.fromRandom(), 1)
82
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
83
- })
84
-
85
- it('back-compat: default pushTxCore is still SIGHASH_ALL|FORKID', function () {
86
- var lock = new Script()
87
- lock.add(Opcode.OP_DUP)
88
- P.pushTxCore(lock) // no opts → 0x41
89
- lock.add(Opcode.OP_VERIFY)
90
- P.assertSighashAll(lock)
91
- lock.add(Opcode.OP_DROP).add(Opcode.OP_TRUE)
92
- var out = p2pkhOutput(PrivateKey.fromRandom(), SATS - 500)
93
- var spend = fundAndSpend(lock, SATS, { outputs: [out] }).spend
94
- var g = P.grind(spend, 0, lock, SATS) // default sighash
95
- spend.inputs[0].setScript(new Script().add(g.preimage))
96
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
97
- })
98
- })
@@ -1,175 +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
-
24
- function spendOf (lock, sats, outputs) {
25
- return fundAndSpend(lock, sats, { outputs: outputs }).spend
26
- }
27
-
28
- describe('single-key authorizer (default)', function () {
29
- var alice = PrivateKey.fromRandom()
30
- var bob = PrivateKey.fromRandom()
31
-
32
- it('transfers and rejects a non-owner', function () {
33
- var lock = Token.ownershipToken(FEE, Token.ownerId(alice))
34
- var bobHash = Token.ownerId(bob)
35
- var next = Token.ownershipToken(FEE, bobHash)
36
- var spend = spendOf(lock, SATS, [new Transaction.Output({ script: next, satoshis: SATS - FEE })])
37
- spend.inputs[0].setScript(Token.unlockTransfer(alice, bobHash, spend, SATS, lock))
38
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
39
-
40
- var bad = spendOf(lock, SATS, [new Transaction.Output({ script: next, satoshis: SATS - FEE })])
41
- bad.inputs[0].setScript(Token.unlockTransfer(bob, bobHash, bad, SATS, lock)) // bob does not own alice's token
42
- verify(bad.inputs[0].script, lock, { tx: bad, satoshis: SATS }).ok.should.equal(false)
43
- })
44
- })
45
-
46
- describe('multisig authorizer (m-of-n)', function () {
47
- var a = PrivateKey.fromRandom()
48
- var b = PrivateKey.fromRandom()
49
- var c = PrivateKey.fromRandom()
50
- var keys = [a.toPublicKey(), b.toPublicKey(), c.toPublicKey()]
51
- var auth23 = Auth.multisig(2, 3)
52
- var commit = auth23.commit(keys)
53
- var nextOwner = Token.ownerId(PrivateKey.fromRandom())
54
-
55
- function transfer23 (signWith) {
56
- var lock = Token.ownershipToken(FEE, commit, auth23)
57
- var next = Token.ownershipToken(FEE, nextOwner, auth23) // covenant recreates the SAME (multisig) code
58
- var spend = spendOf(lock, SATS, [new Transaction.Output({ script: next, satoshis: SATS - FEE })])
59
- spend.inputs[0].setScript(
60
- Token.unlockTransfer({ keys: keys, signWith: signWith }, nextOwner, spend, SATS, lock, { auth: auth23 }))
61
- return { spend: spend, lock: lock }
62
- }
63
-
64
- it('accepts a valid 2-of-3 (keys a,c)', function () {
65
- var r = transfer23([a, c])
66
- verify(r.spend.inputs[0].script, r.lock, { tx: r.spend, satoshis: SATS }).ok.should.equal(true)
67
- })
68
-
69
- it('accepts a different valid pair (keys b,c)', function () {
70
- var r = transfer23([b, c])
71
- verify(r.spend.inputs[0].script, r.lock, { tx: r.spend, satoshis: SATS }).ok.should.equal(true)
72
- })
73
-
74
- it('rejects a single signature (below threshold)', function () {
75
- var run = function () { transfer23([a]) }
76
- run.should.throw(/at least 2/)
77
- })
78
-
79
- it('rejects a signer outside the committed set', function () {
80
- var outsider = PrivateKey.fromRandom()
81
- var run = function () { transfer23([a, outsider]) }
82
- run.should.throw(/not in the committed key set/)
83
- })
84
-
85
- it('rejects a forged set (right threshold, wrong keys)', function () {
86
- // Commit to {a,b,c} but try to spend revealing a different key set {a,b,x}.
87
- var x = PrivateKey.fromRandom()
88
- var wrongKeys = [a.toPublicKey(), b.toPublicKey(), x.toPublicKey()]
89
- var lock = Token.ownershipToken(FEE, commit, auth23)
90
- var next = Token.ownershipToken(FEE, nextOwner, auth23)
91
- var spend = spendOf(lock, SATS, [new Transaction.Output({ script: next, satoshis: SATS - FEE })])
92
- spend.inputs[0].setScript(
93
- Token.unlockTransfer({ keys: wrongKeys, signWith: [a, b] }, nextOwner, spend, SATS, lock, { auth: auth23 }))
94
- // HASH160(forged redeem) != committed redeem hash => fails.
95
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
96
- })
97
- })
98
-
99
- describe('predicate authorizer (custom escape hatch)', function () {
100
- // Re-implement single-key through the generic predicate interface to prove
101
- // the contract is sufficient for custom schemes.
102
- var Opcode = bsv.Opcode
103
- var Hash = bsv.crypto.Hash
104
- var owner = PrivateKey.fromRandom()
105
- var custom = Auth.predicate({
106
- name: 'custom-single-key',
107
- commit: function (key) { return Hash.sha256ripemd160(key.toPublicKey().toBuffer()) },
108
- emit: function (s) {
109
- return s.add(Opcode.OP_OVER).add(Opcode.OP_HASH160).add(Opcode.OP_EQUALVERIFY).add(Opcode.OP_CHECKSIGVERIFY)
110
- },
111
- unlockArgs: function (spend, inputIndex, lock, sats, key) {
112
- return [help.signInput(spend, key, inputIndex, lock, sats), key.toPublicKey().toBuffer()]
113
- }
114
- })
115
-
116
- it('authorizes a transfer via a custom predicate', function () {
117
- var lock = Token.ownershipToken(FEE, custom.commit(owner), custom)
118
- var nextOwner = Token.ownerId(PrivateKey.fromRandom())
119
- var next = Token.ownershipToken(FEE, nextOwner)
120
- var spend = spendOf(lock, SATS, [new Transaction.Output({ script: next, satoshis: SATS - FEE })])
121
- spend.inputs[0].setScript(Token.unlockTransfer(owner, nextOwner, spend, SATS, lock, { auth: custom }))
122
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
123
- })
124
- })
125
-
126
- describe('multi-output token (N-output)', function () {
127
- var alice = PrivateKey.fromRandom()
128
- var bob = PrivateKey.fromRandom()
129
-
130
- function serialize (output) { return output.toBufferWriter().toBuffer() }
131
- function le8 (v) { return new bsv.crypto.BN(v).toBuffer({ endian: 'little', size: 8 }) }
132
-
133
- it('recreates the token among other outputs and binds them all', function () {
134
- var lock = Token.ownershipTokenMulti(Token.ownerId(alice))
135
- var bobHash = Token.ownerId(bob)
136
- var nextToken = Token.ownershipTokenMulti(bobHash)
137
-
138
- var payment = p2pkhOutput(PrivateKey.fromRandom(), 4000)
139
- var tokenValue = 1000
140
- var tokenOutput = new Transaction.Output({ script: nextToken, satoshis: tokenValue })
141
- var dataOutput = new Transaction.Output({
142
- script: new Script().add(bsv.Opcode.OP_FALSE).add(bsv.Opcode.OP_RETURN).add(Buffer.from('hello')),
143
- satoshis: 0
144
- })
145
-
146
- // tx outputs in order: [payment, token, data]
147
- var spend = spendOf(lock, SATS, [payment, tokenOutput, dataOutput])
148
- spend.inputs[0].setScript(Token.unlockTransferMulti(alice, bobHash, spend, SATS, lock, {
149
- before: serialize(payment),
150
- after: serialize(dataOutput),
151
- tokenValue: le8(tokenValue)
152
- }))
153
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
154
- })
155
-
156
- it('rejects a tampered surrounding output', function () {
157
- var lock = Token.ownershipTokenMulti(Token.ownerId(alice))
158
- var bobHash = Token.ownerId(bob)
159
- var nextToken = Token.ownershipTokenMulti(bobHash)
160
- var payment = p2pkhOutput(PrivateKey.fromRandom(), 4000)
161
- var tokenValue = 1000
162
- var tokenOutput = new Transaction.Output({ script: nextToken, satoshis: tokenValue })
163
-
164
- var spend = spendOf(lock, SATS, [payment, tokenOutput])
165
- // Claim a DIFFERENT `before` than the real first output => HASH160 mismatch.
166
- var lyingPayment = p2pkhOutput(PrivateKey.fromRandom(), 4000)
167
- spend.inputs[0].setScript(Token.unlockTransferMulti(alice, bobHash, spend, SATS, lock, {
168
- before: serialize(lyingPayment),
169
- after: Buffer.alloc(0),
170
- tokenValue: le8(tokenValue)
171
- }))
172
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
173
- })
174
- })
175
- })
@@ -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
- })