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)
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