Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/ldg 508 nano app implement getpublickey method #9

Merged
merged 20 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ff10ce4
feat(pubkey): implement get public key command and tests
keiff3r Nov 29, 2024
83078bb
fix(pubkey): correct signature input data in public key response
keiff3r Nov 29, 2024
67792e4
feat(ui): add BIP32 derivation path display screen
keiff3r Nov 29, 2024
e6a2edf
feat(ui): implement NBGL display for public key verification
keiff3r Nov 29, 2024
01154d8
test: generate golden snapshots
keiff3r Nov 30, 2024
d906cd5
chore: remove useless 'TODO'
keiff3r Nov 30, 2024
aa6efcc
feat(tests): refactor pubkey cmd tests and add navigation utils
keiff3r Nov 30, 2024
ebb962f
style: format the codebase
keiff3r Nov 30, 2024
90f7150
style: format a test
keiff3r Nov 30, 2024
e984ede
feat(tests): add unpack_get_signed_public_key_response function
keiff3r Nov 30, 2024
f5deb6b
fix(tests): add missing type in a test
keiff3r Nov 30, 2024
d4624cf
chore: comment out boilerplate test files and mark docs for update
keiff3r Nov 30, 2024
9d600fc
chore: comment out unused unit test configuration
keiff3r Nov 30, 2024
b199d4f
style: format utils.py
keiff3r Nov 30, 2024
88bffd1
feat(ui): enhance public key display with governance and signing info
keiff3r Nov 30, 2024
2581c61
Merge branch 'main' into feat/LDG-508--nano-app-implement-getpublicke…
keiff3r Nov 30, 2024
1babf1d
refactor(pubkey): remove governance display for new derivation path
keiff3r Dec 2, 2024
59a2e54
Merge branch 'main' into feat/LDG-508--nano-app-implement-getpublicke…
keiff3r Dec 2, 2024
46050c3
fix(fuzzing): add the fuzzer from main branch
keiff3r Dec 2, 2024
7a44ff7
chore: delete printf in src/helper/util.c
keiff3r Dec 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ ledger/

# macOS
.DS_Store

