@bsv/sdk 1.2.15 → 1.2.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.2.15",
3
+ "version": "1.2.17",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -188,7 +188,7 @@
188
188
  "build:umd": "webpack --config webpack.config.js",
189
189
  "dev": "tsc -b -w",
190
190
  "prepublish": "npm run build",
191
- "doc": "ts2md --inputFilename=src/script/index.ts --outputFilename=docs/script.md --filenameSubString=script --firstHeadingLevel=1 && ts2md --inputFilename=src/primitives/index.ts --outputFilename=docs/primitives.md --filenameSubString=primitives --firstHeadingLevel=1 && ts2md --inputFilename=src/transaction/index.ts --outputFilename=docs/transaction.md --filenameSubString=transaction --firstHeadingLevel=1 && ts2md --inputFilename=src/messages/index.ts --outputFilename=docs/messages.md --filenameSubString=messages --firstHeadingLevel=1 && ts2md --inputFilename=src/compat/index.ts --outputFilename=docs/compat.md --filenameSubString=compat --firstHeadingLevel=1 && ts2md --inputFilename=src/wallet/index.ts --outputFilename=docs/wallet.md --filenameSubString=wallet --firstHeadingLevel=1 && ts2md --inputFilename=src/totp/index.ts --outputFilename=docs/totp.md --filenameSubString=totp --firstHeadingLevel=1 && ts2md --inputFilename=src/overlay-tools/index.ts --outputFilename=docs/overlay-tools.md --filenameSubString=overlay-tools --firstHeadingLevel=1"
191
+ "doc": "ts2md --inputFilename=src/script/index.ts --outputFilename=docs/script.md --filenameSubString=script --firstHeadingLevel=1 && ts2md --inputFilename=src/primitives/index.ts --outputFilename=docs/primitives.md --filenameSubString=primitives --firstHeadingLevel=1 && ts2md --inputFilename=src/transaction/index.ts --outputFilename=docs/transaction.md --filenameSubString=transaction --firstHeadingLevel=1 && ts2md --inputFilename=src/messages/index.ts --outputFilename=docs/messages.md --filenameSubString=messages --firstHeadingLevel=1 && ts2md --inputFilename=src/compat/index.ts --outputFilename=docs/compat.md --filenameSubString=compat --firstHeadingLevel=1 && ts2md --inputFilename=src/wallet/index.ts --outputFilename=docs/wallet.md --filenameSubString=wallet --firstHeadingLevel=1 && ts2md --inputFilename=src/wallet/substrates/index.ts --outputFilename=docs/wallet-substrates.md --filenameSubString=wallet/substrates --firstHeadingLevel=1 && ts2md --inputFilename=src/totp/index.ts --outputFilename=docs/totp.md --filenameSubString=totp --firstHeadingLevel=1 && ts2md --inputFilename=src/overlay-tools/index.ts --outputFilename=docs/overlay-tools.md --filenameSubString=overlay-tools --firstHeadingLevel=1"
192
192
  },
