From 2eff4c03225c6634e194b3bdaee6b77006bae28c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Kov=C3=A1cs?= Date: Wed, 6 Oct 2021 12:01:06 +0200 Subject: [PATCH] add required signers --- src/cardano.h | 1 + src/securityPolicy.c | 20 +++++++ src/securityPolicy.h | 2 + src/signTx.c | 118 +++++++++++++++++++++++++++++++++++++-- src/signTx.h | 5 ++ src/txHashBuilder.c | 73 ++++++++++++++++++++++-- src/txHashBuilder.h | 12 +++- src/txHashBuilder_test.c | 1 + 8 files changed, 222 insertions(+), 10 deletions(-) diff --git a/src/cardano.h b/src/cardano.h index da5e7bf7..4f2e7f11 100644 --- a/src/cardano.h +++ b/src/cardano.h @@ -23,6 +23,7 @@ STATIC_ASSERT(LOVELACE_MAX_SUPPLY < LOVELACE_INVALID, "bad LOVELACE_INVALID"); #define SCRIPT_HASH_LENGTH 28 #define SCRIPT_DATA_HASH_LENGTH 32 #define OUTPUT_DATUM_HASH_LENGTH 32 +#define VKEY_LENGTH 32 #define MINTING_POLICY_ID_SIZE 28 #define ASSET_NAME_SIZE_MAX 32 diff --git a/src/securityPolicy.c b/src/securityPolicy.c index 82a5c544..ffb8b0ff 100644 --- a/src/securityPolicy.c +++ b/src/securityPolicy.c @@ -870,6 +870,26 @@ security_policy_t policyForSignTxCollaterals(const sign_tx_signingmode_t txSigni DENY(); } +security_policy_t policyForSignTxRequiredSigners(const sign_tx_signingmode_t txSigningMode) +{ + switch (txSigningMode) { + case SIGN_TX_SIGNINGMODE_ORDINARY_TX: + case SIGN_TX_SIGNINGMODE_MULTISIG_TX: + SHOW(); + break; + + case SIGN_TX_SIGNINGMODE_POOL_REGISTRATION_OWNER: + case SIGN_TX_SIGNINGMODE_POOL_REGISTRATION_OPERATOR: + DENY(); + break; + + default: + ASSERT(false); + } + + DENY(); +} + security_policy_t policyForSignTxConfirm() { PROMPT(); diff --git a/src/securityPolicy.h b/src/securityPolicy.h index 6ec76ef1..4ddd4b7d 100644 --- a/src/securityPolicy.h +++ b/src/securityPolicy.h @@ -106,6 +106,8 @@ security_policy_t policyForSignTxScriptDataHash(const sign_tx_signingmode_t txSi security_policy_t policyForSignTxCollaterals(const sign_tx_signingmode_t txSigningMode); +security_policy_t policyForSignTxRequiredSigners(const sign_tx_signingmode_t txSigningMode); + security_policy_t policyForSignTxWitness( sign_tx_signingmode_t txSigningMode, const bip44_path_t* pathSpec, diff --git a/src/signTx.c b/src/signTx.c index b2843cae..03d51234 100644 --- a/src/signTx.c +++ b/src/signTx.c @@ -28,6 +28,7 @@ static inline void initTxBodyCtx() BODY_CTX->currentCertificate = 0; BODY_CTX->currentWithdrawal = 0; BODY_CTX->currentCollateral = 0; + BODY_CTX->currentRequiredSigners = 0; BODY_CTX->feeReceived = false; BODY_CTX->ttlReceived = false; BODY_CTX->validityIntervalStartReceived = false; @@ -92,7 +93,8 @@ static inline void advanceStage() ctx->includeValidityIntervalStart, ctx->includeMint, ctx->includeScriptDataHash, - ctx->numCollaterals + ctx->numCollaterals, + ctx->numRequiredSigners ); txHashBuilder_enterInputs(&BODY_CTX->txHashBuilder); } @@ -217,6 +219,16 @@ static inline void advanceStage() case SIGN_STAGE_BODY_COLLATERALS: ASSERT(BODY_CTX->currentCollateral == ctx->numCollaterals); + ctx->stage = SIGN_STAGE_BODY_REQUIRED_SIGNERS; + if (ctx->numRequiredSigners > 0) { + txHashBuilder_enterRequiredSigners(&BODY_CTX->txHashBuilder); + break; + } + + // intentional fallthrough + + case SIGN_STAGE_BODY_REQUIRED_SIGNERS: + ASSERT(BODY_CTX->currentRequiredSigners == ctx->numRequiredSigners); txHashBuilder_addNetworkId(&BODY_CTX->txHashBuilder, ctx->commonTxData.networkId); ctx->stage = SIGN_STAGE_CONFIRM; break; @@ -421,6 +433,7 @@ static void signTx_handleInitAPDU(uint8_t p2, uint8_t* wireDataBuffer, size_t wi uint8_t numWithdrawals[4]; uint8_t numWitnesses[4]; uint8_t numCollaterals[4]; + uint8_t numRequiredSigners[4]; }* wireHeader = (void*) wireDataBuffer; VALIDATE(SIZEOF(*wireHeader) == wireDataSize, ERR_INVALID_DATA); @@ -469,22 +482,25 @@ static void signTx_handleInitAPDU(uint8_t p2, uint8_t* wireDataBuffer, size_t wi ASSERT_TYPE(ctx->numWithdrawals, uint16_t); ASSERT_TYPE(ctx->numWitnesses, uint16_t); ASSERT_TYPE(ctx->numCollaterals, uint16_t); + ASSERT_TYPE(ctx->numRequiredSigners, uint16_t); ctx->numInputs = (uint16_t) u4be_read(wireHeader->numInputs); ctx->numOutputs = (uint16_t) u4be_read(wireHeader->numOutputs); ctx->numCertificates = (uint16_t) u4be_read(wireHeader->numCertificates); ctx->numWithdrawals = (uint16_t) u4be_read(wireHeader->numWithdrawals); ctx->numWitnesses = (uint16_t) u4be_read(wireHeader->numWitnesses); ctx->numCollaterals = (uint16_t) u4be_read(wireHeader->numCollaterals); + ctx->numRequiredSigners = (uint16_t) u4be_read(wireHeader->numRequiredSigners); TRACE( - "num inputs, outputs, certificates, withdrawals, witnesses, collaterals: %d %d %d %d %d %d", - ctx->numInputs, ctx->numOutputs, ctx->numCertificates, ctx->numWithdrawals, ctx->numWitnesses, ctx->numCollaterals + "num inputs, outputs, certificates, withdrawals, witnesses, collaterals, required signers: %d %d %d %d %d %d %d", + ctx->numInputs, ctx->numOutputs, ctx->numCertificates, ctx->numWithdrawals, ctx->numWitnesses, ctx->numCollaterals, ctx->numRequiredSigners ); VALIDATE(ctx->numInputs <= SIGN_MAX_INPUTS, ERR_INVALID_DATA); VALIDATE(ctx->numOutputs <= SIGN_MAX_OUTPUTS, ERR_INVALID_DATA); VALIDATE(ctx->numCertificates <= SIGN_MAX_CERTIFICATES, ERR_INVALID_DATA); VALIDATE(ctx->numWithdrawals <= SIGN_MAX_REWARD_WITHDRAWALS, ERR_INVALID_DATA); VALIDATE(ctx->numCollaterals <= SIGN_MAX_COLLATERALS, ERR_INVALID_DATA); + VALIDATE(ctx->numRequiredSigners <= SIGN_MAX_REQUIRED_SIGNERS, ERR_INVALID_DATA); // Current code design assumes at least one input. // If this is to be relaxed, stage switching logic needs to be re-visited. @@ -1754,6 +1770,97 @@ static void signTx_handleCollateralAPDU(uint8_t p2, uint8_t* wireDataBuffer, siz signTx_handleCollateral_ui_runStep(); } +// ========================= REQUIRED SIGNERS =========================== + +enum { + HANDLE_REQUIRED_SIGNERS_STEP_DISPLAY = 1400, + HANDLE_REQUIRED_SIGNERS_STEP_RESPOND, + HANDLE_REQUIRED_SIGNERS_STEP_INVALID, +}; + +static void signTx_handleRequiredSigner_ui_runStep() +{ + TRACE("UI step %d", ctx->ui_step); + ui_callback_fn_t* this_fn = signTx_handleRequiredSigner_ui_runStep; + + UI_STEP_BEGIN(ctx->ui_step, this_fn); + + UI_STEP(HANDLE_REQUIRED_SIGNERS_STEP_DISPLAY) { + ui_displayHexBufferScreen("Required signer", BODY_CTX->stageData.requiredSigner, SIZEOF(BODY_CTX->stageData.requiredSigner), this_fn); + } + + UI_STEP(HANDLE_REQUIRED_SIGNERS_STEP_RESPOND) { + respondSuccessEmptyMsg(); + + // Advance stage to the next input + ASSERT(BODY_CTX->currentRequiredSigners < ctx->numRequiredSigners); + BODY_CTX->currentRequiredSigners++; + + if (BODY_CTX->currentRequiredSigners == ctx->numRequiredSigners) { + advanceStage(); + } + } + UI_STEP_END(HANDLE_REQUIRED_SIGNERS_STEP_INVALID); +} + +__noinline_due_to_stack__ +static void signTx_handleRequiredSignerAPDU(uint8_t p2, uint8_t* wireDataBuffer, size_t wireDataSize) +{ + TRACE_STACK_USAGE(); + { + // sanity checks + CHECK_STAGE(SIGN_STAGE_BODY_REQUIRED_SIGNERS); + ASSERT(BODY_CTX->currentRequiredSigners < ctx->numRequiredSigners); + + VALIDATE(p2 == P2_UNUSED, ERR_INVALID_REQUEST_PARAMETERS); + ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); + } + + { + TRACE_BUFFER(wireDataBuffer, wireDataSize); + + read_view_t view = make_read_view(wireDataBuffer, wireDataBuffer + wireDataSize); + STATIC_ASSERT(SIZEOF(BODY_CTX->stageData.requiredSigner) == VKEY_LENGTH, "wrong vkey length"); + view_copyWireToBuffer(BODY_CTX->stageData.requiredSigner, &view, VKEY_LENGTH); + VALIDATE(view_remainingSize(&view) == 0, ERR_INVALID_DATA); + } + + { + // add to tx + TRACE("Adding required signer to tx hash"); + if (BODY_CTX->stageData.requiredSigner.type == REQUIRED_SIGNER_WITH_PATH) { + uint8_t keyHash[ADDRESS_KEY_HASH_LENGTH]; + bip44_pathToKeyHash(&BODY_CTX->stageData.requiredSigner.keyPath, keyHash, SIZEOF(keyHash)); + txHashBuilder_addRequiredSigner( + &BODY_CTX->txHashBuilder, + keyHash, SIZEOF(keyHash) + ); + } else { + txHashBuilder_addRequiredSigner( + &BODY_CTX->txHashBuilder, + BODY_CTX->stageData.requiredSigner.keyHash, SIZEOF(BODY_CTX->stageData.requiredSigner.keyHash) + ); + } + } + + security_policy_t policy = policyForSignTxRequiredSigners(ctx->commonTxData.txSigningMode); + TRACE("Policy: %d", (int) policy); + ENSURE_NOT_DENIED(policy); + + { + // select UI steps + switch (policy) { +# define CASE(POLICY, UI_STEP) case POLICY: {ctx->ui_step=UI_STEP; break;} + CASE(POLICY_SHOW_BEFORE_RESPONSE, HANDLE_REQUIRED_SIGNERS_STEP_DISPLAY); + CASE(POLICY_ALLOW_WITHOUT_PROMPT, HANDLE_REQUIRED_SIGNERS_STEP_RESPOND); +# undef CASE + default: + THROW(ERR_NOT_IMPLEMENTED); + } + } + signTx_handleRequiredSigner_ui_runStep(); +} + // ============================== CONFIRM ============================== enum { @@ -1977,6 +2084,7 @@ static subhandler_fn_t* lookup_subhandler(uint8_t p1) CASE(0x0b, signTx_handleMintAPDU); CASE(0x0c, signTx_handleScriptDataHashAPDU); CASE(0x0d, signTx_handleCollateralAPDU); + CASE(0x0e, signTx_handleRequiredSignerAPDU); CASE(0x0a, signTx_handleConfirmAPDU); CASE(0x0f, signTx_handleWitnessAPDU); DEFAULT(NULL) @@ -2017,7 +2125,8 @@ void signTx_handleAPDU( case SIGN_STAGE_BODY_MINT: case SIGN_STAGE_BODY_MINT_SUBMACHINE: case SIGN_STAGE_BODY_SCRIPT_DATA_HASH: - case SIGN_STAGE_BODY_COLLATERALS: { + case SIGN_STAGE_BODY_COLLATERALS: + case SIGN_STAGE_BODY_REQUIRED_SIGNERS: { explicit_bzero(&BODY_CTX->stageData, SIZEOF(BODY_CTX->stageData)); break; } @@ -2063,6 +2172,7 @@ ins_sign_tx_body_context_t* accessBodyContext() case SIGN_STAGE_BODY_MINT_SUBMACHINE: case SIGN_STAGE_BODY_SCRIPT_DATA_HASH: case SIGN_STAGE_BODY_COLLATERALS: + case SIGN_STAGE_BODY_REQUIRED_SIGNERS: case SIGN_STAGE_CONFIRM: return &(ctx->txPartCtx.body_ctx); diff --git a/src/signTx.h b/src/signTx.h index 873df41a..bd6c19a4 100644 --- a/src/signTx.h +++ b/src/signTx.h @@ -39,6 +39,7 @@ typedef enum { SIGN_STAGE_BODY_MINT_SUBMACHINE = 36, SIGN_STAGE_BODY_SCRIPT_DATA_HASH = 37, SIGN_STAGE_BODY_COLLATERALS = 38, + SIGN_STAGE_BODY_REQUIRED_SIGNERS = 39, SIGN_STAGE_CONFIRM = 40, SIGN_STAGE_WITNESSES = 41, } sign_tx_stage_t; @@ -49,6 +50,7 @@ enum { SIGN_MAX_CERTIFICATES = UINT16_MAX, SIGN_MAX_REWARD_WITHDRAWALS = UINT16_MAX, SIGN_MAX_COLLATERALS = UINT16_MAX, + SIGN_MAX_REQUIRED_SIGNERS = UINT16_MAX, SIGN_MAX_WITNESSES = SIGN_MAX_INPUTS + SIGN_MAX_OUTPUTS + SIGN_MAX_CERTIFICATES + SIGN_MAX_REWARD_WITHDRAWALS, }; @@ -112,6 +114,7 @@ typedef struct { uint16_t currentCertificate; uint16_t currentWithdrawal; uint16_t currentCollateral; + uint16_t currentRequiredSigners; bool feeReceived; bool ttlReceived; @@ -131,6 +134,7 @@ typedef struct { uint64_t validityIntervalStart; uint8_t scriptDataHash[SCRIPT_DATA_HASH_LENGTH]; sign_tx_transaction_input_t collateral; + uint8_t requiredSigner[VKEY_LENGTH]; } stageData; // TODO rename to reflect single-APDU scope union { @@ -162,6 +166,7 @@ typedef struct { bool includeMint; bool includeScriptDataHash; uint16_t numCollaterals; + uint16_t numRequiredSigners; uint16_t numWitnesses; uint8_t auxDataHash[AUX_DATA_HASH_LENGTH]; diff --git a/src/txHashBuilder.c b/src/txHashBuilder.c index 1221865a..2be62ac5 100644 --- a/src/txHashBuilder.c +++ b/src/txHashBuilder.c @@ -66,7 +66,8 @@ void txHashBuilder_init( bool includeValidityIntervalStart, bool includeMint, bool includeScriptDataHash, - uint16_t numCollaterals + uint16_t numCollaterals, + uint16_t numRequiredSigners ) { TRACE("numInputs = %u", numInputs); @@ -116,10 +117,13 @@ void txHashBuilder_init( builder->remainingCollaterals = numCollaterals; if (numCollaterals > 0) numItems++; + builder->remainingRequiredSigners = numRequiredSigners; + if (numRequiredSigners > 0) numItems++; + // network id always included numItems++; - ASSERT((4 <= numItems) && (numItems <= 12)); + ASSERT((4 <= numItems) && (numItems <= 13)); _TRACE("Serializing tx body with %u items", numItems); BUILDER_APPEND_CBOR(CBOR_TYPE_MAP, numItems); @@ -1297,7 +1301,7 @@ void txHashBuilder_addCollateral( } } -void txHashBuilder_assertCanLeaveCollaterals(tx_hash_builder_t* builder) +static void txHashBuilder_assertCanLeaveCollaterals(tx_hash_builder_t* builder) { _TRACE("state = %u", builder->state); @@ -1315,7 +1319,6 @@ void txHashBuilder_assertCanLeaveCollaterals(tx_hash_builder_t* builder) case TX_HASH_BUILDER_IN_TTL: case TX_HASH_BUILDER_IN_FEE: txHashBuilder_assertCanLeaveScriptDataHash(builder); - ASSERT(!builder->includeScriptDataHash); break; default: @@ -1323,12 +1326,72 @@ void txHashBuilder_assertCanLeaveCollaterals(tx_hash_builder_t* builder) } } +void txHashBuilder_enterRequiredSigners(tx_hash_builder_t* builder) +{ + _TRACE("state = %d", builder->state); + + txHashBuilder_assertCanLeaveCollaterals(builder); + { + // Enter inputs + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, TX_BODY_KEY_REQUIRED_SIGNERS); + BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, builder->remainingRequiredSigners); + } + builder->state = TX_HASH_BUILDER_IN_REQUIRED_SIGNERS; +} + +void txHashBuilder_addRequiredSigner( + tx_hash_builder_t* builder, + const uint8_t* vkeyBuffer, size_t vkeySize +) +{ + _TRACE("state = %d, remainingRequiredSigners = %u", builder->state, builder->remainingRequiredSigners); + + ASSERT(vkeySize < BUFFER_SIZE_PARANOIA); + ASSERT(builder->state == TX_HASH_BUILDER_IN_REQUIRED_SIGNERS); + ASSERT(builder->remainingRequiredSigners > 0); + builder->remainingRequiredSigners--; + // Array(2)[ + // Bytes[hash], + // Unsigned[index] + // ] + { + ASSERT(vkeySize == VKEY_LENGTH); + BUILDER_APPEND_CBOR(CBOR_TYPE_BYTES, vkeySize); + BUILDER_APPEND_DATA(vkeyBuffer, vkeySize); + } +} + +static void txHashBuilder_assertCanLeaveRequiredSigners(tx_hash_builder_t* builder) +{ + _TRACE("state = %u", builder->state); + + switch (builder->state) { + case TX_HASH_BUILDER_IN_REQUIRED_SIGNERS: + ASSERT(builder->remainingRequiredSigners == 0); + break; + + case TX_HASH_BUILDER_IN_COLLATERALS: + case TX_HASH_BUILDER_IN_SCRIPT_HASH_DATA: + case TX_HASH_BUILDER_IN_MINT: + case TX_HASH_BUILDER_IN_VALIDITY_INTERVAL_START: + case TX_HASH_BUILDER_IN_AUX_DATA: + case TX_HASH_BUILDER_IN_WITHDRAWALS: + case TX_HASH_BUILDER_IN_CERTIFICATES: + case TX_HASH_BUILDER_IN_TTL: + case TX_HASH_BUILDER_IN_FEE: + txHashBuilder_assertCanLeaveCollaterals(builder); + break; + + default: + ASSERT(false); + } +} void txHashBuilder_addNetworkId(tx_hash_builder_t* builder, uint8_t networkId) { _TRACE("state = %d", builder->state); - txHashBuilder_assertCanLeaveCollaterals(builder); + txHashBuilder_assertCanLeaveRequiredSigners(builder); // add network id item into the main tx body map BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, TX_BODY_KEY_NETWORK_ID); diff --git a/src/txHashBuilder.h b/src/txHashBuilder.h index 53d41f20..6be5a71e 100644 --- a/src/txHashBuilder.h +++ b/src/txHashBuilder.h @@ -17,6 +17,7 @@ enum { TX_BODY_KEY_MINT = 9, TX_BODY_KEY_SCRIPT_HASH_DATA = 11, TX_BODY_KEY_COLLATERALS = 13, + TX_BODY_KEY_REQUIRED_SIGNERS = 14, TX_BODY_KEY_NETWORK_ID = 15, }; @@ -58,6 +59,7 @@ typedef enum { TX_HASH_BUILDER_IN_MINT_TOKEN = 1012, TX_HASH_BUILDER_IN_SCRIPT_HASH_DATA = 1100, TX_HASH_BUILDER_IN_COLLATERALS = 1200, + TX_HASH_BUILDER_IN_REQUIRED_SIGNERS = 1300, TX_HASH_BUILDER_IN_NETWORK_ID = 1400, TX_HASH_BUILDER_FINISHED = 1500, } tx_hash_builder_state_t; @@ -68,6 +70,7 @@ typedef struct { uint16_t remainingWithdrawals; uint16_t remainingCertificates; uint16_t remainingCollaterals; + uint16_t remainingRequiredSigners; bool includeTtl; bool includeAuxData; bool includeValidityIntervalStart; @@ -102,7 +105,8 @@ void txHashBuilder_init( bool includeValidityIntervalStart, bool includeMint, bool includeScriptDataHash, - uint16_t numCollaterals + uint16_t numCollaterals, + uint16_t numRequiredSigners ); void txHashBuilder_enterInputs(tx_hash_builder_t* builder); @@ -243,6 +247,12 @@ void txHashBuilder_addCollateral( uint32_t utxoIndex ); +void txHashBuilder_enterRequiredSigners(tx_hash_builder_t* builder); +void txHashBuilder_addRequiredSigner( + tx_hash_builder_t* builder, + const uint8_t* vkeyBuffer, size_t vkeySize +); + void txHashBuilder_addNetworkId(tx_hash_builder_t* builder, uint8_t networkId); void txHashBuilder_finalize( diff --git a/src/txHashBuilder_test.c b/src/txHashBuilder_test.c index acb9680a..6d5ec0bb 100644 --- a/src/txHashBuilder_test.c +++ b/src/txHashBuilder_test.c @@ -372,6 +372,7 @@ void run_txHashBuilder_test() true, // validity interval start true, // mint false, // script hash data + 0, //TODO(KoMa) testing 0 //TODO(KoMa) testing );