본문으로 건너뛰기

온체인 랜덤니스

Move 함수는 RandomGenerator의 새 instance를 만들고 이를 사용해 서로 다른 type의 random value를 생성할 수 있다. 예를 들어 generate_u128(&mut generator), generate_u8_in_range(&mut generator, 1, 6) 또는 다음 예시와 같다:

entry fun roll_dice(r: &Random, ctx: &mut TxContext): Dice {
let mut generator = new_generator(r, ctx); // generator는 PRG이다
Dice { value: random::generate_u8_in_range(&mut generator, 1, 6) }
}

Random은 예약된 address 0x8을 가진다. Sui에서 randomness에 접근하는 Move API는 random.move를 참조한다.

정보

Random은 shared object이지만 mutable operation에는 접근할 수 없다. 이를 수정하려는 transaction은 실패한다.

Limited resources and Random dependent flows

transaction이 사용할 수 있는 일부 resource에는 제한이 있다는 점에 유의한다. 주의하지 않으면 공격자가 함수의 resource가 고갈되는 지점을 의도적으로 제어하여 애플리케이션을 망가뜨리거나 악용할 수 있다. 구체적으로 gas가 그러한 resource이다.

Random API는 이런 종류의 공격을 자동으로 막아주지 않으므로 contract를 설계할 때 이 미묘한 점을 인지해야 한다.

transaction당 고려해야 할 다른 제한 resource는 다음과 같다:

  • 새 object의 수.

  • dynamic field를 포함해 사용할 수 있는 object의 수.

  • 발생시키는 event의 수.

  • 생성, 삭제, 전송되는 UID의 수.

Click to open

ProtocolConfig limit의 전체 목록.

