Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- changed: Cut the Android native payload by roughly a third with dead-code elimination (--gc-sections against the 4-symbol JNI export list, identical-function folding, packed relocations), a trimmed OpenSSL build (36 unused features removed, QUIC and SM2 tables included), and size-tuned optimization levels. APK-stripped librnmonero.so: arm64-v8a 15.2 MiB to 9.5 MiB, armeabi-v7a 12.4 MiB to 8.3 MiB. The npm artifact also drops DWARF at link time (symbol table kept), halving its size.

## 0.4.2 (2026-07-17)

- changed: Bump vtnerd/lwsf to da8e2617958312f10fe4406808c2a951c5cf0a09
Expand Down
21 changes: 20 additions & 1 deletion scripts/build-native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ const ffi = defineLib({
await build.exec(useCxx ? platform.tools.CXX : platform.tools.CC, [
'-c',
...(useCxx ? ['-std=c++17'] : []),
'-Oz',
'-ffunction-sections',
'-fdata-sections',
...sdkFlags.split(' '),
...includePaths.map(path => `-I${path}`),
`-o${object}`,
Expand Down Expand Up @@ -160,7 +163,23 @@ const ffi = defineLib({
'-llog',
`-Wl,--version-script=${join(srcPath, 'jni/exports.map')}`,
'-Wl,--no-undefined',
'-Wl,-z,max-page-size=16384'
'-Wl,-z,max-page-size=16384',

// The version script above exports only 4 JNI symbols, so let the
// linker discard everything unreachable from those (the statically
// linked deps include daemon, consensus, and mining code we never
// call). Requires -ffunction-sections in each library's build:
'-Wl,--gc-sections',
// Fold duplicate identical functions (mostly template instances):
'-Wl,--icf=safe',
// Pack relative relocations (APS2 format, supported since API 23).
// Saves over 1 MiB of .rela.dyn on arm64:
'-Wl,--pack-dyn-relocs=android',
// Keep the symbol table for crash symbolication, but drop DWARF.
// This is what AGP ships in the APK anyway, and it keeps the npm
// package tens of MiB smaller:
'-Wl,--build-id=sha1',
'-Wl,--strip-debug'
])
build.log('done')
}
Expand Down
14 changes: 13 additions & 1 deletion scripts/libraries/libexpat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { defineLib } from '../utils/lib'

export const libexpat = defineLib({
name: 'libexpat',
cacheTag: '0',
cacheTag: '1',

// R_2_7_3:
url: 'https://github.com/libexpat/libexpat.git',
Expand All @@ -16,6 +16,18 @@ export const libexpat = defineLib({
build.exportEnv({ ...platform.tools })
if (platform.type === 'ios') build.exportEnv({ ...platform.sdkFlags })

// Without an explicit CFLAGS, autoconf defaults to "-g -O2". XML
// parsing is nowhere near a hot path, so optimize for size, and add
// section flags so the final --gc-sections link can drop the parts
// the wallet never calls:
const sizeFlags = '-Oz -g -ffunction-sections -fdata-sections'
build.exportEnv({
CFLAGS:
platform.type === 'ios'
? `${platform.sdkFlags.CFLAGS} ${sizeFlags}`
: sizeFlags
})

await build.exec('./buildconf.sh')
await build.exec('./configure', [
'--enable-static',
Expand Down
14 changes: 11 additions & 3 deletions scripts/libraries/libsodium.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,26 @@ import { defineLib } from '../utils/lib'

export const libsodium = defineLib({
name: 'libsodium',
cacheTag: '0',
cacheTag: '1',

// v1.0.20:
url: 'https://github.com/jedisct1/libsodium.git',
hash: '9511c982fb1d046470a8b42aa36556cdb7da15de',

build: async (build, platform, prefixPath) => {
build.exportEnv({ ...platform.tools })

build.exportEnv({ ...platform.tools })
if (platform.type === 'ios') build.exportEnv({ ...platform.sdkFlags })

// Keep -O2 here (this is the crypto hot path), but add section flags
// so the final --gc-sections link can drop unused primitives:
const sizeFlags = '-O2 -g -ffunction-sections -fdata-sections'
build.exportEnv({
CFLAGS:
platform.type === 'ios'
? `${platform.sdkFlags.CFLAGS} ${sizeFlags}`
: sizeFlags
})

await build.exec('./configure', [
'--enable-static',
'--disable-shared',
Expand Down
18 changes: 17 additions & 1 deletion scripts/libraries/libunbound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { defineLib } from '../utils/lib'
export const libunbound = defineLib({
name: 'libunbound',
libDeps: ['libexpat', 'openssl'],
cacheTag: '0',
cacheTag: '1',

// 1.24.1 (upstream wants 1.4.16)
url: 'https://github.com/NLnetLabs/unbound.git',
Expand All @@ -19,9 +19,25 @@ export const libunbound = defineLib({
})
if (platform.type === 'ios') build.exportEnv({ ...platform.sdkFlags })

// Without an explicit CFLAGS, autoconf defaults to "-g -O2". DNS
// resolution (OpenAlias) is a cold path, so optimize for size, and
// add section flags so the final --gc-sections link can drop the
// resolver features the wallet never uses:
const sizeFlags = '-Oz -g -ffunction-sections -fdata-sections'
build.exportEnv({
CFLAGS:
platform.type === 'ios'
? `${platform.sdkFlags.CFLAGS} ${sizeFlags}`
: sizeFlags
})

await build.exec('./configure', [
'--enable-static',
'--disable-shared',
// GOST verification needs the OpenSSL ENGINE API, which our trimmed
// OpenSSL build removes (no-engine no-gost). No resolver we query
// serves GOST-signed records anyway:
'--disable-gost',
`--host=${platform.triple}`,
`--prefix=${prefixPath}`,
`--with-ssl=${prefixPath}`,
Expand Down
14 changes: 11 additions & 3 deletions scripts/libraries/lwsf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,10 @@ export const lwsf = defineLib({
// Bump this whenever the rpc.cpp / config patch below changes, or the build
// silently reuses the cached (unpatched) library. The literal tag does not
// hash the patch content, so edits here are invisible to the cache otherwise.
cacheTag: '2-da8e261-txcap',
cacheTag: '3-da8e261-sizeflags',
// Note: nothing zmq ever links into the shipped .so, but monero's
// CMakeLists declares libzmq with pkg_check_modules(REQUIRED), so the
// build must still exist for lwsf's configure step to pass.
libDeps: ['boost', 'libsodium', 'libunbound', 'libzmq', 'openssl'],
deps: ['monero.clone'],

Expand Down Expand Up @@ -467,8 +470,13 @@ namespace nymfetch {
`-B${join(build.cwd, 'cmake')}`,
// Build options:
`-DCMAKE_BUILD_TYPE=Release`,
`-DCMAKE_CXX_FLAGS=-DLWSF_MASTER_ENABLE`,
`-DCMAKE_C_FLAGS=-D_DARWIN_C_SOURCE`,
`-DCMAKE_CXX_FLAGS=-DLWSF_MASTER_ENABLE -ffunction-sections -fdata-sections`,
`-DCMAKE_C_FLAGS=-D_DARWIN_C_SOURCE -ffunction-sections -fdata-sections`,
// CMake's default Release level is -O3. -O2 emits noticeably smaller
// code, and the final --gc-sections link can only drop dead functions,
// not shrink the live ones:
`-DCMAKE_C_FLAGS_RELEASE=-O2 -DNDEBUG`,
`-DCMAKE_CXX_FLAGS_RELEASE=-O2 -DNDEBUG`,
`-DCMAKE_FIND_ROOT_PATH=${prefixPath};${platform.sysroot}"`,
`-DCMAKE_INSTALL_PREFIX=${prefixPath}`,
`-DCMAKE_PREFIX_PATH=${prefixPath}`,
Expand Down
52 changes: 51 additions & 1 deletion scripts/libraries/openssl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,52 @@ import { dirname } from 'path'
import { defineLib } from '../utils/lib'
import type { Platform } from '../utils/platforms'

// Features a TLS client wallet never touches, but which the default build
// links into the shipped binary (QUIC stack, SM2 precompute tables, legacy
// ciphers, CMS/OCSP/TS PKI tooling). Names validated against 3.6.0's
// Configure disablables list.
const sizeConfig = [
'no-apps',
'no-aria',
'no-blake2',
'no-camellia',
'no-cast',
'no-cmp',
'no-cms',
'no-comp',
'no-ct',
'no-docs',
'no-dso',
'no-dtls',
'no-ec2m',
'no-engine',
'no-gost',
'no-http',
'no-idea',
'no-legacy',
'no-md4',
'no-mdc2',
'no-ocsp',
'no-psk',
'no-quic',
'no-rc2',
'no-rc4',
'no-rc5',
'no-rmd160',
'no-seed',
'no-sm2',
'no-sm3',
'no-sm4',
'no-srp',
'no-srtp',
'no-tests',
'no-ts',
'no-whirlpool'
]

export const openssl = defineLib({
name: 'openssl',
cacheTag: '0',
cacheTag: '1',

// 3.6.0
url: 'https://github.com/openssl/openssl.git',
Expand Down Expand Up @@ -48,6 +91,13 @@ export const openssl = defineLib({
`--prefix=${prefixPath}`,
'no-async',
'no-shared',
...sizeConfig,
// Configure forwards dash-prefixed args to the compiler. These come
// after its own -O3, so -Oz wins. The hot AES/SHA/EC paths use
// hand-written asm and are unaffected by the C optimization level:
'-Oz',
'-ffunction-sections',
'-fdata-sections',
...extraConfig
])
await build.exec('make', [])
Expand Down