01bcb742ae
Move native code from native/ to Expo-compatible layout with android/
and ios/ at package root. Add Android JNI bridge (OnLoad.cpp,
ExpoSalviumCryptoModule.java, ExpoSalviumCryptoPackage.java).
Prebuilt binaries now go in prebuilt/ instead of native/lib/.
Test fixes:
- wallet-sync: update stale DEFAULT_BATCH_SIZE assertion (10 -> 100)
- bulletproofs+: fix hashToPoint -> hashToPointMonero rename in test,
fix serializeProof to include V array matching parseProof format
- transaction-builder: expect 2 outputs for exact-amount tx (zero-change
output is always added for privacy)
291 lines
13 KiB
C++
291 lines
13 KiB
C++
/**
|
|
* SalviumCryptoModule.cpp — JSI HostObject implementation
|
|
*
|
|
* Each JS-callable method:
|
|
* 1. Extracts Uint8Array data from JSI arguments
|
|
* 2. Calls the corresponding Rust FFI function (extern "C")
|
|
* 3. Returns a new Uint8Array with the result
|
|
*/
|
|
|
|
#include "SalviumCryptoModule.h"
|
|
#include "salvium_crypto.h"
|
|
|
|
#include <jsi/jsi.h>
|
|
#include <vector>
|
|
#include <string>
|
|
|
|
namespace salvium {
|
|
|
|
using namespace facebook;
|
|
|
|
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Extract raw bytes from a JSI Uint8Array (TypedArray) argument.
|
|
*/
|
|
static std::vector<uint8_t> getBytes(jsi::Runtime &rt, const jsi::Value &val) {
|
|
auto obj = val.asObject(rt);
|
|
auto buf = obj.getArrayBuffer(rt);
|
|
size_t len = buf.size(rt);
|
|
auto *data = buf.data(rt);
|
|
return std::vector<uint8_t>(data, data + len);
|
|
}
|
|
|
|
/**
|
|
* Create a JSI Uint8Array from raw bytes.
|
|
*/
|
|
static jsi::Value makeUint8Array(jsi::Runtime &rt, const uint8_t *data,
|
|
size_t len) {
|
|
auto arrayBuffer = jsi::ArrayBuffer(rt, len);
|
|
memcpy(arrayBuffer.data(rt), data, len);
|
|
|
|
auto uint8ArrayCtor =
|
|
rt.global().getPropertyAsFunction(rt, "Uint8Array");
|
|
return uint8ArrayCtor.callAsConstructor(rt, std::move(arrayBuffer));
|
|
}
|
|
|
|
static uint32_t getUint32(jsi::Runtime &rt, const jsi::Value &val) {
|
|
return static_cast<uint32_t>(val.asNumber());
|
|
}
|
|
|
|
// ─── Macro for common 32-in / 32-out patterns ──────────────────────────────
|
|
|
|
#define DEFINE_OP_2x32(name, ffi_fn) \
|
|
if (propName == #name) { \
|
|
return jsi::Function::createFromHostFunction( \
|
|
rt, jsi::PropNameID::forAscii(rt, #name), 2, \
|
|
[](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, \
|
|
size_t count) -> jsi::Value { \
|
|
auto a = getBytes(rt, args[0]); \
|
|
auto b = getBytes(rt, args[1]); \
|
|
uint8_t out[32]; \
|
|
ffi_fn(a.data(), b.data(), out); \
|
|
return makeUint8Array(rt, out, 32); \
|
|
}); \
|
|
}
|
|
|
|
#define DEFINE_OP_3x32(name, ffi_fn) \
|
|
if (propName == #name) { \
|
|
return jsi::Function::createFromHostFunction( \
|
|
rt, jsi::PropNameID::forAscii(rt, #name), 3, \
|
|
[](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, \
|
|
size_t count) -> jsi::Value { \
|
|
auto a = getBytes(rt, args[0]); \
|
|
auto b = getBytes(rt, args[1]); \
|
|
auto c = getBytes(rt, args[2]); \
|
|
uint8_t out[32]; \
|
|
ffi_fn(a.data(), b.data(), c.data(), out); \
|
|
return makeUint8Array(rt, out, 32); \
|
|
}); \
|
|
}
|
|
|
|
#define DEFINE_OP_1x32(name, ffi_fn) \
|
|
if (propName == #name) { \
|
|
return jsi::Function::createFromHostFunction( \
|
|
rt, jsi::PropNameID::forAscii(rt, #name), 1, \
|
|
[](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, \
|
|
size_t count) -> jsi::Value { \
|
|
auto a = getBytes(rt, args[0]); \
|
|
uint8_t out[32]; \
|
|
ffi_fn(a.data(), out); \
|
|
return makeUint8Array(rt, out, 32); \
|
|
}); \
|
|
}
|
|
|
|
#define DEFINE_BOOL_1x32(name, ffi_fn) \
|
|
if (propName == #name) { \
|
|
return jsi::Function::createFromHostFunction( \
|
|
rt, jsi::PropNameID::forAscii(rt, #name), 1, \
|
|
[](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, \
|
|
size_t count) -> jsi::Value { \
|
|
auto a = getBytes(rt, args[0]); \
|
|
return jsi::Value(ffi_fn(a.data()) != 0); \
|
|
}); \
|
|
}
|
|
|
|
// ─── HostObject Implementation ──────────────────────────────────────────────
|
|
|
|
jsi::Value SalviumCryptoHostObject::get(jsi::Runtime &rt,
|
|
const jsi::PropNameID &name) {
|
|
auto propName = name.utf8(rt);
|
|
|
|
// ─── Hashing ────────────────────────────────────────────────────────────
|
|
|
|
if (propName == "keccak256") {
|
|
return jsi::Function::createFromHostFunction(
|
|
rt, jsi::PropNameID::forAscii(rt, "keccak256"), 1,
|
|
[](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args,
|
|
size_t count) -> jsi::Value {
|
|
auto data = getBytes(rt, args[0]);
|
|
uint8_t out[32];
|
|
salvium_keccak256(data.data(), data.size(), out);
|
|
return makeUint8Array(rt, out, 32);
|
|
});
|
|
}
|
|
|
|
if (propName == "blake2b") {
|
|
return jsi::Function::createFromHostFunction(
|
|
rt, jsi::PropNameID::forAscii(rt, "blake2b"), 2,
|
|
[](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args,
|
|
size_t count) -> jsi::Value {
|
|
auto data = getBytes(rt, args[0]);
|
|
auto outLen = static_cast<size_t>(args[1].asNumber());
|
|
std::vector<uint8_t> out(outLen);
|
|
salvium_blake2b(data.data(), data.size(), outLen, out.data());
|
|
return makeUint8Array(rt, out.data(), outLen);
|
|
});
|
|
}
|
|
|
|
if (propName == "blake2bKeyed") {
|
|
return jsi::Function::createFromHostFunction(
|
|
rt, jsi::PropNameID::forAscii(rt, "blake2bKeyed"), 3,
|
|
[](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args,
|
|
size_t count) -> jsi::Value {
|
|
auto data = getBytes(rt, args[0]);
|
|
auto outLen = static_cast<size_t>(args[1].asNumber());
|
|
auto key = getBytes(rt, args[2]);
|
|
std::vector<uint8_t> out(outLen);
|
|
salvium_blake2b_keyed(data.data(), data.size(), outLen, key.data(),
|
|
key.size(), out.data());
|
|
return makeUint8Array(rt, out.data(), outLen);
|
|
});
|
|
}
|
|
|
|
// ─── Scalar Operations ──────────────────────────────────────────────────
|
|
|
|
DEFINE_OP_2x32(scAdd, salvium_sc_add)
|
|
DEFINE_OP_2x32(scSub, salvium_sc_sub)
|
|
DEFINE_OP_2x32(scMul, salvium_sc_mul)
|
|
DEFINE_OP_3x32(scMulAdd, salvium_sc_mul_add)
|
|
DEFINE_OP_3x32(scMulSub, salvium_sc_mul_sub)
|
|
DEFINE_OP_1x32(scReduce32, salvium_sc_reduce32)
|
|
DEFINE_OP_1x32(scInvert, salvium_sc_invert)
|
|
|
|
if (propName == "scReduce64") {
|
|
return jsi::Function::createFromHostFunction(
|
|
rt, jsi::PropNameID::forAscii(rt, "scReduce64"), 1,
|
|
[](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args,
|
|
size_t count) -> jsi::Value {
|
|
auto s = getBytes(rt, args[0]); // 64 bytes input
|
|
uint8_t out[32];
|
|
salvium_sc_reduce64(s.data(), out);
|
|
return makeUint8Array(rt, out, 32);
|
|
});
|
|
}
|
|
|
|
DEFINE_BOOL_1x32(scCheck, salvium_sc_check)
|
|
DEFINE_BOOL_1x32(scIsZero, salvium_sc_is_zero)
|
|
|
|
// ─── Point Operations ───────────────────────────────────────────────────
|
|
|
|
DEFINE_OP_1x32(scalarMultBase, salvium_scalar_mult_base)
|
|
DEFINE_OP_2x32(scalarMultPoint, salvium_scalar_mult_point)
|
|
DEFINE_OP_2x32(pointAdd, salvium_point_add)
|
|
DEFINE_OP_2x32(pointSub, salvium_point_sub)
|
|
DEFINE_OP_1x32(pointNegate, salvium_point_negate)
|
|
|
|
if (propName == "doubleScalarMultBase") {
|
|
return jsi::Function::createFromHostFunction(
|
|
rt, jsi::PropNameID::forAscii(rt, "doubleScalarMultBase"), 3,
|
|
[](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args,
|
|
size_t count) -> jsi::Value {
|
|
auto a = getBytes(rt, args[0]);
|
|
auto p = getBytes(rt, args[1]);
|
|
auto b = getBytes(rt, args[2]);
|
|
uint8_t out[32];
|
|
salvium_double_scalar_mult_base(a.data(), p.data(), b.data(), out);
|
|
return makeUint8Array(rt, out, 32);
|
|
});
|
|
}
|
|
|
|
// ─── Hash-to-Point & Key Derivation ─────────────────────────────────────
|
|
|
|
if (propName == "hashToPoint") {
|
|
return jsi::Function::createFromHostFunction(
|
|
rt, jsi::PropNameID::forAscii(rt, "hashToPoint"), 1,
|
|
[](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args,
|
|
size_t count) -> jsi::Value {
|
|
auto data = getBytes(rt, args[0]);
|
|
uint8_t out[32];
|
|
salvium_hash_to_point(data.data(), data.size(), out);
|
|
return makeUint8Array(rt, out, 32);
|
|
});
|
|
}
|
|
|
|
DEFINE_OP_2x32(generateKeyDerivation, salvium_generate_key_derivation)
|
|
DEFINE_OP_2x32(generateKeyImage, salvium_generate_key_image)
|
|
|
|
if (propName == "derivePublicKey") {
|
|
return jsi::Function::createFromHostFunction(
|
|
rt, jsi::PropNameID::forAscii(rt, "derivePublicKey"), 3,
|
|
[](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args,
|
|
size_t count) -> jsi::Value {
|
|
auto derivation = getBytes(rt, args[0]);
|
|
auto outputIndex = getUint32(rt, args[1]);
|
|
auto basePub = getBytes(rt, args[2]);
|
|
uint8_t out[32];
|
|
salvium_derive_public_key(derivation.data(), outputIndex,
|
|
basePub.data(), out);
|
|
return makeUint8Array(rt, out, 32);
|
|
});
|
|
}
|
|
|
|
if (propName == "deriveSecretKey") {
|
|
return jsi::Function::createFromHostFunction(
|
|
rt, jsi::PropNameID::forAscii(rt, "deriveSecretKey"), 3,
|
|
[](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args,
|
|
size_t count) -> jsi::Value {
|
|
auto derivation = getBytes(rt, args[0]);
|
|
auto outputIndex = getUint32(rt, args[1]);
|
|
auto baseSec = getBytes(rt, args[2]);
|
|
uint8_t out[32];
|
|
salvium_derive_secret_key(derivation.data(), outputIndex,
|
|
baseSec.data(), out);
|
|
return makeUint8Array(rt, out, 32);
|
|
});
|
|
}
|
|
|
|
// ─── Pedersen Commitments ───────────────────────────────────────────────
|
|
|
|
DEFINE_OP_2x32(pedersenCommit, salvium_pedersen_commit)
|
|
DEFINE_OP_1x32(zeroCommit, salvium_zero_commit)
|
|
DEFINE_OP_1x32(genCommitmentMask, salvium_gen_commitment_mask)
|
|
|
|
return jsi::Value::undefined();
|
|
}
|
|
|
|
std::vector<jsi::PropNameID>
|
|
SalviumCryptoHostObject::getPropertyNames(jsi::Runtime &rt) {
|
|
std::vector<jsi::PropNameID> names;
|
|
const char *props[] = {
|
|
// Hashing
|
|
"keccak256", "blake2b", "blake2bKeyed",
|
|
// Scalar ops
|
|
"scAdd", "scSub", "scMul", "scMulAdd", "scMulSub",
|
|
"scReduce32", "scReduce64", "scInvert", "scCheck", "scIsZero",
|
|
// Point ops
|
|
"scalarMultBase", "scalarMultPoint", "pointAdd", "pointSub",
|
|
"pointNegate", "doubleScalarMultBase",
|
|
// Hash-to-point & key derivation
|
|
"hashToPoint", "generateKeyDerivation", "generateKeyImage",
|
|
"derivePublicKey", "deriveSecretKey",
|
|
// Pedersen commitments
|
|
"pedersenCommit", "zeroCommit", "genCommitmentMask",
|
|
};
|
|
for (auto &p : props) {
|
|
names.push_back(jsi::PropNameID::forAscii(rt, p));
|
|
}
|
|
return names;
|
|
}
|
|
|
|
// ─── Install ────────────────────────────────────────────────────────────────
|
|
|
|
void install(jsi::Runtime &rt) {
|
|
auto hostObject =
|
|
jsi::Object::createFromHostObject(rt,
|
|
std::make_shared<SalviumCryptoHostObject>());
|
|
rt.global().setProperty(rt, "__SalviumCrypto", std::move(hostObject));
|
|
}
|
|
|
|
} // namespace salvium
|