Security Guidelines for Radiant dApps

Version 1.0 Last Updated: February 2026

V2 Hard Fork Note (Radiant Core 2.1): At block 410,000, six new/re-enabled opcodes activate (OP_BLAKE3, OP_K12, OP_LSHIFT, OP_RSHIFT, OP_2MUL, OP_2DIV). dApps using these opcodes must ensure they are only used in transactions broadcast after the activation height. The minimum relay fee increases to 0.1 RXD/kB after block 415,000. Additionally, OP_PUSH_TX_STATE (0xed) provides access to the current txid, total input sum, and total output sum — useful for fee verification and induction proof contracts.


1. Security Principles

1.1 Defense in Depth

Never rely on a single security control. Layer multiple defenses:

┌───────────────────────────────────────────┐
│              USER INTERFACE                │
│  ┌───────────────────────────────────────┐│
│  │          INPUT VALIDATION              ││
│  │  ┌───────────────────────────────────┐││
│  │  │       AUTHENTICATION               │││
│  │  │  ┌───────────────────────────────┐│││
│  │  │  │     AUTHORIZATION              ││││
│  │  │  │  ┌───────────────────────────┐││││
│  │  │  │  │   SMART CONTRACT          │││││
│  │  │  │  │  ┌───────────────────────┐│││││
│  │  │  │  │  │       FUNDS           ││││││
│  │  │  │  │  └───────────────────────┘│││││
│  │  │  │  └───────────────────────────┘││││
│  │  │  └───────────────────────────────┘│││
│  │  └───────────────────────────────────┘││
│  └───────────────────────────────────────┘│
└───────────────────────────────────────────┘

1.2 Principle of Least Privilege

// ✅ GOOD: Request only needed permissions
const permissions = {
  viewBalance: true,
  signTransactions: true,
  // NOT: fullWalletAccess: true
};

// ✅ GOOD: Limit transaction scope
const txLimits = {
  maxAmount: 1000000,  // 0.01 RXD max per tx
  allowedAddresses: ['1Known...', '1Trusted...'],
  requireConfirmation: true
};

1.3 Fail Securely

// ✅ GOOD: Fail securely — don't expose internal details
async function processTransaction(tx) {
  try {
    await validateTransaction(tx);
    await signTransaction(tx);
    await broadcastTransaction(tx);
  } catch (error) {
    logger.error('Transaction failed', { txid: tx.id, error: error.message });
    throw new UserFacingError('Transaction could not be completed.');
  }
}

// ❌ BAD: Expose internal state
catch (error) {
  throw new Error(`Failed: ${error.stack} - DB: ${dbConnection}`);
}

1.4 Trust Nothing

// ✅ GOOD: Validate everything — even from "trusted" sources
function processElectrumResponse(response) {
  if (!isValidElectrumResponse(response)) throw new Error('Invalid response');
  if (!isValidTxHex(response.tx)) throw new Error('Invalid tx hex');
  if (response.amount < 0 || response.amount > MAX_SUPPLY) throw new Error('Invalid amount');
  return sanitize(response);
}

2. Key Management

2.1 Never Store Private Keys in Plain Text

// ❌ NEVER
localStorage.setItem('privateKey', privateKey.toWIF());

// ✅ GOOD: Encrypted storage with user password
async function storeKey(privateKey, password) {
  const salt = crypto.getRandomValues(new Uint8Array(32));
  const key = await deriveKey(password, salt);
  const encrypted = await encrypt(privateKey.toWIF(), key);
  await secureStorage.set('encryptedKey', { encrypted, salt });
}

2.2 Use Secure Key Derivation

// ✅ GOOD: BIP39/BIP44 derivation
function deriveKeys(mnemonic, accountIndex = 0) {
  const seed = Mnemonic.fromPhrase(mnemonic);
  const hdKey = seed.toHDPrivateKey();
  const path = `m/44'/0'/${accountIndex}'/0`;
  const account = hdKey.deriveChild(path);
  return {
    addresses: Array.from({ length: 20 }, (_, i) =>
      account.deriveChild(i).publicKey.toAddress()
    )
  };
}

