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:
parent
b4d681ea1c
commit
2d18cad676
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user