Skip to content

Commit

Permalink
compute shielded hash (#60)
Browse files Browse the repository at this point in the history
  • Loading branch information
abenso authored Aug 23, 2024
1 parent b8acb10 commit 86dffc6
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 35 deletions.
1 change: 1 addition & 0 deletions app/src/common/parser_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ typedef enum {
parser_invalid_number_of_converts,
parser_invalid_rk,
parser_invalid_cv,
parser_invalid_target_hash,
} parser_error_t;

typedef struct {
Expand Down
2 changes: 2 additions & 0 deletions app/src/parser_impl.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ parser_error_t _read(parser_context_t *ctx, parser_tx_t *v) {

CHECK_ERROR(validateTransactionParams(v))

CHECK_ERROR(verifyShieldedHash(ctx))

if (ctx->offset != ctx->bufferLen) {
return parser_unexpected_unparsed_bytes;
}
Expand Down
1 change: 1 addition & 0 deletions app/src/parser_impl_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ parser_error_t readVote(bytes_t *vote, yay_vote_type_e type, char *strVote, uint
parser_error_t readHeader(parser_context_t *ctx, parser_tx_t *v);
parser_error_t readSections(parser_context_t *ctx, parser_tx_t *v);
parser_error_t validateTransactionParams(parser_tx_t *txObj);
parser_error_t verifyShieldedHash(parser_context_t *ctx);

parser_error_t readPGFPaymentAction(parser_context_t *ctx, pgf_payment_action_t *paymentAction);
parser_error_t readTransferSourceTarget(parser_context_t *ctx, AddressAlt *owner, AddressAlt *token, bytes_t *amount, uint8_t *amount_denom, const char** symbol);
Expand Down
11 changes: 9 additions & 2 deletions app/src/parser_impl_masp.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
#include "nvdata.h"
#include "crypto_helper.h"
#include "parser_address.h"
#include "tx_hash.h"

#if defined(TARGET_NANOS) || defined(TARGET_NANOS2) || defined(TARGET_NANOX) || defined(TARGET_STAX)
#include "cx.h"
#include "cx_sha256.h"
#include "cx_blake2b.h"
#endif

static parser_error_t readCompactSize(parser_context_t *ctx, uint64_t *result) {
uint8_t tag = 0;
Expand Down Expand Up @@ -105,8 +112,8 @@ static parser_error_t readSaplingBundle(parser_context_t *ctx, masp_sapling_bund

// Read outputs proofs
if (bundle->n_shielded_outputs != 0) {
bundle->zkproof_shielded_spends.len = ZKPROFF_LEN * bundle->n_shielded_outputs;
CHECK_ERROR(readBytes(ctx, &bundle->zkproof_shielded_spends.ptr, bundle->zkproof_shielded_spends.len))
bundle->zkproof_shielded_outputs.len = ZKPROFF_LEN * bundle->n_shielded_outputs;
CHECK_ERROR(readBytes(ctx, &bundle->zkproof_shielded_outputs.ptr, bundle->zkproof_shielded_outputs.len))
}

// Read authorization signature
Expand Down
27 changes: 23 additions & 4 deletions app/src/parser_impl_txn.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "txn_delegation.h"
#include "stdbool.h"
#include "parser_address.h"
#include "tx_hash.h"

#include <zxformat.h>

Expand Down Expand Up @@ -559,8 +560,7 @@ static parser_error_t readTransferTxn(const bytes_t *data, parser_tx_t *v) {
CHECK_ERROR(readByte(&ctx, &v->transfer.has_shielded_hash))
if (v->transfer.has_shielded_hash){
v->transfer.shielded_hash.len = HASH_LEN;
// we are not displaying these bytes
ctx.offset += v->transfer.shielded_hash.len;
CHECK_ERROR(readBytes(&ctx, &v->transfer.shielded_hash.ptr, v->transfer.shielded_hash.len))
}

if (ctx.offset != ctx.bufferLen) {
Expand Down Expand Up @@ -935,8 +935,7 @@ static parser_error_t readIBCTxn(const bytes_t *data, parser_tx_t *v) {
CHECK_ERROR(readByte(&ctx, &v->ibc.transfer.has_shielded_hash))
if (v->ibc.transfer.has_shielded_hash){
v->ibc.transfer.shielded_hash.len = HASH_LEN;
// we are not displaying these bytes
ctx.offset += v->ibc.transfer.shielded_hash.len;
CHECK_ERROR(readBytes(&ctx, &v->ibc.transfer.shielded_hash.ptr, v->ibc.transfer.shielded_hash.len))
}
}

Expand Down Expand Up @@ -1414,3 +1413,23 @@ parser_error_t validateTransactionParams(parser_tx_t *txObj) {

return parser_ok;
}

parser_error_t verifyShieldedHash(parser_context_t *ctx) {
if (ctx == NULL) {
return parser_unexpected_error;
}

#if defined(LEDGER_SPECIFIC)
// compute tx_id hash
uint8_t tx_id_hash[HASH_LEN] = {0};
tx_hash_txId(ctx->tx_obj, tx_id_hash);

if (ctx->tx_obj->transaction.sections.maspBuilder.target_hash.len == HASH_LEN) {
if (memcmp(tx_id_hash, ctx->tx_obj->transaction.sections.maspBuilder.target_hash.ptr, HASH_LEN) != 0) {
return parser_invalid_target_hash;
}
}
#endif

return parser_ok;
}
18 changes: 16 additions & 2 deletions app/src/parser_txdef.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ extern "C" {
#define EPK_LEN 32
#define ENC_CIPHER_LEN 612
#define OUT_CIPHER_LEN 80
#define COMPACT_NOTE_SIZE 52
#define NOTE_PLAINTEXT_SIZE 564
#define COMPACT_NOTE_SIZE 84
#define NOTE_PLAINTEXT_SIZE 512
#define POSITION_LEN 8
#define RANDOM_LEN 32
#define IDENTIFIER_LEN 32
Expand Down Expand Up @@ -148,6 +148,20 @@ typedef struct {
bytes_t authorization; // 64 bytes: rbar: [u8; 32], sbar: [u8; 32],
} masp_sapling_bundle_t;

typedef struct {
uint8_t cv[CV_LEN];
uint8_t nullifier[NULLIFIER_LEN];
uint8_t rk[RK_LEN];
} shielded_spends_t;

typedef struct {
uint8_t cv[CV_LEN];
uint8_t cmu[CMU_LEN];
uint8_t ephemeral_key[EPK_LEN];
uint8_t enc_ciphertext[ENC_CIPHER_LEN];
uint8_t out_ciphertext[OUT_CIPHER_LEN];
} shielded_outputs_t;

typedef struct {
uint64_t n_vin;
bytes_t vin; // [u8;60]
Expand Down
120 changes: 93 additions & 27 deletions app/src/tx_hash.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <zxformat.h>
#include <zxmacros.h>
#include "parser_txdef.h"
#include "parser_impl_masp.h"

// Hash MaspTx Header information
zxerr_t tx_hash_header_data(const parser_tx_t *txObj, uint8_t *output) {
Expand Down Expand Up @@ -123,13 +124,20 @@ zxerr_t tx_hash_sapling_spends(const parser_tx_t *txObj, uint8_t *output) {
CHECK_CX_OK(cx_blake2b_init2_no_throw(&nc_ctx, 256, NULL, 0, (uint8_t*)ZCASH_SAPLING_SPENDS_NONCOMPACT_HASH_PERSONALIZATION, PERSONALIZATION_SIZE));

const uint8_t *spend = txObj->transaction.sections.maspTx.data.sapling_bundle.shielded_spends.ptr;
const uint64_t n_shielded_spends = txObj->transaction.sections.maspTx.data.sapling_bundle.n_shielded_spends;
const bool has_spend_anchor = txObj->transaction.sections.maspBuilder.builder.sapling_builder.has_spend_anchor;
const uint8_t *spend_anchor_ptr = txObj->transaction.sections.maspBuilder.builder.sapling_builder.spend_anchor.ptr;

for(uint64_t i = 0; i < txObj->transaction.sections.maspTx.data.sapling_bundle.n_shielded_spends; i++, spend += SHIELDED_SPENDS_LEN){
CHECK_CX_OK(cx_hash_no_throw(&nullifier_ctx.header, 0, spend + CV_LEN, NULLIFIER_LEN, NULL,0));
for (uint64_t i = 0; i < n_shielded_spends; i++, spend += SHIELDED_SPENDS_LEN) {
shielded_spends_t *shielded_spends = (shielded_spends_t *)spend;

CHECK_CX_OK(cx_hash_no_throw(&nc_ctx.header, 0, spend, CV_LEN, NULL, 0));
CHECK_CX_OK(cx_hash_no_throw(&nc_ctx.header, 0, txObj->transaction.sections.maspTx.data.sapling_bundle.anchor_shielded_spends.ptr, ANCHOR_LEN, NULL, 0));
CHECK_CX_OK(cx_hash_no_throw(&nc_ctx.header, 0, spend + CV_LEN + NULLIFIER_LEN, RK_LEN, NULL, 0));
CHECK_CX_OK(cx_hash_no_throw(&nullifier_ctx.header, 0, shielded_spends->nullifier, NULLIFIER_LEN, NULL, 0));

CHECK_CX_OK(cx_hash_no_throw(&nc_ctx.header, 0, shielded_spends->cv, CV_LEN, NULL, 0));
if (has_spend_anchor) {
CHECK_CX_OK(cx_hash_no_throw(&nc_ctx.header, 0, spend_anchor_ptr, ANCHOR_LEN, NULL, 0));
}
CHECK_CX_OK(cx_hash_no_throw(&nc_ctx.header, 0, shielded_spends->rk, RK_LEN, NULL, 0));
}

uint8_t nullifier_hash[HASH_SIZE] = {0};
Expand Down Expand Up @@ -200,20 +208,21 @@ zxerr_t tx_hash_sapling_outputs(const parser_tx_t *txObj, uint8_t *output) {
cx_blake2b_t non_compact_ctx = {0};
CHECK_CX_OK(cx_blake2b_init2_no_throw(&non_compact_ctx, 256, NULL, 0, (uint8_t*)ZCASH_SAPLING_OUTPUTS_NONCOMPACT_HASH_PERSONALIZATION, PERSONALIZATION_SIZE));

const uint8_t *out = txObj->transaction.sections.maspTx.data.sapling_bundle.shielded_outputs.ptr;
const uint8_t *shielded_outputs_ptr = txObj->transaction.sections.maspTx.data.sapling_bundle.shielded_outputs.ptr;
const uint64_t n_shielded_outputs = txObj->transaction.sections.maspTx.data.sapling_bundle.n_shielded_outputs;

for (uint64_t i = 0; i < txObj->transaction.sections.maspTx.data.sapling_bundle.n_shielded_outputs; i++, out += SHIELDED_OUTPUTS_LEN) {
CHECK_CX_OK(cx_hash_no_throw(&compact_ctx.header, 0, out + CMU_OFFSET, CMU_LEN, NULL,0));
CHECK_CX_OK(cx_hash_no_throw(&compact_ctx.header, 0, out + EPK_OFFSET, EPK_OFFSET, NULL,0));
CHECK_CX_OK(cx_hash_no_throw(&compact_ctx.header, 0, out + ENC_CIPHER_OFFSET, COMPACT_NOTE_SIZE, NULL,0));
for (uint64_t i = 0; i < n_shielded_outputs; i++, shielded_outputs_ptr += SHIELDED_OUTPUTS_LEN) {
const shielded_outputs_t *shielded_output = (const shielded_outputs_t *)shielded_outputs_ptr;

CHECK_CX_OK(cx_hash_no_throw(&memo_ctx.header, 0,out + ENC_CIPHER_OFFSET + COMPACT_NOTE_SIZE, NOTE_PLAINTEXT_SIZE, NULL,0));
CHECK_CX_OK(cx_hash_no_throw(&compact_ctx.header, 0, shielded_output->cmu, CMU_LEN, NULL, 0));
CHECK_CX_OK(cx_hash_no_throw(&compact_ctx.header, 0, shielded_output->ephemeral_key, EPK_LEN, NULL, 0));
CHECK_CX_OK(cx_hash_no_throw(&compact_ctx.header, 0, shielded_output->enc_ciphertext, COMPACT_NOTE_SIZE, NULL, 0));

CHECK_CX_OK(cx_hash_no_throw(&non_compact_ctx.header, 0, out, CV_LEN, NULL,0));
CHECK_CX_OK(cx_hash_no_throw(&non_compact_ctx.header, 0, out + ENC_CIPHER_OFFSET + COMPACT_NOTE_SIZE + NOTE_PLAINTEXT_SIZE ,
ENC_CIPHER_LEN - (COMPACT_NOTE_SIZE + NOTE_PLAINTEXT_SIZE), NULL, 0));
CHECK_CX_OK(cx_hash_no_throw(&non_compact_ctx.header, 0, out + OUT_CIPHER_OFFSET, OUT_CIPHER_LEN, NULL,0));
CHECK_CX_OK(cx_hash_no_throw(&memo_ctx.header, 0, shielded_output->enc_ciphertext + COMPACT_NOTE_SIZE, NOTE_PLAINTEXT_SIZE, NULL, 0));

CHECK_CX_OK(cx_hash_no_throw(&non_compact_ctx.header, 0, shielded_output->cv, CV_LEN, NULL, 0));
CHECK_CX_OK(cx_hash_no_throw(&non_compact_ctx.header, 0, shielded_output->enc_ciphertext + COMPACT_NOTE_SIZE + NOTE_PLAINTEXT_SIZE, ENC_CIPHER_LEN - COMPACT_NOTE_SIZE - NOTE_PLAINTEXT_SIZE, NULL, 0));
CHECK_CX_OK(cx_hash_no_throw(&non_compact_ctx.header, 0, shielded_output->out_ciphertext, OUT_CIPHER_LEN, NULL, 0));
}

uint8_t compact_hash[HASH_SIZE] = {0};
Expand All @@ -237,19 +246,42 @@ zxerr_t tx_hash_sapling_data(const parser_tx_t *txObj, uint8_t *output) {
}

cx_blake2b_t ctx = {0};
CHECK_CX_OK(cx_blake2b_init2_no_throw(&ctx, 256, NULL, 0, (uint8_t*)ZCASH_SAPLING_OUTPUTS_HASH_PERSONALIZATION, PERSONALIZATION_SIZE));
CHECK_CX_OK(cx_blake2b_init2_no_throw(&ctx, 256, NULL, 0, (uint8_t *)ZCASH_SAPLING_HASH_PERSONALIZATION, PERSONALIZATION_SIZE));

uint8_t spends_hash[32] = {0};
uint8_t converts_hash[32] = {0};
uint8_t outputs_hash[32] = {0};

CHECK_ZXERR(tx_hash_transparent_inputs(txObj, spends_hash));
CHECK_ZXERR(tx_hash_transparent_outputs(txObj, converts_hash));
CHECK_ZXERR(tx_hash_transparent_outputs(txObj, outputs_hash));
if (txObj->transaction.sections.maspTx.data.sapling_bundle.n_shielded_outputs != 0) {
CHECK_ZXERR(tx_hash_sapling_spends(txObj, spends_hash));

// TODO: there is not an example to validate converts
CHECK_ZXERR(tx_hash_sapling_converts(txObj, converts_hash));

CHECK_ZXERR(tx_hash_sapling_outputs(txObj, outputs_hash));

CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, spends_hash, HASH_SIZE, NULL, 0));
CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, converts_hash, HASH_SIZE, NULL, 0));
CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, outputs_hash, HASH_SIZE, NULL, 0));

if (txObj->transaction.sections.maspTx.data.sapling_bundle.n_value_sum_asset_type == 0) {
uint8_t zero_byte = 0;
CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, &zero_byte, 1, NULL, 0));
} else {
// TODO: while debugging
// https://github.com/anoma/masp/blob/8d83b172698098fba393006016072bc201ed9ab7/masp_primitives/src/transaction/txid.rs#L234,
// there is a 0x01 byte at the beginning. Is this byte representing the n_value_sum_asset_type?
uint8_t asset_type = (uint8_t)txObj->transaction.sections.maspTx.data.sapling_bundle.n_value_sum_asset_type;
CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, &asset_type, 1, NULL, 0));

CHECK_CX_OK(cx_hash_no_throw(
&ctx.header, 0, txObj->transaction.sections.maspTx.data.sapling_bundle.value_sum_asset_type.ptr,
(ASSET_ID_LEN + INT_128_LEN) * txObj->transaction.sections.maspTx.data.sapling_bundle.n_value_sum_asset_type,
NULL, 0));
}
}

CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, spends_hash, HASH_SIZE, NULL, 0));
CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, converts_hash, HASH_SIZE, NULL, 0));
CHECK_CX_OK(cx_hash_no_throw(&ctx.header, CX_LAST, outputs_hash, HASH_SIZE, output, HASH_SIZE));
CHECK_CX_OK(cx_hash_final(&ctx.header, output));

return zxerr_ok;
}
Expand All @@ -260,16 +292,50 @@ zxerr_t tx_hash_transparent_data(const parser_tx_t *txObj, uint8_t *output) {
}

