Matt Hess 01bcb742ae ● Restructure native module for Expo auto-discovery, fix 3 test failures
Move native code from native/ to Expo-compatible layout with android/
  and ios/ at package root. Add Android JNI bridge (OnLoad.cpp,
  ExpoSalviumCryptoModule.java, ExpoSalviumCryptoPackage.java).
  Prebuilt binaries now go in prebuilt/ instead of native/lib/.

  Test fixes:
  - wallet-sync: update stale DEFAULT_BATCH_SIZE assertion (10 -> 100)
  - bulletproofs+: fix hashToPoint -> hashToPointMonero rename in test,
    fix serializeProof to include V array matching parseProof format
  - transaction-builder: expect 2 outputs for exact-amount tx (zero-change
    output is always added for privacy)
2026-02-09 22:50:15 +00:00
2026-01-05 03:13:07 +00:00

salvium-js

JavaScript library for Salvium cryptocurrency - wallet generation, address handling, RPC clients, key derivation, and cryptographic utilities.

Features

  • Wallet Generation - Generate seeds, derive keys, create addresses
  • Mnemonic Support - 25-word seed phrases in 12 languages
  • Key Derivation - CryptoNote and CARROT key derivation from seeds
  • Address Creation - Create all 18 Salvium address types
  • Subaddress Generation - Generate CryptoNote and CARROT subaddresses
  • Integrated Addresses - Create and parse integrated addresses with payment IDs
  • Address Validation - Parse and validate any Salvium address
  • Transaction Scanning - Detect owned outputs, decrypt amounts, view tags
  • Key Images - Generate and validate key images for spend detection
  • Transaction Construction - Pedersen commitments, CLSAG/TCLSAG signatures, serialization
  • Bulletproofs+ Range Proofs - Pure JS proof generation AND verification (mobile-friendly)
  • RandomX Proof-of-Work - WASM-JIT implementation with official test vectors
  • Stratum Mining Client - Connect to mining pools with multi-threaded hashing
  • Wallet Class - Unified wallet management with view-only mode support
  • UTXO Selection - Multiple strategies (minimize inputs, minimize change, etc.)
  • Transaction Builder - High-level buildTransaction() and signTransaction() API
  • Transaction Parser - Decode and analyze transaction data
  • RPC Clients - Full daemon and wallet RPC implementations
  • Signature Verification - Verify message signatures (V1 and V2 formats)
  • Cryptographic Primitives - Blake2b, Keccak-256, Ed25519, Base58
  • Multi-Network Support - Mainnet, Testnet, Stagenet
  • TypeScript Support - Full type definitions included
  • Minimal Dependencies - Only @noble/curves and @noble/hashes

Address Types Supported

Network Format Standard Integrated Subaddress
Mainnet Legacy SaLv... SaLvi... SaLvs...
Mainnet CARROT SC1... SC1i... SC1s...
Testnet Legacy SaLvT... SaLvTi... SaLvTs...
Testnet CARROT SC1T... SC1Ti... SC1Ts...
Stagenet Legacy SaLvS... SaLvSi... SaLvSs...
Stagenet CARROT SC1S... SC1Si... SC1Ss...

Installation

npm install salvium-js

Or include directly in browser:

<script type="module">
  import salvium from './salvium-js/src/index.js';
</script>

Quick Start: Generate a Wallet

import {
  generateSeed,
  seedToMnemonic,
  deriveKeys,
  createAddress,
  generateCNSubaddress,
  bytesToHex
} from 'salvium-js';

// 1. Generate a cryptographically secure random seed
const seed = generateSeed();  // 32 random bytes

// 2. Convert to 25-word mnemonic for backup
const mnemonic = seedToMnemonic(seed, { language: 'english' });
console.log('Backup these words:', mnemonic);

// 3. Derive wallet keys from seed
const keys = deriveKeys(seed);
// {
//   spendSecretKey: Uint8Array(32),
//   spendPublicKey: Uint8Array(32),
//   viewSecretKey: Uint8Array(32),
//   viewPublicKey: Uint8Array(32)
// }

// 4. Create main wallet address
const address = createAddress({
  network: 'mainnet',
  format: 'legacy',
  type: 'standard',
  spendPublicKey: keys.spendPublicKey,
  viewPublicKey: keys.viewPublicKey
});
console.log('Main address:', address);  // SaLv...

// 5. Generate subaddresses
const subaddress = generateCNSubaddress({
  network: 'mainnet',
  spendPublicKey: keys.spendPublicKey,
  viewSecretKey: keys.viewSecretKey,
  major: 0,  // account index
  minor: 1   // address index
});
console.log('Subaddress:', subaddress.address);  // SaLvs...

