mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-12-03 18:45:48 +00:00
[DEBUG] multiple bug fixes with blob creation
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
var crypt = require('./crypt').Crypt;
|
var crypt = require('./crypt').Crypt;
|
||||||
|
var SignedRequest = require('./signedrequest').SignedRequest;
|
||||||
var request = require('superagent');
|
var request = require('superagent');
|
||||||
var extend = require("extend");
|
var extend = require("extend");
|
||||||
|
|
||||||
@@ -34,7 +35,6 @@ BlobObj.ops = {
|
|||||||
|
|
||||||
|
|
||||||
BlobObj.opsReverseMap = [ ];
|
BlobObj.opsReverseMap = [ ];
|
||||||
|
|
||||||
for (var name in BlobObj.ops) {
|
for (var name in BlobObj.ops) {
|
||||||
BlobObj.opsReverseMap[BlobObj.ops[name]] = name;
|
BlobObj.opsReverseMap[BlobObj.ops[name]] = name;
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ var entityTypes = [
|
|||||||
'individual',
|
'individual',
|
||||||
'organization',
|
'organization',
|
||||||
'corporation'
|
'corporation'
|
||||||
]
|
];
|
||||||
|
|
||||||
var addressFields = [
|
var addressFields = [
|
||||||
'contact',
|
'contact',
|
||||||
@@ -153,7 +153,9 @@ BlobObj.prototype.consolidate = function(fn) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
var signed = crypt.signRequestHmac(config, this.data.auth_secret, this.id);
|
var signedRequest = new SignedRequest(config);
|
||||||
|
|
||||||
|
var signed = signedRequest.signHmac(this.data.auth_secret, this.id);
|
||||||
|
|
||||||
request.post(signed.url)
|
request.post(signed.url)
|
||||||
.send(signed.data)
|
.send(signed.data)
|
||||||
@@ -198,8 +200,7 @@ BlobObj.prototype.applyEncryptedPatch = function(patch) {
|
|||||||
*
|
*
|
||||||
* @param {string} secretUnlockkey
|
* @param {string} secretUnlockkey
|
||||||
*/
|
*/
|
||||||
|
BlobObj.prototype.encryptSecret = function (secretUnlockKey, secret) {
|
||||||
BlobObj.prototype.encryptSecret = function(secretUnlockKey, secret) {
|
|
||||||
return crypt.encrypt(secretUnlockKey, secret);
|
return crypt.encrypt(secretUnlockKey, secret);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -483,7 +484,10 @@ BlobObj.prototype.postUpdate = function(op, pointer, params, fn) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var signed = crypt.signRequestHmac(config, this.data.auth_secret, this.id);
|
|
||||||
|
var signedRequest = new SignedRequest(config);
|
||||||
|
|
||||||
|
var signed = signedRequest.signHmac(this.data.auth_secret, this.id);
|
||||||
|
|
||||||
request.post(signed.url)
|
request.post(signed.url)
|
||||||
.send(signed.data)
|
.send(signed.data)
|
||||||
@@ -781,6 +785,7 @@ exports.getRippleName = function(url, address, fn) {
|
|||||||
return fn (new Error('Invalid ripple address'));
|
return fn (new Error('Invalid ripple address'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!crypt.isValidAddress(address)) return fn (new Error("Invalid ripple address"));
|
||||||
request.get(url + '/v1/user/' + address, function(err, resp){
|
request.get(url + '/v1/user/' + address, function(err, resp){
|
||||||
if (err) {
|
if (err) {
|
||||||
fn(new Error('Unable to access vault sever'));
|
fn(new Error('Unable to access vault sever'));
|
||||||
@@ -840,11 +845,11 @@ BlobClient.create = function(options, fn) {
|
|||||||
blob.revision = 0;
|
blob.revision = 0;
|
||||||
|
|
||||||
blob.data = {
|
blob.data = {
|
||||||
auth_secret: crypt.createSecret(8),
|
auth_secret : crypt.createSecret(8),
|
||||||
account_id: crypt.getAddress(options.masterkey),
|
account_id : crypt.getAddress(options.masterkey),
|
||||||
email: options.email,
|
email : options.email,
|
||||||
contacts: [],
|
contacts : [],
|
||||||
created: (new Date()).toJSON()
|
created : (new Date()).toJSON()
|
||||||
};
|
};
|
||||||
|
|
||||||
blob.encrypted_secret = blob.encryptSecret(options.unlock, options.masterkey);
|
blob.encrypted_secret = blob.encryptSecret(options.unlock, options.masterkey);
|
||||||
@@ -856,30 +861,33 @@ BlobClient.create = function(options, fn) {
|
|||||||
|
|
||||||
//post to the blob vault to create
|
//post to the blob vault to create
|
||||||
var config = {
|
var config = {
|
||||||
method: 'POST',
|
method : 'POST',
|
||||||
url: options.url + '/v1/user',
|
url : options.url + '/v1/user',
|
||||||
data: {
|
data : {
|
||||||
blob_id: options.id,
|
blob_id : options.id,
|
||||||
username: options.username,
|
username : options.username,
|
||||||
address: blob.data.account_id,
|
address : blob.data.account_id,
|
||||||
auth_secret: blob.data.auth_secret,
|
auth_secret : blob.data.auth_secret,
|
||||||
data: blob.encrypt(),
|
data : blob.encrypt(),
|
||||||
email: options.email,
|
email : options.email,
|
||||||
hostlink: options.activateLink,
|
hostlink : options.activateLink,
|
||||||
encrypted_blobdecrypt_key: blob.encryptBlobCrypt(options.masterkey, options.crypt),
|
encrypted_blobdecrypt_key : blob.encryptBlobCrypt(options.masterkey, options.crypt),
|
||||||
encrypted_secret: blob.encrypted_secret
|
encrypted_secret : blob.encrypted_secret
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var signed = crypt.signRequestAsymmetric(config, options.masterkey, blob.data.account_id, options.id);
|
var signedRequest = new SignedRequest(config);
|
||||||
|
var signed = signedRequest.signAsymmetric(options.masterkey, blob.data.account_id, options.id);
|
||||||
|
|
||||||
request.post(signed)
|
request.post(signed.url)
|
||||||
.send(signed.data)
|
.send(signed.data)
|
||||||
.end(function(err, resp) {
|
.end(function(err, resp) {
|
||||||
if (err) {
|
if (err) {
|
||||||
fn(err);
|
fn(err);
|
||||||
} else if (resp.body && resp.body.result === 'success') {
|
} else if (resp.body && resp.body.result === 'success') {
|
||||||
fn(null, blob,resp.body);
|
fn(null, blob,resp.body);
|
||||||
|
} else if (resp.body && resp.body.result === 'error') {
|
||||||
|
fn(new Error(resp.body.message));
|
||||||
} else {
|
} else {
|
||||||
fn(new Error('Could not create blob'));
|
fn(new Error('Could not create blob'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
var sjcl = require('./utils').sjcl;
|
var sjcl = require('./utils').sjcl;
|
||||||
var base = require('./base').Base;
|
var base = require('./base').Base;
|
||||||
|
var Seed = require('./seed').Seed;
|
||||||
var UInt160 = require('./uint160').UInt160;
|
var UInt160 = require('./uint160').UInt160;
|
||||||
var message = require('./message');
|
var UInt256 = require('./uint256').UInt256;
|
||||||
var request = require('superagent');
|
var request = require('superagent');
|
||||||
var querystring = require('querystring');
|
var querystring = require('querystring');
|
||||||
var extend = require("extend");
|
var extend = require("extend");
|
||||||
var parser = require("url");
|
var parser = require("url");
|
||||||
@@ -143,56 +144,7 @@ Crypt.derive = function(opts, purpose, username, secret, fn) {
|
|||||||
* Imported from ripple-client
|
* Imported from ripple-client
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Crypt.RippleAddress = (function() {
|
|
||||||
|
|
||||||
function append_int(a, i) {
|
|
||||||
return [].concat(a, i >> 24, (i >> 16) & 0xff, (i >> 8) & 0xff, i & 0xff);
|
|
||||||
}
|
|
||||||
|
|
||||||
function firstHalfOfSHA512(bytes) {
|
|
||||||
return sjcl.bitArray.bitSlice(
|
|
||||||
sjcl.hash.sha512.hash(sjcl.codec.bytes.toBits(bytes)),
|
|
||||||
0, 256
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function SHA256_RIPEMD160(bits) {
|
|
||||||
return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
|
|
||||||
};
|
|
||||||
|
|
||||||
return function(seed) {
|
|
||||||
this.seed = base.decode_check(33, seed);
|
|
||||||
|
|
||||||
if (!this.seed) {
|
|
||||||
throw new Error('Invalid seed.');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.getAddress = function(seq) {
|
|
||||||
seq = seq || 0;
|
|
||||||
|
|
||||||
var private_gen, public_gen, i = 0;
|
|
||||||
|
|
||||||
do {
|
|
||||||
private_gen = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(this.seed, i)));
|
|
||||||
i++;
|
|
||||||
} while (!sjcl.ecc.curves.c256.r.greaterEquals(private_gen));
|
|
||||||
|
|
||||||
public_gen = sjcl.ecc.curves.c256.G.mult(private_gen);
|
|
||||||
|
|
||||||
var sec;
|
|
||||||
i = 0;
|
|
||||||
|
|
||||||
do {
|
|
||||||
sec = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(append_int(public_gen.toBytesCompressed(), seq), i)));
|
|
||||||
i++;
|
|
||||||
} while (!sjcl.ecc.curves.c256.r.greaterEquals(sec));
|
|
||||||
|
|
||||||
var pubKey = sjcl.ecc.curves.c256.G.mult(sec).toJac().add(public_gen).toAffine();
|
|
||||||
|
|
||||||
return base.encode_check(0, sjcl.codec.bytes.fromBits(SHA256_RIPEMD160(sjcl.codec.bytes.toBits(pubKey.toBytesCompressed()))));
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt data
|
* Encrypt data
|
||||||
@@ -255,7 +207,7 @@ Crypt.isValidAddress = function (address) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate a ripple address
|
* Create an encryption key
|
||||||
*
|
*
|
||||||
* @param {integer} nWords - number of words
|
* @param {integer} nWords - number of words
|
||||||
*/
|
*/
|
||||||
@@ -280,19 +232,32 @@ Crypt.createMaster = function () {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
Crypt.getAddress = function (masterkey) {
|
Crypt.getAddress = function (masterkey) {
|
||||||
return new Crypt.RippleAddress(masterkey).getAddress();
|
return Seed.from_json(masterkey).get_key().get_address().to_json();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hash data
|
* Hash data using SHA-512.
|
||||||
*
|
*
|
||||||
* @param {string} data
|
* @param {string|bitArray} data
|
||||||
|
* @return {string} Hash of the data
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Crypt.hashSha512 = function (data) {
|
Crypt.hashSha512 = function (data) {
|
||||||
|
// XXX Should return a UInt512
|
||||||
return sjcl.codec.hex.fromBits(sjcl.hash.sha512.hash(data));
|
return sjcl.codec.hex.fromBits(sjcl.hash.sha512.hash(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash data using SHA-512 and return the first 256 bits.
|
||||||
|
*
|
||||||
|
* @param {string|bitArray} data
|
||||||
|
* @return {UInt256} Hash of the data
|
||||||
|
*/
|
||||||
|
Crypt.hashSha512Half = function (data) {
|
||||||
|
return UInt256.from_hex(Crypt.hashSha512(data).substr(0, 64));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign a data string with a secret key
|
* Sign a data string with a secret key
|
||||||
*
|
*
|
||||||
@@ -345,179 +310,4 @@ Crypt.base64UrlToBase64 = function(encodedData) {
|
|||||||
return encodedData;
|
return encodedData;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a string from request parameters that
|
|
||||||
* will be used to sign a request
|
|
||||||
*
|
|
||||||
* @param {Object} config - request params
|
|
||||||
* @param {Object} parsed - parsed url
|
|
||||||
* @param {Object} date
|
|
||||||
* @param {Object} mechanism - type of signing
|
|
||||||
*/
|
|
||||||
|
|
||||||
Crypt.getStringToSign = function(config, parsed, date, mechanism) {
|
|
||||||
// XXX This method doesn't handle signing GET requests correctly. The data
|
|
||||||
// field will be merged into the search string, not the request body.
|
|
||||||
// Sort the properties of the JSON object into canonical form
|
|
||||||
var canonicalData = JSON.stringify(copyObjectWithSortedKeys(config.data));
|
|
||||||
|
|
||||||
// We're using URL parsing using browser functionality. Unfortunately the
|
|
||||||
// parsing result slightly differs in IE - it is missing a leading slash.
|
|
||||||
// XXX Proper fix would be to use a pure JS URL parser.
|
|
||||||
var pathname = parsed.pathname;
|
|
||||||
|
|
||||||
// IE11 Workaround
|
|
||||||
if (pathname[0] !== '/') pathname = '/' + pathname;
|
|
||||||
|
|
||||||
// Canonical request using Amazon's v4 signature format
|
|
||||||
// See: http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
|
|
||||||
var canonicalRequest = [
|
|
||||||
config.method || 'GET',
|
|
||||||
pathname || '',
|
|
||||||
parsed.search || '',
|
|
||||||
// XXX Headers signing not supported
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
Crypt.hashSha512(canonicalData).toLowerCase()
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
// String to sign inspired by Amazon's v4 signature format
|
|
||||||
// See: http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
|
|
||||||
//
|
|
||||||
// We don't have a credential scope, so we skip it.
|
|
||||||
//
|
|
||||||
// But that modifies the format, so the format ID is RIPPLE1, instead of AWS4.
|
|
||||||
return [
|
|
||||||
mechanism,
|
|
||||||
date,
|
|
||||||
Crypt.hashSha512(canonicalRequest).toLowerCase()
|
|
||||||
].join('\n');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HMAC signed request
|
|
||||||
*
|
|
||||||
* @param {Object} config
|
|
||||||
* @param {Object} auth_secret
|
|
||||||
* @param {Object} blob_id
|
|
||||||
*/
|
|
||||||
|
|
||||||
Crypt.signRequestHmac = function(config, auth_secret, blob_id) {
|
|
||||||
config = extend(true, {}, config);
|
|
||||||
|
|
||||||
// Parse URL
|
|
||||||
var parsed = parser.parse(config.url);
|
|
||||||
var date = dateAsIso8601();
|
|
||||||
var signatureType = 'RIPPLE1-HMAC-SHA512';
|
|
||||||
var stringToSign = Crypt.getStringToSign(config, parsed, date, signatureType);
|
|
||||||
var signature = Crypt.signString(auth_secret, stringToSign);
|
|
||||||
|
|
||||||
var query = querystring.stringify({
|
|
||||||
signature: Crypt.base64ToBase64Url(signature),
|
|
||||||
signature_date: date,
|
|
||||||
signature_blob_id: blob_id,
|
|
||||||
signature_type: signatureType
|
|
||||||
});
|
|
||||||
|
|
||||||
config.url += (parsed.search ? '&' : '?') + query;
|
|
||||||
return config;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asymmetric signed request
|
|
||||||
*
|
|
||||||
* @param {Object} config
|
|
||||||
* @param {Object} secretKey
|
|
||||||
* @param {Object} account
|
|
||||||
* @param {Object} blob_id
|
|
||||||
*/
|
|
||||||
|
|
||||||
Crypt.signRequestAsymmetric = function(config, secretKey, account, blob_id) {
|
|
||||||
config = extend(true, {}, config);
|
|
||||||
|
|
||||||
// Parse URL
|
|
||||||
var parsed = parser.parse(config.url);
|
|
||||||
var date = dateAsIso8601();
|
|
||||||
var signatureType = 'RIPPLE1-ECDSA-SHA512';
|
|
||||||
var stringToSign = Crypt.getStringToSign(config, parsed, date, signatureType);
|
|
||||||
var signature = message.signMessage(stringToSign, secretKey);
|
|
||||||
|
|
||||||
var query = querystring.stringify({
|
|
||||||
signature: Crypt.base64ToBase64Url(signature),
|
|
||||||
signature_date: date,
|
|
||||||
signature_blob_id: blob_id,
|
|
||||||
signature_account: account,
|
|
||||||
signature_type: signatureType
|
|
||||||
})
|
|
||||||
|
|
||||||
config.url += (parsed.search ? '&' : '?') + query;
|
|
||||||
|
|
||||||
return config;
|
|
||||||
};
|
|
||||||
|
|
||||||
//prepare for signing
|
|
||||||
function copyObjectWithSortedKeys(object) {
|
|
||||||
if (isPlainObject(object)) {
|
|
||||||
var newObj = {};
|
|
||||||
var keysSorted = Object.keys(object).sort();
|
|
||||||
var key;
|
|
||||||
|
|
||||||
for (var i in keysSorted) {
|
|
||||||
key = keysSorted[i];
|
|
||||||
if (Object.prototype.hasOwnProperty.call(object, key)) {
|
|
||||||
newObj[key] = copyObjectWithSortedKeys(object[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newObj;
|
|
||||||
} else if (Array.isArray(object)) {
|
|
||||||
return object.map(copyObjectWithSortedKeys);
|
|
||||||
} else {
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//from npm extend
|
|
||||||
function isPlainObject(obj) {
|
|
||||||
var hasOwn = Object.prototype.hasOwnProperty;
|
|
||||||
var toString = Object.prototype.toString;
|
|
||||||
|
|
||||||
if (!obj || toString.call(obj) !== '[object Object]' || obj.nodeType || obj.setInterval) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var has_own_constructor = hasOwn.call(obj, 'constructor');
|
|
||||||
var has_is_property_of_method = hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');
|
|
||||||
|
|
||||||
// Not own constructor property must be Object
|
|
||||||
if (obj.constructor && !has_own_constructor && !has_is_property_of_method) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Own properties are enumerated firstly, so to speed up,
|
|
||||||
// if last one is own, then all properties are own.
|
|
||||||
var key;
|
|
||||||
|
|
||||||
for ( key in obj ) {}
|
|
||||||
|
|
||||||
return key === void(0) || hasOwn.call( obj, key );
|
|
||||||
}
|
|
||||||
|
|
||||||
var dateAsIso8601 = (function() {
|
|
||||||
function pad(n) {
|
|
||||||
return (n < 0 || n > 9 ? '' : '0') + n;
|
|
||||||
};
|
|
||||||
|
|
||||||
return function dateAsIso8601() {
|
|
||||||
var date = new Date();
|
|
||||||
return date.getUTCFullYear() + "-" +
|
|
||||||
pad(date.getUTCMonth() + 1) + "-" +
|
|
||||||
pad(date.getUTCDate()) + "T" +
|
|
||||||
pad(date.getUTCHours()) + ":" +
|
|
||||||
pad(date.getUTCMinutes()) + ":" +
|
|
||||||
pad(date.getUTCSeconds()) + ".000Z";
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
exports.Crypt = Crypt;
|
exports.Crypt = Crypt;
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ exports.Seed = require('./seed').Seed;
|
|||||||
exports.Meta = require('./meta').Meta;
|
exports.Meta = require('./meta').Meta;
|
||||||
exports.SerializedObject = require('./serializedobject').SerializedObject;
|
exports.SerializedObject = require('./serializedobject').SerializedObject;
|
||||||
exports.RippleError = require('./rippleerror').RippleError;
|
exports.RippleError = require('./rippleerror').RippleError;
|
||||||
exports.Message = require('./message');
|
exports.Message = require('./message').Message;
|
||||||
exports.VaultClient = require('./vaultclient').VaultClient;
|
exports.VaultClient = require('./vaultclient').VaultClient;
|
||||||
exports.binformat = require('./binformat');
|
exports.binformat = require('./binformat');
|
||||||
exports.utils = require('./utils');
|
exports.utils = require('./utils');
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ var SHAMap = require('./shamap').SHAMap;
|
|||||||
var SHAMapTreeNode = require('./shamap').SHAMapTreeNode;
|
var SHAMapTreeNode = require('./shamap').SHAMapTreeNode;
|
||||||
var SerializedObject = require('./serializedobject').SerializedObject;
|
var SerializedObject = require('./serializedobject').SerializedObject;
|
||||||
var stypes = require('./serializedtypes');
|
var stypes = require('./serializedtypes');
|
||||||
|
var UInt160 = require('./uint160').UInt160;
|
||||||
|
var Currency = require('./currency').Currency;
|
||||||
|
var stypes = require('./serializedtypes');
|
||||||
|
var sjcl = require('./utils').sjcl;
|
||||||
|
var Crypt = require('./crypt').Crypt;
|
||||||
|
|
||||||
function Ledger()
|
function Ledger()
|
||||||
{
|
{
|
||||||
@@ -17,6 +22,91 @@ Ledger.from_json = function (v) {
|
|||||||
return ledger;
|
return ledger;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Ledger.space = require('./ledgerspaces');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the key for an AccountRoot entry.
|
||||||
|
*
|
||||||
|
* @param {String|UInt160} account Ripple Account
|
||||||
|
* @return {UInt256}
|
||||||
|
*/
|
||||||
|
Ledger.calcAccountRootEntryHash =
|
||||||
|
Ledger.prototype.calcAccountRootEntryHash = function (account) {
|
||||||
|
account = UInt160.from_json(account);
|
||||||
|
|
||||||
|
var index = new SerializedObject();
|
||||||
|
|
||||||
|
index.append([0, Ledger.space.account.charCodeAt(0)]);
|
||||||
|
index.append(account.to_bytes());
|
||||||
|
|
||||||
|
return index.hash();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the key for an Offer entry.
|
||||||
|
*
|
||||||
|
* @param {String|UInt160} account Ripple Account
|
||||||
|
* @param {Number} sequence Sequence number of the OfferCreate transaction
|
||||||
|
* that instantiated this offer.
|
||||||
|
* @return {UInt256}
|
||||||
|
*/
|
||||||
|
Ledger.calcOfferEntryHash =
|
||||||
|
Ledger.prototype.calcOfferEntryHash = function (account, sequence) {
|
||||||
|
account = UInt160.from_json(account);
|
||||||
|
sequence = parseInt(sequence);
|
||||||
|
|
||||||
|
var index = new SerializedObject();
|
||||||
|
|
||||||
|
index.append([0, Ledger.space.offer.charCodeAt(0)]);
|
||||||
|
index.append(account.to_bytes());
|
||||||
|
stypes.Int32.serialize(index, sequence);
|
||||||
|
|
||||||
|
return index.hash();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the key for a RippleState entry.
|
||||||
|
*
|
||||||
|
* The ordering of the two account parameters does not matter.
|
||||||
|
*
|
||||||
|
* @param {String|UInt160} account1 First Ripple Account
|
||||||
|
* @param {String|UInt160} account2 Second Ripple Account
|
||||||
|
* @param {String|Currency} currency The currency code
|
||||||
|
* @return {UInt256}
|
||||||
|
*/
|
||||||
|
Ledger.calcRippleStateEntryHash =
|
||||||
|
Ledger.prototype.calcRippleStateEntryHash = function (account1, account2, currency) {
|
||||||
|
currency = Currency.from_json(currency);
|
||||||
|
account1 = UInt160.from_json(account1);
|
||||||
|
account2 = UInt160.from_json(account2);
|
||||||
|
|
||||||
|
if (!account1.is_valid()) {
|
||||||
|
throw new Error("Invalid first account");
|
||||||
|
}
|
||||||
|
if (!account2.is_valid()) {
|
||||||
|
throw new Error("Invalid second account");
|
||||||
|
}
|
||||||
|
if (!currency.is_valid()) {
|
||||||
|
throw new Error("Invalid currency");
|
||||||
|
}
|
||||||
|
|
||||||
|
// The lower ID has to come first
|
||||||
|
if (account1.to_bn().greaterEquals(account2.to_bn())) {
|
||||||
|
var tmp = account2;
|
||||||
|
account2 = account1;
|
||||||
|
account1 = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = new SerializedObject();
|
||||||
|
|
||||||
|
index.append([0, Ledger.space.rippleState.charCodeAt(0)]);
|
||||||
|
index.append(account1.to_bytes());
|
||||||
|
index.append(account2.to_bytes());
|
||||||
|
index.append(currency.to_bytes());
|
||||||
|
|
||||||
|
return index.hash();
|
||||||
|
};
|
||||||
|
|
||||||
Ledger.prototype.parse_json = function (v) {
|
Ledger.prototype.parse_json = function (v) {
|
||||||
this.ledger_json = v;
|
this.ledger_json = v;
|
||||||
};
|
};
|
||||||
|
|||||||
22
src/js/ripple/ledgerspaces.js
Normal file
22
src/js/ripple/ledgerspaces.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Ripple ledger namespace prefixes.
|
||||||
|
*
|
||||||
|
* The Ripple ledger is a key-value store. In order to avoid name collisions,
|
||||||
|
* names are partitioned into namespaces.
|
||||||
|
*
|
||||||
|
* Each namespace is just a single character prefix.
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
account : 'a',
|
||||||
|
dirNode : 'd',
|
||||||
|
generatorMap : 'g',
|
||||||
|
nickname : 'n',
|
||||||
|
rippleState : 'r',
|
||||||
|
offer : 'o', // Entry for an offer.
|
||||||
|
ownerDir : 'O', // Directory of things owned by an account.
|
||||||
|
bookDir : 'B', // Directory of order books.
|
||||||
|
contract : 'c',
|
||||||
|
skipList : 's',
|
||||||
|
amendment : 'f',
|
||||||
|
feeSettings : 'e'
|
||||||
|
};
|
||||||
@@ -200,4 +200,4 @@ Message.verifyHashSignature = function(data, remote, callback) {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Message;
|
exports.Message = Message;
|
||||||
|
|||||||
@@ -64,9 +64,10 @@ RippleTxt.prototype.get = function(domain, fn) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
RippleTxt.prototype.parse = function(txt) {
|
RippleTxt.prototype.parse = function(txt) {
|
||||||
var txt = txt.replace(/\r?\n/g, '\n').split('\n')
|
|
||||||
var currentSection = '';
|
var currentSection = '';
|
||||||
var sections = { };
|
var sections = { };
|
||||||
|
|
||||||
|
txt = txt.replace(/\r?\n/g, '\n').split('\n');
|
||||||
|
|
||||||
for (var i = 0, l = txt.length; i < l; i++) {
|
for (var i = 0, l = txt.length; i < l; i++) {
|
||||||
var line = txt[i];
|
var line = txt[i];
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ var extend = require('extend');
|
|||||||
var binformat = require('./binformat');
|
var binformat = require('./binformat');
|
||||||
var stypes = require('./serializedtypes');
|
var stypes = require('./serializedtypes');
|
||||||
var UInt256 = require('./uint256').UInt256;
|
var UInt256 = require('./uint256').UInt256;
|
||||||
|
var Crypt = require('./crypt').Crypt;
|
||||||
var utils = require('./utils');
|
var utils = require('./utils');
|
||||||
|
|
||||||
var sjcl = utils.sjcl;
|
var sjcl = utils.sjcl;
|
||||||
@@ -247,20 +248,23 @@ SerializedObject.prototype.serialize = function(typedef, obj) {
|
|||||||
|
|
||||||
SerializedObject.prototype.hash = function(prefix) {
|
SerializedObject.prototype.hash = function(prefix) {
|
||||||
var sign_buffer = new SerializedObject();
|
var sign_buffer = new SerializedObject();
|
||||||
stypes.Int32.serialize(sign_buffer, prefix);
|
|
||||||
|
// Add hashing prefix
|
||||||
|
if ("undefined" !== typeof prefix) {
|
||||||
|
stypes.Int32.serialize(sign_buffer, prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy buffer to temporary buffer
|
||||||
sign_buffer.append(this.buffer);
|
sign_buffer.append(this.buffer);
|
||||||
return sign_buffer.hash_sha512_half();
|
|
||||||
|
// XXX We need a proper Buffer class then Crypt could accept that
|
||||||
|
var bits = sjcl.codec.bytes.toBits(sign_buffer.buffer);
|
||||||
|
return Crypt.hashSha512Half(bits);
|
||||||
};
|
};
|
||||||
|
|
||||||
// DEPRECATED
|
// DEPRECATED
|
||||||
SerializedObject.prototype.signing_hash = SerializedObject.prototype.hash;
|
SerializedObject.prototype.signing_hash = SerializedObject.prototype.hash;
|
||||||
|
|
||||||
SerializedObject.prototype.hash_sha512_half = function() {
|
|
||||||
var bits = sjcl.codec.bytes.toBits(this.buffer);
|
|
||||||
var hash = sjcl.bitArray.bitSlice(sjcl.hash.sha512.hash(bits), 0, 256);
|
|
||||||
return UInt256.from_hex(sjcl.codec.hex.fromBits(hash));
|
|
||||||
};
|
|
||||||
|
|
||||||
SerializedObject.prototype.serialize_field = function(spec, obj) {
|
SerializedObject.prototype.serialize_field = function(spec, obj) {
|
||||||
var name = spec[0];
|
var name = spec[0];
|
||||||
var presence = spec[1];
|
var presence = spec[1];
|
||||||
|
|||||||
174
src/js/ripple/signedrequest.js
Normal file
174
src/js/ripple/signedrequest.js
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
var Crypt = require('./crypt').Crypt;
|
||||||
|
var Message = require('./message').Message;
|
||||||
|
var parser = require("url");
|
||||||
|
var querystring = require('querystring');
|
||||||
|
var extend = require("extend");
|
||||||
|
|
||||||
|
var SignedRequest = function (config) {
|
||||||
|
// XXX Constructor should be generalized and constructing from an Angular.js
|
||||||
|
// $http config should be a SignedRequest.from... utility method.
|
||||||
|
this.config = extend(true, {}, config);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a string from request parameters that
|
||||||
|
* will be used to sign a request
|
||||||
|
* @param {Object} parsed - parsed url
|
||||||
|
* @param {Object} date
|
||||||
|
* @param {Object} mechanism - type of signing
|
||||||
|
*/
|
||||||
|
SignedRequest.prototype.getStringToSign = function (parsed, date, mechanism) {
|
||||||
|
// XXX This method doesn't handle signing GET requests correctly. The data
|
||||||
|
// field will be merged into the search string, not the request body.
|
||||||
|
|
||||||
|
// Sort the properties of the JSON object into canonical form
|
||||||
|
var canonicalData = JSON.stringify(copyObjectWithSortedKeys(this.config.data));
|
||||||
|
|
||||||
|
// Canonical request using Amazon's v4 signature format
|
||||||
|
// See: http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
|
||||||
|
var canonicalRequest = [
|
||||||
|
this.config.method || 'GET',
|
||||||
|
parsed.pathname || '',
|
||||||
|
parsed.search || '',
|
||||||
|
// XXX Headers signing not supported
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
Crypt.hashSha512(canonicalData).toLowerCase()
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
// String to sign inspired by Amazon's v4 signature format
|
||||||
|
// See: http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
|
||||||
|
//
|
||||||
|
// We don't have a credential scope, so we skip it.
|
||||||
|
//
|
||||||
|
// But that modifies the format, so the format ID is RIPPLE1, instead of AWS4.
|
||||||
|
return [
|
||||||
|
mechanism,
|
||||||
|
date,
|
||||||
|
Crypt.hashSha512(canonicalRequest).toLowerCase()
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
//prepare for signing
|
||||||
|
function copyObjectWithSortedKeys(object) {
|
||||||
|
if (isPlainObject(object)) {
|
||||||
|
var newObj = {};
|
||||||
|
var keysSorted = Object.keys(object).sort();
|
||||||
|
var key;
|
||||||
|
for (var i in keysSorted) {
|
||||||
|
key = keysSorted[i];
|
||||||
|
if (Object.prototype.hasOwnProperty.call(object, key)) {
|
||||||
|
newObj[key] = copyObjectWithSortedKeys(object[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newObj;
|
||||||
|
} else if (Array.isArray(object)) {
|
||||||
|
return object.map(copyObjectWithSortedKeys);
|
||||||
|
} else {
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//from npm extend
|
||||||
|
function isPlainObject(obj) {
|
||||||
|
var hasOwn = Object.prototype.hasOwnProperty;
|
||||||
|
var toString = Object.prototype.toString;
|
||||||
|
|
||||||
|
if (!obj || toString.call(obj) !== '[object Object]' || obj.nodeType || obj.setInterval)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var has_own_constructor = hasOwn.call(obj, 'constructor');
|
||||||
|
var has_is_property_of_method = hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');
|
||||||
|
// Not own constructor property must be Object
|
||||||
|
if (obj.constructor && !has_own_constructor && !has_is_property_of_method)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Own properties are enumerated firstly, so to speed up,
|
||||||
|
// if last one is own, then all properties are own.
|
||||||
|
var key;
|
||||||
|
for ( key in obj ) {}
|
||||||
|
|
||||||
|
return key === undefined || hasOwn.call( obj, key );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HMAC signed request
|
||||||
|
* @param {Object} config
|
||||||
|
* @param {Object} auth_secret
|
||||||
|
* @param {Object} blob_id
|
||||||
|
*/
|
||||||
|
SignedRequest.prototype.signHmac = function (auth_secret, blob_id) {
|
||||||
|
var config = extend(true, {}, this.config);
|
||||||
|
|
||||||
|
// Parse URL
|
||||||
|
var parsed = parser.parse(config.url);
|
||||||
|
var date = dateAsIso8601();
|
||||||
|
var signatureType = 'RIPPLE1-HMAC-SHA512';
|
||||||
|
var stringToSign = this.getStringToSign(parsed, date, signatureType);
|
||||||
|
var signature = Crypt.signString(auth_secret, stringToSign);
|
||||||
|
|
||||||
|
var query = querystring.stringify({
|
||||||
|
signature: Crypt.base64ToBase64Url(signature),
|
||||||
|
signature_date: date,
|
||||||
|
signature_blob_id: blob_id,
|
||||||
|
signature_type: signatureType
|
||||||
|
});
|
||||||
|
|
||||||
|
config.url += (parsed.search ? '&' : '?') + query;
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asymmetric signed request
|
||||||
|
* @param {Object} config
|
||||||
|
* @param {Object} secretKey
|
||||||
|
* @param {Object} account
|
||||||
|
* @param {Object} blob_id
|
||||||
|
*/
|
||||||
|
SignedRequest.prototype.signAsymmetric = function (secretKey, account, blob_id) {
|
||||||
|
var config = extend(true, {}, this.config);
|
||||||
|
|
||||||
|
// Parse URL
|
||||||
|
var parsed = parser.parse(config.url);
|
||||||
|
var date = dateAsIso8601();
|
||||||
|
var signatureType = 'RIPPLE1-ECDSA-SHA512';
|
||||||
|
var stringToSign = this.getStringToSign(parsed, date, signatureType);
|
||||||
|
var signature = Message.signMessage(stringToSign, secretKey);
|
||||||
|
|
||||||
|
var query = querystring.stringify({
|
||||||
|
signature: Crypt.base64ToBase64Url(signature),
|
||||||
|
signature_date: date,
|
||||||
|
signature_blob_id: blob_id,
|
||||||
|
signature_account: account,
|
||||||
|
signature_type: signatureType
|
||||||
|
});
|
||||||
|
|
||||||
|
config.url += (parsed.search ? '&' : '?') + query;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
var dateAsIso8601 = (function () {
|
||||||
|
function pad(n) {
|
||||||
|
return (n < 0 || n > 9 ? "" : "0") + n;
|
||||||
|
}
|
||||||
|
|
||||||
|
return function dateAsIso8601() {
|
||||||
|
var date = new Date();
|
||||||
|
return date.getUTCFullYear() + "-"
|
||||||
|
+ pad(date.getUTCMonth() + 1) + "-"
|
||||||
|
+ pad(date.getUTCDate()) + "T"
|
||||||
|
+ pad(date.getUTCHours()) + ":"
|
||||||
|
+ pad(date.getUTCMinutes()) + ":"
|
||||||
|
+ pad(date.getUTCSeconds()) + ".000Z";
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
// XXX Add methods for verifying requests
|
||||||
|
// SignedRequest.prototype.verifySignatureHmac
|
||||||
|
// SignedRequest.prototype.verifySignatureAsymetric
|
||||||
|
|
||||||
|
exports.SignedRequest = SignedRequest;
|
||||||
|
|
||||||
@@ -323,29 +323,29 @@ VaultClient.prototype.register = function(options, fn) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function create(authInfo, keys) {
|
function create(authInfo, keys, callback) {
|
||||||
var params = {
|
var params = {
|
||||||
url: authInfo.blobvault,
|
url: authInfo.blobvault,
|
||||||
id: keys.loginKeys.id,
|
id: keys.login.id,
|
||||||
crypt: keys.loginKeys.crypt,
|
crypt: keys.login.crypt,
|
||||||
unlock: keys.unlockKeys.unlock,
|
unlock: keys.unlock.unlock,
|
||||||
username: username,
|
username: username,
|
||||||
email: options.email,
|
email: options.email,
|
||||||
masterkey: options.masterkey || crypt.createMaster(),
|
masterkey: options.masterkey || crypt.createMaster(),
|
||||||
activateLink: options.activateLink,
|
activateLink: options.activateLink,
|
||||||
oldUserBlob: options.oldUserBlob
|
oldUserBlob: options.oldUserBlob
|
||||||
};
|
};
|
||||||
|
|
||||||
blobClient.create(params, function(err, blob) {
|
blobClient.create(params, function(err, blob) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
} else {
|
} else {
|
||||||
callback(null, blob, loginKeys, authInfo.username);
|
callback(null, blob, keys, authInfo.username);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
async.waterfall([ getAuthInfo, deriveKeys ], create);
|
async.waterfall([ getAuthInfo, deriveKeys, create], fn);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.VaultClient = VaultClient;
|
exports.VaultClient = VaultClient;
|
||||||
|
|||||||
@@ -34,6 +34,55 @@ describe('Ledger', function() {
|
|||||||
create_ledger_test(38129);
|
create_ledger_test(38129);
|
||||||
// Because, why not.
|
// Because, why not.
|
||||||
create_ledger_test(40000);
|
create_ledger_test(40000);
|
||||||
|
|
||||||
|
describe('#calcAccountRootEntryHash', function () {
|
||||||
|
it('will calculate the AccountRoot entry hash for rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () {
|
||||||
|
var account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
|
||||||
|
var expectedEntryHash = '2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8';
|
||||||
|
var actualEntryHash = Ledger.calcAccountRootEntryHash(account);
|
||||||
|
|
||||||
|
assert.equal(actualEntryHash.to_hex(), expectedEntryHash);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#calcRippleStateEntryHash', function () {
|
||||||
|
it('will calculate the RippleState entry hash for rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh and rB5TihdPbKgMrkFqrqUC3yLdE8hhv4BdeY in USD', function () {
|
||||||
|
var account1 = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
|
||||||
|
var account2 = 'rB5TihdPbKgMrkFqrqUC3yLdE8hhv4BdeY';
|
||||||
|
var currency = 'USD';
|
||||||
|
|
||||||
|
var expectedEntryHash = 'C683B5BB928F025F1E860D9D69D6C554C2202DE0D45877ADB3077DA4CB9E125C';
|
||||||
|
var actualEntryHash1 = Ledger.calcRippleStateEntryHash(account1, account2, currency);
|
||||||
|
var actualEntryHash2 = Ledger.calcRippleStateEntryHash(account2, account1, currency);
|
||||||
|
|
||||||
|
assert.equal(actualEntryHash1.to_hex(), expectedEntryHash);
|
||||||
|
assert.equal(actualEntryHash2.to_hex(), expectedEntryHash);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('will calculate the RippleState entry hash for r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV and rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj in UAM', function () {
|
||||||
|
var account1 = 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV';
|
||||||
|
var account2 = 'rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj';
|
||||||
|
var currency = 'UAM';
|
||||||
|
|
||||||
|
var expectedEntryHash = 'AE9ADDC584358E5847ADFC971834E471436FC3E9DE6EA1773DF49F419DC0F65E';
|
||||||
|
var actualEntryHash1 = Ledger.calcRippleStateEntryHash(account1, account2, currency);
|
||||||
|
var actualEntryHash2 = Ledger.calcRippleStateEntryHash(account2, account1, currency);
|
||||||
|
|
||||||
|
assert.equal(actualEntryHash1.to_hex(), expectedEntryHash);
|
||||||
|
assert.equal(actualEntryHash2.to_hex(), expectedEntryHash);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#calcOfferEntryHash', function () {
|
||||||
|
it('will calculate the Offer entry hash for r32UufnaCGL82HubijgJGDmdE5hac7ZvLw, sequence 137', function () {
|
||||||
|
var account = 'r32UufnaCGL82HubijgJGDmdE5hac7ZvLw';
|
||||||
|
var sequence = 137
|
||||||
|
var expectedEntryHash = '03F0AED09DEEE74CEF85CD57A0429D6113507CF759C597BABB4ADB752F734CE3';
|
||||||
|
var actualEntryHash = Ledger.calcOfferEntryHash(account, sequence);
|
||||||
|
|
||||||
|
assert.equal(actualEntryHash.to_hex(), expectedEntryHash);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// vim:sw=2:sts=2:ts=8:et
|
// vim:sw=2:sts=2:ts=8:et
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var sjcl = require('../build/sjcl');
|
var sjcl = require('../build/sjcl');
|
||||||
var Message = require('../src/js/ripple/message');
|
var Message = require('../src/js/ripple/message').Message;
|
||||||
var Seed = require('../src/js/ripple/seed').Seed;
|
var Seed = require('../src/js/ripple/seed').Seed;
|
||||||
var Remote = require('../src/js/ripple/remote').Remote;
|
var Remote = require('../src/js/ripple/remote').Remote;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user