@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.
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/script/Spend.js +239 -42
- package/dist/cjs/src/script/Spend.js.map +1 -1
- package/dist/cjs/src/transaction/Transaction.js +5 -2
- package/dist/cjs/src/transaction/Transaction.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/script/Spend.js +236 -37
- package/dist/esm/src/script/Spend.js.map +1 -1
- package/dist/esm/src/transaction/Transaction.js +5 -2
- package/dist/esm/src/transaction/Transaction.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/script/Spend.d.ts +7 -1
- package/dist/types/src/script/Spend.d.ts.map +1 -1
- package/dist/types/src/transaction/Transaction.d.ts +3 -1
- package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/docs/transaction.md +5 -1
- package/package.json +1 -1
- package/src/script/Spend.ts +253 -42
- package/src/transaction/Transaction.ts +6 -2
- package/src/transaction/__tests/Transaction.test.ts +125 -1
|
@@ -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
|
-
|
|
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
|
})
|