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 functionality for account signature checking. #312

Merged
merged 8 commits into from
Jul 15, 2023
Merged
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
4 changes: 4 additions & 0 deletions .github/workflows/linter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ jobs:
- examples/smart-contract-upgrade/contract-version2/Cargo.toml
- examples/offchain-transfers/Cargo.toml
- examples/credential-registry-storage-contract/Cargo.toml
- examples/account-signature-checks/Cargo.toml
abizjak marked this conversation as resolved.
Show resolved Hide resolved

steps:
- name: Checkout sources
Expand Down Expand Up @@ -407,6 +408,7 @@ jobs:
- examples/cis2-multi-royalties/Cargo.toml
- examples/nametoken/Cargo.toml
- examples/credential-registry-storage-contract/Cargo.toml
- examples/account-signature-checks/Cargo.toml

features:
-
Expand Down Expand Up @@ -505,6 +507,7 @@ jobs:
- examples/smart-contract-upgrade/contract-version2/Cargo.toml
- examples/offchain-transfers/Cargo.toml
- examples/credential-registry-storage-contract/Cargo.toml
- examples/account-signature-checks/Cargo.toml

steps:
- name: Checkout sources
Expand Down Expand Up @@ -635,6 +638,7 @@ jobs:
- examples/smart-contract-upgrade/contract-version2/Cargo.toml
- examples/offchain-transfers/Cargo.toml
- examples/credential-registry-storage-contract/Cargo.toml
- examples/account-signature-checks/Cargo.toml

steps:
- name: Checkout sources
Expand Down
5 changes: 5 additions & 0 deletions concordium-std/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
- Support adding `#[concordium(forward = n)]`, for enum variants, where `n` is either an unsigned integer literal, `cis2_events`, `cis3_events`, `cis4_events` or an array of the same options.
Setting this attribute on a variant overrides the (de)serialization to flatten with the (de)serialization of the inner field when using derive macros such as `Serial`, `Deserial`, `DeserialWithState` and `SchemaType`.
Note that setting `#[concordium(repr(u*))]` is required when using this attribute.
- Support protocol 6 smart contract extensions. In particular the `HasHost`
trait is extended with two additional host operations, `account_public_keys`
and `check_account_signature` corresponding to the two new host functions
available in protocol 6. Two new types were added to support these operations,
`AccountSignatures` and `AccountPublicKeys`.

## concordium-std 7.0.0 (2023-06-16)

Expand Down
111 changes: 111 additions & 0 deletions concordium-std/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1814,6 +1814,10 @@ const INVOKE_QUERY_CONTRACT_BALANCE_TAG: u32 = 3;
/// Tag of the query exchange rates operation expected by the host. See
/// [prims::invoke].
const INVOKE_QUERY_EXCHANGE_RATES_TAG: u32 = 4;
/// Tag of the operation to check the account's signature [prims::invoke].
const INVOKE_CHECK_ACCOUNT_SIGNATURE_TAG: u32 = 5;
/// Tag of the query account's public keys [prims::invoke].
const INVOKE_QUERY_ACCOUNT_PUBLIC_KEYS_TAG: u32 = 6;

/// Check whether the response code from calling `invoke` is encoding a failure
/// and map out the byte used for the error code.
Expand Down Expand Up @@ -1982,6 +1986,56 @@ fn parse_query_contract_balance_response_code(
}
}

/// Decode the account public keys query response code.
///
/// - Success if the last 5 bytes are all zero:
/// - the first 3 bytes encodes the return value index.
/// - In case of failure the 4th byte is used, and encodes the enviroment
/// failure where:
/// - '0x02' encodes missing account.
fn parse_query_account_public_keys_response_code(
code: u64,
) -> Result<ExternCallResponse, QueryAccountPublicKeysError> {
if let Some(error_code) = get_invoke_failure_code(code) {
if error_code == 0x02 {
Err(QueryAccountPublicKeysError)
} else {
unsafe { crate::hint::unreachable_unchecked() }
}
} else {
// Map out the 3 bytes encoding the return value index.
let return_value_index = NonZeroU32::new((code >> 40) as u32).unwrap_abort();
Ok(ExternCallResponse::new(return_value_index))
}
}