193
193
  "repository": {
194
194
  "type": "git",
@@ -3,6 +3,7 @@ import Transaction from './Transaction.js'
3
3
  import ChainTracker from './ChainTracker.js'
4
4
  import BeefTx from './BeefTx.js'
5
5
  import { Reader, Writer, toHex, toArray } from '../primitives/utils.js'
6
+ import { hash256 } from '../primitives/Hash.js'
6
7
 
7
8
  export const BEEF_MAGIC = 4022206465 // 0100BEEF in LE order
8
9
  export const BEEF_MAGIC_V2 = 4022206466 // 0200BEEF in LE order
@@ -308,9 +309,12 @@ export class Beef {
308
309
 
309
310
  mergeBeefTx (btx: BeefTx): BeefTx {
310
311
  let beefTx = this.findTxid(btx.txid)
311
- if (!beefTx && btx.isTxidOnly) { beefTx = this.mergeTxidOnly(btx.txid) } else if (!beefTx || beefTx.isTxidOnly) {
312
- if (btx._tx) { beefTx = this.mergeTransaction(btx._tx) } else { beefTx = this.mergeRawTx(btx._rawTx) }
313
- }
312
+ if (btx.isTxidOnly && !beefTx)
313
+ beefTx = this.mergeTxidOnly(btx.txid)
314
+ else if (btx._tx && (!beefTx || beefTx.isTxidOnly))
315
+ beefTx = this.mergeTransaction(btx._tx)
316
+ else if (btx._rawTx && (!beefTx || beefTx.isTxidOnly))
317
+ beefTx = this.mergeRawTx(btx._rawTx)
314
318
  return beefTx
315
319
  }
316
320
 
@@ -442,8 +446,9 @@ export class Beef {
442
446
  /**
443
447
  * Serialize this Beef as AtomicBEEF.
444
448
  *
445
- * `txid` must exist and be the last transaction
446
- * in sorted (dependency) order.
449
+ * `txid` must exist
450
+ *
451
+ * after sorting, if txid is not last txid, creates a clone and removes newer txs
447
452
  *
448
453
  * @param txid
449
454
  * @returns serialized contents of this Beef with AtomicBEEF prefix.
@@ -452,11 +457,16 @@ export class Beef {
452
457
  this.sortTxs()
453
458
  const tx = this.findTxid(txid)
454
459
  if (!tx) { throw new Error(`${txid} does not exist in this Beef`) }
455
- if (this.txs[this.txs.length - 1] !== tx) { throw new Error(`${txid} is not the last transaction in this Beef`) }
460
+ let beef: Beef = this
461
+ if (this.txs[this.txs.length - 1] !== tx) {
462
+ beef = this.clone()
463
+ const i = this.txs.findIndex(t => t.txid === txid)
464
+ beef.txs.splice(i + 1)
465
+ }
456
466
  const writer = new Writer()
457
467
  writer.writeUInt32LE(ATOMIC_BEEF)
458
468
  writer.write(toArray(txid, 'hex'))
459
- this.toWriter(writer)
469
+ beef.toWriter(writer)
460
470
  return writer.toArray()
461
471
  }
462
472
 
@@ -480,7 +490,7 @@ export class Beef {
480
490
  const beef = new Beef(version === BEEF_MAGIC_V2 ? 'V2' : undefined)
481
491
  const bumpsLength = br.readVarIntNum()
482
492
  for (let i = 0; i < bumpsLength; i++) {
483
- const bump = MerklePath.fromReader(br)
493
+ const bump = MerklePath.fromReader(br, false)
484
494
  beef.bumps.push(bump)
485
495
  }
486
496
  const txsLength = br.readVarIntNum()
@@ -697,6 +707,37 @@ export class Beef {
697
707
  }
698
708
  return log
699
709
  }
710
+
711
+ /**
712
+ * In some circumstances it may be helpful for the BUMP MerkePaths to include
713
+ * leaves that can be computed from row zero.
714
+ */
715
+ addComputedLeaves() {
716
+ const beef = this
717
+ const hash = (m: string): string => toHex((
718
+ hash256(toArray(m, 'hex').reverse())
719
+ ).reverse())
720
+
721
+ for (const bump of beef.bumps) {
722
+ for (let row = 1; row < bump.path.length; row++) {
723
+ for (const leafL of bump.path[row - 1]) {
724
+ if (leafL.hash && (leafL.offset & 1) === 0) {
725
+ const leafR = bump.path[row - 1].find(l => l.offset === leafL.offset + 1)
726
+ const offsetOnRow = leafL.offset >> 1
727
+ if (leafR && leafR.hash && -1 === bump.path[row].findIndex(l => l.offset === offsetOnRow)) {
728
+ // computable leaf is missing... add it.
729
+ bump.path[row].push({
730
+ offset: offsetOnRow,
731
+ // string concatenation puts the right leaf on the left of the left leaf hash :-)
732
+ hash: hash(leafR.hash + leafL.hash)
733
+ })
734
+ }
735
+ }
736
+ }
737
+ }
738
+ }
739
+ }
740
+
700
741
  }
701
742
 
702
743
  export default Beef
@@ -50,7 +50,7 @@ export default class MerklePath {
50
50
  return MerklePath.fromBinary(toArray(hex, 'hex'))
51
51
  }
52
52
 
53
- static fromReader (reader: Reader): MerklePath {
53
+ static fromReader (reader: Reader, legalOffsetsOnly: boolean = true): MerklePath {
54
54
  const blockHeight = reader.readVarIntNum()
55
55
  const treeHeight = reader.readUInt8()
56
56
  const path = Array(treeHeight).fill(0).map(() => ([]))
@@ -79,7 +79,7 @@ export default class MerklePath {
79
79
  }
80
80
  path[level].sort((a, b) => a.offset - b.offset)
81
81
  }
82
- return new MerklePath(blockHeight, path)
82
+ return new MerklePath(blockHeight, path, legalOffsetsOnly)
83
83
  }
84
84
 
85
85
  /**
@@ -114,7 +114,7 @@ export default class MerklePath {
114
114
  hash?: string
115
115
  txid?: boolean
116
116
  duplicate?: boolean
117
- }>>) {
117
+ }>>, legalOffsetsOnly: boolean = true) {
118
118
  this.blockHeight = blockHeight
119
119
  this.path = path
120
120
 
@@ -135,7 +135,7 @@ export default class MerklePath {
135
135
  }
136
136
  }
137
137
  } else {
138
- if (!legalOffsets[height].has(leaf.offset)) {
138
+ if (legalOffsetsOnly && !legalOffsets[height].has(leaf.offset)) {
139
139
  throw new Error(`Invalid offset: ${leaf.offset}, at height: ${height}, with legal offsets: ${Array.from(legalOffsets[height]).join(', ')}`)
140
140
  }
141
141
  }
@@ -4,13 +4,17 @@ import Beef from "../../../dist/cjs/src/transaction/Beef"
4
4
  import BeefParty from "../../../dist/cjs/src/transaction/BeefParty"
5
5
  import { BEEF_MAGIC, BEEF_MAGIC_V2 } from "../../../dist/cjs/src/transaction/Beef"
6
6
  import Transaction from "../../../dist/cjs/src/transaction/Transaction"
7
+ import { fromBase58 } from '../../../dist/cjs/src/primitives/utils'
7
8
 
8
9
  // The following imports allow full type checking by the VsCode editor, but tests will fail to run:
9
- //import BeefTx from '../BeefTx'
10
- //import Beef from '../Beef'
11
- //import BeefParty from "../BeefParty"
12
- //import { BEEF_MAGIC, BEEF_MAGIC_V2 } from "../Beef"
13
- //import Transaction from "../Transaction"
10
+ /*
11
+ import BeefTx from '../BeefTx'
12
+ import Beef from '../Beef'
13
+ import BeefParty from "../BeefParty"
14
+ import { BEEF_MAGIC, BEEF_MAGIC_V2 } from "../Beef"
15
+ import Transaction from "../Transaction"
16
+ import { fromBase58 } from "../../primitives/utils"
17
+ */
14
18
 
15
19
  describe('Beef tests', () => {
16
20
  jest.setTimeout(99999999)
@@ -323,8 +327,23 @@ describe('Beef tests', () => {
323
327
  expect(beef.txs[1].txid).toBe('a')
324
328
  }
325
329
  })
330
+
331
+ test('10_deserialize beef with extra leaves', async () => {
332
+ const b58Beef = b58Beef_10
333
+
334
+ const beef = Beef.fromBinary(fromBase58(b58Beef))
335
+ expect(beef.verifyValid().valid).toBe(true)
336
+ const abeef = beef.toBinaryAtomic(beef.txs[beef.txs.length -1].txid)
337
+ const pbeef = Beef.fromBinary(abeef)
338
+ pbeef.addComputedLeaves();
339
+ const pbeefBinary = pbeef.toBinary();
340
+ expect(pbeefBinary).toBeTruthy()
341
+ expect(pbeef.verifyValid().valid).toBe(true)
342
+ })
326
343
  })
327
344
 
345
+ const b58Beef_10 = 'gno9MC7VXii1KoCkc2nsVyYJpqzN3dhBzYATETJcys62emMKfpBof4R7GozwYEaSapUtnNvqQ57aaYYjm3U2dv9eUJ1sV46boHkQgppYmAz9YH8FdZduV8aJayPViaKcyPmbDhEw6UW8TM5iFZLXNs7HBnJHUKCeTdNK4FUEL7vAugxAV9WUUZ43BZjJk2SmSeps9TCXjt1Ci9fKWp3d9QSoYvTpxwzyUFHjRKtbUgwq55ZfkBp5bV2Bpz9qSuKywKewW7Hh4S1nCUScwwzpKDozb3zic1V9p2k8rQxoPsRxjUJ8bjhNDdsN8d7KukFuc3n47fXzdWttvnxwsujLJRGnQbgJuknQqx3KLf5kJXHzwjG6TzigZk2t24qeB6d3hbYiaDr2fFkUJBL3tukTHhfNkQYRXuz3kucVDzvejHyqJaF51mXG8BjMN5aQj91ZJXCaPVqkMWCzmvyaqmXMdRiJdSAynhXbQK91xf6RwdNhz1tg5f9B6oJJMhsi9UYSVymmax8VLKD9AKzBCBDcfyD83m3jyS1VgKGZn3SkQmr6bsoWq88L3GsMnnmYUGogvdAYarTqg3pzkjCMxHzmJBMN6ofnUk8c1sRTXQue7BbyUaN5uZu3KW6CmFsEfpuqVvnqFW93TU1jrPP2S8yz8AexAnARPCKE8Yz7RfVaT6RCavwQKL3u5iookwRWEZXW1QWmM37yJWHD87SjVynyg327a1CLwcBxmE2CB48QeNVGyQki4CTQMqw2o8TMhDPJej1g68oniAjBcxBLSCs7KGvK3k7AfrHbCMULX9CTibYhCjdFjbsbBoocqJpxxcvkMo1fEEiAzZuiBVZQDYktDdTVbhKHvYkW25HcYX75NJrpNAhm7AjFeKLzEVxqAQkMfvTufpESNRZF4kQqg2Rg8h2ajcKTd5cpEPwXCrZLHm4EaZEmZVbg3QNfGhn7BJu1bHMtLqPD4y8eJxm2uGrW6saf6qKYmmu64F8A667NbD4yskPRQ1S863VzwGpxxmgLc1Ta3R46jEqsAoRDoZVUaCgBBZG3Yg1CTgi1EVBMXU7qvY4n3h8o2FLCEMWY4KadnV3iD4FbcdCmg4yxBosNAZgbPjhgGjCimjh4YsLd9zymGLmivmz2ZBg5m3xaiXT9NN81X9C1JUujd'
346
+
328
347
  const txs: string[] = [
329
348
  // 0
330
349
  "0100000001074d631cc91344daf964cb8b1f43fd3439b804eb673cbe82d9bde3dcc6394abd020000006b48304502210096e24b07db278344a32c12667bac0d38ea87b28e9c52f3630ff7c11760d4002e02201b9dc879465c82152920f650828bfabba0d0bc8f7e15b568b196f1fa7e2cb4644121034e6dcc60f278b83e3e7b2b6570c13a184f4cf27a279199625c981adfe42de997ffffffff03b706000000000000fdc20421029b09fdddfae493e309d3d97b919f6ab2902a789158f6f78489ad903b7a14baeaac2131546f446f44744b7265457a6248594b466a6d6f42756475466d53585855475a474d3004a1edf497c9db470e7862d8c8319b4312f197a18db529a5839ba7c7e4f4dcee4ff4b4651a2a30040ef0aab1c2a4386620b96cb068c2ad2bc06ed90cebed65643710497f785f48c91482ac86893dd675c3de5d7db2e305fb30a588c75282f062367dee1dd4d930502f64f043c45cd1f220f875ff749f5449c99b29a200a86287a842a4f1df21dafda06a1b39b590b33b15109ee8a700728f1a80016ed370f639b35c9d079dea938d7fe451a52c744605a64a89ac7ebfcf692ef345a1e41fba055c197f7e3f608196001ed150bd56883970f3b8954a63aa70e4fa1c33799d43caaacf7ca30f8e873a6a20a04b1508f851633fcb4f30de75954747e702775b143c668c3b80cef96001980f05a8322726a567a379a2901db030057400bf0484c0852b1ec42cdaae6bf9f258ba0c914d83a96522d63b46a71571087fc4ad4ea0be0f6f354e7f638a7c316ceca3be69698104c1662ce0d5f4334b3327377497f9bb8605388c86a5a3928b22dbafd20a488442862c0ea4cd72778ae3b1bfd1d517299969f553b21173f75b530d1c19e23835dbc423c0ab5d3a894560a064951e237d2956db5d007d2132721a949849df49c5c5ab21f9eacb9ea5aa704c37cd101c17bee37954eae01472096f0e3e52b9f98d46426f35437c76c643c6f5d4d0a5ceb89d03a31c451d7882a527c9e78abf01f6178446cd74a7ad4190f364cc79fccc91583f3521e706bd548dc19633234fe96e40e19ef6424b252691e2183484cbf088e965066d4b93b80de927f3426e71f265152d898537c6112ed4500b8b7f23d033c18bad6c3ae5a2e069b03af5b619a334713f367082d41fdff8d5b17ab90a8aff224fbec3a8ca93828fe18c24b54fdcffdd9ee53c40808a4d233a56ed6f21bda0ebf1f4375785f2427da484ba9cdcd45bd28f8072e885bcb687c575ed00bea520508e795b32b7c882ffdd36062e9f748fddbb99a71e7afaed26c24e631ccce19ceb92f1920fb45ae30b58dde09389062eccf1292c55aca96e7eae53a3b6721a568b1bc8f1bad201c0e3b3fe7caec178e491dc12e97a0d0448136f7fb9970c7b053a85a553927a2273682eb2ae4c41c12f1d893d427f659048bbfa6c4be0682d8226d21e4b522e0a2d575655dea64d36d1e0f5719d4f6d06b47f4b4e32731652f0c12e28a9642e90d049b0fbc2c2c94079106c017ad85a81882344ddb71e3c5de31f5b42df9c269e1f64e36b3179ef5e9581fc57c1ed919962ba11686ef0e0a40694f8733bd1e2232196ca4f35c33df1b27872bd3aa0e258601192e50ec5bb64c6ae1a60f580cb6434048c7a419a76028f95442ce5c628f1324281ca3e8050c7ea8a90c3782a6df2cfee5d735e43f518bc215017cd036f70011312204bf885c615b1d1601626de7a1ff9ff21a44818fa19b7a8ed241b6f16380c9e376c6c7a2f48414269e4a3f130ac5c01a7ca8ad4af044ed4288657f59b675e89287c3811c8e81ba1c75f68b8ec85a35d15b5f1cee0fa25874730450221008d6e0b7d98442477d5637fc12fa871f224fd82194d2c3d8e524a12e389ddc35602206286a3b292ef3bac9f047c842a3e9b418102617546e9dc6f9dcc6653c82e645c6d75c8000000000000001976a9142e1ba3aade0562035e69630cb40024f403c0599e88ac77420000000000001976a914eeea6c76530f46d0f2a20ad522c4a6886bad686d88ac00000000"