montana/Montana-Protocol/Code/crates/mt-bindings/src/ffi_jni.rs

173 lines
5.6 KiB
Rust
Raw Normal View History

2026-05-26 21:14:51 +03:00
//! JNI surface для Android Kotlin.
//!
//! Реэкспортирует те же функции что ffi_c.rs, но через JNI ABI с правильными
//! Java_<pkg>_<class>_<method> именами. Класс на стороне Kotlin: `quest.montana.app.MtBindings`.
//!
//! Сборка: `cargo ndk -t arm64-v8a -t armeabi-v7a -t x86_64 -p mt-bindings build --release`.
//! Артефакт: `libmt_bindings.so` (3 ABI) → `Android/MontanaApp/app/src/main/jniLibs/{abi}/`.
#![cfg(target_os = "android")]
use jni::objects::{JByteArray, JClass, JString};
use jni::sys::{jbyteArray, jint};
use jni::JNIEnv;
use mt_codec::domain;
use mt_crypto::{keypair_from_seed, sign as mldsa_sign, verify as mldsa_verify, PublicKey, SecretKey, Signature};
use mt_mnemonic::{entropy_to_mnemonic, mldsa_seed_for_role, mnemonic_to_master_seed};
use mt_state::derive_account_id;
const MT_SUITE_MLDSA65: u16 = 0x0001;
/// Соответствует error codes из ffi_c.rs (отрицательные).
/// На Kotlin стороне трактуются как throw IllegalArgumentException/IllegalStateException.
#[no_mangle]
pub extern "system" fn Java_quest_montana_app_MtBindings_nativeAbiVersion(
_env: JNIEnv,
_class: JClass,
) -> jint {
super::ABI_VERSION as jint
}
/// 24 слова → 64-байт master_seed. Возвращает byte[64] или null если ошибка.
#[no_mangle]
pub extern "system" fn Java_quest_montana_app_MtBindings_nativeMnemonicToMasterSeed<'local>(
mut env: JNIEnv<'local>,
_class: JClass<'local>,
mnemonic_jstr: JString<'local>,
) -> jbyteArray {
let null = std::ptr::null_mut();
let mnemonic: String = match env.get_string(&mnemonic_jstr) {
Ok(s) => s.into(),
Err(_) => return null,
};
let seed = match mnemonic_to_master_seed(&mnemonic) {
Ok(s) => s,
Err(_) => return null,
};
match env.byte_array_from_slice(&seed) {
Ok(arr) => arr.into_raw(),
Err(_) => null,
}
}
/// 32 байта entropy → 24-словная UTF-8 мнемоника.
#[no_mangle]
pub extern "system" fn Java_quest_montana_app_MtBindings_nativeEntropyToMnemonic<'local>(
mut env: JNIEnv<'local>,
_class: JClass<'local>,
entropy: JByteArray<'local>,
) -> jni::sys::jstring {
let null = std::ptr::null_mut();
let bytes = match env.convert_byte_array(entropy) {
Ok(b) if b.len() == 32 => b,
_ => return null,
};
let mut arr = [0u8; 32];
arr.copy_from_slice(&bytes);
let mnemonic = entropy_to_mnemonic(&arr);
match env.new_string(mnemonic) {
Ok(s) => s.into_raw(),
Err(_) => null,
}
}
/// 24 слова → ML-DSA-65 account: byte[1952 + 4032 + 32] = pk||sk||account_id.
#[no_mangle]
pub extern "system" fn Java_quest_montana_app_MtBindings_nativeAccountFromMnemonic<'local>(
mut env: JNIEnv<'local>,
_class: JClass<'local>,
mnemonic_jstr: JString<'local>,
) -> jbyteArray {
let null = std::ptr::null_mut();
let mnemonic: String = match env.get_string(&mnemonic_jstr) {
Ok(s) => s.into(),
Err(_) => return null,
};
let master = match mnemonic_to_master_seed(&mnemonic) {
Ok(s) => s,
Err(_) => return null,
};
let acc_seed = mldsa_seed_for_role(&master, domain::ACCOUNT_KEY);
let (pk, sk) = match keypair_from_seed(&acc_seed) {
Ok(kp) => kp,
Err(_) => return null,
};
let account_id = derive_account_id(MT_SUITE_MLDSA65, pk.as_bytes());
let mut buf =
Vec::with_capacity(super::MT_MLDSA_PUBKEY_SIZE + super::MT_MLDSA_SECKEY_SIZE + super::MT_ACCOUNT_ID_LEN);
buf.extend_from_slice(pk.as_bytes());
buf.extend_from_slice(sk.as_bytes());
buf.extend_from_slice(&account_id);
match env.byte_array_from_slice(&buf) {
Ok(arr) => arr.into_raw(),
Err(_) => null,
}
}
/// ML-DSA-65 sign(seckey[4032], msg) → signature[3309] (или null при ошибке).
#[no_mangle]
pub extern "system" fn Java_quest_montana_app_MtBindings_nativeSign<'local>(
mut env: JNIEnv<'local>,
_class: JClass<'local>,
seckey: JByteArray<'local>,
msg: JByteArray<'local>,
) -> jbyteArray {
let null = std::ptr::null_mut();
let sk_bytes = match env.convert_byte_array(seckey) {
Ok(b) => b,
Err(_) => return null,
};
let m = match env.convert_byte_array(msg) {
Ok(b) => b,
Err(_) => return null,
};
let sk = match SecretKey::from_slice(&sk_bytes) {
Some(k) => k,
None => return null,
};
let sig = match mldsa_sign(&sk, &m) {
Ok(s) => s,
Err(_) => return null,
};
match env.byte_array_from_slice(sig.as_bytes()) {
Ok(arr) => arr.into_raw(),
Err(_) => null,
}
}
/// ML-DSA-65 verify. Возвращает 1 (valid) / 0 (invalid) / -1 (input error).
#[no_mangle]
pub extern "system" fn Java_quest_montana_app_MtBindings_nativeVerify<'local>(
mut env: JNIEnv<'local>,
_class: JClass<'local>,
pubkey: JByteArray<'local>,
msg: JByteArray<'local>,
sig: JByteArray<'local>,
) -> jint {
let pk_bytes = match env.convert_byte_array(pubkey) {
Ok(b) => b,
Err(_) => return -1,
};
let m = match env.convert_byte_array(msg) {
Ok(b) => b,
Err(_) => return -1,
};
let sig_bytes = match env.convert_byte_array(sig) {
Ok(b) => b,
Err(_) => return -1,
};
let pk = match PublicKey::from_slice(&pk_bytes) {
Some(k) => k,
None => return -1,
};
let signature = match Signature::from_slice(&sig_bytes) {
Some(s) => s,
None => return -1,
};
if mldsa_verify(&pk, &m, &signature) { 1 } else { 0 }
}