/// Constants that change the behavior of the protocol.
///
/// The value of each constant here must be fixed for a given protocol version. To change the value
/// of a constant, advance the protocol version, and add support for it in `get_for_version` under
/// the new version number.
/// (below).
///
/// To add a new field to this struct, use the following procedure:
/// - Advance the protocol version.
/// - Add the field as a private `Option<T>` to the struct.
/// - Initialize the field to `None` in prior protocol versions.
/// - Initialize the field to `Some(val)` for your new protocol version.
/// - Add a public getter that simply unwraps the field.
/// - Two public getters of the form `field(&self) -> field_type`
/// and `field_as_option(&self) -> Option<field_type>` will be automatically generated for you.
/// Example for a field: `new_constant: Option<u64>`
/// ```rust,ignore
/// pub fn new_constant(&self) -> u64 {
/// self.new_constant.expect(Self::CONSTANT_ERR_MSG)
/// }
/// pub fn new_constant_as_option(&self) -> Option<u64> {
/// self.new_constant.expect(Self::CONSTANT_ERR_MSG)
/// }
/// ```
/// With `pub fn new_constant(&self) -> u64`, if the constant is accessed in a protocol version
/// in which it is not defined, the validator will crash. (Crashing is necessary because
/// this type of error would almost always result in forking if not prevented here).
/// If you don't want the validator to crash, you can use the
/// `pub fn new_constant_as_option(&self) -> Option<u64>` getter, which will
/// return `None` if the field is not defined at that version.
/// - If you want a customized getter, you can add a method in the impl.
#[skip_serializing_none]
#[derive(Clone, Serialize, Debug, ProtocolConfigAccessors, ProtocolConfigOverride)]
pub struct ProtocolConfig {
pub version: ProtocolVersion,

feature_flags: FeatureFlags,

// ==== Transaction input limits ====
/// Maximum serialized size of a transaction (in bytes).
max_tx_size_bytes: Option<u64>,

/// Maximum number of input objects to a transaction. Enforced by the transaction input checker
max_input_objects: Option<u64>,

/// Max size of objects a transaction can write to disk after completion. Enforce by the Sui adapter.
/// This is the sum of the serialized size of all objects written to disk.
/// The max size of individual objects on the other hand is `max_move_object_size`.
max_size_written_objects: Option<u64>,
/// Max size of objects a system transaction can write to disk after completion. Enforce by the Sui adapter.
/// Similar to `max_size_written_objects` but for system transactions.
max_size_written_objects_system_tx: Option<u64>,

/// Maximum size of serialized transaction effects.
max_serialized_tx_effects_size_bytes: Option<u64>,

/// Maximum size of serialized transaction effects for system transactions.
max_serialized_tx_effects_size_bytes_system_tx: Option<u64>,

/// Maximum number of gas payment objects for a transaction.
max_gas_payment_objects: Option<u32>,

/// Maximum number of modules in a Publish transaction.
max_modules_in_publish: Option<u32>,

/// Maximum number of transitive dependencies in a package when publishing.
max_package_dependencies: Option<u32>,

/// Maximum number of arguments in a move call or a ProgrammableTransaction's
/// TransferObjects command.
max_arguments: Option<u32>,

/// Maximum number of total type arguments, computed recursively.
max_type_arguments: Option<u32>,

/// Maximum depth of an individual type argument.
max_type_argument_depth: Option<u32>,

/// Maximum size of a Pure CallArg.
max_pure_argument_size: Option<u32>,

/// Maximum number of Commands in a ProgrammableTransaction.
max_programmable_tx_commands: Option<u32>,

// ==== Move VM, Move bytecode verifier, and execution limits ===
/// Maximum Move bytecode version the VM understands. All older versions are accepted.
move_binary_format_version: Option<u32>,
min_move_binary_format_version: Option<u32>,

/// Configuration controlling binary tables size.
binary_module_handles: Option<u16>,
binary_struct_handles: Option<u16>,
binary_function_handles: Option<u16>,
binary_function_instantiations: Option<u16>,
binary_signatures: Option<u16>,
binary_constant_pool: Option<u16>,
binary_identifiers: Option<u16>,
binary_address_identifiers: Option<u16>,
binary_struct_defs: Option<u16>,
binary_struct_def_instantiations: Option<u16>,
binary_function_defs: Option<u16>,
binary_field_handles: Option<u16>,
binary_field_instantiations: Option<u16>,
binary_friend_decls: Option<u16>,
binary_enum_defs: Option<u16>,
binary_enum_def_instantiations: Option<u16>,
binary_variant_handles: Option<u16>,
binary_variant_instantiation_handles: Option<u16>,

/// Maximum size of the `contents` part of an object, in bytes. Enforced by the Sui adapter when effects are produced.
max_move_object_size: Option<u64>,

// TODO: Option<increase to 500 KB. currently, publishing a package > 500 KB exceeds the max computation gas cost
/// Maximum size of a Move package object, in bytes. Enforced by the Sui adapter at the end of a publish transaction.
max_move_package_size: Option<u64>,

/// Max number of publish or upgrade commands allowed in a programmable transaction block.
max_publish_or_upgrade_per_ptb: Option<u64>,

/// Maximum gas budget in MIST that a transaction can use.
max_tx_gas: Option<u64>,

/// Maximum amount of the proposed gas price in MIST (defined in the transaction).
max_gas_price: Option<u64>,

/// For aborted txns, we cap the gas price at a factor of RGP. This lowers risk of setting higher priority gas price
/// if there's a chance the txn will abort.
max_gas_price_rgp_factor_for_aborted_transactions: Option<u64>,

/// The max computation bucket for gas. This is the max that can be charged for computation.
max_gas_computation_bucket: Option<u64>,

// Define the value used to round up computation gas charges
gas_rounding_step: Option<u64>,

/// Maximum number of nested loops. Enforced by the Move bytecode verifier.
max_loop_depth: Option<u64>,

/// Maximum number of type arguments that can be bound to generic type parameters. Enforced by the Move bytecode verifier.
max_generic_instantiation_length: Option<u64>,

/// Maximum number of parameters that a Move function can have. Enforced by the Move bytecode verifier.
max_function_parameters: Option<u64>,

/// Maximum number of basic blocks that a Move function can have. Enforced by the Move bytecode verifier.
max_basic_blocks: Option<u64>,

/// Maximum stack size value. Enforced by the Move bytecode verifier.
max_value_stack_size: Option<u64>,

/// Maximum number of "type nodes", a metric for how big a SignatureToken will be when expanded into a fully qualified type. Enforced by the Move bytecode verifier.
max_type_nodes: Option<u64>,

/// Maximum number of push instructions in one function. Enforced by the Move bytecode verifier.
max_push_size: Option<u64>,

/// Maximum number of struct definitions in a module. Enforced by the Move bytecode verifier.
max_struct_definitions: Option<u64>,

/// Maximum number of function definitions in a module. Enforced by the Move bytecode verifier.
max_function_definitions: Option<u64>,

/// Maximum number of fields allowed in a struct definition. Enforced by the Move bytecode verifier.
max_fields_in_struct: Option<u64>,

/// Maximum dependency depth. Enforced by the Move linker when loading dependent modules.
max_dependency_depth: Option<u64>,

/// Maximum number of Move events that a single transaction can emit. Enforced by the VM during execution.
max_num_event_emit: Option<u64>,

/// Maximum number of new IDs that a single transaction can create. Enforced by the VM during execution.
max_num_new_move_object_ids: Option<u64>,

/// Maximum number of new IDs that a single system transaction can create. Enforced by the VM during execution.
max_num_new_move_object_ids_system_tx: Option<u64>,

/// Maximum number of IDs that a single transaction can delete. Enforced by the VM during execution.
max_num_deleted_move_object_ids: Option<u64>,

/// Maximum number of IDs that a single system transaction can delete. Enforced by the VM during execution.
max_num_deleted_move_object_ids_system_tx: Option<u64>,

/// Maximum number of IDs that a single transaction can transfer. Enforced by the VM during execution.
max_num_transferred_move_object_ids: Option<u64>,

/// Maximum number of IDs that a single system transaction can transfer. Enforced by the VM during execution.
max_num_transferred_move_object_ids_system_tx: Option<u64>,

/// Maximum size of a Move user event. Enforced by the VM during execution.
max_event_emit_size: Option<u64>,

/// Maximum size of a Move user event. Enforced by the VM during execution.
max_event_emit_size_total: Option<u64>,

/// Maximum length of a vector in Move. Enforced by the VM during execution, and for constants, by the verifier.
max_move_vector_len: Option<u64>,

/// Maximum length of an `Identifier` in Move. Enforced by the bytecode verifier at signing.
max_move_identifier_len: Option<u64>,

/// Maximum depth of a Move value within the VM.
max_move_value_depth: Option<u64>,

/// Maximum number of variants in an enum. Enforced by the bytecode verifier at signing.
max_move_enum_variants: Option<u64>,

/// Maximum number of back edges in Move function. Enforced by the bytecode verifier at signing.
max_back_edges_per_function: Option<u64>,

/// Maximum number of back edges in Move module. Enforced by the bytecode verifier at signing.
max_back_edges_per_module: Option<u64>,

/// Maximum number of meter `ticks` spent verifying a Move function. Enforced by the bytecode verifier at signing.
max_verifier_meter_ticks_per_function: Option<u64>,

/// Maximum number of meter `ticks` spent verifying a Move module. Enforced by the bytecode verifier at signing.
max_meter_ticks_per_module: Option<u64>,

/// Maximum number of meter `ticks` spent verifying a Move package. Enforced by the bytecode verifier at signing.
max_meter_ticks_per_package: Option<u64>,

// === Object runtime internal operation limits ====
// These affect dynamic fields
/// Maximum number of cached objects in the object runtime ObjectStore. Enforced by object runtime during execution
object_runtime_max_num_cached_objects: Option<u64>,

/// Maximum number of cached objects in the object runtime ObjectStore in system transaction. Enforced by object runtime during execution
object_runtime_max_num_cached_objects_system_tx: Option<u64>,

/// Maximum number of stored objects accessed by object runtime ObjectStore. Enforced by object runtime during execution
object_runtime_max_num_store_entries: Option<u64>,

/// Maximum number of stored objects accessed by object runtime ObjectStore in system transaction. Enforced by object runtime during execution
object_runtime_max_num_store_entries_system_tx: Option<u64>,

// === Execution gas costs ====
/// Base cost for any Sui transaction
base_tx_cost_fixed: Option<u64>,

/// Additional cost for a transaction that publishes a package
/// i.e., the base cost of such a transaction is base_tx_cost_fixed + package_publish_cost_fixed
package_publish_cost_fixed: Option<u64>,

/// Cost per byte of a Move call transaction
/// i.e., the cost of such a transaction is base_cost + (base_tx_cost_per_byte * size)
base_tx_cost_per_byte: Option<u64>,

/// Cost per byte for a transaction that publishes a package
package_publish_cost_per_byte: Option<u64>,

// Per-byte cost of reading an object during transaction execution
obj_access_cost_read_per_byte: Option<u64>,

// Per-byte cost of writing an object during transaction execution
obj_access_cost_mutate_per_byte: Option<u64>,

// Per-byte cost of deleting an object during transaction execution
obj_access_cost_delete_per_byte: Option<u64>,

/// Per-byte cost charged for each input object to a transaction.
/// Meant to approximate the cost of checking locks for each object
// TODO: Option<I'm not sure that this cost makes sense. Checking locks is "free"
// in the sense that an invalid tx that can never be committed/pay gas can
// force validators to check an arbitrary number of locks. If those checks are
// "free" for invalid transactions, why charge for them in valid transactions
// TODO: Option<if we keep this, I think we probably want it to be a fixed cost rather
// than a per-byte cost. checking an object lock should not require loading an
// entire object, just consulting an ID -> tx digest map
obj_access_cost_verify_per_byte: Option<u64>,

// Maximal nodes which are allowed when converting to a type layout.
max_type_to_layout_nodes: Option<u64>,

// Maximal size in bytes that a PTB value can be
max_ptb_value_size: Option<u64>,

// === Gas version. gas model ===
/// Gas model version, what code we are using to charge gas
gas_model_version: Option<u64>,

// === Storage gas costs ===
/// Per-byte cost of storing an object in the Sui global object store. Some of this cost may be refundable if the object is later freed
obj_data_cost_refundable: Option<u64>,

// Per-byte cost of storing an object in the Sui transaction log (e.g., in CertifiedTransactionEffects)
// This depends on the size of various fields including the effects
// TODO: Option<I don't fully understand this^ and more details would be useful
obj_metadata_cost_non_refundable: Option<u64>,

// === Tokenomics ===

// TODO: Option<this should be changed to u64.
/// Sender of a txn that touches an object will get this percent of the storage rebate back.
/// In basis point.
storage_rebate_rate: Option<u64>,

/// 5% of the storage fund's share of rewards are reinvested into the storage fund.
/// In basis point.
storage_fund_reinvest_rate: Option<u64>,

/// The share of rewards that will be slashed and redistributed is 50%.
/// In basis point.
reward_slashing_rate: Option<u64>,

/// Unit gas price, Mist per internal gas unit.
storage_gas_price: Option<u64>,

/// Per-object storage cost for accumulator objects, used during end-of-epoch accounting.
accumulator_object_storage_cost: Option<u64>,

// === Core Protocol ===
/// Max number of transactions per checkpoint.
/// Note that this is a protocol constant and not a config as validators must have this set to
/// the same value, otherwise they *will* fork.
max_transactions_per_checkpoint: Option<u64>,

/// Max size of a checkpoint in bytes.
/// Note that this is a protocol constant and not a config as validators must have this set to
/// the same value, otherwise they *will* fork.
max_checkpoint_size_bytes: Option<u64>,

/// A protocol upgrade always requires 2f+1 stake to agree. We support a buffer of additional
/// stake (as a fraction of f, expressed in basis points) that is required before an upgrade
/// can happen automatically. 10000bps would indicate that complete unanimity is required (all
/// 3f+1 must vote), while 0bps would indicate that 2f+1 is sufficient.
buffer_stake_for_protocol_upgrade_bps: Option<u64>,

// === Native Function Costs ===

// `address` module
// Cost params for the Move native function `address::from_bytes(bytes: vector<u8>)`
address_from_bytes_cost_base: Option<u64>,
// Cost params for the Move native function `address::to_u256(address): u256`
address_to_u256_cost_base: Option<u64>,
// Cost params for the Move native function `address::from_u256(u256): address`
address_from_u256_cost_base: Option<u64>,

// `config` module
// Cost params for the Move native function `read_setting_impl<Name: copy + drop + store,
// SettingValue: key + store, SettingDataValue: store, Value: copy + drop + store,
// >(config: address, name: address, current_epoch: u64): Option<Value>`
config_read_setting_impl_cost_base: Option<u64>,
config_read_setting_impl_cost_per_byte: Option<u64>,

// `dynamic_field` module
// Cost params for the Move native function `hash_type_and_key<K: copy + drop + store>(parent: address, k: K): address`
dynamic_field_hash_type_and_key_cost_base: Option<u64>,
dynamic_field_hash_type_and_key_type_cost_per_byte: Option<u64>,
dynamic_field_hash_type_and_key_value_cost_per_byte: Option<u64>,
dynamic_field_hash_type_and_key_type_tag_cost_per_byte: Option<u64>,
// Cost params for the Move native function `add_child_object<Child: key>(parent: address, child: Child)`
dynamic_field_add_child_object_cost_base: Option<u64>,
dynamic_field_add_child_object_type_cost_per_byte: Option<u64>,
dynamic_field_add_child_object_value_cost_per_byte: Option<u64>,
dynamic_field_add_child_object_struct_tag_cost_per_byte: Option<u64>,
// Cost params for the Move native function `borrow_child_object_mut<Child: key>(parent: &mut UID, id: address): &mut Child`
dynamic_field_borrow_child_object_cost_base: Option<u64>,
dynamic_field_borrow_child_object_child_ref_cost_per_byte: Option<u64>,
dynamic_field_borrow_child_object_type_cost_per_byte: Option<u64>,
// Cost params for the Move native function `remove_child_object<Child: key>(parent: address, id: address): Child`
dynamic_field_remove_child_object_cost_base: Option<u64>,
dynamic_field_remove_child_object_child_cost_per_byte: Option<u64>,
dynamic_field_remove_child_object_type_cost_per_byte: Option<u64>,
// Cost params for the Move native function `has_child_object(parent: address, id: address): bool`
dynamic_field_has_child_object_cost_base: Option<u64>,
// Cost params for the Move native function `has_child_object_with_ty<Child: key>(parent: address, id: address): bool`
dynamic_field_has_child_object_with_ty_cost_base: Option<u64>,
dynamic_field_has_child_object_with_ty_type_cost_per_byte: Option<u64>,
dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte: Option<u64>,

// `event` module
// Cost params for the Move native function `event::emit<T: copy + drop>(event: T)`
event_emit_cost_base: Option<u64>,
event_emit_value_size_derivation_cost_per_byte: Option<u64>,
event_emit_tag_size_derivation_cost_per_byte: Option<u64>,
event_emit_output_cost_per_byte: Option<u64>,
event_emit_auth_stream_cost: Option<u64>,

// `object` module
// Cost params for the Move native function `borrow_uid<T: key>(obj: &T): &UID`
object_borrow_uid_cost_base: Option<u64>,
// Cost params for the Move native function `delete_impl(id: address)`
object_delete_impl_cost_base: Option<u64>,
// Cost params for the Move native function `record_new_uid(id: address)`
object_record_new_uid_cost_base: Option<u64>,

// Transfer
// Cost params for the Move native function `transfer_impl<T: key>(obj: T, recipient: address)`
transfer_transfer_internal_cost_base: Option<u64>,
// Cost params for the Move native function `party_transfer_impl<T: key>(obj: T, party_members: vector<address>)`
transfer_party_transfer_internal_cost_base: Option<u64>,
// Cost params for the Move native function `freeze_object<T: key>(obj: T)`
transfer_freeze_object_cost_base: Option<u64>,
// Cost params for the Move native function `share_object<T: key>(obj: T)`
transfer_share_object_cost_base: Option<u64>,
// Cost params for the Move native function
// `receive_object<T: key>(p: &mut UID, recv: Receiving<T>T)`
transfer_receive_object_cost_base: Option<u64>,

// TxContext
// Cost params for the Move native function `transfer_impl<T: key>(obj: T, recipient: address)`
tx_context_derive_id_cost_base: Option<u64>,
tx_context_fresh_id_cost_base: Option<u64>,
tx_context_sender_cost_base: Option<u64>,
tx_context_epoch_cost_base: Option<u64>,
tx_context_epoch_timestamp_ms_cost_base: Option<u64>,
tx_context_sponsor_cost_base: Option<u64>,
tx_context_rgp_cost_base: Option<u64>,
tx_context_gas_price_cost_base: Option<u64>,
tx_context_gas_budget_cost_base: Option<u64>,
tx_context_ids_created_cost_base: Option<u64>,
tx_context_replace_cost_base: Option<u64>,

// Types
// Cost params for the Move native function `is_one_time_witness<T: drop>(_: &T): bool`
types_is_one_time_witness_cost_base: Option<u64>,
types_is_one_time_witness_type_tag_cost_per_byte: Option<u64>,
types_is_one_time_witness_type_cost_per_byte: Option<u64>,

// Validator
// Cost params for the Move native function `validate_metadata_bcs(metadata: vector<u8>)`
validator_validate_metadata_cost_base: Option<u64>,
validator_validate_metadata_data_cost_per_byte: Option<u64>,

// Crypto natives
crypto_invalid_arguments_cost: Option<u64>,
// bls12381::bls12381_min_sig_verify
bls12381_bls12381_min_sig_verify_cost_base: Option<u64>,
bls12381_bls12381_min_sig_verify_msg_cost_per_byte: Option<u64>,
bls12381_bls12381_min_sig_verify_msg_cost_per_block: Option<u64>,

// bls12381::bls12381_min_pk_verify
bls12381_bls12381_min_pk_verify_cost_base: Option<u64>,
bls12381_bls12381_min_pk_verify_msg_cost_per_byte: Option<u64>,
bls12381_bls12381_min_pk_verify_msg_cost_per_block: Option<u64>,

// ecdsa_k1::ecrecover
ecdsa_k1_ecrecover_keccak256_cost_base: Option<u64>,
ecdsa_k1_ecrecover_keccak256_msg_cost_per_byte: Option<u64>,
ecdsa_k1_ecrecover_keccak256_msg_cost_per_block: Option<u64>,
ecdsa_k1_ecrecover_sha256_cost_base: Option<u64>,
ecdsa_k1_ecrecover_sha256_msg_cost_per_byte: Option<u64>,
ecdsa_k1_ecrecover_sha256_msg_cost_per_block: Option<u64>,

// ecdsa_k1::decompress_pubkey
ecdsa_k1_decompress_pubkey_cost_base: Option<u64>,

// ecdsa_k1::secp256k1_verify
ecdsa_k1_secp256k1_verify_keccak256_cost_base: Option<u64>,
ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_byte: Option<u64>,
ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_block: Option<u64>,
ecdsa_k1_secp256k1_verify_sha256_cost_base: Option<u64>,
ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_byte: Option<u64>,
ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_block: Option<u64>,

// ecdsa_r1::ecrecover
ecdsa_r1_ecrecover_keccak256_cost_base: Option<u64>,
ecdsa_r1_ecrecover_keccak256_msg_cost_per_byte: Option<u64>,
ecdsa_r1_ecrecover_keccak256_msg_cost_per_block: Option<u64>,
ecdsa_r1_ecrecover_sha256_cost_base: Option<u64>,
ecdsa_r1_ecrecover_sha256_msg_cost_per_byte: Option<u64>,
ecdsa_r1_ecrecover_sha256_msg_cost_per_block: Option<u64>,

// ecdsa_r1::secp256k1_verify
ecdsa_r1_secp256r1_verify_keccak256_cost_base: Option<u64>,
ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_byte: Option<u64>,
ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_block: Option<u64>,
ecdsa_r1_secp256r1_verify_sha256_cost_base: Option<u64>,
ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_byte: Option<u64>,
ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_block: Option<u64>,

// ecvrf::verify
ecvrf_ecvrf_verify_cost_base: Option<u64>,
ecvrf_ecvrf_verify_alpha_string_cost_per_byte: Option<u64>,
ecvrf_ecvrf_verify_alpha_string_cost_per_block: Option<u64>,

// ed25519
ed25519_ed25519_verify_cost_base: Option<u64>,
ed25519_ed25519_verify_msg_cost_per_byte: Option<u64>,
ed25519_ed25519_verify_msg_cost_per_block: Option<u64>,

// groth16::prepare_verifying_key
groth16_prepare_verifying_key_bls12381_cost_base: Option<u64>,
groth16_prepare_verifying_key_bn254_cost_base: Option<u64>,

// groth16::verify_groth16_proof_internal
groth16_verify_groth16_proof_internal_bls12381_cost_base: Option<u64>,
groth16_verify_groth16_proof_internal_bls12381_cost_per_public_input: Option<u64>,
groth16_verify_groth16_proof_internal_bn254_cost_base: Option<u64>,
groth16_verify_groth16_proof_internal_bn254_cost_per_public_input: Option<u64>,
groth16_verify_groth16_proof_internal_public_input_cost_per_byte: Option<u64>,

// hash::blake2b256
hash_blake2b256_cost_base: Option<u64>,
hash_blake2b256_data_cost_per_byte: Option<u64>,
hash_blake2b256_data_cost_per_block: Option<u64>,

// hash::keccak256
hash_keccak256_cost_base: Option<u64>,
hash_keccak256_data_cost_per_byte: Option<u64>,
hash_keccak256_data_cost_per_block: Option<u64>,

// poseidon::poseidon_bn254
poseidon_bn254_cost_base: Option<u64>,
poseidon_bn254_cost_per_block: Option<u64>,

// group_ops
group_ops_bls12381_decode_scalar_cost: Option<u64>,
group_ops_bls12381_decode_g1_cost: Option<u64>,
group_ops_bls12381_decode_g2_cost: Option<u64>,
group_ops_bls12381_decode_gt_cost: Option<u64>,
group_ops_bls12381_scalar_add_cost: Option<u64>,
group_ops_bls12381_g1_add_cost: Option<u64>,
group_ops_bls12381_g2_add_cost: Option<u64>,
group_ops_bls12381_gt_add_cost: Option<u64>,
group_ops_bls12381_scalar_sub_cost: Option<u64>,
group_ops_bls12381_g1_sub_cost: Option<u64>,
group_ops_bls12381_g2_sub_cost: Option<u64>,
group_ops_bls12381_gt_sub_cost: Option<u64>,
group_ops_bls12381_scalar_mul_cost: Option<u64>,
group_ops_bls12381_g1_mul_cost: Option<u64>,
group_ops_bls12381_g2_mul_cost: Option<u64>,
group_ops_bls12381_gt_mul_cost: Option<u64>,
group_ops_bls12381_scalar_div_cost: Option<u64>,
group_ops_bls12381_g1_div_cost: Option<u64>,
group_ops_bls12381_g2_div_cost: Option<u64>,
group_ops_bls12381_gt_div_cost: Option<u64>,
group_ops_bls12381_g1_hash_to_base_cost: Option<u64>,
group_ops_bls12381_g2_hash_to_base_cost: Option<u64>,
group_ops_bls12381_g1_hash_to_cost_per_byte: Option<u64>,
group_ops_bls12381_g2_hash_to_cost_per_byte: Option<u64>,
group_ops_bls12381_g1_msm_base_cost: Option<u64>,
group_ops_bls12381_g2_msm_base_cost: Option<u64>,
group_ops_bls12381_g1_msm_base_cost_per_input: Option<u64>,
group_ops_bls12381_g2_msm_base_cost_per_input: Option<u64>,
group_ops_bls12381_msm_max_len: Option<u32>,
group_ops_bls12381_pairing_cost: Option<u64>,
group_ops_bls12381_g1_to_uncompressed_g1_cost: Option<u64>,
group_ops_bls12381_uncompressed_g1_to_g1_cost: Option<u64>,
group_ops_bls12381_uncompressed_g1_sum_base_cost: Option<u64>,
group_ops_bls12381_uncompressed_g1_sum_cost_per_term: Option<u64>,
group_ops_bls12381_uncompressed_g1_sum_max_terms: Option<u64>,

group_ops_ristretto_decode_scalar_cost: Option<u64>,
group_ops_ristretto_decode_point_cost: Option<u64>,
group_ops_ristretto_scalar_add_cost: Option<u64>,
group_ops_ristretto_point_add_cost: Option<u64>,
group_ops_ristretto_scalar_sub_cost: Option<u64>,
group_ops_ristretto_point_sub_cost: Option<u64>,
group_ops_ristretto_scalar_mul_cost: Option<u64>,
group_ops_ristretto_point_mul_cost: Option<u64>,
group_ops_ristretto_scalar_div_cost: Option<u64>,
group_ops_ristretto_point_div_cost: Option<u64>,

// hmac::hmac_sha3_256
hmac_hmac_sha3_256_cost_base: Option<u64>,
hmac_hmac_sha3_256_input_cost_per_byte: Option<u64>,
hmac_hmac_sha3_256_input_cost_per_block: Option<u64>,

// zklogin::check_zklogin_id
check_zklogin_id_cost_base: Option<u64>,
// zklogin::check_zklogin_issuer
check_zklogin_issuer_cost_base: Option<u64>,

vdf_verify_vdf_cost: Option<u64>,
vdf_hash_to_input_cost: Option<u64>,

// nitro_attestation::load_nitro_attestation
nitro_attestation_parse_base_cost: Option<u64>,
nitro_attestation_parse_cost_per_byte: Option<u64>,
nitro_attestation_verify_base_cost: Option<u64>,
nitro_attestation_verify_cost_per_cert: Option<u64>,

// Stdlib costs
bcs_per_byte_serialized_cost: Option<u64>,
bcs_legacy_min_output_size_cost: Option<u64>,
bcs_failure_cost: Option<u64>,

hash_sha2_256_base_cost: Option<u64>,
hash_sha2_256_per_byte_cost: Option<u64>,
hash_sha2_256_legacy_min_input_len_cost: Option<u64>,
hash_sha3_256_base_cost: Option<u64>,
hash_sha3_256_per_byte_cost: Option<u64>,
hash_sha3_256_legacy_min_input_len_cost: Option<u64>,
type_name_get_base_cost: Option<u64>,
type_name_get_per_byte_cost: Option<u64>,
type_name_id_base_cost: Option<u64>,

string_check_utf8_base_cost: Option<u64>,
string_check_utf8_per_byte_cost: Option<u64>,
string_is_char_boundary_base_cost: Option<u64>,
string_sub_string_base_cost: Option<u64>,
string_sub_string_per_byte_cost: Option<u64>,
string_index_of_base_cost: Option<u64>,
string_index_of_per_byte_pattern_cost: Option<u64>,
string_index_of_per_byte_searched_cost: Option<u64>,

vector_empty_base_cost: Option<u64>,
vector_length_base_cost: Option<u64>,
vector_push_back_base_cost: Option<u64>,
vector_push_back_legacy_per_abstract_memory_unit_cost: Option<u64>,
vector_borrow_base_cost: Option<u64>,
vector_pop_back_base_cost: Option<u64>,
vector_destroy_empty_base_cost: Option<u64>,
vector_swap_base_cost: Option<u64>,
debug_print_base_cost: Option<u64>,
debug_print_stack_trace_base_cost: Option<u64>,

// ==== Ephemeral (consensus only) params deleted ====
//
// Const params for consensus scoring decision
// The scaling factor property for the MED outlier detection
// scoring_decision_mad_divisor: Option<f64>,
// The cutoff value for the MED outlier detection
// scoring_decision_cutoff_value: Option<f64>,
/// === Execution Version ===
execution_version: Option<u64>,

// Dictates the threshold (percentage of stake) that is used to calculate the "bad" nodes to be
// swapped when creating the consensus schedule. The values should be of the range [0 - 33]. Anything
// above 33 (f) will not be allowed.
consensus_bad_nodes_stake_threshold: Option<u64>,

max_jwk_votes_per_validator_per_epoch: Option<u64>,
// The maximum age of a JWK in epochs before it is removed from the AuthenticatorState object.
// Applied at the end of an epoch as a delta from the new epoch value, so setting this to 1
// will cause the new epoch to start with JWKs from the previous epoch still valid.
max_age_of_jwk_in_epochs: Option<u64>,

// === random beacon ===
/// Maximum allowed precision loss when reducing voting weights for the random beacon
/// protocol.
random_beacon_reduction_allowed_delta: Option<u16>,

/// Minimum number of shares below which voting weights will not be reduced for the
/// random beacon protocol.
random_beacon_reduction_lower_bound: Option<u32>,

/// Consensus Round after which DKG should be aborted and randomness disabled for
/// the epoch, if it hasn't already completed.
random_beacon_dkg_timeout_round: Option<u32>,

/// Minimum interval between consecutive rounds of generated randomness.
random_beacon_min_round_interval_ms: Option<u64>,

/// Version of the random beacon DKG protocol.
/// 0 was deprecated (and currently not supported), 1 is the default version.
random_beacon_dkg_version: Option<u64>,

/// The maximum serialised transaction size (in bytes) accepted by consensus. That should be bigger than the
/// `max_tx_size_bytes` with some additional headroom.
consensus_max_transaction_size_bytes: Option<u64>,
/// The maximum size of transactions included in a consensus block.
consensus_max_transactions_in_block_bytes: Option<u64>,
/// The maximum number of transactions included in a consensus block.
consensus_max_num_transactions_in_block: Option<u64>,

/// The maximum number of rounds where transaction voting is allowed.
consensus_voting_rounds: Option<u32>,

/// DEPRECATED. Do not use.
max_accumulated_txn_cost_per_object_in_narwhal_commit: Option<u64>,

/// The max number of consensus rounds a transaction can be deferred due to shared object congestion.
/// Transactions will be cancelled after this many rounds.
max_deferral_rounds_for_congestion_control: Option<u64>,

/// DEPRECATED. Do not use.
max_txn_cost_overage_per_object_in_commit: Option<u64>,

/// DEPRECATED. Do not use.
allowed_txn_cost_overage_burst_per_object_in_commit: Option<u64>,

/// Minimum interval of commit timestamps between consecutive checkpoints.
min_checkpoint_interval_ms: Option<u64>,

/// Version number to use for version_specific_data in `CheckpointSummary`.
checkpoint_summary_version_specific_data: Option<u64>,

/// The max number of transactions that can be included in a single Soft Bundle.
max_soft_bundle_size: Option<u64>,

/// Whether to try to form bridge committee
// Note: this is not a feature flag because we want to distinguish between
// `None` and `Some(false)`, as committee was already finalized on Testnet.
bridge_should_try_to_finalize_committee: Option<bool>,

/// The max accumulated txn execution cost per object in a mysticeti. Transactions
/// in a commit will be deferred once their touch shared objects hit this limit,
/// unless the selected congestion control mode allows overage.
/// This config plays the same role as `max_accumulated_txn_cost_per_object_in_narwhal_commit`
/// but for mysticeti commits due to that mysticeti has higher commit rate.
max_accumulated_txn_cost_per_object_in_mysticeti_commit: Option<u64>,

/// As above, but separate per-commit budget for transactions that use randomness.
/// If not configured, uses the setting for `max_accumulated_txn_cost_per_object_in_mysticeti_commit`.
max_accumulated_randomness_txn_cost_per_object_in_mysticeti_commit: Option<u64>,

/// Configures the garbage collection depth for consensus. When is unset or `0` then the garbage collection
/// is disabled.
consensus_gc_depth: Option<u32>,

/// DEPRECATED. Do not use.
gas_budget_based_txn_cost_cap_factor: Option<u64>,

/// DEPRECATED. Do not use.
gas_budget_based_txn_cost_absolute_cap_commit_count: Option<u64>,

/// SIP-45: K in the formula `amplification_factor = max(0, gas_price / reference_gas_price - K)`.
/// This is the threshold for activating consensus amplification.
sip_45_consensus_amplification_threshold: Option<u64>,

/// DEPRECATED: this was an ephemeral feature flag only used in per-epoch tables, which has now
/// been deployed everywhere.
use_object_per_epoch_marker_table_v2: Option<bool>,

/// The number of commits to consider when computing a deterministic commit rate.
consensus_commit_rate_estimation_window_size: Option<u32>,

/// A list of effective AliasedAddress.
/// For each pair, `aliased` is allowed to act as `original` for any of the transaction digests
/// listed in `tx_digests`
#[serde(skip_serializing_if = "Vec::is_empty")]
aliased_addresses: Vec<AliasedAddress>,

/// The base charge for each command in a programmable transaction. This is a fixed cost to
/// account for the overhead of processing each command.
translation_per_command_base_charge: Option<u64>,

/// The base charge for each input in a programmable transaction regardless of if it is used or
/// not, or a pure/object/funds withdrawal input.
translation_per_input_base_charge: Option<u64>,

/// The base charge for each byte of pure input in a programmable transaction.
translation_pure_input_per_byte_charge: Option<u64>,

/// The multiplier for the number of type nodes when charging for type loading.
/// This is multiplied by the number of type nodes to get the total cost.
/// This should be a small number to avoid excessive gas costs for loading types.
translation_per_type_node_charge: Option<u64>,

/// The multiplier for the number of type references when charging for type checking and reference
/// checking.
translation_per_reference_node_charge: Option<u64>,

/// The multiplier for each linkage entry when charging for linkage tables that we have
/// created.
translation_per_linkage_entry_charge: Option<u64>,

/// The maximum number of updates per settlement transaction.
max_updates_per_settlement_txn: Option<u32>,
}

