v1.0.7-r001: Cross-platform builds, FFI expansion, license, and bug fixes

- Version all crates at 1.0.7-r001 (tracking Salvium C++ 1.0.7)
  - Source-available license: free for author/designees and non-commercial
    use; annual commercial license required for any revenue-generating use
  - Build scripts for Android/iOS/Linux/macOS/Windows producing both
    libsalvium_crypto and libsalvium_ffi
  - CI workflow publishes platform archives to salvium-rs-releases
  - 23 new FFI functions: storage ops, address book, tx notes, attributes,
    staked balance, key derivation, mnemonic
  - Fix stake detection: count owned outputs regardless of spent state
  - Auto-resume testnet integration tests from current chain height
This commit is contained in:
Matt Hess
2026-02-23 17:01:33 +00:00
parent 7757735ac6
commit f71176d1f6
26 changed files with 1265 additions and 155 deletions
+204
View File
@@ -0,0 +1,204 @@
name: Build & Publish Libraries
on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
release_tag:
description: 'Release tag (e.g. v0.1.0)'
required: true
env:
CARGO_TERM_COLOR: always
jobs:
# ── Android (arm64-v8a, armeabi-v7a, x86_64) ─────────────────────────────
android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-linux-android, armv7-linux-androideabi, x86_64-linux-android
- uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r26d
- name: Build Android libraries
env:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
run: ./scripts/build-android.sh
- uses: actions/upload-artifact@v4
with:
name: android
path: prebuilt/android/
# ── Linux x86_64 ──────────────────────────────────────────────────────────
linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
- name: Build Linux libraries
run: ./scripts/build-linux.sh
- uses: actions/upload-artifact@v4
with:
name: linux-x86_64
path: prebuilt/linux-x86_64/
# ── macOS (universal arm64 + x86_64) ──────────────────────────────────────
macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-darwin, x86_64-apple-darwin
- name: Build macOS libraries
run: ./scripts/build-macos.sh
- uses: actions/upload-artifact@v4
with:
name: macos
path: prebuilt/macos/
# ── iOS (device + simulator xcframework) ──────────────────────────────────
ios:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-ios, aarch64-apple-ios-sim, x86_64-apple-ios
- name: Build iOS frameworks
run: ./scripts/build-ios.sh
- uses: actions/upload-artifact@v4
with:
name: ios
path: prebuilt/ios/
# ── Windows x86_64 (cross-compiled from Linux) ───────────────────────────
windows:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-pc-windows-gnu
- name: Install MinGW
run: sudo apt-get update && sudo apt-get install -y gcc-mingw-w64-x86-64
- name: Build Windows libraries
run: ./scripts/build-windows.sh
- uses: actions/upload-artifact@v4
with:
name: windows-x86_64
path: prebuilt/windows-x86_64/
# ── Publish to salvium-rs-release ─────────────────────────────────────────
publish:
needs: [android, linux, macos, ios, windows]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
path: artifacts/
- name: Determine tag
id: tag
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "tag=${{ github.event.inputs.release_tag }}" >> "$GITHUB_OUTPUT"
else
echo "tag=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
fi
- name: Package platform archives
run: |
TAG="${{ steps.tag.outputs.tag }}"
mkdir -p release
# Android
cd artifacts/android
tar czf "../../release/salvium-libs-android-${TAG}.tar.gz" .
cd ../..
# Linux
cd artifacts/linux-x86_64
tar czf "../../release/salvium-libs-linux-x86_64-${TAG}.tar.gz" .
cd ../..
# macOS
cd artifacts/macos
tar czf "../../release/salvium-libs-macos-universal-${TAG}.tar.gz" .
cd ../..
# iOS
cd artifacts/ios
tar czf "../../release/salvium-libs-ios-${TAG}.tar.gz" .
cd ../..
# Windows
cd artifacts/windows-x86_64
zip -r "../../release/salvium-libs-windows-x86_64-${TAG}.zip" .
cd ../..
ls -lh release/
- name: Push to salvium-rs-release
env:
RELEASE_TOKEN: ${{ secrets.RELEASES_PAT }}
run: |
TAG="${{ steps.tag.outputs.tag }}"
REPO="salvium-project/salvium-rs-releases"
# Create release via GitHub API
RELEASE_ID=$(curl -s -X POST \
-H "Authorization: token $RELEASE_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${REPO}/releases" \
-d "{\"tag_name\":\"${TAG}\",\"name\":\"${TAG}\",\"body\":\"Pre-built Salvium Rust libraries for all platforms.\n\nBuilt from salvium-rs ${TAG}.\",\"draft\":false}" \
| jq -r '.id')
echo "Release ID: $RELEASE_ID"
# Upload each archive as a release asset
for file in release/*; do
filename=$(basename "$file")
echo "Uploading $filename..."
curl -s -X POST \
-H "Authorization: token $RELEASE_TOKEN" \
-H "Content-Type: application/octet-stream" \
"https://uploads.github.com/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${filename}" \
--data-binary "@$file"
done
echo "==> Published to https://github.com/${REPO}/releases/tag/${TAG}"
Generated
+14 -13
View File
@@ -1928,7 +1928,7 @@ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "salvium-cli"
version = "0.1.0"
version = "1.0.7-r001"
dependencies = [
"clap",
"dirs",
@@ -1949,7 +1949,7 @@ dependencies = [
[[package]]
name = "salvium-consensus"
version = "0.1.0"
version = "1.0.7-r001"
dependencies = [
"hex",
"salvium-types",
@@ -1961,7 +1961,7 @@ dependencies = [
[[package]]
name = "salvium-crypto"
version = "0.1.0"
version = "1.0.7-r001"
dependencies = [
"aes-gcm",
"argon2",
@@ -1974,6 +1974,7 @@ dependencies = [
"log",
"p256",
"rusqlite",
"salvium-types",
"serde",
"serde_json",
"sha2",
@@ -1985,7 +1986,7 @@ dependencies = [
[[package]]
name = "salvium-ffi"
version = "0.1.0"
version = "1.0.7-r001"
dependencies = [
"hex",
"log",
@@ -2002,7 +2003,7 @@ dependencies = [
[[package]]
name = "salvium-miner"
version = "0.1.0"
version = "1.0.7-r001"
dependencies = [
"clap",
"hex",
@@ -2017,7 +2018,7 @@ dependencies = [
[[package]]
name = "salvium-miner-gr"
version = "0.1.0"
version = "1.0.7-r001"
dependencies = [
"cc",
"clap",
@@ -2029,7 +2030,7 @@ dependencies = [
[[package]]
name = "salvium-miner-v2"
version = "0.1.0"
version = "1.0.7-r001"
dependencies = [
"cc",
"clap",
@@ -2044,7 +2045,7 @@ dependencies = [
[[package]]
name = "salvium-multisig"
version = "0.1.0"
version = "1.0.7-r001"
dependencies = [
"hex",
"rand 0.8.5",
@@ -2055,7 +2056,7 @@ dependencies = [
[[package]]
name = "salvium-rpc"
version = "0.1.0"
version = "1.0.7-r001"
dependencies = [
"base64",
"hex",
@@ -2069,7 +2070,7 @@ dependencies = [
[[package]]
name = "salvium-sync-bench"
version = "0.1.0"
version = "1.0.7-r001"
dependencies = [
"clap",
"env_logger",
@@ -2086,7 +2087,7 @@ dependencies = [
[[package]]
name = "salvium-tx"
version = "0.1.0"
version = "1.0.7-r001"
dependencies = [
"curve25519-dalek",
"hex",
@@ -2103,7 +2104,7 @@ dependencies = [
[[package]]
name = "salvium-types"
version = "0.1.0"
version = "1.0.7-r001"
dependencies = [
"hex",
"serde",
@@ -2113,7 +2114,7 @@ dependencies = [
[[package]]
name = "salvium-wallet"
version = "0.1.0"
version = "1.0.7-r001"
dependencies = [
"aes-gcm",
"dirs",
+2 -1
View File
@@ -17,8 +17,9 @@ members = [
]
[workspace.package]
version = "1.0.7-r001"
edition = "2021"
license = "MIT"
license = "LicenseRef-Salvium-RS"
repository = "https://github.com/salvium/salvium-rs"
[workspace.dependencies]
+74 -77
View File
@@ -1,99 +1,96 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Salvium-RS Source-Available License
Version 1.0, February 2026
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
Copyright (c) 2024-2026 The Salvium Project. All rights reserved.
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
TERMS AND CONDITIONS
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
1. Definitions.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"Software" means the source code, object code, documentation, and
any associated files contained in this repository.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Author" means The Salvium Project and its principal copyright holders.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Designee" means any individual, organization, or entity that has
received express written authorization from the Author to use the
Software under the terms of Section 2.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Commercial License" means a separate written agreement between the
Author and a licensee, executed under the terms described in Section 3
and the accompanying LICENSE-COMMERCIAL file.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
2. Author and Designee Rights.
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
The Author and all Designees are granted a perpetual, worldwide,
royalty-free, irrevocable license to use, copy, modify, distribute,
sublicense, and create derivative works of the Software, without
restriction, for any purpose including commercial use.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner.
3. Non-Commercial Use — Free.
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
Any individual or entity that is not the Author or a Designee may
use, copy, modify, and create derivative works of the Software free
of charge, provided that ALL of the following conditions are met:
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor
hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable copyright license to reproduce,
prepare Derivative Works of, publicly display, publicly perform,
sublicense, and distribute the Work and such Derivative Works in
Source or Object form.
(a) The Software and any derivative works are used solely for
non-commercial purposes. "Non-commercial" means the user does
not charge fees, generate revenue, accept donations, or derive
any direct or indirect monetary benefit from any product or
service that incorporates the Software.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor
hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
and otherwise transfer the Work.
(b) Any public distribution of the Software or derivative works
must retain this license notice in its entirety and clearly
attribute The Salvium Project as the original author.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative
Works thereof in any medium, with or without modifications.
(c) The Software is not sublicensed to any third party for
commercial use.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally
submitted for inclusion in the Work shall be under the terms of this
License.
If at any time the user begins to derive monetary benefit from a
product or service incorporating the Software, they must obtain a
Commercial License within 30 days or cease all use. There is no
revenue threshold — any monetary benefit triggers this requirement.
6. Trademarks.
This License does not grant permission to use the trade names,
trademarks, service marks, or product names of the Licensor.
4. Commercial Use — License Required.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor
provides the Work on an "AS IS" BASIS.
Any use of the Software that does not qualify under Section 2
(Author/Designee) or Section 3 (Non-Commercial) requires a
Commercial License obtained directly from the Author. This includes
but is not limited to:
8. Limitation of Liability.
In no event shall any Contributor be liable for damages.
(a) incorporating the Software into any product or service that
generates revenue, directly or indirectly,
(b) distributing compiled libraries or binaries of the Software
as part of a commercial offering,
(c) offering paid services built on the Software, or
(d) using the Software within an organization that charges
customers for products or services that depend on it.
9. Accepting Warranty or Additional Liability.
While redistributing the Work, You may choose to offer support or
warranty obligations on Your own behalf.
See the LICENSE-COMMERCIAL file for details on obtaining a
Commercial License.
END OF TERMS AND CONDITIONS
5. Third-Party Components.
This Software incorporates open-source dependencies governed by their
own licenses (MIT, Apache-2.0, BSD, ISC, etc.). Those licenses apply
solely to their respective components and are unaffected by this
license. Run `cargo license` for a full list of third-party licenses.
6. No Warranty.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES, OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE,
ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
7. Termination.
Any use of the Software that violates the terms of this license
automatically and immediately terminates all rights granted herein.
Upon termination, the violating party must cease all use and destroy
all copies of the Software in their possession.
+64 -10
View File
@@ -1,16 +1,70 @@
Commercial License Notice
Salvium-RS Commercial License
This software is available under a commercial license for proprietary use,
closed-source redistribution, or other use cases not compatible with the
Apache License, Version 2.0.
Copyright (c) 2024-2026 The Salvium Project. All rights reserved.
Commercial licenses are offered by the Salvium project authors and may
include alternative terms, warranties, indemnification, or support.
For commercial licensing inquiries, contact:
OVERVIEW
mxhess@proton.me
The Salvium-RS Software is source-available under the terms described
in the LICENSE file. Use beyond personal educational study of the source
code requires a Commercial License from the Author.
Use of this software without a commercial license is governed solely by
the Apache License, Version 2.0.
This file describes the terms under which a Commercial License may be
obtained.
WHO NEEDS A COMMERCIAL LICENSE
You need a Commercial License if you are not the Author or a Designee
(as defined in the LICENSE file) and you intend to:
- Use the Software or its compiled libraries in any application,
product, or service (whether commercial or non-commercial)
- Distribute the Software or derivative works to third parties
- Deploy the Software's compiled binaries (libsalvium_crypto,
libsalvium_ffi, or any other build artifact) in any environment
- Integrate the Software's APIs into your own software
COMMERCIAL LICENSE TERMS
Commercial licenses are issued on an annual basis and must be renewed
each year for continued use. A lapsed license requires the licensee to
cease all use of the Software until renewed or renegotiated.
License terms are negotiated individually and may include, at the
Author's discretion:
- An annual license fee (the specific amount is set by contract)
- Per-seat, per-deployment, or revenue-based pricing
- Scope restrictions (single product, single organization, etc.)
- Source code access and modification rights
- Technical support and maintenance
- Warranty and indemnification provisions
There are no free commercial licenses. Any use that generates revenue,
directly or indirectly, requires a paid annual license from the Author.
HOW TO OBTAIN A COMMERCIAL LICENSE
Contact the Author:
The Salvium Project
Email: mxhess@proton.me
Web: https://salvium.io
Please include:
- Your name and organization
- A description of your intended use
- Expected scale (users, deployments, revenue)
DESIGNEE STATUS
The Author may grant Designee status in writing, which confers the
same rights as the Author under the LICENSE file. Designee status
is granted at the Author's sole discretion and may be revoked with
reasonable written notice.
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "salvium-cli"
version = "0.1.0"
version.workspace = true
edition.workspace = true
description = "Command-line wallet for Salvium"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "salvium-consensus"
version = "0.1.0"
version.workspace = true
edition.workspace = true
description = "Blockchain validation rules, tree hash, oracle pricing, and mining utilities for Salvium"
+2 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "salvium-crypto"
version = "0.1.0"
version.workspace = true
edition = "2021"
description = "Crypto primitives for salvium-rs"
@@ -27,6 +27,7 @@ aes-gcm = "0.10"
serde_json = "1"
hex = "0.4"
log = "0.4"
salvium-types = { path = "../salvium-types" }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
p256 = { version = "0.13", features = ["ecdsa"] }
+668
View File
@@ -2140,6 +2140,674 @@ pub unsafe extern "C" fn salvium_storage_delete_stakes_above(
})
}
// ─── Storage: Output Freeze / Thaw / Unspend ────────────────────────────────
/// Mark an output as unspent (reverse a previous mark_spent).
#[no_mangle]
pub unsafe extern "C" fn salvium_storage_mark_unspent(
handle: u32,
key_image: *const u8,
ki_len: usize,
) -> i32 {
catch_ffi(|| {
let ki_str = match std::str::from_utf8(slice::from_raw_parts(key_image, ki_len)) {
Ok(s) => s,
Err(_) => return -1,
};
match crate::storage::with_db(handle, |db| db.mark_unspent(ki_str)) {
Ok(()) => 0,
Err(e) => { log::error!("salvium_storage_mark_unspent: {e}"); -1 }
}
})
}
/// Freeze an output (exclude from selection).
#[no_mangle]
pub unsafe extern "C" fn salvium_storage_freeze_output(
handle: u32,
key_image: *const u8,
ki_len: usize,
) -> i32 {
catch_ffi(|| {
let ki_str = match std::str::from_utf8(slice::from_raw_parts(key_image, ki_len)) {
Ok(s) => s,
Err(_) => return -1,
};
match crate::storage::with_db(handle, |db| db.freeze_output(ki_str)) {
Ok(()) => 0,
Err(e) => { log::error!("salvium_storage_freeze_output: {e}"); -1 }
}
})
}
/// Thaw a previously frozen output.
#[no_mangle]
pub unsafe extern "C" fn salvium_storage_thaw_output(
handle: u32,
key_image: *const u8,
ki_len: usize,
) -> i32 {
catch_ffi(|| {
let ki_str = match std::str::from_utf8(slice::from_raw_parts(key_image, ki_len)) {
Ok(s) => s,
Err(_) => return -1,
};
match crate::storage::with_db(handle, |db| db.thaw_output(ki_str)) {
Ok(()) => 0,
Err(e) => { log::error!("salvium_storage_thaw_output: {e}"); -1 }
}
})
}
/// Look up an output by its one-time public key (hex).
/// Returns JSON OutputRow or -1 if not found.
#[no_mangle]
pub unsafe extern "C" fn salvium_storage_get_output_by_public_key(
handle: u32,
public_key: *const u8,
pk_len: usize,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32 {
catch_ffi(|| {
let pk_str = match std::str::from_utf8(slice::from_raw_parts(public_key, pk_len)) {
Ok(s) => s,
Err(_) => return -1,
};
match crate::storage::with_db(handle, |db| db.get_output_by_public_key(pk_str)) {
Ok(Some(row)) => {
let json = match serde_json::to_vec(&row) {
Ok(j) => j,
Err(_) => return -1,
};
let len = json.len();
let buf = json.into_boxed_slice();
let raw = Box::into_raw(buf);
*out_ptr = (*raw).as_mut_ptr();
*out_len = len;
0
}
Ok(None) => -1,
Err(e) => { log::error!("salvium_storage_get_output_by_public_key: {e}"); -1 }
}
})
}
// ─── Storage: Transaction Notes ─────────────────────────────────────────────
/// Set a note on a transaction.
#[no_mangle]
pub unsafe extern "C" fn salvium_storage_set_tx_note(
handle: u32,
tx_hash: *const u8,
th_len: usize,
note: *const u8,
note_len: usize,
) -> i32 {
catch_ffi(|| {
let hash_str = match std::str::from_utf8(slice::from_raw_parts(tx_hash, th_len)) {
Ok(s) => s,
Err(_) => return -1,
};
let note_str = match std::str::from_utf8(slice::from_raw_parts(note, note_len)) {
Ok(s) => s,
Err(_) => return -1,
};
match crate::storage::with_db(handle, |db| db.set_tx_note(hash_str, note_str)) {
Ok(()) => 0,
Err(e) => { log::error!("salvium_storage_set_tx_note: {e}"); -1 }
}
})
}
/// Get notes for multiple transactions.
/// Input: JSON array of tx hashes: ["hash1","hash2"]
/// Returns JSON object: {"hash1":"note1","hash2":"note2"}
#[no_mangle]
pub unsafe extern "C" fn salvium_storage_get_tx_notes(
handle: u32,
json_buf: *const u8,
json_len: usize,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32 {
catch_ffi(|| {
let json_str = match std::str::from_utf8(slice::from_raw_parts(json_buf, json_len)) {
Ok(s) => s,
Err(_) => return -1,
};
let hashes: Vec<String> = match serde_json::from_str(json_str) {
Ok(v) => v,
Err(_) => return -1,
};
let hash_refs: Vec<&str> = hashes.iter().map(|s| s.as_str()).collect();
match crate::storage::with_db(handle, |db| db.get_tx_notes(&hash_refs)) {
Ok(notes) => {
let json = match serde_json::to_vec(&notes) {
Ok(j) => j,
Err(_) => return -1,
};
let len = json.len();
let buf = json.into_boxed_slice();
let raw = Box::into_raw(buf);
*out_ptr = (*raw).as_mut_ptr();
*out_len = len;
0
}
Err(e) => { log::error!("salvium_storage_get_tx_notes: {e}"); -1 }
}
})
}
// ─── Storage: Address Book ──────────────────────────────────────────────────
/// Add an address book entry. Returns the new row_id on success (as i32), -1 on error.
#[no_mangle]
pub unsafe extern "C" fn salvium_storage_add_address_book_entry(
handle: u32,
json_buf: *const u8,
json_len: usize,
) -> i32 {
catch_ffi(|| {
let json_str = match std::str::from_utf8(slice::from_raw_parts(json_buf, json_len)) {
Ok(s) => s,
Err(e) => { log::error!("salvium_storage_add_address_book_entry: invalid UTF-8: {e}"); return -1; }
};
let data: serde_json::Value = match serde_json::from_str(json_str) {
Ok(v) => v,
Err(e) => { log::error!("salvium_storage_add_address_book_entry: JSON parse error: {e}"); return -1; }
};
let address = data["address"].as_str().unwrap_or("");
let label = data["label"].as_str().unwrap_or("");
let description = data["description"].as_str().unwrap_or("");
let payment_id = data["paymentId"].as_str().unwrap_or("");
match crate::storage::with_db(handle, |db| {
db.add_address_book_entry(address, label, description, payment_id)
}) {
Ok(row_id) => row_id as i32,
Err(e) => { log::error!("salvium_storage_add_address_book_entry: DB error: {e}"); -1 }
}
})
}
/// Get all address book entries. Returns JSON array.
#[no_mangle]
pub unsafe extern "C" fn salvium_storage_get_address_book(
handle: u32,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32 {
catch_ffi(|| {
match crate::storage::with_db(handle, |db| db.get_address_book()) {
Ok(entries) => {
let json = match serde_json::to_vec(&entries) {
Ok(j) => j,
Err(_) => return -1,
};
let len = json.len();
let buf = json.into_boxed_slice();
let raw = Box::into_raw(buf);
*out_ptr = (*raw).as_mut_ptr();
*out_len = len;
0
}
Err(e) => { log::error!("salvium_storage_get_address_book: {e}"); -1 }
}
})
}
/// Get a single address book entry by row_id. Returns JSON object or -1 if not found.
#[no_mangle]
pub unsafe extern "C" fn salvium_storage_get_address_book_entry(
handle: u32,
row_id: i64,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32 {
catch_ffi(|| {
match crate::storage::with_db(handle, |db| db.get_address_book_entry(row_id)) {
Ok(Some(entry)) => {
let json = match serde_json::to_vec(&entry) {
Ok(j) => j,
Err(_) => return -1,
};
let len = json.len();
let buf = json.into_boxed_slice();
let raw = Box::into_raw(buf);
*out_ptr = (*raw).as_mut_ptr();
*out_len = len;
0
}
Ok(None) => -1,
Err(e) => { log::error!("salvium_storage_get_address_book_entry: {e}"); -1 }
}
})
}
/// Edit an address book entry. Input: JSON with "rowId" and optional "address","label","description","paymentId".
/// Returns 1 if updated, 0 if not found, -1 on error.
#[no_mangle]
pub unsafe extern "C" fn salvium_storage_edit_address_book_entry(
handle: u32,
json_buf: *const u8,
json_len: usize,
) -> i32 {
catch_ffi(|| {
let json_str = match std::str::from_utf8(slice::from_raw_parts(json_buf, json_len)) {
Ok(s) => s,
Err(_) => return -1,
};
let data: serde_json::Value = match serde_json::from_str(json_str) {
Ok(v) => v,
Err(_) => return -1,
};
let row_id = data["rowId"].as_i64().unwrap_or(-1);
if row_id < 0 { return -1; }
let address = data.get("address").and_then(|v| v.as_str());
let label = data.get("label").and_then(|v| v.as_str());
let description = data.get("description").and_then(|v| v.as_str());
let payment_id = data.get("paymentId").and_then(|v| v.as_str());
match crate::storage::with_db(handle, |db| {
db.edit_address_book_entry(row_id, address, label, description, payment_id)
}) {
Ok(true) => 1,
Ok(false) => 0,
Err(e) => { log::error!("salvium_storage_edit_address_book_entry: {e}"); -1 }
}
})
}
/// Delete an address book entry by row_id. Returns 1 if deleted, 0 if not found, -1 on error.
#[no_mangle]
pub unsafe extern "C" fn salvium_storage_delete_address_book_entry(
handle: u32,
row_id: i64,
) -> i32 {
catch_ffi(|| {
match crate::storage::with_db(handle, |db| db.delete_address_book_entry(row_id)) {
Ok(true) => 1,
Ok(false) => 0,
Err(e) => { log::error!("salvium_storage_delete_address_book_entry: {e}"); -1 }
}
})
}
// ─── Storage: Key-Value Attributes ──────────────────────────────────────────
/// Set a wallet attribute (key-value pair).
#[no_mangle]
pub unsafe extern "C" fn salvium_storage_set_attribute(
handle: u32,
key: *const u8,
key_len: usize,
value: *const u8,
value_len: usize,
) -> i32 {
catch_ffi(|| {
let key_str = match std::str::from_utf8(slice::from_raw_parts(key, key_len)) {
Ok(s) => s,
Err(_) => return -1,
};
let val_str = match std::str::from_utf8(slice::from_raw_parts(value, value_len)) {
Ok(s) => s,
Err(_) => return -1,
};
match crate::storage::with_db(handle, |db| db.set_attribute(key_str, val_str)) {
Ok(()) => 0,
Err(e) => { log::error!("salvium_storage_set_attribute: {e}"); -1 }
}
})
}
/// Get a wallet attribute by key. Returns the value string or -1 if not found.
#[no_mangle]
pub unsafe extern "C" fn salvium_storage_get_attribute(
handle: u32,
key: *const u8,
key_len: usize,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32 {
catch_ffi(|| {
let key_str = match std::str::from_utf8(slice::from_raw_parts(key, key_len)) {
Ok(s) => s,
Err(_) => return -1,
};
match crate::storage::with_db(handle, |db| db.get_attribute(key_str)) {
Ok(Some(value)) => {
let bytes = value.into_bytes().into_boxed_slice();
let len = bytes.len();
let raw = Box::into_raw(bytes);
*out_ptr = (*raw).as_mut_ptr();
*out_len = len;
0
}
Ok(None) => -1,
Err(e) => { log::error!("salvium_storage_get_attribute: {e}"); -1 }
}
})
}
// ─── Storage: Staked Balance ────────────────────────────────────────────────
/// Get total staked balance for a specific asset type.
/// Returns JSON: {"stakedBalance":"<atomic_units>"}
#[no_mangle]
pub unsafe extern "C" fn salvium_storage_get_staked_balance(
handle: u32,
asset_type: *const u8,
at_len: usize,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32 {
catch_ffi(|| {
let at_str = match std::str::from_utf8(slice::from_raw_parts(asset_type, at_len)) {
Ok(s) => s,
Err(_) => return -1,
};
match crate::storage::with_db(handle, |db| db.get_staked_balance(at_str)) {
Ok(balance) => {
let result = serde_json::json!({ "stakedBalance": balance.to_string() });
let json = serde_json::to_vec(&result).unwrap();
let len = json.len();
let buf = json.into_boxed_slice();
let raw = Box::into_raw(buf);
*out_ptr = (*raw).as_mut_ptr();
*out_len = len;
0
}
Err(e) => { log::error!("salvium_storage_get_staked_balance: {e}"); -1 }
}
})
}
/// Get staked balances for all asset types.
/// Returns JSON: {"SAL1":"<amount>", ...}
#[no_mangle]
pub unsafe extern "C" fn salvium_storage_get_all_staked_balances(
handle: u32,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32 {
catch_ffi(|| {
match crate::storage::with_db(handle, |db| db.get_all_staked_balances()) {
Ok(balances) => {
let string_map: std::collections::HashMap<String, String> = balances
.into_iter()
.map(|(k, v)| (k, v.to_string()))
.collect();
let json = serde_json::to_vec(&string_map).unwrap();
let len = json.len();
let buf = json.into_boxed_slice();
let raw = Box::into_raw(buf);
*out_ptr = (*raw).as_mut_ptr();
*out_len = len;
0
}
Err(e) => { log::error!("salvium_storage_get_all_staked_balances: {e}"); -1 }
}
})
}
/// Look up a locked stake by its expected return output key.
/// Returns JSON StakeRow or -1 if not found.
#[no_mangle]
pub unsafe extern "C" fn salvium_storage_get_locked_stake_by_return_output_key(
handle: u32,
key_buf: *const u8,
key_len: usize,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32 {
catch_ffi(|| {
let key_str = match std::str::from_utf8(slice::from_raw_parts(key_buf, key_len)) {
Ok(s) => s,
Err(_) => return -1,
};
match crate::storage::with_db(handle, |db| db.get_locked_stake_by_return_output_key(key_str)) {
Ok(Some(stake)) => {
let json = match serde_json::to_vec(&stake) {
Ok(j) => j,
Err(_) => return -1,
};
let len = json.len();
let buf = json.into_boxed_slice();
let raw = Box::into_raw(buf);
*out_ptr = (*raw).as_mut_ptr();
*out_len = len;
0
}
Ok(None) => -1,
Err(e) => { log::error!("salvium_storage_get_locked_stake_by_return_output_key: {e}"); -1 }
}
})
}
// ─── Storage: Rekey ─────────────────────────────────────────────────────────
/// Change the database encryption key. new_key must be exactly 32 bytes.
#[no_mangle]
pub unsafe extern "C" fn salvium_storage_rekey(
handle: u32,
new_key: *const u8,
key_len: usize,
) -> i32 {
catch_ffi(|| {
let key = slice::from_raw_parts(new_key, key_len);
match crate::storage::with_db(handle, |db| {
db.rekey(key).map_err(|e| e)
}) {
Ok(()) => 0,
Err(e) => { log::error!("salvium_storage_rekey: {e}"); -1 }
}
})
}
// ─── Wallet Key Derivation ──────────────────────────────────────────────────
/// Derive full wallet keys (CryptoNote + CARROT) from a 32-byte seed.
///
/// Output: 416 bytes (13 × 32):
/// [ 0.. 32] cn_spend_secret_key
/// [ 32.. 64] cn_spend_public_key
/// [ 64.. 96] cn_view_secret_key
/// [ 96..128] cn_view_public_key
/// [128..416] CARROT keys (288 bytes = 9 × 32, same layout as derive_carrot_keys_batch)
#[no_mangle]
pub unsafe extern "C" fn salvium_derive_wallet_keys_from_seed(
seed: *const u8,
out: *mut u8,
) -> i32 {
catch_ffi(|| {
let seed_bytes = crate::to32(slice::from_raw_parts(seed, 32));
// CryptoNote key derivation
let spend_secret = crate::sc_reduce32(&seed_bytes);
let spend_secret_32 = crate::to32(&spend_secret);
let spend_public = crate::scalar_mult_base(&spend_secret_32);
let view_secret = crate::sc_reduce32(&crate::keccak256(&spend_secret_32));
let view_secret_32 = crate::to32(&view_secret);
let view_public = crate::scalar_mult_base(&view_secret_32);
// CARROT key derivation (master_secret = spend_secret)
let carrot = crate::carrot_keys::derive_carrot_keys(&spend_secret_32);
// Write CN keys (128 bytes)
ptr::copy_nonoverlapping(spend_secret.as_ptr(), out, 32);
ptr::copy_nonoverlapping(spend_public.as_ptr(), out.add(32), 32);
ptr::copy_nonoverlapping(view_secret.as_ptr(), out.add(64), 32);
ptr::copy_nonoverlapping(view_public.as_ptr(), out.add(96), 32);
// Write CARROT keys (288 bytes)
ptr::copy_nonoverlapping(carrot.as_ptr(), out.add(128), 288);
0
})
}
// ─── Address Generation & Parsing ───────────────────────────────────────────
/// Create an address string from components.
///
/// Parameters:
/// network: 0 = mainnet, 1 = testnet, 2 = stagenet
/// format: 0 = legacy, 1 = carrot
/// addr_type: 0 = standard, 1 = integrated, 2 = subaddress
/// spend_pub: 32 bytes
/// view_pub: 32 bytes
/// payment_id: 16 bytes (only for integrated), null otherwise
/// payment_id_len: 0 or 16
///
/// Returns the address string via out_ptr/out_len.
#[no_mangle]
pub unsafe extern "C" fn salvium_create_address(
network: u8,
format: u8,
addr_type: u8,
spend_pub: *const u8,
view_pub: *const u8,
payment_id: *const u8,
payment_id_len: usize,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32 {
catch_ffi(|| {
use salvium_types::constants::{AddressFormat, AddressType, Network};
let net = match network {
0 => Network::Mainnet,
1 => Network::Testnet,
2 => Network::Stagenet,
_ => return -1,
};
let fmt = match format {
0 => AddressFormat::Legacy,
1 => AddressFormat::Carrot,
_ => return -1,
};
let at = match addr_type {
0 => AddressType::Standard,
1 => AddressType::Integrated,
2 => AddressType::Subaddress,
_ => return -1,
};
let spend = slice::from_raw_parts(spend_pub, 32);
let view = slice::from_raw_parts(view_pub, 32);
let pid = if payment_id_len > 0 && !payment_id.is_null() {
Some(slice::from_raw_parts(payment_id, payment_id_len))
} else {
None
};
match salvium_types::address::create_address_raw(net, fmt, at, spend, view, pid) {
Ok(addr) => {
let bytes = addr.into_bytes().into_boxed_slice();
let len = bytes.len();
let raw = Box::into_raw(bytes);
*out_ptr = (*raw).as_mut_ptr();
*out_len = len;
0
}
Err(e) => { log::error!("salvium_create_address: {e}"); -1 }
}
})
}
/// Parse an address string into its components.
///
/// Returns JSON:
/// {
/// "network": "mainnet"|"testnet"|"stagenet",
/// "format": "legacy"|"carrot",
/// "addressType": "standard"|"integrated"|"subaddress",
/// "spendPublicKey": "hex...",
/// "viewPublicKey": "hex...",
/// "paymentId": "hex..." | null
/// }
#[no_mangle]
pub unsafe extern "C" fn salvium_parse_address(
addr: *const u8,
addr_len: usize,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32 {
catch_ffi(|| {
let addr_str = match std::str::from_utf8(slice::from_raw_parts(addr, addr_len)) {
Ok(s) => s,
Err(_) => return -1,
};
match salvium_types::address::parse_address(addr_str) {
Ok(parsed) => {
let pid = parsed.payment_id.map(|p| hex::encode(p));
let result = serde_json::json!({
"network": format!("{:?}", parsed.network).to_lowercase(),
"format": format!("{:?}", parsed.format).to_lowercase(),
"addressType": format!("{:?}", parsed.address_type).to_lowercase(),
"spendPublicKey": hex::encode(parsed.spend_public_key),
"viewPublicKey": hex::encode(parsed.view_public_key),
"paymentId": pid,
});
let json = serde_json::to_vec(&result).unwrap();
let len = json.len();
let buf = json.into_boxed_slice();
let raw = Box::into_raw(buf);
*out_ptr = (*raw).as_mut_ptr();
*out_len = len;
0
}
Err(e) => { log::error!("salvium_parse_address: {e}"); -1 }
}
})
}
// ─── Mnemonic Seed Encoding ─────────────────────────────────────────────────
/// Convert a 32-byte seed to a 25-word mnemonic (English).
/// Returns the mnemonic string via out_ptr/out_len.
#[no_mangle]
pub unsafe extern "C" fn salvium_seed_to_mnemonic(
seed: *const u8,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32 {
catch_ffi(|| {
let seed_arr = crate::to32(slice::from_raw_parts(seed, 32));
match salvium_types::mnemonic::seed_to_mnemonic(&seed_arr, Some("english")) {
Ok(words) => {
let bytes = words.into_bytes().into_boxed_slice();
let len = bytes.len();
let raw = Box::into_raw(bytes);
*out_ptr = (*raw).as_mut_ptr();
*out_len = len;
0
}
Err(e) => { log::error!("salvium_seed_to_mnemonic: {e}"); -1 }
}
})
}
/// Convert a mnemonic string to a 32-byte seed.
/// out: must be at least 32 bytes.
#[no_mangle]
pub unsafe extern "C" fn salvium_mnemonic_to_seed(
mnemonic: *const u8,
mnemonic_len: usize,
out: *mut u8,
) -> i32 {
catch_ffi(|| {
let words = match std::str::from_utf8(slice::from_raw_parts(mnemonic, mnemonic_len)) {
Ok(s) => s,
Err(_) => return -1,
};
match salvium_types::mnemonic::mnemonic_to_seed(words, None) {
Ok(result) => {
ptr::copy_nonoverlapping(result.seed.as_ptr(), out, 32);
0
}
Err(e) => { log::error!("salvium_mnemonic_to_seed: {e}"); -1 }
}
})
}
/// Free Rust-allocated result buffer.
#[no_mangle]
pub unsafe extern "C" fn salvium_storage_free_buf(buf_ptr: *mut u8, len: usize) {
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "salvium-ffi"
version = "0.1.0"
version.workspace = true
edition.workspace = true
description = "C FFI shim for wallet, daemon, and transaction operations"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "salvium-miner-gr"
version = "0.1.0"
version.workspace = true
edition = "2021"
description = "GhostRider CPU miner for Salvium cryptocurrency (experimental)"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "salvium-miner-v2"
version = "0.1.0"
version.workspace = true
edition = "2021"
description = "RandomX v2 CPU miner for Salvium cryptocurrency (experimental)"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "salvium-miner"
version = "0.1.0"
version.workspace = true
edition = "2021"
description = "Native RandomX CPU miner for Salvium cryptocurrency"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "salvium-multisig"
version = "0.1.0"
version.workspace = true
edition.workspace = true
license.workspace = true
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "salvium-rpc"
version = "0.1.0"
version.workspace = true
edition.workspace = true
description = "Async and blocking RPC clients for Salvium daemon and wallet"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "salvium-sync-bench"
version = "0.1.0"
version.workspace = true
edition.workspace = true
[[bin]]
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "salvium-tx"
version = "0.1.0"
version.workspace = true
edition.workspace = true
description = "Transaction construction, parsing, and analysis for Salvium"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "salvium-types"
version = "0.1.0"
version.workspace = true
edition.workspace = true
description = "Core types and constants for the Salvium cryptocurrency"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "salvium-wallet"
version = "0.1.0"
version.workspace = true
edition.workspace = true
description = "Wallet core: key management, output scanning, sync engine, balance, UTXO selection"
+4 -1
View File
@@ -1285,10 +1285,13 @@ fn detect_spent_outputs(
.get_output(ki_hex)
.map_err(|e| WalletError::Storage(e.to_string()))?;
if let Some(row) = output {
// Count all outputs that belong to us, even if already marked spent
// (e.g. by mark_inputs_spent after TX submission). This ensures
// stake recording still triggers when the block is later synced.
spent_count += 1;
if !row.is_spent {
db.mark_spent(ki_hex, tx_hash_hex, block_height as i64)
.map_err(|e| WalletError::Storage(e.to_string()))?;
spent_count += 1;
}
}
}
+27 -2
View File
@@ -136,6 +136,23 @@ fn resume_from_hf() -> u8 {
.unwrap_or(0)
}
/// Auto-detect the appropriate resume HF based on the daemon's current height.
///
/// If the chain has already progressed past a fork boundary, the daemon will
/// reject transactions built with that fork's parameters (wrong rct_type, etc.).
/// Find the latest fork whose height the chain has passed, and resume from there.
fn auto_resume_hf(daemon_height: u64) -> u8 {
let mut resume = 0u8;
for (i, fork) in FORKS.iter().enumerate() {
let next_height = FORKS.get(i + 1).map(|f| f.height).unwrap_or(u64::MAX);
if daemon_height >= next_height {
// Chain is past this fork AND the next one — skip this fork
resume = fork.hf + 1;
}
}
resume
}
async fn get_daemon_height(daemon: &DaemonRpc) -> u64 {
let info = daemon.get_info().await.expect("failed to get daemon info");
info.height
@@ -1265,7 +1282,7 @@ async fn full_testnet_hardfork_progression() {
println!("=== Phase 0: Setup ===\n");
let url = daemon_url();
let resume_hf = resume_from_hf();
let env_resume_hf = resume_from_hf();
let daemon = DaemonRpc::new(&url);
let info = daemon.get_info().await.expect("cannot connect to daemon");
@@ -1276,8 +1293,16 @@ async fn full_testnet_hardfork_progression() {
);
assert!(info.synchronized, "daemon is not synchronized");
// Auto-detect resume point from chain state if not explicitly set.
// If the chain has already advanced past fork boundaries, skip those
// forks — the daemon will reject TXs built with outdated parameters.
let resume_hf = if env_resume_hf > 0 {
env_resume_hf
} else {
auto_resume_hf(info.height)
};
if resume_hf > 0 {
println!(" Resuming from HF{}", resume_hf);
println!(" Resuming from HF{} (chain already at height {})", resume_hf, info.height);
}
let fixture = TestFixture::create();
+39 -14
View File
@@ -1,20 +1,22 @@
#!/usr/bin/env bash
# Build libsalvium_crypto.so for Android (arm64, armv7, x86_64)
# Build Salvium shared libraries for Android (arm64, armv7, x86_64)
#
# Produces:
# prebuilt/android/arm64-v8a/libsalvium_crypto.so
# prebuilt/android/arm64-v8a/libsalvium_ffi.so
# prebuilt/android/armeabi-v7a/libsalvium_crypto.so
# prebuilt/android/armeabi-v7a/libsalvium_ffi.so
# prebuilt/android/x86_64/libsalvium_crypto.so
# prebuilt/android/x86_64/libsalvium_ffi.so
#
# Prerequisites:
# rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android
# Set ANDROID_NDK_HOME to your NDK path (e.g. ~/Android/Sdk/ndk/26.1.10909125)
#
# Produces:
# prebuilt/android/arm64-v8a/libsalvium_crypto.so
# prebuilt/android/armeabi-v7a/libsalvium_crypto.so
# prebuilt/android/x86_64/libsalvium_crypto.so
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$SCRIPT_DIR/.."
CRATE_DIR="$ROOT_DIR/crates/salvium-crypto"
OUT_DIR="$ROOT_DIR/prebuilt/android"
if [ -z "${ANDROID_NDK_HOME:-}" ]; then
@@ -40,38 +42,61 @@ declare -A TARGET_CC=(
# Find the NDK toolchain bin directory
TOOLCHAIN="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin"
if [ ! -d "$TOOLCHAIN" ]; then
# Try macOS path
TOOLCHAIN="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin"
fi
if [ ! -d "$TOOLCHAIN" ]; then
TOOLCHAIN="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/darwin-aarch64/bin"
fi
if [ ! -d "$TOOLCHAIN" ]; then
echo "Error: Cannot find NDK toolchain in $ANDROID_NDK_HOME"
exit 1
fi
echo "==> Building salvium-crypto for Android targets..."
# Crates to build (order matters: ffi depends on crypto, but cargo handles that)
CRATES=("salvium-crypto" "salvium-ffi")
echo "==> Building Salvium libraries for Android"
echo " NDK: $ANDROID_NDK_HOME"
echo " Crates: ${CRATES[*]}"
echo ""
for target in "${!TARGET_ABI[@]}"; do
abi="${TARGET_ABI[$target]}"
cc="${TARGET_CC[$target]}"
echo " -> $target ($abi)"
echo " ── $target ($abi) ──"
# Set linker for this target via CARGO_TARGET env vars
# Set linker/compiler for this target via CARGO_TARGET env vars
target_upper="${target//-/_}"
target_upper="${target_upper^^}"
export "CARGO_TARGET_${target_upper}_LINKER=$TOOLCHAIN/$cc"
export "CC_${target//-/_}=$TOOLCHAIN/$cc"
export "AR_${target//-/_}=$TOOLCHAIN/llvm-ar"
cargo build --release --target "$target" --manifest-path "$CRATE_DIR/Cargo.toml"
for crate in "${CRATES[@]}"; do
echo " Building $crate..."
cargo build --release \
--target "$target" \
-p "$crate" \
--manifest-path "$ROOT_DIR/Cargo.toml"
done
# Copy built .so files to prebuilt directory
mkdir -p "$OUT_DIR/$abi"
cp "$CRATE_DIR/target/$target/release/libsalvium_crypto.so" \
# salvium-crypto -> libsalvium_crypto.so
cp "$ROOT_DIR/target/$target/release/libsalvium_crypto.so" \
"$OUT_DIR/$abi/libsalvium_crypto.so"
# salvium-ffi -> libsalvium_ffi.so
cp "$ROOT_DIR/target/$target/release/libsalvium_ffi.so" \
"$OUT_DIR/$abi/libsalvium_ffi.so"
echo ""
done
echo "==> Done. Libraries:"
for target in "${!TARGET_ABI[@]}"; do
abi="${TARGET_ABI[$target]}"
ls -lh "$OUT_DIR/$abi/libsalvium_crypto.so"
echo " $abi:"
ls -lh "$OUT_DIR/$abi/"*.so
done
+48 -24
View File
@@ -1,16 +1,17 @@
#!/usr/bin/env bash
# Build libsalvium_crypto.a for iOS (device + simulator) as an xcframework.
# Build Salvium static libraries for iOS (device + simulator) as xcframeworks.
#
# Produces:
# prebuilt/ios/SalviumCrypto.xcframework
# prebuilt/ios/SalviumFfi.xcframework
#
# Prerequisites:
# rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios
#
# Produces: prebuilt/ios/SalviumCrypto.xcframework
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$SCRIPT_DIR/.."
CRATE_DIR="$ROOT_DIR/crates/salvium-crypto"
OUT_DIR="$ROOT_DIR/prebuilt/ios"
WORK_DIR="$OUT_DIR/build"
@@ -20,37 +21,60 @@ TARGETS=(
x86_64-apple-ios # Simulator (Intel)
)
echo "==> Building salvium-crypto for iOS targets..."
# Libraries to build and package
declare -A LIB_NAMES=(
[salvium-crypto]="libsalvium_crypto"
[salvium-ffi]="libsalvium_ffi"
)
declare -A FRAMEWORK_NAMES=(
[salvium-crypto]="SalviumCrypto"
[salvium-ffi]="SalviumFfi"
)
echo "==> Building Salvium libraries for iOS targets..."
for target in "${TARGETS[@]}"; do
echo " -> $target"
cargo build --release --target "$target" --manifest-path "$CRATE_DIR/Cargo.toml"
for crate in "${!LIB_NAMES[@]}"; do
cargo build --release \
--target "$target" \
-p "$crate" \
--manifest-path "$ROOT_DIR/Cargo.toml"
done
done
echo "==> Creating xcframework..."
echo "==> Creating xcframeworks..."
mkdir -p "$WORK_DIR"
# Device .a (single arch, no lipo needed)
DEVICE_LIB="$CRATE_DIR/target/aarch64-apple-ios/release/libsalvium_crypto.a"
for crate in "${!LIB_NAMES[@]}"; do
lib="${LIB_NAMES[$crate]}"
framework="${FRAMEWORK_NAMES[$crate]}"
# Merge simulator slices (aarch64-sim + x86_64) into one fat .a
SIM_FAT="$WORK_DIR/libsalvium_crypto-sim.a"
lipo -create \
"$CRATE_DIR/target/aarch64-apple-ios-sim/release/libsalvium_crypto.a" \
"$CRATE_DIR/target/x86_64-apple-ios/release/libsalvium_crypto.a" \
-output "$SIM_FAT"
echo " -> $framework.xcframework"
# Remove old xcframework if present
rm -rf "$OUT_DIR/SalviumCrypto.xcframework"
# Device .a (single arch, no lipo needed)
DEVICE_LIB="$ROOT_DIR/target/aarch64-apple-ios/release/${lib}.a"
# Create xcframework from device .a + simulator fat .a
xcodebuild -create-xcframework \
-library "$DEVICE_LIB" \
-library "$SIM_FAT" \
-output "$OUT_DIR/SalviumCrypto.xcframework"
# Merge simulator slices (aarch64-sim + x86_64) into one fat .a
SIM_FAT="$WORK_DIR/${lib}-sim.a"
lipo -create \
"$ROOT_DIR/target/aarch64-apple-ios-sim/release/${lib}.a" \
"$ROOT_DIR/target/x86_64-apple-ios/release/${lib}.a" \
-output "$SIM_FAT"
# Remove old xcframework if present
rm -rf "$OUT_DIR/$framework.xcframework"
# Create xcframework from device .a + simulator fat .a
xcodebuild -create-xcframework \
-library "$DEVICE_LIB" \
-library "$SIM_FAT" \
-output "$OUT_DIR/$framework.xcframework"
done
# Clean up work dir
rm -rf "$WORK_DIR"
echo "==> Done: $OUT_DIR/SalviumCrypto.xcframework"
echo "==> Done:"
ls -d "$OUT_DIR"/*.xcframework
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Build Salvium shared libraries for Linux (x86_64)
#
# Produces:
# prebuilt/linux-x86_64/libsalvium_crypto.so
# prebuilt/linux-x86_64/libsalvium_ffi.so
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$SCRIPT_DIR/.."
OUT_DIR="$ROOT_DIR/prebuilt/linux-x86_64"
echo "==> Building Salvium libraries for Linux x86_64..."
cargo build --release \
-p salvium-crypto \
-p salvium-ffi \
--manifest-path "$ROOT_DIR/Cargo.toml"
mkdir -p "$OUT_DIR"
cp "$ROOT_DIR/target/release/libsalvium_crypto.so" "$OUT_DIR/"
cp "$ROOT_DIR/target/release/libsalvium_ffi.so" "$OUT_DIR/"
echo "==> Done:"
ls -lh "$OUT_DIR/"*.so
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Build Salvium shared/static libraries for macOS (arm64 + x86_64)
#
# Produces:
# prebuilt/macos/libsalvium_crypto.dylib (universal)
# prebuilt/macos/libsalvium_ffi.dylib (universal)
# prebuilt/macos/libsalvium_crypto.a (universal)
# prebuilt/macos/libsalvium_ffi.a (universal)
#
# Prerequisites:
# rustup target add aarch64-apple-darwin x86_64-apple-darwin
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$SCRIPT_DIR/.."
OUT_DIR="$ROOT_DIR/prebuilt/macos"
TARGETS=(aarch64-apple-darwin x86_64-apple-darwin)
echo "==> Building Salvium libraries for macOS..."
for target in "${TARGETS[@]}"; do
echo " -> $target"
cargo build --release \
--target "$target" \
-p salvium-crypto \
-p salvium-ffi \
--manifest-path "$ROOT_DIR/Cargo.toml"
done
echo "==> Creating universal binaries..."
mkdir -p "$OUT_DIR"
for lib in libsalvium_crypto libsalvium_ffi; do
# Universal dylib
lipo -create \
"$ROOT_DIR/target/aarch64-apple-darwin/release/${lib}.dylib" \
"$ROOT_DIR/target/x86_64-apple-darwin/release/${lib}.dylib" \
-output "$OUT_DIR/${lib}.dylib"
# Universal static lib
lipo -create \
"$ROOT_DIR/target/aarch64-apple-darwin/release/${lib}.a" \
"$ROOT_DIR/target/x86_64-apple-darwin/release/${lib}.a" \
-output "$OUT_DIR/${lib}.a"
done
echo "==> Done:"
ls -lh "$OUT_DIR/"*
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Build Salvium shared libraries for Windows (x86_64, cross-compile from Linux)
#
# Produces:
# prebuilt/windows-x86_64/salvium_crypto.dll
# prebuilt/windows-x86_64/salvium_ffi.dll
#
# Prerequisites:
# rustup target add x86_64-pc-windows-gnu
# apt install gcc-mingw-w64-x86-64 (or equivalent)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$SCRIPT_DIR/.."
OUT_DIR="$ROOT_DIR/prebuilt/windows-x86_64"
echo "==> Building Salvium libraries for Windows x86_64..."
cargo build --release \
--target x86_64-pc-windows-gnu \
-p salvium-crypto \
-p salvium-ffi \
--manifest-path "$ROOT_DIR/Cargo.toml"
mkdir -p "$OUT_DIR"
cp "$ROOT_DIR/target/x86_64-pc-windows-gnu/release/salvium_crypto.dll" "$OUT_DIR/"
cp "$ROOT_DIR/target/x86_64-pc-windows-gnu/release/salvium_ffi.dll" "$OUT_DIR/"
echo "==> Done:"
ls -lh "$OUT_DIR/"*.dll