2.3 Secure Memory Handling

// ✅ GOOD: Clear sensitive data from memory
class SecureKey {
  constructor(key) { this.keyBuffer = Buffer.from(key); }

  destroy() {
    crypto.getRandomValues(this.keyBuffer);  // Overwrite with random
    this.keyBuffer = new Uint8Array(0);
  }

  static async withKey(key, fn) {
    const secureKey = new SecureKey(key);
    try { return await fn(secureKey); }
    finally { secureKey.destroy(); }
  }
}

3. Transaction Security

3.1 Validate Before Signing

async function validateBeforeSigning(tx) {
  // 1. Validate addresses
  for (const output of tx.outputs) {
    if (!Address.isValid(output.address)) throw new Error('Invalid address');
    if (await isBlacklisted(output.address)) throw new Error('Blacklisted address');
  }

  // 2. Validate amounts
  const totalOutput = tx.outputs.reduce((sum, o) => sum + o.value, 0);
  const totalInput = tx.inputs.reduce((sum, i) => sum + i.value, 0);
  if (totalOutput > totalInput) throw new Error('Output exceeds input');

  // 3. Validate fee
  const fee = totalInput - totalOutput;
  const feeRate = fee / tx.getSize();
  if (feeRate > MAX_REASONABLE_FEE_RATE) throw new Error('Unreasonably high fee');
  if (fee < MIN_FEE) throw new Error('Fee too low');

  // 4. Check for dust
  for (const output of tx.outputs) {
    if (output.value > 0 && output.value < DUST_LIMIT) throw new Error('Dust output');
  }
}

3.2 User Confirmation

// ✅ Always show transaction details before signing
async function confirmTransaction(tx) {
  const details = {
    recipientAddress: formatAddress(tx.outputs[0].address),
    amount: formatAmount(tx.outputs[0].value),
    fee: formatAmount(tx.getFee()),
    warnings: []
  };

  if (tx.outputs[0].value > LARGE_TX_THRESHOLD)
    details.warnings.push('Large transaction amount');
  if (tx.getFee() > HIGH_FEE_THRESHOLD)
    details.warnings.push('Higher than normal fee');
  if (!await isKnownAddress(tx.outputs[0].address))
    details.warnings.push('First time sending to this address');

  return await showConfirmationDialog(details);
}

3.3 Prevent Double-Spending

class UTXOManager {
  private pendingUtxos = new Set();

  async getAvailableUTXOs(address) {
    const allUtxos = await electrum.listUnspent(address);
    return allUtxos.filter(utxo => !this.pendingUtxos.has(`${utxo.txid}:${utxo.vout}`));
  }

  markAsPending(utxo) { this.pendingUtxos.add(`${utxo.txid}:${utxo.vout}`); }
}

4. Frontend Security

4.1 Content Security Policy

<meta http-equiv="Content-Security-Policy" content="
  default-src 'self';
  script-src 'self' 'wasm-unsafe-eval';
  style-src 'self' 'unsafe-inline';
  connect-src 'self' wss://electrumx.radiantexplorer.com:*;
  img-src 'self' data: https:;
  object-src 'none';
  frame-ancestors 'none';
  upgrade-insecure-requests;
">

4.2 XSS Prevention

// ✅ GOOD: Sanitize all user input before display
import DOMPurify from 'dompurify';
function displayUserContent(content) {
  return DOMPurify.sanitize(content, { ALLOWED_TAGS: [] });
}

// ❌ BAD: Dangerous innerHTML
element.innerHTML = userProvidedContent;

4.3 Clipboard Security

async function copyToClipboard(text, type) {
  await navigator.clipboard.writeText(text);
  if (type === 'seed') {
    setTimeout(async () => await navigator.clipboard.writeText(''), 60000);
    showWarning('Seed phrase copied. Clipboard will be cleared in 60 seconds.');
  }
}

5. Backend Security

5.1 API Authentication & Rate Limiting

