@prism-ing/wallet 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/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +596 -0
- package/SPEC.md +192 -0
- package/dist/backends/squads-recovery-backend.d.ts +59 -0
- package/dist/backends/squads-recovery-backend.d.ts.map +1 -0
- package/dist/backends/squads-recovery-backend.js +81 -0
- package/dist/backends/squads-recovery-backend.js.map +1 -0
- package/dist/backends/squads-types.d.ts +74 -0
- package/dist/backends/squads-types.d.ts.map +1 -0
- package/dist/backends/squads-types.js +22 -0
- package/dist/backends/squads-types.js.map +1 -0
- package/dist/backends/zerodev-policy-mapper.d.ts +41 -0
- package/dist/backends/zerodev-policy-mapper.d.ts.map +1 -0
- package/dist/backends/zerodev-policy-mapper.js +127 -0
- package/dist/backends/zerodev-policy-mapper.js.map +1 -0
- package/dist/backends/zerodev-session-backend.d.ts +43 -0
- package/dist/backends/zerodev-session-backend.d.ts.map +1 -0
- package/dist/backends/zerodev-session-backend.js +63 -0
- package/dist/backends/zerodev-session-backend.js.map +1 -0
- package/dist/backends/zerodev-types.d.ts +104 -0
- package/dist/backends/zerodev-types.d.ts.map +1 -0
- package/dist/backends/zerodev-types.js +13 -0
- package/dist/backends/zerodev-types.js.map +1 -0
- package/dist/create-wallet.d.ts +89 -0
- package/dist/create-wallet.d.ts.map +1 -0
- package/dist/create-wallet.js +235 -0
- package/dist/create-wallet.js.map +1 -0
- package/dist/cross-chain.d.ts +64 -0
- package/dist/cross-chain.d.ts.map +1 -0
- package/dist/cross-chain.js +200 -0
- package/dist/cross-chain.js.map +1 -0
- package/dist/errors.d.ts +115 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +97 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/base58.d.ts +8 -0
- package/dist/internal/base58.d.ts.map +1 -0
- package/dist/internal/base58.js +34 -0
- package/dist/internal/base58.js.map +1 -0
- package/dist/internal/eip712.d.ts +41 -0
- package/dist/internal/eip712.d.ts.map +1 -0
- package/dist/internal/eip712.js +182 -0
- package/dist/internal/eip712.js.map +1 -0
- package/dist/internal/file-spend-persistence.d.ts +9 -0
- package/dist/internal/file-spend-persistence.d.ts.map +1 -0
- package/dist/internal/file-spend-persistence.js +58 -0
- package/dist/internal/file-spend-persistence.js.map +1 -0
- package/dist/internal/onebalance-client.d.ts +59 -0
- package/dist/internal/onebalance-client.d.ts.map +1 -0
- package/dist/internal/onebalance-client.js +2 -0
- package/dist/internal/onebalance-client.js.map +1 -0
- package/dist/internal/onebalance-http-client.d.ts +25 -0
- package/dist/internal/onebalance-http-client.d.ts.map +1 -0
- package/dist/internal/onebalance-http-client.js +161 -0
- package/dist/internal/onebalance-http-client.js.map +1 -0
- package/dist/internal/onebalance-types.d.ts +201 -0
- package/dist/internal/onebalance-types.d.ts.map +1 -0
- package/dist/internal/onebalance-types.js +39 -0
- package/dist/internal/onebalance-types.js.map +1 -0
- package/dist/internal/platform.d.ts +14 -0
- package/dist/internal/platform.d.ts.map +1 -0
- package/dist/internal/platform.js +22 -0
- package/dist/internal/platform.js.map +1 -0
- package/dist/internal/quote-verifier.d.ts +71 -0
- package/dist/internal/quote-verifier.d.ts.map +1 -0
- package/dist/internal/quote-verifier.js +172 -0
- package/dist/internal/quote-verifier.js.map +1 -0
- package/dist/internal/recovery-manager.d.ts +29 -0
- package/dist/internal/recovery-manager.d.ts.map +1 -0
- package/dist/internal/recovery-manager.js +161 -0
- package/dist/internal/recovery-manager.js.map +1 -0
- package/dist/result.d.ts +132 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +114 -0
- package/dist/result.js.map +1 -0
- package/dist/schemas.d.ts +184 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +76 -0
- package/dist/schemas.js.map +1 -0
- package/dist/session-keys.d.ts +53 -0
- package/dist/session-keys.d.ts.map +1 -0
- package/dist/session-keys.js +345 -0
- package/dist/session-keys.js.map +1 -0
- package/dist/signers/node-signing-backend.d.ts +11 -0
- package/dist/signers/node-signing-backend.d.ts.map +1 -0
- package/dist/signers/node-signing-backend.js +120 -0
- package/dist/signers/node-signing-backend.js.map +1 -0
- package/dist/signers/ows-adapter.d.ts +70 -0
- package/dist/signers/ows-adapter.d.ts.map +1 -0
- package/dist/signers/ows-adapter.js +53 -0
- package/dist/signers/ows-adapter.js.map +1 -0
- package/dist/signers/ows-signing-backend.d.ts +25 -0
- package/dist/signers/ows-signing-backend.d.ts.map +1 -0
- package/dist/signers/ows-signing-backend.js +192 -0
- package/dist/signers/ows-signing-backend.js.map +1 -0
- package/dist/signers/secure-enclave-backend.d.ts +19 -0
- package/dist/signers/secure-enclave-backend.d.ts.map +1 -0
- package/dist/signers/secure-enclave-backend.js +201 -0
- package/dist/signers/secure-enclave-backend.js.map +1 -0
- package/dist/signers/secure-enclave-types.d.ts +98 -0
- package/dist/signers/secure-enclave-types.d.ts.map +1 -0
- package/dist/signers/secure-enclave-types.js +12 -0
- package/dist/signers/secure-enclave-types.js.map +1 -0
- package/dist/types.d.ts +371 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OWS Signer Adapter — wraps platform-specific OWS APIs behind PrismSigner.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* On iOS, this calls the Swift bridge to the Secure Enclave.
|
|
6
|
+
* On Node.js, it uses OWS's software signing path.
|
|
7
|
+
* The interface is identical on all platforms.
|
|
8
|
+
*
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
import type { Address, Hex, Result } from '../result.js';
|
|
12
|
+
import type { PrismSigner, TypedDataPayload, Platform } from '../types.js';
|
|
13
|
+
import type { WalletError } from '../errors.js';
|
|
14
|
+
/**
|
|
15
|
+
* The OWS adapter implements PrismSigner for all platforms.
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* Created via the platform-specific factory. Never instantiate directly.
|
|
19
|
+
*/
|
|
20
|
+
export interface OWSAdapter extends PrismSigner {
|
|
21
|
+
/** The platform this adapter was created for. */
|
|
22
|
+
readonly platform: Platform;
|
|
23
|
+
/** Zero all key material held by the underlying backend. */
|
|
24
|
+
destroy?(): void;
|
|
25
|
+
}
|
|
26
|
+
/** Key material returned by OWS key generation. */
|
|
27
|
+
export interface OWSKeyPair {
|
|
28
|
+
readonly evmAddress: Address;
|
|
29
|
+
readonly solanaAddress: string;
|
|
30
|
+
readonly platform: Platform;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Platform-specific signing backend.
|
|
34
|
+
*
|
|
35
|
+
* @remarks
|
|
36
|
+
* Each platform provides its own implementation:
|
|
37
|
+
* - iOS: Swift bridge to Secure Enclave
|
|
38
|
+
* - Node: OWS CLI or npm package
|
|
39
|
+
* - Browser: Software signing fallback
|
|
40
|
+
*/
|
|
41
|
+
export interface SigningBackend {
|
|
42
|
+
generateKeyPair(): Promise<Result<OWSKeyPair, WalletError>>;
|
|
43
|
+
signMessage(keys: OWSKeyPair, message: Hex): Promise<Result<Hex, WalletError>>;
|
|
44
|
+
signTypedData(keys: OWSKeyPair, payload: TypedDataPayload): Promise<Result<Hex, WalletError>>;
|
|
45
|
+
signTransaction<T>(keys: OWSKeyPair, tx: T): Promise<Result<T, WalletError>>;
|
|
46
|
+
/**
|
|
47
|
+
* Execute a function within a single authentication context.
|
|
48
|
+
* On Secure Enclave, this means one biometric prompt for all
|
|
49
|
+
* signing operations within fn(). On other backends, passthrough.
|
|
50
|
+
*/
|
|
51
|
+
withAuthContext?<T>(prompt: string, fn: () => Promise<T>): Promise<Result<T, WalletError>>;
|
|
52
|
+
/** Zero all in-memory key material. No-op for hardware-backed backends. */
|
|
53
|
+
destroy?(): void;
|
|
54
|
+
/** Zero and remove a specific key pair by EVM address. */
|
|
55
|
+
removeKeyPair?(evmAddress: string): Result<void, WalletError>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Create an OWSAdapter from a platform-specific SigningBackend.
|
|
59
|
+
*
|
|
60
|
+
* @remarks
|
|
61
|
+
* Generates a key pair via the backend, then wraps all signing operations
|
|
62
|
+
* behind the PrismSigner interface. The adapter is the "imperative shell" —
|
|
63
|
+
* Result errors from the backend are unwrapped and thrown here, since
|
|
64
|
+
* PrismSigner methods return plain `Promise<Hex>`.
|
|
65
|
+
*
|
|
66
|
+
* @param backend - The platform-specific signing backend.
|
|
67
|
+
* @returns A Result containing the OWSAdapter or a WalletError.
|
|
68
|
+
*/
|
|
69
|
+
export declare function createOWSAdapter(backend: SigningBackend): Promise<Result<OWSAdapter, WalletError>>;
|
|
70
|
+
//# sourceMappingURL=ows-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ows-adapter.d.ts","sourceRoot":"","sources":["../../src/signers/ows-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;;;GAKG;AACH,MAAM,WAAW,UAAW,SAAQ,WAAW;IAC7C,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,4DAA4D;IAC5D,OAAO,CAAC,IAAI,IAAI,CAAC;CAClB;AAED,mDAAmD;AACnD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC;IAC5D,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC;IAC/E,aAAa,CACX,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC;IACrC,eAAe,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC;IAC7E;;;;OAIG;IACH,eAAe,CAAC,CAAC,CAAC,EAChB,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC;IACnC,2EAA2E;IAC3E,OAAO,CAAC,IAAI,IAAI,CAAC;IACjB,0DAA0D;IAC1D,aAAa,CAAC,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;CAC/D;AAMD;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC,CAwC1C"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Ok } from '../result.js';
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Factory
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
/**
|
|
6
|
+
* Create an OWSAdapter from a platform-specific SigningBackend.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* Generates a key pair via the backend, then wraps all signing operations
|
|
10
|
+
* behind the PrismSigner interface. The adapter is the "imperative shell" —
|
|
11
|
+
* Result errors from the backend are unwrapped and thrown here, since
|
|
12
|
+
* PrismSigner methods return plain `Promise<Hex>`.
|
|
13
|
+
*
|
|
14
|
+
* @param backend - The platform-specific signing backend.
|
|
15
|
+
* @returns A Result containing the OWSAdapter or a WalletError.
|
|
16
|
+
*/
|
|
17
|
+
export async function createOWSAdapter(backend) {
|
|
18
|
+
const keyResult = await backend.generateKeyPair();
|
|
19
|
+
if (!keyResult.ok)
|
|
20
|
+
return keyResult;
|
|
21
|
+
const keys = keyResult.value;
|
|
22
|
+
const adapter = {
|
|
23
|
+
evmAddress: keys.evmAddress,
|
|
24
|
+
solanaAddress: keys.solanaAddress,
|
|
25
|
+
platform: keys.platform,
|
|
26
|
+
async signMessage(message) {
|
|
27
|
+
const result = await backend.signMessage(keys, message);
|
|
28
|
+
if (!result.ok) {
|
|
29
|
+
throw new Error(`Signing failed: ${result.error.code}`);
|
|
30
|
+
}
|
|
31
|
+
return result.value;
|
|
32
|
+
},
|
|
33
|
+
async signTypedData(payload) {
|
|
34
|
+
const result = await backend.signTypedData(keys, payload);
|
|
35
|
+
if (!result.ok) {
|
|
36
|
+
throw new Error(`Signing failed: ${result.error.code}`);
|
|
37
|
+
}
|
|
38
|
+
return result.value;
|
|
39
|
+
},
|
|
40
|
+
async signTransaction(tx) {
|
|
41
|
+
const result = await backend.signTransaction(keys, tx);
|
|
42
|
+
if (!result.ok) {
|
|
43
|
+
throw new Error(`Signing failed: ${result.error.code}`);
|
|
44
|
+
}
|
|
45
|
+
return result.value;
|
|
46
|
+
},
|
|
47
|
+
destroy() {
|
|
48
|
+
backend.destroy?.();
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
return Ok(adapter);
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=ows-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ows-adapter.js","sourceRoot":"","sources":["../../src/signers/ows-adapter.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAwDlC,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAuB;IAEvB,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,eAAe,EAAE,CAAC;IAClD,IAAI,CAAC,SAAS,CAAC,EAAE;QAAE,OAAO,SAAS,CAAC;IACpC,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC;IAE7B,MAAM,OAAO,GAAe;QAC1B,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,QAAQ,EAAE,IAAI,CAAC,QAAQ;QAEvB,KAAK,CAAC,WAAW,CAAC,OAAY;YAC5B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACxD,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;gBACf,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YAC1D,CAAC;YACD,OAAO,MAAM,CAAC,KAAK,CAAC;QACtB,CAAC;QAED,KAAK,CAAC,aAAa,CAAC,OAAyB;YAC3C,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC1D,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;gBACf,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YAC1D,CAAC;YACD,OAAO,MAAM,CAAC,KAAK,CAAC;QACtB,CAAC;QAED,KAAK,CAAC,eAAe,CAAI,EAAK;YAC5B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACvD,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;gBACf,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YAC1D,CAAC;YACD,OAAO,MAAM,CAAC,KAAK,CAAC;QACtB,CAAC;QAED,OAAO;YACL,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;QACtB,CAAC;KACF,CAAC;IAEF,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { SigningBackend } from './ows-adapter.js';
|
|
2
|
+
/** Configuration for the OWS signing backend. */
|
|
3
|
+
export interface OWSBackendConfig {
|
|
4
|
+
/** OWS wallet name. Same name = same wallet across sessions. */
|
|
5
|
+
readonly walletName: string;
|
|
6
|
+
/** Owner passphrase for OWS vault. Omit to use OWS_PASSPHRASE env var or apiKey. */
|
|
7
|
+
readonly passphrase?: string;
|
|
8
|
+
/** Agent API key (ows_key_...). Enables policy-gated, scoped access. */
|
|
9
|
+
readonly apiKey?: string;
|
|
10
|
+
/** Custom vault path. Defaults to ~/.ows/. Useful for testing. */
|
|
11
|
+
readonly vaultPath?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create a persistent OWS signing backend.
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* On first call with a given wallet name, creates a new OWS wallet
|
|
18
|
+
* (BIP-39 mnemonic → secp256k1 + ed25519 accounts, encrypted at rest).
|
|
19
|
+
* On subsequent calls, loads the existing wallet — same keys, same addresses.
|
|
20
|
+
*
|
|
21
|
+
* @param config - Wallet name, auth credentials, and optional vault path.
|
|
22
|
+
* @returns A `SigningBackend` backed by OWS persistent storage.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createOWSSigningBackend(config: OWSBackendConfig): SigningBackend;
|
|
25
|
+
//# sourceMappingURL=ows-signing-backend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ows-signing-backend.d.ts","sourceRoot":"","sources":["../../src/signers/ows-signing-backend.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,kBAAkB,CAAC;AAOnE,iDAAiD;AACjD,MAAM,WAAW,gBAAgB;IAC/B,gEAAgE;IAChE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,oFAAoF;IACpF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,wEAAwE;IACxE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAeD;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,gBAAgB,GAAG,cAAc,CAwGhF"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OWS signing backend — persistent key storage via Open Wallet Standard.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Uses `@open-wallet-standard/core` (native NAPI bindings) for encrypted
|
|
6
|
+
* key persistence at `~/.ows/`. Same wallet name = same keys across sessions.
|
|
7
|
+
*
|
|
8
|
+
* Two access modes:
|
|
9
|
+
* - **Owner mode**: passphrase decrypts the vault (scrypt + AES-256-GCM)
|
|
10
|
+
* - **Agent mode**: API key token (`ows_key_...`) enables policy-gated signing
|
|
11
|
+
*
|
|
12
|
+
* All OWS functions are synchronous (Rust NAPI). This module wraps them
|
|
13
|
+
* in the async `SigningBackend` interface with `Result` error handling.
|
|
14
|
+
*
|
|
15
|
+
* @packageDocumentation
|
|
16
|
+
*/
|
|
17
|
+
import { createWallet as owsCreateWallet, getWallet as owsGetWallet, signMessage as owsSignMessage, signTypedData as owsSignTypedData, signTransaction as owsSignTransaction, } from '@open-wallet-standard/core';
|
|
18
|
+
import { Ok, Err, toAddress } from '../result.js';
|
|
19
|
+
import { walletErrors } from '../errors.js';
|
|
20
|
+
import { inferDomainType, isSolanaTransaction, hexToBytes } from '../internal/eip712.js';
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Constants
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
const EVM_CHAIN_ID = 'eip155:1';
|
|
25
|
+
const SOLANA_CHAIN_PREFIX = 'solana:';
|
|
26
|
+
const OWS_CHAIN_EVM = 'ethereum';
|
|
27
|
+
const OWS_CHAIN_SOLANA = 'solana';
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Factory
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
/**
|
|
32
|
+
* Create a persistent OWS signing backend.
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* On first call with a given wallet name, creates a new OWS wallet
|
|
36
|
+
* (BIP-39 mnemonic → secp256k1 + ed25519 accounts, encrypted at rest).
|
|
37
|
+
* On subsequent calls, loads the existing wallet — same keys, same addresses.
|
|
38
|
+
*
|
|
39
|
+
* @param config - Wallet name, auth credentials, and optional vault path.
|
|
40
|
+
* @returns A `SigningBackend` backed by OWS persistent storage.
|
|
41
|
+
*/
|
|
42
|
+
export function createOWSSigningBackend(config) {
|
|
43
|
+
const auth = resolveAuth(config);
|
|
44
|
+
const backend = {
|
|
45
|
+
async generateKeyPair() {
|
|
46
|
+
try {
|
|
47
|
+
const wallet = getOrCreateWallet(config);
|
|
48
|
+
return extractKeyPair(wallet);
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
return Err(mapOWSError(e));
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
async signMessage(_keys, message) {
|
|
55
|
+
try {
|
|
56
|
+
// Strip 0x prefix — OWS expects raw hex
|
|
57
|
+
const hexMsg = message.startsWith('0x')
|
|
58
|
+
? message.slice(2)
|
|
59
|
+
: message;
|
|
60
|
+
const result = owsSignMessage(config.walletName, OWS_CHAIN_EVM, hexMsg, auth, 'hex', null, config.vaultPath ?? null);
|
|
61
|
+
return Ok(formatEvmSignature(result.signature, result.recoveryId));
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
return Err(mapOWSError(e));
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
async signTypedData(_keys, payload) {
|
|
68
|
+
try {
|
|
69
|
+
// OWS requires EIP712Domain in the types object
|
|
70
|
+
const typedDataWithDomain = {
|
|
71
|
+
...payload,
|
|
72
|
+
types: {
|
|
73
|
+
...payload.types,
|
|
74
|
+
EIP712Domain: inferDomainType(payload.domain),
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
const result = owsSignTypedData(config.walletName, OWS_CHAIN_EVM, JSON.stringify(typedDataWithDomain), auth, null, config.vaultPath ?? null);
|
|
78
|
+
return Ok(formatEvmSignature(result.signature, result.recoveryId));
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
return Err(mapOWSError(e));
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
async signTransaction(_keys, tx) {
|
|
85
|
+
try {
|
|
86
|
+
if (!isSolanaTransaction(tx)) {
|
|
87
|
+
return Err(walletErrors.signingRejected('Unsupported transaction type'));
|
|
88
|
+
}
|
|
89
|
+
const messageBytes = tx.message.serialize();
|
|
90
|
+
const messageHex = bytesToHex(messageBytes);
|
|
91
|
+
const result = owsSignTransaction(config.walletName, OWS_CHAIN_SOLANA, messageHex, auth, null, config.vaultPath ?? null);
|
|
92
|
+
// OWS returns hex-encoded ed25519 signature (128 hex chars = 64 bytes)
|
|
93
|
+
tx.signatures[0] = hexToBytes(result.signature);
|
|
94
|
+
return Ok(tx);
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
return Err(mapOWSError(e));
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
async withAuthContext(_prompt, fn) {
|
|
101
|
+
return Ok(await fn());
|
|
102
|
+
},
|
|
103
|
+
destroy() {
|
|
104
|
+
// OWS keys live in encrypted vault on disk, not in JS memory.
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
return backend;
|
|
108
|
+
}
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Wallet Management
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
/**
|
|
113
|
+
* Get an existing OWS wallet or create a new one.
|
|
114
|
+
* This is the persistence mechanism — same name = same wallet.
|
|
115
|
+
*/
|
|
116
|
+
function getOrCreateWallet(config) {
|
|
117
|
+
try {
|
|
118
|
+
return owsGetWallet(config.walletName, config.vaultPath ?? null);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// Wallet doesn't exist yet — create it
|
|
122
|
+
const auth = resolveAuth(config);
|
|
123
|
+
return owsCreateWallet(config.walletName, auth, 12, // 12-word mnemonic
|
|
124
|
+
config.vaultPath ?? null);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Extract EVM and Solana addresses from OWS wallet accounts.
|
|
129
|
+
*/
|
|
130
|
+
function extractKeyPair(wallet) {
|
|
131
|
+
const evmAccount = wallet.accounts.find((a) => a.chainId === EVM_CHAIN_ID);
|
|
132
|
+
const solanaAccount = wallet.accounts.find((a) => a.chainId.startsWith(SOLANA_CHAIN_PREFIX));
|
|
133
|
+
if (!evmAccount) {
|
|
134
|
+
return Err(walletErrors.keyNotFound('No EVM account in OWS wallet'));
|
|
135
|
+
}
|
|
136
|
+
const evmAddressResult = toAddress(evmAccount.address);
|
|
137
|
+
if (!evmAddressResult.ok) {
|
|
138
|
+
return Err(walletErrors.keyNotFound(evmAccount.address));
|
|
139
|
+
}
|
|
140
|
+
return Ok({
|
|
141
|
+
evmAddress: evmAddressResult.value,
|
|
142
|
+
solanaAddress: solanaAccount?.address ?? '',
|
|
143
|
+
platform: 'node',
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// Auth Resolution
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
function resolveAuth(config) {
|
|
150
|
+
return config.apiKey ?? config.passphrase ?? process.env.OWS_PASSPHRASE ?? null;
|
|
151
|
+
}
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// Signature Formatting
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
/**
|
|
156
|
+
* Format an OWS EVM signature as a 0x-prefixed 65-byte hex string.
|
|
157
|
+
*
|
|
158
|
+
* @remarks
|
|
159
|
+
* OWS returns the signature as a hex string (no 0x prefix) with v already
|
|
160
|
+
* encoded as the last byte. The recoveryId (27 or 28) confirms this.
|
|
161
|
+
*/
|
|
162
|
+
function formatEvmSignature(sigHex, _recoveryId) {
|
|
163
|
+
// OWS signature is already r + s + v (65 bytes = 130 hex chars)
|
|
164
|
+
return `0x${sigHex}`;
|
|
165
|
+
}
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// Hex Utilities
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
function bytesToHex(bytes) {
|
|
170
|
+
return Array.from(bytes)
|
|
171
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
172
|
+
.join('');
|
|
173
|
+
}
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// Error Mapping
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
/**
|
|
178
|
+
* Map OWS errors to WalletError.
|
|
179
|
+
* OWS throws plain Error objects with descriptive messages.
|
|
180
|
+
*/
|
|
181
|
+
function mapOWSError(e) {
|
|
182
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
183
|
+
const lower = msg.toLowerCase();
|
|
184
|
+
if (lower.includes('not found') || lower.includes('no such')) {
|
|
185
|
+
return walletErrors.keyNotFound(msg);
|
|
186
|
+
}
|
|
187
|
+
if (lower.includes('passphrase') || lower.includes('authentication') || lower.includes('decrypt')) {
|
|
188
|
+
return walletErrors.signingRejected(msg);
|
|
189
|
+
}
|
|
190
|
+
return walletErrors.signingRejected(`OWS error: ${msg}`);
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=ows-signing-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ows-signing-backend.js","sourceRoot":"","sources":["../../src/signers/ows-signing-backend.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SigningBackend } from './ows-adapter.js';
|
|
2
|
+
import type { SecureEnclaveConfig } from './secure-enclave-types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Create an iOS Secure Enclave signing backend.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* Uses the iOS Secure Enclave for hardware-backed key protection.
|
|
8
|
+
* Private keys are encrypted by the Secure Enclave's P-256 key and stored
|
|
9
|
+
* in the iOS Keychain. All signing operations require biometric authentication.
|
|
10
|
+
*
|
|
11
|
+
* The backend delegates all cryptographic operations to the native bridge.
|
|
12
|
+
* TypeScript handles EIP-191/EIP-712 hash computation before passing the
|
|
13
|
+
* hash to the native layer for signing.
|
|
14
|
+
*
|
|
15
|
+
* @param config - Secure Enclave configuration including the native bridge.
|
|
16
|
+
* @returns A {@link SigningBackend} backed by the iOS Secure Enclave.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createSecureEnclaveBackend(config: SecureEnclaveConfig): SigningBackend;
|
|
19
|
+
//# sourceMappingURL=secure-enclave-backend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secure-enclave-backend.d.ts","sourceRoot":"","sources":["../../src/signers/secure-enclave-backend.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,kBAAkB,CAAC;AACnE,OAAO,KAAK,EAAE,mBAAmB,EAA6B,MAAM,2BAA2B,CAAC;AAmFhG;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,mBAAmB,GAC1B,cAAc,CA6IhB"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* iOS Secure Enclave signing backend.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Implements the {@link SigningBackend} interface using the iOS Secure Enclave
|
|
6
|
+
* for hardware-backed key protection. Private keys (secp256k1 for EVM,
|
|
7
|
+
* ed25519 for Solana) are encrypted by the Secure Enclave's P-256 key and
|
|
8
|
+
* stored in the iOS Keychain. Signing requires biometric authentication
|
|
9
|
+
* (Face ID / Touch ID) before the key is decrypted in-process.
|
|
10
|
+
*
|
|
11
|
+
* The TypeScript layer handles EVM address derivation, EIP-191/EIP-712
|
|
12
|
+
* hashing, and signature encoding. The native bridge handles key storage,
|
|
13
|
+
* encryption/decryption, and biometric gating.
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
import { keccak_256 } from '@noble/hashes/sha3';
|
|
18
|
+
import { bytesToHex } from '@noble/hashes/utils';
|
|
19
|
+
import { Ok, Err, fromPromise } from '../result.js';
|
|
20
|
+
import { walletErrors } from '../errors.js';
|
|
21
|
+
import { base58Encode } from '../internal/base58.js';
|
|
22
|
+
import { hashTypedData, personalSignHash, isSolanaTransaction } from '../internal/eip712.js';
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Constants
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
const DEFAULT_KEY_TAG = 'prism-wallet-default';
|
|
27
|
+
const DEFAULT_BIOMETRIC_PROMPT = 'Authorize transaction';
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// EVM Helpers
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
/** Derive an EVM address from an uncompressed secp256k1 public key. */
|
|
32
|
+
function deriveEvmAddress(publicKey) {
|
|
33
|
+
// Drop the 0x04 prefix byte if present, hash the remaining 64 bytes
|
|
34
|
+
const keyBytes = publicKey.length === 65 ? publicKey.subarray(1) : publicKey;
|
|
35
|
+
const hash = keccak_256(keyBytes);
|
|
36
|
+
return `0x${bytesToHex(hash.subarray(12))}`;
|
|
37
|
+
}
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Signature Encoding
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
/**
|
|
42
|
+
* Encode a raw secp256k1 signature (64 bytes: r || s) with recovery ID
|
|
43
|
+
* into the standard 65-byte Ethereum format (r || s || v).
|
|
44
|
+
*
|
|
45
|
+
* @remarks
|
|
46
|
+
* The native bridge returns a 65-byte signature with recovery ID appended,
|
|
47
|
+
* matching the format from `secp256k1.sign()`.
|
|
48
|
+
*/
|
|
49
|
+
function encodeSignature(rawSig) {
|
|
50
|
+
// rawSig is expected to be 65 bytes: r(32) || s(32) || v(1)
|
|
51
|
+
return `0x${bytesToHex(rawSig)}`;
|
|
52
|
+
}
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Error Mapping
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
function mapNativeError(error) {
|
|
57
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
58
|
+
// Biometric cancellation / failure patterns from iOS
|
|
59
|
+
const biometricPatterns = [
|
|
60
|
+
'user cancel',
|
|
61
|
+
'biometric',
|
|
62
|
+
'authentication failed',
|
|
63
|
+
'passcode',
|
|
64
|
+
'LAError',
|
|
65
|
+
];
|
|
66
|
+
if (biometricPatterns.some((p) => message.toLowerCase().includes(p))) {
|
|
67
|
+
return walletErrors.signingRejected(message);
|
|
68
|
+
}
|
|
69
|
+
// Secure Enclave unavailability
|
|
70
|
+
const enclavePatterns = [
|
|
71
|
+
'secure enclave',
|
|
72
|
+
'not available',
|
|
73
|
+
'no hardware',
|
|
74
|
+
'errSecUnimplemented',
|
|
75
|
+
];
|
|
76
|
+
if (enclavePatterns.some((p) => message.toLowerCase().includes(p))) {
|
|
77
|
+
return walletErrors.enclaveUnavailable('ios');
|
|
78
|
+
}
|
|
79
|
+
// Key not found
|
|
80
|
+
if (message.toLowerCase().includes('key not found') || message.includes('errSecItemNotFound')) {
|
|
81
|
+
return walletErrors.keyNotFound('secure-enclave');
|
|
82
|
+
}
|
|
83
|
+
// Default: signing rejected
|
|
84
|
+
return walletErrors.signingRejected(message);
|
|
85
|
+
}
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// Factory
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
/**
|
|
90
|
+
* Create an iOS Secure Enclave signing backend.
|
|
91
|
+
*
|
|
92
|
+
* @remarks
|
|
93
|
+
* Uses the iOS Secure Enclave for hardware-backed key protection.
|
|
94
|
+
* Private keys are encrypted by the Secure Enclave's P-256 key and stored
|
|
95
|
+
* in the iOS Keychain. All signing operations require biometric authentication.
|
|
96
|
+
*
|
|
97
|
+
* The backend delegates all cryptographic operations to the native bridge.
|
|
98
|
+
* TypeScript handles EIP-191/EIP-712 hash computation before passing the
|
|
99
|
+
* hash to the native layer for signing.
|
|
100
|
+
*
|
|
101
|
+
* @param config - Secure Enclave configuration including the native bridge.
|
|
102
|
+
* @returns A {@link SigningBackend} backed by the iOS Secure Enclave.
|
|
103
|
+
*/
|
|
104
|
+
export function createSecureEnclaveBackend(config) {
|
|
105
|
+
const bridge = config.nativeBridge;
|
|
106
|
+
const keyTag = config.keyTag ?? DEFAULT_KEY_TAG;
|
|
107
|
+
const biometricPrompt = config.biometricPrompt ?? DEFAULT_BIOMETRIC_PROMPT;
|
|
108
|
+
let storedKeyPair;
|
|
109
|
+
const backend = {
|
|
110
|
+
async generateKeyPair() {
|
|
111
|
+
// Check if key already exists (idempotent)
|
|
112
|
+
const existsResult = await fromPromise(bridge.keyExists(keyTag), (e) => mapNativeError(e));
|
|
113
|
+
if (!existsResult.ok)
|
|
114
|
+
return existsResult;
|
|
115
|
+
if (existsResult.value && storedKeyPair !== undefined) {
|
|
116
|
+
return Ok(storedKeyPair);
|
|
117
|
+
}
|
|
118
|
+
// Generate or retrieve key pair via native bridge
|
|
119
|
+
const genResult = await fromPromise(bridge.generateKey(keyTag), (e) => mapNativeError(e));
|
|
120
|
+
if (!genResult.ok)
|
|
121
|
+
return genResult;
|
|
122
|
+
const { evmPublicKey, solanaPublicKey } = genResult.value;
|
|
123
|
+
const evmAddress = deriveEvmAddress(evmPublicKey);
|
|
124
|
+
const solanaAddress = base58Encode(solanaPublicKey);
|
|
125
|
+
const keyPair = {
|
|
126
|
+
evmAddress,
|
|
127
|
+
solanaAddress,
|
|
128
|
+
platform: 'ios',
|
|
129
|
+
};
|
|
130
|
+
storedKeyPair = keyPair;
|
|
131
|
+
return Ok(keyPair);
|
|
132
|
+
},
|
|
133
|
+
async signMessage(keys, message) {
|
|
134
|
+
if (storedKeyPair === undefined || keys.evmAddress !== storedKeyPair.evmAddress) {
|
|
135
|
+
return Err(walletErrors.keyNotFound(keys.evmAddress));
|
|
136
|
+
}
|
|
137
|
+
// Compute EIP-191 hash, then sign via native bridge
|
|
138
|
+
const hash = personalSignHash(message);
|
|
139
|
+
const signResult = await fromPromise(bridge.sign(keyTag, hash, biometricPrompt), (e) => mapNativeError(e));
|
|
140
|
+
if (!signResult.ok)
|
|
141
|
+
return signResult;
|
|
142
|
+
return Ok(encodeSignature(signResult.value.signature));
|
|
143
|
+
},
|
|
144
|
+
async signTypedData(keys, payload) {
|
|
145
|
+
if (storedKeyPair === undefined || keys.evmAddress !== storedKeyPair.evmAddress) {
|
|
146
|
+
return Err(walletErrors.keyNotFound(keys.evmAddress));
|
|
147
|
+
}
|
|
148
|
+
// Compute EIP-712 hash, then sign via native bridge
|
|
149
|
+
const hash = hashTypedData(payload);
|
|
150
|
+
const signResult = await fromPromise(bridge.sign(keyTag, hash, biometricPrompt), (e) => mapNativeError(e));
|
|
151
|
+
if (!signResult.ok)
|
|
152
|
+
return signResult;
|
|
153
|
+
return Ok(encodeSignature(signResult.value.signature));
|
|
154
|
+
},
|
|
155
|
+
async signTransaction(keys, tx) {
|
|
156
|
+
if (storedKeyPair === undefined || keys.evmAddress !== storedKeyPair.evmAddress) {
|
|
157
|
+
return Err(walletErrors.keyNotFound(keys.evmAddress));
|
|
158
|
+
}
|
|
159
|
+
if (!isSolanaTransaction(tx)) {
|
|
160
|
+
return Err(walletErrors.signingRejected('Unsupported transaction type'));
|
|
161
|
+
}
|
|
162
|
+
const messageBytes = tx.message.serialize();
|
|
163
|
+
const signResult = await fromPromise(bridge.signSolana(keyTag, messageBytes, biometricPrompt), (e) => mapNativeError(e));
|
|
164
|
+
if (!signResult.ok)
|
|
165
|
+
return signResult;
|
|
166
|
+
tx.signatures[0] = signResult.value.signature;
|
|
167
|
+
return Ok(tx);
|
|
168
|
+
},
|
|
169
|
+
async withAuthContext(prompt, fn) {
|
|
170
|
+
if (bridge.beginAuthContext === undefined || bridge.endAuthContext === undefined) {
|
|
171
|
+
// Bridge doesn't support auth context — passthrough
|
|
172
|
+
try {
|
|
173
|
+
return Ok(await fn());
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
return Err(mapNativeError(e));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const contextResult = await fromPromise(bridge.beginAuthContext(prompt), (e) => mapNativeError(e));
|
|
180
|
+
if (!contextResult.ok)
|
|
181
|
+
return contextResult;
|
|
182
|
+
try {
|
|
183
|
+
const result = await fn();
|
|
184
|
+
return Ok(result);
|
|
185
|
+
}
|
|
186
|
+
catch (e) {
|
|
187
|
+
return Err(mapNativeError(e));
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
await bridge.endAuthContext(contextResult.value.contextId).catch(() => {
|
|
191
|
+
// Best-effort cleanup — context will expire naturally
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
destroy() {
|
|
196
|
+
storedKeyPair = undefined;
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
return backend;
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=secure-enclave-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secure-enclave-backend.js","sourceRoot":"","sources":["../../src/signers/secure-enclave-backend.ts"],"names":[],"mappings":"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"}
|