var assert = require('assert'); var sjcl = require('ripple-lib').sjcl; var Message = require('ripple-lib').Message; var Seed = require('ripple-lib').Seed; var Remote = require('ripple-lib').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(){ // Annoyingly non hex can be fed to the BigInteger(s, 16) constructor and // it will parse as a number. Before the commit of this comment, this test // involved a fixture of 32 chars, which was assumed to be hex. The test // passed, but for the wrong wreasons. There was a bug in Seed.parse_json. // Seed.from_json only creates invalid seeds from empty strings or invalid // base58 starting with an s, which it tries to base 58 decode/check sum. // The rest will be assumed to be a passphrase. // This is a bad b58 seed var secret_string = 'sbadsafRpB5euNL52PZPTSqrE9gvuFwTC'; 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.requestAccountInfo = function(options, callback) { var account = options.account; 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.requestAccountInfo = function(options, callback) { var account = options.account; 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(); }); }); }); });