/// Decode the response from checking account signatures.
///
/// - Success if the last 5 bytes are all zero:
/// - In case of failure the 4th byte is used, and encodes the enviroment
/// failure where:
/// - '0x02' encodes missing account.
/// - '0x0a' encodes malformed data, i.e., the call was made with incorrect
/// data.
/// - '0x0b' encodes that signature validation failed.
fn parse_check_account_signature_response_code(
code: u64,
) -> Result<bool, CheckAccountSignatureError> {
if let Some(error_code) = get_invoke_failure_code(code) {
if error_code == 0x02 {
Err(CheckAccountSignatureError::MissingAccount)
} else if error_code == 0x0a {
Err(CheckAccountSignatureError::MalformedData)
} else if error_code == 0x0b {
Ok(false)
} else {
unsafe { crate::hint::unreachable_unchecked() }
}
} else {
Ok(true)
}
}

/// Decode the exchange rate response code.
///
/// - Success if the last 5 bytes are all zero:
Expand Down Expand Up @@ -2070,6 +2124,37 @@ fn query_exchange_rates_worker() -> ExchangeRates {
ExchangeRates::deserial(&mut response).unwrap_abort()
}

/// Helper factoring out the common behaviour of `account_public_keys` for the
/// two extern hosts below.
fn query_account_public_keys_worker(address: AccountAddress) -> QueryAccountPublicKeysResult {
abizjak marked this conversation as resolved.
Show resolved Hide resolved
let data: &[u8] = address.as_ref();
let response = unsafe {
prims::invoke(INVOKE_QUERY_ACCOUNT_PUBLIC_KEYS_TAG, data.as_ptr() as *const u8, 32)
};
let mut return_value = parse_query_account_public_keys_response_code(response)?;
Ok(AccountPublicKeys::deserial(&mut return_value).unwrap_abort())
}

fn check_account_signature_worker(
address: AccountAddress,
signatures: &AccountSignatures,
data: &[u8],
) -> CheckAccountSignatureResult {
let mut buffer = address.0.to_vec();
signatures.serial(&mut buffer).unwrap_abort();
(data.len() as u32).serial(&mut buffer).unwrap_abort();
buffer.extend_from_slice(data);

let response = unsafe {
prims::invoke(
INVOKE_CHECK_ACCOUNT_SIGNATURE_TAG,
buffer.as_ptr() as *const u8,
buffer.len() as u32,
)
};
parse_check_account_signature_response_code(response)
}

impl<S> StateBuilder<S>
where
S: HasStateApi,
Expand Down Expand Up @@ -2317,6 +2402,19 @@ where
parse_upgrade_response_code(response)
}

fn account_public_keys(&self, address: AccountAddress) -> QueryAccountPublicKeysResult {
query_account_public_keys_worker(address)
}

fn check_account_signature(
&self,
address: AccountAddress,
signatures: &AccountSignatures,
data: &[u8],
) -> CheckAccountSignatureResult {
check_account_signature_worker(address, signatures, data)
}

fn state(&self) -> &S { &self.state }

fn state_mut(&mut self) -> &mut S { &mut self.state }
Expand Down Expand Up @@ -2382,6 +2480,19 @@ impl HasHost<ExternStateApi> for ExternLowLevelHost {
parse_upgrade_response_code(response)
}

fn account_public_keys(&self, address: AccountAddress) -> QueryAccountPublicKeysResult {
query_account_public_keys_worker(address)
}

fn check_account_signature(
&self,
address: AccountAddress,
signatures: &AccountSignatures,
data: &[u8],
) -> CheckAccountSignatureResult {
check_account_signature_worker(address, signatures, data)
}

abizjak marked this conversation as resolved.
Show resolved Hide resolved
#[inline(always)]
fn state(&self) -> &ExternStateApi { &self.state_api }

