From fc3b31836bfd24a0a2c98c6c547cdc9835e1f946 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 20 Oct 2017 08:15:04 -0700 Subject: [PATCH] wallet/http: improve validation. --- lib/hd/hd.js | 2 + lib/http/base.js | 4 - lib/http/rpc.js | 130 +++++++------- lib/http/server.js | 55 +++--- lib/node/config.js | 21 ++- lib/primitives/address.js | 14 +- lib/utils/base32.js | 5 + lib/utils/base58.js | 4 + lib/utils/bech32.js | 7 + lib/utils/validator.js | 357 +++++++++++++++++++++++++++++++++----- lib/wallet/http.js | 225 ++++++++++++------------ lib/wallet/rpc.js | 90 +++++----- 12 files changed, 591 insertions(+), 323 deletions(-) diff --git a/lib/hd/hd.js b/lib/hd/hd.js index 9b7ebc4a..3be26994 100644 --- a/lib/hd/hd.js +++ b/lib/hd/hd.js @@ -181,4 +181,6 @@ HD.HD = HD; HD.Mnemonic = Mnemonic; HD.PrivateKey = HDPrivateKey; HD.PublicKey = HDPublicKey; +HD.HDPrivateKey = HDPrivateKey; +HD.HDPublicKey = HDPublicKey; HD.wordlist = wordlist; diff --git a/lib/http/base.js b/lib/http/base.js index bb8c1ef0..772a34ad 100644 --- a/lib/http/base.js +++ b/lib/http/base.js @@ -1329,10 +1329,6 @@ Request.prototype.rewrite = function rewrite(url) { return req; }; -Request.prototype.valid = function valid() { - return new Validator([this.query, this.params, this.body]); -}; - Request.prototype.pipe = function pipe(dest) { return this.req.pipe(dest); }; diff --git a/lib/http/rpc.js b/lib/http/rpc.js index 30fafd90..fe2eb4f3 100644 --- a/lib/http/rpc.js +++ b/lib/http/rpc.js @@ -250,7 +250,7 @@ RPC.prototype.addNode = async function addNode(args, help) { if (help || args.length !== 2) throw new RPCError(errs.MISC_ERROR, 'addnode "node" "add|remove|onetry"'); - const valid = new Validator([args]); + const valid = new Validator(args); const node = valid.str(0, ''); const cmd = valid.str(1, ''); @@ -282,7 +282,7 @@ RPC.prototype.disconnectNode = async function disconnectNode(args, help) { if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'disconnectnode "node"'); - const valid = new Validator([args]); + const valid = new Validator(args); const str = valid.str(0, ''); const addr = parseIP(str, this.network); @@ -299,7 +299,7 @@ RPC.prototype.getAddedNodeInfo = async function getAddedNodeInfo(args, help) { throw new RPCError(errs.MISC_ERROR, 'getaddednodeinfo ( "node" )'); const hosts = this.pool.hosts; - const valid = new Validator([args]); + const valid = new Validator(args); const addr = valid.str(0, ''); let target; @@ -438,7 +438,7 @@ RPC.prototype.ping = async function ping(args, help) { }; RPC.prototype.setBan = async function setBan(args, help) { - const valid = new Validator([args]); + const valid = new Validator(args); const str = valid.str(0, ''); const action = valid.str(1, ''); @@ -533,8 +533,8 @@ RPC.prototype.getBlock = async function getBlock(args, help) { if (help || args.length < 1 || args.length > 3) throw new RPCError(errs.MISC_ERROR, 'getblock "hash" ( verbose )'); - const valid = new Validator([args]); - const hash = valid.hash(0); + const valid = new Validator(args); + const hash = valid.rhash(0); const verbose = valid.bool(1, true); const details = valid.bool(2, false); @@ -570,7 +570,7 @@ RPC.prototype.getBlockByHeight = async function getBlockByHeight(args, help) { 'getblockbyheight "height" ( verbose )'); } - const valid = new Validator([args]); + const valid = new Validator(args); const height = valid.u32(0, -1); const verbose = valid.bool(1, true); const details = valid.bool(2, false); @@ -605,7 +605,7 @@ RPC.prototype.getBlockHash = async function getBlockHash(args, help) { if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'getblockhash index'); - const valid = new Validator([args]); + const valid = new Validator(args); const height = valid.u32(0); if (height == null || height > this.chain.height) @@ -623,8 +623,8 @@ RPC.prototype.getBlockHeader = async function getBlockHeader(args, help) { if (help || args.length < 1 || args.length > 2) throw new RPCError(errs.MISC_ERROR, 'getblockheader "hash" ( verbose )'); - const valid = new Validator([args]); - const hash = valid.hash(0); + const valid = new Validator(args); + const hash = valid.rhash(0); const verbose = valid.bool(1, true); if (!hash) @@ -694,8 +694,8 @@ RPC.prototype.getMempoolAncestors = async function getMempoolAncestors(args, hel if (help || args.length < 1 || args.length > 2) throw new RPCError(errs.MISC_ERROR, 'getmempoolancestors txid (verbose)'); - const valid = new Validator([args]); - const hash = valid.hash(0); + const valid = new Validator(args); + const hash = valid.rhash(0); const verbose = valid.bool(1, false); if (!this.mempool) @@ -727,8 +727,8 @@ RPC.prototype.getMempoolDescendants = async function getMempoolDescendants(args, if (help || args.length < 1 || args.length > 2) throw new RPCError(errs.MISC_ERROR, 'getmempooldescendants txid (verbose)'); - const valid = new Validator([args]); - const hash = valid.hash(0); + const valid = new Validator(args); + const hash = valid.rhash(0); const verbose = valid.bool(1, false); if (!this.mempool) @@ -760,8 +760,8 @@ RPC.prototype.getMempoolEntry = async function getMempoolEntry(args, help) { if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'getmempoolentry txid'); - const valid = new Validator([args]); - const hash = valid.hash(0); + const valid = new Validator(args); + const hash = valid.rhash(0); if (!this.mempool) throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); @@ -781,7 +781,7 @@ RPC.prototype.getRawMempool = async function getRawMempool(args, help) { if (help || args.length > 1) throw new RPCError(errs.MISC_ERROR, 'getrawmempool ( verbose )'); - const valid = new Validator([args]); + const valid = new Validator(args); const verbose = valid.bool(0, false); if (!this.mempool) @@ -805,8 +805,8 @@ RPC.prototype.getTXOut = async function getTXOut(args, help) { if (help || args.length < 2 || args.length > 3) throw new RPCError(errs.MISC_ERROR, 'gettxout "txid" n ( includemempool )'); - const valid = new Validator([args]); - const hash = valid.hash(0); + const valid = new Validator(args); + const hash = valid.rhash(0); const index = valid.u32(1); const mempool = valid.bool(2, true); @@ -848,9 +848,9 @@ RPC.prototype.getTXOutProof = async function getTXOutProof(args, help) { 'gettxoutproof ["txid",...] ( blockhash )'); } - const valid = new Validator([args]); + const valid = new Validator(args); const txids = valid.array(0); - const hash = valid.hash(1); + const hash = valid.rhash(1); if (this.chain.options.spv) throw new RPCError(errs.MISC_ERROR, 'Cannot get coins in SPV mode.'); @@ -861,14 +861,14 @@ RPC.prototype.getTXOutProof = async function getTXOutProof(args, help) { if (!txids || txids.length === 0) throw new RPCError(errs.INVALID_PARAMETER, 'Invalid TXIDs.'); - const items = new Validator([txids]); + const items = new Validator(txids); const set = new Set(); const hashes = []; let last = null; for (let i = 0; i < txids.length; i++) { - const hash = items.hash(i); + const hash = items.rhash(i); if (!hash) throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.'); @@ -915,7 +915,7 @@ RPC.prototype.verifyTXOutProof = async function verifyTXOutProof(args, help) { if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'verifytxoutproof "proof"'); - const valid = new Validator([args]); + const valid = new Validator(args); const data = valid.buf(0); if (!data) @@ -982,7 +982,7 @@ RPC.prototype.verifyChain = async function verifyChain(args, help) { if (help || args.length > 2) throw new RPCError(errs.MISC_ERROR, 'verifychain ( checklevel numblocks )'); - const valid = new Validator([args]); + const valid = new Validator(args); const level = valid.u32(0); const blocks = valid.u32(1); @@ -1115,7 +1115,7 @@ RPC.prototype.getWork = async function getWork(args, help) { throw new RPCError(errs.MISC_ERROR, 'getwork ( "data" )'); if (args.length === 1) { - const valid = new Validator([args]); + const valid = new Validator(args); const data = valid.buf(0); if (!data) @@ -1133,7 +1133,7 @@ RPC.prototype.submitBlock = async function submitBlock(args, help) { 'submitblock "hexdata" ( "jsonparametersobject" )'); } - const valid = new Validator([args]); + const valid = new Validator(args); const data = valid.buf(0); const block = Block.fromRaw(data); @@ -1147,9 +1147,9 @@ RPC.prototype.getBlockTemplate = async function getBlockTemplate(args, help) { 'getblocktemplate ( "jsonrequestobject" )'); } - const validator = new Validator([args]); + const validator = new Validator(args); const options = validator.obj(0, {}); - const valid = new Validator([options]); + const valid = new Validator(options); const mode = valid.str('mode', 'template'); if (mode !== 'template' && mode !== 'proposal') @@ -1487,7 +1487,7 @@ RPC.prototype.getNetworkHashPS = async function getNetworkHashPS(args, help) { if (help || args.length > 2) throw new RPCError(errs.MISC_ERROR, 'getnetworkhashps ( blocks height )'); - const valid = new Validator([args]); + const valid = new Validator(args); const lookup = valid.u32(0, 120); const height = valid.u32(1); @@ -1500,8 +1500,8 @@ RPC.prototype.prioritiseTransaction = async function prioritiseTransaction(args, 'prioritisetransaction '); } - const valid = new Validator([args]); - const hash = valid.hash(0); + const valid = new Validator(args); + const hash = valid.rhash(0); const pri = valid.i64(1); const fee = valid.i64(2); @@ -1528,7 +1528,7 @@ RPC.prototype.verifyBlock = async function verifyBlock(args, help) { if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'verifyblock "block-hex"'); - const valid = new Validator([args]); + const valid = new Validator(args); const data = valid.buf(0); if (!data) @@ -1564,7 +1564,7 @@ RPC.prototype.setGenerate = async function setGenerate(args, help) { if (help || args.length < 1 || args.length > 2) throw new RPCError(errs.MISC_ERROR, 'setgenerate mine ( proclimit )'); - const valid = new Validator([args]); + const valid = new Validator(args); const mine = valid.bool(0, false); const limit = valid.u32(1, 0); @@ -1590,7 +1590,7 @@ RPC.prototype.generate = async function generate(args, help) { if (help || args.length < 1 || args.length > 2) throw new RPCError(errs.MISC_ERROR, 'generate numblocks ( maxtries )'); - const valid = new Validator([args]); + const valid = new Validator(args); const blocks = valid.u32(0, 1); const tries = valid.u32(1); @@ -1608,7 +1608,7 @@ RPC.prototype.generateToAddress = async function generateToAddress(args, help) { 'generatetoaddress numblocks address ( maxtries )'); } - const valid = new Validator([args]); + const valid = new Validator(args); const blocks = valid.u32(0, 1); const str = valid.str(1, ''); const tries = valid.u32(2); @@ -1631,7 +1631,7 @@ RPC.prototype.createRawTransaction = async function createRawTransaction(args, h + ' ( locktime )'); } - const valid = new Validator([args]); + const valid = new Validator(args); const inputs = valid.array(0); const sendTo = valid.obj(1); const locktime = valid.u32(2); @@ -1647,8 +1647,8 @@ RPC.prototype.createRawTransaction = async function createRawTransaction(args, h tx.locktime = locktime; for (const obj of inputs) { - const valid = new Validator([obj]); - const hash = valid.hash('txid'); + const valid = new Validator(obj); + const hash = valid.rhash('txid'); const index = valid.u32('vout'); let sequence = valid.u32('sequence', 0xffffffff); @@ -1666,7 +1666,7 @@ RPC.prototype.createRawTransaction = async function createRawTransaction(args, h tx.inputs.push(input); } - const sends = new Validator([sendTo]); + const sends = new Validator(sendTo); const uniq = new Set(); for (const key of Object.keys(sendTo)) { @@ -1711,7 +1711,7 @@ RPC.prototype.decodeRawTransaction = async function decodeRawTransaction(args, h if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'decoderawtransaction "hexstring"'); - const valid = new Validator([args]); + const valid = new Validator(args); const data = valid.buf(0); if (!data) @@ -1726,7 +1726,7 @@ RPC.prototype.decodeScript = async function decodeScript(args, help) { if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'decodescript "hex"'); - const valid = new Validator([args]); + const valid = new Validator(args); const data = valid.buf(0); let script = new Script(); @@ -1746,8 +1746,8 @@ RPC.prototype.getRawTransaction = async function getRawTransaction(args, help) { if (help || args.length < 1 || args.length > 2) throw new RPCError(errs.MISC_ERROR, 'getrawtransaction "txid" ( verbose )'); - const valid = new Validator([args]); - const hash = valid.hash(0); + const valid = new Validator(args); + const hash = valid.rhash(0); const verbose = valid.bool(1, false); if (!hash) @@ -1780,7 +1780,7 @@ RPC.prototype.sendRawTransaction = async function sendRawTransaction(args, help) 'sendrawtransaction "hexstring" ( allowhighfees )'); } - const valid = new Validator([args]); + const valid = new Validator(args); const data = valid.buf(0); if (!data) @@ -1803,7 +1803,7 @@ RPC.prototype.signRawTransaction = async function signRawTransaction(args, help) + ' sighashtype )'); } - const valid = new Validator([args]); + const valid = new Validator(args); const data = valid.buf(0); const prevout = valid.array(1); const secrets = valid.array(2); @@ -1822,7 +1822,7 @@ RPC.prototype.signRawTransaction = async function signRawTransaction(args, help) const keys = []; if (secrets) { - const valid = new Validator([secrets]); + const valid = new Validator(secrets); for (let i = 0; i < secrets.length; i++) { const secret = valid.str(i, ''); const key = parseSecret(secret, this.network); @@ -1833,8 +1833,8 @@ RPC.prototype.signRawTransaction = async function signRawTransaction(args, help) if (prevout) { for (const prev of prevout) { - const valid = new Validator([prev]); - const hash = valid.hash('txid'); + const valid = new Validator(prev); + const hash = valid.rhash('txid'); const index = valid.u32('vout'); const scriptRaw = valid.buf('scriptPubKey'); const value = valid.ufixed('amount', 8); @@ -1914,7 +1914,7 @@ RPC.prototype.createMultisig = async function createMultisig(args, help) { if (help || args.length < 2 || args.length > 2) throw new RPCError(errs.MISC_ERROR, 'createmultisig nrequired ["key",...]'); - const valid = new Validator([args]); + const valid = new Validator(args); const keys = valid.array(1, []); const m = valid.u32(0, 0); const n = keys.length; @@ -1922,7 +1922,7 @@ RPC.prototype.createMultisig = async function createMultisig(args, help) { if (m < 1 || n < m || n > 16) throw new RPCError(errs.INVALID_PARAMETER, 'Invalid m and n values.'); - const items = new Validator([keys]); + const items = new Validator(keys); for (let i = 0; i < keys.length; i++) { const key = items.buf(i); @@ -1953,7 +1953,7 @@ RPC.prototype.createWitnessAddress = async function createWitnessAddress(args, h if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'createwitnessaddress "script"'); - const valid = new Validator([args]); + const valid = new Validator(args); const raw = valid.buf(0); if (!raw) @@ -1973,7 +1973,7 @@ RPC.prototype.validateAddress = async function validateAddress(args, help) { if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'validateaddress "bitcoinaddress"'); - const valid = new Validator([args]); + const valid = new Validator(args); const str = valid.str(0, ''); let addr; @@ -2002,7 +2002,7 @@ RPC.prototype.verifyMessage = async function verifyMessage(args, help) { 'verifymessage "bitcoinaddress" "signature" "message"'); } - const valid = new Validator([args]); + const valid = new Validator(args); const b58 = valid.str(0, ''); const sig = valid.buf(1, null, 'base64'); const str = valid.str(2); @@ -2028,7 +2028,7 @@ RPC.prototype.signMessageWithPrivkey = async function signMessageWithPrivkey(arg 'signmessagewithprivkey "privkey" "message"'); } - const valid = new Validator([args]); + const valid = new Validator(args); const wif = valid.str(0, ''); const str = valid.str(1, ''); @@ -2044,7 +2044,7 @@ RPC.prototype.estimateFee = async function estimateFee(args, help) { if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'estimatefee nblocks'); - const valid = new Validator([args]); + const valid = new Validator(args); const blocks = valid.u32(0, 1); if (!this.fees) @@ -2062,7 +2062,7 @@ RPC.prototype.estimatePriority = async function estimatePriority(args, help) { if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'estimatepriority nblocks'); - const valid = new Validator([args]); + const valid = new Validator(args); const blocks = valid.u32(0, 1); if (!this.fees) @@ -2075,7 +2075,7 @@ RPC.prototype.estimateSmartFee = async function estimateSmartFee(args, help) { if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'estimatesmartfee nblocks'); - const valid = new Validator([args]); + const valid = new Validator(args); const blocks = valid.u32(0, 1); if (!this.fees) @@ -2098,7 +2098,7 @@ RPC.prototype.estimateSmartPriority = async function estimateSmartPriority(args, if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'estimatesmartpriority nblocks'); - const valid = new Validator([args]); + const valid = new Validator(args); const blocks = valid.u32(0, 1); if (!this.fees) @@ -2116,8 +2116,8 @@ RPC.prototype.invalidateBlock = async function invalidateBlock(args, help) { if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'invalidateblock "hash"'); - const valid = new Validator([args]); - const hash = valid.hash(0); + const valid = new Validator(args); + const hash = valid.rhash(0); if (!hash) throw new RPCError(errs.TYPE_ERROR, 'Invalid block hash.'); @@ -2131,8 +2131,8 @@ RPC.prototype.reconsiderBlock = async function reconsiderBlock(args, help) { if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'reconsiderblock "hash"'); - const valid = new Validator([args]); - const hash = valid.hash(0); + const valid = new Validator(args); + const hash = valid.rhash(0); if (!hash) throw new RPCError(errs.TYPE_ERROR, 'Invalid block hash.'); @@ -2146,7 +2146,7 @@ RPC.prototype.setMockTime = async function setMockTime(args, help) { if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'setmocktime timestamp'); - const valid = new Validator([args]); + const valid = new Validator(args); const time = valid.u32(0); if (time == null) @@ -2172,7 +2172,7 @@ RPC.prototype.setLogLevel = async function setLogLevel(args, help) { if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'setloglevel "level"'); - const valid = new Validator([args]); + const valid = new Validator(args); const level = valid.str(0, ''); this.logger.setLevel(level); diff --git a/lib/http/server.js b/lib/http/server.js index d259aed7..25913833 100644 --- a/lib/http/server.js +++ b/lib/http/server.js @@ -141,7 +141,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // UTXO by address this.get('/coin/address/:address', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const address = valid.str('address'); enforce(address, 'Address is required.'); @@ -158,8 +158,8 @@ HTTPServer.prototype.initRouter = function initRouter() { // UTXO by id this.get('/coin/:hash/:index', async (req, res) => { - const valid = req.valid(); - const hash = valid.hash('hash'); + const valid = Validator.fromRequest(req); + const hash = valid.rhash('hash'); const index = valid.u32('index'); enforce(hash, 'Hash is required.'); @@ -178,7 +178,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Bulk read UTXOs this.post('/coin/address', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const address = valid.array('addresses'); enforce(address, 'Address is required.'); @@ -195,8 +195,8 @@ HTTPServer.prototype.initRouter = function initRouter() { // TX by hash this.get('/tx/:hash', async (req, res) => { - const valid = req.valid(); - const hash = valid.hash('hash'); + const valid = Validator.fromRequest(req); + const hash = valid.rhash('hash'); enforce(hash, 'Hash is required.'); enforce(!this.chain.options.spv, 'Cannot get TX in SPV mode.'); @@ -215,7 +215,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // TX by address this.get('/tx/address/:address', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const address = valid.str('address'); enforce(address, 'Address is required.'); @@ -234,7 +234,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Bulk read TXs this.post('/tx/address', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const address = valid.array('addresses'); enforce(address, 'Address is required.'); @@ -253,17 +253,12 @@ HTTPServer.prototype.initRouter = function initRouter() { // Block by hash/height this.get('/block/:block', async (req, res) => { - const valid = req.valid(); - let hash = valid.get('block'); + const valid = Validator.fromRequest(req); + const hash = valid.uintrhash('block'); - enforce(typeof hash === 'string', 'Hash or height required.'); + enforce(hash != null, 'Hash or height required.'); enforce(!this.chain.options.spv, 'Cannot get block in SPV mode.'); - if (hash.length === 64) - hash = util.revHex(hash); - else - hash = parseInt(hash, 10); - const block = await this.chain.getBlock(hash); if (!block) { @@ -298,7 +293,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Broadcast TX this.post('/broadcast', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const raw = valid.buf('tx'); enforce(raw, 'TX is required.'); @@ -312,7 +307,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Estimate fee this.get('/fee', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const blocks = valid.u32('blocks'); if (!this.fees) { @@ -327,7 +322,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Reset chain this.post('/reset', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const height = valid.u32('height'); enforce(height != null, 'Height is required.'); @@ -364,7 +359,7 @@ HTTPServer.prototype.handleSocket = function handleSocket(socket) { throw new Error('Already authed.'); if (!this.options.noAuth) { - const valid = new Validator([args]); + const valid = new Validator(args); const key = valid.str(0, ''); if (key.length > 255) @@ -419,7 +414,7 @@ HTTPServer.prototype.handleAuth = function handleAuth(socket) { }); socket.hook('set filter', (args) => { - const valid = new Validator([args]); + const valid = new Validator(args); const data = valid.buf(0); if (!data) @@ -435,8 +430,8 @@ HTTPServer.prototype.handleAuth = function handleAuth(socket) { }); socket.hook('get entry', async (args) => { - const valid = new Validator([args]); - const block = valid.numhash(0); + const valid = new Validator(args); + const block = valid.uintrhash(0); if (block == null) throw new Error('Invalid parameter.'); @@ -453,7 +448,7 @@ HTTPServer.prototype.handleAuth = function handleAuth(socket) { }); socket.hook('get hashes', async (args) => { - const valid = new Validator([args]); + const valid = new Validator(args); const start = valid.i32(0, -1); const end = valid.i32(1, -1); @@ -461,7 +456,7 @@ HTTPServer.prototype.handleAuth = function handleAuth(socket) { }); socket.hook('add filter', (args) => { - const valid = new Validator([args]); + const valid = new Validator(args); const chunks = valid.array(0); if (!chunks) @@ -470,7 +465,7 @@ HTTPServer.prototype.handleAuth = function handleAuth(socket) { if (!socket.filter) throw new Error('No filter set.'); - const items = new Validator([chunks]); + const items = new Validator(chunks); for (let i = 0; i < chunks.length; i++) { const data = items.buf(i); @@ -493,7 +488,7 @@ HTTPServer.prototype.handleAuth = function handleAuth(socket) { }); socket.hook('estimate fee', (args) => { - const valid = new Validator([args]); + const valid = new Validator(args); const blocks = valid.u32(0); if (!this.fees) @@ -503,7 +498,7 @@ HTTPServer.prototype.handleAuth = function handleAuth(socket) { }); socket.hook('send', (args) => { - const valid = new Validator([args]); + const valid = new Validator(args); const data = valid.buf(0); if (!data) @@ -517,8 +512,8 @@ HTTPServer.prototype.handleAuth = function handleAuth(socket) { }); socket.hook('rescan', (args) => { - const valid = new Validator([args]); - const start = valid.numhash(0); + const valid = new Validator(args); + const start = valid.uintrhash(0); if (start == null) throw new Error('Invalid parameter.'); diff --git a/lib/node/config.js b/lib/node/config.js index 549fffb9..01e32f31 100644 --- a/lib/node/config.js +++ b/lib/node/config.js @@ -285,7 +285,7 @@ Config.prototype.str = function str(key, fallback) { */ Config.prototype.int = function int(key, fallback) { - let value = this.get(key); + const value = this.get(key); if (fallback === undefined) fallback = null; @@ -306,12 +306,12 @@ Config.prototype.int = function int(key, fallback) { if (!/^\-?\d+$/.test(value)) throw new Error(`${fmt(key)} must be an int.`); - value = parseInt(value, 10); + const num = parseInt(value, 10); - if (!Number.isSafeInteger(value)) + if (!Number.isSafeInteger(num)) throw new Error(`${fmt(key)} must be an int.`); - return value; + return num; }; /** @@ -344,7 +344,7 @@ Config.prototype.uint = function uint(key, fallback) { */ Config.prototype.float = function float(key, fallback) { - let value = this.get(key); + const value = this.get(key); if (fallback === undefined) fallback = null; @@ -368,12 +368,12 @@ Config.prototype.float = function float(key, fallback) { if (!/\d/.test(value)) throw new Error(`${fmt(key)} must be a float.`); - value = parseFloat(value); + const num = parseFloat(value); - if (!isFinite(value)) + if (!isFinite(num)) throw new Error(`${fmt(key)} must be a float.`); - return value; + return num; }; /** @@ -569,7 +569,7 @@ Config.prototype.obj = function obj(key, fallback) { if (value === null) return fallback; - if (typeof value !== 'object') + if (typeof value !== 'object' || Array.isArray(value)) throw new Error(`${fmt(key)} must be an object.`); return value; @@ -613,6 +613,9 @@ Config.prototype.path = function path(key, fallback) { if (value === null) return fallback; + if (value.length === 0) + return fallback; + switch (value[0]) { case '~': // home dir value = Path.join(HOME, value.substring(1)); diff --git a/lib/primitives/address.js b/lib/primitives/address.js index 16068a66..f4637802 100644 --- a/lib/primitives/address.js +++ b/lib/primitives/address.js @@ -27,16 +27,16 @@ const bech32 = require('../utils/bech32'); * @property {Number} version */ -function Address(options) { +function Address(options, network) { if (!(this instanceof Address)) - return new Address(options); + return new Address(options, network); this.type = Address.types.PUBKEYHASH; this.version = -1; this.hash = encoding.ZERO_HASH160; if (options) - this.fromOptions(options); + this.fromOptions(options, network); } /** @@ -63,9 +63,9 @@ Address.typesByVal = util.reverse(Address.types); * @param {Object} options */ -Address.prototype.fromOptions = function fromOptions(options) { +Address.prototype.fromOptions = function fromOptions(options, network) { if (typeof options === 'string') - return this.fromString(options); + return this.fromString(options, network); return this.fromHash(options.hash, options.type, options.version); }; @@ -76,8 +76,8 @@ Address.prototype.fromOptions = function fromOptions(options) { * @returns {Address} */ -Address.fromOptions = function fromOptions(options) { - return new Address().fromOptions(options); +Address.fromOptions = function fromOptions(options, network) { + return new Address().fromOptions(options, network); }; /** diff --git a/lib/utils/base32.js b/lib/utils/base32.js index a70f4f92..94a58150 100644 --- a/lib/utils/base32.js +++ b/lib/utils/base32.js @@ -10,6 +10,7 @@ * @module utils/base32 */ +const assert = require('assert'); const base32 = 'abcdefghijklmnopqrstuvwxyz234567'; const padding = [0, 6, 4, 3, 1]; const unbase32 = {}; @@ -24,6 +25,8 @@ for (let i = 0; i < base32.length; i++) */ exports.encode = function encode(data) { + assert(Buffer.isBuffer(data)); + let str = ''; let mode = 0; let left = 0; @@ -77,6 +80,8 @@ exports.encode = function encode(data) { */ exports.decode = function decode(str) { + assert(typeof str === 'string'); + const data = Buffer.allocUnsafe(str.length * 5 / 8 | 0); let mode = 0; let left = 0; diff --git a/lib/utils/base58.js b/lib/utils/base58.js index 5ef9baf2..f04afe75 100644 --- a/lib/utils/base58.js +++ b/lib/utils/base58.js @@ -36,6 +36,8 @@ for (let i = 0; i < base58.length; i++) */ exports.encode = function encode(data) { + assert(Buffer.isBuffer(data)); + let zeroes = 0; let i = 0; @@ -93,6 +95,8 @@ if (native) */ exports.decode = function decode(str) { + assert(typeof str === 'string'); + let zeroes = 0; let i = 0; diff --git a/lib/utils/bech32.js b/lib/utils/bech32.js index b4d08df6..bc171425 100644 --- a/lib/utils/bech32.js +++ b/lib/utils/bech32.js @@ -29,6 +29,7 @@ 'use strict'; +const assert = require('assert'); const native = require('../native').binding; /** @@ -261,6 +262,10 @@ function convert(data, output, frombits, tobits, pad, off) { */ function encode(hrp, version, hash) { + assert(typeof hrp === 'string'); + assert((version & 0xff) === version); + assert(Buffer.isBuffer(hash)); + const output = POOL65; if (version < 0 || version > 16) @@ -284,6 +289,8 @@ if (native) */ function decode(str) { + assert(typeof str === 'string'); + const [hrp, data] = deserialize(str); if (data.length === 0 || data.length > 65) diff --git a/lib/utils/validator.js b/lib/utils/validator.js index 15985143..a982d339 100644 --- a/lib/utils/validator.js +++ b/lib/utils/validator.js @@ -13,32 +13,53 @@ const util = require('../utils/util'); * Validator * @alias module:utils.Validator * @constructor - * @param {Object} options + * @param {Object} map + * @param {Boolean} [loose=false] */ -function Validator(data) { +function Validator(map, loose) { if (!(this instanceof Validator)) - return new Validator(data); + return new Validator(map, loose); - this.data = []; + if (!map || typeof map !== 'object') + throw new ValidationError('map', 'object'); - if (data) - this.init(data); + this.map = map; + this.loose = loose || false; } /** - * Initialize the validator. - * @private - * @param {Object} data + * Create a multi validator. + * @param {Object[]} maps + * @param {Boolean} [loose=false] + * @returns {MultiValidator} */ -Validator.prototype.init = function init(data) { - assert(data && typeof data === 'object'); +Validator.multi = function multi(maps, loose) { + return new MultiValidator(maps, loose); +}; - if (!Array.isArray(data)) - data = [data]; +/** + * Create a multi validator from an http request. + * @param {Object} req + * @returns {MultiValidator} + */ - this.data = data; +Validator.fromRequest = function fromRequest(req) { + const query = new Validator(req.query, true); + const params = new Validator(req.params, true); + const body = new Validator(req.body, false); + return new MultiValidator([query, params, body]); +}; + +/** + * Create a child validator. + * @param {String} key + * @returns {Validator} + */ + +Validator.prototype.child = function child(key) { + return new Validator(this.get(key)); }; /** @@ -48,16 +69,7 @@ Validator.prototype.init = function init(data) { */ Validator.prototype.has = function has(key) { - assert(typeof key === 'string' || typeof key === 'number', - 'Key must be a string or number.'); - - for (const map of this.data) { - const value = map[key]; - if (value != null) - return true; - } - - return false; + return this.get(key) != null; }; /** @@ -84,15 +96,10 @@ Validator.prototype.get = function get(key, fallback) { assert(typeof key === 'string' || typeof key === 'number', 'Key must be a string or number.'); - for (const map of this.data) { - if (!map || typeof map !== 'object') - throw new ValidationError('data', 'object'); + const value = this.map[key]; - const value = map[key]; - - if (value != null) - return value; - } + if (value != null) + return value; return fallback; }; @@ -142,7 +149,7 @@ Validator.prototype.str = function str(key, fallback) { */ Validator.prototype.int = function int(key, fallback) { - let value = this.get(key); + const value = this.get(key); if (fallback === undefined) fallback = null; @@ -160,15 +167,18 @@ Validator.prototype.int = function int(key, fallback) { return value; } + if (!this.loose) + throw new ValidationError(key, 'int'); + if (!/^\-?\d+$/.test(value)) throw new ValidationError(key, 'int'); - value = parseInt(value, 10); + const num = parseInt(value, 10); - if (!Number.isSafeInteger(value)) + if (!Number.isSafeInteger(num)) throw new ValidationError(key, 'int'); - return value; + return num; }; /** @@ -201,7 +211,7 @@ Validator.prototype.uint = function uint(key, fallback) { */ Validator.prototype.float = function float(key, fallback) { - let value = this.get(key); + const value = this.get(key); if (fallback === undefined) fallback = null; @@ -219,18 +229,21 @@ Validator.prototype.float = function float(key, fallback) { return value; } + if (!this.loose) + throw new ValidationError(key, 'float'); + if (!/^\-?\d*(?:\.\d*)?$/.test(value)) throw new ValidationError(key, 'float'); if (!/\d/.test(value)) throw new ValidationError(key, 'float'); - value = parseFloat(value); + const num = parseFloat(value); - if (!isFinite(value)) + if (!isFinite(num)) throw new ValidationError(key, 'float'); - return value; + return num; }; /** @@ -482,6 +495,68 @@ Validator.prototype.hash = function hash(key, fallback) { return value.toString('hex'); } + if (value.length !== 64) + throw new ValidationError(key, 'hex string'); + + if (!/^[0-9a-f]+$/i.test(value)) + throw new ValidationError(key, 'hex string'); + + return value.toLowerCase(); +}; + +/** + * Get a value (as a number or hash). + * @param {String} key + * @param {Object?} fallback + * @returns {Number|Hash|null} + */ + +Validator.prototype.uinthash = function uinthash(key, fallback) { + const value = this.get(key); + + if (fallback == null) + fallback = null; + + if (value == null) + return fallback; + + if (Buffer.isBuffer(value)) + return this.hash(key, fallback); + + if (typeof value === 'string') { + if (!this.loose || value.length === 64) + return this.hash(key, fallback); + } + + return this.uint(key, fallback); +}; + +/** + * Get a value (as a reverse hash). + * @param {String} key + * @param {Object?} fallback + * @returns {Hash|null} + */ + +Validator.prototype.rhash = function rhash(key, fallback) { + const value = this.get(key); + + if (fallback === undefined) + fallback = null; + + if (value === null) + return fallback; + + if (typeof value !== 'string') { + if (!Buffer.isBuffer(value)) + throw new ValidationError(key, 'hash'); + + if (value.length !== 32) + throw new ValidationError(key, 'hash'); + + return value.toString('hex'); + } + if (value.length !== 64) throw new ValidationError(key, 'hex string'); @@ -493,7 +568,7 @@ Validator.prototype.hash = function hash(key, fallback) { for (let i = 0; i < value.length; i += 2) out = value.slice(i, i + 2) + out; - return out; + return out.toLowerCase(); }; /** @@ -503,9 +578,23 @@ Validator.prototype.hash = function hash(key, fallback) { * @returns {Number|Hash|null} */ -Validator.prototype.numhash = function numhash(key, fallback) { - if (this.typeOf(key) === 'string') - return this.hash(key, fallback); +Validator.prototype.uintrhash = function uintrhash(key, fallback) { + const value = this.get(key); + + if (fallback == null) + fallback = null; + + if (value == null) + return fallback; + + if (Buffer.isBuffer(value)) + return this.rhash(key, fallback); + + if (typeof value === 'string') { + if (!this.loose || value.length === 64) + return this.rhash(key, fallback); + } + return this.uint(key, fallback); }; @@ -540,6 +629,9 @@ Validator.prototype.bool = function bool(key, fallback) { return value; } + if (!this.loose) + throw new ValidationError(key, 'hash'); + if (value === 'true' || value === '1') return true; @@ -605,6 +697,9 @@ Validator.prototype.array = function array(key, fallback) { return value; } + if (!this.loose) + throw new ValidationError(key, 'hash'); + const parts = value.trim().split(/\s*,\s*/); const result = []; @@ -634,7 +729,7 @@ Validator.prototype.obj = function obj(key, fallback) { if (value === null) return fallback; - if (typeof value !== 'object') + if (typeof value !== 'object' || Array.isArray(value)) throw new ValidationError(key, 'object'); return value; @@ -662,6 +757,178 @@ Validator.prototype.func = function func(key, fallback) { return value; }; +/* + * Constants + */ + +const SENTINEL = new Validator(Object.create(null)); + +/** + * MultiValidator + * @alias module:utils.MultiValidator + * @constructor + * @extends Validator + * @param {Object[]} maps + * @param {Boolean} [loose=false] + */ + +function MultiValidator(maps, loose) { + if (!(this instanceof MultiValidator)) + return new MultiValidator(maps, loose); + + this.maps = []; + + this.init(maps, loose); +} + +/** + * Initialize the validator. + * @private + * @param {Object[]} maps + * @param {Boolean} [loose=false] + */ + +MultiValidator.prototype.init = function init(maps, loose) { + assert(Array.isArray(maps)); + assert(maps.length > 0); + + for (const map of maps) { + if (!(map instanceof Validator)) { + assert(map && typeof map === 'object'); + this.maps.push(new Validator(map, loose)); + continue; + } + this.maps.push(map); + } +}; + +/** + * Get a validator. + * @private + * @param {String} key + * @returns {Validator} + */ + +MultiValidator.prototype.find = function find(key) { + for (const map of this.maps) { + if (map.has(key)) + return map; + } + return SENTINEL; +}; + +MultiValidator.prototype.child = function child(key) { + return this.find(key).child(key); +}; + +MultiValidator.prototype.has = function has(key) { + return this.find(key).has(key); +}; + +MultiValidator.prototype.get = function get(key, fallback) { + return this.find(key).get(key, fallback); +}; + +MultiValidator.prototype.typeOf = function typeOf(key) { + return this.find(key).typeOf(key); +}; + +MultiValidator.prototype.str = function str(key, fallback) { + return this.find(key).str(key, fallback); +}; + +MultiValidator.prototype.int = function int(key, fallback) { + return this.find(key).int(key, fallback); +}; + +MultiValidator.prototype.uint = function uint(key, fallback) { + return this.find(key).uint(key, fallback); +}; + +MultiValidator.prototype.float = function float(key, fallback) { + return this.find(key).float(key, fallback); +}; + +MultiValidator.prototype.ufloat = function ufloat(key, fallback) { + return this.find(key).ufloat(key, fallback); +}; + +MultiValidator.prototype.fixed = function fixed(key, exp, fallback) { + return this.find(key).fixed(key, exp, fallback); +}; + +MultiValidator.prototype.ufixed = function ufixed(key, exp, fallback) { + return this.find(key).ufixed(key, exp, fallback); +}; + +MultiValidator.prototype.i8 = function i8(key, fallback) { + return this.find(key).i8(key, fallback); +}; + +MultiValidator.prototype.i16 = function i16(key, fallback) { + return this.find(key).i16(key, fallback); +}; + +MultiValidator.prototype.i32 = function i32(key, fallback) { + return this.find(key).i32(key, fallback); +}; + +MultiValidator.prototype.i64 = function i64(key, fallback) { + return this.find(key).i64(key, fallback); +}; + +MultiValidator.prototype.u8 = function u8(key, fallback) { + return this.find(key).u8(key, fallback); +}; + +MultiValidator.prototype.u16 = function u16(key, fallback) { + return this.find(key).u16(key, fallback); +}; + +MultiValidator.prototype.u32 = function u32(key, fallback) { + return this.find(key).u32(key, fallback); +}; + +MultiValidator.prototype.u64 = function u64(key, fallback) { + return this.find(key).u64(key, fallback); +}; + +MultiValidator.prototype.hash = function hash(key, fallback) { + return this.find(key).hash(key, fallback); +}; + +MultiValidator.prototype.uinthash = function uinthash(key, fallback) { + return this.find(key).uinthash(key, fallback); +}; + +MultiValidator.prototype.rhash = function rhash(key, fallback) { + return this.find(key).rhash(key, fallback); +}; + +MultiValidator.prototype.uintrhash = function uintrhash(key, fallback) { + return this.find(key).uintrhash(key, fallback); +}; + +MultiValidator.prototype.bool = function bool(key, fallback) { + return this.find(key).bool(key, fallback); +}; + +MultiValidator.prototype.buf = function buf(key, fallback, enc) { + return this.find(key).buf(key, fallback, enc); +}; + +MultiValidator.prototype.array = function array(key, fallback) { + return this.find(key).array(key, fallback); +}; + +MultiValidator.prototype.obj = function obj(key, fallback) { + return this.find(key).obj(key, fallback); +}; + +MultiValidator.prototype.func = function func(key, fallback) { + return this.find(key).func(key, fallback); +}; + /* * Helpers */ diff --git a/lib/wallet/http.js b/lib/wallet/http.js index 172ee6e4..4dfb1e72 100644 --- a/lib/wallet/http.js +++ b/lib/wallet/http.js @@ -22,6 +22,9 @@ const Network = require('../protocol/network'); const Validator = require('../utils/validator'); const Address = require('../primitives/address'); const KeyRing = require('../primitives/keyring'); +const Mnemonic = require('../hd/mnemonic'); +const HDPrivateKey = require('../hd/private'); +const HDPublicKey = require('../hd/public'); const common = require('./common'); /** @@ -109,7 +112,7 @@ HTTPServer.prototype.initRouter = function initRouter() { this.use(this.jsonRPC(this.rpc)); this.hook(async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); if (req.path.length === 0) return; @@ -157,7 +160,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Rescan this.post('/_admin/rescan', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const height = valid.u32('height'); res.send(200, { success: true }); @@ -174,7 +177,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Backup WalletDB this.post('/_admin/backup', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const path = valid.str('path'); enforce(path, 'Path is required.'); @@ -201,31 +204,22 @@ HTTPServer.prototype.initRouter = function initRouter() { res.send(200, req.wallet.master.toJSON(true)); }); - // Create wallet (compat) - this.post('/', async (req, res) => { - const valid = req.valid(); - - const wallet = await this.wdb.create({ - id: valid.str('id'), - type: valid.str('type'), - m: valid.u32('m'), - n: valid.u32('n'), - passphrase: valid.str('passphrase'), - master: valid.str('master'), - mnemonic: valid.str('mnemonic'), - witness: valid.bool('witness'), - accountKey: valid.str('accountKey'), - watchOnly: valid.bool('watchOnly') - }); - - const balance = await wallet.getBalance(); - - res.send(200, wallet.toJSON(false, balance)); - }); - // Create wallet this.put('/:id', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); + + let master = valid.str('master'); + let mnemonic = valid.str('mnemonic'); + let accountKey = valid.str('accountKey'); + + if (master) + master = HDPrivateKey.fromBase58(master, this.network); + + if (mnemonic) + mnemonic = Mnemonic.fromPhrase(mnemonic); + + if (accountKey) + accountKey = HDPublicKey.fromBase58(accountKey, this.network); const wallet = await this.wdb.create({ id: valid.str('id'), @@ -233,10 +227,10 @@ HTTPServer.prototype.initRouter = function initRouter() { m: valid.u32('m'), n: valid.u32('n'), passphrase: valid.str('passphrase'), - master: valid.str('master'), - mnemonic: valid.str('mnemonic'), + master: master, + mnemonic: mnemonic, witness: valid.bool('witness'), - accountKey: valid.str('accountKey'), + accountKey: accountKey, watchOnly: valid.bool('watchOnly') }); @@ -253,7 +247,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Get account this.get('/:id/account/:account', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const acct = valid.str('account'); const account = await req.wallet.getAccount(acct); @@ -267,33 +261,16 @@ HTTPServer.prototype.initRouter = function initRouter() { res.send(200, account.toJSON(balance)); }); - // Create account (compat) - this.post('/:id/account', async (req, res) => { - const valid = req.valid(); - const passphrase = valid.str('passphrase'); - - const options = { - name: valid.str(['account', 'name']), - witness: valid.bool('witness'), - watchOnly: valid.bool('watchOnly'), - type: valid.str('type'), - m: valid.u32('m'), - n: valid.u32('n'), - accountKey: valid.str('accountKey'), - lookahead: valid.u32('lookahead') - }; - - const account = await req.wallet.createAccount(options, passphrase); - const balance = await req.wallet.getBalance(account.accountIndex); - - res.send(200, account.toJSON(balance)); - }); - // Create account this.put('/:id/account/:account', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const passphrase = valid.str('passphrase'); + let accountKey = valid.get('accountKey'); + + if (accountKey) + accountKey = HDPublicKey.fromBase58(accountKey, this.network); + const options = { name: valid.str('account'), witness: valid.bool('witness'), @@ -301,7 +278,7 @@ HTTPServer.prototype.initRouter = function initRouter() { type: valid.str('type'), m: valid.u32('m'), n: valid.u32('n'), - accountKey: valid.str('accountKey'), + accountKey: accountKey, lookahead: valid.u32('lookahead') }; @@ -313,7 +290,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Change passphrase this.post('/:id/passphrase', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const passphrase = valid.str('passphrase'); const old = valid.str('old'); @@ -326,7 +303,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Unlock wallet this.post('/:id/unlock', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const passphrase = valid.str('passphrase'); const timeout = valid.u32('timeout'); @@ -345,7 +322,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Import key this.post('/:id/import', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const acct = valid.str('account'); const passphrase = valid.str('passphrase'); const pub = valid.buf('publicKey'); @@ -353,7 +330,7 @@ HTTPServer.prototype.initRouter = function initRouter() { const b58 = valid.str('address'); if (pub) { - const key = KeyRing.fromPublic(pub, this.network); + const key = KeyRing.fromPublic(pub); await req.wallet.importKey(acct, key); res.send(200, { success: true }); return; @@ -378,7 +355,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Generate new token this.post('/:id/retoken', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const passphrase = valid.str('passphrase'); const token = await req.wallet.retoken(passphrase); @@ -389,7 +366,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Send TX this.post('/:id/send', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const passphrase = valid.str('passphrase'); const outputs = valid.array('outputs', []); @@ -406,17 +383,20 @@ HTTPServer.prototype.initRouter = function initRouter() { }; for (const output of outputs) { - const valid = new Validator([output]); - const raw = valid.buf('script'); + const valid = new Validator(output); - let script = null; + let addr = valid.str('address'); + let script = valid.buf('script'); - if (raw) - script = Script.fromRaw(raw); + if (addr) + addr = Address.fromString(addr, this.network); + + if (script) + script = Script.fromRaw(script); options.outputs.push({ + address: addr, script: script, - address: valid.str('address'), value: valid.u64('value') }); } @@ -430,7 +410,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Create TX this.post('/:id/create', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const passphrase = valid.str('passphrase'); const outputs = valid.array('outputs', []); @@ -446,17 +426,20 @@ HTTPServer.prototype.initRouter = function initRouter() { }; for (const output of outputs) { - const valid = new Validator([output]); - const raw = valid.buf('script'); + const valid = new Validator(output); - let script = null; + let addr = valid.str('address'); + let script = valid.buf('script'); - if (raw) - script = Script.fromRaw(raw); + if (addr) + addr = Address.fromString(addr, this.network); + + if (script) + script = Script.fromRaw(script); options.outputs.push({ + address: addr, script: script, - address: valid.str('address'), value: valid.u64('value') }); } @@ -470,7 +453,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Sign TX this.post('/:id/sign', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const passphrase = valid.str('passphrase'); const raw = valid.buf('tx'); @@ -486,7 +469,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Zap Wallet TXs this.post('/:id/zap', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const acct = valid.str('account'); const age = valid.u32('age'); @@ -499,8 +482,8 @@ HTTPServer.prototype.initRouter = function initRouter() { // Abandon Wallet TX this.del('/:id/tx/:hash', async (req, res) => { - const valid = req.valid(); - const hash = valid.hash('hash'); + const valid = Validator.fromRequest(req); + const hash = valid.rhash('hash'); enforce(hash, 'Hash is required.'); @@ -517,7 +500,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Get Block Record this.get('/:id/block/:height', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const height = valid.u32('height'); enforce(height != null, 'Height is required.'); @@ -534,11 +517,13 @@ HTTPServer.prototype.initRouter = function initRouter() { // Add key this.put('/:id/shared-key', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const acct = valid.str('account'); - const key = valid.str('accountKey'); + const b58 = valid.str('accountKey'); - enforce(key, 'Key is required.'); + enforce(b58, 'Key is required.'); + + const key = HDPublicKey.fromBase58(b58, this.network); await req.wallet.addSharedKey(acct, key); @@ -547,11 +532,13 @@ HTTPServer.prototype.initRouter = function initRouter() { // Remove key this.del('/:id/shared-key', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const acct = valid.str('account'); - const key = valid.str('accountKey'); + const b58 = valid.str('accountKey'); - enforce(key, 'Key is required.'); + enforce(b58, 'Key is required.'); + + const key = HDPublicKey.fromBase58(b58, this.network); await req.wallet.removeSharedKey(acct, key); @@ -560,12 +547,13 @@ HTTPServer.prototype.initRouter = function initRouter() { // Get key by address this.get('/:id/key/:address', async (req, res) => { - const valid = req.valid(); - const address = valid.str('address'); + const valid = Validator.fromRequest(req); + const b58 = valid.str('address'); - enforce(address, 'Address is required.'); + enforce(b58, 'Address is required.'); - const key = await req.wallet.getKey(address); + const addr = Address.fromString(b58, this.network); + const key = await req.wallet.getKey(addr); if (!key) { res.send(404); @@ -577,13 +565,14 @@ HTTPServer.prototype.initRouter = function initRouter() { // Get private key this.get('/:id/wif/:address', async (req, res) => { - const valid = req.valid(); - const address = valid.str('address'); + const valid = Validator.fromRequest(req); + const b58 = valid.str('address'); const passphrase = valid.str('passphrase'); - enforce(address, 'Address is required.'); + enforce(b58, 'Address is required.'); - const key = await req.wallet.getPrivateKey(address, passphrase); + const addr = Address.fromString(b58, this.network); + const key = await req.wallet.getPrivateKey(addr, passphrase); if (!key) { res.send(404); @@ -595,34 +584,34 @@ HTTPServer.prototype.initRouter = function initRouter() { // Create address this.post('/:id/address', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const acct = valid.str('account'); - const address = await req.wallet.createReceive(acct); + const addr = await req.wallet.createReceive(acct); - res.send(200, address.toJSON(this.network)); + res.send(200, addr.toJSON(this.network)); }); // Create change address this.post('/:id/change', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const acct = valid.str('account'); - const address = await req.wallet.createChange(acct); + const addr = await req.wallet.createChange(acct); - res.send(200, address.toJSON(this.network)); + res.send(200, addr.toJSON(this.network)); }); // Create nested address this.post('/:id/nested', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const acct = valid.str('account'); - const address = await req.wallet.createNested(acct); + const addr = await req.wallet.createNested(acct); - res.send(200, address.toJSON(this.network)); + res.send(200, addr.toJSON(this.network)); }); // Wallet Balance this.get('/:id/balance', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const acct = valid.str('account'); const balance = await req.wallet.getBalance(acct); @@ -636,7 +625,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Wallet UTXOs this.get('/:id/coin', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const acct = valid.str('account'); const coins = await req.wallet.getCoins(acct); const result = []; @@ -662,8 +651,8 @@ HTTPServer.prototype.initRouter = function initRouter() { // Lock coin this.put('/:id/locked/:hash/:index', async (req, res) => { - const valid = req.valid(); - const hash = valid.hash('hash'); + const valid = Validator.fromRequest(req); + const hash = valid.rhash('hash'); const index = valid.u32('index'); enforce(hash, 'Hash is required.'); @@ -678,8 +667,8 @@ HTTPServer.prototype.initRouter = function initRouter() { // Unlock coin this.del('/:id/locked/:hash/:index', async (req, res) => { - const valid = req.valid(); - const hash = valid.hash('hash'); + const valid = Validator.fromRequest(req); + const hash = valid.rhash('hash'); const index = valid.u32('index'); enforce(hash, 'Hash is required.'); @@ -694,8 +683,8 @@ HTTPServer.prototype.initRouter = function initRouter() { // Wallet Coin this.get('/:id/coin/:hash/:index', async (req, res) => { - const valid = req.valid(); - const hash = valid.hash('hash'); + const valid = Validator.fromRequest(req); + const hash = valid.rhash('hash'); const index = valid.u32('index'); enforce(hash, 'Hash is required.'); @@ -713,7 +702,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Wallet TXs this.get('/:id/tx/history', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const acct = valid.str('account'); const txs = await req.wallet.getHistory(acct); @@ -731,7 +720,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Wallet Pending TXs this.get('/:id/tx/unconfirmed', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const acct = valid.str('account'); const txs = await req.wallet.getPending(acct); @@ -748,7 +737,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Wallet TXs within time range this.get('/:id/tx/range', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const acct = valid.str('account'); const options = { @@ -770,7 +759,7 @@ HTTPServer.prototype.initRouter = function initRouter() { // Last Wallet TXs this.get('/:id/tx/last', async (req, res) => { - const valid = req.valid(); + const valid = Validator.fromRequest(req); const acct = valid.str('account'); const limit = valid.u32('limit'); const txs = await req.wallet.getLast(acct, limit); @@ -785,8 +774,8 @@ HTTPServer.prototype.initRouter = function initRouter() { // Wallet TX this.get('/:id/tx/:hash', async (req, res) => { - const valid = req.valid(); - const hash = valid.hash('hash'); + const valid = Validator.fromRequest(req); + const hash = valid.rhash('hash'); enforce(hash, 'Hash is required.'); @@ -869,7 +858,7 @@ HTTPServer.prototype.handleSocket = function handleSocket(socket) { throw new Error('Already authed.'); if (!this.options.noAuth) { - const valid = new Validator([args]); + const valid = new Validator(args); const key = valid.str(0, ''); if (key.length > 255) @@ -900,7 +889,7 @@ HTTPServer.prototype.handleSocket = function handleSocket(socket) { HTTPServer.prototype.handleAuth = function handleAuth(socket) { socket.hook('wallet join', async (args) => { - const valid = new Validator([args]); + const valid = new Validator(args); const id = valid.str(0, ''); const token = valid.buf(1); @@ -934,7 +923,7 @@ HTTPServer.prototype.handleAuth = function handleAuth(socket) { }); socket.hook('wallet leave', (args) => { - const valid = new Validator([args]); + const valid = new Validator(args); const id = valid.str(0, ''); if (!id) diff --git a/lib/wallet/rpc.js b/lib/wallet/rpc.js index 37335a4f..8c269f35 100644 --- a/lib/wallet/rpc.js +++ b/lib/wallet/rpc.js @@ -137,7 +137,7 @@ RPC.prototype.fundRawTransaction = async function fundRawTransaction(args, help) } const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); const data = valid.buf(0); const options = valid.obj(1); @@ -155,7 +155,7 @@ RPC.prototype.fundRawTransaction = async function fundRawTransaction(args, help) let change = null; if (options) { - const valid = new Validator([options]); + const valid = new Validator(options); rate = valid.ufixed('feeRate', 8); change = valid.str('changeAddress'); @@ -213,7 +213,7 @@ RPC.prototype.addWitnessAddress = async function addWitnessAddress(args, help) { }; RPC.prototype.backupWallet = async function backupWallet(args, help) { - const valid = new Validator([args]); + const valid = new Validator(args); const dest = valid.str(0); if (help || args.length !== 1 || !dest) @@ -229,7 +229,7 @@ RPC.prototype.dumpPrivKey = async function dumpPrivKey(args, help) { throw new RPCError(errs.MISC_ERROR, 'dumpprivkey "bitcoinaddress"'); const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); const addr = valid.str(0, ''); const hash = parseHash(addr, this.network); @@ -246,7 +246,7 @@ RPC.prototype.dumpWallet = async function dumpWallet(args, help) { throw new RPCError(errs.MISC_ERROR, 'dumpwallet "filename"'); const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); const file = valid.str(0); if (!file) @@ -304,7 +304,7 @@ RPC.prototype.encryptWallet = async function encryptWallet(args, help) { if (!wallet.master.encrypted && (help || args.length !== 1)) throw new RPCError(errs.MISC_ERROR, 'encryptwallet "passphrase"'); - const valid = new Validator([args]); + const valid = new Validator(args); const passphrase = valid.str(0, ''); if (wallet.master.encrypted) { @@ -329,7 +329,7 @@ RPC.prototype.getAccountAddress = async function getAccountAddress(args, help) { throw new RPCError(errs.MISC_ERROR, 'getaccountaddress "account"'); const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); let name = valid.str(0, ''); if (!name) @@ -348,7 +348,7 @@ RPC.prototype.getAccount = async function getAccount(args, help) { throw new RPCError(errs.MISC_ERROR, 'getaccount "bitcoinaddress"'); const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); const addr = valid.str(0, ''); const hash = parseHash(addr, this.network); @@ -365,7 +365,7 @@ RPC.prototype.getAddressesByAccount = async function getAddressesByAccount(args, throw new RPCError(errs.MISC_ERROR, 'getaddressesbyaccount "account"'); const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); let name = valid.str(0, ''); const addrs = []; @@ -389,7 +389,7 @@ RPC.prototype.getBalance = async function getBalance(args, help) { } const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); let name = valid.str(0); const minconf = valid.u32(1, 1); const watchOnly = valid.bool(2, false); @@ -419,7 +419,7 @@ RPC.prototype.getNewAddress = async function getNewAddress(args, help) { throw new RPCError(errs.MISC_ERROR, 'getnewaddress ( "account" )'); const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); let name = valid.str(0); if (name === '') @@ -447,7 +447,7 @@ RPC.prototype.getReceivedByAccount = async function getReceivedByAccount(args, h } const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); let name = valid.str(0); const minconf = valid.u32(1, 0); const height = this.wdb.state.height; @@ -492,7 +492,7 @@ RPC.prototype.getReceivedByAddress = async function getReceivedByAddress(args, h } const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); const addr = valid.str(0, ''); const minconf = valid.u32(1, 0); const height = this.wdb.state.height; @@ -595,8 +595,8 @@ RPC.prototype.getTransaction = async function getTransaction(args, help) { } const wallet = this.wallet; - const valid = new Validator([args]); - const hash = valid.hash(0); + const valid = new Validator(args); + const hash = valid.rhash(0); const watchOnly = valid.bool(1, false); if (!hash) @@ -615,8 +615,8 @@ RPC.prototype.abandonTransaction = async function abandonTransaction(args, help) throw new RPCError(errs.MISC_ERROR, 'abandontransaction "txid"'); const wallet = this.wallet; - const valid = new Validator([args]); - const hash = valid.hash(0); + const valid = new Validator(args); + const hash = valid.rhash(0); if (!hash) throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); @@ -666,7 +666,7 @@ RPC.prototype.importPrivKey = async function importPrivKey(args, help) { } const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); const secret = valid.str(0); const rescan = valid.bool(2, false); @@ -685,7 +685,7 @@ RPC.prototype.importWallet = async function importWallet(args, help) { throw new RPCError(errs.MISC_ERROR, 'importwallet "filename" ( rescan )'); const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); const file = valid.str(0); const rescan = valid.bool(1, false); @@ -731,7 +731,7 @@ RPC.prototype.importAddress = async function importAddress(args, help) { } const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); let addr = valid.str(0, ''); const rescan = valid.bool(2, false); const p2sh = valid.bool(3, false); @@ -765,7 +765,7 @@ RPC.prototype.importPubkey = async function importPubkey(args, help) { } const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); const data = valid.buf(0); const rescan = valid.bool(2, false); @@ -795,7 +795,7 @@ RPC.prototype.listAccounts = async function listAccounts(args, help) { } const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); const minconf = valid.u32(0, 0); const watchOnly = valid.bool(1, false); @@ -848,7 +848,7 @@ RPC.prototype.listReceivedByAccount = async function listReceivedByAccount(args, 'listreceivedbyaccount ( minconf includeempty includeWatchonly )'); } - const valid = new Validator([args]); + const valid = new Validator(args); const minconf = valid.u32(0, 0); const includeEmpty = valid.bool(1, false); const watchOnly = valid.bool(2, false); @@ -862,7 +862,7 @@ RPC.prototype.listReceivedByAddress = async function listReceivedByAddress(args, 'listreceivedbyaddress ( minconf includeempty includeWatchonly )'); } - const valid = new Validator([args]); + const valid = new Validator(args); const minconf = valid.u32(0, 0); const includeEmpty = valid.bool(1, false); const watchOnly = valid.bool(2, false); @@ -955,8 +955,8 @@ RPC.prototype._listReceived = async function _listReceived(minconf, empty, watch RPC.prototype.listSinceBlock = async function listSinceBlock(args, help) { const wallet = this.wallet; const chainHeight = this.wdb.state.height; - const valid = new Validator([args]); - const block = valid.hash(0); + const valid = new Validator(args); + const block = valid.rhash(0); const minconf = valid.u32(1, 0); const watchOnly = valid.bool(2, false); @@ -1084,7 +1084,7 @@ RPC.prototype.listTransactions = async function listTransactions(args, help) { } const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); let name = valid.str(0); const count = valid.u32(1, 10); const from = valid.u32(2, 0); @@ -1120,7 +1120,7 @@ RPC.prototype.listUnspent = async function listUnspent(args, help) { } const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); const minDepth = valid.u32(0, 1); const maxDepth = valid.u32(1, 9999999); const addrs = valid.array(2); @@ -1129,7 +1129,7 @@ RPC.prototype.listUnspent = async function listUnspent(args, help) { const map = new Set(); if (addrs) { - const valid = new Validator([addrs]); + const valid = new Validator(addrs); for (let i = 0; i < addrs.length; i++) { const addr = valid.str(i, ''); const hash = parseHash(addr, this.network); @@ -1193,7 +1193,7 @@ RPC.prototype.lockUnspent = async function lockUnspent(args, help) { } const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); const unlock = valid.bool(0, false); const outputs = valid.array(1); @@ -1207,8 +1207,8 @@ RPC.prototype.lockUnspent = async function lockUnspent(args, help) { throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); for (const output of outputs) { - const valid = new Validator([output]); - const hash = valid.hash('txid'); + const valid = new Validator(output); + const hash = valid.rhash('txid'); const index = valid.u32('vout'); if (hash == null || index == null) @@ -1240,7 +1240,7 @@ RPC.prototype.sendFrom = async function sendFrom(args, help) { } const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); let name = valid.str(0); const str = valid.str(1); const value = valid.ufixed(2, 8); @@ -1276,7 +1276,7 @@ RPC.prototype.sendMany = async function sendMany(args, help) { } const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); let name = valid.str(0); const sendTo = valid.obj(1); const minconf = valid.u32(2, 1); @@ -1288,7 +1288,7 @@ RPC.prototype.sendMany = async function sendMany(args, help) { if (!sendTo) throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); - const to = new Validator([sendTo]); + const to = new Validator(sendTo); const uniq = new Set(); const outputs = []; @@ -1331,7 +1331,7 @@ RPC.prototype.sendToAddress = async function sendToAddress(args, help) { } const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); const str = valid.str(0); const value = valid.ufixed(1, 8); const subtract = valid.bool(4, false); @@ -1365,7 +1365,7 @@ RPC.prototype.setAccount = async function setAccount(args, help) { }; RPC.prototype.setTXFee = async function setTXFee(args, help) { - const valid = new Validator([args]); + const valid = new Validator(args); const rate = valid.ufixed(0, 8); if (help || args.length < 1 || args.length > 1) @@ -1386,7 +1386,7 @@ RPC.prototype.signMessage = async function signMessage(args, help) { } const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); const b58 = valid.str(0, ''); const str = valid.str(1, ''); @@ -1430,7 +1430,7 @@ RPC.prototype.walletPassphraseChange = async function walletPassphraseChange(arg + ' "oldpassphrase" "newpassphrase"'); } - const valid = new Validator([args]); + const valid = new Validator(args); const old = valid.str(0, ''); const passphrase = valid.str(1, ''); @@ -1447,7 +1447,7 @@ RPC.prototype.walletPassphraseChange = async function walletPassphraseChange(arg RPC.prototype.walletPassphrase = async function walletPassphrase(args, help) { const wallet = this.wallet; - const valid = new Validator([args]); + const valid = new Validator(args); const passphrase = valid.str(0, ''); const timeout = valid.u32(1); @@ -1476,7 +1476,7 @@ RPC.prototype.importPrunedFunds = async function importPrunedFunds(args, help) { 'importprunedfunds "rawtransaction" "txoutproof" ( "label" )'); } - const valid = new Validator([args]); + const valid = new Validator(args); const txRaw = valid.buf(0); const blockRaw = valid.buf(1); @@ -1515,8 +1515,8 @@ RPC.prototype.removePrunedFunds = async function removePrunedFunds(args, help) { throw new RPCError(errs.MISC_ERROR, 'removeprunedfunds "txid"'); const wallet = this.wallet; - const valid = new Validator([args]); - const hash = valid.hash(0); + const valid = new Validator(args); + const hash = valid.rhash(0); if (!hash) throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); @@ -1528,7 +1528,7 @@ RPC.prototype.removePrunedFunds = async function removePrunedFunds(args, help) { }; RPC.prototype.selectWallet = async function selectWallet(args, help) { - const valid = new Validator([args]); + const valid = new Validator(args); const id = valid.str(0); if (help || args.length !== 1) @@ -1555,7 +1555,7 @@ RPC.prototype.setLogLevel = async function setLogLevel(args, help) { if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'setloglevel "level"'); - const valid = new Validator([args]); + const valid = new Validator(args); const level = valid.str(0, ''); this.logger.setLevel(level);