diff --git a/docs/administration.md b/docs/administration.md index c952549..3a7111f 100644 --- a/docs/administration.md +++ b/docs/administration.md @@ -68,6 +68,7 @@ are: * [**modify_account_status**](#modify_account_status): `{"status": "active"|"hidden"|"inactive", "addresses":[...]}` * [**reject_requests**](#reject_requests): `{"type": "import"|"create", "addresses":[...]}` * [**rescan**](#rescan): `{"height":..., "addresses":[...]}` + * [**validate**](#validate): `{"spend_public_hex":..., "view_public_hex":..., "view_key_hex":...}` * [**webhook_add**](#webhook_add): `{"type":"tx-confirmation", "address":"...", "url":"...", ...}` with optional fields: * **token**: A string to be returned when the webhook is triggered * **payment_id**: 16 hex characters representing a unique identifier for a transaction @@ -143,6 +144,46 @@ information from that endpoint on how to use this one. This tells the scanner to rescan specific account(s) from the specified height. +### validate +This takes the spend_public, view_public, and view key all as hex, and then +does basic validation for the caller: (1) that each value is 64 hex-ascii +characters, (2) that the public keys are valid ed25519 points, and that (3) +the view_key matches the view_public. + +The return value is the `address` on success, and `error` object on +validation failure: + +#### Example Request +```json +{ + "params": { + "view_public_hex": "3e77c1ee5a396cd6c3f68ce343882b2a09317649d6501078911fb17a1adac0b6", + "spend_public_hex": "7028d918af2cc4f1cfa499cca2ef014e57124982b99004e9630caf0b25c13954", + "view_key_hex": "80..." + }, + "auth": "f50922f5fcd186eaa4bd7070b8072b66fea4fd736f06bd82df702e2314187d09" +} +``` + +#### Example Failure Return +```json +{ + "error": { + "field": "view_public_hex", + "details": "Invalid public key format" + } +} +``` +HTTP error codes are still returned if the JSON itself is invalid. + +#### Example Success Return +```json +{ + "address": "9wRAu3giCtKhSsVnkZJ7LLE6zqzrmMKpPg39S8aoC7T6F6GobeDpz8TcvVfTQT3ucW82oTYKG8v3ZMAeh8SZVXWwMdvwZew" +} +``` + + ### webhook_add This is used to track events happening in the database: (1) a new payment to an optional payment_id, or (2) a new account creation. This endpoint always diff --git a/src/rest_server.cpp b/src/rest_server.cpp index bdbb651..814c955 100644 --- a/src/rest_server.cpp +++ b/src/rest_server.cpp @@ -777,6 +777,7 @@ namespace lws {"/modify_account_status", call_admin, 50 * 1024}, {"/reject_requests", call_admin, 50 * 1024}, {"/rescan", call_admin, 50 * 1024}, + {"/validate", call_admin, 50 * 1024}, {"/webhook_add", call_admin, 50 * 1024}, {"/webhook_delete", call_admin, 50 * 1024}, {"/webhook_delete_uuid", call_admin,50 * 1024}, diff --git a/src/rpc/admin.cpp b/src/rpc/admin.cpp index 87c0e7f..98a986c 100644 --- a/src/rpc/admin.cpp +++ b/src/rpc/admin.cpp @@ -167,6 +167,10 @@ namespace lws { namespace rpc { read_addresses(source, self, WIRE_FIELD(height)); } + void read_bytes(wire::reader& source, validate_req& self) + { + wire::object(source, WIRE_FIELD(spend_public_hex), WIRE_FIELD(view_public_hex), WIRE_FIELD(view_key_hex)); + } void read_bytes(wire::reader& source, webhook_add_req& self) { boost::optional address; @@ -237,6 +241,67 @@ namespace lws { namespace rpc return write_addresses(dest, disk.rescan(req.height, epee::to_span(req.addresses))); } + namespace + { + struct validate_error + { + std::string field; + std::string details; + }; + + void write_bytes(wire::writer& dest, const validate_error& self) + { + wire::object(dest, WIRE_FIELD(field), WIRE_FIELD(details)); + } + + expect output_error(wire::writer& dest, std::string field, std::string details) + { + wire::object(dest, wire::field("error", validate_error{std::move(field), std::move(details)})); + return success(); + } + + template + bool convert_key(wire::writer& dest, T& out, const boost::string_ref in, const boost::string_ref field) + { + if (in.size() != sizeof(out) * 2) + { + output_error(dest, std::string{field}, "Expected " + std::to_string(sizeof(out) * 2) + " characters"); + return false; + } + if (!epee::from_hex::to_buffer(epee::as_mut_byte_span(out), in)) + { + output_error(dest, std::string{field}, "Invalid hex"); + return false; + } + return true; + } + } + + expect validate_::operator()(wire::writer& dest, const db::storage&, const request& req) const + { + db::account_address address{}; + crypto::secret_key view_key{}; + + if (!convert_key(dest, address.spend_public, req.spend_public_hex, "spend_public_hex")) + return success(); // error is delivered in JSON as opposed to HTTP codes + if (!convert_key(dest, address.view_public, req.view_public_hex, "view_public_hex")) + return success(); + if (!convert_key(dest, unwrap(unwrap(view_key)), req.view_key_hex, "view_key_hex")) + return success(); + + if (!crypto::check_key(address.spend_public)) + return output_error(dest, "spend_public_hex", "Invalid public key format"); + if (!crypto::check_key(address.view_public)) + return output_error(dest, "view_public_hex", "Invalid public key format"); + + crypto::public_key test{}; + if (!crypto::secret_key_to_public_key(view_key, test) || test != address.view_public) + return output_error(dest, "view_key_hex", "view_key and view_public do not match"); + + wire::object(dest, wire::field("address", db::address_string(address))); + return success(); + } + expect webhook_add_::operator()(wire::writer& dest, db::storage disk, request&& req) const { switch (req.type) diff --git a/src/rpc/admin.h b/src/rpc/admin.h index 84b0956..7344387 100644 --- a/src/rpc/admin.h +++ b/src/rpc/admin.h @@ -70,6 +70,14 @@ namespace rpc }; void read_bytes(wire::reader&, rescan_req&); + struct validate_req + { + std::string spend_public_hex; + std::string view_public_hex; + std::string view_key_hex; + }; + void read_bytes(wire::reader&, validate_req&); + struct webhook_add_req { std::string url; @@ -147,6 +155,13 @@ namespace rpc }; constexpr const rescan_ rescan{}; + struct validate_ + { + using request = validate_req; + expect operator()(wire::writer& dest, const db::storage&, const request& req) const; + }; + constexpr const validate_ validate{}; + struct webhook_add_ { using request = webhook_add_req; @@ -177,5 +192,4 @@ namespace rpc { return (*this)(dest, std::move(disk)); } }; constexpr const webhook_list_ webhook_list{}; - }} // lws // rpc