추첨 winner나 lottery number를 고르는 경우처럼 randomness와 무관하게 실행되는 code에서는 이 공격이 문제가 되지 않는 경우가 많다. 하지만 이 공격이 문제가 될 수 있는 다른 사용 사례에서는 다음 접근법 중 하나를 고려한다.

Divide logic

서로 다른 transaction이 호출해야 하는 2개의 함수로 로직을 나눈다.

  1. 첫 번째 transaction tx1은 첫 번째 함수를 호출하여 random value를 가져오고 이를 tx1의 다른 command가 읽을 수 없는 object에 저장한다. 예를 들어 이 함수는 object를 caller에게 transfer하거나 transaction digest를 저장하고 읽을 때 그것이 다름을 확인할 수 있다.

  2. 두 번째 transaction tx2는 두 번째 함수를 호출해 저장된 값을 읽고 작업을 완료한다. tx2는 실패할 수 있지만 이제 random value는 고정되었고 반복 호출로 수정할 수 없다.

공격자가 tx1이 commit한 randomness를 본 뒤 입력을 수정할 수 있으므로, 두 번째 함수의 입력이 고정되어 있고 tx1 이후에 수정될 수 없다는 점이 중요하다.

두 번째 단계가 끝내 완료되지 않는 경우를 우아하게 처리해야 한다. 예를 들어 첫 번째 단계에서 fee를 청구하는 방법으로 이를 달성할 수 있다.

