Skip to content

Commit

Permalink
Merge pull request #32 from blooo-io/feat/LDG-516--nano-app-implement…
Browse files Browse the repository at this point in the history
…-exportprivatekey-method

Feat/ldg 516  nano app implement exportprivatekey method
  • Loading branch information
n4l5u0r authored Dec 10, 2024
2 parents 2d8a950 + c234921 commit 837c7a8
Show file tree
Hide file tree
Showing 38 changed files with 265 additions and 36 deletions.
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 @@ -314,5 +320,38 @@ def sign_configure_delegation(
# ) 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

0 comments on commit 837c7a8

Please sign in to comment.