Skip to content

Commit

Permalink
Simplified signature verification
Browse files Browse the repository at this point in the history
  • Loading branch information
DOBEN committed Mar 26, 2024
1 parent 2cd2eb8 commit bf44947
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 56 deletions.
115 changes: 63 additions & 52 deletions examples/cis5-smart-contract-wallet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,9 @@ pub struct DepositNativeCurrencyEvent {
#[derive(Debug, Serialize, SchemaType, PartialEq, Eq)]
pub struct NonceEvent {
/// Account that signed the `PermitMessage`.
pub account: AccountAddress,
pub public_key: PublicKeyEd25519,
/// The nonce that was used in the `PermitMessage`.
pub nonce: u64,
pub nonce: u64,
}

/// The state for each address.
Expand Down Expand Up @@ -542,11 +542,7 @@ pub struct Cis2TokensInternalTransferBatch {
}

#[derive(Debug, Serialize, Clone, SchemaType)]
pub struct Cis2TokensInternalTransfer {
/// The address owning the tokens being transferred.
pub signer: PublicKeyEd25519,
/// The address owning the tokens being transferred.
pub signature: SignatureEd25519,
pub struct Cis2TokensInternalTransferMessage {
/// The address owning the tokens being transferred.
pub entry_point: OwnedEntrypointName,
/// The address owning the tokens being transferred.
Expand Down Expand Up @@ -577,12 +573,14 @@ pub struct Cis2TokensInternalTransferParameter {
pub transfers: Vec<Cis2TokensInternalTransfer>,
}

#[derive(Serialize)]
pub struct Cis2TokensInternalTransferPartial {
#[derive(Debug, Serialize, Clone, SchemaType)]
pub struct Cis2TokensInternalTransfer {
/// The address owning the tokens being transferred.
pub signer: PublicKeyEd25519,
/// The address owning the tokens being transferred.
pub signature: SignatureEd25519,
///
pub message: Cis2TokensInternalTransferMessage,
}

fn calculate_message_hash_from_bytes(
Expand All @@ -601,11 +599,45 @@ fn calculate_message_hash_from_bytes(
Ok(crypto_primitives.hash_sha2_256(&[&msg_prepend[0..48], &message_bytes].concat()).0)
}

fn validate_signature_and_increase_nonce(
message: Cis2TokensInternalTransferMessage,
signer: PublicKeyEd25519,
signature: SignatureEd25519,
host: &mut Host<State>,
crypto_primitives: &impl HasCryptoPrimitives,
ctx: &ReceiveContext,
) -> ContractResult<()> {
// Check signature is not expired.
ensure!(message.expiry_time > ctx.metadata().slot_time(), CustomContractError::Expired.into());

// Calculate the message hash.
let message_hash =
calculate_message_hash_from_bytes(&to_bytes(&message), crypto_primitives, ctx)?;

// Check signature.
let valid_signature =
crypto_primitives.verify_ed25519_signature(signer, signature, &message_hash);
ensure!(valid_signature, CustomContractError::WrongSignature.into());

// Update the nonce.
let mut entry = host.state_mut().nonces_registry.entry(signer).or_insert_with(|| 0);

// Get the current nonce.
let nonce_state = *entry;
// Bump nonce.
*entry += 1;

// Check the nonce to prevent replay attacks.
ensure_eq!(message.nonce, nonce_state, CustomContractError::NonceMismatch.into());

Ok(())
}

/// Helper function to calculate the `message_hash`.
#[receive(
contract = "smart_contract_wallet",
name = "viewMessageHash",
parameter = "Cis2TokensInternalTransfer",
parameter = "Cis2TokensInternalTransferMessage",
return_value = "[u8;32]",
error = "ContractError",
crypto_primitives,
Expand All @@ -617,21 +649,9 @@ fn contract_view_message_hash(
crypto_primitives: &impl HasCryptoPrimitives,
) -> ContractResult<[u8; 32]> {
// Parse the parameter.
let mut cursor = ctx.parameter_cursor();
// The input parameter is `PermitParam` but we only read the initial part of it
// with `PermitParamPartial`. I.e. we read the `signature` and the
// `signer`, but not the `message` here.
let _param: Cis2TokensInternalTransferPartial = cursor.get()?;

// The input parameter is `PermitParam` but we have only read the initial part
// of it with `PermitParamPartial` so far. We read in the `message` now.
// `(cursor.size() - cursor.cursor_position()` is the length of the message in
// bytes.
let mut message_bytes = vec![0; (cursor.size() - cursor.cursor_position()) as usize];
let param: Cis2TokensInternalTransferMessage = ctx.parameter_cursor().get()?;

cursor.read_exact(&mut message_bytes)?;

calculate_message_hash_from_bytes(&message_bytes, crypto_primitives, ctx)
calculate_message_hash_from_bytes(&to_bytes(&param), crypto_primitives, ctx)
}

///
Expand All @@ -657,48 +677,34 @@ fn internal_transfer_cis2_tokens(
let Cis2TokensInternalTransfer {
signer,
signature,
message,
} = cis2_tokens_internal_transfer.clone();

let Cis2TokensInternalTransferMessage {
entry_point,
expiry_time,
expiry_time: _,
nonce,
service_fee_recipient,
service_fee_token_amount,
service_fee_token_id,
service_fee_cis2_token_contract_address,
simple_transfers,
} = cis2_tokens_internal_transfer.clone();

// Update the nonce.
let mut entry = host.state_mut().nonces_registry.entry(signer).or_insert_with(|| 0);

// Get the current nonce.
let nonce_state = *entry;
// Bump nonce.
*entry += 1;
drop(entry);

// Check the nonce to prevent replay attacks.
ensure_eq!(nonce, nonce_state, CustomContractError::NonceMismatch.into());
} = message.clone();

ensure_eq!(
entry_point,
"internalTransferCis2Tokens",
CustomContractError::WrongEntryPoint.into()
);

// Check signature is not expired.
ensure!(expiry_time > ctx.metadata().slot_time(), CustomContractError::Expired.into());

// We start message_bytes at 96 to remove signature and signer
let message_bytes = &to_bytes(&cis2_tokens_internal_transfer)[96..];

// Calculate the message hash.
let message_hash =
calculate_message_hash_from_bytes(message_bytes, crypto_primitives, ctx)?;

// Check signature.
let valid_signature =
crypto_primitives.verify_ed25519_signature(signer, signature, &message_hash);
ensure!(valid_signature, CustomContractError::WrongSignature.into());
validate_signature_and_increase_nonce(
message.clone(),
signer,
signature,
host,
crypto_primitives,
ctx,
)?;

// Transfer service fee
host.state_mut().transfer_cis2_tokens(
Expand Down Expand Up @@ -727,6 +733,11 @@ fn internal_transfer_cis2_tokens(
logger,
)?;
}

logger.log(&Event::Nonce(NonceEvent {
public_key: signer,
nonce,
}))?;
}

Ok(())
Expand Down
16 changes: 12 additions & 4 deletions examples/cis5-smart-contract-wallet/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,7 @@ fn test_internal_transfer_cis2_tokens() {
let transfer_amount: TokenAmountU256 = TokenAmountU256(5.into());
let contract_token_id: TokenIdVec = TokenIdVec(vec![TOKEN_ID.0]);

let mut internal_transfer_param = Cis2TokensInternalTransfer {
signer: alice_public_key,
signature: SIGNATURE,
let message = Cis2TokensInternalTransferMessage {
entry_point: OwnedEntrypointName::new_unchecked("internalTransferCis2Tokens".to_string()),
expiry_time: Timestamp::now(),
nonce: 0u64,
Expand All @@ -133,6 +131,12 @@ fn test_internal_transfer_cis2_tokens() {
service_fee_cis2_token_contract_address: cis2_token_contract_address,
};

let mut internal_transfer_param = Cis2TokensInternalTransfer {
signer: alice_public_key,
signature: SIGNATURE,
message: message.clone(),
};

// Get the message hash to be signed.
let invoke = chain
.contract_invoke(ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload {
Expand All @@ -141,7 +145,7 @@ fn test_internal_transfer_cis2_tokens() {
receive_name: OwnedReceiveName::new_unchecked(
"smart_contract_wallet.viewMessageHash".to_string(),
),
message: OwnedParameter::from_serial(&internal_transfer_param)
message: OwnedParameter::from_serial(&message)
.expect("Should be a valid inut parameter"),
})
.expect("Should be able to query viewMessageHash");
Expand Down Expand Up @@ -204,6 +208,10 @@ fn test_internal_transfer_cis2_tokens() {
cis2_token_contract_address,
from: alice_public_key,
to: BOB_PUBLIC_KEY
}),
Event::Nonce(NonceEvent {
public_key: alice_public_key,
nonce: 0,
})
]);
}
Expand Down

0 comments on commit bf44947

Please sign in to comment.