@smartledger/bsv 5.4.2 → 5.5.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 (93) hide show
  1. package/.mocharc.json +5 -0
  2. package/CHANGELOG.md +66 -0
  3. package/README.md +28 -8
  4. package/bsv-covenant.min.js +8 -8
  5. package/bsv-gdaf.min.js +11 -11
  6. package/bsv-ltp.min.js +11 -11
  7. package/bsv-smartcontract.min.js +8 -8
  8. package/bsv.bundle.js +11 -11
  9. package/bsv.min.js +11 -11
  10. package/lib/smart_contract/authorizers.js +159 -0
  11. package/lib/smart_contract/covenant_helpers.js +3 -3
  12. package/lib/smart_contract/dsl.js +1 -1
  13. package/lib/smart_contract/index.js +9 -8
  14. package/lib/smart_contract/pels.js +1 -0
  15. package/lib/smart_contract/pushtx.js +51 -7
  16. package/lib/smart_contract/token.js +179 -35
  17. package/package.json +3 -1
  18. package/test/address.js +629 -0
  19. package/test/block/block.js +239 -0
  20. package/test/block/blockheader.js +270 -0
  21. package/test/block/merkleblock.js +207 -0
  22. package/test/cli/smoke.js +261 -0
  23. package/test/credentials-test.js +329 -0
  24. package/test/crypto/bn.js +168 -0
  25. package/test/crypto/ecdsa.js +328 -0
  26. package/test/crypto/elliptic-fixed.js +61 -0
  27. package/test/crypto/hash.browser.js +121 -0
  28. package/test/crypto/hash.js +122 -0
  29. package/test/crypto/point.js +193 -0
  30. package/test/crypto/random.js +32 -0
  31. package/test/crypto/security.js +127 -0
  32. package/test/crypto/shamir.js +169 -0
  33. package/test/crypto/signature.js +318 -0
  34. package/test/data/bip69.json +215 -0
  35. package/test/data/bitcoind/base58_keys_invalid.json +152 -0
  36. package/test/data/bitcoind/base58_keys_valid.json +452 -0
  37. package/test/data/bitcoind/blocks.json +27 -0
  38. package/test/data/bitcoind/script_tests.json +2244 -0
  39. package/test/data/bitcoind/sig_canonical.json +7 -0
  40. package/test/data/bitcoind/sig_noncanonical.json +22 -0
  41. package/test/data/bitcoind/tx_invalid.json +177 -0
  42. package/test/data/bitcoind/tx_valid.json +224 -0
  43. package/test/data/blk86756-testnet.dat +0 -0
  44. package/test/data/blk86756-testnet.js +12 -0
  45. package/test/data/blk86756-testnet.json +684 -0
  46. package/test/data/ecdsa.json +230 -0
  47. package/test/data/merkleblocks.js +486 -0
  48. package/test/data/messages.json +22 -0
  49. package/test/data/sighash.json +1004 -0
  50. package/test/data/tx_creation.json +85 -0
  51. package/test/ecies/bitcore-ecies.js +170 -0
  52. package/test/ecies/electrum-ecies.js +206 -0
  53. package/test/encoding/base58.js +131 -0
  54. package/test/encoding/base58check.js +136 -0
  55. package/test/encoding/bufferreader.js +328 -0
  56. package/test/encoding/bufferwriter.js +160 -0
  57. package/test/encoding/varint.js +104 -0
  58. package/test/gdaf/canonicalize.js +140 -0
  59. package/test/hdkeys.js +365 -0
  60. package/test/hdprivatekey.js +330 -0
  61. package/test/hdpublickey.js +293 -0
  62. package/test/index.js +16 -0
  63. package/test/ltp/right.js +198 -0
  64. package/test/message/message.js +190 -0
  65. package/test/mnemonic/data/fixtures.json +300 -0
  66. package/test/mnemonic/mnemonic.js +245 -0
  67. package/test/mnemonic/mocha.opts +1 -0
  68. package/test/mnemonic/pbkdf2.test.js +43 -0
  69. package/test/networks.js +162 -0
  70. package/test/opcode.js +161 -0
  71. package/test/privatekey.js +430 -0
  72. package/test/publickey.js +411 -0
  73. package/test/script/genesis_limits.js +80 -0
  74. package/test/script/interpreter.js +723 -0
  75. package/test/script/script.js +1256 -0
  76. package/test/script/string_ops.js +72 -0
  77. package/test/smart_contract/covenants.js +247 -0
  78. package/test/smart_contract/dsl_debugger.js +103 -0
  79. package/test/smart_contract/token_generalized.js +187 -0
  80. package/test/transaction/deserialize.js +33 -0
  81. package/test/transaction/input/input.js +92 -0
  82. package/test/transaction/input/multisig.js +174 -0
  83. package/test/transaction/input/multisigscripthash.js +111 -0
  84. package/test/transaction/input/publickey.js +68 -0
  85. package/test/transaction/input/publickeyhash.js +59 -0
  86. package/test/transaction/output.js +185 -0
  87. package/test/transaction/sighash.js +71 -0
  88. package/test/transaction/signature.js +127 -0
  89. package/test/transaction/transaction.js +1299 -0
  90. package/test/transaction/unspentoutput.js +92 -0
  91. package/test/util/js.js +76 -0
  92. package/test/util/preconditions.js +79 -0
  93. package/test/vcjwt/interop.js +126 -0
