@bsv/sdk 1.3.5 → 1.3.7

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 (108) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/auth/Peer.js +1 -1
  3. package/dist/cjs/src/auth/Peer.js.map +1 -1
  4. package/dist/cjs/src/auth/certificates/Certificate.js +25 -6
  5. package/dist/cjs/src/auth/certificates/Certificate.js.map +1 -1
  6. package/dist/cjs/src/auth/certificates/MasterCertificate.js +99 -17
  7. package/dist/cjs/src/auth/certificates/MasterCertificate.js.map +1 -1
  8. package/dist/cjs/src/auth/certificates/VerifiableCertificate.js +3 -4
  9. package/dist/cjs/src/auth/certificates/VerifiableCertificate.js.map +1 -1
  10. package/dist/cjs/src/auth/certificates/__tests/CompletedProtoWallet.js +80 -0
  11. package/dist/cjs/src/auth/certificates/__tests/CompletedProtoWallet.js.map +1 -0
  12. package/dist/cjs/src/auth/certificates/index.js +1 -0
  13. package/dist/cjs/src/auth/certificates/index.js.map +1 -1
  14. package/dist/cjs/src/auth/utils/index.js +0 -1
  15. package/dist/cjs/src/auth/utils/index.js.map +1 -1
  16. package/dist/cjs/src/transaction/Beef.js +0 -1
  17. package/dist/cjs/src/transaction/Beef.js.map +1 -1
  18. package/dist/cjs/src/transaction/index.js +16 -3
  19. package/dist/cjs/src/transaction/index.js.map +1 -1
  20. package/dist/cjs/src/wallet/CachedKeyDeriver.js.map +1 -1
  21. package/dist/cjs/src/wallet/KeyDeriver.js +3 -2
  22. package/dist/cjs/src/wallet/KeyDeriver.js.map +1 -1
  23. package/dist/cjs/src/wallet/ProtoWallet.js +1 -1
  24. package/dist/cjs/src/wallet/ProtoWallet.js.map +1 -1
  25. package/dist/cjs/src/wallet/WalletClient.js.map +1 -1
  26. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  27. package/dist/esm/src/auth/Peer.js +1 -1
  28. package/dist/esm/src/auth/Peer.js.map +1 -1
  29. package/dist/esm/src/auth/certificates/Certificate.js +25 -6
  30. package/dist/esm/src/auth/certificates/Certificate.js.map +1 -1
  31. package/dist/esm/src/auth/certificates/MasterCertificate.js +100 -18
  32. package/dist/esm/src/auth/certificates/MasterCertificate.js.map +1 -1
  33. package/dist/esm/src/auth/certificates/VerifiableCertificate.js +3 -4
  34. package/dist/esm/src/auth/certificates/VerifiableCertificate.js.map +1 -1
  35. package/dist/esm/src/auth/certificates/__tests/CompletedProtoWallet.js +76 -0
  36. package/dist/esm/src/auth/certificates/__tests/CompletedProtoWallet.js.map +1 -0
  37. package/dist/esm/src/auth/certificates/index.js +1 -0
  38. package/dist/esm/src/auth/certificates/index.js.map +1 -1
  39. package/dist/esm/src/auth/utils/index.js +0 -1
  40. package/dist/esm/src/auth/utils/index.js.map +1 -1
  41. package/dist/esm/src/transaction/Beef.js +0 -1
  42. package/dist/esm/src/transaction/Beef.js.map +1 -1
  43. package/dist/esm/src/transaction/index.js +1 -1
  44. package/dist/esm/src/transaction/index.js.map +1 -1
  45. package/dist/esm/src/wallet/CachedKeyDeriver.js.map +1 -1
  46. package/dist/esm/src/wallet/KeyDeriver.js +3 -2
  47. package/dist/esm/src/wallet/KeyDeriver.js.map +1 -1
  48. package/dist/esm/src/wallet/ProtoWallet.js +1 -1
  49. package/dist/esm/src/wallet/ProtoWallet.js.map +1 -1
  50. package/dist/esm/src/wallet/WalletClient.js.map +1 -1
  51. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  52. package/dist/types/src/auth/Peer.d.ts +1 -1
  53. package/dist/types/src/auth/Peer.d.ts.map +1 -1
  54. package/dist/types/src/auth/certificates/Certificate.d.ts +16 -3
  55. package/dist/types/src/auth/certificates/Certificate.d.ts.map +1 -1
  56. package/dist/types/src/auth/certificates/MasterCertificate.d.ts +46 -13
  57. package/dist/types/src/auth/certificates/MasterCertificate.d.ts.map +1 -1
  58. package/dist/types/src/auth/certificates/VerifiableCertificate.d.ts +1 -1
  59. package/dist/types/src/auth/certificates/VerifiableCertificate.d.ts.map +1 -1
  60. package/dist/types/src/auth/certificates/__tests/CompletedProtoWallet.d.ts +24 -0
  61. package/dist/types/src/auth/certificates/__tests/CompletedProtoWallet.d.ts.map +1 -0
  62. package/dist/types/src/auth/certificates/index.d.ts +1 -0
  63. package/dist/types/src/auth/certificates/index.d.ts.map +1 -1
  64. package/dist/types/src/auth/utils/index.d.ts +0 -1
  65. package/dist/types/src/auth/utils/index.d.ts.map +1 -1
  66. package/dist/types/src/transaction/Beef.d.ts +0 -1
  67. package/dist/types/src/transaction/Beef.d.ts.map +1 -1
  68. package/dist/types/src/transaction/index.d.ts +1 -1
  69. package/dist/types/src/transaction/index.d.ts.map +1 -1
  70. package/dist/types/src/wallet/CachedKeyDeriver.d.ts.map +1 -1
  71. package/dist/types/src/wallet/KeyDeriver.d.ts +5 -7
  72. package/dist/types/src/wallet/KeyDeriver.d.ts.map +1 -1
  73. package/dist/types/src/wallet/ProtoWallet.d.ts.map +1 -1
  74. package/dist/types/src/wallet/Wallet.interfaces.d.ts.map +1 -1
  75. package/dist/types/src/wallet/WalletClient.d.ts +1 -2
  76. package/dist/types/src/wallet/WalletClient.d.ts.map +1 -1
  77. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  78. package/dist/umd/bundle.js +1 -1
  79. package/docs/auth.md +111 -87
  80. package/docs/wallet.md +26 -97
  81. package/package.json +1 -1
  82. package/src/auth/Peer.ts +23 -23
  83. package/src/auth/__tests/Peer.test.ts +19 -27
  84. package/src/auth/certificates/Certificate.ts +31 -8
  85. package/src/auth/certificates/MasterCertificate.ts +134 -23
  86. package/src/auth/certificates/VerifiableCertificate.ts +6 -6
  87. package/src/auth/certificates/__tests/Certificate.test.ts +45 -7
  88. package/src/auth/certificates/__tests/CompletedProtoWallet.ts +101 -0
  89. package/src/auth/certificates/__tests/MasterCertificate.test.ts +273 -0
  90. package/src/auth/certificates/__tests/VerifiableCertificate.test.ts +117 -0
  91. package/src/auth/certificates/index.ts +2 -1
  92. package/src/auth/utils/index.ts +0 -1
  93. package/src/transaction/Beef.ts +0 -2
  94. package/src/transaction/__tests/Beef.test.ts +1 -1
  95. package/src/transaction/index.ts +1 -1
  96. package/src/wallet/CachedKeyDeriver.ts +1 -2
  97. package/src/wallet/KeyDeriver.ts +18 -20
  98. package/src/wallet/ProtoWallet.ts +21 -21
  99. package/src/wallet/Wallet.interfaces.ts +8 -9
  100. package/src/wallet/WalletClient.ts +1 -2
  101. package/src/wallet/__tests/KeyDeriver.test.ts +2 -2
  102. package/dist/cjs/src/auth/utils/certificateHelpers.js +0 -51
  103. package/dist/cjs/src/auth/utils/certificateHelpers.js.map +0 -1
  104. package/dist/esm/src/auth/utils/certificateHelpers.js +0 -47
  105. package/dist/esm/src/auth/utils/certificateHelpers.js.map +0 -1
  106. package/dist/types/src/auth/utils/certificateHelpers.d.ts +0 -26
  107. package/dist/types/src/auth/utils/certificateHelpers.d.ts.map +0 -1
  108. package/src/auth/utils/certificateHelpers.ts +0 -86
