@smartledger/bsv 5.5.0 → 5.5.2
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.
- package/.mocharc.json +5 -0
- package/CHANGELOG.md +40 -0
- package/README.md +38 -38
- package/bsv-covenant.min.js +2 -2
- package/bsv-gdaf.min.js +2 -2
- package/bsv-ltp.min.js +2 -2
- package/bsv-smartcontract.min.js +2 -2
- package/bsv.bundle.js +2 -2
- package/bsv.min.js +2 -2
- package/docs/MODULE_REFERENCE_COMPLETE.md +27 -27
- package/docs/advanced/UTXO_MANAGER_GUIDE.md +1 -1
- package/docs/getting-started/INSTALLATION.md +23 -23
- package/docs/getting-started/QUICK_START.md +7 -7
- package/docs/migration/FROM_BSV_1_5_6.md +5 -5
- package/lib/smart_contract/covenant_helpers.js +3 -3
- package/lib/smart_contract/index.js +4 -5
- package/package.json +5 -1
- package/test/address.js +629 -0
- package/test/block/block.js +239 -0
- package/test/block/blockheader.js +270 -0
- package/test/block/merkleblock.js +207 -0
- package/test/cli/smoke.js +261 -0
- package/test/credentials-test.js +329 -0
- package/test/crypto/bn.js +168 -0
- package/test/crypto/ecdsa.js +328 -0
- package/test/crypto/elliptic-fixed.js +61 -0
- package/test/crypto/hash.browser.js +121 -0
- package/test/crypto/hash.js +122 -0
- package/test/crypto/point.js +193 -0
- package/test/crypto/random.js +32 -0
- package/test/crypto/security.js +127 -0
- package/test/crypto/shamir.js +169 -0
- package/test/crypto/signature.js +318 -0
- package/test/data/bip69.json +215 -0
- package/test/data/bitcoind/base58_keys_invalid.json +152 -0
- package/test/data/bitcoind/base58_keys_valid.json +452 -0
- package/test/data/bitcoind/blocks.json +27 -0
- package/test/data/bitcoind/script_tests.json +2244 -0
- package/test/data/bitcoind/sig_canonical.json +7 -0
- package/test/data/bitcoind/sig_noncanonical.json +22 -0
- package/test/data/bitcoind/tx_invalid.json +177 -0
- package/test/data/bitcoind/tx_valid.json +224 -0
- package/test/data/blk86756-testnet.dat +0 -0
- package/test/data/blk86756-testnet.js +12 -0
- package/test/data/blk86756-testnet.json +684 -0
- package/test/data/ecdsa.json +230 -0
- package/test/data/merkleblocks.js +486 -0
- package/test/data/messages.json +22 -0
- package/test/data/sighash.json +1004 -0
- package/test/data/tx_creation.json +85 -0
- package/test/ecies/bitcore-ecies.js +170 -0
- package/test/ecies/electrum-ecies.js +206 -0
- package/test/encoding/base58.js +131 -0
- package/test/encoding/base58check.js +136 -0
- package/test/encoding/bufferreader.js +328 -0
- package/test/encoding/bufferwriter.js +160 -0
- package/test/encoding/varint.js +104 -0
- package/test/gdaf/canonicalize.js +140 -0
- package/test/hdkeys.js +365 -0
- package/test/hdprivatekey.js +330 -0
- package/test/hdpublickey.js +293 -0
- package/test/index.js +16 -0
- package/test/ltp/right.js +198 -0
- package/test/message/message.js +190 -0
- package/test/mnemonic/data/fixtures.json +300 -0
- package/test/mnemonic/mnemonic.js +245 -0
- package/test/mnemonic/mocha.opts +1 -0
- package/test/mnemonic/pbkdf2.test.js +43 -0
- package/test/networks.js +162 -0
- package/test/opcode.js +161 -0
- package/test/privatekey.js +430 -0
- package/test/publickey.js +411 -0
- package/test/script/genesis_limits.js +80 -0
- package/test/script/interpreter.js +723 -0
- package/test/script/script.js +1256 -0
- package/test/script/string_ops.js +72 -0
- package/test/smart_contract/covenants.js +247 -0
- package/test/smart_contract/dsl_debugger.js +103 -0
- package/test/smart_contract/token_generalized.js +187 -0
- package/test/transaction/deserialize.js +33 -0
- package/test/transaction/input/input.js +92 -0
- package/test/transaction/input/multisig.js +174 -0
- package/test/transaction/input/multisigscripthash.js +111 -0
- package/test/transaction/input/publickey.js +68 -0
- package/test/transaction/input/publickeyhash.js +59 -0
- package/test/transaction/output.js +185 -0
- package/test/transaction/sighash.js +71 -0
- package/test/transaction/signature.js +127 -0
- package/test/transaction/transaction.js +1299 -0
- package/test/transaction/unspentoutput.js +92 -0
- package/test/util/js.js +76 -0
- package/test/util/preconditions.js +79 -0
- 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
|
+
})
|