@bsv/sdk 1.10.4 → 2.0.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.
- package/dist/cjs/mod.js +1 -0
- package/dist/cjs/mod.js.map +1 -1
- package/dist/cjs/package.json +2 -3
- package/dist/cjs/src/auth/Peer.js +18 -20
- package/dist/cjs/src/auth/Peer.js.map +1 -1
- package/dist/cjs/src/identity/IdentityClient.js +20 -124
- package/dist/cjs/src/identity/IdentityClient.js.map +1 -1
- package/dist/cjs/src/primitives/TransactionSignature.js +115 -10
- package/dist/cjs/src/primitives/TransactionSignature.js.map +1 -1
- package/dist/cjs/src/primitives/utils.js +13 -112
- package/dist/cjs/src/primitives/utils.js.map +1 -1
- package/dist/cjs/src/remittance/CommsLayer.js +3 -0
- package/dist/cjs/src/remittance/CommsLayer.js.map +1 -0
- package/dist/cjs/src/remittance/IdentityLayer.js +3 -0
- package/dist/cjs/src/remittance/IdentityLayer.js.map +1 -0
- package/dist/cjs/src/remittance/RemittanceManager.js +1245 -0
- package/dist/cjs/src/remittance/RemittanceManager.js.map +1 -0
- package/dist/cjs/src/remittance/RemittanceModule.js +3 -0
- package/dist/cjs/src/remittance/RemittanceModule.js.map +1 -0
- package/dist/cjs/src/remittance/index.js +23 -0
- package/dist/cjs/src/remittance/index.js.map +1 -0
- package/dist/cjs/src/remittance/modules/BasicBRC29.js +225 -0
- package/dist/cjs/src/remittance/modules/BasicBRC29.js.map +1 -0
- package/dist/cjs/src/remittance/modules/index.js +18 -0
- package/dist/cjs/src/remittance/modules/index.js.map +1 -0
- package/dist/cjs/src/remittance/types.js +22 -0
- package/dist/cjs/src/remittance/types.js.map +1 -0
- package/dist/cjs/src/script/OP.js +15 -13
- package/dist/cjs/src/script/OP.js.map +1 -1
- package/dist/cjs/src/script/Script.js +4 -1
- package/dist/cjs/src/script/Script.js.map +1 -1
- package/dist/cjs/src/script/Spend.js +128 -46
- package/dist/cjs/src/script/Spend.js.map +1 -1
- package/dist/cjs/src/transaction/BeefTx.js +2 -2
- package/dist/cjs/src/transaction/Transaction.js +160 -0
- package/dist/cjs/src/transaction/Transaction.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/mod.js +1 -0
- package/dist/esm/mod.js.map +1 -1
- package/dist/esm/src/auth/Peer.js +18 -20
- package/dist/esm/src/auth/Peer.js.map +1 -1
- package/dist/esm/src/identity/IdentityClient.js +20 -124
- package/dist/esm/src/identity/IdentityClient.js.map +1 -1
- package/dist/esm/src/primitives/TransactionSignature.js +115 -10
- package/dist/esm/src/primitives/TransactionSignature.js.map +1 -1
- package/dist/esm/src/primitives/utils.js +13 -112
- package/dist/esm/src/primitives/utils.js.map +1 -1
- package/dist/esm/src/remittance/CommsLayer.js +2 -0
- package/dist/esm/src/remittance/CommsLayer.js.map +1 -0
- package/dist/esm/src/remittance/IdentityLayer.js +2 -0
- package/dist/esm/src/remittance/IdentityLayer.js.map +1 -0
- package/dist/esm/src/remittance/RemittanceManager.js +1254 -0
- package/dist/esm/src/remittance/RemittanceManager.js.map +1 -0
- package/dist/esm/src/remittance/RemittanceModule.js +2 -0
- package/dist/esm/src/remittance/RemittanceModule.js.map +1 -0
- package/dist/esm/src/remittance/index.js +7 -0
- package/dist/esm/src/remittance/index.js.map +1 -0
- package/dist/esm/src/remittance/modules/BasicBRC29.js +227 -0
- package/dist/esm/src/remittance/modules/BasicBRC29.js.map +1 -0
- package/dist/esm/src/remittance/modules/index.js +2 -0
- package/dist/esm/src/remittance/modules/index.js.map +1 -0
- package/dist/esm/src/remittance/types.js +19 -0
- package/dist/esm/src/remittance/types.js.map +1 -0
- package/dist/esm/src/script/OP.js +15 -13
- package/dist/esm/src/script/OP.js.map +1 -1
- package/dist/esm/src/script/Script.js +4 -1
- package/dist/esm/src/script/Script.js.map +1 -1
- package/dist/esm/src/script/Spend.js +129 -46
- package/dist/esm/src/script/Spend.js.map +1 -1
- package/dist/esm/src/transaction/BeefTx.js +3 -3
- package/dist/esm/src/transaction/BeefTx.js.map +1 -1
- package/dist/esm/src/transaction/Transaction.js +160 -0
- package/dist/esm/src/transaction/Transaction.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/mod.d.ts +1 -0
- package/dist/types/mod.d.ts.map +1 -1
- package/dist/types/src/auth/Peer.d.ts +3 -7
- package/dist/types/src/auth/Peer.d.ts.map +1 -1
- package/dist/types/src/identity/IdentityClient.d.ts +0 -8
- package/dist/types/src/identity/IdentityClient.d.ts.map +1 -1
- package/dist/types/src/primitives/TransactionSignature.d.ts +16 -4
- package/dist/types/src/primitives/TransactionSignature.d.ts.map +1 -1
- package/dist/types/src/primitives/utils.d.ts +1 -0
- package/dist/types/src/primitives/utils.d.ts.map +1 -1
- package/dist/types/src/remittance/CommsLayer.d.ts +50 -0
- package/dist/types/src/remittance/CommsLayer.d.ts.map +1 -0
- package/dist/types/src/remittance/IdentityLayer.d.ts +35 -0
- package/dist/types/src/remittance/IdentityLayer.d.ts.map +1 -0
- package/dist/types/src/remittance/RemittanceManager.d.ts +452 -0
- package/dist/types/src/remittance/RemittanceManager.d.ts.map +1 -0
- package/dist/types/src/remittance/RemittanceModule.d.ts +106 -0
- package/dist/types/src/remittance/RemittanceModule.d.ts.map +1 -0
- package/dist/types/src/remittance/index.d.ts +7 -0
- package/dist/types/src/remittance/index.d.ts.map +1 -0
- package/dist/types/src/remittance/modules/BasicBRC29.d.ts +133 -0
- package/dist/types/src/remittance/modules/BasicBRC29.d.ts.map +1 -0
- package/dist/types/src/remittance/modules/index.d.ts +2 -0
- package/dist/types/src/remittance/modules/index.d.ts.map +1 -0
- package/dist/types/src/remittance/types.d.ts +238 -0
- package/dist/types/src/remittance/types.d.ts.map +1 -0
- package/dist/types/src/script/OP.d.ts +5 -3
- package/dist/types/src/script/OP.d.ts.map +1 -1
- package/dist/types/src/script/Script.d.ts.map +1 -1
- package/dist/types/src/script/Spend.d.ts +7 -0
- package/dist/types/src/script/Spend.d.ts.map +1 -1
- package/dist/types/src/transaction/BeefTx.d.ts +2 -2
- package/dist/types/src/transaction/Transaction.d.ts +14 -0
- package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
- package/dist/types/src/wallet/Wallet.interfaces.d.ts +5 -5
- package/dist/types/src/wallet/Wallet.interfaces.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +13 -13
- package/dist/umd/bundle.js.map +1 -1
- package/docs/index.md +2 -14
- package/docs/reference/auth.md +6 -12
- package/docs/reference/primitives.md +20 -78
- package/docs/reference/remittance.md +2166 -0
- package/docs/reference/script.md +11 -3
- package/docs/reference/transaction.md +27 -1
- package/docs/reference/wallet.md +6 -5
- package/docs/remittance-getting-started.md +138 -0
- package/mod.ts +1 -0
- package/package.json +12 -3
- package/src/auth/Peer.ts +18 -29
- package/src/auth/__tests/Peer.test.ts +253 -1
- package/src/identity/IdentityClient.ts +29 -153
- package/src/identity/__tests/IdentityClient.test.ts +1 -289
- package/src/overlay-tools/__tests/SHIPBroadcaster.test.ts +7 -9
- package/src/primitives/TransactionSignature.ts +129 -10
- package/src/primitives/__tests/utils.test.ts +30 -7
- package/src/primitives/utils.ts +13 -129
- package/src/remittance/CommsLayer.ts +41 -0
- package/src/remittance/IdentityLayer.ts +32 -0
- package/src/remittance/RemittanceManager.ts +1672 -0
- package/src/remittance/RemittanceModule.ts +92 -0
- package/src/remittance/__tests/BasicBRC29.test.ts +188 -0
- package/src/remittance/__tests/RemittanceManager.test.ts +493 -0
- package/src/remittance/__tests/examples.ts +130 -0
- package/src/remittance/index.ts +6 -0
- package/src/remittance/modules/BasicBRC29.ts +361 -0
- package/src/remittance/modules/index.ts +1 -0
- package/src/remittance/types.ts +284 -0
- package/src/script/OP.ts +15 -13
- package/src/script/Script.ts +3 -1
- package/src/script/Spend.ts +128 -52
- package/src/script/__tests/Chronicle.test.ts +186 -0
- package/src/script/__tests/Spend.test.ts +1 -1
- package/src/script/__tests/SpendValildVectors.test.ts +63 -0
- package/src/script/__tests/lrshiftnum.test.ts +185 -0
- package/src/script/__tests/sighashTestData.ts +1031 -0
- package/src/script/__tests/spend.valid.vectors.ts +9 -16
- package/src/transaction/BeefTx.ts +3 -3
- package/src/transaction/Transaction.ts +186 -0
- package/src/transaction/__tests/Beef.test.ts +2 -0
- package/src/transaction/__tests/Transaction.test.ts +641 -3
- package/src/wallet/Wallet.interfaces.ts +5 -5
|
@@ -15,41 +15,14 @@ jest.mock('../../script', () => {
|
|
|
15
15
|
}
|
|
16
16
|
})
|
|
17
17
|
|
|
18
|
-
// Store mock functions for LookupResolver so tests can configure them
|
|
19
|
-
const mockLookupResolverQuery = jest.fn()
|
|
20
|
-
|
|
21
18
|
jest.mock('../../overlay-tools/index.js', () => {
|
|
22
19
|
return {
|
|
23
20
|
TopicBroadcaster: jest.fn().mockImplementation(() => ({
|
|
24
21
|
broadcast: jest.fn().mockResolvedValue('broadcastResult')
|
|
25
|
-
})),
|
|
26
|
-
LookupResolver: jest.fn().mockImplementation(() => ({
|
|
27
|
-
query: mockLookupResolverQuery
|
|
28
|
-
}))
|
|
29
|
-
}
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
// Mock VerifiableCertificate for resolveViaLookup tests
|
|
33
|
-
const mockDecryptFields = jest.fn()
|
|
34
|
-
const mockVerify = jest.fn()
|
|
35
|
-
|
|
36
|
-
jest.mock('../../auth/certificates/VerifiableCertificate.js', () => {
|
|
37
|
-
return {
|
|
38
|
-
VerifiableCertificate: jest.fn().mockImplementation(() => ({
|
|
39
|
-
decryptFields: mockDecryptFields,
|
|
40
|
-
verify: mockVerify
|
|
41
22
|
}))
|
|
42
23
|
}
|
|
43
24
|
})
|
|
44
25
|
|
|
45
|
-
// Mock ProtoWallet for resolveViaLookup tests
|
|
46
|
-
jest.mock('../../wallet/ProtoWallet.js', () => {
|
|
47
|
-
return {
|
|
48
|
-
__esModule: true,
|
|
49
|
-
default: jest.fn().mockImplementation(() => ({}))
|
|
50
|
-
}
|
|
51
|
-
})
|
|
52
|
-
|
|
53
26
|
jest.mock('../../transaction/index.js', () => {
|
|
54
27
|
return {
|
|
55
28
|
Transaction: {
|
|
@@ -294,187 +267,6 @@ describe('IdentityClient', () => {
|
|
|
294
267
|
// Wallet method should not be called when contact is found
|
|
295
268
|
expect(walletMock.discoverByIdentityKey).not.toHaveBeenCalled()
|
|
296
269
|
})
|
|
297
|
-
|
|
298
|
-
it('should fallback to LookupResolver when no wallet is available', async () => {
|
|
299
|
-
// Mock wallet methods to throw "No wallet available" error
|
|
300
|
-
walletMock.discoverByIdentityKey = jest.fn().mockRejectedValue(
|
|
301
|
-
new Error('No wallet available over any communication substrate. Install a BSV wallet today!')
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
// Mock ContactsManager to also throw (since it requires wallet)
|
|
305
|
-
const mockContactsManager = identityClient['contactsManager']
|
|
306
|
-
mockContactsManager.getContacts = jest.fn().mockRejectedValue(
|
|
307
|
-
new Error('No wallet available')
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
// Setup mock certificate data that would come from lookup
|
|
311
|
-
const certData = {
|
|
312
|
-
type: KNOWN_IDENTITY_TYPES.xCert,
|
|
313
|
-
serialNumber: 'serial123',
|
|
314
|
-
subject: 'test-identity-key',
|
|
315
|
-
certifier: '02cf6cdf466951d8dfc9e7c9367511d0007ed6fba35ed42d425cc412fd6cfd4a17',
|
|
316
|
-
revocationOutpoint: 'outpoint',
|
|
317
|
-
fields: { userName: 'encrypted', profilePhoto: 'encrypted' },
|
|
318
|
-
keyring: { fieldKey: 'keyringValue' },
|
|
319
|
-
signature: 'sig123'
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Mock LookupResolver to return output-list with certificate data
|
|
323
|
-
mockLookupResolverQuery.mockResolvedValue({
|
|
324
|
-
type: 'output-list',
|
|
325
|
-
outputs: [{
|
|
326
|
-
beef: new Uint8Array([1, 2, 3]),
|
|
327
|
-
outputIndex: 0
|
|
328
|
-
}]
|
|
329
|
-
})
|
|
330
|
-
|
|
331
|
-
// Mock PushDrop.decode to return certificate JSON
|
|
332
|
-
const { PushDrop } = require('../../script')
|
|
333
|
-
PushDrop.decode.mockReturnValue({
|
|
334
|
-
fields: [new TextEncoder().encode(JSON.stringify(certData))]
|
|
335
|
-
})
|
|
336
|
-
|
|
337
|
-
// Mock VerifiableCertificate methods
|
|
338
|
-
mockDecryptFields.mockResolvedValue({
|
|
339
|
-
userName: 'TestUser',
|
|
340
|
-
profilePhoto: 'test-photo-url'
|
|
341
|
-
})
|
|
342
|
-
mockVerify.mockResolvedValue(true)
|
|
343
|
-
|
|
344
|
-
const identities = await identityClient.resolveByIdentityKey(
|
|
345
|
-
{ identityKey: 'test-identity-key' },
|
|
346
|
-
false // Don't override with contacts
|
|
347
|
-
)
|
|
348
|
-
|
|
349
|
-
// Verify wallet was called and threw
|
|
350
|
-
expect(walletMock.discoverByIdentityKey).toHaveBeenCalled()
|
|
351
|
-
|
|
352
|
-
// Verify LookupResolver was queried
|
|
353
|
-
expect(mockLookupResolverQuery).toHaveBeenCalledWith({
|
|
354
|
-
service: 'ls_identity',
|
|
355
|
-
query: {
|
|
356
|
-
certifiers: ['02cf6cdf466951d8dfc9e7c9367511d0007ed6fba35ed42d425cc412fd6cfd4a17'],
|
|
357
|
-
identityKey: 'test-identity-key'
|
|
358
|
-
}
|
|
359
|
-
})
|
|
360
|
-
|
|
361
|
-
// Verify results were returned from fallback
|
|
362
|
-
expect(identities).toHaveLength(1)
|
|
363
|
-
expect(identities[0].name).toBe('TestUser')
|
|
364
|
-
expect(identities[0].identityKey).toBe('test-identity-key')
|
|
365
|
-
})
|
|
366
|
-
|
|
367
|
-
it('should return empty array when LookupResolver returns no results', async () => {
|
|
368
|
-
walletMock.discoverByIdentityKey = jest.fn().mockRejectedValue(
|
|
369
|
-
new Error('No wallet available')
|
|
370
|
-
)
|
|
371
|
-
|
|
372
|
-
const mockContactsManager = identityClient['contactsManager']
|
|
373
|
-
mockContactsManager.getContacts = jest.fn().mockRejectedValue(
|
|
374
|
-
new Error('No wallet available')
|
|
375
|
-
)
|
|
376
|
-
|
|
377
|
-
// Mock LookupResolver to return empty results
|
|
378
|
-
mockLookupResolverQuery.mockResolvedValue({
|
|
379
|
-
type: 'output-list',
|
|
380
|
-
outputs: []
|
|
381
|
-
})
|
|
382
|
-
|
|
383
|
-
const identities = await identityClient.resolveByIdentityKey(
|
|
384
|
-
{ identityKey: 'nonexistent-key' },
|
|
385
|
-
false
|
|
386
|
-
)
|
|
387
|
-
|
|
388
|
-
expect(identities).toHaveLength(0)
|
|
389
|
-
})
|
|
390
|
-
|
|
391
|
-
it('should return empty array when LookupResolver returns non-output-list type', async () => {
|
|
392
|
-
walletMock.discoverByIdentityKey = jest.fn().mockRejectedValue(
|
|
393
|
-
new Error('No wallet available')
|
|
394
|
-
)
|
|
395
|
-
|
|
396
|
-
const mockContactsManager = identityClient['contactsManager']
|
|
397
|
-
mockContactsManager.getContacts = jest.fn().mockRejectedValue(
|
|
398
|
-
new Error('No wallet available')
|
|
399
|
-
)
|
|
400
|
-
|
|
401
|
-
// Mock LookupResolver to return different type
|
|
402
|
-
mockLookupResolverQuery.mockResolvedValue({
|
|
403
|
-
type: 'freeform',
|
|
404
|
-
result: {}
|
|
405
|
-
})
|
|
406
|
-
|
|
407
|
-
const identities = await identityClient.resolveByIdentityKey(
|
|
408
|
-
{ identityKey: 'some-key' },
|
|
409
|
-
false
|
|
410
|
-
)
|
|
411
|
-
|
|
412
|
-
expect(identities).toHaveLength(0)
|
|
413
|
-
})
|
|
414
|
-
|
|
415
|
-
it('should skip invalid certificates and continue processing', async () => {
|
|
416
|
-
walletMock.discoverByIdentityKey = jest.fn().mockRejectedValue(
|
|
417
|
-
new Error('No wallet available')
|
|
418
|
-
)
|
|
419
|
-
|
|
420
|
-
const mockContactsManager = identityClient['contactsManager']
|
|
421
|
-
mockContactsManager.getContacts = jest.fn().mockRejectedValue(
|
|
422
|
-
new Error('No wallet available')
|
|
423
|
-
)
|
|
424
|
-
|
|
425
|
-
// Mock LookupResolver to return multiple outputs
|
|
426
|
-
mockLookupResolverQuery.mockResolvedValue({
|
|
427
|
-
type: 'output-list',
|
|
428
|
-
outputs: [
|
|
429
|
-
{ beef: new Uint8Array([1]), outputIndex: 0 },
|
|
430
|
-
{ beef: new Uint8Array([2]), outputIndex: 0 }
|
|
431
|
-
]
|
|
432
|
-
})
|
|
433
|
-
|
|
434
|
-
// First call throws (invalid cert), second succeeds
|
|
435
|
-
const { PushDrop } = require('../../script')
|
|
436
|
-
PushDrop.decode
|
|
437
|
-
.mockImplementationOnce(() => { throw new Error('Invalid script') })
|
|
438
|
-
.mockImplementationOnce(() => ({
|
|
439
|
-
fields: [new TextEncoder().encode(JSON.stringify({
|
|
440
|
-
type: KNOWN_IDENTITY_TYPES.xCert,
|
|
441
|
-
serialNumber: 'serial',
|
|
442
|
-
subject: 'valid-key',
|
|
443
|
-
certifier: 'certifier',
|
|
444
|
-
revocationOutpoint: 'outpoint',
|
|
445
|
-
fields: {},
|
|
446
|
-
keyring: {},
|
|
447
|
-
signature: 'sig'
|
|
448
|
-
}))]
|
|
449
|
-
}))
|
|
450
|
-
|
|
451
|
-
mockDecryptFields.mockResolvedValue({ userName: 'ValidUser' })
|
|
452
|
-
mockVerify.mockResolvedValue(true)
|
|
453
|
-
|
|
454
|
-
const identities = await identityClient.resolveByIdentityKey(
|
|
455
|
-
{ identityKey: 'some-key' },
|
|
456
|
-
false
|
|
457
|
-
)
|
|
458
|
-
|
|
459
|
-
// Should have 1 identity (the valid one)
|
|
460
|
-
expect(identities).toHaveLength(1)
|
|
461
|
-
expect(identities[0].name).toBe('ValidUser')
|
|
462
|
-
})
|
|
463
|
-
|
|
464
|
-
it('should re-throw non-wallet errors', async () => {
|
|
465
|
-
walletMock.discoverByIdentityKey = jest.fn().mockRejectedValue(
|
|
466
|
-
new Error('Network timeout')
|
|
467
|
-
)
|
|
468
|
-
|
|
469
|
-
const mockContactsManager = identityClient['contactsManager']
|
|
470
|
-
mockContactsManager.getContacts = jest.fn().mockRejectedValue(
|
|
471
|
-
new Error('No wallet available')
|
|
472
|
-
)
|
|
473
|
-
|
|
474
|
-
await expect(
|
|
475
|
-
identityClient.resolveByIdentityKey({ identityKey: 'test-key' }, false)
|
|
476
|
-
).rejects.toThrow('Network timeout')
|
|
477
|
-
})
|
|
478
270
|
})
|
|
479
271
|
|
|
480
272
|
it('should throw if createAction returns no tx', async () => {
|
|
@@ -575,11 +367,7 @@ describe('IdentityClient', () => {
|
|
|
575
367
|
{
|
|
576
368
|
name: 'Alice Smith',
|
|
577
369
|
identityKey: 'alice-key',
|
|
578
|
-
avatarURL: '',
|
|
579
|
-
abbreviatedKey: 'alice-i...',
|
|
580
|
-
badgeIconURL: '',
|
|
581
|
-
badgeLabel: '',
|
|
582
|
-
badgeClickURL: ''
|
|
370
|
+
avatarURL: '', abbreviatedKey: 'alice-i...', badgeIconURL: '', badgeLabel: '', badgeClickURL: ''
|
|
583
371
|
}
|
|
584
372
|
]
|
|
585
373
|
|
|
@@ -628,82 +416,6 @@ describe('IdentityClient', () => {
|
|
|
628
416
|
expect(identities[0].name).toBe('alice@example.com') // Should be discovered identity, not contact
|
|
629
417
|
expect(mockContactsManager.getContacts).not.toHaveBeenCalled() // Should not fetch contacts
|
|
630
418
|
})
|
|
631
|
-
|
|
632
|
-
it('should fallback to LookupResolver when no wallet is available for attribute search', async () => {
|
|
633
|
-
walletMock.discoverByAttributes = jest.fn().mockRejectedValue(
|
|
634
|
-
new Error('No wallet available')
|
|
635
|
-
)
|
|
636
|
-
|
|
637
|
-
// Setup mock certificate data
|
|
638
|
-
const certData = {
|
|
639
|
-
type: KNOWN_IDENTITY_TYPES.emailCert,
|
|
640
|
-
serialNumber: 'serial123',
|
|
641
|
-
subject: 'found-identity-key',
|
|
642
|
-
certifier: '02cf6cdf466951d8dfc9e7c9367511d0007ed6fba35ed42d425cc412fd6cfd4a17',
|
|
643
|
-
revocationOutpoint: 'outpoint',
|
|
644
|
-
fields: { email: 'encrypted' },
|
|
645
|
-
keyring: { fieldKey: 'keyringValue' },
|
|
646
|
-
signature: 'sig123'
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
mockLookupResolverQuery.mockResolvedValue({
|
|
650
|
-
type: 'output-list',
|
|
651
|
-
outputs: [{
|
|
652
|
-
beef: new Uint8Array([1, 2, 3]),
|
|
653
|
-
outputIndex: 0
|
|
654
|
-
}]
|
|
655
|
-
})
|
|
656
|
-
|
|
657
|
-
const { PushDrop } = require('../../script')
|
|
658
|
-
PushDrop.decode.mockReturnValue({
|
|
659
|
-
fields: [new TextEncoder().encode(JSON.stringify(certData))]
|
|
660
|
-
})
|
|
661
|
-
|
|
662
|
-
mockDecryptFields.mockResolvedValue({ email: 'found@example.com' })
|
|
663
|
-
mockVerify.mockResolvedValue(true)
|
|
664
|
-
|
|
665
|
-
const identities = await identityClient.resolveByAttributes(
|
|
666
|
-
{ attributes: { email: 'found@example.com' } },
|
|
667
|
-
false
|
|
668
|
-
)
|
|
669
|
-
|
|
670
|
-
// Verify LookupResolver was queried with attributes
|
|
671
|
-
expect(mockLookupResolverQuery).toHaveBeenCalledWith({
|
|
672
|
-
service: 'ls_identity',
|
|
673
|
-
query: {
|
|
674
|
-
certifiers: ['02cf6cdf466951d8dfc9e7c9367511d0007ed6fba35ed42d425cc412fd6cfd4a17'],
|
|
675
|
-
attributes: { any: 'found@example.com' }
|
|
676
|
-
}
|
|
677
|
-
})
|
|
678
|
-
|
|
679
|
-
expect(identities).toHaveLength(1)
|
|
680
|
-
expect(identities[0].name).toBe('found@example.com')
|
|
681
|
-
})
|
|
682
|
-
|
|
683
|
-
it('should use multi-attribute query when multiple attributes provided', async () => {
|
|
684
|
-
walletMock.discoverByAttributes = jest.fn().mockRejectedValue(
|
|
685
|
-
new Error('No wallet available')
|
|
686
|
-
)
|
|
687
|
-
|
|
688
|
-
mockLookupResolverQuery.mockResolvedValue({
|
|
689
|
-
type: 'output-list',
|
|
690
|
-
outputs: []
|
|
691
|
-
})
|
|
692
|
-
|
|
693
|
-
await identityClient.resolveByAttributes(
|
|
694
|
-
{ attributes: { firstName: 'John', lastName: 'Doe' } },
|
|
695
|
-
false
|
|
696
|
-
)
|
|
697
|
-
|
|
698
|
-
// Verify query uses full attributes object when more than one
|
|
699
|
-
expect(mockLookupResolverQuery).toHaveBeenCalledWith({
|
|
700
|
-
service: 'ls_identity',
|
|
701
|
-
query: {
|
|
702
|
-
certifiers: ['02cf6cdf466951d8dfc9e7c9367511d0007ed6fba35ed42d425cc412fd6cfd4a17'],
|
|
703
|
-
attributes: { firstName: 'John', lastName: 'Doe' }
|
|
704
|
-
}
|
|
705
|
-
})
|
|
706
|
-
})
|
|
707
419
|
})
|
|
708
420
|
|
|
709
421
|
describe('parseIdentity', () => {
|
|
@@ -14,9 +14,16 @@ const mockResolver = {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
describe('SHIPCast', () => {
|
|
17
|
+
let consoleErrorSpy: jest.SpyInstance
|
|
18
|
+
|
|
17
19
|
beforeEach(() => {
|
|
18
20
|
mockFacilitator.send.mockReset()
|
|
19
21
|
mockResolver.query.mockReset()
|
|
22
|
+
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
consoleErrorSpy.mockRestore()
|
|
20
27
|
})
|
|
21
28
|
|
|
22
29
|
it('Handles constructor errors', () => {
|
|
@@ -339,12 +346,8 @@ describe('SHIPCast', () => {
|
|
|
339
346
|
})
|
|
340
347
|
const testTx = new Transaction(1, [], [], 0)
|
|
341
348
|
|
|
342
|
-
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { })
|
|
343
|
-
|
|
344
349
|
const result = await b.broadcast(testTx)
|
|
345
350
|
|
|
346
|
-
expect(consoleErrorSpy).toHaveBeenCalled()
|
|
347
|
-
|
|
348
351
|
expect(result).toEqual({
|
|
349
352
|
status: 'error',
|
|
350
353
|
code: 'ERR_ALL_HOSTS_REJECTED',
|
|
@@ -1080,13 +1083,8 @@ describe('SHIPCast', () => {
|
|
|
1080
1083
|
resolver: mockResolver as unknown as LookupResolver
|
|
1081
1084
|
})
|
|
1082
1085
|
const testTx = new Transaction(1, [], [], 0)
|
|
1083
|
-
|
|
1084
|
-
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { })
|
|
1085
|
-
|
|
1086
1086
|
const response = await b.broadcast(testTx)
|
|
1087
1087
|
|
|
1088
|
-
expect(consoleErrorSpy).toHaveBeenCalled()
|
|
1089
|
-
|
|
1090
1088
|
// Since the host responded (successfully in terms of HTTP), but with invalid data, we should consider it a failure
|
|
1091
1089
|
expect(response).toEqual({
|
|
1092
1090
|
status: 'error',
|
|
@@ -3,6 +3,7 @@ import BigNumber from './BigNumber.js'
|
|
|
3
3
|
import * as Hash from './Hash.js'
|
|
4
4
|
import { toArray, Writer } from './utils.js'
|
|
5
5
|
import Script from '../script/Script.js'
|
|
6
|
+
import OP from '../script/OP.js'
|
|
6
7
|
import TransactionInput from '../transaction/TransactionInput.js'
|
|
7
8
|
import TransactionOutput from '../transaction/TransactionOutput.js'
|
|
8
9
|
|
|
@@ -26,6 +27,10 @@ interface TransactionSignatureFormatParams {
|
|
|
26
27
|
lockTime: number
|
|
27
28
|
scope: number
|
|
28
29
|
cache?: SignatureHashCache
|
|
30
|
+
/**
|
|
31
|
+
* Supports running bitcoin-abc test vectors which reuses the CHRONICLE bit.
|
|
32
|
+
*/
|
|
33
|
+
ignoreChronicle?: boolean
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
const EMPTY_SCRIPT = new Uint8Array(0)
|
|
@@ -34,18 +39,99 @@ export default class TransactionSignature extends Signature {
|
|
|
34
39
|
public static readonly SIGHASH_ALL = 0x00000001
|
|
35
40
|
public static readonly SIGHASH_NONE = 0x00000002
|
|
36
41
|
public static readonly SIGHASH_SINGLE = 0x00000003
|
|
42
|
+
public static readonly SIGHASH_CHRONICLE = 0x00000020
|
|
37
43
|
public static readonly SIGHASH_FORKID = 0x00000040
|
|
38
44
|
public static readonly SIGHASH_ANYONECANPAY = 0x00000080
|
|
39
45
|
|
|
40
46
|
scope: number
|
|
41
47
|
|
|
42
48
|
/**
|
|
43
|
-
*
|
|
44
|
-
* @param params
|
|
45
|
-
* @
|
|
49
|
+
* Implements the original bitcoin transaction signature digest preimage algorithm (OTDA).
|
|
50
|
+
* @param params
|
|
51
|
+
* @returns preimage as a byte array
|
|
46
52
|
*/
|
|
47
|
-
static
|
|
48
|
-
|
|
53
|
+
static formatOTDA (params: TransactionSignatureFormatParams): Uint8Array {
|
|
54
|
+
const isAnyoneCanPay = (params.scope & TransactionSignature.SIGHASH_ANYONECANPAY) === TransactionSignature.SIGHASH_ANYONECANPAY
|
|
55
|
+
const isSingle = (params.scope & 31) === TransactionSignature.SIGHASH_SINGLE
|
|
56
|
+
const isNone = (params.scope & 31) === TransactionSignature.SIGHASH_NONE
|
|
57
|
+
const isAll = (params.scope & 31) === TransactionSignature.SIGHASH_ALL || (!isSingle && !isNone)
|
|
58
|
+
|
|
59
|
+
const subscript = new Script([...params.subscript.chunks])
|
|
60
|
+
subscript.findAndDelete(new Script().writeOpCode(OP.OP_CODESEPARATOR))
|
|
61
|
+
|
|
62
|
+
const currentInput = {
|
|
63
|
+
sourceTXID: params.sourceTXID,
|
|
64
|
+
sourceOutputIndex: params.sourceOutputIndex,
|
|
65
|
+
sequence: params.inputSequence,
|
|
66
|
+
script: subscript.toBinary()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const writer = new Writer()
|
|
70
|
+
|
|
71
|
+
function writeInputs (inputs: Array<{ sourceTXID: string, sourceOutputIndex: number, sequence: number, script: number[] }>): void {
|
|
72
|
+
writer.writeVarIntNum(inputs.length)
|
|
73
|
+
for (const input of inputs) {
|
|
74
|
+
writer.writeReverse(toArray(input.sourceTXID, 'hex'))
|
|
75
|
+
writer.writeUInt32LE(input.sourceOutputIndex)
|
|
76
|
+
writer.writeVarIntNum(input.script.length)
|
|
77
|
+
writer.write(input.script)
|
|
78
|
+
writer.writeUInt32LE(input.sequence)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function writeOutputs (outputs: Array<{ satoshis: number, script: number[] }>): void {
|
|
83
|
+
writer.writeVarIntNum(outputs.length)
|
|
84
|
+
for (const output of outputs) {
|
|
85
|
+
writer.writeUInt64LE(output.satoshis)
|
|
86
|
+
writer.writeVarIntNum(output.script.length)
|
|
87
|
+
writer.write(output.script)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Version
|
|
92
|
+
writer.writeInt32LE(params.transactionVersion)
|
|
93
|
+
|
|
94
|
+
const emptyScript = new Script().toBinary()
|
|
95
|
+
|
|
96
|
+
if (!isAnyoneCanPay) {
|
|
97
|
+
const inputs = params.otherInputs.map(input => ({
|
|
98
|
+
sourceTXID: input.sourceTXID ?? input.sourceTransaction?.id('hex') ?? '',
|
|
99
|
+
sourceOutputIndex: input.sourceOutputIndex,
|
|
100
|
+
sequence: (isSingle || isNone) ? 0 : (input.sequence ?? 0xffffffff), // Default to max sequence number
|
|
101
|
+
script: emptyScript
|
|
102
|
+
}))
|
|
103
|
+
inputs.splice(params.inputIndex, 0, currentInput)
|
|
104
|
+
writeInputs(inputs)
|
|
105
|
+
} else if (isAnyoneCanPay) {
|
|
106
|
+
writeInputs([currentInput])
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (isAll) {
|
|
110
|
+
const outputs = params.outputs.map(output => ({
|
|
111
|
+
satoshis: output.satoshis ?? 0, // Default to 0 if undefined
|
|
112
|
+
script: output.lockingScript.toBinary()
|
|
113
|
+
}))
|
|
114
|
+
writeOutputs(outputs)
|
|
115
|
+
} else if (isSingle) {
|
|
116
|
+
const outputs: Array<{ satoshis: number, script: number[] }> = []
|
|
117
|
+
for (let i = 0; i < params.inputIndex; i++) outputs.push({ satoshis: -1, script: emptyScript })
|
|
118
|
+
const o = params.outputs[params.inputIndex]
|
|
119
|
+
if (o !== undefined) { outputs.push({ satoshis: o.satoshis ?? 0, script: o.lockingScript.toBinary() }) }
|
|
120
|
+
writeOutputs(outputs)
|
|
121
|
+
} else if (isNone) {
|
|
122
|
+
writeOutputs([])
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Locktime
|
|
126
|
+
writer.writeUInt32LE(params.lockTime)
|
|
127
|
+
|
|
128
|
+
// sighashType
|
|
129
|
+
writer.writeUInt32LE(params.scope >>> 0)
|
|
130
|
+
|
|
131
|
+
const buf = writer.toUint8Array()
|
|
132
|
+
// const preimage = toHex(buf)
|
|
133
|
+
// const sighash = toHex(Hash.hash256(buf))
|
|
134
|
+
return buf
|
|
49
135
|
}
|
|
50
136
|
|
|
51
137
|
/**
|
|
@@ -54,7 +140,7 @@ export default class TransactionSignature extends Signature {
|
|
|
54
140
|
* @param params.cache - Optional `SignatureHashCache` that may already contain hashed prefixes and is populated during formatting.
|
|
55
141
|
* @returns Bytes for signing.
|
|
56
142
|
*/
|
|
57
|
-
static
|
|
143
|
+
static formatBip143 (params: TransactionSignatureFormatParams): Uint8Array {
|
|
58
144
|
const cache = params.cache
|
|
59
145
|
const currentInput = {
|
|
60
146
|
sourceTXID: params.sourceTXID,
|
|
@@ -79,7 +165,9 @@ export default class TransactionSignature extends Signature {
|
|
|
79
165
|
writer.writeUInt32LE(input.sourceOutputIndex)
|
|
80
166
|
}
|
|
81
167
|
|
|
82
|
-
|
|
168
|
+
const buf = writer.toUint8Array()
|
|
169
|
+
const ret = Hash.hash256(buf)
|
|
170
|
+
return ret
|
|
83
171
|
}
|
|
84
172
|
|
|
85
173
|
const getSequenceHash = (): number[] => {
|
|
@@ -90,7 +178,9 @@ export default class TransactionSignature extends Signature {
|
|
|
90
178
|
writer.writeUInt32LE(sequence)
|
|
91
179
|
}
|
|
92
180
|
|
|
93
|
-
|
|
181
|
+
const buf = writer.toUint8Array()
|
|
182
|
+
const ret = Hash.hash256(buf)
|
|
183
|
+
return ret
|
|
94
184
|
}
|
|
95
185
|
|
|
96
186
|
function getOutputsHash (outputIndex?: number): number[] {
|
|
@@ -120,7 +210,9 @@ export default class TransactionSignature extends Signature {
|
|
|
120
210
|
writer.write(script)
|
|
121
211
|
}
|
|
122
212
|
|
|
123
|
-
|
|
213
|
+
const buf = writer.toUint8Array()
|
|
214
|
+
const ret = Hash.hash256(buf)
|
|
215
|
+
return ret
|
|
124
216
|
}
|
|
125
217
|
|
|
126
218
|
let hashPrevouts = new Array(32).fill(0)
|
|
@@ -210,7 +302,34 @@ export default class TransactionSignature extends Signature {
|
|
|
210
302
|
// sighashType
|
|
211
303
|
writer.writeUInt32LE(params.scope >>> 0)
|
|
212
304
|
|
|
213
|
-
|
|
305
|
+
const buf = writer.toUint8Array()
|
|
306
|
+
// const preimage = toHex(buf)
|
|
307
|
+
// const sighash = toHex(Hash.hash256(buf))
|
|
308
|
+
return buf
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Formats the SIGHASH preimage for the targeted input, optionally using a cache to skip recomputing shared hash prefixes.
|
|
313
|
+
* @param params - Context for the signing input plus transaction metadata.
|
|
314
|
+
* @param params.cache - Optional cache storing previously computed `hashPrevouts`, `hashSequence`, or `hashOutputs*` values; it will be populated if present.
|
|
315
|
+
*/
|
|
316
|
+
static format (params: TransactionSignatureFormatParams): number[] {
|
|
317
|
+
return Array.from(this.formatBytes(params))
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
static formatBytes (params: TransactionSignatureFormatParams): Uint8Array {
|
|
321
|
+
const hasForkId = (params.scope & TransactionSignature.SIGHASH_FORKID) !== 0
|
|
322
|
+
const hasChronicle = params.ignoreChronicle !== true && (params.scope & TransactionSignature.SIGHASH_CHRONICLE) !== 0
|
|
323
|
+
|
|
324
|
+
if (hasForkId && !hasChronicle) {
|
|
325
|
+
return TransactionSignature.formatBip143(params)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (!hasForkId || (hasForkId && hasChronicle)) {
|
|
329
|
+
return TransactionSignature.formatOTDA(params)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return new Uint8Array(0)
|
|
214
333
|
}
|
|
215
334
|
|
|
216
335
|
// The format used in a tx
|
|
@@ -323,13 +323,17 @@ describe('verifyNotNull', () => {
|
|
|
323
323
|
})
|
|
324
324
|
})
|
|
325
325
|
|
|
326
|
-
describe('toUTF8
|
|
326
|
+
describe('toUTF8 UTF-8 decoding', () => {
|
|
327
327
|
|
|
328
|
-
it('
|
|
328
|
+
it('decodes ASCII bytes', () => {
|
|
329
|
+
expect(toUTF8([0x48, 0x65, 0x6c, 0x6c, 0x6f])).toBe('Hello')
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
it('replaces invalid 2-byte sequences with U+FFFD and continues decoding', () => {
|
|
329
333
|
// 0xC2 should expect a continuation byte 0x80–0xBF
|
|
330
334
|
const arr = [0xC2, 0x20] // 0x20 is INVALID continuation
|
|
331
335
|
const str = toUTF8(arr)
|
|
332
|
-
expect(str).toBe('\uFFFD')
|
|
336
|
+
expect(str).toBe('\uFFFD ')
|
|
333
337
|
})
|
|
334
338
|
|
|
335
339
|
it('decodes valid 3-byte sequences', () => {
|
|
@@ -337,10 +341,10 @@ describe('toUTF8 strict UTF-8 decoding (TOB-21)', () => {
|
|
|
337
341
|
expect(toUTF8(euro)).toBe('€')
|
|
338
342
|
})
|
|
339
343
|
|
|
340
|
-
it('replaces invalid 3-byte sequences', () => {
|
|
344
|
+
it('replaces invalid 3-byte sequences with U+FFFD and continues decoding', () => {
|
|
341
345
|
// Middle byte invalid
|
|
342
346
|
const arr = [0xE2, 0x20, 0xAC]
|
|
343
|
-
expect(toUTF8(arr)).toBe('\uFFFD')
|
|
347
|
+
expect(toUTF8(arr)).toBe('\uFFFD \uFFFD')
|
|
344
348
|
})
|
|
345
349
|
|
|
346
350
|
it('decodes valid 4-byte sequences into surrogate pairs', () => {
|
|
@@ -348,10 +352,10 @@ describe('toUTF8 strict UTF-8 decoding (TOB-21)', () => {
|
|
|
348
352
|
expect(toUTF8(smile)).toBe('😀')
|
|
349
353
|
})
|
|
350
354
|
|
|
351
|
-
it('replaces invalid 4-byte sequences with U+FFFD', () => {
|
|
355
|
+
it('replaces invalid 4-byte sequences with U+FFFD and continues decoding', () => {
|
|
352
356
|
// 0x9F is valid, 0x20 is INVALID continuation for byte 3
|
|
353
357
|
const arr = [0xF0, 0x9F, 0x20, 0x80]
|
|
354
|
-
expect(toUTF8(arr)).toBe('\uFFFD')
|
|
358
|
+
expect(toUTF8(arr)).toBe('\uFFFD \uFFFD')
|
|
355
359
|
})
|
|
356
360
|
|
|
357
361
|
it('replaces incomplete UTF-8 sequence at end', () => {
|
|
@@ -359,6 +363,25 @@ describe('toUTF8 strict UTF-8 decoding (TOB-21)', () => {
|
|
|
359
363
|
expect(toUTF8(arr)).toBe('\uFFFD')
|
|
360
364
|
})
|
|
361
365
|
|
|
366
|
+
it('replaces overlong sequences with U+FFFD', () => {
|
|
367
|
+
expect(toUTF8([0xC0, 0xAF])).toBe('\uFFFD\uFFFD')
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
it('replaces UTF-8 encoded surrogates with U+FFFD', () => {
|
|
371
|
+
expect(toUTF8([0xED, 0xA0, 0x80])).toBe('\uFFFD\uFFFD\uFFFD')
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
describe("toArray('utf8') UTF-8 encoding", () => {
|
|
377
|
+
it('round-trips UTF-8 strings', () => {
|
|
378
|
+
const s = 'Hello € 😀'
|
|
379
|
+
expect(toUTF8(toArray(s, 'utf8') as number[])).toBe(s)
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
it('replaces lone surrogates with U+FFFD when encoding', () => {
|
|
383
|
+
expect(toArray('\uD800', 'utf8')).toEqual([0xEF, 0xBF, 0xBD])
|
|
384
|
+
})
|
|
362
385
|
})
|
|
363
386
|
|
|
364
387
|
describe('Point.encode infinity handling', () => {
|