@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-OGSUGATA.js"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { KitError } from './chunk-AMYFJQAC.js';
|
|
2
|
+
|
|
3
|
+
// src/anchors.ts
|
|
4
|
+
var BATTLE_TESTED_ANCHORS = Object.freeze({
|
|
5
|
+
/** Apple Touch ID/Face ID emit high-S ~50% of the time; we normalize to low-S. */
|
|
6
|
+
LOW_S_NORMALIZATION: "low-s-normalization",
|
|
7
|
+
/** Non-ES256 credentials (e.g. some Windows Hello configs) cannot verify on-chain. */
|
|
8
|
+
RS256_HARD_FAIL: "rs256-hard-fail",
|
|
9
|
+
/** Safari requires create/get inside a user activation or it silently rejects. */
|
|
10
|
+
APPLE_USER_GESTURE: "apple-user-gesture"
|
|
11
|
+
});
|
|
12
|
+
var ALG_ES256 = -7;
|
|
13
|
+
function assertES256(alg) {
|
|
14
|
+
if (alg !== ALG_ES256) {
|
|
15
|
+
throw new KitError("ES256_NOT_SUPPORTED", `COSE alg ${alg} is not ES256 (-7)`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function assertUserActivation(activation) {
|
|
19
|
+
const ua = activation ?? globalThis.navigator?.userActivation;
|
|
20
|
+
if (ua && !ua.isActive) {
|
|
21
|
+
throw new KitError(
|
|
22
|
+
"USER_CANCELLED",
|
|
23
|
+
"WebAuthn ceremony must be invoked inside a user activation (click/tap)"
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { BATTLE_TESTED_ANCHORS, assertES256, assertUserActivation };
|
|
29
|
+
//# sourceMappingURL=chunk-TPBBZNPP.js.map
|
|
30
|
+
//# sourceMappingURL=chunk-TPBBZNPP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/anchors.ts"],"names":[],"mappings":";;;AAQO,IAAM,qBAAA,GAAwB,OAAO,MAAA,CAAO;AAAA;AAAA,EAEjD,mBAAA,EAAqB,qBAAA;AAAA;AAAA,EAErB,eAAA,EAAiB,iBAAA;AAAA;AAAA,EAEjB,kBAAA,EAAoB;AACtB,CAAU;AAKV,IAAM,SAAA,GAAY,EAAA;AAOX,SAAS,YAAY,GAAA,EAAmB;AAC7C,EAAA,IAAI,QAAQ,SAAA,EAAW;AACrB,IAAA,MAAM,IAAI,QAAA,CAAS,qBAAA,EAAuB,CAAA,SAAA,EAAY,GAAG,CAAA,kBAAA,CAAoB,CAAA;AAAA,EAC/E;AACF;AAkBO,SAAS,qBAAqB,UAAA,EAAuC;AAC1E,EAAA,MAAM,EAAA,GACJ,UAAA,IACC,UAAA,CAAW,SAAA,EAAmE,cAAA;AACjF,EAAA,IAAI,EAAA,IAAM,CAAC,EAAA,CAAG,QAAA,EAAU;AACtB,IAAA,MAAM,IAAI,QAAA;AAAA,MACR,gBAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACF","file":"chunk-TPBBZNPP.js","sourcesContent":["import { KitError } from './errors';\n\n/**\n * Stable identifiers for the three battle-tested anchors (S04 / YK-430) — the\n * bugs that silently break ~50% of users. Referenced by stable name from the\n * threat model (S14) and the compatibility matrix (S09) so each mitigation has\n * one canonical id across code and docs.\n */\nexport const BATTLE_TESTED_ANCHORS = Object.freeze({\n /** Apple Touch ID/Face ID emit high-S ~50% of the time; we normalize to low-S. */\n LOW_S_NORMALIZATION: 'low-s-normalization',\n /** Non-ES256 credentials (e.g. some Windows Hello configs) cannot verify on-chain. */\n RS256_HARD_FAIL: 'rs256-hard-fail',\n /** Safari requires create/get inside a user activation or it silently rejects. */\n APPLE_USER_GESTURE: 'apple-user-gesture',\n} as const);\n\nexport type BattleTestedAnchor = (typeof BATTLE_TESTED_ANCHORS)[keyof typeof BATTLE_TESTED_ANCHORS];\n\n/** COSE algorithm id for ES256 (ECDSA w/ SHA-256 over P-256). */\nconst ALG_ES256 = -7;\n\n/**\n * Anchor `RS256_HARD_FAIL`. The Soroban webauthn-wallet verifies *only*\n * secp256r1, so a non-ES256 credential would produce an unusable account.\n * Hard-fail rather than continue (invariant #1).\n */\nexport function assertES256(alg: number): void {\n if (alg !== ALG_ES256) {\n throw new KitError('ES256_NOT_SUPPORTED', `COSE alg ${alg} is not ES256 (-7)`);\n }\n}\n\ninterface UserActivationLike {\n isActive: boolean;\n}\n\n/**\n * Anchor `APPLE_USER_GESTURE`. `navigator.credentials.create/get` on\n * Safari/WebKit must run inside a transient user activation (a real click/tap)\n * or the promise silently rejects. Call this at the very top of a create/sign\n * handler.\n *\n * UI pattern (for S18): never auto-trigger the ceremony from a mount/effect —\n * always gate it behind an explicit user click, and surface `USER_CANCELLED`\n * in the flow state machine. Lenient when `navigator.userActivation` is\n * unavailable (non-browser / older engines) so the SDK stays isomorphic; pass\n * an explicit `{ isActive }` to force the check.\n */\nexport function assertUserActivation(activation?: UserActivationLike): void {\n const ua =\n activation ??\n (globalThis.navigator as { userActivation?: UserActivationLike } | undefined)?.userActivation;\n if (ua && !ua.isActive) {\n throw new KitError(\n 'USER_CANCELLED',\n 'WebAuthn ceremony must be invoked inside a user activation (click/tap)',\n );\n }\n}\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { buildAddSignerOperation, sendSmartWalletTx, buildRemoveSignerOperation } from './chunk-UV5HORMF.js';
|
|
2
|
+
import { decodeChallenge } from './chunk-JNJ2FY45.js';
|
|
3
|
+
|
|
4
|
+
// src/ceremonies/addDevice.ts
|
|
5
|
+
function toRawCredentialId(id) {
|
|
6
|
+
return typeof id === "string" ? decodeChallenge(id) : id;
|
|
7
|
+
}
|
|
8
|
+
async function addSigner(options) {
|
|
9
|
+
const { walletContractId, newSigner, ...wiring } = options;
|
|
10
|
+
const operation = buildAddSignerOperation({
|
|
11
|
+
walletContractId,
|
|
12
|
+
signer: {
|
|
13
|
+
credentialId: toRawCredentialId(newSigner.credentialId),
|
|
14
|
+
publicKey: newSigner.publicKey,
|
|
15
|
+
expiration: newSigner.expiration,
|
|
16
|
+
storage: newSigner.storage
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
return sendSmartWalletTx({ ...wiring, operation });
|
|
20
|
+
}
|
|
21
|
+
async function removeSigner(options) {
|
|
22
|
+
const { walletContractId, credentialId, ...wiring } = options;
|
|
23
|
+
const operation = buildRemoveSignerOperation({
|
|
24
|
+
walletContractId,
|
|
25
|
+
credentialId: toRawCredentialId(credentialId)
|
|
26
|
+
});
|
|
27
|
+
return sendSmartWalletTx({ ...wiring, operation });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { addSigner, removeSigner };
|
|
31
|
+
//# sourceMappingURL=chunk-UQKNPO44.js.map
|
|
32
|
+
//# sourceMappingURL=chunk-UQKNPO44.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ceremonies/addDevice.ts"],"names":[],"mappings":";;;;AA+CA,SAAS,kBAAkB,EAAA,EAAqC;AAC9D,EAAA,OAAO,OAAO,EAAA,KAAO,QAAA,GAAW,eAAA,CAAgB,EAAE,CAAA,GAAI,EAAA;AACxD;AAOA,eAAsB,UAAU,OAAA,EAAkD;AAChF,EAAA,MAAM,EAAE,gBAAA,EAAkB,SAAA,EAAW,GAAG,QAAO,GAAI,OAAA;AACnD,EAAA,MAAM,YAAY,uBAAA,CAAwB;AAAA,IACxC,gBAAA;AAAA,IACA,MAAA,EAAQ;AAAA,MACN,YAAA,EAAc,iBAAA,CAAkB,SAAA,CAAU,YAAY,CAAA;AAAA,MACtD,WAAW,SAAA,CAAU,SAAA;AAAA,MACrB,YAAY,SAAA,CAAU,UAAA;AAAA,MACtB,SAAS,SAAA,CAAU;AAAA;AACrB,GACD,CAAA;AACD,EAAA,OAAO,iBAAA,CAAkB,EAAE,GAAG,MAAA,EAAQ,WAAW,CAAA;AACnD;AAGA,eAAsB,aAAa,OAAA,EAAqD;AACtF,EAAA,MAAM,EAAE,gBAAA,EAAkB,YAAA,EAAc,GAAG,QAAO,GAAI,OAAA;AACtD,EAAA,MAAM,YAAY,0BAAA,CAA2B;AAAA,IAC3C,gBAAA;AAAA,IACA,YAAA,EAAc,kBAAkB,YAAY;AAAA,GAC7C,CAAA;AACD,EAAA,OAAO,iBAAA,CAAkB,EAAE,GAAG,MAAA,EAAQ,WAAW,CAAA;AACnD","file":"chunk-UQKNPO44.js","sourcesContent":["import { decodeChallenge } from '../webauthn/clientData';\nimport type { SubmitResult } from '../adapters/types';\nimport {\n buildAddSignerOperation,\n buildRemoveSignerOperation,\n type SignerStorage,\n} from '../soroban/signer';\nimport { sendSmartWalletTx, type SendSmartWalletTxOptions } from '../soroban/submit';\n\n/**\n * Multi-device recovery: add (or remove) a passkey signer on an existing\n * smart-wallet, on-chain. Adding a signer from a NEW device — while an existing\n * device is still available — is what keeps a lost device from locking the user\n * out. The new device produces a `{credentialId, publicKey}` via `registerPasskey`;\n * an EXISTING device authorizes `add_signer` here.\n *\n * These are thin wrappers over {@link sendSmartWalletTx}: build the wallet-\n * authorized `add_signer` / `remove_signer` invocation, then sign it with the\n * existing passkey and submit (recording → sign → enforcing re-sim → submit).\n */\n\n/** The new device's signer to enroll. `credentialId` may be base64url or raw bytes. */\nexport interface NewDeviceSigner {\n credentialId: string | Uint8Array;\n /** SEC-1 uncompressed public key (65 bytes) from `registerPasskey`. */\n publicKey: Uint8Array;\n /** Optional UNIX-seconds expiry (v1). Omit for a non-expiring recovery signer. */\n expiration?: number | bigint;\n /** SignerStorage; defaults to `Persistent`. */\n storage?: SignerStorage;\n}\n\n/** Shared wiring for a wallet-authorized signer change — the submit options minus the operation. */\nexport interface WalletCallOptions extends Omit<SendSmartWalletTxOptions, 'operation'> {\n /** The smart-wallet C-address being modified. */\n walletContractId: string;\n}\n\nexport interface AddSignerOptions extends WalletCallOptions {\n newSigner: NewDeviceSigner;\n}\n\nexport interface RemoveSignerOptions extends WalletCallOptions {\n /** Credential id of the signer to remove (base64url or raw bytes). */\n credentialId: string | Uint8Array;\n}\n\nfunction toRawCredentialId(id: string | Uint8Array): Uint8Array {\n return typeof id === 'string' ? decodeChallenge(id) : id;\n}\n\n/**\n * Add a new passkey signer to a smart-wallet on-chain (multi-device recovery),\n * authorized by an existing device. Returns the submission result (tx hash on\n * success). Throws a typed {@link KitError} on any failure.\n */\nexport async function addSigner(options: AddSignerOptions): Promise<SubmitResult> {\n const { walletContractId, newSigner, ...wiring } = options;\n const operation = buildAddSignerOperation({\n walletContractId,\n signer: {\n credentialId: toRawCredentialId(newSigner.credentialId),\n publicKey: newSigner.publicKey,\n expiration: newSigner.expiration,\n storage: newSigner.storage,\n },\n });\n return sendSmartWalletTx({ ...wiring, operation });\n}\n\n/** Remove a passkey signer from a smart-wallet on-chain (authorized by an existing device). */\nexport async function removeSigner(options: RemoveSignerOptions): Promise<SubmitResult> {\n const { walletContractId, credentialId, ...wiring } = options;\n const operation = buildRemoveSignerOperation({\n walletContractId,\n credentialId: toRawCredentialId(credentialId),\n });\n return sendSmartWalletTx({ ...wiring, operation });\n}\n"]}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { buildSignerKeyScVal, signTransaction } from './chunk-BIRF5I6I.js';
|
|
2
|
+
import { KitError } from './chunk-AMYFJQAC.js';
|
|
3
|
+
import { xdr, rpc, TransactionBuilder, Keypair, nativeToScVal, Contract } from '@stellar/stellar-sdk';
|
|
4
|
+
|
|
5
|
+
function noneTuple() {
|
|
6
|
+
return xdr.ScVal.scvVec([xdr.ScVal.scvVoid()]);
|
|
7
|
+
}
|
|
8
|
+
function expirationScVal(expiration) {
|
|
9
|
+
if (expiration === void 0) return noneTuple();
|
|
10
|
+
const ts = BigInt(expiration);
|
|
11
|
+
if (ts < 0n) {
|
|
12
|
+
throw new KitError(
|
|
13
|
+
"CONTRACT_AUTH_FAILED",
|
|
14
|
+
"signer expiration must be a non-negative UNIX-seconds timestamp (v1)"
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
return xdr.ScVal.scvVec([nativeToScVal(ts, { type: "u64" })]);
|
|
18
|
+
}
|
|
19
|
+
function storageScVal(storage = "Persistent") {
|
|
20
|
+
return xdr.ScVal.scvVec([xdr.ScVal.scvSymbol(storage)]);
|
|
21
|
+
}
|
|
22
|
+
function buildSecp256r1Signer(spec) {
|
|
23
|
+
if (spec.credentialId.length === 0) {
|
|
24
|
+
throw new KitError("CONTRACT_AUTH_FAILED", "buildSecp256r1Signer: empty credential id");
|
|
25
|
+
}
|
|
26
|
+
if (spec.publicKey.length !== 65 || spec.publicKey[0] !== 4) {
|
|
27
|
+
throw new KitError(
|
|
28
|
+
"INVALID_PUBLIC_KEY",
|
|
29
|
+
"buildSecp256r1Signer: publicKey must be a 65-byte SEC-1 uncompressed point (0x04-prefixed)"
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return xdr.ScVal.scvVec([
|
|
33
|
+
xdr.ScVal.scvSymbol("Secp256r1"),
|
|
34
|
+
xdr.ScVal.scvBytes(Buffer.from(spec.credentialId)),
|
|
35
|
+
xdr.ScVal.scvBytes(Buffer.from(spec.publicKey)),
|
|
36
|
+
expirationScVal(spec.expiration),
|
|
37
|
+
// SignerExpiration
|
|
38
|
+
noneTuple(),
|
|
39
|
+
// SignerLimits(None)
|
|
40
|
+
storageScVal(spec.storage)
|
|
41
|
+
// SignerStorage
|
|
42
|
+
]);
|
|
43
|
+
}
|
|
44
|
+
function contractCall(contractId, method, ...args) {
|
|
45
|
+
let contract;
|
|
46
|
+
try {
|
|
47
|
+
contract = new Contract(contractId);
|
|
48
|
+
} catch (cause) {
|
|
49
|
+
throw new KitError("CONTRACT_AUTH_FAILED", `invalid smart-wallet contract id "${contractId}"`, {
|
|
50
|
+
cause
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return contract.call(method, ...args);
|
|
54
|
+
}
|
|
55
|
+
function buildAddSignerOperation(options) {
|
|
56
|
+
const signerScVal = options.signer instanceof xdr.ScVal ? options.signer : buildSecp256r1Signer(options.signer);
|
|
57
|
+
return contractCall(options.walletContractId, "add_signer", signerScVal);
|
|
58
|
+
}
|
|
59
|
+
function buildRemoveSignerOperation(options) {
|
|
60
|
+
if (options.credentialId.length === 0) {
|
|
61
|
+
throw new KitError("CONTRACT_AUTH_FAILED", "buildRemoveSignerOperation: empty credential id");
|
|
62
|
+
}
|
|
63
|
+
return contractCall(
|
|
64
|
+
options.walletContractId,
|
|
65
|
+
"remove_signer",
|
|
66
|
+
buildSignerKeyScVal(options.credentialId)
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
function directSubmission(options) {
|
|
70
|
+
const server = new rpc.Server(options.rpcUrl, {
|
|
71
|
+
allowHttp: options.allowHttp ?? options.rpcUrl.startsWith("http://")
|
|
72
|
+
});
|
|
73
|
+
const attempts = options.pollAttempts ?? 30;
|
|
74
|
+
const intervalMs = options.pollIntervalMs ?? 1e3;
|
|
75
|
+
return {
|
|
76
|
+
async send(signedTxXdr) {
|
|
77
|
+
const tx = TransactionBuilder.fromXDR(signedTxXdr, options.networkPassphrase);
|
|
78
|
+
let sent;
|
|
79
|
+
try {
|
|
80
|
+
sent = await server.sendTransaction(tx);
|
|
81
|
+
} catch (cause) {
|
|
82
|
+
throw new KitError("NETWORK_ERROR", "failed to submit transaction to soroban-rpc", {
|
|
83
|
+
cause
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (sent.status === "ERROR") {
|
|
87
|
+
return {
|
|
88
|
+
status: "FAILED",
|
|
89
|
+
hash: sent.hash,
|
|
90
|
+
errorResultXdr: sent.errorResult?.toXDR("base64")
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
let final;
|
|
94
|
+
try {
|
|
95
|
+
final = await server.pollTransaction(sent.hash, {
|
|
96
|
+
attempts,
|
|
97
|
+
sleepStrategy: () => intervalMs
|
|
98
|
+
});
|
|
99
|
+
} catch (cause) {
|
|
100
|
+
throw new KitError(
|
|
101
|
+
"NETWORK_ERROR",
|
|
102
|
+
`transaction ${sent.hash} was not confirmed after ${String(attempts)} polls`,
|
|
103
|
+
{ cause }
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
if (final.status === rpc.Api.GetTransactionStatus.NOT_FOUND) {
|
|
107
|
+
throw new KitError(
|
|
108
|
+
"NETWORK_ERROR",
|
|
109
|
+
`transaction ${sent.hash} still NOT_FOUND after ${String(attempts)} polls`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const success = final.status === rpc.Api.GetTransactionStatus.SUCCESS;
|
|
113
|
+
return {
|
|
114
|
+
status: success ? "SUCCESS" : "FAILED",
|
|
115
|
+
hash: sent.hash,
|
|
116
|
+
returnValue: success ? final.returnValue : void 0
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
var DEFAULT_EXPIRATION_OFFSET = 60;
|
|
122
|
+
async function sendSmartWalletTx(options) {
|
|
123
|
+
const server = new rpc.Server(options.rpcUrl, {
|
|
124
|
+
allowHttp: options.allowHttp ?? options.rpcUrl.startsWith("http://")
|
|
125
|
+
});
|
|
126
|
+
let source;
|
|
127
|
+
try {
|
|
128
|
+
source = Keypair.fromSecret(options.sourceSecret);
|
|
129
|
+
} catch (cause) {
|
|
130
|
+
throw new KitError("CONTRACT_AUTH_FAILED", "invalid fee-source secret", { cause });
|
|
131
|
+
}
|
|
132
|
+
const account = await server.getAccount(source.publicKey());
|
|
133
|
+
const tx = new TransactionBuilder(account, {
|
|
134
|
+
fee: options.fee ?? "2000000",
|
|
135
|
+
networkPassphrase: options.networkPassphrase
|
|
136
|
+
}).addOperation(options.operation).setTimeout(options.timeoutSeconds ?? 120).build();
|
|
137
|
+
const recSim = await server.simulateTransaction(tx);
|
|
138
|
+
if (!rpc.Api.isSimulationSuccess(recSim)) {
|
|
139
|
+
const detail = "error" in recSim ? String(recSim.error) : "unknown simulation error";
|
|
140
|
+
throw new KitError(
|
|
141
|
+
"CONTRACT_AUTH_FAILED",
|
|
142
|
+
`smart-wallet tx simulation failed: ${detail.slice(0, 300)}`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
const prepared = rpc.assembleTransaction(tx, recSim).build();
|
|
146
|
+
const signatureExpirationLedger = (await server.getLatestLedger()).sequence + (options.signatureExpirationLedgerOffset ?? DEFAULT_EXPIRATION_OFFSET);
|
|
147
|
+
const signedAuthXdr = await signTransaction(prepared.toXDR(), {
|
|
148
|
+
networkPassphrase: options.networkPassphrase,
|
|
149
|
+
sign: options.sign,
|
|
150
|
+
target: "smart-wallet",
|
|
151
|
+
signatureExpirationLedger,
|
|
152
|
+
verify: options.verify
|
|
153
|
+
});
|
|
154
|
+
const signedTx = TransactionBuilder.fromXDR(signedAuthXdr, options.networkPassphrase);
|
|
155
|
+
const enfSim = await server.simulateTransaction(signedTx);
|
|
156
|
+
if (!rpc.Api.isSimulationSuccess(enfSim)) {
|
|
157
|
+
const detail = "error" in enfSim ? String(enfSim.error) : "unknown simulation error";
|
|
158
|
+
throw new KitError(
|
|
159
|
+
"CONTRACT_AUTH_FAILED",
|
|
160
|
+
`signed smart-wallet tx failed enforcing simulation (auth rejected?): ${detail.slice(0, 300)}`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
const finalTx = rpc.assembleTransaction(signedTx, enfSim).build();
|
|
164
|
+
finalTx.sign(source);
|
|
165
|
+
const submission = options.submission ?? directSubmission({
|
|
166
|
+
rpcUrl: options.rpcUrl,
|
|
167
|
+
networkPassphrase: options.networkPassphrase,
|
|
168
|
+
allowHttp: options.allowHttp
|
|
169
|
+
});
|
|
170
|
+
return submission.send(finalTx.toXDR());
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export { buildAddSignerOperation, buildRemoveSignerOperation, buildSecp256r1Signer, directSubmission, sendSmartWalletTx };
|
|
174
|
+
//# sourceMappingURL=chunk-UV5HORMF.js.map
|
|
175
|
+
//# sourceMappingURL=chunk-UV5HORMF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/soroban/signer.ts","../src/adapters/direct.ts","../src/soroban/submit.ts"],"names":["rpc","TransactionBuilder"],"mappings":"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","file":"chunk-UV5HORMF.js","sourcesContent":["import { Contract, nativeToScVal, xdr } from '@stellar/stellar-sdk';\nimport { KitError } from '../errors';\nimport { buildSignerKeyScVal } from './smartWallet';\n\n/**\n * passkey-kit v1 (v0.13.0, Protocol 27) smart-wallet SIGNER encoders — the WRITE\n * path (what `add_signer` / `remove_signer` consume), complementing\n * `smartWallet.ts` (the signature READ path the wallet's `__check_auth` reads).\n *\n * A signer is added on-chain as\n *\n * Signer::Secp256r1(Bytes id, BytesN<65> public_key,\n * SignerExpiration, SignerLimits, SignerStorage)\n *\n * which `process_signer` reduces to `SignerKey::Secp256r1(id) ->\n * SignerVal::Secp256r1(public_key, ..)`: the raw credential id is the key, the\n * SEC-1 public key lives in the value. Shape + field order confirmed by kalepail\n * (passkey-kit author) in issue #32 and proven on testnet against the audited v1\n * wasm (`scripts/smartwallet-v1-e2e.ts`).\n *\n * v1 note: `SignerExpiration` is a UNIX-seconds `u64` timestamp (Protocol 27),\n * NOT a ledger sequence as in the pre-v1 kit.\n */\n\n/** `SignerStorage` — where the contract persists the signer entry. */\nexport type SignerStorage = 'Persistent' | 'Temporary';\n\nexport interface Secp256r1SignerSpec {\n /** Raw WebAuthn credential-id bytes (base64url-decoded `rawId`) — becomes the SignerKey. */\n credentialId: Uint8Array;\n /** SEC-1 uncompressed public key (65 bytes, `0x04 ‖ X ‖ Y`) — stored in SignerVal. */\n publicKey: Uint8Array;\n /**\n * `SignerExpiration` — a UNIX-seconds timestamp (v1 / Protocol 27) after which\n * the signer stops being valid. Omit for `None` (a non-expiring signer, the\n * usual choice for a recovery / second device).\n */\n expiration?: number | bigint;\n /** `SignerStorage` — defaults to `Persistent`. */\n storage?: SignerStorage;\n}\n\n/** A tuple-struct `Option` field set to `None` → `scvVec([Void])`. */\nfunction noneTuple(): xdr.ScVal {\n return xdr.ScVal.scvVec([xdr.ScVal.scvVoid()]);\n}\n\n/** `SignerExpiration(Option<u64>)` — `None` or `Some(unixSeconds)`. */\nfunction expirationScVal(expiration?: number | bigint): xdr.ScVal {\n if (expiration === undefined) return noneTuple();\n const ts = BigInt(expiration);\n if (ts < 0n) {\n throw new KitError(\n 'CONTRACT_AUTH_FAILED',\n 'signer expiration must be a non-negative UNIX-seconds timestamp (v1)',\n );\n }\n return xdr.ScVal.scvVec([nativeToScVal(ts, { type: 'u64' })]);\n}\n\n/** `SignerStorage::Persistent | Temporary` → `scvVec([Symbol(variant)])`. */\nfunction storageScVal(storage: SignerStorage = 'Persistent'): xdr.ScVal {\n return xdr.ScVal.scvVec([xdr.ScVal.scvSymbol(storage)]);\n}\n\n/**\n * Encode a `Signer::Secp256r1(..)` for `add_signer`. `SignerLimits` is always\n * `None` (unrestricted) — policy/limited signers are out of scope for this\n * minimal passkey SDK. Validates the credential id and the SEC-1 public key so a\n * malformed signer can never be handed to the contract.\n */\nexport function buildSecp256r1Signer(spec: Secp256r1SignerSpec): xdr.ScVal {\n if (spec.credentialId.length === 0) {\n throw new KitError('CONTRACT_AUTH_FAILED', 'buildSecp256r1Signer: empty credential id');\n }\n if (spec.publicKey.length !== 65 || spec.publicKey[0] !== 0x04) {\n throw new KitError(\n 'INVALID_PUBLIC_KEY',\n 'buildSecp256r1Signer: publicKey must be a 65-byte SEC-1 uncompressed point (0x04-prefixed)',\n );\n }\n return xdr.ScVal.scvVec([\n xdr.ScVal.scvSymbol('Secp256r1'),\n xdr.ScVal.scvBytes(Buffer.from(spec.credentialId)),\n xdr.ScVal.scvBytes(Buffer.from(spec.publicKey)),\n expirationScVal(spec.expiration), // SignerExpiration\n noneTuple(), // SignerLimits(None)\n storageScVal(spec.storage), // SignerStorage\n ]);\n}\n\nfunction contractCall(contractId: string, method: string, ...args: xdr.ScVal[]): xdr.Operation {\n let contract: Contract;\n try {\n contract = new Contract(contractId);\n } catch (cause) {\n throw new KitError('CONTRACT_AUTH_FAILED', `invalid smart-wallet contract id \"${contractId}\"`, {\n cause,\n });\n }\n return contract.call(method, ...args);\n}\n\nexport interface BuildAddSignerOptions {\n /** The smart-wallet C-address the signer is being added to. */\n walletContractId: string;\n /** The signer to add — a `Secp256r1SignerSpec`, or a pre-built `Signer` ScVal. */\n signer: Secp256r1SignerSpec | xdr.ScVal;\n}\n\n/**\n * Build the `wallet.add_signer(signer)` invocation. The resulting invoke-host\n * operation requires the wallet's OWN auth: an EXISTING signer authorizes adding\n * the new one, which is what keeps a lost device from locking the user out.\n * Sign the produced transaction's auth entry with\n * `signTransaction(.., { target: 'smart-wallet' })`.\n */\nexport function buildAddSignerOperation(options: BuildAddSignerOptions): xdr.Operation {\n const signerScVal =\n options.signer instanceof xdr.ScVal ? options.signer : buildSecp256r1Signer(options.signer);\n return contractCall(options.walletContractId, 'add_signer', signerScVal);\n}\n\nexport interface BuildRemoveSignerOptions {\n walletContractId: string;\n /** Raw credential-id bytes of the signer to remove (its `SignerKey`). */\n credentialId: Uint8Array;\n}\n\n/** Build the `wallet.remove_signer(SignerKey::Secp256r1(id))` invocation (wallet-authorized). */\nexport function buildRemoveSignerOperation(options: BuildRemoveSignerOptions): xdr.Operation {\n if (options.credentialId.length === 0) {\n throw new KitError('CONTRACT_AUTH_FAILED', 'buildRemoveSignerOperation: empty credential id');\n }\n return contractCall(\n options.walletContractId,\n 'remove_signer',\n buildSignerKeyScVal(options.credentialId),\n );\n}\n","import { rpc, TransactionBuilder } from '@stellar/stellar-sdk';\nimport { KitError } from '../errors';\nimport type { SubmissionAdapter, SubmitResult } from './types';\n\nexport interface DirectSubmissionOptions {\n rpcUrl: string;\n networkPassphrase: string;\n allowHttp?: boolean;\n /** Max confirmation-poll attempts before failing with NETWORK_ERROR (default 30). */\n pollAttempts?: number;\n /** Delay between confirmation polls, in ms (default 1000). */\n pollIntervalMs?: number;\n}\n\n/**\n * Default, zero-infra submission: send straight to soroban-rpc and poll. No\n * relayer, no API key. Swappable with `launchtube`/`openzeppelinRelayer` with\n * zero ceremony changes (same `SubmissionAdapter` shape).\n */\nexport function directSubmission(options: DirectSubmissionOptions): SubmissionAdapter {\n const server = new rpc.Server(options.rpcUrl, {\n allowHttp: options.allowHttp ?? options.rpcUrl.startsWith('http://'),\n });\n const attempts = options.pollAttempts ?? 30;\n const intervalMs = options.pollIntervalMs ?? 1000;\n return {\n async send(signedTxXdr: string): Promise<SubmitResult> {\n const tx = TransactionBuilder.fromXDR(signedTxXdr, options.networkPassphrase);\n let sent;\n try {\n sent = await server.sendTransaction(tx);\n } catch (cause) {\n throw new KitError('NETWORK_ERROR', 'failed to submit transaction to soroban-rpc', {\n cause,\n });\n }\n if (sent.status === 'ERROR') {\n return {\n status: 'FAILED',\n hash: sent.hash,\n errorResultXdr: sent.errorResult?.toXDR('base64'),\n };\n }\n // Bounded polling: give up (NETWORK_ERROR) instead of hanging forever.\n let final;\n try {\n final = await server.pollTransaction(sent.hash, {\n attempts,\n sleepStrategy: () => intervalMs,\n });\n } catch (cause) {\n throw new KitError(\n 'NETWORK_ERROR',\n `transaction ${sent.hash} was not confirmed after ${String(attempts)} polls`,\n { cause },\n );\n }\n if (final.status === rpc.Api.GetTransactionStatus.NOT_FOUND) {\n throw new KitError(\n 'NETWORK_ERROR',\n `transaction ${sent.hash} still NOT_FOUND after ${String(attempts)} polls`,\n );\n }\n const success = final.status === rpc.Api.GetTransactionStatus.SUCCESS;\n return {\n status: success ? 'SUCCESS' : 'FAILED',\n hash: sent.hash,\n returnValue: success ? (final as { returnValue?: unknown }).returnValue : undefined,\n };\n },\n };\n}\n","import { Keypair, TransactionBuilder, rpc, xdr } from '@stellar/stellar-sdk';\nimport { KitError } from '../errors';\nimport { directSubmission } from '../adapters/direct';\nimport type { SubmissionAdapter, SubmitResult } from '../adapters/types';\nimport { signTransaction, type SignVerifyOptions, type WebAuthnSigner } from './sign';\n\nexport interface SendSmartWalletTxOptions {\n /** The single wallet-authorized invocation to run (SAC transfer, contract call, add_signer, …). */\n operation: xdr.Operation;\n networkPassphrase: string;\n rpcUrl: string;\n /**\n * Secret of the funded classic account that pays the fee + sequences the tx. The\n * passkey provides the wallet's AUTH; this only sponsors the envelope\n * (ephemeral/friendbot-funded in a demo; a relayer key otherwise).\n */\n sourceSecret: string;\n /** The wallet's passkey signer that authorizes the invocation. */\n sign: WebAuthnSigner;\n /** Where the signed tx is submitted. Defaults to `direct` (soroban-rpc). */\n submission?: SubmissionAdapter;\n /** Opt-in pre-flight validation of the authorizing assertion. */\n verify?: SignVerifyOptions;\n fee?: string;\n timeoutSeconds?: number;\n /** Ledgers ahead of `latestLedger` to expire the auth signature at (default 60). */\n signatureExpirationLedgerOffset?: number;\n allowHttp?: boolean;\n}\n\nconst DEFAULT_EXPIRATION_OFFSET = 60;\n\n/**\n * Sign and submit a single passkey-authorized smart-wallet transaction — the\n * general primitive behind every smart-wallet action (payment, contract call,\n * add/remove signer). The full dance:\n *\n * 1. recording sim → assemble (discovers the wallet's auth requirement);\n * 2. sign the wallet auth entry with the passkey (smart-wallet Signatures map),\n * binding the expiration ledger the contract re-derives;\n * 3. enforcing re-sim of the SIGNED tx — recording auth never runs the custom\n * account's `__check_auth`, so it misses the signer-storage reads and\n * under-counts instructions; the enforcing pass yields the true footprint +\n * resources, and doubles as a client-side proof the auth is accepted;\n * 4. pay the fee from the classic source and submit via the pluggable adapter.\n *\n * tx.fee + sorobanData are outside the auth preimage, so re-assembling with the\n * enforcing resources never invalidates the signature. Throws a typed KitError on\n * any simulation/validation failure.\n */\nexport async function sendSmartWalletTx(options: SendSmartWalletTxOptions): Promise<SubmitResult> {\n const server = new rpc.Server(options.rpcUrl, {\n allowHttp: options.allowHttp ?? options.rpcUrl.startsWith('http://'),\n });\n let source: Keypair;\n try {\n source = Keypair.fromSecret(options.sourceSecret);\n } catch (cause) {\n throw new KitError('CONTRACT_AUTH_FAILED', 'invalid fee-source secret', { cause });\n }\n\n const account = await server.getAccount(source.publicKey());\n const tx = new TransactionBuilder(account, {\n fee: options.fee ?? '2000000',\n networkPassphrase: options.networkPassphrase,\n })\n .addOperation(options.operation)\n .setTimeout(options.timeoutSeconds ?? 120)\n .build();\n\n const recSim = await server.simulateTransaction(tx);\n if (!rpc.Api.isSimulationSuccess(recSim)) {\n const detail = 'error' in recSim ? String(recSim.error) : 'unknown simulation error';\n throw new KitError(\n 'CONTRACT_AUTH_FAILED',\n `smart-wallet tx simulation failed: ${detail.slice(0, 300)}`,\n );\n }\n const prepared = rpc.assembleTransaction(tx, recSim).build();\n\n const signatureExpirationLedger =\n (await server.getLatestLedger()).sequence +\n (options.signatureExpirationLedgerOffset ?? DEFAULT_EXPIRATION_OFFSET);\n const signedAuthXdr = await signTransaction(prepared.toXDR(), {\n networkPassphrase: options.networkPassphrase,\n sign: options.sign,\n target: 'smart-wallet',\n signatureExpirationLedger,\n verify: options.verify,\n });\n\n const signedTx = TransactionBuilder.fromXDR(signedAuthXdr, options.networkPassphrase);\n const enfSim = await server.simulateTransaction(signedTx);\n if (!rpc.Api.isSimulationSuccess(enfSim)) {\n const detail = 'error' in enfSim ? String(enfSim.error) : 'unknown simulation error';\n throw new KitError(\n 'CONTRACT_AUTH_FAILED',\n `signed smart-wallet tx failed enforcing simulation (auth rejected?): ${detail.slice(0, 300)}`,\n );\n }\n\n const finalTx = rpc.assembleTransaction(signedTx, enfSim).build();\n finalTx.sign(source);\n const submission =\n options.submission ??\n directSubmission({\n rpcUrl: options.rpcUrl,\n networkPassphrase: options.networkPassphrase,\n allowHttp: options.allowHttp,\n });\n return submission.send(finalTx.toXDR());\n}\n"]}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { decodeCborFirst, concatBytes, parseAuthenticatorData } from './chunk-JNJ2FY45.js';
|
|
2
|
+
import { bytesToBase64Url, utf8ToBytes, browserWebAuthnClient, randomBytes, defaultCredentialStorage } from './chunk-LB4HOTLV.js';
|
|
3
|
+
import { assertES256, assertUserActivation } from './chunk-TPBBZNPP.js';
|
|
4
|
+
import { KitError } from './chunk-AMYFJQAC.js';
|
|
5
|
+
import { p256 } from '@noble/curves/nist';
|
|
6
|
+
|
|
7
|
+
// src/webauthn/createOptions.ts
|
|
8
|
+
function buildCreateOptions(input) {
|
|
9
|
+
const residentKey = input.residentKey ?? "required";
|
|
10
|
+
return {
|
|
11
|
+
rp: { id: input.rpId, name: input.rpName },
|
|
12
|
+
user: {
|
|
13
|
+
id: bytesToBase64Url(input.userId ?? utf8ToBytes(input.userName)),
|
|
14
|
+
name: input.userName,
|
|
15
|
+
displayName: input.userDisplayName ?? input.userName
|
|
16
|
+
},
|
|
17
|
+
challenge: bytesToBase64Url(input.challenge),
|
|
18
|
+
pubKeyCredParams: [{ type: "public-key", alg: -7 }],
|
|
19
|
+
authenticatorSelection: {
|
|
20
|
+
residentKey,
|
|
21
|
+
requireResidentKey: residentKey === "required",
|
|
22
|
+
userVerification: input.userVerification ?? "preferred"
|
|
23
|
+
},
|
|
24
|
+
attestation: "none",
|
|
25
|
+
timeout: input.timeout ?? 6e4
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
var COSE_KTY = 1;
|
|
29
|
+
var COSE_ALG = 3;
|
|
30
|
+
var COSE_CRV = -1;
|
|
31
|
+
var COSE_X = -2;
|
|
32
|
+
var COSE_Y = -3;
|
|
33
|
+
var KTY_EC2 = 2;
|
|
34
|
+
var CRV_P256 = 1;
|
|
35
|
+
function coseKeyToSec1(coseKey) {
|
|
36
|
+
let decoded;
|
|
37
|
+
try {
|
|
38
|
+
decoded = decodeCborFirst(coseKey);
|
|
39
|
+
} catch (cause) {
|
|
40
|
+
throw new KitError("INVALID_PUBLIC_KEY", "failed to CBOR-decode COSE key", { cause });
|
|
41
|
+
}
|
|
42
|
+
if (!(decoded instanceof Map)) {
|
|
43
|
+
throw new KitError("INVALID_PUBLIC_KEY", "COSE key is not a CBOR map");
|
|
44
|
+
}
|
|
45
|
+
const map = decoded;
|
|
46
|
+
const alg = map.get(COSE_ALG);
|
|
47
|
+
if (typeof alg !== "number") {
|
|
48
|
+
throw new KitError("ES256_NOT_SUPPORTED", `COSE alg ${String(alg)} is not ES256 (-7)`);
|
|
49
|
+
}
|
|
50
|
+
assertES256(alg);
|
|
51
|
+
if (map.get(COSE_KTY) !== KTY_EC2) {
|
|
52
|
+
throw new KitError(
|
|
53
|
+
"INVALID_PUBLIC_KEY",
|
|
54
|
+
`COSE kty ${String(map.get(COSE_KTY))} is not EC2 (2)`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
if (map.get(COSE_CRV) !== CRV_P256) {
|
|
58
|
+
throw new KitError(
|
|
59
|
+
"INVALID_PUBLIC_KEY",
|
|
60
|
+
`COSE crv ${String(map.get(COSE_CRV))} is not P-256 (1)`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
const x = map.get(COSE_X);
|
|
64
|
+
const y = map.get(COSE_Y);
|
|
65
|
+
if (!(x instanceof Uint8Array) || x.length !== 32) {
|
|
66
|
+
throw new KitError("INVALID_PUBLIC_KEY", "COSE key X coordinate is not 32 bytes");
|
|
67
|
+
}
|
|
68
|
+
if (!(y instanceof Uint8Array) || y.length !== 32) {
|
|
69
|
+
throw new KitError("INVALID_PUBLIC_KEY", "COSE key Y coordinate is not 32 bytes");
|
|
70
|
+
}
|
|
71
|
+
const sec1 = concatBytes(new Uint8Array([4]), x, y);
|
|
72
|
+
try {
|
|
73
|
+
p256.Point.fromHex(sec1);
|
|
74
|
+
} catch (cause) {
|
|
75
|
+
throw new KitError("INVALID_PUBLIC_KEY", "public key is not a valid P-256 point", { cause });
|
|
76
|
+
}
|
|
77
|
+
return sec1;
|
|
78
|
+
}
|
|
79
|
+
function extractPublicKeyFromAuthData(authData) {
|
|
80
|
+
const parsed = parseAuthenticatorData(authData);
|
|
81
|
+
if (!parsed.credentialPublicKey) {
|
|
82
|
+
throw new KitError(
|
|
83
|
+
"INVALID_PUBLIC_KEY",
|
|
84
|
+
"authenticatorData has no attested credential public key"
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
return coseKeyToSec1(parsed.credentialPublicKey);
|
|
88
|
+
}
|
|
89
|
+
function extractPublicKeyFromAttestationObject(attestationObject) {
|
|
90
|
+
let decoded;
|
|
91
|
+
try {
|
|
92
|
+
decoded = decodeCborFirst(attestationObject);
|
|
93
|
+
} catch (cause) {
|
|
94
|
+
throw new KitError("INVALID_PUBLIC_KEY", "failed to CBOR-decode attestationObject", { cause });
|
|
95
|
+
}
|
|
96
|
+
if (!(decoded instanceof Map)) {
|
|
97
|
+
throw new KitError("INVALID_PUBLIC_KEY", "attestationObject is not a CBOR map");
|
|
98
|
+
}
|
|
99
|
+
const authData = decoded.get("authData");
|
|
100
|
+
if (!(authData instanceof Uint8Array)) {
|
|
101
|
+
throw new KitError("INVALID_PUBLIC_KEY", "attestationObject has no authData byte string");
|
|
102
|
+
}
|
|
103
|
+
return extractPublicKeyFromAuthData(authData);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/ceremonies/create.ts
|
|
107
|
+
async function registerPasskey(options) {
|
|
108
|
+
assertUserActivation(options.userActivation);
|
|
109
|
+
const webauthn = options.webauthn ?? browserWebAuthnClient();
|
|
110
|
+
const creationOptions = buildCreateOptions({
|
|
111
|
+
rpId: options.rpId,
|
|
112
|
+
rpName: options.rpName,
|
|
113
|
+
userName: options.userName,
|
|
114
|
+
userId: options.userId,
|
|
115
|
+
challenge: options.challenge ?? randomBytes(32),
|
|
116
|
+
residentKey: options.residentKey,
|
|
117
|
+
userVerification: options.userVerification
|
|
118
|
+
});
|
|
119
|
+
const credential = await webauthn.create(creationOptions);
|
|
120
|
+
const publicKey = extractPublicKeyFromAttestationObject(credential.attestationObject);
|
|
121
|
+
return { credentialId: credential.id, publicKey };
|
|
122
|
+
}
|
|
123
|
+
async function createPasskey(options) {
|
|
124
|
+
const storage = options.storage ?? defaultCredentialStorage();
|
|
125
|
+
const { credentialId, publicKey } = await registerPasskey(options);
|
|
126
|
+
const { contractId } = await options.deployer.deploy({ publicKey, credentialId });
|
|
127
|
+
storage.set(options.rpId, credentialId);
|
|
128
|
+
return { contractId, credentialId, publicKey };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export { buildCreateOptions, coseKeyToSec1, createPasskey, extractPublicKeyFromAttestationObject, extractPublicKeyFromAuthData, registerPasskey };
|
|
132
|
+
//# sourceMappingURL=chunk-W75OQKHR.js.map
|
|
133
|
+
//# sourceMappingURL=chunk-W75OQKHR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/webauthn/createOptions.ts","../src/webauthn/publicKey.ts","../src/ceremonies/create.ts"],"names":[],"mappings":"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","file":"chunk-W75OQKHR.js","sourcesContent":["import { bytesToBase64Url } from '../internal/encoding';\nimport { utf8ToBytes } from '../internal/encoding';\n\nexport type ResidentKeyRequirement = 'discouraged' | 'preferred' | 'required';\nexport type UserVerificationRequirement = 'discouraged' | 'preferred' | 'required';\n\nexport interface BuildCreateOptionsInput {\n rpId: string;\n rpName: string;\n userName: string;\n /** Stable, opaque user handle. Defaults to UTF-8 bytes of `userName`. */\n userId?: Uint8Array;\n userDisplayName?: string;\n /** Raw challenge bytes (base64url-encoded into the options). */\n challenge: Uint8Array;\n residentKey?: ResidentKeyRequirement;\n userVerification?: UserVerificationRequirement;\n timeout?: number;\n}\n\n/** ES256-only credential parameter. The `alg: -7` literal is non-negotiable. */\nexport interface PubKeyCredParam {\n type: 'public-key';\n alg: -7;\n}\n\nexport interface PublicKeyCredentialCreationOptionsJSON {\n rp: { id: string; name: string };\n user: { id: string; name: string; displayName: string };\n challenge: string;\n pubKeyCredParams: PubKeyCredParam[];\n authenticatorSelection: {\n residentKey: ResidentKeyRequirement;\n requireResidentKey: boolean;\n userVerification: UserVerificationRequirement;\n };\n attestation: 'none';\n timeout: number;\n}\n\n/**\n * Build WebAuthn registration options that offer ES256 *and only* ES256\n * (`pubKeyCredParams: [{ type: 'public-key', alg: -7 }]`) — invariant #1.\n * Soroban verifies only secp256r1, so we never advertise any other algorithm.\n */\nexport function buildCreateOptions(\n input: BuildCreateOptionsInput,\n): PublicKeyCredentialCreationOptionsJSON {\n const residentKey = input.residentKey ?? 'required';\n return {\n rp: { id: input.rpId, name: input.rpName },\n user: {\n id: bytesToBase64Url(input.userId ?? utf8ToBytes(input.userName)),\n name: input.userName,\n displayName: input.userDisplayName ?? input.userName,\n },\n challenge: bytesToBase64Url(input.challenge),\n pubKeyCredParams: [{ type: 'public-key', alg: -7 }],\n authenticatorSelection: {\n residentKey,\n requireResidentKey: residentKey === 'required',\n userVerification: input.userVerification ?? 'preferred',\n },\n attestation: 'none',\n timeout: input.timeout ?? 60_000,\n };\n}\n","import { p256 } from '@noble/curves/nist';\nimport { KitError } from '../errors';\nimport { concatBytes } from '../internal/bytes';\nimport { decodeCborFirst, type CborMap } from '../internal/cbor';\nimport { parseAuthenticatorData } from './authData';\nimport { assertES256 } from '../anchors';\n\n// COSE key common parameters (RFC 9052) and EC2 key parameters.\nconst COSE_KTY = 1;\nconst COSE_ALG = 3;\nconst COSE_CRV = -1;\nconst COSE_X = -2;\nconst COSE_Y = -3;\n\nconst KTY_EC2 = 2;\nconst CRV_P256 = 1;\n\n/**\n * Convert a CBOR-encoded COSE EC2 public key to the 65-byte SEC-1 uncompressed\n * point `0x04 ‖ X ‖ Y`. Enforces ES256/P-256 — anything else (e.g. RS256)\n * throws `ES256_NOT_SUPPORTED` (invariant #1).\n */\nexport function coseKeyToSec1(coseKey: Uint8Array): Uint8Array {\n let decoded: ReturnType<typeof decodeCborFirst>;\n try {\n decoded = decodeCborFirst(coseKey);\n } catch (cause) {\n throw new KitError('INVALID_PUBLIC_KEY', 'failed to CBOR-decode COSE key', { cause });\n }\n if (!(decoded instanceof Map)) {\n throw new KitError('INVALID_PUBLIC_KEY', 'COSE key is not a CBOR map');\n }\n const map = decoded as CborMap;\n const alg = map.get(COSE_ALG);\n if (typeof alg !== 'number') {\n throw new KitError('ES256_NOT_SUPPORTED', `COSE alg ${String(alg)} is not ES256 (-7)`);\n }\n assertES256(alg); // anchor RS256_HARD_FAIL\n if (map.get(COSE_KTY) !== KTY_EC2) {\n throw new KitError(\n 'INVALID_PUBLIC_KEY',\n `COSE kty ${String(map.get(COSE_KTY))} is not EC2 (2)`,\n );\n }\n if (map.get(COSE_CRV) !== CRV_P256) {\n throw new KitError(\n 'INVALID_PUBLIC_KEY',\n `COSE crv ${String(map.get(COSE_CRV))} is not P-256 (1)`,\n );\n }\n const x = map.get(COSE_X);\n const y = map.get(COSE_Y);\n if (!(x instanceof Uint8Array) || x.length !== 32) {\n throw new KitError('INVALID_PUBLIC_KEY', 'COSE key X coordinate is not 32 bytes');\n }\n if (!(y instanceof Uint8Array) || y.length !== 32) {\n throw new KitError('INVALID_PUBLIC_KEY', 'COSE key Y coordinate is not 32 bytes');\n }\n const sec1 = concatBytes(new Uint8Array([0x04]), x, y);\n // Reject off-curve / invalid points so an invalid key can never be persisted.\n try {\n p256.Point.fromHex(sec1);\n } catch (cause) {\n throw new KitError('INVALID_PUBLIC_KEY', 'public key is not a valid P-256 point', { cause });\n }\n return sec1;\n}\n\n/** Extract the SEC-1 public key from raw authenticatorData (attested data). */\nexport function extractPublicKeyFromAuthData(authData: Uint8Array): Uint8Array {\n const parsed = parseAuthenticatorData(authData);\n if (!parsed.credentialPublicKey) {\n throw new KitError(\n 'INVALID_PUBLIC_KEY',\n 'authenticatorData has no attested credential public key',\n );\n }\n return coseKeyToSec1(parsed.credentialPublicKey);\n}\n\n/** Extract the SEC-1 public key from a CBOR attestationObject (create ceremony). */\nexport function extractPublicKeyFromAttestationObject(attestationObject: Uint8Array): Uint8Array {\n let decoded: ReturnType<typeof decodeCborFirst>;\n try {\n decoded = decodeCborFirst(attestationObject);\n } catch (cause) {\n throw new KitError('INVALID_PUBLIC_KEY', 'failed to CBOR-decode attestationObject', { cause });\n }\n if (!(decoded instanceof Map)) {\n throw new KitError('INVALID_PUBLIC_KEY', 'attestationObject is not a CBOR map');\n }\n const authData = (decoded as CborMap).get('authData');\n if (!(authData instanceof Uint8Array)) {\n throw new KitError('INVALID_PUBLIC_KEY', 'attestationObject has no authData byte string');\n }\n return extractPublicKeyFromAuthData(authData);\n}\n","import { assertUserActivation } from '../anchors';\nimport { buildCreateOptions } from '../webauthn/createOptions';\nimport { extractPublicKeyFromAttestationObject } from '../webauthn/publicKey';\nimport type { PasskeyCredential } from '../types';\nimport { browserWebAuthnClient, defaultCredentialStorage } from './browserClient';\nimport { randomBytes } from './random';\nimport type { AccountDeployer, CredentialStorage, WebAuthnClient } from './types';\n\nexport interface CreatePasskeyOptions {\n rpId: string;\n rpName: string;\n userName: string;\n /** Deploys the smart account for the new passkey (contract-specific; see AccountDeployer). */\n deployer: AccountDeployer;\n webauthn?: WebAuthnClient;\n storage?: CredentialStorage;\n userId?: Uint8Array;\n challenge?: Uint8Array;\n residentKey?: 'discouraged' | 'preferred' | 'required';\n userVerification?: 'discouraged' | 'preferred' | 'required';\n /** Pass navigator.userActivation to enforce the Safari gesture requirement (S04). */\n userActivation?: { isActive: boolean };\n}\n\n/** Options for {@link registerPasskey} — a create WITHOUT deploy (see below). */\nexport type RegisterPasskeyOptions = Omit<CreatePasskeyOptions, 'deployer' | 'storage'>;\n\n/** A freshly-registered passkey credential (no account deployed yet). */\nexport interface RegisteredPasskey {\n /** base64url credential id returned by the authenticator. */\n credentialId: string;\n /** SEC-1 uncompressed public key (65 bytes). */\n publicKey: Uint8Array;\n}\n\n/**\n * `registerPasskey` — register an ES256-only passkey and extract its SEC-1 public\n * key (RS256 hard-fails), but do NOT deploy an account or persist anything. This\n * is the \"new device\" primitive for multi-device recovery: a fresh device\n * produces a `{credentialId, publicKey}` signer spec that an existing device then\n * authorizes on-chain via `addSigner`. (`createPasskey` is this plus a deploy.)\n */\nexport async function registerPasskey(options: RegisterPasskeyOptions): Promise<RegisteredPasskey> {\n assertUserActivation(options.userActivation); // anchor: apple-user-gesture (S04)\n const webauthn = options.webauthn ?? browserWebAuthnClient();\n\n const creationOptions = buildCreateOptions({\n rpId: options.rpId,\n rpName: options.rpName,\n userName: options.userName,\n userId: options.userId,\n challenge: options.challenge ?? randomBytes(32),\n residentKey: options.residentKey,\n userVerification: options.userVerification,\n });\n\n const credential = await webauthn.create(creationOptions);\n // ES256-only enforcement happens here: a non-ES256 credential throws ES256_NOT_SUPPORTED.\n const publicKey = extractPublicKeyFromAttestationObject(credential.attestationObject);\n return { credentialId: credential.id, publicKey };\n}\n\n/**\n * `createPasskey` — register an ES256-only passkey (see {@link registerPasskey}),\n * deploy a smart account for it via the factory, persist the credential id, and\n * return the account.\n */\nexport async function createPasskey(options: CreatePasskeyOptions): Promise<PasskeyCredential> {\n const storage = options.storage ?? defaultCredentialStorage();\n const { credentialId, publicKey } = await registerPasskey(options);\n const { contractId } = await options.deployer.deploy({ publicKey, credentialId });\n storage.set(options.rpId, credentialId);\n return { contractId, credentialId, publicKey };\n}\n"]}
|
package/dist/connect.cjs
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
var KitError = class _KitError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
name = "KitError";
|
|
7
|
+
constructor(code, message, options) {
|
|
8
|
+
super(message ?? code, options);
|
|
9
|
+
this.code = code;
|
|
10
|
+
Object.setPrototypeOf(this, _KitError.prototype);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// src/internal/encoding.ts
|
|
15
|
+
function base64UrlToBytes(input) {
|
|
16
|
+
const base64 = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
17
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
18
|
+
const binary = atob(padded);
|
|
19
|
+
const out = new Uint8Array(new ArrayBuffer(binary.length));
|
|
20
|
+
for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/ceremonies/random.ts
|
|
25
|
+
function randomBytes(length) {
|
|
26
|
+
const webcrypto = globalThis.crypto;
|
|
27
|
+
if (typeof webcrypto?.getRandomValues !== "function") {
|
|
28
|
+
throw new KitError(
|
|
29
|
+
"UNSUPPORTED_AUTHENTICATOR",
|
|
30
|
+
"no secure random source (globalThis.crypto.getRandomValues) is available in this environment"
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
const out = new Uint8Array(new ArrayBuffer(length));
|
|
34
|
+
webcrypto.getRandomValues(out);
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/ceremonies/browserClient.ts
|
|
39
|
+
function asKitError(error) {
|
|
40
|
+
if (error instanceof KitError) return error;
|
|
41
|
+
const name = typeof error === "object" && error !== null && "name" in error ? error.name : void 0;
|
|
42
|
+
if (name === "NotAllowedError") {
|
|
43
|
+
return new KitError("USER_CANCELLED", "The passkey prompt was dismissed.", { cause: error });
|
|
44
|
+
}
|
|
45
|
+
const message = error instanceof Error ? error.message : "WebAuthn request failed";
|
|
46
|
+
const detail = typeof name === "string" ? `${name}: ${message}` : message;
|
|
47
|
+
return new KitError("UNSUPPORTED_AUTHENTICATOR", detail, { cause: error });
|
|
48
|
+
}
|
|
49
|
+
function browserWebAuthnClient() {
|
|
50
|
+
const credentials = globalThis.navigator?.credentials;
|
|
51
|
+
if (!credentials) {
|
|
52
|
+
throw new KitError("UNSUPPORTED_AUTHENTICATOR", "navigator.credentials is unavailable");
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
async create(options) {
|
|
56
|
+
const publicKey = {
|
|
57
|
+
challenge: base64UrlToBytes(options.challenge),
|
|
58
|
+
rp: options.rp,
|
|
59
|
+
user: {
|
|
60
|
+
id: base64UrlToBytes(options.user.id),
|
|
61
|
+
name: options.user.name,
|
|
62
|
+
displayName: options.user.displayName
|
|
63
|
+
},
|
|
64
|
+
pubKeyCredParams: options.pubKeyCredParams,
|
|
65
|
+
authenticatorSelection: options.authenticatorSelection,
|
|
66
|
+
attestation: options.attestation,
|
|
67
|
+
timeout: options.timeout
|
|
68
|
+
};
|
|
69
|
+
let credential;
|
|
70
|
+
try {
|
|
71
|
+
credential = await credentials.create({ publicKey });
|
|
72
|
+
} catch (error) {
|
|
73
|
+
throw asKitError(error);
|
|
74
|
+
}
|
|
75
|
+
if (!credential) throw new KitError("USER_CANCELLED", "credential creation returned null");
|
|
76
|
+
const response = credential.response;
|
|
77
|
+
return {
|
|
78
|
+
id: credential.id,
|
|
79
|
+
rawId: new Uint8Array(credential.rawId),
|
|
80
|
+
attestationObject: new Uint8Array(response.attestationObject)
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
async get(options) {
|
|
84
|
+
const publicKey = {
|
|
85
|
+
rpId: options.rpId,
|
|
86
|
+
challenge: options.challenge ? new Uint8Array(options.challenge) : randomBytes(32),
|
|
87
|
+
allowCredentials: (options.allowCredentials ?? []).map((id) => ({
|
|
88
|
+
type: "public-key",
|
|
89
|
+
id: base64UrlToBytes(id)
|
|
90
|
+
})),
|
|
91
|
+
userVerification: options.userVerification
|
|
92
|
+
};
|
|
93
|
+
const request = { publicKey };
|
|
94
|
+
if (options.mediation) request.mediation = options.mediation;
|
|
95
|
+
let credential;
|
|
96
|
+
try {
|
|
97
|
+
credential = await credentials.get(request);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
throw asKitError(error);
|
|
100
|
+
}
|
|
101
|
+
if (!credential) throw new KitError("USER_CANCELLED", "assertion returned null");
|
|
102
|
+
const response = credential.response;
|
|
103
|
+
return {
|
|
104
|
+
id: credential.id,
|
|
105
|
+
rawId: new Uint8Array(credential.rawId),
|
|
106
|
+
authenticatorData: new Uint8Array(response.authenticatorData),
|
|
107
|
+
clientDataJSON: new Uint8Array(response.clientDataJSON),
|
|
108
|
+
signature: new Uint8Array(response.signature)
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function defaultCredentialStorage() {
|
|
114
|
+
const key = (rpId) => `stellar-passkey:${rpId}`;
|
|
115
|
+
const ls = globalThis.localStorage;
|
|
116
|
+
if (ls) {
|
|
117
|
+
return {
|
|
118
|
+
get: (rpId) => ls.getItem(key(rpId)),
|
|
119
|
+
set: (rpId, credentialId) => {
|
|
120
|
+
ls.setItem(key(rpId), credentialId);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const memory = /* @__PURE__ */ new Map();
|
|
125
|
+
return {
|
|
126
|
+
get: (rpId) => memory.get(rpId) ?? null,
|
|
127
|
+
set: (rpId, credentialId) => {
|
|
128
|
+
memory.set(rpId, credentialId);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/ceremonies/connect.ts
|
|
134
|
+
async function probeConditionalMediation() {
|
|
135
|
+
const pkc = globalThis.PublicKeyCredential;
|
|
136
|
+
if (!pkc?.isConditionalMediationAvailable) return false;
|
|
137
|
+
try {
|
|
138
|
+
return await pkc.isConditionalMediationAvailable();
|
|
139
|
+
} catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async function connect(options) {
|
|
144
|
+
const storage = options.storage ?? defaultCredentialStorage();
|
|
145
|
+
const credentialId = storage.get(options.rpId);
|
|
146
|
+
if (!credentialId) return null;
|
|
147
|
+
const silentSupported = options.silentMediationSupported ?? await probeConditionalMediation();
|
|
148
|
+
if (silentSupported) {
|
|
149
|
+
const webauthn = options.webauthn ?? browserWebAuthnClient();
|
|
150
|
+
try {
|
|
151
|
+
await webauthn.get({
|
|
152
|
+
rpId: options.rpId,
|
|
153
|
+
allowCredentials: [credentialId],
|
|
154
|
+
mediation: "silent"
|
|
155
|
+
});
|
|
156
|
+
} catch {
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const accounts = await options.indexer.resolveByCredential(credentialId);
|
|
160
|
+
const contractId = accounts[0]?.contractId;
|
|
161
|
+
return contractId ? { contractId, credentialId } : null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
exports.connect = connect;
|
|
165
|
+
//# sourceMappingURL=connect.cjs.map
|
|
166
|
+
//# sourceMappingURL=connect.cjs.map
|