Skip to content

Commit 7002acb

Browse files
committed
update readmes
1 parent 6918936 commit 7002acb

File tree

4 files changed

+202
-95
lines changed

4 files changed

+202
-95
lines changed
Lines changed: 37 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,58 @@
1-
# Swift Demo Wallet 
1+
# Swift Demo Wallet
22

3-
The Swift Demo Wallet is a sample iOS/macOS application that demonstrates how to build a simple wallet experience using Turnkey infrastructure. It showcases session handling, wallet creation/import, and transaction signing in a native SwiftUI application.
3+
A minimal iOS/macOS application demonstrating how to build an embedded wallet experience using Turnkey infrastructure and Auth Proxy.
44

5-
---
5+
## What this demo shows
66

7-
## Quick Start
7+
A high-level summary of the user experience and what you can see on screen:
88

9-
### 1. Clone the Repository
9+
- **Authentication**: Log in with passkeys, OTP (email/SMS), or OAuth (Google, Apple, Discord, X)
10+
- **Session Management**: Automatic session handling with secure key storage in Secure Enclave
11+
- **Wallet Operations**: Create, import, and export wallets with mnemonic phrases
12+
- **Message Signing**: Sign messages and raw payloads with wallet accounts
13+
- **User Management**: Update email/phone and view wallet details
1014

