@bsv/wallet-toolbox 1.6.42 → 1.7.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/docs/client.md +152 -88
  3. package/docs/storage.md +47 -11
  4. package/docs/wallet.md +152 -88
  5. package/mobile/out/src/sdk/WERR_errors.d.ts +13 -0
  6. package/mobile/out/src/sdk/WERR_errors.d.ts.map +1 -1
  7. package/mobile/out/src/sdk/WERR_errors.js +28 -1
  8. package/mobile/out/src/sdk/WERR_errors.js.map +1 -1
  9. package/mobile/out/src/sdk/WalletStorage.interfaces.d.ts +3 -1
  10. package/mobile/out/src/sdk/WalletStorage.interfaces.d.ts.map +1 -1
  11. package/mobile/out/src/storage/methods/createAction.d.ts +1 -13
  12. package/mobile/out/src/storage/methods/createAction.d.ts.map +1 -1
  13. package/mobile/out/src/storage/methods/createAction.js +3 -40
  14. package/mobile/out/src/storage/methods/createAction.js.map +1 -1
  15. package/mobile/out/src/storage/methods/getBeefForTransaction.js +9 -1
  16. package/mobile/out/src/storage/methods/getBeefForTransaction.js.map +1 -1
  17. package/mobile/out/src/storage/methods/offsetKey.d.ts +24 -0
  18. package/mobile/out/src/storage/methods/offsetKey.d.ts.map +1 -0
  19. package/mobile/out/src/storage/methods/offsetKey.js +67 -0
  20. package/mobile/out/src/storage/methods/offsetKey.js.map +1 -0
  21. package/mobile/package-lock.json +6 -6
  22. package/mobile/package.json +2 -2
  23. package/out/src/sdk/WERR_errors.d.ts +13 -0
  24. package/out/src/sdk/WERR_errors.d.ts.map +1 -1
  25. package/out/src/sdk/WERR_errors.js +28 -1
  26. package/out/src/sdk/WERR_errors.js.map +1 -1
  27. package/out/src/sdk/WalletStorage.interfaces.d.ts +3 -1
  28. package/out/src/sdk/WalletStorage.interfaces.d.ts.map +1 -1
  29. package/out/src/storage/methods/__test/offsetKey.test.d.ts +2 -0
  30. package/out/src/storage/methods/__test/offsetKey.test.d.ts.map +1 -0
  31. package/out/src/storage/methods/__test/offsetKey.test.js +216 -0
  32. package/out/src/storage/methods/__test/offsetKey.test.js.map +1 -0
  33. package/out/src/storage/methods/createAction.d.ts +1 -13
  34. package/out/src/storage/methods/createAction.d.ts.map +1 -1
  35. package/out/src/storage/methods/createAction.js +3 -40
  36. package/out/src/storage/methods/createAction.js.map +1 -1
  37. package/out/src/storage/methods/getBeefForTransaction.js +9 -1
  38. package/out/src/storage/methods/getBeefForTransaction.js.map +1 -1
  39. package/out/src/storage/methods/offsetKey.d.ts +24 -0
  40. package/out/src/storage/methods/offsetKey.d.ts.map +1 -0
  41. package/out/src/storage/methods/offsetKey.js +67 -0
  42. package/out/src/storage/methods/offsetKey.js.map +1 -0
  43. package/out/test/Wallet/support/operations.man.test.js +2 -2
  44. package/out/test/Wallet/support/operations.man.test.js.map +1 -1
  45. package/out/test/WalletClient/WERR.man.test.js +26 -16
  46. package/out/test/WalletClient/WERR.man.test.js.map +1 -1
  47. package/out/test/utils/TestUtilsWalletStorage.d.ts +1 -0
  48. package/out/test/utils/TestUtilsWalletStorage.d.ts.map +1 -1
  49. package/out/test/utils/TestUtilsWalletStorage.js +2 -0
  50. package/out/test/utils/TestUtilsWalletStorage.js.map +1 -1
  51. package/out/tsconfig.all.tsbuildinfo +1 -1
  52. package/package.json +2 -2
  53. package/src/sdk/WERR_errors.ts +28 -0
  54. package/src/sdk/WalletStorage.interfaces.ts +3 -0
  55. package/src/storage/methods/__test/offsetKey.test.ts +274 -0
  56. package/src/storage/methods/createAction.ts +3 -68
  57. package/src/storage/methods/getBeefForTransaction.ts +10 -2
  58. package/src/storage/methods/offsetKey.ts +89 -0
  59. package/test/Wallet/support/operations.man.test.ts +2 -2
  60. package/test/WalletClient/WERR.man.test.ts +27 -16
  61. package/test/utils/TestUtilsWalletStorage.ts +4 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/wallet-toolbox",
3
- "version": "1.6.42",
3
+ "version": "1.7.0",
4
4
  "description": "BRC100 conforming wallet, wallet storage and wallet signer components",
5
5
  "main": "./out/src/index.js",
6
6
  "types": "./out/src/index.d.ts",
@@ -33,7 +33,7 @@
33
33
  "dependencies": {
34
34
  "@bsv/auth-express-middleware": "^1.2.3",
35
35
  "@bsv/payment-express-middleware": "^1.2.3",
36
- "@bsv/sdk": "^1.8.11",
36
+ "@bsv/sdk": "^1.9.3",
37
37
  "express": "^4.21.2",
38
38
  "idb": "^8.0.2",
39
39
  "knex": "^3.1.0",
@@ -56,11 +56,37 @@ export class WERR_INVALID_PARAMETER extends WalletError {
56
56
  }
57
57
  override toJson(): string {
58
58
  const obj = JSON.parse(super.toJson())
59
+ obj.code = 6 // Must match HTTPWalletJSON.ts code
59
60
  obj.parameter = this.parameter
60
61
  return JSON.stringify(obj)
61
62
  }
62
63
  }
63
64
 