// 6. Display secret keys (keep these safe!)
console.log('Spend secret key:', bytesToHex(keys.spendSecretKey));
console.log('View secret key:', bytesToHex(keys.viewSecretKey));

Restore Wallet from Mnemonic

import { mnemonicToSeed, deriveKeys, createAddress } from 'salvium-js';

const mnemonic = 'abbey ability able about above absent ...';  // 25 words

const result = mnemonicToSeed(mnemonic, { language: 'english' });
if (result.valid) {
  const keys = deriveKeys(result.seed);
  const address = createAddress({
    network: 'mainnet',
    format: 'legacy',
    type: 'standard',
    spendPublicKey: keys.spendPublicKey,
    viewPublicKey: keys.viewPublicKey
  });
  console.log('Restored address:', address);
} else {
  console.error('Invalid mnemonic:', result.error);
}

CARROT Address Format

import {
  generateSeed,
  deriveKeys,
  deriveCarrotKeys,
  createAddress,
  generateCarrotSubaddress,
  hexToBytes,
  scalarMultPoint
} from 'salvium-js';

const seed = generateSeed();

// Derive CryptoNote keys (for public keys)
const keys = deriveKeys(seed);

// Derive CARROT-specific keys
const carrotKeys = deriveCarrotKeys(seed);
// {
//   masterSecret: '...',           // hex string
//   proveSpendKey: '...',          // k_ps
//   viewBalanceSecret: '...',      // s_vb
//   generateImageKey: '...',       // k_gi
//   viewIncomingKey: '...',        // k_vi
//   generateAddressSecret: '...'   // s_ga
// }

// Create CARROT main address
const carrotAddress = createAddress({
  network: 'mainnet',
  format: 'carrot',
  type: 'standard',
  spendPublicKey: keys.spendPublicKey,
  viewPublicKey: keys.viewPublicKey
});
console.log('CARROT address:', carrotAddress);  // SC1...

// Generate CARROT subaddress
const carrotSub = generateCarrotSubaddress({
  network: 'mainnet',
  accountSpendPubkey: keys.spendPublicKey,
  accountViewPubkey: keys.viewPublicKey,
  generateAddressSecret: hexToBytes(carrotKeys.generateAddressSecret),
  major: 0,
  minor: 1
});
console.log('CARROT subaddress:', carrotSub.address);  // SC1s...

Integrated Addresses (with Payment ID)

import {
  toIntegratedAddress,
  toStandardAddress,
  getPaymentId,
  generateRandomPaymentId,
  createIntegratedAddressWithRandomId,
  bytesToHex
} from 'salvium-js';

// Create integrated address with specific payment ID
const integrated = toIntegratedAddress('SaLv...', 'deadbeef12345678');
console.log('Integrated:', integrated);  // SaLvi...

// Create with random payment ID
const result = createIntegratedAddressWithRandomId('SaLv...');
console.log('Integrated:', result.address);
console.log('Payment ID:', result.paymentIdHex);

// Extract payment ID from integrated address
const paymentId = getPaymentId(integrated);
console.log('Payment ID:', bytesToHex(paymentId));

// Get standard address from integrated
const standard = toStandardAddress(integrated);

Validate and Parse Addresses

import { isValidAddress, parseAddress, describeAddress } from 'salvium-js';

// Simple validation
if (isValidAddress('SC1...')) {
  console.log('Valid!');
}

// Detailed parsing
const info = parseAddress('SC1...');
// {
//   valid: true,
//   network: 'mainnet',
//   format: 'carrot',
//   type: 'standard',
//   prefix: 'SC1',
//   spendPublicKey: Uint8Array(32),
//   viewPublicKey: Uint8Array(32),
//   paymentId: null,
//   error: null
// }

// Human-readable description
console.log(describeAddress('SaLvi...'));
// "Mainnet Legacy integrated (Payment ID: abcd1234...)"

Check Address Properties

import {
  isMainnet, isTestnet, isStagenet,
  isCarrot, isLegacy,
  isStandard, isIntegrated, isSubaddress
} from 'salvium-js';

const addr = 'SC1...';

isMainnet(addr);    // true
isCarrot(addr);     // true
isStandard(addr);   // true
isSubaddress(addr); // false

Mnemonic Seeds

import {
  seedToMnemonic,
  mnemonicToSeed,
  validateMnemonic,
  detectLanguage,
  getAvailableLanguages
} from 'salvium-js';

// Generate mnemonic from seed
const mnemonic = seedToMnemonic(seed, { language: 'english' });
// 25 words

