@qntx/wallet-native 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 (69) hide show
  1. package/README.md +123 -2
  2. package/WalletNative.podspec +74 -0
  3. package/android/CMakeLists.txt +99 -0
  4. package/android/build.gradle +177 -0
  5. package/android/consumer-rules.pro +4 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/cpp/cpp-adapter.cpp +29 -0
  8. package/android/src/main/java/com/margelo/nitro/qntxwallet/AppLifecycle.kt +35 -0
  9. package/android/src/main/java/com/margelo/nitro/qntxwallet/KernelPbkdf2.java +59 -0
  10. package/android/src/main/java/com/margelo/nitro/qntxwallet/Keystore.kt +280 -0
  11. package/android/src/main/java/com/margelo/nitro/qntxwallet/Passkey.kt +147 -0
  12. package/android/src/main/java/com/margelo/nitro/qntxwallet/VaultDirectory.kt +28 -0
  13. package/android/src/main/java/com/margelo/nitro/qntxwallet/WalletNativePackage.java +30 -0
  14. package/cpp/host/HybridKernelHost.cpp +162 -0
  15. package/cpp/host/HybridKernelHost.hpp +49 -0
  16. package/cpp/host/KernelFile.cpp +120 -0
  17. package/cpp/host/KernelFile.hpp +24 -0
  18. package/cpp/host/Keystore.hpp +70 -0
  19. package/cpp/host/Lifecycle.cpp +77 -0
  20. package/cpp/host/Lifecycle.hpp +71 -0
  21. package/cpp/host/Passkey.hpp +39 -0
  22. package/cpp/host/VaultDirectory.hpp +16 -0
  23. package/cpp/platform/KeystoreAndroid.cpp +175 -0
  24. package/cpp/platform/KeystoreIos.mm +300 -0
  25. package/cpp/platform/LifecycleAndroid.cpp +55 -0
  26. package/cpp/platform/LifecycleIos.mm +36 -0
  27. package/cpp/platform/PasskeyAndroid.cpp +118 -0
  28. package/cpp/platform/PasskeyIos.mm +20 -0
  29. package/cpp/platform/VaultDirectory.mm +58 -0
  30. package/cpp/platform/VaultDirectoryAndroid.cpp +35 -0
  31. package/cpp/runtime/KernelBackends.cpp +297 -0
  32. package/cpp/runtime/KernelBackends.hpp +20 -0
  33. package/cpp/runtime/KernelBuffer.cpp +12 -0
  34. package/cpp/runtime/KernelBuffer.hpp +96 -0
  35. package/cpp/runtime/KernelGlobals.cpp +535 -0
  36. package/cpp/runtime/KernelGlobals.hpp +107 -0
  37. package/cpp/runtime/KernelJs.cpp +155 -0
  38. package/cpp/runtime/KernelJs.hpp +81 -0
  39. package/cpp/runtime/KernelKeystore.cpp +224 -0
  40. package/cpp/runtime/KernelKeystore.hpp +28 -0
  41. package/cpp/runtime/KernelPasskey.cpp +137 -0
  42. package/cpp/runtime/KernelPasskey.hpp +23 -0
  43. package/cpp/runtime/KernelRuntime.cpp +496 -0
  44. package/cpp/runtime/KernelRuntime.hpp +94 -0
  45. package/cpp/runtime/platform/KernelPbkdf2.cpp +198 -0
  46. package/cpp/runtime/platform/KernelPbkdf2.hpp +38 -0
  47. package/dist/index.d.mts +44 -0
  48. package/dist/index.mjs +170 -0
  49. package/dist/index.mjs.map +1 -0
  50. package/kernel/generated/kernel-entry.js +42967 -0
  51. package/kernel/generated/kernel.manifest.json +80 -0
  52. package/nitrogen/generated/.gitattributes +1 -0
  53. package/nitrogen/generated/android/WalletNative+autolinking.cmake +81 -0
  54. package/nitrogen/generated/android/WalletNative+autolinking.gradle +27 -0
  55. package/nitrogen/generated/android/WalletNativeOnLoad.cpp +49 -0
  56. package/nitrogen/generated/android/WalletNativeOnLoad.hpp +34 -0
  57. package/nitrogen/generated/android/kotlin/com/margelo/nitro/qntxwallet/WalletNativeOnLoad.kt +35 -0
  58. package/nitrogen/generated/ios/WalletNative+autolinking.rb +62 -0
  59. package/nitrogen/generated/ios/WalletNative-Swift-Cxx-Bridge.cpp +17 -0
  60. package/nitrogen/generated/ios/WalletNative-Swift-Cxx-Bridge.hpp +27 -0
  61. package/nitrogen/generated/ios/WalletNative-Swift-Cxx-Umbrella.hpp +38 -0
  62. package/nitrogen/generated/ios/WalletNativeAutolinking.mm +35 -0
  63. package/nitrogen/generated/ios/WalletNativeAutolinking.swift +16 -0
  64. package/nitrogen/generated/shared/c++/HybridKernelHostSpec.cpp +25 -0
  65. package/nitrogen/generated/shared/c++/HybridKernelHostSpec.hpp +69 -0
  66. package/nitrogen/generated/shared/c++/KernelExitReason.hpp +84 -0
  67. package/package.json +61 -4
  68. package/react-native.config.js +14 -0
  69. package/scripts/compile-kernel.mjs +118 -0
