#![allow(deprecated)]
use crate::{
base::{
AccountThreshold, AggregateSigPairing, AmountFraction, BakerAggregationVerifyKey,
BakerElectionVerifyKey, BakerKeyPairs, BakerSignatureVerifyKey, ContractAddress,
CredentialRegistrationID, DelegationTarget, Energy, Nonce, OpenStatus, UrlText,
},
common::{
self,
cbor::{CborDecoder, CborDeserialize, CborEncoder, CborSerializationResult, CborSerialize},
types::{Amount, KeyIndex, KeyPair, Timestamp, TransactionSignature, TransactionTime, *},
Buffer, Deserial, Get, ParseResult, Put, ReadBytesExt, SerdeDeserialize, SerdeSerialize,
Serial, Serialize,
},
constants::*,
encrypted_transfers::types::{EncryptedAmountTransferData, SecToPubAmountTransferData},
hashes,
id::types::{
AccountAddress, AccountCredentialMessage, AccountKeys, CredentialDeploymentInfo,
CredentialPublicKeys, VerifyKey,
},
protocol_level_tokens::TokenOperationsPayload,
random_oracle::RandomOracle,
smart_contracts, updates,
};
use concordium_contracts_common as concordium_std;
use concordium_std::SignatureThreshold;
use derive_more::*;
use rand::{CryptoRng, Rng};
use sha2::Digest;
use std::{collections::BTreeMap, marker::PhantomData};
use thiserror::Error;
#[derive(SerdeSerialize, SerdeDeserialize, Serial, Debug, Clone, Eq, PartialEq, AsRef, Into)]
#[serde(transparent)]
pub struct Memo {
#[serde(with = "crate::internal::byte_array_hex")]
#[size_length = 2]
bytes: Vec<u8>,
}
impl CborSerialize for Memo {
fn serialize<C: CborEncoder>(&self, encoder: C) -> CborSerializationResult<()> {
encoder.encode_bytes(&self.bytes)
}
}
impl CborDeserialize for Memo {
fn deserialize<C: CborDecoder>(decoder: C) -> CborSerializationResult<Self>
where
Self: Sized,
{
let bytes = decoder.decode_bytes()?;
Ok(Self { bytes })
}
}
#[derive(Display, Error, Debug)]
pub struct TooBig;
impl TryFrom<Vec<u8>> for Memo {
type Error = TooBig;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
if value.len() <= crate::constants::MAX_MEMO_SIZE {
Ok(Self { bytes: value })
} else {
Err(TooBig)
}
}
}
impl Deserial for Memo {
fn deserial<R: crate::common::ReadBytesExt>(source: &mut R) -> ParseResult<Self> {
let len: u16 = source.get()?;
anyhow::ensure!(
usize::from(len) <= crate::constants::MAX_MEMO_SIZE,
"Memo too big.."
);
let bytes = crate::common::deserial_bytes(source, len.into())?;
Ok(Memo { bytes })
}
}
#[derive(SerdeSerialize, SerdeDeserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, Display)]
#[serde(rename_all = "camelCase")]
pub enum TransactionType {
DeployModule,
InitContract,
Update,
Transfer,
#[deprecated(
since = "5.0.1",
note = "baking is changed in protocol 4, use ConfigureBaker or ConfigureDelegation instead"
)]
AddBaker,
#[deprecated(
since = "5.0.1",
note = "baking is changed in protocol 4, use ConfigureBaker or ConfigureDelegation instead"
)]
RemoveBaker,
#[deprecated(
since = "5.0.1",
note = "baking is changed in protocol 4, use ConfigureBaker or ConfigureDelegation instead"
)]
UpdateBakerStake,
#[deprecated(
since = "5.0.1",
note = "baking is changed in protocol 4, use ConfigureBaker or ConfigureDelegation instead"
)]
UpdateBakerRestakeEarnings,
#[deprecated(
since = "5.0.1",
note = "baking is changed in protocol 4, use ConfigureBaker or ConfigureDelegation instead"
)]
UpdateBakerKeys,
UpdateCredentialKeys,
#[deprecated(
since = "5.0.1",
note = "encrypted transfers are deprecated and partially removed since protocol version 7"
)]
EncryptedAmountTransfer,
#[deprecated(
since = "5.0.1",
note = "encrypted transfers are deprecated and partially removed since protocol version 7"
)]
TransferToEncrypted,
TransferToPublic,
TransferWithSchedule,
UpdateCredentials,
RegisterData,
TransferWithMemo,
#[deprecated(
since = "5.0.1",
note = "encrypted transfers are deprecated and partially removed since protocol version 7"
)]
EncryptedAmountTransferWithMemo,
TransferWithScheduleAndMemo,
ConfigureBaker,
ConfigureDelegation,
TokenUpdate,
}
#[derive(Debug, Error)]
#[error("{0} is not a valid TransactionType tag.")]
pub struct TransactionTypeConversionError(pub i32);
impl TryFrom<i32> for TransactionType {
type Error = TransactionTypeConversionError;
fn try_from(value: i32) -> Result<Self, Self::Error> {
Ok(match value {
0 => Self::DeployModule,
1 => Self::InitContract,
2 => Self::Update,
3 => Self::Transfer,
4 => Self::AddBaker,
5 => Self::RemoveBaker,
6 => Self::UpdateBakerStake,
7 => Self::UpdateBakerRestakeEarnings,
8 => Self::UpdateBakerKeys,
9 => Self::UpdateCredentialKeys,
10 => Self::EncryptedAmountTransfer,
11 => Self::TransferToEncrypted,
12 => Self::TransferToPublic,
13 => Self::TransferWithSchedule,
14 => Self::UpdateCredentials,
15 => Self::RegisterData,
16 => Self::TransferWithMemo,
17 => Self::EncryptedAmountTransferWithMemo,
18 => Self::TransferWithScheduleAndMemo,
19 => Self::ConfigureBaker,
20 => Self::ConfigureDelegation,
21 => Self::TokenUpdate,
n => return Err(TransactionTypeConversionError(n)),
})
}
}
#[derive(
Debug, Copy, Clone, Serial, SerdeSerialize, SerdeDeserialize, Into, From, Display, Eq, PartialEq,
)]
#[serde(transparent)]
pub struct PayloadSize {
pub(crate) size: u32,
}
impl Deserial for PayloadSize {
fn deserial<R: ReadBytesExt>(source: &mut R) -> ParseResult<Self> {
let size: u32 = source.get()?;
anyhow::ensure!(
size <= MAX_PAYLOAD_SIZE,
"Size of the payload exceeds maximum allowed."
);
Ok(PayloadSize { size })
}
}
#[derive(Debug, Clone, Serialize, SerdeSerialize, SerdeDeserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionHeader {
pub sender: AccountAddress,
pub nonce: Nonce,
pub energy_amount: Energy,
pub payload_size: PayloadSize,
pub expiry: TransactionTime,
}
#[derive(Debug, Clone, SerdeSerialize, SerdeDeserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct TransactionHeaderV1 {
pub sender: AccountAddress,
pub nonce: Nonce,
pub energy_amount: Energy,
pub payload_size: PayloadSize,
pub expiry: TransactionTime,
pub sponsor: Option<AccountAddress>,
}
const DEFINED_FEATURES_MASK: u16 = SPONSOR_MASK;
const SPONSOR_MASK: u16 = 1 << 0;
impl Serial for TransactionHeaderV1 {
fn serial<B: Buffer>(&self, out: &mut B) {
let bitmap: u16 = if self.sponsor.is_some() {
SPONSOR_MASK
} else {
0
};
let header_v0 = TransactionHeader {
sender: self.sender,
nonce: self.nonce,
energy_amount: self.energy_amount,
payload_size: self.payload_size,
expiry: self.expiry,
};
bitmap.serial(out);
header_v0.serial(out);
if let Some(sponsor) = self.sponsor {
sponsor.serial(out);
}
}
}
impl Deserial for TransactionHeaderV1 {
fn deserial<R: ReadBytesExt>(source: &mut R) -> ParseResult<Self> {
let bitmap: u16 = source.get()?;
anyhow::ensure!(
(bitmap & !DEFINED_FEATURES_MASK) == 0,
"Unknown feature bits set in transaction header: {:b}",
bitmap
);
let header_v0: TransactionHeader = source.get()?;
let sponsor = if (bitmap & SPONSOR_MASK) != 0 {
Some(source.get()?)
} else {
None
};
Ok(TransactionHeaderV1 {
sender: header_v0.sender,
nonce: header_v0.nonce,
energy_amount: header_v0.energy_amount,
payload_size: header_v0.payload_size,
expiry: header_v0.expiry,
sponsor,
})
}
}
#[derive(Debug, Clone, SerdeSerialize, SerdeDeserialize, Into, PartialEq, Eq, AsRef)]
#[serde(transparent)]
pub struct EncodedPayload {
#[serde(with = "crate::internal::byte_array_hex")]
pub(crate) payload: Vec<u8>,
}
#[derive(Debug, Error)]
#[error("The given byte array of size {actual}B exceeds maximum payload size {max}B")]
pub struct ExceedsPayloadSize {
pub actual: usize,
pub max: u32,
}
impl TryFrom<Vec<u8>> for EncodedPayload {
type Error = ExceedsPayloadSize;
fn try_from(payload: Vec<u8>) -> Result<Self, Self::Error> {
let actual = payload.len();
if actual
.try_into()
.map_or(false, |x: u32| x <= MAX_PAYLOAD_SIZE)
{
Ok(Self { payload })
} else {
Err(ExceedsPayloadSize {
actual,
max: MAX_PAYLOAD_SIZE,
})
}
}
}
impl EncodedPayload {
pub fn decode(&self) -> ParseResult<Payload> {
let mut source = std::io::Cursor::new(&self.payload);
let payload = source.get()?;
let consumed = source.position();
anyhow::ensure!(
consumed == self.payload.len() as u64,
"Payload length information is inaccurate: {} bytes of input remaining.",
self.payload.len() as u64 - consumed
);
Ok(payload)
}
}
impl Serial for EncodedPayload {
fn serial<B: Buffer>(&self, out: &mut B) {
out.write_all(&self.payload)
.expect("Writing to buffer should succeed.");
}
}
pub fn get_encoded_payload<R: ReadBytesExt>(
source: &mut R,
len: PayloadSize,
) -> ParseResult<EncodedPayload> {
let payload = crate::common::deserial_bytes(source, u32::from(len) as usize)?;
Ok(EncodedPayload { payload })
}
pub trait PayloadLike {
fn encode(&self) -> EncodedPayload;
fn encode_to_buffer<B: Buffer>(&self, out: &mut B);
}
impl PayloadLike for EncodedPayload {
fn encode(&self) -> EncodedPayload {
self.clone()
}
fn encode_to_buffer<B: Buffer>(&self, out: &mut B) {
out.write_all(&self.payload)
.expect("Writing to buffer is always safe.");
}
}
#[derive(Debug, Clone, SerdeDeserialize, SerdeSerialize)]
#[serde(rename_all = "camelCase")]
pub struct AccountTransaction<PayloadType> {
pub signature: TransactionSignature,
pub header: TransactionHeader,
pub payload: PayloadType,
}
impl<P: PayloadLike> Serial for AccountTransaction<P> {
fn serial<B: Buffer>(&self, out: &mut B) {
out.put(&self.signature);
out.put(&self.header);
self.payload.encode_to_buffer(out)
}
}
impl Deserial for AccountTransaction<EncodedPayload> {
fn deserial<R: ReadBytesExt>(source: &mut R) -> ParseResult<Self> {
let signature = source.get()?;
let header: TransactionHeader = source.get()?;
let payload = get_encoded_payload(source, header.payload_size)?;
Ok(AccountTransaction {
signature,
header,
payload,
})
}
}
impl Deserial for AccountTransaction<Payload> {
fn deserial<R: ReadBytesExt>(source: &mut R) -> ParseResult<Self> {
let signature = source.get()?;
let header: TransactionHeader = source.get()?;
let payload_len = u64::from(u32::from(header.payload_size));
let mut limited = <&mut R as std::io::Read>::take(source, payload_len);
let payload = limited.get()?;
anyhow::ensure!(
limited.limit() == 0,
"Payload length information is inaccurate: {} bytes of input remaining.",
limited.limit()
);
Ok(AccountTransaction {
signature,
header,
payload,
})
}
}
impl<P: PayloadLike> AccountTransaction<P> {
pub fn verify_transaction_signature(&self, keys: &impl HasAccountAccessStructure) -> bool {
let hash = compute_transaction_sign_hash(&self.header, &self.payload);
verify_signature_transaction_sign_hash(keys, &hash, &self.signature)
}
}
#[derive(Debug, Clone, SerdeDeserialize, SerdeSerialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AccountTransactionV1<PayloadType> {
pub signatures: TransactionSignaturesV1,
pub header: TransactionHeaderV1,
pub payload: PayloadType,
}
impl<P: PayloadLike> Serial for AccountTransactionV1<P> {
fn serial<B: Buffer>(&self, out: &mut B) {
out.put(&self.signatures);
out.put(&self.header);
self.payload.encode_to_buffer(out)
}
}
impl Deserial for AccountTransactionV1<EncodedPayload> {
fn deserial<R: ReadBytesExt>(source: &mut R) -> ParseResult<Self> {
let signatures = source.get()?;
let header: TransactionHeaderV1 = source.get()?;
let payload = get_encoded_payload(source, header.payload_size)?;
Ok(AccountTransactionV1 {
signatures,
header,
payload,
})
}
}
impl Deserial for AccountTransactionV1<Payload> {
fn deserial<R: ReadBytesExt>(source: &mut R) -> ParseResult<Self> {
let signatures: TransactionSignaturesV1 = source.get()?;
let header: TransactionHeaderV1 = source.get()?;
let payload_len = u64::from(u32::from(header.payload_size));
let mut limited = <&mut R as std::io::Read>::take(source, payload_len);
let payload = limited.get()?;
anyhow::ensure!(
limited.limit() == 0,
"Payload length information is inaccurate: {} bytes of input remaining.",
limited.limit()
);
Ok(AccountTransactionV1 {
signatures,
header,
payload,
})
}
}
impl<P: PayloadLike> AccountTransactionV1<P> {
pub fn verify_transaction_signature(
&self,
sender_keys: &impl HasAccountAccessStructure,
sponsor_keys: &impl HasAccountAccessStructure,
) -> bool {
let hash = compute_transaction_sign_hash_v1(&self.header, &self.payload);
verify_signature_transaction_sign_hash_v1(
sender_keys,
sponsor_keys,
&hash,
&self.signatures,
)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum AddBakerKeysMarker {}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum UpdateBakerKeysMarker {}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum ConfigureBakerKeysMarker {}
#[derive(Debug, Clone, SerdeDeserialize, SerdeSerialize)]
#[serde(rename_all = "camelCase")]
pub struct BakerKeysPayload<V> {
#[serde(skip)] phantom: PhantomData<V>,
pub election_verify_key: BakerElectionVerifyKey,
pub signature_verify_key: BakerSignatureVerifyKey,
pub aggregation_verify_key: BakerAggregationVerifyKey,
pub proof_sig: crate::eddsa_ed25519::Ed25519DlogProof,
pub proof_election: crate::eddsa_ed25519::Ed25519DlogProof,
pub proof_aggregation: crate::aggregate_sig::Proof<AggregateSigPairing>,
}
pub type BakerAddKeysPayload = BakerKeysPayload<AddBakerKeysMarker>;
pub type BakerUpdateKeysPayload = BakerKeysPayload<UpdateBakerKeysMarker>;
pub type ConfigureBakerKeysPayload = BakerKeysPayload<ConfigureBakerKeysMarker>;
impl<T> BakerKeysPayload<T> {
fn new_payload<R: Rng + CryptoRng>(
baker_keys: &BakerKeyPairs,
sender: AccountAddress,
challenge_prefix: &[u8],
csprng: &mut R,
) -> Self {
let mut challenge = challenge_prefix.to_vec();
sender.serial(&mut challenge);
baker_keys.election_verify.serial(&mut challenge);
baker_keys.signature_verify.serial(&mut challenge);
baker_keys.aggregation_verify.serial(&mut challenge);
let proof_election = crate::eddsa_ed25519::prove_dlog_ed25519(
csprng,
&mut RandomOracle::domain(&challenge),
&baker_keys.election_verify.verify_key,
&baker_keys.election_sign.sign_key,
);
let proof_sig = crate::eddsa_ed25519::prove_dlog_ed25519(
csprng,
&mut RandomOracle::domain(&challenge),
&baker_keys.signature_verify.verify_key,
&baker_keys.signature_sign.sign_key,
);
let proof_aggregation = baker_keys
.aggregation_sign
.prove(csprng, &mut RandomOracle::domain(&challenge));
BakerKeysPayload {
phantom: PhantomData,
election_verify_key: baker_keys.election_verify.clone(),
signature_verify_key: baker_keys.signature_verify.clone(),
aggregation_verify_key: baker_keys.aggregation_verify.clone(),
proof_sig,
proof_election,
proof_aggregation,
}
}
}
impl BakerAddKeysPayload {
pub fn new<T: Rng + CryptoRng>(
baker_keys: &BakerKeyPairs,
sender: AccountAddress,
csprng: &mut T,
) -> Self {
BakerKeysPayload::new_payload(baker_keys, sender, b"addBaker", csprng)
}
}
impl BakerUpdateKeysPayload {
pub fn new<T: Rng + CryptoRng>(
baker_keys: &BakerKeyPairs,
sender: AccountAddress,
csprng: &mut T,
) -> Self {
BakerKeysPayload::new_payload(baker_keys, sender, b"updateBakerKeys", csprng)
}
}
impl ConfigureBakerKeysPayload {
pub fn new<T: Rng + CryptoRng>(
baker_keys: &BakerKeyPairs,
sender: AccountAddress,
csprng: &mut T,
) -> Self {
BakerKeysPayload::new_payload(baker_keys, sender, b"configureBaker", csprng)
}
}
#[derive(Debug, Clone, SerdeDeserialize, SerdeSerialize)]
#[serde(rename_all = "camelCase")]
pub struct AddBakerPayload {
#[serde(flatten)]
pub keys: BakerAddKeysPayload,
pub baking_stake: Amount,
pub restake_earnings: bool,
}
#[derive(Debug, Clone, SerdeDeserialize, SerdeSerialize)]
#[serde(rename_all = "camelCase")]
pub struct InitContractPayload {
pub amount: Amount,
pub mod_ref: concordium_contracts_common::ModuleReference,
pub init_name: smart_contracts::OwnedContractName,
pub param: smart_contracts::OwnedParameter,
}
impl InitContractPayload {
pub fn size(&self) -> usize {
8 + 32 + 2 + self.init_name.as_contract_name().get_chain_name().len() +
2 + self.param.as_ref().len()
}
}
#[derive(Debug, Clone, SerdeDeserialize, SerdeSerialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateContractPayload {
pub amount: Amount,
pub address: ContractAddress,
pub receive_name: smart_contracts::OwnedReceiveName,
pub message: smart_contracts::OwnedParameter,
}
impl UpdateContractPayload {
pub fn size(&self) -> usize {
8 + 16 + 2 + self.receive_name.as_receive_name().get_chain_name().len() +
2 + self.message.as_ref().len()
}
}
#[derive(Debug, Clone, SerdeDeserialize, SerdeSerialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct ConfigureBakerPayload {
pub capital: Option<Amount>,
pub restake_earnings: Option<bool>,
pub open_for_delegation: Option<OpenStatus>,
pub keys_with_proofs: Option<ConfigureBakerKeysPayload>,
pub metadata_url: Option<UrlText>,
pub transaction_fee_commission: Option<AmountFraction>,
pub baking_reward_commission: Option<AmountFraction>,
pub finalization_reward_commission: Option<AmountFraction>,
pub suspend: Option<bool>,
}
impl ConfigureBakerPayload {
pub fn new() -> Self {
Self::default()
}
pub fn new_remove_baker() -> Self {
Self {
capital: Some(Amount::from_micro_ccd(0)),
..Self::new()
}
}
pub fn set_capital(&mut self, amount: Amount) -> &mut Self {
self.capital = Some(amount);
self
}
pub fn set_restake_earnings(&mut self, restake_earnings: bool) -> &mut Self {
self.restake_earnings = Some(restake_earnings);
self
}
pub fn set_open_for_delegation(&mut self, open_for_delegation: OpenStatus) -> &mut Self {
self.open_for_delegation = Some(open_for_delegation);
self
}
pub fn add_keys<T: Rng + CryptoRng>(
&mut self,
baker_keys: &BakerKeyPairs,
sender: AccountAddress,
csprng: &mut T,
) -> &mut Self {
let keys_with_proofs =
BakerKeysPayload::new_payload(baker_keys, sender, b"configureBaker", csprng);
self.keys_with_proofs = Some(keys_with_proofs);
self
}
pub fn set_metadata_url(&mut self, metadata_url: UrlText) -> &mut Self {
self.metadata_url = Some(metadata_url);
self
}
pub fn set_transaction_fee_commission(
&mut self,
transaction_fee_commission: AmountFraction,
) -> &mut Self {
self.transaction_fee_commission = Some(transaction_fee_commission);
self
}
pub fn set_baking_reward_commission(
&mut self,
baking_reward_commission: AmountFraction,
) -> &mut Self {
self.baking_reward_commission = Some(baking_reward_commission);
self
}
pub fn set_finalization_reward_commission(
&mut self,
finalization_reward_commission: AmountFraction,
) -> &mut Self {
self.finalization_reward_commission = Some(finalization_reward_commission);
self
}
pub fn set_suspend(&mut self, suspend: bool) -> &mut Self {
self.suspend = Some(suspend);
self
}
}
#[derive(Debug, Clone, SerdeDeserialize, SerdeSerialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct ConfigureDelegationPayload {
pub capital: Option<Amount>,
pub restake_earnings: Option<bool>,
pub delegation_target: Option<DelegationTarget>,
}
impl ConfigureDelegationPayload {
pub fn new() -> Self {
Self::default()
}
pub fn new_remove_delegation() -> Self {
Self {
capital: Some(Amount::from_micro_ccd(0)),
..Self::new()
}
}
pub fn set_capital(&mut self, amount: Amount) -> &mut Self {
self.capital = Some(amount);
self
}
pub fn set_restake_earnings(&mut self, restake_earnings: bool) -> &mut Self {
self.restake_earnings = Some(restake_earnings);
self
}
pub fn set_delegation_target(&mut self, target: DelegationTarget) -> &mut Self {
self.delegation_target = Some(target);
self
}
}
#[derive(SerdeSerialize, SerdeDeserialize, Serial, Debug, Clone, AsRef, Into, AsMut)]
#[serde(transparent)]
pub struct RegisteredData {
#[serde(with = "crate::internal::byte_array_hex")]
#[size_length = 2]
bytes: Vec<u8>,
}
#[derive(Debug, Error, Copy, Clone)]
#[error("Data is too large to be registered ({actual_size}).")]
pub struct TooLargeError {
actual_size: usize,
}
impl TryFrom<Vec<u8>> for RegisteredData {
type Error = TooLargeError;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
let actual_size = bytes.len();
if actual_size <= crate::constants::MAX_REGISTERED_DATA_SIZE {
Ok(RegisteredData { bytes })
} else {
Err(TooLargeError { actual_size })
}
}
}
impl From<[u8; 32]> for RegisteredData {
fn from(data: [u8; 32]) -> Self {
Self {
bytes: data.to_vec(),
}
}
}
impl<M> From<crate::hashes::HashBytes<M>> for RegisteredData {
fn from(data: crate::hashes::HashBytes<M>) -> Self {
Self {
bytes: data.as_ref().to_vec(),
}
}
}
impl Deserial for RegisteredData {
fn deserial<R: crate::common::ReadBytesExt>(source: &mut R) -> ParseResult<Self> {
let len: u16 = source.get()?;
anyhow::ensure!(
usize::from(len) <= crate::constants::MAX_REGISTERED_DATA_SIZE,
"Data too big to register."
);
let bytes = crate::common::deserial_bytes(source, len.into())?;
Ok(RegisteredData { bytes })
}
}
pub type AccountCredentialsMap = BTreeMap<
CredentialIndex,
CredentialDeploymentInfo<
crate::id::constants::IpPairing,
crate::id::constants::ArCurve,
crate::id::constants::AttributeKind,
>,
>;
#[derive(Debug, Clone, SerdeDeserialize, SerdeSerialize)]
#[serde(rename_all = "camelCase")]
pub enum Payload {
DeployModule {
#[serde(rename = "mod")]
module: smart_contracts::WasmModule,
},
InitContract {
#[serde(flatten)]
payload: InitContractPayload,
},
Update {
#[serde(flatten)]
payload: UpdateContractPayload,
},
Transfer {
to_address: AccountAddress,
amount: Amount,
},
AddBaker {
#[serde(flatten)]
payload: Box<AddBakerPayload>,
},
RemoveBaker,
UpdateBakerStake {
stake: Amount,
},
UpdateBakerRestakeEarnings {
restake_earnings: bool,
},
UpdateBakerKeys {
#[serde(flatten)]
payload: Box<BakerUpdateKeysPayload>,
},
UpdateCredentialKeys {
cred_id: CredentialRegistrationID,
keys: CredentialPublicKeys,
},
#[deprecated(
since = "5.0.1",
note = "encrypted transfers are deprecated and partially removed since protocol version 7"
)]
EncryptedAmountTransfer {
to: AccountAddress,
data: Box<EncryptedAmountTransferData<EncryptedAmountsCurve>>,
},
#[deprecated(
since = "5.0.1",
note = "encrypted transfers are deprecated and partially removed since protocol version 7"
)]
TransferToEncrypted {
amount: Amount,
},
TransferToPublic {
#[serde(flatten)]
data: Box<SecToPubAmountTransferData<EncryptedAmountsCurve>>,
},
TransferWithSchedule {
to: AccountAddress,
schedule: Vec<(Timestamp, Amount)>,
},
UpdateCredentials {
new_cred_infos: AccountCredentialsMap,
remove_cred_ids: Vec<CredentialRegistrationID>,
new_threshold: AccountThreshold,
},
RegisterData {
data: RegisteredData,
},
TransferWithMemo {
to_address: AccountAddress,
memo: Memo,
amount: Amount,
},
#[deprecated(
since = "5.0.1",
note = "encrypted transfers are deprecated and partially removed since protocol version 7"
)]
EncryptedAmountTransferWithMemo {
to: AccountAddress,
memo: Memo,
data: Box<EncryptedAmountTransferData<EncryptedAmountsCurve>>,
},
TransferWithScheduleAndMemo {
to: AccountAddress,
memo: Memo,
schedule: Vec<(Timestamp, Amount)>,
},
ConfigureBaker {
#[serde(flatten)]
data: Box<ConfigureBakerPayload>,
},
ConfigureDelegation {
#[serde(flatten)]
data: ConfigureDelegationPayload,
},
TokenUpdate {
#[serde(flatten)]
payload: TokenOperationsPayload,
},
}
impl Payload {
pub fn transaction_type(&self) -> TransactionType {
match self {
Payload::DeployModule { .. } => TransactionType::DeployModule,
Payload::InitContract { .. } => TransactionType::InitContract,
Payload::Update { .. } => TransactionType::Update,
Payload::Transfer { .. } => TransactionType::Transfer,
Payload::AddBaker { .. } => TransactionType::AddBaker,
Payload::RemoveBaker { .. } => TransactionType::RemoveBaker,
Payload::UpdateBakerStake { .. } => TransactionType::UpdateBakerStake,
Payload::UpdateBakerRestakeEarnings { .. } => {
TransactionType::UpdateBakerRestakeEarnings
}
Payload::UpdateBakerKeys { .. } => TransactionType::UpdateBakerKeys,
Payload::UpdateCredentialKeys { .. } => TransactionType::UpdateCredentialKeys,
Payload::EncryptedAmountTransfer { .. } => TransactionType::EncryptedAmountTransfer,
Payload::TransferToEncrypted { .. } => TransactionType::TransferToEncrypted,
Payload::TransferToPublic { .. } => TransactionType::TransferToPublic,
Payload::TransferWithSchedule { .. } => TransactionType::TransferWithSchedule,
Payload::UpdateCredentials { .. } => TransactionType::UpdateCredentials,
Payload::RegisterData { .. } => TransactionType::RegisterData,
Payload::TransferWithMemo { .. } => TransactionType::TransferWithMemo,
Payload::EncryptedAmountTransferWithMemo { .. } => {
TransactionType::EncryptedAmountTransferWithMemo
}
Payload::TransferWithScheduleAndMemo { .. } => {
TransactionType::TransferWithScheduleAndMemo
}
Payload::ConfigureBaker { .. } => TransactionType::ConfigureBaker,
Payload::ConfigureDelegation { .. } => TransactionType::ConfigureDelegation,
Payload::TokenUpdate { .. } => TransactionType::TokenUpdate,
}
}
}
impl Serial for Payload {
fn serial<B: Buffer>(&self, out: &mut B) {
match &self {
Payload::DeployModule { module } => {
out.put(&0u8);
out.put(module);
}
Payload::InitContract { payload } => {
out.put(&1u8);
out.put(payload)
}
Payload::Update { payload } => {
out.put(&2u8);
out.put(payload)
}
Payload::Transfer { to_address, amount } => {
out.put(&3u8);
out.put(to_address);
out.put(amount);
}
Payload::AddBaker { payload } => {
out.put(&4u8);
out.put(payload);
}
Payload::RemoveBaker => {
out.put(&5u8);
}
Payload::UpdateBakerStake { stake } => {
out.put(&6u8);
out.put(stake);
}
Payload::UpdateBakerRestakeEarnings { restake_earnings } => {
out.put(&7u8);
out.put(restake_earnings);
}
Payload::UpdateBakerKeys { payload } => {
out.put(&8u8);
out.put(payload)
}
Payload::UpdateCredentialKeys { cred_id, keys } => {
out.put(&13u8);
out.put(cred_id);
out.put(keys);
}
Payload::EncryptedAmountTransfer { to, data } => {
out.put(&16u8);
out.put(to);
out.put(data);
}
Payload::TransferToEncrypted { amount } => {
out.put(&17u8);
out.put(amount);
}
Payload::TransferToPublic { data } => {
out.put(&18u8);
out.put(data);
}
Payload::TransferWithSchedule { to, schedule } => {
out.put(&19u8);
out.put(to);
out.put(&(schedule.len() as u8));
crate::common::serial_vector_no_length(schedule, out);
}
Payload::UpdateCredentials {
new_cred_infos,
remove_cred_ids,
new_threshold,
} => {
out.put(&20u8);
out.put(&(new_cred_infos.len() as u8));
crate::common::serial_map_no_length(new_cred_infos, out);
out.put(&(remove_cred_ids.len() as u8));
crate::common::serial_vector_no_length(remove_cred_ids, out);
out.put(new_threshold);
}
Payload::RegisterData { data } => {
out.put(&21u8);
out.put(data);
}
Payload::TransferWithMemo {
to_address,
memo,
amount,
} => {
out.put(&22u8);
out.put(to_address);
out.put(memo);
out.put(amount);
}
Payload::EncryptedAmountTransferWithMemo { to, memo, data } => {
out.put(&23u8);
out.put(to);
out.put(memo);
out.put(data);
}
Payload::TransferWithScheduleAndMemo { to, memo, schedule } => {
out.put(&24u8);
out.put(to);
out.put(memo);
out.put(&(schedule.len() as u8));
crate::common::serial_vector_no_length(schedule, out);
}
Payload::ConfigureBaker { data } => {
out.put(&25u8);
let set_if = |n, b| if b { 1u16 << n } else { 0 };
let bitmap: u16 = set_if(0, data.capital.is_some())
| set_if(1, data.restake_earnings.is_some())
| set_if(2, data.open_for_delegation.is_some())
| set_if(3, data.keys_with_proofs.is_some())
| set_if(4, data.metadata_url.is_some())
| set_if(5, data.transaction_fee_commission.is_some())
| set_if(6, data.baking_reward_commission.is_some())
| set_if(7, data.finalization_reward_commission.is_some())
| set_if(8, data.suspend.is_some());
out.put(&bitmap);
if let Some(capital) = &data.capital {
out.put(capital);
}
if let Some(restake_earnings) = &data.restake_earnings {
out.put(restake_earnings);
}
if let Some(open_for_delegation) = &data.open_for_delegation {
out.put(open_for_delegation);
}
if let Some(keys_with_proofs) = &data.keys_with_proofs {
out.put(&keys_with_proofs.election_verify_key);
out.put(&keys_with_proofs.proof_election);
out.put(&keys_with_proofs.signature_verify_key);
out.put(&keys_with_proofs.proof_sig);
out.put(&keys_with_proofs.aggregation_verify_key);
out.put(&keys_with_proofs.proof_aggregation);
}
if let Some(metadata_url) = &data.metadata_url {
out.put(metadata_url);
}
if let Some(transaction_fee_commission) = &data.transaction_fee_commission {
out.put(transaction_fee_commission);
}
if let Some(baking_reward_commission) = &data.baking_reward_commission {
out.put(baking_reward_commission);
}
if let Some(finalization_reward_commission) = &data.finalization_reward_commission {
out.put(finalization_reward_commission);
}
if let Some(suspend) = &data.suspend {
out.put(suspend);
}
}
Payload::ConfigureDelegation {
data:
ConfigureDelegationPayload {
capital,
restake_earnings,
delegation_target,
},
} => {
out.put(&26u8);
let set_if = |n, b| if b { 1u16 << n } else { 0 };
let bitmap: u16 = set_if(0, capital.is_some())
| set_if(1, restake_earnings.is_some())
| set_if(2, delegation_target.is_some());
out.put(&bitmap);
if let Some(capital) = capital {
out.put(capital);
}
if let Some(restake_earnings) = restake_earnings {
out.put(restake_earnings);
}
if let Some(delegation_target) = delegation_target {
out.put(delegation_target);
}
}
Payload::TokenUpdate { payload } => {
out.put(&27u8);
out.put(&payload.token_id);
out.put(&payload.operations);
}
}
}
}
impl Deserial for Payload {
fn deserial<R: ReadBytesExt>(source: &mut R) -> ParseResult<Self> {
let tag: u8 = source.get()?;
match tag {
0 => {
let module = source.get()?;
Ok(Payload::DeployModule { module })
}
1 => {
let payload = source.get()?;
Ok(Payload::InitContract { payload })
}
2 => {
let payload = source.get()?;
Ok(Payload::Update { payload })
}
3 => {
let to_address = source.get()?;
let amount = source.get()?;
Ok(Payload::Transfer { to_address, amount })
}
4 => {
let payload_data = source.get()?;
Ok(Payload::AddBaker {
payload: Box::new(payload_data),
})
}
5 => Ok(Payload::RemoveBaker),
6 => {
let stake = source.get()?;
Ok(Payload::UpdateBakerStake { stake })
}
7 => {
let restake_earnings = source.get()?;
Ok(Payload::UpdateBakerRestakeEarnings { restake_earnings })
}
8 => {
let payload_data = source.get()?;
Ok(Payload::UpdateBakerKeys {
payload: Box::new(payload_data),
})
}
13 => {
let cred_id = source.get()?;
let keys = source.get()?;
Ok(Payload::UpdateCredentialKeys { cred_id, keys })
}
16 => {
let to = source.get()?;
let data = source.get()?;
Ok(Payload::EncryptedAmountTransfer { to, data })
}
17 => {
let amount = source.get()?;
Ok(Payload::TransferToEncrypted { amount })
}
18 => {
let data_data = source.get()?;
Ok(Payload::TransferToPublic {
data: Box::new(data_data),
})
}
19 => {
let to = source.get()?;
let len: u8 = source.get()?;
let schedule = crate::common::deserial_vector_no_length(source, len.into())?;
Ok(Payload::TransferWithSchedule { to, schedule })
}
20 => {
let cred_infos_len: u8 = source.get()?;
let new_cred_infos =
crate::common::deserial_map_no_length(source, cred_infos_len.into())?;
let remove_cred_ids_len: u8 = source.get()?;
let remove_cred_ids =
crate::common::deserial_vector_no_length(source, remove_cred_ids_len.into())?;
let new_threshold = source.get()?;
Ok(Payload::UpdateCredentials {
new_cred_infos,
remove_cred_ids,
new_threshold,
})
}
21 => {
let data = source.get()?;
Ok(Payload::RegisterData { data })
}
22 => {
let to_address = source.get()?;
let memo = source.get()?;
let amount = source.get()?;
Ok(Payload::TransferWithMemo {
to_address,
memo,
amount,
})
}
23 => {
let to = source.get()?;
let memo = source.get()?;
let data = source.get()?;
Ok(Payload::EncryptedAmountTransferWithMemo { to, memo, data })
}
24 => {
let to = source.get()?;
let memo = source.get()?;
let len: u8 = source.get()?;
let schedule = crate::common::deserial_vector_no_length(source, len.into())?;
Ok(Payload::TransferWithScheduleAndMemo { to, memo, schedule })
}
25 => {
let bitmap: u16 = source.get()?;
let mut capital = None;
let mut restake_earnings = None;
let mut open_for_delegation = None;
let mut keys_with_proofs = None;
let mut metadata_url = None;
let mut transaction_fee_commission = None;
let mut baking_reward_commission = None;
let mut finalization_reward_commission = None;
let mut suspend = None;
if bitmap & 1 != 0 {
capital = Some(source.get()?);
}
if bitmap & (1 << 1) != 0 {
restake_earnings = Some(source.get()?);
}
if bitmap & (1 << 2) != 0 {
open_for_delegation = Some(source.get()?);
}
if bitmap & (1 << 3) != 0 {
let election_verify_key = source.get()?;
let proof_election = source.get()?;
let signature_verify_key = source.get()?;
let proof_sig = source.get()?;
let aggregation_verify_key = source.get()?;
let proof_aggregation = source.get()?;
keys_with_proofs = Some(BakerKeysPayload {
phantom: PhantomData,
election_verify_key,
signature_verify_key,
aggregation_verify_key,
proof_sig,
proof_election,
proof_aggregation,
});
}
if bitmap & (1 << 4) != 0 {
metadata_url = Some(source.get()?);
}
if bitmap & (1 << 5) != 0 {
transaction_fee_commission = Some(source.get()?);
}
if bitmap & (1 << 6) != 0 {
baking_reward_commission = Some(source.get()?);
}
if bitmap & (1 << 7) != 0 {
finalization_reward_commission = Some(source.get()?);
}
if bitmap & (1 << 8) != 0 {
suspend = Some(source.get()?);
}
let data = Box::new(ConfigureBakerPayload {
capital,
restake_earnings,
open_for_delegation,
keys_with_proofs,
metadata_url,
transaction_fee_commission,
baking_reward_commission,
finalization_reward_commission,
suspend,
});
Ok(Payload::ConfigureBaker { data })
}
26 => {
let mut data = ConfigureDelegationPayload::default();
let bitmap: u16 = source.get()?;
anyhow::ensure!(
bitmap & 0b111 == bitmap,
"Incorrect bitmap for configure delegation."
);
if bitmap & 1 != 0 {
data.capital = Some(source.get()?);
}
if bitmap & (1 << 1) != 0 {
data.restake_earnings = Some(source.get()?);
}
if bitmap & (1 << 2) != 0 {
data.delegation_target = Some(source.get()?);
}
Ok(Payload::ConfigureDelegation { data })
}
27 => {
let token_id = source.get()?;
let operations = source.get()?;
let payload = TokenOperationsPayload {
token_id,
operations,
};
Ok(Payload::TokenUpdate { payload })
}
_ => {
anyhow::bail!("Unsupported transaction payload tag {}", tag)
}
}
}
}
impl PayloadLike for Payload {
fn encode(&self) -> EncodedPayload {
let payload = crate::common::to_bytes(&self);
EncodedPayload { payload }
}
fn encode_to_buffer<B: Buffer>(&self, out: &mut B) {
out.put(&self)
}
}
impl EncodedPayload {
pub fn size(&self) -> PayloadSize {
let size = self.payload.len() as u32;
PayloadSize { size }
}
}
pub fn compute_transaction_sign_hash(
header: &TransactionHeader,
payload: &impl PayloadLike,
) -> hashes::TransactionSignHash {
let mut hasher = sha2::Sha256::new();
hasher.put(header);
payload.encode_to_buffer(&mut hasher);
hashes::HashBytes::new(hasher.result())
}
const TRANSACTION_HEADER_PREFIX_V1: [u8; 32] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
];
pub fn compute_transaction_sign_hash_v1(
header: &TransactionHeaderV1,
payload: &impl PayloadLike,
) -> hashes::TransactionSignHash {
let mut hasher = sha2::Sha256::new();
hasher.put(&TRANSACTION_HEADER_PREFIX_V1);
hasher.put(header);
payload.encode_to_buffer(&mut hasher);
hashes::HashBytes::new(hasher.result())
}
pub trait TransactionSigner {
fn sign_transaction_hash(
&self,
hash_to_sign: &hashes::TransactionSignHash,
) -> TransactionSignature;
}
pub trait ExactSizeTransactionSigner: TransactionSigner {
fn num_keys(&self) -> u32;
}
impl<S: TransactionSigner> TransactionSigner for std::sync::Arc<S> {
fn sign_transaction_hash(
&self,
hash_to_sign: &hashes::TransactionSignHash,
) -> TransactionSignature {
self.as_ref().sign_transaction_hash(hash_to_sign)
}
}
impl<S: ExactSizeTransactionSigner> ExactSizeTransactionSigner for std::sync::Arc<S> {
fn num_keys(&self) -> u32 {
self.as_ref().num_keys()
}
}
impl<S: TransactionSigner> TransactionSigner for std::rc::Rc<S> {
fn sign_transaction_hash(
&self,
hash_to_sign: &hashes::TransactionSignHash,
) -> TransactionSignature {
self.as_ref().sign_transaction_hash(hash_to_sign)
}
}
impl<S: ExactSizeTransactionSigner> ExactSizeTransactionSigner for std::rc::Rc<S> {
fn num_keys(&self) -> u32 {
self.as_ref().num_keys()
}
}
impl TransactionSigner for AccountKeys {
fn sign_transaction_hash(
&self,
hash_to_sign: &hashes::TransactionSignHash,
) -> TransactionSignature {
let iter = self
.keys
.iter()
.take(usize::from(u8::from(self.threshold)))
.map(|(k, v)| {
(k, {
let num = u8::from(v.threshold);
v.keys.iter().take(num.into())
})
});
let mut signatures = BTreeMap::<CredentialIndex, BTreeMap<KeyIndex, _>>::new();
for (ci, cred_keys) in iter {
let cred_sigs = cred_keys
.into_iter()
.map(|(ki, kp)| (*ki, kp.sign(hash_to_sign.as_ref()).into()))
.collect::<BTreeMap<_, _>>();
signatures.insert(*ci, cred_sigs);
}
TransactionSignature { signatures }
}
}
impl ExactSizeTransactionSigner for AccountKeys {
fn num_keys(&self) -> u32 {
self.keys
.values()
.take(usize::from(u8::from(self.threshold)))
.map(|v| u32::from(u8::from(v.threshold)))
.sum::<u32>()
}
}
impl<'a, X: TransactionSigner> TransactionSigner for &'a X {
fn sign_transaction_hash(
&self,
hash_to_sign: &hashes::TransactionSignHash,
) -> TransactionSignature {
(*self).sign_transaction_hash(hash_to_sign)
}
}
impl<'a, X: ExactSizeTransactionSigner> ExactSizeTransactionSigner for &'a X {
fn num_keys(&self) -> u32 {
(*self).num_keys()
}
}
impl TransactionSigner for BTreeMap<CredentialIndex, BTreeMap<KeyIndex, KeyPair>> {
fn sign_transaction_hash(
&self,
hash_to_sign: &hashes::TransactionSignHash,
) -> TransactionSignature {
let mut signatures = BTreeMap::<CredentialIndex, BTreeMap<KeyIndex, _>>::new();
for (ci, cred_keys) in self {
let cred_sigs = cred_keys
.iter()
.map(|(ki, kp)| (*ki, kp.sign(hash_to_sign.as_ref()).into()))
.collect::<BTreeMap<_, _>>();
signatures.insert(*ci, cred_sigs);
}
TransactionSignature { signatures }
}
}
impl ExactSizeTransactionSigner for BTreeMap<CredentialIndex, BTreeMap<KeyIndex, KeyPair>> {
fn num_keys(&self) -> u32 {
self.values().map(|v| v.len() as u32).sum::<u32>()
}
}
pub fn sign_transaction<S: TransactionSigner, P: PayloadLike>(
signer: &S,
header: TransactionHeader,
payload: P,
) -> AccountTransaction<P> {
let hash_to_sign = compute_transaction_sign_hash(&header, &payload);
let signature = signer.sign_transaction_hash(&hash_to_sign);
AccountTransaction {
signature,
header,
payload,
}
}
pub trait HasAccountAccessStructure {
fn threshold(&self) -> AccountThreshold;
fn credential_keys(&self, idx: CredentialIndex) -> Option<&CredentialPublicKeys>;
}
#[derive(PartialEq, Eq, Debug, Clone, concordium_std::Serialize)]
pub struct AccountAccessStructure {
#[concordium(size_length = 1)]
pub keys: BTreeMap<CredentialIndex, CredentialPublicKeys>,
pub threshold: AccountThreshold,
}
pub type AccountStructure<'a> = &'a [(
CredentialIndex,
SignatureThreshold,
&'a [(KeyIndex, ed25519_dalek::VerifyingKey)],
)];
impl AccountAccessStructure {
pub fn new(account_threshold: AccountThreshold, structure: AccountStructure) -> Self {
let mut map: BTreeMap<CredentialIndex, CredentialPublicKeys> = BTreeMap::new();
for credential_structure in structure {
let mut inner_map: BTreeMap<KeyIndex, VerifyKey> = BTreeMap::new();
for key_structure in credential_structure.2 {
inner_map.insert(
key_structure.0,
VerifyKey::Ed25519VerifyKey(key_structure.1),
);
}
map.insert(
credential_structure.0,
CredentialPublicKeys {
keys: inner_map,
threshold: credential_structure.1,
},
);
}
AccountAccessStructure {
keys: map,
threshold: account_threshold,
}
}
pub fn singleton(public_key: ed25519_dalek::VerifyingKey) -> Self {
Self::new(
AccountThreshold::ONE,
&[(0.into(), SignatureThreshold::ONE, &[(0.into(), public_key)])],
)
}
}
impl From<&AccountKeys> for AccountAccessStructure {
fn from(value: &AccountKeys) -> Self {
Self {
threshold: value.threshold,
keys: value
.keys
.iter()
.map(|(k, v)| {
(
*k,
CredentialPublicKeys {
keys: v
.keys
.iter()
.map(|(ki, kp)| {
(
*ki,
VerifyKey::Ed25519VerifyKey(kp.as_ref().verifying_key()),
)
})
.collect(),
threshold: v.threshold,
},
)
})
.collect(),
}
}
}
impl HasAccountAccessStructure for AccountAccessStructure {
fn threshold(&self) -> AccountThreshold {
self.threshold
}
fn credential_keys(&self, idx: CredentialIndex) -> Option<&CredentialPublicKeys> {
self.keys.get(&idx)
}
}
impl Serial for AccountAccessStructure {
fn serial<B: Buffer>(&self, out: &mut B) {
(self.keys.len() as u8).serial(out);
for (k, v) in self.keys.iter() {
k.serial(out);
v.serial(out);
}
self.threshold.serial(out)
}
}
impl Deserial for AccountAccessStructure {
fn deserial<R: ReadBytesExt>(source: &mut R) -> ParseResult<Self> {
let len = u8::deserial(source)?;
let keys = common::deserial_map_no_length(source, len.into())?;
let threshold = source.get()?;
Ok(Self { threshold, keys })
}
}
impl AccountAccessStructure {
pub fn num_keys(&self) -> u32 {
self.keys.values().map(|m| m.keys.len() as u32).sum()
}
}
pub fn verify_signature_transaction_sign_hash(
keys: &impl HasAccountAccessStructure,
hash: &hashes::TransactionSignHash,
signature: &TransactionSignature,
) -> bool {
verify_data_signature(keys, hash, &signature.signatures)
}
pub fn verify_signature_transaction_sign_hash_v1(
sender_keys: &impl HasAccountAccessStructure,
sponsor_keys: &impl HasAccountAccessStructure,
hash: &hashes::TransactionSignHash,
signatures: &TransactionSignaturesV1,
) -> bool {
let sender_sig_ok = verify_data_signature(sender_keys, hash, &signatures.sender.signatures);
let sponsor_sig_ok = signatures.sponsor.as_ref().map_or(true, |sig| {
verify_data_signature(sponsor_keys, hash, &sig.signatures)
});
sender_sig_ok && sponsor_sig_ok
}
pub fn verify_data_signature<T: ?Sized + AsRef<[u8]>>(
keys: &impl HasAccountAccessStructure,
data: &T,
signatures: &BTreeMap<CredentialIndex, BTreeMap<KeyIndex, Signature>>,
) -> bool {
if usize::from(u8::from(keys.threshold())) > signatures.len() {
return false;
}
for (&ci, cred_sigs) in signatures.iter() {
if let Some(cred_keys) = keys.credential_keys(ci) {
if usize::from(u8::from(cred_keys.threshold)) > cred_sigs.len() {
return false;
}
for (&ki, sig) in cred_sigs {
if let Some(pk) = cred_keys.get(ki) {
if !pk.verify(data, sig) {
return false;
}
} else {
return false;
}
}
} else {
return false;
}
}
true
}
#[derive(Debug, Clone)]
pub enum BlockItem<PayloadType> {
AccountTransaction(AccountTransaction<PayloadType>),
CredentialDeployment(
Box<
AccountCredentialMessage<
crate::id::constants::IpPairing,
crate::id::constants::ArCurve,
crate::id::constants::AttributeKind,
>,
>,
),
UpdateInstruction(updates::UpdateInstruction),
AccountTransactionV1(AccountTransactionV1<PayloadType>),
}
impl<PayloadType> From<AccountTransaction<PayloadType>> for BlockItem<PayloadType> {
fn from(at: AccountTransaction<PayloadType>) -> Self {
Self::AccountTransaction(at)
}
}
impl<PayloadType>
From<
AccountCredentialMessage<
crate::id::constants::IpPairing,
crate::id::constants::ArCurve,
crate::id::constants::AttributeKind,
>,
> for BlockItem<PayloadType>
{
fn from(
at: AccountCredentialMessage<
crate::id::constants::IpPairing,
crate::id::constants::ArCurve,
crate::id::constants::AttributeKind,
>,
) -> Self {
Self::CredentialDeployment(Box::new(at))
}
}
impl<PayloadType> From<updates::UpdateInstruction> for BlockItem<PayloadType> {
fn from(ui: updates::UpdateInstruction) -> Self {
Self::UpdateInstruction(ui)
}
}
impl<PayloadType> BlockItem<PayloadType> {
pub fn hash(&self) -> hashes::TransactionHash
where
BlockItem<PayloadType>: Serial,
{
let mut hasher = sha2::Sha256::new();
hasher.put(&self);
hashes::HashBytes::new(hasher.result())
}
}
impl<V> Serial for BakerKeysPayload<V> {
fn serial<B: Buffer>(&self, out: &mut B) {
out.put(&self.election_verify_key);
out.put(&self.signature_verify_key);
out.put(&self.aggregation_verify_key);
out.put(&self.proof_sig);
out.put(&self.proof_election);
out.put(&self.proof_aggregation);
}
}
impl<V> Deserial for BakerKeysPayload<V> {
fn deserial<R: ReadBytesExt>(source: &mut R) -> ParseResult<Self> {
let election_verify_key = source.get()?;
let signature_verify_key = source.get()?;
let aggregation_verify_key = source.get()?;
let proof_sig = source.get()?;
let proof_election = source.get()?;
let proof_aggregation = source.get()?;
Ok(Self {
phantom: PhantomData,
election_verify_key,
signature_verify_key,
aggregation_verify_key,
proof_sig,
proof_election,
proof_aggregation,
})
}
}
impl Serial for AddBakerPayload {
fn serial<B: Buffer>(&self, out: &mut B) {
out.put(&self.keys);
out.put(&self.baking_stake);
out.put(&self.restake_earnings);
}
}
impl Deserial for AddBakerPayload {
fn deserial<R: ReadBytesExt>(source: &mut R) -> ParseResult<Self> {
let keys = source.get()?;
let baking_stake = source.get()?;
let restake_earnings = source.get()?;
Ok(Self {
keys,
baking_stake,
restake_earnings,
})
}
}
impl Serial for InitContractPayload {
fn serial<B: Buffer>(&self, out: &mut B) {
out.put(&self.amount);
out.put(&self.mod_ref);
out.put(&self.init_name);
out.put(&self.param);
}
}
impl Deserial for InitContractPayload {
fn deserial<R: ReadBytesExt>(source: &mut R) -> ParseResult<Self> {
let amount = source.get()?;
let mod_ref = source.get()?;
let init_name = source.get()?;
let param = source.get()?;
Ok(InitContractPayload {
amount,
mod_ref,
init_name,
param,
})
}
}
impl Serial for UpdateContractPayload {
fn serial<B: Buffer>(&self, out: &mut B) {
out.put(&self.amount);
out.put(&self.address);
out.put(&self.receive_name);
out.put(&self.message);
}
}
impl Deserial for UpdateContractPayload {
fn deserial<R: ReadBytesExt>(source: &mut R) -> ParseResult<Self> {
let amount = source.get()?;
let address = source.get()?;
let receive_name = source.get()?;
let message = source.get()?;
Ok(UpdateContractPayload {
amount,
address,
receive_name,
message,
})
}
}
impl<P: PayloadLike> Serial for BlockItem<P> {
fn serial<B: Buffer>(&self, out: &mut B) {
match &self {
BlockItem::AccountTransaction(at) => {
out.put(&0u8);
out.put(at)
}
BlockItem::CredentialDeployment(acdi) => {
out.put(&1u8);
out.put(acdi);
}
BlockItem::UpdateInstruction(ui) => {
out.put(&2u8);
out.put(ui);
}
BlockItem::AccountTransactionV1(atv1) => {
out.put(&3u8);
out.put(atv1);
}
}
}
}
impl Deserial for BlockItem<EncodedPayload> {
fn deserial<R: ReadBytesExt>(source: &mut R) -> ParseResult<Self> {
let tag: u8 = source.get()?;
match tag {
0 => {
let at = source.get()?;
Ok(BlockItem::AccountTransaction(at))
}
1 => {
let acdi = source.get()?;
Ok(BlockItem::CredentialDeployment(acdi))
}
2 => {
let ui = source.get()?;
Ok(BlockItem::UpdateInstruction(ui))
}
_ => anyhow::bail!("Unsupported block item type: {}.", tag),
}
}
}
pub mod cost {
use crate::id::types::CredentialType;
use super::*;
pub const A: u64 = 100;
pub const B: u64 = 1;
pub fn base_cost(transaction_size: u64, num_signatures: u32) -> Energy {
Energy::from(B * transaction_size + A * u64::from(num_signatures))
}
pub const SIMPLE_TRANSFER: Energy = Energy { energy: 300 };
pub const PLT_OPERATIONS_TRANSACTIONS: Energy = Energy { energy: 300 };
pub const PLT_TRANSFER: Energy = Energy { energy: 100 };
pub const PLT_MINT: Energy = Energy { energy: 50 };
pub const PLT_BURN: Energy = Energy { energy: 50 };
pub const PLT_LIST_UPDATE: Energy = Energy { energy: 50 };
pub const PLT_PAUSE: Energy = Energy { energy: 50 };
#[deprecated(
since = "5.0.1",
note = "encrypted transfers are deprecated and partially removed since protocol version 7"
)]
pub const ENCRYPTED_TRANSFER: Energy = Energy { energy: 27000 };
#[deprecated(
since = "5.0.1",
note = "encrypted transfers are deprecated and partially removed since protocol version 7"
)]
pub const TRANSFER_TO_ENCRYPTED: Energy = Energy { energy: 600 };
pub const TRANSFER_TO_PUBLIC: Energy = Energy { energy: 14850 };
pub fn scheduled_transfer(num_releases: u16) -> Energy {
Energy::from(u64::from(num_releases) * (300 + 64))
}
pub const ADD_BAKER: Energy = Energy { energy: 4050 };
pub const UPDATE_BAKER_KEYS: Energy = Energy { energy: 4050 };
pub const UPDATE_BAKER_STAKE: Energy = Energy { energy: 300 };
pub const UPDATE_BAKER_RESTAKE: Energy = Energy { energy: 300 };
pub const REMOVE_BAKER: Energy = Energy { energy: 300 };
pub fn update_credential_keys(num_credentials_before: u16, num_keys: u16) -> Energy {
Energy {
energy: 500u64 * u64::from(num_credentials_before) + 100 * u64::from(num_keys),
}
}
pub fn update_credentials(num_credentials_before: u16, num_keys: &[u16]) -> Energy {
UPDATE_CREDENTIALS_BASE + update_credentials_variable(num_credentials_before, num_keys)
}
pub const REGISTER_DATA: Energy = Energy { energy: 300 };
pub const CONFIGURE_BAKER_WITH_KEYS: Energy = Energy { energy: 4050 };
pub const CONFIGURE_BAKER_WITHOUT_KEYS: Energy = Energy { energy: 300 };
pub const CONFIGURE_DELEGATION: Energy = Energy { energy: 300 };
pub fn deploy_module(module_size: u64) -> Energy {
Energy::from(module_size / 10)
}
const UPDATE_CREDENTIALS_BASE: Energy = Energy { energy: 500 };
pub fn deploy_credential(ty: CredentialType, num_keys: u16) -> Energy {
match ty {
CredentialType::Initial => Energy::from(1000 + 100 * u64::from(num_keys)),
CredentialType::Normal => Energy::from(54000 + 100 * u64::from(num_keys)),
}
}
fn update_credentials_variable(num_credentials_before: u16, num_keys: &[u16]) -> Energy {
let energy: u64 = 500 * u64::from(num_credentials_before)
+ num_keys
.iter()
.map(|&nk| u64::from(deploy_credential(CredentialType::Normal, nk)))
.sum::<u64>();
Energy::from(energy)
}
}
pub mod construct {
use super::*;
use crate::common::upward::Upward;
use crate::{
common::cbor,
protocol_level_tokens::{RawCbor, TokenId, TokenOperation, TokenOperations},
};
#[derive(Debug, Clone, SerdeSerialize)]
#[serde(rename_all = "camelCase")]
pub struct PreAccountTransaction {
pub header: TransactionHeader,
pub payload: Payload,
#[serde(skip_serializing)]
pub encoded: EncodedPayload,
pub hash_to_sign: hashes::TransactionSignHash,
}
impl PreAccountTransaction {
pub fn sign(self, signer: &impl TransactionSigner) -> AccountTransaction<EncodedPayload> {
sign_transaction(signer, self.header, self.encoded)
}
pub fn extend(self) -> PreAccountTransactionV1 {
let header_v1 = TransactionHeaderV1 {
sender: self.header.sender,
nonce: self.header.nonce,
energy_amount: self.header.energy_amount + Energy::from(2),
payload_size: self.header.payload_size,
expiry: self.header.expiry,
sponsor: None,
};
let hash_to_sign = compute_transaction_sign_hash_v1(&header_v1, &self.encoded);
PreAccountTransactionV1 {
payload: self.payload,
header: header_v1,
encoded: self.encoded,
sender_signature: None,
sponsor_signature: None,
hash_to_sign,
}
}
}
impl Serial for PreAccountTransaction {
fn serial<B: Buffer>(&self, out: &mut B) {
self.header.serial(out);
self.encoded.serial(out);
}
}
impl Deserial for PreAccountTransaction {
fn deserial<R: ReadBytesExt>(source: &mut R) -> ParseResult<Self> {
let header: TransactionHeader = source.get()?;
let encoded = get_encoded_payload(source, header.payload_size)?;
let payload = encoded.decode()?;
let hash_to_sign = compute_transaction_sign_hash(&header, &encoded);
Ok(Self {
header,
payload,
encoded,
hash_to_sign,
})
}
}
#[derive(Debug, Clone, SerdeSerialize)]
#[serde(rename_all = "camelCase")]
pub struct PreAccountTransactionV1 {
pub header: TransactionHeaderV1,
pub payload: Payload,
#[serde(skip_serializing)]
pub encoded: EncodedPayload,
pub hash_to_sign: hashes::TransactionSignHash,
pub sender_signature: Option<TransactionSignature>,
pub sponsor_signature: Option<TransactionSignature>,
}
impl PreAccountTransactionV1 {
pub fn add_sponsor(
&mut self,
sponsor: AccountAddress,
num_sponsor_sigs: u32,
) -> Result<&mut Self, String> {
if let Some(_sponsor) = self.header.sponsor {
return Err(String::from(
"Failed to add sponsor. A sponsor is already present.",
));
}
self.header.sponsor = Some(sponsor);
self.header.energy_amount = self.header.energy_amount
+ Energy::from(32 + cost::A * u64::from(num_sponsor_sigs));
self.hash_to_sign = compute_transaction_sign_hash_v1(&self.header, &self.encoded);
Ok(self)
}
pub fn sign(&mut self, sender: &impl TransactionSigner) -> &mut Self {
let signature = sender.sign_transaction_hash(&self.hash_to_sign);
self.sender_signature = Some(signature);
self
}
pub fn sponsor(&mut self, sponsor: &impl TransactionSigner) -> Result<&mut Self, String> {
self.header.sponsor.ok_or(String::from(
"Failed to sponsor. Missing sponsor. Add sponsor account before signing!",
))?;
let signature = sponsor.sign_transaction_hash(&self.hash_to_sign);
self.sponsor_signature = Some(signature);
Ok(self)
}
pub fn finalize(&self) -> Result<AccountTransactionV1<EncodedPayload>, String> {
let sender_signature = self.sender_signature.to_owned().ok_or(String::from(
"Failed to finalize. Missing sender signature.",
))?;
Ok(AccountTransactionV1 {
signatures: TransactionSignaturesV1 {
sender: sender_signature,
sponsor: self.sponsor_signature.clone(),
},
header: self.header.clone(),
payload: self.encoded.clone(),
})
}
}
struct TransactionBuilder {
header: TransactionHeader,
payload: Payload,
encoded: EncodedPayload,
}
pub const TRANSACTION_HEADER_SIZE: u64 = 32 + 8 + 8 + 4 + 8;
impl TransactionBuilder {
pub fn new(
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
payload: Payload,
) -> Self {
let encoded = payload.encode();
let header = TransactionHeader {
sender,
nonce,
energy_amount: 0.into(),
payload_size: encoded.size(),
expiry,
};
Self {
header,
payload,
encoded,
}
}
#[inline]
fn size(&self) -> u64 {
TRANSACTION_HEADER_SIZE + u64::from(u32::from(self.header.payload_size))
}
#[inline]
pub fn construct(mut self, f: impl FnOnce(u64) -> Energy) -> PreAccountTransaction {
let size = self.size();
self.header.energy_amount = f(size);
let hash_to_sign = compute_transaction_sign_hash(&self.header, &self.encoded);
PreAccountTransaction {
header: self.header,
payload: self.payload,
encoded: self.encoded,
hash_to_sign,
}
}
}
pub fn transfer(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
receiver: AccountAddress,
amount: Amount,
) -> PreAccountTransaction {
let payload = Payload::Transfer {
to_address: receiver,
amount,
};
make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add {
num_sigs,
energy: cost::SIMPLE_TRANSFER,
},
payload,
)
}
pub fn transfer_with_memo(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
receiver: AccountAddress,
amount: Amount,
memo: Memo,
) -> PreAccountTransaction {
let payload = Payload::TransferWithMemo {
to_address: receiver,
memo,
amount,
};
make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add {
num_sigs,
energy: cost::SIMPLE_TRANSFER,
},
payload,
)
}
fn token_operations_txn_energy(operations: &TokenOperations) -> Energy {
cost::PLT_OPERATIONS_TRANSACTIONS
+ operations
.operations
.iter()
.map(|op| match op {
Upward::Known(TokenOperation::Transfer(_)) => cost::PLT_TRANSFER,
Upward::Known(TokenOperation::Mint(_)) => cost::PLT_MINT,
Upward::Known(TokenOperation::Burn(_)) => cost::PLT_BURN,
Upward::Known(TokenOperation::AddAllowList(_))
| Upward::Known(TokenOperation::RemoveAllowList(_))
| Upward::Known(TokenOperation::AddDenyList(_))
| Upward::Known(TokenOperation::RemoveDenyList(_)) => cost::PLT_LIST_UPDATE,
Upward::Known(TokenOperation::Pause(_))
| Upward::Known(TokenOperation::Unpause(_)) => cost::PLT_PAUSE,
Upward::Unknown(_) => Default::default(),
})
.sum()
}
pub fn token_update_operations(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
token_id: TokenId,
operations: TokenOperations,
) -> CborSerializationResult<PreAccountTransaction> {
let energy = token_operations_txn_energy(&operations);
let operations = RawCbor::from(cbor::cbor_encode(&operations)?);
let payload = Payload::TokenUpdate {
payload: TokenOperationsPayload {
token_id,
operations,
},
};
Ok(make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add { num_sigs, energy },
payload,
))
}
#[deprecated(
since = "5.0.1",
note = "encrypted transfers are deprecated and partially removed since protocol version 7"
)]
pub fn encrypted_transfer(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
receiver: AccountAddress,
data: EncryptedAmountTransferData<EncryptedAmountsCurve>,
) -> PreAccountTransaction {
let payload = Payload::EncryptedAmountTransfer {
to: receiver,
data: Box::new(data),
};
make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add {
num_sigs,
energy: cost::ENCRYPTED_TRANSFER,
},
payload,
)
}
#[deprecated(
since = "5.0.1",
note = "encrypted transfers are deprecated and partially removed since protocol version 7"
)]
pub fn encrypted_transfer_with_memo(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
receiver: AccountAddress,
data: EncryptedAmountTransferData<EncryptedAmountsCurve>,
memo: Memo,
) -> PreAccountTransaction {
let payload = Payload::EncryptedAmountTransferWithMemo {
to: receiver,
memo,
data: Box::new(data),
};
make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add {
num_sigs,
energy: cost::ENCRYPTED_TRANSFER,
},
payload,
)
}
#[deprecated(
since = "5.0.1",
note = "encrypted transfers are deprecated and partially removed since protocol version 7"
)]
pub fn transfer_to_encrypted(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
amount: Amount,
) -> PreAccountTransaction {
let payload = Payload::TransferToEncrypted { amount };
make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add {
num_sigs,
energy: cost::TRANSFER_TO_ENCRYPTED,
},
payload,
)
}
pub fn transfer_to_public(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
data: SecToPubAmountTransferData<EncryptedAmountsCurve>,
) -> PreAccountTransaction {
let payload = Payload::TransferToPublic {
data: Box::new(data),
};
make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add {
num_sigs,
energy: cost::TRANSFER_TO_PUBLIC,
},
payload,
)
}
pub fn transfer_with_schedule(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
receiver: AccountAddress,
schedule: Vec<(Timestamp, Amount)>,
) -> PreAccountTransaction {
let num_releases = schedule.len() as u16;
let payload = Payload::TransferWithSchedule {
to: receiver,
schedule,
};
make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add {
num_sigs,
energy: cost::scheduled_transfer(num_releases),
},
payload,
)
}
pub fn transfer_with_schedule_and_memo(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
receiver: AccountAddress,
schedule: Vec<(Timestamp, Amount)>,
memo: Memo,
) -> PreAccountTransaction {
let num_releases = schedule.len() as u16;
let payload = Payload::TransferWithScheduleAndMemo {
to: receiver,
memo,
schedule,
};
make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add {
num_sigs,
energy: cost::scheduled_transfer(num_releases),
},
payload,
)
}
#[deprecated(
since = "2.0.0",
note = "This transaction only applies to protocol versions 1-3. Use configure_baker \
instead."
)]
#[doc(hidden)]
pub fn add_baker(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
baking_stake: Amount,
restake_earnings: bool,
keys: BakerAddKeysPayload,
) -> PreAccountTransaction {
let payload = Payload::AddBaker {
payload: Box::new(AddBakerPayload {
keys,
baking_stake,
restake_earnings,
}),
};
make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add {
num_sigs,
energy: cost::ADD_BAKER,
},
payload,
)
}
#[deprecated(
since = "2.0.0",
note = "This transaction only applies to protocol versions 1-3. Use configure_baker \
instead."
)]
#[doc(hidden)]
pub fn update_baker_keys(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
keys: BakerUpdateKeysPayload,
) -> PreAccountTransaction {
let payload = Payload::UpdateBakerKeys {
payload: Box::new(keys),
};
make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add {
num_sigs,
energy: cost::UPDATE_BAKER_KEYS,
},
payload,
)
}
#[deprecated(
since = "2.0.0",
note = "This transaction only applies to protocol versions 1-3. Use configure_baker \
instead."
)]
#[doc(hidden)]
pub fn remove_baker(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
) -> PreAccountTransaction {
let payload = Payload::RemoveBaker;
make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add {
num_sigs,
energy: cost::REMOVE_BAKER,
},
payload,
)
}
#[deprecated(
since = "2.0.0",
note = "This transaction only applies to protocol versions 1-3. Use configure_baker \
instead."
)]
#[doc(hidden)]
pub fn update_baker_stake(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
new_stake: Amount,
) -> PreAccountTransaction {
let payload = Payload::UpdateBakerStake { stake: new_stake };
make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add {
num_sigs,
energy: cost::UPDATE_BAKER_STAKE,
},
payload,
)
}
#[deprecated(
since = "2.0.0",
note = "This transaction only applies to protocol versions 1-3. Use configure_baker \
instead."
)]
#[doc(hidden)]
pub fn update_baker_restake_earnings(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
restake_earnings: bool,
) -> PreAccountTransaction {
let payload = Payload::UpdateBakerRestakeEarnings { restake_earnings };
make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add {
num_sigs,
energy: cost::UPDATE_BAKER_RESTAKE,
},
payload,
)
}
pub fn register_data(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
data: RegisteredData,
) -> PreAccountTransaction {
let payload = Payload::RegisterData { data };
make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add {
num_sigs,
energy: cost::REGISTER_DATA,
},
payload,
)
}
pub fn deploy_module(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
module: smart_contracts::WasmModule,
) -> PreAccountTransaction {
let module_size = module.source.size();
let payload = Payload::DeployModule { module };
make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add {
num_sigs,
energy: cost::deploy_module(module_size),
},
payload,
)
}
pub fn init_contract(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
payload: InitContractPayload,
energy: Energy,
) -> PreAccountTransaction {
let payload = Payload::InitContract { payload };
make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add { num_sigs, energy },
payload,
)
}
pub fn update_contract(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
payload: UpdateContractPayload,
energy: Energy,
) -> PreAccountTransaction {
let payload = Payload::Update { payload };
make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add { num_sigs, energy },
payload,
)
}
pub fn configure_baker(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
payload: ConfigureBakerPayload,
) -> PreAccountTransaction {
let energy = if payload.keys_with_proofs.is_some() {
cost::CONFIGURE_BAKER_WITH_KEYS
} else {
cost::CONFIGURE_BAKER_WITHOUT_KEYS
};
let payload = Payload::ConfigureBaker {
data: Box::new(payload),
};
make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add { num_sigs, energy },
payload,
)
}
pub fn configure_delegation(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
payload: ConfigureDelegationPayload,
) -> PreAccountTransaction {
let payload = Payload::ConfigureDelegation { data: payload };
make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add {
num_sigs,
energy: cost::CONFIGURE_DELEGATION,
},
payload,
)
}
pub fn update_credential_keys(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
num_existing_credentials: u16,
cred_id: CredentialRegistrationID,
keys: CredentialPublicKeys,
) -> PreAccountTransaction {
let num_cred_keys = keys.keys.len() as u16;
let payload = Payload::UpdateCredentialKeys { cred_id, keys };
make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add {
energy: cost::update_credential_keys(num_existing_credentials, num_cred_keys),
num_sigs,
},
payload,
)
}
#[allow(clippy::too_many_arguments)]
pub fn update_credentials(
num_sigs: u32,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
num_existing_credentials: u16,
new_credentials: AccountCredentialsMap,
remove_credentials: Vec<CredentialRegistrationID>,
new_threshold: AccountThreshold,
) -> PreAccountTransaction {
let num_cred_keys = new_credentials
.values()
.map(|v| v.values.cred_key_info.keys.len() as u16)
.collect::<Vec<_>>();
let payload = Payload::UpdateCredentials {
new_cred_infos: new_credentials,
remove_cred_ids: remove_credentials,
new_threshold,
};
make_transaction(
sender,
nonce,
expiry,
GivenEnergy::Add {
energy: cost::update_credentials(num_existing_credentials, &num_cred_keys),
num_sigs,
},
payload,
)
}
pub enum GivenEnergy {
Absolute(Energy),
Add { energy: Energy, num_sigs: u32 },
}
pub fn make_transaction(
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
energy: GivenEnergy,
payload: Payload,
) -> PreAccountTransaction {
let builder = TransactionBuilder::new(sender, nonce, expiry, payload);
let cost = |size| match energy {
GivenEnergy::Absolute(energy) => energy,
GivenEnergy::Add { num_sigs, energy } => cost::base_cost(size, num_sigs) + energy,
};
builder.construct(cost)
}
}
pub mod send {
use super::*;
use crate::protocol_level_tokens::{TokenId, TokenOperations};
pub fn transfer(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
receiver: AccountAddress,
amount: Amount,
) -> AccountTransaction<EncodedPayload> {
construct::transfer(signer.num_keys(), sender, nonce, expiry, receiver, amount).sign(signer)
}
pub fn transfer_with_memo(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
receiver: AccountAddress,
amount: Amount,
memo: Memo,
) -> AccountTransaction<EncodedPayload> {
construct::transfer_with_memo(
signer.num_keys(),
sender,
nonce,
expiry,
receiver,
amount,
memo,
)
.sign(signer)
}
pub fn token_update_operations(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
token_id: TokenId,
operations: TokenOperations,
) -> CborSerializationResult<AccountTransaction<EncodedPayload>> {
Ok(construct::token_update_operations(
signer.num_keys(),
sender,
nonce,
expiry,
token_id,
operations,
)?
.sign(signer))
}
#[deprecated(
since = "5.0.1",
note = "encrypted transfers are deprecated and partially removed since protocol version 7"
)]
pub fn encrypted_transfer(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
receiver: AccountAddress,
data: EncryptedAmountTransferData<EncryptedAmountsCurve>,
) -> AccountTransaction<EncodedPayload> {
construct::encrypted_transfer(signer.num_keys(), sender, nonce, expiry, receiver, data)
.sign(signer)
}
#[deprecated(
since = "5.0.1",
note = "encrypted transfers are deprecated and partially removed since protocol version 7"
)]
pub fn encrypted_transfer_with_memo(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
receiver: AccountAddress,
data: EncryptedAmountTransferData<EncryptedAmountsCurve>,
memo: Memo,
) -> AccountTransaction<EncodedPayload> {
construct::encrypted_transfer_with_memo(
signer.num_keys(),
sender,
nonce,
expiry,
receiver,
data,
memo,
)
.sign(signer)
}
#[deprecated(
since = "5.0.1",
note = "encrypted transfers are deprecated and partially removed since protocol version 7"
)]
pub fn transfer_to_encrypted(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
amount: Amount,
) -> AccountTransaction<EncodedPayload> {
construct::transfer_to_encrypted(signer.num_keys(), sender, nonce, expiry, amount)
.sign(signer)
}
pub fn transfer_to_public(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
data: SecToPubAmountTransferData<EncryptedAmountsCurve>,
) -> AccountTransaction<EncodedPayload> {
construct::transfer_to_public(signer.num_keys(), sender, nonce, expiry, data).sign(signer)
}
pub fn transfer_with_schedule(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
receiver: AccountAddress,
schedule: Vec<(Timestamp, Amount)>,
) -> AccountTransaction<EncodedPayload> {
construct::transfer_with_schedule(
signer.num_keys(),
sender,
nonce,
expiry,
receiver,
schedule,
)
.sign(signer)
}
pub fn transfer_with_schedule_and_memo(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
receiver: AccountAddress,
schedule: Vec<(Timestamp, Amount)>,
memo: Memo,
) -> AccountTransaction<EncodedPayload> {
construct::transfer_with_schedule_and_memo(
signer.num_keys(),
sender,
nonce,
expiry,
receiver,
schedule,
memo,
)
.sign(signer)
}
#[deprecated(
since = "2.0.0",
note = "This transaction only applies to protocol versions 1-3. Use configure_baker \
instead."
)]
#[doc(hidden)]
pub fn add_baker(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
baking_stake: Amount,
restake_earnings: bool,
keys: BakerAddKeysPayload,
) -> AccountTransaction<EncodedPayload> {
construct::add_baker(
signer.num_keys(),
sender,
nonce,
expiry,
baking_stake,
restake_earnings,
keys,
)
.sign(signer)
}
#[deprecated(
since = "2.0.0",
note = "This transaction only applies to protocol versions 1-3. Use configure_baker \
instead."
)]
#[doc(hidden)]
pub fn update_baker_keys(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
keys: BakerUpdateKeysPayload,
) -> AccountTransaction<EncodedPayload> {
construct::update_baker_keys(signer.num_keys(), sender, nonce, expiry, keys).sign(signer)
}
#[deprecated(
since = "2.0.0",
note = "This transaction only applies to protocol versions 1-3. Use configure_baker \
instead."
)]
#[doc(hidden)]
pub fn remove_baker(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
) -> AccountTransaction<EncodedPayload> {
construct::remove_baker(signer.num_keys(), sender, nonce, expiry).sign(signer)
}
#[deprecated(
since = "2.0.0",
note = "This transaction only applies to protocol versions 1-3. Use configure_baker \
instead."
)]
#[doc(hidden)]
pub fn update_baker_stake(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
new_stake: Amount,
) -> AccountTransaction<EncodedPayload> {
construct::update_baker_stake(signer.num_keys(), sender, nonce, expiry, new_stake)
.sign(signer)
}
#[deprecated(
since = "2.0.0",
note = "This transaction only applies to protocol versions 1-3. Use configure_baker \
instead."
)]
#[doc(hidden)]
pub fn update_baker_restake_earnings(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
restake_earnings: bool,
) -> AccountTransaction<EncodedPayload> {
construct::update_baker_restake_earnings(
signer.num_keys(),
sender,
nonce,
expiry,
restake_earnings,
)
.sign(signer)
}
pub fn configure_baker(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
payload: ConfigureBakerPayload,
) -> AccountTransaction<EncodedPayload> {
construct::configure_baker(signer.num_keys(), sender, nonce, expiry, payload).sign(signer)
}
pub fn configure_delegation(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
payload: ConfigureDelegationPayload,
) -> AccountTransaction<EncodedPayload> {
construct::configure_delegation(signer.num_keys(), sender, nonce, expiry, payload)
.sign(signer)
}
pub fn update_credential_keys(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
num_existing_credentials: u16,
cred_id: CredentialRegistrationID,
keys: CredentialPublicKeys,
) -> AccountTransaction<EncodedPayload> {
construct::update_credential_keys(
signer.num_keys(),
sender,
nonce,
expiry,
num_existing_credentials,
cred_id,
keys,
)
.sign(signer)
}
#[allow(clippy::too_many_arguments)]
pub fn update_credentials(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
num_existing_credentials: u16,
new_credentials: AccountCredentialsMap,
remove_credentials: Vec<CredentialRegistrationID>,
new_threshold: AccountThreshold,
) -> AccountTransaction<EncodedPayload> {
construct::update_credentials(
signer.num_keys(),
sender,
nonce,
expiry,
num_existing_credentials,
new_credentials,
remove_credentials,
new_threshold,
)
.sign(signer)
}
pub fn register_data(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
data: RegisteredData,
) -> AccountTransaction<EncodedPayload> {
construct::register_data(signer.num_keys(), sender, nonce, expiry, data).sign(signer)
}
pub fn deploy_module(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
module: smart_contracts::WasmModule,
) -> AccountTransaction<EncodedPayload> {
construct::deploy_module(signer.num_keys(), sender, nonce, expiry, module).sign(signer)
}
pub fn init_contract(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
payload: InitContractPayload,
energy: Energy,
) -> AccountTransaction<EncodedPayload> {
construct::init_contract(signer.num_keys(), sender, nonce, expiry, payload, energy)
.sign(signer)
}
pub fn update_contract(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
payload: UpdateContractPayload,
energy: Energy,
) -> AccountTransaction<EncodedPayload> {
construct::update_contract(signer.num_keys(), sender, nonce, expiry, payload, energy)
.sign(signer)
}
#[derive(Debug, Copy, Clone)]
pub enum GivenEnergy {
Absolute(Energy),
Add(Energy),
}
pub fn make_and_sign_transaction(
signer: &impl ExactSizeTransactionSigner,
sender: AccountAddress,
nonce: Nonce,
expiry: TransactionTime,
energy: GivenEnergy,
payload: Payload,
) -> AccountTransaction<EncodedPayload> {
match energy {
GivenEnergy::Absolute(energy) => construct::make_transaction(
sender,
nonce,
expiry,
construct::GivenEnergy::Absolute(energy),
payload,
)
.sign(signer),
GivenEnergy::Add(energy) => construct::make_transaction(
sender,
nonce,
expiry,
construct::GivenEnergy::Add {
energy,
num_sigs: signer.num_keys(),
},
payload,
)
.sign(signer),
}
}
}
#[cfg(test)]
mod tests {
use crate::{
hashes::TransactionSignHash,
id::types::{SignatureThreshold, VerifyKey},
};
use rand::{rngs::ThreadRng, Rng};
use std::convert::TryFrom;
use super::*;
const fn dummy_header_v1() -> TransactionHeaderV1 {
TransactionHeaderV1 {
sender: AccountAddress([42u8; ACCOUNT_ADDRESS_SIZE]),
nonce: Nonce { nonce: 1u64 },
energy_amount: Energy {
energy: 18446744073709551615u64,
},
payload_size: PayloadSize {
size: MAX_PAYLOAD_SIZE,
},
expiry: TransactionTime {
seconds: 18446744073709551615u64,
},
sponsor: Some(AccountAddress([43u8; ACCOUNT_ADDRESS_SIZE])),
}
}
fn create_keys(rng: &mut ThreadRng) -> BTreeMap<CredentialIndex, BTreeMap<KeyIndex, KeyPair>> {
let mut keys = BTreeMap::<CredentialIndex, BTreeMap<KeyIndex, KeyPair>>::new();
let bound: usize = rng.gen_range(1..20);
for _ in 0..bound {
let c_idx = CredentialIndex::from(rng.gen::<u8>());
if keys.get(&c_idx).is_none() {
let inner_bound: usize = rng.gen_range(1..20);
let mut cred_keys = BTreeMap::new();
for _ in 0..inner_bound {
let k_idx = KeyIndex::from(rng.gen::<u8>());
cred_keys.insert(k_idx, KeyPair::generate(rng));
}
keys.insert(c_idx, cred_keys);
}
}
keys
}
#[test]
fn test_transaction_header_v1_serialization() {
let header0 = dummy_header_v1();
let mut buf = Vec::<u8>::new();
let buf0: Vec<u8> = [
0, 1, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 0, 0, 0, 0, 0, 0, 0, 1, 255, 255, 255,
255, 255, 255, 255, 255, 0, 8, 0, 9, 255, 255, 255, 255, 255, 255, 255, 255, 43, 43,
43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
43, 43, 43, 43, 43, 43, 43, 43,
]
.to_vec();
header0.serial(&mut buf);
assert_eq!(
buf0, buf,
"The serialization of TransactionHeaderV1 is not equal to the expected one."
);
let binary_result = crate::common::serialize_deserialize(&header0)
.expect("Binary transaction header v1 serialization is not invertible.");
assert_eq!(
binary_result, header0,
"Binary transaction header v1 parses incorrectly."
);
}
#[test]
fn test_account_transaction_v1_serialization() {
let mut rng = rand::thread_rng();
let hash = TransactionSignHash::new(rng.gen());
let sender_keys = create_keys(&mut rng);
let sponsor_keys = create_keys(&mut rng);
let sender_sig = sender_keys.sign_transaction_hash(&hash);
let sponsor_sig = sponsor_keys.sign_transaction_hash(&hash);
let sigs = TransactionSignaturesV1 {
sender: sender_sig,
sponsor: Some(sponsor_sig),
};
const HEADER: TransactionHeaderV1 = dummy_header_v1();
const PAYLOAD_SIZE: usize = HEADER.payload_size.size as usize;
let at = AccountTransactionV1 {
signatures: sigs.clone(),
header: HEADER,
payload: EncodedPayload {
payload: [0; PAYLOAD_SIZE].to_vec(),
},
};
let binary_result = crate::common::serialize_deserialize(&at)
.expect("Binary account transaction v1 serialization is not invertible.");
assert_eq!(
binary_result, at,
"Binary account tranasction v1 parses incorrectly."
);
}
#[test]
fn test_transaction_signature_check() {
let mut rng = rand::thread_rng();
let keys = create_keys(&mut rng);
let hash = TransactionSignHash::new(rng.gen());
let sig = keys.sign_transaction_hash(&hash);
let threshold =
AccountThreshold::try_from(rng.gen_range(1..(keys.len() + 1) as u8)).unwrap();
let pub_keys = keys
.iter()
.map(|(&ci, keys)| {
let threshold =
SignatureThreshold::try_from(rng.gen_range(1..keys.len() + 1) as u8).unwrap();
let keys = keys
.iter()
.map(|(&ki, kp)| (ki, VerifyKey::from(kp)))
.collect();
(ci, CredentialPublicKeys { keys, threshold })
})
.collect::<BTreeMap<_, _>>();
let mut access_structure = AccountAccessStructure {
threshold,
keys: pub_keys,
};
assert!(
verify_signature_transaction_sign_hash(&access_structure, &hash, &sig),
"Transaction signature must validate."
);
access_structure.threshold = AccountThreshold::try_from((keys.len() + 1) as u8).unwrap();
assert!(
!verify_signature_transaction_sign_hash(&access_structure, &hash, &sig),
"Transaction signature must not validate with invalid threshold."
);
}
#[test]
fn test_sponsored_transaction_signature_check() {
let mut rng = rand::thread_rng();
let sender_keys = create_keys(&mut rng);
let sponsor_keys = create_keys(&mut rng);
let hash = TransactionSignHash::new(rng.gen());
let sender_sig = sender_keys.sign_transaction_hash(&hash);
let sender_and_sponsor_sigs = TransactionSignaturesV1 {
sender: sender_sig.clone(),
sponsor: Some(sponsor_keys.sign_transaction_hash(&hash)),
};
let sender_threshold =
AccountThreshold::try_from(rng.gen_range(1..(sender_keys.len() + 1) as u8)).unwrap();
let sender_pub_keys = sender_keys
.iter()
.map(|(&ci, keys)| {
let threshold =
SignatureThreshold::try_from(rng.gen_range(1..keys.len() + 1) as u8).unwrap();
let keys = keys
.iter()
.map(|(&ki, kp)| (ki, VerifyKey::from(kp)))
.collect();
(ci, CredentialPublicKeys { keys, threshold })
})
.collect::<BTreeMap<_, _>>();
let sponsor_threshold =
AccountThreshold::try_from(rng.gen_range(1..(sponsor_keys.len() + 1) as u8)).unwrap();
let sponsor_pub_keys = sponsor_keys
.iter()
.map(|(&ci, keys)| {
let threshold =
SignatureThreshold::try_from(rng.gen_range(1..keys.len() + 1) as u8).unwrap();
let keys = keys
.iter()
.map(|(&ki, kp)| (ki, VerifyKey::from(kp)))
.collect();
(ci, CredentialPublicKeys { keys, threshold })
})
.collect::<BTreeMap<_, _>>();
let mut sender_access_structure = AccountAccessStructure {
threshold: sender_threshold,
keys: sender_pub_keys,
};
let mut sponsor_access_structure = AccountAccessStructure {
threshold: sponsor_threshold,
keys: sponsor_pub_keys,
};
assert!(
verify_signature_transaction_sign_hash_v1(
&sender_access_structure,
&sponsor_access_structure,
&hash,
&sender_and_sponsor_sigs
),
"Sponsored transaction signature must validate."
);
sponsor_access_structure.threshold =
AccountThreshold::try_from((sponsor_keys.len() + 1) as u8).unwrap();
assert!(
!verify_signature_transaction_sign_hash_v1(
&sender_access_structure,
&sponsor_access_structure,
&hash,
&sender_and_sponsor_sigs
),
"Sponsored transaction signature must not validate with invalid sponsor threshold."
);
sponsor_access_structure.threshold =
AccountThreshold::try_from((sponsor_keys.len()) as u8).unwrap();
sender_access_structure.threshold =
AccountThreshold::try_from((sender_keys.len() + 1) as u8).unwrap();
assert!(
!verify_signature_transaction_sign_hash_v1(
&sender_access_structure,
&sponsor_access_structure,
&hash,
&sender_and_sponsor_sigs
),
"Sponsored transaction signature must not validate with invalid sender threshold."
);
}
}