# Helpers
diff
5 changes: 3 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
"string": "c",
"string_view": "c",
"unordered_map": "c",
"vector": "c"
"vector": "c",
"locale": "c"
},
"C_Cpp.clang_format_path": "/usr/bin/clang-format",
"editor.formatOnSave": false,
"editor.formatOnSave": true,
"python.terminal.activateEnvironment": false,
"git.ignoreLimitWarning": true,
"ledgerDevTools.appSettings": {
Expand Down
26 changes: 14 additions & 12 deletions doc/APDU.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// TODO: EDIT THIS TO MATCH THE CURRENT APP

# Application Protocol Data Unit (APDU)

The communication protocol used by [BOLOS](https://ledger.readthedocs.io/en/latest/bolos/overview.html) to exchange [APDU](https://en.wikipedia.org/wiki/Smart_card_application_protocol_data_unit) is very close to [ISO 7816-4](https://www.iso.org/standard/77180.html) with a few differences:
Expand All @@ -11,18 +13,18 @@ Status words tend to be similar to common [APDU responses](https://www.eftlab.co

## Command APDU

| Field name | Length (bytes) | Description |
| --- | --- | --- |
| CLA | 1 | Instruction class - indicates the type of command |
| INS | 1 | Instruction code - indicates the specific command |
| P1 | 1 | Instruction parameter 1 for the command |
| P2 | 1 | Instruction parameter 2 for the command |
| Lc | 1 | The number of bytes of command data to follow (a value from 0 to 255) |
| CData | var | Command data with `Lc` bytes |
| Field name | Length (bytes) | Description |
| ---------- | -------------- | --------------------------------------------------------------------- |
| CLA | 1 | Instruction class - indicates the type of command |
| INS | 1 | Instruction code - indicates the specific command |
| P1 | 1 | Instruction parameter 1 for the command |
| P2 | 1 | Instruction parameter 2 for the command |
| Lc | 1 | The number of bytes of command data to follow (a value from 0 to 255) |
| CData | var | Command data with `Lc` bytes |

## Response APDU

| Field name | Length (bytes) | Description |
| --- | --- | --- |
| RData | var | Response data (can be empty) |
| SW | 2 | Status word containing command processing status (e.g. `0x9000` for success) |
| Field name | Length (bytes) | Description |
| ---------- | -------------- | ---------------------------------------------------------------------------- |
| RData | var | Response data (can be empty) |
| SW | 2 | Status word containing command processing status (e.g. `0x9000` for success) |
40 changes: 21 additions & 19 deletions doc/TRANSACTION.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// TODO: EDIT THIS TO MATCH THE CURRENT APP

# BOLOK Transaction Serialization

## Overview

The custom transaction serialization presented is for the purely fictitious BOLOK *chain* which has been inspired by other popular blockchain (see [Links](#links)).
The custom transaction serialization presented is for the purely fictitious BOLOK _chain_ which has been inspired by other popular blockchain (see [Links](#links)).

## Amount units

The base unit in BOLOK *chain* is the CCD and the smallest unit used in raw transaction is the *bolino* or mBOL: 1 CCD = 1000 mCCD.
The base unit in BOLOK _chain_ is the CCD and the smallest unit used in raw transaction is the _bolino_ or mBOL: 1 CCD = 1000 mCCD.

## Address format

Expand All @@ -16,29 +18,29 @@ BOLOK addresses are hexadecimal numbers, identifiers derived from the last 20 by

### Transaction

| Field | Size (bytes) | Description |
| --- | :---: | --- |
| `nonce` | 8 | A sequence number used to prevent message replay |
| `to` | 20 | The destination address |
| `value` | 8 | The amount in mBOL to send to the destination address |
| `memo_len` | 1-9 | length of the memo as [varint](#variablelenghtinteger) |
| `memo` | var | A text ASCII-encoded of length `memo_len` to show your love |
| `v` | 1 | 0x01 if y-coordinate of R is odd, 0x00 otherwise |
| `r` | 32 | x-coordinate of R in ECDSA signature |
| `s` | 32 | x-coordinate of S in ECDSA signature |
| Field | Size (bytes) | Description |
| ---------- | :----------: | ----------------------------------------------------------- |
| `nonce` | 8 | A sequence number used to prevent message replay |
| `to` | 20 | The destination address |
| `value` | 8 | The amount in mBOL to send to the destination address |
| `memo_len` | 1-9 | length of the memo as [varint](#variablelenghtinteger) |
| `memo` | var | A text ASCII-encoded of length `memo_len` to show your love |
| `v` | 1 | 0x01 if y-coordinate of R is odd, 0x00 otherwise |
| `r` | 32 | x-coordinate of R in ECDSA signature |
| `s` | 32 | x-coordinate of S in ECDSA signature |

### Variable length integer (varint)

Integer can be encoded depending on the represented value to save space.
Variable length integers always precede an array of a type of data that may vary in length.
Longer numbers are encoded in little endian.

| Value | Storage length (bytes) | Format |
| --- | :---: | --- |
| < 0xFD | 1 | uint8_t |
| <= 0xFFFF | 3 | 0xFD followed by the length as uint16_t |
| <= 0xFFFF FFFF | 5 | 0xFE followed by the length as uint32_t |
| - | 9 | 0xFF followed by the length as uint64_t |
| Value | Storage length (bytes) | Format |
| -------------- | :--------------------: | --------------------------------------- |
| < 0xFD | 1 | uint8_t |
| <= 0xFFFF | 3 | 0xFD followed by the length as uint16_t |
| <= 0xFFFF FFFF | 5 | 0xFE followed by the length as uint32_t |
| - | 9 | 0xFF followed by the length as uint64_t |

### Signature

Expand All @@ -47,7 +49,7 @@ The signed message is `m = Keccak-256(nonce || to || value || memo_len || memo)`

### Fee

You won't find any fee in the transaction structure because the BOLOK *chain* has constant fees.
You won't find any fee in the transaction structure because the BOLOK _chain_ has constant fees.

## Links

Expand Down
18 changes: 6 additions & 12 deletions fuzzing/fuzz_tx_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,20 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {

if (status == PARSING_OK) {
// Format recipient address
format_hex(tx.transaction.simple_transfer.recipient,
ADDRESS_LEN,
address,
sizeof(address));
format_hex(tx.transaction.simple_transfer.recipient, ADDRESS_LEN, address, sizeof(address));
printf("recipient: %s\n", address);

// Format sender address
format_hex(tx.transaction.simple_transfer.sender,
ADDRESS_LEN,
address,
sizeof(address));
format_hex(tx.transaction.simple_transfer.sender, ADDRESS_LEN, address, sizeof(address));
printf("sender: %s\n", address);

// Format amount
format_fpu64(amount,
sizeof(amount),
tx.transaction.simple_transfer.value,
3); // exponent of smallest unit is 3
sizeof(amount),
tx.transaction.simple_transfer.value,
3); // exponent of smallest unit is 3
printf("amount: %s\n", amount);
}

return 0;
}
}
4 changes: 2 additions & 2 deletions src/apdu/dispatcher.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ int apdu_dispatcher(const command_t *cmd) {
return handler_verify_address(&buf, (bool) cmd->p1);

case GET_PUBLIC_KEY:
if (cmd->p1 > 1 || cmd->p2 > 0) {
if (cmd->p1 > 1 || cmd->p2 > 1) {
return io_send_sw(SW_WRONG_P1P2);
}

Expand All @@ -83,7 +83,7 @@ int apdu_dispatcher(const command_t *cmd) {
buf.size = cmd->lc;
buf.offset = 0;

return handler_get_public_key(&buf, (bool) cmd->p1);
return handler_get_public_key(&buf, (bool) cmd->p1, (bool) cmd->p2);
case SIGN_SIMPLE_TRANSFER:
if ((cmd->p1 == P1_START && cmd->p2 != P2_MORE) || //
cmd->p1 > P1_MAX || //
Expand Down
27 changes: 27 additions & 0 deletions src/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,30 @@ enum new_key_indexes { NEW_SIGN_KEY = 0, NEW_ID_CRED = 1, NEW_PRF_KEY = 2, NEW_M
* Concordium version byte.
*/
#define CONCORDIUM_VERSION_BYTE 1

/**
* Length of the public key.
*/
#define PUBKEY_LEN 32

/**
* Maximum length of a string representing a BIP32 derivation path.
* Each step is up to 11 characters (10 decimal digits, plus the "hardened" symbol),
* and there is 1 separator before each step.
*/
#define MAX_SERIALIZED_BIP32_PATH_LENGTH (12 * MAX_BIP32_PATH_SUPPORTED)

/**
* Index of the purpose in the legacy derivation path.
*/
#define LEGACY_PATH_PURPOSE_INDEX 3

/**
* Index of the subtree in the legacy derivation path.
*/
#define LEGACY_PATH_SUBTREE_INDEX 2

/**
* Subtree value for governance keys in the legacy derivation path.
*/
#define LEGACY_GOVERNANCE_SUBTREE 0x80000001
111 changes: 100 additions & 11 deletions src/handler/get_public_key.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,31 +31,120 @@
#include "../types.h"
#include "../sw.h"
#include "../ui/display.h"
#include "../helper/util.h"
#include "../helper/send_response.h"

int handler_get_public_key(buffer_t *cdata, bool display) {
int get_public_key(uint32_t *path, size_t path_len, uint8_t *public_key_array) {
cx_ecfp_private_key_t private_key;
cx_ecfp_public_key_t public_key;
int rtn = get_private_key_from_path(path, path_len, &private_key);
if (rtn != 0) {
return rtn;
}
if (cx_ecfp_generate_pair_no_throw(CX_CURVE_Ed25519, &public_key, &private_key, 1) != CX_OK) {
// Clear the private key
explicit_bzero(&private_key, sizeof(private_key));
return -3;
}

explicit_bzero(&private_key, sizeof(private_key));

// Format the public key
for (int i = 0; i < 32; i++) {
public_key_array[i] = public_key.W[64 - i];
}
if ((public_key.W[32] & 1) != 0) {
public_key_array[31] |= 0x80;
}
return 0;
}

int handler_get_public_key(buffer_t *cdata, bool display, bool sign_public_key) {
explicit_bzero(&G_context, sizeof(G_context));
G_context.req_type = CONFIRM_ADDRESS;
G_context.req_type = CONFIRM_PUBLIC_KEY;
G_context.state = STATE_NONE;

if (!buffer_read_u8(cdata, &G_context.bip32_path_len) ||
!buffer_read_bip32_path(cdata, G_context.bip32_path, (size_t) G_context.bip32_path_len)) {
return io_send_sw(SW_WRONG_DATA_LENGTH);
}
int path_type = derivation_path_type(G_context.bip32_path, G_context.bip32_path_len);
if (path_type <= 0) {
PRINTF("ERROR: Invalid path type: %d\n", path_type);
return io_send_sw(SW_INVALID_PATH);
}
harden_derivation_path(G_context.bip32_path, G_context.bip32_path_len);
int rtn = get_public_key(G_context.bip32_path,
G_context.bip32_path_len,
G_context.pk_info.public_key);
// Error handling
switch (rtn) {
case -1:
// Derivation path failed
return io_send_sw(SW_DERIVATION_PATH_FAIL);
case -2:
// Key initialization failed
return io_send_sw(SW_KEY_INIT_FAIL);
case -3:
// Public key derivation failed
return io_send_sw(SW_PUBLIC_KEY_DERIVATION_FAIL);
case 0:
break;
default:
// This should never happen
return io_send_sw(SW_BAD_STATE);
}

cx_err_t error = bip32_derive_get_pubkey_256(CX_CURVE_256K1,
G_context.bip32_path,
G_context.bip32_path_len,
G_context.pk_info.raw_public_key,
G_context.pk_info.chain_code,
CX_SHA512);
if (sign_public_key) {
G_context.pk_info.sign_public_key = true;
}
// Change the title of the public key depending on if signed or not or if it is a governance key
char temp_title[36];
size_t offset = 0;
strncpy(temp_title, "Get ", 4);
offset += 4;

if (error != CX_OK) {
return io_send_sw(error);
// Append "Signed" to the title if needed
if (sign_public_key) {
strncpy(temp_title + offset, "Signed ", 7);
offset += 7;
}

// Append "Gov." to the title if needed
if (path_type == 11) {
strncpy(temp_title + offset, "Gov. ", 5);
offset += 5;
// Get the purpose of the key
uint32_t purpose = G_context.bip32_path[LEGACY_PATH_PURPOSE_INDEX];
// Change the title of the public key depending on the purpose
switch (purpose) {
case 0 | HARDENED_OFFSET:
strncpy(temp_title + offset, "Root ", 5);
offset += 5;
break;
case 1 | HARDENED_OFFSET:
strncpy(temp_title + offset, "Level 1 ", 7);
offset += 7;
break;
case 2 | HARDENED_OFFSET:
strncpy(temp_title + offset, "Level 2 ", 7);
offset += 7;
break;
default:
PRINTF("Unknown purpose: %lx\n", purpose);
return io_send_sw(SW_INVALID_PATH);
}
}

// Append "Public Key" to the title
strncpy(temp_title + offset, "Public Key", 11);
offset += 11;
// Add null terminator
temp_title[offset] = '\0';
strncpy(G_context.pk_info.public_key_title, temp_title, 36);

if (display) {
return ui_display_address();
return ui_display_pubkey();
}

return helper_send_response_pubkey();
Expand Down
26 changes: 25 additions & 1 deletion src/handler/get_public_key.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,32 @@
* Command data with BIP32 path.
* @param[in] display
* Whether to display address on screen or not.
* @param[in] sign_public_key
* Whether to sign the public key or not.
*
* @return zero or positive integer if success, negative integer otherwise.
*
*/
int handler_get_public_key(buffer_t *cdata, bool display);
int handler_get_public_key(buffer_t *cdata, bool display, bool sign_public_key);

/**
* Derives the Ed25519 public key for a given BIP32 path.
*
* This function performs the following steps:
* 1. Derives the private key from the provided BIP32 path
* 2. Generates the corresponding Ed25519 public key
* 3. Formats the public key according to the expected format (32 bytes)
*
* @param[in] path Pointer to the BIP32 path array
* @param[in] path_len Length of the BIP32 path
* @param[out] public_key_array Buffer to store the formatted public key (must be at least 32
* bytes)
*
* @return 0 on success
* @return -1 if the derivation path is invalid
* @return -2 if key initialization fails
* @return -3 if public key generation fails
*
* @note The function securely clears sensitive private key data after use
*/
int get_public_key(uint32_t *path, size_t path_len, uint8_t *public_key_array);
1 change: 0 additions & 1 deletion src/handler/sign_simple_transfer.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
#include "../transaction/types.h"
#include "../transaction/deserialize.h"

// TODO: FINISH THIS
int handler_sign_simple_transfer(buffer_t *cdata, uint8_t chunk, bool more) {
if (chunk == 0) { // first APDU, parse BIP32 path
explicit_bzero(&G_context, sizeof(G_context));
Expand Down
Loading
Loading