@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,88 +0,0 @@
1
- 'use strict'
2
-
3
- /* global describe, it */
4
- require('chai').should()
5
- var bsv = require('../..')
6
- var Interpreter = bsv.Script.Interpreter
7
- var Script = bsv.Script
8
- var Opcode = bsv.Opcode
9
- var Transaction = bsv.Transaction
10
- var CHRONICLE = Interpreter.SCRIPT_ENABLE_CHRONICLE
11
-
12
- // OP_SUBSTR / OP_LEFT / OP_RIGHT — Chronicle string opcodes at 0xb3/0xb4/0xb5.
13
- //
14
- // Every case here passes SCRIPT_ENABLE_CHRONICLE, because without it these
15
- // bytes are UPGRADABLE NOPS, exactly as they are on a pre-Chronicle node. An
16
- // earlier version of these tests ran them with flags=0 and passed, which is
17
- // what hid the fact that the interpreter was executing them unconditionally —
18
- // more permissively than the network.
19
- describe('String opcodes OP_SUBSTR / OP_LEFT / OP_RIGHT', function () {
20
- var DATA = Buffer.from('aabbccdd', 'hex')
21
-
22
- // Build `<DATA> <ops...>` and assert the top of stack equals `expected`.
23
- function yields (buildOps, expectedHex) {
24
- var lock = new Script().add(DATA)
25
- buildOps(lock)
26
- lock.add(Buffer.from(expectedHex, 'hex')).add(Opcode.OP_EQUAL)
27
- var interp = new Interpreter()
28
- return interp.verify(new Script(), lock, new Transaction(), 0, CHRONICLE)
29
- }
30
- function errors (buildOps) {
31
- var lock = new Script().add(DATA)
32
- buildOps(lock)
33
- var interp = new Interpreter()
34
- var ok
35
- try { ok = interp.verify(new Script(), lock, new Transaction(), 0, CHRONICLE) } catch (e) { ok = false }
36
- return ok === false
37
- }
38
-
39
- it('OP_LEFT keeps the first n bytes', function () {
40
- yields(function (s) { s.add(Opcode.OP_2).add(Opcode.OP_LEFT) }, 'aabb').should.equal(true)
41
- })
42
-
43
- it('OP_RIGHT keeps the last n bytes', function () {
44
- yields(function (s) { s.add(Opcode.OP_2).add(Opcode.OP_RIGHT) }, 'ccdd').should.equal(true)
45
- })
46
-
47
- it('OP_RIGHT 0 yields the empty string (not the whole string)', function () {
48
- yields(function (s) { s.add(Opcode.OP_0).add(Opcode.OP_RIGHT) }, '').should.equal(true)
49
- })
50
-
51
- it('OP_LEFT 0 yields the empty string', function () {
52
- yields(function (s) { s.add(Opcode.OP_0).add(Opcode.OP_LEFT) }, '').should.equal(true)
53
- })
54
-
55
- it('OP_SUBSTR extracts in[begin : begin+size]', function () {
56
- // begin=1 size=2 -> bbcc. Stack order is (in begin size -- out).
57
- yields(function (s) { s.add(Opcode.OP_1).add(Opcode.OP_2).add(Opcode.OP_SUBSTR) }, 'bbcc').should.equal(true)
58
- })
59
-
60
- // The node ERRORS rather than clamping:
61
- // if(len < 0 || len > size) return SCRIPT_ERR_INVALID_NUMBER_RANGE;
62
- // Clamping made scripts the node rejects succeed here.
63
- it('OP_LEFT rejects n greater than the length', function () {
64
- errors(function (s) { s.add(Opcode.OP_10).add(Opcode.OP_LEFT) }).should.equal(true)
65
- })
66
-
67
- it('OP_RIGHT rejects n greater than the length', function () {
68
- errors(function (s) { s.add(Opcode.OP_10).add(Opcode.OP_RIGHT) }).should.equal(true)
69
- })
70
-
71
- it('is a no-op without SCRIPT_ENABLE_CHRONICLE, as a pre-Chronicle node is', function () {
72
- const interp = new Interpreter()
73
- const lock = new Script().add(DATA).add(Opcode.OP_2).add(Opcode.OP_LEFT)
74
- interp.verify(new Script(), lock, new Transaction(), 0, 0).should.equal(true)
75
- // Untouched: the opcode did nothing, so both operands survive.
76
- interp.stack.length.should.equal(2)
77
- })
78
-
79
- it('rejects a negative size', function () {
80
- errors(function (s) { s.add(Opcode.OP_1NEGATE).add(Opcode.OP_LEFT) }).should.equal(true)
81
- })
82
-
83
- it('rejects OP_LEFT with too few stack items', function () {
84
- var interp = new Interpreter()
85
- var lock = new Script().add(Opcode.OP_LEFT) // nothing to operate on
86
- interp.verify(new Script(), lock, new Transaction(), 0, CHRONICLE).should.equal(false)
87
- })
88
- })
@@ -1,104 +0,0 @@
1
- 'use strict'
2
-
3
- /* global describe, it */
4
-
5
- // FAIL-CLOSED CONTRACTS — a mechanical guard for the operating principle that every
6
- // security-critical verification "returns a bool or throws, never a chainable object"
7
- // and that a forged/invalid input is REJECTED (never accepted).
8
- //
9
- // This exists because the v6.0.0 security-hardening pass fixed four CRITICAL fail-open
10
- // forgery bugs, all of the same shape: code read a truthy return value (an ECDSA
11
- // *instance*, a stub `{verified:true}`) as if it meant "valid". These tests assert the
12
- // negative — a bad input must not verify — and pin the return-type contract so a
13
- // regression to fail-open trips a red test instead of shipping.
14
-
15
- require('chai').should()
16
- var assert = require('assert')
17
- var bsv = require('../..')
18
-
19
- var Hash = bsv.crypto.Hash
20
- var ECDSA = bsv.crypto.ECDSA
21
-
22
- function isStrictBool (v) { return v === true || v === false }
23
-
24
- describe('security: fail-closed verification contracts', function () {
25
- // A signature over `hash` by `signer`, and a different key it was NOT signed by.
26
- var signer = bsv.PrivateKey.fromRandom()
27
- var hash = Hash.sha256(Buffer.from('the message'))
28
- var goodSig = ECDSA.sign(hash, signer)
29
- var rightPub = signer.toPublicKey()
30
- var wrongPub = bsv.PrivateKey.fromRandom().toPublicKey()
31
-
32
- describe('ECDSA signature verification', function () {
33
- it('static ECDSA.verify returns a STRICT boolean (never a truthy object)', function () {
34
- isStrictBool(ECDSA.verify(hash, goodSig, rightPub)).should.equal(true)
35
- isStrictBool(ECDSA.verify(hash, goodSig, wrongPub)).should.equal(true)
36
- })
37
-
38
- it('static ECDSA.verify accepts a valid signature and REJECTS a forged one', function () {
39
- ECDSA.verify(hash, goodSig, rightPub).should.equal(true)
40
- ECDSA.verify(hash, goodSig, wrongPub).should.equal(false) // forged: wrong key
41
- })
42
-
43
- it('instance verifyBool() is the safe boolean path and fails closed on a forgery', function () {
44
- function inst (pub) {
45
- var e = new ECDSA(); e.hashbuf = hash; e.sig = goodSig; e.pubkey = pub; return e
46
- }
47
- isStrictBool(inst(rightPub).verifyBool()).should.equal(true)
48
- inst(rightPub).verifyBool().should.equal(true)
49
- inst(wrongPub).verifyBool().should.equal(false)
50
- })
51
-
52
- // 7.0: the trap is CLOSED. Instance .verify() now returns a STRICT boolean, so
53
- // `if (ecdsa.verify())` correctly rejects a forgery. (Pre-7.0 it returned the
54
- // truthy instance with the real result on .verified; that idiom is gone.)
55
- it('instance .verify() returns a strict boolean and fails closed on a forgery', function () {
56
- var good = new ECDSA(); good.hashbuf = hash; good.sig = goodSig; good.pubkey = rightPub
57
- isStrictBool(good.verify()).should.equal(true)
58
- good.verify().should.equal(true)
59
-
60
- var forged = new ECDSA(); forged.hashbuf = hash; forged.sig = goodSig; forged.pubkey = wrongPub
61
- isStrictBool(forged.verify()).should.equal(true)
62
- forged.verify().should.equal(false) // forged → false, and `if (forged.verify())` does NOT enter
63
- // The result is still mirrored on .verified as a side effect.
64
- forged.verified.should.equal(false)
65
- })
66
- })
67
-
68
- describe('SmartVerify (hardened secp256k1 verify)', function () {
69
- it('returns a STRICT boolean and rejects a forged signature', function () {
70
- var ok = bsv.SmartVerify.smartVerify(hash, goodSig.toBuffer(), rightPub)
71
- var bad = bsv.SmartVerify.smartVerify(hash, goodSig.toBuffer(), wrongPub)
72
- isStrictBool(ok).should.equal(true)
73
- isStrictBool(bad).should.equal(true)
74
- ok.should.equal(true)
75
- bad.should.equal(false)
76
- })
77
- })
78
-
79
- describe('SPV inclusion / header-chain verification', function () {
80
- it('verifyHeaderChain fails closed on empty / malformed input (throws, not truthy)', function () {
81
- assert.throws(function () { bsv.SPV.verifyHeaderChain([], {}) })
82
- })
83
-
84
- it('verifyMerkleProof rejects a bad proof without ever returning a truthy non-boolean', function () {
85
- var fakeRoot = Buffer.alloc(32, 1)
86
- var fakeTxid = Buffer.alloc(32, 2)
87
- var res
88
- try {
89
- res = bsv.SPV.verifyMerkleProof(fakeTxid, { index: 0, nodes: ['00'.repeat(32)] }, fakeRoot)
90
- } catch (e) { res = false } // throwing is an acceptable fail-closed outcome
91
- // Whatever the shape, it must not be a truthy value that reads as "included".
92
- var included = (res === true) || (res && res.valid === true) || (res && res.verified === true)
93
- included.should.equal(false)
94
- })
95
- })
96
-
97
- describe('SmartContract script verification', function () {
98
- it('verifyScript reports a boolean ok and fails an unsatisfiable lock', function () {
99
- var r = bsv.SmartContract.verifyScript(new bsv.Script(), new bsv.Script().add(bsv.Opcode.OP_FALSE))
100
- isStrictBool(r.ok).should.equal(true)
101
- r.ok.should.equal(false)
102
- })
103
- })
104
- })
@@ -1,31 +0,0 @@
1
- 'use strict'
2
-
3
- /* global describe, it */
4
-
5
- // Keeps docs/THREAT_MODEL.md honest: every `test/…js` the threat model cites as the
6
- // enforcer of a security property must actually exist. If a security test is renamed
7
- // or deleted, the threat model can no longer silently point at a ghost — this gate goes
8
- // red. (Same "docs are tested claims" principle as the type-drift gate.)
9
-
10
- require('chai').should()
11
- var fs = require('fs')
12
- var path = require('path')
13
-
14
- var ROOT = path.resolve(__dirname, '../..')
15
- var MODEL = path.join(ROOT, 'docs/THREAT_MODEL.md')
16
-
17
- describe('security: threat model references real tests', function () {
18
- var text = fs.readFileSync(MODEL, 'utf8')
19
- // Every referenced test path, e.g. `test/security/fail_closed_contracts.js`.
20
- var refs = (text.match(/test\/[A-Za-z0-9_./-]+\.js/g) || [])
21
- var unique = refs.filter(function (r, i) { return refs.indexOf(r) === i })
22
-
23
- it('cites a meaningful number of enforcing tests', function () {
24
- unique.length.should.be.above(6)
25
- })
26
-
27
- it('every cited test file exists', function () {
28
- var missing = unique.filter(function (rel) { return !fs.existsSync(path.join(ROOT, rel)) })
29
- missing.should.deep.equal([], 'THREAT_MODEL.md cites test files that do not exist:\n ' + missing.join('\n '))
30
- })
31
- })
@@ -1,245 +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 Locks = SC.Locks
12
- var PushTx = SC.PushTx
13
- var Token = SC.Token
14
- var help = SC.CovenantHelpers
15
- var fundAndSpend = help.fundAndSpend
16
- var p2pkhOutput = help.p2pkhOutput
17
- var verify = help.verify
18
-
19
- var SATS = 100000
20
-
21
- describe('SmartContract covenants (v4.2.0)', 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() // OP_PUSH_TX covenants need post-Genesis limits
29
- })
30
- after(function () {
31
- I.setLimits(saved)
32
- })
33
-
34
- var alice = PrivateKey.fromRandom()
35
- var bob = PrivateKey.fromRandom()
36
- var carol = PrivateKey.fromRandom()
37
-
38
- function spendOf (lock, sats, outputs) {
39
- return fundAndSpend(lock, sats, { outputs: outputs }).spend
40
- }
41
-
42
- describe('Locks', function () {
43
- it('hash-lock accepts the right secret and rejects a wrong one', function () {
44
- var secret = Buffer.from('correct horse battery staple')
45
- var c = Locks.hashLock(secret)
46
- var spend = spendOf(c.lock, SATS, [p2pkhOutput(alice, SATS - 500)])
47
- spend.inputs[0].setScript(c.unlock(spend, SATS))
48
- verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
49
-
50
- spend = spendOf(c.lock, SATS, [p2pkhOutput(alice, SATS - 500)])
51
- spend.inputs[0].setScript(c.unlock(spend, SATS, Buffer.from('wrong')))
52
- verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
53
- })
54
-
55
- it('P2PKH verifies a valid signature', function () {
56
- var c = Locks.p2pkh(alice)
57
- var spend = spendOf(c.lock, SATS, [p2pkhOutput(bob, SATS - 500)])
58
- spend.inputs[0].setScript(c.unlock(spend, SATS))
59
- verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
60
- })
61
-
62
- it('CLTV accepts a matured spend and rejects an immature one', function () {
63
- var lt = 800000
64
- var c = Locks.timeLockCLTV(alice, lt)
65
- var spend = spendOf(c.lock, SATS, [p2pkhOutput(alice, SATS - 500)])
66
- spend.nLockTime = lt; spend.inputs[0].sequenceNumber = 0xfffffffe
67
- spend.inputs[0].setScript(c.unlock(spend, SATS))
68
- verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
69
-
70
- spend = spendOf(c.lock, SATS, [p2pkhOutput(alice, SATS - 500)])
71
- spend.nLockTime = lt - 1; spend.inputs[0].sequenceNumber = 0xfffffffe
72
- spend.inputs[0].setScript(c.unlock(spend, SATS))
73
- verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
74
- })
75
-
76
- it('2-of-3 multisig verifies with two valid signatures', function () {
77
- var c = Locks.multisig(2, [alice, bob, carol])
78
- var spend = spendOf(c.lock, SATS, [p2pkhOutput(bob, SATS - 500)])
79
- spend.inputs[0].setScript(c.unlock(spend, SATS, [alice, bob]))
80
- verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
81
- })
82
-
83
- it('HTLC supports claim and rejects an early refund', function () {
84
- var secret = Buffer.from('lightning-preimage-32bytes------')
85
- var timeout = 750000
86
- var c = Locks.htlc({ secret: secret, receiver: bob, sender: alice, timeout: timeout })
87
- var spend = spendOf(c.lock, SATS, [p2pkhOutput(bob, SATS - 500)])
88
- spend.inputs[0].setScript(c.unlockClaim(spend, SATS))
89
- verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
90
-
91
- spend = spendOf(c.lock, SATS, [p2pkhOutput(alice, SATS - 500)])
92
- spend.nLockTime = timeout - 1; spend.inputs[0].sequenceNumber = 0xfffffffe
93
- spend.inputs[0].setScript(c.unlockRefund(spend, SATS))
94
- verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
95
- })
96
- })
97
-
98
- describe('OP_PUSH_TX', function () {
99
- it('accepts an authentic preimage and rejects a tampered one', function () {
100
- var lock = PushTx.authenticator()
101
- var spend = spendOf(lock, SATS, [p2pkhOutput(alice, SATS - 500)])
102
- var g = PushTx.grind(spend, 0, lock, SATS)
103
- spend.inputs[0].setScript(new Script().add(g.preimage))
104
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
105
-
106
- var bad = Buffer.from(g.preimage); bad[bad.length - 5] ^= 0xff
107
- spend.inputs[0].setScript(new Script().add(bad))
108
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
109
- })
110
-
111
- it('value covenant forces coins to the required outputs', function () {
112
- var beneficiary = PrivateKey.fromRandom()
113
- var attacker = PrivateKey.fromRandom()
114
- var required = [p2pkhOutput(beneficiary, SATS - 500)]
115
- var lock = PushTx.valueCovenant(PushTx.hashOutputs(required))
116
-
117
- var spend = spendOf(lock, SATS, required)
118
- spend.inputs[0].setScript(new Script().add(PushTx.grind(spend, 0, lock, SATS).preimage))
119
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
120
-
121
- spend = spendOf(lock, SATS, [p2pkhOutput(attacker, SATS - 500)])
122
- spend.inputs[0].setScript(new Script().add(PushTx.grind(spend, 0, lock, SATS).preimage))
123
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
124
- })
125
-
126
- it('grind yields a CANONICAL low-S signature, enforced under SCRIPT_VERIFY_LOW_S', function () {
127
- var BN = bsv.crypto.BN
128
- var Hash = bsv.crypto.Hash
129
- var halfN = BN.fromBuffer(Buffer.from('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0', 'hex'))
130
- var I = bsv.Script.Interpreter
131
- // The hardened verify flags enforce low-S.
132
- ;(help.flags() & I.SCRIPT_VERIFY_LOW_S).should.not.equal(0)
133
-
134
- var lock = PushTx.authenticator()
135
- var spend = spendOf(lock, SATS, [p2pkhOutput(alice, SATS - 500)])
136
- var preimage = PushTx.grind(spend, 0, lock, SATS).preimage
137
- // The in-script signature s = (HASH256(preimage)+Gx) mod n is provably low-S.
138
- var s = BN.fromBuffer(Hash.sha256sha256(preimage)).add(BN.fromBuffer(PushTx.Gx)).mod(PushTx.N)
139
- s.gt(halfN).should.equal(false)
140
- // And it verifies with LOW_S enforced (a high-S signature would be rejected here).
141
- spend.inputs[0].setScript(new Script().add(preimage))
142
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
143
- })
144
- })
145
-
146
- describe('PELS (perpetual covenant)', function () {
147
- var FEE = 500
148
- it('recreates the same script and chains a second hop', function () {
149
- var lock = SC.perpetualCovenant(FEE)
150
- var spend = spendOf(lock, SATS, [new Transaction.Output({ script: lock, satoshis: SATS - FEE })])
151
- spend.inputs[0].setScript(new Script().add(PushTx.grind(spend, 0, lock, SATS).preimage))
152
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
153
-
154
- var inSats = SATS - FEE
155
- spend = spendOf(lock, inSats, [new Transaction.Output({ script: lock, satoshis: inSats - FEE })])
156
- spend.inputs[0].setScript(new Script().add(PushTx.grind(spend, 0, lock, inSats).preimage))
157
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: inSats }).ok.should.equal(true)
158
- })
159
-
160
- it('rejects escaping to a P2PKH', function () {
161
- var lock = SC.perpetualCovenant(FEE)
162
- var spend = spendOf(lock, SATS, [p2pkhOutput(PrivateKey.fromRandom(), SATS - FEE)])
163
- spend.inputs[0].setScript(new Script().add(PushTx.grind(spend, 0, lock, SATS).preimage))
164
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
165
- })
166
- })
167
-
168
- describe('Token (stateful NFT)', function () {
169
- var FEE = 500
170
-
171
- // Owners are keys, not secrets: ownerId = HASH160(pubkey); transfers are ECDSA-signed.
172
- function transfer (currentLock, inSats, ownerKey, nextHash, outputOverride) {
173
- var nextLock = Token.ownershipToken(FEE, nextHash)
174
- var out = outputOverride || new Transaction.Output({ script: nextLock, satoshis: inSats - FEE })
175
- var spend = spendOf(currentLock, inSats, [out])
176
- spend.inputs[0].setScript(Token.unlockTransfer(ownerKey, nextHash, spend, inSats, currentLock))
177
- return { spend: spend, nextLock: nextLock }
178
- }
179
-
180
- it('transfers ownership across a chain and rejects a non-owner', function () {
181
- var aliceToken = Token.ownershipToken(FEE, Token.ownerId(alice))
182
- var bobHash = Token.ownerId(bob)
183
- var carolHash = Token.ownerId(carol)
184
-
185
- var r = transfer(aliceToken, SATS, alice, bobHash)
186
- verify(r.spend.inputs[0].script, aliceToken, { tx: r.spend, satoshis: SATS }).ok.should.equal(true)
187
-
188
- var inSats = SATS - FEE
189
- var r2 = transfer(r.nextLock, inSats, bob, carolHash)
190
- verify(r2.spend.inputs[0].script, r.nextLock, { tx: r2.spend, satoshis: inSats }).ok.should.equal(true)
191
-
192
- var bad = transfer(aliceToken, SATS, carol, bobHash) // carol does not own alice's token
193
- verify(bad.spend.inputs[0].script, aliceToken, { tx: bad.spend, satoshis: SATS }).ok.should.equal(false)
194
- })
195
-
196
- it('rejects breaking the token out to a P2PKH', function () {
197
- var aliceToken = Token.ownershipToken(FEE, Token.ownerId(alice))
198
- var r = transfer(aliceToken, SATS, alice, Token.ownerId(bob),
199
- p2pkhOutput(PrivateKey.fromRandom(), SATS - FEE))
200
- verify(r.spend.inputs[0].script, aliceToken, { tx: r.spend, satoshis: SATS }).ok.should.equal(false)
201
- })
202
-
203
- it('defeats front-running: a third party cannot redirect a signed transfer', function () {
204
- var aliceToken = Token.ownershipToken(FEE, Token.ownerId(alice))
205
- var bobHash = Token.ownerId(bob)
206
- var mallory = PrivateKey.fromRandom()
207
- var malloryHash = Token.ownerId(mallory)
208
-
209
- // Alice signs a valid transfer to Bob.
210
- var r = transfer(aliceToken, SATS, alice, bobHash)
211
- verify(r.spend.inputs[0].script, aliceToken, { tx: r.spend, satoshis: SATS }).ok.should.equal(true)
212
-
213
- // A mempool watcher lifts Alice's signature + pubkey (chunks 1 and 2 of her
214
- // unlock script) and tries to redirect the token to Mallory by recreating
215
- // the token output owned by Mallory and reusing Alice's signature.
216
- var chunks = r.spend.inputs[0].script.chunks
217
- var stolenSig = chunks[1].buf
218
- var stolenPub = chunks[2].buf
219
-
220
- var malloryLock = Token.ownershipToken(FEE, malloryHash)
221
- var evil = spendOf(aliceToken, SATS, [new Transaction.Output({ script: malloryLock, satoshis: SATS - FEE })])
222
- var g = PushTx.grind(evil, 0, aliceToken, SATS) // a valid OP_PUSH_TX preimage for the evil tx
223
- evil.inputs[0].setScript(new Script()
224
- .add(Buffer.from(malloryHash))
225
- .add(stolenSig) // Alice signed the Bob-output spend, not this one
226
- .add(stolenPub)
227
- .add(Buffer.from(g.preimage)))
228
-
229
- // OP_CHECKSIG recomputes the sighash over the Mallory output => Alice's sig fails.
230
- verify(evil.inputs[0].script, aliceToken, { tx: evil, satoshis: SATS }).ok.should.equal(false)
231
- })
232
-
233
- it('grinds the input sequence so a token can coexist with a pinned nLockTime', function () {
234
- var aliceToken = Token.ownershipToken(FEE, Token.ownerId(alice))
235
- var bobHash = Token.ownerId(bob)
236
- var nextLock = Token.ownershipToken(FEE, bobHash)
237
- var spend = spendOf(aliceToken, SATS, [new Transaction.Output({ script: nextLock, satoshis: SATS - FEE })])
238
- spend.nLockTime = 500000 // caller pins nLockTime; grind must not clobber it
239
- spend.inputs[0].setScript(
240
- Token.unlockTransfer(alice, bobHash, spend, SATS, aliceToken, { grind: { field: 'sequence' } }))
241
- spend.nLockTime.should.equal(500000)
242
- verify(spend.inputs[0].script, aliceToken, { tx: spend, satoshis: SATS }).ok.should.equal(true)
243
- })
244
- })
245
- })
@@ -1,101 +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 Script = bsv.Script
8
- var PrivateKey = bsv.PrivateKey
9
- var Opcode = bsv.Opcode
10
- var H = SC.CovenantHelpers
11
- var verify = H.verify
12
-
13
- var SATS = 100000
14
-
15
- describe('SmartContract covenant DSL + debugger (v4.5.0)', function () {
16
- this.timeout(20000)
17
- var I = bsv.Script.Interpreter
18
- var saved
19
- before(function () {
20
- saved = I.getLimits()
21
- SC.enableGenesis()
22
- })
23
- after(function () {
24
- I.setLimits(saved)
25
- })
26
-
27
- var alice = PrivateKey.fromRandom()
28
- var attacker = PrivateKey.fromRandom()
29
- function spendWith (lock, outputs) {
30
- return H.fundAndSpend(lock, SATS, { outputs: outputs }).spend
31
- }
32
-
33
- describe('policy() DSL', function () {
34
- it('payTo: compiles a covenant that pins the output and rejects redirects', function () {
35
- var c = SC.policy().payTo(alice.toAddress(), SATS - 500).compile()
36
- var spend = spendWith(c.lock, c.outputs)
37
- spend.inputs[0].setScript(c.unlock(spend, SATS))
38
- verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
39
-
40
- spend = spendWith(c.lock, [H.p2pkhOutput(attacker, SATS - 500)])
41
- spend.inputs[0].setScript(c.unlock(spend, SATS))
42
- verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
43
- })
44
-
45
- it('multi-output: pins the exact ordered set and rejects a tweaked amount', function () {
46
- var c = SC.policy().payTo(alice.toAddress(), 60000).payTo(attacker.toAddress(), 39500).compile()
47
- var spend = spendWith(c.lock, c.outputs)
48
- spend.inputs[0].setScript(c.unlock(spend, SATS))
49
- verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
50
-
51
- var bad = spendWith(c.lock, [H.p2pkhOutput(alice, 60001), H.p2pkhOutput(attacker, 39499)])
52
- bad.inputs[0].setScript(c.unlock(bad, SATS))
53
- verify(bad.inputs[0].script, c.lock, { tx: bad, satoshis: SATS }).ok.should.equal(false)
54
- })
55
-
56
- it('payTo AND lockUntil compose; rejects an early locktime', function () {
57
- var height = 800000
58
- var c = SC.policy().payTo(alice.toAddress(), SATS - 500).lockUntil(height).compile()
59
- var spend = spendWith(c.lock, c.outputs)
60
- spend.inputs[0].setScript(c.unlock(spend, SATS))
61
- verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
62
-
63
- spend = spendWith(c.lock, c.outputs)
64
- spend.nLockTime = height - 1
65
- spend.inputs[0].sequenceNumber = 0xfffffffe
66
- spend.inputs[0].setScript(new Script().add(SC.PushTx.grind(spend, 0, c.lock, SATS).preimage))
67
- verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
68
- })
69
-
70
- it('exposes .perpetual()/.token() shortcuts', function () {
71
- SC.policy.perpetual(500).toBuffer().length.should.be.above(252)
72
- var h = bsv.crypto.Hash.sha256ripemd160(bsv.PrivateKey.fromRandom().toPublicKey().toBuffer())
73
- SC.policy.token(500, h).toBuffer().length.should.be.above(252)
74
- })
75
-
76
- it('throws on an empty policy', function () {
77
- (function () { SC.policy().compile() }).should.throw(/empty policy/)
78
- })
79
- })
80
-
81
- describe('trace() debugger', function () {
82
- it('traces a hash-lock and agrees with the interpreter (true and false)', function () {
83
- var secret = Buffer.from('hello')
84
- var lock = new Script().add(Opcode.OP_SHA256).add(bsv.crypto.Hash.sha256(secret)).add(Opcode.OP_EQUAL)
85
- var r = SC.trace(new Script().add(secret), lock, { satoshis: 1000 })
86
- r.ok.should.equal(true)
87
- r.steps[r.steps.length - 1].stack.join().should.equal('01')
88
- SC.trace(new Script().add(Buffer.from('nope')), lock, { satoshis: 1000 }).ok.should.equal(false)
89
- })
90
-
91
- it('traces an OP_PUSH_TX covenant ending in OP_CHECKSIG -> 01', function () {
92
- var lock = SC.PushTx.authenticator()
93
- var spend = H.fundAndSpend(lock, SATS, { outputs: [H.p2pkhOutput(alice, SATS - 500)] }).spend
94
- spend.inputs[0].setScript(new Script().add(SC.PushTx.grind(spend, 0, lock, SATS).preimage))
95
- var r = SC.trace(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS })
96
- r.ok.should.equal(true)
97
- r.steps[r.steps.length - 1].op.should.equal('OP_CHECKSIG')
98
- SC.Debugger.format(r).should.contain('VALID')
99
- })
100
- })
101
- })
@@ -1,61 +0,0 @@
1
- 'use strict'
2
-
3
- /* global describe, it */
4
-
5
- // Regression for the inverted OP_SPLIT/OP_DROP idiom in CovenantBuilder.extractField.
6
- // After OP_SPLIT the RIGHT part is on top, so keeping it needs OP_NIP; the code used
7
- // OP_DROP (which keeps the LEFT), so RIGHT-strategy fields (value, nSequence,
8
- // hashOutputs, nLocktime, sighashType) extracted the WRONG bytes — a covenant that
9
- // either fails to constrain the spend or is unspendable. These tests drive the real
10
- // builder through the interpreter and assert the extracted bytes are correct.
11
-
12
- require('chai').should()
13
- var bsv = require('../..')
14
- var Interpreter = bsv.Script.Interpreter
15
- var Script = bsv.Script
16
- var Opcode = bsv.Opcode
17
- var Transaction = bsv.Transaction
18
- var CovenantBuilder = require('../../lib/smart_contract/covenant_builder').CovenantBuilder
19
-
20
- // Synthetic preimage: 120 bytes of filler + a 52-byte BIP-143 right zone whose
21
- // fields are individually recognizable.
22
- var FILLER = Buffer.alloc(120, 0x11)
23
- var VALUE = Buffer.alloc(8, 0xaa)
24
- var NSEQ = Buffer.alloc(4, 0xbb)
25
- var HASHOUT = Buffer.alloc(32, 0xcc)
26
- var NLOCK = Buffer.alloc(4, 0xdd)
27
- var SIGHASH = Buffer.alloc(4, 0xee)
28
- var PREIMAGE = Buffer.concat([FILLER, VALUE, NSEQ, HASHOUT, NLOCK, SIGHASH])
29
-
30
- // Run `<PREIMAGE> <extractField(name) ops> <expected> OP_EQUAL` through the
31
- // interpreter (fromASM avoids the separately-tracked _asmToHex issue).
32
- function extractYields (fieldName, expected) {
33
- var b = new CovenantBuilder()
34
- b.extractField(fieldName)
35
- var extractScript = Script.fromASM(b.operations.join(' '))
36
- var lock = new Script().add(PREIMAGE)
37
- extractScript.chunks.forEach(function (c) { lock.chunks.push(c) })
38
- lock.add(expected).add(Opcode.OP_EQUAL)
39
- var interp = new Interpreter()
40
- return interp.verify(new Script(), lock, new Transaction(), 0,
41
- Interpreter.SCRIPT_ENABLE_MAGNETIC_OPCODES | Interpreter.SCRIPT_ENABLE_MONOLITH_OPCODES)
42
- }
43
-
44
- describe('CovenantBuilder.extractField (OP_SPLIT/OP_NIP regression)', function () {
45
- it('extracts RIGHT field "value" (offsetFromEnd 52, len 8)', function () {
46
- extractYields('value', VALUE).should.equal(true)
47
- })
48
- it('extracts RIGHT field "hashOutputs" (offsetFromEnd 40, len 32)', function () {
49
- extractYields('hashOutputs', HASHOUT).should.equal(true)
50
- })
51
- it('extracts RIGHT field "nSequence" (offsetFromEnd 44, len 4)', function () {
52
- extractYields('nSequence', NSEQ).should.equal(true)
53
- })
54
- it('extracts RIGHT field "sighashType" (offsetFromEnd 4, len 4)', function () {
55
- extractYields('sighashType', SIGHASH).should.equal(true)
56
- })
57
- it('extracts LEFT field "hashPrevouts" (offset 4, len 32)', function () {
58
- // First 4 bytes are filler(nVersion); bytes 4..36 are filler too here.
59
- extractYields('hashPrevouts', PREIMAGE.slice(4, 36)).should.equal(true)
60
- })
61
- })
@@ -1,44 +0,0 @@
1
- 'use strict'
2
-
3
- /* global describe, it */
4
-
5
- // Phase 1 consolidation guard: the two NON-ENFORCING covenant builders
6
- // (SmartContract.Covenant and SmartContract.Builder) reduce to plain P2PK and
7
- // bind nothing about the spend. They must fail loud rather than silently hand back
8
- // a fake covenant — callers are redirected to the enforcing OP_PUSH_TX API
9
- // (policy() / PushTx / Token). Explicit { allowNonEnforcing: true } opts in.
10
-
11
- require('chai').should()
12
- var bsv = require('../..')
13
- var SC = bsv.SmartContract
14
- var PrivateKey = bsv.PrivateKey
15
-
16
- describe('Non-enforcing covenant builders fail loud (Phase 1)', function () {
17
- var key = PrivateKey.fromRandom()
18
-
19
- it('SmartContract.Covenant.createFromP2PKH throws by default', function () {
20
- ;(function () {
21
- new SC.Covenant(key).createFromP2PKH({ satoshis: 1000, script: '00' })
22
- }).should.throw(/NON-ENFORCING|allowNonEnforcing/)
23
- })
24
-
25
- it('SmartContract.Builder.buildLockingScript throws by default but works with allowNonEnforcing', function () {
26
- ;(function () {
27
- new SC.Builder(key).buildLockingScript({})
28
- }).should.throw(/NON-ENFORCING|allowNonEnforcing/)
29
-
30
- var script = new SC.Builder(key, { allowNonEnforcing: true }).buildLockingScript({})
31
- script.toBuffer().length.should.be.greaterThan(0)
32
- })
33
-
34
- it('SmartContract.Builder.createCovenant throws by default', function () {
35
- ;(function () {
36
- new SC.Builder(key).createCovenant({ satoshis: 1000, script: '00' })
37
- }).should.throw(/NON-ENFORCING|allowNonEnforcing/)
38
- })
39
-
40
- it('the enforcing path (ordinalLock / valueCovenant) still produces scripts', function () {
41
- SC.ordinalLock(bsv.SmartContract.Token.ownerId(key)).toBuffer().length.should.be.greaterThan(0)
42
- SC.valueCovenant(Buffer.alloc(32)).toBuffer().length.should.be.greaterThan(0)
43
- })
44
- })