@smartledger/bsv 7.0.2 → 7.2.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.
@@ -68,4 +68,158 @@ describe('Ordinals inscriptions', function () {
68
68
  Ord.parseInscription(outs[0].script).contentText.should.equal('a')
69
69
  Ord.parseInscription(outs[1].script).contentText.should.equal('b')
70
70
  })
71
+
72
+ // The 1Sat spec allows the locking script to be prepended OR appended to the
73
+ // envelope. The parser only ever looked before it, so an appended lock was dropped
74
+ // and an owned ordinal was reported as carrying no locking script at all.
75
+ describe('recovers the lock wherever the spec allows it to sit', function () {
76
+ var p2pkh = bsv.Script.buildPublicKeyHashOut(address)
77
+
78
+ function concat () {
79
+ var out = new bsv.Script()
80
+ Array.prototype.slice.call(arguments).forEach(function (part) {
81
+ part.chunks.forEach(function (c) { out.chunks.push(c) })
82
+ })
83
+ return out
84
+ }
85
+
86
+ function envelope () {
87
+ return Ord.buildInscription({
88
+ lock: new bsv.Script(), allowEmptyLock: true, contentType: 'text/plain', content: 'hello'
89
+ })
90
+ }
91
+
92
+ it('recovers a lock that precedes the envelope', function () {
93
+ var parsed = Ord.parseInscription(concat(p2pkh, envelope()))
94
+ parsed.lock.toHex().should.equal(p2pkh.toHex())
95
+ parsed.contentText.should.equal('hello')
96
+ })
97
+
98
+ it('recovers a lock that follows the envelope', function () {
99
+ var parsed = Ord.parseInscription(concat(envelope(), p2pkh))
100
+ parsed.lock.toHex().should.equal(p2pkh.toHex())
101
+ parsed.contentText.should.equal('hello')
102
+ })
103
+
104
+ it('keeps a separating OP_CODESEPARATOR, which really does run', function () {
105
+ var sep = concat(p2pkh, new bsv.Script().add(bsv.Opcode.OP_CODESEPARATOR), envelope())
106
+ var parsed = Ord.parseInscription(sep)
107
+ parsed.lock.chunks.length.should.equal(p2pkh.chunks.length + 1)
108
+ parsed.contentText.should.equal('hello')
109
+ })
110
+
111
+ it('still reports no lock when there genuinely is none', function () {
112
+ Ord.parseInscription(envelope()).lock.chunks.length.should.equal(0)
113
+ })
114
+ })
115
+
116
+ // Every case below used to succeed and emit a well-formed script that inscribed
117
+ // something other than what the caller asked for. An inscription is permanent, so
118
+ // each one now fails loudly at build time instead of on-chain.
119
+ describe('rejects arguments that would silently inscribe the wrong thing', function () {
120
+ it('throws when content is omitted rather than inscribing an empty payload', function () {
121
+ (function () {
122
+ Ord.buildInscription({ address: address, contentType: 'text/plain' })
123
+ }).should.throw(/requires `content`/)
124
+ })
125
+
126
+ it('names the field the caller actually passed', function () {
127
+ // The real-world report: a wallet whose own builder calls the field `data`.
128
+ (function () {
129
+ Ord.buildInscription({ address: address, contentType: 'text/plain', data: 'hello world' })
130
+ }).should.throw(/received `data`, which is not read/)
131
+ })
132
+
133
+ it('still allows an explicitly empty payload', function () {
134
+ var s = Ord.buildInscription({ address: address, contentType: 'text/plain', content: '' })
135
+ Ord.parseInscription(s).content.length.should.equal(0)
136
+ })
137
+
138
+ it('throws rather than stringifying an object into the payload', function () {
139
+ // String({}) is '[object Object]' — permanent, and never intended.
140
+ (function () {
141
+ Ord.buildInscription({ address: address, contentType: 'text/plain', content: { a: 1 } })
142
+ }).should.throw(/content must be a string or Buffer, got an object/)
143
+ })
144
+
145
+ it('throws on any non-string, non-Buffer content', function () {
146
+ [42, true, null, ['a'], undefined].forEach(function (v) {
147
+ (function () {
148
+ Ord.buildInscription({ address: address, contentType: 'text/plain', content: v })
149
+ }).should.throw(Error)
150
+ })
151
+ })
152
+
153
+ it('requires a contentType for Buffer content instead of labelling it text/plain', function () {
154
+ var png = Buffer.from([0x89, 0x50, 0x4e, 0x47])
155
+ ;(function () {
156
+ Ord.buildInscription({ address: address, content: png })
157
+ }).should.throw(/contentType is required when content is a Buffer/)
158
+ // Declared explicitly, it builds.
159
+ var s = Ord.buildInscription({ address: address, contentType: 'image/png', content: png })
160
+ Ord.parseInscription(s).contentType.should.equal('image/png')
161
+ })
162
+
163
+ it('keeps the text/plain default for string content', function () {
164
+ var s = Ord.buildInscription({ address: address, content: 'hi' })
165
+ Ord.parseInscription(s).contentType.should.equal('text/plain')
166
+ })
167
+
168
+ it('throws on an empty or non-string contentType', function () {
169
+ (function () {
170
+ Ord.buildInscription({ address: address, contentType: '', content: 'x' })
171
+ }).should.throw(/contentType must not be empty/)
172
+ ;(function () {
173
+ Ord.buildInscription({ address: address, contentType: {}, content: 'x' })
174
+ }).should.throw(/contentType must be a string or Buffer/)
175
+ })
176
+
177
+ it('throws on an empty base lock, which would be anyone-can-spend', function () {
178
+ // With no base lock the script is just the inert envelope: OP_FALSE OP_IF skips
179
+ // to OP_ENDIF, so whatever the spender pushed is the final stack and any spender
180
+ // succeeds. Proven below rather than asserted.
181
+ (function () {
182
+ Ord.buildInscription({ lock: Buffer.alloc(0), contentType: 'text/plain', content: 'x' })
183
+ }).should.throw(/spendable by anyone/)
184
+ })
185
+
186
+ it('proves the empty-lock script really is anyone-can-spend', function () {
187
+ var envelope = Ord.buildInscription({
188
+ lock: new bsv.Script(), allowEmptyLock: true, contentType: 'text/plain', content: 'x'
189
+ })
190
+ // An unlocking script from a key unrelated to `address` satisfies it.
191
+ var unlock = new bsv.Script().add(bsv.Opcode.OP_1)
192
+ var interp = new bsv.Script.Interpreter()
193
+ var ok = interp.verify(unlock, envelope, new bsv.Transaction(), 0, 0)
194
+ ok.should.equal(true)
195
+ })
196
+
197
+ it('permits an empty lock only when the caller opts in', function () {
198
+ var s = Ord.buildInscription({
199
+ lock: new bsv.Script(), allowEmptyLock: true, contentType: 'text/plain', content: 'x'
200
+ })
201
+ Ord.isInscription(s).should.equal(true)
202
+ })
203
+
204
+ it('throws when both lock and address are given instead of ignoring one', function () {
205
+ var lock = bsv.Script.buildPublicKeyHashOut(address)
206
+ var other = bsv.PrivateKey.fromRandom().toAddress()
207
+ ;(function () {
208
+ Ord.buildInscription({ lock: lock, address: other, contentType: 'text/plain', content: 'x' })
209
+ }).should.throw(/not both/)
210
+ })
211
+
212
+ it('rejects a satoshi amount that carries no ordinal', function () {
213
+ [0, '1', 1.5, -1].forEach(function (v) {
214
+ (function () {
215
+ Ord.createInscriptionOutput({
216
+ address: address, contentType: 'text/plain', content: 'x', satoshis: v
217
+ })
218
+ }).should.throw(Error)
219
+ })
220
+ Ord.createInscriptionOutput({
221
+ address: address, contentType: 'text/plain', content: 'x'
222
+ }).satoshis.should.equal(1)
223
+ })
224
+ })
71
225
  })