@@ -1,5 +1,5 @@
1
1
  import { Certificate } from '../../../../dist/cjs/src/auth/index.js'
2
- import { ProtoWallet } from '../../../../dist/cjs/src/wallet/index.js'
2
+ import { CompletedProtoWallet } from '../../../../dist/cjs/src/auth/certificates/__tests/CompletedProtoWallet.js'
3
3
  import { Utils, PrivateKey } from '../../../../dist/cjs/src/primitives/index.js'
4
4
 
5
5
  describe('Certificate', () => {
@@ -73,7 +73,7 @@ describe('Certificate', () => {
73
73
  )
74
74
 
75
75
  // Sign the certificate
76
- const certifierWallet = new ProtoWallet(sampleCertifierPrivateKey)
76
+ const certifierWallet = new CompletedProtoWallet(sampleCertifierPrivateKey)
77
77
  await certificate.sign(certifierWallet)
78
78
 
79
79
  const serialized = certificate.toBinary(true) // Include signature
@@ -100,7 +100,7 @@ describe('Certificate', () => {
100
100
  )
101
101
 
102
102
  // Sign the certificate
103
- const certifierWallet = new ProtoWallet(sampleCertifierPrivateKey)
103
+ const certifierWallet = new CompletedProtoWallet(sampleCertifierPrivateKey)
104
104
  await certificate.sign(certifierWallet)
105
105
 
106
106
  // Verify the signature
@@ -120,7 +120,7 @@ describe('Certificate', () => {
120
120
  )
121
121
 
122
122
  // Sign the certificate
123
- const certifierWallet = new ProtoWallet(sampleCertifierPrivateKey)
123
+ const certifierWallet = new CompletedProtoWallet(sampleCertifierPrivateKey)
124
124
  await certificate.sign(certifierWallet)
125
125
 
126
126
  // Tamper with the certificate (modify a field)
@@ -172,7 +172,7 @@ describe('Certificate', () => {
172
172
  )
173
173
 
174
174
  // Sign the certificate
175
- const certifierWallet = new ProtoWallet(sampleCertifierPrivateKey)
175
+ const certifierWallet = new CompletedProtoWallet(sampleCertifierPrivateKey)
176
176
  await certificate.sign(certifierWallet)
177
177
 
178
178
  // Serialize and deserialize
@@ -223,7 +223,7 @@ describe('Certificate', () => {
223
223
  )
224
224
 
225
225
  // Sign the certificate
226
- const certifierWallet = new ProtoWallet(sampleCertifierPrivateKey)
226
+ const certifierWallet = new CompletedProtoWallet(sampleCertifierPrivateKey)
227
227
  await certificate.sign(certifierWallet)
228
228
 
229
229
  // Serialize and deserialize
@@ -266,7 +266,7 @@ describe('Certificate', () => {
266
266
  )
267
267
 
268
268
  // Sign the certificate
269
- const certifierWallet = new ProtoWallet(sampleCertifierPrivateKey)
269
+ const certifierWallet = new CompletedProtoWallet(sampleCertifierPrivateKey)
270
270
  await certificate.sign(certifierWallet)
271
271
 
272
272
  // Serialize and deserialize
@@ -279,4 +279,42 @@ describe('Certificate', () => {
279
279
  const isValid = await deserializedCertificate.verify()
280
280
  expect(isValid).toBe(true)
281
281
  })
282
+
283
+ it('should throw if already signed, and should update the certifier field if it differs from the wallet\'s public key', async () => {
284
+ // Scenario 1: Certificate already has a signature
285
+ const preSignedCertificate = new Certificate(
286
+ sampleType,
287
+ sampleSerialNumber,
288
+ sampleSubjectPubKey,
289
+ sampleCertifierPubKey, // We'll pretend this was signed by them
290
+ sampleRevocationOutpoint,
291
+ sampleFields,
292
+ 'deadbeef' // Already has a placeholder signature
293
+ )
294
+ const certifierWallet = new CompletedProtoWallet(sampleCertifierPrivateKey)
295
+
296
+ // Trying to sign again should throw
297
+ await expect(preSignedCertificate.sign(certifierWallet)).rejects.toThrow(
298
+ 'Certificate has already been signed!'
299
+ )
300
+
301
+ // Scenario 2: The certifier property is set to something different from the wallet's public key
302
+ const mismatchedCertifierPubKey = PrivateKey.fromRandom().toPublicKey().toString()
303
+ const certificateWithMismatch = new Certificate(
304
+ sampleType,
305
+ sampleSerialNumber,
306
+ sampleSubjectPubKey,
307
+ mismatchedCertifierPubKey, // Different from actual wallet key
308
+ sampleRevocationOutpoint,
309
+ sampleFields
310
+ )
311
+
312
+ // Sign the certificate; it should automatically update
313
+ // the certifier field to match the wallet's actual public key
314
+ const certifierPubKey = ((await certifierWallet.getPublicKey({ identityKey: true })).publicKey)
315
+ await certificateWithMismatch.sign(certifierWallet)
316
+ expect(certificateWithMismatch.certifier).toBe(certifierPubKey)
317
+ expect(await certificateWithMismatch.verify()).toBe(true)
318
+ })
319
+
282
320
  })
@@ -0,0 +1,101 @@
1
+ import { PrivateKey } from "mod.js"
2
+ import { ProtoWallet, Wallet, CreateActionArgs, OriginatorDomainNameStringUnder250Bytes, CreateActionResult, SignActionArgs, SignActionResult, AbortActionArgs, AbortActionResult, ListActionsArgs, ListActionsResult, InternalizeActionArgs, InternalizeActionResult, ListOutputsArgs, ListOutputsResult, RelinquishOutputArgs, RelinquishOutputResult, AcquireCertificateArgs, AcquireCertificateResult, ListCertificatesArgs, ListCertificatesResult, ProveCertificateArgs, ProveCertificateResult, RelinquishCertificateArgs, RelinquishCertificateResult, DiscoverByIdentityKeyArgs, DiscoverCertificatesResult, DiscoverByAttributesArgs, GetHeightResult, GetHeaderArgs, GetHeaderResult, KeyDeriverApi, KeyDeriver, GetPublicKeyArgs, GetPublicKeyResult, PubKeyHex } from "../../../wallet/index.js"
3
+
4
+ // Test Mock wallet which extends ProtoWallet but still implements Wallet interface
5
+ // Unsupported methods throw
6
+ export class CompletedProtoWallet extends ProtoWallet implements Wallet {
7
+ constructor(rootKeyOrKeyDeriver: PrivateKey | 'anyone' | KeyDeriverApi) {
8
+ super(rootKeyOrKeyDeriver)
9
+ if (typeof rootKeyOrKeyDeriver['identityKey'] !== 'string') {
10
+ rootKeyOrKeyDeriver = new KeyDeriver(rootKeyOrKeyDeriver as PrivateKey | 'anyone')
11
+ }
12
+ this.keyDeriver = rootKeyOrKeyDeriver as KeyDeriver
13
+ }
14
+
15
+ async getPublicKey(
16
+ args: GetPublicKeyArgs,
17
+ originator?: OriginatorDomainNameStringUnder250Bytes
18
+ ): Promise<{ publicKey: PubKeyHex }> {
19
+ if (args.privileged) {
20
+ throw new Error('no privilege support')
21
+ }
22
+ if (args.identityKey) {
23
+ return { publicKey: this.keyDeriver.rootKey.toPublicKey().toString() }
24
+ } else {
25
+ if (!args.protocolID || !args.keyID) {
26
+ throw new Error('protocolID and keyID are required if identityKey is false or undefined.')
27
+ }
28
+ return {
29
+ publicKey: this.keyDeriver
30
+ .derivePublicKey(
31
+ args.protocolID,
32
+ args.keyID,
33
+ args.counterparty || 'self',
34
+ args.forSelf
35
+ )
36
+ .toString()
37
+ }
38
+ }
39
+ }
40
+
41
+ async createAction(args: CreateActionArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
42
+ : Promise<CreateActionResult> {
43
+ throw new Error("not implemented")
44
+ }
45
+ async signAction(args: SignActionArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
46
+ : Promise<SignActionResult> {
47
+ throw new Error("not implemented")
48
+ }
49
+ async abortAction(args: AbortActionArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
50
+ : Promise<AbortActionResult> {
51
+ throw new Error("not implemented")
52
+ }
53
+ async listActions(args: ListActionsArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
54
+ : Promise<ListActionsResult> {
55
+ throw new Error("not implemented")
56
+ }
57
+ async internalizeAction(args: InternalizeActionArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
58
+ : Promise<InternalizeActionResult> {
59
+ throw new Error("not implemented")
60
+ }
61
+ async listOutputs(args: ListOutputsArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
62
+ : Promise<ListOutputsResult> {
63
+ throw new Error("not implemented")
64
+ }
65
+ async relinquishOutput(args: RelinquishOutputArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
66
+ : Promise<RelinquishOutputResult> {
67
+ throw new Error("not implemented")
68
+ }
69
+ async acquireCertificate(args: AcquireCertificateArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
70
+ : Promise<AcquireCertificateResult> {
71
+ throw new Error("not implemented")
72
+ }
73
+ async listCertificates(args: ListCertificatesArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
74
+ : Promise<ListCertificatesResult> {
75
+ throw new Error("not implemented")
76
+ }
77
+ async proveCertificate(args: ProveCertificateArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
78
+ : Promise<ProveCertificateResult> {
79
+ throw new Error("not implemented")
80
+ }
81
+ async relinquishCertificate(args: RelinquishCertificateArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
82
+ : Promise<RelinquishCertificateResult> {
83
+ throw new Error("not implemented")
84
+ }
85
+ async discoverByIdentityKey(args: DiscoverByIdentityKeyArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
86
+ : Promise<DiscoverCertificatesResult> {
87
+ throw new Error("not implemented")
88
+ }
89
+ async discoverByAttributes(args: DiscoverByAttributesArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
90
+ : Promise<DiscoverCertificatesResult> {
91
+ throw new Error("not implemented")
92
+ }
93
+ async getHeight(args: {}, originator?: OriginatorDomainNameStringUnder250Bytes)
94
+ : Promise<GetHeightResult> {
95
+ throw new Error("not implemented")
96
+ }
97
+ async getHeaderForHeight(args: GetHeaderArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
98
+ : Promise<GetHeaderResult> {
99
+ throw new Error("not implemented")
100
+ }
101
+ }
@@ -0,0 +1,273 @@
1
+ import { MasterCertificate } from '../../../../dist/cjs/src/auth/certificates/MasterCertificate.js'
2
+ import { VerifiableCertificate } from '../../../../dist/cjs/src/auth/certificates/VerifiableCertificate.js'
3
+ import { PrivateKey, SymmetricKey, Utils, Random } from '../../../../dist/cjs/src/primitives/index.js'
4
+ import { CompletedProtoWallet } from '../../../../dist/cjs/src/auth/certificates/__tests/CompletedProtoWallet.js'
5
+
6
+ describe('MasterCertificate', () => {
7
+ const subjectPrivateKey = PrivateKey.fromRandom()
8
+ const certifierPrivateKey = PrivateKey.fromRandom()
9
+
10
+ // A mock revocation outpoint for testing
11
+ const mockRevocationOutpoint = 'deadbeefdeadbeefdeadbeefdeadbeef00000000000000000000000000000000.1'
12
+
13
+ // Arbitrary certificate data (in plaintext)
14
+ const plaintextFields = {
15
+ name: 'Alice',
16
+ email: 'alice@example.com',
17
+ department: 'Engineering'
18
+ }
19
+
20
+ const subjectWallet = new CompletedProtoWallet(subjectPrivateKey)
21
+ const certifierWallet = new CompletedProtoWallet(certifierPrivateKey)
22
+ let subjectPubKey, certifierPubKey
23
+
24
+ beforeAll(async () => {
25
+ subjectPubKey = (await subjectWallet.getPublicKey({ identityKey: true })).publicKey
26
+ certifierPubKey = (await certifierWallet.getPublicKey({ identityKey: true })).publicKey
27
+ })
28
+
29
+ describe('constructor', () => {
30
+ it('should construct a MasterCertificate successfully when masterKeyring is valid', () => {
31
+ // Prepare a minimal valid MasterCertificate
32
+ const fieldSymKey = SymmetricKey.fromRandom()
33
+ const encryptedFieldValue = Utils.toBase64(
34
+ fieldSymKey.encrypt(Utils.toArray('Alice', 'utf8')) as number[]
35
+ )
36
+
37
+ const encryptedKeyForSubject = Utils.toBase64([0, 1, 2, 3])
38
+ // We assume we have the same fieldName in both `fields` and `masterKeyring`.
39
+ const fields = { name: encryptedFieldValue }
40
+ const masterKeyring = { name: encryptedKeyForSubject }
41
+
42
+ const certificate = new MasterCertificate(
43
+ Utils.toBase64(Random(16)), // type
44
+ Utils.toBase64(Random(16)), // serialNumber
45
+ subjectPubKey,
46
+ certifierPubKey,
47
+ mockRevocationOutpoint,
48
+ fields,
49
+ masterKeyring
50
+ )
51
+
52
+ expect(certificate).toBeInstanceOf(MasterCertificate)
53
+ expect(certificate.fields).toEqual(fields)
54
+ expect(certificate.masterKeyring).toEqual(masterKeyring)
55
+ expect(certificate.subject).toEqual(subjectPubKey)
56
+ expect(certificate.certifier).toEqual(certifierPubKey)
57
+ })
58
+
59
+ it('should throw if masterKeyring is missing a key for any field', () => {
60
+ const fields = { name: 'encrypted_value' }
61
+ const masterKeyring = {} // intentionally empty
62
+
63
+ expect(() => {
64
+ new MasterCertificate(
65
+ Utils.toBase64(Random(16)), // type
66
+ Utils.toBase64(Random(16)), // serialNumber
67
+ subjectPubKey,
68
+ certifierPubKey,
69
+ mockRevocationOutpoint,
70
+ fields,
71
+ masterKeyring
72
+ )
73
+ }).toThrowError(/Master keyring must contain a value for every field/)
74
+ })
75
+ })
76
+
77
+ describe('decryptFields', () => {
78
+ it('should decrypt all fields correctly using subject wallet', async () => {
79
+ // Issue a certificate for the subject, which includes a valid masterKeyring
80
+ const certificate = await MasterCertificate.issueCertificateForSubject(
81
+ certifierWallet,
82
+ subjectPubKey,
83
+ plaintextFields,
84
+ 'TEST_CERT'
85
+ )
86
+
87
+ // Now subject should be able to decrypt all fields
88
+ const decrypted = await certificate.decryptFields(subjectWallet)
89
+ expect(decrypted).toEqual(plaintextFields)
90
+ })
91
+
92
+ it('should throw if masterKeyring is empty or invalid', async () => {
93
+ // Manually create a MasterCertificate with an empty masterKeyring
94
+ expect(() => new MasterCertificate(
95
+ Utils.toBase64(Random(16)),
96
+ Utils.toBase64(Random(16)),
97
+ subjectPubKey,
98
+ certifierPubKey,
99
+ mockRevocationOutpoint,
100
+ { name: Utils.toBase64([1, 2, 3]) },
101
+ {}
102
+ )).toThrow("Master keyring must contain a value for every field. Missing key for field: \"name\"");
103
+ })
104
+
105
+ it('should throw if decryption fails for any field', async () => {
106
+ // Manually craft a scenario where the key is incorrect
107
+ const badKeyMasterKeyring = Utils.toBase64([9, 9, 9, 9]) // Not the correct key
108
+ const badKeyCertificate = new MasterCertificate(
109
+ Utils.toBase64(Random(16)),
110
+ Utils.toBase64(Random(16)),
111
+ subjectPubKey,
112
+ certifierPubKey,
113
+ mockRevocationOutpoint,
114
+ {
115
+ name: Utils.toBase64(SymmetricKey.fromRandom().encrypt(Utils.toArray('Alice', 'utf8')) as number[])
116
+ },
117
+ { name: badKeyMasterKeyring }
118
+ )
119
+
120
+ await expect(badKeyCertificate.decryptFields(subjectWallet))
121
+ .rejects
122
+ .toThrow('Failed to decrypt all master certificate fields.')
123
+ })
124
+ })
125
+
126
+ describe('createKeyringForVerifier', () => {
127
+ const verifierPrivateKey = PrivateKey.fromRandom()
128
+ const verifierWallet = new CompletedProtoWallet(verifierPrivateKey)
129
+ let verifierPubKey
130
+
131
+ let issuedCert: MasterCertificate
132
+
133
+ beforeAll(async () => {
134
+ verifierPubKey = (await verifierWallet.getPublicKey({ identityKey: true })).publicKey
135
+ issuedCert = await MasterCertificate.issueCertificateForSubject(
136
+ certifierWallet,
137
+ subjectPubKey,
138
+ plaintextFields,
139
+ 'TEST_CERT'
140
+ )
141
+ })
142
+
143
+ it('should create a verifier keyring for specified fields', async () => {
144
+ // We only want to share "name" with the verifier
145
+ const fieldsToReveal = ['name']
146
+ const keyringForVerifier = await issuedCert.createKeyringForVerifier(
147
+ subjectWallet,
148
+ verifierPubKey,
149
+ fieldsToReveal
150
+ )
151
+
152
+ // The new keyring should only contain "name"
153
+ expect(Object.keys(keyringForVerifier)).toHaveLength(1)
154
+ expect(keyringForVerifier).toHaveProperty('name')
155
+
156
+ // Now let's create a VerifiableCertificate for the verifier
157
+ const verifiableCert = new VerifiableCertificate(
158
+ issuedCert.type,
159
+ issuedCert.serialNumber,
160
+ issuedCert.subject,
161
+ issuedCert.certifier,
162
+ issuedCert.revocationOutpoint,
163
+ issuedCert.fields,
164
+ issuedCert.signature,
165
+ keyringForVerifier
166
+ )
167
+
168
+ // The verifier should successfully decrypt the "name" field
169
+ const decrypted = await verifiableCert.decryptFields(verifierWallet)
170
+ expect(decrypted).toEqual({ name: plaintextFields.name })
171
+ })
172
+
173
+ it('should throw if fields to reveal are not subset of the certificate fields', async () => {
174
+ await expect(
175
+ issuedCert.createKeyringForVerifier(subjectWallet, verifierPubKey, ['nonexistent_field'])
176
+ ).rejects.toThrow(
177
+ /Fields to reveal must be a subset of the certificate fields\. Missing the "nonexistent_field" field\./
178
+ )
179
+ })
180
+
181
+ it('should throw if the master key fails to decrypt the corresponding field', async () => {
182
+ // We'll tamper the certificate's masterKeyring so that a field key is invalid
183
+ const tamperedCert = new MasterCertificate(
184
+ issuedCert.type,
185
+ issuedCert.serialNumber,
186
+ issuedCert.subject,
187
+ issuedCert.certifier,
188
+ issuedCert.revocationOutpoint,
189
+ issuedCert.fields,
190
+ {
191
+ // Tamper: replace 'name' field with nonsense
192
+ name: Utils.toBase64([66, 66, 66]),
193
+ email: issuedCert.masterKeyring.email,
194
+ department: issuedCert.masterKeyring.department
195
+ },
196
+ issuedCert.signature
197
+ )
198
+
199
+ await expect(
200
+ tamperedCert.createKeyringForVerifier(subjectWallet, verifierPubKey, ['name'])
201
+ ).rejects.toThrow('Decryption failed!')
202
+ })
203
+
204
+ it('should support optional originator parameter', async () => {
205
+ // Just to ensure coverage for the originator-based flows
206
+ const fieldsToReveal = ['name']
207
+ const keyringForVerifier = await issuedCert.createKeyringForVerifier(
208
+ subjectWallet,
209
+ verifierPubKey,
210
+ fieldsToReveal,
211
+ 'my-originator'
212
+ )
213
+ expect(keyringForVerifier).toHaveProperty('name')
214
+ })
215
+
216
+ it('should support counterparty of "anyone"', async () => {
217
+ // Create a keyring for public disclosure of selected fields.
218
+ const fieldsToReveal = ['name']
219
+ const keyringForVerifier = await issuedCert.createKeyringForVerifier(
220
+ subjectWallet,
221
+ 'anyone',
222
+ fieldsToReveal,
223
+ 'my-originator'
224
+ )
225
+ expect(keyringForVerifier).toHaveProperty('name')
226
+ })
227
+ it('should support counterparty of "self"', async () => {
228
+ const fieldsToReveal = ['name']
229
+ const keyringForVerifier = await issuedCert.createKeyringForVerifier(
230
+ subjectWallet,
231
+ 'self',
232
+ fieldsToReveal,
233
+ 'my-originator'
234
+ )
235
+ expect(keyringForVerifier).toHaveProperty('name')
236
+ })
237
+ })
238
+
239
+ describe('issueCertificateForSubject', () => {
240
+ it('should issue a valid MasterCertificate for the given subject', async () => {
241
+ const newPlaintextFields = {
242
+ project: 'Top Secret',
243
+ clearanceLevel: 'High'
244
+ }
245
+
246
+ const revocationFn = jest.fn().mockResolvedValue(mockRevocationOutpoint)
247
+
248
+ const newCert = await MasterCertificate.issueCertificateForSubject(
249
+ certifierWallet,
250
+ subjectPubKey,
251
+ newPlaintextFields,
252
+ 'TEST_CERT',
253
+ revocationFn,
254
+ )
255
+
256
+ expect(newCert).toBeInstanceOf(MasterCertificate)
257
+ // The certificate's fields should be encrypted base64
258
+ for (const fieldName in newPlaintextFields) {
259
+ expect(newCert.fields[fieldName]).toMatch(/^[A-Za-z0-9+/]+=*$/) // base64 check
260
+ }
261
+ // The masterKeyring should also contain base64 strings
262
+ for (const fieldName in newPlaintextFields) {
263
+ expect(newCert.masterKeyring[fieldName]).toMatch(/^[A-Za-z0-9+/]+=*$/)
264
+ }
265
+ // Check revocation outpoint is from mock
266
+ expect(newCert.revocationOutpoint).toEqual(mockRevocationOutpoint)
267
+ // Check we have a signature
268
+ expect(newCert.signature).toBeDefined()
269
+ // Check that the revocationFn were called
270
+ expect(revocationFn).toHaveBeenCalledWith(newCert.serialNumber)
271
+ })
272
+ })
273
+ })
@@ -0,0 +1,117 @@
1
+ import { VerifiableCertificate } from '../../../../dist/cjs/src/auth/certificates/VerifiableCertificate.js'
2
+ import { PrivateKey, SymmetricKey, Utils } from '../../../../dist/cjs/src/primitives/index.js'
3
+ import { CompletedProtoWallet } from '../../../../dist/cjs/src/auth/certificates/__tests/CompletedProtoWallet.js'
4
+ import { Certificate } from '../../../../dist/cjs/src/auth/certificates/index.js'
5
+
6
+ describe('VerifiableCertificate', () => {
7
+ const subjectPrivateKey = PrivateKey.fromRandom()
8
+ const subjectPubKey = subjectPrivateKey.toPublicKey().toString()
9
+ const certifierPrivateKey = PrivateKey.fromRandom()
10
+ const certifierPubKey = certifierPrivateKey.toPublicKey().toString()
11
+ const verifierPrivateKey = PrivateKey.fromRandom()
12
+ const verifierPubKey = verifierPrivateKey.toPublicKey().toString()
13
+
14
+ const subjectWallet = new CompletedProtoWallet(subjectPrivateKey)
15
+ const verifierWallet = new CompletedProtoWallet(verifierPrivateKey)
16
+
17
+ const sampleType = Utils.toBase64(new Array(32).fill(1))
18
+ const sampleSerialNumber = Utils.toBase64(new Array(32).fill(2))
19
+ const sampleRevocationOutpoint = 'deadbeefdeadbeefdeadbeefdeadbeef00000000000000000000000000000000.1'
20
+
21
+ const plaintextFields = {
22
+ name: 'Alice',
23
+ email: 'alice@example.com',
24
+ organization: 'Example Corp'
25
+ }
26
+
27
+ let verifiableCert: VerifiableCertificate
28
+
29
+ beforeEach(async () => {
30
+ // For each test, we'll build a fresh VerifiableCertificate with valid encryption
31
+ const certificateFields = {}
32
+ const keyring = {}
33
+
34
+ for (const fieldName in plaintextFields) {
35
+ // Generate a random field symmetric key
36
+ const fieldSymKey = SymmetricKey.fromRandom()
37
+ // Encrypt the field's plaintext
38
+ const encryptedFieldValue = fieldSymKey.encrypt(Utils.toArray(plaintextFields[fieldName], 'utf8'))
39
+ certificateFields[fieldName] = Utils.toBase64(encryptedFieldValue as number[])
40
+
41
+ // Now encrypt the fieldSymKey for the verifier
42
+ const { ciphertext: encryptedRevelationKey } = await subjectWallet.encrypt({
43
+ plaintext: fieldSymKey.toArray(),
44
+ ...Certificate.getCertificateFieldEncryptionDetails(sampleSerialNumber, fieldName),
45
+ counterparty: verifierPubKey
46
+ })
47
+ keyring[fieldName] = Utils.toBase64(encryptedRevelationKey)
48
+ }
49
+
50
+ verifiableCert = new VerifiableCertificate(
51
+ sampleType,
52
+ sampleSerialNumber,
53
+ subjectPubKey,
54
+ certifierPubKey,
55
+ sampleRevocationOutpoint,
56
+ certificateFields,
57
+ undefined, // signature
58
+ keyring
59
+ )
60
+ })
61
+
62
+ describe('constructor', () => {
63
+ it('should create a VerifiableCertificate with all required properties', () => {
64
+ expect(verifiableCert).toBeInstanceOf(VerifiableCertificate)
65
+ expect(verifiableCert.type).toEqual(sampleType)
66
+ expect(verifiableCert.serialNumber).toEqual(sampleSerialNumber)
67
+ expect(verifiableCert.subject).toEqual(subjectPubKey)
68
+ expect(verifiableCert.certifier).toEqual(certifierPubKey)
69
+ expect(verifiableCert.revocationOutpoint).toEqual(sampleRevocationOutpoint)
70
+ expect(verifiableCert.fields).toBeDefined()
71
+ expect(verifiableCert.keyring).toBeDefined()
72
+ })
73
+ })
74
+
75
+ describe('decryptFields', () => {
76
+ it('should decrypt fields successfully when provided the correct verifier wallet and keyring', async () => {
77
+ const decrypted = await verifiableCert.decryptFields(verifierWallet)
78
+ expect(decrypted).toEqual(plaintextFields)
79
+ })
80
+
81
+ it('should fail if the verifier wallet does not have the correct private key (wrong key)', async () => {
82
+ const wrongPrivateKey = PrivateKey.fromRandom()
83
+ const wrongWallet = new CompletedProtoWallet(wrongPrivateKey)
84
+
85
+ await expect(verifiableCert.decryptFields(wrongWallet)).rejects.toThrow(
86
+ /Failed to decrypt selectively revealed certificate fields using keyring/
87
+ )
88
+ })
89
+
90
+ it('should fail if the keyring is empty or missing keys', async () => {
91
+ // Create a new VerifiableCertificate but with an empty keyring
92
+ const fields = verifiableCert.fields
93
+ const emptyKeyringCert = new VerifiableCertificate(
94
+ verifiableCert.type,
95
+ verifiableCert.serialNumber,
96
+ verifiableCert.subject,
97
+ verifiableCert.certifier,
98
+ verifiableCert.revocationOutpoint,
99
+ fields,
100
+ verifiableCert.signature,
101
+ {} // empty
102
+ )
103
+
104
+ await expect(emptyKeyringCert.decryptFields(verifierWallet)).rejects.toThrow(
105
+ 'A keyring is required to decrypt certificate fields for the verifier.'
106
+ )
107
+ })
108
+
109
+ it('should fail if the encrypted field or its key is tampered', async () => {
110
+ // Tamper the keyring so it doesn't match the field encryption
111
+ verifiableCert.keyring.name = Utils.toBase64([9, 9, 9, 9])
112
+ await expect(verifiableCert.decryptFields(verifierWallet)).rejects.toThrow(
113
+ /Failed to decrypt selectively revealed certificate fields using keyring/
114
+ )
115
+ })
116
+ })
117
+ })
@@ -1,3 +1,4 @@
1
1
  export { default as Certificate } from './Certificate.js'
2
2
  export * from './MasterCertificate.js'
3
- export * from './VerifiableCertificate.js'
3
+ export * from './VerifiableCertificate.js'
4
+ export * from './__tests/CompletedProtoWallet.js'
@@ -2,4 +2,3 @@ export * from './verifyNonce.js'
2
2
  export * from './createNonce.js'
3
3
  export * from './getVerifiableCertificates.js'
4
4
  export * from './validateCertificates.js'
5
- export * from './certificateHelpers.js'
@@ -716,5 +716,3 @@ export class Beef {
716
716
  }
717
717
  }
718
718
  }
719
-
720
- export default Beef
@@ -1,7 +1,7 @@
1
1
  // The following imports allow flag a large number type checking errors by the VsCode editor due to missing type information:
2
2
  import BeefTx from "../../../dist/cjs/src/transaction/BeefTx"
3
3
  import BeefParty from "../../../dist/cjs/src/transaction/BeefParty"
4
- import Beef, { BEEF_V1, BEEF_V2 } from "../../../dist/cjs/src/transaction/Beef"
4
+ import { Beef, BEEF_V1, BEEF_V2 } from "../../../dist/cjs/src/transaction/Beef"
5
5
  import Transaction from "../../../dist/cjs/src/transaction/Transaction"
6
6
  import { fromBase58 } from '../../../dist/cjs/src/primitives/utils'
7
7
 
@@ -6,5 +6,5 @@ export type { Broadcaster, BroadcastFailure, BroadcastResponse } from './Broadca
6
6
  export { isBroadcastResponse, isBroadcastFailure } from './Broadcaster.js'
7
7
  export type { default as ChainTracker } from './ChainTracker.js'
8
8
  export { default as BeefTx } from './BeefTx.js'
9
- export { default as Beef } from './Beef.js'
9
+ export * from './Beef.js'
10
10
  export { default as BeefParty } from './BeefParty.js'
@@ -24,7 +24,6 @@ export default class CachedKeyDeriver {
24
24
  this.maxCacheSize = options?.maxCacheSize || 1000
25
25
  }
26
26
 
27
-
28
27
  /**
29
28
  * Derives a public key based on protocol ID, key ID, and counterparty.
30
29
  * Caches the result for future calls with the same parameters.
@@ -188,7 +187,7 @@ export default class CachedKeyDeriver {
188
187
  if (this.cache.size >= this.maxCacheSize) {
189
188
  // Evict the least recently used item (first item in Map)
190
189
  const firstKey = this.cache.keys().next().value
191
- this.cache.delete(firstKey!)
190
+ this.cache.delete(firstKey)
192
191
  }
193
192
  this.cache.set(cacheKey, value)
194
193
  }