Remove message.js

This commit is contained in:
Nicholas Dudfield
2015-06-26 15:33:26 +07:00
parent 3960b4e11f
commit 5b4deabd90
3 changed files with 0 additions and 540 deletions

View File

@@ -13,7 +13,6 @@ exports.Seed = require('./seed').Seed;
exports.Meta = require('./meta').Meta;
exports.SerializedObject = require('./serializedobject').SerializedObject;
exports.RippleError = require('./rippleerror').RippleError;
exports.Message = require('./message').Message;
exports.binformat = require('./binformat');
exports.utils = require('./utils');
exports.Server = require('./server').Server;

View File

@@ -1,209 +0,0 @@
/* eslint-disable valid-jsdoc */
'use strict';
const async = require('async');
const sjcl = require('./utils').sjcl;
const Remote = require('./remote').Remote;
const Seed = require('./seed').Seed;
const KeyPair = require('./keypair').KeyPair;
const Account = require('./account').Account;
const UInt160 = require('./uint160').UInt160;
// Message class (static)
const Message = {};
Message.hashFunction = sjcl.hash.sha512.hash;
Message.MAGIC_BYTES = 'Ripple Signed Message:\n';
const REGEX_HEX = /^[0-9a-fA-F]+$/;
const REGEX_BASE64 =
/^([A-Za-z0-9\+]{4})*([A-Za-z0-9\+]{2}==)|([A-Za-z0-9\+]{3}=)?$/;
/**
* Produce a Base64-encoded signature on the given message with
* the string 'Ripple Signed Message:\n' prepended.
*
* Note that this signature uses the signing function that includes
* a recovery_factor to be able to extract the public key from the signature
* without having to pass the public key along with the signature.
*
* @static
*
* @param {String} message
* @param {sjcl.ecc.ecdsa.secretKey|Any format accepted by Seed.from_json}
* secret_key
* @param {RippleAddress} [The first key] account Field to specify the signing
* account. If this is omitted the first account produced by the secret
* generator will be used.
* @return {Base64-encoded String} signature
*/
Message.signMessage = function(message, secret_key, account) {
return Message.signHash(Message.hashFunction(Message.MAGIC_BYTES + message),
secret_key, account);
};
/**
* Produce a Base64-encoded signature on the given hex-encoded hash.
*
* Note that this signature uses the signing function that includes
* a recovery_factor to be able to extract the public key from the signature
* without having to pass the public key along with the signature.
*
* @static
*
* @param {bitArray|Hex-encoded String} hash
* @param {sjcl.ecc.ecdsa.secretKey|Any format accepted by Seed.from_json}
* secret_key
* @param {RippleAddress} [The first key] account Field to specify the
* signing account. If this is omitted the first account produced by
* the secret generator will be used.
* @returns {Base64-encoded String} signature
*/
Message.signHash = function(_hash, secret_key_, account) {
const hash = typeof _hash === 'string' && /^[0-9a-fA-F]+$/.test(_hash) ?
sjcl.codec.hex.toBits(_hash) : _hash;
if (typeof hash !== 'object' ||
typeof hash[0] !== 'number' ||
hash.length <= 0) {
throw new Error('Hash must be a bitArray or hex-encoded string');
}
const secret_key = !(secret_key_ instanceof sjcl.ecc.ecdsa.secretKey) ?
Seed.from_json(secret_key_).get_key(account)._secret : secret_key_;
const PARANOIA_256_BITS = 6; // sjcl constant for ensuring 256 bits of entropy
const signature_bits = secret_key.signWithRecoverablePublicKey(hash,
PARANOIA_256_BITS);
const signature_base64 = sjcl.codec.base64.fromBits(signature_bits);
return signature_base64;
};
/**
* Verify the signature on a given message.
*
* Note that this function is asynchronous.
* The ripple-lib remote is used to check that the public
* key extracted from the signature corresponds to one that is currently
* active for the given account.
*
* @static
*
* @param {String} data.message
* @param {RippleAddress} data.account
* @param {Base64-encoded String} data.signature
* @param {ripple-lib Remote} remote
* @param {Function} callback
*
* @callback callback
* @param {Error} error
* @param {boolean} is_valid true if the signature is valid, false otherwise
*/
Message.verifyMessageSignature = function(data, remote, callback) {
if (typeof data.message === 'string') {
data.hash = Message.hashFunction(Message.MAGIC_BYTES + data.message);
} else {
return callback(new Error(
'Data object must contain message field to verify signature'));
}
return Message.verifyHashSignature(data, remote, callback);
};
/**
* Verify the signature on a given hash.
*
* Note that this function is asynchronous.
* The ripple-lib remote is used to check that the public
* key extracted from the signature corresponds to one that is currently
* active for the given account.
*
* @static
*
* @param {bitArray|Hex-encoded String} data.hash
* @param {RippleAddress} data.account
* @param {Base64-encoded String} data.signature
* @param {ripple-lib Remote} remote
* @param {Function} callback
*
* @callback callback
* @param {Error} error
* @param {boolean} is_valid true if the signature is valid, false otherwise
*/
Message.verifyHashSignature = function(data, remote, callback) {
if (typeof callback !== 'function') {
throw new Error('Must supply callback function');
}
const hash = REGEX_HEX.test(data.hash) ?
sjcl.codec.hex.toBits(data.hash) :
data.hash;
if (typeof hash !== 'object' || hash.length <= 0
|| typeof hash[0] !== 'number') {
return callback(new Error('Hash must be a bitArray or hex-encoded string'));
}
const account = data.account || data.address;
if (!account || !UInt160.from_json(account).is_valid()) {
return callback(new Error('Account must be a valid ripple address'));
}
const signature = data.signature;
if (typeof signature !== 'string' || !REGEX_BASE64.test(signature)) {
return callback(new Error('Signature must be a Base64-encoded string'));
}
const signature_bits = sjcl.codec.base64.toBits(signature);
if (!(remote instanceof Remote) || remote.state !== 'online') {
return callback(new Error(
'Must supply connected Remote to verify signature'));
}
function recoverPublicKey(async_callback) {
let public_key;
try {
public_key =
sjcl.ecc.ecdsa.publicKey.recoverFromSignature(hash, signature_bits);
} catch (err) {
return async_callback(err);
}
if (public_key) {
async_callback(null, public_key);
} else {
async_callback(new Error('Could not recover public key from signature'));
}
}
function checkPublicKeyIsValid(public_key, async_callback) {
// Get hex-encoded public key
const key_pair = new KeyPair();
key_pair._pubkey = public_key;
const public_key_hex = key_pair.to_hex_pub();
const account_class_instance = new Account(remote, account);
account_class_instance.publicKeyIsActive(public_key_hex, async_callback);
}
const steps = [
recoverPublicKey,
checkPublicKeyIsValid
];
async.waterfall(steps, callback);
};
exports.Message = Message;

