@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.
- package/CHANGELOG.md +67 -0
- package/README.md +38 -38
- package/bsv-gdaf.min.js +24 -24
- package/bsv-ltp.min.js +24 -24
- package/bsv-smartcontract.min.js +11 -11
- package/bsv.bundle.js +24 -24
- package/bsv.d.ts +6 -0
- package/bsv.min.js +24 -24
- 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/crypto/signature.js +2 -0
- package/lib/script/interpreter.js +74 -2
- package/lib/transaction/sighash.js +17 -1
- package/package.json +2 -2
- package/test/script/chronicle.js +222 -0
- package/version.js +1 -1
|
@@ -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