cx_blake2b_t ctx = {0};
CHECK_CX_OK(cx_blake2b_init2_no_throw(&ctx, 256, NULL, 0, (uint8_t*)ZCASH_SAPLING_OUTPUTS_HASH_PERSONALIZATION, PERSONALIZATION_SIZE));
CHECK_CX_OK(cx_blake2b_init2_no_throw(&ctx, 256, NULL, 0, (uint8_t *)ZCASH_TRANSPARENT_HASH_PERSONALIZATION, PERSONALIZATION_SIZE));

uint8_t outputs_hash[32] = {0};
uint8_t inputs_hash[32] = {0};

CHECK_ZXERR(tx_hash_transparent_inputs(txObj, inputs_hash));
CHECK_ZXERR(tx_hash_transparent_outputs(txObj, outputs_hash));
if (txObj->transaction.sections.maspTx.data.transparent_bundle.n_vin > 0 ||
txObj->transaction.sections.maspTx.data.transparent_bundle.n_vout > 0) {
CHECK_ZXERR(tx_hash_transparent_inputs(txObj, inputs_hash));
CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, inputs_hash, HASH_SIZE, NULL, 0));

CHECK_ZXERR(tx_hash_transparent_outputs(txObj, outputs_hash));
CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, outputs_hash, HASH_SIZE, NULL, 0));
}

