@smartledger/bsv 7.6.1 → 7.7.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.
@@ -0,0 +1,222 @@
1
+ 'use strict'
2
+
3
+ /* global describe, it */
4
+
5
+ // The BSV Chronicle script surface: OP_2MUL/OP_2DIV restored, OP_VER/OP_VERIF/
6
+ // OP_VERNOTIF given meaning, and SIGHASH_CHRONICLE selecting the Original
7
+ // Transaction Digest Algorithm. All of it sits behind SCRIPT_ENABLE_CHRONICLE.
8
+ //
9
+ // The default (flag off) behaviour is pinned just as carefully as the enabled
10
+ // behaviour. Enabling an opcode by accident is a consensus change, and the
11
+ // pre-Chronicle rules are what every existing signature and script was written
12
+ // against.
13
+
14
+ require('chai').should()
15
+ var bsv = require('../..')
16
+ var Interpreter = bsv.Script.Interpreter
17
+ var Opcode = bsv.Opcode
18
+ var Script = bsv.Script
19
+ var BN = bsv.crypto.BN
20
+ var Signature = bsv.crypto.Signature
21
+
22
+ var CHRONICLE = Interpreter.SCRIPT_ENABLE_CHRONICLE
23
+
24
+ /** Evaluate `build`ed script against a tx of `version`, returning the outcome. */
25
+ function run (build, opts) {
26
+ opts = opts || {}
27
+ var tx = new bsv.Transaction()
28
+ if (opts.version != null) tx.version = opts.version
29
+ var script = new Script()
30
+ build(script)
31
+ var interp = new Interpreter()
32
+ var verified = interp.verify(
33
+ new Script(), script, tx, 0, opts.flags == null ? CHRONICLE : opts.flags, new BN(0)
34
+ )
35
+ return {
36
+ verified: verified,
37
+ errstr: interp.errstr || '',
38
+ stack: (interp.stack || []).map(function (b) { return BN.fromScriptNumBuffer(b).toString() })
39
+ }
40
+ }
41
+
42
+ describe('Chronicle script surface', function () {
43
+ describe('SCRIPT_ENABLE_CHRONICLE flag', function () {
44
+ it('is a distinct bit that does not collide with the other opcode flags', function () {
45
+ CHRONICLE.should.be.a('number')
46
+ ;[
47
+ Interpreter.SCRIPT_ENABLE_MONOLITH_OPCODES,
48
+ Interpreter.SCRIPT_ENABLE_MAGNETIC_OPCODES,
49
+ Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID,
50
+ Interpreter.SCRIPT_ENABLE_REPLAY_PROTECTION
51
+ ].forEach(function (other) {
52
+ (CHRONICLE & other).should.equal(0)
53
+ })
54
+ })
55
+
56
+ it('is off by default, so none of this changes existing evaluation', function () {
57
+ (Interpreter.DEFAULT_FLAGS & CHRONICLE).should.equal(0)
58
+ })
59
+ })
60
+
61
+ describe('OP_2MUL / OP_2DIV', function () {
62
+ it('stay DISABLED without the flag, even in an unexecuted branch', function () {
63
+ // "Disabled" is stronger than "unimplemented": a disabled opcode fails the
64
+ // script even where it is never executed. That is why the gate lives in
65
+ // isOpcodeDisabled rather than in the evaluation switch — losing this
66
+ // distinction would silently loosen consensus.
67
+ var r = run(function (s) {
68
+ s.add(Opcode.OP_0).add(Opcode.OP_IF).add(Opcode.OP_2MUL).add(Opcode.OP_ENDIF).add(Opcode.OP_1)
69
+ }, { flags: 0 })
70
+ r.verified.should.equal(false)
71
+ r.errstr.should.match(/DISABLED_OPCODE/)
72
+ })
73
+
74
+ it('are usable in an unexecuted branch once enabled', function () {
75
+ var r = run(function (s) {
76
+ s.add(Opcode.OP_0).add(Opcode.OP_IF).add(Opcode.OP_2MUL).add(Opcode.OP_ENDIF).add(Opcode.OP_1)
77
+ })
78
+ r.verified.should.equal(true)
79
+ })
80
+
81
+ it('OP_2MUL doubles, over positive, negative and zero', function () {
82
+ ;[[0, '0'], [1, '2'], [7, '14'], [-3, '-6'], [-1, '-2']].forEach(function (t) {
83
+ var r = run(function (s) { s.add(new BN(t[0]).toScriptNumBuffer()).add(Opcode.OP_2MUL) })
84
+ r.stack[r.stack.length - 1].should.equal(t[1])
85
+ })
86
+ })
87
+
88
+ it('OP_2DIV truncates toward zero, so it agrees with OP_DIV by 2', function () {
89
+ // -5 OP_2DIV is -2, not -3. A shift would give -3; bn.js `shrn` also asserts
90
+ // on negatives. This is the case where a rounding change would hide.
91
+ ;[0, 1, 5, -5, 7, -7, -1, 1000, -1000].forEach(function (v) {
92
+ var viaOp = run(function (s) { s.add(new BN(v).toScriptNumBuffer()).add(Opcode.OP_2DIV) })
93
+ var viaDiv = run(function (s) {
94
+ s.add(new BN(v).toScriptNumBuffer()).add(new BN(2).toScriptNumBuffer()).add(Opcode.OP_DIV)
95
+ })
96
+ viaOp.stack[viaOp.stack.length - 1].should.equal(viaDiv.stack[viaDiv.stack.length - 1])
97
+ })
98
+ })
99
+ })
100
+
101
+ describe('OP_VER', function () {
102
+ it('is BAD_OPCODE without the flag', function () {
103
+ var r = run(function (s) { s.add(Opcode.OP_VER) }, { flags: 0 })
104
+ r.verified.should.equal(false)
105
+ r.errstr.should.match(/BAD_OPCODE/)
106
+ })
107
+
108
+ it('pushes the executing transaction version', function () {
109
+ ;[1, 2, 10].forEach(function (v) {
110
+ var r = run(function (s) { s.add(Opcode.OP_VER) }, { version: v })
111
+ r.stack[r.stack.length - 1].should.equal(String(v))
112
+ })
113
+ })
114
+ })
115
+
116
+ describe('OP_VERIF / OP_VERNOTIF', function () {
117
+ it('are BAD_OPCODE without the flag EVEN IN AN UNEXECUTED BRANCH', function () {
118
+ // A rule Bitcoin applies to no other opcode, which is why the flag check sits
119
+ // outside the fExec guard. If it moved inside, this script would pass.
120
+ var r = run(function (s) {
121
+ s.add(Opcode.OP_0).add(Opcode.OP_IF).add(Opcode.OP_VERIF).add(Opcode.OP_ENDIF).add(Opcode.OP_1)
122
+ }, { flags: 0 })
123
+ r.verified.should.equal(false)
124
+ r.errstr.should.match(/BAD_OPCODE/)
125
+ })
126
+
127
+ it('OP_VERIF takes the branch when the top of stack equals the tx version', function () {
128
+ var taken = run(function (s) {
129
+ s.add(new BN(2).toScriptNumBuffer()).add(Opcode.OP_VERIF).add(Opcode.OP_1).add(Opcode.OP_ENDIF)
130
+ }, { version: 2 })
131
+ taken.verified.should.equal(true)
132
+
133
+ var notTaken = run(function (s) {
134
+ s.add(new BN(9).toScriptNumBuffer()).add(Opcode.OP_VERIF).add(Opcode.OP_1).add(Opcode.OP_ENDIF).add(Opcode.OP_1)
135
+ }, { version: 2 })
136
+ notTaken.verified.should.equal(true)
137
+ notTaken.stack.length.should.equal(1) // only the trailing OP_1
138
+ })
139
+
140
+ it('OP_VERNOTIF is its negation', function () {
141
+ var r = run(function (s) {
142
+ s.add(new BN(9).toScriptNumBuffer()).add(Opcode.OP_VERNOTIF).add(Opcode.OP_1).add(Opcode.OP_ENDIF)
143
+ }, { version: 2 })
144
+ r.verified.should.equal(true)
145
+ })
146
+
147
+ it('CONSUMES its operand, mirroring OP_IF', function () {
148
+ // The spec describes the comparison but not the stack effect. An IF that left
149
+ // its condition behind would unbalance every script using it, so this pops.
150
+ // Pinned so a correction shows up as a failure rather than silently.
151
+ var r = run(function (s) {
152
+ s.add(Opcode.OP_7).add(Opcode.OP_2).add(Opcode.OP_VERIF).add(Opcode.OP_ENDIF)
153
+ }, { version: 2 })
154
+ r.verified.should.equal(true)
155
+ r.stack.should.deep.equal(['7']) // the 2 was consumed, the 7 remains
156
+ })
157
+
158
+ it('fails on an empty stack rather than reading past it', function () {
159
+ var r = run(function (s) {
160
+ s.add(Opcode.OP_VERIF).add(Opcode.OP_ENDIF).add(Opcode.OP_1)
161
+ }, { version: 2 })
162
+ r.verified.should.equal(false)
163
+ r.errstr.should.match(/UNBALANCED_CONDITIONAL/)
164
+ })
165
+ })
166
+
167
+ describe('SIGHASH_CHRONICLE selects the original digest algorithm', function () {
168
+ function fixture () {
169
+ var key = bsv.PrivateKey.fromBuffer(Buffer.alloc(32, 5))
170
+ var sub = Script.buildPublicKeyHashOut(key.toAddress())
171
+ var tx = new bsv.Transaction()
172
+ tx.uncheckedAddInput(new bsv.Transaction.Input({
173
+ prevTxId: 'aa'.repeat(32), outputIndex: 0, script: Script.empty()
174
+ }))
175
+ tx.addOutput(new bsv.Transaction.Output({ script: sub, satoshis: 1000 }))
176
+ return { tx: tx, sub: sub, amt: new BN(2000) }
177
+ }
178
+
179
+ it('has the constant at 0x20', function () {
180
+ Signature.SIGHASH_CHRONICLE.should.equal(0x20)
181
+ })
182
+
183
+ it('routes to OTDA when the bit is set AND the flag is enabled', function () {
184
+ var f = fixture()
185
+ var type = Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID | Signature.SIGHASH_CHRONICLE
186
+ var enabled = bsv.Transaction.sighash.sighash(
187
+ f.tx, type, 0, f.sub, f.amt,
188
+ Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID | CHRONICLE
189
+ )
190
+ var forcedOtda = bsv.Transaction.sighash.sighash(f.tx, type, 0, f.sub, f.amt, 0)
191
+ enabled.toString('hex').should.equal(forcedOtda.toString('hex'))
192
+ })
193
+
194
+ it('OVERRIDES FORKID — otherwise the bit could never select OTDA in practice', function () {
195
+ // FORKID is set on essentially every BSV signature written since 2018, so a
196
+ // CHRONICLE bit that only applied when FORKID was absent would mean nothing.
197
+ var f = fixture()
198
+ var type = Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID | Signature.SIGHASH_CHRONICLE
199
+ var chronicle = bsv.Transaction.sighash.sighash(
200
+ f.tx, type, 0, f.sub, f.amt,
201
+ Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID | CHRONICLE
202
+ )
203
+ var bip143 = bsv.Transaction.sighash.sighash(
204
+ f.tx, type, 0, f.sub, f.amt, Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID
205
+ )
206
+ chronicle.toString('hex').should.not.equal(bip143.toString('hex'))
207
+ })
208
+
209
+ it('is IGNORED without the flag, so existing BIP-143 signatures are unaffected', function () {
210
+ // Before the upgrade the 0x20 bit means nothing, so signatures already exist
211
+ // whose type byte happens to set it. Honouring it unconditionally would
212
+ // reinterpret those as OTDA — a silent validity change on historic data.
213
+ var f = fixture()
214
+ var withBit = Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID | Signature.SIGHASH_CHRONICLE
215
+ var viaForkId = bsv.Transaction.sighash.sighash(
216
+ f.tx, withBit, 0, f.sub, f.amt, Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID
217
+ )
218
+ var forcedOtda = bsv.Transaction.sighash.sighash(f.tx, withBit, 0, f.sub, f.amt, 0)
219
+ viaForkId.toString('hex').should.not.equal(forcedOtda.toString('hex'))
220
+ })
221
+ })
222
+ })
package/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  'use strict'
2
2
  // GENERATED by scripts/sync-version.js on `npm version` — do not edit.
3
- module.exports = '7.6.1'
3
+ module.exports = '7.7.0'