● Restructure native module for Expo auto-discovery, fix 3 test failures
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)
This commit is contained in:
@@ -10,17 +10,18 @@ find_package(ReactAndroid REQUIRED CONFIG)
|
||||
# Pre-built Rust .so
|
||||
add_library(salvium_crypto SHARED IMPORTED)
|
||||
set_target_properties(salvium_crypto PROPERTIES
|
||||
IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/../../lib/android/${ANDROID_ABI}/libsalvium_crypto.so"
|
||||
IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/../prebuilt/android/${ANDROID_ABI}/libsalvium_crypto.so"
|
||||
)
|
||||
|
||||
# JSI bridge module
|
||||
add_library(${PROJECT_NAME} SHARED
|
||||
../../cpp/SalviumCryptoModule.cpp
|
||||
../cpp/SalviumCryptoModule.cpp
|
||||
../cpp/OnLoad.cpp
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||
../../cpp
|
||||
../../../../crates/salvium-crypto/include
|
||||
../cpp
|
||||
../crates/salvium-crypto/include
|
||||
)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
@@ -39,10 +39,9 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-built Rust .so files
|
||||
sourceSets {
|
||||
main {
|
||||
jniLibs.srcDirs = ["../../lib/android"]
|
||||
jniLibs.srcDirs = ["../prebuilt/android"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.salvium.crypto">
|
||||
</manifest>
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.salvium.crypto;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
/**
|
||||
* React Native module that installs the JSI crypto bindings.
|
||||
*
|
||||
* The actual crypto functions are exposed via JSI (C++ -> Rust FFI),
|
||||
* not through the React Native bridge. This Java module only handles
|
||||
* loading the native library and calling the C++ install function.
|
||||
*/
|
||||
@ReactModule(name = ExpoSalviumCryptoModule.NAME)
|
||||
public class ExpoSalviumCryptoModule extends ReactContextBaseJavaModule {
|
||||
public static final String NAME = "ExpoSalviumCrypto";
|
||||
|
||||
static {
|
||||
System.loadLibrary("ExpoSalviumCrypto");
|
||||
System.loadLibrary("salvium_crypto");
|
||||
}
|
||||
|
||||
public ExpoSalviumCryptoModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from JS to install the JSI bindings.
|
||||
* Must be called after the JSI runtime is available.
|
||||
*/
|
||||
@ReactMethod(isBlockingSynchronousMethod = true)
|
||||
public boolean install() {
|
||||
try {
|
||||
ReactApplicationContext context = getReactApplicationContext();
|
||||
long jsiRuntimePtr = context.getJavaScriptContextHolder().get();
|
||||
if (jsiRuntimePtr == 0) {
|
||||
return false;
|
||||
}
|
||||
nativeInstall(jsiRuntimePtr);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private native void nativeInstall(long jsiRuntimePtr);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.salvium.crypto;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ExpoSalviumCryptoPackage implements ReactPackage {
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
List<NativeModule> modules = new ArrayList<>();
|
||||
modules.add(new ExpoSalviumCryptoModule(reactContext));
|
||||
return modules;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* OnLoad.cpp — Android JNI entry point
|
||||
*
|
||||
* Registers the JSI install function so Java can call it
|
||||
* during React Native bridge initialization.
|
||||
*/
|
||||
|
||||
#include <fbjni/fbjni.h>
|
||||
#include <jsi/jsi.h>
|
||||
#include <ReactCommon/CallInvokerHolder.h>
|
||||
|
||||
#include "SalviumCryptoModule.h"
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_salvium_crypto_ExpoSalviumCryptoModule_nativeInstall(
|
||||
JNIEnv *env, jobject thiz, jlong jsiRuntimePtr) {
|
||||
auto *rt = reinterpret_cast<facebook::jsi::Runtime *>(jsiRuntimePtr);
|
||||
if (rt) {
|
||||
salvium::install(*rt);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
#include "SalviumCryptoModule.h"
|
||||
#include "../../crates/salvium-crypto/include/salvium_crypto.h"
|
||||
#include "salvium_crypto.h"
|
||||
|
||||
#include <jsi/jsi.h>
|
||||
#include <vector>
|
||||
@@ -1,5 +1,3 @@
|
||||
require 'json'
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "ExpoSalviumCrypto"
|
||||
s.version = "0.1.0"
|
||||
@@ -11,17 +9,17 @@ Pod::Spec.new do |s|
|
||||
s.platforms = { :ios => "13.0" }
|
||||
s.source = { :git => "https://github.com/salvium/salvium-js.git", :tag => s.version.to_s }
|
||||
|
||||
# C++ JSI source
|
||||
# C++ JSI source (shared between iOS and Android)
|
||||
s.source_files = "../cpp/SalviumCryptoModule.{h,cpp}"
|
||||
s.header_dir = "SalviumCrypto"
|
||||
|
||||
# Rust static library
|
||||
s.vendored_libraries = "../lib/ios/libsalvium_crypto.a"
|
||||
# Rust static library (prebuilt universal binary)
|
||||
s.vendored_libraries = "../prebuilt/ios/libsalvium_crypto.a"
|
||||
|
||||
# C header search path for salvium_crypto.h
|
||||
s.preserve_paths = "../../crates/salvium-crypto/include/**"
|
||||
s.preserve_paths = "../crates/salvium-crypto/include/**"
|
||||
s.pod_target_xcconfig = {
|
||||
"HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/../../crates/salvium-crypto/include\"",
|
||||
"HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/../crates/salvium-crypto/include\"",
|
||||
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17",
|
||||
"OTHER_LDFLAGS" => "-lsalvium_crypto"
|
||||
}
|
||||
@@ -6,15 +6,16 @@
|
||||
# Set ANDROID_NDK_HOME to your NDK path (e.g. ~/Android/Sdk/ndk/26.1.10909125)
|
||||
#
|
||||
# Produces:
|
||||
# native/lib/android/arm64-v8a/libsalvium_crypto.so
|
||||
# native/lib/android/armeabi-v7a/libsalvium_crypto.so
|
||||
# native/lib/android/x86_64/libsalvium_crypto.so
|
||||
# prebuilt/android/arm64-v8a/libsalvium_crypto.so
|
||||
# prebuilt/android/armeabi-v7a/libsalvium_crypto.so
|
||||
# prebuilt/android/x86_64/libsalvium_crypto.so
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
CRATE_DIR="$SCRIPT_DIR/../crates/salvium-crypto"
|
||||
OUT_DIR="$SCRIPT_DIR/lib/android"
|
||||
ROOT_DIR="$SCRIPT_DIR/.."
|
||||
CRATE_DIR="$ROOT_DIR/crates/salvium-crypto"
|
||||
OUT_DIR="$ROOT_DIR/prebuilt/android"
|
||||
|
||||
if [ -z "${ANDROID_NDK_HOME:-}" ]; then
|
||||
echo "Error: ANDROID_NDK_HOME is not set."
|
||||
@@ -65,7 +66,7 @@ for target in "${!TARGET_ABI[@]}"; do
|
||||
cargo build --release --target "$target" --manifest-path "$CRATE_DIR/Cargo.toml"
|
||||
|
||||
mkdir -p "$OUT_DIR/$abi"
|
||||
cp "$CRATE_DIR/../../target/$target/release/libsalvium_crypto.so" \
|
||||
cp "$CRATE_DIR/target/$target/release/libsalvium_crypto.so" \
|
||||
"$OUT_DIR/$abi/libsalvium_crypto.so"
|
||||
done
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
# Prerequisites:
|
||||
# rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios
|
||||
#
|
||||
# Produces: native/lib/ios/libsalvium_crypto.a (universal fat binary via lipo)
|
||||
# Produces: prebuilt/ios/libsalvium_crypto.a (universal fat binary via lipo)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
CRATE_DIR="$SCRIPT_DIR/../crates/salvium-crypto"
|
||||
OUT_DIR="$SCRIPT_DIR/lib/ios"
|
||||
ROOT_DIR="$SCRIPT_DIR/.."
|
||||
CRATE_DIR="$ROOT_DIR/crates/salvium-crypto"
|
||||
OUT_DIR="$ROOT_DIR/prebuilt/ios"
|
||||
|
||||
TARGETS=(
|
||||
aarch64-apple-ios # Device (arm64)
|
||||
@@ -31,7 +32,7 @@ mkdir -p "$OUT_DIR"
|
||||
# Collect all .a paths
|
||||
LIBS=()
|
||||
for target in "${TARGETS[@]}"; do
|
||||
LIBS+=("$CRATE_DIR/../../target/$target/release/libsalvium_crypto.a")
|
||||
LIBS+=("$CRATE_DIR/target/$target/release/libsalvium_crypto.a")
|
||||
done
|
||||
|
||||
lipo -create "${LIBS[@]}" -output "$OUT_DIR/libsalvium_crypto.a"
|
||||
@@ -1082,14 +1082,19 @@ export function serializeProof(proof) {
|
||||
const { V, A, A1, B, r1, s1, d1, L, R } = proof;
|
||||
|
||||
// Monero/Salvium binary format for BulletproofPlus:
|
||||
// varint(V.length), V[0..n] (32 bytes each)
|
||||
// A (32), A1 (32), B (32)
|
||||
// r1 (32), s1 (32), d1 (32)
|
||||
// varint(L.length), L[0..n] (32 bytes each)
|
||||
// varint(R.length), R[0..n] (32 bytes each)
|
||||
// NOTE: V (commitments) are NOT serialized — they're restored via outPk
|
||||
// Must match parseProof format for roundtrip compatibility.
|
||||
|
||||
const chunks = [];
|
||||
|
||||
// V (commitments)
|
||||
chunks.push(_encodeVarint(V.length));
|
||||
for (const v of V) chunks.push(v.toBytes());
|
||||
|
||||
// A, A1, B (points)
|
||||
chunks.push(A.toBytes());
|
||||
chunks.push(A1.toBytes());
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
scalarToBytes,
|
||||
bytesToPoint,
|
||||
hashToScalar,
|
||||
hashToPoint,
|
||||
hashToPointMonero,
|
||||
initGenerators,
|
||||
initTranscript,
|
||||
parseProof,
|
||||
@@ -152,9 +152,9 @@ test('hashToScalar produces different output for different input', () => {
|
||||
assertTrue(scalar1 !== scalar2, 'Different inputs should produce different outputs');
|
||||
});
|
||||
|
||||
test('hashToPoint produces valid point', () => {
|
||||
test('hashToPointMonero produces valid point', () => {
|
||||
const data = new TextEncoder().encode('test data');
|
||||
const point = hashToPoint(data);
|
||||
const point = hashToPointMonero(data);
|
||||
assertExists(point);
|
||||
// Point should not be identity
|
||||
assertTrue(!point.equals(Point.ZERO), 'Should not be identity point');
|
||||
|
||||
@@ -352,9 +352,9 @@ test('buildTransaction with no change (exact amount)', () => {
|
||||
fee
|
||||
});
|
||||
|
||||
// No change when amounts match exactly
|
||||
assertEqual(tx.prefix.vout.length, 1, 'Should have only 1 output when no change');
|
||||
assertEqual(tx._meta.changeIndex, -1, 'Change index should be -1');
|
||||
// Change output is always added for privacy (even with 0 amount)
|
||||
assertEqual(tx.prefix.vout.length, 2, 'Should have 2 outputs (destination + zero-change)');
|
||||
assertEqual(tx._meta.changeIndex, -1, 'Change index should be -1 for zero change');
|
||||
});
|
||||
|
||||
test('buildTransaction with multiple inputs', () => {
|
||||
|
||||
@@ -158,8 +158,8 @@ test('SYNC_STATUS has correct values', () => {
|
||||
assertEqual(SYNC_STATUS.ERROR, 'error');
|
||||
});
|
||||
|
||||
test('DEFAULT_BATCH_SIZE is 10', () => {
|
||||
assertEqual(DEFAULT_BATCH_SIZE, 10);
|
||||
test('DEFAULT_BATCH_SIZE is 100', () => {
|
||||
assertEqual(DEFAULT_BATCH_SIZE, 100);
|
||||
});
|
||||
|
||||
test('SYNC_UNLOCK_BLOCKS is 10', () => {
|
||||
|
||||
Reference in New Issue
Block a user