// Convert mnemonic back to seed
const result = mnemonicToSeed(mnemonic, { language: 'english' });
if (result.valid) {
  console.log('Seed:', result.seed);
}

// Auto-detect language
const detected = detectLanguage(mnemonic);
console.log('Language:', detected.language.name);

// Validate without converting
const validation = validateMnemonic(mnemonic, { language: 'auto' });
console.log('Valid:', validation.valid);

// Available languages
console.log(getAvailableLanguages());
// ['english', 'spanish', 'french', 'italian', 'german', 'portuguese',
//  'russian', 'japanese', 'chinese_simplified', 'dutch', 'esperanto', 'lojban']

RPC Clients

Full-featured RPC clients for Salvium daemon and wallet services.

Default Ports (from cryptonote_config.h)

Service Mainnet Testnet Stagenet
Daemon RPC 19081 29081 39081
ZMQ RPC 19083 29083 39083
Wallet RPC* 19083 29083 39083

*Wallet RPC has no default in source - port is user-specified, conventionally daemon+1

Daemon RPC

import { createDaemonRPC } from 'salvium-js/rpc';

const daemon = createDaemonRPC({ url: 'http://localhost:19081' });

// Get node info
const info = await daemon.getInfo();
if (info.success) {
  console.log('Height:', info.result.height);
  console.log('Network hashrate:', info.result.difficulty / 120);
  console.log('Synchronized:', info.result.synchronized);
}

// Get block by height
const block = await daemon.getBlockHeaderByHeight(100000);

// Get transactions
const txs = await daemon.getTransactions(['txhash1', 'txhash2']);

// Get fee estimate
const fee = await daemon.getFeeEstimate();
console.log('Fee per byte:', fee.result.fee);

// Mining
const template = await daemon.getBlockTemplate({
  wallet_address: 'SaLv...',
  reserve_size: 8
});

Wallet RPC

import { createWalletRPC, PRIORITY } from 'salvium-js/rpc';

const wallet = createWalletRPC({
  url: 'http://localhost:19083',
  username: 'user',
  password: 'pass'
});

// Open wallet
await wallet.openWallet({ filename: 'mywallet', password: 'secret' });

// Get balance (amounts in atomic units, divide by 1e8 for SAL)
const balance = await wallet.getBalance();
if (balance.success) {
  console.log('Balance:', balance.result.balance / 1e8, 'SAL');
  console.log('Unlocked:', balance.result.unlocked_balance / 1e8, 'SAL');
}

// Get address
const addr = await wallet.getAddress();
console.log('Address:', addr.result.address);

// Send transaction (1 SAL = 100000000 atomic units)
const tx = await wallet.transfer({
  destinations: [{ address: 'SaLv...', amount: 100000000 }],
  priority: PRIORITY.NORMAL
});

// Get transaction history
const transfers = await wallet.getTransfers({ in: true, out: true });

// Create subaddress
const newAddr = await wallet.createAddress({ account_index: 0 });

// Close wallet
await wallet.closeWallet();

RPC Client Options

const daemon = createDaemonRPC({
  url: 'http://localhost:19081',
  timeout: 30000,        // Request timeout in ms (default: 30000)
  retries: 2,            // Retry attempts (default: 2)
  retryDelay: 1000,      // Delay between retries in ms (default: 1000)
  username: 'user',      // HTTP basic auth username
  password: 'pass'       // HTTP basic auth password
});

Available Daemon RPC Methods

  • Network: getInfo, getHeight, syncInfo, hardForkInfo, getNetStats, getConnections, getPeerList
  • Blocks: getBlockHash, getBlock, getBlockHeaderByHash, getBlockHeaderByHeight, getBlockHeadersRange, getLastBlockHeader
  • Transactions: getTransactions, getTransactionPool, sendRawTransaction, relayTx
  • Outputs: getOuts, getOutputHistogram, getOutputDistribution, isKeyImageSpent
  • Mining: getBlockTemplate, submitBlock, getMinerData, calcPow
  • Fees: getFeeEstimate, getBaseFeeEstimate, getCoinbaseTxSum

Available Wallet RPC Methods

  • Wallet: createWallet, openWallet, closeWallet, restoreDeterministicWallet, generateFromKeys
  • Accounts: getAccounts, createAccount, labelAccount, getAddress, createAddress
  • Balance: getBalance, getTransfers, getTransferByTxid, incomingTransfers
  • Transfers: transfer, transferSplit, sweepAll, sweepSingle, sweepDust
  • Proofs: getTxKey, checkTxKey, getTxProof, checkTxProof, getReserveProof
  • Keys: queryKey, getMnemonic, exportOutputs, importOutputs, exportKeyImages
  • Signing: sign, verify, signMultisig, submitMultisig