Click to open

Example

entry fun reveal_alternative2_step1(nft: AirDropNFT, r: &Random, ctx: &mut TxContext) {
destroy_airdrop_nft(nft);

let mut generator = new_generator(r, ctx);
let v = generator.generate_u8_in_range(1, 100);

transfer::public_transfer(
RandomnessNFT { id: object::new(ctx), value: v },
ctx.sender(),
);
}
public fun reveal_alternative2_step2(nft: RandomnessNFT, ctx: &mut TxContext): MetalNFT {
let RandomnessNFT { id, value } = nft;
delete(id);

let metal = if (value <= 10) GOLD
else if (10 < value && value <= 40) SILVER
else BRONZE;

MetalNFT {
id: object::new(ctx),
metal,
}
}

Resource usage

주요 처리 경로가 무거운 작업을 수행하고 early-exit 경로는 빠르게 반환하도록 함수를 작성한다. 다음 사항을 염두에 둔다:

  • external function과 native function 모두 미래에 바뀔 수 있으며, 그 결과 test를 수행했을 때와 다른 cost가 생길 수 있다.

  • transaction cost를 벤치마크하려면 Profile the transaction을 사용한다.

  • 같은 transaction에서 생성되고 삭제된 UID는 생성되거나 삭제된 UID 수 제한에 포함되지 않는다.

