@bsv/sdk 1.2.20 → 1.2.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/dist/cjs/package.json +3 -3
  2. package/dist/cjs/src/auth/Peer.js +536 -0
  3. package/dist/cjs/src/auth/Peer.js.map +1 -0
  4. package/dist/cjs/src/auth/SessionManager.js +66 -0
  5. package/dist/cjs/src/auth/SessionManager.js.map +1 -0
  6. package/dist/cjs/src/auth/{Certificate.js → certificates/Certificate.js} +22 -26
  7. package/dist/cjs/src/auth/certificates/Certificate.js.map +1 -0
  8. package/dist/cjs/src/auth/certificates/MasterCertificate.js +79 -0
  9. package/dist/cjs/src/auth/certificates/MasterCertificate.js.map +1 -0
  10. package/dist/cjs/src/auth/certificates/VerifiableCertificate.js +49 -0
  11. package/dist/cjs/src/auth/certificates/VerifiableCertificate.js.map +1 -0
  12. package/dist/cjs/src/auth/certificates/index.js +25 -0
  13. package/dist/cjs/src/auth/certificates/index.js.map +1 -0
  14. package/dist/cjs/src/auth/clients/AuthFetch.js +411 -0
  15. package/dist/cjs/src/auth/clients/AuthFetch.js.map +1 -0
  16. package/dist/cjs/src/auth/clients/index.js +18 -0
  17. package/dist/cjs/src/auth/clients/index.js.map +1 -0
  18. package/dist/cjs/src/auth/index.js +20 -5
  19. package/dist/cjs/src/auth/index.js.map +1 -1
  20. package/dist/cjs/src/auth/transports/SimplifiedFetchTransport.js +259 -0
  21. package/dist/cjs/src/auth/transports/SimplifiedFetchTransport.js.map +1 -0
  22. package/dist/cjs/src/auth/transports/index.js +18 -0
  23. package/dist/cjs/src/auth/transports/index.js.map +1 -0
  24. package/dist/cjs/src/auth/types.js +3 -0
  25. package/dist/cjs/src/auth/types.js.map +1 -0
  26. package/dist/cjs/src/auth/utils/certificateHelpers.js +51 -0
  27. package/dist/cjs/src/auth/utils/certificateHelpers.js.map +1 -0
  28. package/dist/cjs/src/auth/utils/createNonce.js +19 -0
  29. package/dist/cjs/src/auth/utils/createNonce.js.map +1 -0
  30. package/dist/cjs/src/auth/utils/getVerifiableCertificates.js +31 -0
  31. package/dist/cjs/src/auth/utils/getVerifiableCertificates.js.map +1 -0
  32. package/dist/cjs/src/auth/utils/index.js +22 -0
  33. package/dist/cjs/src/auth/utils/index.js.map +1 -0
  34. package/dist/cjs/src/auth/utils/validateCertificates.js +42 -0
  35. package/dist/cjs/src/auth/utils/validateCertificates.js.map +1 -0
  36. package/dist/cjs/src/auth/utils/verifyNonce.js +27 -0
  37. package/dist/cjs/src/auth/utils/verifyNonce.js.map +1 -0
  38. package/dist/cjs/src/primitives/Point.js +1 -1
  39. package/dist/cjs/src/primitives/Point.js.map +1 -1
  40. package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js +1 -1
  41. package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js.map +1 -1
  42. package/dist/cjs/src/wallet/substrates/WalletWireTransceiver.js +148 -148
  43. package/dist/cjs/src/wallet/substrates/WalletWireTransceiver.js.map +1 -1
  44. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  45. package/dist/esm/src/auth/Peer.js +533 -0
  46. package/dist/esm/src/auth/Peer.js.map +1 -0
  47. package/dist/esm/src/auth/SessionManager.js +63 -0
  48. package/dist/esm/src/auth/SessionManager.js.map +1 -0
  49. package/dist/esm/src/auth/{Certificate.js → certificates/Certificate.js} +1 -2
  50. package/dist/esm/src/auth/certificates/Certificate.js.map +1 -0
  51. package/dist/esm/src/auth/certificates/MasterCertificate.js +73 -0
  52. package/dist/esm/src/auth/certificates/MasterCertificate.js.map +1 -0
  53. package/dist/esm/src/auth/certificates/VerifiableCertificate.js +44 -0
  54. package/dist/esm/src/auth/certificates/VerifiableCertificate.js.map +1 -0
  55. package/dist/esm/src/auth/certificates/index.js +4 -0
  56. package/dist/esm/src/auth/certificates/index.js.map +1 -0
  57. package/dist/esm/src/auth/clients/AuthFetch.js +409 -0
  58. package/dist/esm/src/auth/clients/AuthFetch.js.map +1 -0
  59. package/dist/esm/src/auth/clients/index.js +2 -0
  60. package/dist/esm/src/auth/clients/index.js.map +1 -0
  61. package/dist/esm/src/auth/index.js +7 -1
  62. package/dist/esm/src/auth/index.js.map +1 -1
  63. package/dist/esm/src/auth/transports/SimplifiedFetchTransport.js +258 -0
  64. package/dist/esm/src/auth/transports/SimplifiedFetchTransport.js.map +1 -0
  65. package/dist/esm/src/auth/transports/index.js +2 -0
  66. package/dist/esm/src/auth/transports/index.js.map +1 -0
  67. package/dist/esm/src/auth/types.js +2 -0
  68. package/dist/esm/src/auth/types.js.map +1 -0
  69. package/dist/esm/src/auth/utils/certificateHelpers.js +47 -0
  70. package/dist/esm/src/auth/utils/certificateHelpers.js.map +1 -0
  71. package/dist/esm/src/auth/utils/createNonce.js +16 -0
  72. package/dist/esm/src/auth/utils/createNonce.js.map +1 -0
  73. package/dist/esm/src/auth/utils/getVerifiableCertificates.js +27 -0
  74. package/dist/esm/src/auth/utils/getVerifiableCertificates.js.map +1 -0
  75. package/dist/esm/src/auth/utils/index.js +6 -0
  76. package/dist/esm/src/auth/utils/index.js.map +1 -0
  77. package/dist/esm/src/auth/utils/validateCertificates.js +38 -0
  78. package/dist/esm/src/auth/utils/validateCertificates.js.map +1 -0
  79. package/dist/esm/src/auth/utils/verifyNonce.js +24 -0
  80. package/dist/esm/src/auth/utils/verifyNonce.js.map +1 -0
  81. package/dist/esm/src/primitives/Point.js +1 -1
  82. package/dist/esm/src/primitives/Point.js.map +1 -1
  83. package/dist/esm/src/wallet/substrates/WalletWireProcessor.js +1 -1
  84. package/dist/esm/src/wallet/substrates/WalletWireProcessor.js.map +1 -1
  85. package/dist/esm/src/wallet/substrates/WalletWireTransceiver.js +1 -1
  86. package/dist/esm/src/wallet/substrates/WalletWireTransceiver.js.map +1 -1
  87. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  88. package/dist/types/src/auth/Peer.d.ts +193 -0
  89. package/dist/types/src/auth/Peer.d.ts.map +1 -0
  90. package/dist/types/src/auth/SessionManager.d.ts +42 -0
  91. package/dist/types/src/auth/SessionManager.d.ts.map +1 -0
  92. package/dist/types/src/auth/{Certificate.d.ts → certificates/Certificate.d.ts} +1 -1
  93. package/dist/types/src/auth/certificates/Certificate.d.ts.map +1 -0
  94. package/dist/types/src/auth/certificates/MasterCertificate.d.ts +38 -0
  95. package/dist/types/src/auth/certificates/MasterCertificate.d.ts.map +1 -0
  96. package/dist/types/src/auth/certificates/VerifiableCertificate.d.ts +26 -0
  97. package/dist/types/src/auth/certificates/VerifiableCertificate.d.ts.map +1 -0
  98. package/dist/types/src/auth/certificates/index.d.ts +4 -0
  99. package/dist/types/src/auth/certificates/index.d.ts.map +1 -0
  100. package/dist/types/src/auth/clients/AuthFetch.d.ts +87 -0
  101. package/dist/types/src/auth/clients/AuthFetch.d.ts.map +1 -0
  102. package/dist/types/src/auth/clients/index.d.ts +2 -0
  103. package/dist/types/src/auth/clients/index.d.ts.map +1 -0
  104. package/dist/types/src/auth/index.d.ts +7 -1
  105. package/dist/types/src/auth/index.d.ts.map +1 -1
  106. package/dist/types/src/auth/transports/SimplifiedFetchTransport.d.ts +51 -0
  107. package/dist/types/src/auth/transports/SimplifiedFetchTransport.d.ts.map +1 -0
  108. package/dist/types/src/auth/transports/index.d.ts +2 -0
  109. package/dist/types/src/auth/transports/index.d.ts.map +1 -0
  110. package/dist/types/src/auth/types.d.ts +31 -0
  111. package/dist/types/src/auth/types.d.ts.map +1 -0
  112. package/dist/types/src/auth/utils/certificateHelpers.d.ts +26 -0
  113. package/dist/types/src/auth/utils/certificateHelpers.d.ts.map +1 -0
  114. package/dist/types/src/auth/utils/createNonce.d.ts +8 -0
  115. package/dist/types/src/auth/utils/createNonce.d.ts.map +1 -0
  116. package/dist/types/src/auth/utils/getVerifiableCertificates.d.ts +13 -0
  117. package/dist/types/src/auth/utils/getVerifiableCertificates.d.ts.map +1 -0
  118. package/dist/types/src/auth/utils/index.d.ts +6 -0
  119. package/dist/types/src/auth/utils/index.d.ts.map +1 -0
  120. package/dist/types/src/auth/utils/validateCertificates.d.ts +12 -0
  121. package/dist/types/src/auth/utils/validateCertificates.d.ts.map +1 -0
  122. package/dist/types/src/auth/utils/verifyNonce.d.ts +9 -0
  123. package/dist/types/src/auth/utils/verifyNonce.d.ts.map +1 -0
  124. package/dist/types/src/primitives/Point.d.ts.map +1 -1
  125. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  126. package/dist/umd/bundle.js +1 -1
  127. package/docs/README.md +1 -0
  128. package/docs/auth.md +1193 -0
  129. package/package.json +13 -3
  130. package/src/auth/Peer.ts +600 -0
  131. package/src/auth/SessionManager.ts +71 -0
  132. package/src/auth/__tests/Peer.test.ts +599 -0
  133. package/src/auth/__tests/SessionManager.test.ts +87 -0
  134. package/src/auth/{Certificate.ts → certificates/Certificate.ts} +15 -8
  135. package/src/auth/certificates/MasterCertificate.ts +106 -0
  136. package/src/auth/certificates/VerifiableCertificate.ts +73 -0
  137. package/src/auth/certificates/__tests/Certificate.test.ts +282 -0
  138. package/src/auth/certificates/index.ts +3 -0
  139. package/src/auth/clients/AuthFetch.ts +482 -0
  140. package/src/auth/clients/index.ts +1 -0
  141. package/src/auth/index.ts +7 -1
  142. package/src/auth/transports/SimplifiedFetchTransport.ts +288 -0
  143. package/src/auth/transports/index.ts +1 -0
  144. package/src/auth/types.ts +41 -0
  145. package/src/auth/utils/__tests/cryptononce.test.ts +84 -0
  146. package/src/auth/utils/__tests/getVerifiableCertificates.test.ts +126 -0
  147. package/src/auth/utils/__tests/validateCertificates.test.ts +142 -0
  148. package/src/auth/utils/certificateHelpers.ts +86 -0
  149. package/src/auth/utils/createNonce.ts +16 -0
  150. package/src/auth/utils/getVerifiableCertificates.ts +40 -0
  151. package/src/auth/utils/index.ts +5 -0
  152. package/src/auth/utils/validateCertificates.ts +54 -0
  153. package/src/auth/utils/verifyNonce.ts +27 -0
  154. package/src/primitives/Point.ts +59 -59
  155. package/src/wallet/substrates/WalletWireProcessor.ts +1 -1
  156. package/src/wallet/substrates/WalletWireTransceiver.ts +1 -1
  157. package/dist/cjs/src/auth/Certificate.js.map +0 -1
  158. package/dist/esm/src/auth/Certificate.js.map +0 -1
  159. package/dist/types/src/auth/Certificate.d.ts.map +0 -1
  160. package/src/auth/__tests/Certificate.test.ts +0 -282