@@ -1237,8 +1237,10 @@ describe('Script', function () {
1237
1237
  Script(s2).getSignatureOperationsCount(false).should.equal(20)
1238
1238
  })
1239
1239
  it('should handle P2SH-multisig-in scripts from utility', function () {
1240
- // create a well-formed signature, does not need to match pubkeys
1241
- var signature = bsv.crypto.Signature.fromString('30060201FF0201FF')
1240
+ // create a well-formed signature, does not need to match pubkeys.
1241
+ // r and s are 0x00ff: the pad is required, as a bare 0xff would have its
1242
+ // high bit set and so would not be a canonical DER integer.
1243
+ var signature = bsv.crypto.Signature.fromString('3008020200FF020200FF')
1242
1244
  var signatures = [ signature.toBuffer() ]
1243
1245
  var p2sh = Script.buildP2SHMultisigIn(pubKeyHexes, 1, signatures, {})
1244
1246
  p2sh.getSignatureOperationsCount(true).should.equal(0)
@@ -0,0 +1,72 @@
1
+ 'use strict'
2
+
3
+ /* global describe, it */
4
+
5
+ require('chai').should()
6
+ var expect = require('chai').expect
7
+
8
+ var Id = require('../../lib/util/id')
9
+
10
+ var UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
11
+
12
+ describe('util/id', function () {
13
+ describe('#uuid4', function () {
14
+ it('produces a well-formed RFC 4122 v4 uuid', function () {
15
+ Id.uuid4().should.match(UUID_V4)
16
+ })
17
+
18
+ it('sets the version and variant bits on every draw', function () {
19
+ for (var i = 0; i < 200; i++) {
20
+ Id.uuid4().should.match(UUID_V4)
21
+ }
22
+ })
23
+
24
+ it('does not repeat', function () {
25
+ var seen = {}
26
+ for (var i = 0; i < 2000; i++) {
27
+ var id = Id.uuid4()
28
+ expect(seen[id], 'duplicate uuid: ' + id).to.equal(undefined)
29
+ seen[id] = true
30
+ }
31
+ })
32
+ })
33
+
34
+ describe('#randomHex', function () {
35
+ it('returns the requested number of hex characters', function () {
36
+ Id.randomHex(16).should.match(/^[0-9a-f]{16}$/)
37
+ Id.randomHex(12).should.match(/^[0-9a-f]{12}$/)
38
+ Id.randomHex(1).should.match(/^[0-9a-f]$/)
39
+ })
40
+
41
+ it('does not repeat', function () {
42
+ var seen = {}
43
+ for (var i = 0; i < 2000; i++) {
44
+ var id = Id.randomHex(16)
45
+ expect(seen[id], 'duplicate id: ' + id).to.equal(undefined)
46
+ seen[id] = true
47
+ }
48
+ })
49
+ })
50
+
51
+ describe('entropy source', function () {
52
+ // Identifiers must come from the CSPRNG. Freezing the engine PRNG must not
53
+ // affect them; if it does, they are predictable from observed output.
54
+ var withFrozenMathRandom = function (fn) {
55
+ var original = Math.random
56
+ Math.random = function () { return 0.5 }
57
+ try { return fn() } finally { Math.random = original }
58
+ }
59
+
60
+ it('uuid4 is unaffected by the engine PRNG', function () {
61
+ withFrozenMathRandom(function () {
62
+ Id.uuid4().should.not.equal(Id.uuid4())
63
+ })
64
+ })
65
+
66
+ it('randomHex is unaffected by the engine PRNG', function () {
67
+ withFrozenMathRandom(function () {
68
+ Id.randomHex(16).should.not.equal(Id.randomHex(16))
69
+ })
70
+ })
71
+ })
72
+ })
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.0.2'
3
+ module.exports = '7.2.0'