@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.
- package/dist/cjs/package.json +3 -3
- package/dist/cjs/src/auth/Peer.js +536 -0
- package/dist/cjs/src/auth/Peer.js.map +1 -0
- package/dist/cjs/src/auth/SessionManager.js +66 -0
- package/dist/cjs/src/auth/SessionManager.js.map +1 -0
- package/dist/cjs/src/auth/{Certificate.js → certificates/Certificate.js} +22 -26
- package/dist/cjs/src/auth/certificates/Certificate.js.map +1 -0
- package/dist/cjs/src/auth/certificates/MasterCertificate.js +79 -0
- package/dist/cjs/src/auth/certificates/MasterCertificate.js.map +1 -0
- package/dist/cjs/src/auth/certificates/VerifiableCertificate.js +49 -0
- package/dist/cjs/src/auth/certificates/VerifiableCertificate.js.map +1 -0
- package/dist/cjs/src/auth/certificates/index.js +25 -0
- package/dist/cjs/src/auth/certificates/index.js.map +1 -0
- package/dist/cjs/src/auth/clients/AuthFetch.js +411 -0
- package/dist/cjs/src/auth/clients/AuthFetch.js.map +1 -0
- package/dist/cjs/src/auth/clients/index.js +18 -0
- package/dist/cjs/src/auth/clients/index.js.map +1 -0
- package/dist/cjs/src/auth/index.js +20 -5
- package/dist/cjs/src/auth/index.js.map +1 -1
- package/dist/cjs/src/auth/transports/SimplifiedFetchTransport.js +259 -0
- package/dist/cjs/src/auth/transports/SimplifiedFetchTransport.js.map +1 -0
- package/dist/cjs/src/auth/transports/index.js +18 -0
- package/dist/cjs/src/auth/transports/index.js.map +1 -0
- package/dist/cjs/src/auth/types.js +3 -0
- package/dist/cjs/src/auth/types.js.map +1 -0
- package/dist/cjs/src/auth/utils/certificateHelpers.js +51 -0
- package/dist/cjs/src/auth/utils/certificateHelpers.js.map +1 -0
- package/dist/cjs/src/auth/utils/createNonce.js +19 -0
- package/dist/cjs/src/auth/utils/createNonce.js.map +1 -0
- package/dist/cjs/src/auth/utils/getVerifiableCertificates.js +31 -0
- package/dist/cjs/src/auth/utils/getVerifiableCertificates.js.map +1 -0
- package/dist/cjs/src/auth/utils/index.js +22 -0
- package/dist/cjs/src/auth/utils/index.js.map +1 -0
- package/dist/cjs/src/auth/utils/validateCertificates.js +42 -0
- package/dist/cjs/src/auth/utils/validateCertificates.js.map +1 -0
- package/dist/cjs/src/auth/utils/verifyNonce.js +27 -0
- package/dist/cjs/src/auth/utils/verifyNonce.js.map +1 -0
- package/dist/cjs/src/primitives/Point.js +1 -1
- package/dist/cjs/src/primitives/Point.js.map +1 -1
- package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js +1 -1
- package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js.map +1 -1
- package/dist/cjs/src/wallet/substrates/WalletWireTransceiver.js +148 -148
- package/dist/cjs/src/wallet/substrates/WalletWireTransceiver.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/auth/Peer.js +533 -0
- package/dist/esm/src/auth/Peer.js.map +1 -0
- package/dist/esm/src/auth/SessionManager.js +63 -0
- package/dist/esm/src/auth/SessionManager.js.map +1 -0
- package/dist/esm/src/auth/{Certificate.js → certificates/Certificate.js} +1 -2
- package/dist/esm/src/auth/certificates/Certificate.js.map +1 -0
- package/dist/esm/src/auth/certificates/MasterCertificate.js +73 -0
- package/dist/esm/src/auth/certificates/MasterCertificate.js.map +1 -0
- package/dist/esm/src/auth/certificates/VerifiableCertificate.js +44 -0
- package/dist/esm/src/auth/certificates/VerifiableCertificate.js.map +1 -0
- package/dist/esm/src/auth/certificates/index.js +4 -0
- package/dist/esm/src/auth/certificates/index.js.map +1 -0
- package/dist/esm/src/auth/clients/AuthFetch.js +409 -0
- package/dist/esm/src/auth/clients/AuthFetch.js.map +1 -0
- package/dist/esm/src/auth/clients/index.js +2 -0
- package/dist/esm/src/auth/clients/index.js.map +1 -0
- package/dist/esm/src/auth/index.js +7 -1
- package/dist/esm/src/auth/index.js.map +1 -1
- package/dist/esm/src/auth/transports/SimplifiedFetchTransport.js +258 -0
- package/dist/esm/src/auth/transports/SimplifiedFetchTransport.js.map +1 -0
- package/dist/esm/src/auth/transports/index.js +2 -0
- package/dist/esm/src/auth/transports/index.js.map +1 -0
- package/dist/esm/src/auth/types.js +2 -0
- package/dist/esm/src/auth/types.js.map +1 -0
- package/dist/esm/src/auth/utils/certificateHelpers.js +47 -0
- package/dist/esm/src/auth/utils/certificateHelpers.js.map +1 -0
- package/dist/esm/src/auth/utils/createNonce.js +16 -0
- package/dist/esm/src/auth/utils/createNonce.js.map +1 -0
- package/dist/esm/src/auth/utils/getVerifiableCertificates.js +27 -0
- package/dist/esm/src/auth/utils/getVerifiableCertificates.js.map +1 -0
- package/dist/esm/src/auth/utils/index.js +6 -0
- package/dist/esm/src/auth/utils/index.js.map +1 -0
- package/dist/esm/src/auth/utils/validateCertificates.js +38 -0
- package/dist/esm/src/auth/utils/validateCertificates.js.map +1 -0
- package/dist/esm/src/auth/utils/verifyNonce.js +24 -0
- package/dist/esm/src/auth/utils/verifyNonce.js.map +1 -0
- package/dist/esm/src/primitives/Point.js +1 -1
- package/dist/esm/src/primitives/Point.js.map +1 -1
- package/dist/esm/src/wallet/substrates/WalletWireProcessor.js +1 -1
- package/dist/esm/src/wallet/substrates/WalletWireProcessor.js.map +1 -1
- package/dist/esm/src/wallet/substrates/WalletWireTransceiver.js +1 -1
- package/dist/esm/src/wallet/substrates/WalletWireTransceiver.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/auth/Peer.d.ts +193 -0
- package/dist/types/src/auth/Peer.d.ts.map +1 -0
- package/dist/types/src/auth/SessionManager.d.ts +42 -0
- package/dist/types/src/auth/SessionManager.d.ts.map +1 -0
- package/dist/types/src/auth/{Certificate.d.ts → certificates/Certificate.d.ts} +1 -1
- package/dist/types/src/auth/certificates/Certificate.d.ts.map +1 -0
- package/dist/types/src/auth/certificates/MasterCertificate.d.ts +38 -0
- package/dist/types/src/auth/certificates/MasterCertificate.d.ts.map +1 -0
- package/dist/types/src/auth/certificates/VerifiableCertificate.d.ts +26 -0
- package/dist/types/src/auth/certificates/VerifiableCertificate.d.ts.map +1 -0
- package/dist/types/src/auth/certificates/index.d.ts +4 -0
- package/dist/types/src/auth/certificates/index.d.ts.map +1 -0
- package/dist/types/src/auth/clients/AuthFetch.d.ts +87 -0
- package/dist/types/src/auth/clients/AuthFetch.d.ts.map +1 -0
- package/dist/types/src/auth/clients/index.d.ts +2 -0
- package/dist/types/src/auth/clients/index.d.ts.map +1 -0
- package/dist/types/src/auth/index.d.ts +7 -1
- package/dist/types/src/auth/index.d.ts.map +1 -1
- package/dist/types/src/auth/transports/SimplifiedFetchTransport.d.ts +51 -0
- package/dist/types/src/auth/transports/SimplifiedFetchTransport.d.ts.map +1 -0
- package/dist/types/src/auth/transports/index.d.ts +2 -0
- package/dist/types/src/auth/transports/index.d.ts.map +1 -0
- package/dist/types/src/auth/types.d.ts +31 -0
- package/dist/types/src/auth/types.d.ts.map +1 -0
- package/dist/types/src/auth/utils/certificateHelpers.d.ts +26 -0
- package/dist/types/src/auth/utils/certificateHelpers.d.ts.map +1 -0
- package/dist/types/src/auth/utils/createNonce.d.ts +8 -0
- package/dist/types/src/auth/utils/createNonce.d.ts.map +1 -0
- package/dist/types/src/auth/utils/getVerifiableCertificates.d.ts +13 -0
- package/dist/types/src/auth/utils/getVerifiableCertificates.d.ts.map +1 -0
- package/dist/types/src/auth/utils/index.d.ts +6 -0
- package/dist/types/src/auth/utils/index.d.ts.map +1 -0
- package/dist/types/src/auth/utils/validateCertificates.d.ts +12 -0
- package/dist/types/src/auth/utils/validateCertificates.d.ts.map +1 -0
- package/dist/types/src/auth/utils/verifyNonce.d.ts +9 -0
- package/dist/types/src/auth/utils/verifyNonce.d.ts.map +1 -0
- package/dist/types/src/primitives/Point.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/docs/README.md +1 -0
- package/docs/auth.md +1193 -0
- package/package.json +13 -3
- package/src/auth/Peer.ts +600 -0
- package/src/auth/SessionManager.ts +71 -0
- package/src/auth/__tests/Peer.test.ts +599 -0
- package/src/auth/__tests/SessionManager.test.ts +87 -0
- package/src/auth/{Certificate.ts → certificates/Certificate.ts} +15 -8
- package/src/auth/certificates/MasterCertificate.ts +106 -0
- package/src/auth/certificates/VerifiableCertificate.ts +73 -0
- package/src/auth/certificates/__tests/Certificate.test.ts +282 -0
- package/src/auth/certificates/index.ts +3 -0
- package/src/auth/clients/AuthFetch.ts +482 -0
- package/src/auth/clients/index.ts +1 -0
- package/src/auth/index.ts +7 -1
- package/src/auth/transports/SimplifiedFetchTransport.ts +288 -0
- package/src/auth/transports/index.ts +1 -0
- package/src/auth/types.ts +41 -0
- package/src/auth/utils/__tests/cryptononce.test.ts +84 -0
- package/src/auth/utils/__tests/getVerifiableCertificates.test.ts +126 -0
- package/src/auth/utils/__tests/validateCertificates.test.ts +142 -0
- package/src/auth/utils/certificateHelpers.ts +86 -0
- package/src/auth/utils/createNonce.ts +16 -0
- package/src/auth/utils/getVerifiableCertificates.ts +40 -0
- package/src/auth/utils/index.ts +5 -0
- package/src/auth/utils/validateCertificates.ts +54 -0
- package/src/auth/utils/verifyNonce.ts +27 -0
- package/src/primitives/Point.ts +59 -59
- package/src/wallet/substrates/WalletWireProcessor.ts +1 -1
- package/src/wallet/substrates/WalletWireTransceiver.ts +1 -1
- package/dist/cjs/src/auth/Certificate.js.map +0 -1
- package/dist/esm/src/auth/Certificate.js.map +0 -1
- package/dist/types/src/auth/Certificate.d.ts.map +0 -1
- package/src/auth/__tests/Certificate.test.ts +0 -282
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { AuthMessage, RequestedCertificateSet, Transport } from "../types.js"
|
|
2
|
+
import { Utils } from '../../../mod.js'
|
|
3
|
+
|
|
4
|
+
const SUCCESS_STATUS_CODES = [200, 402]
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Implements an HTTP-specific transport for handling Peer mutual authentication messages.
|
|
8
|
+
* This class integrates with fetch to send and receive authenticated messages between peers.
|
|
9
|
+
*/
|
|
10
|
+
export class SimplifiedFetchTransport implements Transport {
|
|
11
|
+
private onDataCallback?: (message: AuthMessage) => void
|
|
12
|
+
fetchClient: typeof fetch
|
|
13
|
+
baseUrl: string
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Constructs a new instance of SimplifiedFetchTransport.
|
|
17
|
+
* @param baseUrl - The base URL for all HTTP requests made by this transport.
|
|
18
|
+
* @param fetchClient - A fetch implementation to use for HTTP requests (default: global fetch).
|
|
19
|
+
*/
|
|
20
|
+
constructor(baseUrl: string, fetchClient = fetch) {
|
|
21
|
+
this.fetchClient = fetchClient
|
|
22
|
+
this.baseUrl = baseUrl
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Sends a message to an HTTP server using the transport mechanism.
|
|
27
|
+
* Handles both general and authenticated message types. For general messages,
|
|
28
|
+
* the payload is deserialized and sent as an HTTP request. For other message types,
|
|
29
|
+
* the message is sent as a POST request to the `/auth` endpoint.
|
|
30
|
+
*
|
|
31
|
+
* @param message - The AuthMessage to send.
|
|
32
|
+
* @returns A promise that resolves when the message is successfully sent.
|
|
33
|
+
*
|
|
34
|
+
* @throws Will throw an error if no listener has been registered via `onData`.
|
|
35
|
+
*/
|
|
36
|
+
async send(message: AuthMessage): Promise<void> {
|
|
37
|
+
if (!this.onDataCallback) {
|
|
38
|
+
throw new Error('Listen before you start speaking. God gave you two ears and one mouth for a reason.')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (message.messageType !== 'general') {
|
|
42
|
+
const response = await this.fetchClient(`${this.baseUrl}/.well-known/auth`, {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: {
|
|
45
|
+
'Content-Type': 'application/json'
|
|
46
|
+
},
|
|
47
|
+
body: JSON.stringify(message)
|
|
48
|
+
})
|
|
49
|
+
// Handle the response if data is received and callback is set
|
|
50
|
+
if (response.ok && this.onDataCallback) {
|
|
51
|
+
const responseMessage = await response.json()
|
|
52
|
+
if (responseMessage?.status !== 'certificate received') {
|
|
53
|
+
this.onDataCallback(responseMessage as AuthMessage)
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
// Server may be a non authenticated server
|
|
57
|
+
throw new Error('HTTP server failed to authenticate')
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
// Parse message payload
|
|
61
|
+
const httpRequest = this.deserializeRequestPayload(message.payload)
|
|
62
|
+
|
|
63
|
+
// Send the byte array as the HTTP payload
|
|
64
|
+
const url = `${this.baseUrl}${httpRequest.urlPostfix}`
|
|
65
|
+
let httpRequestWithAuthHeaders: any = httpRequest
|
|
66
|
+
if (typeof httpRequest.headers !== 'object') {
|
|
67
|
+
httpRequestWithAuthHeaders.headers = {}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Append auth headers in request to server
|
|
71
|
+
httpRequestWithAuthHeaders.headers['x-bsv-auth-version'] = message.version
|
|
72
|
+
httpRequestWithAuthHeaders.headers['x-bsv-auth-identity-key'] = message.identityKey
|
|
73
|
+
httpRequestWithAuthHeaders.headers['x-bsv-auth-nonce'] = message.nonce
|
|
74
|
+
httpRequestWithAuthHeaders.headers['x-bsv-auth-your-nonce'] = message.yourNonce
|
|
75
|
+
httpRequestWithAuthHeaders.headers['x-bsv-auth-signature'] = Utils.toHex(message.signature)
|
|
76
|
+
httpRequestWithAuthHeaders.headers['x-bsv-auth-request-id'] = httpRequest.requestId
|
|
77
|
+
|
|
78
|
+
// Ensure Content-Type is set for requests with a body
|
|
79
|
+
if (httpRequestWithAuthHeaders.body) {
|
|
80
|
+
const headers = httpRequestWithAuthHeaders.headers;
|
|
81
|
+
if (!headers['content-type']) {
|
|
82
|
+
throw new Error('Content-Type header is required for requests with a body.');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const contentType = headers['content-type'];
|
|
86
|
+
|
|
87
|
+
// Transform body based on Content-Type
|
|
88
|
+
if (contentType.includes('application/json')) {
|
|
89
|
+
// Convert byte array to JSON string
|
|
90
|
+
httpRequestWithAuthHeaders.body = Utils.toUTF8(httpRequestWithAuthHeaders.body);
|
|
91
|
+
} else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
92
|
+
// Convert byte array to URL-encoded string
|
|
93
|
+
httpRequestWithAuthHeaders.body = Utils.toUTF8(httpRequestWithAuthHeaders.body);
|
|
94
|
+
} else if (contentType.includes('text/plain')) {
|
|
95
|
+
// Convert byte array to plain UTF-8 string
|
|
96
|
+
httpRequestWithAuthHeaders.body = Utils.toUTF8(httpRequestWithAuthHeaders.body);
|
|
97
|
+
} else {
|
|
98
|
+
// For all other content types, treat as binary data
|
|
99
|
+
httpRequestWithAuthHeaders.body = new Uint8Array(httpRequestWithAuthHeaders.body);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
// Send the actual fetch request to the server
|
|
105
|
+
const response = await this.fetchClient(url, {
|
|
106
|
+
method: httpRequestWithAuthHeaders.method,
|
|
107
|
+
headers: httpRequestWithAuthHeaders.headers,
|
|
108
|
+
body: httpRequestWithAuthHeaders.body
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
// Check for an acceptable status
|
|
112
|
+
if (!SUCCESS_STATUS_CODES.includes(response.status)) {
|
|
113
|
+
// Try parsing JSON error
|
|
114
|
+
let errorInfo;
|
|
115
|
+
try {
|
|
116
|
+
errorInfo = await response.json();
|
|
117
|
+
} catch {
|
|
118
|
+
// Fallback to text if JSON parse fails
|
|
119
|
+
const text = await response.text().catch(() => '');
|
|
120
|
+
throw new Error(`HTTP ${response.status} - ${text || 'Unknown error'}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// If we find a known { status: 'error', code, description } structure
|
|
124
|
+
if (errorInfo?.status === 'error' && typeof errorInfo.description === 'string') {
|
|
125
|
+
const msg = `HTTP ${response.status} - ${errorInfo.description}`;
|
|
126
|
+
throw new Error(errorInfo.code ? `${msg} (code: ${errorInfo.code})` : msg);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Otherwise just throw whatever we got
|
|
130
|
+
throw new Error(`HTTP ${response.status} - ${JSON.stringify(errorInfo)}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const parsedBody = await response.arrayBuffer()
|
|
134
|
+
const payloadWriter = new Utils.Writer()
|
|
135
|
+
payloadWriter.write(Utils.toArray(response.headers.get('x-bsv-auth-request-id'), 'base64'))
|
|
136
|
+
payloadWriter.writeVarIntNum(response.status)
|
|
137
|
+
|
|
138
|
+
// Filter out headers the server signed:
|
|
139
|
+
// - Custom headers prefixed with x-bsv are included, except auth
|
|
140
|
+
// - x-bsv-auth headers are not allowed
|
|
141
|
+
// - authorization header is signed by the server
|
|
142
|
+
const includedHeaders: [string, string][] = []
|
|
143
|
+
// Collect headers into a raw array for sorting
|
|
144
|
+
const headersArray: [string, string][] = []
|
|
145
|
+
response.headers.forEach((value, key) => {
|
|
146
|
+
const lowerKey = key.toLowerCase()
|
|
147
|
+
if (lowerKey.startsWith('x-bsv-') || lowerKey === 'authorization') {
|
|
148
|
+
if (!lowerKey.startsWith('x-bsv-auth')) {
|
|
149
|
+
headersArray.push([lowerKey, value])
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// Sort headers explicitly to match server-side order
|
|
155
|
+
headersArray.sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
|
|
156
|
+
includedHeaders.push(...headersArray)
|
|
157
|
+
|
|
158
|
+
// nHeaders
|
|
159
|
+
payloadWriter.writeVarIntNum(includedHeaders.length)
|
|
160
|
+
for (let i = 0; i < includedHeaders.length; i++) {
|
|
161
|
+
// headerKeyLength
|
|
162
|
+
const headerKeyAsArray = Utils.toArray(includedHeaders[i][0], 'utf8')
|
|
163
|
+
payloadWriter.writeVarIntNum(headerKeyAsArray.length)
|
|
164
|
+
// headerKey
|
|
165
|
+
payloadWriter.write(headerKeyAsArray)
|
|
166
|
+
// headerValueLength
|
|
167
|
+
const headerValueAsArray = Utils.toArray(includedHeaders[i][1], 'utf8')
|
|
168
|
+
payloadWriter.writeVarIntNum(headerValueAsArray.length)
|
|
169
|
+
// headerValue
|
|
170
|
+
payloadWriter.write(headerValueAsArray)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Handle body
|
|
174
|
+
if (parsedBody) {
|
|
175
|
+
const bodyAsArray = Array.from(new Uint8Array(parsedBody))
|
|
176
|
+
payloadWriter.writeVarIntNum(bodyAsArray.length)
|
|
177
|
+
payloadWriter.write(bodyAsArray)
|
|
178
|
+
} else {
|
|
179
|
+
payloadWriter.writeVarIntNum(-1)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Build the correct AuthMessage for the response
|
|
183
|
+
const responseMessage: AuthMessage = {
|
|
184
|
+
version: response.headers.get('x-bsv-auth-version'),
|
|
185
|
+
messageType: response.headers.get('x-bsv-auth-message-type') === 'certificateRequest' ? 'certificateRequest' : 'general',
|
|
186
|
+
identityKey: response.headers.get('x-bsv-auth-identity-key'),
|
|
187
|
+
nonce: response.headers.get('x-bsv-auth-nonce'),
|
|
188
|
+
yourNonce: response.headers.get('x-bsv-auth-your-nonce'),
|
|
189
|
+
requestedCertificates: JSON.parse(response.headers.get('x-bsv-auth-requested-certificates')) as RequestedCertificateSet,
|
|
190
|
+
payload: payloadWriter.toArray(),
|
|
191
|
+
signature: Utils.toArray(response.headers.get('x-bsv-auth-signature'), 'hex'),
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// If the server didn't provide the correct authentication headers, throw an error
|
|
195
|
+
if (!responseMessage.version) {
|
|
196
|
+
throw new Error('HTTP server failed to authenticate')
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Handle the response if data is received and callback is set
|
|
200
|
+
this.onDataCallback(responseMessage)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Registers a callback to handle incoming messages.
|
|
206
|
+
* This must be called before sending any messages to ensure responses can be processed.
|
|
207
|
+
*
|
|
208
|
+
* @param callback - A function to invoke when an incoming AuthMessage is received.
|
|
209
|
+
* @returns A promise that resolves once the callback is set.
|
|
210
|
+
*/
|
|
211
|
+
async onData(callback: (message: AuthMessage) => Promise<void>): Promise<void> {
|
|
212
|
+
this.onDataCallback = (m) => {
|
|
213
|
+
callback(m)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Deserializes a request payload from a byte array into an HTTP request-like structure.
|
|
219
|
+
*
|
|
220
|
+
* @param payload - The serialized payload to deserialize.
|
|
221
|
+
* @returns An object representing the deserialized request, including the method,
|
|
222
|
+
* URL postfix (path and query string), headers, body, and request ID.
|
|
223
|
+
*/
|
|
224
|
+
deserializeRequestPayload(payload: number[]): {
|
|
225
|
+
method: string,
|
|
226
|
+
urlPostfix: string,
|
|
227
|
+
headers: Record<string, string>,
|
|
228
|
+
body: number[],
|
|
229
|
+
requestId: string
|
|
230
|
+
} {
|
|
231
|
+
// Create a reader
|
|
232
|
+
const requestReader = new Utils.Reader(payload)
|
|
233
|
+
// The first 32 bytes is the requestId
|
|
234
|
+
const requestId = Utils.toBase64(requestReader.read(32))
|
|
235
|
+
|
|
236
|
+
// Method
|
|
237
|
+
const methodLength = requestReader.readVarIntNum()
|
|
238
|
+
let method = 'GET'
|
|
239
|
+
if (methodLength > 0) {
|
|
240
|
+
method = Utils.toUTF8(requestReader.read(methodLength))
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Path
|
|
244
|
+
const pathLength = requestReader.readVarIntNum()
|
|
245
|
+
let path = ''
|
|
246
|
+
if (pathLength > 0) {
|
|
247
|
+
path = Utils.toUTF8(requestReader.read(pathLength))
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Search
|
|
251
|
+
const searchLength = requestReader.readVarIntNum()
|
|
252
|
+
let search = ''
|
|
253
|
+
if (searchLength > 0) {
|
|
254
|
+
search = Utils.toUTF8(requestReader.read(searchLength))
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Read headers
|
|
258
|
+
const requestHeaders = {}
|
|
259
|
+
const nHeaders = requestReader.readVarIntNum()
|
|
260
|
+
if (nHeaders > 0) {
|
|
261
|
+
for (let i = 0; i < nHeaders; i++) {
|
|
262
|
+
const nHeaderKeyBytes = requestReader.readVarIntNum()
|
|
263
|
+
const headerKeyBytes = requestReader.read(nHeaderKeyBytes)
|
|
264
|
+
const headerKey = Utils.toUTF8(headerKeyBytes)
|
|
265
|
+
const nHeaderValueBytes = requestReader.readVarIntNum()
|
|
266
|
+
const headerValueBytes = requestReader.read(nHeaderValueBytes)
|
|
267
|
+
const headerValue = Utils.toUTF8(headerValueBytes)
|
|
268
|
+
requestHeaders[headerKey] = headerValue
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Read body
|
|
273
|
+
let requestBody
|
|
274
|
+
const requestBodyBytes = requestReader.readVarIntNum()
|
|
275
|
+
if (requestBodyBytes > 0) {
|
|
276
|
+
requestBody = requestReader.read(requestBodyBytes)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Return the deserialized RequestInit
|
|
280
|
+
return {
|
|
281
|
+
urlPostfix: path + search,
|
|
282
|
+
method,
|
|
283
|
+
headers: requestHeaders,
|
|
284
|
+
body: requestBody,
|
|
285
|
+
requestId
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './SimplifiedFetchTransport.js'
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { VerifiableCertificate } from './certificates/VerifiableCertificate.js'
|
|
2
|
+
|
|
3
|
+
export interface RequestedCertificateTypeIDAndFieldList {
|
|
4
|
+
[certificateTypeID: string]: string[]
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// Define the structure for the requested certificates set
|
|
8
|
+
export interface RequestedCertificateSet {
|
|
9
|
+
certifiers: string[]
|
|
10
|
+
types: RequestedCertificateTypeIDAndFieldList
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface AuthMessage {
|
|
14
|
+
version: string
|
|
15
|
+
messageType:
|
|
16
|
+
| 'initialRequest'
|
|
17
|
+
| 'initialResponse'
|
|
18
|
+
| 'certificateRequest'
|
|
19
|
+
| 'certificateResponse'
|
|
20
|
+
| 'general'
|
|
21
|
+
identityKey: string // Sender's public key (used for identity verification)
|
|
22
|
+
nonce?: string // Sender's nonce (256-bit random value)
|
|
23
|
+
initialNonce?: string
|
|
24
|
+
yourNonce?: string // The recipient's nonce from a previous message (if applicable)
|
|
25
|
+
certificates?: VerifiableCertificate[] // Optional: List of certificates (if required/requested)
|
|
26
|
+
requestedCertificates?: RequestedCertificateSet // Optional: List of requested certificates
|
|
27
|
+
payload?: number[] // The actual message data (optional, could be a string or an object)
|
|
28
|
+
signature?: number[] // Digital signature covering the entire message
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface Transport {
|
|
32
|
+
send: (message: AuthMessage) => Promise<void>
|
|
33
|
+
onData: (callback: (message: AuthMessage) => Promise<void>) => Promise<void>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface PeerSession {
|
|
37
|
+
isAuthenticated: boolean
|
|
38
|
+
sessionNonce?: string
|
|
39
|
+
peerNonce?: string
|
|
40
|
+
peerIdentityKey?: string
|
|
41
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { PrivateKey } from '../../../../dist/cjs/src/primitives/index.js'
|
|
2
|
+
import { ProtoWallet } from '../../../../dist/cjs/src/wallet/index.js'
|
|
3
|
+
import { Wallet } from '../../../../dist/cjs/src/wallet/Wallet.interfaces.js'
|
|
4
|
+
import { createNonce } from '../../../../dist/cjs/src/auth/utils/createNonce.js'
|
|
5
|
+
import { verifyNonce } from '../../../../dist/cjs/src/auth/utils/verifyNonce.js'
|
|
6
|
+
|
|
7
|
+
describe('createNonce', () => {
|
|
8
|
+
let mockWallet: Wallet
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
mockWallet = {
|
|
12
|
+
createHmac: jest.fn().mockResolvedValue({ hmac: new Uint8Array(16) }),
|
|
13
|
+
} as unknown as Wallet
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
jest.clearAllMocks()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('throws an error if wallet fails to create HMAC', async () => {
|
|
21
|
+
// Mock failure of HMAC creation
|
|
22
|
+
(mockWallet.createHmac as jest.Mock).mockRejectedValue(new Error('Failed to create HMAC'))
|
|
23
|
+
|
|
24
|
+
await expect(createNonce(mockWallet)).rejects.toThrow('Failed to create HMAC')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('creates a 256-bit nonce', async () => {
|
|
28
|
+
const nonce = await createNonce(mockWallet)
|
|
29
|
+
expect(Buffer.from(nonce, 'base64').byteLength).toEqual(32)
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('verifyNonce', () => {
|
|
34
|
+
let mockWallet: Wallet
|
|
35
|
+
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
mockWallet = {
|
|
38
|
+
createHmac: jest.fn().mockResolvedValue({ hmac: new Uint8Array(16) }),
|
|
39
|
+
verifyHmac: jest.fn().mockResolvedValue({ valid: true }),
|
|
40
|
+
} as unknown as Wallet
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
jest.clearAllMocks()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('does not verify an invalid nonce', async () => {
|
|
48
|
+
(mockWallet.verifyHmac as jest.Mock).mockResolvedValue({ valid: false })
|
|
49
|
+
|
|
50
|
+
const nonce = await createNonce(mockWallet)
|
|
51
|
+
await expect(verifyNonce(nonce + 'ABC', mockWallet)).resolves.toEqual(false)
|
|
52
|
+
await expect(verifyNonce(nonce + '=', mockWallet)).resolves.toEqual(false)
|
|
53
|
+
await expect(verifyNonce(Buffer.from(nonce + Buffer.from('extra').toString('base64'), 'base64').toString('base64'), mockWallet)).resolves.toEqual(false)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('returns false for an invalid HMAC verification', async () => {
|
|
57
|
+
(mockWallet.verifyHmac as jest.Mock).mockResolvedValue({ valid: false })
|
|
58
|
+
|
|
59
|
+
const nonce = await createNonce(mockWallet)
|
|
60
|
+
await expect(verifyNonce(nonce, mockWallet)).resolves.toEqual(false)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('verifies a 256-bit nonce', async () => {
|
|
64
|
+
(mockWallet.verifyHmac as jest.Mock).mockResolvedValue({ valid: true })
|
|
65
|
+
|
|
66
|
+
const nonce1 = await createNonce(mockWallet)
|
|
67
|
+
const nonce2 = await createNonce(mockWallet)
|
|
68
|
+
|
|
69
|
+
expect(Buffer.from(nonce1, 'base64').byteLength).toEqual(32)
|
|
70
|
+
expect(Buffer.from(nonce2, 'base64').byteLength).toEqual(32)
|
|
71
|
+
|
|
72
|
+
await expect(verifyNonce(nonce1, mockWallet)).resolves.toEqual(true)
|
|
73
|
+
await expect(verifyNonce(nonce2, mockWallet)).resolves.toEqual(true)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('verifies nonce using real createHmac and verifyHmac', async () => {
|
|
77
|
+
const realWallet = new ProtoWallet(PrivateKey.fromRandom())
|
|
78
|
+
|
|
79
|
+
const nonce = await createNonce(realWallet)
|
|
80
|
+
const isValid = await verifyNonce(nonce, realWallet)
|
|
81
|
+
|
|
82
|
+
expect(isValid).toEqual(true)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Wallet } from '../../../../dist/cjs/src/wallet/Wallet.interfaces.js'
|
|
2
|
+
import { getVerifiableCertificates } from '../../../../dist/cjs/src/auth/utils/getVerifiableCertificates'
|
|
3
|
+
import { RequestedCertificateSet } from '../../../../dist/cjs/src/auth/types.js'
|
|
4
|
+
import { VerifiableCertificate } from '../../../../dist/cjs/src/auth/certificates/VerifiableCertificate.js'
|
|
5
|
+
|
|
6
|
+
describe('getVerifiableCertificates', () => {
|
|
7
|
+
let mockWallet: Wallet
|
|
8
|
+
let requestedCertificates: RequestedCertificateSet
|
|
9
|
+
let verifierIdentityKey: string
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
mockWallet = {
|
|
13
|
+
listCertificates: jest.fn(),
|
|
14
|
+
proveCertificate: jest.fn(),
|
|
15
|
+
} as unknown as Wallet
|
|
16
|
+
|
|
17
|
+
requestedCertificates = {
|
|
18
|
+
certifiers: ['certifier1', 'certifier2'],
|
|
19
|
+
types: {
|
|
20
|
+
certType1: ['field1', 'field2'],
|
|
21
|
+
certType2: ['field3'],
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
verifierIdentityKey = 'verifier_public_key'
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('retrieves matching certificates based on requested set', async () => {
|
|
29
|
+
const mockCertificate = {
|
|
30
|
+
type: 'certType1',
|
|
31
|
+
serialNumber: 'serial1',
|
|
32
|
+
subject: 'subject1',
|
|
33
|
+
certifier: 'certifier1',
|
|
34
|
+
revocationOutpoint: 'outpoint1',
|
|
35
|
+
fields: { field1: 'encryptedData1', field2: 'encryptedData2' },
|
|
36
|
+
signature: 'signature1',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
(mockWallet.listCertificates as jest.Mock).mockResolvedValue({
|
|
40
|
+
certificates: [mockCertificate]
|
|
41
|
+
});
|
|
42
|
+
(mockWallet.proveCertificate as jest.Mock).mockResolvedValue({
|
|
43
|
+
keyringForVerifier: { field1: 'key1', field2: 'key2' }
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const result = await getVerifiableCertificates(mockWallet, requestedCertificates, verifierIdentityKey)
|
|
47
|
+
|
|
48
|
+
expect(mockWallet.listCertificates).toHaveBeenCalledWith({
|
|
49
|
+
certifiers: requestedCertificates.certifiers,
|
|
50
|
+
types: Object.keys(requestedCertificates.types),
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
expect(mockWallet.proveCertificate).toHaveBeenCalledWith({
|
|
54
|
+
certificate: mockCertificate,
|
|
55
|
+
fieldsToReveal: requestedCertificates.types[mockCertificate.type],
|
|
56
|
+
verifier: verifierIdentityKey,
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
expect(result).toHaveLength(1)
|
|
60
|
+
expect(result[0]).toBeInstanceOf(VerifiableCertificate)
|
|
61
|
+
expect(result[0]).toMatchObject({
|
|
62
|
+
type: 'certType1',
|
|
63
|
+
serialNumber: 'serial1',
|
|
64
|
+
subject: 'subject1',
|
|
65
|
+
certifier: 'certifier1',
|
|
66
|
+
revocationOutpoint: 'outpoint1',
|
|
67
|
+
fields: { field1: 'encryptedData1', field2: 'encryptedData2' },
|
|
68
|
+
signature: 'signature1',
|
|
69
|
+
keyring: { field1: 'key1', field2: 'key2' }
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
it('returns an empty array when no matching certificates are found', async () => {
|
|
75
|
+
(mockWallet.listCertificates as jest.Mock).mockResolvedValue({ certificates: [] })
|
|
76
|
+
|
|
77
|
+
const result = await getVerifiableCertificates(mockWallet, requestedCertificates, verifierIdentityKey)
|
|
78
|
+
|
|
79
|
+
expect(result).toEqual([])
|
|
80
|
+
expect(mockWallet.listCertificates).toHaveBeenCalled()
|
|
81
|
+
expect(mockWallet.proveCertificate).not.toHaveBeenCalled()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('propagates errors from listCertificates', async () => {
|
|
85
|
+
(mockWallet.listCertificates as jest.Mock).mockRejectedValue(new Error('listCertificates failed'))
|
|
86
|
+
|
|
87
|
+
await expect(getVerifiableCertificates(mockWallet, requestedCertificates, verifierIdentityKey)).rejects.toThrow(
|
|
88
|
+
'listCertificates failed'
|
|
89
|
+
)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('propagates errors from proveCertificate', async () => {
|
|
93
|
+
const mockCertificate = {
|
|
94
|
+
type: 'certType1',
|
|
95
|
+
serialNumber: 'serial1',
|
|
96
|
+
subject: 'subject1',
|
|
97
|
+
certifier: 'certifier1',
|
|
98
|
+
revocationOutpoint: 'outpoint1',
|
|
99
|
+
fields: { field1: 'encryptedData1', field2: 'encryptedData2' },
|
|
100
|
+
signature: 'signature1',
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
(mockWallet.listCertificates as jest.Mock).mockResolvedValue({
|
|
104
|
+
certificates: [mockCertificate],
|
|
105
|
+
});
|
|
106
|
+
(mockWallet.proveCertificate as jest.Mock).mockRejectedValue(new Error('proveCertificate failed'))
|
|
107
|
+
|
|
108
|
+
await expect(getVerifiableCertificates(mockWallet, requestedCertificates, verifierIdentityKey)).rejects.toThrow(
|
|
109
|
+
'proveCertificate failed'
|
|
110
|
+
)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('handles empty requested certificates gracefully', async () => {
|
|
114
|
+
requestedCertificates = { certifiers: [], types: {} };
|
|
115
|
+
|
|
116
|
+
(mockWallet.listCertificates as jest.Mock).mockResolvedValue({ certificates: [] })
|
|
117
|
+
|
|
118
|
+
const result = await getVerifiableCertificates(mockWallet, requestedCertificates, verifierIdentityKey)
|
|
119
|
+
|
|
120
|
+
expect(result).toEqual([])
|
|
121
|
+
expect(mockWallet.listCertificates).toHaveBeenCalledWith({
|
|
122
|
+
certifiers: [],
|
|
123
|
+
types: [],
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
})
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { validateCertificates } from '../../../../dist/cjs/src/auth/utils/validateCertificates.js'
|
|
2
|
+
import { VerifiableCertificate } from '../../../../dist/cjs/src/auth/certificates/VerifiableCertificate.js'
|
|
3
|
+
import { ProtoWallet } from '../../../../dist/cjs/src/wallet/index.js'
|
|
4
|
+
import { PrivateKey } from '../../../../dist/cjs/src/primitives/index.js'
|
|
5
|
+
|
|
6
|
+
let mockVerify = jest.fn(() => Promise.resolve(true))
|
|
7
|
+
let mockDecryptFields = jest.fn(() => Promise.resolve({ field1: 'decryptedValue1' }))
|
|
8
|
+
const mockInstances = []
|
|
9
|
+
|
|
10
|
+
jest.mock('../../../../dist/cjs/src/auth/certificates/VerifiableCertificate.js', () => {
|
|
11
|
+
return {
|
|
12
|
+
VerifiableCertificate: jest.fn().mockImplementation(() => {
|
|
13
|
+
const instance = {
|
|
14
|
+
type: 'requested_type',
|
|
15
|
+
serialNumber: 'valid_serial',
|
|
16
|
+
subject: 'valid_subject',
|
|
17
|
+
certifier: 'valid_certifier',
|
|
18
|
+
revocationOutpoint: 'outpoint',
|
|
19
|
+
fields: { field1: 'encryptedData1' },
|
|
20
|
+
decryptedFields: {},
|
|
21
|
+
verify: mockVerify,
|
|
22
|
+
decryptFields: mockDecryptFields,
|
|
23
|
+
}
|
|
24
|
+
mockInstances.push(instance)
|
|
25
|
+
return instance
|
|
26
|
+
}),
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe('validateCertificates', () => {
|
|
31
|
+
let verifierWallet
|
|
32
|
+
let message
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
jest.clearAllMocks()
|
|
36
|
+
mockInstances.length = 0 // Clear tracked instances
|
|
37
|
+
|
|
38
|
+
// Reset state
|
|
39
|
+
mockVerify = jest.fn(() => Promise.resolve(true))
|
|
40
|
+
mockDecryptFields = jest.fn(() => Promise.resolve({ field1: 'decryptedValue1' }))
|
|
41
|
+
|
|
42
|
+
verifierWallet = new ProtoWallet(new PrivateKey(1))
|
|
43
|
+
message = {
|
|
44
|
+
identityKey: 'valid_subject',
|
|
45
|
+
certificates: [
|
|
46
|
+
{
|
|
47
|
+
type: 'requested_type',
|
|
48
|
+
serialNumber: 'valid_serial',
|
|
49
|
+
subject: 'valid_subject',
|
|
50
|
+
certifier: 'valid_certifier',
|
|
51
|
+
revocationOutpoint: 'outpoint',
|
|
52
|
+
fields: { field1: 'encryptedData1' },
|
|
53
|
+
decryptedFields: {},
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('completes without errors for valid input', async () => {
|
|
60
|
+
await expect(validateCertificates(verifierWallet, message)).resolves.not.toThrow()
|
|
61
|
+
|
|
62
|
+
expect(VerifiableCertificate).toHaveBeenCalledTimes(message.certificates.length)
|
|
63
|
+
expect(mockVerify).toHaveBeenCalledTimes(message.certificates.length)
|
|
64
|
+
expect(mockDecryptFields).toHaveBeenCalledWith(verifierWallet)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('throws an error for mismatched identity key', async () => {
|
|
68
|
+
message.identityKey = 'different_subject'
|
|
69
|
+
|
|
70
|
+
await expect(validateCertificates(verifierWallet, message)).rejects.toThrow(
|
|
71
|
+
`The subject of one of your certificates ("valid_subject") is not the same as the request sender ("different_subject").`
|
|
72
|
+
)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('throws an error if certificate signature is invalid', async () => {
|
|
76
|
+
mockVerify.mockResolvedValueOnce(false)
|
|
77
|
+
|
|
78
|
+
await expect(validateCertificates(verifierWallet, message)).rejects.toThrow(
|
|
79
|
+
`The signature for the certificate with serial number valid_serial is invalid!`
|
|
80
|
+
)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('throws an error for unrequested certifier', async () => {
|
|
84
|
+
const certificatesRequested = {
|
|
85
|
+
certifiers: ['another_certifier'],
|
|
86
|
+
types: { requested_type: ['field1'] },
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
await expect(
|
|
90
|
+
validateCertificates(verifierWallet, message, certificatesRequested)
|
|
91
|
+
).rejects.toThrow(
|
|
92
|
+
`Certificate with serial number valid_serial has an unrequested certifier: valid_certifier`
|
|
93
|
+
)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('throws an error for unrequested certificate type', async () => {
|
|
97
|
+
const certificatesRequested = {
|
|
98
|
+
certifiers: ['valid_certifier'],
|
|
99
|
+
types: { another_type: ['field1'] },
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await expect(
|
|
103
|
+
validateCertificates(verifierWallet, message, certificatesRequested)
|
|
104
|
+
).rejects.toThrow(
|
|
105
|
+
`Certificate with type requested_type was not requested`
|
|
106
|
+
)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('decrypts fields without throwing errors', async () => {
|
|
110
|
+
await expect(validateCertificates(verifierWallet, message)).resolves.not.toThrow()
|
|
111
|
+
for (const instance of mockInstances) {
|
|
112
|
+
expect(instance.decryptFields).toHaveBeenCalledWith(verifierWallet)
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('throws an error if a field decryption fails', async () => {
|
|
117
|
+
mockDecryptFields.mockRejectedValue(new Error('Decryption failed'))
|
|
118
|
+
await expect(validateCertificates(verifierWallet, message)).rejects.toThrow('Decryption failed')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('handles multiple certificates properly', async () => {
|
|
122
|
+
const anotherCertificate = {
|
|
123
|
+
type: 'requested_type',
|
|
124
|
+
serialNumber: 'another_serial',
|
|
125
|
+
subject: 'valid_subject',
|
|
126
|
+
certifier: 'valid_certifier',
|
|
127
|
+
revocationOutpoint: 'outpoint',
|
|
128
|
+
fields: { field1: 'encryptedData1' },
|
|
129
|
+
decryptedFields: {},
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
message.certificates.push(anotherCertificate)
|
|
133
|
+
|
|
134
|
+
await expect(validateCertificates(verifierWallet, message)).resolves.not.toThrow()
|
|
135
|
+
|
|
136
|
+
expect(VerifiableCertificate).toHaveBeenCalledTimes(2)
|
|
137
|
+
expect(mockVerify).toHaveBeenCalledTimes(2)
|
|
138
|
+
for (const instance of mockInstances) {
|
|
139
|
+
expect(instance.decryptFields).toHaveBeenCalledWith(verifierWallet)
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
})
|