@@ -0,0 +1,71 @@
1
+ import { PeerSession } from './types.js'
2
+
3
+ /**
4
+ * Manages sessions for peers, allowing sessions to be added, retrieved, updated, and removed
5
+ * by relevant identifiers (sessionNonce and peerIdentityKey).
6
+ */
7
+ export class SessionManager {
8
+ private readonly identifierToSession: Map<string, PeerSession>
9
+
10
+ constructor () {
11
+ this.identifierToSession = new Map<string, PeerSession>()
12
+ }
13
+
14
+ /**
15
+ * Adds a session to the manager, associating it with relevant identifiers for retrieval.
16
+ *
17
+ * @param {PeerSession} session - The peer session to add.
18
+ */
19
+ addSession (session: PeerSession): void {
20
+ if (!session.sessionNonce && !session.peerIdentityKey) {
21
+ throw new Error('Invalid session: at least one of sessionNonce or peerIdentityKey is required.')
22
+ }
23
+
24
+ if (session.sessionNonce) {
25
+ this.identifierToSession.set(session.sessionNonce, session)
26
+ }
27
+ if (session.peerIdentityKey) {
28
+ this.identifierToSession.set(session.peerIdentityKey, session)
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Updates a session in the manager, ensuring that all identifiers are correctly associated.
34
+ *
35
+ * @param {PeerSession} session - The peer session to update.
36
+ */
37
+ updateSession (session: PeerSession): void {
38
+ this.removeSession(session)
39
+ this.addSession(session)
40
+ }
41
+
42
+ /**
43
+ * Retrieves a session based on a given identifier.
44
+ *
45
+ * @param {string} identifier - The identifier for the session (sessionNonce or peerIdentityKey).
46
+ * @returns {PeerSession | undefined} - The matching peer session, or undefined if not found.
47
+ */
48
+ getSession (identifier: string): PeerSession | undefined {
49
+ return this.identifierToSession.get(identifier)
50
+ }
51
+
52
+ /**
53
+ * Removes a session from the manager by clearing all associated identifiers.
54
+ *
55
+ * @param {PeerSession} session - The peer session to remove.
56
+ */
57
+ removeSession (session: PeerSession): void {
58
+ this.identifierToSession.delete(session.sessionNonce)
59
+ this.identifierToSession.delete(session.peerIdentityKey)
60
+ }
61
+
62
+ /**
63
+ * Checks if a session exists based on a given identifier.
64
+ *
65
+ * @param {string} identifier - The identifier to check.
66
+ * @returns {boolean} - True if the session exists, false otherwise.
67
+ */
68
+ hasSession (identifier: string): boolean {
69
+ return this.identifierToSession.has(identifier)
70
+ }
71
+ }
@@ -0,0 +1,599 @@
1
+ import { Peer } from "../../../dist/cjs/src/auth/Peer.js"
2
+ import { AuthMessage, Transport } from "../../../dist/cjs/src/auth/types.js"
3
+ import { jest } from '@jest/globals'
4
+ import { Wallet } from "../../../dist/cjs/src/wallet/Wallet.interfaces.js"
5
+ import { ProtoWallet } from '../../../dist/cjs/src/wallet/index.js'
6
+ import { Utils, PrivateKey, SymmetricKey } from '../../../dist/cjs/src/primitives/index.js'
7
+ import { VerifiableCertificate, } from "../../../dist/cjs/src/auth/certificates/VerifiableCertificate.js"
8
+ import { MasterCertificate } from '../../../dist/cjs/src/auth/certificates/MasterCertificate.js'
9
+ import { getVerifiableCertificates } from '../../../dist/cjs/src/auth/utils/getVerifiableCertificates.js'
10
+ jest.mock('../../../dist/cjs/src/auth/utils/getVerifiableCertificates.js')
11
+
12
+ /**
13
+ * A helper function to decrypt a VerifiableCertificate's fields using the provided wallets.
14
+ */
15
+ async function decryptCertificateFields(
16
+ cert: VerifiableCertificate,
17
+ localWallet: Wallet,
18
+ counterpartyWallet: Wallet
19
+ ): Promise<Record<string, string>> {
20
+ const entries = await Promise.all(
21
+ Object.entries(cert.keyring).map(async ([fieldName, encryptedKey]) => {
22
+ // Decrypt the per-field symmetric key
23
+ const { plaintext: masterFieldKey } = await localWallet.decrypt({
24
+ ciphertext: Utils.toArray(encryptedKey, 'base64'),
25
+ protocolID: [2, 'certificate field encryption'],
26
+ keyID: `${cert.serialNumber} ${fieldName}`,
27
+ counterparty: (await counterpartyWallet.getPublicKey({ identityKey: true })).publicKey,
28
+ })
29
+
30
+ // Decrypt the actual field contents using the decrypted symmetric key
31
+ try {
32
+ const decryptedData = new SymmetricKey(masterFieldKey).decrypt(
33
+ Utils.toArray(cert.fields[fieldName], 'base64')
34
+ )
35
+ return { key: fieldName, value: Utils.toUTF8(decryptedData as number[]) }
36
+ } catch (_) {
37
+ throw new Error(`Decryption of the "${fieldName}" field with its revelation key failed.`)
38
+ }
39
+ })
40
+ )
41
+
42
+ return entries.reduce((acc, { key, value }) => {
43
+ acc[key] = value
44
+ return acc
45
+ }, {} as Record<string, string>)
46
+ }
47
+
48
+ class LocalTransport implements Transport {
49
+ private peerTransport?: LocalTransport
50
+ private onDataCallback?: (message: AuthMessage) => void
51
+
52
+ connect(peerTransport: LocalTransport): void {
53
+ this.peerTransport = peerTransport
54
+ peerTransport.peerTransport = this
55
+ }
56
+
57
+ async send(message: AuthMessage): Promise<void> {
58
+ if (this.peerTransport && this.peerTransport.onDataCallback) {
59
+ // Simulate message delivery by calling the onData callback of the peer
60
+ this.peerTransport.onDataCallback(message)
61
+ } else {
62
+ throw new Error("Peer transport is not connected or not listening for data.")
63
+ }
64
+ }
65
+
66
+ async onData(callback: (message: AuthMessage) => Promise<void>): Promise<void> {
67
+ this.onDataCallback = callback
68
+ }
69
+ }
70
+
71
+ describe('Peer class mutual authentication and certificate exchange', () => {
72
+ let walletA: Wallet, walletB: Wallet
73
+ let transportA: LocalTransport, transportB: LocalTransport
74
+ let alice: Peer, bob: Peer
75
+ let certificatesReceivedByAlice: VerifiableCertificate[] | undefined
76
+ let certificatesReceivedByBob: VerifiableCertificate[] | undefined
77
+
78
+ const certificateType = Utils.toBase64(new Array(32).fill(1))
79
+ const certificateSerialNumber = Utils.toBase64(new Array(32).fill(2))
80
+ const certifierPrivateKey = PrivateKey.fromRandom()
81
+ const certifierPublicKey = certifierPrivateKey.toPublicKey().toString()
82
+ const certificatesToRequest = {
83
+ certifiers: [certifierPublicKey],
84
+ types: { [certificateType]: ['name', 'email'] }
85
+ }
86
+
87
+ const aliceFields = { name: 'Alice', email: 'alice@example.com', libraryCardNumber: 'A123456' }
88
+ const bobFields = { name: 'Bob', email: 'bob@example.com', libraryCardNumber: 'B654321' }
89
+
90
+ async function createMasterCertificate(wallet: Wallet, fields: Record<string, string>) {
91
+ const certificateFields: Record<string, string> = {}
92
+ const masterKeyring: Record<string, string> = {}
93
+
94
+ for (const fieldName in fields) {
95
+ const fieldSymmetricKey = SymmetricKey.fromRandom()
96
+ const encryptedFieldValue = fieldSymmetricKey.encrypt(Utils.toArray(fields[fieldName], 'utf8'))
97
+ certificateFields[fieldName] = Utils.toBase64(encryptedFieldValue as number[])
98
+
99
+ const encryptedFieldKey = await wallet.encrypt({
100
+ plaintext: fieldSymmetricKey.toArray(),
101
+ protocolID: [2, 'certificate field encryption'],
102
+ keyID: `${certificateSerialNumber} ${fieldName}`,
103
+ counterparty: 'self'
104
+ })
105
+ masterKeyring[fieldName] = Utils.toBase64(encryptedFieldKey.ciphertext)
106
+ }
107
+
108
+ return new MasterCertificate(
109
+ certificateType,
110
+ certificateSerialNumber,
111
+ (await wallet.getPublicKey({ identityKey: true })).publicKey,
112
+ certifierPublicKey,
113
+ 'revocationOutpoint',
114
+ certificateFields,
115
+ masterKeyring
116
+ )
117
+ }
118
+
119
+ async function createVerifiableCertificate(
120
+ masterCertificate: MasterCertificate,
121
+ wallet: Wallet,
122
+ verifierIdentityKey: string,
123
+ fieldsToReveal: string[]
124
+ ): Promise<VerifiableCertificate> {
125
+ const certifierWallet = new ProtoWallet(certifierPrivateKey)
126
+ await masterCertificate.sign(certifierWallet)
127
+
128
+ const keyringForVerifier = await masterCertificate.createKeyringForVerifier(wallet, verifierIdentityKey, fieldsToReveal)
129
+ return new VerifiableCertificate(
130
+ masterCertificate.type,
131
+ masterCertificate.serialNumber,
132
+ masterCertificate.subject,
133
+ masterCertificate.certifier,
134
+ masterCertificate.revocationOutpoint,
135
+ masterCertificate.fields,
136
+ masterCertificate.signature,
137
+ keyringForVerifier
138
+ )
139
+ }
140
+
141
+ function setupPeers(
142
+ aliceRequests: boolean,
143
+ bobRequests: boolean,
144
+ options: {
145
+ aliceCertsToRequest?: typeof certificatesToRequest,
146
+ bobCertsToRequest?: typeof certificatesToRequest
147
+ } = {}
148
+ ) {
149
+ const { aliceCertsToRequest = certificatesToRequest, bobCertsToRequest = certificatesToRequest } = options
150
+
151
+ alice = new Peer(walletA, transportA, aliceRequests ? aliceCertsToRequest : undefined)
152
+ bob = new Peer(walletB, transportB, bobRequests ? bobCertsToRequest : undefined)
153
+
154
+ const aliceReceivedCertificates = new Promise<void>((resolve) => {
155
+ alice.listenForCertificatesReceived((senderPublicKey, certificates) => {
156
+ certificatesReceivedByAlice = certificates
157
+ resolve()
158
+ })
159
+ })
160
+
161
+ const bobReceivedCertificates = new Promise<void>((resolve) => {
162
+ bob.listenForCertificatesReceived((senderPublicKey, certificates) => {
163
+ certificatesReceivedByBob = certificates
164
+ resolve()
165
+ })
166
+ })
167
+
168
+ return { aliceReceivedCertificates, bobReceivedCertificates }
169
+ }
170
+
171
+ function mockGetVerifiableCertificates(
172
+ aliceCertificate: VerifiableCertificate | undefined,
173
+ bobCertificate: VerifiableCertificate | undefined,
174
+ alicePubKey: string,
175
+ bobPubKey: string
176
+ ) {
177
+ (getVerifiableCertificates as jest.Mock).mockImplementation((wallet, _, verifierIdentityKey) => {
178
+ if (wallet === walletA && verifierIdentityKey === bobPubKey) {
179
+ return aliceCertificate ? Promise.resolve([aliceCertificate]) : Promise.resolve([])
180
+ } else if (wallet === walletB && verifierIdentityKey === alicePubKey) {
181
+ return bobCertificate ? Promise.resolve([bobCertificate]) : Promise.resolve([])
182
+ }
183
+ return Promise.resolve([])
184
+ });
185
+ }
186
+
187
+ beforeEach(async () => {
188
+ transportA = new LocalTransport()
189
+ transportB = new LocalTransport()
190
+ transportA.connect(transportB)
191
+
192
+ certificatesReceivedByAlice = []
193
+ certificatesReceivedByBob = []
194
+
195
+ walletA = new ProtoWallet(PrivateKey.fromRandom())
196
+ walletB = new ProtoWallet(PrivateKey.fromRandom())
197
+ })
198
+
199
+ it('Neither Alice nor Bob request certificates, mutual authentication completes successfully', async () => {
200
+ const { aliceReceivedCertificates, bobReceivedCertificates } = setupPeers(false, false)
201
+
202
+ const bobReceivedGeneralMessage = new Promise<void>((resolve) => {
203
+ bob.listenForGeneralMessages(async (senderPublicKey, payload) => {
204
+ console.log('Bob received message:', Utils.toUTF8(payload))
205
+ await bob.toPeer(Utils.toArray('Hello Alice!'), senderPublicKey)
206
+ resolve()
207
+ })
208
+ })
209
+ const aliceReceivedGeneralMessage = new Promise<void>((resolve) => {
210
+ alice.listenForGeneralMessages(async (senderPublicKey, payload) => {
211
+ console.log('Alice received message:', Utils.toUTF8(payload))
212
+ resolve()
213
+ })
214
+ })
215
+
216
+ await alice.toPeer(Utils.toArray('Hello Bob!'))
217
+ await bobReceivedGeneralMessage
218
+ await aliceReceivedGeneralMessage
219
+
220
+ expect(certificatesReceivedByAlice).toEqual([])
221
+ expect(certificatesReceivedByBob).toEqual([])
222
+ }, 15000)
223
+
224
+ it('Bob requests certificates from Alice, Alice does not request any from Bob', async () => {
225
+ const alicePubKey = (await walletA.getPublicKey({ identityKey: true })).publicKey
226
+ const bobPubKey = (await walletB.getPublicKey({ identityKey: true })).publicKey
227
+
228
+ const aliceMasterCertificate = await createMasterCertificate(walletA, aliceFields)
229
+ const aliceVerifiableCertificate = await createVerifiableCertificate(
230
+ aliceMasterCertificate,
231
+ walletA,
232
+ bobPubKey,
233
+ certificatesToRequest.types[certificateType]
234
+ )
235
+
236
+ const { bobReceivedCertificates } = setupPeers(false, true)
237
+ mockGetVerifiableCertificates(aliceVerifiableCertificate, undefined, alicePubKey, bobPubKey)
238
+
239
+ const bobReceivedGeneralMessage = new Promise<void>((resolve) => {
240
+ bob.listenForGeneralMessages(async (senderPublicKey, payload) => {
241
+ await bobReceivedCertificates
242
+ if (certificatesReceivedByBob?.length !== 0) {
243
+ certificatesReceivedByBob?.forEach(async cert => {
244
+ // Decrypt to ensure it has the correct fields
245
+ const decryptedFields = await decryptCertificateFields(cert, walletB, walletA)
246
+ if (cert.certifier !== 'bob') {
247
+ console.log('Bob accepted the message:', Utils.toUTF8(payload))
248
+ console.log('Decrypted fields:', decryptedFields)
249
+ }
250
+ })
251
+ }
252
+ resolve()
253
+ })
254
+ })
255
+
256
+ await alice.toPeer(Utils.toArray('Hello Bob!'))
257
+ await bobReceivedGeneralMessage
258
+
259
+ expect(certificatesReceivedByAlice).toEqual([])
260
+ expect(certificatesReceivedByBob).toEqual([aliceVerifiableCertificate])
261
+ }, 15000)
262
+
263
+ it('Alice requests Bob to present his library card before lending him a book', async () => {
264
+ const alicePubKey = (await walletA.getPublicKey({ identityKey: true })).publicKey
265
+ const bobPubKey = (await walletB.getPublicKey({ identityKey: true })).publicKey
266
+
267
+ // Bob's certificate includes his library card number
268
+ const bobMasterCertificate = await createMasterCertificate(walletB, bobFields)
269
+ const bobVerifiableCertificate = await createVerifiableCertificate(
270
+ bobMasterCertificate,
271
+ walletB,
272
+ alicePubKey,
273
+ ['libraryCardNumber']
274
+ )
275
+
276
+ // Alice requires Bob to present his library card number
277
+ const aliceCertificatesToRequest = {
278
+ certifiers: [certifierPublicKey],
279
+ types: { [certificateType]: ['libraryCardNumber'] }
280
+ }
281
+
282
+ const { aliceReceivedCertificates } = setupPeers(true, false, { aliceCertsToRequest: aliceCertificatesToRequest })
283
+ mockGetVerifiableCertificates(undefined, bobVerifiableCertificate, alicePubKey, bobPubKey)
284
+
285
+ const aliceAcceptedLibraryCard = jest.fn()
286
+
287
+ alice.listenForCertificatesReceived(async (senderPublicKey, certificates) => {
288
+ for (const cert of certificates) {
289
+ // Decrypt Bob's certificate fields
290
+ const decryptedFields = await decryptCertificateFields(cert, walletA, walletB)
291
+
292
+ // Check and use the decrypted fields
293
+ if (Object.keys(decryptedFields).length !== 0 && decryptedFields.libraryCardNumber) {
294
+ console.log(`Alice received Bob's library card number: ${decryptedFields.libraryCardNumber}`)
295
+ aliceAcceptedLibraryCard()
296
+ }
297
+ }
298
+ })
299
+
300
+ const bobReceivedGeneralMessage = new Promise<void>((resolve) => {
301
+ bob.listenForGeneralMessages((senderPublicKey, payload) => {
302
+ console.log('Bob received message from Alice:', Utils.toUTF8(payload))
303
+ resolve()
304
+ })
305
+ })
306
+
307
+ // Alice sends a message to Bob requesting his library card before lending him a book
308
+ await alice.toPeer(Utils.toArray('Please present your library card to borrow a book.'))
309
+ await aliceReceivedCertificates
310
+ await bobReceivedGeneralMessage
311
+
312
+ expect(aliceAcceptedLibraryCard).toHaveBeenCalled()
313
+ expect(certificatesReceivedByAlice).toEqual([bobVerifiableCertificate])
314
+ expect(certificatesReceivedByBob).toEqual([]) // Bob did not request any certificates
315
+ }, 15000)
316
+
317
+ it('Bob requests additional certificates from Alice after initial communication', async () => {
318
+ const alicePubKey = (await walletA.getPublicKey({ identityKey: true })).publicKey
319
+ const bobPubKey = (await walletB.getPublicKey({ identityKey: true })).publicKey
320
+
321
+ const aliceMasterCertificate = await createMasterCertificate(walletA, { name: 'Alice' })
322
+ const aliceVerifiableCertificate = await createVerifiableCertificate(
323
+ aliceMasterCertificate,
324
+ walletA,
325
+ bobPubKey,
326
+ ['name']
327
+ )
328
+
329
+ const { bobReceivedCertificates } = setupPeers(false, true)
330
+ mockGetVerifiableCertificates(aliceVerifiableCertificate, undefined, alicePubKey, bobPubKey)
331
+
332
+ const bobReceivedGeneralMessage = new Promise<void>((resolve) => {
333
+ bob.listenForGeneralMessages(async (senderPublicKey, payload) => {
334
+ await bobReceivedCertificates
335
+ console.log('Bob received message:', Utils.toUTF8(payload))
336
+
337
+ // Bob requests additional certificates after initial communication
338
+ await bob.requestCertificates(certificatesToRequest, senderPublicKey)
339
+ resolve()
340
+ })
341
+ })
342
+
343
+ // Initial communication from Alice
344
+ await alice.toPeer(Utils.toArray('Hello Bob!'))
345
+ await bobReceivedGeneralMessage
346
+
347
+ // Listen for certificates received from the additional request
348
+ const bobReceivedAdditionalCertificates = new Promise<void>((resolve) => {
349
+ bob.listenForCertificatesReceived(async (senderPublicKey, certificates) => {
350
+ if (certificates.length > 0) {
351
+ // Decrypt to confirm
352
+ for (const cert of certificates) {
353
+ const decrypted = await decryptCertificateFields(cert, walletB, walletA)
354
+ console.log('Bob received additional certificates from Alice:', cert)
355
+ console.log('Decrypted fields:', decrypted)
356
+ }
357
+ resolve()
358
+ }
359
+ })
360
+ })
361
+
362
+ await bobReceivedAdditionalCertificates
363
+
364
+ expect(certificatesReceivedByBob).toEqual([aliceVerifiableCertificate])
365
+ }, 15000)
366
+
367
+ it('Bob requests Alice to provide her membership status before granting access to premium content', async () => {
368
+ const alicePubKey = (await walletA.getPublicKey({ identityKey: true })).publicKey
369
+ const bobPubKey = (await walletB.getPublicKey({ identityKey: true })).publicKey
370
+
371
+ // Alice's certificate includes her membership status
372
+ const aliceMasterCertificate = await createMasterCertificate(walletA, { ...aliceFields, membershipStatus: 'Gold' })
373
+ const aliceVerifiableCertificate = await createVerifiableCertificate(
374
+ aliceMasterCertificate,
375
+ walletA,
376
+ bobPubKey,
377
+ ['membershipStatus']
378
+ )
379
+
380
+ // Bob requires Alice to present her membership status
381
+ const bobCertificatesToRequest = {
382
+ certifiers: [certifierPublicKey],
383
+ types: { [certificateType]: ['membershipStatus'] }
384
+ }
385
+
386
+ const { bobReceivedCertificates } = setupPeers(false, true, { bobCertsToRequest: bobCertificatesToRequest })
387
+ mockGetVerifiableCertificates(aliceVerifiableCertificate, undefined, alicePubKey, bobPubKey)
388
+
389
+ const bobAcceptedMembershipStatus = jest.fn()
390
+
391
+ const waitForCerts = new Promise<void>((resolve) => {
392
+ bob.listenForCertificatesReceived(async (senderPublicKey, certificates) => {
393
+ for (const cert of certificates) {
394
+ // Decrypt Alice's certificate fields
395
+ const decryptedFields = await decryptCertificateFields(cert, walletB, walletA)
396
+ if (decryptedFields.membershipStatus) {
397
+ console.log(`Bob received Alice's membership status: ${decryptedFields.membershipStatus}`)
398
+ bobAcceptedMembershipStatus()
399
+ resolve()
400
+ }
401
+ }
402
+ })
403
+ })
404
+
405
+ const bobReceivedGeneralMessage = new Promise<void>((resolve) => {
406
+ bob.listenForGeneralMessages((senderPublicKey, payload) => {
407
+ console.log('Bob received message from Alice:', Utils.toUTF8(payload))
408
+ resolve()
409
+ })
410
+ })
411
+
412
+ // Alice sends a message to Bob requesting access to premium content
413
+ await alice.toPeer(Utils.toArray('I would like to access the premium content.'))
414
+ await bobReceivedCertificates
415
+ await bobReceivedGeneralMessage
416
+ await waitForCerts
417
+
418
+ expect(bobAcceptedMembershipStatus).toHaveBeenCalled()
419
+ expect(certificatesReceivedByBob).toEqual([aliceVerifiableCertificate])
420
+ expect(certificatesReceivedByAlice).toEqual([]) // Alice did not request any certificates
421
+ }, 15000)
422
+
423
+ it('Both peers require each other\'s driver\'s license before carpooling', async () => {
424
+ const alicePubKey = (await walletA.getPublicKey({ identityKey: true })).publicKey
425
+ const bobPubKey = (await walletB.getPublicKey({ identityKey: true })).publicKey
426
+
427
+ // Both Alice and Bob have driver's license certificates
428
+ const aliceMasterCertificate = await createMasterCertificate(walletA, { ...aliceFields, driversLicenseNumber: 'DLA123456' })
429
+ const aliceVerifiableCertificate = await createVerifiableCertificate(
430
+ aliceMasterCertificate,
431
+ walletA,
432
+ bobPubKey,
433
+ ['driversLicenseNumber']
434
+ )
435
+
436
+ const bobMasterCertificate = await createMasterCertificate(walletB, { ...bobFields, driversLicenseNumber: 'DLB654321' })
437
+ const bobVerifiableCertificate = await createVerifiableCertificate(
438
+ bobMasterCertificate,
439
+ walletB,
440
+ alicePubKey,
441
+ ['driversLicenseNumber']
442
+ )
443
+
444
+ // Both peers require the driver's license number
445
+ const certificatesToRequestDriversLicense = {
446
+ certifiers: [certifierPublicKey],
447
+ types: { [certificateType]: ['driversLicenseNumber'] }
448
+ }
449
+
450
+ const { aliceReceivedCertificates, bobReceivedCertificates } = setupPeers(true, true, {
451
+ aliceCertsToRequest: certificatesToRequestDriversLicense,
452
+ bobCertsToRequest: certificatesToRequestDriversLicense
453
+ })
454
+ mockGetVerifiableCertificates(aliceVerifiableCertificate, bobVerifiableCertificate, alicePubKey, bobPubKey)
455
+
456
+ const aliceAcceptedBobDL = jest.fn()
457
+ const bobAcceptedAliceDL = jest.fn()
458
+
459
+ const waitForAliceToAcceptBobDL = new Promise<void>((resolve) => {
460
+ alice.listenForCertificatesReceived(async (senderPublicKey, certificates) => {
461
+ for (const cert of certificates) {
462
+ const decryptedFields = await decryptCertificateFields(cert, walletA, walletB)
463
+ if (decryptedFields.driversLicenseNumber) {
464
+ console.log(`Alice received Bob's driver's license number: ${decryptedFields.driversLicenseNumber}`)
465
+ aliceAcceptedBobDL()
466
+ resolve()
467
+ }
468
+ }
469
+ })
470
+ })
471
+
472
+ const waitForBobToAcceptAliceDL = new Promise<void>((resolve) => {
473
+ bob.listenForCertificatesReceived(async (senderPublicKey, certificates) => {
474
+ for (const cert of certificates) {
475
+ const decryptedFields = await decryptCertificateFields(cert, walletB, walletA)
476
+ if (decryptedFields.driversLicenseNumber) {
477
+ console.log(`Bob received Alice's driver's license number: ${decryptedFields.driversLicenseNumber}`)
478
+ bobAcceptedAliceDL()
479
+ resolve()
480
+ }
481
+ }
482
+ })
483
+ })
484
+
485
+ const bobReceivedGeneralMessage = new Promise<void>((resolve) => {
486
+ bob.listenForGeneralMessages(async (senderPublicKey, payload) => {
487
+ console.log('Bob received message from Alice:', Utils.toUTF8(payload))
488
+ await bob.toPeer(Utils.toArray('Looking forward to carpooling!'), senderPublicKey)
489
+ resolve()
490
+ })
491
+ })
492
+
493
+ const aliceReceivedGeneralMessage = new Promise<void>((resolve) => {
494
+ alice.listenForGeneralMessages((senderPublicKey, payload) => {
495
+ console.log('Alice received message from Bob:', Utils.toUTF8(payload))
496
+ resolve()
497
+ })
498
+ })
499
+
500
+ // Alice initiates the conversation
501
+ await alice.toPeer(Utils.toArray('Please share your driver\'s license number for carpooling.'))
502
+ await aliceReceivedCertificates
503
+ await bobReceivedCertificates
504
+ await bobReceivedGeneralMessage
505
+ await aliceReceivedGeneralMessage
506
+
507
+ // Wait for both sides to fully accept each other's certificate
508
+ await waitForAliceToAcceptBobDL
509
+ await waitForBobToAcceptAliceDL
510
+
511
+ expect(aliceAcceptedBobDL).toHaveBeenCalled()
512
+ expect(bobAcceptedAliceDL).toHaveBeenCalled()
513
+ expect(certificatesReceivedByAlice).toEqual([bobVerifiableCertificate])
514
+ expect(certificatesReceivedByBob).toEqual([aliceVerifiableCertificate])
515
+ }, 20000)
516
+
517
+ it('Peers accept partial certificates if at least one required field is present', async () => {
518
+ const alicePubKey = (await walletA.getPublicKey({ identityKey: true })).publicKey
519
+ const bobPubKey = (await walletB.getPublicKey({ identityKey: true })).publicKey
520
+
521
+ // Alice's certificate contains 'name' and 'email'; Bob's contains only 'email'
522
+ const aliceMasterCertificate = await createMasterCertificate(walletA, { name: 'Alice', email: 'alice@example.com' })
523
+ const aliceVerifiableCertificate = await createVerifiableCertificate(
524
+ aliceMasterCertificate,
525
+ walletA,
526
+ bobPubKey,
527
+ ['name', 'email']
528
+ )
529
+
530
+ const bobMasterCertificate = await createMasterCertificate(walletB, { email: 'bob@example.com' })
531
+ const bobVerifiableCertificate = await createVerifiableCertificate(
532
+ bobMasterCertificate,
533
+ walletB,
534
+ alicePubKey,
535
+ ['email']
536
+ )
537
+
538
+ const partialCertificatesToRequest = {
539
+ certifiers: [certifierPublicKey],
540
+ types: { [certificateType]: ['name', 'email'] }
541
+ }
542
+
543
+ const { aliceReceivedCertificates, bobReceivedCertificates } = setupPeers(true, true, {
544
+ aliceCertsToRequest: partialCertificatesToRequest,
545
+ bobCertsToRequest: partialCertificatesToRequest
546
+ })
547
+ mockGetVerifiableCertificates(aliceVerifiableCertificate, bobVerifiableCertificate, alicePubKey, bobPubKey)
548
+
549
+ const aliceAcceptedPartialCert = jest.fn()
550
+ const bobAcceptedPartialCert = jest.fn()
551
+
552
+ const waitForAlicePartialCert = new Promise<void>((resolve) => {
553
+ alice.listenForCertificatesReceived(async (senderPublicKey, certificates) => {
554
+ for (const cert of certificates) {
555
+ const decryptedFields = await decryptCertificateFields(cert, walletA, walletB)
556
+ if (decryptedFields.email || decryptedFields.name) {
557
+ console.log(`Alice received Bob's certificate with fields: ${Object.keys(decryptedFields).join(', ')}`)
558
+ aliceAcceptedPartialCert()
559
+ resolve()
560
+ }
561
+ }
562
+ })
563
+ })
564
+
565
+ const waitForBobPartialCert = new Promise<void>((resolve) => {
566
+ bob.listenForCertificatesReceived(async (senderPublicKey, certificates) => {
567
+ for (const cert of certificates) {
568
+ const decryptedFields = await decryptCertificateFields(cert, walletB, walletA)
569
+ if (decryptedFields.email || decryptedFields.name) {
570
+ console.log(`Bob received Alice's certificate with fields: ${Object.keys(decryptedFields).join(', ')}`)
571
+ bobAcceptedPartialCert()
572
+ resolve()
573
+ }
574
+ }
575
+ })
576
+ })
577
+
578
+ const bobReceivedGeneralMessage = new Promise<void>((resolve) => {
579
+ bob.listenForGeneralMessages((senderPublicKey, payload) => {
580
+ console.log('Bob received message:', Utils.toUTF8(payload))
581
+ resolve()
582
+ })
583
+ })
584
+
585
+ await alice.toPeer(Utils.toArray('Hello Bob!'))
586
+ await aliceReceivedCertificates
587
+ await bobReceivedCertificates
588
+ await bobReceivedGeneralMessage
589
+
590
+ // Wait for both sides to fully accept the partial cert
591
+ await waitForAlicePartialCert
592
+ await waitForBobPartialCert
593
+
594
+ expect(aliceAcceptedPartialCert).toHaveBeenCalled()
595
+ expect(bobAcceptedPartialCert).toHaveBeenCalled()
596
+ expect(certificatesReceivedByAlice).toEqual([bobVerifiableCertificate])
597
+ expect(certificatesReceivedByBob).toEqual([aliceVerifiableCertificate])
598
+ }, 20000)
599
+ })