Updated user json message schema.

This commit is contained in:
ravinsp
2020-06-24 12:47:39 +05:30
parent 817ccd6a88
commit 8103ef7af6
8 changed files with 71 additions and 224 deletions

View File

@@ -54,14 +54,14 @@ function main() {
let inp_container = {
nonce: (new Date()).getTime().toString(),
input: inp.toString('hex'),
max_ledger_seqno: 9999999
max_lcl_seqno: 9999999
}
let inp_container_bytes = JSON.stringify(inp_container);
let sig_bytes = sodium.crypto_sign_detached(inp_container_bytes, keys.privateKey);
let signed_inp_container = {
type: "contract_input",
content: inp_container_bytes.toString('hex'),
input_container: inp_container_bytes.toString('hex'),
sig: Buffer.from(sig_bytes).toString('hex')
}
@@ -80,7 +80,7 @@ function main() {
// sign the challenge and send back the response
var sigbytes = sodium.crypto_sign_detached(m.challenge, keys.privateKey);
var response = {
type: 'challenge_resp',
type: 'handshake_response',
challenge: m.challenge,
sig: Buffer.from(sigbytes).toString('hex'),
pubkey: pkhex
@@ -128,13 +128,13 @@ function main() {
return
}
if (m.type == 'public_challenge') {
if (m.type == 'handshake_challenge') {
handle_public_challange(m);
}
else if (m.type == 'contract_output') {
console.log("Contract says: " + Buffer.from(m.content, 'hex').toString());
}
else if (m.type == 'request_status_result') {
else if (m.type == 'contract_input_status') {
if (m.status != "accepted")
console.log("Input status: " + m.status);
}

View File

@@ -1,116 +0,0 @@
//
// HotPocket client example code adopted from:
// https://github.com/codetsunami/hotpocket/blob/master/hp_client.js
//
const fs = require('fs')
const ws_api = require('ws');
const sodium = require('libsodium-wrappers')
const readline = require('readline')
// sodium has a trigger when it's ready, we will wait and execute from there
sodium.ready.then(main).catch((e) => { console.log(e) })
function main() {
if (process.argv.length != 6) {
console.log("Incorrect format");
console.log("node client.js <ip_address> <port> <bytes_per_message> <interval>");
process.exit();
}
var args = process.argv.slice(2);
var keys = sodium.crypto_sign_keypair()
// check for client keys
if (!fs.existsSync('.hp_client_keys')) {
keys.privateKey = sodium.to_hex(keys.privateKey)
keys.publicKey = sodium.to_hex(keys.publicKey)
fs.writeFileSync('.hp_client_keys', JSON.stringify(keys))
} else {
keys = JSON.parse(fs.readFileSync('.hp_client_keys'))
keys.privateKey = Uint8Array.from(Buffer.from(keys.privateKey, 'hex'))
keys.publicKey = Uint8Array.from(Buffer.from(keys.publicKey, 'hex'))
}
var server = 'wss://localhost:8080'
if (process.argv.length == 3) server = 'wss://localhost:' + args[0]
if (process.argv.length == 4) server = 'wss://' + args[0] + ':' + args[1]
var ws = new ws_api(server, {
rejectUnauthorized: false
})
/* anatomy of a public challenge
{
version: '0.1',
type: 'public_challenge',
challenge: '<hex string>'
}
*/
// if the console ctrl + c's us we should close ws gracefully
process.once('SIGINT', function (code) {
console.log('SIGINT received...');
ws.close()
});
ws.on('message', (m) => {
console.log("-----Received raw message-----")
console.log(m.toString())
console.log("------------------------------")
try {
m = JSON.parse(m)
} catch (e) {
return
}
if (m.type != 'public_challenge') return
console.log("Received challenge message")
console.log(m)
let pkhex = 'ed' + Buffer.from(keys.publicKey).toString('hex');
console.log('My public key is: ' + pkhex);
// sign the challenge and send back the response
var sigbytes = sodium.crypto_sign_detached(m.challenge, keys.privateKey);
var response = {
type: 'challenge_resp',
challenge: m.challenge,
sig: Buffer.from(sigbytes).toString('hex'),
pubkey: pkhex
}
console.log('Sending challenge response.');
ws.send(JSON.stringify(response));
setInterval(() => {
var message = generateRandomMessage(args[2]);
console.log("Message :" + message);
ws.send(message);
}, args[3]);
});
ws.on('close', () => {
console.log('Server disconnected.');
});
}
function generateRandomMessage(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}

View File

@@ -58,14 +58,14 @@ function main() {
let inp_container = {
nonce: (new Date()).getTime().toString(),
input: Buffer.from(inp).toString('hex'),
max_ledger_seqno: 9999999
max_lcl_seqno: 9999999
}
let inp_container_bytes = JSON.stringify(inp_container);
let sig_bytes = sodium.crypto_sign_detached(inp_container_bytes, keys.privateKey);
let signed_inp_container = {
type: "contract_input",
content: inp_container_bytes.toString('hex'),
input_container: inp_container_bytes.toString('hex'),
sig: Buffer.from(sig_bytes).toString('hex')
}
@@ -97,7 +97,7 @@ function main() {
// sign the challenge and send back the response
var sigbytes = sodium.crypto_sign_detached(m.challenge, keys.privateKey);
var response = {
type: 'challenge_resp',
type: 'handshake_response',
challenge: m.challenge,
sig: Buffer.from(sigbytes).toString('hex'),
pubkey: pkhex
@@ -144,13 +144,13 @@ function main() {
return
}
if (m.type == 'public_challenge') {
if (m.type == 'handshake_challenge') {
handle_public_challange(m);
}
else if (m.type == 'contract_output' || m.type == 'contract_read_response') {
console.log(Buffer.from(m.content, 'hex').toString());
}
else if (m.type == 'request_status_result') {
else if (m.type == 'contract_input_status') {
if (m.status != "accepted")
console.log("Input status: " + m.status);
}

View File

@@ -380,11 +380,11 @@ namespace cons
{
std::string nonce;
std::string input;
uint64_t maxledgerseqno;
jusrmsg::extract_input_container(nonce, input, maxledgerseqno, umsg.content);
uint64_t max_lcl_seqno;
jusrmsg::extract_input_container(input, nonce, max_lcl_seqno, umsg.content);
// Ignore the input if our ledger has passed the input TTL.
if (maxledgerseqno > ctx.led_seq_no)
if (max_lcl_seqno > ctx.led_seq_no)
{
if (!appbill_balance_exceeded)
{
@@ -399,7 +399,7 @@ namespace cons
{
ctx.candidate_user_inputs.try_emplace(
hash,
candidate_user_input(pubkey, std::move(input), maxledgerseqno));
candidate_user_input(pubkey, std::move(input), max_lcl_seqno));
}
else
{
@@ -435,11 +435,10 @@ namespace cons
// Send the request status result if this user is connected to us.
if (session != NULL)
{
usr::send_request_status_result(*session,
reject_reason == NULL ? jusrmsg::STATUS_ACCEPTED : jusrmsg::STATUS_REJECTED,
reject_reason == NULL ? "" : reject_reason,
jusrmsg::MSGTYPE_CONTRACT_INPUT,
jusrmsg::origin_data_for_contract_input(umsg.sig));
usr::send_input_status(*session,
reject_reason == NULL ? jusrmsg::STATUS_ACCEPTED : jusrmsg::STATUS_REJECTED,
reject_reason == NULL ? "" : reject_reason,
umsg.sig);
}
}
}

View File

@@ -7,10 +7,6 @@
namespace jsonschema::usrmsg
{
// User JSON message schema version
constexpr const char *SCHEMA_VERSION = "0.1";
// Separators
constexpr const char *SEP_COMMA = "\",\"";
constexpr const char *SEP_COLON = "\":\"";
@@ -18,19 +14,19 @@ namespace jsonschema::usrmsg
constexpr const char *SEP_COLON_NOQUOTE = "\":";
// Message field names
const char *const FLD_VERSION = "version";
constexpr const char *FLD_TYPE = "type";
constexpr const char *FLD_CHALLENGE = "challenge";
constexpr const char *FLD_SIG = "sig";
constexpr const char *FLD_PUBKEY = "pubkey";
constexpr const char *FLD_INPUT = "input";
constexpr const char *FLD_MAX_LED_SEQ = "max_ledger_seqno";
constexpr const char *FLD_INPUT_CONTAINER = "input_container";
constexpr const char *FLD_INPUT_SIG = "input_sig";
constexpr const char *FLD_MAX_LCL_SEQ = "max_lcl_seqno";
constexpr const char *FLD_CONTENT = "content";
constexpr const char *FLD_NONCE = "nonce";
constexpr const char *FLD_LCL = "lcl";
constexpr const char *FLD_LCL_SEQ = "lcl_seqno";
constexpr const char *FLD_STATUS = "status";
constexpr const char *FLD_ORIGIN = "origin";
constexpr const char *FLD_REASON = "reason";
// Length of user random challenge bytes.
@@ -44,8 +40,7 @@ namespace jsonschema::usrmsg
* @param msg String reference to copy the generated json message string into.
* Message format:
* {
* "version": "<protocol version>",
* "type": "public_challenge",
* "type": "handshake_challenge",
* "challenge": "<hex challenge string>"
* }
* @param challengehex String reference to copy the generated hex challenge string into.
@@ -69,13 +64,9 @@ namespace jsonschema::usrmsg
// so allocating 128bytes for heap padding.
msg.reserve(128);
msg.append("{\"")
.append(FLD_VERSION)
.append(SEP_COLON)
.append(SCHEMA_VERSION)
.append(SEP_COMMA)
.append(FLD_TYPE)
.append(SEP_COLON)
.append(MSGTYPE_CHALLENGE)
.append(MSGTYPE_HANDSHAKE_CHALLENGE)
.append(SEP_COMMA)
.append(FLD_CHALLENGE)
.append(SEP_COLON)
@@ -88,7 +79,7 @@ namespace jsonschema::usrmsg
* @param msg String reference to copy the generated json message string into.
* Message format:
* {
* "type": "stat_resp",
* "type": "stat_response",
* "lcl": "<lcl id>",
* "lcl_seqno": <integer>
* }
@@ -99,7 +90,7 @@ namespace jsonschema::usrmsg
msg.append("{\"")
.append(FLD_TYPE)
.append(SEP_COLON)
.append(MSGTYPE_STAT_RESP)
.append(MSGTYPE_STAT_RESPONSE)
.append(SEP_COMMA)
.append(FLD_LCL)
.append(SEP_COLON)
@@ -112,30 +103,29 @@ namespace jsonschema::usrmsg
}
/**
* Constructs a request result message.
* Constructs a contract input status message.
* @param msg String reference to copy the generated json message string into.
* Message format:
* {
* "type": "request_status_result",
* "type": "contract_input_status",
* "status": "<accepted|rejected>",
* "reason": "",
* "origin": {
* "type": "<original msg type>",
* ...
* }
* "reason": "<reson>",
* "input_sig": "<hex sig of original input message>"
* }
* @param is_accepted Whether the original message was accepted or not.
* @param reason Rejected reason. Empty if accepted.
* @param origin_type Original message type which generated this result.
* @param origin_extra_data Extra field data string to be injected into origin.
* @param input_sig Binary signature of the original input message which generated this result.
*/
void create_request_status_result(std::string &msg, std::string_view status, std::string_view reason, std::string_view origin_type, std::string_view origin_extra_data)
void create_contract_input_status(std::string &msg, std::string_view status, std::string_view reason, std::string_view input_sig)
{
std::string sighex;
util::bin2hex(sighex, reinterpret_cast<const unsigned char *>(input_sig.data()), input_sig.length());
msg.reserve(128);
msg.append("{\"")
.append(FLD_TYPE)
.append(SEP_COLON)
.append(MSGTYPE_REQUEST_STATUS_RESULT)
.append(MSGTYPE_CONTRACT_INPUT_STATUS)
.append(SEP_COMMA)
.append(FLD_STATUS)
.append(SEP_COLON)
@@ -145,36 +135,12 @@ namespace jsonschema::usrmsg
.append(SEP_COLON)
.append(reason)
.append(SEP_COMMA)
.append(FLD_ORIGIN)
.append("\":{\"")
.append(FLD_TYPE)
.append(SEP_COLON)
.append(origin_type)
.append("\"")
.append(origin_extra_data)
.append("}}");
}
/**
* Returns concatenated string for contract input origin data fields to be included in request result.
* @param sig Binary singature of the original contract input.
*/
std::string origin_data_for_contract_input(std::string_view sig)
{
std::string sighex;
util::bin2hex(sighex, reinterpret_cast<const unsigned char *>(sig.data()), sig.length());
std::string extra_data;
extra_data.append(",\"")
.append(FLD_SIG)
.append(FLD_INPUT_SIG)
.append(SEP_COLON)
.append(sighex)
.append("\"");
return extra_data;
.append("\"}");
}
/**
* Constructs a contract read response message.
* @param msg String reference to copy the generated json message string into.
@@ -268,7 +234,7 @@ namespace jsonschema::usrmsg
return -1;
// Validate msg type.
if (d[FLD_TYPE] != MSGTYPE_CHALLENGE_RESP)
if (d[FLD_TYPE] != MSGTYPE_HANDSHAKE_RESPONSE)
{
LOG_DBG << "User challenge response type invalid. 'challenge_response' expected.";
return -1;
@@ -357,27 +323,27 @@ namespace jsonschema::usrmsg
/**
* Extracts a signed input container message sent by user.
*
* @param extracted_content The content extracted from the message.
* @param extracted_input_container The input container extracted from the message.
* @param extracted_sig The binary signature extracted from the message.
* @param d The json document holding the input container.
* Accepted signed input container format:
* {
* "type": "contract_input",
* "content": "<stringified json input container message>",
* "input_container": "<stringified json input container message>",
* "sig": "<hex encoded signature of the content>"
* }
* @return 0 on successful extraction. -1 for failure.
*/
int extract_signed_input_container(
std::string &extracted_content, std::string &extracted_sig, const rapidjson::Document &d)
std::string &extracted_input_container, std::string &extracted_sig, const rapidjson::Document &d)
{
if (!d.HasMember(FLD_CONTENT) || !d.HasMember(FLD_SIG))
if (!d.HasMember(FLD_INPUT_CONTAINER) || !d.HasMember(FLD_SIG))
{
LOG_DBG << "User signed input required fields missing.";
return -1;
}
if (!d[FLD_CONTENT].IsString() || !d[FLD_SIG].IsString())
if (!d[FLD_INPUT_CONTAINER].IsString() || !d[FLD_SIG].IsString())
{
LOG_DBG << "User signed input invalid field values.";
return -1;
@@ -386,32 +352,32 @@ namespace jsonschema::usrmsg
// We do not verify the signature of the content here since we need to let each node
// (including self) to verify that individually after we broadcast the NUP proposal.
const std::string content(d[FLD_CONTENT].GetString(), d[FLD_CONTENT].GetStringLength());
const std::string input_container(d[FLD_INPUT_CONTAINER].GetString(), d[FLD_INPUT_CONTAINER].GetStringLength());
const std::string_view sighex(d[FLD_SIG].GetString(), d[FLD_SIG].GetStringLength());
std::string sig;
sig.resize(crypto_sign_ed25519_BYTES);
util::hex2bin(reinterpret_cast<unsigned char *>(sig.data()), sig.length(), sighex);
extracted_content = std::move(content);
extracted_input_container = std::move(input_container);
extracted_sig = std::move(sig);
return 0;
}
/**
* Extract the individual components of a given input container json.
* @param nonce The extracted nonce.
* @param input The extracted input.
* @param max_ledger_seqno Themaxledgerseqno extracted max ledger sequence no.
* @param nonce The extracted nonce.
* @param max_lcl_seqno The extracted max ledger sequence no.
* @param contentjson The json string containing the input container message.
* {
* "nonce": "<random string with optional sorted order>",
* "input": "<hex encoded contract input content>",
* "max_ledger_seqno": <integer>
* "nonce": "<random string with optional sorted order>",
* "max_lcl_seqno": <integer>
* }
* @return 0 on succesful extraction. -1 on failure.
*/
int extract_input_container(std::string &nonce, std::string &input, uint64_t &max_ledger_seqno, std::string_view contentjson)
int extract_input_container(std::string &input, std::string &nonce, uint64_t &max_lcl_seqno, std::string_view contentjson)
{
rapidjson::Document d;
d.Parse(contentjson.data());
@@ -421,13 +387,13 @@ namespace jsonschema::usrmsg
return -1;
}
if (!d.HasMember(FLD_NONCE) || !d.HasMember(FLD_INPUT) || !d.HasMember(FLD_MAX_LED_SEQ))
if (!d.HasMember(FLD_NONCE) || !d.HasMember(FLD_INPUT) || !d.HasMember(FLD_MAX_LCL_SEQ))
{
LOG_DBG << "User input container required fields missing.";
return -1;
}
if (!d[FLD_NONCE].IsString() || !d[FLD_INPUT].IsString() || !d[FLD_MAX_LED_SEQ].IsUint64())
if (!d[FLD_NONCE].IsString() || !d[FLD_INPUT].IsString() || !d[FLD_MAX_LCL_SEQ].IsUint64())
{
LOG_DBG << "User input container invalid field values.";
return -1;
@@ -448,7 +414,7 @@ namespace jsonschema::usrmsg
}
nonce = d[FLD_NONCE].GetString();
max_ledger_seqno = d[FLD_MAX_LED_SEQ].GetUint64();
max_lcl_seqno = d[FLD_MAX_LCL_SEQ].GetUint64();
return 0;
}

View File

@@ -10,15 +10,15 @@ namespace jsonschema::usrmsg
extern const char* const FLD_TYPE;
// Message types
constexpr const char* MSGTYPE_CHALLENGE = "public_challenge";
constexpr const char* MSGTYPE_CHALLENGE_RESP = "challenge_resp";
constexpr const char* MSGTYPE_HANDSHAKE_CHALLENGE = "handshake_challenge";
constexpr const char* MSGTYPE_HANDSHAKE_RESPONSE = "handshake_response";
constexpr const char* MSGTYPE_CONTRACT_READ_REQUEST = "contract_read_request";
constexpr const char* MSGTYPE_CONTRACT_READ_RESPONSE = "contract_read_response";
constexpr const char* MSGTYPE_CONTRACT_INPUT = "contract_input";
constexpr const char* MSGTYPE_CONTRACT_INPUT_STATUS = "contract_input_status";
constexpr const char* MSGTYPE_CONTRACT_OUTPUT = "contract_output";
constexpr const char* MSGTYPE_STAT = "stat";
constexpr const char* MSGTYPE_STAT_RESP = "stat_resp";
constexpr const char* MSGTYPE_REQUEST_STATUS_RESULT = "request_status_result";
constexpr const char* MSGTYPE_STAT_RESPONSE = "stat_response";
constexpr const char* MSGTYPE_UNKNOWN = "unknown";
constexpr const char *STATUS_ACCEPTED = "accepted";
@@ -34,9 +34,7 @@ void create_user_challenge(std::string &msg, std::string &challengehex);
void create_status_response(std::string &msg);
void create_request_status_result(std::string &msg, std::string_view status, std::string_view reason, std::string_view origin_type, std::string_view origin_extra_data);
std::string origin_data_for_contract_input(std::string_view sig);
void create_contract_input_status(std::string &msg, std::string_view status, std::string_view reason, std::string_view input_sig);
void create_contract_read_response_container(std::string &msg, std::string_view content);
@@ -46,9 +44,9 @@ int verify_user_challenge_response(std::string &extracted_pubkeyhex, std::string
int extract_read_request(std::string &extracted_content, const rapidjson::Document &d);
int extract_signed_input_container(std::string &extracted_content, std::string &extracted_sig, const rapidjson::Document &d);
int extract_signed_input_container(std::string &extracted_input_container, std::string &extracted_sig, const rapidjson::Document &d);
int extract_input_container(std::string &nonce, std::string &input, uint64_t &max_ledger_seqno, std::string_view contentjson);
int extract_input_container(std::string &input, std::string &nonce, uint64_t &max_lcl_seqno, std::string_view contentjson);
int parse_user_message(rapidjson::Document &d, std::string_view message);

View File

@@ -147,7 +147,7 @@ namespace usr
}
else
{
send_request_status_result(user.session, jusrmsg::STATUS_REJECTED, jusrmsg::REASON_BAD_MSG_FORMAT, msg_type, "");
send_input_status(user.session, jusrmsg::STATUS_REJECTED, jusrmsg::REASON_BAD_MSG_FORMAT, "");
return -1;
}
}
@@ -155,21 +155,21 @@ namespace usr
{
// Message is a contract input message.
std::string contentjson;
std::string input_container_json;
std::string sig;
if (jusrmsg::extract_signed_input_container(contentjson, sig, d) == 0)
if (jusrmsg::extract_signed_input_container(input_container_json, sig, d) == 0)
{
std::lock_guard<std::mutex> lock(ctx.users_mutex);
//Add to the submitted input list.
user.submitted_inputs.push_back(user_submitted_message(
std::move(contentjson),
std::move(input_container_json),
std::move(sig)));
return 0;
}
else
{
send_request_status_result(user.session, jusrmsg::STATUS_REJECTED, jusrmsg::REASON_BAD_SIG, msg_type, jusrmsg::origin_data_for_contract_input(sig));
send_input_status(user.session, jusrmsg::STATUS_REJECTED, jusrmsg::REASON_BAD_SIG, sig);
return -1;
}
}
@@ -183,25 +183,25 @@ namespace usr
else
{
LOG_DBG << "Invalid user message type: " << msg_type;
send_request_status_result(user.session, jusrmsg::STATUS_REJECTED, jusrmsg::REASON_INVALID_MSG_TYPE, msg_type, "");
send_input_status(user.session, jusrmsg::STATUS_REJECTED, jusrmsg::REASON_INVALID_MSG_TYPE, "");
return -1;
}
}
else
{
// Bad message.
send_request_status_result(user.session, jusrmsg::STATUS_REJECTED, jusrmsg::REASON_BAD_MSG_FORMAT, msg_type, "");
send_input_status(user.session, jusrmsg::STATUS_REJECTED, jusrmsg::REASON_BAD_MSG_FORMAT, "");
return -1;
}
}
/**
* Send the specified status result via the provided session.
* Send the specified contract input status result via the provided session.
*/
void send_request_status_result(const comm::comm_session &session, std::string_view status, std::string_view reason, std::string_view origin_type, std::string_view origin_extra_data)
void send_input_status(const comm::comm_session &session, std::string_view status, std::string_view reason, std::string_view input_sig)
{
std::string msg;
jusrmsg::create_request_status_result(msg, status, reason, origin_type, origin_extra_data);
jusrmsg::create_contract_input_status(msg, status, reason, input_sig);
session.send(msg);
}

View File

@@ -72,7 +72,7 @@ namespace usr
int handle_user_message(connected_user &user, std::string_view message);
void send_request_status_result(const comm::comm_session &session, std::string_view status, std::string_view reason, std::string_view origin_type, std::string_view origin_extra_data);
void send_input_status(const comm::comm_session &session, std::string_view status, std::string_view reason, std::string_view input_sig);
int add_user(const comm::comm_session &session, const std::string &pubkey);