@authon/react-native-wallet 0.0.0-stage → 0.4.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 (183) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +251 -2
  3. package/android/build.gradle +40 -0
  4. package/android/consumer-rules.pro +10 -0
  5. package/android/src/main/AndroidManifest.xml +8 -0
  6. package/android/src/main/java/dev/authon/reactnative/wallet/AuthonWalletModule.kt +235 -0
  7. package/android/src/vendor/GENERATED.md +4 -0
  8. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/AndroidCapabilityProbe.kt +86 -0
  9. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/AuthonWallet.kt +218 -0
  10. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Bitcoin.kt +296 -0
  11. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Bytes.kt +74 -0
  12. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Chains.kt +157 -0
  13. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/CredentialManagerPasskeys.kt +192 -0
  14. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Der.kt +17 -0
  15. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/DeviceStore.kt +170 -0
  16. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/DialogWalletApprover.kt +141 -0
  17. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Eip712.kt +176 -0
  18. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Encodings.kt +133 -0
  19. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/EvmAccount.kt +45 -0
  20. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/EvmTransaction.kt +180 -0
  21. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/GfTables.kt +44 -0
  22. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/HdWallet.kt +130 -0
  23. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Http.kt +86 -0
  24. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/JsValues.kt +83 -0
  25. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/KeystoreDeviceStore.kt +236 -0
  26. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Prf.kt +111 -0
  27. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Proof.kt +134 -0
  28. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Shamir.kt +106 -0
  29. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/SignSummary.kt +335 -0
  30. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Solana.kt +195 -0
  31. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Tron.kt +184 -0
  32. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletApi.kt +361 -0
  33. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletCapabilities.kt +91 -0
  34. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletError.kt +74 -0
  35. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletOps.kt +551 -0
  36. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletTypes.kt +78 -0
  37. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Xrp.kt +193 -0
  38. package/build/authonContext.d.ts +19 -0
  39. package/build/authonContext.d.ts.map +1 -0
  40. package/build/authonContext.js +20 -0
  41. package/build/authonContext.js.map +1 -0
  42. package/build/client.d.ts +92 -0
  43. package/build/client.d.ts.map +1 -0
  44. package/build/client.js +431 -0
  45. package/build/client.js.map +1 -0
  46. package/build/errors.d.ts +25 -0
  47. package/build/errors.d.ts.map +1 -0
  48. package/build/errors.js +15 -0
  49. package/build/errors.js.map +1 -0
  50. package/build/index.d.ts +7 -0
  51. package/build/index.d.ts.map +1 -0
  52. package/build/index.js +4 -0
  53. package/build/index.js.map +1 -0
  54. package/build/native.d.ts +43 -0
  55. package/build/native.d.ts.map +1 -0
  56. package/build/native.js +12 -0
  57. package/build/native.js.map +1 -0
  58. package/build/types.d.ts +167 -0
  59. package/build/types.d.ts.map +1 -0
  60. package/build/types.js +3 -0
  61. package/build/types.js.map +1 -0
  62. package/build/useAuthonWallet.d.ts +25 -0
  63. package/build/useAuthonWallet.d.ts.map +1 -0
  64. package/build/useAuthonWallet.js +77 -0
  65. package/build/useAuthonWallet.js.map +1 -0
  66. package/expo-module.config.json +9 -0
  67. package/ios/AuthonReactNativeWallet.podspec +41 -0
  68. package/ios/AuthonWalletModule.swift +233 -0
  69. package/ios/vendor/AuthonWallet/AuthonWallet.swift +232 -0
  70. package/ios/vendor/AuthonWallet/Capabilities.swift +149 -0
  71. package/ios/vendor/AuthonWallet/Chains/Bitcoin.swift +296 -0
  72. package/ios/vendor/AuthonWallet/Chains/Solana.swift +207 -0
  73. package/ios/vendor/AuthonWallet/Chains/Tron.swift +181 -0
  74. package/ios/vendor/AuthonWallet/Chains/XRP.swift +191 -0
  75. package/ios/vendor/AuthonWallet/Crypto/BIP39Wordlist.swift +262 -0
  76. package/ios/vendor/AuthonWallet/Crypto/Chains.swift +278 -0
  77. package/ios/vendor/AuthonWallet/Crypto/HDKey.swift +108 -0
  78. package/ios/vendor/AuthonWallet/Crypto/Secp256k1.swift +128 -0
  79. package/ios/vendor/AuthonWallet/Crypto/Shamir.swift +123 -0
  80. package/ios/vendor/AuthonWallet/EVM/Address.swift +57 -0
  81. package/ios/vendor/AuthonWallet/EVM/EvmAccount.swift +48 -0
  82. package/ios/vendor/AuthonWallet/EVM/Transaction.swift +165 -0
  83. package/ios/vendor/AuthonWallet/EVM/TypedData.swift +193 -0
  84. package/ios/vendor/AuthonWallet/Support/BigInteger.swift +220 -0
  85. package/ios/vendor/AuthonWallet/Support/Bytes.swift +89 -0
  86. package/ios/vendor/AuthonWallet/Support/JSONValue.swift +341 -0
  87. package/ios/vendor/AuthonWallet/UI/WalletApprover.swift +250 -0
  88. package/ios/vendor/AuthonWallet/Wallet/Backup.swift +69 -0
  89. package/ios/vendor/AuthonWallet/Wallet/DeviceStore.swift +351 -0
  90. package/ios/vendor/AuthonWallet/Wallet/Passkeys.swift +190 -0
  91. package/ios/vendor/AuthonWallet/Wallet/Proof.swift +143 -0
  92. package/ios/vendor/AuthonWallet/Wallet/Shares.swift +116 -0
  93. package/ios/vendor/AuthonWallet/Wallet/Summary.swift +342 -0
  94. package/ios/vendor/AuthonWallet/Wallet/WalletAPI.swift +361 -0
  95. package/ios/vendor/AuthonWallet/Wallet/WalletOps.swift +654 -0
  96. package/ios/vendor/AuthonWallet/WalletError.swift +84 -0
  97. package/ios/vendor/CKeccak/LICENSE +22 -0
  98. package/ios/vendor/CKeccak/README.md +30 -0
  99. package/ios/vendor/CKeccak/byte_order.h +59 -0
  100. package/ios/vendor/CKeccak/include/ckeccak.h +13 -0
  101. package/ios/vendor/CKeccak/memzero.c +75 -0
  102. package/ios/vendor/CKeccak/memzero.h +8 -0
  103. package/ios/vendor/CKeccak/options.h +99 -0
  104. package/ios/vendor/CKeccak/ripemd160.c +343 -0
  105. package/ios/vendor/CKeccak/ripemd160.h +22 -0
  106. package/ios/vendor/CKeccak/sha3.c +413 -0
  107. package/ios/vendor/CKeccak/sha3.h +89 -0
  108. package/ios/vendor/CMonocypher/LICENCE.md +173 -0
  109. package/ios/vendor/CMonocypher/README.md +27 -0
  110. package/ios/vendor/CMonocypher/include/monocypher-ed25519.h +140 -0
  111. package/ios/vendor/CMonocypher/include/monocypher.h +321 -0
  112. package/ios/vendor/CMonocypher/monocypher-ed25519.c +500 -0
  113. package/ios/vendor/CMonocypher/monocypher.c +2956 -0
  114. package/ios/vendor/CSecp256k1/COPYING +19 -0
  115. package/ios/vendor/CSecp256k1/README.md +35 -0
  116. package/ios/vendor/CSecp256k1/build/precomputed_ecmult.c +3 -0
  117. package/ios/vendor/CSecp256k1/build/precomputed_ecmult_gen.c +3 -0
  118. package/ios/vendor/CSecp256k1/build/secp256k1.c +3 -0
  119. package/ios/vendor/CSecp256k1/include/secp256k1.h +899 -0
  120. package/ios/vendor/CSecp256k1/include/secp256k1_preallocated.h +134 -0
  121. package/ios/vendor/CSecp256k1/include/secp256k1_recovery.h +113 -0
  122. package/ios/vendor/CSecp256k1/src/assumptions.h +87 -0
  123. package/ios/vendor/CSecp256k1/src/checkmem.h +102 -0
  124. package/ios/vendor/CSecp256k1/src/ecdsa.h +21 -0
  125. package/ios/vendor/CSecp256k1/src/ecdsa_impl.h +304 -0
  126. package/ios/vendor/CSecp256k1/src/eckey.h +25 -0
  127. package/ios/vendor/CSecp256k1/src/eckey_impl.h +92 -0
  128. package/ios/vendor/CSecp256k1/src/ecmult.h +61 -0
  129. package/ios/vendor/CSecp256k1/src/ecmult_const.h +38 -0
  130. package/ios/vendor/CSecp256k1/src/ecmult_const_impl.h +399 -0
  131. package/ios/vendor/CSecp256k1/src/ecmult_gen.h +143 -0
  132. package/ios/vendor/CSecp256k1/src/ecmult_gen_impl.h +341 -0
  133. package/ios/vendor/CSecp256k1/src/ecmult_impl.h +853 -0
  134. package/ios/vendor/CSecp256k1/src/field.h +350 -0
  135. package/ios/vendor/CSecp256k1/src/field_10x26.h +57 -0
  136. package/ios/vendor/CSecp256k1/src/field_10x26_impl.h +1232 -0
  137. package/ios/vendor/CSecp256k1/src/field_5x52.h +62 -0
  138. package/ios/vendor/CSecp256k1/src/field_5x52_impl.h +522 -0
  139. package/ios/vendor/CSecp256k1/src/field_5x52_int128_impl.h +274 -0
  140. package/ios/vendor/CSecp256k1/src/field_impl.h +457 -0
  141. package/ios/vendor/CSecp256k1/src/group.h +212 -0
  142. package/ios/vendor/CSecp256k1/src/group_impl.h +974 -0
  143. package/ios/vendor/CSecp256k1/src/hash.h +44 -0
  144. package/ios/vendor/CSecp256k1/src/hash_impl.h +299 -0
  145. package/ios/vendor/CSecp256k1/src/hsort.h +33 -0
  146. package/ios/vendor/CSecp256k1/src/hsort_impl.h +125 -0
  147. package/ios/vendor/CSecp256k1/src/int128.h +90 -0
  148. package/ios/vendor/CSecp256k1/src/int128_impl.h +18 -0
  149. package/ios/vendor/CSecp256k1/src/int128_native.h +19 -0
  150. package/ios/vendor/CSecp256k1/src/int128_native_impl.h +94 -0
  151. package/ios/vendor/CSecp256k1/src/int128_struct.h +14 -0
  152. package/ios/vendor/CSecp256k1/src/int128_struct_impl.h +205 -0
  153. package/ios/vendor/CSecp256k1/src/modinv32.h +43 -0
  154. package/ios/vendor/CSecp256k1/src/modinv32_impl.h +725 -0
  155. package/ios/vendor/CSecp256k1/src/modinv64.h +47 -0
  156. package/ios/vendor/CSecp256k1/src/modinv64_impl.h +780 -0
  157. package/ios/vendor/CSecp256k1/src/modules/recovery/main_impl.h +159 -0
  158. package/ios/vendor/CSecp256k1/src/precomputed_ecmult.c +16456 -0
  159. package/ios/vendor/CSecp256k1/src/precomputed_ecmult.h +38 -0
  160. package/ios/vendor/CSecp256k1/src/precomputed_ecmult_gen.c +1779 -0
  161. package/ios/vendor/CSecp256k1/src/precomputed_ecmult_gen.h +26 -0
  162. package/ios/vendor/CSecp256k1/src/scalar.h +105 -0
  163. package/ios/vendor/CSecp256k1/src/scalar_4x64.h +19 -0
  164. package/ios/vendor/CSecp256k1/src/scalar_4x64_impl.h +1000 -0
  165. package/ios/vendor/CSecp256k1/src/scalar_8x32.h +19 -0
  166. package/ios/vendor/CSecp256k1/src/scalar_8x32_impl.h +816 -0
  167. package/ios/vendor/CSecp256k1/src/scalar_impl.h +321 -0
  168. package/ios/vendor/CSecp256k1/src/scalar_low.h +24 -0
  169. package/ios/vendor/CSecp256k1/src/scalar_low_impl.h +206 -0
  170. package/ios/vendor/CSecp256k1/src/scratch.h +44 -0
  171. package/ios/vendor/CSecp256k1/src/scratch_impl.h +99 -0
  172. package/ios/vendor/CSecp256k1/src/secp256k1.c +831 -0
  173. package/ios/vendor/CSecp256k1/src/selftest.h +32 -0
  174. package/ios/vendor/CSecp256k1/src/util.h +451 -0
  175. package/ios/vendor/GENERATED.md +4 -0
  176. package/package.json +66 -3
  177. package/src/authonContext.ts +30 -0
  178. package/src/client.ts +459 -0
  179. package/src/errors.ts +58 -0
  180. package/src/index.ts +26 -0
  181. package/src/native.ts +52 -0
  182. package/src/types.ts +171 -0
  183. package/src/useAuthonWallet.ts +103 -0
