The SDK supplies a small set of explicit, testable native operations that cross the Java/native boundary once per useful operation. It is separate from the general bytecode compiler:
- the compiler turns eligible Java control flow and arithmetic into C++;
- the SDK supplies reviewed C++ implementations of named hashing/encoding/ cryptographic-style primitives;
- JNI and, later, FFM adapt Java calls to the same stable C ABI.
V1 is not a general native-memory API, a transparent replacement for JDK providers, or an algorithm-recognition engine. It does not promise a speedup before benchmark results exist.
- Explicit use. Java calls an SDK API, or a later compiler intrinsic refers to an exact versioned SDK symbol. The compiler does not guess that arbitrary bytecode “looks like SHA-256.”
- C ABI at the seam. C++ internals may evolve, but exported symbols use fixed-width C-compatible types, explicit capacities, and status returns. This lets JNI and FFM share one implementation.
- No exception or allocation ambiguity. C++ exceptions never cross the C ABI. Caller-owned buffers and checked lengths are preferred. Java adapters map statuses to documented Java exceptions.
- Java semantics are explicit. Null behavior, malformed encoding input, padding/alphabet policy, maximum lengths, overlap, output size, and constant-time scope are part of the API contract.
- No hand-written cryptography. A security/legal review selects a pinned upstream implementation, update policy, and build configuration. The SDK wrapper is not itself evidence that the implementation is secure.
- Portable baseline first. C++17, no CPU-specific instruction required. Optional optimized implementations are selected by tested runtime dispatch, never by producing a binary that crashes on the baseline CPU.
- Observable backend. The Java API reports native availability, SDK ABI, implementation version, and capabilities. It never silently calls a Java fallback while reporting native execution.
sdk/
include/native_obfuscator/sdk/
export.h # visibility and calling-convention macros
version.h # ABI version and capability identifiers
status.h # C ABI status codes
c_api.h # complete stable C ABI
bytes.hpp # internal C++17 byte spans, checked sizes
hash.hpp # C++ hash interface
encoding.hpp # C++ encoding interface
src/
c_api.cpp
sha256.cpp # adapter to approved implementation
base64.cpp
hex.cpp
constant_time.cpp
jni/
jni_bridge.cpp
jni_arrays.cpp
jni_exceptions.cpp
third_party/
README.md # provenance, license, pinned revision, update owner
... # only after human approval
Generated projects consume installed/copied SDK headers and sources as ordinary build inputs. CMake and Zig manifests list each file explicitly; recursive source discovery is avoided so an accidental file cannot enter a release.
Names include a major ABI suffix. Exact names and namespace are a human API decision.
/* c_api.h */
#include <stdint.h>
typedef enum no_sdk_status_v1 {
NO_SDK_OK_V1 = 0,
NO_SDK_NULL_V1 = 1,
NO_SDK_INVALID_ARGUMENT_V1 = 2,
NO_SDK_BUFFER_TOO_SMALL_V1 = 3,
NO_SDK_INVALID_ENCODING_V1 = 4,
NO_SDK_SIZE_OVERFLOW_V1 = 5,
NO_SDK_UNAVAILABLE_V1 = 6,
NO_SDK_INTERNAL_V1 = 7
} no_sdk_status_v1;
typedef struct no_sdk_bytes_v1 {
const uint8_t *data;
uint64_t size;
} no_sdk_bytes_v1;
typedef struct no_sdk_mut_bytes_v1 {
uint8_t *data;
uint64_t capacity;
} no_sdk_mut_bytes_v1;
uint32_t no_sdk_abi_version_v1(void);
uint64_t no_sdk_capabilities_v1(void);
const char *no_sdk_implementation_version_v1(void);
no_sdk_status_v1 no_sdk_sha256_v1(
no_sdk_bytes_v1 input,
no_sdk_mut_bytes_v1 output_32);
no_sdk_status_v1 no_sdk_base64_encoded_size_v1(
uint64_t input_size,
uint32_t flags,
uint64_t *required);
no_sdk_status_v1 no_sdk_base64_encode_v1(
no_sdk_bytes_v1 input,
uint32_t flags,
no_sdk_mut_bytes_v1 output,
uint64_t *written);
no_sdk_status_v1 no_sdk_base64_decoded_max_size_v1(
uint64_t input_size,
uint64_t *required);
no_sdk_status_v1 no_sdk_base64_decode_v1(
no_sdk_bytes_v1 input,
uint32_t flags,
no_sdk_mut_bytes_v1 output,
uint64_t *written);
no_sdk_status_v1 no_sdk_hex_encode_v1(
no_sdk_bytes_v1 input,
uint32_t flags,
no_sdk_mut_bytes_v1 output,
uint64_t *written);
no_sdk_status_v1 no_sdk_hex_decode_v1(
no_sdk_bytes_v1 input,
uint32_t flags,
no_sdk_mut_bytes_v1 output,
uint64_t *written);
no_sdk_status_v1 no_sdk_equal_constant_time_v1(
no_sdk_bytes_v1 left,
no_sdk_bytes_v1 right,
uint8_t *equal);export.h defines visibility and the Windows calling convention consistently.
No STL type, bool, exception, compiler-specific class layout, or ownership of
returned heap memory crosses this boundary. The implementation-version string
is immutable process-lifetime storage.
The zero-length contract must state whether {NULL, 0} is accepted. Nonzero
length with null data is always invalid. Every conversion from jint, jsize,
size_t, or uint64_t is checked before allocation or pointer arithmetic.
bytes.hpp, hash.hpp, and encoding.hpp wrap the C ABI/internal
implementations for generated C++ without exporting C++ ABI:
namespace native_obfuscator::sdk {
struct Bytes { const std::uint8_t* data; std::size_t size; };
struct MutableBytes { std::uint8_t* data; std::size_t size; };
Status sha256(Bytes input, MutableBytes output) noexcept;
Status base64_encode(Bytes input, Base64Options options,
MutableBytes output, std::size_t& written) noexcept;
Status base64_decode(Bytes input, Base64Options options,
MutableBytes output, std::size_t& written) noexcept;
}The project uses a small C++17 span rather than requiring C++20 std::span.
Core functions perform no JNI calls and are independently fuzzable and
microbenchmarkable.
Package naming remains a human decision; the repository's current namespace
suggests by.radioegor146.nativeobfuscator.sdk.
public final class NativeSdk {
public static boolean isAvailable();
public static int abiVersion();
public static String implementationVersion();
public static Set<Capability> capabilities();
public static void requireAvailable();
}
public final class NativeHash {
public static byte[] sha256(byte[] input);
}
public final class NativeEncoding {
public static byte[] base64Encode(byte[] input, Base64Variant variant,
boolean padding);
public static byte[] base64Decode(byte[] input, Base64Variant variant,
PaddingPolicy padding);
public static String hexEncode(byte[] input, HexCase letterCase);
public static byte[] hexDecode(CharSequence input);
}
public final class NativeBytes {
/**
* Content work is constant-time for equal-length inputs; lengths are not
* secret and a length mismatch returns false.
*/
public static boolean contentEqualsConstantTime(byte[] left, byte[] right);
}Public methods are ordinary Java validation/availability wrappers around
package-private native methods. Null input throws NullPointerException;
malformed Base64/hex throws IllegalArgumentException; allocation failure
propagates OutOfMemoryError; an unavailable or ABI-mismatched library throws a
dedicated linkage exception. Exact messages are not stable unless explicitly
documented.
V1 has no raw address, allocate/free, mutable key-holder, callback, or
long-lived native context. A Java fallback, if wanted, is a separately selected
backend and reports itself as such.
Implementation order is:
- ABI/version/capability negotiation and error mapping.
- Hex and strict RFC 4648 Base64 encode/decode. These validate buffer sizing, malformed input, and JNI/FFM parity without first taking cryptographic risk.
- SHA-256 one-shot digest, using an approved implementation and published known-answer vectors.
- Constant-time byte equality with a precise equal-length contract.
SHA-256 is chosen for interoperability and test-vector availability, not an unmeasured performance advantage. Input sizes include empty, boundary, very large, and allocation-failure cases.
Deferred beyond v1:
- AEAD/encryption until key ownership, nonce policy, authentication-failure behavior, zeroization limits, provider/FIPS requirements, and security review are approved;
- streaming digest state until lifecycle, concurrency, cleanup, and FFM arena ownership are designed;
- nonstandard hashes until there is a product use case and algorithm/version contract;
- string concatenation until the compiler string model is correct and benchmarks show value;
- raw native memory permanently unless a separate safety case is accepted.
- A small Java bridge owns the package-private
nativedeclarations. - The existing loader (or a replacement) loads one library and checks ABI.
JNI_OnLoadobtains the requested JNI environment, performs only failure-safe initialization, and usesRegisterNativeswith explicit names and descriptors. Public API names are not coupled to exportedJava_package_Class_methodsymbols.- Initialization is idempotent and thread-safe. Failure leaves no partially initialized “available” state.
- JDK 24/25 documentation and tests cover
--enable-native-accessand--illegal-native-access=deny.
V1 uses checked copies as the conservative default:
- validate null and
GetArrayLength; - allocate bounded native storage or stream chunks;
- copy with
GetByteArrayRegion; - call the JNI-free C++ core;
- allocate the exact Java result and use
SetByteArrayRegion; - check for a pending exception after each JNI operation.
GetPrimitiveArrayCritical is not a generic zero-copy guarantee: an
implementation may copy, and the critical region constrains blocking and JNI
operations and can affect GC. It may be introduced only for a bounded,
non-blocking kernel after collector-aware measurements. No JNI call, allocation,
logging, lock wait, or exception construction occurs while a critical pointer
is held.
A later direct-ByteBuffer overload can avoid heap-array copying when the caller
chooses native memory. It validates directness, address, capacity, position/
limit semantics, read-only state, and lifetime.
- C status is translated after native buffers are released.
- Local reference scopes are bounded with
PushLocalFrame/PopLocalFrameor equivalent explicit cleanup. - No
JNIEnv*or local reference is shared across threads. - Native code checks pending exceptions and returns immediately rather than clearing application exceptions.
- All registration descriptors are unit-tested against the Java bridge.
Ordinary Java callers use the JNI adapter. A method compiled by the future IR backend can recognize an exact SDK API owner/name/descriptor plus ABI version and emit a direct call to the C++ core/C ABI, avoiding a callback through Java. This is a declared intrinsic:
- the IR has a typed
SdkCall(id, abi, args)operation; - frontend validation confirms the exact SDK artifact/version;
- Java and direct-C++ paths share null/error/output semantics;
- differential tests force both paths;
- ABI mismatch is a build error, never a fallback;
- no user method is replaced based on bytecode similarity.
FFM is a separate adapter over c_api.h, not a second implementation.
- The release adapter targets the standard
java.lang.foreignAPI on JDK 22+. JDK 21 preview API is not shipped as a compatibility target. - A separate artifact is preferred over a multi-release JAR so JDK 17 cannot accidentally verify or link FFM classes.
- Symbol lookup validates ABI/capabilities before exposing operations.
- Layouts correspond to fixed-width C ABI fields; size conversions are checked.
- V1 uses downcalls only, no upcalls or native-owned memory.
- JNI/FFM adapters run the same vectors and malformed-input suite and must return equivalent Java results/exceptions.
- Native access must be enabled according to deployment policy; FFM does not remove the JDK 24+ native-access operational requirement.
Each release records:
- SDK API and ABI versions;
- exact third-party source revision, checksum, license, patches, and build flags;
- generated SBOM and vulnerability scan;
- compiler/version/target/standard library and enabled CPU dispatch variants;
- reproducible source archive and known-answer/fuzz/sanitizer evidence;
- symbols exported from the final library, checked against an allowlist.
The library loader verifies it loaded the expected ABI before native method registration. Java resources and native artifacts are checksummed in the build manifest. Signing and platform notarization are production-release gates.
- C++ unit tests for every status, size boundary, alias/overlap rule, and capability combination;
- official SHA-256 and RFC 4648 vectors plus cross-checks with JDK providers;
- property tests and fuzzing for encode/decode round trips and malformed input;
- JNI
-Xcheck:jni, ASan/UBSan, compiler warnings-as-errors, allocation-failure, repeated load, concurrency, and class-loader tests; - JNI/FFM/direct-C++ parity tests;
- JMH and native microbenchmarks at multiple sizes with raw output;
- ABI symbol/layout checks on every supported target.
Security review, not test count, decides whether a cryptographic primitive can ship.
The Gemini sketch had a useful high-level separation between Java facade, native core, and JNI, but it is not an implementation specification:
- accept the one-high-level-call boundary and C++ core concept;
- revise “Java 8 through 25 seamlessly” into explicit per-version evidence;
- revise FFM to JDK 22+ standard API; JDK 21 is preview;
- reject raw
NativeMemory.allocate/freefrom v1; - reject automatic replacement of
MessageDigest/Stringcalls; - reject unconditional
GetPrimitiveArrayCritical/GetStringCriticalas “zero-copy”; - reject placeholder crypto code and mixed library claims as shippable code;
- replace mangled JNI exports with registered private natives over a versioned C ABI;
- add deterministic error, ABI, supply-chain, fuzzing, and native-access contracts before any performance statement.