Expand Down
19 changes: 19 additions & 0 deletions concordium-std/src/test_infrastructure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1560,6 +1560,25 @@ impl<State: Serial + DeserialWithState<TestStateApi> + StateClone<TestStateApi>>
fn state_and_builder(&mut self) -> (&mut State, &mut StateBuilder<Self::StateApiType>) {
(&mut self.state, &mut self.state_builder)
}

fn account_public_keys(&self, _address: AccountAddress) -> QueryAccountPublicKeysResult {
unimplemented!(
"The test infrastructure will be deprecated and so does not implement new \
functionality."
)
}

fn check_account_signature(
&self,
_address: AccountAddress,
_signatures: &AccountSignatures,
_data: &[u8],
) -> CheckAccountSignatureResult {
unimplemented!(
"The test infrastructure will be deprecated and so does not implement new \
functionality."
)
}
}

impl<State: Serial + DeserialWithState<TestStateApi>> TestHost<State> {
Expand Down
24 changes: 22 additions & 2 deletions concordium-std/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
use crate::vec::Vec;
use crate::{
types::{LogError, StateError},
CallContractResult, EntryRaw, ExchangeRates, HashKeccak256, HashSha2256, HashSha3256, Key,
OccupiedEntryRaw, PublicKeyEcdsaSecp256k1, PublicKeyEd25519, QueryAccountBalanceResult,
AccountSignatures, CallContractResult, CheckAccountSignatureResult, EntryRaw, ExchangeRates,
HashKeccak256, HashSha2256, HashSha3256, Key, OccupiedEntryRaw, PublicKeyEcdsaSecp256k1,
PublicKeyEd25519, QueryAccountBalanceResult, QueryAccountPublicKeysResult,
QueryContractBalanceResult, ReadOnlyCallContractResult, SignatureEcdsaSecp256k1,
SignatureEd25519, StateBuilder, TransferResult, UpgradeResult, VacantEntryRaw,
};
Expand Down Expand Up @@ -406,6 +407,25 @@ pub trait HasHost<State>: Sized {
/// including the amount transferred as part of the invocation.
fn contract_balance(&self, address: ContractAddress) -> QueryContractBalanceResult;

/// Get the account's public keys.
fn account_public_keys(&self, address: AccountAddress) -> QueryAccountPublicKeysResult;

/// Verify the signature with account's public keys.
abizjak marked this conversation as resolved.
Show resolved Hide resolved
///
/// - `address` is the address of the account
/// - `signatures` is the [`AccountSignatures`] that are to be checked
/// - `data` is the data that the signatures are on.
///
/// The response is an error if the account is missing, and if the
/// signatures were correctly parsed then it is a boolean indicating
/// whether the check succeeded or failed.
fn check_account_signature(
&self,
address: AccountAddress,
signatures: &AccountSignatures,
data: &[u8],
) -> CheckAccountSignatureResult;

/// Get an immutable reference to the contract state.
fn state(&self) -> &State;

Expand Down
89 changes: 88 additions & 1 deletion concordium-std/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use crate as concordium_std;
use crate::{
cell::UnsafeCell, marker::PhantomData, num::NonZeroU32, Cursor, HasStateApi, Serial, Vec,
};
use concordium_contracts_common::{AccountBalance, Amount, ParseError};
use concordium_contracts_common::{
AccountBalance, AccountThreshold, Amount, ParseError, SchemaType, SignatureThreshold,
};
use core::{fmt, str::FromStr};
// Re-export for backward compatibility.
pub use concordium_contracts_common::ExchangeRates;
Expand Down Expand Up @@ -643,6 +646,25 @@ pub struct QueryAccountBalanceError;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct QueryContractBalanceError;

/// Error for querying account's public keys.
/// No account found for the provided account address.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct QueryAccountPublicKeysError;

/// Error for checking an account signature.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum CheckAccountSignatureError {
/// The account does not exist in the state.
MissingAccount,
/// The signature data could not be parsed, i.e.,
/// we could not deserialize the signature map and the data to check the
/// signature against. This should typically not happen since the
/// `concordium-std` library prevents calls that could trigger it, but
/// is here for completeness since it is a possible error returned from
/// the node.
MalformedData,
abizjak marked this conversation as resolved.
Show resolved Hide resolved
}

/// A wrapper around [`Result`] that fixes the error variant to
/// [`CallContractError`], and the result to `(bool, Option<A>)`.
/// If the result is `Ok` then the boolean indicates whether the state was
Expand Down Expand Up @@ -672,6 +694,71 @@ pub type QueryAccountBalanceResult = Result<AccountBalance, QueryAccountBalanceE
/// [`QueryContractBalanceError`] and result to [`Amount`].
pub type QueryContractBalanceResult = Result<Amount, QueryContractBalanceError>;

/// A wrapper around [`Result`] that fixes the error variant to
/// [`QueryAccountPublicKeysError`] and result to [`AccountPublicKeys`].
pub type QueryAccountPublicKeysResult = Result<AccountPublicKeys, QueryAccountPublicKeysError>;

/// A wrapper around [`Result`] that fixes the error variant to
/// [`CheckAccountSignatureError`] and result to [`bool`].
pub type CheckAccountSignatureResult = Result<bool, CheckAccountSignatureError>;

pub(crate) type KeyIndex = u8;

#[derive(crate::Serialize, Debug, SchemaType)]
/// A public indexed by the signature scheme. Currently only a
/// single scheme is supported, `ed25519`.
pub(crate) enum PublicKey {
Ed25519(PublicKeyEd25519),
}

#[derive(crate::Serialize, Debug, SchemaType)]
abizjak marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) struct CredentialPublicKeys {
#[concordium(size_length = 1)]
pub(crate) keys: crate::collections::BTreeMap<KeyIndex, PublicKey>,
abizjak marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) threshold: SignatureThreshold,
}

