@bsv/sdk 1.4.20 → 1.4.22

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.
@@ -761,6 +761,8 @@ export default class Transaction {
761
761
  * Verifies the legitimacy of the Bitcoin transaction according to the rules of SPV by ensuring all the input transactions link back to valid block headers, the chain of spends for all inputs are valid, and the sum of inputs is not less than the sum of outputs.
762
762
  *
763
763
  * @param chainTracker - An instance of ChainTracker, a Bitcoin block header tracker. If the value is set to 'scripts only', headers will not be verified. If not provided then the default chain tracker will be used.
764
+ * @param feeModel - An instance of FeeModel, a fee model to use for fee calculation. If not provided then the default fee model will be used.
765
+ * @param memoryLimit - The maximum memory in bytes usage allowed for script evaluation. If not provided then the default memory limit will be used.
764
766
  *
765
767
  * @returns Whether the transaction is valid according to the rules of SPV.
766
768
  *
@@ -768,7 +770,8 @@ export default class Transaction {
768
770
  */
769
771
  async verify (
770
772
  chainTracker: ChainTracker | 'scripts only' = defaultChainTracker(),
771
- feeModel?: FeeModel
773
+ feeModel?: FeeModel,
774
+ memoryLimit?: number
772
775
  ): Promise<boolean> {
773
776
  const verifiedTxids = new Set<string>()
774
777
  const txQueue: Transaction[] = [this]
@@ -856,7 +859,8 @@ export default class Transaction {
856
859
  inputSequence: input.sequence ?? 0,
857
860
  inputIndex: i,
858
861
  outputs: tx.outputs,
859
- lockTime: tx.lockTime
862
+ lockTime: tx.lockTime,
863
+ memoryLimit
860
864
  })
861
865
  const spendValid = spend.validate()
862
866
 
@@ -13,6 +13,7 @@ import P2PKH from '../../script/templates/P2PKH'
13
13
  import fromUtxo from '../../compat/Utxo'
14
14
  import MerklePath from '../../transaction/MerklePath'
15
15
  import { BEEF_V1 } from '../../transaction/Beef'
16
+ import SatoshisPerKilobyte from '../../transaction/fee-models/SatoshisPerKilobyte'
16
17
 
17
18
  import sighashVectors from '../../primitives/__tests/sighash.vectors'
18
19
  import invalidTransactions from './tx.invalid.vectors'
@@ -1138,6 +1139,7 @@ describe('Transaction', () => {
1138
1139
  describe('addP2PKHOutput', () => {
1139
1140
  it('should create an output on the current transaction using an address hash or string', async () => {
1140
1141
  const privateKey = PrivateKey.fromRandom()
1142
+ const pubKeyHash = privateKey.toPublicKey().toHash()
1141
1143
  const lockingScript = new P2PKH().lock(privateKey.toAddress())
1142
1144
  const tx = new Transaction()
1143
1145
  tx.addInput({
@@ -1146,11 +1148,16 @@ describe('Transaction', () => {
1146
1148
  unlockingScriptTemplate: new P2PKH().unlock(privateKey)
1147
1149
  })
1148
1150
  tx.addP2PKHOutput(privateKey.toAddress(), 10000)
1149
- expect(tx.outputs.length).toEqual(1)
1151
+ tx.addP2PKHOutput(pubKeyHash, 10000)
1152
+ expect(tx.outputs.length).toEqual(2)
1150
1153
  expect(tx.outputs[0].satoshis).toEqual(10000)
1154
+ expect(tx.outputs[1].satoshis).toEqual(10000)
1151
1155
  expect(
1152
1156
  tx.outputs[0].lockingScript.toHex() === lockingScript.toHex()
1153
1157
  ).toBeTruthy()
1158
+ expect(
1159
+ tx.outputs[0].lockingScript.toHex() === tx.outputs[1].lockingScript.toHex()
1160
+ ).toBeTruthy()
1154
1161
  })
1155
1162
  })
1156
1163
  })
@@ -1293,4 +1300,121 @@ describe('Transaction', () => {
1293
1300
  })
1294
1301
  })
1295
1302
  })
