@soropass/core 0.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/dist/checkAuth-C2AcXqFe.d.cts +110 -0
- package/dist/checkAuth-C2AcXqFe.d.ts +110 -0
- package/dist/chunk-AHFGTIBY.js +36 -0
- package/dist/chunk-AHFGTIBY.js.map +1 -0
- package/dist/chunk-AMYFJQAC.js +29 -0
- package/dist/chunk-AMYFJQAC.js.map +1 -0
- package/dist/chunk-BIRF5I6I.js +265 -0
- package/dist/chunk-BIRF5I6I.js.map +1 -0
- package/dist/chunk-JNJ2FY45.js +209 -0
- package/dist/chunk-JNJ2FY45.js.map +1 -0
- package/dist/chunk-LB4HOTLV.js +135 -0
- package/dist/chunk-LB4HOTLV.js.map +1 -0
- package/dist/chunk-LM2AZXKZ.js +108 -0
- package/dist/chunk-LM2AZXKZ.js.map +1 -0
- package/dist/chunk-LUDXP56M.js +3 -0
- package/dist/chunk-LUDXP56M.js.map +1 -0
- package/dist/chunk-MMB4ZIW3.js +48 -0
- package/dist/chunk-MMB4ZIW3.js.map +1 -0
- package/dist/chunk-MOLZIRIH.js +22 -0
- package/dist/chunk-MOLZIRIH.js.map +1 -0
- package/dist/chunk-OGSUGATA.js +3 -0
- package/dist/chunk-OGSUGATA.js.map +1 -0
- package/dist/chunk-TPBBZNPP.js +30 -0
- package/dist/chunk-TPBBZNPP.js.map +1 -0
- package/dist/chunk-UQKNPO44.js +32 -0
- package/dist/chunk-UQKNPO44.js.map +1 -0
- package/dist/chunk-UV5HORMF.js +175 -0
- package/dist/chunk-UV5HORMF.js.map +1 -0
- package/dist/chunk-W75OQKHR.js +133 -0
- package/dist/chunk-W75OQKHR.js.map +1 -0
- package/dist/connect.cjs +166 -0
- package/dist/connect.cjs.map +1 -0
- package/dist/connect.d.cts +24 -0
- package/dist/connect.d.ts +24 -0
- package/dist/connect.js +6 -0
- package/dist/connect.js.map +1 -0
- package/dist/create.cjs +483 -0
- package/dist/create.cjs.map +1 -0
- package/dist/create.d.cts +106 -0
- package/dist/create.d.ts +106 -0
- package/dist/create.js +8 -0
- package/dist/create.js.map +1 -0
- package/dist/index.cjs +1542 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +197 -0
- package/dist/index.d.ts +197 -0
- package/dist/index.js +354 -0
- package/dist/index.js.map +1 -0
- package/dist/recover-0wbOXDjR.d.cts +23 -0
- package/dist/recover-COnsxhYk.d.ts +23 -0
- package/dist/recover.cjs +802 -0
- package/dist/recover.cjs.map +1 -0
- package/dist/recover.d.cts +49 -0
- package/dist/recover.d.ts +49 -0
- package/dist/recover.js +10 -0
- package/dist/recover.js.map +1 -0
- package/dist/sign-Cfg0NM0T.d.cts +73 -0
- package/dist/sign-Cfg0NM0T.d.ts +73 -0
- package/dist/sign.cjs +883 -0
- package/dist/sign.cjs.map +1 -0
- package/dist/sign.d.cts +143 -0
- package/dist/sign.d.ts +143 -0
- package/dist/sign.js +8 -0
- package/dist/sign.js.map +1 -0
- package/dist/submit-BDKADQq0.d.cts +112 -0
- package/dist/submit-BO0Nh49I.d.ts +112 -0
- package/dist/testing.cjs +955 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +86 -0
- package/dist/testing.d.ts +86 -0
- package/dist/testing.js +235 -0
- package/dist/testing.js.map +1 -0
- package/dist/types--WNl-Sda.d.cts +125 -0
- package/dist/types--WNl-Sda.d.ts +125 -0
- package/dist/types-dMRWEEo0.d.cts +73 -0
- package/dist/types-lW2h8Uxn.d.ts +73 -0
- package/dist/types.cjs +44 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +5 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +117 -0
package/dist/sign.cjs
ADDED
|
@@ -0,0 +1,883 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var nist = require('@noble/curves/nist');
|
|
4
|
+
var sha256 = require('@noble/hashes/sha256');
|
|
5
|
+
var stellarSdk = require('@stellar/stellar-sdk');
|
|
6
|
+
|
|
7
|
+
// src/webauthn/signature.ts
|
|
8
|
+
var KitError = class _KitError extends Error {
|
|
9
|
+
code;
|
|
10
|
+
name = "KitError";
|
|
11
|
+
constructor(code, message, options) {
|
|
12
|
+
super(message ?? code, options);
|
|
13
|
+
this.code = code;
|
|
14
|
+
Object.setPrototypeOf(this, _KitError.prototype);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// src/webauthn/signature.ts
|
|
19
|
+
var MAX_DER_LENGTH = 72;
|
|
20
|
+
function derToCompact(der) {
|
|
21
|
+
if (der.length > MAX_DER_LENGTH) {
|
|
22
|
+
throw new KitError(
|
|
23
|
+
"INVALID_SIGNATURE_DER",
|
|
24
|
+
`DER signature is ${der.length} bytes, exceeds max ${MAX_DER_LENGTH}`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return nist.p256.Signature.fromDER(der).toCompactRawBytes();
|
|
29
|
+
} catch (cause) {
|
|
30
|
+
throw new KitError("INVALID_SIGNATURE_DER", "failed to parse DER signature", { cause });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function assertCompactLength(compactSignature) {
|
|
34
|
+
if (compactSignature.length !== 64) {
|
|
35
|
+
throw new KitError(
|
|
36
|
+
"INVALID_SIGNATURE_DER",
|
|
37
|
+
`compact signature must be 64 bytes, got ${compactSignature.length}`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function isLowS(compactSignature) {
|
|
42
|
+
assertCompactLength(compactSignature);
|
|
43
|
+
return !nist.p256.Signature.fromCompact(compactSignature).hasHighS();
|
|
44
|
+
}
|
|
45
|
+
function normalizeLowS(compactSignature) {
|
|
46
|
+
assertCompactLength(compactSignature);
|
|
47
|
+
return nist.p256.Signature.fromCompact(compactSignature).normalizeS().toCompactRawBytes();
|
|
48
|
+
}
|
|
49
|
+
function derToCompactLowS(der) {
|
|
50
|
+
return normalizeLowS(derToCompact(der));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/internal/bytes.ts
|
|
54
|
+
function concatBytes(...arrays) {
|
|
55
|
+
const total = arrays.reduce((n, a) => n + a.length, 0);
|
|
56
|
+
const out = new Uint8Array(total);
|
|
57
|
+
let offset = 0;
|
|
58
|
+
for (const a of arrays) {
|
|
59
|
+
out.set(a, offset);
|
|
60
|
+
offset += a.length;
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
function equalBytes(a, b) {
|
|
65
|
+
if (a.length !== b.length) return false;
|
|
66
|
+
let diff = 0;
|
|
67
|
+
for (let i = 0; i < a.length; i += 1) diff |= (a[i] ?? 0) ^ (b[i] ?? 0);
|
|
68
|
+
return diff === 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/webauthn/payload.ts
|
|
72
|
+
function reconstructSignedPayload(input) {
|
|
73
|
+
const clientDataHash = sha256.sha256(input.clientDataJSON);
|
|
74
|
+
return sha256.sha256(concatBytes(input.authenticatorData, clientDataHash));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/internal/encoding.ts
|
|
78
|
+
function bytesToBase64Url(bytes) {
|
|
79
|
+
let binary = "";
|
|
80
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
81
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
82
|
+
}
|
|
83
|
+
function base64UrlToBytes(input) {
|
|
84
|
+
const base64 = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
85
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
86
|
+
const binary = atob(padded);
|
|
87
|
+
const out = new Uint8Array(new ArrayBuffer(binary.length));
|
|
88
|
+
for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
function utf8ToBytes(text) {
|
|
92
|
+
return new TextEncoder().encode(text);
|
|
93
|
+
}
|
|
94
|
+
function bytesToUtf8(bytes) {
|
|
95
|
+
return new TextDecoder().decode(bytes);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/webauthn/clientData.ts
|
|
99
|
+
function encodeChallenge(challenge) {
|
|
100
|
+
return bytesToBase64Url(challenge);
|
|
101
|
+
}
|
|
102
|
+
function decodeChallenge(challenge) {
|
|
103
|
+
return base64UrlToBytes(challenge);
|
|
104
|
+
}
|
|
105
|
+
function parseClientDataJSON(clientDataJSON) {
|
|
106
|
+
let raw;
|
|
107
|
+
try {
|
|
108
|
+
raw = JSON.parse(bytesToUtf8(clientDataJSON));
|
|
109
|
+
} catch (cause) {
|
|
110
|
+
throw new KitError("UNSUPPORTED_AUTHENTICATOR", "clientDataJSON is not valid JSON", { cause });
|
|
111
|
+
}
|
|
112
|
+
if (typeof raw !== "object" || raw === null) {
|
|
113
|
+
throw new KitError("UNSUPPORTED_AUTHENTICATOR", "clientDataJSON is not a JSON object");
|
|
114
|
+
}
|
|
115
|
+
const obj = raw;
|
|
116
|
+
const { type, challenge, origin, crossOrigin } = obj;
|
|
117
|
+
if (typeof type !== "string" || typeof challenge !== "string" || typeof origin !== "string") {
|
|
118
|
+
throw new KitError("UNSUPPORTED_AUTHENTICATOR", "clientDataJSON is missing required fields");
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
type,
|
|
122
|
+
challenge,
|
|
123
|
+
origin,
|
|
124
|
+
crossOrigin: typeof crossOrigin === "boolean" ? crossOrigin : void 0
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function verifyClientDataJSON(clientDataJSON, expected) {
|
|
128
|
+
const data = parseClientDataJSON(clientDataJSON);
|
|
129
|
+
if (data.type !== "webauthn.get") {
|
|
130
|
+
throw new KitError(
|
|
131
|
+
"UNSUPPORTED_AUTHENTICATOR",
|
|
132
|
+
`clientDataJSON.type "${data.type}" is not "webauthn.get"`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
const allowed = Array.isArray(expected.origin) ? expected.origin : [expected.origin];
|
|
136
|
+
if (!allowed.includes(data.origin)) {
|
|
137
|
+
throw new KitError("ORIGIN_MISMATCH", `origin "${data.origin}" not in [${allowed.join(", ")}]`);
|
|
138
|
+
}
|
|
139
|
+
if (data.challenge !== expected.challenge) {
|
|
140
|
+
throw new KitError("CHALLENGE_MISMATCH", "clientDataJSON challenge does not match expected");
|
|
141
|
+
}
|
|
142
|
+
return data;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/internal/cbor.ts
|
|
146
|
+
function byteAt(bytes, index) {
|
|
147
|
+
const value = bytes[index];
|
|
148
|
+
if (value === void 0) throw new Error(`CBOR: unexpected end of input at byte ${index}`);
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
var MAX_DEPTH = 16;
|
|
152
|
+
function decodeCbor(bytes, offset = 0, depth = 0) {
|
|
153
|
+
if (depth > MAX_DEPTH) throw new Error("CBOR: maximum nesting depth exceeded");
|
|
154
|
+
const first = byteAt(bytes, offset);
|
|
155
|
+
const major = first >> 5;
|
|
156
|
+
const info = first & 31;
|
|
157
|
+
let pos = offset + 1;
|
|
158
|
+
let argument;
|
|
159
|
+
if (info < 24) {
|
|
160
|
+
argument = info;
|
|
161
|
+
} else if (info === 24) {
|
|
162
|
+
argument = byteAt(bytes, pos);
|
|
163
|
+
pos += 1;
|
|
164
|
+
} else if (info === 25) {
|
|
165
|
+
argument = byteAt(bytes, pos) << 8 | byteAt(bytes, pos + 1);
|
|
166
|
+
pos += 2;
|
|
167
|
+
} else if (info === 26) {
|
|
168
|
+
argument = byteAt(bytes, pos) * 16777216 + (byteAt(bytes, pos + 1) << 16) + (byteAt(bytes, pos + 2) << 8) + byteAt(bytes, pos + 3);
|
|
169
|
+
pos += 4;
|
|
170
|
+
} else {
|
|
171
|
+
throw new Error(`CBOR: unsupported length encoding (additional info ${info})`);
|
|
172
|
+
}
|
|
173
|
+
switch (major) {
|
|
174
|
+
case 0:
|
|
175
|
+
return { value: argument, length: pos - offset };
|
|
176
|
+
case 1:
|
|
177
|
+
return { value: -1 - argument, length: pos - offset };
|
|
178
|
+
case 2: {
|
|
179
|
+
const end = pos + argument;
|
|
180
|
+
if (end > bytes.length) throw new Error("CBOR: byte string exceeds input");
|
|
181
|
+
return { value: bytes.slice(pos, end), length: end - offset };
|
|
182
|
+
}
|
|
183
|
+
case 3: {
|
|
184
|
+
const end = pos + argument;
|
|
185
|
+
if (end > bytes.length) throw new Error("CBOR: text string exceeds input");
|
|
186
|
+
return { value: new TextDecoder().decode(bytes.slice(pos, end)), length: end - offset };
|
|
187
|
+
}
|
|
188
|
+
case 4: {
|
|
189
|
+
const items = [];
|
|
190
|
+
let cursor = pos;
|
|
191
|
+
for (let i = 0; i < argument; i += 1) {
|
|
192
|
+
const item = decodeCbor(bytes, cursor, depth + 1);
|
|
193
|
+
items.push(item.value);
|
|
194
|
+
cursor += item.length;
|
|
195
|
+
}
|
|
196
|
+
return { value: items, length: cursor - offset };
|
|
197
|
+
}
|
|
198
|
+
case 5: {
|
|
199
|
+
const map = /* @__PURE__ */ new Map();
|
|
200
|
+
let cursor = pos;
|
|
201
|
+
for (let i = 0; i < argument; i += 1) {
|
|
202
|
+
const key = decodeCbor(bytes, cursor, depth + 1);
|
|
203
|
+
cursor += key.length;
|
|
204
|
+
const val = decodeCbor(bytes, cursor, depth + 1);
|
|
205
|
+
cursor += val.length;
|
|
206
|
+
if (typeof key.value !== "number" && typeof key.value !== "string") {
|
|
207
|
+
throw new Error("CBOR: unsupported map key type");
|
|
208
|
+
}
|
|
209
|
+
if (map.has(key.value)) {
|
|
210
|
+
throw new Error("CBOR: duplicate map key (non-canonical)");
|
|
211
|
+
}
|
|
212
|
+
map.set(key.value, val.value);
|
|
213
|
+
}
|
|
214
|
+
return { value: map, length: cursor - offset };
|
|
215
|
+
}
|
|
216
|
+
default:
|
|
217
|
+
throw new Error(`CBOR: unsupported major type ${major}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/webauthn/authData.ts
|
|
222
|
+
var MIN_LENGTH = 37;
|
|
223
|
+
function parseAuthenticatorData(authData) {
|
|
224
|
+
if (authData.length < MIN_LENGTH) {
|
|
225
|
+
throw new KitError(
|
|
226
|
+
"UNSUPPORTED_AUTHENTICATOR",
|
|
227
|
+
`authenticatorData is ${authData.length} bytes, expected at least ${MIN_LENGTH}`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
const view = new DataView(authData.buffer, authData.byteOffset, authData.byteLength);
|
|
231
|
+
const rpIdHash = authData.slice(0, 32);
|
|
232
|
+
const bits = authData[32] ?? 0;
|
|
233
|
+
const flags = {
|
|
234
|
+
up: (bits & 1) !== 0,
|
|
235
|
+
uv: (bits & 4) !== 0,
|
|
236
|
+
be: (bits & 8) !== 0,
|
|
237
|
+
bs: (bits & 16) !== 0,
|
|
238
|
+
at: (bits & 64) !== 0,
|
|
239
|
+
ed: (bits & 128) !== 0,
|
|
240
|
+
bits
|
|
241
|
+
};
|
|
242
|
+
const signCount = view.getUint32(33, false);
|
|
243
|
+
const result = { rpIdHash, flags, signCount };
|
|
244
|
+
if (flags.at) {
|
|
245
|
+
if (authData.length < 55) {
|
|
246
|
+
throw new KitError(
|
|
247
|
+
"UNSUPPORTED_AUTHENTICATOR",
|
|
248
|
+
"authenticatorData truncated in attested data"
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
result.aaguid = authData.slice(37, 53);
|
|
252
|
+
const credIdLength = view.getUint16(53, false);
|
|
253
|
+
const credIdEnd = 55 + credIdLength;
|
|
254
|
+
if (authData.length < credIdEnd) {
|
|
255
|
+
throw new KitError(
|
|
256
|
+
"UNSUPPORTED_AUTHENTICATOR",
|
|
257
|
+
"authenticatorData truncated in credentialId"
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
result.credentialId = authData.slice(55, credIdEnd);
|
|
261
|
+
const rest = authData.slice(credIdEnd);
|
|
262
|
+
const { length } = decodeCbor(rest, 0);
|
|
263
|
+
result.credentialPublicKey = rest.slice(0, length);
|
|
264
|
+
}
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
function verifyRpIdHash(authData, rpId) {
|
|
268
|
+
const actual = parseAuthenticatorData(authData).rpIdHash;
|
|
269
|
+
const expected = sha256.sha256(utf8ToBytes(rpId));
|
|
270
|
+
if (!equalBytes(actual, expected)) {
|
|
271
|
+
throw new KitError(
|
|
272
|
+
"RP_ID_MISMATCH",
|
|
273
|
+
`authenticatorData rpIdHash does not match SHA-256("${rpId}")`
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function verifyAssertionSignature(input) {
|
|
278
|
+
const payload = reconstructSignedPayload(input);
|
|
279
|
+
const compact = input.signature.length === 64 ? input.signature : derToCompact(input.signature);
|
|
280
|
+
return nist.p256.verify(compact, payload, input.publicKey, { lowS: false, format: "compact" });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// src/ceremonies/random.ts
|
|
284
|
+
function randomBytes(length) {
|
|
285
|
+
const webcrypto = globalThis.crypto;
|
|
286
|
+
if (typeof webcrypto?.getRandomValues !== "function") {
|
|
287
|
+
throw new KitError(
|
|
288
|
+
"UNSUPPORTED_AUTHENTICATOR",
|
|
289
|
+
"no secure random source (globalThis.crypto.getRandomValues) is available in this environment"
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
const out = new Uint8Array(new ArrayBuffer(length));
|
|
293
|
+
webcrypto.getRandomValues(out);
|
|
294
|
+
return out;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/ceremonies/browserClient.ts
|
|
298
|
+
function asKitError(error) {
|
|
299
|
+
if (error instanceof KitError) return error;
|
|
300
|
+
const name = typeof error === "object" && error !== null && "name" in error ? error.name : void 0;
|
|
301
|
+
if (name === "NotAllowedError") {
|
|
302
|
+
return new KitError("USER_CANCELLED", "The passkey prompt was dismissed.", { cause: error });
|
|
303
|
+
}
|
|
304
|
+
const message = error instanceof Error ? error.message : "WebAuthn request failed";
|
|
305
|
+
const detail = typeof name === "string" ? `${name}: ${message}` : message;
|
|
306
|
+
return new KitError("UNSUPPORTED_AUTHENTICATOR", detail, { cause: error });
|
|
307
|
+
}
|
|
308
|
+
function browserWebAuthnClient() {
|
|
309
|
+
const credentials = globalThis.navigator?.credentials;
|
|
310
|
+
if (!credentials) {
|
|
311
|
+
throw new KitError("UNSUPPORTED_AUTHENTICATOR", "navigator.credentials is unavailable");
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
async create(options) {
|
|
315
|
+
const publicKey = {
|
|
316
|
+
challenge: base64UrlToBytes(options.challenge),
|
|
317
|
+
rp: options.rp,
|
|
318
|
+
user: {
|
|
319
|
+
id: base64UrlToBytes(options.user.id),
|
|
320
|
+
name: options.user.name,
|
|
321
|
+
displayName: options.user.displayName
|
|
322
|
+
},
|
|
323
|
+
pubKeyCredParams: options.pubKeyCredParams,
|
|
324
|
+
authenticatorSelection: options.authenticatorSelection,
|
|
325
|
+
attestation: options.attestation,
|
|
326
|
+
timeout: options.timeout
|
|
327
|
+
};
|
|
328
|
+
let credential;
|
|
329
|
+
try {
|
|
330
|
+
credential = await credentials.create({ publicKey });
|
|
331
|
+
} catch (error) {
|
|
332
|
+
throw asKitError(error);
|
|
333
|
+
}
|
|
334
|
+
if (!credential) throw new KitError("USER_CANCELLED", "credential creation returned null");
|
|
335
|
+
const response = credential.response;
|
|
336
|
+
return {
|
|
337
|
+
id: credential.id,
|
|
338
|
+
rawId: new Uint8Array(credential.rawId),
|
|
339
|
+
attestationObject: new Uint8Array(response.attestationObject)
|
|
340
|
+
};
|
|
341
|
+
},
|
|
342
|
+
async get(options) {
|
|
343
|
+
const publicKey = {
|
|
344
|
+
rpId: options.rpId,
|
|
345
|
+
challenge: options.challenge ? new Uint8Array(options.challenge) : randomBytes(32),
|
|
346
|
+
allowCredentials: (options.allowCredentials ?? []).map((id) => ({
|
|
347
|
+
type: "public-key",
|
|
348
|
+
id: base64UrlToBytes(id)
|
|
349
|
+
})),
|
|
350
|
+
userVerification: options.userVerification
|
|
351
|
+
};
|
|
352
|
+
const request = { publicKey };
|
|
353
|
+
if (options.mediation) request.mediation = options.mediation;
|
|
354
|
+
let credential;
|
|
355
|
+
try {
|
|
356
|
+
credential = await credentials.get(request);
|
|
357
|
+
} catch (error) {
|
|
358
|
+
throw asKitError(error);
|
|
359
|
+
}
|
|
360
|
+
if (!credential) throw new KitError("USER_CANCELLED", "assertion returned null");
|
|
361
|
+
const response = credential.response;
|
|
362
|
+
return {
|
|
363
|
+
id: credential.id,
|
|
364
|
+
rawId: new Uint8Array(credential.rawId),
|
|
365
|
+
authenticatorData: new Uint8Array(response.authenticatorData),
|
|
366
|
+
clientDataJSON: new Uint8Array(response.clientDataJSON),
|
|
367
|
+
signature: new Uint8Array(response.signature)
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/ceremonies/browserSigner.ts
|
|
374
|
+
function browserPasskeySigner(options) {
|
|
375
|
+
const webauthn = options.webauthn ?? browserWebAuthnClient();
|
|
376
|
+
return async (challenge) => {
|
|
377
|
+
const assertion = await webauthn.get({
|
|
378
|
+
rpId: options.rpId,
|
|
379
|
+
challenge: base64UrlToBytes(challenge),
|
|
380
|
+
allowCredentials: options.allowCredentials ?? [],
|
|
381
|
+
userVerification: options.userVerification ?? "preferred"
|
|
382
|
+
});
|
|
383
|
+
return {
|
|
384
|
+
authenticatorData: assertion.authenticatorData,
|
|
385
|
+
clientDataJSON: assertion.clientDataJSON,
|
|
386
|
+
signature: assertion.signature,
|
|
387
|
+
credentialId: assertion.rawId
|
|
388
|
+
};
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
function authEntryChallengeBytes(entry, networkPassphrase) {
|
|
392
|
+
const credentials = entry.credentials();
|
|
393
|
+
if (credentials.switch().name !== "sorobanCredentialsAddress") {
|
|
394
|
+
throw new KitError("CONTRACT_AUTH_FAILED", "auth entry has no address credentials to sign");
|
|
395
|
+
}
|
|
396
|
+
const address = credentials.address();
|
|
397
|
+
const preimage = stellarSdk.xdr.HashIdPreimage.envelopeTypeSorobanAuthorization(
|
|
398
|
+
new stellarSdk.xdr.HashIdPreimageSorobanAuthorization({
|
|
399
|
+
networkId: Buffer.from(stellarSdk.hash(Buffer.from(utf8ToBytes(networkPassphrase)))),
|
|
400
|
+
nonce: address.nonce(),
|
|
401
|
+
signatureExpirationLedger: address.signatureExpirationLedger(),
|
|
402
|
+
invocation: entry.rootInvocation()
|
|
403
|
+
})
|
|
404
|
+
);
|
|
405
|
+
return new Uint8Array(stellarSdk.hash(preimage.toXDR()));
|
|
406
|
+
}
|
|
407
|
+
function authEntryChallenge(entry, networkPassphrase) {
|
|
408
|
+
return bytesToBase64Url(authEntryChallengeBytes(entry, networkPassphrase));
|
|
409
|
+
}
|
|
410
|
+
function applyAssertionToEntry(entry, assertion) {
|
|
411
|
+
entry.credentials().address().signature(buildSignatureScVal(assertion));
|
|
412
|
+
return entry;
|
|
413
|
+
}
|
|
414
|
+
function buildSignatureScVal(assertion) {
|
|
415
|
+
return stellarSdk.xdr.ScVal.scvMap([
|
|
416
|
+
bytesField("authenticator_data", assertion.authenticatorData),
|
|
417
|
+
bytesField("client_data_json", assertion.clientDataJSON),
|
|
418
|
+
bytesField("signature", assertion.signature)
|
|
419
|
+
]);
|
|
420
|
+
}
|
|
421
|
+
function bytesField(name, bytes) {
|
|
422
|
+
return new stellarSdk.xdr.ScMapEntry({
|
|
423
|
+
key: stellarSdk.xdr.ScVal.scvSymbol(name),
|
|
424
|
+
val: stellarSdk.xdr.ScVal.scvBytes(Buffer.from(bytes))
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
function buildSignerKeyScVal(credentialId) {
|
|
428
|
+
return stellarSdk.xdr.ScVal.scvVec([
|
|
429
|
+
stellarSdk.xdr.ScVal.scvSymbol("Secp256r1"),
|
|
430
|
+
stellarSdk.xdr.ScVal.scvBytes(Buffer.from(credentialId))
|
|
431
|
+
]);
|
|
432
|
+
}
|
|
433
|
+
function buildSmartWalletSignatureVariant(assertion) {
|
|
434
|
+
return stellarSdk.xdr.ScVal.scvVec([stellarSdk.xdr.ScVal.scvSymbol("Secp256r1"), buildSignatureScVal(assertion)]);
|
|
435
|
+
}
|
|
436
|
+
function decomposeSignerKey(key) {
|
|
437
|
+
const vec = key.vec();
|
|
438
|
+
if (!vec || vec.length < 2) {
|
|
439
|
+
throw new KitError("CONTRACT_AUTH_FAILED", "malformed SignerKey (expected an enum vec)");
|
|
440
|
+
}
|
|
441
|
+
const variant = vec[0].sym().toString();
|
|
442
|
+
const el = vec[1];
|
|
443
|
+
const payload = el.switch().name === "scvBytes" ? new Uint8Array(el.bytes()) : new Uint8Array(el.toXDR());
|
|
444
|
+
return { variant, payload };
|
|
445
|
+
}
|
|
446
|
+
function compareBytes(a, b) {
|
|
447
|
+
const n = Math.min(a.length, b.length);
|
|
448
|
+
for (let i = 0; i < n; i++) {
|
|
449
|
+
if (a[i] !== b[i]) return a[i] < b[i] ? -1 : 1;
|
|
450
|
+
}
|
|
451
|
+
return a.length - b.length;
|
|
452
|
+
}
|
|
453
|
+
function compareSignerKeyScVal(a, b) {
|
|
454
|
+
const ka = decomposeSignerKey(a);
|
|
455
|
+
const kb = decomposeSignerKey(b);
|
|
456
|
+
if (ka.variant !== kb.variant) return ka.variant < kb.variant ? -1 : 1;
|
|
457
|
+
return compareBytes(ka.payload, kb.payload);
|
|
458
|
+
}
|
|
459
|
+
function applyAssertionToSmartWalletEntry(entry, assertion) {
|
|
460
|
+
const credentials = entry.credentials();
|
|
461
|
+
if (credentials.switch().name !== "sorobanCredentialsAddress") {
|
|
462
|
+
throw new KitError("CONTRACT_AUTH_FAILED", "auth entry has no address credentials to sign");
|
|
463
|
+
}
|
|
464
|
+
const key = buildSignerKeyScVal(assertion.credentialId);
|
|
465
|
+
const newEntry = new stellarSdk.xdr.ScMapEntry({ key, val: buildSmartWalletSignatureVariant(assertion) });
|
|
466
|
+
const current = credentials.address().signature();
|
|
467
|
+
let mapEntries;
|
|
468
|
+
switch (current.switch().name) {
|
|
469
|
+
case "scvVoid":
|
|
470
|
+
mapEntries = [newEntry];
|
|
471
|
+
break;
|
|
472
|
+
case "scvVec": {
|
|
473
|
+
const existing = current.vec()?.[0]?.map() ?? [];
|
|
474
|
+
mapEntries = existing.filter((e) => compareSignerKeyScVal(e.key(), key) !== 0);
|
|
475
|
+
mapEntries.push(newEntry);
|
|
476
|
+
break;
|
|
477
|
+
}
|
|
478
|
+
default:
|
|
479
|
+
throw new KitError(
|
|
480
|
+
"CONTRACT_AUTH_FAILED",
|
|
481
|
+
"unexpected existing signature shape for a smart-wallet entry"
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
mapEntries.sort((a, b) => compareSignerKeyScVal(a.key(), b.key()));
|
|
485
|
+
credentials.address().signature(stellarSdk.xdr.ScVal.scvVec([stellarSdk.xdr.ScVal.scvMap(mapEntries)]));
|
|
486
|
+
return entry;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/soroban/sign.ts
|
|
490
|
+
function preflightAssertion(assertion, challenge, verify) {
|
|
491
|
+
let clientData;
|
|
492
|
+
if (verify.origin !== void 0) {
|
|
493
|
+
clientData = verifyClientDataJSON(assertion.clientDataJSON, {
|
|
494
|
+
origin: verify.origin,
|
|
495
|
+
challenge
|
|
496
|
+
});
|
|
497
|
+
} else {
|
|
498
|
+
clientData = parseClientDataJSON(assertion.clientDataJSON);
|
|
499
|
+
if (clientData.type !== "webauthn.get") {
|
|
500
|
+
throw new KitError(
|
|
501
|
+
"UNSUPPORTED_AUTHENTICATOR",
|
|
502
|
+
`clientDataJSON.type "${clientData.type}" is not "webauthn.get"`
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
if (clientData.challenge !== challenge) {
|
|
506
|
+
throw new KitError("CHALLENGE_MISMATCH", "assertion challenge does not match the auth entry");
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (clientData.crossOrigin === true && !verify.allowCrossOrigin) {
|
|
510
|
+
throw new KitError(
|
|
511
|
+
"ORIGIN_MISMATCH",
|
|
512
|
+
"assertion was made in a cross-origin context (crossOrigin=true)"
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
const parsed = parseAuthenticatorData(assertion.authenticatorData);
|
|
516
|
+
if (!parsed.flags.up) {
|
|
517
|
+
throw new KitError(
|
|
518
|
+
"UNSUPPORTED_AUTHENTICATOR",
|
|
519
|
+
"assertion is missing the User-Present (UP) flag"
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
if (verify.requireUserVerification && !parsed.flags.uv) {
|
|
523
|
+
throw new KitError(
|
|
524
|
+
"UNSUPPORTED_AUTHENTICATOR",
|
|
525
|
+
"assertion is missing the required User-Verified (UV) flag"
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
if (verify.rpId !== void 0) {
|
|
529
|
+
verifyRpIdHash(assertion.authenticatorData, verify.rpId);
|
|
530
|
+
}
|
|
531
|
+
if (verify.publicKey !== void 0) {
|
|
532
|
+
const ok = verifyAssertionSignature({
|
|
533
|
+
publicKey: verify.publicKey,
|
|
534
|
+
authenticatorData: assertion.authenticatorData,
|
|
535
|
+
clientDataJSON: assertion.clientDataJSON,
|
|
536
|
+
signature: assertion.signature
|
|
537
|
+
});
|
|
538
|
+
if (!ok) {
|
|
539
|
+
throw new KitError(
|
|
540
|
+
"CONTRACT_AUTH_FAILED",
|
|
541
|
+
"assertion signature failed pre-flight verification"
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
async function signEntryInPlace(entry, options) {
|
|
547
|
+
if (options.signatureExpirationLedger !== void 0) {
|
|
548
|
+
const creds = entry.credentials();
|
|
549
|
+
if (creds.switch().name === "sorobanCredentialsAddress") {
|
|
550
|
+
creds.address().signatureExpirationLedger(options.signatureExpirationLedger);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
const challenge = authEntryChallenge(entry, options.networkPassphrase);
|
|
554
|
+
const assertion = await options.sign(challenge);
|
|
555
|
+
if (options.verify) preflightAssertion(assertion, challenge, options.verify);
|
|
556
|
+
const signature = assertion.signature.length === 64 ? assertion.signature : derToCompactLowS(assertion.signature);
|
|
557
|
+
const normalized = {
|
|
558
|
+
credentialId: assertion.credentialId,
|
|
559
|
+
authenticatorData: assertion.authenticatorData,
|
|
560
|
+
clientDataJSON: assertion.clientDataJSON,
|
|
561
|
+
signature
|
|
562
|
+
};
|
|
563
|
+
if (options.target === "smart-wallet") {
|
|
564
|
+
applyAssertionToSmartWalletEntry(entry, normalized);
|
|
565
|
+
} else {
|
|
566
|
+
applyAssertionToEntry(entry, normalized);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
async function signAuthEntry(entryXdr, options) {
|
|
570
|
+
const entry = stellarSdk.xdr.SorobanAuthorizationEntry.fromXDR(entryXdr, "base64");
|
|
571
|
+
await signEntryInPlace(entry, options);
|
|
572
|
+
return entry.toXDR("base64");
|
|
573
|
+
}
|
|
574
|
+
function operationsForSigning(envelope) {
|
|
575
|
+
const kind = envelope.switch().name;
|
|
576
|
+
if (kind === "envelopeTypeTx") return envelope.v1().tx().operations();
|
|
577
|
+
if (kind === "envelopeTypeTxFeeBump") {
|
|
578
|
+
const inner = envelope.feeBump().tx().innerTx();
|
|
579
|
+
if (inner.switch().name !== "envelopeTypeTx") {
|
|
580
|
+
throw new KitError(
|
|
581
|
+
"CONTRACT_AUTH_FAILED",
|
|
582
|
+
"signTransaction: fee-bump inner transaction is not a v1 transaction"
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
return inner.v1().tx().operations();
|
|
586
|
+
}
|
|
587
|
+
throw new KitError(
|
|
588
|
+
"CONTRACT_AUTH_FAILED",
|
|
589
|
+
`signTransaction: unsupported envelope type "${kind}" (expected v1 or fee-bump)`
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
async function signTransaction(txXdr, options) {
|
|
593
|
+
const envelope = stellarSdk.xdr.TransactionEnvelope.fromXDR(txXdr, "base64");
|
|
594
|
+
for (const op of operationsForSigning(envelope)) {
|
|
595
|
+
if (op.body().switch().name !== "invokeHostFunction") continue;
|
|
596
|
+
const entries = op.body().invokeHostFunctionOp().auth();
|
|
597
|
+
for (const entry of entries) {
|
|
598
|
+
if (entry.credentials().switch().name !== "sorobanCredentialsAddress") continue;
|
|
599
|
+
await signEntryInPlace(entry, options);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return envelope.toXDR("base64");
|
|
603
|
+
}
|
|
604
|
+
function noneTuple() {
|
|
605
|
+
return stellarSdk.xdr.ScVal.scvVec([stellarSdk.xdr.ScVal.scvVoid()]);
|
|
606
|
+
}
|
|
607
|
+
function expirationScVal(expiration) {
|
|
608
|
+
if (expiration === void 0) return noneTuple();
|
|
609
|
+
const ts = BigInt(expiration);
|
|
610
|
+
if (ts < 0n) {
|
|
611
|
+
throw new KitError(
|
|
612
|
+
"CONTRACT_AUTH_FAILED",
|
|
613
|
+
"signer expiration must be a non-negative UNIX-seconds timestamp (v1)"
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
return stellarSdk.xdr.ScVal.scvVec([stellarSdk.nativeToScVal(ts, { type: "u64" })]);
|
|
617
|
+
}
|
|
618
|
+
function storageScVal(storage = "Persistent") {
|
|
619
|
+
return stellarSdk.xdr.ScVal.scvVec([stellarSdk.xdr.ScVal.scvSymbol(storage)]);
|
|
620
|
+
}
|
|
621
|
+
function buildSecp256r1Signer(spec) {
|
|
622
|
+
if (spec.credentialId.length === 0) {
|
|
623
|
+
throw new KitError("CONTRACT_AUTH_FAILED", "buildSecp256r1Signer: empty credential id");
|
|
624
|
+
}
|
|
625
|
+
if (spec.publicKey.length !== 65 || spec.publicKey[0] !== 4) {
|
|
626
|
+
throw new KitError(
|
|
627
|
+
"INVALID_PUBLIC_KEY",
|
|
628
|
+
"buildSecp256r1Signer: publicKey must be a 65-byte SEC-1 uncompressed point (0x04-prefixed)"
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
return stellarSdk.xdr.ScVal.scvVec([
|
|
632
|
+
stellarSdk.xdr.ScVal.scvSymbol("Secp256r1"),
|
|
633
|
+
stellarSdk.xdr.ScVal.scvBytes(Buffer.from(spec.credentialId)),
|
|
634
|
+
stellarSdk.xdr.ScVal.scvBytes(Buffer.from(spec.publicKey)),
|
|
635
|
+
expirationScVal(spec.expiration),
|
|
636
|
+
// SignerExpiration
|
|
637
|
+
noneTuple(),
|
|
638
|
+
// SignerLimits(None)
|
|
639
|
+
storageScVal(spec.storage)
|
|
640
|
+
// SignerStorage
|
|
641
|
+
]);
|
|
642
|
+
}
|
|
643
|
+
function contractCall(contractId, method, ...args) {
|
|
644
|
+
let contract;
|
|
645
|
+
try {
|
|
646
|
+
contract = new stellarSdk.Contract(contractId);
|
|
647
|
+
} catch (cause) {
|
|
648
|
+
throw new KitError("CONTRACT_AUTH_FAILED", `invalid smart-wallet contract id "${contractId}"`, {
|
|
649
|
+
cause
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
return contract.call(method, ...args);
|
|
653
|
+
}
|
|
654
|
+
function buildAddSignerOperation(options) {
|
|
655
|
+
const signerScVal = options.signer instanceof stellarSdk.xdr.ScVal ? options.signer : buildSecp256r1Signer(options.signer);
|
|
656
|
+
return contractCall(options.walletContractId, "add_signer", signerScVal);
|
|
657
|
+
}
|
|
658
|
+
function buildRemoveSignerOperation(options) {
|
|
659
|
+
if (options.credentialId.length === 0) {
|
|
660
|
+
throw new KitError("CONTRACT_AUTH_FAILED", "buildRemoveSignerOperation: empty credential id");
|
|
661
|
+
}
|
|
662
|
+
return contractCall(
|
|
663
|
+
options.walletContractId,
|
|
664
|
+
"remove_signer",
|
|
665
|
+
buildSignerKeyScVal(options.credentialId)
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
function fail(reason) {
|
|
669
|
+
return { success: false, challengeBound: false, signatureValid: false, reason };
|
|
670
|
+
}
|
|
671
|
+
function verifySecp256r1StructMap(structMap, publicKey, signaturePayload) {
|
|
672
|
+
const field = (name) => {
|
|
673
|
+
const e = structMap.find((x) => x.key().sym().toString() === name);
|
|
674
|
+
return e ? new Uint8Array(e.val().bytes()) : void 0;
|
|
675
|
+
};
|
|
676
|
+
const authenticatorData = field("authenticator_data");
|
|
677
|
+
const clientDataJSON = field("client_data_json");
|
|
678
|
+
const signature = field("signature");
|
|
679
|
+
if (!authenticatorData || !clientDataJSON || !signature) {
|
|
680
|
+
return { ok: false, challengeBound: false, signatureValid: false, reason: "missing fields" };
|
|
681
|
+
}
|
|
682
|
+
if (signature.length !== 64) {
|
|
683
|
+
return {
|
|
684
|
+
ok: false,
|
|
685
|
+
challengeBound: false,
|
|
686
|
+
signatureValid: false,
|
|
687
|
+
reason: "not 64-byte compact"
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
let challengeBound = false;
|
|
691
|
+
try {
|
|
692
|
+
const cdj = JSON.parse(bytesToUtf8(clientDataJSON));
|
|
693
|
+
challengeBound = cdj.challenge === bytesToBase64Url(signaturePayload);
|
|
694
|
+
} catch {
|
|
695
|
+
challengeBound = false;
|
|
696
|
+
}
|
|
697
|
+
const digest = sha256.sha256(concatBytes(authenticatorData, sha256.sha256(clientDataJSON)));
|
|
698
|
+
const signatureValid = nist.p256.verify(signature, digest, publicKey, {
|
|
699
|
+
lowS: false,
|
|
700
|
+
format: "compact"
|
|
701
|
+
});
|
|
702
|
+
return { ok: challengeBound && signatureValid, challengeBound, signatureValid };
|
|
703
|
+
}
|
|
704
|
+
function referenceCheckAuth(entry, publicKey, networkPassphrase) {
|
|
705
|
+
if (entry.credentials().switch().name !== "sorobanCredentialsAddress") {
|
|
706
|
+
return fail("no address credentials");
|
|
707
|
+
}
|
|
708
|
+
const structMap = entry.credentials().address().signature().map();
|
|
709
|
+
if (!structMap) return fail("signature is not a Secp256r1Signature map");
|
|
710
|
+
const signaturePayload = authEntryChallengeBytes(entry, networkPassphrase);
|
|
711
|
+
const r = verifySecp256r1StructMap(structMap, publicKey, signaturePayload);
|
|
712
|
+
return { success: r.ok, challengeBound: r.challengeBound, signatureValid: r.signatureValid };
|
|
713
|
+
}
|
|
714
|
+
function referenceSmartWalletCheckAuth(entry, publicKeyFor, networkPassphrase) {
|
|
715
|
+
if (entry.credentials().switch().name !== "sorobanCredentialsAddress") {
|
|
716
|
+
return { success: false, signers: [], reason: "no address credentials" };
|
|
717
|
+
}
|
|
718
|
+
const sig = entry.credentials().address().signature();
|
|
719
|
+
const map = sig.switch().name === "scvVec" ? sig.vec()?.[0]?.map() : void 0;
|
|
720
|
+
if (!map) {
|
|
721
|
+
return { success: false, signers: [], reason: "signature is not a Signatures(Map) vec" };
|
|
722
|
+
}
|
|
723
|
+
if (map.length === 0) return { success: false, signers: [], reason: "empty signatures map" };
|
|
724
|
+
const signaturePayload = authEntryChallengeBytes(entry, networkPassphrase);
|
|
725
|
+
const signers = [];
|
|
726
|
+
let allOk = true;
|
|
727
|
+
for (const e of map) {
|
|
728
|
+
const keyVec = e.key().vec();
|
|
729
|
+
const idBytes = keyVec?.[1]?.bytes();
|
|
730
|
+
const credentialId = idBytes ? Buffer.from(idBytes).toString("hex") : "";
|
|
731
|
+
const structMap = e.val().vec()?.[1]?.map();
|
|
732
|
+
const publicKey = publicKeyFor(credentialId);
|
|
733
|
+
if (!structMap || !publicKey) {
|
|
734
|
+
allOk = false;
|
|
735
|
+
signers.push({ credentialId, challengeBound: false, signatureValid: false });
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
const r = verifySecp256r1StructMap(structMap, publicKey, signaturePayload);
|
|
739
|
+
if (!r.ok) allOk = false;
|
|
740
|
+
signers.push({
|
|
741
|
+
credentialId,
|
|
742
|
+
challengeBound: r.challengeBound,
|
|
743
|
+
signatureValid: r.signatureValid
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
return { success: allOk, signers };
|
|
747
|
+
}
|
|
748
|
+
function directSubmission(options) {
|
|
749
|
+
const server = new stellarSdk.rpc.Server(options.rpcUrl, {
|
|
750
|
+
allowHttp: options.allowHttp ?? options.rpcUrl.startsWith("http://")
|
|
751
|
+
});
|
|
752
|
+
const attempts = options.pollAttempts ?? 30;
|
|
753
|
+
const intervalMs = options.pollIntervalMs ?? 1e3;
|
|
754
|
+
return {
|
|
755
|
+
async send(signedTxXdr) {
|
|
756
|
+
const tx = stellarSdk.TransactionBuilder.fromXDR(signedTxXdr, options.networkPassphrase);
|
|
757
|
+
let sent;
|
|
758
|
+
try {
|
|
759
|
+
sent = await server.sendTransaction(tx);
|
|
760
|
+
} catch (cause) {
|
|
761
|
+
throw new KitError("NETWORK_ERROR", "failed to submit transaction to soroban-rpc", {
|
|
762
|
+
cause
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
if (sent.status === "ERROR") {
|
|
766
|
+
return {
|
|
767
|
+
status: "FAILED",
|
|
768
|
+
hash: sent.hash,
|
|
769
|
+
errorResultXdr: sent.errorResult?.toXDR("base64")
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
let final;
|
|
773
|
+
try {
|
|
774
|
+
final = await server.pollTransaction(sent.hash, {
|
|
775
|
+
attempts,
|
|
776
|
+
sleepStrategy: () => intervalMs
|
|
777
|
+
});
|
|
778
|
+
} catch (cause) {
|
|
779
|
+
throw new KitError(
|
|
780
|
+
"NETWORK_ERROR",
|
|
781
|
+
`transaction ${sent.hash} was not confirmed after ${String(attempts)} polls`,
|
|
782
|
+
{ cause }
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
if (final.status === stellarSdk.rpc.Api.GetTransactionStatus.NOT_FOUND) {
|
|
786
|
+
throw new KitError(
|
|
787
|
+
"NETWORK_ERROR",
|
|
788
|
+
`transaction ${sent.hash} still NOT_FOUND after ${String(attempts)} polls`
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
const success = final.status === stellarSdk.rpc.Api.GetTransactionStatus.SUCCESS;
|
|
792
|
+
return {
|
|
793
|
+
status: success ? "SUCCESS" : "FAILED",
|
|
794
|
+
hash: sent.hash,
|
|
795
|
+
returnValue: success ? final.returnValue : void 0
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// src/soroban/submit.ts
|
|
802
|
+
var DEFAULT_EXPIRATION_OFFSET = 60;
|
|
803
|
+
async function sendSmartWalletTx(options) {
|
|
804
|
+
const server = new stellarSdk.rpc.Server(options.rpcUrl, {
|
|
805
|
+
allowHttp: options.allowHttp ?? options.rpcUrl.startsWith("http://")
|
|
806
|
+
});
|
|
807
|
+
let source;
|
|
808
|
+
try {
|
|
809
|
+
source = stellarSdk.Keypair.fromSecret(options.sourceSecret);
|
|
810
|
+
} catch (cause) {
|
|
811
|
+
throw new KitError("CONTRACT_AUTH_FAILED", "invalid fee-source secret", { cause });
|
|
812
|
+
}
|
|
813
|
+
const account = await server.getAccount(source.publicKey());
|
|
814
|
+
const tx = new stellarSdk.TransactionBuilder(account, {
|
|
815
|
+
fee: options.fee ?? "2000000",
|
|
816
|
+
networkPassphrase: options.networkPassphrase
|
|
817
|
+
}).addOperation(options.operation).setTimeout(options.timeoutSeconds ?? 120).build();
|
|
818
|
+
const recSim = await server.simulateTransaction(tx);
|
|
819
|
+
if (!stellarSdk.rpc.Api.isSimulationSuccess(recSim)) {
|
|
820
|
+
const detail = "error" in recSim ? String(recSim.error) : "unknown simulation error";
|
|
821
|
+
throw new KitError(
|
|
822
|
+
"CONTRACT_AUTH_FAILED",
|
|
823
|
+
`smart-wallet tx simulation failed: ${detail.slice(0, 300)}`
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
const prepared = stellarSdk.rpc.assembleTransaction(tx, recSim).build();
|
|
827
|
+
const signatureExpirationLedger = (await server.getLatestLedger()).sequence + (options.signatureExpirationLedgerOffset ?? DEFAULT_EXPIRATION_OFFSET);
|
|
828
|
+
const signedAuthXdr = await signTransaction(prepared.toXDR(), {
|
|
829
|
+
networkPassphrase: options.networkPassphrase,
|
|
830
|
+
sign: options.sign,
|
|
831
|
+
target: "smart-wallet",
|
|
832
|
+
signatureExpirationLedger,
|
|
833
|
+
verify: options.verify
|
|
834
|
+
});
|
|
835
|
+
const signedTx = stellarSdk.TransactionBuilder.fromXDR(signedAuthXdr, options.networkPassphrase);
|
|
836
|
+
const enfSim = await server.simulateTransaction(signedTx);
|
|
837
|
+
if (!stellarSdk.rpc.Api.isSimulationSuccess(enfSim)) {
|
|
838
|
+
const detail = "error" in enfSim ? String(enfSim.error) : "unknown simulation error";
|
|
839
|
+
throw new KitError(
|
|
840
|
+
"CONTRACT_AUTH_FAILED",
|
|
841
|
+
`signed smart-wallet tx failed enforcing simulation (auth rejected?): ${detail.slice(0, 300)}`
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
const finalTx = stellarSdk.rpc.assembleTransaction(signedTx, enfSim).build();
|
|
845
|
+
finalTx.sign(source);
|
|
846
|
+
const submission = options.submission ?? directSubmission({
|
|
847
|
+
rpcUrl: options.rpcUrl,
|
|
848
|
+
networkPassphrase: options.networkPassphrase,
|
|
849
|
+
allowHttp: options.allowHttp
|
|
850
|
+
});
|
|
851
|
+
return submission.send(finalTx.toXDR());
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
exports.applyAssertionToEntry = applyAssertionToEntry;
|
|
855
|
+
exports.applyAssertionToSmartWalletEntry = applyAssertionToSmartWalletEntry;
|
|
856
|
+
exports.authEntryChallenge = authEntryChallenge;
|
|
857
|
+
exports.authEntryChallengeBytes = authEntryChallengeBytes;
|
|
858
|
+
exports.browserPasskeySigner = browserPasskeySigner;
|
|
859
|
+
exports.buildAddSignerOperation = buildAddSignerOperation;
|
|
860
|
+
exports.buildRemoveSignerOperation = buildRemoveSignerOperation;
|
|
861
|
+
exports.buildSecp256r1Signer = buildSecp256r1Signer;
|
|
862
|
+
exports.buildSignerKeyScVal = buildSignerKeyScVal;
|
|
863
|
+
exports.buildSmartWalletSignatureVariant = buildSmartWalletSignatureVariant;
|
|
864
|
+
exports.compareSignerKeyScVal = compareSignerKeyScVal;
|
|
865
|
+
exports.decodeChallenge = decodeChallenge;
|
|
866
|
+
exports.derToCompact = derToCompact;
|
|
867
|
+
exports.derToCompactLowS = derToCompactLowS;
|
|
868
|
+
exports.encodeChallenge = encodeChallenge;
|
|
869
|
+
exports.isLowS = isLowS;
|
|
870
|
+
exports.normalizeLowS = normalizeLowS;
|
|
871
|
+
exports.parseAuthenticatorData = parseAuthenticatorData;
|
|
872
|
+
exports.parseClientDataJSON = parseClientDataJSON;
|
|
873
|
+
exports.reconstructSignedPayload = reconstructSignedPayload;
|
|
874
|
+
exports.referenceCheckAuth = referenceCheckAuth;
|
|
875
|
+
exports.referenceSmartWalletCheckAuth = referenceSmartWalletCheckAuth;
|
|
876
|
+
exports.sendSmartWalletTx = sendSmartWalletTx;
|
|
877
|
+
exports.signAuthEntry = signAuthEntry;
|
|
878
|
+
exports.signTransaction = signTransaction;
|
|
879
|
+
exports.verifyAssertionSignature = verifyAssertionSignature;
|
|
880
|
+
exports.verifyClientDataJSON = verifyClientDataJSON;
|
|
881
|
+
exports.verifyRpIdHash = verifyRpIdHash;
|
|
882
|
+
//# sourceMappingURL=sign.cjs.map
|
|
883
|
+
//# sourceMappingURL=sign.cjs.map
|