@smartledger/bsv 9.0.0 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/CHANGELOG.md +137 -0
  2. package/README.md +152 -1041
  3. package/STABILITY.md +134 -0
  4. package/bsv-gdaf.min.js +53 -48
  5. package/bsv-ltp.min.js +23 -18
  6. package/bsv-smartcontract.min.js +18 -18
  7. package/bsv.bundle.js +53 -48
  8. package/bsv.min.js +53 -48
  9. package/docs/MODULE_REFERENCE_COMPLETE.md +27 -27
  10. package/docs/advanced/UTXO_MANAGER_GUIDE.md +1 -1
  11. package/docs/getting-started/INSTALLATION.md +23 -23
  12. package/docs/getting-started/QUICK_START.md +7 -7
  13. package/docs/migration/FROM_BSV_1_5_6.md +5 -5
  14. package/docs/proposals/10.0.0-package-split.md +131 -0
  15. package/index.js +5 -0
  16. package/lib/block/merkleblock.js +19 -5
  17. package/lib/hdprivatekey.js +15 -0
  18. package/lib/script/interpreter.js +109 -1
  19. package/lib/smart_contract/builder.js +14 -0
  20. package/lib/util/deprecate.js +159 -0
  21. package/package.json +19 -82
  22. package/version.js +1 -1
  23. package/.mocharc.json +0 -5
  24. package/test/address.js +0 -629
  25. package/test/block/block.js +0 -239
  26. package/test/block/blockheader.js +0 -270
  27. package/test/block/merkleblock.js +0 -207
  28. package/test/build/bundle_crypto_shim.js +0 -104
  29. package/test/build/bundle_externals.js +0 -125
  30. package/test/build/bundle_smoke.js +0 -55
  31. package/test/build/esbuild_main.js +0 -65
  32. package/test/build/esm_wrapper.js +0 -97
  33. package/test/build/exports_resolution.js +0 -84
  34. package/test/build/version_sync.js +0 -14
  35. package/test/cli/smoke.js +0 -261
  36. package/test/consensus/base58-vectors.js +0 -120
  37. package/test/consensus/sv-script-vectors.js +0 -81
  38. package/test/consensus/sv-sighash-vectors.js +0 -51
  39. package/test/consensus/sv-tx-vectors.js +0 -48
  40. package/test/credentials-test.js +0 -332
  41. package/test/crypto/backend_selection.js +0 -126
  42. package/test/crypto/bn.js +0 -168
  43. package/test/crypto/ecdsa.js +0 -426
  44. package/test/crypto/elliptic-fixed.js +0 -61
  45. package/test/crypto/hash.browser.js +0 -121
  46. package/test/crypto/hash.js +0 -122
  47. package/test/crypto/point.js +0 -193
  48. package/test/crypto/random.js +0 -105
  49. package/test/crypto/security.js +0 -176
  50. package/test/crypto/shamir.js +0 -177
  51. package/test/crypto/shamir_rngcap.js +0 -29
  52. package/test/crypto/signature.js +0 -399
  53. package/test/data/bip69.json +0 -215
  54. package/test/data/bitcoin-sv/README.md +0 -170
  55. package/test/data/bitcoin-sv/base58_encode_decode.json +0 -14
  56. package/test/data/bitcoin-sv/base58_keys_invalid.json +0 -152
  57. package/test/data/bitcoin-sv/base58_keys_valid.json +0 -452
  58. package/test/data/bitcoin-sv/script_tests.json +0 -2591
  59. package/test/data/bitcoin-sv/sighash.json +0 -1003
  60. package/test/data/bitcoin-sv/tx_invalid.json +0 -285
  61. package/test/data/bitcoin-sv/tx_valid.json +0 -367
  62. package/test/data/bitcoind/base58_keys_invalid.json +0 -152
  63. package/test/data/bitcoind/base58_keys_valid.json +0 -452
  64. package/test/data/bitcoind/blocks.json +0 -27
  65. package/test/data/bitcoind/script_tests.json +0 -2244
  66. package/test/data/bitcoind/sig_canonical.json +0 -7
  67. package/test/data/bitcoind/sig_noncanonical.json +0 -22
  68. package/test/data/bitcoind/tx_invalid.json +0 -177
  69. package/test/data/bitcoind/tx_valid.json +0 -224
  70. package/test/data/blk86756-testnet.dat +0 -0
  71. package/test/data/blk86756-testnet.js +0 -12
  72. package/test/data/blk86756-testnet.json +0 -684
  73. package/test/data/brc220-batch-vector.json +0 -129
  74. package/test/data/ecdsa.json +0 -230
  75. package/test/data/merkleblocks.js +0 -486
  76. package/test/data/messages.json +0 -22
  77. package/test/data/sighash.json +0 -1004
  78. package/test/data/tx_creation.json +0 -85
  79. package/test/didweb/relationships.js +0 -144
  80. package/test/ecies/bitcore-ecies.js +0 -178
  81. package/test/ecies/electrum-ecies.js +0 -206
  82. package/test/encoding/base58.js +0 -131
  83. package/test/encoding/base58check.js +0 -145
  84. package/test/encoding/bufferreader.js +0 -328
  85. package/test/encoding/bufferwriter.js +0 -160
  86. package/test/encoding/varint.js +0 -104
  87. package/test/gdaf/anchor_no_key_leak.js +0 -146
  88. package/test/gdaf/anchor_spv.js +0 -163
  89. package/test/gdaf/canonicalization.js +0 -106
  90. package/test/gdaf/canonicalize.js +0 -140
  91. package/test/gdaf/zk_prover.js +0 -204
  92. package/test/hdkeys.js +0 -365
  93. package/test/hdprivatekey.js +0 -339
  94. package/test/hdpublickey.js +0 -293
  95. package/test/index.js +0 -16
  96. package/test/ltp/ids.js +0 -74
  97. package/test/ltp/right.js +0 -198
  98. package/test/ltp/verify_failclosed.js +0 -134
  99. package/test/message/message.js +0 -190
  100. package/test/mnemonic/data/fixtures.json +0 -300
  101. package/test/mnemonic/mnemonic.js +0 -277
  102. package/test/mnemonic/mocha.opts +0 -1
  103. package/test/mnemonic/pbkdf2.test.js +0 -43
  104. package/test/networks.js +0 -207
  105. package/test/notaryhash/batch_leaf.js +0 -140
  106. package/test/notaryhash/batch_vector.js +0 -282
  107. package/test/notaryhash/certificate.js +0 -249
  108. package/test/notaryhash/encoding.js +0 -186
  109. package/test/notaryhash/interop.js +0 -112
  110. package/test/notaryhash/merkle.js +0 -181
  111. package/test/notaryhash/script.js +0 -270
  112. package/test/notaryhash/verify.js +0 -342
  113. package/test/opcode.js +0 -186
  114. package/test/ordinals/bsv20.js +0 -337
  115. package/test/ordinals/inscription.js +0 -329
  116. package/test/ordinals/ordlock.js +0 -567
  117. package/test/privatekey.js +0 -540
  118. package/test/publickey.js +0 -411
  119. package/test/regressions.js +0 -215
  120. package/test/script/chronicle.js +0 -543
  121. package/test/script/defaults.js +0 -160
  122. package/test/script/genesis_limits.js +0 -203
  123. package/test/script/interpreter.js +0 -776
  124. package/test/script/script.js +0 -1259
  125. package/test/script/string_ops.js +0 -88
  126. package/test/security/fail_closed_contracts.js +0 -104
  127. package/test/security/threat_model_coverage.js +0 -31
  128. package/test/smart_contract/covenants.js +0 -207
  129. package/test/smart_contract/dsl_debugger.js +0 -92
  130. package/test/smart_contract/extract_field.js +0 -61
  131. package/test/smart_contract/nonenforcing_guard.js +0 -44
  132. package/test/smart_contract/ordinal_transfer.js +0 -71
  133. package/test/smart_contract/preimage.js +0 -98
  134. package/test/smart_contract/sighash_marketplace.js +0 -98
  135. package/test/smart_contract/token_generalized.js +0 -175
  136. package/test/spv/headerchain.js +0 -86
  137. package/test/spv/merkleproof.js +0 -133
  138. package/test/statuslist/failclosed.js +0 -78
  139. package/test/transaction/deserialize.js +0 -33
  140. package/test/transaction/input/input.js +0 -92
  141. package/test/transaction/input/multisig.js +0 -174
  142. package/test/transaction/input/multisigscripthash.js +0 -111
  143. package/test/transaction/input/publickey.js +0 -68
  144. package/test/transaction/input/publickeyhash.js +0 -59
  145. package/test/transaction/output.js +0 -185
  146. package/test/transaction/sighash.js +0 -91
  147. package/test/transaction/signature.js +0 -127
  148. package/test/transaction/transaction.js +0 -1299
  149. package/test/transaction/unspentoutput.js +0 -97
  150. package/test/types/dts_drift.js +0 -124
  151. package/test/types/surface_honesty.js +0 -102
  152. package/test/util/id.js +0 -72
  153. package/test/util/js.js +0 -76
  154. package/test/util/preconditions.js +0 -79
  155. package/test/vcjwt/interop.js +0 -126