Use (non-public) entry functions

composition은 smart contract에 매우 강력하지만 randomness를 사용하는 함수에 대한 공격의 문도 연다.

예를 들어 dice를 굴리기 위해 randomness를 사용하는 betting game을 생각해 보자:

module games::dice {
...
public enum Ticket has drop {
Lost,
Won,
}

public fun is_winner(t: &Ticket): bool {
match (t) {
Ticket::Won => true,
Ticket::Lost => false,
}
}

/// 출력을 맞히면 GuessedCorrectly object를 받는다.
/// 그렇지 않으면 아무것도 받지 못한다.
public fun play_dice(guess: u8, fee: Coin<SUI>, r: &Random, ctx: &mut TxContext): Ticket {
// 차례 비용을 지불한다
assert!(coin::value(&fee) == 1000000, EInvalidAmount);
transfer::public_transfer(fee, CREATOR_ADDRESS);

// 주사위를 굴린다
let mut generator = new_generator(r, ctx);
if (guess == generator.generate_u8_in_range(1, 6)) {
Ticket::Won
} else {
Ticket::Lost
}
}
...
}

공격자는 다음 함수를 배포할 수 있다:

public fun attack(guess: u8, r: &Random, ctx: &mut TxContext): Ticket {
let t = dice::play_dice(guess, r, ctx);
// play_dice에서 졌다면 transaction을 되돌린다
assert!(!dice::is_winner(&t), 0);
t
}

