LCOV - code coverage report
Current view: top level - powhsm/src - auth_receipt.c (source / functions) Hit Total Coverage
Test: powHSM firmware Lines: 92 104 88.5 %
Date: 2025-07-10 13:49:13 Functions: 6 6 100.0 %

          Line data    Source code
       1             : /**
       2             :  * The MIT License (MIT)
       3             :  *
       4             :  * Copyright (c) 2021 RSK Labs Ltd
       5             :  *
       6             :  * Permission is hereby granted, free of charge, to any person obtaining a copy
       7             :  * of this software and associated documentation files (the "Software"), to
       8             :  * deal in the Software without restriction, including without limitation the
       9             :  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
      10             :  * sell copies of the Software, and to permit persons to whom the Software is
      11             :  * furnished to do so, subject to the following conditions:
      12             :  *
      13             :  * The above copyright notice and this permission notice shall be included in
      14             :  * all copies or substantial portions of the Software.
      15             :  *
      16             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      17             :  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      18             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      19             :  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      20             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      21             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
      22             :  * IN THE SOFTWARE.
      23             :  */
      24             : 
      25             : #include <string.h>
      26             : 
      27             : #include "hal/platform.h"
      28             : #include "hal/exceptions.h"
      29             : 
      30             : #include "auth.h"
      31             : #include "auth_constants.h"
      32             : #include "mem.h"
      33             : #include "memutil.h"
      34             : #include "srlp.h"
      35             : #include "flags.h"
      36             : #include "util.h"
      37             : 
      38             : #include "hal/log.h"
      39             : 
      40             : // -----------------------------------------------------------------------
      41             : // RLP parser callbacks
      42             : // -----------------------------------------------------------------------
      43             : 
      44             : // Valid RSK receipts have the form [field_0, ..., field_5]
      45             : // (six fields in the top-level list).
      46             : //
      47             : // In particular, field_3 (the fourth top level field) has the form
      48             : // [log_0, ..., log_n], with n >= 0. This is the log list.
      49             : //
      50             : // Each log_m with 0 <= m <= n is of the form
      51             : // [address, topics, data] (exactly three elements)
      52             : // with:
      53             : // - address being the emitter address
      54             : // - topics: [topic_0, ..., topic_p] with p >=0 the list of topics
      55             : // - data being the unindexed data associated with the event (not used)
      56             : //
      57             : // In particular, we're interested in finding a log with:
      58             : // - a specific address (hardcoded, predefined) - the bridge address
      59             : // - exactly three topics, topic_0, topic_1 and topic_2, of which:
      60             : //   - topic_0 (aka the event signature) must match a specific
      61             : //     value (harcoded, predefined)
      62             : //   - topic_2 must match the current BTC tx hash
      63             : 
      64             : // RSK receipt constants
      65             : // All indexes and levels are 1-based
      66             : 
      67             : #define LIST_ELEMENTS (6)
      68             : #define LOG_ELEMENTS (3)
      69             : 
      70             : #define TOP_LEVEL (1)
      71             : #define LOG_LEVEL (3)
      72             : #define TOPIC_LEVEL (4)
      73             : 
      74             : #define LOGS_INDEX (4)
      75             : #define EVENT_EMITTER_INDEX (1)
      76             : #define TOPICS_INDEX (2)
      77             : #define TOPIC_SIGNATURE_INDEX (1)
      78             : #define TOPIC_TXHASH_INDEX (3)
      79             : 
      80             : // Flags
      81             : #define IS_INIT (0x01)
      82             : #define IS_VALID_EMITTER (0x02)
      83             : #define IS_VALID_SIGNATURE (0x04)
      84             : #define IS_VALID_TXHASH (0x08)
      85             : #define IS_MATCH (0x10)
      86             : 
      87             : __attribute__((always_inline)) static inline void update_indexes() {
      88         644 :     if (auth.receipt.level > 0)
      89         874 :         auth.receipt.index[auth.receipt.level - 1]++;
      90         924 :     for (uint8_t i = auth.receipt.level;
      91         924 :          i < MIN(auth.receipt.level, RECEIPT_MAX_DEPTH) - 1;
      92           0 :          i++)
      93           0 :         auth.receipt.index[i] = 0;
      94         924 : }
      95             : 
      96         280 : static void list_start(const uint16_t size) {
      97             :     update_indexes();
      98             : 
      99         280 :     ++auth.receipt.level;
     100             : 
     101             :     // About to start parsing a log? => clear the flags and counters
     102         280 :     if (auth.receipt.index[TOP_LEVEL - 1] == LOGS_INDEX &&
     103         230 :         auth.receipt.level == LOG_LEVEL) {
     104          92 :         CLR_FLAG(auth.receipt.flags, IS_VALID_EMITTER);
     105          92 :         CLR_FLAG(auth.receipt.flags, IS_VALID_SIGNATURE);
     106          92 :         CLR_FLAG(auth.receipt.flags, IS_VALID_TXHASH);
     107             :     }
     108             : 
     109             :     // Count total number of bytes remaining for the entire receipt
     110         280 :     if (auth.receipt.level == TOP_LEVEL) {
     111          50 :         auth.receipt.remaining_bytes = size + rlp_list_prefix_size(size);
     112             :     }
     113         280 : }
     114             : 
     115         280 : static void list_end() {
     116             :     // Have we just finished parsing a log? =>
     117             :     // set the match bit only if we found a match
     118         280 :     if (auth.receipt.index[TOP_LEVEL - 1] == LOGS_INDEX &&
     119         230 :         auth.receipt.level == LOG_LEVEL) {
     120             :         // Validate the parsed log had the exact number of elements
     121          92 :         if (auth.receipt.index[LOG_LEVEL - 1] != LOG_ELEMENTS) {
     122           0 :             LOG("[E] Found log with %u elements\n",
     123           0 :                 auth.receipt.index[LOG_LEVEL - 1]);
     124           0 :             THROW(ERR_AUTH_RECEIPT_INVALID);
     125             :         }
     126             : 
     127          92 :         if (HAS_FLAG(auth.receipt.flags, IS_VALID_EMITTER) &&
     128          88 :             HAS_FLAG(auth.receipt.flags, IS_VALID_SIGNATURE) &&
     129          54 :             HAS_FLAG(auth.receipt.flags, IS_VALID_TXHASH)) {
     130          42 :             SET_FLAG(auth.receipt.flags, IS_MATCH);
     131             :         }
     132             :     }
     133             : 
     134             :     // Validate the parsed receipt had the exact number of
     135             :     // top level elements
     136         280 :     if (auth.receipt.level == TOP_LEVEL &&
     137          50 :         auth.receipt.index[TOP_LEVEL - 1] != LIST_ELEMENTS) {
     138           2 :         LOG("[E] Receipt had %u elements\n", auth.receipt.index[TOP_LEVEL - 1]);
     139           2 :         THROW(ERR_AUTH_RECEIPT_INVALID);
     140             :     }
     141             : 
     142         278 :     --auth.receipt.level;
     143             : 
     144             :     // Reset indexes of lower levels
     145         884 :     for (uint8_t i = auth.receipt.level; i < RECEIPT_MAX_DEPTH; i++)
     146         606 :         auth.receipt.index[i] = 0;
     147         278 : }
     148             : 
     149         644 : static void str_start(const uint16_t size) {
     150             :     UNUSED(size);
     151             :     
     152             :     // Top level must be a list
     153         644 :     if (auth.receipt.level == 0) {
     154           0 :         LOG("[E] Receipt not a list\n");
     155           0 :         THROW(ERR_AUTH_RECEIPT_INVALID);
     156             :     }
     157             : 
     158             :     update_indexes();
     159             : 
     160             :     // Save strings only for desired fields
     161         644 :     if (auth.receipt.index[TOP_LEVEL - 1] == LOGS_INDEX &&
     162         400 :         auth.receipt.level >= LOG_LEVEL) {
     163         398 :         auth.receipt.aux_offset = 0;
     164             :     }
     165         644 : }
     166             : 
     167         906 : static void str_chunk(const uint8_t* chunk, const size_t size) {
     168             :     // Save strings only for desired fields
     169             :     // Also prevent erroring while saving
     170             :     // undesired fields that could be too big
     171         906 :     if (auth.receipt.index[TOP_LEVEL - 1] == LOGS_INDEX &&
     172         524 :         auth.receipt.level >= LOG_LEVEL &&
     173         522 :         auth.receipt.aux_offset + size <= sizeof(auth.receipt.aux)) {
     174        1036 :         SAFE_MEMMOVE(auth.receipt.aux,
     175             :                      sizeof(auth.receipt.aux),
     176             :                      auth.receipt.aux_offset,
     177             :                      chunk,
     178             :                      size,
     179             :                      MEMMOVE_ZERO_OFFSET,
     180             :                      size,
     181             :                      THROW(ERR_AUTH_INVALID_DATA_SIZE));
     182         518 :         auth.receipt.aux_offset += size;
     183             :     }
     184         906 : }
     185             : 
     186         644 : static void str_end() {
     187             :     // Compare values with expected values
     188         644 :     if (auth.receipt.index[TOP_LEVEL - 1] == LOGS_INDEX &&
     189         400 :         auth.receipt.level >= LOG_LEVEL) {
     190         398 :         switch (auth.receipt.level) {
     191         184 :         case LOG_LEVEL:
     192         184 :             if (auth.receipt.index[LOG_LEVEL - 1] == EVENT_EMITTER_INDEX &&
     193          92 :                 auth.receipt.aux_offset == sizeof(EVENT_EMITTER) &&
     194          92 :                 !memcmp(auth.receipt.aux,
     195             :                         (void*)PIC(EVENT_EMITTER),
     196             :                         sizeof(EVENT_EMITTER)))
     197          88 :                 SET_FLAG(auth.receipt.flags, IS_VALID_EMITTER);
     198         184 :             break;
     199         214 :         case TOPIC_LEVEL:
     200         214 :             if (auth.receipt.index[LOG_LEVEL - 1] == TOPICS_INDEX) {
     201         214 :                 if (auth.receipt.index[TOPIC_LEVEL - 1] ==
     202          92 :                         TOPIC_SIGNATURE_INDEX &&
     203          92 :                     auth.receipt.aux_offset == sizeof(EVENT_SIGNATURE) &&
     204          92 :                     !memcmp(auth.receipt.aux,
     205             :                             (void*)PIC(EVENT_SIGNATURE),
     206             :                             sizeof(EVENT_SIGNATURE)))
     207          56 :                     SET_FLAG(auth.receipt.flags, IS_VALID_SIGNATURE);
     208         158 :                 else if (auth.receipt.index[TOPIC_LEVEL - 1] ==
     209          60 :                              TOPIC_TXHASH_INDEX &&
     210          60 :                          auth.receipt.aux_offset == sizeof(auth.tx_hash) &&
     211          60 :                          !memcmp(auth.receipt.aux,
     212             :                                  auth.tx_hash,
     213             :                                  sizeof(auth.tx_hash)))
     214          46 :                     SET_FLAG(auth.receipt.flags, IS_VALID_TXHASH);
     215             :             }
     216         214 :             break;
     217             :         }
     218             :     }
     219         644 : }
     220             : 
     221             : static const rlp_callbacks_t callbacks = {
     222             :     str_start,
     223             :     str_chunk,
     224             :     str_end,
     225             :     list_start,
     226             :     list_end,
     227             : };
     228             : 
     229             : /*
     230             :  * Implement the RSK receipt parsing and validation portion of the signing
     231             :  * authorization protocol.
     232             :  *
     233             :  * @arg[in] rx      number of received bytes from the host
     234             :  * @ret             number of transmited bytes to the host
     235             :  */
     236         338 : unsigned int auth_sign_handle_receipt(volatile unsigned int rx) {
     237         338 :     if (auth.state != STATE_AUTH_RECEIPT) {
     238           0 :         LOG("[E] Expected to be in the receipt state\n");
     239           0 :         THROW(ERR_AUTH_INVALID_STATE);
     240             :     }
     241             : 
     242         338 :     if (!HAS_FLAG(auth.receipt.flags, IS_INIT)) {
     243          50 :         rlp_start(&callbacks);
     244          50 :         hash_keccak256_init(&auth.receipt.hash_ctx);
     245          50 :         SET_FLAG(auth.receipt.flags, IS_INIT);
     246             :     }
     247             : 
     248         338 :     int res = rlp_consume(APDU_DATA_PTR, APDU_DATA_SIZE(rx));
     249         336 :     if (res < 0) {
     250           0 :         LOG("[E] RLP parser returned error %d\n", res);
     251           0 :         THROW(ERR_AUTH_RECEIPT_RLP);
     252             :     }
     253         336 :     auth.receipt.remaining_bytes -= APDU_DATA_SIZE(rx);
     254             : 
     255           0 :     hash_keccak256_update(
     256         336 :         &auth.receipt.hash_ctx, APDU_DATA_PTR, APDU_DATA_SIZE(rx));
     257             : 
     258         336 :     if (auth.receipt.remaining_bytes == 0) {
     259          48 :         if (HAS_FLAG(auth.receipt.flags, IS_MATCH)) {
     260             :             // Finalize the hash calculation
     261          42 :             hash_keccak256_final(&auth.receipt.hash_ctx, auth.receipt_hash);
     262             : 
     263             :             // Log hash for debugging purposes
     264          42 :             LOG_HEX(
     265             :                 "Receipt hash: ", auth.receipt_hash, sizeof(auth.receipt_hash));
     266             : 
     267             :             // Request RSK transaction receipt
     268          42 :             SET_APDU_OP(P1_MERKLEPROOF);
     269          42 :             SET_APDU_TXLEN(AUTH_MAX_EXCHANGE_SIZE);
     270          42 :             auth.expected_bytes = APDU_TXLEN();
     271          42 :             auth_transition_to(STATE_AUTH_MERKLEPROOF);
     272          42 :             return TX_FOR_TXLEN();
     273             :         }
     274             : 
     275             :         // No match
     276           6 :         LOG("[E] No log match found in the receipt\n");
     277             :         // To comply with the legacy implementation
     278           6 :         THROW(ERR_AUTH_INVALID_DATA_SIZE);
     279             :     }
     280             : 
     281         288 :     SET_APDU_TXLEN(MIN(auth.receipt.remaining_bytes, AUTH_MAX_EXCHANGE_SIZE));
     282         288 :     auth.expected_bytes = APDU_TXLEN();
     283         288 :     return TX_FOR_TXLEN();
     284             : }

Generated by: LCOV version 1.16