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

Add sign message #82

Open
wants to merge 5 commits into
base: export-identity-data
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Added

* Added Support for signing messages.
* Allow export of prfKey, IdCredSec, blinding randomness and attribute commitment randomness using the new paths.

## 4.0.0
Expand Down
16 changes: 16 additions & 0 deletions doc/ins_sign_message.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Sign message

Sign an arbitrary message with the private key of the account.

What is signed is the sha 256 hash of the account address (32 bytes) || 8 zero bytes || the message bytes.

The message is displayed as UTF8, unless the initial command uses P2 = 1, in which case the message will be displayed using hex encoding instead.

## Protocol description

* Multiple commands

INS | P1 | P2 | CDATA | Comment |
|----|--------|-----|-------------|----|
| `0x38` | `0x00` | `0x00` / `0x01` | `path_length path[uint32]x[8] signer_address[32 bytes] message_length[uint16]` | The signer address has to be base58. |
| `0x38` | `0x01` | `0x00` | `message[1...255 bytes]` | The message should be sent in batches of up to 255 bytes. |
44 changes: 41 additions & 3 deletions rust_tests/src/bin/path.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
use hex;

pub fn generate_testnet_key_derivation_path() -> Vec<u8> {
generate_path(build_testnet_key_derivation_path())
}

pub fn generate_key_derivation_path() -> Vec<u8> {
let mut key_derivation_path = build_key_derivation_path();
generate_path(build_legacy_key_derivation_path())
}