// JWT with proper validation
function verifyToken(token) {
  return jwt.verify(token, JWT_SECRET, {
    issuer: 'radiant-dapp', audience: 'radiant-dapp-api',
    algorithms: ['HS256'], maxAge: '1h'
  });
}

// Rate limiting
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, max: 100,
  message: 'Too many requests'
});
app.use('/api/', apiLimiter);

5.2 Input Validation

const transactionSchema = Joi.object({
  recipientAddress: Joi.string()
    .pattern(/^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/).required(),
  amount: Joi.number().integer().min(546).max(2100000000000000).required(),
  memo: Joi.string().max(256).optional()
});

5.3 Database Security

// ✅ GOOD: Parameterized queries
const result = await db.query('SELECT * FROM transactions WHERE address = $1', [address]);

// ❌ BAD: SQL injection
const result = await db.query(`SELECT * FROM transactions WHERE address = '${address}'`);

5.4 Secrets Management

// ✅ GOOD: Environment-based secrets — fail if missing
function requireEnv(name) {
  const value = process.env[name];
  if (!value) throw new Error(`Required env var ${name} is not set`);
  return value;
}

const config = {
  jwtSecret: requireEnv('JWT_SECRET'),
  dbPassword: requireEnv('DB_PASSWORD'),
};

6. Smart Contract Integration

6.1 Verify Contract Code

async function verifyContract(contractOutput, expectedTemplate) {
  const codeScript = extractCodeScript(contractOutput.script);
  const expectedCode = expectedTemplate.generateCode(parseContractParams(contractOutput.script));
  if (!codeScript.equals(expectedCode)) throw new Error('Contract code mismatch');
  return true;
}

6.2 Simulate Before Executing

async function executeContractCall(contract, functionName, args) {
  const tx = await contract.functions[functionName](...args);
  const simulation = await simulateTransaction(tx);
  if (!simulation.success) throw new Error(`Simulation failed: ${simulation.error}`);
  const confirmed = await confirmTransaction(tx, simulation);
  if (!confirmed) throw new Error('User cancelled');
  return await signAndBroadcast(tx);
}

7. API Security

7.1 ElectrumX Connection Security

class SecureElectrumClient {
  async connect(host, port) {
    const url = `wss://${host}:${port}`;  // Use WSS (secure)
    this.ws = new WebSocket(url);
    // Verify server certificate on upgrade...
  }

  async request(method, params) {
    const id = this.nextId++;
    const response = await this.sendAndWait({ jsonrpc: '2.0', id, method, params });
    if (response.id !== id) throw new Error('Response ID mismatch');
    return response.result;
  }
}

8. User Protection

8.1 Phishing Prevention

const OFFICIAL_DOMAINS = ['photonic.radiant4people.com', 'localhost'];

function verifyDomain() {
  if (!OFFICIAL_DOMAINS.includes(window.location.hostname)) {
    showPhishingWarning(window.location.hostname);
    disableWalletOperations();
  }
}
window.addEventListener('load', verifyDomain);

8.2 Transaction Warnings

const warnings = {
  largeAmount: (amount) => amount > 10000000000 ? 'Large transaction (>100 RXD)' : null,
  newAddress: async (address) => !await hasTransactionHistory(address) ? 'New address' : null,
  highFee: (feeRate) => feeRate > 10 ? 'Fee is higher than typical' : null,
  contractInteraction: (script) => isComplexScript(script) ? 'Involves a smart contract' : null,
  burnWarning: (outputs) => outputs.some(o => o.value === 0) ? 'May burn tokens' : null
};

9. Incident Response

Response Plan

Immediate (0-1 hour):

Short-term (1-24 hours):

Resolution (24-72 hours):

Post-Incident:


10. Security Checklist

Development Phase

Pre-Launch

Post-Launch


Resources

Reporting Vulnerabilities

Email: info@radiantfoundation.org
Subject: [SECURITY] dApp Name - Brief Description

Security is everyone's responsibility. When in doubt, ask for review.