@burnt-labs/expo-satya-attest 3.7.1 → 3.8.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.
Files changed (178) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/PROVIDER_CONFIG.md +5 -0
  3. package/PUBLISHING.md +30 -0
  4. package/README.md +40 -10
  5. package/TEE_REPLAY.md +231 -0
  6. package/android/build.gradle +2 -2
  7. package/android/src/main/java/expo/modules/satyaattest/SatyaAttestModule.kt +19 -2
  8. package/android/src/main/jniLibs/arm64-v8a/libsatya_ffi.so +0 -0
  9. package/android/src/main/jniLibs/armeabi-v7a/libsatya_ffi.so +0 -0
  10. package/android/src/main/jniLibs/x86_64/libsatya_ffi.so +0 -0
  11. package/build/SatyaAttestModule.d.ts +7 -0
  12. package/build/SatyaAttestModule.d.ts.map +1 -1
  13. package/build/SatyaAttestModule.js.map +1 -1
  14. package/build/SatyaAttestModule.web.d.ts +1 -0
  15. package/build/SatyaAttestModule.web.d.ts.map +1 -1
  16. package/build/SatyaAttestModule.web.js +3 -0
  17. package/build/SatyaAttestModule.web.js.map +1 -1
  18. package/build/SatyaMobileClient.d.ts +10 -2
  19. package/build/SatyaMobileClient.d.ts.map +1 -1
  20. package/build/SatyaMobileClient.js +105 -2
  21. package/build/SatyaMobileClient.js.map +1 -1
  22. package/build/SatyaProviderAttestButton.d.ts +2 -1
  23. package/build/SatyaProviderAttestButton.d.ts.map +1 -1
  24. package/build/SatyaProviderAttestButton.js +23 -3
  25. package/build/SatyaProviderAttestButton.js.map +1 -1
  26. package/build/components/providerAttest/flowSelection.d.ts +18 -0
  27. package/build/components/providerAttest/flowSelection.d.ts.map +1 -1
  28. package/build/components/providerAttest/flowSelection.js +37 -1
  29. package/build/components/providerAttest/flowSelection.js.map +1 -1
  30. package/build/components/providerAttest/providerResolution.d.ts.map +1 -1
  31. package/build/components/providerAttest/providerResolution.js +18 -1
  32. package/build/components/providerAttest/providerResolution.js.map +1 -1
  33. package/build/components/providerAttest/replayRewrite.d.ts +2 -0
  34. package/build/components/providerAttest/replayRewrite.d.ts.map +1 -1
  35. package/build/components/providerAttest/replayRewrite.js +9 -0
  36. package/build/components/providerAttest/replayRewrite.js.map +1 -1
  37. package/build/components/providerAttest/tee/teeResult.d.ts +6 -0
  38. package/build/components/providerAttest/tee/teeResult.d.ts.map +1 -0
  39. package/build/components/providerAttest/tee/teeResult.js +27 -0
  40. package/build/components/providerAttest/tee/teeResult.js.map +1 -0
  41. package/build/components/providerAttest/tee/useTeeReplaySwap.d.ts +53 -0
  42. package/build/components/providerAttest/tee/useTeeReplaySwap.d.ts.map +1 -0
  43. package/build/components/providerAttest/tee/useTeeReplaySwap.js +421 -0
  44. package/build/components/providerAttest/tee/useTeeReplaySwap.js.map +1 -0
  45. package/build/components/providerAttest/types.d.ts +11 -1
  46. package/build/components/providerAttest/types.d.ts.map +1 -1
  47. package/build/components/providerAttest/types.js.map +1 -1
  48. package/build/components/providerAttest/useFunnelEmitter.d.ts +2 -7
  49. package/build/components/providerAttest/useFunnelEmitter.d.ts.map +1 -1
  50. package/build/components/providerAttest/useFunnelEmitter.js +9 -9
  51. package/build/components/providerAttest/useFunnelEmitter.js.map +1 -1
  52. package/build/components/providerAttest/useProviderAttestFlow.d.ts +2 -2
  53. package/build/components/providerAttest/useProviderAttestFlow.d.ts.map +1 -1
  54. package/build/components/providerAttest/useProviderAttestFlow.js +120 -8
  55. package/build/components/providerAttest/useProviderAttestFlow.js.map +1 -1
  56. package/build/components/providerAttest/useProviderFlowLifecycle.d.ts +4 -2
  57. package/build/components/providerAttest/useProviderFlowLifecycle.d.ts.map +1 -1
  58. package/build/components/providerAttest/useProviderFlowLifecycle.js +14 -4
  59. package/build/components/providerAttest/useProviderFlowLifecycle.js.map +1 -1
  60. package/build/index.d.ts +4 -1
  61. package/build/index.d.ts.map +1 -1
  62. package/build/index.js +5 -0
  63. package/build/index.js.map +1 -1
  64. package/build/providerRegistry/template.d.ts.map +1 -1
  65. package/build/providerRegistry/template.js +6 -3
  66. package/build/providerRegistry/template.js.map +1 -1
  67. package/build/satyaConfig.d.ts +21 -0
  68. package/build/satyaConfig.d.ts.map +1 -1
  69. package/build/satyaConfig.js +80 -1
  70. package/build/satyaConfig.js.map +1 -1
  71. package/build/sdkEventLog.d.ts +2 -0
  72. package/build/sdkEventLog.d.ts.map +1 -1
  73. package/build/sdkEventLog.js +21 -0
  74. package/build/sdkEventLog.js.map +1 -1
  75. package/build/sdkMetadata.generated.d.ts +1 -1
  76. package/build/sdkMetadata.generated.js +1 -1
  77. package/build/sdkMetadata.generated.js.map +1 -1
  78. package/build/tee/claims.d.ts +10 -0
  79. package/build/tee/claims.d.ts.map +1 -0
  80. package/build/tee/claims.js +54 -0
  81. package/build/tee/claims.js.map +1 -0
  82. package/build/tee/client.d.ts +112 -0
  83. package/build/tee/client.d.ts.map +1 -0
  84. package/build/tee/client.js +514 -0
  85. package/build/tee/client.js.map +1 -0
  86. package/build/tee/cookies.d.ts +3 -0
  87. package/build/tee/cookies.d.ts.map +1 -0
  88. package/build/tee/cookies.js +14 -0
  89. package/build/tee/cookies.js.map +1 -0
  90. package/build/tee/crypto.d.ts +19 -0
  91. package/build/tee/crypto.d.ts.map +1 -0
  92. package/build/tee/crypto.js +111 -0
  93. package/build/tee/crypto.js.map +1 -0
  94. package/build/tee/errors.d.ts +98 -0
  95. package/build/tee/errors.d.ts.map +1 -0
  96. package/build/tee/errors.js +149 -0
  97. package/build/tee/errors.js.map +1 -0
  98. package/build/tee/index.d.ts +11 -0
  99. package/build/tee/index.d.ts.map +1 -0
  100. package/build/tee/index.js +7 -0
  101. package/build/tee/index.js.map +1 -0
  102. package/build/tee/mobileRelay.d.ts +35 -0
  103. package/build/tee/mobileRelay.d.ts.map +1 -0
  104. package/build/tee/mobileRelay.js +353 -0
  105. package/build/tee/mobileRelay.js.map +1 -0
  106. package/build/tee/quoteBinding.d.ts +26 -0
  107. package/build/tee/quoteBinding.d.ts.map +1 -0
  108. package/build/tee/quoteBinding.js +159 -0
  109. package/build/tee/quoteBinding.js.map +1 -0
  110. package/build/tee/relayGuard.d.ts +20 -0
  111. package/build/tee/relayGuard.d.ts.map +1 -0
  112. package/build/tee/relayGuard.js +55 -0
  113. package/build/tee/relayGuard.js.map +1 -0
  114. package/build/tee/secureRandom.d.ts +24 -0
  115. package/build/tee/secureRandom.d.ts.map +1 -0
  116. package/build/tee/secureRandom.js +103 -0
  117. package/build/tee/secureRandom.js.map +1 -0
  118. package/build/tee/telemetry.d.ts +16 -0
  119. package/build/tee/telemetry.d.ts.map +1 -0
  120. package/build/tee/telemetry.js +75 -0
  121. package/build/tee/telemetry.js.map +1 -0
  122. package/build/tee/types.d.ts +85 -0
  123. package/build/tee/types.d.ts.map +1 -0
  124. package/build/tee/types.js +3 -0
  125. package/build/tee/types.js.map +1 -0
  126. package/build/tee-entry.d.ts +9 -0
  127. package/build/tee-entry.d.ts.map +1 -0
  128. package/build/tee-entry.js +19 -0
  129. package/build/tee-entry.js.map +1 -0
  130. package/build/types/error.types.d.ts +16 -1
  131. package/build/types/error.types.d.ts.map +1 -1
  132. package/build/types/error.types.js +39 -0
  133. package/build/types/error.types.js.map +1 -1
  134. package/build/types/mobile.types.d.ts +2 -0
  135. package/build/types/mobile.types.d.ts.map +1 -1
  136. package/build/types/mobile.types.js.map +1 -1
  137. package/ios/Frameworks/libsatya_ffi-rs.xcframework/ios-arm64/libsatya_ffi.a +0 -0
  138. package/ios/Frameworks/libsatya_ffi-rs.xcframework/ios-arm64_x86_64-simulator/libsatya_ffi.a +0 -0
  139. package/ios/SatyaAttest.podspec +1 -1
  140. package/ios/SatyaAttestModule.swift +245 -192
  141. package/ios/SatyaCompositeReplay.swift +26 -15
  142. package/ios/SatyaWebViewCookies.swift +86 -0
  143. package/package.json +12 -5
  144. package/scripts/build-rust-android.sh +1 -1
  145. package/src/SatyaAttestModule.ts +7 -0
  146. package/src/SatyaAttestModule.web.ts +3 -0
  147. package/src/SatyaMobileClient.ts +180 -3
  148. package/src/SatyaProviderAttestButton.tsx +42 -6
  149. package/src/components/providerAttest/flowSelection.ts +55 -2
  150. package/src/components/providerAttest/providerResolution.ts +18 -1
  151. package/src/components/providerAttest/replayRewrite.ts +17 -0
  152. package/src/components/providerAttest/tee/teeResult.ts +36 -0
  153. package/src/components/providerAttest/tee/useTeeReplaySwap.ts +541 -0
  154. package/src/components/providerAttest/types.ts +24 -0
  155. package/src/components/providerAttest/useFunnelEmitter.ts +32 -29
  156. package/src/components/providerAttest/useProviderAttestFlow.ts +190 -41
  157. package/src/components/providerAttest/useProviderFlowLifecycle.ts +18 -3
  158. package/src/index.ts +31 -0
  159. package/src/providerRegistry/template.ts +8 -3
  160. package/src/satyaConfig.ts +122 -1
  161. package/src/sdkEventLog.ts +24 -0
  162. package/src/sdkMetadata.generated.ts +1 -1
  163. package/src/tee/claims.ts +70 -0
  164. package/src/tee/client.ts +755 -0
  165. package/src/tee/cookies.ts +13 -0
  166. package/src/tee/crypto.ts +143 -0
  167. package/src/tee/errors.ts +204 -0
  168. package/src/tee/index.ts +21 -0
  169. package/src/tee/mobileRelay.ts +438 -0
  170. package/src/tee/quoteBinding.ts +184 -0
  171. package/src/tee/relayGuard.ts +83 -0
  172. package/src/tee/secureRandom.ts +124 -0
  173. package/src/tee/telemetry.ts +87 -0
  174. package/src/tee/types.ts +88 -0
  175. package/src/tee-entry.ts +31 -0
  176. package/src/types/error.types.ts +44 -0
  177. package/src/types/mobile.types.ts +2 -0
  178. package/tee/package.json +7 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.8.0 - 2026-09-14
