1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-17 05:03:07 +00:00

feat(legacy): Implement AuthorizeCoinJoin.

This commit is contained in:
Andrew Kozlik 2022-12-22 13:27:21 +01:00 committed by matejcik
parent b4d681ea1c
commit 2d18cad676
8 changed files with 265 additions and 13 deletions

View File

@ -128,6 +128,8 @@ typedef struct {
uint32_t last_use;
uint8_t seed[64];
secbool seedCached;
MessageType authorization_type;
AuthorizeCoinJoin coinjoin_authorization;
} Session;
static void session_clearCache(Session *session);
@ -429,6 +431,9 @@ void session_clearCache(Session *session) {
memzero(session->id, sizeof(session->id));
memzero(session->seed, sizeof(session->seed));
session->seedCached = false;
session->authorization_type = 0;
memzero(&session->coinjoin_authorization,
sizeof(session->coinjoin_authorization));
}
void config_lockDevice(void) {
@ -641,6 +646,50 @@ const uint8_t *config_getSeed(void) {
return NULL;
}
bool config_setCoinJoinAuthorization(const AuthorizeCoinJoin *authorization) {
if (activeSessionCache == NULL) {
fsm_sendFailure(FailureType_Failure_InvalidSession, "Invalid session");
return false;
}
if (authorization != NULL) {
activeSessionCache->authorization_type =
MessageType_MessageType_AuthorizeCoinJoin;
memcpy(&activeSessionCache->coinjoin_authorization, authorization,
sizeof(AuthorizeCoinJoin));
} else {
activeSessionCache->authorization_type = 0;
memzero(&activeSessionCache->coinjoin_authorization,
sizeof(AuthorizeCoinJoin));
}
return true;
}
MessageType config_getAuthorizationType(void) {
if (activeSessionCache == NULL) {
return 0;
}
return activeSessionCache->authorization_type;
}
const AuthorizeCoinJoin *config_getCoinJoinAuthorization(void) {
if (activeSessionCache == NULL) {
fsm_sendFailure(FailureType_Failure_InvalidSession, "Invalid session");
return NULL;
}
if (activeSessionCache->authorization_type !=
MessageType_MessageType_AuthorizeCoinJoin) {
fsm_sendFailure(FailureType_Failure_InvalidSession,
"Coinjoin not authorized");
return NULL;
}
return &activeSessionCache->coinjoin_authorization;
}
static bool config_loadNode(const StorageHDNode *node, const char *curve,
HDNode *out) {
return hdnode_from_xprv(node->depth, node->child_num, node->chain_code.bytes,

View File

@ -21,8 +21,10 @@
#define __CONFIG_H__
#include "bip32.h"
#include "messages-bitcoin.pb.h"
#include "messages-common.pb.h"
#include "messages-management.pb.h"
#include "messages.pb.h"
#define STORAGE_FIELD(TYPE, NAME) \
bool has_##NAME; \
@ -102,6 +104,10 @@ void config_loadDevice(const LoadDevice *msg);
const uint8_t *config_getSeed(void);
bool config_setCoinJoinAuthorization(const AuthorizeCoinJoin *authorization);
MessageType config_getAuthorizationType(void);
const AuthorizeCoinJoin *config_getCoinJoinAuthorization(void);
bool config_getU2FRoot(HDNode *node);
bool config_getRootNode(HDNode *node, const char *curve);

View File

@ -29,6 +29,9 @@
#include "messages-nem.pb.h"
#include "messages-stellar.pb.h"
// CoinJoin fee rate multiplier.
#define FEE_RATE_DECIMALS (1000000)
// message functions
void fsm_sendSuccess(const char *text);
@ -82,6 +85,8 @@ void fsm_msgSignMessage(const SignMessage *msg);
void fsm_msgVerifyMessage(const VerifyMessage *msg);
void fsm_msgGetOwnershipId(const GetOwnershipId *msg);
void fsm_msgGetOwnershipProof(const GetOwnershipProof *msg);
void fsm_msgAuthorizeCoinJoin(const AuthorizeCoinJoin *msg);
void fsm_msgCancelAuthorization(const CancelAuthorization *msg);
// crypto
void fsm_msgCipherKeyValue(const CipherKeyValue *msg);

View File

@ -557,3 +557,147 @@ void fsm_msgGetOwnershipProof(const GetOwnershipProof *msg) {
msg_write(MessageType_MessageType_OwnershipProof, resp);
layoutHome();
}
void fsm_msgAuthorizeCoinJoin(const AuthorizeCoinJoin *msg) {
CHECK_INITIALIZED
CHECK_PIN
const size_t MAX_COORDINATOR_LEN = 36;
const uint64_t MAX_ROUNDS = 500;
const uint64_t MAX_COORDINATOR_FEE_RATE = 5 * FEE_RATE_DECIMALS; // 5 %
const CoinInfo *coin = fsm_getCoin(msg->has_coin_name, msg->coin_name);
if (!coin) {
return;
}
if (strnlen(msg->coordinator, sizeof(msg->coordinator)) >
MAX_COORDINATOR_LEN) {
fsm_sendFailure(FailureType_Failure_DataError,
_("Invalid coordinator name."));
layoutHome();
return;
}
for (size_t i = 0; msg->coordinator[i] != '\0'; ++i) {
if (msg->coordinator[i] < 32 || msg->coordinator[i] > 126) {
fsm_sendFailure(FailureType_Failure_DataError,
_("Invalid coordinator name."));
layoutHome();
return;
}
}
if (msg->max_rounds < 1) {
fsm_sendFailure(FailureType_Failure_DataError,
_("Invalid number of rounds."));
layoutHome();
return;
}
bool safety_checks_is_strict =
(config_getSafetyCheckLevel() == SafetyCheckLevel_Strict);
if (msg->max_rounds > MAX_ROUNDS && safety_checks_is_strict) {
fsm_sendFailure(FailureType_Failure_DataError,
_("The number of rounds is unexpectedly large."));
layoutHome();
return;
}
if (msg->max_coordinator_fee_rate > MAX_COORDINATOR_FEE_RATE &&
safety_checks_is_strict) {
fsm_sendFailure(FailureType_Failure_DataError,
_("The coordination fee rate is unexpectedly large."));
layoutHome();
return;
}
if (msg->max_fee_per_kvbyte > 10 * coin->maxfee_kb &&
safety_checks_is_strict) {
fsm_sendFailure(FailureType_Failure_DataError,
_("The fee per vbyte is unexpectedly large."));
layoutHome();
return;
}
if (msg->address_n_count == 0) {
fsm_sendFailure(FailureType_Failure_DataError,
_("Empty path not allowed."));
layoutHome();
return;
}
if (msg->address_n[0] != PATH_SLIP25_PURPOSE && safety_checks_is_strict) {
fsm_sendFailure(FailureType_Failure_DataError, _("Forbidden key path."));
layoutHome();
return;
}
layoutAuthorizeCoinJoin(coin, msg->max_rounds, msg->max_fee_per_kvbyte);
if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
layoutHome();
return;
}
bool path_warning_shown = false;
if (msg->address_n[0] != PATH_SLIP25_PURPOSE) {
if (!fsm_layoutPathWarning()) {
layoutHome();
return;
}
path_warning_shown = true;
}
// AuthorizeCoinJoin contains only the path prefix without change and index.
if ((size_t)(msg->address_n_count + 2) >
sizeof(msg->address_n) / sizeof(msg->address_n[0])) {
fsm_sendFailure(FailureType_Failure_DataError, _("Forbidden key path."));
layoutHome();
return;
}
if (!fsm_checkCoinPath(coin, msg->script_type, msg->address_n_count + 2,
msg->address_n, false, !path_warning_shown)) {
layoutHome();
return;
}
if (msg->max_fee_per_kvbyte > coin->maxfee_kb) {
layoutFeeRateOverThreshold(coin, msg->max_fee_per_kvbyte);
if (!protectButton(ButtonRequestType_ButtonRequest_FeeOverThreshold,
false)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
layoutHome();
return;
}
}
// Cache the seed.
if (config_getSeed() == NULL) {
layoutHome();
return;
}
if (!config_setCoinJoinAuthorization(msg)) {
layoutHome();
return;
}
fsm_sendSuccess(_("Coinjoin authorized"));
layoutHome();
}
void fsm_msgCancelAuthorization(const CancelAuthorization *msg) {
(void)msg;
if (!config_setCoinJoinAuthorization(NULL)) {
layoutHome();
return;
}
fsm_sendSuccess(_("Authorization cancelled"));
layoutHome();
}

View File

@ -510,9 +510,10 @@ static uint64_t div_round(uint64_t numer, uint64_t denom) {
return numer / denom + (2 * (numer % denom) >= denom);
}
static bool formatFeeRate(uint64_t fee, uint64_t tx_weight, char *output,
size_t output_length, bool segwit) {
// Convert transaction weight to virtual transaction size, which is is defined
static bool formatComputedFeeRate(uint64_t fee, uint64_t tx_weight,
char *output, size_t output_length,
bool segwit, bool parentheses) {
// Convert transaction weight to virtual transaction size, which is defined
// as tx_weight / 4 rounded up to the next integer.
// https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#transaction-size-calculations
uint64_t tx_size = (tx_weight + 3) / 4;
@ -522,9 +523,27 @@ static bool formatFeeRate(uint64_t fee, uint64_t tx_weight, char *output,
// two decimal digits.
uint64_t fee_rate_multiplied = div_round(100 * fee, tx_size);
return bn_format_amount(fee_rate_multiplied, "(",
segwit ? " sat/vB)" : " sat/B)", 2, output,
output_length) != 0;
size_t length =
bn_format_amount(fee_rate_multiplied, parentheses ? "(" : NULL,
segwit ? " sat/vB" : " sat/B", 2, output, output_length);
if (length == 0) {
return false;
}
if (parentheses) {
if (length + 2 > output_length) {
return false;
}
output[length] = ')';
output[length + 1] = '\0';
}
return true;
}
static bool formatFeeRate(uint64_t fee_per_kvbyte, char *output,
size_t output_length, bool segwit) {
return formatComputedFeeRate(fee_per_kvbyte, 4000, output, output_length,
segwit, false);
}
void layoutConfirmTx(const CoinInfo *coin, AmountUnit amount_unit,
@ -544,8 +563,8 @@ void layoutConfirmTx(const CoinInfo *coin, AmountUnit amount_unit,
bool show_fee_rate = total_in >= total_out;
if (show_fee_rate) {
formatFeeRate(total_in - total_out, tx_weight, str_fee_rate,
sizeof(str_fee_rate), coin->has_segwit);
formatComputedFeeRate(total_in - total_out, tx_weight, str_fee_rate,
sizeof(str_fee_rate), coin->has_segwit, true);
}
layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL,
@ -621,8 +640,8 @@ void layoutConfirmModifyFee(const CoinInfo *coin, AmountUnit amount_unit,
char str_fee_rate[32] = {0};
formatFeeRate(fee_new, tx_weight, str_fee_rate, sizeof(str_fee_rate),
coin->has_segwit);
formatComputedFeeRate(fee_new, tx_weight, str_fee_rate, sizeof(str_fee_rate),
coin->has_segwit, true);
layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL,
question, str_fee_change, _("Transaction fee:"),
@ -638,6 +657,15 @@ void layoutFeeOverThreshold(const CoinInfo *coin, AmountUnit amount_unit,
_("Send anyway?"), NULL);
}
void layoutFeeRateOverThreshold(const CoinInfo *coin, uint32_t fee_per_kvbyte) {
char str_fee_rate[32] = {0};
formatFeeRate(fee_per_kvbyte, str_fee_rate, sizeof(str_fee_rate),
coin->has_segwit);
layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL,
_("Fee rate"), str_fee_rate, _("is unexpectedly high."),
NULL, _("Proceed anyway?"), NULL);
}
void layoutChangeCountOverThreshold(uint32_t change_count) {
char str_change[21] = {0};
snprintf(str_change, sizeof(str_change), "There are %" PRIu32, change_count);
@ -679,6 +707,20 @@ void layoutConfirmNondefaultLockTime(uint32_t lock_time,
}
}
void layoutAuthorizeCoinJoin(const CoinInfo *coin, uint64_t max_rounds,
uint32_t max_fee_per_kvbyte) {
char str_max_rounds[32] = {0};
char str_fee_rate[32] = {0};
bn_format_amount(max_rounds, NULL, NULL, 0, str_max_rounds,
sizeof(str_max_rounds));
formatFeeRate(max_fee_per_kvbyte, str_fee_rate, sizeof(str_fee_rate),
coin->has_segwit);
layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"),
_("Authorize coinjoin"), _("Maximum rounds:"),
str_max_rounds, _("Maximum mining fee:"), str_fee_rate,
NULL, NULL);
}
void layoutVerifyAddress(const CoinInfo *coin, const char *address) {
render_address_dialog(coin, address, _("Confirm address?"),
_("Message signed by:"), 0);

View File

@ -69,10 +69,13 @@ void layoutConfirmModifyFee(const CoinInfo *coin, AmountUnit amount_unit,
uint64_t tx_weight);
void layoutFeeOverThreshold(const CoinInfo *coin, AmountUnit amount_unit,
uint64_t fee);
void layoutFeeRateOverThreshold(const CoinInfo *coin, uint32_t fee_per_kvbyte);
void layoutChangeCountOverThreshold(uint32_t change_count);
void layoutConfirmUnverifiedExternalInputs(void);
void layoutConfirmNondefaultLockTime(uint32_t lock_time,
bool lock_time_disabled);
void layoutAuthorizeCoinJoin(const CoinInfo *coin, uint64_t max_rounds,
uint32_t max_fee_per_kvbyte);
void layoutVerifyAddress(const CoinInfo *coin, const char *address);
void layoutCipherKeyValue(bool encrypt, const char *key);
void layoutEncryptMessage(const uint8_t *msg, uint32_t len, bool signing);

View File

@ -4,8 +4,7 @@ endif
SKIPPED_MESSAGES := Binance Cardano DebugMonero Eos Monero Ontology Ripple SdProtect Tezos WebAuthn \
DebugLinkRecordScreen DebugLinkEraseSdCard DebugLinkWatchLayout \
AuthorizeCoinJoin DoPreauthorized \
CancelAuthorization DebugLinkLayout GetNonce SetBusy UnlockPath \
DoPreauthorized DebugLinkLayout GetNonce SetBusy UnlockPath \
TxAckInput TxAckOutput TxAckPrev TxAckPaymentRequest \
EthereumSignTypedData EthereumTypedDataStructRequest EthereumTypedDataStructAck \
EthereumTypedDataValueRequest EthereumTypedDataValueAck

View File

@ -92,8 +92,12 @@ GetOwnershipProof.commitment_data max_size:70
OwnershipProof.ownership_proof max_size:147
OwnershipProof.signature max_size:71
AuthorizeCoinJoin.coordinator max_size:37
AuthorizeCoinJoin.address_n max_count:8
AuthorizeCoinJoin.coin_name max_size:21
# Unused messages.
AuthorizeCoinJoin skip_message:true
TxAckPaymentRequest skip_message:true
PaymentRequestMemo skip_message:true
CoinJoinRequest skip_message:true