@@ -0,0 +1,120 @@
1
+ #include "KernelFile.hpp"
2
+
3
+ #include <cerrno>
4
+ #include <cstring>
5
+ #include <stdexcept>
6
+
7
+ #if defined(_WIN32)
8
+ #error "KernelFile has no Windows implementation"
9
+ #else
10
+ #include <fcntl.h>
11
+ #include <sys/stat.h>
12
+ #include <sys/types.h>
13
+ #include <unistd.h>
14
+ #endif
15
+
16
+ namespace qntx {
17
+ namespace {
18
+
19
+ [[noreturn]] void fail(const char* what) {
20
+ const int saved = errno;
21
+ throw std::runtime_error(
22
+ std::string(what) + ": " + std::strerror(saved) + " (errno " + std::to_string(saved) + ")");
23
+ }
24
+
25
+ void fsyncOrFail(int fd, const char* what, bool full = false) {
26
+ // Files: on Apple the documented durable barrier is fcntl(F_FULLFSYNC).
27
+ // Directories: plain fsync everywhere (F_FULLFSYNC is for files only).
28
+ const int rc =
29
+ #if defined(__APPLE__)
30
+ full ? ::fcntl(fd, F_FULLFSYNC) : ::fsync(fd);
31
+ #else
32
+ (::fsync(fd));
33
+ #endif
34
+ if (rc != 0) {
35
+ fail(what);
36
+ }
37
+ }
38
+
39
+ std::string pathFor(const std::string& directory, const char* leaf) {
40
+ return directory + "/" + leaf;
41
+ }
42
+
43
+ } // namespace
44
+
45
+ std::optional<std::vector<uint8_t>> kernelFileRead(const std::string& directory) {
46
+ const std::string path = pathFor(directory, "vault.cbor");
47
+ const int fd = ::open(path.c_str(), O_RDONLY);
48
+ if (fd < 0) {
49
+ if (errno == ENOENT) {
50
+ return std::nullopt;
51
+ }
52
+ fail("open vault file");
53
+ }
54
+ std::vector<uint8_t> bytes;
55
+ uint8_t chunk[65536];
56
+ for (;;) {
57
+ const ssize_t n = ::read(fd, chunk, sizeof(chunk));
58
+ if (n < 0) {
59
+ const int saved = errno;
60
+ ::close(fd);
61
+ errno = saved;
62
+ fail("read vault file");
63
+ }
64
+ if (n == 0) {
65
+ break;
66
+ }
67
+ bytes.insert(bytes.end(), chunk, chunk + n);
68
+ }
69
+ ::close(fd);
70
+ return bytes;
71
+ }
72
+
73
+ void kernelFileWrite(const std::string& directory, const std::vector<uint8_t>& bytes) {
74
+ const std::string tmp = pathFor(directory, "vault.cbor.tmp");
75
+ const std::string final = pathFor(directory, "vault.cbor");
76
+ const int fd = ::open(tmp.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0600);
77
+ if (fd < 0) {
78
+ fail("open temp vault file");
79
+ }
80
+ size_t off = 0;
81
+ while (off < bytes.size()) {
82
+ const ssize_t n = ::write(fd, bytes.data() + off, bytes.size() - off);
83
+ if (n <= 0) {
84
+ const int saved = errno;
85
+ ::close(fd);
86
+ ::unlink(tmp.c_str());
87
+ errno = saved;
88
+ fail("write temp vault file");
89
+ }
90
+ off += static_cast<size_t>(n);
91
+ }
92
+ try {
93
+ fsyncOrFail(fd, "fsync temp vault file", /*full=*/true);
94
+ } catch (...) {
95
+ ::close(fd);
96
+ ::unlink(tmp.c_str());
97
+ throw;
98
+ }
99
+ if (::close(fd) != 0) {
100
+ ::unlink(tmp.c_str());
101
+ fail("close temp vault file");
102
+ }
103
+ if (::rename(tmp.c_str(), final.c_str()) != 0) {
104
+ ::unlink(tmp.c_str());
105
+ fail("rename vault file");
106
+ }
107
+ const int dirFd = ::open(directory.c_str(), O_RDONLY);
108
+ if (dirFd < 0) {
109
+ fail("open vault directory");
110
+ }
111
+ try {
112
+ fsyncOrFail(dirFd, "fsync vault directory");
113
+ } catch (...) {
114
+ ::close(dirFd);
115
+ throw;
116
+ }
117
+ ::close(dirFd);
118
+ }
119
+
120
+ } // namespace qntx
@@ -0,0 +1,24 @@
1
+ #pragma once
2
+
3
+ #include <optional>
4
+ #include <string>
5
+ #include <vector>
6
+
7
+ namespace qntx {
8
+
9
+ // The vault image file lives in a platform-provided directory
10
+ // (platformVaultDirectory()): <directory>/vault.cbor.
11
+ //
12
+ // The whole file, or nullopt when it does not exist. Throws std::runtime_error
13
+ // naming the failing step and errno — the file store turns that into a typed
14
+ // rejection and never silently treats it as an empty vault.
15
+ std::optional<std::vector<uint8_t>> kernelFileRead(const std::string& directory);
16
+
17
+ // Atomic replace of vault.cbor in `directory`: write vault.cbor.tmp in the
18
+ // same directory, fsync it, rename it over the target, then fsync the
19
+ // directory so the rename itself is durable. Throws std::runtime_error naming
20
+ // the failing step and errno; a failed write leaves the previous file intact
21
+ // and unlinks the temp file.
22
+ void kernelFileWrite(const std::string& directory, const std::vector<uint8_t>& bytes);
23
+
24
+ } // namespace qntx
@@ -0,0 +1,70 @@
1
+ #pragma once
2
+
3
+ #include <functional>
4
+ #include <string>
5
+ #include <vector>
6
+
7
+ #include "../runtime/KernelBuffer.hpp"
8
+
9
+ namespace qntx {
10
+
11
+ // The platform keystore behind `__qntx.keystore` (W5): a hardware key per
12
+ // vault factor that seals and opens the factor's KEK behind a system user
13
+ // verification. iOS: Secure Enclave P-256 + ECIES; Android: Keystore
14
+ // AES-256-GCM behind BiometricPrompt. Each function has exactly one strong
15
+ // definition per platform (KeystoreIos.mm, KeystoreAndroid.cpp); host tests
16
+ // link a fake.
17
+
18
+ struct KeystorePrompt {
19
+ std::string title;
20
+ std::string description;
21
+ };
22
+
23
+ // How a keystore call ended. The binding maps each status to the kernel's
24
+ // error code: cancelled → "cancelled", invalidated → "factor-invalidated",
25
+ // unavailable → "factor-unavailable", failed → "unavailable". The values are
26
+ // part of the Android JNI contract (Keystore.kt).
27
+ enum class KeystoreStatus { ok = 0, cancelled = 1, invalidated = 2, unavailable = 3, failed = 4 };
28
+
29
+ struct KeystoreResult {
30
+ KeystoreStatus status = KeystoreStatus::failed;
31
+ // seal: the platform ciphertext; open: the KEK; empty otherwise.
32
+ SecretBytes bytes;
33
+ };
34
+
35
+ // Completion callbacks run exactly once, on any thread.
36
+ using KeystoreDone = std::function<void(KeystoreResult)>;
37
+
38
+ // Creates the hardware key for vault factor `id` and encrypts `secret` under
39
+ // it. Android prompts (encryption needs the authenticated cipher); iOS does
40
+ // not (ECIES encrypts with the public key).
41
+ void keystoreSeal(
42
+ const std::string& id,
43
+ SecretBytes secret,
44
+ KeystorePrompt prompt,
45
+ KeystoreDone done);
46
+
47
+ // Decrypts `sealed` with factor `id`'s key after a fresh system verification
48
+ // that shows `prompt`. A key the system removed (lock screen disabled,
49
+ // device restored) completes `invalidated`.
50
+ void keystoreOpen(
51
+ const std::string& id,
52
+ std::vector<uint8_t> sealed,
53
+ KeystorePrompt prompt,
54
+ KeystoreDone done);
55
+
56
+ // A device-owner check with no key involved (vault reset, WD-055).
57
+ void keystoreAuthenticate(KeystorePrompt prompt, KeystoreDone done);
58
+
59
+ // Deletes every vault key this app created.
60
+ void keystoreClear(KeystoreDone done);
61
+
62
+ // Dismisses the system prompt of an in-flight call, if any; that call still
63
+ // completes (normally `cancelled`). Called when a kernel runtime tears down.
64
+ void keystoreCancel();
65
+
66
+ // The platform's preferred language as a BCP 47 tag, read natively so the
67
+ // main runtime cannot choose the prompt language.
68
+ std::string keystoreLocale();
69
+
70
+ } // namespace qntx
@@ -0,0 +1,77 @@
1
+ #include "Lifecycle.hpp"
2
+
3
+
4
+
5
+ namespace qntx {
6
+
7
+ KernelLifecycleDispatcher& KernelLifecycleDispatcher::shared() {
8
+ static KernelLifecycleDispatcher dispatcher;
9
+ return dispatcher;
10
+ }
11
+
12
+ void KernelLifecycleDispatcher::registerHost(
13
+ const std::weak_ptr<LifecycleLockTarget>& host, int seconds) {
14
+ std::lock_guard<std::mutex> lock(mutex_);
15
+ entries_.push_back(Entry{host, seconds, std::nullopt});
16
+ }
17
+
18
+ void KernelLifecycleDispatcher::unregister(const LifecycleLockTarget* host) {
19
+ std::lock_guard<std::mutex> lock(mutex_);
20
+ std::erase_if(entries_, [&](const Entry& entry) {
21
+ const auto target = entry.host.lock();
22
+ return !target || target.get() == host;
23
+ });
24
+ }
25
+
26
+ void KernelLifecycleDispatcher::onBackground() {
27
+ std::vector<std::shared_ptr<LifecycleLockTarget>> immediate;
28
+ {
29
+ std::lock_guard<std::mutex> lock(mutex_);
30
+ const auto now = clock_();
31
+ for (auto& entry : entries_) {
32
+ const auto host = entry.host.lock();
33
+ if (!host) {
34
+ continue;
35
+ }
36
+ if (entry.seconds == 0) {
37
+ immediate.push_back(host);
38
+ } else {
39
+ entry.backgroundAtMs = now;
40
+ }
41
+ }
42
+ }
43
+ // Lock outside the registry mutex — the target's own lock may re-enter here
44
+ // (unregister on exit).
45
+ for (const auto& host : immediate) {
46
+ host->lockForBackground();
47
+ }
48
+ }
49
+
50
+ void KernelLifecycleDispatcher::onForeground() {
51
+ std::vector<std::shared_ptr<LifecycleLockTarget>> expired;
52
+ {
53
+ std::lock_guard<std::mutex> lock(mutex_);
54
+ const auto now = clock_();
55
+ for (auto& entry : entries_) {
56
+ const auto host = entry.host.lock();
57
+ if (!host) {
58
+ continue;
59
+ }
60
+ if (entry.backgroundAtMs.has_value() &&
61
+ backgroundExpired(*entry.backgroundAtMs, now, entry.seconds)) {
62
+ expired.push_back(host);
63
+ }
64
+ entry.backgroundAtMs.reset();
65
+ }
66
+ }
67
+ for (const auto& host : expired) {
68
+ host->lockForBackground();
69
+ }
70
+ }
71
+
72
+ void KernelLifecycleDispatcher::setClock(std::function<int64_t()> clock) {
73
+ std::lock_guard<std::mutex> lock(mutex_);
74
+ clock_ = std::move(clock);
75
+ }
76
+
77
+ } // namespace qntx
@@ -0,0 +1,71 @@
1
+ #pragma once
2
+
3
+ #include <cstdint>
4
+ #include <functional>
5
+ #include <memory>
6
+ #include <optional>
7
+ #include <mutex>
8
+ #include <vector>
9
+
10
+ namespace qntx {
11
+
12
+ /**
13
+ * The background-expiry decision: a host whose lock deadline was
14
+ * `backgroundAtMs + seconds*1000` on the sleep-inclusive monotonic clock is
15
+ * expired when nowMs is past it. seconds==0 locks the moment the app
16
+ * backgrounds — the caller never reaches this function for it.
17
+ */
18
+ inline bool backgroundExpired(int64_t backgroundAtMs, int64_t nowMs, int seconds) {
19
+ return nowMs >= backgroundAtMs + static_cast<int64_t>(seconds) * 1000;
20
+ }
21
+
22
+ /** Milliseconds on the clock that keeps running while the device sleeps —
23
+ * Apple CLOCK_MONOTONIC, Android CLOCK_BOOTTIME. */
24
+ int64_t sleepInclusiveMonotonicMs();
25
+
26
+ /** What the platform's lifecycle hooks lock; HybridKernelHost implements it
27
+ * (exit(BACKGROUND)). The interface keeps the dispatcher testable without a
28
+ * Nitro runtime. */
29
+ class LifecycleLockTarget {
30
+ public:
31
+ virtual ~LifecycleLockTarget() = default;
32
+ virtual void lockForBackground() = 0;
33
+ };
34
+
35
+ /**
36
+ * One process-wide registry of live kernel hosts with their
37
+ * backgroundLockSeconds. `onBackground` stamps every registered host (or locks
38
+ * it immediately when its seconds is 0); `onForeground` locks the hosts whose
39
+ * deadline passed while backgrounded. All calls are main-thread safe and take
40
+ * weak_ptr so a destroyed host is simply skipped.
41
+ */
42
+ class KernelLifecycleDispatcher {
43
+ public:
44
+ static KernelLifecycleDispatcher& shared();
45
+
46
+ void registerHost(const std::weak_ptr<LifecycleLockTarget>& host, int seconds);
47
+ /** Drop the registration — called on every host exit. */
48
+ void unregister(const LifecycleLockTarget* host);
49
+
50
+ void onBackground();
51
+ void onForeground();
52
+
53
+ /** Test seam: the clock defaults to sleepInclusiveMonotonicMs. */
54
+ void setClock(std::function<int64_t()> clock);
55
+
56
+ private:
57
+ struct Entry {
58
+ std::weak_ptr<LifecycleLockTarget> host;
59
+ int seconds;
60
+ std::optional<int64_t> backgroundAtMs;
61
+ };
62
+ std::mutex mutex_;
63
+ std::vector<Entry> entries_;
64
+ std::function<int64_t()> clock_ = sleepInclusiveMonotonicMs;
65
+ };
66
+
67
+ /** Install the platform's app-lifecycle observer exactly once — NSNotificationCenter
68
+ * on Apple, ProcessLifecycleOwner on Android. Called from KernelHost.start. */
69
+ void installLifecycleObserver();
70
+
71
+ } // namespace qntx
@@ -0,0 +1,39 @@
1
+ #pragma once
2
+
3
+ #include <functional>
4
+ #include <string>
5
+
6
+ namespace qntx {
7
+
8
+ // The platform passkey authenticator behind `__qntx.passkey` (W6). The kernel
9
+ // builds the WebAuthn request JSON and parses the response JSON itself; the
10
+ // platform only runs the system ceremony. Android: Credential Manager. iOS:
11
+ // not yet available (W6c needs an associated domain) — every call completes
12
+ // `unavailable`. Each function has exactly one strong definition per platform
13
+ // (PasskeyAndroid.cpp, PasskeyIos.mm); host tests link a fake.
14
+
15
+ // The binding maps each status to the kernel's error code: cancelled →
16
+ // "cancelled", unavailable → "factor-unavailable", failed → "unavailable".
17
+ // The values are part of the Android JNI contract (Passkey.kt).
18
+ enum class PasskeyStatus { ok = 0, cancelled = 1, unavailable = 3, failed = 4 };
19
+
20
+ struct PasskeyResult {
21
+ PasskeyStatus status = PasskeyStatus::failed;
22
+ // The WebAuthn registration or authentication response JSON on success.
23
+ std::string responseJson;
24
+ };
25
+
26
+ // Completion callbacks run exactly once, on any thread.
27
+ using PasskeyDone = std::function<void(PasskeyResult)>;
28
+
29
+ // Registers a discoverable credential from PublicKeyCredentialCreationOptions JSON.
30
+ void passkeyCreate(std::string requestJson, PasskeyDone done);
31
+
32
+ // Asserts a credential from PublicKeyCredentialRequestOptions JSON.
33
+ void passkeyGet(std::string requestJson, PasskeyDone done);
34
+
35
+ // Dismisses the system UI of an in-flight ceremony, if any; that call still
36
+ // completes (normally `cancelled`). Called when a kernel runtime tears down.
37
+ void passkeyCancel();
38
+
39
+ } // namespace qntx
@@ -0,0 +1,16 @@
1
+ #pragma once
2
+
3
+ #include <string>
4
+
5
+ namespace qntx {
6
+
7
+ // The directory the vault image file lives in — resolved once per
8
+ // KernelRuntime::start and passed explicitly to kernelFileRead/kernelFileWrite:
9
+ // iOS <Application Support>/qntx-wallet
10
+ // Android <noBackupFilesDir>/qntx-wallet
11
+ // Defined only by a platform translation unit (platform/VaultDirectory.mm on iOS,
12
+ // platform/VaultDirectoryAndroid.cpp on Android): a host that links neither fails
13
+ // at link time instead of misbehaving at runtime.
14
+ std::string platformVaultDirectory();
15
+
16
+ } // namespace qntx
@@ -0,0 +1,175 @@
1
+ // Android keystore: the Kotlin Keystore object owns the AndroidKeyStore key
2
+ // and the BiometricPrompt; this file forwards each call through fbjni with a
3
+ // completion token and resolves the parked completion when Kotlin calls back
4
+ // through nativeComplete (on the main thread).
5
+ #include "../host/Keystore.hpp"
6
+
7
+ #ifdef __ANDROID__
8
+
9
+ #include <fbjni/fbjni.h>
10
+ #include <jni.h>
11
+
12
+ #include <cstdint>
13
+ #include <mutex>
14
+ #include <stdexcept>
15
+ #include <unordered_map>
16
+ #include <utility>
17
+
18
+ namespace qntx {
19
+ namespace {
20
+
21
+ namespace jni = facebook::jni;
22
+
23
+ jni::global_ref<jclass> g_keystoreClass;
24
+
25
+ std::mutex g_mutex;
26
+ int64_t g_nextToken = 1;
27
+ std::unordered_map<int64_t, KeystoreDone> g_pending;
28
+
29
+ int64_t park(KeystoreDone done) {
30
+ std::lock_guard<std::mutex> lock(g_mutex);
31
+ const int64_t token = g_nextToken++;
32
+ g_pending.emplace(token, std::move(done));
33
+ return token;
34
+ }
35
+
36
+ KeystoreDone take(int64_t token) {
37
+ std::lock_guard<std::mutex> lock(g_mutex);
38
+ auto found = g_pending.find(token);
39
+ if (found == g_pending.end()) {
40
+ return nullptr;
41
+ }
42
+ auto done = std::move(found->second);
43
+ g_pending.erase(found);
44
+ return done;
45
+ }
46
+
47
+ jni::alias_ref<jclass> keystoreClass() {
48
+ if (!g_keystoreClass) {
49
+ throw std::runtime_error("keystore: JNI_OnLoad init did not run");
50
+ }
51
+ return g_keystoreClass;
52
+ }
53
+
54
+ jni::local_ref<jni::JArrayByte> byteArray(const uint8_t* data, size_t size) {
55
+ auto array = jni::JArrayByte::newArray(size);
56
+ array->setRegion(0, static_cast<jsize>(size), reinterpret_cast<const jbyte*>(data));
57
+ return array;
58
+ }
59
+
60
+ // Runs one Kotlin entry point; a JNI failure completes the call as `failed`
61
+ // instead of leaving it parked forever.
62
+ template <typename Call>
63
+ void dispatch(KeystoreDone done, Call&& call) {
64
+ const int64_t token = park(std::move(done));
65
+ try {
66
+ jni::ThreadScope threadScope;
67
+ call(static_cast<jlong>(token));
68
+ } catch (...) {
69
+ if (auto parked = take(token)) {
70
+ parked(KeystoreResult{KeystoreStatus::failed, {}});
71
+ }
72
+ }
73
+ }
74
+
75
+ } // namespace
76
+
77
+ void kernelKeystoreInit(jni::alias_ref<jclass> clazz) {
78
+ g_keystoreClass = jni::make_global(clazz);
79
+ }
80
+
81
+ void keystoreSeal(
82
+ const std::string& id,
83
+ SecretBytes secret,
84
+ KeystorePrompt prompt,
85
+ KeystoreDone done) {
86
+ dispatch(std::move(done), [&](jlong token) {
87
+ auto method = keystoreClass()->getStaticMethod<void(
88
+ jlong, jni::alias_ref<jstring>, jni::alias_ref<jni::JArrayByte>, jni::alias_ref<jstring>,
89
+ jni::alias_ref<jstring>)>("seal");
90
+ method(
91
+ keystoreClass(),
92
+ token,
93
+ jni::make_jstring(id),
94
+ byteArray(secret.data(), secret.size()),
95
+ jni::make_jstring(prompt.title),
96
+ jni::make_jstring(prompt.description));
97
+ });
98
+ }
99
+
100
+ void keystoreOpen(
101
+ const std::string& id,
102
+ std::vector<uint8_t> sealed,
103
+ KeystorePrompt prompt,
104
+ KeystoreDone done) {
105
+ dispatch(std::move(done), [&](jlong token) {
106
+ auto method = keystoreClass()->getStaticMethod<void(
107
+ jlong, jni::alias_ref<jstring>, jni::alias_ref<jni::JArrayByte>, jni::alias_ref<jstring>,
108
+ jni::alias_ref<jstring>)>("open");
109
+ method(
110
+ keystoreClass(),
111
+ token,
112
+ jni::make_jstring(id),
113
+ byteArray(sealed.data(), sealed.size()),
114
+ jni::make_jstring(prompt.title),
115
+ jni::make_jstring(prompt.description));
116
+ });
117
+ }
118
+
119
+ void keystoreAuthenticate(KeystorePrompt prompt, KeystoreDone done) {
120
+ dispatch(std::move(done), [&](jlong token) {
121
+ auto method = keystoreClass()->getStaticMethod<void(
122
+ jlong, jni::alias_ref<jstring>, jni::alias_ref<jstring>)>("authenticate");
123
+ method(
124
+ keystoreClass(),
125
+ token,
126
+ jni::make_jstring(prompt.title),
127
+ jni::make_jstring(prompt.description));
128
+ });
129
+ }
130
+
131
+ void keystoreClear(KeystoreDone done) {
132
+ dispatch(std::move(done), [&](jlong token) {
133
+ auto method = keystoreClass()->getStaticMethod<void(jlong)>("clear");
134
+ method(keystoreClass(), token);
135
+ });
136
+ }
137
+
138
+ void keystoreCancel() {
139
+ jni::ThreadScope threadScope;
140
+ auto method = keystoreClass()->getStaticMethod<void()>("cancel");
141
+ method(keystoreClass());
142
+ }
143
+
144
+ std::string keystoreLocale() {
145
+ jni::ThreadScope threadScope;
146
+ auto method = keystoreClass()->getStaticMethod<jni::JString::javaobject()>("locale");
147
+ return method(keystoreClass())->toStdString();
148
+ }
149
+
150
+ } // namespace qntx
151
+
152
+ // Kotlin's Keystore.nativeComplete: resolves the parked call. The byte array
153
+ // is copied into wiping storage; Kotlin wipes its own copy afterwards.
154
+ extern "C" JNIEXPORT void JNICALL
155
+ Java_com_margelo_nitro_qntxwallet_Keystore_nativeComplete(
156
+ JNIEnv* env, jclass, jlong token, jint status, jbyteArray bytes) {
157
+ auto done = qntx::take(static_cast<int64_t>(token));
158
+ if (!done) {
159
+ return;
160
+ }
161
+ qntx::KeystoreResult result;
162
+ result.status = static_cast<qntx::KeystoreStatus>(status);
163
+ if (status < 0 || status > static_cast<jint>(qntx::KeystoreStatus::failed)) {
164
+ result.status = qntx::KeystoreStatus::failed;
165
+ }
166
+ if (bytes != nullptr && result.status == qntx::KeystoreStatus::ok) {
167
+ const jsize size = env->GetArrayLength(bytes);
168
+ qntx::SecretBytes copy(static_cast<size_t>(size));
169
+ env->GetByteArrayRegion(bytes, 0, size, reinterpret_cast<jbyte*>(copy.data()));
170
+ result.bytes = std::move(copy);
171
+ }
172
+ done(std::move(result));
173
+ }
174
+
175
+ #endif // __ANDROID__