CHECK_CX_OK(cx_hash_no_throw(&ctx.header, CX_LAST, NULL, 0, output, HASH_SIZE));
return zxerr_ok;
}

zxerr_t tx_hash_txId(const parser_tx_t *txObj, uint8_t *output) {
if (txObj == NULL || output == NULL) {
return zxerr_no_data;
}

uint8_t personal[16] = {0};
MEMCPY(personal, ZCASH_TX_PERSONALIZATION_PREFIX, 12);

// Use BRANCH_ID_IDENTIFIER to set the last 4 bytes
uint32_t branch_id = BRANCH_ID_IDENTIFIER;
memcpy(&personal[12], &branch_id, sizeof(branch_id));

cx_blake2b_t ctx_hash = {0};
CHECK_CX_OK(cx_blake2b_init2_no_throw(&ctx_hash, 256, NULL, 0, personal, PERSONALIZATION_SIZE));

uint8_t header[32] = {0};
uint8_t transparent[32] = {0};
uint8_t sapling[32] = {0};

CHECK_ZXERR(tx_hash_header_data(txObj, header));
CHECK_ZXERR(tx_hash_transparent_data(txObj, transparent));
CHECK_ZXERR(tx_hash_sapling_data(txObj, sapling));

CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, inputs_hash, HASH_SIZE, NULL, 0));
CHECK_CX_OK(cx_hash_no_throw(&ctx.header, CX_LAST, outputs_hash, HASH_SIZE, output, HASH_SIZE));
CHECK_CX_OK(cx_hash_no_throw(&ctx_hash.header, 0, header, HASH_SIZE, NULL, 0));
CHECK_CX_OK(cx_hash_no_throw(&ctx_hash.header, 0, transparent, HASH_SIZE, NULL, 0));
CHECK_CX_OK(cx_hash_no_throw(&ctx_hash.header, CX_LAST, sapling, HASH_SIZE, output, HASH_SIZE));

return zxerr_ok;
}
1 change: 1 addition & 0 deletions app/src/tx_hash.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ zxerr_t tx_hash_sapling_converts(const parser_tx_t *txObj, uint8_t *output);
zxerr_t tx_hash_sapling_outputs(const parser_tx_t *txObj, uint8_t *output);
zxerr_t tx_hash_sapling_data(const parser_tx_t *txObj, uint8_t *output);
zxerr_t tx_hash_transparent_data(const parser_tx_t *txObj, uint8_t *output);
zxerr_t tx_hash_txId(const parser_tx_t *txObj, uint8_t *output);

0 comments on commit 86dffc6

Please sign in to comment.