Transaction Scanning

Detect owned outputs in transactions and decrypt amounts.

import {
  generateKeyDerivation,
  derivePublicKey,
  deriveSecretKey,
  checkOutputOwnership,
  scanOutput,
  ecdhDecode,
  deriveViewTag
} from 'salvium-js';

// Given a transaction with tx public key R and your view secret key a:
const derivation = generateKeyDerivation(txPubKey, viewSecretKey);

// Check if output at index n belongs to you
const outputKey = tx.outputs[n].target;
const owned = checkOutputOwnership(derivation, n, spendPublicKey, outputKey);

if (owned) {
  // Decrypt the amount
  const amount = ecdhDecode(encryptedAmount, derivation, n);
  console.log('Received:', amount, 'atomic units');

  // Derive the secret key to spend this output
  const outputSecretKey = deriveSecretKey(derivation, n, spendSecretKey);
}

// Full output scan with view tag optimization
const result = scanOutput({
  derivation,
  outputIndex: 0,
  outputPublicKey: tx.outputs[0].target,
  spendPublicKey,
  viewTag: tx.outputs[0].viewTag,  // optional
  encryptedAmount: tx.outputs[0].encryptedAmount
});

if (result.owned) {
  console.log('Amount:', result.amount);
  console.log('Output secret:', result.outputSecretKey);
}

Key Images

Generate key images for spend detection (required for full wallet functionality).

import {
  hashToPoint,
  generateKeyImage,
  isValidKeyImage,
  exportKeyImages,
  importKeyImages
} from 'salvium-js';

// Generate key image for an output you own
// KI = x * H_p(P) where x is your output secret key, P is output public key
const keyImage = generateKeyImage(outputPublicKey, outputSecretKey);

// Validate a key image
if (isValidKeyImage(keyImage)) {
  console.log('Valid key image');
}

// Export key images for view-only wallet sync
const exported = exportKeyImages([
  { keyImage: ki1, txHash: 'abc...', outputIndex: 0 },
  { keyImage: ki2, txHash: 'def...', outputIndex: 1 }
]);

// Import key images into a view-only wallet
const keyImageMap = importKeyImages(exported);
// Can now detect which outputs have been spent

Transaction Construction

Build transactions with Pedersen commitments and CLSAG/TCLSAG ring signatures.

import {
  // Scalar operations
  scAdd, scSub, scMul, scRandom, scInvert,
  // Commitments
  commit, zeroCommit, genCommitmentMask,
  // Output creation
  generateOutputKeys, createOutput,
  // Signing (CLSAG for standard, TCLSAG for SalviumOne)
  clsagSign, clsagVerify,
  tclsagSign, tclsagVerify,
  // Serialization
  serializeTxPrefix, getTxPrefixHash, encodeVarint
} from 'salvium-js';

// Create a transaction output
const txSecretKey = scRandom();  // r - transaction secret key
const output = createOutput(
  txSecretKey,
  recipientViewPublicKey,
  recipientSpendPublicKey,
  1000000000n,  // 10 SAL in atomic units
  0,            // output index
  false         // isSubaddress
);
// Returns: { outputPublicKey, txPublicKey, commitment, encryptedAmount, mask }

// Pedersen commitment: C = mask*G + amount*H
const mask = scRandom();
const commitment = commit(1000000000n, mask);

// Zero commitment for fees (public amount)
const feeCommitment = zeroCommit(10000000n);  // 0.1 SAL fee

// CLSAG ring signature (standard RingCT)
const signature = clsagSign(
  message,           // Pre-MLSAG hash
  ring,              // Array of public keys (decoys + real)
  secretKey,         // Your secret key
  commitments,       // Ring member commitments
  maskDiff,          // Your mask - pseudo output mask
  pseudoCommitment,  // Pseudo output commitment
  secretIndex        // Your position in ring
);

// Verify CLSAG signature
const valid = clsagVerify(message, signature, ring, commitments, pseudoCommitment);

// TCLSAG ring signature (SalviumOne - uses dual generators G and T)
const tclsagSig = tclsagSign(
  message,           // Pre-MLSAG hash
  ring,              // Array of public keys
  secretKeyX,        // Spend secret key component
  secretKeyY,        // Auxiliary secret key component
  commitments,       // Ring member commitments
  maskDiff,          // Commitment mask difference
  pseudoCommitment,  // Pseudo output commitment
  secretIndex        // Your position in ring
);
// Returns { sx: [], sy: [], c1, I, D } - dual scalar arrays