View File

@@ -1,330 +0,0 @@
/* eslint-disable max-len */
'use strict';
const assert = require('assert');
const sjcl = require('ripple-lib').sjcl;
const Message = require('ripple-lib').Message;
const Seed = require('ripple-lib').Seed;
const Remote = require('ripple-lib').Remote;
describe('Message', function() {
describe('signMessage', function() {
it('should prepend the MAGIC_BYTES, call the hashFunction, and then call signHash', function() {
const normal_signHash = Message.signHash;
const message_text = 'Hello World!';
let signHash_called = false;
Message.signHash = function(hash) {
signHash_called = true;
assert.deepEqual(hash, Message.hashFunction(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() {
const normal_random = sjcl.random.randomWords;
sjcl.random.randomWords = function(num_words) {
const words = [];
for (let w = 0; w < num_words; w++) {
words.push(sjcl.codec.hex.toBits('00000000'));
}
return words;
};
const secret_string = 'safRpB5euNL52PZPTSqrE9gvuFwTC';
// const address = 'rLLzaq61D633b5hhbNXKM9CkrYHboobVv3';
const hash = 'e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778';
const signature1 = Message.signHash(hash, secret_string);
const 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() {
const normal_random = sjcl.random.randomWords;
sjcl.random.randomWords = function(num_words) {
const words = [];
for (let w = 0; w < num_words; w++) {
words.push(sjcl.codec.hex.toBits('00000000'));
}
return words;
};
const secret_string = 'safRpB5euNL52PZPTSqrE9gvuFwTC';
// const address = 'rLLzaq61D633b5hhbNXKM9CkrYHboobVv3';
const hash = 'e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778';
const signature1 = Message.signHash(hash, secret_string);
const 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
const secret_string = 'sbadsafRpB5euNL52PZPTSqrE9gvuFwTC';
const 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() {
const secret_string = 'safRpB5euNL52PZPTSqrE9gvuFwTC';
const 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() {
const REGEX_BASE64 = /^([A-Za-z0-9\+]{4})*([A-Za-z0-9\+]{2}==)|([A-Za-z0-9\+]{3}=)?$/;
const normal_random = sjcl.random.randomWords;
sjcl.random.randomWords = function(num_words) {
const words = [];
for (let w = 0; w < num_words; w++) {
words.push(sjcl.codec.hex.toBits('00000000'));
}
return words;
};
const secret_string = 'safRpB5euNL52PZPTSqrE9gvuFwTC';
// const address = 'rLLzaq61D633b5hhbNXKM9CkrYHboobVv3';
const hash = 'e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778';
const 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 hashFunction, and then call verifyHashSignature', function() {
const normal_verifyHashSignature = Message.verifyHashSignature;
const data = {
message: 'Hello world!',
signature: 'AAAAGzFa1pYjhssCpDFZgFSnYQ8qCnMkLaZrg0mXZyNQ2NxgMQ8z9U3ngYerxSZCEt3Q4raMIpt03db7jDNGbfmHy8I='
};
let verifyHashSignature_called = false;
Message.verifyHashSignature = function(vhs_data, remote, callback) {
verifyHashSignature_called = true;
assert.deepEqual(vhs_data.hash, Message.hashFunction(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() {
const data = {
message: 'Hello world!',
hash: '861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8',
signature: 'AAAAHOUJQzG/7BO82fGNt1TNE+GGVXKuQQ0N2nTO+iJETE69PiHnaAkkOzovM177OosxbKjpt3KvwuJflgUB2YGvgjk=',
account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'
};
assert.throws(function() {
Message.verifyHashSignature(data);
}, /(?=.*callback\ function).*/);
});
it('should respond with an error if the hash is missing or invalid', function(done) {
const data = {
message: 'Hello world!',
signature: 'AAAAHOUJQzG/7BO82fGNt1TNE+GGVXKuQQ0N2nTO+iJETE69PiHnaAkkOzovM177OosxbKjpt3KvwuJflgUB2YGvgjk=',
account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'
};
const test_remote = new Remote();
test_remote.state = 'online';
Message.verifyHashSignature(data, test_remote, function(err) {
assert(/hash/i.test(err.message));
done();
});
});
it('should respond with an error if the account is missing or invalid', function(done) {
const data = {
message: 'Hello world!',
hash: '861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8',
signature: 'AAAAHOUJQzG/7BO82fGNt1TNE+GGVXKuQQ0N2nTO+iJETE69PiHnaAkkOzovM177OosxbKjpt3KvwuJflgUB2YGvgjk='
};
const test_remote = new Remote();
test_remote.state = 'online';
Message.verifyHashSignature(data, test_remote, function(err) {
assert(/account|address/i.test(err.message));
done();
});
});
it('should respond with an error if the signature is missing or invalid', function(done) {
const data = {
message: 'Hello world!',
hash: '861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8',
account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'
};
const test_remote = new Remote();
test_remote.state = 'online';
Message.verifyHashSignature(data, test_remote, function(err) {
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) {
const data = {
message: 'Hello world!',
hash: 'e9a82ea40514787918959b1100481500a5d384030f8770575c6a587675025fe212e6623e25643f251666a7b8b23af476c2850a8ea92153de5724db432892c752',
account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz',
signature: 'AAAAHMIPCQGLgdnpX1Ccv1wHb56H4NggxIM6U08Qkb9mUjN2Vn9pZ3CHvq1yWLBi6NqpW+7kedLnmfu4VG2+y43p4Xs='
};
const test_remote = new Remote();
test_remote.state = 'online';
test_remote.requestAccountInfo = function(options, callback) {
const 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
const data = {
message: 'Hello world!',
hash: 'e9a82ea40514787918959b1100481500a5d384030f8770575c6a587675025fe212e6623e25643f251666a7b8b23af476c2850a8ea92153de5724db432892c752',
account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz',
signature: 'AAAAG+dB/rAjZ5m8eQ/opcqQOJsFbKxOu9jq9KrOAlNO4OdcBDXyCBlkZqS9Xr8oZI2uh0boVsgYOS3pOLJz+Dh3Otk='
};
const test_remote = new Remote();
test_remote.state = 'online';
test_remote.requestAccountInfo = function(options, callback) {
const 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();
});
});
});
});