65
+ /**
66
+ * Invalid merkleRoot ${merkleRoot} for block ${blockHash} at height ${blockHeight}${txid ? ` for txid ${txid}` : ''}.
67
+ *
68
+ * Typically thrown when a chain tracker fails to validate a merkle root.
69
+ */
70
+ export class WERR_INVALID_MERKLE_ROOT extends WalletError {
71
+ constructor(
72
+ public blockHash: string,
73
+ public blockHeight: number,
74
+ public merkleRoot: string,
75
+ public txid?: string,
76
+ ) {
77
+ super('WERR_INVALID_MERKLE_ROOT', `Invalid merkleRoot ${merkleRoot} for block ${blockHash} at height ${blockHeight}${txid ? ` for txid ${txid}` : ''}.`)
78
+ }
79
+ override toJson(): string {
80
+ const obj = JSON.parse(super.toJson())
81
+ obj.code = 8 // Must match HTTPWalletJSON.ts code
82
+ obj.blockHash = this.blockHash
83
+ obj.blockHeight = this.blockHeight
84
+ obj.merkleRoot = this.merkleRoot
85
+ obj.txid = this.txid
86
+ return JSON.stringify(obj)
87
+ }
88
+ }
89
+
64
90
  /**
65
91
  * The required ${parameter} parameter is missing.
66
92
  *
@@ -139,6 +165,7 @@ export class WERR_INSUFFICIENT_FUNDS extends WalletError {
139
165
  }
140
166
  override toJson(): string {
141
167
  const obj = JSON.parse(super.toJson())
168
+ obj.code = 7 // Must match HTTPWalletJSON.ts code
142
169
  obj.totalSatoshisNeeded = this.totalSatoshisNeeded
143
170
  obj.moreSatoshisNeeded = this.moreSatoshisNeeded
144
171
  return JSON.stringify(obj)
@@ -189,6 +216,7 @@ export class WERR_REVIEW_ACTIONS extends WalletError {
189
216
  }
190
217
  override toJson(): string {
191
218
  const obj = JSON.parse(super.toJson())
219
+ obj.code = 5 // Must match HTTPWalletJSON.ts code
192
220
  obj.reviewActionResults = this.reviewActionResults
193
221
  obj.sendWithResults = this.sendWithResults
194
222
  obj.txid = this.txid
@@ -2,6 +2,7 @@ import {
2
2
  AbortActionArgs,
3
3
  AbortActionResult,
4
4
  Beef,
5
+ ChainTracker,
5
6
  InternalizeActionArgs,
6
7
  InternalizeActionResult,
7
8
  ListActionsArgs,
@@ -387,6 +388,8 @@ export interface StorageGetBeefOptions {
387
388
  ignoreNewProven?: boolean
388
389
  /** optional. Default is zero. Ignores available merkle paths until recursion detpth equals or exceeds value */
389
390
  minProofLevel?: number
391
+ /** optional. If valid, any merkleRoot that fails to validate will result in an exception without merging to `mergeToBeef`. */
392
+ chainTracker?: ChainTracker
390
393
  }
391
394
 
