@smartledger/bsv 9.10.0 → 9.11.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.
@@ -1,8 +1,9 @@
1
1
  'use strict'
2
2
 
3
+ var assert = require('assert')
4
+
3
5
  var Address = require('../address')
4
6
  var BufferReader = require('../encoding/bufferreader')
5
- var BufferWriter = require('../encoding/bufferwriter')
6
7
  var Hash = require('../crypto/hash')
7
8
  var Opcode = require('../opcode')
8
9
  var PublicKey = require('../publickey')
@@ -107,29 +108,59 @@ Script.fromBuffer = function (buffer) {
107
108
  }
108
109
 
109
110
  Script.prototype.toBuffer = function () {
110
- var bw = new BufferWriter()
111
+ // Sized first and written into one allocation. A locking script can have
112
+ // hundreds of thousands of chunks, and this runs for every signature check
113
+ // over it; a writer that allocates per chunk made that the dominant cost.
114
+ // Writes go through Buffer's checked writers, so a malformed chunk throws
115
+ // exactly where the per-chunk writer threw.
116
+ var chunks = this.chunks
117
+ var size = 0
118
+ var i, chunk, opcodenum
119
+ for (i = 0; i < chunks.length; i++) {
120
+ chunk = chunks[i]
121
+ opcodenum = chunk.opcodenum
122
+ size += 1
123
+ if (chunk.buf) {
124
+ if (opcodenum < Opcode.OP_PUSHDATA1) {
125
+ size += bufLength(chunk.buf)
126
+ } else if (opcodenum === Opcode.OP_PUSHDATA1) {
127
+ size += 1 + bufLength(chunk.buf)
128
+ } else if (opcodenum === Opcode.OP_PUSHDATA2) {
129
+ size += 2 + bufLength(chunk.buf)
130
+ } else if (opcodenum === Opcode.OP_PUSHDATA4) {
131
+ size += 4 + bufLength(chunk.buf)
132
+ }
133
+ }
134
+ }
111
135
 
112
- for (var i = 0; i < this.chunks.length; i++) {
113
- var chunk = this.chunks[i]
114
- var opcodenum = chunk.opcodenum
115
- bw.writeUInt8(chunk.opcodenum)
136
+ var out = Buffer.alloc(size)
137
+ var o = 0
138
+ for (i = 0; i < chunks.length; i++) {
139
+ chunk = chunks[i]
140
+ opcodenum = chunk.opcodenum
141
+ o = out.writeUInt8(chunk.opcodenum, o)
116
142
  if (chunk.buf) {
117
143
  if (opcodenum < Opcode.OP_PUSHDATA1) {
118
- bw.write(chunk.buf)
144
+ o += chunk.buf.copy(out, o)
119
145
  } else if (opcodenum === Opcode.OP_PUSHDATA1) {
120
- bw.writeUInt8(chunk.len)
121
- bw.write(chunk.buf)
146
+ o = out.writeUInt8(chunk.len, o)
147
+ o += chunk.buf.copy(out, o)
122
148
  } else if (opcodenum === Opcode.OP_PUSHDATA2) {
123
- bw.writeUInt16LE(chunk.len)
124
- bw.write(chunk.buf)
149
+ o = out.writeUInt16LE(chunk.len, o)
150
+ o += chunk.buf.copy(out, o)
125
151
  } else if (opcodenum === Opcode.OP_PUSHDATA4) {
126
- bw.writeUInt32LE(chunk.len)
127
- bw.write(chunk.buf)
152
+ o = out.writeUInt32LE(chunk.len, o)
153
+ o += chunk.buf.copy(out, o)
128
154
  }
129
155
  }
130
156
  }
157
+ return out
158
+ }
131
159
 
132
- return bw.concat()
160
+ // A chunk's data must be a Buffer; BufferWriter#write asserted as much.
161
+ function bufLength (buf) {
162
+ if (!Buffer.isBuffer(buf)) throw new assert.AssertionError({ message: 'chunk data must be a Buffer', actual: buf, expected: 'Buffer', operator: 'isBuffer' })
163
+ return buf.length
133
164
  }
134
165
 
135
166
  Script.fromASM = function (str) {
@@ -1081,20 +1112,40 @@ Script.prototype.toAddress = function (network) {
1081
1112
  */
1082
1113
  Script.prototype.findAndDelete = function (script) {
1083
1114
  var buf = script.toBuffer()
1084
- var hex = buf.toString('hex')
1115
+ // A chunk serializes to bytes starting with its opcode, so only chunks whose
1116
+ // opcode is the first byte of `buf` can match. Every other chunk is still
1117
+ // serialized when its encoding would throw, so malformed input fails as it
1118
+ // always did.
1119
+ var first = buf.length ? buf[0] : -1
1085
1120
  for (var i = 0; i < this.chunks.length; i++) {
1086
- var script2 = Script({
1087
- chunks: [this.chunks[i]]
1088
- })
1089
- var buf2 = script2.toBuffer()
1090
- var hex2 = buf2.toString('hex')
1091
- if (hex === hex2) {
1121
+ var chunk = this.chunks[i]
1122
+ if (chunk.opcodenum !== first && serializesCleanly(chunk)) continue
1123
+ var buf2 = Script({
1124
+ chunks: [chunk]
1125
+ }).toBuffer()
1126
+ if (buf.equals(buf2)) {
1127
+ // Look at the chunk that slid into this position too: CScript::FindAndDelete
1128
+ // removes consecutive occurrences.
1092
1129
  this.chunks.splice(i, 1)
1130
+ i--
1093
1131
  }
1094
1132
  }
1095
1133
  return this
1096
1134
  }
1097
1135
 
1136
+ function serializesCleanly (chunk) {
1137
+ var op = chunk.opcodenum
1138
+ if (typeof op !== 'number' || !Number.isInteger(op) || op < 0 || op > 255) return false
1139
+ if (!chunk.buf) return true
1140
+ if (op > Opcode.OP_PUSHDATA4) return true
1141
+ if (!Buffer.isBuffer(chunk.buf)) return false
1142
+ var len = chunk.len
1143
+ if (op === Opcode.OP_PUSHDATA1) return Number.isInteger(len) && len >= 0 && len <= 0xff
1144
+ if (op === Opcode.OP_PUSHDATA2) return Number.isInteger(len) && len >= 0 && len <= 0xffff
1145
+ if (op === Opcode.OP_PUSHDATA4) return Number.isInteger(len) && len >= 0 && len <= 0xffffffff
1146
+ return true
1147
+ }
1148
+
1098
1149
  /**
1099
1150
  * Comes from bitcoind's script interpreter CheckMinimalPush function
1100
1151
  * @returns {boolean} if the chunk {i} is the smallest way to push that particular data.
@@ -102,8 +102,9 @@ var sighashPreimageForForkId = function (transaction, sighashType, inputNumber,
102
102
  writer.writeUInt32LE(input.outputIndex)
103
103
 
104
104
  // scriptCode of the input (serialized as scripts inside CTxOuts)
105
- writer.writeVarintNum(subscript.toBuffer().length)
106
- writer.write(subscript.toBuffer())
105
+ var scriptCode = subscript.toBuffer()
106
+ writer.writeVarintNum(scriptCode.length)
107
+ writer.write(scriptCode)
107
108
 
108
109
  // value of the output spent by this input (8-byte little endian)
109
110
  writer.writeUInt64LEBN(satoshisBN)
@@ -148,8 +149,13 @@ var sighashPreimage = function sighashPreimage (transaction, sighashType, inputN
148
149
  // Copy transaction
149
150
  var txcopy = Transaction.shallowCopy(transaction)
150
151
 
151
- // Copy script
152
- subscript = new Script(subscript)
152
+ // The BIP-143 path below only reads the script. The original algorithm
153
+ // modifies it, so it takes its own copy there; copying a large script up
154
+ // front cost more than the whole digest. Anything that is not a Script yet
155
+ // is converted, as the copy did.
156
+ if (!(subscript instanceof Script)) {
157
+ subscript = new Script(subscript)
158
+ }
153
159
 
154
160
  if (flags & Interpreter.SCRIPT_ENABLE_REPLAY_PROTECTION) {
155
161
  // Legacy chain's value for fork id must be of the form 0xffxxxx.
@@ -192,7 +198,8 @@ var sighashPreimage = function sighashPreimage (transaction, sighashType, inputN
192
198
  return sighashPreimageForForkId(txcopy, sighashType, inputNumber, subscript, satoshisBN)
193
199
  }
194
200
 
195
- // For no ForkId sighash, separators need to be removed.
201
+ // For no ForkId sighash, separators need to be removed — from a copy.
202
+ subscript = new Script(subscript)
196
203
  subscript.removeCodeseparators()
197
204
 
198
205
  var i
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartledger/bsv",
3
- "version": "9.10.0",
3
+ "version": "9.11.0",
4
4
  "description": "Bitcoin SV library with an interpreter-verified script engine: OP_PUSH_TX covenants, BIP-143 preimage tooling, and consensus flags that match what miners actually enforce. Also ships DID:web / W3C VC-JWT credentials and the Legal Token Protocol.",
5
5
  "author": "SmartLedger Technology <hello@smartledger.technology> (https://smartledger.technology)",
6
6
  "homepage": "https://github.com/codenlighten/smartledger-bsv#readme",
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 = '9.10.0'
3
+ module.exports = '9.11.0'