// Verify TCLSAG signature
const tclsagValid = tclsagVerify(message, tclsagSig, ring, commitments, pseudoCommitment);

Bulletproofs+ Range Proofs

Generate and verify range proofs that prove transaction amounts are in valid range (0 to 2^64-1) without revealing the actual amounts. 100% pure JavaScript - no WASM required.

import {
  // Proof generation
  proveRange,
  proveRangeMultiple,
  randomScalar,
  serializeProof,
  // Verification
  verifyBulletproofPlus,
  verifyRangeProof,
  parseProof,
  initGenerators,
  bytesToPoint
} from 'salvium-js';

// === PROOF GENERATION ===

// Generate a range proof for a single amount
const amount = 1000000000n;  // 10 SAL in atomic units
const mask = randomScalar();  // Blinding factor (commitment mask)

const proof = proveRange(amount, mask);
// proof = { V, A, A1, B, r1, s1, d1, L, R }
// V[0] is the Pedersen commitment: mask*G + amount*H

// Generate proof for multiple amounts (batched, more efficient)
const amounts = [1000000000n, 500000000n];
const masks = [randomScalar(), randomScalar()];
const batchProof = proveRangeMultiple(amounts, masks);

// Serialize for transmission
const proofBytes = serializeProof(proof);

// === VERIFICATION ===

// Verify a generated proof
const valid = verifyBulletproofPlus(proof.V, proof);

// Verify from serialized bytes (e.g., from a transaction)
const isValid = verifyRangeProof(
  tx.rctSig.outPk,      // Array of commitment bytes
  tx.rctSig.proofBytes  // Serialized Bulletproof+ proof
);

// Parse and verify separately for more control
const parsedProof = parseProof(proofBytes);
const verified = verifyBulletproofPlus(commitments, parsedProof);

Performance (pure JavaScript, no WASM):

Operation Time
Generator init (1024 pts) ~800ms (one-time, cached)
Single proof generation ~1100ms
Proof verification ~300ms
Round-trip (generate + verify) ~1400ms

This is fast enough for mobile wallets (React Native on iOS/Android).

// Serialize transaction
const tx = {
  version: 2,
  unlockTime: 0n,
  inputs: [{ amount: 0n, keyOffsets: [100n, 50n], keyImage }],
  outputs: [{ amount: 0n, target: output.outputPublicKey, viewTag: 0x42 }],
  extra: { txPubKey: output.txPublicKey }
};
const serialized = serializeTxPrefix(tx);
const prefixHash = getTxPrefixHash(tx);

RandomX Proof-of-Work

WASM-JIT accelerated RandomX implementation for mining and verification. Validated against official test vectors.

import {
  RandomXContext,
  rxSlowHash,
  verifyHash,
  checkDifficulty,
  mine
} from 'salvium-js';

// === BASIC HASHING ===

// One-shot hash (creates temporary context)
const hash = await rxSlowHash('block header hash', 'block blob');

// === REUSABLE CONTEXT (Recommended) ===

// For multiple hashes with same key, reuse context
const ctx = new RandomXContext();
await ctx.init('test key 000');  // Initialize 256MB cache

const hash1 = ctx.hash('This is a test');
const hash2 = ctx.hash('Another input');  // Much faster, reuses cache

// Verify against official test vectors
console.log(ctx.hashHex('This is a test'));
// '639183aae1bf4c9a35884cb46b09cad9175f04efd7684e7262a0ac1c2f0b4e3f'

// === VERIFICATION ===

// Verify a hash
const isValid = await verifyHash(key, input, expectedHash);

// Check if hash meets difficulty target
const meetsDifficulty = checkDifficulty(hash, difficulty);

// === MINING ===

// Find nonce that meets difficulty
const result = await mine(
  key,            // Cache key (prev block hash)
  blockBlob,      // Block blob with nonce placeholder
  nonceOffset,    // Byte offset of nonce in blob
  difficulty,     // Target difficulty (BigInt)
  maxNonce        // Max nonce to try (default: 2^32)
);

if (result) {
  console.log('Found nonce:', result.nonce);
  console.log('Hash:', result.hash);
}

Modes:

  • Light mode (default) - 256MB cache per thread, suitable for verification and mining
  • Full mode - 2GB shared dataset, faster hashing for dedicated miners

Multi-threaded Mining:

import { RandomXWorkerPool, getAvailableCores } from 'salvium-js';

const pool = new RandomXWorkerPool(getAvailableCores());
await pool.init(key);
const hash = await pool.hash(input);
pool.terminate();

