feat(companion): backup envelope + NIP-46 signer crypto in the native core
Extends archy-fips-core with the two companion-release features' crypto (#128, #139), same JNI-over-JSON contract as the mesh surface: backup.rs — the ADR-005 encrypted-backup envelope, byte-compatible with the node's backup code (Argon2id default params + ChaCha20-Poly1305, blob = base64(salt||nonce||ct)); decrypt ignores extra envelope fields so node backups read here too. Round-trip, tamper, wrong-passphrase and cross-shape tests included. nostr.rs — the phone-side remote-signer crypto: nsec/npub bech32 keys, BIP340 schnorr event signing (NIP-01 id serialization), NIP-44 v2 payloads (HKDF-SHA256 + ChaCha20 + HMAC-SHA256, both padding prefixes), NIP-04 fallback, nostrconnect:// parsing with repeated relay params. Verified against the official NIP-44 vectors (conversation keys, message keys, padded lengths, byte-exact encrypt vectors), the BIP-340 reference sign vectors, and round-trip/tamper/failure tests. secp256k1 0.29 note: Keypair::public_key() is the 33-byte compressed key — x-only pubkeys must go through .x_only_public_key().0 (one real bug the vectors caught). JNI glue adds com.archipelago.app.NativeCore: backupEncrypt/Decrypt, nostrGenerateSecret/SecretFromAny/ParseConnectUri/SignEvent and the NIP-44/NIP-04 cipher pairs. Android arm64 build verified via cargo-ndk (7.2 MB .so, +0.4 MB for both modules). Host: cargo test 23/23, clippy clean.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
//! JNI surface for `com.archipelago.app.fips.FipsNative` — JSON over strings,
|
||||
//! no codegen (the myco / nostr-vpn embedding pattern). Errors come back as
|
||||
//! JNI surface for `com.archipelago.app.fips.FipsNative` and
|
||||
//! `com.archipelago.app.NativeCore` — JSON over strings, no codegen (the
|
||||
//! myco / nostr-vpn embedding pattern). Errors come back as
|
||||
//! `{"error": "…"}` so Kotlin never sees a raw exception from native code.
|
||||
|
||||
use std::sync::Once;
|
||||
@@ -127,3 +128,177 @@ pub extern "system" fn Java_com_archipelago_app_fips_FipsNative_statusJson(
|
||||
) -> jstring {
|
||||
out(&env, mesh::status_json())
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// com.archipelago.app.NativeCore — companion backup (#128) and NIP-46 remote
|
||||
// signer crypto (#139). Same library, JSON-over-strings contract.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Kotlin: `external fun backupEncrypt(payload: String, passphrase: String): String`
|
||||
/// Returns the ADR-005 envelope JSON or `{"error": …}`.
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_archipelago_app_NativeCore_backupEncrypt(
|
||||
mut env: JNIEnv,
|
||||
_class: JClass,
|
||||
payload: JString,
|
||||
passphrase: JString,
|
||||
) -> jstring {
|
||||
init_logging();
|
||||
let payload = jstr(&mut env, &payload);
|
||||
let passphrase = jstr(&mut env, &passphrase);
|
||||
let json = match crate::backup::encrypt(&payload, &passphrase) {
|
||||
Ok(envelope) => envelope,
|
||||
Err(e) => err_json(e),
|
||||
};
|
||||
out(&env, json)
|
||||
}
|
||||
|
||||
/// Kotlin: `external fun backupDecrypt(envelope: String, passphrase: String): String`
|
||||
/// Returns the decrypted payload JSON or `{"error": …}`.
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_archipelago_app_NativeCore_backupDecrypt(
|
||||
mut env: JNIEnv,
|
||||
_class: JClass,
|
||||
envelope: JString,
|
||||
passphrase: JString,
|
||||
) -> jstring {
|
||||
init_logging();
|
||||
let envelope = jstr(&mut env, &envelope);
|
||||
let passphrase = jstr(&mut env, &passphrase);
|
||||
let json = match crate::backup::decrypt(&envelope, &passphrase) {
|
||||
Ok(payload) => payload,
|
||||
Err(e) => err_json(e),
|
||||
};
|
||||
out(&env, json)
|
||||
}
|
||||
|
||||
/// Kotlin: `external fun nostrGenerateSecret(): String`
|
||||
/// Returns `{"secret": hex, "pubkey": hex, "npub": …, "nsec": …}` or `{"error": …}`.
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_archipelago_app_NativeCore_nostrGenerateSecret(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
) -> jstring {
|
||||
init_logging();
|
||||
let json = match crate::nostr::generate_secret() {
|
||||
Ok(secret) => nostr_key_info_json(&secret),
|
||||
Err(e) => err_json(e),
|
||||
};
|
||||
out(&env, json)
|
||||
}
|
||||
|
||||
/// Kotlin: `external fun nostrSecretFromAny(secret: String): String`
|
||||
/// Accepts hex or `nsec…`; returns key-info JSON or `{"error": …}`.
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_archipelago_app_NativeCore_nostrSecretFromAny(
|
||||
mut env: JNIEnv,
|
||||
_class: JClass,
|
||||
secret: JString,
|
||||
) -> jstring {
|
||||
init_logging();
|
||||
let secret = jstr(&mut env, &secret);
|
||||
let json = match crate::nostr::secret_from_any(&secret) {
|
||||
Ok(hex) => nostr_key_info_json(&hex),
|
||||
Err(e) => err_json(e),
|
||||
};
|
||||
out(&env, json)
|
||||
}
|
||||
|
||||
fn nostr_key_info_json(secret_hex: &str) -> String {
|
||||
match (
|
||||
crate::nostr::pubkey_hex(secret_hex),
|
||||
crate::nostr::npub_from_pubkey(&crate::nostr::pubkey_hex(secret_hex).unwrap_or_default()),
|
||||
crate::nostr::nsec_from_secret(secret_hex),
|
||||
) {
|
||||
(Ok(pubkey), Ok(npub), Ok(nsec)) => serde_json::json!({
|
||||
"secret": secret_hex,
|
||||
"pubkey": pubkey,
|
||||
"npub": npub,
|
||||
"nsec": nsec,
|
||||
})
|
||||
.to_string(),
|
||||
(e, _, _) => err_json(e.unwrap_err()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Kotlin: `external fun nostrParseConnectUri(uri: String): String`
|
||||
/// Returns the parsed URI fields or `{"error": …}`.
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_archipelago_app_NativeCore_nostrParseConnectUri(
|
||||
mut env: JNIEnv,
|
||||
_class: JClass,
|
||||
uri: JString,
|
||||
) -> jstring {
|
||||
init_logging();
|
||||
let uri = jstr(&mut env, &uri);
|
||||
let json = match crate::nostr::parse_connect_uri(&uri) {
|
||||
Ok(info) => info.to_json().to_string(),
|
||||
Err(e) => err_json(e),
|
||||
};
|
||||
out(&env, json)
|
||||
}
|
||||
|
||||
/// Kotlin: `external fun nostrSignEvent(secretHex: String, eventJson: String): String`
|
||||
/// Returns the signed event JSON or `{"error": …}`. The approve/deny decision
|
||||
/// is made in Kotlin BEFORE this is called — native code never signs unasked.
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_archipelago_app_NativeCore_nostrSignEvent(
|
||||
mut env: JNIEnv,
|
||||
_class: JClass,
|
||||
secret_hex: JString,
|
||||
event_json: JString,
|
||||
) -> jstring {
|
||||
init_logging();
|
||||
let secret = jstr(&mut env, &secret_hex);
|
||||
let event = jstr(&mut env, &event_json);
|
||||
let json = match crate::nostr::sign_event(&secret, &event) {
|
||||
Ok(signed) => signed,
|
||||
Err(e) => err_json(e),
|
||||
};
|
||||
out(&env, json)
|
||||
}
|
||||
|
||||
macro_rules! nostr_cipher {
|
||||
($name:ident, $doc:literal, $fn:path) => {
|
||||
#[doc = $doc]
|
||||
#[no_mangle]
|
||||
pub extern "system" fn $name(
|
||||
mut env: JNIEnv,
|
||||
_class: JClass,
|
||||
secret_hex: JString,
|
||||
peer_pub: JString,
|
||||
text: JString,
|
||||
) -> jstring {
|
||||
init_logging();
|
||||
let secret = jstr(&mut env, &secret_hex);
|
||||
let peer = jstr(&mut env, &peer_pub);
|
||||
let text = jstr(&mut env, &text);
|
||||
let json = match $fn(&secret, &peer, &text) {
|
||||
Ok(out) => serde_json::json!({ "result": out }).to_string(),
|
||||
Err(e) => err_json(e),
|
||||
};
|
||||
out(&env, json)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
nostr_cipher!(
|
||||
Java_com_archipelago_app_NativeCore_nostrNip44Encrypt,
|
||||
"Kotlin: `external fun nostrNip44Encrypt(secretHex: String, peerPub: String, plaintext: String): String` — returns `{\"result\": payload}` or `{\"error\": …}`.",
|
||||
crate::nostr::nip44_encrypt
|
||||
);
|
||||
nostr_cipher!(
|
||||
Java_com_archipelago_app_NativeCore_nostrNip44Decrypt,
|
||||
"Kotlin: `external fun nostrNip44Decrypt(secretHex: String, peerPub: String, payload: String): String`",
|
||||
crate::nostr::nip44_decrypt
|
||||
);
|
||||
nostr_cipher!(
|
||||
Java_com_archipelago_app_NativeCore_nostrNip04Encrypt,
|
||||
"Kotlin: `external fun nostrNip04Encrypt(secretHex: String, peerPub: String, plaintext: String): String`",
|
||||
crate::nostr::nip04_encrypt
|
||||
);
|
||||
nostr_cipher!(
|
||||
Java_com_archipelago_app_NativeCore_nostrNip04Decrypt,
|
||||
"Kotlin: `external fun nostrNip04Decrypt(secretHex: String, peerPub: String, payload: String): String`",
|
||||
crate::nostr::nip04_decrypt
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user