From cf3a21a712e01e832026afe6b4212407a7befe46 Mon Sep 17 00:00:00 2001 From: Evan Schwartz Date: Thu, 1 May 2014 20:23:20 -0700 Subject: [PATCH] [CHORE] Merged PubKeyValidator into Account class --- src/js/ripple/account.js | 122 ++++++- src/js/ripple/message.js | 6 +- src/js/ripple/pubkeyvalidator.js | 139 -------- ...ubkeyvalidator-test.js => account-test.js} | 100 +++--- test/message-test.js | 327 ++++++++++++++++++ 5 files changed, 491 insertions(+), 203 deletions(-) delete mode 100644 src/js/ripple/pubkeyvalidator.js rename test/{pubkeyvalidator-test.js => account-test.js} (61%) create mode 100644 test/message-test.js diff --git a/src/js/ripple/account.js b/src/js/ripple/account.js index 4f221068..b1a0f144 100644 --- a/src/js/ripple/account.js +++ b/src/js/ripple/account.js @@ -10,13 +10,15 @@ // // var network = require("./network.js"); - +var async = require('async'); var EventEmitter = require('events').EventEmitter; var util = require('util'); var extend = require('extend'); var Amount = require('./amount').Amount; var UInt160 = require('./uint160').UInt160; var TransactionManager = require('./transactionmanager').TransactionManager; +var sjcl = require('./utils').sjcl; +var Base = require('./base').Base; /** * @constructor Account @@ -278,6 +280,124 @@ Account.prototype.submit = function(transaction) { this._transactionManager.submit(transaction); }; + +/** + * Check whether the given public key is valid for this account + * + * @param {Hex-encoded String|RippleAddress} public_key + * @param {Function} callback + * + * @callback + * @param {Error} err + * @param {Boolean} true if the public key is valid and active, false otherwise + */ +Account.prototype.publicKeyIsActive = function(public_key, callback) { + + var self = this; + + var public_key_as_uint160; + try { + public_key_as_uint160 = Account._publicKeyToAddress(public_key); + } catch (err) { + return callback(err); + } + + function getAccountInfo(async_callback) { + self.getInfo(function(err, account_info_res){ + + // If the remote responds with an Account Not Found error then the account + // is unfunded and thus we can assume that the master key is active + if (err && err.remote && err.remote.error === 'actNotFound') { + async_callback(null, null); + } else { + async_callback(err, account_info_res); + } + }); + }; + + function publicKeyIsValid(account_info_res, async_callback) { + // Catch the case of unfunded accounts + if (!account_info_res) { + + if (public_key_as_uint160 === self._account_id) { + async_callback(null, true); + } else { + async_callback(null, false); + } + + return; + } + + var account_info = account_info_res.account_data; + + // Respond with true if the RegularKey is set and matches the given public key or + // if the public key matches the account address and the lsfDisableMaster is not set + if (account_info.RegularKey && + account_info.RegularKey === public_key_as_uint160) { + + async_callback(null, true); + + } else if (account_info.Account === public_key_as_uint160 && + ((account_info.Flags & 0x00100000) === 0)) { + + async_callback(null, true); + + } else { + + async_callback(null, false); + + } + + }; + + var steps = [ + getAccountInfo, + publicKeyIsValid + ]; + + async.waterfall(steps, callback); + +}; + +/** + * Convert a hex-encoded public key to a Ripple Address + * + * @static + * + * @param {Hex-encoded string|RippleAddress} public_key + * @returns {RippleAddress} + */ +Account._publicKeyToAddress = function(public_key) { + + // Based on functions in /src/js/ripple/keypair.js + function hexToUInt160(public_key) { + + var bits = sjcl.codec.hex.toBits(public_key); + var hash = sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits)); + var address = UInt160.from_bits(hash); + address.set_version(Base.VER_ACCOUNT_ID); + + return address.to_json(); + + } + + if (UInt160.is_valid(public_key)) { + + return public_key; + + } else if (/^[0-9a-fA-F]+$/.test(public_key)) { + + return hexToUInt160(public_key); + + } else { + + throw(new Error('Public key is invalid. Must be a UInt160 or a hex string')); + + } +}; + + + exports.Account = Account; // vim:sw=2:sts=2:ts=8:et diff --git a/src/js/ripple/message.js b/src/js/ripple/message.js index 33b5918d..a6043740 100644 --- a/src/js/ripple/message.js +++ b/src/js/ripple/message.js @@ -4,7 +4,7 @@ var sjcl = require('./utils').sjcl; var Remote = require('./remote').Remote; var Seed = require('./seed').Seed; var KeyPair = require('./keypair').KeyPair; -var PublicKeyValidator = require('./pubkeyvalidator'); +var Account = require('./account').Account; var UInt160 = require('./uint160').UInt160; // Message class (static) @@ -186,8 +186,8 @@ Message.verifyHashSignature = function(data, remote, callback) { key_pair._pubkey = public_key; var public_key_hex = key_pair.to_hex_pub(); - var public_key_validator = new PublicKeyValidator(remote); - public_key_validator.validate(account, public_key_hex, async_callback); + var account_class_instance = new Account(remote, account); + account_class_instance.publicKeyIsActive(public_key_hex, async_callback); }; diff --git a/src/js/ripple/pubkeyvalidator.js b/src/js/ripple/pubkeyvalidator.js deleted file mode 100644 index 5ac3d501..00000000 --- a/src/js/ripple/pubkeyvalidator.js +++ /dev/null @@ -1,139 +0,0 @@ -var async = require('async'); -var UInt160 = require('./uint160').UInt160; -var sjcl = require('./utils').sjcl; -var Base = require('./base').Base; - - -/** - * @constructor PubKeyValidator - * @param {Remote} remote - */ - -function PubKeyValidator(remote) { - - var self = this; - - if (remote) { - self._remote = remote; - } else { - throw(new Error('Must instantiate the PubKeyValidator with a ripple-lib Remote')); - } - -} - -/** - * Check whether the public key is valid for the specified address. - * - * @param {String} address - * @param {String} public_key - * @param {Function} callback - * - * callback function is called with (err, is_valid), where is_valid - * is a boolean indicating whether the public_key supplied is active - */ - -PubKeyValidator.prototype.validate = function(address, public_key, callback) { - - var self = this; - - var public_key_as_uint160; - try { - public_key_as_uint160 = self._parsePublicKey(public_key); - } catch (err) { - return callback(err); - } - - - function getAccountInfo(async_callback) { - self._remote.account(address).getInfo(function(err, account_info_res){ - - // If the remote responds with an Account Not Found error then the account - // is unfunded and thus we can assume that the master key is active - if (err && err.remote && err.remote.error === 'actNotFound') { - async_callback(null, null); - } else { - async_callback(err, account_info_res); - } - }); - }; - - function publicKeyIsValid(account_info_res, async_callback) { - // Catch the case of unfunded accounts - if (!account_info_res) { - - if (public_key_as_uint160 === address) { - async_callback(null, true); - } else { - async_callback(null, false); - } - - return; - } - - var account_info = account_info_res.account_data; - - // Respond with true if the RegularKey is set and matches the given public key or - // if the public key matches the account address and the lsfDisableMaster is not set - if (account_info.RegularKey && - account_info.RegularKey === public_key_as_uint160) { - - async_callback(null, true); - - } else if (account_info.Account === public_key_as_uint160 && - ((account_info.Flags & 0x00100000) === 0)) { - - async_callback(null, true); - - } else { - - async_callback(null, false); - - } - - }; - - var steps = [ - getAccountInfo, - publicKeyIsValid - ]; - - async.waterfall(steps, callback); - -}; - -/** - * Convert a hex-encoded public key to a Ripple Address - * - * @param {Hex-encoded string|RippleAddress} public_key - * @returns {RippleAddress} - */ -PubKeyValidator.prototype._parsePublicKey = function(public_key) { - - // Based on functions in /src/js/ripple/keypair.js - function hexToUInt160(public_key) { - - var bits = sjcl.codec.hex.toBits(public_key); - var hash = sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits)); - var address = UInt160.from_bits(hash); - address.set_version(Base.VER_ACCOUNT_ID); - - return address.to_json(); - - } - - if (UInt160.is_valid(public_key)) { - - return public_key; - - } else if (/^[0-9a-fA-F]+$/.test(public_key)) { - - return hexToUInt160(public_key); - - } else { - - throw(new Error('Public key is invalid. Must be a UInt160 or a hex string')); - - } -}; - -module.exports = PubKeyValidator; diff --git a/test/pubkeyvalidator-test.js b/test/account-test.js similarity index 61% rename from test/pubkeyvalidator-test.js rename to test/account-test.js index b0ead7be..78e4fb48 100644 --- a/test/pubkeyvalidator-test.js +++ b/test/account-test.js @@ -1,44 +1,41 @@ var assert = require('assert'); -var PubKeyValidator = require('../src/js/ripple/pubkeyvalidator'); +var Account = require('../src/js/ripple/account').Account; -describe('PubKeyValidator', function(){ +describe('Account', function(){ - describe('._parsePublicKey()', function(){ - - var pkv = new PubKeyValidator({}); + describe('._publicKeyToAddress()', function(){ it('should throw an error if the key is invalid', function(){ try { - pkv._parsePublicKey('not a real key'); + Account._publicKeyToAddress('not a real key'); } catch (e) { assert(e); } }); it('should return unchanged a valid UINT160', function(){ - assert('rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz' === pkv._parsePublicKey('rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz')); + assert('rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz' === Account._publicKeyToAddress('rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz')); }); it('should parse a hex-encoded public key as a UINT160', function(){ - assert('rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz' === pkv._parsePublicKey('025B32A54BFA33FB781581F49B235C0E2820C929FF41E677ADA5D3E53CFBA46332')); + assert('rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz' === Account._publicKeyToAddress('025B32A54BFA33FB781581F49B235C0E2820C929FF41E677ADA5D3E53CFBA46332')); - assert('rLpq5RcRzA8FU1yUqEPW4xfsdwon7casuM' === pkv._parsePublicKey('03BFA879C00D58CF55F2B5975FF9B5293008FF49BEFB3EE6BEE2814247BF561A23')); + assert('rLpq5RcRzA8FU1yUqEPW4xfsdwon7casuM' === Account._publicKeyToAddress('03BFA879C00D58CF55F2B5975FF9B5293008FF49BEFB3EE6BEE2814247BF561A23')); - assert('rP4yWwjoDGF2iZSBdAQAgpC449YDezEbT1' === pkv._parsePublicKey('02DF0AB18930B6410CA9F55CB37541F1FED891B8EDF8AB1D01D8F23018A4B204A7')); + assert('rP4yWwjoDGF2iZSBdAQAgpC449YDezEbT1' === Account._publicKeyToAddress('02DF0AB18930B6410CA9F55CB37541F1FED891B8EDF8AB1D01D8F23018A4B204A7')); - assert('rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ' === pkv._parsePublicKey('0310C451A40CAFFD39D6B8A3BD61BF65BCA55246E9DABC3170EBE431D30655B61F')); + assert('rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ' === Account._publicKeyToAddress('0310C451A40CAFFD39D6B8A3BD61BF65BCA55246E9DABC3170EBE431D30655B61F')); }); }); - describe('.validate()', function(){ + describe('.publicKeyIsActive()', function(){ it('should respond true if the public key corresponds to the account address and the master key IS NOT disabled', function(){ - var pkv = new PubKeyValidator({ - account: function(address){ - return { - getInfo: function(callback) { + var account = new Account({ + on: function(){}, + request_account_info: function(address, callback) { if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') { callback(null, { account_data: { Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', @@ -47,10 +44,8 @@ describe('PubKeyValidator', function(){ }}); } } - } - } - }); - pkv.validate('rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', '025B32A54BFA33FB781581F49B235C0E2820C929FF41E677ADA5D3E53CFBA46332', function(err, is_valid){ + }, 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'); + account.publicKeyIsActive('025B32A54BFA33FB781581F49B235C0E2820C929FF41E677ADA5D3E53CFBA46332', function(err, is_valid){ assert(err === null); assert(is_valid === true); }); @@ -59,10 +54,9 @@ describe('PubKeyValidator', function(){ it('should respond false if the public key corresponds to the account address and the master key IS disabled', function(){ - var pkv = new PubKeyValidator({ - account: function(address){ - return { - getInfo: function(callback) { + var account = new Account({ + on: function(){}, + request_account_info: function(address, callback) { if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') { callback(null, { account_data: { Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', @@ -71,10 +65,8 @@ describe('PubKeyValidator', function(){ }}); } } - } - } - }); - pkv.validate('rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', '025B32A54BFA33FB781581F49B235C0E2820C929FF41E677ADA5D3E53CFBA46332', function(err, is_valid){ + }, 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'); + account.publicKeyIsActive('025B32A54BFA33FB781581F49B235C0E2820C929FF41E677ADA5D3E53CFBA46332', function(err, is_valid){ assert(err === null); assert(is_valid === false); }); @@ -83,10 +75,9 @@ describe('PubKeyValidator', function(){ it('should respond true if the public key corresponds to the regular key', function(){ - var pkv = new PubKeyValidator({ - account: function(address){ - return { - getInfo: function(callback) { + var account = new Account({ + on: function(){}, + request_account_info: function(address, callback) { if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') { callback(null, { account_data: { Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', @@ -96,10 +87,8 @@ describe('PubKeyValidator', function(){ }}); } } - } - } - }); - pkv.validate('rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', '02BE53B7ACBB0900E0BB7729C9CAC1033A0137993B17800BD1191BBD1B29D96A8C', function(err, is_valid){ + }, 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'); + account.publicKeyIsActive('02BE53B7ACBB0900E0BB7729C9CAC1033A0137993B17800BD1191BBD1B29D96A8C', function(err, is_valid){ assert(err === null); assert(is_valid === true); }); @@ -108,10 +97,9 @@ describe('PubKeyValidator', function(){ it('should respond false if the public key does not correspond to an active public key for the account', function(){ - var pkv = new PubKeyValidator({ - account: function(address){ - return { - getInfo: function(callback) { + var account = new Account({ + on: function(){}, + request_account_info: function(address, callback) { if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') { callback(null, { account_data: { Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', @@ -121,10 +109,8 @@ describe('PubKeyValidator', function(){ }}); } } - } - } - }); - pkv.validate('rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', '032ECDA93970BC7E8872EF6582CB52A5557F117244A949EB4FA8AC7688CF24FBC8', function(err, is_valid){ + }, 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'); + account.publicKeyIsActive('032ECDA93970BC7E8872EF6582CB52A5557F117244A949EB4FA8AC7688CF24FBC8', function(err, is_valid){ assert(err === null); assert(is_valid === false); }); @@ -133,10 +119,9 @@ describe('PubKeyValidator', function(){ it('should respond false if the public key is invalid', function(){ - var pkv = new PubKeyValidator({ - account: function(address){ - return { - getInfo: function(callback) { + var account = new Account({ + on: function(){}, + request_account_info: function(address, callback) { if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') { callback(null, { account_data: { Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', @@ -146,10 +131,8 @@ describe('PubKeyValidator', function(){ }}); } } - } - } - }); - pkv.validate('rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', 'not a real public key', function(err, is_valid){ + }, 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'); + account.publicKeyIsActive('not a real public key', function(err, is_valid){ assert(err); }); @@ -157,10 +140,9 @@ describe('PubKeyValidator', function(){ it('should assume the master key is valid for unfunded accounts', function(){ - var pkv = new PubKeyValidator({ - account: function(address){ - return { - getInfo: function(callback) { + var account = new Account({ + on: function(){}, + request_account_info: function(address, callback) { if (address === 'rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ') { callback({ error: 'remoteError', error_message: 'Remote reported an error.', @@ -186,10 +168,8 @@ describe('PubKeyValidator', function(){ }); } } - } - } - }); - pkv.validate('rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ', '0310C451A40CAFFD39D6B8A3BD61BF65BCA55246E9DABC3170EBE431D30655B61F', function(err, is_valid){ + }, 'rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ'); + account.publicKeyIsActive('0310C451A40CAFFD39D6B8A3BD61BF65BCA55246E9DABC3170EBE431D30655B61F', function(err, is_valid){ assert(!err); assert(is_valid); }); diff --git a/test/message-test.js b/test/message-test.js new file mode 100644 index 00000000..c6e8d29d --- /dev/null +++ b/test/message-test.js @@ -0,0 +1,327 @@ +var assert = require('assert'); +var sjcl = require('../build/sjcl'); +var Message = require('../src/js/ripple/message'); +var Seed = require('../src/js/ripple/seed').Seed; +var Remote = require('../src/js/ripple/remote').Remote; + +describe('Message', function(){ + + describe('signMessage', function(){ + + it('should prepend the MAGIC_BYTES, call the HASH_FUNCTION, and then call signHash', function(){ + + var normal_signHash = Message.signHash; + + var message_text = 'Hello World!'; + + var signHash_called = false; + Message.signHash = function(hash) { + signHash_called = true; + assert.deepEqual(hash, Message.HASH_FUNCTION(Message.MAGIC_BYTES + message_text)); + }; + + Message.signMessage(message_text); + assert(signHash_called); + + Message.signHash = normal_signHash; + + }); + + }); + + describe('signHash', function(){ + + it('should accept the hash as either a hex string or a bitArray', function(){ + + var normal_random = sjcl.random.randomWords; + + sjcl.random.randomWords = function(num_words){ + var words = []; + for (var w = 0; w < num_words; w++) { + words.push(sjcl.codec.hex.toBits('00000000')); + } + return words; + }; + + var secret_string = 'safRpB5euNL52PZPTSqrE9gvuFwTC'; + // var address = 'rLLzaq61D633b5hhbNXKM9CkrYHboobVv3'; + var hash = 'e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778'; + + var signature1 = Message.signHash(hash, secret_string); + var signature2 = Message.signHash(sjcl.codec.hex.toBits(hash), secret_string); + + assert.strictEqual(signature1, signature2); + + sjcl.random.randomWords = normal_random; + + }); + + it('should accept the secret as a string or scjl.ecc.ecdsa.secretKey object', function(){ + + var normal_random = sjcl.random.randomWords; + + sjcl.random.randomWords = function(num_words){ + var words = []; + for (var w = 0; w < num_words; w++) { + words.push(sjcl.codec.hex.toBits('00000000')); + } + return words; + }; + + var secret_string = 'safRpB5euNL52PZPTSqrE9gvuFwTC'; + // var address = 'rLLzaq61D633b5hhbNXKM9CkrYHboobVv3'; + var hash = 'e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778'; + + var signature1 = Message.signHash(hash, secret_string); + var signature2 = Message.signHash(hash, Seed.from_json(secret_string).get_key()._secret); + + assert.strictEqual(signature1, signature2); + + sjcl.random.randomWords = normal_random; + + }); + + it('should throw an error if given an invalid secret key', function(){ + + var secret_string = 'badsafRpB5euNL52PZPTSqrE9gvuFwTC'; + var hash = 'e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778'; + + assert.throws(function(){ + Message.signHash(hash, secret_string); + }, /Cannot\ generate\ keys\ from\ invalid\ seed/); + + }); + + it('should throw an error if the parameters are reversed', function(){ + + var secret_string = 'safRpB5euNL52PZPTSqrE9gvuFwTC'; + var hash = 'e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778'; + + assert.throws(function(){ + Message.signHash(secret_string, hash); + }, Error); + + assert.throws(function(){ + Message.signHash(secret_string, sjcl.codec.hex.toBits(hash)); + }, Error); + + assert.throws(function(){ + Message.signHash(Seed.from_json(secret_string).get_key()._secret, hash); + }, Error); + + assert.throws(function(){ + Message.signHash(Seed.from_json(secret_string).get_key()._secret, sjcl.codec.hex.toBits(hash)); + }, Error); + + }); + + it('should produce a base64-encoded signature', function(){ + var REGEX_BASE64 = /^([A-Za-z0-9\+]{4})*([A-Za-z0-9\+]{2}==)|([A-Za-z0-9\+]{3}=)?$/; + + var normal_random = sjcl.random.randomWords; + + sjcl.random.randomWords = function(num_words){ + var words = []; + for (var w = 0; w < num_words; w++) { + words.push(sjcl.codec.hex.toBits('00000000')); + } + return words; + }; + + var secret_string = 'safRpB5euNL52PZPTSqrE9gvuFwTC'; + // var address = 'rLLzaq61D633b5hhbNXKM9CkrYHboobVv3'; + var hash = 'e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778'; + + var signature = Message.signHash(hash, secret_string); + + assert(REGEX_BASE64.test(signature)); + + sjcl.random.randomWords = normal_random; + }); + + }); + + describe('verifyMessageSignature', function(){ + + it('should prepend the MAGIC_BYTES, call the HASH_FUNCTION, and then call verifyHashSignature', function(){ + + var normal_verifyHashSignature = Message.verifyHashSignature; + + var data = { + message: 'Hello world!', + signature: 'AAAAGzFa1pYjhssCpDFZgFSnYQ8qCnMkLaZrg0mXZyNQ2NxgMQ8z9U3ngYerxSZCEt3Q4raMIpt03db7jDNGbfmHy8I=' + }; + + var verifyHashSignature_called = false; + Message.verifyHashSignature = function(vhs_data, remote, callback) { + verifyHashSignature_called = true; + + assert.deepEqual(vhs_data.hash, Message.HASH_FUNCTION(Message.MAGIC_BYTES + data.message)); + assert.strictEqual(vhs_data.signature, data.signature); + callback(); + + }; + + Message.verifyMessageSignature(data, {}, function(err){ + assert(!err); + }); + assert(verifyHashSignature_called); + + Message.verifyHashSignature = normal_verifyHashSignature; + + }); + + }); + + describe('verifyHashSignature', function(){ + + it('should throw an error if a callback function is not supplied', function(){ + + var data = { + message: 'Hello world!', + hash: '861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8', + signature: 'AAAAHOUJQzG/7BO82fGNt1TNE+GGVXKuQQ0N2nTO+iJETE69PiHnaAkkOzovM177OosxbKjpt3KvwuJflgUB2YGvgjk=', + account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz' + }; + + Remote.prototype.addServer = function(){}; + var test_remote = new Remote({}); + + assert.throws(function(){ + Message.verifyHashSignature(data); + }, /(?=.*callback\ function).*/); + + + }); + + it('should respond with an error if the hash is missing or invalid', function(done){ + + var data = { + message: 'Hello world!', + signature: 'AAAAHOUJQzG/7BO82fGNt1TNE+GGVXKuQQ0N2nTO+iJETE69PiHnaAkkOzovM177OosxbKjpt3KvwuJflgUB2YGvgjk=', + account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz' + }; + + Remote.prototype.addServer = function(){}; + var test_remote = new Remote({}); + test_remote.state = 'online'; + + Message.verifyHashSignature(data, test_remote, function(err, valid){ + assert(/hash/i.test(err.message)); + done(); + }); + + }); + + it('should respond with an error if the account is missing or invalid', function(done){ + + var data = { + message: 'Hello world!', + hash: '861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8', + signature: 'AAAAHOUJQzG/7BO82fGNt1TNE+GGVXKuQQ0N2nTO+iJETE69PiHnaAkkOzovM177OosxbKjpt3KvwuJflgUB2YGvgjk=' + }; + + Remote.prototype.addServer = function(){}; + var test_remote = new Remote({}); + test_remote.state = 'online'; + + Message.verifyHashSignature(data, test_remote, function(err, valid){ + assert(/account|address/i.test(err.message)); + done(); + }); + + }); + + it('should respond with an error if the signature is missing or invalid', function(done){ + + var data = { + message: 'Hello world!', + hash: '861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8', + account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz' + }; + + Remote.prototype.addServer = function(){}; + var test_remote = new Remote({}); + test_remote.state = 'online'; + + Message.verifyHashSignature(data, test_remote, function(err, valid){ + assert(/signature/i.test(err.message)); + done(); + }); + + }); + + it('should respond true if the signature is valid and corresponds to an active public key for the account', function(done){ + + var data = { + message: 'Hello world!', + hash: 'e9a82ea40514787918959b1100481500a5d384030f8770575c6a587675025fe212e6623e25643f251666a7b8b23af476c2850a8ea92153de5724db432892c752', + account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', + signature: 'AAAAHMIPCQGLgdnpX1Ccv1wHb56H4NggxIM6U08Qkb9mUjN2Vn9pZ3CHvq1yWLBi6NqpW+7kedLnmfu4VG2+y43p4Xs=' + }; + + Remote.prototype.addServer = function(){}; + var test_remote = new Remote({}); + test_remote.state = 'online'; + test_remote.request_account_info = function(account, callback) { + if (account === data.account) { + callback(null, { + "account_data": { + "Account": "rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz", + "Flags": 1114112, + "LedgerEntryType": "AccountRoot", + "RegularKey": "rHq2wyUtLkAad3vURUk33q9gozd97skhSf" + } + }); + } else { + callback(new Error('wrong account')); + } + }; + + Message.verifyHashSignature(data, test_remote, function(err, valid){ + assert(!err); + assert(valid); + done(); + }); + + }); + + it('should respond false if a key can be recovered from the signature but it does not correspond to an active public key', function(done){ + + // Signature created by disabled master key + var data = { + message: 'Hello world!', + hash: 'e9a82ea40514787918959b1100481500a5d384030f8770575c6a587675025fe212e6623e25643f251666a7b8b23af476c2850a8ea92153de5724db432892c752', + account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', + signature: 'AAAAG+dB/rAjZ5m8eQ/opcqQOJsFbKxOu9jq9KrOAlNO4OdcBDXyCBlkZqS9Xr8oZI2uh0boVsgYOS3pOLJz+Dh3Otk=' + }; + + Remote.prototype.addServer = function(){}; + var test_remote = new Remote({}); + test_remote.state = 'online'; + test_remote.request_account_info = function(account, callback) { + if (account === data.account) { + callback(null, { + "account_data": { + "Account": "rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz", + "Flags": 1114112, + "LedgerEntryType": "AccountRoot", + "RegularKey": "rHq2wyUtLkAad3vURUk33q9gozd97skhSf" + } + }); + } else { + callback(new Error('wrong account')); + } + }; + + Message.verifyHashSignature(data, test_remote, function(err, valid){ + assert(!err); + assert(!valid); + done(); + }); + + }); + + }); + +}); \ No newline at end of file