@@ -0,0 +1,341 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonWallet/Support/JSONValue.swift. Do not edit: change the source and run the script.
2
+ import Foundation
3
+
4
+ /// JSON with object key order and number literals preserved. Typed data and transactions
5
+ /// arrive as JSON text or Foundation values; JavaScript type checks (`typeof x === 'number'`)
6
+ /// in the frame map to the cases here.
7
+ public indirect enum JSONValue: Equatable, Sendable {
8
+ case null
9
+ case bool(Bool)
10
+ /// The number literal as written (no precision loss for large integers).
11
+ case number(String)
12
+ case string(String)
13
+ case array([JSONValue])
14
+ case object(JSONObject)
15
+
16
+ public subscript(key: String) -> JSONValue? {
17
+ if case .object(let o) = self { return o[key] }
18
+ return nil
19
+ }
20
+
21
+ var stringValue: String? {
22
+ if case .string(let s) = self { return s }
23
+ return nil
24
+ }
25
+
26
+ var objectValue: JSONObject? {
27
+ if case .object(let o) = self { return o }
28
+ return nil
29
+ }
30
+
31
+ var arrayValue: [JSONValue]? {
32
+ if case .array(let a) = self { return a }
33
+ return nil
34
+ }
35
+
36
+ /// JavaScript truthiness.
37
+ var isTruthy: Bool {
38
+ switch self {
39
+ case .null: return false
40
+ case .bool(let b): return b
41
+ case .number(let n): return (Double(n) ?? 0) != 0
42
+ case .string(let s): return !s.isEmpty
43
+ case .array, .object: return true
44
+ }
45
+ }
46
+
47
+ /// `BigInt(value)` for numbers (integral only), strings and booleans.
48
+ var jsBigInt: BigInteger? {
49
+ switch self {
50
+ case .number(let literal): return JSONValue.integer(literal)
51
+ case .string(let s): return BigInteger(jsString: s)
52
+ case .bool(let b): return BigInteger(BigUInt(b ? 1 : 0))
53
+ default: return nil
54
+ }
55
+ }
56
+
57
+ static func integer(_ literal: String) -> BigInteger? {
58
+ if let exact = BigInteger(jsString: literal), !literal.isEmpty, !literal.lowercased().hasPrefix("0x") { return exact }
59
+ guard let d = Double(literal), d.isFinite, d.rounded() == d else { return nil }
60
+ let text = String(format: "%.0f", d.magnitude)
61
+ guard let mag = BigUInt(decimal: text) else { return nil }
62
+ return BigInteger(mag, negative: d < 0)
63
+ }
64
+
65
+ // MARK: - Conversion
66
+
67
+ public static func parse(_ text: String) throws -> JSONValue {
68
+ var parser = JSONParser(Array(text.utf8))
69
+ let v = try parser.parseDocument()
70
+ return v
71
+ }
72
+
73
+ /// Converts Foundation values ([String: Any], [Any], NSNumber, String, NSNull). Dictionary keys are sorted.
74
+ public static func from(_ value: Any?) throws -> JSONValue {
75
+ guard let value else { return .null }
76
+ if let v = value as? JSONValue { return v }
77
+ if value is NSNull { return .null }
78
+ if let s = value as? String { return .string(s) }
79
+ // NSNumber first: a bridged 1 would also cast to Bool.
80
+ if let n = value as? NSNumber {
81
+ if CFGetTypeID(n) == CFBooleanGetTypeID() { return .bool(n.boolValue) }
82
+ let type = String(cString: n.objCType)
83
+ if type == "q" || type == "l" || type == "i" || type == "s" || type == "c" { return .number(String(n.int64Value)) }
84
+ if type == "Q" || type == "L" || type == "I" || type == "S" || type == "C" { return .number(String(n.uint64Value)) }
85
+ let d = n.doubleValue
86
+ guard d.isFinite else { throw WalletError(.invalidRequest, "non-finite number") }
87
+ if d.rounded() == d, d.magnitude < 1e18 { return .number(String(Int64(d))) }
88
+ return .number(String(d))
89
+ }
90
+ if let a = value as? [Any?] { return .array(try a.map { try from($0) }) }
91
+ if let a = value as? [Any] { return .array(try a.map { try from($0) }) }
92
+ if let d = value as? [String: Any?] {
93
+ return .object(JSONObject(try d.keys.sorted().map { ($0, try from(d[$0] ?? nil)) }))
94
+ }
95
+ if let d = value as? [String: Any] {
96
+ return .object(JSONObject(try d.keys.sorted().map { ($0, try from(d[$0])) }))
97
+ }
98
+ throw WalletError(.invalidRequest, "unsupported value \(type(of: value))")
99
+ }
100
+
101
+ /// `JSON.stringify(value, null, 2)`.
102
+ func pretty(indent: Int = 0) -> String {
103
+ let pad = String(repeating: " ", count: indent + 1)
104
+ let close = String(repeating: " ", count: indent)
105
+ switch self {
106
+ case .null: return "null"
107
+ case .bool(let b): return b ? "true" : "false"
108
+ case .number(let n): return n
109
+ case .string(let s): return JSONValue.quote(s)
110
+ case .array(let a):
111
+ if a.isEmpty { return "[]" }
112
+ return "[\n" + a.map { pad + $0.pretty(indent: indent + 1) }.joined(separator: ",\n") + "\n" + close + "]"
113
+ case .object(let o):
114
+ if o.entries.isEmpty { return "{}" }
115
+ return "{\n" + o.entries.map { pad + JSONValue.quote($0.key) + ": " + $0.value.pretty(indent: indent + 1) }
116
+ .joined(separator: ",\n") + "\n" + close + "}"
117
+ }
118
+ }
119
+
120
+ /// `JSON.stringify(value)`.
121
+ func compact() -> String {
122
+ switch self {
123
+ case .null, .bool, .number, .string: return pretty()
124
+ case .array(let a): return "[" + a.map { $0.compact() }.joined(separator: ",") + "]"
125
+ case .object(let o): return "{" + o.entries.map { JSONValue.quote($0.key) + ":" + $0.value.compact() }.joined(separator: ",") + "}"
126
+ }
127
+ }
128
+
129
+ static func quote(_ s: String) -> String {
130
+ var out = "\""
131
+ for u in s.unicodeScalars {
132
+ switch u {
133
+ case "\"": out += "\\\""
134
+ case "\\": out += "\\\\"
135
+ case "\n": out += "\\n"
136
+ case "\r": out += "\\r"
137
+ case "\t": out += "\\t"
138
+ case "\u{08}": out += "\\b"
139
+ case "\u{0C}": out += "\\f"
140
+ default:
141
+ if u.value < 0x20 { out += String(format: "\\u%04x", u.value) } else { out.unicodeScalars.append(u) }
142
+ }
143
+ }
144
+ return out + "\""
145
+ }
146
+ }
147
+
148
+ public struct JSONObject: Equatable, Sendable {
149
+ public var entries: [(key: String, value: JSONValue)]
150
+
151
+ public init(_ entries: [(String, JSONValue)] = []) {
152
+ self.entries = entries.map { (key: $0.0, value: $0.1) }
153
+ }
154
+
155
+ public subscript(key: String) -> JSONValue? {
156
+ get { entries.last(where: { $0.key == key })?.value }
157
+ set {
158
+ if let i = entries.firstIndex(where: { $0.key == key }) {
159
+ if let newValue { entries[i].value = newValue } else { entries.remove(at: i) }
160
+ } else if let newValue {
161
+ entries.append((key: key, value: newValue))
162
+ }
163
+ }
164
+ }
165
+
166
+ public var keys: [String] { entries.map(\.key) }
167
+
168
+ public static func == (a: JSONObject, b: JSONObject) -> Bool {
169
+ a.entries.count == b.entries.count && zip(a.entries, b.entries).allSatisfy { $0.key == $1.key && $0.value == $1.value }
170
+ }
171
+ }
172
+
173
+ private struct JSONParser {
174
+ let s: [UInt8]
175
+ var i = 0
176
+ var depth = 0
177
+
178
+ init(_ s: [UInt8]) { self.s = s }
179
+
180
+ private func fail() -> WalletError { WalletError(.invalidRequest, "invalid JSON") }
181
+
182
+ mutating func parseDocument() throws -> JSONValue {
183
+ let v = try value()
184
+ skip()
185
+ guard i == s.count else { throw fail() }
186
+ return v
187
+ }
188
+
189
+ mutating func skip() {
190
+ while i < s.count, s[i] == 0x20 || s[i] == 0x0a || s[i] == 0x0d || s[i] == 0x09 { i += 1 }
191
+ }
192
+
193
+ mutating func value() throws -> JSONValue {
194
+ skip()
195
+ guard i < s.count else { throw fail() }
196
+ switch s[i] {
197
+ case UInt8(ascii: "{"): return try object()
198
+ case UInt8(ascii: "["): return try array()
199
+ case UInt8(ascii: "\""): return .string(try string())
200
+ case UInt8(ascii: "t"): try literal("true"); return .bool(true)
201
+ case UInt8(ascii: "f"): try literal("false"); return .bool(false)
202
+ case UInt8(ascii: "n"): try literal("null"); return .null
203
+ default: return try number()
204
+ }
205
+ }
206
+
207
+ mutating func literal(_ word: String) throws {
208
+ let w = Array(word.utf8)
209
+ guard i + w.count <= s.count, Array(s[i..<i + w.count]) == w else { throw fail() }
210
+ i += w.count
211
+ }
212
+
213
+ mutating func object() throws -> JSONValue {
214
+ depth += 1
215
+ guard depth < 64 else { throw fail() }
216
+ defer { depth -= 1 }
217
+ i += 1
218
+ var obj = JSONObject()
219
+ skip()
220
+ if i < s.count, s[i] == UInt8(ascii: "}") { i += 1; return .object(obj) }
221
+ while true {
222
+ skip()
223
+ guard i < s.count, s[i] == UInt8(ascii: "\"") else { throw fail() }
224
+ let k = try string()
225
+ skip()
226
+ guard i < s.count, s[i] == UInt8(ascii: ":") else { throw fail() }
227
+ i += 1
228
+ let v = try value()
229
+ // JSON.parse keeps the first position and the last value of a duplicate key.
230
+ obj[k] = v
231
+ skip()
232
+ guard i < s.count else { throw fail() }
233
+ if s[i] == UInt8(ascii: ",") { i += 1; continue }
234
+ if s[i] == UInt8(ascii: "}") { i += 1; return .object(obj) }
235
+ throw fail()
236
+ }
237
+ }
238
+
239
+ mutating func array() throws -> JSONValue {
240
+ depth += 1
241
+ guard depth < 64 else { throw fail() }
242
+ defer { depth -= 1 }
243
+ i += 1
244
+ var out = [JSONValue]()
245
+ skip()
246
+ if i < s.count, s[i] == UInt8(ascii: "]") { i += 1; return .array(out) }
247
+ while true {
248
+ out.append(try value())
249
+ skip()
250
+ guard i < s.count else { throw fail() }
251
+ if s[i] == UInt8(ascii: ",") { i += 1; continue }
252
+ if s[i] == UInt8(ascii: "]") { i += 1; return .array(out) }
253
+ throw fail()
254
+ }
255
+ }
256
+
257
+ mutating func string() throws -> String {
258
+ i += 1
259
+ var scalars = String.UnicodeScalarView()
260
+ var raw = [UInt8]()
261
+ func flush() throws {
262
+ if raw.isEmpty { return }
263
+ guard let str = String(bytes: raw, encoding: .utf8) else { throw fail() }
264
+ scalars.append(contentsOf: str.unicodeScalars)
265
+ raw.removeAll()
266
+ }
267
+ while i < s.count {
268
+ let c = s[i]
269
+ if c == UInt8(ascii: "\"") {
270
+ i += 1
271
+ try flush()
272
+ return String(scalars)
273
+ }
274
+ if c < 0x20 { throw fail() }
275
+ if c == UInt8(ascii: "\\") {
276
+ try flush()
277
+ i += 1
278
+ guard i < s.count else { throw fail() }
279
+ let e = s[i]
280
+ i += 1
281
+ switch e {
282
+ case UInt8(ascii: "\""): scalars.append("\"")
283
+ case UInt8(ascii: "\\"): scalars.append("\\")
284
+ case UInt8(ascii: "/"): scalars.append("/")
285
+ case UInt8(ascii: "b"): scalars.append("\u{08}")
286
+ case UInt8(ascii: "f"): scalars.append("\u{0C}")
287
+ case UInt8(ascii: "n"): scalars.append("\n")
288
+ case UInt8(ascii: "r"): scalars.append("\r")
289
+ case UInt8(ascii: "t"): scalars.append("\t")
290
+ case UInt8(ascii: "u"):
291
+ var unit = try hex4()
292
+ if (0xD800..<0xDC00).contains(unit), i + 1 < s.count, s[i] == UInt8(ascii: "\\"), s[i + 1] == UInt8(ascii: "u") {
293
+ let save = i
294
+ i += 2
295
+ let low = try hex4()
296
+ if (0xDC00..<0xE000).contains(low) {
297
+ unit = 0x10000 + ((unit - 0xD800) << 10) + (low - 0xDC00)
298
+ } else {
299
+ i = save
300
+ }
301
+ }
302
+ // Lone surrogates become U+FFFD (JavaScript keeps them; Swift strings cannot).
303
+ scalars.append(Unicode.Scalar(unit) ?? "\u{FFFD}")
304
+ default: throw fail()
305
+ }
306
+ continue
307
+ }
308
+ raw.append(c)
309
+ i += 1
310
+ }
311
+ throw fail()
312
+ }
313
+
314
+ mutating func hex4() throws -> UInt32 {
315
+ guard i + 4 <= s.count, let v = UInt32(String(decoding: s[i..<i + 4], as: UTF8.self), radix: 16) else { throw fail() }
316
+ i += 4
317
+ return v
318
+ }
319
+
320
+ mutating func number() throws -> JSONValue {
321
+ let start = i
322
+ if i < s.count, s[i] == UInt8(ascii: "-") { i += 1 }
323
+ let intStart = i
324
+ while i < s.count, (0x30...0x39).contains(s[i]) { i += 1 }
325
+ guard i > intStart, !(s[intStart] == 0x30 && i - intStart > 1) else { throw fail() }
326
+ if i < s.count, s[i] == UInt8(ascii: ".") {
327
+ i += 1
328
+ let f = i
329
+ while i < s.count, (0x30...0x39).contains(s[i]) { i += 1 }
330
+ guard i > f else { throw fail() }
331
+ }
332
+ if i < s.count, s[i] == UInt8(ascii: "e") || s[i] == UInt8(ascii: "E") {
333
+ i += 1
334
+ if i < s.count, s[i] == UInt8(ascii: "+") || s[i] == UInt8(ascii: "-") { i += 1 }
335
+ let e = i
336
+ while i < s.count, (0x30...0x39).contains(s[i]) { i += 1 }
337
+ guard i > e else { throw fail() }
338
+ }
339
+ return .number(String(decoding: s[start..<i], as: UTF8.self))
340
+ }
341
+ }
@@ -0,0 +1,250 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonWallet/UI/WalletApprover.swift. Do not edit: change the source and run the script.
2
+ import Foundation
3
+ import LocalAuthentication
4
+ import SwiftUI
5
+ #if canImport(UIKit)
6
+ import UIKit
7
+ #elseif canImport(AppKit)
8
+ import AppKit
9
+ #endif
10
+
11
+ /// Passkey operations an approver may confirm before the system passkey sheet appears.
12
+ public enum PasskeyAction: String, Sendable {
13
+ case protect
14
+ case upgradeBackup
15
+ case restore
16
+ /// Restore that revokes every other device.
17
+ case restoreReset
18
+ }
19
+
20
+ /// Approves signatures. It runs before the company share is requested, so a refused request
21
+ /// never assembles the key. Apps can replace the default sheet; the app is inside the trust
22
+ /// boundary anyway (docs/wallet/NATIVE.md section 2).
23
+ public protocol WalletApprover: Sendable {
24
+ /// Return to approve; throw (USER_CANCELLED) to refuse.
25
+ func approveSignature(_ summary: SignSummary) async throws
26
+ /// Return to continue; throw (USER_CANCELLED) to stop.
27
+ func confirmPasskeyAction(_ action: PasskeyAction) async throws
28
+ }
29
+
30
+ extension WalletApprover {
31
+ public func confirmPasskeyAction(_ action: PasskeyAction) async throws {}
32
+ }
33
+
34
+ /// Extra device-owner check (Face ID, Touch ID or passcode) before each signature.
35
+ public protocol DeviceAuthenticator: Sendable {
36
+ func authenticate(reason: String) async throws
37
+ }
38
+
39
+ public struct LocalDeviceAuthenticator: DeviceAuthenticator {
40
+ public init() {}
41
+
42
+ public func authenticate(reason: String) async throws {
43
+ let context = LAContext()
44
+ var error: NSError?
45
+ guard context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) else {
46
+ throw WalletError(.forbidden, "device authentication is not available")
47
+ }
48
+ let ok: Bool
49
+ do {
50
+ ok = try await context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason)
51
+ } catch {
52
+ throw WalletError(.userCancelled)
53
+ }
54
+ if !ok { throw WalletError(.userCancelled) }
55
+ }
56
+ }
57
+
58
+ /// The default approver: a SwiftUI sheet over the key window. It never waits for a sheet
59
+ /// that is not on screen: without a window to present from, or when the sheet has not
60
+ /// appeared within `appearTimeout`, the request fails with CONFIRM_WINDOW_BLOCKED (nothing
61
+ /// is signed). Rejecting in the sheet is USER_CANCELLED.
62
+ public struct SheetWalletApprover: WalletApprover {
63
+ #if canImport(UIKit)
64
+ typealias Presenter = @MainActor @Sendable () -> UIViewController?
65
+ #else
66
+ typealias Presenter = @MainActor @Sendable () -> NSWindow?
67
+ #endif
68
+
69
+ let appearTimeout: TimeInterval
70
+ let presenter: Presenter
71
+
72
+ public init(appearTimeout: TimeInterval = 2) {
73
+ self.init(appearTimeout: appearTimeout, presenter: { Self.defaultPresenter() })
74
+ }
75
+
76
+ init(appearTimeout: TimeInterval, presenter: @escaping Presenter) {
77
+ self.appearTimeout = appearTimeout
78
+ self.presenter = presenter
79
+ }
80
+
81
+ enum Outcome { case approved, rejected, blocked }
82
+
83
+ private static func check(_ outcome: Outcome) throws {
84
+ switch outcome {
85
+ case .approved: return
86
+ case .rejected: throw WalletError(.userCancelled)
87
+ case .blocked: throw WalletError(.confirmWindowBlocked, "the approval sheet could not be shown")
88
+ }
89
+ }
90
+
91
+ public func approveSignature(_ summary: SignSummary) async throws {
92
+ try Self.check(await present { finish in ApprovalView(summary: summary, onFinish: finish) })
93
+ }
94
+
95
+ public func confirmPasskeyAction(_ action: PasskeyAction) async throws {
96
+ // The system passkey sheet is the confirmation for the other actions.
97
+ guard action == .restoreReset else { return }
98
+ try Self.check(await present { finish in ResetConfirmView(onFinish: finish) })
99
+ }
100
+
101
+ @MainActor
102
+ func present<V: View>(_ build: @escaping (@escaping (Bool) -> Void) -> V) async -> Outcome {
103
+ await withCheckedContinuation { (cont: CheckedContinuation<Outcome, Never>) in
104
+ var finished = false
105
+ var appeared = false
106
+ #if canImport(UIKit)
107
+ var host: UIViewController?
108
+ let complete: (Outcome) -> Void = { outcome in
109
+ guard !finished else { return }
110
+ finished = true
111
+ // Resume once the sheet is gone, so a following request can present its own
112
+ // sheet (presenting on a controller that is being dismissed silently fails).
113
+ guard let h = host, h.presentingViewController != nil else {
114
+ cont.resume(returning: outcome)
115
+ return
116
+ }
117
+ h.dismiss(animated: true) { cont.resume(returning: outcome) }
118
+ }
119
+ let controller = UIHostingController(rootView: build { complete($0 ? .approved : .rejected) })
120
+ controller.isModalInPresentation = true
121
+ host = controller
122
+ guard let top = presenter() else {
123
+ complete(.blocked)
124
+ return
125
+ }
126
+ top.present(controller, animated: true) { appeared = true }
127
+ let isOnScreen = { appeared || controller.viewIfLoaded?.window != nil }
128
+ #else
129
+ var sheet: NSWindow?
130
+ var parent: NSWindow?
131
+ let complete: (Outcome) -> Void = { outcome in
132
+ guard !finished else { return }
133
+ finished = true
134
+ if let sheet, sheet.sheetParent != nil { parent?.endSheet(sheet) }
135
+ cont.resume(returning: outcome)
136
+ }
137
+ guard let window = presenter() else {
138
+ complete(.blocked)
139
+ return
140
+ }
141
+ let w = NSWindow(contentViewController: NSHostingController(rootView: build { complete($0 ? .approved : .rejected) }.frame(minWidth: 420, minHeight: 360)))
142
+ sheet = w
143
+ parent = window
144
+ window.beginSheet(w)
145
+ let isOnScreen = { appeared || w.sheetParent != nil || w.isVisible }
146
+ #endif
147
+ DispatchQueue.main.asyncAfter(deadline: .now() + appearTimeout) {
148
+ if !finished && !isOnScreen() { complete(.blocked) }
149
+ }
150
+ }
151
+ }
152
+
153
+ #if canImport(UIKit)
154
+ @MainActor
155
+ static func defaultPresenter() -> UIViewController? {
156
+ let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
157
+ let window = scenes.flatMap(\.windows).first(where: \.isKeyWindow) ?? scenes.first?.windows.first
158
+ var top = window?.rootViewController
159
+ while let presented = top?.presentedViewController, !presented.isBeingDismissed { top = presented }
160
+ return top
161
+ }
162
+ #else
163
+ @MainActor
164
+ static func defaultPresenter() -> NSWindow? {
165
+ NSApplication.shared.keyWindow ?? NSApplication.shared.windows.first(where: \.isVisible)
166
+ }
167
+ #endif
168
+ }
169
+
170
+ struct ApprovalView: View {
171
+ let summary: SignSummary
172
+ let onFinish: (Bool) -> Void
173
+ @State private var readToEnd = false
174
+
175
+ var body: some View {
176
+ VStack(alignment: .leading, spacing: 16) {
177
+ Text(summary.title).font(.title2.bold())
178
+ ForEach(Array(summary.warnings.enumerated()), id: \.offset) { _, w in
179
+ Label(w, systemImage: "exclamationmark.triangle.fill")
180
+ .foregroundColor(.red)
181
+ .font(.callout)
182
+ }
183
+ VStack(alignment: .leading, spacing: 8) {
184
+ ForEach(Array(summary.rows.enumerated()), id: \.offset) { _, row in
185
+ VStack(alignment: .leading, spacing: 2) {
186
+ Text(row.label).font(.caption).foregroundColor(.secondary)
187
+ Text(row.value)
188
+ .font(row.mono ? .system(.footnote, design: .monospaced) : .body)
189
+ .textSelection(.enabled)
190
+ }
191
+ }
192
+ }
193
+ if let text = summary.body {
194
+ ScrollView {
195
+ LazyVStack(alignment: .leading, spacing: 0) {
196
+ Text(text)
197
+ .font(.system(.footnote, design: .monospaced))
198
+ .frame(maxWidth: .infinity, alignment: .leading)
199
+ .textSelection(.enabled)
200
+ Color.clear.frame(height: 1).onAppear { readToEnd = true }
201
+ }
202
+ .padding(8)
203
+ }
204
+ .frame(maxHeight: 280)
205
+ .background(Color.secondary.opacity(0.1))
206
+ .cornerRadius(8)
207
+ if !readToEnd {
208
+ Text("Scroll to the end to sign.").font(.caption).foregroundColor(.secondary)
209
+ }
210
+ }
211
+ Spacer(minLength: 0)
212
+ HStack(spacing: 12) {
213
+ Button(role: .cancel) { onFinish(false) } label: {
214
+ Text("Reject").frame(maxWidth: .infinity)
215
+ }
216
+ .buttonStyle(.bordered)
217
+ Button { onFinish(true) } label: {
218
+ Text("Sign").frame(maxWidth: .infinity)
219
+ }
220
+ .buttonStyle(.borderedProminent)
221
+ .tint(summary.warnings.isEmpty ? .accentColor : .red)
222
+ .disabled(summary.body != nil && !readToEnd)
223
+ }
224
+ .controlSize(.large)
225
+ }
226
+ .padding(20)
227
+ }
228
+ }
229
+
230
+ struct ResetConfirmView: View {
231
+ let onFinish: (Bool) -> Void
232
+
233
+ var body: some View {
234
+ VStack(alignment: .leading, spacing: 16) {
235
+ Text("Restore and sign out other devices").font(.title2.bold())
236
+ Text("Your wallet will be restored on this device with your passkey. Every other device and browser that holds this wallet will stop working and must restore again.")
237
+ .font(.body)
238
+ Spacer(minLength: 0)
239
+ HStack(spacing: 12) {
240
+ Button(role: .cancel) { onFinish(false) } label: { Text("Cancel").frame(maxWidth: .infinity) }
241
+ .buttonStyle(.bordered)
242
+ Button { onFinish(true) } label: { Text("Restore").frame(maxWidth: .infinity) }
243
+ .buttonStyle(.borderedProminent)
244
+ .tint(.red)
245
+ }
246
+ .controlSize(.large)
247
+ }
248
+ .padding(20)
249
+ }
250
+ }
@@ -0,0 +1,69 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonWallet/Wallet/Backup.swift. Do not edit: change the source and run the script.
2
+ import CryptoKit
3
+ import Foundation
4
+
5
+ /// Passkey backup encryption, byte-identical to `apps/wallet/src/prf.ts`.
6
+ public enum BackupVersion: Int, Sendable {
7
+ /// Salt SHA-256("authon-wallet-backup-v1"); ciphertext without prefix.
8
+ case v1 = 1
9
+ /// Salt bound to project and wallet; ciphertext stored as `v2.<base64>`.
10
+ case v2 = 2
11
+ }
12
+
13
+ enum WalletBackup {
14
+ static let saltLabelV1 = "authon-wallet-backup-v1"
15
+ static let saltLabelV2 = "authon-wallet-backup-v2"
16
+ static let hkdfInfo = "authon-wallet-backup-v1/aes-gcm-256"
17
+ static let v2Prefix = "v2."
18
+
19
+ static func passkeyUserId(projectId: String, userId: String) -> [UInt8] {
20
+ Bytes.sha256(Array("\(projectId):\(userId)".utf8))
21
+ }
22
+
23
+ static func prfSalt(_ version: BackupVersion, projectId: String, walletId: String) -> [UInt8] {
24
+ switch version {
25
+ case .v1: return Bytes.sha256(Array(saltLabelV1.utf8))
26
+ case .v2: return Bytes.sha256(Array("\(saltLabelV2)\u{0}\(projectId)\u{0}\(walletId)".utf8))
27
+ }
28
+ }
29
+
30
+ static func version(of payload: String) -> BackupVersion { payload.hasPrefix(v2Prefix) ? .v2 : .v1 }
31
+
32
+ /// HKDF-SHA256(ikm = PRF output, salt = empty, info) -> AES-256 key.
33
+ static func deriveKey(prfOutput: [UInt8]) -> SymmetricKey {
34
+ HKDF<SHA256>.deriveKey(
35
+ inputKeyMaterial: SymmetricKey(data: prfOutput),
36
+ salt: Data(),
37
+ info: Data(hkdfInfo.utf8),
38
+ outputByteCount: 32
39
+ )
40
+ }
41
+
42
+ private static func aad(_ walletId: String, _ version: BackupVersion) -> Data {
43
+ Data((version == .v1 ? "backup:\(walletId)" : "backup:v2:\(walletId)").utf8)
44
+ }
45
+
46
+ static func encrypt(key: SymmetricKey, share: [UInt8], walletId: String, version: BackupVersion, iv: [UInt8]? = nil) throws -> String {
47
+ let nonce = try AES.GCM.Nonce(data: iv ?? (try Bytes.random(12)))
48
+ let box = try AES.GCM.seal(share, using: key, nonce: nonce, authenticating: aad(walletId, version))
49
+ let body = Bytes.base64(Array(nonce) + Array(box.ciphertext) + Array(box.tag))
50
+ return version == .v1 ? body : v2Prefix + body
51
+ }
52
+
53
+ static func decrypt(key: SymmetricKey, payload: String, walletId: String) throws -> [UInt8] {
54
+ let version = version(of: payload)
55
+ guard let raw = Bytes.fromBase64(version == .v1 ? payload : String(payload.dropFirst(v2Prefix.count))), raw.count >= 28 else {
56
+ throw WalletError(.invalidRequest, "backup ciphertext malformed")
57
+ }
58
+ do {
59
+ let box = try AES.GCM.SealedBox(
60
+ nonce: AES.GCM.Nonce(data: raw[0..<12]),
61
+ ciphertext: raw[12..<(raw.count - 16)],
62
+ tag: raw[(raw.count - 16)...]
63
+ )
64
+ return Array(try AES.GCM.open(box, using: key, authenticating: aad(walletId, version)))
65
+ } catch {
66
+ throw WalletError(.invalidRequest, "backup cannot be decrypted with this passkey")
67
+ }
68
+ }
69
+ }