@@ -0,0 +1,72 @@
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
+
11
+ // OP_SUBSTR / OP_LEFT / OP_RIGHT — re-enabled BSV (Chronicle) string opcodes,
12
+ // original Satoshi semantics. Declared in the opcode map at 0xb3/0xb4/0xb5 but
13
+ // previously unimplemented (BAD_OPCODE). These tests verify behavior end-to-end.
14
+ describe('String opcodes OP_SUBSTR / OP_LEFT / OP_RIGHT', function () {
15
+ var DATA = Buffer.from('aabbccdd', 'hex')
16
+
17
+ // Build `<DATA> <ops...>` and assert the top of stack equals `expected`.
18
+ function yields (buildOps, expectedHex) {
19
+ var lock = new Script().add(DATA)
20
+ buildOps(lock)
21
+ lock.add(Buffer.from(expectedHex, 'hex')).add(Opcode.OP_EQUAL)
22
+ var interp = new Interpreter()
23
+ return interp.verify(new Script(), lock, new Transaction(), 0, 0)
24
+ }
25
+ function errors (buildOps) {
26
+ var lock = new Script().add(DATA)
27
+ buildOps(lock)
28
+ var interp = new Interpreter()
29
+ var ok
30
+ try { ok = interp.verify(new Script(), lock, new Transaction(), 0, 0) } catch (e) { ok = false }
31
+ return ok === false
32
+ }
33
+
34
+ it('OP_LEFT keeps the first n bytes', function () {
35
+ yields(function (s) { s.add(Opcode.OP_2).add(Opcode.OP_LEFT) }, 'aabb').should.equal(true)
36
+ })
37
+
38
+ it('OP_RIGHT keeps the last n bytes', function () {
39
+ yields(function (s) { s.add(Opcode.OP_2).add(Opcode.OP_RIGHT) }, 'ccdd').should.equal(true)
40
+ })
41
+
42
+ it('OP_RIGHT 0 yields the empty string (not the whole string)', function () {
43
+ yields(function (s) { s.add(Opcode.OP_0).add(Opcode.OP_RIGHT) }, '').should.equal(true)
44
+ })
45
+
46
+ it('OP_LEFT 0 yields the empty string', function () {
47
+ yields(function (s) { s.add(Opcode.OP_0).add(Opcode.OP_LEFT) }, '').should.equal(true)
48
+ })
49
+
50
+ it('OP_SUBSTR extracts in[begin : begin+size]', function () {
51
+ // begin=1 size=2 -> bbcc. Stack order is (in begin size -- out).
52
+ yields(function (s) { s.add(Opcode.OP_1).add(Opcode.OP_2).add(Opcode.OP_SUBSTR) }, 'bbcc').should.equal(true)
53
+ })
54
+
55
+ it('OP_LEFT clamps n greater than the length', function () {
56
+ yields(function (s) { s.add(Opcode.OP_10).add(Opcode.OP_LEFT) }, 'aabbccdd').should.equal(true)
57
+ })
58
+
59
+ it('OP_RIGHT clamps n greater than the length', function () {
60
+ yields(function (s) { s.add(Opcode.OP_10).add(Opcode.OP_RIGHT) }, 'aabbccdd').should.equal(true)
61
+ })
62
+
63
+ it('rejects a negative size', function () {
64
+ errors(function (s) { s.add(Opcode.OP_1NEGATE).add(Opcode.OP_LEFT) }).should.equal(true)
65
+ })
66
+
67
+ it('rejects OP_LEFT with too few stack items', function () {
68
+ var interp = new Interpreter()
69
+ var lock = new Script().add(Opcode.OP_LEFT) // nothing to operate on
70
+ interp.verify(new Script(), lock, new Transaction(), 0, 0).should.equal(false)
71
+ })
72
+ })
@@ -0,0 +1,247 @@
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 = { el: I.MAX_SCRIPT_ELEMENT_SIZE, num: I.MAXIMUM_ELEMENT_SIZE, ops: I.MAX_OPS_PER_SCRIPT }
28
+ SC.enableGenesis() // OP_PUSH_TX covenants need post-Genesis limits
29
+ })
30
+ after(function () {
31
+ I.MAX_SCRIPT_ELEMENT_SIZE = saved.el
32
+ I.MAXIMUM_ELEMENT_SIZE = saved.num
33
+ I.MAX_OPS_PER_SCRIPT = saved.ops
34
+ })
35
+
36
+ var alice = PrivateKey.fromRandom()
37
+ var bob = PrivateKey.fromRandom()
38
+ var carol = PrivateKey.fromRandom()
39
+
40
+ function spendOf (lock, sats, outputs) {
41
+ return fundAndSpend(lock, sats, { outputs: outputs }).spend
42
+ }
43
+
44
+ describe('Locks', function () {
45
+ it('hash-lock accepts the right secret and rejects a wrong one', function () {
46
+ var secret = Buffer.from('correct horse battery staple')
47
+ var c = Locks.hashLock(secret)
48
+ var spend = spendOf(c.lock, SATS, [p2pkhOutput(alice, SATS - 500)])
49
+ spend.inputs[0].setScript(c.unlock(spend, SATS))
50
+ verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
51
+
52
+ spend = spendOf(c.lock, SATS, [p2pkhOutput(alice, SATS - 500)])
53
+ spend.inputs[0].setScript(c.unlock(spend, SATS, Buffer.from('wrong')))
54
+ verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
55
+ })
56
+
57
+ it('P2PKH verifies a valid signature', function () {
58
+ var c = Locks.p2pkh(alice)
59
+ var spend = spendOf(c.lock, SATS, [p2pkhOutput(bob, SATS - 500)])
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
+
64
+ it('CLTV accepts a matured spend and rejects an immature one', function () {
65
+ var lt = 800000
66
+ var c = Locks.timeLockCLTV(alice, lt)
67
+ var spend = spendOf(c.lock, SATS, [p2pkhOutput(alice, SATS - 500)])
68
+ spend.nLockTime = lt; spend.inputs[0].sequenceNumber = 0xfffffffe
69
+ spend.inputs[0].setScript(c.unlock(spend, SATS))
70
+ verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
71
+
72
+ spend = spendOf(c.lock, SATS, [p2pkhOutput(alice, SATS - 500)])
73
+ spend.nLockTime = lt - 1; spend.inputs[0].sequenceNumber = 0xfffffffe
74
+ spend.inputs[0].setScript(c.unlock(spend, SATS))
75
+ verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
76
+ })
77
+
78
+ it('2-of-3 multisig verifies with two valid signatures', function () {
79
+ var c = Locks.multisig(2, [alice, bob, carol])
80
+ var spend = spendOf(c.lock, SATS, [p2pkhOutput(bob, SATS - 500)])
81
+ spend.inputs[0].setScript(c.unlock(spend, SATS, [alice, bob]))
82
+ verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
83
+ })
84
+
85
+ it('HTLC supports claim and rejects an early refund', function () {
86
+ var secret = Buffer.from('lightning-preimage-32bytes------')
87
+ var timeout = 750000
88
+ var c = Locks.htlc({ secret: secret, receiver: bob, sender: alice, timeout: timeout })
89
+ var spend = spendOf(c.lock, SATS, [p2pkhOutput(bob, SATS - 500)])
90
+ spend.inputs[0].setScript(c.unlockClaim(spend, SATS))
91
+ verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
92
+
93
+ spend = spendOf(c.lock, SATS, [p2pkhOutput(alice, SATS - 500)])
94
+ spend.nLockTime = timeout - 1; spend.inputs[0].sequenceNumber = 0xfffffffe
95
+ spend.inputs[0].setScript(c.unlockRefund(spend, SATS))
96
+ verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
97
+ })
98
+ })
99
+
100
+ describe('OP_PUSH_TX', function () {
101
+ it('accepts an authentic preimage and rejects a tampered one', function () {
102
+ var lock = PushTx.authenticator()
103
+ var spend = spendOf(lock, SATS, [p2pkhOutput(alice, SATS - 500)])
104
+ var g = PushTx.grind(spend, 0, lock, SATS)
105
+ spend.inputs[0].setScript(new Script().add(g.preimage))
106
+ verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
107
+
108
+ var bad = Buffer.from(g.preimage); bad[bad.length - 5] ^= 0xff
109
+ spend.inputs[0].setScript(new Script().add(bad))
110
+ verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
111
+ })
112
+
113
+ it('value covenant forces coins to the required outputs', function () {
114
+ var beneficiary = PrivateKey.fromRandom()
115
+ var attacker = PrivateKey.fromRandom()
116
+ var required = [p2pkhOutput(beneficiary, SATS - 500)]
117
+ var lock = PushTx.valueCovenant(PushTx.hashOutputs(required))
118
+
119
+ var spend = spendOf(lock, SATS, required)
120
+ spend.inputs[0].setScript(new Script().add(PushTx.grind(spend, 0, lock, SATS).preimage))
121
+ verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
122
+
123
+ spend = spendOf(lock, SATS, [p2pkhOutput(attacker, SATS - 500)])
124
+ spend.inputs[0].setScript(new Script().add(PushTx.grind(spend, 0, lock, SATS).preimage))
125
+ verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
126
+ })
127
+
128
+ it('grind yields a CANONICAL low-S signature, enforced under SCRIPT_VERIFY_LOW_S', function () {
129
+ var BN = bsv.crypto.BN
130
+ var Hash = bsv.crypto.Hash
131
+ var halfN = BN.fromBuffer(Buffer.from('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0', 'hex'))
132
+ var I = bsv.Script.Interpreter
133
+ // The hardened verify flags enforce low-S.
134
+ ;(help.flags() & I.SCRIPT_VERIFY_LOW_S).should.not.equal(0)
135
+
136
+ var lock = PushTx.authenticator()
137
+ var spend = spendOf(lock, SATS, [p2pkhOutput(alice, SATS - 500)])
138
+ var preimage = PushTx.grind(spend, 0, lock, SATS).preimage
139
+ // The in-script signature s = (HASH256(preimage)+Gx) mod n is provably low-S.
140
+ var s = BN.fromBuffer(Hash.sha256sha256(preimage)).add(BN.fromBuffer(PushTx.Gx)).mod(PushTx.N)
141
+ s.gt(halfN).should.equal(false)
142
+ // And it verifies with LOW_S enforced (a high-S signature would be rejected here).
143
+ spend.inputs[0].setScript(new Script().add(preimage))
144
+ verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
145
+ })
146
+ })
147
+
148
+ describe('PELS (perpetual covenant)', function () {
149
+ var FEE = 500
150
+ it('recreates the same script and chains a second hop', function () {
151
+ var lock = SC.perpetualCovenant(FEE)
152
+ var spend = spendOf(lock, SATS, [new Transaction.Output({ script: lock, satoshis: SATS - FEE })])
153
+ spend.inputs[0].setScript(new Script().add(PushTx.grind(spend, 0, lock, SATS).preimage))
154
+ verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
155
+
156
+ var inSats = SATS - FEE
157
+ spend = spendOf(lock, inSats, [new Transaction.Output({ script: lock, satoshis: inSats - FEE })])
158
+ spend.inputs[0].setScript(new Script().add(PushTx.grind(spend, 0, lock, inSats).preimage))
159
+ verify(spend.inputs[0].script, lock, { tx: spend, satoshis: inSats }).ok.should.equal(true)
160
+ })
161
+
162
+ it('rejects escaping to a P2PKH', function () {
163
+ var lock = SC.perpetualCovenant(FEE)
164
+ var spend = spendOf(lock, SATS, [p2pkhOutput(PrivateKey.fromRandom(), SATS - FEE)])
165
+ spend.inputs[0].setScript(new Script().add(PushTx.grind(spend, 0, lock, SATS).preimage))
166
+ verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
167
+ })
168
+ })
169
+
170
+ describe('Token (stateful NFT)', function () {
171
+ var FEE = 500
172
+
173
+ // Owners are keys, not secrets: ownerId = HASH160(pubkey); transfers are ECDSA-signed.
174
+ function transfer (currentLock, inSats, ownerKey, nextHash, outputOverride) {
175
+ var nextLock = Token.ownershipToken(FEE, nextHash)
176
+ var out = outputOverride || new Transaction.Output({ script: nextLock, satoshis: inSats - FEE })
177
+ var spend = spendOf(currentLock, inSats, [out])
178
+ spend.inputs[0].setScript(Token.unlockTransfer(ownerKey, nextHash, spend, inSats, currentLock))
179
+ return { spend: spend, nextLock: nextLock }
180
+ }
181
+
182
+ it('transfers ownership across a chain and rejects a non-owner', function () {
183
+ var aliceToken = Token.ownershipToken(FEE, Token.ownerId(alice))
184
+ var bobHash = Token.ownerId(bob)
185
+ var carolHash = Token.ownerId(carol)
186
+
187
+ var r = transfer(aliceToken, SATS, alice, bobHash)
188
+ verify(r.spend.inputs[0].script, aliceToken, { tx: r.spend, satoshis: SATS }).ok.should.equal(true)
189
+
190
+ var inSats = SATS - FEE
191
+ var r2 = transfer(r.nextLock, inSats, bob, carolHash)
192
+ verify(r2.spend.inputs[0].script, r.nextLock, { tx: r2.spend, satoshis: inSats }).ok.should.equal(true)
193
+
194
+ var bad = transfer(aliceToken, SATS, carol, bobHash) // carol does not own alice's token
195
+ verify(bad.spend.inputs[0].script, aliceToken, { tx: bad.spend, satoshis: SATS }).ok.should.equal(false)
196
+ })
197
+
198
+ it('rejects breaking the token out to a P2PKH', function () {
199
+ var aliceToken = Token.ownershipToken(FEE, Token.ownerId(alice))
200
+ var r = transfer(aliceToken, SATS, alice, Token.ownerId(bob),
201
+ p2pkhOutput(PrivateKey.fromRandom(), SATS - FEE))
202
+ verify(r.spend.inputs[0].script, aliceToken, { tx: r.spend, satoshis: SATS }).ok.should.equal(false)
203
+ })
204
+
205
+ it('defeats front-running: a third party cannot redirect a signed transfer', function () {
206
+ var aliceToken = Token.ownershipToken(FEE, Token.ownerId(alice))
207
+ var bobHash = Token.ownerId(bob)
208
+ var mallory = PrivateKey.fromRandom()
209
+ var malloryHash = Token.ownerId(mallory)
210
+
211
+ // Alice signs a valid transfer to Bob.
212
+ var r = transfer(aliceToken, SATS, alice, bobHash)
213
+ verify(r.spend.inputs[0].script, aliceToken, { tx: r.spend, satoshis: SATS }).ok.should.equal(true)
214
+
215
+ // A mempool watcher lifts Alice's signature + pubkey (chunks 1 and 2 of her
216
+ // unlock script) and tries to redirect the token to Mallory by recreating
217
+ // the token output owned by Mallory and reusing Alice's signature.
218
+ var chunks = r.spend.inputs[0].script.chunks
219
+ var stolenSig = chunks[1].buf
220
+ var stolenPub = chunks[2].buf
221
+
222
+ var malloryLock = Token.ownershipToken(FEE, malloryHash)
223
+ var evil = spendOf(aliceToken, SATS, [new Transaction.Output({ script: malloryLock, satoshis: SATS - FEE })])
224
+ var g = PushTx.grind(evil, 0, aliceToken, SATS) // a valid OP_PUSH_TX preimage for the evil tx
225
+ evil.inputs[0].setScript(new Script()
226
+ .add(Buffer.from(malloryHash))
227
+ .add(stolenSig) // Alice signed the Bob-output spend, not this one
228
+ .add(stolenPub)
229
+ .add(Buffer.from(g.preimage)))
230
+
231
+ // OP_CHECKSIG recomputes the sighash over the Mallory output => Alice's sig fails.
232
+ verify(evil.inputs[0].script, aliceToken, { tx: evil, satoshis: SATS }).ok.should.equal(false)
233
+ })
234
+
235
+ it('grinds the input sequence so a token can coexist with a pinned nLockTime', function () {
236
+ var aliceToken = Token.ownershipToken(FEE, Token.ownerId(alice))
237
+ var bobHash = Token.ownerId(bob)
238
+ var nextLock = Token.ownershipToken(FEE, bobHash)
239
+ var spend = spendOf(aliceToken, SATS, [new Transaction.Output({ script: nextLock, satoshis: SATS - FEE })])
240
+ spend.nLockTime = 500000 // caller pins nLockTime; grind must not clobber it
241
+ spend.inputs[0].setScript(
242
+ Token.unlockTransfer(alice, bobHash, spend, SATS, aliceToken, { grind: { field: 'sequence' } }))
243
+ spend.nLockTime.should.equal(500000)
244
+ verify(spend.inputs[0].script, aliceToken, { tx: spend, satoshis: SATS }).ok.should.equal(true)
245
+ })
246
+ })
247
+ })
@@ -0,0 +1,103 @@
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 = { el: I.MAX_SCRIPT_ELEMENT_SIZE, num: I.MAXIMUM_ELEMENT_SIZE, ops: I.MAX_OPS_PER_SCRIPT }
21
+ SC.enableGenesis()
22
+ })
23
+ after(function () {
24
+ I.MAX_SCRIPT_ELEMENT_SIZE = saved.el
25
+ I.MAXIMUM_ELEMENT_SIZE = saved.num
26
+ I.MAX_OPS_PER_SCRIPT = saved.ops
27
+ })
28
+
29
+ var alice = PrivateKey.fromRandom()
30
+ var attacker = PrivateKey.fromRandom()
31
+ function spendWith (lock, outputs) {
32
+ return H.fundAndSpend(lock, SATS, { outputs: outputs }).spend
33
+ }
34
+
35
+ describe('policy() DSL', function () {
36
+ it('payTo: compiles a covenant that pins the output and rejects redirects', function () {
37
+ var c = SC.policy().payTo(alice.toAddress(), SATS - 500).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
+ spend = spendWith(c.lock, [H.p2pkhOutput(attacker, SATS - 500)])
43
+ spend.inputs[0].setScript(c.unlock(spend, SATS))
44
+ verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
45
+ })
46
+
47
+ it('multi-output: pins the exact ordered set and rejects a tweaked amount', function () {
48
+ var c = SC.policy().payTo(alice.toAddress(), 60000).payTo(attacker.toAddress(), 39500).compile()
49
+ var spend = spendWith(c.lock, c.outputs)
50
+ spend.inputs[0].setScript(c.unlock(spend, SATS))
51
+ verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
52
+
53
+ var bad = spendWith(c.lock, [H.p2pkhOutput(alice, 60001), H.p2pkhOutput(attacker, 39499)])
54
+ bad.inputs[0].setScript(c.unlock(bad, SATS))
55
+ verify(bad.inputs[0].script, c.lock, { tx: bad, satoshis: SATS }).ok.should.equal(false)
56
+ })
57
+
58
+ it('payTo AND lockUntil compose; rejects an early locktime', function () {
59
+ var height = 800000
60
+ var c = SC.policy().payTo(alice.toAddress(), SATS - 500).lockUntil(height).compile()
61
+ var spend = spendWith(c.lock, c.outputs)
62
+ spend.inputs[0].setScript(c.unlock(spend, SATS))
63
+ verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
64
+
65
+ spend = spendWith(c.lock, c.outputs)
66
+ spend.nLockTime = height - 1
67
+ spend.inputs[0].sequenceNumber = 0xfffffffe
68
+ spend.inputs[0].setScript(new Script().add(SC.PushTx.grind(spend, 0, c.lock, SATS).preimage))
69
+ verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
70
+ })
71
+
72
+ it('exposes .perpetual()/.token() shortcuts', function () {
73
+ SC.policy.perpetual(500).toBuffer().length.should.be.above(252)
74
+ var h = bsv.crypto.Hash.sha256ripemd160(bsv.PrivateKey.fromRandom().toPublicKey().toBuffer())
75
+ SC.policy.token(500, h).toBuffer().length.should.be.above(252)
76
+ })
77
+
78
+ it('throws on an empty policy', function () {
79
+ (function () { SC.policy().compile() }).should.throw(/empty policy/)
80
+ })
81
+ })
82
+
83
+ describe('trace() debugger', function () {
84
+ it('traces a hash-lock and agrees with the interpreter (true and false)', function () {
85
+ var secret = Buffer.from('hello')
86
+ var lock = new Script().add(Opcode.OP_SHA256).add(bsv.crypto.Hash.sha256(secret)).add(Opcode.OP_EQUAL)
87
+ var r = SC.trace(new Script().add(secret), lock, { satoshis: 1000 })
88
+ r.ok.should.equal(true)
89
+ r.steps[r.steps.length - 1].stack.join().should.equal('01')
90
+ SC.trace(new Script().add(Buffer.from('nope')), lock, { satoshis: 1000 }).ok.should.equal(false)
91
+ })
92
+
93
+ it('traces an OP_PUSH_TX covenant ending in OP_CHECKSIG -> 01', function () {
94
+ var lock = SC.PushTx.authenticator()
95
+ var spend = H.fundAndSpend(lock, SATS, { outputs: [H.p2pkhOutput(alice, SATS - 500)] }).spend
96
+ spend.inputs[0].setScript(new Script().add(SC.PushTx.grind(spend, 0, lock, SATS).preimage))
97
+ var r = SC.trace(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS })
98
+ r.ok.should.equal(true)
99
+ r.steps[r.steps.length - 1].op.should.equal('OP_CHECKSIG')
100
+ SC.Debugger.format(r).should.contain('VALID')
101
+ })
102
+ })
103
+ })
@@ -0,0 +1,187 @@
1
+ 'use strict'
2
+
3
+ /* global describe, it, before, after */
4
+ require('chai').should()
5
+ var bsv = require('../..')
6
+ var SC = bsv.SmartContract
7
+ var PrivateKey = bsv.PrivateKey
8
+ var Script = bsv.Script
9
+ var Transaction = bsv.Transaction
10
+
11
+ var Token = SC.Token
12
+ var Auth = SC.Authorizers
13
+ var help = SC.CovenantHelpers
14
+ var fundAndSpend = help.fundAndSpend
15
+ var p2pkhOutput = help.p2pkhOutput
16
+ var verify = help.verify
17
+
18
+ var SATS = 100000
19
+ var FEE = 500
20
+
21
+ describe('SmartContract generalized token (pluggable auth + multi-output)', function () {
22
+ this.timeout(20000)
23
+ var I = bsv.Script.Interpreter
24
+ var saved
25
+
26
+ before(function () {
27
+ saved = { el: I.MAX_SCRIPT_ELEMENT_SIZE, num: I.MAXIMUM_ELEMENT_SIZE, ops: I.MAX_OPS_PER_SCRIPT }
28
+ SC.enableGenesis()
29
+ })
30
+ after(function () {
31
+ I.MAX_SCRIPT_ELEMENT_SIZE = saved.el
32
+ I.MAXIMUM_ELEMENT_SIZE = saved.num
33
+ I.MAX_OPS_PER_SCRIPT = saved.ops
34
+ })
35
+
36
+ function spendOf (lock, sats, outputs) {
37
+ return fundAndSpend(lock, sats, { outputs: outputs }).spend
38
+ }
39
+
40
+ describe('single-key authorizer (default)', function () {
41
+ var alice = PrivateKey.fromRandom()
42
+ var bob = PrivateKey.fromRandom()
43
+
44
+ it('transfers and rejects a non-owner', function () {
45
+ var lock = Token.ownershipToken(FEE, Token.ownerId(alice))
46
+ var bobHash = Token.ownerId(bob)
47
+ var next = Token.ownershipToken(FEE, bobHash)
48
+ var spend = spendOf(lock, SATS, [new Transaction.Output({ script: next, satoshis: SATS - FEE })])
49
+ spend.inputs[0].setScript(Token.unlockTransfer(alice, bobHash, spend, SATS, lock))
50
+ verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
51
+
52
+ var bad = spendOf(lock, SATS, [new Transaction.Output({ script: next, satoshis: SATS - FEE })])
53
+ bad.inputs[0].setScript(Token.unlockTransfer(bob, bobHash, bad, SATS, lock)) // bob does not own alice's token
54
+ verify(bad.inputs[0].script, lock, { tx: bad, satoshis: SATS }).ok.should.equal(false)
55
+ })
56
+ })
57
+
58
+ describe('multisig authorizer (m-of-n)', function () {
59
+ var a = PrivateKey.fromRandom()
60
+ var b = PrivateKey.fromRandom()
61
+ var c = PrivateKey.fromRandom()
62
+ var keys = [a.toPublicKey(), b.toPublicKey(), c.toPublicKey()]
63
+ var auth23 = Auth.multisig(2, 3)
64
+ var commit = auth23.commit(keys)
65
+ var nextOwner = Token.ownerId(PrivateKey.fromRandom())
66
+
67
+ function transfer23 (signWith) {
68
+ var lock = Token.ownershipToken(FEE, commit, auth23)
69
+ var next = Token.ownershipToken(FEE, nextOwner, auth23) // covenant recreates the SAME (multisig) code
70
+ var spend = spendOf(lock, SATS, [new Transaction.Output({ script: next, satoshis: SATS - FEE })])
71
+ spend.inputs[0].setScript(
72
+ Token.unlockTransfer({ keys: keys, signWith: signWith }, nextOwner, spend, SATS, lock, { auth: auth23 }))
73
+ return { spend: spend, lock: lock }
74
+ }
75
+
76
+ it('accepts a valid 2-of-3 (keys a,c)', function () {
77
+ var r = transfer23([a, c])
78
+ verify(r.spend.inputs[0].script, r.lock, { tx: r.spend, satoshis: SATS }).ok.should.equal(true)
79
+ })
80
+
81
+ it('accepts a different valid pair (keys b,c)', function () {
82
+ var r = transfer23([b, c])
83
+ verify(r.spend.inputs[0].script, r.lock, { tx: r.spend, satoshis: SATS }).ok.should.equal(true)
84
+ })
85
+
86
+ it('rejects a single signature (below threshold)', function () {
87
+ var run = function () { transfer23([a]) }
88
+ run.should.throw(/at least 2/)
89
+ })
90
+
91
+ it('rejects a signer outside the committed set', function () {
92
+ var outsider = PrivateKey.fromRandom()
93
+ var run = function () { transfer23([a, outsider]) }
94
+ run.should.throw(/not in the committed key set/)
95
+ })
96
+
97
+ it('rejects a forged set (right threshold, wrong keys)', function () {
98
+ // Commit to {a,b,c} but try to spend revealing a different key set {a,b,x}.
99
+ var x = PrivateKey.fromRandom()
100
+ var wrongKeys = [a.toPublicKey(), b.toPublicKey(), x.toPublicKey()]
101
+ var lock = Token.ownershipToken(FEE, commit, auth23)
102
+ var next = Token.ownershipToken(FEE, nextOwner, auth23)
103
+ var spend = spendOf(lock, SATS, [new Transaction.Output({ script: next, satoshis: SATS - FEE })])
104
+ spend.inputs[0].setScript(
105
+ Token.unlockTransfer({ keys: wrongKeys, signWith: [a, b] }, nextOwner, spend, SATS, lock, { auth: auth23 }))
106
+ // HASH160(forged redeem) != committed redeem hash => fails.
107
+ verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
108
+ })
109
+ })
110
+
111
+ describe('predicate authorizer (custom escape hatch)', function () {
112
+ // Re-implement single-key through the generic predicate interface to prove
113
+ // the contract is sufficient for custom schemes.
114
+ var Opcode = bsv.Opcode
115
+ var Hash = bsv.crypto.Hash
116
+ var owner = PrivateKey.fromRandom()
117
+ var custom = Auth.predicate({
118
+ name: 'custom-single-key',
119
+ commit: function (key) { return Hash.sha256ripemd160(key.toPublicKey().toBuffer()) },
120
+ emit: function (s) {
121
+ return s.add(Opcode.OP_OVER).add(Opcode.OP_HASH160).add(Opcode.OP_EQUALVERIFY).add(Opcode.OP_CHECKSIGVERIFY)
122
+ },
123
+ unlockArgs: function (spend, inputIndex, lock, sats, key) {
124
+ return [help.signInput(spend, key, inputIndex, lock, sats), key.toPublicKey().toBuffer()]
125
+ }
126
+ })
127
+
128
+ it('authorizes a transfer via a custom predicate', function () {
129
+ var lock = Token.ownershipToken(FEE, custom.commit(owner), custom)
130
+ var nextOwner = Token.ownerId(PrivateKey.fromRandom())
131
+ var next = Token.ownershipToken(FEE, nextOwner)
132
+ var spend = spendOf(lock, SATS, [new Transaction.Output({ script: next, satoshis: SATS - FEE })])
133
+ spend.inputs[0].setScript(Token.unlockTransfer(owner, nextOwner, spend, SATS, lock, { auth: custom }))
134
+ verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
135
+ })
136
+ })
137
+
138
+ describe('multi-output token (N-output)', function () {
139
+ var alice = PrivateKey.fromRandom()
140
+ var bob = PrivateKey.fromRandom()
141
+
142
+ function serialize (output) { return output.toBufferWriter().toBuffer() }
143
+ function le8 (v) { return new bsv.crypto.BN(v).toBuffer({ endian: 'little', size: 8 }) }
144
+
145
+ it('recreates the token among other outputs and binds them all', function () {
146
+ var lock = Token.ownershipTokenMulti(Token.ownerId(alice))
147
+ var bobHash = Token.ownerId(bob)
148
+ var nextToken = Token.ownershipTokenMulti(bobHash)
149
+
150
+ var payment = p2pkhOutput(PrivateKey.fromRandom(), 4000)
151
+ var tokenValue = 1000
152
+ var tokenOutput = new Transaction.Output({ script: nextToken, satoshis: tokenValue })
153
+ var dataOutput = new Transaction.Output({
154
+ script: new Script().add(bsv.Opcode.OP_FALSE).add(bsv.Opcode.OP_RETURN).add(Buffer.from('hello')),
155
+ satoshis: 0
156
+ })
157
+
158
+ // tx outputs in order: [payment, token, data]
159
+ var spend = spendOf(lock, SATS, [payment, tokenOutput, dataOutput])
160
+ spend.inputs[0].setScript(Token.unlockTransferMulti(alice, bobHash, spend, SATS, lock, {
161
+ before: serialize(payment),
162
+ after: serialize(dataOutput),
163
+ tokenValue: le8(tokenValue)
164
+ }))
165
+ verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
166
+ })
167
+
168
+ it('rejects a tampered surrounding output', function () {
169
+ var lock = Token.ownershipTokenMulti(Token.ownerId(alice))
170
+ var bobHash = Token.ownerId(bob)
171
+ var nextToken = Token.ownershipTokenMulti(bobHash)
172
+ var payment = p2pkhOutput(PrivateKey.fromRandom(), 4000)
173
+ var tokenValue = 1000
174
+ var tokenOutput = new Transaction.Output({ script: nextToken, satoshis: tokenValue })
175
+
176
+ var spend = spendOf(lock, SATS, [payment, tokenOutput])
177
+ // Claim a DIFFERENT `before` than the real first output => HASH160 mismatch.
178
+ var lyingPayment = p2pkhOutput(PrivateKey.fromRandom(), 4000)
179
+ spend.inputs[0].setScript(Token.unlockTransferMulti(alice, bobHash, spend, SATS, lock, {
180
+ before: serialize(lyingPayment),
181
+ after: Buffer.alloc(0),
182
+ tokenValue: le8(tokenValue)
183
+ }))
184
+ verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
185
+ })
186
+ })
187
+ })
@@ -0,0 +1,33 @@
1
+ 'use strict'
2
+
3
+ var Transaction = require('../../lib/transaction')
4
+
5
+ var vectorsValid = require('../data/bitcoind/tx_valid.json')
6
+ var vectorsInvalid = require('../data/bitcoind/tx_invalid.json')
7
+
8
+ describe('Transaction deserialization', function () {
9
+ describe('valid transaction test case', function () {
10
+ var index = 0
11
+ vectorsValid.forEach(function (vector) {
12
+ it('vector #' + index, function () {
13
+ if (vector.length > 1) {
14
+ var hexa = vector[1]
15
+ Transaction(hexa).serialize(true).should.equal(hexa)
16
+ index++
17
+ }
18
+ })
19
+ })
20
+ })
21
+ describe('invalid transaction test case', function () {
22
+ var index = 0
23
+ vectorsInvalid.forEach(function (vector) {
24
+ it('invalid vector #' + index, function () {
25
+ if (vector.length > 1) {
26
+ var hexa = vector[1]
27
+ Transaction(hexa).serialize(true).should.equal(hexa)
28
+ index++
29
+ }
30
+ })
31
+ })
32
+ })
33
+ })