Files
salvium-rs/README.md
T
2026-01-28 16:54:22 +00:00

1099 lines
34 KiB
Markdown

# 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
```bash
npm install salvium-js
```
Or include directly in browser:
```html
<script type="module">
import salvium from './salvium-js/src/index.js';
</script>
```
## Quick Start: Generate a Wallet
```javascript
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
```javascript
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
```javascript
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)
```javascript
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
```javascript
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
```javascript
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
```javascript
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
```javascript
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
```javascript
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
```javascript
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.
```javascript
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).
```javascript
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.
```javascript
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.
```javascript
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).
```javascript
// 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](https://github.com/tevador/RandomX/blob/master/src/tests/tests.cpp).
```javascript
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:**
```javascript
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
```javascript
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
```javascript
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 = 8*s*P |
| `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 |
| `RandomXVM` | Virtual machine for RandomX program execution |
| `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.
```javascript
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.
```javascript
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.
```javascript
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.
```javascript
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.
```javascript
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
```bash
# 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