173 lines
5.6 KiB
Rust
173 lines
5.6 KiB
Rust
//! 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 }
|
||
}
|