Verify Message Signatures

import { verifySignature, parseSignature } from 'salvium-js';

// Verify a signature created with `sign` command in salvium-wallet-cli
const result = verifySignature(
  'Hello, World!',    // The original message
  'SC1...',           // The signer's address
  'SigV2...'          // The signature string
);

console.log(result);
// {
//   valid: true,
//   version: 2,
//   keyType: 'spend',  // or 'view'
//   error: null
// }

Cryptographic Primitives

import { keccak256, keccak256Hex, blake2b, blake2bHex } from 'salvium-js';

// Keccak-256 (CryptoNote fast hash)
const hash = keccak256('hello');        // Uint8Array(32)
const hex = keccak256Hex('hello');      // "1c8aff950685..."

// Blake2b (CARROT key derivation)
const b2hash = blake2b(data, 32);       // 32-byte output
const b2keyed = blake2b(data, 32, key); // Keyed hash (MAC)

API Reference

Wallet Generation

Function Description
generateSeed() Generate 32-byte cryptographically secure random seed
deriveKeys(seed) Derive CryptoNote keys from seed
deriveCarrotKeys(seed) Derive CARROT keys from seed
createAddress(options) Create address from public keys

Mnemonic Functions

Function Description
seedToMnemonic(seed, options) Convert 32-byte seed to 25-word mnemonic
mnemonicToSeed(mnemonic, options) Convert mnemonic to seed
validateMnemonic(mnemonic, options) Validate mnemonic without converting
detectLanguage(mnemonic) Auto-detect mnemonic language
getAvailableLanguages() List supported languages

Subaddress Functions

Function Description
generateCNSubaddress(options) Generate CryptoNote subaddress
generateCarrotSubaddress(options) Generate CARROT subaddress
generateRandomPaymentId() Generate 8-byte random payment ID

Address Functions

Function Description
parseAddress(addr) Parse address, returns detailed info object
isValidAddress(addr) Returns true if valid
isMainnet(addr) Check if mainnet address
isTestnet(addr) Check if testnet address
isStagenet(addr) Check if stagenet address
isCarrot(addr) Check if CARROT format
isLegacy(addr) Check if legacy CryptoNote format
isStandard(addr) Check if standard address
isIntegrated(addr) Check if integrated address
isSubaddress(addr) Check if subaddress
getSpendPublicKey(addr) Extract 32-byte spend public key
getViewPublicKey(addr) Extract 32-byte view public key
getPaymentId(addr) Extract 8-byte payment ID (integrated only)
toIntegratedAddress(addr, paymentId) Create integrated from standard
toStandardAddress(addr) Extract standard from integrated
describeAddress(addr) Human-readable description

Transaction Scanning Functions

Function Description
generateKeyDerivation(pubKey, secretKey) Compute ECDH shared secret D = 8sP
derivationToScalar(derivation, outputIndex) Derive scalar from derivation
derivePublicKey(derivation, index, spendPub) Derive one-time public key
deriveSecretKey(derivation, index, spendSec) Derive one-time secret key
deriveViewTag(derivation, index) Compute view tag for fast filtering
checkOutputOwnership(derivation, index, spendPub, outputPub) Check if output belongs to wallet
scanOutput(params) Full output scan with amount decryption
ecdhDecode(encrypted, derivation, index) Decrypt amount
ecdhEncode(amount, derivation, index) Encrypt amount

Key Image Functions

Function Description
hashToPoint(data) Hash to curve point (Elligator 2)
generateKeyImage(outputPub, outputSec) Generate key image KI = x*H_p(P)
isValidKeyImage(keyImage) Validate key image is on curve
exportKeyImages(outputs) Format key images for export
importKeyImages(data) Create lookup map from exported data

Transaction Construction Functions

Function Description
scAdd(a, b) Add scalars mod L
scSub(a, b) Subtract scalars mod L
scMul(a, b) Multiply scalars mod L
scRandom() Generate random scalar
scInvert(a) Compute modular inverse
commit(amount, mask) Create Pedersen commitment
zeroCommit(amount) Create commitment with zero mask
generateOutputKeys(txSec, viewPub, spendPub, index) Generate one-time output keys
createOutput(txSec, viewPub, spendPub, amount, index) Create full transaction output
clsagSign(message, ring, secret, commitments, mask, pseudo, index) Generate CLSAG signature
clsagVerify(message, sig, ring, commitments, pseudo) Verify CLSAG signature
tclsagSign(message, ring, secretX, secretY, commitments, mask, pseudo, index) Generate TCLSAG signature (SalviumOne)
tclsagVerify(message, sig, ring, commitments, pseudo) Verify TCLSAG signature
serializeTxPrefix(tx) Serialize transaction prefix
getTxPrefixHash(tx) Compute transaction prefix hash
encodeVarint(value) Encode integer as varint
decodeVarint(bytes, offset) Decode varint from bytes

