diff --git a/app/controllers/messages.js b/app/controllers/messages.js new file mode 100644 index 00000000..5c2fbfc6 --- /dev/null +++ b/app/controllers/messages.js @@ -0,0 +1,27 @@ +'use strict'; + +var common = require('./common'); +var Rpc = require('../../lib/Rpc'); + + +exports.verify = function(req, res) { + var address = req.param('address'), + signature = req.param('signature'), + message = req.param('message'); + + if(typeof(address) == 'undefined' + || typeof(signature) == 'undefined' + || typeof(message) == 'undefined') { + return common.handleErrors({ + message: 'Missing parameters (expected "address", "signature" and "message")', + code: 1 + }, res); + } + + Rpc.verifyMessage(address, signature, message, function(err, result) { + if (err) { + return common.handleErrors(err, res); + } + res.json({'result' : result}); + }); +}; diff --git a/app/controllers/transactions.js b/app/controllers/transactions.js index d8090696..ed01c329 100644 --- a/app/controllers/transactions.js +++ b/app/controllers/transactions.js @@ -6,6 +6,7 @@ var Address = require('../models/Address'); var async = require('async'); var common = require('./common'); +var util = require('util'); var Rpc = require('../../lib/Rpc'); @@ -14,7 +15,21 @@ var bdb = require('../../lib/BlockDb').default(); exports.send = function(req, res) { Rpc.sendRawTransaction(req.body.rawtx, function(err, txid) { - if (err) return common.handleErrors(err, res); + if (err) { + var message; + if(err.code == -25) { + message = util.format( + 'Generic error %s (code %s)', + err.message, err.code); + } else if(err.code == -26) { + message = util.format( + 'Transaction rejected by network (code %s). Reason: %s', + err.code, err.message); + } else { + message = util.format('%s (code %s)', err.message, err.code); + } + return res.status(400).send(message); + } res.json({'txid' : txid}); }); }; diff --git a/config/routes.js b/config/routes.js index 36efa238..c99ed8e2 100644 --- a/config/routes.js +++ b/config/routes.js @@ -60,6 +60,11 @@ module.exports = function(app) { app.get(apiPrefix + '/email/validate', emailPlugin.validate); } + // Address routes + var messages = require('../app/controllers/messages'); + app.get(apiPrefix + '/messages/verify', messages.verify); + app.post(apiPrefix + '/messages/verify', messages.verify); + //Home route var index = require('../app/controllers/index'); app.get(apiPrefix + '/version', index.version); diff --git a/lib/Rpc.js b/lib/Rpc.js index a71c3969..bcf8d329 100644 --- a/lib/Rpc.js +++ b/lib/Rpc.js @@ -97,15 +97,25 @@ Rpc.getBlock = function(hash, cb) { }; Rpc.sendRawTransaction = function(rawtx, cb) { - var self = this; bitcoreRpc.sendRawTransaction(rawtx, function(err, txid) { - if (err && err.code === -5) return cb(err); // transaction already in block chain - if (err) return cb(self.errMsg(err)); + if (err) return cb(err); return cb(err, txid.result); }); }; +Rpc.verifyMessage = function(address, signature, message, cb) { + var self = this; + bitcoreRpc.verifyMessage(address, signature, message, function(err, message) { + if (err && (err.code === -3 || err.code === -5)) + return cb(err); // -3 = invalid address, -5 = malformed base64 / etc. + if (err) + return cb(self.errMsg(err)); + + return cb(err, message.result); + }); +}; + module.exports = require('soop')(Rpc); diff --git a/test/integration/messages.js b/test/integration/messages.js new file mode 100644 index 00000000..d51ccbaa --- /dev/null +++ b/test/integration/messages.js @@ -0,0 +1,90 @@ +#!/usr/bin/env node +'use strict'; + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +var assert = require('assert'), + config = require('../../config/config'), + messages = require('../../app/controllers/messages'), + correctMessage = 'test2', + correctAddress, + correctSignature; + +if(config.network === 'livenet') { + correctAddress = '16Q7eRty2LrpAWvP3VTtaXXCMZj2v4xm57', + correctSignature = 'HERpcxkyOezkBPPwvUUAaxYXR/9X/8eyVjp8WKGYl7Aw8' + + 'pMsiMXDWXf8G1t/SOUEWy94I+KA/SrBKYs2LfIHA0Q='; +} else { + correctAddress = 'mhtJo5nZLcreM5Arrf8EDABpCevp2MfmCW', + correctSignature = 'G/y2UhjZ4qBPLQGmOhl/4p/EIwTHIO1iq95kPxDk9RjYr' + + '1JKL6dsCSuhXat7VLTGwAM3PdgRh/jwGxi6x6dNeSE='; +} + +function createMockReq(body) { + // create a simplified mock of express' request object, suitable for the + // needs of test cases in this file + return { + body: body, + param: function(name) { + return this.body[name]; + } + }; +} + +describe('messages.verify', function() { + + it('should return true with correct message', function(done) { + var mockReq = createMockReq({ + address: correctAddress, + signature: correctSignature, + message: correctMessage + }); + var mockRes = { + json: function(data) { + assert.deepEqual(data, { + result: true, + }); + done(); + } + }; + messages.verify(mockReq, mockRes); + }); + + it('should return false with incorrect message', function(done) { + var mockReq = createMockReq({ + address: correctAddress, + signature: correctSignature, + message: 'NOPE' + }); + var mockRes = { + json: function(data) { + assert.deepEqual(data, { + result: false, + }); + done(); + } + }; + + messages.verify(mockReq, mockRes); + }); + + it('should return error with incorrect parameters', function(done) { + var mockReq = createMockReq({ + address: correctAddress, + message: correctMessage + }); + var mockRes = { + status: function(code) { + assert.equal(code, 400); + return this; + }, + send: function(data) { + assert.ok(data.match(/^Missing parameters/), + "Match not found, got '" + data + "' instead") + done(); + } + }; + messages.verify(mockReq, mockRes); + }); + +});