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 516 nano app implement exportprivatekey method #32

Merged
merged 19 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
31 changes: 21 additions & 10 deletions doc/export_private_key.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,24 @@ If P2 = 0x02, the BLS12-381 private keys will be exported instead. (Generated us

## Protocol description

- Single command

| INS | P1 | P2 | CDATA | Comment |
| ------ | ------ | ------ | ------------------ | -------------------------------------------------------------------------------------------------------------- |
| `0x05` | `0x00` | `0x01` | `identity[uint32]` | Export of PRF key seed for the BLS12-381 KeyGen algorithm (Deprecated) |
| `0x05` | `0x01` | `0x01` | `identity[uint32]` | Export of PRF key seed for the BLS12-381 KeyGen algorithm with alternative display (for recovery) (Deprecated) |
| `0x05` | `0x02` | `0x01` | `identity[uint32]` | Export of PRF key and IdCredSec seeds for the BLS12-381 KeyGen algorithm (Deprecated) |
| `0x05` | `0x00` | `0x02` | `identity[uint32]` | Export of PRF key (BLS12-381) |
| `0x05` | `0x01` | `0x02` | `identity[uint32]` | Export of PRF key with alternative display (for recovery) (BLS12-381) |
| `0x05` | `0x02` | `0x02` | `identity[uint32]` | Export of PRF key and IdCredSec (BLS12-381) |
For legacy paths:

| INS | P1 | P2 | CDATA | Comment |
| ------ | ------ | ------ | ----------------------- | -------------------------------------------------------------------------------------------------------------- |
| `0x05` | `0x00` | `0x01` | `0x00,identity[uint32]` | Export of PRF key seed for the BLS12-381 KeyGen algorithm (Deprecated) |
| `0x05` | `0x01` | `0x01` | `0x00,identity[uint32]` | Export of PRF key seed for the BLS12-381 KeyGen algorithm with alternative display (for recovery) (Deprecated) |
| `0x05` | `0x02` | `0x01` | `0x00,identity[uint32]` | Export of PRF key and IdCredSec seeds for the BLS12-381 KeyGen algorithm (Deprecated) |
| `0x05` | `0x00` | `0x02` | `0x00,identity[uint32]` | Export of PRF key (BLS12-381) |
| `0x05` | `0x01` | `0x02` | `0x00,identity[uint32]` | Export of PRF key with alternative display (for recovery) (BLS12-381) |
| `0x05` | `0x02` | `0x02` | `0x00,identity[uint32]` | Export of PRF key and IdCredSec (BLS12-381) |

For new paths:

| INS | P1 | P2 | CDATA | Comment |
| ------ | ------ | ------ | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `0x05` | `0x00` | `0x01` | `0x01,identity_provider[uint32],identity[uint32]` | Export of PRF key seed for the BLS12-381 KeyGen algorithm (Deprecated) |
| `0x05` | `0x01` | `0x01` | `0x01,identity_provider[uint32],identity[uint32]` | Export of PRF key seed for the BLS12-381 KeyGen algorithm with alternative display (for recovery) (Deprecated) |
| `0x05` | `0x02` | `0x01` | `0x01,identity_provider[uint32],identity[uint32]` | Export of PRF key and IdCredSec seeds for the BLS12-381 KeyGen algorithm (Deprecated) |
| `0x05` | `0x00` | `0x02` | `0x01,identity_provider[uint32],identity[uint32]` | Export of PRF key (BLS12-381) |
| `0x05` | `0x01` | `0x02` | `0x01,identity_provider[uint32],identity[uint32]` | Export of PRF key with alternative display (for recovery) (BLS12-381) |
| `0x05` | `0x02` | `0x02` | `0x01,identity_provider[uint32],identity[uint32]` | Export of PRF key and IdCredSec (BLS12-381) |
101 changes: 79 additions & 22 deletions src/exportPrivateKey.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,34 @@
static const uint32_t HARDENED_OFFSET = 0x80000000;
static exportPrivateKeyContext_t *ctx = &global.exportPrivateKeyContext;

#define ID_CRED_SEC 0
#define PRF_KEY 1

#define pathLength 6

void exportPrivateKeySeed(void) {
cx_ecfp_private_key_t privateKey;
BEGIN_TRY {
TRY {
ctx->path[5] = PRF_KEY | HARDENED_OFFSET;
getPrivateKey(ctx->path, pathLength, &privateKey);
uint8_t lastSubPath;
uint8_t lastSubPathIndex;
if (ctx->isNewPath) {
lastSubPath = NEW_PRF_KEY;
lastSubPathIndex = 4;
} else {
lastSubPath = LEGACY_PRF_KEY;
lastSubPathIndex = 5;
}
ctx->path[lastSubPathIndex] = lastSubPath | HARDENED_OFFSET;
getPrivateKey(ctx->path, lastSubPathIndex + 1, &privateKey);
uint8_t tx = 0;
for (int i = 0; i < 32; i++) {
G_io_apdu_buffer[tx++] = privateKey.d[i];
}

if (ctx->exportBoth) {
ctx->path[5] = ID_CRED_SEC | HARDENED_OFFSET;
getPrivateKey(ctx->path, pathLength, &privateKey);
if (ctx->isNewPath) {
lastSubPath = NEW_ID_CRED_SEC;
} else {
lastSubPath = LEGACY_ID_CRED_SEC;
}
ctx->path[lastSubPathIndex] = lastSubPath | HARDENED_OFFSET;
getPrivateKey(ctx->path, lastSubPathIndex + 1, &privateKey);
for (int i = 0; i < 32; i++) {
G_io_apdu_buffer[tx++] = privateKey.d[i];
}
Expand All @@ -53,15 +62,29 @@ void exportPrivateKeyBls(void) {
uint8_t privateKey[32];
BEGIN_TRY {
TRY {
ctx->path[5] = PRF_KEY | HARDENED_OFFSET;
getBlsPrivateKey(ctx->path, pathLength, privateKey, sizeof(privateKey));
uint8_t lastSubPath;
uint8_t lastSubPathIndex;
if (ctx->isNewPath) {
lastSubPath = NEW_PRF_KEY;
lastSubPathIndex = 4;
} else {
lastSubPath = LEGACY_PRF_KEY;
lastSubPathIndex = 5;
}
ctx->path[lastSubPathIndex] = lastSubPath | HARDENED_OFFSET;
getBlsPrivateKey(ctx->path, lastSubPathIndex + 1, privateKey, sizeof(privateKey));
uint8_t tx = 0;
memmove(G_io_apdu_buffer, privateKey, sizeof(privateKey));
tx += sizeof(privateKey);

if (ctx->exportBoth) {
ctx->path[5] = ID_CRED_SEC | HARDENED_OFFSET;
getBlsPrivateKey(ctx->path, pathLength, privateKey, sizeof(privateKey));
if (ctx->isNewPath) {
lastSubPath = NEW_ID_CRED_SEC;
} else {
lastSubPath = LEGACY_ID_CRED_SEC;
}
ctx->path[lastSubPathIndex] = lastSubPath | HARDENED_OFFSET;
getBlsPrivateKey(ctx->path, lastSubPathIndex + 1, privateKey, sizeof(privateKey));
memmove(G_io_apdu_buffer + tx, privateKey, sizeof(privateKey));
tx += sizeof(privateKey);
}
Expand Down Expand Up @@ -105,19 +128,53 @@ void handleExportPrivateKey(uint8_t *dataBuffer,
(p2 != P2_KEY && p2 != P2_SEED)) {
THROW(ERROR_INVALID_PARAM);
}
uint32_t identity = U4BE(dataBuffer, 0);
uint32_t keyDerivationPath[5] = {LEGACY_PURPOSE | HARDENED_OFFSET,
LEGACY_COIN_TYPE | HARDENED_OFFSET,
ACCOUNT_SUBTREE | HARDENED_OFFSET,
NORMAL_ACCOUNTS | HARDENED_OFFSET,
identity | HARDENED_OFFSET};
memmove(ctx->path, keyDerivationPath, sizeof(keyDerivationPath));
size_t offset = 0;

ctx->isNewPath = (bool) dataBuffer[offset];
offset += 1;

uint32_t identity_provider;
uint32_t identity;
if (ctx->isNewPath) {
identity_provider = U4BE(dataBuffer, offset);
offset += 4;
}
identity = U4BE(dataBuffer, offset);
uint32_t *keyDerivationPath;
size_t pathLength;
if (ctx->isNewPath) {
keyDerivationPath = (uint32_t[4]){NEW_PURPOSE | HARDENED_OFFSET,
NEW_COIN_TYPE | HARDENED_OFFSET,
identity_provider | HARDENED_OFFSET,
identity | HARDENED_OFFSET};
pathLength = 4;
} else {
keyDerivationPath = (uint32_t[5]){LEGACY_PURPOSE | HARDENED_OFFSET,
LEGACY_COIN_TYPE | HARDENED_OFFSET,
ACCOUNT_SUBTREE | HARDENED_OFFSET,
NORMAL_ACCOUNTS | HARDENED_OFFSET,
identity | HARDENED_OFFSET};
pathLength = 5;
}
memmove(ctx->path, keyDerivationPath, pathLength * sizeof(uint32_t));
ctx->pathLength = pathLength * sizeof(uint32_t);

ctx->exportBoth = p1 == P1_BOTH;
ctx->exportSeed = p2 == P2_SEED;

memmove(ctx->display, "ID #", 4);
bin2dec(ctx->display + 4, sizeof(ctx->display) - 4, identity);
// Reset the offset to 0
offset = 0;
if (ctx->isNewPath) {
memmove(ctx->display, "IDP#", 4);
offset += 4;
offset += bin2dec(ctx->display + offset, sizeof(ctx->display) - offset, identity_provider);
// Remove the null terminator
offset -= 1;
}

memmove(ctx->display + offset, " ID#", 4);
offset += 4;
bin2dec(ctx->display + offset, sizeof(ctx->display) - offset, identity);

if (p1 == P1_BOTH) {
memmove(ctx->displayHeader, "Create credential", 18);
Expand Down
3 changes: 2 additions & 1 deletion src/exportPrivateKey.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ void handleExportPrivateKey(uint8_t *dataBuffer,

typedef struct {
uint8_t displayHeader[20];
uint8_t display[15];
uint8_t display[22];
bool exportBoth;
bool exportSeed;
uint32_t path[6];
uint8_t pathLength;
bool isNewPath;
} exportPrivateKeyContext_t;

void uiExportPrivateKey(volatile unsigned int *flags);
Expand Down
8 changes: 7 additions & 1 deletion src/globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,13 @@
#define ACCOUNT_TRANSACTION_HEADER_LENGTH 60
#define UPDATE_HEADER_LENGTH 28

typedef enum { LEGACY_PRF_KEY = 1, NEW_PRF_KEY = 3 } derivation_path_keys_t;
typedef enum {
LEGACY_ID_CRED_SEC = 0,
LEGACY_PRF_KEY = 1,
// New path
NEW_ID_CRED_SEC = 2,
NEW_PRF_KEY = 3
} derivation_path_keys_t;

typedef enum {
DEPLOY_MODULE = 0,
Expand Down
41 changes: 40 additions & 1 deletion tests/application_client/boilerplate_command_sender.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from enum import IntEnum
from typing import Generator, Optional
from typing import Generator, Literal, Optional
from contextlib import contextmanager

from ragger.backend.interface import BackendInterface, RAPDU
Expand All @@ -23,6 +23,10 @@ class P1(IntEnum):
# Parameter 1 for scheduled transfer with memo
P1_MEMO_SCHEDULED_TRANSFER = 0x03
P1_INITIAL_SCHEDULED_TRANSFER_WITH_MEMO = 0x02
# Parameter 1 for export private key
P1_EXPORT_PRIVATE_KEY = 0x00
P1_EXPORT_WITH_ALTERNATIVE_DISPLAY = 0x01
P1_EXPORT_PRFKEY_AND_IDCREDSEC = 0x02
# Basic P1 for all instructions
P1_NONE = 0x00

Expand All @@ -31,6 +35,8 @@ class P2(IntEnum):
# Parameter 2 for sign for GET_PUBLIC_KEY.
P2_SIGN = 0x01
P2_NO_SIGN = 0x00
# Parameter 2 for export private key
P2_EXPORT_BLS_KEY = 0x02
# Basic P2 for all instructions
P2_NONE = 0x00
# # Parameter 2 for last APDU to receive.
Expand Down Expand Up @@ -298,5 +304,38 @@ def sign_tx_with_schedule_and_memo_part_3(
# ) as response:
# yield response

@contextmanager
def export_private_key(
self,
export_type: Literal["standard", "recovery", "prfkey_and_idcredsec"],
identity_index: int,
idp_index: int = -1,
) -> Generator[None, None, None]:
data = b""
if export_type == "standard":
p1 = P1.P1_EXPORT_PRIVATE_KEY
elif export_type == "recovery":
p1 = P1.P1_EXPORT_WITH_ALTERNATIVE_DISPLAY
elif export_type == "prfkey_and_idcredsec":
p1 = P1.P1_EXPORT_PRFKEY_AND_IDCREDSEC
else:
raise ValueError(f"Invalid export type: {export_type}")
if idp_index != -1:
data += bytes.fromhex("01")
data += idp_index.to_bytes(4, byteorder="big")
else:
data += bytes.fromhex("00")

data += identity_index.to_bytes(4, byteorder="big")
print("km------------data", data.hex())
with self.backend.exchange_async(
cla=CLA,
ins=InsType.EXPORT_PRIVATE_KEY,
p1=p1,
p2=P2.P2_EXPORT_BLS_KEY,
data=data,
) as response:
yield response

def get_async_response(self) -> Optional[RAPDU]:
return self.backend.last_async_response
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
106 changes: 106 additions & 0 deletions tests/test_export_private_key.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import pytest

from application_client.boilerplate_command_sender import (
BoilerplateCommandSender,
Errors,
)
from application_client.boilerplate_response_unpacker import (
unpack_get_public_key_response,
)
from ragger.bip import calculate_public_key_and_chaincode, CurveChoice
from ragger.error import ExceptionRAPDU
from ragger.navigator import NavInsID, NavIns
from ragger.firmware import Firmware
from utils import navigate_until_text_and_compare


@pytest.mark.active_test_scope
def test_export_standard_private_key_legacy_path(
backend, firmware, navigator, test_name, default_screenshot_path
):
client = BoilerplateCommandSender(backend)
with client.export_private_key(export_type="standard", identity_index=0):
navigate_until_text_and_compare(
firmware,
navigator,
"Accept",
default_screenshot_path,
test_name,
screen_change_before_first_instruction=False,
screen_change_after_last_instruction=True,
)
result = client.get_async_response()
print("km------------result", result)
assert result.data == bytes.fromhex(
"48235b90248b6e552d59bf8b533292d25c5afd1f8e1ad5d1e00478794642ba38"
)


@pytest.mark.active_test_scope
def test_export_recovery_private_key_legacy_path(
backend, firmware, navigator, test_name, default_screenshot_path
):
client = BoilerplateCommandSender(backend)
with client.export_private_key(export_type="recovery", identity_index=0):
navigate_until_text_and_compare(
firmware,
navigator,
"Accept",
default_screenshot_path,
test_name,
screen_change_before_first_instruction=False,
screen_change_after_last_instruction=True,
)
result = client.get_async_response()
print("km------------result", result)
assert result.data == bytes.fromhex(
"48235b90248b6e552d59bf8b533292d25c5afd1f8e1ad5d1e00478794642ba38"
)


@pytest.mark.active_test_scope
def test_export_prfkey_and_idcredsed_private_key_legacy_path(
backend, firmware, navigator, test_name, default_screenshot_path
):
client = BoilerplateCommandSender(backend)
with client.export_private_key(
export_type="prfkey_and_idcredsec", identity_index=0
):
navigate_until_text_and_compare(
firmware,
navigator,
"Accept",
default_screenshot_path,
test_name,
screen_change_before_first_instruction=False,
screen_change_after_last_instruction=True,
)
result = client.get_async_response()
print("km------------result", result)
assert result.data == bytes.fromhex(
"48235b90248b6e552d59bf8b533292d25c5afd1f8e1ad5d1e00478794642ba3802a5a44c0b2e0abcaf313c77fa05f6449c092ad449a081098bd48515bf95e947"
)


@pytest.mark.active_test_scope
def test_export_standard_private_key_new_path(
backend, firmware, navigator, test_name, default_screenshot_path
):
client = BoilerplateCommandSender(backend)
with client.export_private_key(
export_type="standard", identity_index=0, idp_index=0
):
navigate_until_text_and_compare(
firmware,
navigator,
"Accept",
default_screenshot_path,
test_name,
screen_change_before_first_instruction=False,
screen_change_after_last_instruction=True,
)
result = client.get_async_response()
print("km------------result", result)
assert result.data == bytes.fromhex(
"00beb8ab5d68b55f39dacc0d0847bb9cd62a327549d41a4dfe7c5845f70c5562"
)
Loading
Loading