이제 공격자는 추측값과 함께 attack을 호출하고 추측이 틀렸을 때는 fee transfer를 항상 revert할 수 있다. composition attack으로부터 보호하려면 함수를 private entry function으로 정의하여 다른 module의 함수가 이를 호출할 수 없게 한다.

Move compiler는 Random을 인자로 받는 public 함수를 거부함으로써 이 동작을 강제한다.

Programmable transaction block (PTB) restrictions

앞서 설명한 것과 비슷한 공격은 play_dice가 private entry function으로 정의되어 있더라도 PTB를 통해 가능하다. 예를 들어 앞서 정의한 entry play_dice(guess: u8, fee: Coin<SUI>, r: &Random, ctx: &mut TxContext): Ticket { … } 함수를 생각해 보면, 공격자는 다음 함수를 게시하고 첫 번째 command의 출력인 Result(0)를 사용해 play_dice(...), attack(Result(0)) command를 가진 PTB를 보낼 수 있다:

public fun attack(t: Ticket): Ticket {
assert!(!dice::is_winner(&t), 0);
t
}

이 공격은 PTB의 atomic한 성질을 악용해 추측이 틀렸다면 fee를 지불하지 않고 전체 transaction을 항상 revert한다. 여러 transaction을 보내면 공격을 반복할 수 있고, 각 transaction은 서로 다른 randomness로 실행되며 추측이 틀리면 revert된다.

