Skip to content

Commit

Permalink
add required signers
Browse files Browse the repository at this point in the history
  • Loading branch information
mkv-vcm committed Oct 21, 2021
1 parent 5dfb0dc commit 2eff4c0
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 10 deletions.
1 change: 1 addition & 0 deletions src/cardano.h
Expand Up @@ -23,6 +23,7 @@ STATIC_ASSERT(LOVELACE_MAX_SUPPLY < LOVELACE_INVALID, "bad LOVELACE_INVALID");
#define SCRIPT_HASH_LENGTH 28
#define SCRIPT_DATA_HASH_LENGTH 32
#define OUTPUT_DATUM_HASH_LENGTH 32
#define VKEY_LENGTH 32

#define MINTING_POLICY_ID_SIZE 28
#define ASSET_NAME_SIZE_MAX 32
Expand Down
20 changes: 20 additions & 0 deletions src/securityPolicy.c
Expand Up @@ -870,6 +870,26 @@ security_policy_t policyForSignTxCollaterals(const sign_tx_signingmode_t txSigni
DENY();
}

security_policy_t policyForSignTxRequiredSigners(const sign_tx_signingmode_t txSigningMode)
{
switch (txSigningMode) {
case SIGN_TX_SIGNINGMODE_ORDINARY_TX:
case SIGN_TX_SIGNINGMODE_MULTISIG_TX:
SHOW();
break;

case SIGN_TX_SIGNINGMODE_POOL_REGISTRATION_OWNER:
case SIGN_TX_SIGNINGMODE_POOL_REGISTRATION_OPERATOR:
DENY();
break;

default:
ASSERT(false);
}

DENY();
}