Bulletproofs+ Functions

Function Description
proveRange(amount, mask) Generate range proof for single amount
proveRangeMultiple(amounts, masks) Generate batched range proof for multiple amounts
randomScalar() Generate cryptographically secure random scalar
serializeProof(proof) Serialize proof to bytes for transmission
verifyBulletproofPlus(V, proof) Verify single range proof
verifyBulletproofPlusBatch(proofs) Batch verify multiple proofs
verifyRangeProof(commitments, proofBytes) Verify from raw bytes
initGenerators(n) Initialize Gi/Hi generators (cached)
parseProof(proofBytes) Parse proof from serialized bytes
multiScalarMul(scalars, points) Multiscalar multiplication
bytesToPoint(bytes) Decode compressed point
bytesToScalar(bytes) Decode scalar from bytes

RandomX Functions

Function Description
RandomXContext Reusable context class for repeated hashing with same key
rxSlowHash(key, input) One-shot RandomX hash computation
randomxHash(key, input) Alias for rxSlowHash
verifyHash(key, input, expected) Verify RandomX hash matches expected
checkDifficulty(hash, difficulty) Check if hash meets difficulty target
mine(key, blob, nonceOffset, difficulty, maxNonce, onProgress) Find nonce meeting difficulty
calculateCommitment(input, hashIn) Calculate hash commitment
RandomXCache Cache class for Argon2d-initialized memory
initDatasetItem(cache, itemNumber) Generate single dataset item from cache
RandomXNative WASM-JIT RandomX context (vendored randomx.js)
Blake2Generator Pseudo-random byte generator using Blake2b
generateSuperscalar(gen) Generate superscalar program
executeSuperscalar(registers, program) Execute superscalar program on registers
reciprocal(divisor) Compute reciprocal for IMUL_RCP instruction
argon2d(password, salt, tCost, mCost, parallelism, outLen) Argon2d hash function
argon2InitCache(key) Initialize RandomX cache with Argon2d

Wallet Functions

Function Description
Wallet Wallet class with full and view-only modes
createWallet(options?) Create new wallet with mnemonic
restoreWallet(mnemonic, options?) Restore wallet from mnemonic
createViewOnlyWallet(options) Create view-only wallet from keys
wallet.getAddress() Get main wallet address
wallet.getSubaddress(major, minor) Generate subaddress
wallet.getBalance() Get balance (total, unlocked, locked)
wallet.canSign() Check if wallet can sign transactions
wallet.canScan() Check if wallet can scan for outputs
wallet.toJSON(includeSecrets?) Serialize wallet to JSON
Wallet.fromJSON(json) Restore wallet from JSON

UTXO Selection Functions

Function Description
selectUTXOs(utxos, amount, options?) Select UTXOs for transaction
UTXO_STRATEGY.MINIMIZE_INPUTS Strategy to minimize number of inputs
UTXO_STRATEGY.MINIMIZE_CHANGE Strategy to minimize change amount
UTXO_STRATEGY.OLDEST_FIRST Strategy to spend oldest UTXOs first
UTXO_STRATEGY.NEWEST_FIRST Strategy to spend newest UTXOs first
UTXO_STRATEGY.RANDOM Random UTXO selection

Transaction Builder Functions

Function Description
buildTransaction(options) Build complete transaction with signatures
signTransaction(unsignedTx, spendSecretKey) Sign an unsigned transaction
prepareInputs(utxos, viewSecretKey) Prepare inputs with ring members
estimateTransactionFee(numInputs, numOutputs, feePerByte?) Estimate transaction fee
validateTransaction(tx) Validate transaction structure
serializeTransaction(tx) Serialize transaction for broadcast

Transaction Parser Functions

Function Description
parseTransaction(txData) Parse raw transaction bytes/hex
parseExtra(extra) Parse transaction extra field
extractTxPubKey(extra) Extract transaction public key
extractPaymentId(extra) Extract payment ID from extra
decodeAmount(encrypted, derivation, index) Decrypt encrypted amount
summarizeTransaction(tx) Get transaction summary
getTransactionHashFromParsed(tx) Compute hash from parsed transaction

Utility Functions