fn generate_path(mut key_derivation_path: Vec<u8>) -> Vec<u8> {
let key_path_length: u8 = (key_derivation_path.len() / 4) as u8;

let mut result = Vec::new();
Expand All @@ -11,7 +18,37 @@ pub fn generate_key_derivation_path() -> Vec<u8> {
return result;
}

fn build_key_derivation_path() -> Vec<u8> {
fn build_testnet_key_derivation_path() -> Vec<u8> {
// Purpose = 44
let mut purpose = hex::decode("0000002C").unwrap();

// Coin type = 1.
let mut coin_type = hex::decode("00000001").unwrap();

// Identity provider
let mut identity_provider = hex::decode("00000000").unwrap();

// Identity
let mut identity = hex::decode("00000000").unwrap();

// Account index
let mut account_index = hex::decode("00000000").unwrap();

// Signature index
let mut sig_index = hex::decode("00000000").unwrap();

let mut key_derivation_path = Vec::new();
key_derivation_path.append(&mut purpose);
key_derivation_path.append(&mut coin_type);
key_derivation_path.append(&mut identity_provider);
key_derivation_path.append(&mut identity);
key_derivation_path.append(&mut account_index);
key_derivation_path.append(&mut sig_index);

return key_derivation_path;
}

fn build_legacy_key_derivation_path() -> Vec<u8> {
// Purpose = 1105.
let mut purpose = hex::decode("00000451").unwrap();

Expand Down Expand Up @@ -49,5 +86,6 @@ fn build_key_derivation_path() -> Vec<u8> {
return key_derivation_path;
}


#[allow(dead_code)]
fn main() { }
fn main() { }
7 changes: 7 additions & 0 deletions rust_tests/src/bin/sign_hex_message.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod sign_message;

fn main() {
let message = hex::decode("057366ddef5d7edefe03fef5743012b995b5e3bcdae130b4e01b396853b65bb9b687bde11540c7dc5ed6e237de5a604b8f75b72a58d5f69f66abb5e52efd8c5d8381eaea64650ac1ecae6cca898e9677a061f8cb7e0501787ac6c659719f80d3d22d38d8f5aab812bd8013490230c8ce3781a14f8be337111111057366ddef5d7edefe03fef5743012b995b5e3bcdae130b4e01b396853b65bb9b687bde11540c7dc5ed6e237de5a604b8f75b72a58d5f69f66abb5e52efd8c5d8381eaea64650ac1ecae6cca898e9677a061f8cb7e0501787ac6c659719f80d3d22d38d8f5aab812bd8013490230c8ce3781a14f8be337111111abcd057366ddef5d7edefe03fef5743012b995b5e3bcdae130b4e01b396853b65bb9b687bde11540c7dc5ed6e237de5a604b8f75b72a58d5f69f66abb5e52efd8c5d8381eaea64650ac1ecae6cca898e9677a061f8cb7e0501787ac6c659719f80d3d22d38d8f5aab812bd8013490230c8ce3781a14f8be337111111057366ddef5d7edefe03fef5743012b995b5e3bcdae130b4e01b396853b65bb9b687bde11540c7dc5ed6e237de5a604b8f75b72a58d5f69f66abb5e52efd8c5d8381eaea64650ac1ecae6cca898e9677a061f8cb7e0501787ac6c659719f80d3d22d38d8f5aab812bd8013490230c8ce3781a14f8be337111111abcd").unwrap();
let p2 = 1;
sign_message::sign_message(message, p2)
}
49 changes: 49 additions & 0 deletions rust_tests/src/bin/sign_message.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#[path = "path.rs"] mod path;
use ledger::{ApduCommand, LedgerApp};
use base58check::*;

pub fn sign_message(message: Vec<u8>, p2: u8) {
let ins = 0x38;
let signer_address = "3C8N65hBwc2cNtJkGmVyGeWYxhZ6R3X77mLWTwAKsnAnyworTq";

let mut signer_address = signer_address.from_base58check().unwrap().1;
println!("{}", message.len());

// Build transaction payload bytes.
let mut initial_payload = path::generate_testnet_key_derivation_path();
initial_payload.append(&mut signer_address);
initial_payload.append(&mut (message.len() as u16).to_be_bytes().to_vec());
println!("{:?}", initial_payload);

let command = ApduCommand {
cla: 224, // Has to be this value for all commands.
ins,
p1: 0,
p2,
length: initial_payload.len() as u8,
data: initial_payload
};

let ledger = LedgerApp::new().unwrap();
let mut result = ledger.exchange(command).expect("Sign message initial packet failed.");

for message_part in message.chunks(255) {
let message_command = ApduCommand {
cla: 224, // Has to be this value for all commands.
ins,
p1: 1,
p2: 0,
length: message_part.len() as u8,
data: message_part.to_vec()
};
result = ledger.exchange(message_command).expect("Sign message message packet failed.");
}
println!("{}", hex::encode(result.data));
}

#[allow(dead_code)]
fn main() {
let message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam turpis magna, ultricies elementum suscipit sed, accumsan ut ex. Phasellus non tempus erat. Praesent fermentum turpis vel arcu tempus placerat. Aenean sed elit et erat vulputate aliquet. Nunc eu ultrices tortor, ut dignissim nisl. Nunc congue urna non efficitur laoreet. Aenean sit amet augue id purus consequat molestie. Quisque aliquet purus id enim auctor, non aliquet justo cursus. Donec consequat, nibh rutrum varius porta, urna mi.".as_bytes().to_vec();
let p2 = 0;
sign_message(message, p2)
}
2 changes: 2 additions & 0 deletions src/globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "signTransferToPublic.h"
#include "signTransferWithSchedule.h"
#include "signRegisterData.h"
#include "signMessage.h"

#include "ux.h"

Expand Down Expand Up @@ -104,6 +105,7 @@ typedef union {
exportDataContext_t exportDataContext;
exportPublicKeyContext_t exportPublicKeyContext;
verifyAddressContext_t verifyAddressContext;
signMessageContext_t signMessageContext;

signPublicInformationForIp_t signPublicInformationForIp;
signCredentialDeploymentContext_t signCredentialDeploymentContext;
Expand Down
7 changes: 7 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "signTransferToEncrypted.h"
#include "signTransferToPublic.h"
#include "signTransferWithSchedule.h"
#include "signMessage.h"
#include "ux.h"
#include "verifyAddress.h"

Expand Down Expand Up @@ -92,6 +93,9 @@ accountSender_t global_account_sender;
#define INS_SIGN_TRANSFER_WITH_SCHEDULE_AND_MEMO 0x34
#define INS_REGISTER_DATA 0x35

#define INS_SIGN_MESSAGE 0x38


// Main entry of application that listens for APDU commands that will be received from the
// computer. The APDU commands control what flow is activated, i.e. which control flow is initiated.
static void concordium_main(void) {
Expand Down Expand Up @@ -194,6 +198,9 @@ static void concordium_main(void) {
case INS_SIGN_UPDATE_CREDENTIAL:
handleSignUpdateCredential(cdata, p1, p2, &flags, isInitialCall);
break;
case INS_SIGN_MESSAGE:
handleSignMessage(cdata, p1, p2, lc, &flags, isInitialCall);
break;
default:
THROW(ERROR_INVALID_INSTRUCTION);
break;
Expand Down
2 changes: 1 addition & 1 deletion src/numberHelpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ size_t amountToGtuDisplay(uint8_t *dst, size_t dstLength, uint64_t microGtuAmoun
void toPaginatedHex(uint8_t *byteArray, const uint64_t len, char *asHex, const size_t asHexSize) {
static uint8_t const hex[] = "0123456789abcdef";

if (asHexSize < len * 2 + len / 16 + 1) {
if (asHexSize < len * 2 + len / 8) {
THROW(ERROR_BUFFER_OVERFLOW);
}

Expand Down
135 changes: 135 additions & 0 deletions src/signMessage.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#include <os.h>

#include "base58check.h"
#include "responseCodes.h"
#include "sign.h"
#include "util.h"

static signMessageContext_t *ctx = &global.signMessageContext;
static tx_state_t *tx_state = &global_tx_state;

// Common initial view for signing flows.
UX_STEP_NOCB(ux_sign_message_review, nn, {"Review", "Message request"});

UX_STEP_NOCB(
ux_sign_message_account_view,
bnnn_paging,
{.title = "Signer", .text = (char *) global.signMessageContext.displaySigner});

UX_STEP_NOCB(
ux_sign_message_display_message,
bnnn_paging,
{global.signMessageContext.displayHeader, (char *) global.signMessageContext.display});

UX_STEP_CB(
ux_sign_message_accept,
pnn,
buildAndSignTransactionHash(),
{&C_icon_validate_14, "Sign", "Message"});

UX_STEP_CB(
ux_sign_message_decline,
pnn,
sendUserRejection(),
{&C_icon_crossmark, "Decline to", "sign message"});

UX_STEP_CB(ux_sign_message_continue, nn, sendSuccessNoIdle(), {"Continue", "reviewing message"});

// There will at most be 6 UI steps when the entire message fits in one batch.
const ux_flow_step_t *ux_sign_message[6];

void startSignMessageDisplay(bool displayStart, bool finalChunk) {
uint8_t index = 0;

if (displayStart) {
ux_sign_message[index++] = &ux_sign_message_review;
ux_sign_message[index++] = &ux_sign_message_account_view;
}

ux_sign_message[index++] = &ux_sign_message_display_message;

if (finalChunk) {
ux_sign_message[index++] = &ux_sign_message_accept;
ux_sign_message[index++] = &ux_sign_message_decline;
} else {
ux_sign_message[index++] = &ux_sign_message_continue;
}

ux_sign_message[index++] = FLOW_END_STEP;

ux_flow_init(0, ux_sign_message, NULL);
}


#define P1_SIGN_MESSAGE_INITIAL 0x00
#define P1_SIGN_MESSAGE_MESSAGE 0x01

#define P2_UTF8 0x00
#define P2_HEX 0x01

void handleSignMessage(
uint8_t *cdata,
uint8_t p1,
uint8_t p2,
uint8_t dataLength,
volatile unsigned int *flags,
bool isInitialCall) {

if (p2 != P2_UTF8 && p2 != P2_HEX) {
THROW(ERROR_INVALID_PARAM);
}

if (isInitialCall) {
ctx->state = SIGN_MESSAGE_INITIAL;
}

if (p1 == P1_SIGN_MESSAGE_INITIAL && ctx->state == SIGN_MESSAGE_INITIAL) {
cdata += parseKeyDerivationPath(cdata);
cx_sha256_init(&tx_state->hash);

uint8_t toAddress[32];
memmove(toAddress, cdata, 32);
cx_hash((cx_hash_t *) &tx_state->hash, 0, toAddress, 32, NULL, 0);
size_t displaySignerSize = sizeof(ctx->displaySigner);
if (base58check_encode(toAddress, sizeof(toAddress), ctx->displaySigner, &displaySignerSize) != 0) {
THROW(ERROR_INVALID_TRANSACTION);
}
ctx->displaySigner[55] = '\0';
cdata += 32;
ctx->messageLength = U2BE(cdata, 0);
ctx->displayStart = true;

ctx->initialP2 = p2;

// We hash 8 zero bytes
uint8_t zeroes[8];
memset(zeroes, 0, 8);
cx_hash((cx_hash_t *) &tx_state->hash, 0, zeroes, 8, NULL, 0);
ctx->state = SIGN_MESSAGE_CONTINUED;

sendSuccessNoIdle();
} else if (p1 == P1_SIGN_MESSAGE_MESSAGE && ctx->state == SIGN_MESSAGE_CONTINUED) {
cx_hash((cx_hash_t *) &tx_state->hash, 0, cdata, dataLength, NULL, 0);

if (ctx->messageLength < dataLength) {
THROW(ERROR_INVALID_TRANSACTION);
}

ctx->messageLength -= dataLength;
if (ctx->initialP2 == P2_UTF8) {
memmove(ctx->display, cdata, dataLength);
if (dataLength < 255) {
memmove(ctx->display + dataLength, "\0", 1);
}
memmove(ctx->displayHeader, "Message", 8);
} else {
toPaginatedHex(cdata, dataLength, ctx->display, sizeof(ctx->display));
memmove(ctx->displayHeader, "Message (hex)", 14);
}
startSignMessageDisplay(ctx->displayStart, ctx->messageLength == 0);
ctx->displayStart = false;
*flags |= IO_ASYNCH_REPLY;
} else {
THROW(ERROR_INVALID_STATE);
}
}
31 changes: 31 additions & 0 deletions src/signMessage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#ifndef _CONCORDIUM_APP_MESSAGE_H_
#define _CONCORDIUM_APP_MESSAGE_H_

/**
* Handles the signing flow, including updating the display, for signing a message.
* @param cdata please see /doc/ins_message.md for details
*/
void handleSignMessage(
uint8_t *cdata,
uint8_t p1,
uint8_t p2,
uint8_t dataLength,
volatile unsigned int *flags,
bool isInitialCall);

typedef enum {
SIGN_MESSAGE_INITIAL = 110,
SIGN_MESSAGE_CONTINUED = 111,
} signMessageState_t;

typedef struct {
uint8_t initialP2;
bool displayStart;
uint32_t messageLength;
signMessageState_t state;
char displayHeader[14];
char display[542];
unsigned char displaySigner[57];
} signMessageContext_t;

#endif
Loading