security_policy_t policyForSignTxConfirm()
{
PROMPT();
Expand Down
2 changes: 2 additions & 0 deletions src/securityPolicy.h
Expand Up @@ -106,6 +106,8 @@ security_policy_t policyForSignTxScriptDataHash(const sign_tx_signingmode_t txSi

security_policy_t policyForSignTxCollaterals(const sign_tx_signingmode_t txSigningMode);

security_policy_t policyForSignTxRequiredSigners(const sign_tx_signingmode_t txSigningMode);

security_policy_t policyForSignTxWitness(
sign_tx_signingmode_t txSigningMode,
const bip44_path_t* pathSpec,
Expand Down
118 changes: 114 additions & 4 deletions src/signTx.c
Expand Up @@ -28,6 +28,7 @@ static inline void initTxBodyCtx()
BODY_CTX->currentCertificate = 0;
BODY_CTX->currentWithdrawal = 0;
BODY_CTX->currentCollateral = 0;
BODY_CTX->currentRequiredSigners = 0;
BODY_CTX->feeReceived = false;
BODY_CTX->ttlReceived = false;
BODY_CTX->validityIntervalStartReceived = false;
Expand Down Expand Up @@ -92,7 +93,8 @@ static inline void advanceStage()
ctx->includeValidityIntervalStart,
ctx->includeMint,
ctx->includeScriptDataHash,
ctx->numCollaterals
ctx->numCollaterals,
ctx->numRequiredSigners
);
txHashBuilder_enterInputs(&BODY_CTX->txHashBuilder);
}
Expand Down Expand Up @@ -217,6 +219,16 @@ static inline void advanceStage()

case SIGN_STAGE_BODY_COLLATERALS:
ASSERT(BODY_CTX->currentCollateral == ctx->numCollaterals);
ctx->stage = SIGN_STAGE_BODY_REQUIRED_SIGNERS;
if (ctx->numRequiredSigners > 0) {
txHashBuilder_enterRequiredSigners(&BODY_CTX->txHashBuilder);
break;
}

// intentional fallthrough

case SIGN_STAGE_BODY_REQUIRED_SIGNERS:
ASSERT(BODY_CTX->currentRequiredSigners == ctx->numRequiredSigners);
txHashBuilder_addNetworkId(&BODY_CTX->txHashBuilder, ctx->commonTxData.networkId);
ctx->stage = SIGN_STAGE_CONFIRM;
break;
Expand Down Expand Up @@ -421,6 +433,7 @@ static void signTx_handleInitAPDU(uint8_t p2, uint8_t* wireDataBuffer, size_t wi
uint8_t numWithdrawals[4];
uint8_t numWitnesses[4];
uint8_t numCollaterals[4];
uint8_t numRequiredSigners[4];
}* wireHeader = (void*) wireDataBuffer;

VALIDATE(SIZEOF(*wireHeader) == wireDataSize, ERR_INVALID_DATA);
Expand Down Expand Up @@ -469,22 +482,25 @@ static void signTx_handleInitAPDU(uint8_t p2, uint8_t* wireDataBuffer, size_t wi
ASSERT_TYPE(ctx->numWithdrawals, uint16_t);
ASSERT_TYPE(ctx->numWitnesses, uint16_t);
ASSERT_TYPE(ctx->numCollaterals, uint16_t);
ASSERT_TYPE(ctx->numRequiredSigners, uint16_t);
ctx->numInputs = (uint16_t) u4be_read(wireHeader->numInputs);
ctx->numOutputs = (uint16_t) u4be_read(wireHeader->numOutputs);
ctx->numCertificates = (uint16_t) u4be_read(wireHeader->numCertificates);
ctx->numWithdrawals = (uint16_t) u4be_read(wireHeader->numWithdrawals);
ctx->numWitnesses = (uint16_t) u4be_read(wireHeader->numWitnesses);
ctx->numCollaterals = (uint16_t) u4be_read(wireHeader->numCollaterals);
ctx->numRequiredSigners = (uint16_t) u4be_read(wireHeader->numRequiredSigners);

TRACE(
"num inputs, outputs, certificates, withdrawals, witnesses, collaterals: %d %d %d %d %d %d",
ctx->numInputs, ctx->numOutputs, ctx->numCertificates, ctx->numWithdrawals, ctx->numWitnesses, ctx->numCollaterals
"num inputs, outputs, certificates, withdrawals, witnesses, collaterals, required signers: %d %d %d %d %d %d %d",
ctx->numInputs, ctx->numOutputs, ctx->numCertificates, ctx->numWithdrawals, ctx->numWitnesses, ctx->numCollaterals, ctx->numRequiredSigners
);
VALIDATE(ctx->numInputs <= SIGN_MAX_INPUTS, ERR_INVALID_DATA);
VALIDATE(ctx->numOutputs <= SIGN_MAX_OUTPUTS, ERR_INVALID_DATA);
VALIDATE(ctx->numCertificates <= SIGN_MAX_CERTIFICATES, ERR_INVALID_DATA);
VALIDATE(ctx->numWithdrawals <= SIGN_MAX_REWARD_WITHDRAWALS, ERR_INVALID_DATA);
VALIDATE(ctx->numCollaterals <= SIGN_MAX_COLLATERALS, ERR_INVALID_DATA);
VALIDATE(ctx->numRequiredSigners <= SIGN_MAX_REQUIRED_SIGNERS, ERR_INVALID_DATA);

// Current code design assumes at least one input.
// If this is to be relaxed, stage switching logic needs to be re-visited.
Expand Down Expand Up @@ -1754,6 +1770,97 @@ static void signTx_handleCollateralAPDU(uint8_t p2, uint8_t* wireDataBuffer, siz
signTx_handleCollateral_ui_runStep();
}

// ========================= REQUIRED SIGNERS ===========================

enum {
HANDLE_REQUIRED_SIGNERS_STEP_DISPLAY = 1400,
HANDLE_REQUIRED_SIGNERS_STEP_RESPOND,
HANDLE_REQUIRED_SIGNERS_STEP_INVALID,
};

static void signTx_handleRequiredSigner_ui_runStep()
{
TRACE("UI step %d", ctx->ui_step);
ui_callback_fn_t* this_fn = signTx_handleRequiredSigner_ui_runStep;

UI_STEP_BEGIN(ctx->ui_step, this_fn);

UI_STEP(HANDLE_REQUIRED_SIGNERS_STEP_DISPLAY) {
ui_displayHexBufferScreen("Required signer", BODY_CTX->stageData.requiredSigner, SIZEOF(BODY_CTX->stageData.requiredSigner), this_fn);
}

UI_STEP(HANDLE_REQUIRED_SIGNERS_STEP_RESPOND) {
respondSuccessEmptyMsg();

// Advance stage to the next input
ASSERT(BODY_CTX->currentRequiredSigners < ctx->numRequiredSigners);
BODY_CTX->currentRequiredSigners++;

if (BODY_CTX->currentRequiredSigners == ctx->numRequiredSigners) {
advanceStage();
}
}
UI_STEP_END(HANDLE_REQUIRED_SIGNERS_STEP_INVALID);
}

__noinline_due_to_stack__
static void signTx_handleRequiredSignerAPDU(uint8_t p2, uint8_t* wireDataBuffer, size_t wireDataSize)
{
TRACE_STACK_USAGE();
{
// sanity checks
CHECK_STAGE(SIGN_STAGE_BODY_REQUIRED_SIGNERS);
ASSERT(BODY_CTX->currentRequiredSigners < ctx->numRequiredSigners);

VALIDATE(p2 == P2_UNUSED, ERR_INVALID_REQUEST_PARAMETERS);
ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA);
}

{
TRACE_BUFFER(wireDataBuffer, wireDataSize);

read_view_t view = make_read_view(wireDataBuffer, wireDataBuffer + wireDataSize);
STATIC_ASSERT(SIZEOF(BODY_CTX->stageData.requiredSigner) == VKEY_LENGTH, "wrong vkey length");
view_copyWireToBuffer(BODY_CTX->stageData.requiredSigner, &view, VKEY_LENGTH);
VALIDATE(view_remainingSize(&view) == 0, ERR_INVALID_DATA);
}

{
// add to tx
TRACE("Adding required signer to tx hash");
if (BODY_CTX->stageData.requiredSigner.type == REQUIRED_SIGNER_WITH_PATH) {
uint8_t keyHash[ADDRESS_KEY_HASH_LENGTH];
bip44_pathToKeyHash(&BODY_CTX->stageData.requiredSigner.keyPath, keyHash, SIZEOF(keyHash));
txHashBuilder_addRequiredSigner(
&BODY_CTX->txHashBuilder,
keyHash, SIZEOF(keyHash)
);
} else {
txHashBuilder_addRequiredSigner(
&BODY_CTX->txHashBuilder,
BODY_CTX->stageData.requiredSigner.keyHash, SIZEOF(BODY_CTX->stageData.requiredSigner.keyHash)
);
}
}

security_policy_t policy = policyForSignTxRequiredSigners(ctx->commonTxData.txSigningMode);
TRACE("Policy: %d", (int) policy);
ENSURE_NOT_DENIED(policy);

{
// select UI steps
switch (policy) {
# define CASE(POLICY, UI_STEP) case POLICY: {ctx->ui_step=UI_STEP; break;}
CASE(POLICY_SHOW_BEFORE_RESPONSE, HANDLE_REQUIRED_SIGNERS_STEP_DISPLAY);
CASE(POLICY_ALLOW_WITHOUT_PROMPT, HANDLE_REQUIRED_SIGNERS_STEP_RESPOND);
# undef CASE
default:
THROW(ERR_NOT_IMPLEMENTED);
}
}
signTx_handleRequiredSigner_ui_runStep();
}

// ============================== CONFIRM ==============================

enum {
Expand Down Expand Up @@ -1977,6 +2084,7 @@ static subhandler_fn_t* lookup_subhandler(uint8_t p1)
CASE(0x0b, signTx_handleMintAPDU);
CASE(0x0c, signTx_handleScriptDataHashAPDU);
CASE(0x0d, signTx_handleCollateralAPDU);
CASE(0x0e, signTx_handleRequiredSignerAPDU);
CASE(0x0a, signTx_handleConfirmAPDU);
CASE(0x0f, signTx_handleWitnessAPDU);
DEFAULT(NULL)
Expand Down Expand Up @@ -2017,7 +2125,8 @@ void signTx_handleAPDU(
case SIGN_STAGE_BODY_MINT:
case SIGN_STAGE_BODY_MINT_SUBMACHINE:
case SIGN_STAGE_BODY_SCRIPT_DATA_HASH:
case SIGN_STAGE_BODY_COLLATERALS: {
case SIGN_STAGE_BODY_COLLATERALS:
case SIGN_STAGE_BODY_REQUIRED_SIGNERS: {
explicit_bzero(&BODY_CTX->stageData, SIZEOF(BODY_CTX->stageData));
break;
}
Expand Down Expand Up @@ -2063,6 +2172,7 @@ ins_sign_tx_body_context_t* accessBodyContext()
case SIGN_STAGE_BODY_MINT_SUBMACHINE:
case SIGN_STAGE_BODY_SCRIPT_DATA_HASH:
case SIGN_STAGE_BODY_COLLATERALS:
case SIGN_STAGE_BODY_REQUIRED_SIGNERS:
case SIGN_STAGE_CONFIRM:
return &(ctx->txPartCtx.body_ctx);

Expand Down
5 changes: 5 additions & 0 deletions src/signTx.h
Expand Up @@ -39,6 +39,7 @@ typedef enum {
SIGN_STAGE_BODY_MINT_SUBMACHINE = 36,
SIGN_STAGE_BODY_SCRIPT_DATA_HASH = 37,
SIGN_STAGE_BODY_COLLATERALS = 38,
SIGN_STAGE_BODY_REQUIRED_SIGNERS = 39,
SIGN_STAGE_CONFIRM = 40,
SIGN_STAGE_WITNESSES = 41,
} sign_tx_stage_t;
Expand All @@ -49,6 +50,7 @@ enum {
SIGN_MAX_CERTIFICATES = UINT16_MAX,
SIGN_MAX_REWARD_WITHDRAWALS = UINT16_MAX,
SIGN_MAX_COLLATERALS = UINT16_MAX,
SIGN_MAX_REQUIRED_SIGNERS = UINT16_MAX,
SIGN_MAX_WITNESSES = SIGN_MAX_INPUTS + SIGN_MAX_OUTPUTS + SIGN_MAX_CERTIFICATES + SIGN_MAX_REWARD_WITHDRAWALS,
};

Expand Down Expand Up @@ -112,6 +114,7 @@ typedef struct {
uint16_t currentCertificate;
uint16_t currentWithdrawal;
uint16_t currentCollateral;
uint16_t currentRequiredSigners;

bool feeReceived;
bool ttlReceived;
Expand All @@ -131,6 +134,7 @@ typedef struct {
uint64_t validityIntervalStart;
uint8_t scriptDataHash[SCRIPT_DATA_HASH_LENGTH];
sign_tx_transaction_input_t collateral;
uint8_t requiredSigner[VKEY_LENGTH];
} stageData; // TODO rename to reflect single-APDU scope

union {
Expand Down Expand Up @@ -162,6 +166,7 @@ typedef struct {
bool includeMint;
bool includeScriptDataHash;
uint16_t numCollaterals;
uint16_t numRequiredSigners;
uint16_t numWitnesses;

uint8_t auxDataHash[AUX_DATA_HASH_LENGTH];
Expand Down
73 changes: 68 additions & 5 deletions src/txHashBuilder.c
Expand Up @@ -66,7 +66,8 @@ void txHashBuilder_init(
bool includeValidityIntervalStart,
bool includeMint,
bool includeScriptDataHash,
uint16_t numCollaterals
uint16_t numCollaterals,
uint16_t numRequiredSigners
)
{
TRACE("numInputs = %u", numInputs);
Expand Down Expand Up @@ -116,10 +117,13 @@ void txHashBuilder_init(
builder->remainingCollaterals = numCollaterals;
if (numCollaterals > 0) numItems++;

builder->remainingRequiredSigners = numRequiredSigners;
if (numRequiredSigners > 0) numItems++;

// network id always included
numItems++;

ASSERT((4 <= numItems) && (numItems <= 12));
ASSERT((4 <= numItems) && (numItems <= 13));

_TRACE("Serializing tx body with %u items", numItems);
BUILDER_APPEND_CBOR(CBOR_TYPE_MAP, numItems);
Expand Down Expand Up @@ -1297,7 +1301,7 @@ void txHashBuilder_addCollateral(
}
}

void txHashBuilder_assertCanLeaveCollaterals(tx_hash_builder_t* builder)
static void txHashBuilder_assertCanLeaveCollaterals(tx_hash_builder_t* builder)
{
_TRACE("state = %u", builder->state);

Expand All @@ -1315,20 +1319,79 @@ void txHashBuilder_assertCanLeaveCollaterals(tx_hash_builder_t* builder)
case TX_HASH_BUILDER_IN_TTL:
case TX_HASH_BUILDER_IN_FEE:
txHashBuilder_assertCanLeaveScriptDataHash(builder);
ASSERT(!builder->includeScriptDataHash);
break;

default:
ASSERT(false);
}
}

void txHashBuilder_enterRequiredSigners(tx_hash_builder_t* builder)
{
_TRACE("state = %d", builder->state);

txHashBuilder_assertCanLeaveCollaterals(builder);
{
// Enter inputs
BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, TX_BODY_KEY_REQUIRED_SIGNERS);
BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, builder->remainingRequiredSigners);
}
builder->state = TX_HASH_BUILDER_IN_REQUIRED_SIGNERS;
}

void txHashBuilder_addRequiredSigner(
tx_hash_builder_t* builder,
const uint8_t* vkeyBuffer, size_t vkeySize
)
{
_TRACE("state = %d, remainingRequiredSigners = %u", builder->state, builder->remainingRequiredSigners);

ASSERT(vkeySize < BUFFER_SIZE_PARANOIA);
ASSERT(builder->state == TX_HASH_BUILDER_IN_REQUIRED_SIGNERS);
ASSERT(builder->remainingRequiredSigners > 0);
builder->remainingRequiredSigners--;
// Array(2)[
// Bytes[hash],
// Unsigned[index]
// ]
{
ASSERT(vkeySize == VKEY_LENGTH);
BUILDER_APPEND_CBOR(CBOR_TYPE_BYTES, vkeySize);
BUILDER_APPEND_DATA(vkeyBuffer, vkeySize);
}
}

static void txHashBuilder_assertCanLeaveRequiredSigners(tx_hash_builder_t* builder)
{
_TRACE("state = %u", builder->state);

switch (builder->state) {
case TX_HASH_BUILDER_IN_REQUIRED_SIGNERS:
ASSERT(builder->remainingRequiredSigners == 0);
break;

case TX_HASH_BUILDER_IN_COLLATERALS:
case TX_HASH_BUILDER_IN_SCRIPT_HASH_DATA:
case TX_HASH_BUILDER_IN_MINT:
case TX_HASH_BUILDER_IN_VALIDITY_INTERVAL_START:
case TX_HASH_BUILDER_IN_AUX_DATA:
case TX_HASH_BUILDER_IN_WITHDRAWALS:
case TX_HASH_BUILDER_IN_CERTIFICATES:
case TX_HASH_BUILDER_IN_TTL:
case TX_HASH_BUILDER_IN_FEE:
txHashBuilder_assertCanLeaveCollaterals(builder);
break;

default:
ASSERT(false);
}
}

void txHashBuilder_addNetworkId(tx_hash_builder_t* builder, uint8_t networkId)
{
_TRACE("state = %d", builder->state);

txHashBuilder_assertCanLeaveCollaterals(builder);
txHashBuilder_assertCanLeaveRequiredSigners(builder);

// add network id item into the main tx body map
BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, TX_BODY_KEY_NETWORK_ID);
Expand Down

0 comments on commit 2eff4c0

Please sign in to comment.