Skip to content
This repository has been archived by the owner on Sep 2, 2023. It is now read-only.

Commit

Permalink
Prepare/sign/submit flow for settings
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Clark committed Apr 10, 2015
1 parent 7b1af23 commit eadb0db
Show file tree
Hide file tree
Showing 17 changed files with 444 additions and 45 deletions.
6 changes: 6 additions & 0 deletions api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ var errors = require('./lib/errors');
var serverLib = require('./lib/server-lib');
var createRemote = require('./lib/remote');
var DatabaseInterface = require('./lib/db-interface');
var sign = require('./sign');
var submit = require('./submit');

function RippleAPI(options) {
this.remote = createRemote(options);
Expand Down Expand Up @@ -50,6 +52,10 @@ RippleAPI.prototype = {

wallet: Wallet,

prepareSettings: Settings.prepareSettings,
sign: sign,
submit: submit,

errors: errors,

isConnected: function() {
Expand Down
49 changes: 48 additions & 1 deletion api/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,52 @@ function isValidLedgerWord(ledger) {
return (/^current$|^closed$|^validated$/.test(ledger));
}

function getFeeDrops(remote) {
var feeUnits = 10; // all transactions currently have a fee of 10 fee units
return remote.feeTx(feeUnits).to_text();
}

function addTxInstructions(tx_json, account, remote, options, callback) {
if (options.lastLedgerSequence !== undefined) {
tx_json.LastLedgerSequence = options.lastLedgerSequence;
} else {
var offset = options.lastLedgerOffset !== undefined ?
options.lastLedgerOffset : 3;
tx_json.LastLedgerSequence = remote.getLedgerSequence() + offset;
}

if (options.fixedFee !== undefined) {
tx_json.Fee = xrpToDrops(options.fixedFee);
} else {
var serverFeeDrops = getFeeDrops(remote);
if (options.maxFee !== undefined) {
var maxFeeDrops = xrpToDrops(options.maxFee);
tx_json.Fee = bignum.min(serverFeeDrops, maxFeeDrops).toString();
} else {
tx_json.Fee = serverFeeDrops;
}
}

if (options.sequence !== undefined) {
tx_json.Sequence = options.sequence;
callback(null, {tx_json: tx_json});
} else {
remote.findAccount(account).getNextSequence(function(error, sequence) {
tx_json.Sequence = sequence;
callback(null, {tx_json: tx_json});
});
}
}

function createTxJSON(setTxParameters, remote, instructions, callback) {
var transaction = new ripple.Transaction();
setTxParameters(transaction);
transaction.complete();
var account = transaction.getAccount();
var tx_json = transaction.tx_json;
addTxInstructions(tx_json, account, remote, instructions, callback);
}

module.exports = {
isValidLedgerSequence: isValidLedgerSequence,
isValidLedgerWord: isValidLedgerWord,
Expand All @@ -136,6 +182,7 @@ module.exports = {
parseCurrencyQuery: parseCurrencyQuery,
txFromRestAmount: txFromRestAmount,
compareTransactions: compareTransactions,
renameCounterpartyToIssuer: renameCounterpartyToIssuer
renameCounterpartyToIssuer: renameCounterpartyToIssuer,
createTxJSON: createTxJSON
};

26 changes: 25 additions & 1 deletion api/lib/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,28 @@ function validateValidated(validated) {
}
}

function validateTxJSON(txJSON) {
if (typeof txJSON !== 'object') {
throw error('tx_json must be an object, not: ' + typeof txJSON);
}
if (!isValidAddress(txJSON.Account)) {
throw error('tx_json.Account must be a valid Ripple address, got: '
+ txJSON.Account);
}
}

function validateBlob(blob) {
if (typeof blob !== 'string') {
throw error('tx_blob must be a string, not: ' + typeof blob);
}
if (blob.length === 0) {
throw error('tx_blob must not be empty');
}
if (!blob.match(/[0-9A-F]+/g)) {
throw error('tx_blob must be an uppercase hex string, got: ' + blob);
}
}

function createValidators(validatorMap) {
var result = {};
_.forEach(validatorMap, function(validateFunction, key) {
Expand Down Expand Up @@ -482,5 +504,7 @@ module.exports = createValidators({
pathfind: validatePathFind,
settings: validateSettings,
trustline: validateTrustline,
validated: validateValidated
validated: validateValidated,
txJSON: validateTxJSON,
blob: validateBlob
});
62 changes: 34 additions & 28 deletions api/settings.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable valid-jsdoc */
'use strict';
var _ = require('lodash');
var utils = require('./lib/utils');
var assert = require('assert');
var ripple = require('ripple-lib');
var transactions = require('./transactions.js');
Expand Down Expand Up @@ -226,6 +227,30 @@ function getSettings(account, callback) {
});
}

function setTransactionParameters(account, settings, transaction) {
transaction.accountSet(account);

transactions.setTransactionBitFlags(transaction, {
input: settings,
flags: AccountSetFlags,
clear_setting: CLEAR_SETTING
});
setTransactionIntFlags(transaction, settings, AccountSetIntFlags);
setTransactionFields(transaction, settings, AccountRootFields);

transaction.tx_json.TransferRate = RestToTxConverter.convertTransferRate(
transaction.tx_json.TransferRate);
}

function prepareSettings(account, settings, instructions, callback) {
instructions = instructions || {};
validate.address(account);
validate.settings(settings);

utils.createTxJSON(_.partial(setTransactionParameters, account, settings),
this.remote, instructions, callback);
}

/**
* Change account settings
*
Expand All @@ -239,48 +264,29 @@ function getSettings(account, callback) {
*
*/
function changeSettings(account, settings, secret, options, callback) {
validate.address(account);
validate.settings(settings);

var params = {
secret: secret,
validated: options.validated
};

validate.address(account);
validate.settings(settings);

function setTransactionParameters(transaction) {
transaction.accountSet(account);

transactions.setTransactionBitFlags(transaction, {
input: settings,
flags: AccountSetFlags,
clear_setting: CLEAR_SETTING
});
setTransactionIntFlags(transaction, settings, AccountSetIntFlags);
setTransactionFields(transaction, settings, AccountRootFields);

transaction.tx_json.TransferRate = RestToTxConverter.convertTransferRate(
transaction.tx_json.TransferRate);
}

var hooks = {
formatTransactionResponse: TxToRestConverter.parseSettingResponseFromTx
.bind(undefined, settings),
setTransactionParameters: setTransactionParameters
formatTransactionResponse: _.partial(
TxToRestConverter.parseSettingResponseFromTx, settings),
setTransactionParameters: _.partial(setTransactionParameters,
account, settings)
};

transactions.submit(this, params, new SubmitTransactionHooks(hooks),
function(err, settingsResult) {
if (err) {
return callback(err);
}

callback(null, settingsResult);
});
callback);
}

module.exports = {
get: getSettings,
change: changeSettings,
prepareSettings: prepareSettings,
AccountSetIntFlags: AccountSetIntFlags,
AccountRootFields: AccountRootFields
};
66 changes: 66 additions & 0 deletions api/sign.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'use strict';
var sjcl = require('ripple-lib').sjcl;
var Seed = require('ripple-lib').Seed;
var SerializedObject = require('ripple-lib').SerializedObject;
var validate = require('./lib/validate');

/**
* These prefixes are inserted before the source material used to
* generate various hashes. This is done to put each hash in its own
* "space." This way, two different types of objects with the
* same binary data will produce different hashes.
*
* Each prefix is a 4-byte value with the last byte set to zero
* and the first three bytes formed from the ASCII equivalent of
* some arbitrary string. For example "TXN".
*/
var HASH_TX_ID = 0x54584E00; // 'TXN'
var HASH_TX_SIGN = 0x53545800; // 'STX'
var HASH_TX_SIGN_TESTNET = 0x73747800; // 'stx'

function getKeyPair(address, secret) {
return Seed.from_json(secret).get_key(address);
}

function getPublicKeyHex(keypair) {
return keypair.to_hex_pub();
}

function serialize(txJSON) {
return SerializedObject.from_json(txJSON);
}

function hashSerialization(serialized, prefix) {
return serialized.hash(prefix || HASH_TX_ID).to_hex();
}

function hashJSON(txJSON, prefix) {
return hashSerialization(serialize(txJSON), prefix);
}

function signingHash(txJSON, isTestNet) {
return hashJSON(txJSON, isTestNet ? HASH_TX_SIGN_TESTNET : HASH_TX_SIGN);
}

function computeSignature(txJSON, keypair) {
var signature = keypair.sign(signingHash(txJSON));
return sjcl.codec.hex.fromBits(signature).toUpperCase();
}

function sign(txJSON, secret) {
validate.txJSON(txJSON);
validate.addressAndSecret({address: txJSON.Account, secret: secret});

var keypair = getKeyPair(txJSON.Acccount, secret);
if (txJSON.SigningPubKey === undefined) {
txJSON.SigningPubKey = getPublicKeyHex(keypair);
}
txJSON.TxnSignature = computeSignature(txJSON, keypair);
var serialized = serialize(txJSON);
return {
tx_blob: serialized.to_hex(),
hash: hashSerialization(serialized, HASH_TX_ID)
};
}

module.exports = sign;
12 changes: 12 additions & 0 deletions api/submit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use strict';
var Request = require('ripple-lib').Request;
var validate = require('./lib/validate');

function submit(tx_blob, callback) {
validate.blob(tx_blob);
var request = new Request(this.remote, 'submit');
request.message.tx_blob = tx_blob;
request.request(callback);
}

module.exports = submit;
20 changes: 10 additions & 10 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"morgan": "^1.3.0",
"nconf": "^0.6.9",
"node-uuid": "^1.4.1",
"ripple-lib": "^0.12",
"ripple-lib": "^0.12.3-rc1",
"ripple-lib-transactionparser": "^0.3.0",
"sqlite3": "^3.0.2",
"supertest": "^0.13.0",
Expand Down
Loading

0 comments on commit eadb0db

Please sign in to comment.