1303
+
1304
+ describe('preventResourceExhaustion', () => {
1305
+ it('should run script evaluation but error out as soon as the memory usage exceeds the limit', async () => {
1306
+ const sourceTransaction = new Transaction()
1307
+ sourceTransaction.addInput({
1308
+ sourceTXID: '00'.repeat(32),
1309
+ sourceOutputIndex: 0,
1310
+ unlockingScript: Script.fromASM('OP_TRUE'),
1311
+ })
1312
+ sourceTransaction.addOutput({
1313
+ satoshis: 2,
1314
+ lockingScript: Script.fromASM('OP_2 OP_MUL ' + 'OP_DUP OP_MUL '.repeat(16) + 'OP_DROP'),
1315
+ })
1316
+ await sourceTransaction.sign()
1317
+
1318
+ sourceTransaction.merklePath = new MerklePath(1000, [
1319
+ [
1320
+ { offset: 0, hash: sourceTransaction.id('hex'), txid: true },
1321
+ { offset: 1, duplicate: true }
1322
+ ]
1323
+ ])
1324
+
1325
+ const tx = new Transaction()
1326
+ tx.addInput({
1327
+ sourceTransaction,
1328
+ sourceOutputIndex: 0,
1329
+ unlockingScript: Script.fromASM('OP_TRUE ' + 'deadbeef'.repeat(2))
1330
+ })
1331
+ tx.addOutput({
1332
+ satoshis: 1,
1333
+ lockingScript: Script.fromASM('OP_NOP'),
1334
+ })
1335
+ await tx.fee()
1336
+ await tx.sign()
1337
+
1338
+ // default should be 100KB
1339
+ await expect(tx.verify('scripts only', new SatoshisPerKilobyte(1))).rejects.toThrow('Stack memory usage has exceeded 100000 bytes')
1340
+ })
1341
+ })
1342
+
1343
+ describe('preventResourceExhaustionP2PKH', () => {
1344
+ it('should run script evaluation with only 144 bytes of memory allocation and still be valid for a simple P2PKH', async () => {
1345
+ const key = PrivateKey.fromRandom()
1346
+ const sourceTransaction = new Transaction()
1347
+ sourceTransaction.addInput({
1348
+ sourceTXID: '00'.repeat(32),
1349
+ sourceOutputIndex: 0,
1350
+ unlockingScript: Script.fromASM('OP_TRUE'),
1351
+ })
1352
+ sourceTransaction.addOutput({
1353
+ satoshis: 2,
1354
+ lockingScript: new P2PKH().lock(key.toAddress()),
1355
+ })
1356
+ await sourceTransaction.sign()
1357
+
1358
+ sourceTransaction.merklePath = new MerklePath(1000, [
1359
+ [
1360
+ { offset: 0, hash: sourceTransaction.id('hex'), txid: true },
1361
+ { offset: 1, duplicate: true }
1362
+ ]
1363
+ ])
1364
+
1365
+ const tx = new Transaction()
1366
+ tx.addInput({
1367
+ sourceTransaction,
1368
+ sourceOutputIndex: 0,
1369
+ unlockingScriptTemplate: new P2PKH().unlock(key, 'none', true)
1370
+ })
1371
+ tx.addOutput({
1372
+ satoshis: 1,
1373
+ lockingScript: Script.fromASM('OP_NOP'),
1374
+ })
1375
+ await tx.fee()
1376
+ await tx.sign()
1377
+
1378
+ // P2PKH takes less than 150 bytes apparently
1379
+ await expect(tx.verify('scripts only', new SatoshisPerKilobyte(1), 150)).resolves.toBe(true)
1380
+ })
1381
+ })
1382
+
1383
+ describe('preventResourceExhaustionSmall', () => {
1384
+ it('should run script evaluation and pass so long as we stay within the limit', async () => {
1385
+ const sourceTransaction = new Transaction()
1386
+ sourceTransaction.addInput({
1387
+ sourceTXID: '00'.repeat(32),
1388
+ sourceOutputIndex: 0,
1389
+ unlockingScript: Script.fromASM('OP_TRUE'),
1390
+ })
1391
+ sourceTransaction.addOutput({
1392
+ satoshis: 2,
1393
+ lockingScript: Script.fromASM('OP_2 OP_MUL OP_DUP OP_MUL OP_DUP OP_MUL OP_DROP'),
1394
+ })
1395
+ await sourceTransaction.sign()
1396
+
1397
+ sourceTransaction.merklePath = new MerklePath(1000, [
1398
+ [
1399
+ { offset: 0, hash: sourceTransaction.id('hex'), txid: true },
1400
+ { offset: 1, duplicate: true }
1401
+ ]
1402
+ ])
1403
+
1404
+ const tx = new Transaction()
1405
+ tx.addInput({
1406
+ sourceTransaction,
1407
+ sourceOutputIndex: 0,
1408
+ unlockingScript: Script.fromASM('OP_TRUE ' + 'deadbeef'.repeat(2))
1409
+ })
1410
+ tx.addOutput({
1411
+ satoshis: 1,
1412
+ lockingScript: Script.fromASM('OP_NOP'),
1413
+ })
1414
+ await tx.fee()
1415
+ await tx.sign()
1416
+
1417
+ await expect(tx.verify('scripts only', new SatoshisPerKilobyte(1), 35)).resolves.toBe(true)
1418
+ })
1419
+ })
1296
1420
  })