@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.
- package/README.md +123 -2
- package/WalletNative.podspec +74 -0
- package/android/CMakeLists.txt +99 -0
- package/android/build.gradle +177 -0
- package/android/consumer-rules.pro +4 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +29 -0
- package/android/src/main/java/com/margelo/nitro/qntxwallet/AppLifecycle.kt +35 -0
- package/android/src/main/java/com/margelo/nitro/qntxwallet/KernelPbkdf2.java +59 -0
- package/android/src/main/java/com/margelo/nitro/qntxwallet/Keystore.kt +280 -0
- package/android/src/main/java/com/margelo/nitro/qntxwallet/Passkey.kt +147 -0
- package/android/src/main/java/com/margelo/nitro/qntxwallet/VaultDirectory.kt +28 -0
- package/android/src/main/java/com/margelo/nitro/qntxwallet/WalletNativePackage.java +30 -0
- package/cpp/host/HybridKernelHost.cpp +162 -0
- package/cpp/host/HybridKernelHost.hpp +49 -0
- package/cpp/host/KernelFile.cpp +120 -0
- package/cpp/host/KernelFile.hpp +24 -0
- package/cpp/host/Keystore.hpp +70 -0
- package/cpp/host/Lifecycle.cpp +77 -0
- package/cpp/host/Lifecycle.hpp +71 -0
- package/cpp/host/Passkey.hpp +39 -0
- package/cpp/host/VaultDirectory.hpp +16 -0
- package/cpp/platform/KeystoreAndroid.cpp +175 -0
- package/cpp/platform/KeystoreIos.mm +300 -0
- package/cpp/platform/LifecycleAndroid.cpp +55 -0
- package/cpp/platform/LifecycleIos.mm +36 -0
- package/cpp/platform/PasskeyAndroid.cpp +118 -0
- package/cpp/platform/PasskeyIos.mm +20 -0
- package/cpp/platform/VaultDirectory.mm +58 -0
- package/cpp/platform/VaultDirectoryAndroid.cpp +35 -0
- package/cpp/runtime/KernelBackends.cpp +297 -0
- package/cpp/runtime/KernelBackends.hpp +20 -0
- package/cpp/runtime/KernelBuffer.cpp +12 -0
- package/cpp/runtime/KernelBuffer.hpp +96 -0
- package/cpp/runtime/KernelGlobals.cpp +535 -0
- package/cpp/runtime/KernelGlobals.hpp +107 -0
- package/cpp/runtime/KernelJs.cpp +155 -0
- package/cpp/runtime/KernelJs.hpp +81 -0
- package/cpp/runtime/KernelKeystore.cpp +224 -0
- package/cpp/runtime/KernelKeystore.hpp +28 -0
- package/cpp/runtime/KernelPasskey.cpp +137 -0
- package/cpp/runtime/KernelPasskey.hpp +23 -0
- package/cpp/runtime/KernelRuntime.cpp +496 -0
- package/cpp/runtime/KernelRuntime.hpp +94 -0
- package/cpp/runtime/platform/KernelPbkdf2.cpp +198 -0
- package/cpp/runtime/platform/KernelPbkdf2.hpp +38 -0
- package/dist/index.d.mts +44 -0
- package/dist/index.mjs +170 -0
- package/dist/index.mjs.map +1 -0
- package/kernel/generated/kernel-entry.js +42967 -0
- package/kernel/generated/kernel.manifest.json +80 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/WalletNative+autolinking.cmake +81 -0
- package/nitrogen/generated/android/WalletNative+autolinking.gradle +27 -0
- package/nitrogen/generated/android/WalletNativeOnLoad.cpp +49 -0
- package/nitrogen/generated/android/WalletNativeOnLoad.hpp +34 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/qntxwallet/WalletNativeOnLoad.kt +35 -0
- package/nitrogen/generated/ios/WalletNative+autolinking.rb +62 -0
- package/nitrogen/generated/ios/WalletNative-Swift-Cxx-Bridge.cpp +17 -0
- package/nitrogen/generated/ios/WalletNative-Swift-Cxx-Bridge.hpp +27 -0
- package/nitrogen/generated/ios/WalletNative-Swift-Cxx-Umbrella.hpp +38 -0
- package/nitrogen/generated/ios/WalletNativeAutolinking.mm +35 -0
- package/nitrogen/generated/ios/WalletNativeAutolinking.swift +16 -0
- package/nitrogen/generated/shared/c++/HybridKernelHostSpec.cpp +25 -0
- package/nitrogen/generated/shared/c++/HybridKernelHostSpec.hpp +69 -0
- package/nitrogen/generated/shared/c++/KernelExitReason.hpp +84 -0
- package/package.json +61 -4
- package/react-native.config.js +14 -0
- package/scripts/compile-kernel.mjs +118 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
#include "KernelBackends.hpp"
|
|
2
|
+
|
|
3
|
+
#include "KernelJs.hpp"
|
|
4
|
+
|
|
5
|
+
// qntx_spark.h carries its own extern "C" guard since 0.5.1. Every build
|
|
6
|
+
// resolves the bare include through a search path: iOS via the SparkNative
|
|
7
|
+
// pod's headers (added by the WalletNative podspec), Android and the host
|
|
8
|
+
// test via the package's include/ directory.
|
|
9
|
+
#include "qntx_spark.h"
|
|
10
|
+
#include "nostr_native.h"
|
|
11
|
+
|
|
12
|
+
#include <string>
|
|
13
|
+
|
|
14
|
+
namespace qntx {
|
|
15
|
+
|
|
16
|
+
namespace {
|
|
17
|
+
|
|
18
|
+
constexpr double kUint32Max = 4294967295.0;
|
|
19
|
+
|
|
20
|
+
// Every qs_* call shares this shape: byte inputs go in as borrowed (data,
|
|
21
|
+
// size) views; the library-owned output is copied into a fresh Uint8Array
|
|
22
|
+
// before qs_free wipes and releases it. A nonzero code throws
|
|
23
|
+
// `qntx_spark:<code>` — the kernel-side wrapper maps it to SparkCryptoError.
|
|
24
|
+
template <typename Invoke>
|
|
25
|
+
jsi::Value callOut(
|
|
26
|
+
jsi::Runtime& rt,
|
|
27
|
+
const KernelIntrinsics& intrinsics,
|
|
28
|
+
Invoke&& invoke) {
|
|
29
|
+
uint8_t* out = nullptr;
|
|
30
|
+
size_t outLen = 0;
|
|
31
|
+
const int32_t code = invoke(&out, &outLen);
|
|
32
|
+
jsi::Value result;
|
|
33
|
+
if (code == 0) {
|
|
34
|
+
auto buffer = arrayBufferFromBytes(rt, out, outLen);
|
|
35
|
+
result = jsi::Value(
|
|
36
|
+
rt, makeUint8Array(rt, intrinsics, std::move(buffer)));
|
|
37
|
+
}
|
|
38
|
+
if (out != nullptr) {
|
|
39
|
+
qs_free(out, outLen);
|
|
40
|
+
}
|
|
41
|
+
if (code != 0) {
|
|
42
|
+
throw jsi::JSError(rt, "qntx_spark:" + std::to_string(code));
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
uint32_t uint32Arg(
|
|
48
|
+
jsi::Runtime& rt,
|
|
49
|
+
const jsi::Value& value,
|
|
50
|
+
const char* name) {
|
|
51
|
+
return static_cast<uint32_t>(integerInRange(rt, value, name, 0, kUint32Max));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
} // namespace
|
|
55
|
+
|
|
56
|
+
void installKernelBackends(
|
|
57
|
+
jsi::Runtime& rt,
|
|
58
|
+
jsi::Object& qntx,
|
|
59
|
+
const KernelIntrinsics& intrinsics) {
|
|
60
|
+
auto spark = jsi::Object(rt);
|
|
61
|
+
|
|
62
|
+
spark.setProperty(
|
|
63
|
+
rt, "abiVersion", jsi::Value(static_cast<double>(qs_abi_version())));
|
|
64
|
+
|
|
65
|
+
spark.setProperty(
|
|
66
|
+
rt,
|
|
67
|
+
"frostCommit",
|
|
68
|
+
hostFn(rt, "spark.frostCommit", 2, [&intrinsics](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value {
|
|
69
|
+
if (count < 2) {
|
|
70
|
+
throw jsi::JSError(
|
|
71
|
+
rt, "__qntx.backends.spark.frostCommit expects (hedge, random)");
|
|
72
|
+
}
|
|
73
|
+
auto hedge = byteViewFromValue(rt, args[0], "__qntx.backends.spark.frostCommit hedge", intrinsics);
|
|
74
|
+
auto random = byteViewFromValue(rt, args[1], "__qntx.backends.spark.frostCommit random", intrinsics);
|
|
75
|
+
return callOut(rt, intrinsics, [&](uint8_t** out, size_t* outLen) {
|
|
76
|
+
return qs_frost_commit(
|
|
77
|
+
hedge.data, hedge.size, random.data, random.size, out, outLen);
|
|
78
|
+
});
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
spark.setProperty(
|
|
82
|
+
rt,
|
|
83
|
+
"frostSign",
|
|
84
|
+
hostFn(rt, "spark.frostSign", 1, [&intrinsics](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value {
|
|
85
|
+
if (count < 1) {
|
|
86
|
+
throw jsi::JSError(
|
|
87
|
+
rt, "__qntx.backends.spark.frostSign expects (request)");
|
|
88
|
+
}
|
|
89
|
+
auto request = byteViewFromValue(rt, args[0], "__qntx.backends.spark.frostSign request", intrinsics);
|
|
90
|
+
return callOut(rt, intrinsics, [&](uint8_t** out, size_t* outLen) {
|
|
91
|
+
return qs_frost_sign(request.data, request.size, out, outLen);
|
|
92
|
+
});
|
|
93
|
+
}));
|
|
94
|
+
|
|
95
|
+
spark.setProperty(
|
|
96
|
+
rt,
|
|
97
|
+
"frostAggregate",
|
|
98
|
+
hostFn(rt, "spark.frostAggregate", 1, [&intrinsics](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value {
|
|
99
|
+
if (count < 1) {
|
|
100
|
+
throw jsi::JSError(
|
|
101
|
+
rt, "__qntx.backends.spark.frostAggregate expects (request)");
|
|
102
|
+
}
|
|
103
|
+
auto request = byteViewFromValue(rt, args[0], "__qntx.backends.spark.frostAggregate request", intrinsics);
|
|
104
|
+
return callOut(rt, intrinsics, [&](uint8_t** out, size_t* outLen) {
|
|
105
|
+
return qs_frost_aggregate(request.data, request.size, out, outLen);
|
|
106
|
+
});
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
spark.setProperty(
|
|
110
|
+
rt,
|
|
111
|
+
"eciesEncrypt",
|
|
112
|
+
hostFn(rt, "spark.eciesEncrypt", 3, [&intrinsics](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value {
|
|
113
|
+
if (count < 3) {
|
|
114
|
+
throw jsi::JSError(
|
|
115
|
+
rt, "__qntx.backends.spark.eciesEncrypt expects (publicKey, plaintext, random)");
|
|
116
|
+
}
|
|
117
|
+
auto publicKey = byteViewFromValue(rt, args[0], "__qntx.backends.spark.eciesEncrypt publicKey", intrinsics);
|
|
118
|
+
auto plaintext = byteViewFromValue(rt, args[1], "__qntx.backends.spark.eciesEncrypt plaintext", intrinsics);
|
|
119
|
+
auto random = byteViewFromValue(rt, args[2], "__qntx.backends.spark.eciesEncrypt random", intrinsics);
|
|
120
|
+
return callOut(rt, intrinsics, [&](uint8_t** out, size_t* outLen) {
|
|
121
|
+
return qs_ecies_encrypt(
|
|
122
|
+
publicKey.data,
|
|
123
|
+
publicKey.size,
|
|
124
|
+
plaintext.data,
|
|
125
|
+
plaintext.size,
|
|
126
|
+
random.data,
|
|
127
|
+
random.size,
|
|
128
|
+
out,
|
|
129
|
+
outLen);
|
|
130
|
+
});
|
|
131
|
+
}));
|
|
132
|
+
|
|
133
|
+
spark.setProperty(
|
|
134
|
+
rt,
|
|
135
|
+
"eciesDecrypt",
|
|
136
|
+
hostFn(rt, "spark.eciesDecrypt", 2, [&intrinsics](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value {
|
|
137
|
+
if (count < 2) {
|
|
138
|
+
throw jsi::JSError(
|
|
139
|
+
rt, "__qntx.backends.spark.eciesDecrypt expects (privateKey, ciphertext)");
|
|
140
|
+
}
|
|
141
|
+
auto privateKey = byteViewFromValue(rt, args[0], "__qntx.backends.spark.eciesDecrypt privateKey", intrinsics);
|
|
142
|
+
auto ciphertext = byteViewFromValue(rt, args[1], "__qntx.backends.spark.eciesDecrypt ciphertext", intrinsics);
|
|
143
|
+
return callOut(rt, intrinsics, [&](uint8_t** out, size_t* outLen) {
|
|
144
|
+
return qs_ecies_decrypt(
|
|
145
|
+
privateKey.data, privateKey.size, ciphertext.data, ciphertext.size, out, outLen);
|
|
146
|
+
});
|
|
147
|
+
}));
|
|
148
|
+
|
|
149
|
+
spark.setProperty(
|
|
150
|
+
rt,
|
|
151
|
+
"splitSecretWithProofs",
|
|
152
|
+
hostFn(rt, "spark.splitSecretWithProofs", 4, [&intrinsics](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value {
|
|
153
|
+
if (count < 4) {
|
|
154
|
+
throw jsi::JSError(
|
|
155
|
+
rt, "__qntx.backends.spark.splitSecretWithProofs expects (secret, threshold, shares, random)");
|
|
156
|
+
}
|
|
157
|
+
auto secret = byteViewFromValue(rt, args[0], "__qntx.backends.spark.splitSecretWithProofs secret", intrinsics);
|
|
158
|
+
const auto threshold =
|
|
159
|
+
uint32Arg(rt, args[1], "__qntx.backends.spark.splitSecretWithProofs threshold");
|
|
160
|
+
const auto shares =
|
|
161
|
+
uint32Arg(rt, args[2], "__qntx.backends.spark.splitSecretWithProofs shares");
|
|
162
|
+
auto random = byteViewFromValue(rt, args[3], "__qntx.backends.spark.splitSecretWithProofs random", intrinsics);
|
|
163
|
+
return callOut(rt, intrinsics, [&](uint8_t** out, size_t* outLen) {
|
|
164
|
+
return qs_split_secret_with_proofs(
|
|
165
|
+
secret.data,
|
|
166
|
+
secret.size,
|
|
167
|
+
threshold,
|
|
168
|
+
shares,
|
|
169
|
+
random.data,
|
|
170
|
+
random.size,
|
|
171
|
+
out,
|
|
172
|
+
outLen);
|
|
173
|
+
});
|
|
174
|
+
}));
|
|
175
|
+
|
|
176
|
+
intrinsics.freeze.call(rt, jsi::Value(rt, spark));
|
|
177
|
+
|
|
178
|
+
// The nostr_native_* calls write fixed-size outputs on the stack and return
|
|
179
|
+
// 0/1 rather than an allocator + error code, so they do not go through
|
|
180
|
+
// callOut. Exact-length inputs are checked before the call; verifySerialized
|
|
181
|
+
// answers with a boolean like nobleBackend instead of throwing.
|
|
182
|
+
auto nostr = jsi::Object(rt);
|
|
183
|
+
nostr.setProperty(
|
|
184
|
+
rt, "abiVersion", jsi::Value(static_cast<double>(nostr_native_abi_version())));
|
|
185
|
+
|
|
186
|
+
auto exact32 = [](jsi::Runtime& rt, const ByteView& view, const char* name) {
|
|
187
|
+
if (view.size != 32) {
|
|
188
|
+
throw jsi::JSError(rt, std::string(name) + " expects a 32-byte view");
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
auto wipe = [](uint8_t* data, size_t size) {
|
|
192
|
+
volatile uint8_t* p = data;
|
|
193
|
+
while (size--) {
|
|
194
|
+
*p++ = 0;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
nostr.setProperty(
|
|
199
|
+
rt,
|
|
200
|
+
"publicKey",
|
|
201
|
+
hostFn(rt, "nostr.publicKey", 1, [&intrinsics, &exact32, &wipe](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value {
|
|
202
|
+
if (count < 1) {
|
|
203
|
+
throw jsi::JSError(rt, "__qntx.backends.nostr.publicKey expects (secretKey)");
|
|
204
|
+
}
|
|
205
|
+
auto sk = byteViewFromValue(rt, args[0], "__qntx.backends.nostr.publicKey secretKey", intrinsics);
|
|
206
|
+
exact32(rt, sk, "__qntx.backends.nostr.publicKey secretKey");
|
|
207
|
+
uint8_t out[32];
|
|
208
|
+
const int ok = nostr_native_public_key(sk.data, out);
|
|
209
|
+
jsi::Value result;
|
|
210
|
+
if (ok == 1) {
|
|
211
|
+
result = jsi::Value(rt, makeUint8Array(rt, intrinsics, arrayBufferFromBytes(rt, out, sizeof out)));
|
|
212
|
+
}
|
|
213
|
+
wipe(out, sizeof out);
|
|
214
|
+
if (ok != 1) {
|
|
215
|
+
throw jsi::JSError(rt, "nostr_native: publicKey rejected the input");
|
|
216
|
+
}
|
|
217
|
+
return result;
|
|
218
|
+
}));
|
|
219
|
+
|
|
220
|
+
nostr.setProperty(
|
|
221
|
+
rt,
|
|
222
|
+
"sign",
|
|
223
|
+
hostFn(rt, "nostr.sign", 3, [&intrinsics, &exact32, &wipe](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value {
|
|
224
|
+
if (count < 3) {
|
|
225
|
+
throw jsi::JSError(rt, "__qntx.backends.nostr.sign expects (id, secretKey, auxRand)");
|
|
226
|
+
}
|
|
227
|
+
auto id = byteViewFromValue(rt, args[0], "__qntx.backends.nostr.sign id", intrinsics);
|
|
228
|
+
auto sk = byteViewFromValue(rt, args[1], "__qntx.backends.nostr.sign secretKey", intrinsics);
|
|
229
|
+
auto aux = byteViewFromValue(rt, args[2], "__qntx.backends.nostr.sign auxRand", intrinsics);
|
|
230
|
+
exact32(rt, id, "__qntx.backends.nostr.sign id");
|
|
231
|
+
exact32(rt, sk, "__qntx.backends.nostr.sign secretKey");
|
|
232
|
+
exact32(rt, aux, "__qntx.backends.nostr.sign auxRand");
|
|
233
|
+
uint8_t out[64];
|
|
234
|
+
const int ok = nostr_native_sign(id.data, sk.data, aux.data, out);
|
|
235
|
+
jsi::Value result;
|
|
236
|
+
if (ok == 1) {
|
|
237
|
+
result = jsi::Value(rt, makeUint8Array(rt, intrinsics, arrayBufferFromBytes(rt, out, sizeof out)));
|
|
238
|
+
}
|
|
239
|
+
wipe(out, sizeof out);
|
|
240
|
+
if (ok != 1) {
|
|
241
|
+
throw jsi::JSError(rt, "nostr_native: sign rejected the input");
|
|
242
|
+
}
|
|
243
|
+
return result;
|
|
244
|
+
}));
|
|
245
|
+
|
|
246
|
+
nostr.setProperty(
|
|
247
|
+
rt,
|
|
248
|
+
"verifySerialized",
|
|
249
|
+
hostFn(rt, "nostr.verifySerialized", 4, [&intrinsics](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value {
|
|
250
|
+
if (count < 4) {
|
|
251
|
+
throw jsi::JSError(rt, "__qntx.backends.nostr.verifySerialized expects (serialized, id, pubkey, sig)");
|
|
252
|
+
}
|
|
253
|
+
auto serialized = byteViewFromValue(rt, args[0], "__qntx.backends.nostr.verifySerialized serialized", intrinsics);
|
|
254
|
+
auto id = byteViewFromValue(rt, args[1], "__qntx.backends.nostr.verifySerialized id", intrinsics);
|
|
255
|
+
auto pubkey = byteViewFromValue(rt, args[2], "__qntx.backends.nostr.verifySerialized pubkey", intrinsics);
|
|
256
|
+
auto sig = byteViewFromValue(rt, args[3], "__qntx.backends.nostr.verifySerialized sig", intrinsics);
|
|
257
|
+
if (id.size != 32 || pubkey.size != 32 || sig.size != 64) {
|
|
258
|
+
return jsi::Value(false);
|
|
259
|
+
}
|
|
260
|
+
return jsi::Value(nostr_native_verify_serialized(
|
|
261
|
+
serialized.data, serialized.size, id.data, pubkey.data, sig.data) == 1);
|
|
262
|
+
}));
|
|
263
|
+
|
|
264
|
+
nostr.setProperty(
|
|
265
|
+
rt,
|
|
266
|
+
"ecdhX",
|
|
267
|
+
hostFn(rt, "nostr.ecdhX", 2, [&intrinsics, &exact32, &wipe](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value {
|
|
268
|
+
if (count < 2) {
|
|
269
|
+
throw jsi::JSError(rt, "__qntx.backends.nostr.ecdhX expects (secretKey, publicKeyX)");
|
|
270
|
+
}
|
|
271
|
+
auto sk = byteViewFromValue(rt, args[0], "__qntx.backends.nostr.ecdhX secretKey", intrinsics);
|
|
272
|
+
auto pubX = byteViewFromValue(rt, args[1], "__qntx.backends.nostr.ecdhX publicKeyX", intrinsics);
|
|
273
|
+
exact32(rt, sk, "__qntx.backends.nostr.ecdhX secretKey");
|
|
274
|
+
exact32(rt, pubX, "__qntx.backends.nostr.ecdhX publicKeyX");
|
|
275
|
+
uint8_t out[32];
|
|
276
|
+
const int ok = nostr_native_ecdh_x(sk.data, pubX.data, out);
|
|
277
|
+
jsi::Value result;
|
|
278
|
+
if (ok == 1) {
|
|
279
|
+
result = jsi::Value(rt, makeUint8Array(rt, intrinsics, arrayBufferFromBytes(rt, out, sizeof out)));
|
|
280
|
+
}
|
|
281
|
+
wipe(out, sizeof out);
|
|
282
|
+
if (ok != 1) {
|
|
283
|
+
throw jsi::JSError(rt, "nostr_native: ecdhX rejected the input");
|
|
284
|
+
}
|
|
285
|
+
return result;
|
|
286
|
+
}));
|
|
287
|
+
|
|
288
|
+
intrinsics.freeze.call(rt, jsi::Value(rt, nostr));
|
|
289
|
+
|
|
290
|
+
auto backends = jsi::Object(rt);
|
|
291
|
+
backends.setProperty(rt, "spark", jsi::Value(std::move(spark)));
|
|
292
|
+
backends.setProperty(rt, "nostr", jsi::Value(std::move(nostr)));
|
|
293
|
+
intrinsics.freeze.call(rt, jsi::Value(rt, backends));
|
|
294
|
+
qntx.setProperty(rt, "backends", jsi::Value(std::move(backends)));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
} // namespace qntx
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <jsi/jsi.h>
|
|
4
|
+
|
|
5
|
+
namespace qntx {
|
|
6
|
+
|
|
7
|
+
namespace jsi = facebook::jsi;
|
|
8
|
+
|
|
9
|
+
struct KernelIntrinsics;
|
|
10
|
+
|
|
11
|
+
// Installs `backends` on the `__qntx` object before the caller freezes it:
|
|
12
|
+
// `backends.spark` is the qntx_spark C ABI bound as host functions. Byte
|
|
13
|
+
// inputs are borrowed views (no copy); every qs_* output is copied into a
|
|
14
|
+
// fresh Uint8Array and then freed with qs_free, which wipes the buffer first.
|
|
15
|
+
void installKernelBackends(
|
|
16
|
+
jsi::Runtime& rt,
|
|
17
|
+
jsi::Object& qntx,
|
|
18
|
+
const KernelIntrinsics& intrinsics);
|
|
19
|
+
|
|
20
|
+
} // namespace qntx
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#include "KernelBuffer.hpp"
|
|
2
|
+
|
|
3
|
+
void qntx::kernelWipe(uint8_t* data, size_t size) {
|
|
4
|
+
volatile uint8_t* p = data;
|
|
5
|
+
for (size_t i = 0; i < size; i++) {
|
|
6
|
+
p[i] = 0;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
void qntx::kernelWipe(std::vector<uint8_t>& bytes) {
|
|
11
|
+
kernelWipe(bytes.data(), bytes.size());
|
|
12
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <jsi/jsi.h>
|
|
4
|
+
|
|
5
|
+
#include <cstdint>
|
|
6
|
+
#include <cstring>
|
|
7
|
+
#include <memory>
|
|
8
|
+
#include <vector>
|
|
9
|
+
|
|
10
|
+
namespace qntx {
|
|
11
|
+
|
|
12
|
+
namespace jsi = facebook::jsi;
|
|
13
|
+
|
|
14
|
+
// Zeroes secret bytes without letting the optimizer elide the store.
|
|
15
|
+
void kernelWipe(uint8_t* data, size_t size);
|
|
16
|
+
void kernelWipe(std::vector<uint8_t>& bytes);
|
|
17
|
+
|
|
18
|
+
// Read-only buffer over script bytes retained by shared_ptr; used for embedded
|
|
19
|
+
// bytecode, which Hermes may read lazily for the runtime's lifetime.
|
|
20
|
+
class VectorBuffer final : public jsi::Buffer {
|
|
21
|
+
public:
|
|
22
|
+
explicit VectorBuffer(std::shared_ptr<const std::vector<uint8_t>> bytes)
|
|
23
|
+
: bytes_(std::move(bytes)) {}
|
|
24
|
+
size_t size() const override {
|
|
25
|
+
return bytes_->size();
|
|
26
|
+
}
|
|
27
|
+
const uint8_t* data() const override {
|
|
28
|
+
return bytes_->data();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private:
|
|
32
|
+
std::shared_ptr<const std::vector<uint8_t>> bytes_;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Byte storage that always wipes itself on destruction. Move-only: a moved-from
|
|
36
|
+
// instance carries no bytes, so each live copy has exactly one wiper. Used for
|
|
37
|
+
// KDF inputs and outputs, where a dropped result must not linger in memory.
|
|
38
|
+
class SecretBytes {
|
|
39
|
+
public:
|
|
40
|
+
SecretBytes() = default;
|
|
41
|
+
explicit SecretBytes(std::vector<uint8_t> bytes) : bytes_(std::move(bytes)) {}
|
|
42
|
+
explicit SecretBytes(size_t size) : bytes_(size) {}
|
|
43
|
+
SecretBytes(const uint8_t* data, size_t size) : bytes_(data, data + size) {}
|
|
44
|
+
|
|
45
|
+
SecretBytes(const SecretBytes&) = delete;
|
|
46
|
+
SecretBytes& operator=(const SecretBytes&) = delete;
|
|
47
|
+
|
|
48
|
+
SecretBytes(SecretBytes&& other) noexcept = default;
|
|
49
|
+
SecretBytes& operator=(SecretBytes&& other) noexcept {
|
|
50
|
+
if (this != &other) {
|
|
51
|
+
kernelWipe(bytes_);
|
|
52
|
+
bytes_ = std::move(other.bytes_);
|
|
53
|
+
}
|
|
54
|
+
return *this;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
~SecretBytes() {
|
|
58
|
+
kernelWipe(bytes_);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const uint8_t* data() const {
|
|
62
|
+
return bytes_.data();
|
|
63
|
+
}
|
|
64
|
+
uint8_t* data() {
|
|
65
|
+
return bytes_.data();
|
|
66
|
+
}
|
|
67
|
+
size_t size() const {
|
|
68
|
+
return bytes_.size();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private:
|
|
72
|
+
std::vector<uint8_t> bytes_;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Owning MutableBuffer; the runtime shares ownership of the byte storage and
|
|
76
|
+
// the bytes are wiped when the last reference goes away, because these buffers
|
|
77
|
+
// back ArrayBuffers that can hold secrets.
|
|
78
|
+
class OwningBuffer final : public jsi::MutableBuffer {
|
|
79
|
+
public:
|
|
80
|
+
explicit OwningBuffer(std::vector<uint8_t> storage) : storage_(std::move(storage)) {}
|
|
81
|
+
explicit OwningBuffer(size_t size) : storage_(size) {}
|
|
82
|
+
~OwningBuffer() override {
|
|
83
|
+
kernelWipe(storage_);
|
|
84
|
+
}
|
|
85
|
+
size_t size() const override {
|
|
86
|
+
return storage_.size();
|
|
87
|
+
}
|
|
88
|
+
uint8_t* data() override {
|
|
89
|
+
return storage_.data();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private:
|
|
93
|
+
std::vector<uint8_t> storage_;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
} // namespace qntx
|