@veridex/sdk 1.0.0-beta.9 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +170 -21
- package/README.md +574 -117
- package/dist/EVMClient-DtqvdfUP.d.mts +376 -0
- package/dist/auth/prepareAuth.d.mts +25 -0
- package/dist/auth/prepareAuth.js +2406 -0
- package/dist/auth/prepareAuth.js.map +1 -0
- package/dist/auth/prepareAuth.mjs +151 -0
- package/dist/auth/prepareAuth.mjs.map +1 -0
- package/dist/chains/aptos/index.d.mts +6 -5
- package/dist/chains/aptos/index.js +66 -39
- package/dist/chains/aptos/index.js.map +1 -1
- package/dist/chains/aptos/index.mjs +5 -547
- package/dist/chains/aptos/index.mjs.map +1 -1
- package/dist/chains/avalanche/index.d.mts +137 -0
- package/dist/chains/avalanche/index.js +1555 -0
- package/dist/chains/avalanche/index.js.map +1 -0
- package/dist/chains/avalanche/index.mjs +10 -0
- package/dist/chains/avalanche/index.mjs.map +1 -0
- package/dist/chains/evm/index.d.mts +5 -3
- package/dist/chains/evm/index.js +165 -3
- package/dist/chains/evm/index.js.map +1 -1
- package/dist/chains/evm/index.mjs +8 -1200
- package/dist/chains/evm/index.mjs.map +1 -1
- package/dist/chains/solana/index.d.mts +1 -1
- package/dist/chains/solana/index.js.map +1 -1
- package/dist/chains/solana/index.mjs +4 -486
- package/dist/chains/solana/index.mjs.map +1 -1
- package/dist/chains/stacks/index.d.mts +559 -0
- package/dist/chains/stacks/index.js +1207 -0
- package/dist/chains/stacks/index.js.map +1 -0
- package/dist/chains/stacks/index.mjs +71 -0
- package/dist/chains/stacks/index.mjs.map +1 -0
- package/dist/chains/starknet/index.d.mts +4 -5
- package/dist/chains/starknet/index.js +1 -13
- package/dist/chains/starknet/index.js.map +1 -1
- package/dist/chains/starknet/index.mjs +5 -503
- package/dist/chains/starknet/index.mjs.map +1 -1
- package/dist/chains/sui/index.d.mts +4 -4
- package/dist/chains/sui/index.js +2 -2
- package/dist/chains/sui/index.js.map +1 -1
- package/dist/chains/sui/index.mjs +5 -529
- package/dist/chains/sui/index.mjs.map +1 -1
- package/dist/chunk-5T6KPH7A.mjs +1082 -0
- package/dist/chunk-5T6KPH7A.mjs.map +1 -0
- package/dist/chunk-72ZA3OYQ.mjs +20 -0
- package/dist/chunk-72ZA3OYQ.mjs.map +1 -0
- package/dist/chunk-F3YAGZSW.mjs +269 -0
- package/dist/chunk-F3YAGZSW.mjs.map +1 -0
- package/dist/chunk-GWJRKDSA.mjs +549 -0
- package/dist/chunk-GWJRKDSA.mjs.map +1 -0
- package/dist/chunk-M3MM4YMF.mjs +417 -0
- package/dist/chunk-M3MM4YMF.mjs.map +1 -0
- package/dist/chunk-MLXQHIH2.mjs +426 -0
- package/dist/chunk-MLXQHIH2.mjs.map +1 -0
- package/dist/chunk-N4A2RMUN.mjs +216 -0
- package/dist/chunk-N4A2RMUN.mjs.map +1 -0
- package/dist/chunk-NUWSMJFJ.mjs +179 -0
- package/dist/chunk-NUWSMJFJ.mjs.map +1 -0
- package/dist/chunk-OVMMTL6H.mjs +330 -0
- package/dist/chunk-OVMMTL6H.mjs.map +1 -0
- package/dist/chunk-PDHZ5X5O.mjs +565 -0
- package/dist/chunk-PDHZ5X5O.mjs.map +1 -0
- package/dist/chunk-Q5O3M5LP.mjs +422 -0
- package/dist/chunk-Q5O3M5LP.mjs.map +1 -0
- package/dist/chunk-QDO6NQ7P.mjs +840 -0
- package/dist/chunk-QDO6NQ7P.mjs.map +1 -0
- package/dist/chunk-QT4ZZ4GM.mjs +509 -0
- package/dist/chunk-QT4ZZ4GM.mjs.map +1 -0
- package/dist/chunk-SXXGTQIR.mjs +464 -0
- package/dist/chunk-SXXGTQIR.mjs.map +1 -0
- package/dist/chunk-USDA5JTN.mjs +1249 -0
- package/dist/chunk-USDA5JTN.mjs.map +1 -0
- package/dist/chunk-V636MIV3.mjs +52 -0
- package/dist/chunk-V636MIV3.mjs.map +1 -0
- package/dist/chunk-X7BZMSPQ.mjs +407 -0
- package/dist/chunk-X7BZMSPQ.mjs.map +1 -0
- package/dist/chunk-YCUJZ6Z7.mjs +829 -0
- package/dist/chunk-YCUJZ6Z7.mjs.map +1 -0
- package/dist/constants.d.mts +1 -1
- package/dist/constants.js +26 -12
- package/dist/constants.js.map +1 -1
- package/dist/constants.mjs +16 -375
- package/dist/constants.mjs.map +1 -1
- package/dist/index-DDalBhAm.d.mts +243 -0
- package/dist/index.d.mts +2511 -556
- package/dist/index.js +15216 -10262
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4125 -7839
- package/dist/index.mjs.map +1 -1
- package/dist/passkey.d.mts +182 -0
- package/dist/passkey.js +914 -0
- package/dist/passkey.js.map +1 -0
- package/dist/passkey.mjs +15 -0
- package/dist/passkey.mjs.map +1 -0
- package/dist/payload.js.map +1 -1
- package/dist/payload.mjs +25 -244
- package/dist/payload.mjs.map +1 -1
- package/dist/portfolio-V347KZOL.mjs +13 -0
- package/dist/portfolio-V347KZOL.mjs.map +1 -0
- package/dist/queries/index.js +145 -12
- package/dist/queries/index.js.map +1 -1
- package/dist/queries/index.mjs +14 -1496
- package/dist/queries/index.mjs.map +1 -1
- package/dist/{types-FJL7j6gQ.d.ts → types-B7V5VNbO.d.mts} +6 -2
- package/dist/{types-ChIsqCiw.d.mts → types-DP2CQT8p.d.mts} +12 -1
- package/dist/types.d.mts +16 -0
- package/dist/types.js.map +1 -1
- package/dist/utils.js +25 -11
- package/dist/utils.js.map +1 -1
- package/dist/utils.mjs +19 -371
- package/dist/utils.mjs.map +1 -1
- package/dist/wormhole.js.map +1 -1
- package/dist/wormhole.mjs +25 -397
- package/dist/wormhole.mjs.map +1 -1
- package/package.json +28 -3
- package/scripts/patch-noble-curves.js +78 -0
- package/dist/chains/aptos/index.d.ts +0 -145
- package/dist/chains/evm/index.d.ts +0 -5
- package/dist/chains/solana/index.d.ts +0 -116
- package/dist/chains/starknet/index.d.ts +0 -172
- package/dist/chains/sui/index.d.ts +0 -182
- package/dist/constants.d.ts +0 -150
- package/dist/index-0NXfbk0z.d.ts +0 -637
- package/dist/index-D0dLVjTA.d.mts +0 -637
- package/dist/index.d.ts +0 -3123
- package/dist/payload.d.ts +0 -125
- package/dist/queries/index.d.ts +0 -148
- package/dist/types-ChIsqCiw.d.ts +0 -565
- package/dist/types-FJL7j6gQ.d.mts +0 -172
- package/dist/types.d.ts +0 -407
- package/dist/utils.d.ts +0 -81
- package/dist/wormhole.d.ts +0 -167
package/dist/passkey.js
ADDED
|
@@ -0,0 +1,914 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/passkey.ts
|
|
21
|
+
var passkey_exports = {};
|
|
22
|
+
__export(passkey_exports, {
|
|
23
|
+
PasskeyManager: () => PasskeyManager,
|
|
24
|
+
VERIDEX_RP_ID: () => VERIDEX_RP_ID,
|
|
25
|
+
detectRpId: () => detectRpId,
|
|
26
|
+
supportsRelatedOrigins: () => supportsRelatedOrigins
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(passkey_exports);
|
|
29
|
+
|
|
30
|
+
// src/core/PasskeyManager.ts
|
|
31
|
+
var import_browser = require("@simplewebauthn/browser");
|
|
32
|
+
var import_ethers2 = require("ethers");
|
|
33
|
+
|
|
34
|
+
// src/utils.ts
|
|
35
|
+
var import_ethers = require("ethers");
|
|
36
|
+
function base64URLEncode(buffer) {
|
|
37
|
+
const bytes = Array.from(buffer);
|
|
38
|
+
const binary = String.fromCharCode(...bytes);
|
|
39
|
+
const base64 = btoa(binary);
|
|
40
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
41
|
+
}
|
|
42
|
+
function base64URLDecode(str) {
|
|
43
|
+
const base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
44
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
45
|
+
const binary = atob(padded);
|
|
46
|
+
const bytes = new Uint8Array(binary.length);
|
|
47
|
+
for (let i = 0; i < binary.length; i++) {
|
|
48
|
+
bytes[i] = binary.charCodeAt(i);
|
|
49
|
+
}
|
|
50
|
+
return bytes;
|
|
51
|
+
}
|
|
52
|
+
function parseDERSignature(signature) {
|
|
53
|
+
let offset = 0;
|
|
54
|
+
if (signature[offset++] !== 48) {
|
|
55
|
+
throw new Error("Invalid signature format");
|
|
56
|
+
}
|
|
57
|
+
offset++;
|
|
58
|
+
if (signature[offset++] !== 2) {
|
|
59
|
+
throw new Error("Invalid signature format");
|
|
60
|
+
}
|
|
61
|
+
const rLength = signature[offset++];
|
|
62
|
+
if (rLength === void 0) {
|
|
63
|
+
throw new Error("Invalid signature format: missing r length");
|
|
64
|
+
}
|
|
65
|
+
let r = signature.slice(offset, offset + rLength);
|
|
66
|
+
offset += rLength;
|
|
67
|
+
if (r[0] === 0 && r.length > 32) {
|
|
68
|
+
r = r.slice(1);
|
|
69
|
+
}
|
|
70
|
+
if (r.length < 32) {
|
|
71
|
+
const padded = new Uint8Array(32);
|
|
72
|
+
padded.set(r, 32 - r.length);
|
|
73
|
+
r = padded;
|
|
74
|
+
}
|
|
75
|
+
if (signature[offset++] !== 2) {
|
|
76
|
+
throw new Error("Invalid signature format");
|
|
77
|
+
}
|
|
78
|
+
const sLength = signature[offset++];
|
|
79
|
+
if (sLength === void 0) {
|
|
80
|
+
throw new Error("Invalid signature format: missing s length");
|
|
81
|
+
}
|
|
82
|
+
let s = signature.slice(offset, offset + sLength);
|
|
83
|
+
if (s[0] === 0 && s.length > 32) {
|
|
84
|
+
s = s.slice(1);
|
|
85
|
+
}
|
|
86
|
+
if (s.length < 32) {
|
|
87
|
+
const padded = new Uint8Array(32);
|
|
88
|
+
padded.set(s, 32 - s.length);
|
|
89
|
+
s = padded;
|
|
90
|
+
}
|
|
91
|
+
return { r, s };
|
|
92
|
+
}
|
|
93
|
+
function computeKeyHash(publicKeyX, publicKeyY) {
|
|
94
|
+
return import_ethers.ethers.keccak256(
|
|
95
|
+
import_ethers.ethers.solidityPacked(["uint256", "uint256"], [publicKeyX, publicKeyY])
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/core/PasskeyManager.ts
|
|
100
|
+
var VERIDEX_RP_ID = "veridex.network";
|
|
101
|
+
function detectRpId(forceLocal) {
|
|
102
|
+
if (typeof window === "undefined") return "localhost";
|
|
103
|
+
const hostname = window.location.hostname;
|
|
104
|
+
if (hostname === "localhost" || hostname === "127.0.0.1" || /^\d+\.\d+\.\d+\.\d+$/.test(hostname)) {
|
|
105
|
+
return hostname;
|
|
106
|
+
}
|
|
107
|
+
if (forceLocal) {
|
|
108
|
+
const parts = hostname.split(".");
|
|
109
|
+
if (parts.length <= 2) {
|
|
110
|
+
return hostname;
|
|
111
|
+
}
|
|
112
|
+
return parts.slice(-2).join(".");
|
|
113
|
+
}
|
|
114
|
+
return VERIDEX_RP_ID;
|
|
115
|
+
}
|
|
116
|
+
async function supportsRelatedOrigins() {
|
|
117
|
+
if (typeof window === "undefined" || !window.PublicKeyCredential) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
if ("getClientCapabilities" in PublicKeyCredential) {
|
|
121
|
+
try {
|
|
122
|
+
const getCapabilities = PublicKeyCredential.getClientCapabilities;
|
|
123
|
+
const capabilities = await getCapabilities();
|
|
124
|
+
return capabilities?.relatedOrigins === true;
|
|
125
|
+
} catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
var PasskeyManager = class _PasskeyManager {
|
|
132
|
+
config;
|
|
133
|
+
credential = null;
|
|
134
|
+
constructor(config = {}) {
|
|
135
|
+
this.config = {
|
|
136
|
+
rpName: config.rpName ?? "Veridex Protocol",
|
|
137
|
+
rpId: config.rpId ?? detectRpId(),
|
|
138
|
+
timeout: config.timeout ?? 6e4,
|
|
139
|
+
userVerification: config.userVerification ?? "required",
|
|
140
|
+
authenticatorAttachment: config.authenticatorAttachment ?? "platform",
|
|
141
|
+
relayerUrl: config.relayerUrl ?? ""
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
static isSupported() {
|
|
145
|
+
return (0, import_browser.browserSupportsWebAuthn)();
|
|
146
|
+
}
|
|
147
|
+
static async isPlatformAuthenticatorAvailable() {
|
|
148
|
+
if (typeof window === "undefined" || !window.PublicKeyCredential) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
return await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
|
|
152
|
+
}
|
|
153
|
+
async register(username, displayName) {
|
|
154
|
+
if (!_PasskeyManager.isSupported()) {
|
|
155
|
+
throw new Error("WebAuthn is not supported in this browser");
|
|
156
|
+
}
|
|
157
|
+
const challenge = import_ethers2.ethers.randomBytes(32);
|
|
158
|
+
const challengeBase64 = base64URLEncode(challenge);
|
|
159
|
+
const options = {
|
|
160
|
+
challenge: challengeBase64,
|
|
161
|
+
rp: {
|
|
162
|
+
name: this.config.rpName,
|
|
163
|
+
id: this.config.rpId
|
|
164
|
+
},
|
|
165
|
+
user: {
|
|
166
|
+
id: base64URLEncode(import_ethers2.ethers.toUtf8Bytes(username)),
|
|
167
|
+
name: username,
|
|
168
|
+
displayName
|
|
169
|
+
},
|
|
170
|
+
pubKeyCredParams: [
|
|
171
|
+
{ alg: -7, type: "public-key" },
|
|
172
|
+
// ES256 (P-256) - WebAuthn default
|
|
173
|
+
{ alg: -257, type: "public-key" },
|
|
174
|
+
// RS256 - Widely supported
|
|
175
|
+
{ alg: -8, type: "public-key" },
|
|
176
|
+
// EdDSA - Modern, efficient
|
|
177
|
+
{ alg: -35, type: "public-key" },
|
|
178
|
+
// ES384 (P-384)
|
|
179
|
+
{ alg: -36, type: "public-key" },
|
|
180
|
+
// ES512 (P-521)
|
|
181
|
+
{ alg: -37, type: "public-key" }
|
|
182
|
+
// PS256 - RSA PSS
|
|
183
|
+
],
|
|
184
|
+
authenticatorSelection: {
|
|
185
|
+
authenticatorAttachment: this.config.authenticatorAttachment,
|
|
186
|
+
userVerification: this.config.userVerification,
|
|
187
|
+
residentKey: "required",
|
|
188
|
+
requireResidentKey: true
|
|
189
|
+
},
|
|
190
|
+
timeout: this.config.timeout,
|
|
191
|
+
attestation: "none"
|
|
192
|
+
};
|
|
193
|
+
const response = await (0, import_browser.startRegistration)(options);
|
|
194
|
+
const publicKey = this.extractPublicKeyFromAttestation(response);
|
|
195
|
+
const keyHash = computeKeyHash(publicKey.x, publicKey.y);
|
|
196
|
+
this.credential = {
|
|
197
|
+
credentialId: response.id,
|
|
198
|
+
publicKeyX: publicKey.x,
|
|
199
|
+
publicKeyY: publicKey.y,
|
|
200
|
+
keyHash
|
|
201
|
+
};
|
|
202
|
+
return this.credential;
|
|
203
|
+
}
|
|
204
|
+
async sign(challenge) {
|
|
205
|
+
if (!this.credential) {
|
|
206
|
+
throw new Error("No credential set. Call register() or setCredential() first.");
|
|
207
|
+
}
|
|
208
|
+
const challengeBase64 = base64URLEncode(challenge);
|
|
209
|
+
const options = {
|
|
210
|
+
challenge: challengeBase64,
|
|
211
|
+
rpId: this.config.rpId,
|
|
212
|
+
allowCredentials: [
|
|
213
|
+
{
|
|
214
|
+
id: this.credential.credentialId,
|
|
215
|
+
type: "public-key",
|
|
216
|
+
transports: ["internal"]
|
|
217
|
+
}
|
|
218
|
+
],
|
|
219
|
+
userVerification: this.config.userVerification,
|
|
220
|
+
timeout: this.config.timeout
|
|
221
|
+
};
|
|
222
|
+
const response = await (0, import_browser.startAuthentication)(options);
|
|
223
|
+
return this.parseAuthenticationResponse(response);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Authenticate using a discoverable credential (passkey)
|
|
227
|
+
* This allows sign-in without knowing the credential ID ahead of time.
|
|
228
|
+
* The authenticator will show all available passkeys for this RP.
|
|
229
|
+
*
|
|
230
|
+
* @param challenge - Optional challenge bytes. If not provided, a random challenge is used.
|
|
231
|
+
* @returns The credential that was used to authenticate, along with the signature
|
|
232
|
+
*/
|
|
233
|
+
async authenticate(challenge) {
|
|
234
|
+
if (!_PasskeyManager.isSupported()) {
|
|
235
|
+
throw new Error("WebAuthn is not supported in this browser");
|
|
236
|
+
}
|
|
237
|
+
const actualChallenge = challenge ?? import_ethers2.ethers.randomBytes(32);
|
|
238
|
+
const challengeBase64 = base64URLEncode(actualChallenge);
|
|
239
|
+
const options = {
|
|
240
|
+
challenge: challengeBase64,
|
|
241
|
+
rpId: this.config.rpId,
|
|
242
|
+
userVerification: this.config.userVerification,
|
|
243
|
+
timeout: this.config.timeout
|
|
244
|
+
};
|
|
245
|
+
const response = await (0, import_browser.startAuthentication)(options);
|
|
246
|
+
const credentialId = response.id;
|
|
247
|
+
const signature = this.parseAuthenticationResponse(response);
|
|
248
|
+
let storedCredential = this.findCredentialById(credentialId);
|
|
249
|
+
if (storedCredential) {
|
|
250
|
+
this.credential = storedCredential;
|
|
251
|
+
return { credential: storedCredential, signature };
|
|
252
|
+
}
|
|
253
|
+
if (this.config.relayerUrl) {
|
|
254
|
+
storedCredential = await this.loadCredentialFromRelayer(credentialId);
|
|
255
|
+
if (storedCredential) {
|
|
256
|
+
this.credential = storedCredential;
|
|
257
|
+
this.addCredentialToStorage(storedCredential);
|
|
258
|
+
return { credential: storedCredential, signature };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
const hasRelayer = !!this.config.relayerUrl;
|
|
262
|
+
throw new Error(
|
|
263
|
+
"Credential not found. This passkey was registered on a different device or the data was cleared. " + (hasRelayer ? "The credential was not found. Please register a new passkey." : "Please register a new passkey or ensure the relayer URL is configured.")
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Find a credential by ID in the list of stored credentials
|
|
268
|
+
*/
|
|
269
|
+
findCredentialById(credentialId) {
|
|
270
|
+
if (typeof window === "undefined") return null;
|
|
271
|
+
const stored = this.getAllStoredCredentials();
|
|
272
|
+
return stored.find((c) => c.credentialId === credentialId) || null;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Get all credentials stored in localStorage
|
|
276
|
+
*/
|
|
277
|
+
getAllStoredCredentials(key = "veridex_credentials") {
|
|
278
|
+
if (typeof window === "undefined") return [];
|
|
279
|
+
const stored = localStorage.getItem(key);
|
|
280
|
+
if (!stored) {
|
|
281
|
+
const legacy = localStorage.getItem("veridex_credential");
|
|
282
|
+
if (legacy) {
|
|
283
|
+
try {
|
|
284
|
+
const data = JSON.parse(legacy);
|
|
285
|
+
const cred = this.parseStoredCredential(data);
|
|
286
|
+
if (cred) {
|
|
287
|
+
this.saveCredentials([cred], key);
|
|
288
|
+
localStorage.removeItem("veridex_credential");
|
|
289
|
+
return [cred];
|
|
290
|
+
}
|
|
291
|
+
} catch (e) {
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return [];
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
const data = JSON.parse(stored);
|
|
298
|
+
if (Array.isArray(data)) {
|
|
299
|
+
return data.map((item) => this.parseStoredCredential(item)).filter((c) => c !== null);
|
|
300
|
+
}
|
|
301
|
+
return [];
|
|
302
|
+
} catch (error) {
|
|
303
|
+
console.error("Failed to load credentials:", error);
|
|
304
|
+
return [];
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
parseStoredCredential(data) {
|
|
308
|
+
try {
|
|
309
|
+
return {
|
|
310
|
+
credentialId: data.credentialId,
|
|
311
|
+
publicKeyX: BigInt(data.publicKeyX),
|
|
312
|
+
publicKeyY: BigInt(data.publicKeyY),
|
|
313
|
+
keyHash: data.keyHash
|
|
314
|
+
};
|
|
315
|
+
} catch {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Save a list of credentials to localStorage
|
|
321
|
+
*/
|
|
322
|
+
saveCredentials(credentials, key = "veridex_credentials") {
|
|
323
|
+
if (typeof window === "undefined") return;
|
|
324
|
+
const data = credentials.map((c) => ({
|
|
325
|
+
credentialId: c.credentialId,
|
|
326
|
+
publicKeyX: c.publicKeyX.toString(),
|
|
327
|
+
publicKeyY: c.publicKeyY.toString(),
|
|
328
|
+
keyHash: c.keyHash
|
|
329
|
+
}));
|
|
330
|
+
localStorage.setItem(key, JSON.stringify(data));
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Add a single credential to storage (append or update)
|
|
334
|
+
*/
|
|
335
|
+
addCredentialToStorage(credential, key = "veridex_credentials") {
|
|
336
|
+
const stored = this.getAllStoredCredentials(key);
|
|
337
|
+
const existingIndex = stored.findIndex((c) => c.credentialId === credential.credentialId);
|
|
338
|
+
if (existingIndex >= 0) {
|
|
339
|
+
stored[existingIndex] = credential;
|
|
340
|
+
} else {
|
|
341
|
+
stored.push(credential);
|
|
342
|
+
}
|
|
343
|
+
this.saveCredentials(stored, key);
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Check if there's ANY stored credential for this RP
|
|
347
|
+
*/
|
|
348
|
+
hasStoredCredential() {
|
|
349
|
+
return this.getAllStoredCredentials().length > 0;
|
|
350
|
+
}
|
|
351
|
+
getCredential() {
|
|
352
|
+
return this.credential;
|
|
353
|
+
}
|
|
354
|
+
setCredential(credential) {
|
|
355
|
+
this.credential = credential;
|
|
356
|
+
}
|
|
357
|
+
createCredentialFromPublicKey(credentialId, publicKeyX, publicKeyY) {
|
|
358
|
+
const keyHash = computeKeyHash(publicKeyX, publicKeyY);
|
|
359
|
+
this.credential = {
|
|
360
|
+
credentialId,
|
|
361
|
+
publicKeyX,
|
|
362
|
+
publicKeyY,
|
|
363
|
+
keyHash
|
|
364
|
+
};
|
|
365
|
+
return this.credential;
|
|
366
|
+
}
|
|
367
|
+
clearCredential() {
|
|
368
|
+
this.credential = null;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Save the current credential to localStorage (appends to list)
|
|
372
|
+
*/
|
|
373
|
+
saveToLocalStorage(key = "veridex_credentials") {
|
|
374
|
+
if (!this.credential) {
|
|
375
|
+
throw new Error("No credential to save");
|
|
376
|
+
}
|
|
377
|
+
this.addCredentialToStorage(this.credential, key);
|
|
378
|
+
}
|
|
379
|
+
loadFromLocalStorage(key = "veridex_credentials") {
|
|
380
|
+
if (typeof window === "undefined") {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
const stored = this.getAllStoredCredentials(key);
|
|
384
|
+
if (stored.length > 0) {
|
|
385
|
+
this.credential = stored[stored.length - 1];
|
|
386
|
+
return this.credential;
|
|
387
|
+
}
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
removeFromLocalStorage(key = "veridex_credentials") {
|
|
391
|
+
if (typeof window !== "undefined") {
|
|
392
|
+
localStorage.removeItem(key);
|
|
393
|
+
localStorage.removeItem("veridex_credential");
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// =========================================================================
|
|
397
|
+
// Relayer-based Credential Storage (Cross-Device Recovery)
|
|
398
|
+
// =========================================================================
|
|
399
|
+
/**
|
|
400
|
+
* Save the current credential to the relayer for cross-device recovery.
|
|
401
|
+
* This should be called after registration.
|
|
402
|
+
*/
|
|
403
|
+
async saveCredentialToRelayer() {
|
|
404
|
+
if (!this.credential) {
|
|
405
|
+
throw new Error("No credential to save");
|
|
406
|
+
}
|
|
407
|
+
if (!this.config.relayerUrl) {
|
|
408
|
+
console.warn("Relayer URL not configured; skipping remote credential storage");
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
const response = await fetch(`${this.config.relayerUrl}/api/v1/credential`, {
|
|
413
|
+
method: "POST",
|
|
414
|
+
headers: { "Content-Type": "application/json" },
|
|
415
|
+
body: JSON.stringify({
|
|
416
|
+
keyHash: this.credential.keyHash,
|
|
417
|
+
credentialId: this.credential.credentialId,
|
|
418
|
+
publicKeyX: this.credential.publicKeyX.toString(),
|
|
419
|
+
publicKeyY: this.credential.publicKeyY.toString()
|
|
420
|
+
})
|
|
421
|
+
});
|
|
422
|
+
if (!response.ok) {
|
|
423
|
+
const errorData = await response.json().catch(() => ({}));
|
|
424
|
+
console.error("Failed to save credential to relayer:", errorData);
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
console.log("Credential saved to relayer for cross-device recovery");
|
|
428
|
+
return true;
|
|
429
|
+
} catch (error) {
|
|
430
|
+
console.error("Failed to save credential to relayer:", error);
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Load a credential from the relayer by credential ID.
|
|
436
|
+
* Used during discoverable credential authentication when localStorage is empty.
|
|
437
|
+
*/
|
|
438
|
+
async loadCredentialFromRelayer(credentialId) {
|
|
439
|
+
if (!this.config.relayerUrl) {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
try {
|
|
443
|
+
const response = await fetch(
|
|
444
|
+
`${this.config.relayerUrl}/api/v1/credential/by-id/${encodeURIComponent(credentialId)}`
|
|
445
|
+
);
|
|
446
|
+
if (!response.ok) {
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
const data = await response.json();
|
|
450
|
+
if (!data.exists) {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
if (!data.credentialId || !data.publicKeyX || !data.publicKeyY || !data.keyHash) {
|
|
454
|
+
console.warn("Relayer returned incomplete credential data:", {
|
|
455
|
+
hasCredentialId: !!data.credentialId,
|
|
456
|
+
hasPublicKeyX: !!data.publicKeyX,
|
|
457
|
+
hasPublicKeyY: !!data.publicKeyY,
|
|
458
|
+
hasKeyHash: !!data.keyHash
|
|
459
|
+
});
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
return {
|
|
463
|
+
credentialId: data.credentialId,
|
|
464
|
+
publicKeyX: BigInt(data.publicKeyX),
|
|
465
|
+
publicKeyY: BigInt(data.publicKeyY),
|
|
466
|
+
keyHash: data.keyHash
|
|
467
|
+
};
|
|
468
|
+
} catch (error) {
|
|
469
|
+
console.error("Failed to load credential from relayer:", error);
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Load a credential from the relayer by keyHash.
|
|
475
|
+
* Useful when you know the user's keyHash but not their credential ID.
|
|
476
|
+
*/
|
|
477
|
+
async loadCredentialFromRelayerByKeyHash(keyHash) {
|
|
478
|
+
if (!this.config.relayerUrl) {
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
const response = await fetch(
|
|
483
|
+
`${this.config.relayerUrl}/api/v1/credential/${encodeURIComponent(keyHash)}`
|
|
484
|
+
);
|
|
485
|
+
if (!response.ok) {
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
const data = await response.json();
|
|
489
|
+
if (!data.exists) {
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
if (!data.credentialId || !data.publicKeyX || !data.publicKeyY || !data.keyHash) {
|
|
493
|
+
console.warn("Relayer returned incomplete credential data for keyHash:", {
|
|
494
|
+
hasCredentialId: !!data.credentialId,
|
|
495
|
+
hasPublicKeyX: !!data.publicKeyX,
|
|
496
|
+
hasPublicKeyY: !!data.publicKeyY,
|
|
497
|
+
hasKeyHash: !!data.keyHash
|
|
498
|
+
});
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
return {
|
|
502
|
+
credentialId: data.credentialId,
|
|
503
|
+
publicKeyX: BigInt(data.publicKeyX),
|
|
504
|
+
publicKeyY: BigInt(data.publicKeyY),
|
|
505
|
+
keyHash: data.keyHash
|
|
506
|
+
};
|
|
507
|
+
} catch (error) {
|
|
508
|
+
console.error("Failed to load credential from relayer:", error);
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
// =========================================================================
|
|
513
|
+
// Backup Passkey & Device Migration (Phase 3)
|
|
514
|
+
// =========================================================================
|
|
515
|
+
/**
|
|
516
|
+
* Register a backup passkey for the current identity.
|
|
517
|
+
*
|
|
518
|
+
* This creates a new WebAuthn credential on this device/platform that becomes
|
|
519
|
+
* an additional authorized key for the same Veridex identity. The caller
|
|
520
|
+
* must submit the returned credential to VeridexHub.addKey() for on-chain registration.
|
|
521
|
+
*
|
|
522
|
+
* Use cases:
|
|
523
|
+
* - "Add this device" flow when signing in on a new machine
|
|
524
|
+
* - Proactive backup creation on a separate authenticator
|
|
525
|
+
* - Cross-ecosystem redundancy (iCloud + Google Password Manager)
|
|
526
|
+
*
|
|
527
|
+
* @param username - Username for the new credential (typically same as primary)
|
|
528
|
+
* @param displayName - Display name for the backup (e.g., "MacBook Pro Backup")
|
|
529
|
+
* @param excludeCredentialIds - Credential IDs to exclude (prevents re-registering same authenticator)
|
|
530
|
+
* @returns The newly registered backup credential
|
|
531
|
+
*/
|
|
532
|
+
async registerBackupPasskey(username, displayName, excludeCredentialIds) {
|
|
533
|
+
if (!_PasskeyManager.isSupported()) {
|
|
534
|
+
throw new Error("WebAuthn is not supported in this browser");
|
|
535
|
+
}
|
|
536
|
+
const challenge = import_ethers2.ethers.randomBytes(32);
|
|
537
|
+
const challengeBase64 = base64URLEncode(challenge);
|
|
538
|
+
const excludeList = excludeCredentialIds ?? this.getAllStoredCredentials().map((c) => c.credentialId);
|
|
539
|
+
const options = {
|
|
540
|
+
challenge: challengeBase64,
|
|
541
|
+
rp: {
|
|
542
|
+
name: this.config.rpName,
|
|
543
|
+
id: this.config.rpId
|
|
544
|
+
},
|
|
545
|
+
user: {
|
|
546
|
+
id: base64URLEncode(import_ethers2.ethers.toUtf8Bytes(username)),
|
|
547
|
+
name: username,
|
|
548
|
+
displayName
|
|
549
|
+
},
|
|
550
|
+
pubKeyCredParams: [
|
|
551
|
+
{ alg: -7, type: "public-key" },
|
|
552
|
+
// ES256 (P-256)
|
|
553
|
+
{ alg: -257, type: "public-key" },
|
|
554
|
+
// RS256
|
|
555
|
+
{ alg: -8, type: "public-key" }
|
|
556
|
+
// EdDSA
|
|
557
|
+
],
|
|
558
|
+
excludeCredentials: excludeList.map((id) => ({
|
|
559
|
+
id,
|
|
560
|
+
type: "public-key",
|
|
561
|
+
transports: ["internal", "hybrid"]
|
|
562
|
+
})),
|
|
563
|
+
authenticatorSelection: {
|
|
564
|
+
// Allow cross-platform for backup on security keys
|
|
565
|
+
userVerification: this.config.userVerification,
|
|
566
|
+
residentKey: "required",
|
|
567
|
+
requireResidentKey: true
|
|
568
|
+
},
|
|
569
|
+
timeout: this.config.timeout,
|
|
570
|
+
attestation: "none"
|
|
571
|
+
};
|
|
572
|
+
const response = await (0, import_browser.startRegistration)(options);
|
|
573
|
+
const publicKey = this.extractPublicKeyFromAttestation(response);
|
|
574
|
+
const keyHash = computeKeyHash(publicKey.x, publicKey.y);
|
|
575
|
+
const backupCredential = {
|
|
576
|
+
credentialId: response.id,
|
|
577
|
+
publicKeyX: publicKey.x,
|
|
578
|
+
publicKeyY: publicKey.y,
|
|
579
|
+
keyHash
|
|
580
|
+
};
|
|
581
|
+
this.addCredentialToStorage(backupCredential);
|
|
582
|
+
return backupCredential;
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Get registration info for backup state from a registration response.
|
|
586
|
+
*
|
|
587
|
+
* This extracts the backup eligibility (BE) and backup state (BS) flags
|
|
588
|
+
* from the authenticator data, which indicate whether the credential
|
|
589
|
+
* is eligible for cloud sync and whether it is currently synced.
|
|
590
|
+
*
|
|
591
|
+
* @param authenticatorData - Hex-encoded authenticator data from registration
|
|
592
|
+
* @returns Backup flags, or null if not determinable
|
|
593
|
+
*/
|
|
594
|
+
static parseBackupFlags(authenticatorData) {
|
|
595
|
+
try {
|
|
596
|
+
const data = import_ethers2.ethers.getBytes(authenticatorData);
|
|
597
|
+
if (data.length < 37) return null;
|
|
598
|
+
const flags = data[32];
|
|
599
|
+
return {
|
|
600
|
+
backupEligible: (flags & 8) !== 0,
|
|
601
|
+
backupState: (flags & 16) !== 0
|
|
602
|
+
};
|
|
603
|
+
} catch {
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Get the number of credentials stored locally.
|
|
609
|
+
*/
|
|
610
|
+
getStoredCredentialCount() {
|
|
611
|
+
return this.getAllStoredCredentials().length;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Get all credential IDs stored locally (for exclude lists).
|
|
615
|
+
*/
|
|
616
|
+
getStoredCredentialIds() {
|
|
617
|
+
return this.getAllStoredCredentials().map((c) => c.credentialId);
|
|
618
|
+
}
|
|
619
|
+
extractPublicKeyFromAttestation(response) {
|
|
620
|
+
const attestationObject = base64URLDecode(response.response.attestationObject);
|
|
621
|
+
let offset = 0;
|
|
622
|
+
if (attestationObject[offset] >= 160 && attestationObject[offset] <= 191) {
|
|
623
|
+
offset++;
|
|
624
|
+
}
|
|
625
|
+
while (offset < attestationObject.length - 37) {
|
|
626
|
+
if (attestationObject[offset] === 104 && // text string, 8 bytes
|
|
627
|
+
attestationObject[offset + 1] === 97 && // 'a'
|
|
628
|
+
attestationObject[offset + 2] === 117 && // 'u'
|
|
629
|
+
attestationObject[offset + 3] === 116 && // 't'
|
|
630
|
+
attestationObject[offset + 4] === 104 && // 'h'
|
|
631
|
+
attestationObject[offset + 5] === 68 && // 'D'
|
|
632
|
+
attestationObject[offset + 6] === 97 && // 'a'
|
|
633
|
+
attestationObject[offset + 7] === 116 && // 't'
|
|
634
|
+
attestationObject[offset + 8] === 97) {
|
|
635
|
+
offset += 9;
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
offset++;
|
|
639
|
+
}
|
|
640
|
+
if (attestationObject[offset] === 88 || attestationObject[offset] === 89) {
|
|
641
|
+
const lengthBytes = attestationObject[offset] === 88 ? 1 : 2;
|
|
642
|
+
offset += 1 + lengthBytes;
|
|
643
|
+
}
|
|
644
|
+
offset += 32;
|
|
645
|
+
offset += 1;
|
|
646
|
+
offset += 4;
|
|
647
|
+
offset += 16;
|
|
648
|
+
const credIdLen = attestationObject[offset] << 8 | attestationObject[offset + 1];
|
|
649
|
+
offset += 2;
|
|
650
|
+
offset += credIdLen;
|
|
651
|
+
const coseKey = attestationObject.slice(offset);
|
|
652
|
+
console.log("COSE key length:", coseKey.length);
|
|
653
|
+
console.log("COSE key hex:", this.bytesToHex(coseKey.slice(0, Math.min(100, coseKey.length))));
|
|
654
|
+
const { x, y } = this.parseCOSEKey(coseKey);
|
|
655
|
+
return { x, y };
|
|
656
|
+
}
|
|
657
|
+
parseCOSEKey(coseKey) {
|
|
658
|
+
console.log("COSE key length:", coseKey.length);
|
|
659
|
+
console.log("COSE key hex:", this.bytesToHex(coseKey));
|
|
660
|
+
const parsed = this.tryParseCOSEKeyStrategies(coseKey);
|
|
661
|
+
if (parsed) {
|
|
662
|
+
return parsed;
|
|
663
|
+
}
|
|
664
|
+
return this.parseCOSEKeyWithCBORStructure(coseKey);
|
|
665
|
+
}
|
|
666
|
+
tryParseCOSEKeyStrategies(coseKey) {
|
|
667
|
+
const keyBytes = new Uint8Array(coseKey);
|
|
668
|
+
for (let i = 0; i < keyBytes.length - 40; i++) {
|
|
669
|
+
if (keyBytes[i] === 88 && keyBytes[i + 1] === 32) {
|
|
670
|
+
const potentialX = keyBytes.slice(i + 2, i + 34);
|
|
671
|
+
for (let j = i + 34; j < keyBytes.length - 34; j++) {
|
|
672
|
+
if (keyBytes[j] === 88 && keyBytes[j + 1] === 32) {
|
|
673
|
+
const potentialY = keyBytes.slice(j + 2, j + 34);
|
|
674
|
+
if (this.isValidCoordinate(potentialX) && this.isValidCoordinate(potentialY)) {
|
|
675
|
+
console.log("Found coordinates via pattern matching");
|
|
676
|
+
return {
|
|
677
|
+
x: this.bytesToBigInt(potentialX),
|
|
678
|
+
y: this.bytesToBigInt(potentialY)
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return this.tryParseASN1Structure(keyBytes);
|
|
686
|
+
}
|
|
687
|
+
parseCOSEKeyWithCBORStructure(coseKey) {
|
|
688
|
+
const bytes = new Uint8Array(coseKey);
|
|
689
|
+
let xBytes = null;
|
|
690
|
+
let yBytes = null;
|
|
691
|
+
let i = 0;
|
|
692
|
+
while (i < bytes.length) {
|
|
693
|
+
if (bytes[i] === 88) {
|
|
694
|
+
const length = bytes[i + 1];
|
|
695
|
+
if (length === 32) {
|
|
696
|
+
const start = i + 2;
|
|
697
|
+
const end = start + 32;
|
|
698
|
+
if (end <= bytes.length) {
|
|
699
|
+
const coordinate = bytes.slice(start, end);
|
|
700
|
+
if (!xBytes) {
|
|
701
|
+
xBytes = coordinate;
|
|
702
|
+
console.log("Found x at offset", i);
|
|
703
|
+
} else if (!yBytes) {
|
|
704
|
+
yBytes = coordinate;
|
|
705
|
+
console.log("Found y at offset", i);
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
i = end;
|
|
710
|
+
} else {
|
|
711
|
+
i += length + 2;
|
|
712
|
+
}
|
|
713
|
+
} else if (bytes[i] === 66) {
|
|
714
|
+
if (i + 3 < bytes.length) {
|
|
715
|
+
const length = bytes[i + 1] << 8 | bytes[i + 2];
|
|
716
|
+
if (length === 32) {
|
|
717
|
+
const start = i + 3;
|
|
718
|
+
const end = start + 32;
|
|
719
|
+
if (end <= bytes.length) {
|
|
720
|
+
const coordinate = bytes.slice(start, end);
|
|
721
|
+
if (!xBytes) {
|
|
722
|
+
xBytes = coordinate;
|
|
723
|
+
console.log("Found x at offset", i);
|
|
724
|
+
} else if (!yBytes) {
|
|
725
|
+
yBytes = coordinate;
|
|
726
|
+
console.log("Found y at offset", i);
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
i = end;
|
|
731
|
+
} else {
|
|
732
|
+
i += length + 3;
|
|
733
|
+
}
|
|
734
|
+
} else {
|
|
735
|
+
i++;
|
|
736
|
+
}
|
|
737
|
+
} else if (bytes[i] === 64) {
|
|
738
|
+
i++;
|
|
739
|
+
while (i < bytes.length && bytes[i] !== 255) {
|
|
740
|
+
if (bytes[i] === 4 && i + 65 <= bytes.length) {
|
|
741
|
+
const x = bytes.slice(i + 1, i + 33);
|
|
742
|
+
const y = bytes.slice(i + 33, i + 65);
|
|
743
|
+
if (this.isValidCoordinate(x) && this.isValidCoordinate(y)) {
|
|
744
|
+
xBytes = x;
|
|
745
|
+
yBytes = y;
|
|
746
|
+
console.log("Found coordinates in uncompressed point format");
|
|
747
|
+
break;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
i++;
|
|
751
|
+
}
|
|
752
|
+
if (xBytes && yBytes) break;
|
|
753
|
+
} else {
|
|
754
|
+
i++;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
if (!xBytes || !yBytes) {
|
|
758
|
+
const potentialCoords = this.find32ByteSequences(bytes);
|
|
759
|
+
if (potentialCoords.length >= 2) {
|
|
760
|
+
xBytes = potentialCoords[0];
|
|
761
|
+
yBytes = potentialCoords[1];
|
|
762
|
+
console.log("Fallback: Using first two 32-byte sequences as coordinates");
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
if (!xBytes || !yBytes) {
|
|
766
|
+
console.error("Failed to find coordinates in COSE key. Full dump:");
|
|
767
|
+
console.error("Hex:", this.bytesToHex(bytes));
|
|
768
|
+
console.error("Structure analysis:");
|
|
769
|
+
this.analyzeCOSEStructure(bytes);
|
|
770
|
+
throw new Error("Failed to extract public key coordinates from COSE key. Check console for details.");
|
|
771
|
+
}
|
|
772
|
+
return {
|
|
773
|
+
x: this.bytesToBigInt(xBytes),
|
|
774
|
+
y: this.bytesToBigInt(yBytes)
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
tryParseASN1Structure(bytes) {
|
|
778
|
+
if (bytes[0] === 48) {
|
|
779
|
+
let offset = 2;
|
|
780
|
+
if (bytes[offset] === 3) {
|
|
781
|
+
offset += 2;
|
|
782
|
+
if (bytes[offset] === 48) {
|
|
783
|
+
offset += 2;
|
|
784
|
+
offset += 12;
|
|
785
|
+
if (bytes[offset] === 3 && bytes[offset + 2] === 4) {
|
|
786
|
+
offset += 3;
|
|
787
|
+
const x = bytes.slice(offset, offset + 32);
|
|
788
|
+
const y = bytes.slice(offset + 32, offset + 64);
|
|
789
|
+
if (x.length === 32 && y.length === 32) {
|
|
790
|
+
console.log("Found coordinates via ASN.1 parsing");
|
|
791
|
+
return {
|
|
792
|
+
x: this.bytesToBigInt(x),
|
|
793
|
+
y: this.bytesToBigInt(y)
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
return null;
|
|
801
|
+
}
|
|
802
|
+
find32ByteSequences(bytes) {
|
|
803
|
+
const sequences = [];
|
|
804
|
+
for (let i = 0; i <= bytes.length - 32; i++) {
|
|
805
|
+
const sequence = bytes.slice(i, i + 32);
|
|
806
|
+
if (this.isValidCoordinate(sequence)) {
|
|
807
|
+
sequences.push(sequence);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return sequences;
|
|
811
|
+
}
|
|
812
|
+
isValidCoordinate(bytes) {
|
|
813
|
+
if (bytes.length !== 32) return false;
|
|
814
|
+
let allZeros = true;
|
|
815
|
+
let allOnes = true;
|
|
816
|
+
for (const byte of bytes) {
|
|
817
|
+
if (byte !== 0) allZeros = false;
|
|
818
|
+
if (byte !== 255) allOnes = false;
|
|
819
|
+
}
|
|
820
|
+
return !allZeros && !allOnes;
|
|
821
|
+
}
|
|
822
|
+
bytesToBigInt(bytes) {
|
|
823
|
+
return BigInt("0x" + this.bytesToHex(bytes));
|
|
824
|
+
}
|
|
825
|
+
bytesToHex(bytes) {
|
|
826
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
827
|
+
}
|
|
828
|
+
analyzeCOSEStructure(bytes) {
|
|
829
|
+
console.log("COSE Structure Analysis:");
|
|
830
|
+
console.log("First 20 bytes:", this.bytesToHex(bytes.slice(0, 20)));
|
|
831
|
+
const firstByte = bytes[0];
|
|
832
|
+
console.log("First byte (0x" + firstByte.toString(16) + "):");
|
|
833
|
+
if (firstByte >= 160 && firstByte <= 191) {
|
|
834
|
+
console.log("- Definite length map with", firstByte & 31, "pairs");
|
|
835
|
+
} else if (firstByte === 191) {
|
|
836
|
+
console.log("- Indefinite length map");
|
|
837
|
+
} else if (firstByte === 4) {
|
|
838
|
+
console.log("- Byte string");
|
|
839
|
+
} else if (firstByte === 2) {
|
|
840
|
+
console.log("- Negative integer");
|
|
841
|
+
}
|
|
842
|
+
let count32 = 0;
|
|
843
|
+
for (let i = 0; i <= bytes.length - 32; i++) {
|
|
844
|
+
const chunk = bytes.slice(i, i + 32);
|
|
845
|
+
if (this.isValidCoordinate(chunk)) {
|
|
846
|
+
console.log(
|
|
847
|
+
`Found valid 32-byte sequence at offset ${i}:`,
|
|
848
|
+
this.bytesToHex(chunk.slice(0, 8)) + "..."
|
|
849
|
+
);
|
|
850
|
+
count32++;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
console.log(`Total valid 32-byte sequences: ${count32}`);
|
|
854
|
+
console.log("Looking for known markers:");
|
|
855
|
+
const markers = [
|
|
856
|
+
{ byte: 4, name: "Uncompressed point marker" },
|
|
857
|
+
{ byte: 3, name: "BIT STRING" },
|
|
858
|
+
{ byte: 48, name: "SEQUENCE" },
|
|
859
|
+
{ byte: 2, name: "INTEGER" },
|
|
860
|
+
{ byte: 6, name: "OBJECT IDENTIFIER" },
|
|
861
|
+
{ byte: 88, name: "Byte string with length byte" },
|
|
862
|
+
{ byte: 66, name: "Byte string with 2-byte length" },
|
|
863
|
+
{ byte: 64, name: "Byte string indefinite length" },
|
|
864
|
+
{ byte: 160, name: "Map start" },
|
|
865
|
+
{ byte: 191, name: "Indefinite map start" }
|
|
866
|
+
];
|
|
867
|
+
for (const marker of markers) {
|
|
868
|
+
const indices = [];
|
|
869
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
870
|
+
if (bytes[i] === marker.byte) {
|
|
871
|
+
indices.push(i);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
if (indices.length > 0) {
|
|
875
|
+
console.log(` ${marker.name} (0x${marker.byte.toString(16)}) at positions:`, indices.slice(0, 5).join(", "));
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
parseAuthenticationResponse(response) {
|
|
880
|
+
const authenticatorData = base64URLDecode(response.response.authenticatorData);
|
|
881
|
+
const clientDataJSON = response.response.clientDataJSON;
|
|
882
|
+
const signature = base64URLDecode(response.response.signature);
|
|
883
|
+
const { r, s } = parseDERSignature(signature);
|
|
884
|
+
const P256_N = BigInt("0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551");
|
|
885
|
+
const P256_N_DIV_2 = BigInt(
|
|
886
|
+
"0x7FFFFFFF800000007FFFFFFFFFFFFFFFDE737D56D38BCF4279DCE5617E3192A8"
|
|
887
|
+
);
|
|
888
|
+
const clientDataStr = new TextDecoder().decode(base64URLDecode(clientDataJSON));
|
|
889
|
+
const challengeIndex = clientDataStr.indexOf('"challenge"');
|
|
890
|
+
const typeIndex = clientDataStr.indexOf('"type"');
|
|
891
|
+
if (challengeIndex === -1 || typeIndex === -1) {
|
|
892
|
+
throw new Error("Invalid clientDataJSON format");
|
|
893
|
+
}
|
|
894
|
+
return {
|
|
895
|
+
authenticatorData: import_ethers2.ethers.hexlify(authenticatorData),
|
|
896
|
+
clientDataJSON: clientDataStr,
|
|
897
|
+
challengeIndex,
|
|
898
|
+
typeIndex,
|
|
899
|
+
r: this.bytesToBigInt(r),
|
|
900
|
+
s: (() => {
|
|
901
|
+
const sBig = this.bytesToBigInt(s);
|
|
902
|
+
return sBig > P256_N_DIV_2 ? P256_N - sBig : sBig;
|
|
903
|
+
})()
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
908
|
+
0 && (module.exports = {
|
|
909
|
+
PasskeyManager,
|
|
910
|
+
VERIDEX_RP_ID,
|
|
911
|
+
detectRpId,
|
|
912
|
+
supportsRelatedOrigins
|
|
913
|
+
});
|
|
914
|
+
//# sourceMappingURL=passkey.js.map
|