392
395
  export interface StorageSyncReaderOptions {
@@ -0,0 +1,274 @@
1
+ import {
2
+ Beef,
3
+ BigNumber,
4
+ CreateActionArgs,
5
+ CreateActionInput,
6
+ CreateActionOptions,
7
+ Curve,
8
+ P2PKH,
9
+ PositiveIntegerOrZero,
10
+ PrivateKey,
11
+ SignActionArgs,
12
+ SignActionSpend,
13
+ Utils
14
+ } from '@bsv/sdk'
15
+ import { keyOffsetToHashedSecret, lockScriptWithKeyOffsetFromPubKey, offsetPrivKey, offsetPubKey } from '../offsetKey'
16
+ import { _tu, TestWalletOnly } from '../../../../test/utils/TestUtilsWalletStorage'
17
+ import { Setup } from '../../../Setup'
18
+ import { StorageKnex } from '../../StorageKnex'
19
+ import { FindCommissionsArgs, FindTransactionsArgs } from '../../../sdk'
20
+ import { verifyOne, verifyTruthy } from '../../../utility/utilityHelpers'
21
+ import { TableCommission } from '../../schema/tables/TableCommission'
22
+ import { WalletStorageManager } from '../../WalletStorageManager'
23
+
24
+ describe('offsetKey tests', () => {
25
+ jest.setTimeout(99999999)
26
+
27
+ test('1_offsetPrivKey', async () => {
28
+ const bn2 = BigNumber.fromHex('FFF0000000000000000000000000000000000000000000000000000000000100', 'big')
29
+
30
+ const priv2 = new PrivateKey(bn2)
31
+
32
+ const privKey2 = priv2.toWif()
33
+
34
+ const keyOffset = 'KyaVZ1AnxYN4oB8JnxYVyZ8xYC9ySpq2Umzx6jwzQGVo71k1EgSt'
35
+ const oPrivKey = 'KyMYVLNeyF4qQsgHW3N1eJv9WcRd2aZC8hw7iLgCojQsyizqKsV4'
36
+
37
+ const r12 = offsetPrivKey(privKey2, keyOffset)
38
+
39
+ expect(r12.keyOffset).toBe(keyOffset)
40
+
41
+ expect(r12.offsetPrivKey).toBe(oPrivKey)
42
+ })
43
+
44
+ test('2_offsetPubKey', async () => {
45
+ const bn2 = BigNumber.fromHex('FFF0000000000000000000000000000000000000000000000000000000000100', 'big')
46
+
47
+ const priv2 = new PrivateKey(bn2)
48
+
49
+ const pub2 = priv2.toPublicKey()
50
+
51
+ const keyOffset = 'KyaVZ1AnxYN4oB8JnxYVyZ8xYC9ySpq2Umzx6jwzQGVo71k1EgSt'
52
+ const oPrivKey = 'KyMYVLNeyF4qQsgHW3N1eJv9WcRd2aZC8hw7iLgCojQsyizqKsV4'
53
+ const oPubKey = '024b4362ce98e0afd22bf3319831cfaf691ad2f08471a3386bcda98d65435a0f24'
54
+
55
+ const r22 = offsetPubKey(pub2.toString(), keyOffset)
56
+
57
+ expect(r22.keyOffset).toBe(keyOffset)
58
+
59
+ expect(r22.offsetPubKey).toBe(oPubKey)
60
+
61
+ const pubKey2 = PrivateKey.fromWif(oPrivKey).toPublicKey().toString()
62
+
63
+ expect(pubKey2).toBe(oPubKey)
64
+ })
65
+
66
+ test('3_lockScriptWithKeyOffsetFromPubKey', async () => {
67
+ const pubKey = '0397742eaef6c7f08c4aa057397d45529f93ab90345b84ce5a5aac06ea9cdd132e'
68
+
69
+ const ko = 'Kx9MjojdkjL3bEo5tQwHpwT1voKN1z56NjpATsa2Sx6QTrVjgMQJ'
70
+ const script = '76a9149d09d0ee09b212c548f6b1a7835641f33654246788ac'
71
+
72
+ const r1 = lockScriptWithKeyOffsetFromPubKey(pubKey, ko)
73
+
74
+ expect(r1.script).toBe(script)
75
+ expect(r1.keyOffset).toBe(ko)
76
+
77
+ // And with a random keyOffset...
78
+ const r2 = lockScriptWithKeyOffsetFromPubKey(pubKey)
79
+
80
+ expect(r2.script).not.toBe(script)
81
+ expect(r2.keyOffset).not.toBe(ko)
82
+ })
83
+
84
+ test('4a_check keyOffset address', async () => {
85
+ if (_tu.noEnv('main')) return
86
+
87
+ const env = _tu.getEnv('main')
88
+ const privHex = env.devKeys[env.commissionsIdentity]!
89
+ const priv = PrivateKey.fromHex(privHex)
90
+ const pub = priv.toPublicKey()
91
+
92
+ const keyOffset = 'L2hMY5uW6Vh46DEFMzrYiKSFWDRSMGDTsaeDvhiKNNJGihwKD17w'
93
+
94
+ const r = offsetPrivKey(priv.toWif(), keyOffset)
95
+ const privO = PrivateKey.fromWif(r.offsetPrivKey)
96
+ const address = privO.toAddress()
97
+ expect(address).toBe('1EZz5oxwXoG6LgGLxeYPeg1NfzQrP1vL6M')
98
+ })
99
+
100
+ test('4_redeemServiceCharges', async () => {
101
+ if (_tu.noEnv('main')) return
102
+
103
+ const env = _tu.getEnv('main')
104
+ if (!env.devKeys[env.commissionsIdentity]) {
105
+ throw new Error('No dev key for commissions identity')
106
+ }
107
+
108
+ const knex = Setup.createMySQLKnex(process.env.MAIN_CLOUD_MYSQL_CONNECTION!)
109
+ const storage = new StorageKnex({
110
+ chain: env.chain,
111
+ knex: knex,
112
+ commissionSatoshis: 0,
113
+ commissionPubKeyHex: undefined,
114
+ feeModel: { model: 'sat/kb', value: 1 }
115
+ })
116
+
117
+ let setup: TestWalletOnly
118
+ await storage.makeAvailable()
119
+
120
+ setup = await _tu.createTestWalletWithStorageClient({
121
+ chain: 'main',
122
+ rootKeyHex: env.devKeys[env.commissionsIdentity]
123
+ })
124
+ storage.setServices(setup.services)
125
+
126
+ try {
127
+ // await setup.wallet.abortAction({ reference: 'e9c03bdf603e90ebe482044e8d0f7afbf2d6fe480a13dbd8689e2e5e5183bed4' })
128
+
129
+ // txid b6f72df4224efbacab42a16e1e88f48c217f03929c36987b9067d2556de47c10
130
+ // height 922107
131
+ // hash 00000000000000001957bfadf841d1709d5039b3243c33ba58e4a6a97b44d2a8
132
+ const sm = new WalletStorageManager(setup.identityKey, storage)
133
+ sm.setServices(setup.services)
134
+ await sm.reproveHeader('000000000000000014d97d19bf82956c1f7ce3977da10b7fbdab9a10653c02e7')
135
+
136
+ const comms: TableCommission[] = []
137
+ const beef = new Beef()
138
+ const chainTracker = await setup.services.getChainTracker()
139
+ const inputs: CreateActionInput[] = []
140
+
141
+ const fca: FindCommissionsArgs = {
142
+ partial: { isRedeemed: false },
143
+ paged: { limit: 200, offset: 0 }
144
+ }
145
+
146
+ for (; comms.length < fca.paged!.limit; ) {
147
+ const unredeemedComms = await storage.findCommissions(fca)
148
+ if (unredeemedComms.length === 0) break
149
+ for (const comm of unredeemedComms) {
150
+ const tt = verifyTruthy(await storage.findTransactionById(comm.transactionId, undefined, true))
151
+ if (tt.provenTxId && tt.txid) {
152
+ // Only add merge valid beefs...
153
+ try {
154
+ await storage.getBeefForTransaction(tt.txid, { mergeToBeef: beef, chainTracker })
155
+ } catch (e) {
156
+ // Ignore errors in merging beefs
157
+ }
158
+ const tx = verifyTruthy(beef.findTxid(tt.txid)).tx!
159
+ const commVOut = tx.outputs.findIndex(
160
+ o => o.satoshis === comm.satoshis && o.lockingScript.toHex() === Utils.toHex(comm.lockingScript)
161
+ )
162
+ const commOut = tx.outputs[commVOut]
163
+ const input: CreateActionInput = {
164
+ outpoint: `${tt.txid}.${commVOut}`,
165
+ inputDescription: `commId:${comm.commissionId}`,
166
+ unlockingScriptLength: 108
167
+ }
168
+ inputs.push(input)
169
+ comms.push(comm)
170
+ if (comms.length >= fca.paged!.limit) break
171
+ }
172
+ }
173
+ fca.paged!.offset! += unredeemedComms.length
174
+ }
175
+
176
+ if (comms.length > 0) {
177
+ console.log(beef.toLogString())
178
+ const verified = await beef.verify(await setup.services.getChainTracker(), false)
179
+ expect(verified).toBe(true)
180
+
181
+ const cao: CreateActionOptions = {
182
+ randomizeOutputs: false,
183
+ //signAndProcess: false,
184
+ noSend: true
185
+ }
186
+ const ca: CreateActionArgs = {
187
+ description: 'redeem commissions',
188
+ inputs: inputs,
189
+ inputBEEF: beef.toBinary(),
190
+ options: cao
191
+ }
192
+
193
+ const car = await setup.wallet.createAction(ca)
194
+ expect(car.signableTransaction).toBeTruthy()
195
+
196
+ const st = car.signableTransaction!
197
+ expect(st.reference).toBeTruthy()
198
+ const atomicBeef = Beef.fromBinary(st.tx)
199
+ const txid = atomicBeef.txs[atomicBeef.txs.length - 1].txid!
200
+ const tx = atomicBeef.findTransactionForSigning(txid)!
201
+
202
+ const priv = PrivateKey.fromHex(env.devKeys[env.commissionsIdentity])
203
+ const pub = priv.toPublicKey()
204
+ const curve = new Curve()
205
+ const p2pkh = new P2PKH()
206
+ const spends: Record<PositiveIntegerOrZero, SignActionSpend> = {}
207
+ let vin = 0
208
+ // set an unlockingScriptTemplate for each commission input being redeemed in unsigned tx
209
+ for (const comm of comms) {
210
+ const { hashedSecret } = keyOffsetToHashedSecret(pub, comm.keyOffset)
211
+ const bn = priv.add(hashedSecret).mod(curve.n)
212
+ const offsetPrivKey = new PrivateKey(bn)
213
+ const unlock = p2pkh.unlock(offsetPrivKey, 'all', false)
214
+ tx.inputs[vin].unlockingScriptTemplate = unlock
215
+ vin++
216
+ }
217
+
218
+ // sign each input
219
+ await tx.sign()
220
+
221
+ vin = 0
222
+ // extract all the signed unlocking scripts
223
+ for (const comm of comms) {
224
+ const script = tx.inputs[vin].unlockingScript!
225
+ const unlockingScript = script.toHex()
226
+ spends[vin] = { unlockingScript }
227
+ vin++
228
+ }
229
+
230
+ const signArgs: SignActionArgs = {
231
+ reference: st.reference,
232
+ spends,
233
+ options: {
234
+ returnTXIDOnly: true,
235
+ noSend: true
236
+ }
237
+ }
238
+
239
+ // Forward all the unlocking scripts to storage and create the ProvenTxReq for the noSend txid.
240
+ const sr = await setup.wallet.signAction(signArgs)
241
+ expect(sr.txid).toBeTruthy()
242
+
243
+ // Update the commissions as redeemed in storage
244
+ for (const comm of comms) {
245
+ await storage.updateCommission(comm.commissionId, { isRedeemed: true })
246
+ }
247
+
248
+ {
249
+ // Get the transaction broadcast
250
+ const createArgs: CreateActionArgs = {
251
+ description: `broadcasting noSend`,
252
+ options: {
253
+ acceptDelayedBroadcast: false,
254
+ sendWith: [sr.txid!]
255
+ }
256
+ }
257
+
258
+ const cr = await setup.wallet.createAction(createArgs)
259
+
260
+ expect(cr.noSendChange).not.toBeTruthy()
261
+ expect(cr.sendWithResults?.length).toBe(1)
262
+ const [swr] = cr.sendWithResults!
263
+ expect(swr.status !== 'failed').toBe(true)
264
+ }
265
+ }
266
+ } catch (err) {
267
+ console.error('Error in 4_redeemServiceCharges test:', err)
268
+ throw err
269
+ }
270
+
271
+ await storage.destroy()
272
+ await setup.wallet.destroy()
273
+ })
274
+ })
@@ -1,17 +1,4 @@
1
- import {
2
- Beef,
3
- BigNumber,
4
- Curve,
5
- OriginatorDomainNameStringUnder250Bytes,
6
- P2PKH,
7
- PrivateKey,
8
- PubKeyHex,
9
- PublicKey,
10
- Random,
11
- ReviewActionResult,
12
- Script,
13
- Utils
14
- } from '@bsv/sdk'
1
+ import { Beef, OriginatorDomainNameStringUnder250Bytes, Random, ReviewActionResult, Script, Utils } from '@bsv/sdk'
15
2
  import {
16
3
  generateChangeSdk,
17
4
  GenerateChangeSdkChangeInput,
@@ -37,7 +24,6 @@ import {
37
24
  import { WERR_INTERNAL, WERR_INVALID_PARAMETER, WERR_REVIEW_ACTIONS } from '../../sdk/WERR_errors'
38
25
  import {
39
26
  randomBytesBase64,
40
- sha256Hash,
41
27
  verifyId,
42
28
  verifyInteger,
43
29
  verifyNumber,
@@ -52,6 +38,7 @@ import { TableOutputTag } from '../schema/tables/TableOutputTag'
52
38
  import { TableTransaction } from '../schema/tables/TableTransaction'
53
39
  import { EntityProvenTx } from '../schema/entities/EntityProvenTx'
54
40
  import { throwDummyReviewActions } from '../../Wallet'
41
+ import { createStorageServiceChargeScript } from './offsetKey'
55
42
 
56
43
  let disableDoubleSpendCheckForTest = true
57
44
  export function setDisableDoubleSpendCheckForTest(v: boolean) {
@@ -244,7 +231,7 @@ async function createNewInputs(
244
231
  if (o2.spendable != true) {
245
232
  throw new WERR_INVALID_PARAMETER(
246
233
  `inputs[${i.vin}]`,
247
- `spendable output. output ${o.txid}:${o.vout} appears to have been spent.`
234
+ `spendable output. output ${o.txid}:${o.vout} appears to have been spent (spendable=${o2.spendable}).`
248
235
  )
249
236
  }
250
237
  await storage.updateOutput(
@@ -942,55 +929,3 @@ async function mergeAllocatedChangeBeefs(
942
929
  }
943
930
  return trimInputBeef(beef, vargs)
944
931
  }
945
-
946
- function keyOffsetToHashedSecret(pub: PublicKey, keyOffset?: string): { hashedSecret: BigNumber; keyOffset: string } {
947
- let offset: PrivateKey
948
- if (keyOffset !== undefined && typeof keyOffset === 'string') {
949
- if (keyOffset.length === 64) offset = PrivateKey.fromString(keyOffset, 'hex')
950
- else offset = PrivateKey.fromWif(keyOffset)
951
- } else {
952
- offset = PrivateKey.fromRandom()
953
- keyOffset = offset.toWif()
954
- }
955
-
956
- const sharedSecret = pub.mul(offset).encode(true, undefined) as number[]
957
- const hashedSecret = sha256Hash(sharedSecret)
958
-
959
- return { hashedSecret: new BigNumber(hashedSecret), keyOffset }
960
- }
961
-
962
- export function offsetPubKey(pubKey: string, keyOffset?: string): { offsetPubKey: string; keyOffset: string } {
963
- const pub = PublicKey.fromString(pubKey)
964
-
965
- const r = keyOffsetToHashedSecret(pub, keyOffset)
966
-
967
- // The hashed secret is multiplied by the generator point.
968
- const point = new Curve().g.mul(r.hashedSecret)
969
-
970
- // The resulting point is added to the recipient public key.
971
- const offsetPubKey = new PublicKey(pub.add(point))
972
-
973
- return { offsetPubKey: offsetPubKey.toString(), keyOffset: r.keyOffset }
974
- }
975
-
976
- export function lockScriptWithKeyOffsetFromPubKey(
977
- pubKey: string,
978
- keyOffset?: string
979
- ): { script: string; keyOffset: string } {
980
- const r = offsetPubKey(pubKey, keyOffset)
981
-
982
- const offsetPub = PublicKey.fromString(r.offsetPubKey)
983
-
984
- const hash = offsetPub.toHash() as number[]
985
-
986
- const script = new P2PKH().lock(hash).toHex()
987
-
988
- return { script, keyOffset: r.keyOffset }
989
- }
990
-
991
- export function createStorageServiceChargeScript(pubKeyHex: PubKeyHex): {
992
- script: string
993
- keyOffset: string
994
- } {
995
- return lockScriptWithKeyOffsetFromPubKey(pubKeyHex)
996
- }
@@ -2,7 +2,7 @@ import { Beef } from '@bsv/sdk'
2
2
  import { StorageProvider } from '../StorageProvider'
3
3
  import { ProvenOrRawTx, StorageGetBeefOptions } from '../../sdk/WalletStorage.interfaces'
4
4
  import { EntityProvenTx } from '../schema/entities/EntityProvenTx'
5
- import { WERR_INVALID_OPERATION, WERR_INVALID_PARAMETER } from '../../sdk/WERR_errors'
5
+ import { WERR_INVALID_MERKLE_ROOT, WERR_INVALID_OPERATION, WERR_INVALID_PARAMETER } from '../../sdk/WERR_errors'
6
6
  import { asBsvSdkTx, verifyTruthy } from '../../utility/utilityHelpers'
7
7
 
8
8
  /**
@@ -102,8 +102,16 @@ async function mergeBeefForTransactionRecurse(
102
102
  if (r.proven) {
103
103
  // storage has proven this txid,
104
104
  // merge both the raw transaction and its merkle path
105
+ const mp = new EntityProvenTx(r.proven).getMerklePath()
106
+ if (options.chainTracker) {
107
+ const root = mp.computeRoot()
108
+ const isValid = await options.chainTracker.isValidRootForHeight(root, r.proven.height)
109
+ if (!isValid) {
110
+ throw new WERR_INVALID_MERKLE_ROOT(r.proven.blockHash, r.proven.height, root, txid)
111
+ }
112
+ }
105
113
  beef.mergeRawTx(r.proven.rawTx)
106
- beef.mergeBump(new EntityProvenTx(r.proven).getMerklePath())
114
+ beef.mergeBump(mp)
107
115
  return beef
108
116
  }
109
117
 
@@ -0,0 +1,89 @@
1
+ import { PublicKey, BigNumber, PrivateKey, Curve, P2PKH, PubKeyHex, CreateActionInput } from '@bsv/sdk'
2
+ import { TableCommission } from '../schema/tables/TableCommission'
3
+ import { sha256Hash } from '../../utility/utilityHelpers'
4
+
5
+ export function keyOffsetToHashedSecret(
6
+ pub: PublicKey,
7
+ keyOffset?: string
8
+ ): { hashedSecret: BigNumber; keyOffset: string } {
9
+ let offset: PrivateKey
10
+ if (keyOffset !== undefined && typeof keyOffset === 'string') {
11
+ if (keyOffset.length === 64) offset = PrivateKey.fromString(keyOffset, 'hex')
12
+ else offset = PrivateKey.fromWif(keyOffset)
13
+ } else {
14
+ offset = PrivateKey.fromRandom()
15
+ keyOffset = offset.toWif()
16
+ }
17
+
18
+ const sharedSecret = pub.mul(offset).encode(true, undefined) as number[]
19
+ const hashedSecret = sha256Hash(sharedSecret)
20
+
21
+ return { hashedSecret: new BigNumber(hashedSecret), keyOffset }
22
+ }
23
+
24
+ export function offsetPrivKey(privKey: string, keyOffset?: string): { offsetPrivKey: string; keyOffset: string } {
25
+ const priv = PrivateKey.fromWif(privKey)
26
+
27
+ const pub = priv.toPublicKey()
28
+
29
+ const r = keyOffsetToHashedSecret(pub, keyOffset)
30
+
31
+ const bn = priv.add(r.hashedSecret).mod(new Curve().n)
32
+
33
+ const offsetPrivKey = new PrivateKey(bn).toWif()
34
+
35
+ return { offsetPrivKey, keyOffset: r.keyOffset }
36
+ }
37
+
38
+ export function offsetPubKey(pubKey: string, keyOffset?: string): { offsetPubKey: string; keyOffset: string } {
39
+ const pub = PublicKey.fromString(pubKey)
40
+
41
+ const r = keyOffsetToHashedSecret(pub, keyOffset)
42
+
43
+ // The hashed secret is multiplied by the generator point.
44
+ const point = new Curve().g.mul(r.hashedSecret)
45
+
46
+ // The resulting point is added to the recipient public key.
47
+ const offsetPubKey = new PublicKey(pub.add(point))
48
+
49
+ return { offsetPubKey: offsetPubKey.toString(), keyOffset: r.keyOffset }
50
+ }
51
+
52
+ export function lockScriptWithKeyOffsetFromPubKey(
53
+ pubKey: string,
54
+ keyOffset?: string
55
+ ): { script: string; keyOffset: string } {
56
+ const r = offsetPubKey(pubKey, keyOffset)
57
+
58
+ const offsetPub = PublicKey.fromString(r.offsetPubKey)
59
+
60
+ const hash = offsetPub.toHash() as number[]
61
+
62
+ const script = new P2PKH().lock(hash).toHex()
63
+
64
+ return { script, keyOffset: r.keyOffset }
65
+ }
66
+
67
+ export function createStorageServiceChargeScript(pubKeyHex: PubKeyHex): {
68
+ script: string
69
+ keyOffset: string
70
+ } {
71
+ return lockScriptWithKeyOffsetFromPubKey(pubKeyHex)
72
+ }
73
+
74
+ export function redeemServiceCharges(privateKeyWif: string, charges: TableCommission[]): {}[] {
75
+ const priv = PrivateKey.fromWif(privateKeyWif)
76
+ const pub = priv.toPublicKey()
77
+ const p2pkh = new P2PKH()
78
+
79
+ const inputs: CreateActionInput[] = []
80
+
81
+ for (const c of charges) {
82
+ const { hashedSecret } = keyOffsetToHashedSecret(pub, c.keyOffset)
83
+ const bn = priv.add(hashedSecret).mod(new Curve().n)
84
+ const offsetPrivKey = new PrivateKey(bn)
85
+ //const unlock = p2pkh.unlock(offsetPrivKey, signOutputs, anyoneCanPay)
86
+ }
87
+
88
+ return []
89
+ }
@@ -49,7 +49,7 @@ describe('operations.man tests', () => {
49
49
 
50
50
  test('1 review and unfail false doubleSpends', async () => {
51
51
  const { env, storage, services } = await _tu.createMainReviewSetup()
52
- let offset = 0
52
+ let offset = 400
53
53
  const limit = 100
54
54
  let allUnfails: number[] = []
55
55
  let reviewed = 0
@@ -82,7 +82,7 @@ describe('operations.man tests', () => {
82
82
 
83
83
  test('2 review and unfail false invalids', async () => {
84
84
  const { env, storage, services } = await _tu.createMainReviewSetup()
85
- let offset = 0
85
+ let offset = 500
86
86
  const limit = 100
87
87
  let allUnfails: number[] = []
88
88
  let reviewed = 0
@@ -2,23 +2,34 @@ import { CreateActionArgs, WalletClient } from '@bsv/sdk'
2
2
  import { specOpThrowReviewActions } from '../../src/sdk/types'
3
3
  import { WalletError } from '../../src/sdk/WalletError'
4
4
  import { WERR_REVIEW_ACTIONS } from '../../src/sdk/WERR_errors'
5
- import { validateCreateActionArgs } from '../../src/sdk'
5
+ import { validateCreateActionArgs, WalletErrorFromJson } from '../../src/sdk'
6
6
 
7
- test('0 WERR_REVIEW_ACTIONS via WalletClient', async () => {
8
- const wallet = new WalletClient('auto', '0.WERR.man.test')
7
+ describe('WERR.man tests', () => {
8
+ jest.setTimeout(99999999)
9
9
 
10
- const args: CreateActionArgs = {
11
- labels: [specOpThrowReviewActions],
12
- description: 'must throw'
13
- }
14
- const vargs = validateCreateActionArgs(args)
10
+ test('0 WERR_REVIEW_ACTIONS via WalletClient', async () => {
11
+ const wallet = new WalletClient('auto', '0.WERR.man.test')
15
12
 
16
- try {
17
- const r = await wallet.createAction(args)
18
- expect(true).toBe(false)
19
- } catch (eu: unknown) {
20
- const e = WalletError.fromUnknown(eu) as WERR_REVIEW_ACTIONS
21
- expect(e.code).toBe('WERR_REVIEW_ACTIONS')
22
- expect(e.reviewActionResults).toBeTruthy()
23
- }
13
+ const args: CreateActionArgs = {
14
+ labels: [specOpThrowReviewActions],
15
+ description: 'must throw'
16
+ }
17
+ const vargs = validateCreateActionArgs(args)
18
+
19
+ try {
20
+ const r = await wallet.createAction(args)
21
+ expect(true).toBe(false)
22
+ } catch (eu: unknown) {
23
+ const e = WalletError.fromUnknown(eu) as WERR_REVIEW_ACTIONS
24
+ expect(e.code).toBe('WERR_REVIEW_ACTIONS')
25
+ expect(e.reviewActionResults).toBeTruthy()
26
+ }
27
+ })
28
+
29
+ test('1', async () => {
30
+ const s = `{\"isError\":true,\"name\":\"WERR_REVIEW_ACTIONS\",\"reviewActionResults\":[{\"txid\":\"344556f1c428367689e09e29aa4dc7cfe755db1fab5cf7c1cf8d1f99b507004b\",\"status\":\"doubleSpend\",\"competingTxs\":[\"344556f1c428367689e09e29aa4dc7cfe755db1fab5cf7c1cf8d1f99b507004b\"],\"competingBeef\":[1,0,190,239,1,254,12,52,25,0,3,2,2,2,13,153,15,202,45,179,254,102,34,161,222,144,207,214,224,219,57,76,176,248,213,137,238,7,63,67,204,253,45,62,70,190,3,0,206,69,142,120,152,200,85,221,140,68,16,34,144,39,29,181,67,66,245,76,24,46,209,253,246,204,133,193,175,188,98,241,2,0,0,119,165,81,236,153,173,143,252,69,138,209,229,97,189,36,123,84,160,234,127,63,185,82,5,26,106,157,73,26,107,32,100,1,0,143,243,215,201,227,2,63,39,103,57,216,42,131,190,76,56,216,43,104,59,7,4,174,137,191,49,49,128,249,252,45,42,2,0,0,25,6,223,99,50,23,119,231,67,155,160,205,224,45,74,194,190,202,18,106,135,17,234,165,66,245,89,135,90,255,10,227,1,0,35,96,209,29,146,83,58,174,231,36,40,57,137,94,136,75,149,86,89,160,195,121,92,180,37,191,31,187,23,71,219,209,2,1,0,0,0,2,1,247,147,92,34,59,244,55,220,70,138,231,75,160,174,137,68,202,34,151,142,102,170,47,246,106,0,25,152,90,245,197,0,0,0,0,106,71,48,68,2,32,23,143,242,5,200,252,85,12,167,137,194,54,96,107,40,141,240,219,145,62,182,133,119,224,94,146,94,249,214,213,212,121,2,32,15,130,24,137,152,12,86,54,141,67,39,178,80,29,110,48,77,114,212,69,237,139,89,152,246,124,138,91,19,32,71,43,65,33,3,16,229,154,223,195,210,19,58,106,26,124,66,99,139,137,43,164,213,109,60,35,235,32,30,122,36,252,12,131,190,69,63,255,255,255,255,1,247,147,92,34,59,244,55,220,70,138,231,75,160,174,137,68,202,34,151,142,102,170,47,246,106,0,25,152,90,245,197,2,0,0,0,107,72,48,69,2,33,0,154,55,50,224,237,75,147,58,95,164,173,104,149,13,24,125,198,35,126,194,134,91,150,238,179,25,192,213,136,196,51,66,2,32,98,84,153,139,248,106,241,122,65,180,241,124,30,233,140,74,172,74,217,165,129,143,127,156,49,230,34,46,188,15,28,52,65,33,2,146,89,63,53,57,196,48,37,118,17,182,193,201,75,44,105,67,181,136,23,25,104,127,48,146,231,88,99,197,53,56,239,255,255,255,255,2,225,3,0,0,0,0,0,0,25,118,169,20,255,161,196,19,38,201,6,21,136,26,207,151,137,162,75,232,58,201,53,88,136,172,1,0,0,0,0,0,0,0,25,118,169,20,39,140,42,205,54,197,3,247,165,173,16,224,138,193,127,34,191,29,126,7,136,172,0,0,0,0,1,0,1,0,0,0,1,13,153,15,202,45,179,254,102,34,161,222,144,207,214,224,219,57,76,176,248,213,137,238,7,63,67,204,253,45,62,70,190,0,0,0,0,106,71,48,68,2,32,113,14,233,125,141,249,154,234,138,195,178,164,149,171,255,63,165,28,128,191,230,247,66,128,150,223,65,199,233,39,23,251,2,32,80,77,229,88,242,236,59,27,87,164,79,120,131,240,100,247,182,28,231,200,43,86,4,88,118,214,123,86,89,234,15,220,65,33,2,126,31,155,113,109,251,248,50,61,159,13,216,133,12,125,72,157,20,95,38,54,244,202,14,140,141,176,184,127,210,62,36,255,255,255,255,3,42,0,0,0,0,0,0,0,25,118,169,20,56,12,202,72,139,24,243,136,130,254,20,130,110,109,96,207,112,30,168,123,136,172,1,0,0,0,0,0,0,0,25,118,169,20,70,66,47,177,97,74,57,246,237,66,211,206,113,253,228,175,14,128,182,53,136,172,181,3,0,0,0,0,0,0,25,118,169,20,168,166,193,185,78,43,31,133,215,58,172,227,98,32,56,171,116,85,185,171,136,172,0,0,0,0,0]}],\"sendWithResults\":[{\"txid\":\"344556f1c428367689e09e29aa4dc7cfe755db1fab5cf7c1cf8d1f99b507004b\",\"status\":\"failed\"}],\"txid\":\"344556f1c428367689e09e29aa4dc7cfe755db1fab5cf7c1cf8d1f99b507004b\",\"tx\":[1,1,1,1,75,0,7,181,153,31,141,207,193,247,92,171,31,219,85,231,207,199,77,170,41,158,224,137,118,54,40,196,241,86,69,52,1,0,190,239,1,254,12,52,25,0,3,2,2,2,13,153,15,202,45,179,254,102,34,161,222,144,207,214,224,219,57,76,176,248,213,137,238,7,63,67,204,253,45,62,70,190,3,0,206,69,142,120,152,200,85,221,140,68,16,34,144,39,29,181,67,66,245,76,24,46,209,253,246,204,133,193,175,188,98,241,2,0,0,119,165,81,236,153,173,143,252,69,138,209,229,97,189,36,123,84,160,234,127,63,185,82,5,26,106,157,73,26,107,32,100,1,0,143,243,215,201,227,2,63,39,103,57,216,42,131,190,76,56,216,43,104,59,7,4,174,137,191,49,49,128,249,252,45,42,2,0,0,25,6,223,99,50,23,119,231,67,155,160,205,224,45,74,194,190,202,18,106,135,17,234,165,66,245,89,135,90,255,10,227,1,0,35,96,209,29,146,83,58,174,231,36,40,57,137,94,136,75,149,86,89,160,195,121,92,180,37,191,31,187,23,71,219,209,2,1,0,0,0,2,1,247,147,92,34,59,244,55,220,70,138,231,75,160,174,137,68,202,34,151,142,102,170,47,246,106,0,25,152,90,245,197,0,0,0,0,106,71,48,68,2,32,23,143,242,5,200,252,85,12,167,137,194,54,96,107,40,141,240,219,145,62,182,133,119,224,94,146,94,249,214,213,212,121,2,32,15,130,24,137,152,12,86,54,141,67,39,178,80,29,110,48,77,114,212,69,237,139,89,152,246,124,138,91,19,32,71,43,65,33,3,16,229,154,223,195,210,19,58,106,26,124,66,99,139,137,43,164,213,109,60,35,235,32,30,122,36,252,12,131,190,69,63,255,255,255,255,1,247,147,92,34,59,244,55,220,70,138,231,75,160,174,137,68,202,34,151,142,102,170,47,246,106,0,25,152,90,245,197,2,0,0,0,107,72,48,69,2,33,0,154,55,50,224,237,75,147,58,95,164,173,104,149,13,24,125,198,35,126,194,134,91,150,238,179,25,192,213,136,196,51,66,2,32,98,84,153,139,248,106,241,122,65,180,241,124,30,233,140,74,172,74,217,165,129,143,127,156,49,230,34,46,188,15,28,52,65,33,2,146,89,63,53,57,196,48,37,118,17,182,193,201,75,44,105,67,181,136,23,25,104,127,48,146,231,88,99,197,53,56,239,255,255,255,255,2,225,3,0,0,0,0,0,0,25,118,169,20,255,161,196,19,38,201,6,21,136,26,207,151,137,162,75,232,58,201,53,88,136,172,1,0,0,0,0,0,0,0,25,118,169,20,39,140,42,205,54,197,3,247,165,173,16,224,138,193,127,34,191,29,126,7,136,172,0,0,0,0,1,0,1,0,0,0,1,13,153,15,202,45,179,254,102,34,161,222,144,207,214,224,219,57,76,176,248,213,137,238,7,63,67,204,253,45,62,70,190,0,0,0,0,106,71,48,68,2,32,113,14,233,125,141,249,154,234,138,195,178,164,149,171,255,63,165,28,128,191,230,247,66,128,150,223,65,199,233,39,23,251,2,32,80,77,229,88,242,236,59,27,87,164,79,120,131,240,100,247,182,28,231,200,43,86,4,88,118,214,123,86,89,234,15,220,65,33,2,126,31,155,113,109,251,248,50,61,159,13,216,133,12,125,72,157,20,95,38,54,244,202,14,140,141,176,184,127,210,62,36,255,255,255,255,3,42,0,0,0,0,0,0,0,25,118,169,20,56,12,202,72,139,24,243,136,130,254,20,130,110,109,96,207,112,30,168,123,136,172,1,0,0,0,0,0,0,0,25,118,169,20,70,66,47,177,97,74,57,246,237,66,211,206,113,253,228,175,14,128,182,53,136,172,181,3,0,0,0,0,0,0,25,118,169,20,168,166,193,185,78,43,31,133,215,58,172,227,98,32,56,171,116,85,185,171,136,172,0,0,0,0,0],\"noSendChange\":[\"344556f1c428367689e09e29aa4dc7cfe755db1fab5cf7c1cf8d1f99b507004b.0\"]}`
31
+ const o = JSON.parse(s)
32
+ const e = WalletErrorFromJson(o)
33
+ expect(e instanceof WERR_REVIEW_ACTIONS).toBe(true)
34
+ })
24
35
  })
@@ -77,6 +77,7 @@ export interface TuEnv extends TuEnvFlags {
77
77
  taalApiKey: string
78
78
  bitailsApiKey: string
79
79
  whatsonchainApiKey: string
80
+ commissionsIdentity: string
80
81
  devKeys: Record<string, string>
81
82
  /**
82
83
  * file path to local sqlite file for identityKey
@@ -147,6 +148,8 @@ export abstract class TestUtilsWalletStorage {
147
148
  const bitailsApiKey = (chain === 'main' ? process.env.MAIN_BITAILS_API_KEY : process.env.TEST_BITAILS_API_KEY) || ''
148
149
  const whatsonchainApiKey =
149
150
  (chain === 'main' ? process.env.MAIN_WHATSONCHAIN_API_KEY : process.env.TEST_WHATSONCHAIN_API_KEY) || ''
151
+ const commissionsIdentity =
152
+ (chain === 'main' ? process.env.MAIN_COMMISSIONS_IDENTITY : process.env.TEST_COMMISSIONS_IDENTITY) || ''
150
153
  return {
151
154
  ...flagsEnv,
152
155
  identityKey,
@@ -154,6 +157,7 @@ export abstract class TestUtilsWalletStorage {
154
157
  taalApiKey,
155
158
  bitailsApiKey,
156
159
  whatsonchainApiKey,
160
+ commissionsIdentity,
157
161
  devKeys: JSON.parse(DEV_KEYS) as Record<string, string>,
158
162
  filePath,
159
163
  testIdentityKey,