11-
```
15+
## Getting started
16+
17+
### 1/ Cloning the example
18+
19+
Make sure you have Xcode 15+ installed.
20+
21+
```bash
1222
git clone https://github.com/tkhq/swift-sdk
13-
cd Examples/swift-demo-wallet
23+
cd swift-sdk/Examples/swift-demo-wallet
1424
```
1525

16-
### 2. Open the Project
26+
### 2/ Setting up Turnkey
1727

18-
Open the `Examples/swift-demo-wallet` folder and build the project in Xcode.
28+
1. Set up your Turnkey organization and account. You'll need your **parent organization ID**.
29+
2. Enable **Auth Proxy** from your Turnkey dashboard:
30+
- Choose the user auth methods (Email OTP, SMS OTP, OAuth providers)
31+
- Configure redirect URLs for OAuth (if using)
32+
- Copy your **Auth Proxy Config ID** for the next step
33+
3. (Optional) For passkey authentication, set up your **RP ID** domain with associated domains
1934

20-
### 3. Configure Constants
35+
### 3/ Configure Constants
2136

22-
Edit `Helpers/Constants.swift` and fill in the required values:
37+
Edit `swift-demo-wallet/Helpers/Constants.swift` and add your values:
2338

2439
```swift
2540
enum Constants {
2641
enum App {
2742
static let appName = "Swift Demo Wallet App"
28-
static let rpId = "<your_rp_id>" // e.g. passkeyapp.tkhqlabs.xyz
29-
static let backendBaseUrl = "<your_backend_url>" // e.g. http://localhost:3000
43+
static let rpId = "<your_rp_id>" // required for passkeys
3044
}
3145

3246
enum Turnkey {
3347
static let organizationId = "<your_organization_id>"
34-
static let sessionDuration = "900" // session duration in seconds
3548
static let apiUrl = "https://api.turnkey.com"
49+
50+
// Auth Proxy Configuration
51+
static let authProxyUrl = "https://auth.turnkey.com"
52+
static let authProxyConfigId = "<your_auth_proxy_config_id>"
3653

54+
// Default accounts to create when using the "Create Wallet" button
55+
// Customize this array to create wallets with different curves, paths, or address formats
3756
static let defaultEthereumAccounts: [Components.Schemas.WalletAccountParams] = [
3857
Components.Schemas.WalletAccountParams(
3958
curve: .CURVE_SECP256K1,
@@ -51,65 +70,16 @@ enum Constants {
5170
}
5271
```
5372

54-
---
55-
56-
## Backend Setup
57-
58-
### Why Do We Need a Backend?
59-
60-
Turnkey requires authentication requests (sign-up/login) to be validated (stamped) using your root user API key-pair. Since this key-pair must remain private, it cannot be used directly in the frontend. Instead, authentication requests must be processed and stamped through a backend server before being forwarded to Turnkey.
61-
62-
### 1. Configure Environment Variables
63-
64-
Create a `.env` file inside the `example-server` folder:
65-
66-
```
67-
PORT="3000"
68-
69-
TURNKEY_API_URL="https://api.turnkey.com"
70-
TURNKEY_ORGANIZATION_ID="<your_turnkey_organization_id>"
71-
72-
TURNKEY_API_PUBLIC_KEY="<your_turnkey_api_public_key>"
73-
TURNKEY_API_PRIVATE_KEY="<your_turnkey_api_private_key>"
74-
```
75-
76-
### 2. Start the Server
73+
### 4/ Running the demo
7774

78-
```
79-
cd example-server
80-
npm install
81-
npm run start
82-
```
83-
84-
---
85-
86-
## Passkey Setup
87-
88-
To enable passkey authentication, you must configure your domain and app settings correctly:
89-
90-
### Associated Domains
91-
92-
1. In your app's `Signing & Capabilities` tab, add the `Associated Domains` capability.
93-
2. Add your domain:
94-
95-
```
96-
webcredentials:<your_rpid_domain>
97-
```
98-
99-
3. Host an `apple-app-site-association` file at `https://<your_rpid_domain>/.well-known/apple-app-site-association`
100-
101-
4. Ensure your `rpId` in Constants.swift matches the domain:
102-
103-
```swift
104-
static let rpId = "<your_rpid_domain>"
105-
```
75+
Open `swift-demo-wallet.xcodeproj` in Xcode and run the app on your device or simulator.
10676

10777
---
10878

10979
## Requirements
11080

111-
- iOS 17+ / macOS 13.0+
112-
- Swift 5.7+
81+
- iOS 17+ / macOS 14.0+
82+
- Swift 5.9+
11383
- Xcode 15+
11484

11585
---

Examples/swift-demo-wallet/swift-demo-wallet/Helpers/Constants.swift

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,17 @@ import TurnkeyTypes
55
enum Constants {
66

77
enum App {
8-
static let appName = "Swift Demo Wallet App"
9-
static let rpId = "passkeyapp.tkhqlabs.xyz"
8+
static let rpId = "<your_rp_id>"
109
static let scheme = "swift-demo-wallet"
11-
static let backendBaseUrl = "http://localhost:3000"
1210
}
1311

1412
enum Turnkey {
15-
static let organizationId = "cd473579-efee-4cb1-8a23-734bd1b4be31" // "7533b2e3-01f2-4573-98c3-2c8bee816cb6"
16-
static let sessionDuration = "900"
17-
static let apiUrl = "https://api.turnkey.com" // "http://localhost:8081"
18-
static let authProxyUrl = "https://authproxy.turnkey.com" // http://localhost:8090"
19-
static let authProxyConfigId = "544e423d-f5c9-4dfb-947e-8cf726e3922e" // 5889b4b6-ec95-42ca-8551-660e9d50ed09"
13+
static let organizationId = "<your_organization_id>"
14+
static let apiUrl = "https://api.turnkey.com"
15+
16+
static let authProxyUrl = "https://authproxy.turnkey.com"
17+
static let authProxyConfigId = "<your_auth_proxy_config_id>"
18+
2019
static let defaultEthereumAccounts: [v1WalletAccountParams] = [
2120
v1WalletAccountParams(
2221
addressFormat: v1AddressFormat.address_format_ethereum,
@@ -33,18 +32,18 @@ enum Constants {
3332
}
3433

3534
enum Google {
36-
static let clientId = "776352896366-07enngvt22l7cnq1ctf5a9ddcm1pv1sc.apps.googleusercontent.com"
35+
static let clientId = "<your_google_client_id>"
3736
}
3837

3938
enum Apple {
40-
static let clientId = "withreactnativewalletkit" // Fill with your Apple Services ID (client ID)
39+
static let clientId = "<your_apple_client_id>"
4140
}
4241

4342
enum X {
44-
static let clientId = "d1dFWkNfVk1kdG12SlUxZ3k3NG86MTpjaQ"
43+
static let clientId = "<your_x_client_id>"
4544
}
4645

4746
enum Discord {
48-
static let clientId = "1422294103890067536"
47+
static let clientId = "<your_discord_client_id>"
4948
}
5049
}

Sources/TurnkeyStamper/README.md

Lines changed: 145 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,173 @@
11
# TurnkeyStamper
22

3-
This Swift package provides a unified interface for signing payloads using either API keys or WebAuthn passkeys. It abstracts over the differences between raw keypair signing and passkey-based assertion, and provides a simple method to produce verifiable cryptographic stamps.
3+
This Swift package provides a unified interface for signing payloads using API keys, on-device keys (Secure Enclave or Keychain), or WebAuthn passkeys. It abstracts over the differences between various signing methods and provides a simple `stamp()` method to produce verifiable cryptographic stamps.
44

5-
It is designed to work seamlessly with Turnkeys backend APIs that expect either `X-Stamp` or `X-Stamp-WebAuthn` headers.
5+
It is designed to work seamlessly with Turnkey's backend APIs that expect either `X-Stamp` or `X-Stamp-WebAuthn` headers.
66

77
## Features
88

9-
* Supports both API key-based and WebAuthn passkey-based stamping.
10-
* Unified `stamp()` method returns the correct header name and value.
11-
* Uses P-256 ECDSA signatures (DER for API keys, WebAuthn-compliant for passkeys).
9+
* **API Key Signing**: Sign with raw P-256 keypairs
10+
* **On-Device Key Signing**: Sign with keys stored in Secure Enclave or Keychain
11+
* Automatic backend selection (prefers Secure Enclave when available)
12+
* Manual backend selection for specific requirements
13+
* **Passkey Signing**: WebAuthn-compliant passkey authentication
14+
* **Unified Interface**: Single `stamp()` method returns the correct header name and value
15+
* **Key Management**: Create, list, and delete on-device key pairs
1216

1317
---
1418

15-
## Requirements
19+
## Usage
1620

17-
* iOS 16.0+ / macOS 13.0+
18-
* Swift 5.7+
21+
### 1. API Key Signing
1922

20-
---
23+
Sign with a raw P-256 key pair (both public and private key provided):
2124

22-
## Usage
25+
```swift
26+
import TurnkeyStamper
27+
28+
let stamper = Stamper(apiPublicKey: "<public-key-hex>", apiPrivateKey: "<private-key-hex>")
29+
let (headerName, headerValue) = try await stamper.stamp(payload: jsonPayload)
30+
// headerName: "X-Stamp"
31+
```
2332

24-
### API Key Signing
33+
### 2. On-Device Key Signing
34+
35+
Sign with a key stored in Secure Enclave or Keychain (only public key needed):
2536

2637
```swift
2738
import TurnkeyStamper
2839

29-
let stamper = Stamper(apiPublicKey: "<public-key>", apiPrivateKey: "<private-key>")
40+
// Create a new on-device key pair
41+
let publicKey = try Stamper.createOnDeviceKeyPair()
42+
43+
// Sign with automatic backend selection (prefers Secure Enclave)
44+
let stamper = try Stamper(apiPublicKey: publicKey)
45+
let (headerName, headerValue) = try await stamper.stamp(payload: jsonPayload)
46+
// headerName: "X-Stamp"
3047
```
3148

32-
### Passkey Signing
49+
#### Manual Backend Selection
50+
51+
You can explicitly choose which backend to use:
52+
53+
```swift
54+
// Force Secure Enclave
55+
let stamper = try Stamper(apiPublicKey: publicKey, onDevicePreference: .secureEnclave)
56+
57+
// Force Secure Storage (Keychain)
58+
let stamper = try Stamper(apiPublicKey: publicKey, onDevicePreference: .secureStorage)
59+
60+
// Auto (default) - prefers Secure Enclave when available
61+
let stamper = try Stamper(apiPublicKey: publicKey, onDevicePreference: .auto)
62+
```
63+
64+
#### Key Management
65+
66+
```swift
67+
// Create a new key pair
68+
let publicKey = try Stamper.createOnDeviceKeyPair(preference: .auto)
69+
70+
// List existing key pairs
71+
let keys = try Stamper.listOnDeviceKeyPairs(preference: .auto)
72+
73+
// Delete a key pair
74+
try Stamper.deleteOnDeviceKeyPair(publicKeyHex: publicKey, preference: .auto)
75+
```
76+
77+
#### Advanced: Secure Enclave with Biometric Protection
78+
79+
For Secure Enclave, you can set an authentication policy at key creation time. The policy is embedded in the key and enforced by the hardware:
3380

3481
```swift
3582
import TurnkeyStamper
3683

37-
let stamper = Stamper(rpId: "your.site.com", presentationAnchor: anchor)
84+
// Create config with biometric requirement
85+
let config = SecureEnclaveStamper.SecureEnclaveConfig(authPolicy: .biometryAny)
86+
87+
// Create key with biometric protection
88+
let publicKey = try SecureEnclaveStamper.createKeyPair(config: config)
89+
90+
// All subsequent operations work normally - the biometric prompt happens automatically
91+
let stamp = try SecureEnclaveStamper.stamp(payload: jsonPayload, publicKeyHex: publicKey)
92+
// User is prompted for biometric authentication when signing
3893
```
3994

40-
The resulting header can be attached to any HTTP request for authenticated interaction with Turnkey services.
95+
**Note**: Unlike Secure Storage, Secure Enclave config is only used at key creation. Subsequent operations (list, stamp, delete) don't need the config because the auth policy is permanently embedded in the key by the hardware.
96+
97+
#### Advanced: Secure Storage with Custom Configuration
98+
99+
For Secure Storage (Keychain), you can customize storage attributes like access groups, iCloud sync, or biometric protection:
100+
101+
```swift
102+
import TurnkeyStamper
103+
104+
// Create custom configuration
105+
let config = SecureStorageStamper.SecureStorageConfig(
106+
accessibility: .afterFirstUnlockThisDeviceOnly,
107+
accessControlPolicy: .biometryAny, // Require biometric authentication
108+
authPrompt: "Authenticate to sign",
109+
biometryReuseWindowSeconds: 30,
110+
synchronizable: false, // Don't sync to iCloud
111+
accessGroup: "com.example.shared" // Share keys between apps
112+
)
113+
114+
// Create key with custom config
115+
let publicKey = try SecureStorageStamper.createKeyPair(config: config)
116+
117+
// IMPORTANT: All subsequent operations MUST use the same config
118+
let keys = try SecureStorageStamper.listKeyPairs(config: config)
119+
let stamp = try SecureStorageStamper.stamp(payload: jsonPayload, publicKeyHex: publicKey, config: config)
120+
try SecureStorageStamper.deleteKeyPair(publicKeyHex: publicKey, config: config)
121+
```
122+
123+
**Note**: Keychain queries must match how items were stored. If you create a key with custom config attributes (especially `accessGroup`, `synchronizable`, or `accessControlPolicy`), you must pass that same config to all subsequent operations (`listKeyPairs`, `stamp`, `deleteKeyPair`). If you use default settings, the no-config methods work fine.
124+
125+
### 3. Passkey Signing
126+
127+
Sign with WebAuthn passkeys:
128+
129+
```swift
130+
import TurnkeyStamper
131+
132+
let stamper = Stamper(rpId: "your.site.com", presentationAnchor: window)
133+
let (headerName, headerValue) = try await stamper.stamp(payload: jsonPayload)
134+
// headerName: "X-Stamp-WebAuthn"
135+
```
136+
137+
---
138+
139+
## Architecture
140+
141+
### Secure Enclave Stamper
142+
143+
The **Secure Enclave** is Apple's dedicated secure coprocessor:
144+
145+
* Private keys are generated and stored inside the Secure Enclave
146+
* Keys never leave the secure enclave - signing happens inside
147+
* Available on iPhone 5s and later, iPad Air and later, Macs with Apple Silicon or T2 chip
148+
* Metadata stored in iCloud Keychain for persistence
149+
150+
### Secure Storage Stamper
151+
152+
The **Secure Storage** stamper uses the device's Keychain:
153+
154+
* Private keys stored in local Keychain
155+
* Available after first device unlock (no biometric protection by default)
156+
* Used as fallback when Secure Enclave is unavailable
157+
* Works on all Apple devices
158+
159+
### Automatic Selection
160+
161+
When using `.auto` preference (default), the stamper:
162+
1. Checks if Secure Enclave is available
163+
2. Uses Secure Enclave if supported, otherwise falls back to Secure Storage
164+
165+
---
166+
167+
## Requirements
168+
169+
* iOS 17.0+ / macOS 14.0+
170+
* Swift 5.9+
41171

42172
---
43173

Sources/TurnkeySwift/README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,15 @@ Each session schedules a timer to automatically clear itself 5 seconds before JW
248248

249249
## Demo App
250250

251-
A sample SwiftUI demo app is included in the repository to showcase usage.
251+
A complete SwiftUI demo wallet app is included in the repository at [`Examples/swift-demo-wallet`](../../Examples/swift-demo-wallet). It showcases:
252+
253+
* Passkey and OTP authentication
254+
* Session management
255+
* Wallet creation and import
256+
* Transaction signing
257+
* User profile management
258+
259+
See the [demo app README](../../Examples/swift-demo-wallet/README.md) for setup instructions.
252260

253261
---
254262

0 commit comments

Comments
 (0)