PTB 기반 composition attack으로부터 보호하기 위해 Sui는 Random을 입력으로 사용하는 MoveCall command 뒤에 TransferObjectsMergeCoins가 아닌 command가 오는 PTB를 거부한다.

Instantiating RandomGenerator

RandomGenerator는 이를 소비하는 module이 생성하는 한 안전하다. 이를 인자로 전달하면 caller가 예를 들어 bcs::to_bytes(&generator)를 호출하고 internal state를 parsing하여 그 RandomGenerator instance의 출력을 예측할 수 있다.

Move compiler는 RandomGenerator를 인자로 받는 public 함수를 거부함으로써 이 동작을 강제한다.

Accessing Random from TypeScript

module example에서 roll_dice(r: &Random, ctx: &mut TxContext)를 호출하려면 다음 코드를 사용한다:

const tx = new Transaction();
tx.moveCall({
target: "${PACKAGE_ID}::example::roll_dice",
arguments: [tx.object.random()]
});
...

Best practices

random number에 접근할 수 있다는 것은 secure application을 설계하는 일의 한 부분일 뿐이다. randomness를 어떻게 사용하는지도 세심하게 주의해야 한다.

randomness에 안전하게 접근하려면:

  • 함수를 (private) entry로 정의한다.

  • 함수 로컬 RandomGenerator를 사용해 randomness를 생성하는 방식을 선호한다.