From cf3acaa59e5914d31fc226f8af6b0b4c94d15d45 Mon Sep 17 00:00:00 2001 From: Unheard0840 Date: Sat, 29 Aug 2026 11:25:59 +0000 Subject: [PATCH] Update server/addresses.js Validate Bitcoin addresses before storing --- server/addresses.js | 260 +++++--------------------------------------- 1 file changed, 29 insertions(+), 231 deletions(-) diff --git a/server/addresses.js b/server/addresses.js index 394523d..47cd789 100644 --- a/server/addresses.js +++ b/server/addresses.js @@ -1,9 +1,11 @@ const crypto = require('crypto') const fs = require('fs/promises') -const net = require('net') + +const { + addressToScriptPubKey, +} = require('./bitcoin-address') const storePath = '/data/store.json' -const fulcrumTimeoutMs = 3000 async function readStore() { try { @@ -57,6 +59,18 @@ function normalizeLabel(label) { return normalized || undefined } +function validateBitcoinAddress(address) { + try { + addressToScriptPubKey(address) + } catch (error) { + throw new Error( + error instanceof Error + ? error.message + : 'Invalid Bitcoin address', + ) + } +} + async function listAddresses() { const store = await readStore() @@ -72,10 +86,18 @@ async function addAddress(address, label) { throw new Error('Bitcoin address is required') } + validateBitcoinAddress(normalizedAddress) + const addresses = await listAddresses() - if (addresses.some((item) => item.address === normalizedAddress)) { - throw new Error('Bitcoin address is already being watched') + if ( + addresses.some( + (item) => item.address === normalizedAddress, + ) + ) { + throw new Error( + 'Bitcoin address is already being watched', + ) } const entry = { @@ -114,7 +136,9 @@ async function removeAddress(address) { ) if (filtered.length === addresses.length) { - throw new Error('Bitcoin address is not being watched') + throw new Error( + 'Bitcoin address is not being watched', + ) } store.addresses = filtered @@ -124,234 +148,8 @@ async function removeAddress(address) { return true } -function queryFulcrum(fulcrumUrl, method, params = []) { - return new Promise((resolve) => { - if (!fulcrumUrl) { - resolve({ - ok: false, - result: null, - error: 'Fulcrum URL is not configured', - }) - return - } - - let parsed - - try { - parsed = new URL(fulcrumUrl) - } catch { - resolve({ - ok: false, - result: null, - error: 'Invalid Fulcrum URL', - }) - return - } - - if (parsed.protocol !== 'tcp:') { - resolve({ - ok: false, - result: null, - error: 'Fulcrum URL must use tcp://', - }) - return - } - - const host = parsed.hostname - const port = Number(parsed.port) - - if (!host || !port) { - resolve({ - ok: false, - result: null, - error: 'Fulcrum URL is missing host or port', - }) - return - } - - const socket = net.createConnection({ - host, - port, - }) - - let response = '' - let settled = false - - const finish = (value) => { - if (settled) { - return - } - - settled = true - socket.destroy() - resolve(value) - } - - socket.setTimeout(fulcrumTimeoutMs) - - socket.on('connect', () => { - const request = JSON.stringify({ - jsonrpc: '2.0', - id: 1, - method, - params, - }) + '\n' - - socket.write(request) - }) - - socket.on('data', (data) => { - response += data.toString() - - const lines = response.split('\n') - - for (const line of lines) { - if (!line.trim()) { - continue - } - - let message - - try { - message = JSON.parse(line) - } catch { - continue - } - - if (message.error) { - finish({ - ok: false, - result: null, - error: message.error, - }) - - return - } - - if ( - Object.prototype.hasOwnProperty.call( - message, - 'result', - ) - ) { - finish({ - ok: true, - result: message.result, - error: null, - }) - - return - } - } - }) - - socket.on('timeout', () => { - finish({ - ok: false, - result: null, - error: 'Connection timed out', - }) - }) - - socket.on('error', (error) => { - finish({ - ok: false, - result: null, - error: error.message, - }) - }) - - socket.on('close', () => { - finish({ - ok: false, - result: null, - error: 'Connection closed before a response was received', - }) - }) - }) -} - -async function getAddressData(address) { - const fulcrumUrl = process.env.MUNIN_FULCRUM_URL || null - - if (!fulcrumUrl) { - throw new Error('Fulcrum URL is not configured') - } - - const history = await queryFulcrum( - fulcrumUrl, - 'blockchain.address.get_history', - [address], - ) - - if (!history.ok) { - throw new Error( - `Failed to query address history: ${JSON.stringify(history.error)}`, - ) - } - - const utxos = await queryFulcrum( - fulcrumUrl, - 'blockchain.address.listunspent', - [address], - ) - - if (!utxos.ok) { - throw new Error( - `Failed to query address UTXOs: ${JSON.stringify(utxos.error)}`, - ) - } - - const confirmedBalance = utxos.result.reduce( - (total, item) => total + Number(item.value || 0), - 0, - ) - - return { - address, - confirmedBalance, - unconfirmedBalance: 0, - utxos: utxos.result, - history: history.result, - } -} - -async function getAddressesWithData() { - const addresses = await listAddresses() - - const results = [] - - for (const entry of addresses) { - try { - const data = await getAddressData(entry.address) - - results.push({ - ...entry, - confirmedBalance: data.confirmedBalance, - unconfirmedBalance: data.unconfirmedBalance, - utxos: data.utxos, - history: data.history, - error: null, - }) - } catch (error) { - results.push({ - ...entry, - confirmedBalance: null, - unconfirmedBalance: null, - utxos: [], - history: [], - error: error.message, - }) - } - } - - return results -} - module.exports = { listAddresses, addAddress, removeAddress, - getAddressData, - getAddressesWithData, } \ No newline at end of file