#[derive(crate::Serialize, Debug, SchemaType)]
/// Public keys of an account, together with the thresholds.
/// This type is deliberately made opaque, but it has serialization instances
/// since inside smart contracts there is no need to inspect the values other
/// than to pass them to verification functions.
pub struct AccountPublicKeys {
#[concordium(size_length = 1)]
pub(crate) keys: crate::collections::BTreeMap<CredentialIndex, CredentialPublicKeys>,
pub(crate) threshold: AccountThreshold,
}

pub(crate) type CredentialIndex = u8;

#[derive(crate::Serialize, Debug, SchemaType)]
#[non_exhaustive]
/// A cryptographic signature indexed by the signature scheme. Currently only a
/// single scheme is supported, `ed25519`.
pub enum Signature {
abizjak marked this conversation as resolved.
Show resolved Hide resolved
Ed25519(SignatureEd25519),
}

#[derive(crate::Serialize, Debug, SchemaType)]
#[concordium(transparent)]
/// Account signatures. This is an analogue of transaction signatures that are
/// part of transactions that get sent to the chain.
///
/// This type is deliberately made opaque, but it has serialization instances.
/// It should be thought of as a nested map, indexed on the outer layer by
/// credential indexes, and the inner map maps key indices to [`Signature`]s.
pub struct AccountSignatures {
#[concordium(size_length = 1)]
pub(crate) sigs: crate::collections::BTreeMap<CredentialIndex, CredentialSignatures>,
}

#[derive(crate::Serialize, Debug, SchemaType)]
#[concordium(transparent)]
pub(crate) struct CredentialSignatures {
#[concordium(size_length = 1)]
sigs: crate::collections::BTreeMap<KeyIndex, Signature>,
}

/// A type representing the attributes, lazily acquired from the host.
#[derive(Clone, Copy, Default)]
pub struct AttributesCursor {
Expand Down
2 changes: 2 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ the logic of the contract is reasonable, or safe.
**Do not use these contracts as-is for anything other then experimenting.**

The list of contracts is as follows
- [account-signature-checks](./account-signature-checks) A simple contract that
demonstrates how account signature checks can be performed in smart contracts.
- [two-step-transfer](./two-step-transfer) A contract that acts like an account (can send, store and accept CCD),
but requires n > 1 ordained accounts to agree to the sending of CCD before it is accepted.
- [auction](./auction) A contract implementing an simple auction.
Expand Down
Loading
Loading