@@ -1,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,207 +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
-
24
- var alice = PrivateKey.fromRandom()
25
- var bob = PrivateKey.fromRandom()
26
- var carol = PrivateKey.fromRandom()
27
-
28
- function spendOf (lock, sats, outputs) {
29
- return fundAndSpend(lock, sats, { outputs: outputs }).spend
30
- }
31
-
32
- describe('Locks', function () {
33
- it('hash-lock accepts the right secret and rejects a wrong one', function () {
34
- var secret = Buffer.from('correct horse battery staple')
35
- var c = Locks.hashLock(secret)
36
- var spend = spendOf(c.lock, SATS, [p2pkhOutput(alice, SATS - 500)])
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 = spendOf(c.lock, SATS, [p2pkhOutput(alice, SATS - 500)])
41
- spend.inputs[0].setScript(c.unlock(spend, SATS, Buffer.from('wrong')))
42
- verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
43
- })
44
-
45
- it('P2PKH verifies a valid signature', function () {
46
- var c = Locks.p2pkh(alice)
47
- var spend = spendOf(c.lock, SATS, [p2pkhOutput(bob, SATS - 500)])
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
-
52
- it('2-of-3 multisig verifies with two valid signatures', function () {
53
- var c = Locks.multisig(2, [alice, bob, carol])
54
- var spend = spendOf(c.lock, SATS, [p2pkhOutput(bob, SATS - 500)])
55
- spend.inputs[0].setScript(c.unlock(spend, SATS, [alice, bob]))
56
- verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
57
- })
58
- })
59
-
60
- describe('OP_PUSH_TX', function () {
61
- it('accepts an authentic preimage and rejects a tampered one', function () {
62
- var lock = PushTx.authenticator()
63
- var spend = spendOf(lock, SATS, [p2pkhOutput(alice, SATS - 500)])
64
- var g = PushTx.grind(spend, 0, lock, SATS)
65
- spend.inputs[0].setScript(new Script().add(g.preimage))
66
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
67
-
68
- var bad = Buffer.from(g.preimage); bad[bad.length - 5] ^= 0xff
69
- spend.inputs[0].setScript(new Script().add(bad))
70
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
71
- })
72
-
73
- it('value covenant forces coins to the required outputs', function () {
74
- var beneficiary = PrivateKey.fromRandom()
75
- var attacker = PrivateKey.fromRandom()
76
- var required = [p2pkhOutput(beneficiary, SATS - 500)]
77
- var lock = PushTx.valueCovenant(PushTx.hashOutputs(required))
78
-
79
- var spend = spendOf(lock, SATS, required)
80
- spend.inputs[0].setScript(new Script().add(PushTx.grind(spend, 0, lock, SATS).preimage))
81
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
82
-
83
- spend = spendOf(lock, SATS, [p2pkhOutput(attacker, SATS - 500)])
84
- spend.inputs[0].setScript(new Script().add(PushTx.grind(spend, 0, lock, SATS).preimage))
85
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
86
- })
87
-
88
- it('grind yields a CANONICAL low-S signature, enforced under SCRIPT_VERIFY_LOW_S', function () {
89
- var BN = bsv.crypto.BN
90
- var Hash = bsv.crypto.Hash
91
- var halfN = BN.fromBuffer(Buffer.from('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0', 'hex'))
92
- var I = bsv.Script.Interpreter
93
- // The hardened verify flags enforce low-S.
94
- ;(help.flags() & I.SCRIPT_VERIFY_LOW_S).should.not.equal(0)
95
-
96
- var lock = PushTx.authenticator()
97
- var spend = spendOf(lock, SATS, [p2pkhOutput(alice, SATS - 500)])
98
- var preimage = PushTx.grind(spend, 0, lock, SATS).preimage
99
- // The in-script signature s = (HASH256(preimage)+Gx) mod n is provably low-S.
100
- var s = BN.fromBuffer(Hash.sha256sha256(preimage)).add(BN.fromBuffer(PushTx.Gx)).mod(PushTx.N)
101
- s.gt(halfN).should.equal(false)
102
- // And it verifies with LOW_S enforced (a high-S signature would be rejected here).
103
- spend.inputs[0].setScript(new Script().add(preimage))
104
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
105
- })
106
- })
107
-
108
- describe('PELS (perpetual covenant)', function () {
109
- var FEE = 500
110
- it('recreates the same script and chains a second hop', function () {
111
- var lock = SC.perpetualCovenant(FEE)
112
- var spend = spendOf(lock, SATS, [new Transaction.Output({ script: lock, satoshis: SATS - FEE })])
113
- spend.inputs[0].setScript(new Script().add(PushTx.grind(spend, 0, lock, SATS).preimage))
114
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
115
-
116
- var inSats = SATS - FEE
117
- spend = spendOf(lock, inSats, [new Transaction.Output({ script: lock, satoshis: inSats - FEE })])
118
- spend.inputs[0].setScript(new Script().add(PushTx.grind(spend, 0, lock, inSats).preimage))
119
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: inSats }).ok.should.equal(true)
120
- })
121
-
122
- it('rejects escaping to a P2PKH', function () {
123
- var lock = SC.perpetualCovenant(FEE)
124
- var spend = spendOf(lock, SATS, [p2pkhOutput(PrivateKey.fromRandom(), SATS - FEE)])
125
- spend.inputs[0].setScript(new Script().add(PushTx.grind(spend, 0, lock, SATS).preimage))
126
- verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
127
- })
128
- })
129
-
130
- describe('Token (stateful NFT)', function () {
131
- var FEE = 500
132
-
133
- // Owners are keys, not secrets: ownerId = HASH160(pubkey); transfers are ECDSA-signed.
134
- function transfer (currentLock, inSats, ownerKey, nextHash, outputOverride) {
135
- var nextLock = Token.ownershipToken(FEE, nextHash)
136
- var out = outputOverride || new Transaction.Output({ script: nextLock, satoshis: inSats - FEE })
137
- var spend = spendOf(currentLock, inSats, [out])
138
- spend.inputs[0].setScript(Token.unlockTransfer(ownerKey, nextHash, spend, inSats, currentLock))
139
- return { spend: spend, nextLock: nextLock }
140
- }
141
-
142
- it('transfers ownership across a chain and rejects a non-owner', function () {
143
- var aliceToken = Token.ownershipToken(FEE, Token.ownerId(alice))
144
- var bobHash = Token.ownerId(bob)
145
- var carolHash = Token.ownerId(carol)
146
-
147
- var r = transfer(aliceToken, SATS, alice, bobHash)
148
- verify(r.spend.inputs[0].script, aliceToken, { tx: r.spend, satoshis: SATS }).ok.should.equal(true)
149
-
150
- var inSats = SATS - FEE
151
- var r2 = transfer(r.nextLock, inSats, bob, carolHash)
152
- verify(r2.spend.inputs[0].script, r.nextLock, { tx: r2.spend, satoshis: inSats }).ok.should.equal(true)
153
-
154
- var bad = transfer(aliceToken, SATS, carol, bobHash) // carol does not own alice's token
155
- verify(bad.spend.inputs[0].script, aliceToken, { tx: bad.spend, satoshis: SATS }).ok.should.equal(false)
156
- })
157
-
158
- it('rejects breaking the token out to a P2PKH', function () {
159
- var aliceToken = Token.ownershipToken(FEE, Token.ownerId(alice))
160
- var r = transfer(aliceToken, SATS, alice, Token.ownerId(bob),
161
- p2pkhOutput(PrivateKey.fromRandom(), SATS - FEE))
162
- verify(r.spend.inputs[0].script, aliceToken, { tx: r.spend, satoshis: SATS }).ok.should.equal(false)
163
- })
164
-
165
- it('defeats front-running: a third party cannot redirect a signed transfer', function () {
166
- var aliceToken = Token.ownershipToken(FEE, Token.ownerId(alice))
167
- var bobHash = Token.ownerId(bob)
168
- var mallory = PrivateKey.fromRandom()
169
- var malloryHash = Token.ownerId(mallory)
170
-
171
- // Alice signs a valid transfer to Bob.
172
- var r = transfer(aliceToken, SATS, alice, bobHash)
173
- verify(r.spend.inputs[0].script, aliceToken, { tx: r.spend, satoshis: SATS }).ok.should.equal(true)
174
-
175
- // A mempool watcher lifts Alice's signature + pubkey (chunks 1 and 2 of her
176
- // unlock script) and tries to redirect the token to Mallory by recreating
177
- // the token output owned by Mallory and reusing Alice's signature.
178
- var chunks = r.spend.inputs[0].script.chunks
179
- var stolenSig = chunks[1].buf
180
- var stolenPub = chunks[2].buf
181
-
182
- var malloryLock = Token.ownershipToken(FEE, malloryHash)
183
- var evil = spendOf(aliceToken, SATS, [new Transaction.Output({ script: malloryLock, satoshis: SATS - FEE })])
184
- var g = PushTx.grind(evil, 0, aliceToken, SATS) // a valid OP_PUSH_TX preimage for the evil tx
185
- evil.inputs[0].setScript(new Script()
186
- .add(Buffer.from(malloryHash))
187
- .add(stolenSig) // Alice signed the Bob-output spend, not this one
188
- .add(stolenPub)
189
- .add(Buffer.from(g.preimage)))
190
-
191
- // OP_CHECKSIG recomputes the sighash over the Mallory output => Alice's sig fails.
192
- verify(evil.inputs[0].script, aliceToken, { tx: evil, satoshis: SATS }).ok.should.equal(false)
193
- })
194
-
195
- it('grinds the input sequence so a token can coexist with a pinned nLockTime', function () {
196
- var aliceToken = Token.ownershipToken(FEE, Token.ownerId(alice))
197
- var bobHash = Token.ownerId(bob)
198
- var nextLock = Token.ownershipToken(FEE, bobHash)
199
- var spend = spendOf(aliceToken, SATS, [new Transaction.Output({ script: nextLock, satoshis: SATS - FEE })])
200
- spend.nLockTime = 500000 // caller pins nLockTime; grind must not clobber it
201
- spend.inputs[0].setScript(
202
- Token.unlockTransfer(alice, bobHash, spend, SATS, aliceToken, { grind: { field: 'sequence' } }))
203
- spend.nLockTime.should.equal(500000)
204
- verify(spend.inputs[0].script, aliceToken, { tx: spend, satoshis: SATS }).ok.should.equal(true)
205
- })
206
- })
207
- })
@@ -1,92 +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
-
18
- var alice = PrivateKey.fromRandom()
19
- var attacker = PrivateKey.fromRandom()
20
- function spendWith (lock, outputs) {
21
- return H.fundAndSpend(lock, SATS, { outputs: outputs }).spend
22
- }
23
-
24
- describe('policy() DSL', function () {
25
- it('payTo: compiles a covenant that pins the output and rejects redirects', function () {
26
- var c = SC.policy().payTo(alice.toAddress(), SATS - 500).compile()
27
- var spend = spendWith(c.lock, c.outputs)
28
- spend.inputs[0].setScript(c.unlock(spend, SATS))
29
- verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
30
-
31
- spend = spendWith(c.lock, [H.p2pkhOutput(attacker, SATS - 500)])
32
- spend.inputs[0].setScript(c.unlock(spend, SATS))
33
- verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
34
- })
35
-
36
- it('multi-output: pins the exact ordered set and rejects a tweaked amount', function () {
37
- var c = SC.policy().payTo(alice.toAddress(), 60000).payTo(attacker.toAddress(), 39500).compile()
38
- var spend = spendWith(c.lock, c.outputs)
39
- spend.inputs[0].setScript(c.unlock(spend, SATS))
40
- verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
41
-
42
- var bad = spendWith(c.lock, [H.p2pkhOutput(alice, 60001), H.p2pkhOutput(attacker, 39499)])
43
- bad.inputs[0].setScript(c.unlock(bad, SATS))
44
- verify(bad.inputs[0].script, c.lock, { tx: bad, satoshis: SATS }).ok.should.equal(false)
45
- })
46
-
47
- it('payTo AND lockUntil compose; rejects an early locktime', function () {
48
- var height = 800000
49
- var c = SC.policy().payTo(alice.toAddress(), SATS - 500).lockUntil(height).compile()
50
- var spend = spendWith(c.lock, c.outputs)
51
- spend.inputs[0].setScript(c.unlock(spend, SATS))
52
- verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
53
-
54
- spend = spendWith(c.lock, c.outputs)
55
- spend.nLockTime = height - 1
56
- spend.inputs[0].sequenceNumber = 0xfffffffe
57
- spend.inputs[0].setScript(new Script().add(SC.PushTx.grind(spend, 0, c.lock, SATS).preimage))
58
- verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
59
- })
60
-
61
- it('exposes .perpetual()/.token() shortcuts', function () {
62
- SC.policy.perpetual(500).toBuffer().length.should.be.above(252)
63
- var h = bsv.crypto.Hash.sha256ripemd160(bsv.PrivateKey.fromRandom().toPublicKey().toBuffer())
64
- SC.policy.token(500, h).toBuffer().length.should.be.above(252)
65
- })
66
-
67
- it('throws on an empty policy', function () {
68
- (function () { SC.policy().compile() }).should.throw(/empty policy/)
69
- })
70
- })
71
-
72
- describe('trace() debugger', function () {
73
- it('traces a hash-lock and agrees with the interpreter (true and false)', function () {
74
- var secret = Buffer.from('hello')
75
- var lock = new Script().add(Opcode.OP_SHA256).add(bsv.crypto.Hash.sha256(secret)).add(Opcode.OP_EQUAL)
76
- var r = SC.trace(new Script().add(secret), lock, { satoshis: 1000 })
77
- r.ok.should.equal(true)
78
- r.steps[r.steps.length - 1].stack.join().should.equal('01')
79
- SC.trace(new Script().add(Buffer.from('nope')), lock, { satoshis: 1000 }).ok.should.equal(false)
80
- })
81
-
82
- it('traces an OP_PUSH_TX covenant ending in OP_CHECKSIG -> 01', function () {
83
- var lock = SC.PushTx.authenticator()
84
- var spend = H.fundAndSpend(lock, SATS, { outputs: [H.p2pkhOutput(alice, SATS - 500)] }).spend
85
- spend.inputs[0].setScript(new Script().add(SC.PushTx.grind(spend, 0, lock, SATS).preimage))
86
- var r = SC.trace(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS })
87
- r.ok.should.equal(true)
88
- r.steps[r.steps.length - 1].op.should.equal('OP_CHECKSIG')
89
- SC.Debugger.format(r).should.contain('VALID')
90
- })
91
- })
92
- })
@@ -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
- })