Function Description
bytesToHex(bytes) Convert Uint8Array to hex string
hexToBytes(hex) Convert hex string to Uint8Array
keccak256(data) Keccak-256 hash, returns Uint8Array
keccak256Hex(data) Keccak-256 hash, returns hex string
blake2b(data, outlen, key?) Blake2b hash with optional key

Wallet Class

Unified wallet management with full and view-only modes.

import {
  Wallet,
  createWallet,
  restoreWallet,
  createViewOnlyWallet
} from 'salvium-js';

// Create a new wallet
const { wallet, mnemonic, seed } = createWallet({ network: 'mainnet' });
console.log('Backup:', mnemonic);  // 25 words
console.log('Address:', wallet.getAddress());

// Restore from mnemonic
const restored = restoreWallet('abbey ability able about...');

// Create view-only wallet (can scan, cannot spend)
const viewOnly = createViewOnlyWallet({
  network: 'mainnet',
  viewSecretKey: '...',
  spendPublicKey: '...'
});

console.log('Can scan:', viewOnly.canScan());   // true
console.log('Can sign:', viewOnly.canSign());   // false

// Generate subaddresses
const sub = wallet.getSubaddress(0, 1);  // account 0, index 1

// Serialize/deserialize
const json = wallet.toJSON();
const loaded = Wallet.fromJSON(json);

UTXO Selection

Multiple strategies for selecting transaction inputs.

import { selectUTXOs, UTXO_STRATEGY } from 'salvium-js';

const utxos = [
  { txHash: 'abc...', outputIndex: 0, amount: 1000000000n, publicKey: ... },
  { txHash: 'def...', outputIndex: 1, amount: 500000000n, publicKey: ... },
  // ...
];

const result = selectUTXOs(utxos, 800000000n, {
  strategy: UTXO_STRATEGY.MINIMIZE_INPUTS,  // or MINIMIZE_CHANGE, OLDEST_FIRST, etc.
  feePerByte: 1000n
});

console.log('Selected:', result.selectedUTXOs.length);
console.log('Total:', result.totalAmount);
console.log('Change:', result.changeAmount);
console.log('Fee:', result.estimatedFee);

Transaction Builder

High-level API for building and signing transactions.

import { buildTransaction, signTransaction, validateTransaction } from 'salvium-js';

// Build a transaction
const tx = await buildTransaction({
  utxos: myUTXOs,
  destinations: [
    { address: 'SaLv...', amount: 1000000000n }
  ],
  changeAddress: myChangeAddress,
  viewSecretKey: keys.viewSecretKey,
  spendSecretKey: keys.spendSecretKey,
  ringSize: 16
});

console.log('TX Hash:', tx.txHash);
console.log('Fee:', tx.fee);

// Validate before broadcast
const validation = validateTransaction(tx.tx);
if (!validation.valid) {
  console.error('Errors:', validation.errors);
}

Transaction Parser

Decode and analyze transaction data.

import {
  parseTransaction,
  parseExtra,
  extractTxPubKey,
  extractPaymentId,
  summarizeTransaction
} from 'salvium-js';

// Parse raw transaction
const tx = parseTransaction(txHex);

// Get transaction summary
const summary = summarizeTransaction(tx);
console.log('Hash:', summary.hash);
console.log('Inputs:', summary.numInputs);
console.log('Outputs:', summary.numOutputs);
console.log('Coinbase:', summary.isCoinbase);
console.log('Key images:', summary.keyImages);

// Extract extra fields
const extra = parseExtra(tx.extra);
const txPubKey = extractTxPubKey(extra);
const paymentId = extractPaymentId(extra);

Stratum Mining

Connect to mining pools with the stratum protocol.

import { createMiner } from 'salvium-js';

const miner = createMiner({
  host: 'pool.example.com',
  port: 3333,
  wallet: 'SaLv...',
  password: 'x',
  threads: 4
});

miner.on('hashrate', (rate) => console.log(`${rate} H/s`));
miner.on('share', (accepted) => console.log(accepted ? 'Share accepted' : 'Share rejected'));

await miner.start();
// ... mining ...
miner.stop();

Testing

# Run all tests
bun test/all.js

# Run with integration tests (requires running daemon)
bun test/all.js --integration

# Run against specific daemon
bun test/all.js --integration http://localhost:19081

Test coverage: 19 test suites including CLSAG/TCLSAG signing and official RandomX test vectors.

Contributing

Contributions welcome! Please read the Salvium source code for reference: https://github.com/salvium/salvium

S
Description
Archived mirror of deleted GitHub repo mxhess/salvium-rs
Readme 5.2 MiB
Languages
C 36.6%
Rust 31.6%
JavaScript 24.9%
C++ 4.8%
TypeScript 1%
Other 0.9%