4
+
5
+ ### Fixed
6
+
7
+ - Await WebKit cookies for iOS native replay, with a bounded timeout and correct
8
+ cookie scoping/expiry. Add iOS and Android cookie-read regression coverage.
9
+ - Preserve verifier rejection details and relay failure causes; report TEE proof-generation health separately from verification.
10
+
11
+ ### Added
12
+
13
+ - Add TEE stage logs, typed error reasons and correlated timing/error metadata to the existing SDK events.
14
+ - Opt-in `attestationMode: 'tee-replay'` with the existing registry, WebView,
15
+ capture, proof-plan and partial-proof configuration. Default remains app attestation.
16
+ - One quote authenticates the enclave keys and all replay targets; one signed
17
+ bundle carries full or partial results to `POST /v1/artifacts/verify` (schema 3.1.0).
18
+ - Mode-aware `SatyaMobileClient.attestReplay` / `attestReplayBundle`, online TEE
19
+ verification, typed errors and optional `teeReplayServerUrl` / `teeTrustConfig` restrictions.
20
+ - Device egress relay with provider certificate and hostname checks inside the TEE.
21
+ Provider SPKI pins remain specific to native replay. TEE makes no app/device identity
22
+ or residential-IP/location claim.
23
+ - Credential-free quote preparation during eligible capture and ordered same-origin
24
+ TLS connection reuse within one bundle. Replay requests are never automatically retried.
25
+ - `/tee` `getTeeClaims` returns endpoint-scoped claims with exact JSON text for
26
+ single, multiple and partial runs; requires the matching verifier's claim attribution.
27
+
28
+ ### Integration
29
+
30
+ - For TEE, import from `@burnt-labs/expo-satya-attest/tee`, install the optional
31
+ `react-native-tcp-socket` peer and rebuild the native app. WebView cookies and
32
+ secure randomness use this SDK's native bridge; no cookie package, Gradle patch
33
+ or RNG polyfill is required. See [TEE setup](./TEE_REPLAY.md).
34
+ - Existing app-attestation imports and callback types are unchanged and do not
35
+ require the TCP peer. Only `/tee` uses the native/TEE artifact union; see
36
+ [result types](./TEE_REPLAY.md#result-types).
37
+ - This release prioritizes app attestation. TEE service readiness is deferred to
38
+ a follow-up patch release. Deploy and validate the matching verifier and
39
+ measured replay service before enabling TEE.
40
+ TEE verification is online and single-use; preserve opaque artifact bytes.
41
+ - TEE respects explicit transport app IDs and the same provider policy hash rules
42
+ as native mode. Partial init calls preserve the selected mode; switch back explicitly
43
+ with `attestationMode: 'app-attestation'` before remounting the button.
44
+
3
45
  ## 3.7.1 - 2026-09-09
4
46
 
5
47
  ### Fixed
@@ -1,5 +1,10 @@
1
1
  # Provider Config
2
2
 
3
+ App-attestation and TEE replay use the same registry configuration. TEE ignores
4
+ SPKI pins while retaining exact host authorization and enclave certificate/hostname
5
+ checks; one quote-bound bundle covers full/partial proof plans. See
6
+ [TEE compatibility and setup](./TEE_REPLAY.md#provider-features).
7
+
3
8
  A provider config is **trusted policy** for the native replay flow. One config decides:
4
9
 
5
10
  - which URL may be replayed (`endpoints[].url` + `tlsIdentityPolicy`),
package/PUBLISHING.md CHANGED
@@ -10,6 +10,36 @@ very large native artifacts. The npm tarball still needs the native artifacts un
10
10
  package is redesigned to download or build them during install, which is not recommended
11
11
  for a plug-and-play Expo SDK.
12
12
 
13
+ ## 3.8.0 release preparation
14
+
15
+ Version 3.8.0 prioritizes app attestation and can be published before TEE service
16
+ readiness, which is deferred to a follow-up patch release. TEE remains opt-in;
17
+ deploy and validate the matching verifier and measured service before enabling
18
+ it for consumers.
19
+
20
+ Cloudflare operators deploy the durable-state
21
+ migration before the verifier; Phala images are built/pushed manually using
22
+ `deploy/phala/publish.sh`, then pinned by digest. See [TEE setup](./TEE_REPLAY.md).
23
+
24
+ Rebuild all native artifacts from the release commit. TEE consumers use the `/tee`
25
+ entry point and need a native build for the cookie bridge and optional TCP peer;
26
+ app-attestation imports and callback types stay unchanged. No example-only patches are
27
+ required. Keep captured requests, cookies, local env files and test results out
28
+ of both Git and the package. The example and internal tests are excluded by the
29
+ package whitelist; `check:pack` validates the resulting file list.
30
+
31
+ Preparing a tarball does not publish it:
32
+
33
+ ```bash
34
+ npm run release:check
35
+ npm run test:entrypoint-bundles
36
+ npm pack --pack-destination /tmp
37
+ ```
38
+
39
+ Retain the tarball checksum and source commit with release evidence. Run Android
40
+ native unit tests from the example's generated `android/` project with
41
+ `./gradlew :burnt-labs-expo-satya-attest:testDebugUnitTest` after building it.
42
+
13
43
  ## Package Name
14
44
 
15
45
  Use the scoped public package owned by the Burnt npm organization:
package/README.md CHANGED
@@ -5,6 +5,30 @@ the Rust `satya-sdk` / `satya-ffi` pipeline for artifact construction and verifi
5
5
  policy, while Swift and Kotlin provide platform-specific WebView capture, native HTTPS
6
6
  replay, hardware-backed signing, and platform attestation evidence.
7
7
 
8
+ ## TEE replay
9
+
10
+ Import from `@burnt-labs/expo-satya-attest/tee` and use
11
+ `initSatyaAttest({ appId, attestationMode: 'tee-replay' })` with the normal
12
+ provider WebView and capture flow. The configured verifier advertises the replay
13
+ service; the SDK seals captured credentials only after quote preflight, relays
14
+ TLS through the device and submits family 3 to the same verification endpoint.
15
+ TEE ignores provider SPKI pins; certificate-chain and hostname verification
16
+ remain mandatory inside the enclave. One initial quote and one signed bundle
17
+ cover all selected endpoints and authenticated partial outcomes. `onComplete`
18
+ returns the opaque schema 3.1 envelope and verified completion metadata.
19
+
20
+ For captured-request integrations, `SatyaMobileClient.attestReplay` and
21
+ `attestReplayBundle` honor the init mode. Explicit `attestNativeReplay*` methods
22
+ remain native APIs. TEE results require online verification; `verify(raw,
23
+ transportConfig)` submits a TEE envelope to the shared endpoint. Finalization is
24
+ single-use, so submitting an already verified proof again is rejected.
25
+
26
+ For TEE, install the optional TCP native peer and rebuild the app; cookie access and randomness use
27
+ the SDK native bridge. See [TEE setup and provider compatibility](./TEE_REPLAY.md).
28
+
29
+ App-attestation-only consumers keep the default import and existing callback
30
+ types. They do not need the TCP package or TEE configuration.
31
+
8
32
  ## Prerequisite: register your app for an app id
9
33
 
10
34
  **SDK 3.x requires an `appId`.** Register your app at
@@ -17,16 +41,15 @@ your app its providers, configure the id in any one of:
17
41
  - `initSatyaAttest({ appId })` at startup, or
18
42
  - `EXPO_PUBLIC_SATYA_APP_ID` in the build environment.
19
43
 
20
- The app id is public by design — verification is gated by OS attestation
21
- matching your registered identities plus a per-app provider allowlist, so
22
- possession of the id alone grants nothing, and store binaries ship no verifier
23
- secret. Without a configured app id a verifier-backed flow fails before the
44
+ The app ID is public. Native mode checks registered OS identities and provider
45
+ grants. TEE mode checks the provider grant and enclave evidence without asserting
46
+ app/device identity. Store binaries ship no verifier secret. Without a configured app id a verifier-backed flow fails before the
24
47
  provider WebView opens with `APP_ID_REQUIRED`; a malformed id fails validation
25
48
  with `CONFIG_INVALID`. An unregistered or unapproved id fails at the verifier
26
49
  with `APP_NOT_REGISTERED` / `APP_DISABLED`, and an ungranted provider fails with
27
50
  `PROVIDER_ACCESS_DENIED`. See
28
51
  [`docs/MULTI_TENANT_VERIFICATION.md`](../../docs/MULTI_TENANT_VERIFICATION.md).
29
- Flows also fail with `app_platform_not_configured` when the app has no
52
+ Native flows also fail with `app_platform_not_configured` when the app has no
30
53
  registered identity for the current platform, so register every platform you
31
54
  develop and test on.
32
55
 
@@ -47,6 +70,10 @@ therefore bounded, and verified **at their edges** — the interior of each wind
47
70
  | `react-native` | `>=0.81.0 <0.86.0` | 0.81.5 | 0.85.x |
48
71
  | `react-native-safe-area-context` | `>=5.6.0 <6.0.0` | 5.6.x | 5.6.x |
49
72
  | `react-native-webview` | `>=13.15.0 <14.0.0` | 13.15.0 | 13.16.x |
73
+ | `react-native-tcp-socket` | `>=6.4.2 <7.0.0` | 6.4.2 | 6.4.x |
74
+
75
+ `react-native-tcp-socket` belongs only to the `/tee` entry point. The root package
76
+ does not impose a TCP dependency or version constraint on app-only consumers.
50
77
 
51
78
  Policy:
52
79
 
@@ -65,9 +92,9 @@ Policy:
65
92
  or the example app) outside its declared range; and this table differing from
66
93
  `package.json`.
67
94
 
68
- ## Current Flow
95
+ ## App-attestation flow (default)
69
96
 
70
- The supported Expo flow is **certificate-pinned native replay**. Third-party apps can
97
+ App-attestation mode uses **certificate-pinned native replay**. Third-party apps can
71
98
  use the SDK-owned button component for the full managed flow:
72
99
 
73
100
  1. The app renders `SatyaProviderAttestButton` with a provider id and transport config.
@@ -999,14 +1026,14 @@ HTTPS tunnel or deployed HTTPS endpoint.
999
1026
 
1000
1027
  ## Provider Health Monitoring
1001
1028
 
1002
- Every native replay reports one anonymous health ping to the Burnt provider
1029
+ Every native or TEE replay reports one anonymous health ping to the Burnt provider
1003
1030
  monitor (`POST https://provider-monitor.burnt.com/ingest`) so per-provider
1004
1031
  success rates and proof-generation latency show up on the shared dashboard. This
1005
1032
  is on by default and needs no app configuration or token.
1006
1033
 
1007
1034
  - `outcome: "ok"` fires as soon as the attestation artifact is produced, with
1008
1035
  `latencyMs` measured from captured-request handoff to artifact creation.
1009
- - `outcome: "failed"` fires when the native replay pipeline fails to produce an
1036
+ - `outcome: "failed"` fires when the replay pipeline fails to produce an
1010
1037
  artifact (no latency attached).
1011
1038
  - Artifact verification — the embedded check and the backend verifier — is
1012
1039
  outside the ping: its result depends on device trust (App Attest / Play
@@ -1021,6 +1048,9 @@ unknown provider ids are acknowledged but not recorded.
1021
1048
 
1022
1049
  ## Funnel Event Logging
1023
1050
 
1051
+ TEE uses the same events with `mode: "tee-replay"`, structured stage errors and
1052
+ per-stage timings. See [TEE logging and troubleshooting](./TEE_REPLAY.md#logging-and-troubleshooting).
1053
+
1024
1054
  The SDK emits seven lifecycle events — `PROVIDER_INIT`, `LOGIN_COMPLETED`,
1025
1055
  `ENDPOINT_MATCHED`, `NATIVE_REPLAY_SUCCESS`, `ATTESTATION_GENERATED`,
1026
1056
  `ATTESTATION_VERIFIED`, and `FAILED` — to the SDK-logs ingest
@@ -1031,7 +1061,7 @@ provider-registry Monitoring dashboard. **On by default**; opt out with
1031
1061
  - **Correlation:** each user task mints one UUID `sessionId`, held constant
1032
1062
  across retries (each attempt is numbered with `attemptIndex`) and embedded in
1033
1063
  native artifacts as the unsigned `sdkSessionId` envelope field. From
1034
- `ATTESTATION_GENERATED` on, events also carry the `attestationId` join key
1064
+ `ATTESTATION_GENERATED` on, native events also carry the `attestationId` join key
1035
1065
  (for TeeLS flows, the claim's attestation id). The failure screen shows the
1036
1066
  `sessionId` with a Copy control for support hand-off. **Deploy the
1037
1067
  `sdkSessionId`-aware verifier before rolling out 3.4.0 clients** — older
package/TEE_REPLAY.md ADDED
@@ -0,0 +1,231 @@
1
+ # TEE replay
2
+
3
+ TEE mode uses the same provider registry, login WebView, capture rules and proof
4
+ plans as app attestation. The captured request is replayed inside the TEE through
5
+ an opaque TLS relay on the user's device. A matching verifier and measured TEE
6
+ service must be deployed once by the operator.
7
+
8
+ ## Install and enable
9
+
10
+ ```bash
11
+ npx expo install @burnt-labs/expo-satya-attest react-native-webview react-native-tcp-socket@6.4.2
12
+ ```
13
+
14
+ For TEE, install the optional TCP peer and rebuild the native app. The SDK supplies WebView cookie
15
+ access and OS randomness; no cookie package, patch, RNG import or custom proxy is
16
+ needed. Expo Go cannot load these native modules; use a development or release build.
17
+ Keep the SDK config plugin from the normal installation.
18
+ TCP versions `>=6.4.2 <7.0.0` are checked only when starting the TEE relay;
19
+ app-only integrations may keep their own TCP dependency version.
20
+
21
+ ```tsx
22
+ import { initSatyaAttest, SatyaProviderAttestButton } from '@burnt-labs/expo-satya-attest/tee';
23
+
24
+ initSatyaAttest({
25
+ appId: 'app_your-registered-app-id',
26
+ attestationMode: 'tee-replay',
27
+ });
28
+
29
+ export function ProveProfile() {
30
+ return (
31
+ <SatyaProviderAttestButton
32
+ providerId="kaggle.current_user.v1"
33
+ transportConfig={{ verifierUrl: 'https://your-verifier.example' }}
34
+ onComplete={(result) => {
35
+ if (result.backendVerify?.ok) {
36
+ // Use the verified claims and completion metadata.
37
+ }
38
+ }}
39
+ onError={(error) => console.warn(error.code)}
40
+ />
41
+ );
42
+ }
43
+ ```
44
+
45
+ Use an active public app ID with the required provider grant. In TEE mode the app
46
+ ID selects registry access; it does not prove the app binary or device identity.
47
+ Play Integrity / App Attest setup is needed only when testing app-attestation mode.
48
+
49
+ The verifier advertises the TEE service URL and checks its quote before the SDK
50
+ releases credentials. `teeReplayServerUrl` optionally restricts that URL;
51
+ `teeTrustConfig` optionally narrows accepted identities. Neither is required in
52
+ the app. HTTPS is required for live verifier/service traffic. Configuration
53
+ mismatches fail the run; TEE never silently falls back to app attestation.
54
+
55
+ ## Provider features
56
+
57
+ | Feature | TEE behavior |
58
+ | --- | --- |
59
+ | Registry templates | Use the existing provider ID and configuration. Inline `providerTemplate` is unsupported. |
60
+ | WebView / capture | Uses the existing login, OAuth popup, injection, request matching and guarded rewrite flow. |
61
+ | Multiple endpoints | Pass the existing `proofPlanId`. One quote and one signed bundle cover the selected endpoints. |
62
+ | Partial proofs | Opt in with `partialEndpointProviderIds: [providerId]`, as in native mode. Inspect `completionMode`, `endpointIds`, `omittedEndpointIds` and `planComplete` in the verified result. |
63
+ | Dynamic pinned hosts | Registry host-group membership remains enforced. TEE ignores SPKI pins and verifies the certificate chain and hostname inside the enclave. Native replay still enforces pins. |
64
+ | GET / POST and mutations | Existing methods and bodies are replayed. A mutation can execute during capture and again during replay; there are no automatic replay retries. |
65
+ | Account binding | `claim-equality` proves the governed account claims agree. `shared-session` returns `shared-proof-run`, which proves run membership without asserting account equality. |
66
+ | Verification | Online through `POST /v1/artifacts/verify`, which distinguishes native and TEE artifacts. Deferred verification is unsupported. |
67
+
68
+ Partial mode still attempts the full plan. Authenticated unsuccessful responses
69
+ may be recorded as omissions; TLS, policy, parser and transport failures abort the
70
+ run. Required account-binding endpoints cannot be omitted.
71
+
72
+ The relay normally uses the device's egress. The proof does not attest residential
73
+ IP, location or app/device integrity. A compromised phone can interrupt traffic
74
+ or choose credentials it possesses, but cannot forge the enclave's provider TLS
75
+ response or final signature.
76
+
77
+ ## Switch modes
78
+
79
+ The `/tee` entry point supports both modes with the same callbacks. Keep using
80
+ the default package import for app-attestation-only integrations: its native
81
+ result types are unchanged, and it does not load or require the TCP peer.
82
+
83
+ Initialize before mounting the button. To change modes, finish or cancel the
84
+ current flow, call `initSatyaAttest({ attestationMode: nextMode })`, then remount
85
+ the button, for example with `key={nextMode}`. Values are `app-attestation` and
86
+ `tee-replay`; omitting the mode preserves its current value on later init calls.
87
+ The example app provides an **Attestation mode** selector and keeps the selected
88
+ provider, proof plan and completion preference when switching.
89
+
90
+ For captured-request integrations, use `SatyaMobileClient.attestReplay` or
91
+ `attestReplayBundle`. Explicit `attestNativeReplay*` methods always remain native.
92
+ TEE methods return an already verified result. Final verification consumes the
93
+ challenge once; do not verify the same completed proof a second time.
94
+
95
+ Preserve `artifactBytesB64` as opaque bytes. Parsing and reserializing the signed
96
+ JSON may round large numbers and invalidate it. Use claim `valueBytesB64` when
97
+ exact JSON numeric values matter.
98
+
99
+ ## Result types
100
+
101
+ Existing imports from `@burnt-labs/expo-satya-attest` retain the 3.7.x
102
+ `SatyaProviderAttestationResult` and `onComplete` types. No callback migration is
103
+ needed for app attestation. Using the default button with TEE selected reports
104
+ a configuration error before opening the flow.
105
+
106
+ Only imports from `@burnt-labs/expo-satya-attest/tee` use a native/TEE result union.
107
+ Narrow its artifact before reading fields specific to either mode:
108
+
109
+ ```ts
110
+ function handleComplete(result: SatyaProviderAttestationResult) {
111
+ const artifact = result.artifact;
112
+ if ('artifactBytesB64' in artifact) {
113
+ // TEE envelope: retain artifact.artifactBytesB64 and the backend verdict.
114
+ return;
115
+ }
116
+ // Native artifact: existing artifact.statement handling belongs here.
117
+ }
118
+ ```
119
+
120
+ Import `SatyaProviderAttestationResult` from the `/tee` entry point for this handler. Shared result/verification
121
+ fields remain available in either mode. TEE claim `value` is a JSON convenience
122
+ representation and can round numbers (or be null outside its numeric range);
123
+ `valueBytesB64` carries the authenticated exact JSON value.
124
+
125
+ ## Read claims
126
+
127
+ The signed TEE object uses `body.exchanges[].disclosure[]` for every run: one
128
+ exchange for a single endpoint, several for a proof plan. Each disclosure has
129
+ `name`, `sourcePath` and `value`; the value can contain nested objects and arrays.
130
+ Keep the signed bytes intact and read claims from the trusted verifier response:
131
+
132
+ ```ts
133
+ import { getTeeClaims } from '@burnt-labs/expo-satya-attest/tee';
134
+
135
+ const claims = getTeeClaims(result.backendVerify);
136
+ // Every entry: { endpointId, name, sourcePath, valueJson }
137
+ for (const claim of claims) {
138
+ renderClaim(claim.endpointId, claim.name, claim.valueJson);
139
+ }
140
+ ```
141
+
142
+ `getTeeClaims` requires the matching verifier release: each verified claim carries
143
+ an explicit `endpointId`, original `claimName` and exact `valueBytesB64`. Existing
144
+ `name` and `value` response fields remain available. The helper preserves aliases
145
+ containing `/`, keeps repeated names in different endpoints separate, and rejects
146
+ duplicate endpoint/name pairs or malformed encodings. Omitted endpoints supply no claims;
147
+ use `omittedEndpointIds` and `planComplete` for coverage.
148
+
149
+ `valueJson` is exact JSON text, preserving strings, booleans, nested data, large
150
+ integers and precise decimals. Parse it with your application's JSON parser when
151
+ needed; ordinary `JSON.parse` can round numbers. No lossy fallback is used.
152
+
153
+ This helper extracts data; it does not authenticate a caller-supplied verdict.
154
+ Your backend must authorize using its own successful verifier response, never
155
+ an `ok` flag or claims sent back by an untrusted mobile client. Verification is
156
+ single-use, so route that final verification to your trusted backend and retain
157
+ its result rather than submitting the same proof again.
158
+
159
+ ## Logging and troubleshooting
160
+
161
+ TEE uses the same event names and logging service as App Attest. No service
162
+ update or extra logging setup is required. Enable internal SDK logs separately:
163
+
164
+ ```ts
165
+ initSatyaAttest({
166
+ appId: 'YOUR_APP_ID',
167
+ attestationMode: 'tee-replay',
168
+ logLevel: 'info', // Stage completions and errors; 'debug' also logs starts.
169
+ eventLogging: true, // Default; sends lifecycle events to SDK Monitoring.
170
+ });
171
+ ```
172
+
173
+ Internal entries use the `tee` scope and the existing logger/sink. Each stage
174
+ logs its outcome and duration; no packet-by-packet logs are emitted. Use
175
+ `logLevel: 'silent'` to disable internal logging independently of event delivery.
176
+
177
+ The WebView emits `PROVIDER_INIT`, `LOGIN_COMPLETED` and `ENDPOINT_MATCHED` as
178
+ usual. Successful replay emits `NATIVE_REPLAY_SUCCESS` (the existing wire name)
179
+ and `ATTESTATION_GENERATED`; online verification emits `ATTESTATION_VERIFIED`.
180
+ Every TEE event has `logData.mode: "tee-replay"`. Failed attempts emit one
181
+ `FAILED` event with these fields when available:
182
+
183
+ | Field | Meaning |
184
+ | --- | --- |
185
+ | `teeStage` | `configuration`, `challenge`, `session`, `quoteBinding`, `quoteVerification`, `credentials`, `sealing`, `relay`, `replay`, or `verification`. |
186
+ | `errorCode`, `errorReason`, `errorMessage` | SDK error code, a typed local reason, and a safe summary. |
187
+ | `httpStatus`, `requestId` | Service status and server correlation ID, when returned. |
188
+ | `verifierErrorCode`, `verifierErrorClass`, `verifierErrorStage`, `retryable` | Structured verifier or replay-service error metadata; unknown server codes remain readable. |
189
+ | `endpointId`, `proofPlanId`, `exchangeIndex`, `exchangeCount` | Selected proof and endpoint context; relay faults identify the failing slot. |
190
+ | `teeChallengeMs`, `teeSessionMs`, `teeQuoteBindingMs`, `teeQuoteVerificationMs`, `teeCaptureMs`, `teeSealingMs`, `teeRelayMs`, `teeReplayMs`, `teeVerificationMs` | Completed stage durations. `latencyMs` on failure measures the failed stage. |
191
+ | `totalElapsedMs` | Attempt elapsed time, including login/capture for the WebView flow. |
192
+
193
+ `sessionId` is the common event correlation key and travels to verification as
194
+ `sdkSessionId`. The verified event also carries `attestationId`, `completionMode`
195
+ and `planComplete`. Speculative preparation can overlap WebView capture, so stage
196
+ durations should not be summed as wall-clock latency. Successful stages are
197
+ summarized on subsequent lifecycle events rather than adding new event names.
198
+
199
+ `SatyaTeeStage`, `SatyaTeeErrorReason`, `SatyaTeeErrorContext`, `TEE_STAGES` and
200
+ `TEE_ERROR_REASONS` are exported from `/tee`; their definitions live in
201
+ `src/tee/errors.ts`. Errors remain compatible with `SatyaError` and its
202
+ `diagnostics`. `SatyaTeeClient.verify*` preserves its result-returning API and
203
+ adds structured `errorDetail` on a service rejection or malformed verifier response.
204
+ Network failures, timeouts and cancellation still throw.
205
+
206
+ `SatyaMobileClient.attestReplay*` also emits post-capture TEE lifecycle events.
207
+ Low-level `SatyaTeeClient` operations provide internal stage logs and structured
208
+ errors; the caller owns the surrounding UI lifecycle.
209
+
210
+ Events send `logData` as a JSON string, allowlisted and capped at 2 KB. The new TEE stage
211
+ records exclude request headers/bodies, cookies, relay tokens, raw upstream error
212
+ messages, quotes and proofs, even with verbose logging enabled. Shared WebView
213
+ verbose logging remains intended only for local debugging.
214
+ The optional `eventLogBridgeDebug` tail remains off by default. Delivery is
215
+ best effort with the existing queue/backoff; logging failures never retry a
216
+ provider replay or change proof verification.
217
+
218
+ TEE also sends the existing anonymous provider-generation health ping. Producing
219
+ an artifact reports success before final verification; a subsequent verifier
220
+ rejection does not turn that generation into a failure. No additional verifier
221
+ monitor event names are introduced.
222
+ Client setup failures also report failed generation; user cancellation is excluded.
223
+
224
+ ## Operator setup
225
+
226
+ See the repository's [TEE protocol and verifier configuration](https://github.com/burnt-labs/tls-app-attest/blob/main/docs/TEE_REPLAY_MODE.md)
227
+ and [Phala deployment guide](https://github.com/burnt-labs/tls-app-attest/blob/main/deploy/phala/README.md).
228
+ Deploy durable state (when using Cloudflare), then the matching verifier, measured
229
+ replay image and SDK. Pin the image digest and accept only verified deployment
230
+ identities. A local verifier can use the atomic file journal with production DCAP
231
+ verification; it does not need a local TEE or Cloudflare deployment.
@@ -4,13 +4,13 @@ plugins {
4
4
  }
5
5
 
6
6
  group = 'expo.modules.satyaattest'
7
- version = '3.7.1'
7
+ version = '3.8.0'
8
8
 
9
9
  android {
10
10
  namespace "expo.modules.satyaattest"
11
11
  defaultConfig {
12
12
  versionCode 1
13
- versionName "3.7.1"
13
+ versionName "3.8.0"
14
14
  // StrongBox key attestation needs API 28+; basic Keystore attestation API 24+.
15
15
  minSdkVersion 24
16
16
  }
@@ -113,6 +113,22 @@ class SatyaAttestModule : Module() {
113
113
  Base64.encodeToString(bytes, Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING)
114
114
  }
115
115
 
116
+ // OS entropy, encoded as unpadded base64url.
117
+ Function("secureRandomBytes") { count: Int ->
118
+ require(count in 1..4096) { "secureRandomBytes count must be 1..4096" }
119
+ val bytes = ByteArray(count)
120
+ SecureRandom().nextBytes(bytes)
121
+ Base64.encodeToString(bytes, Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING)
122
+ }
123
+
124
+ AsyncFunction("readWebViewCookies") { url: String ->
125
+ val parsed = url.toHttpUrl()
126
+ require(parsed.isHttps && parsed.username.isEmpty() && parsed.password.isEmpty()) {
127
+ "Cookie URL must be HTTPS without credentials"
128
+ }
129
+ CookieManager.getInstance().getCookie(parsed.toString()) ?: ""
130
+ }.runOnQueue(Queues.MAIN)
131
+
116
132
  AsyncFunction("initialize") { configJson: String -> transportConfigJson = configJson }
117
133
 
118
134
  AsyncFunction("dispose") { transportConfigJson = "{}" }
@@ -495,6 +511,7 @@ class SatyaAttestModule : Module() {
495
511
  // Backs ProofCookieJar seeding by the real WebView cookie store. Reads only;
496
512
  // proof replay never calls setCookie.
497
513
  private class CookieManagerGlobalStore : GlobalCookieStore {
514
+ // getCookie reads synchronously; flush() only persists to disk.
498
515
  override fun cookieHeader(url: String): String? =
499
516
  CookieManager.getInstance().getCookie(url)?.takeIf { it.isNotBlank() }
500
517
 
@@ -624,7 +641,7 @@ class SatyaAttestModule : Module() {
624
641
  replayTargetsById[exchange.endpointId] = target
625
642
  }
626
643
 
627
- // Seed the proof-local jar for every governed replay path.
644
+ // Finish all WebView cookie reads before replay starts.
628
645
  val cookieSeedUrls = effectiveEndpointIds.map { endpointId ->
629
646
  replayTargetsById[endpointId]?.url
630
647
  ?: error("HOST_GROUP_INVALID: no resolved replay target for $endpointId")
@@ -1211,7 +1228,7 @@ class SatyaAttestModule : Module() {
1211
1228
  requestBuilder.header(key, value)
1212
1229
  }
1213
1230
  }
1214
- CookieManager.getInstance().getCookie(replayUrl)?.takeIf { it.isNotBlank() }?.let {
1231
+ CookieManagerGlobalStore().cookieHeader(replayUrl)?.let {
1215
1232
  requestBuilder.header("Cookie", it)
1216
1233
  }
1217
1234
 
@@ -3,6 +3,13 @@ import { VerifyResult } from './types';
3
3
  declare class SatyaAttestModule extends NativeModule<Record<string, never>> {
4
4
  satyaCoreVersion(): string;
5
5
  createWebViewBridgeCapability(): string;
6
+ /**
7
+ * OS entropy as unpadded base64url. Optional so a JS bundle running against a
8
+ * native binary built before 3.8.0 still type-checks; `tee/secureRandom.ts`
9
+ * falls back to `createWebViewBridgeCapability` in that case.
10
+ */
11
+ secureRandomBytes?(count: number): string;
12
+ readWebViewCookies(url: string): Promise<string>;
6
13
  attestNativeReplay(providerTemplateJson: string, capturedRequestJson: string | null): Promise<string>;
7
14
  attestNativeReplayBundle(providerTemplateJson: string, capturedRequestsJson: string, partialEndpointSelectionJson: string | null): Promise<string>;
8
15
  verifyArtifact(artifactJson: string): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"SatyaAttestModule.d.ts","sourceRoot":"","sources":["../src/SatyaAttestModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,OAAO,iBAAkB,SAAQ,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACzE,gBAAgB,IAAI,MAAM;IAC1B,6BAA6B,IAAI,MAAM;IASvC,kBAAkB,CAChB,oBAAoB,EAAE,MAAM,EAC5B,mBAAmB,EAAE,MAAM,GAAG,IAAI,GACjC,OAAO,CAAC,MAAM,CAAC;IAElB,wBAAwB,CACtB,oBAAoB,EAAE,MAAM,EAC5B,oBAAoB,EAAE,MAAM,EAC5B,4BAA4B,EAAE,MAAM,GAAG,IAAI,GAC1C,OAAO,CAAC,MAAM,CAAC;IAElB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IACtD,sBAAsB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAEnE,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC7C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IACxB,iCAAiC,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAC3F,sBAAsB,CACpB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,EAChB,kBAAkB,EAAE,OAAO,GAC1B,OAAO,CAAC,OAAO,CAAC;IACnB,qFAAqF;IACrF,wBAAwB,CAAC,CAAC,SAAS,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAC/D,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC;CACtC;;AAED,wBAAqE"}
1
+ {"version":3,"file":"SatyaAttestModule.d.ts","sourceRoot":"","sources":["../src/SatyaAttestModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,OAAO,iBAAkB,SAAQ,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACzE,gBAAgB,IAAI,MAAM;IAC1B,6BAA6B,IAAI,MAAM;IACvC;;;;OAIG;IACH,iBAAiB,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IACzC,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAShD,kBAAkB,CAChB,oBAAoB,EAAE,MAAM,EAC5B,mBAAmB,EAAE,MAAM,GAAG,IAAI,GACjC,OAAO,CAAC,MAAM,CAAC;IAElB,wBAAwB,CACtB,oBAAoB,EAAE,MAAM,EAC5B,oBAAoB,EAAE,MAAM,EAC5B,4BAA4B,EAAE,MAAM,GAAG,IAAI,GAC1C,OAAO,CAAC,MAAM,CAAC;IAElB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IACtD,sBAAsB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAEnE,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC7C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IACxB,iCAAiC,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAC3F,sBAAsB,CACpB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,EAChB,kBAAkB,EAAE,OAAO,GAC1B,OAAO,CAAC,OAAO,CAAC;IACnB,qFAAqF;IACrF,wBAAwB,CAAC,CAAC,SAAS,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAC/D,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC;CACtC;;AAED,wBAAqE"}
@@ -1 +1 @@
1
- {"version":3,"file":"SatyaAttestModule.js","sourceRoot":"","sources":["../src/SatyaAttestModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AA0CzD,eAAe,mBAAmB,CAAoB,aAAa,CAAC,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from 'expo';\n\nimport { VerifyResult } from './types';\n\ndeclare class SatyaAttestModule extends NativeModule<Record<string, never>> {\n satyaCoreVersion(): string;\n createWebViewBridgeCapability(): string;\n\n // Every parameter below is required, including the nullable ones. Whether iOS\n // tolerates an omitted trailing optional depends on the host app's\n // expo-modules-core (older 3.0.x cores and the optimized JSI path require all\n // of them; 56.x relaxes trailing optionals), and `peerDependencies.expo` is\n // `*`, so this package must satisfy the strictest of them. Marking these `?`\n // would let TypeScript accept a call that crashes on device. See\n // `nativeReplayArgs.ts` for the full mechanism.\n attestNativeReplay(\n providerTemplateJson: string,\n capturedRequestJson: string | null\n ): Promise<string>;\n\n attestNativeReplayBundle(\n providerTemplateJson: string,\n capturedRequestsJson: string,\n partialEndpointSelectionJson: string | null\n ): Promise<string>;\n\n verifyArtifact(artifactJson: string): Promise<boolean>;\n verifyArtifactDetailed(artifactJson: string): Promise<VerifyResult>;\n\n initialize(configJson: string): Promise<void>;\n dispose(): Promise<void>;\n installWebViewDocumentStartScript(viewTag: number, script: string): Promise<boolean | null>;\n setPopupWindowsEnabled(\n viewTag: number,\n enabled: boolean,\n geolocationEnabled: boolean\n ): Promise<boolean>;\n /** Optional for compatibility with native binaries predating privacy concealment. */\n setPopupWindowsConcealed?(concealed: boolean): Promise<boolean>;\n closePopupWindows(): Promise<boolean>;\n}\n\nexport default requireNativeModule<SatyaAttestModule>('SatyaAttest');\n"]}
1
+ {"version":3,"file":"SatyaAttestModule.js","sourceRoot":"","sources":["../src/SatyaAttestModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAiDzD,eAAe,mBAAmB,CAAoB,aAAa,CAAC,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from 'expo';\n\nimport { VerifyResult } from './types';\n\ndeclare class SatyaAttestModule extends NativeModule<Record<string, never>> {\n satyaCoreVersion(): string;\n createWebViewBridgeCapability(): string;\n /**\n * OS entropy as unpadded base64url. Optional so a JS bundle running against a\n * native binary built before 3.8.0 still type-checks; `tee/secureRandom.ts`\n * falls back to `createWebViewBridgeCapability` in that case.\n */\n secureRandomBytes?(count: number): string;\n readWebViewCookies(url: string): Promise<string>;\n\n // Every parameter below is required, including the nullable ones. Whether iOS\n // tolerates an omitted trailing optional depends on the host app's\n // expo-modules-core (older 3.0.x cores and the optimized JSI path require all\n // of them; 56.x relaxes trailing optionals), and `peerDependencies.expo` is\n // `*`, so this package must satisfy the strictest of them. Marking these `?`\n // would let TypeScript accept a call that crashes on device. See\n // `nativeReplayArgs.ts` for the full mechanism.\n attestNativeReplay(\n providerTemplateJson: string,\n capturedRequestJson: string | null\n ): Promise<string>;\n\n attestNativeReplayBundle(\n providerTemplateJson: string,\n capturedRequestsJson: string,\n partialEndpointSelectionJson: string | null\n ): Promise<string>;\n\n verifyArtifact(artifactJson: string): Promise<boolean>;\n verifyArtifactDetailed(artifactJson: string): Promise<VerifyResult>;\n\n initialize(configJson: string): Promise<void>;\n dispose(): Promise<void>;\n installWebViewDocumentStartScript(viewTag: number, script: string): Promise<boolean | null>;\n setPopupWindowsEnabled(\n viewTag: number,\n enabled: boolean,\n geolocationEnabled: boolean\n ): Promise<boolean>;\n /** Optional for compatibility with native binaries predating privacy concealment. */\n setPopupWindowsConcealed?(concealed: boolean): Promise<boolean>;\n closePopupWindows(): Promise<boolean>;\n}\n\nexport default requireNativeModule<SatyaAttestModule>('SatyaAttest');\n"]}
@@ -3,6 +3,7 @@ import { VerifyResult } from './types';
3
3
  declare class SatyaAttestModule extends NativeModule<Record<string, never>> {
4
4
  satyaCoreVersion(): string;
5
5
  createWebViewBridgeCapability(): string;
6
+ readWebViewCookies(_url: string): Promise<string>;
6
7
  attestNativeReplay(_providerTemplateJson: string, _capturedRequestJson: string | null): Promise<string>;
7
8
  attestNativeReplayBundle(_providerTemplateJson: string, _capturedRequestsJson: string, _partialEndpointSelectionJson: string | null): Promise<string>;
8
9
  verifyArtifact(_artifactJson: string): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"SatyaAttestModule.web.d.ts","sourceRoot":"","sources":["../src/SatyaAttestModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,YAAY,EAAE,MAAM,MAAM,CAAC;AAEvD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAMvC,cAAM,iBAAkB,SAAQ,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACjE,gBAAgB,IAAI,MAAM;IAG1B,6BAA6B,IAAI,MAAM;IAGjC,kBAAkB,CACtB,qBAAqB,EAAE,MAAM,EAC7B,oBAAoB,EAAE,MAAM,GAAG,IAAI,GAClC,OAAO,CAAC,MAAM,CAAC;IAGZ,wBAAwB,CAC5B,qBAAqB,EAAE,MAAM,EAC7B,qBAAqB,EAAE,MAAM,EAC7B,6BAA6B,EAAE,MAAM,GAAG,IAAI,GAC3C,OAAO,CAAC,MAAM,CAAC;IAGZ,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAGvD,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAGpE,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAG9C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B;;AAMD,wBAAyE"}
1
+ {"version":3,"file":"SatyaAttestModule.web.d.ts","sourceRoot":"","sources":["../src/SatyaAttestModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,YAAY,EAAE,MAAM,MAAM,CAAC;AAEvD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAMvC,cAAM,iBAAkB,SAAQ,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACjE,gBAAgB,IAAI,MAAM;IAG1B,6BAA6B,IAAI,MAAM;IAGjC,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAGjD,kBAAkB,CACtB,qBAAqB,EAAE,MAAM,EAC7B,oBAAoB,EAAE,MAAM,GAAG,IAAI,GAClC,OAAO,CAAC,MAAM,CAAC;IAGZ,wBAAwB,CAC5B,qBAAqB,EAAE,MAAM,EAC7B,qBAAqB,EAAE,MAAM,EAC7B,6BAA6B,EAAE,MAAM,GAAG,IAAI,GAC3C,OAAO,CAAC,MAAM,CAAC;IAGZ,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAGvD,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAGpE,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAG9C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B;;AAMD,wBAAyE"}
@@ -8,6 +8,9 @@ class SatyaAttestModule extends NativeModule {
8
8
  createWebViewBridgeCapability() {
9
9
  throw unsupportedPlatformError();
10
10
  }
11
+ async readWebViewCookies(_url) {
12
+ throw unsupportedPlatformError();
13
+ }
11
14
  async attestNativeReplay(_providerTemplateJson, _capturedRequestJson) {
12
15
  throw unsupportedPlatformError();
13
16
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SatyaAttestModule.web.js","sourceRoot":"","sources":["../src/SatyaAttestModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAGvD,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjE,MAAM,WAAW,GACf,uFAAuF,CAAC;AAE1F,MAAM,iBAAkB,SAAQ,YAAmC;IACjE,gBAAgB;QACd,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,6BAA6B;QAC3B,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,kBAAkB,CACtB,qBAA6B,EAC7B,oBAAmC;QAEnC,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,wBAAwB,CAC5B,qBAA6B,EAC7B,qBAA6B,EAC7B,6BAA4C;QAE5C,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,cAAc,CAAC,aAAqB;QACxC,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,sBAAsB,CAAC,aAAqB;QAChD,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,UAAU,CAAC,WAAmB;QAClC,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,OAAO;QACX,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;CACF;AAED,SAAS,wBAAwB;IAC/B,MAAM,UAAU,CAAC,cAAc,CAAC,oBAAoB,EAAE,WAAW,CAAC,CAAC;AACrE,CAAC;AAED,eAAe,iBAAiB,CAAC,iBAAiB,EAAE,mBAAmB,CAAC,CAAC","sourcesContent":["import { registerWebModule, NativeModule } from 'expo';\n\nimport { VerifyResult } from './types';\nimport { SatyaErrorCode, satyaError } from './types/error.types';\n\nconst UNSUPPORTED =\n 'expo-satya-attest is not available on web (it requires native App Attest / Keystore).';\n\nclass SatyaAttestModule extends NativeModule<Record<string, never>> {\n satyaCoreVersion(): string {\n throw unsupportedPlatformError();\n }\n createWebViewBridgeCapability(): string {\n throw unsupportedPlatformError();\n }\n async attestNativeReplay(\n _providerTemplateJson: string,\n _capturedRequestJson: string | null\n ): Promise<string> {\n throw unsupportedPlatformError();\n }\n async attestNativeReplayBundle(\n _providerTemplateJson: string,\n _capturedRequestsJson: string,\n _partialEndpointSelectionJson: string | null\n ): Promise<string> {\n throw unsupportedPlatformError();\n }\n async verifyArtifact(_artifactJson: string): Promise<boolean> {\n throw unsupportedPlatformError();\n }\n async verifyArtifactDetailed(_artifactJson: string): Promise<VerifyResult> {\n throw unsupportedPlatformError();\n }\n async initialize(_configJson: string): Promise<void> {\n throw unsupportedPlatformError();\n }\n async dispose(): Promise<void> {\n throw unsupportedPlatformError();\n }\n}\n\nfunction unsupportedPlatformError(): never {\n throw satyaError(SatyaErrorCode.UNSUPPORTED_PLATFORM, UNSUPPORTED);\n}\n\nexport default registerWebModule(SatyaAttestModule, 'SatyaAttestModule');\n"]}
1
+ {"version":3,"file":"SatyaAttestModule.web.js","sourceRoot":"","sources":["../src/SatyaAttestModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAGvD,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjE,MAAM,WAAW,GACf,uFAAuF,CAAC;AAE1F,MAAM,iBAAkB,SAAQ,YAAmC;IACjE,gBAAgB;QACd,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,6BAA6B;QAC3B,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,kBAAkB,CAAC,IAAY;QACnC,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,kBAAkB,CACtB,qBAA6B,EAC7B,oBAAmC;QAEnC,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,wBAAwB,CAC5B,qBAA6B,EAC7B,qBAA6B,EAC7B,6BAA4C;QAE5C,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,cAAc,CAAC,aAAqB;QACxC,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,sBAAsB,CAAC,aAAqB;QAChD,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,UAAU,CAAC,WAAmB;QAClC,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,OAAO;QACX,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;CACF;AAED,SAAS,wBAAwB;IAC/B,MAAM,UAAU,CAAC,cAAc,CAAC,oBAAoB,EAAE,WAAW,CAAC,CAAC;AACrE,CAAC;AAED,eAAe,iBAAiB,CAAC,iBAAiB,EAAE,mBAAmB,CAAC,CAAC","sourcesContent":["import { registerWebModule, NativeModule } from 'expo';\n\nimport { VerifyResult } from './types';\nimport { SatyaErrorCode, satyaError } from './types/error.types';\n\nconst UNSUPPORTED =\n 'expo-satya-attest is not available on web (it requires native App Attest / Keystore).';\n\nclass SatyaAttestModule extends NativeModule<Record<string, never>> {\n satyaCoreVersion(): string {\n throw unsupportedPlatformError();\n }\n createWebViewBridgeCapability(): string {\n throw unsupportedPlatformError();\n }\n async readWebViewCookies(_url: string): Promise<string> {\n throw unsupportedPlatformError();\n }\n async attestNativeReplay(\n _providerTemplateJson: string,\n _capturedRequestJson: string | null\n ): Promise<string> {\n throw unsupportedPlatformError();\n }\n async attestNativeReplayBundle(\n _providerTemplateJson: string,\n _capturedRequestsJson: string,\n _partialEndpointSelectionJson: string | null\n ): Promise<string> {\n throw unsupportedPlatformError();\n }\n async verifyArtifact(_artifactJson: string): Promise<boolean> {\n throw unsupportedPlatformError();\n }\n async verifyArtifactDetailed(_artifactJson: string): Promise<VerifyResult> {\n throw unsupportedPlatformError();\n }\n async initialize(_configJson: string): Promise<void> {\n throw unsupportedPlatformError();\n }\n async dispose(): Promise<void> {\n throw unsupportedPlatformError();\n }\n}\n\nfunction unsupportedPlatformError(): never {\n throw satyaError(SatyaErrorCode.UNSUPPORTED_PLATFORM, UNSUPPORTED);\n}\n\nexport default registerWebModule(SatyaAttestModule, 'SatyaAttestModule');\n"]}
@@ -1,10 +1,11 @@
1
- import type { SatyaBackendVerifyResult } from './components/providerAttest/types';
1
+ import type { SatyaBackendVerifyResult, SatyaTeeProviderAttestationResult } from './components/providerAttest/types';
2
2
  import { SatyaArtifact, CapturedProviderRequest, SatyaProviderTemplate, SatyaProgress, SatyaTransportConfig, VerifyResult } from './types';
3
3
  type ProgressListener = (progress: SatyaProgress) => void;
4
4
  export interface NativeReplayAttestArgs {
5
5
  providerTemplate: SatyaProviderTemplate;
6
6
  transportConfig: SatyaTransportConfig;
7
7
  capturedRequest?: CapturedProviderRequest | null;
8
+ signal?: AbortSignal;
8
9
  }
9
10
  export interface NativeReplayBundleAttestArgs {
10
11
  providerTemplate: SatyaProviderTemplate;
@@ -15,6 +16,8 @@ export interface NativeReplayBundleAttestArgs {
15
16
  partialEndpointsAllowed?: boolean;
16
17
  signal?: AbortSignal;
17
18
  }
19
+ /** Evidence stays discriminated: native signed statement or opaque TEE envelope. */
20
+ export type ModeAwareAttestOutcome = AttestOutcome | SatyaTeeProviderAttestationResult;
18
21
  export interface AttestOutcome {
19
22
  artifact: SatyaArtifact;
20
23
  raw: string;
@@ -26,6 +29,11 @@ export declare class SatyaMobileClient {
26
29
  private readonly listeners;
27
30
  subscribeProgress(listener: ProgressListener): () => void;
28
31
  private emit;
32
+ /** Uses the init mode with the same captured request and registry provider. */
33
+ attestReplay(args: NativeReplayAttestArgs): Promise<ModeAwareAttestOutcome>;
34
+ /** Runs one proof plan using the init mode; TEE success is verified online. */
35
+ attestReplayBundle(args: NativeReplayBundleAttestArgs): Promise<ModeAwareAttestOutcome>;
36
+ private attestTeeRun;
29
37
  attestNativeReplay(args: NativeReplayAttestArgs): Promise<AttestOutcome>;
30
38
  /**
31
39
  * Composite proof-plan replay: one native call replays every plan endpoint
@@ -34,7 +42,7 @@ export declare class SatyaMobileClient {
34
42
  * matching — the TypeScript-side record is convenience, not authority.
35
43
  */
36
44
  attestNativeReplayBundle(args: NativeReplayBundleAttestArgs): Promise<AttestOutcome>;
37
- verify(artifactJson: string): Promise<VerifyResult>;
45
+ verify(artifactJson: string, transportConfig?: SatyaTransportConfig): Promise<VerifyResult>;
38
46
  coreVersion(): string;
39
47
  dispose(): Promise<void>;
40
48
  }