All pages
Powered by GitBook
1 of 9

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Tech Docs

Endless Account

An account on the Endless blockchain represents access control over a set of assets including on-chain currency and NFTs. In Endless, these assets are represented by a Move language primitive known as a resource that emphasizes both access control and scarcity.

Each account on the Endless blockchain is identified by a 32-byte. More details, refer:

Account Address Format

Different from other blockchains where accounts and addresses are implicit, accounts on Endless are explicit and need to be created before they can execute transactions.

The account can be created explicitly or implicitly by transferring Endless Coin(EDS) there. See the Creating an account section for more details.

In a way, this is similar to other chains where an address needs to be sent funds for gas before it can send transactions.

Explicit accounts support "native" Multisig feature via Authentication key; accounts on Endless support k-of-n multisig using Ed25519 signature schemes when constructing the 32-byte authentication key.

There are three types of accounts on Endless:

  • Standard account - This is a typical account corresponding to an address with a corresponding pair of public/private keys.

  • Resource account - An autonomous account without a corresponding private key used by developers to store resources or publish modules on-chain.

  • Object - A complex set of resources stored within a single address representing a single entity.

Creating an account

When a user requests to create an account, for example by using the Endless SDK, the following steps are executed:

  • Generate a new private key, public key pair with Ed25519 authentication scheme.

  • Combine the public key with the public key’s authentication scheme to generate a 32-byte authentication key and the account address.

The user should use the private key for signing the transactions associated with this account.

Account sequence number

The sequence number for an account indicates the number of transactions that have been submitted and committed on-chain from that account. Committed transactions either execute with the resulting state changes committed to the blockchain or abort wherein state changes are discarded and only the transaction is stored.

Every transaction submitted must contain a unique sequence number for the given sender’s account. When the Endless blockchain processes the transaction, it looks at the sequence number in the transaction and compares it with the sequence number in the on-chain account. The transaction is processed only if the sequence number is equal to or larger than the current sequence number. Transactions are only forwarded to other mempools or executed if there is a contiguous series of transactions from the current sequence number. Execution rejects out of order sequence numbers preventing replay attacks of older transactions and guarantees ordering of future transactions.

Authentication key

The initial account address is equal to authentication key during account creation. However, the authentication key content may subsequently change, from one authentication key to a list of authentication keys, for example when you upgrade an account to , adding more accounts as individual signer.

The Endless blockchain supports the following authentication schemes:

  • Ed25519

  • K-of-N multi-signatures

The Endless blockchain defaults to Ed25519 signature.

Ed25519 authentication

To generate an authentication key and the account address for an Ed25519 signature:

  1. Generate a key-pair: Generate a fresh key-pair (privkey_A, pubkey_A). The Endless blockchain uses the PureEdDSA scheme over the Ed25519 curve, as defined in .

  2. Derive a 32-byte authentication key Derive a 32-byte authentication key from the pubkey_A:

where | denotes concatenation. The 0x00 is the 1-byte single-signature scheme identifier.

  1. Use this initial authentication key as the permanent account address.

MultiEd25519 authentication

With K-of-N multisig authentication, there are a total of N signers for the account, and at least K of those N signatures must be used to authenticate a transaction.

To generate a K-of-N multisig account’s authentication key and the account address, we may choose via Endless , or Endless CLI.

Here we use Endless CLI to demonstrates:

  1. Generate key-pairs: Generate N ed25519 public keys p_1, …, p_n.

  2. Decide on the value of K, the threshold number of signatures needed for authenticating the transaction.

  3. Fund these accounts to ensure accounts are created on chain.

  4. Here we choose p_1 as multisig account, and adding left N-1 accounts into p_1

repeat to add all accounts into p_1's authentication key list. Now p_1 account is Multisig account, and Threshold is 1-of-N

  1. Update Threshold K of p_1.

Now approve any transaction on behaves of p_1 account, K signatures is required at least.

State of an account

The state of each account comprises both the code (Move modules) and the data (Move resources). An account may contain an arbitrary number of Move modules and Move resources:

  • Move modules: Move modules contain code, for example, type and procedure declarations; but they do not contain data. A Move module encodes the rules for updating the Endless blockchain’s global state.

  • Move resources: Move resources contain data but no code. Every resource value has a type that is declared in a module published on the Endless blockchain.

Access control with signers

The sender of a transaction is represented by a signer. When a function in a Move module takes signer as an argument, the Endless Move VM translates the identity of the account that signed the transaction into a signer in a Move module entry point. See the below Move example code with signer in the initialize and withdraw functions. When a signer is not specified in a function, for example, the below deposit function, then no signer-based access controls will be provided for this function:

coin.move

authentication keys.
multi-signature account
RFC 8032
Multi-Signature
SDK Typescript
auth_key = sha3-256(pubkey_A | 0x00)
$ endless account add-authentication-key \ 
    --private-key ${p1-private-key} \ 
    --new-private-key ${p2-private-key} 
$ endless account update-signature-required \ 
    -n ${K} \ 
    --private-key ${anyone-in-the-list-of-private-key} 
module Test::Coin {
  struct Coin has key { amount: u64 }
 
  public fun initialize(account: &signer) {
    move_to(account, Coin { amount: 1000 });
  }
 
  public fun withdraw(account: &signer, amount: u64): Coin acquires Coin {
    let balance = &mut borrow_global_mut<Coin>(Signer::address_of(account)).amount;
    *balance = *balance - amount;
    Coin { amount }
  }
 
  public fun deposit(account: address, coin: Coin) acquires Coin {
      let balance = &mut borrow_global_mut<Coin>(account).amount;
      *balance = *balance + coin.amount;
      Coin { amount: _ } = coin;
  }
}

Account Address Format

The Endless blockchain account addresses use Base58 encoding and have the following characteristics:

  1. The length of most regular account addresses is between 43 and 44 characters.

  2. You can quickly differentiate between accounts by checking the first and last few characters of the address.

  3. Vanity addresses are supported.

Account addresses are composed of multiple characters. To ensure the address matches your expectations, checking only the beginning and ending characters is insufficient.

For example, 5SHvmLEaSr76dsKy4XLR5vMht14PRuLzJFx6svJzqorP is an Endless account address.

Currently, addresses in Base58 encoding format are fully supported in both the command line and the block explorer.

Below is an example of how account transactions are displayed on the :

On the backend, Endless addresses are stored as 32-byte arrays. In some cases, you may see this format in the command line or the browser. For instance, the Endless chain has two system account addresses, represented in hexadecimal format:

  • 0x0000000000000000000000000000000000000001, which can be simplified to 0x1. Its role is as the system account, responsible for executing system contracts.

  • 0x0000000000000000000000000000000000000004, which can be simplified to 0x4. Its role is as the system token account, responsible for managing "Tokens" and "NFTs."

To convert between Base58 format and byte format, you can use the following methods:

  • Online Tools: Search for keywords such as "base58 encoder decoder"

  • Python Code (requires the base58 library):

Safety Transaction

Transaction Simulation

Transaction simulation, often referred to as "dry runs," is a method used to predict the outcome of a blockchain transaction before broadcasting it on-chain. This process is especially useful for smart contracts and decentralized applications (dApps) to ensure transactions perform as intended.

Transaction simulation plays a critical role in enhancing security and mitigating malicious activity in the Web3 ecosystem. By simulating a transaction, you can inspect it for unexpected or harmful behavior prior to execution. For instance, when interacting with a new dApp or calling a smart contract function, a simulation may reveal unauthorized token transfers or wallet access that were neither disclosed nor expected. This capability is invaluable for identifying and avoiding scams or malicious contracts designed to compromise user funds.

Sponsored Transaction

Sponsored Transactions

Sponsored transactions on the Endless chain refer to transactions where the gas fees are paid by the contract invoked by the transaction.

On the Endless blockchain, executing transactions requires a fee, also known as a gas fee, which is typically paid in the native token of the Endless chain (EDS). Gas fees are paid to the validators of the Endless chain to ensure network security. However, for new users, especially those transitioning from Web 2.0, this requirement can increase the barrier to entry.

Sponsored transactions lower the difficulty of interacting with contracts for users, particularly beginners. By leveraging sponsored transaction technology, contracts can make it easier for users to interact while ensuring the contract itself covers the gas fees, thereby maintaining stable chain operations.

import base58

def encode_base58(data: bytes) -> str:
    """Encode bytes into a Base58 string"""
    return base58.b58encode(data).decode()

def decode_base58(data: str) -> bytes:
    """Decode a Base58 string into bytes"""
    return base58.b58decode(data)
block explorer
Use Cases
  • Scenario 1 (under construction)

  • Scenario 2 (under construction)

Sponsored Contract Functions

Sponsored transactions are implemented at the contract level. Any contract function intending to offer this feature can include the sponsored keyword before the function declaration. This instructs the VM to mark the function as a "sponsored contract function."

Below is an example:

When a transaction invokes the fund function, the gas fees for that transaction will be paid by the contract. Ensure the contract account has sufficient funds to cover these fees; otherwise, the user will encounter an error indicating insufficient tokens to pay the gas fees when submitting the transaction.

Although sponsored functions are easy to implement, consider the following points before using them:

  • Access Control

  • External Calls Only

Access Control Determine whether the contract function should be accessible to all users. If the function is intended to sponsor only specific users, implement user checks within the contract to exclude unwanted users. Failing to do so may result in unnecessary gas fee expenses.

External Calls Only Mark the contract function as an entry function to allow only direct user-initiated transactions to call it. Prevent external contracts from invoking the sponsored transaction function to guard against potential attacks.

entry sponsored fun fund(dst_addr: &signer) {
    ...
}

Designed Use Cases

  1. Understanding Transaction Outcomes Blockchain transactions, particularly those involving complex smart contracts or DeFi protocols, often yield outcomes that aren't immediately apparent. Simulations clarify the consequences of executing a transaction, providing users with a detailed preview of what they are signing. For instance, in DAO voting or executing intricate financial strategies, transaction simulation can illuminate the implications of a decision, leading to safer and better-informed choices.

  2. Gas Fee Estimation and Cost Savings Ethereum transactions incur gas fees, which can vary significantly based on network congestion and transaction complexity. Simulating transactions provides an accurate gas estimate, helping users avoid issues like overpaying or underpaying gas fees.

Overpaying unnecessarily increases transaction costs. Underpaying risks leaving transactions stuck in the mempool. By simulating a transaction beforehand, users can set appropriate gas limits and prices, minimizing costs and avoiding failures.

Transaction Simulation Spoofing

While transaction simulation is a powerful tool, it is inherently limitedβ€”it only predicts outcomes based on current blockchain states, without actual execution on-chain. This limitation can be exploited by attackers in what is known as transaction simulation spoofing.

In a typical spoofing scenario:

  1. The attacker lures the victim to a malicious website disguised as a legitimate platform.

  2. transaction simulates outputs, such as a "Claim" function, which deceptively shows that the user will receive a small amount of ETH.

  3. Trusting the wallet's simulation result, the victim signs the transaction, unknowingly allowing the attacker to drain their wallet.

  4. Between the simulation and actual execution, the attacker alters the on-chain contract state, causing the approved transaction to perform a different, malicious action.

Real-world Example

A victim signed a fraudulent transaction 30 seconds after an on-chain state change, resulting in the loss of 143.35 ETH.

Phishing transaction hash

This incident highlights the limitations of transaction simulations: on Ethereum and many other blockchains, simulation is not equivalent to "What You See Is What You Sign" (WYSIWYS).

Safety Switch in Endless Wallet

To address the limitations of transaction simulations, Endless Wallet introduces the Safe Transaction feature, equipped with a Safety Switch.

Safety_Switch

When enabled, the Safety Switch ensures that the simulation result (transaction output) is embedded within the transaction itself.

Here's how it works:

  • The Safety Switch hashes the user's balance change(specifically refers to user's balance deduction) resulting from the transaction and embeds the hash into a transaction field.

  • Nodes executing the transaction validate the user's actual balance change by recalculating the hash and comparing it to the embedded value.

  • If the two hashes do not match, the transaction is reverted.

By default enabling the Safety Switch, users can make their transactions deterministic, effectively mitigating the risk of simulation spoofing.

Designing Dapp Considerations

Any decentralized application (Dapp) that may submit non-deterministic transactions, such as those involving dynamic deduction of the sender's account balance, should be carefully considered. Examples include:

  • Liquidity pool swaps in Endless Swap, where the results depend on real-time asset ratios.

  • Any contract interaction that leads to variable deductions from the sender's transaction balance.

It is crucial to address the potential impact of the "Safety Transaction" feature, which may block transaction submissions. Possible solutions include:

  • Remind the end user to disable the "Safety Transaction" feature when submitting transactions via the wallet.

  • Alternatively, modify the program logic to adopt a "deterministic transaction" design.

Image source
transaction_simulation

Endless Coin(EDS)

On the Endless chain, all types of tokens adhere to the FungibleAsset(FA) standard. This concept is equivalent to "fungible tokens" on other blockchains, such as Ethereum. For simplicity, in the following sections, "token" and "asset" are used interchangeably.

Digital Assets

In simple terms, every token corresponds to:

  1. Token Metadata

  1. FungibleStore,the storage location for a specific account's token balance:

  1. FungibleAsset(FA), the memory representation of a fungible token in VM enviroment:

FungibleAsset's maximum_supply and FungibleStore's amount are all u128 type.

FungibleAsset only exists in VM memory; at the end of transaction, any FA needs be merged into FungibleStore of corrsponding Account, otherwise FA is burned or dropped.

Endless provides move functions to handle token operations, such as:

  • public entry fun transfer<T: key>(sender: & signer, from: Object<T>, to: Object<T>, amount: u128)

  • public fun withdraw<T: key>(owner: & signer, store: Object<T>, amount: u64): FungibleAsset

  • public fun deposit<T: key>(store: Object<T>, fa: FungibleAsset)

These functions enable token minting, transferring, and burning between accounts.

EDS Native Token

The metadata for the native token EDS is as follows:

The smallest denomination of EDS is Vein; 1 EDS is equivalent to veins.

Assume Alice has an account on the Endless Chain but does not currently hold any EDS tokens.

If Bob transfers 1 EDS to Alice, the transaction will involve the following steps:

1

Withdraw

Deduct 1 EDS from Bob's account and create a corresponding FungibleAsset with an amount of 1 EDS.

2

CreateFungibleStore

Check if Alice's account has an associated storage location for FungibleStore. If not, create a new storage location and link it to Alice's account.

3

System Function Calls Related

For more details on system functions related to EDS, refer to the

Token Locking & Distribution

Design Purpose

Due to the comparatively massive volumes sold during early-stage rounds, projects may encounter extreme selling pressure after the IDO and after the token has been posted on Centralized Exchange – CEX / Decentralized Exchange – DEX. It significantly lowers the price of tokens by increasing the total amount of tokens in circulation.

For the Token Economy to stay sustainable & effective, the majority of tokens must be kept by investors rather than being sold on the market. Members can stop the value of its token from sinking by locking up tokens. Additionally, it prevents team members from selling their tokens as soon as trade begins, safeguarding holders’ interests.

Endless's locking_coin_ex

struct Metadata has key {
    /// Token name, e.g., "US Dollar Tether"
    name: String,
    /// More commonly used token symbol, e.g., "USDT"
    symbol: String,
    /// Token precision, e.g., EDS or Ether
    decimals: u8,
    /// Token icon URI
    icon_uri: String,
    /// Project URI
    project_uri: String,
}
public fun mint_to<T: key>( ref: & MintRef, store: Object<T>, amount: u128)
  • public fun burn_from<T: key>( ref: & BurnRef, store: Object<T>, amount: u128)

  • ...

  • Deposit

    Call the system function deposit to store the token in Alice's FungibleStore.

    1Γ—1081 \times 10^{8}1Γ—108
    source code
    contract manages token distribution via a locking and unlocking mechanism, ensuring gradual token release over a defined period to control circulation.

    The contract allows any user to:

    • Lock any amount of any fungible token, including native EDS.

    • Specify the recipient account for unlocked tokens.

    • Define a two-stage release plan:

      • Specify a portion of tokens for immediate release at a designated epoch.

      • Release the remaining tokens linearly over a stipulated epoch interval.

    • The recipient invokes the do_claim function to fetch the released asset.

    API

    The following are the main API interfaces provided by this contract:

    View Functions

    Query total lock amount

    total_locks(token_address: address): u128

    • Function: Query the total locked amount for the specified token address.

    • Parameters: token_address - The token address.

    • Return value: Total locked amount.

    List all stakers

    public fun get_all_stakers(token_address: address): vector<address>

    • Function: Query all staker addresses for the specified token address.

    • Parameters: token_address - The token address.

    • Return value: List of staker addresses.

    Query one of staker's token locking amount

    public fun staking_amount(token_address: address, recipient: address): u128

    • Function: Query the staking amount for the specified staker at the specified token address.

    • Parameters: token_address - The token address, recipient - The staker address.

    • Return value: Staking amount.

    UnLockInfo

    the structure contains receiver's address when release, and next release plan(amount and related epoch)

    List all staker's token release-plan

    public fun get_all_stakers_unlock_info(token_address: address): vector<UnlockInfo>

    • Function: Query the unlock information for all stakers at the specified token address.

    • Parameters: token_address - The token address.

    • Return value: List of unlock information.

    Query one of staker's token release-plan

    public fun get_unlock_info(token_address: address, sender: address): UnlockInfo

    • Function: Query the unlock information for the specified staker at the specified token address.

    • Parameters: token_address - The token address, sender - The staker address.

    • Return value: Unlock information.

    Management Functions

    Add Locking Plan

    public entry fun add_locking_plan_from_unlocked_balance(sender: &signer, token_address: address, receiver: address, total_coins: u128, first_unlock_bps: u64, first_unlock_epoch: u64, stable_unlock_interval: u64, stable_unlock_periods: u64)

    • Function: Add a locking plan from the unlocked balance.

    • Parameters:

      • sender - The signer,

      • token_address - The token address,

      • receiver - The receiver address,

      • total_coins - The total locked amount,

      • first_unlock_bps - The first unlock percentage,

      • first_unlock_epoch - The first unlock epoch,

      • stable_unlock_interval - The stable unlock interval,

      • stable_unlock_periods - The number of stable unlock periods.

    Add Locking Plan

    public entry fun add_locking_plan(sender: &signer, token_address: address, receiver: address, total_coins: u128, first_unlock_bps: u64, first_unlock_epoch: u64, stable_unlock_interval: u64, stable_unlock_periods: u64)

    • Function: Add a locking plan.

    • Parameters:

      • sender - The signer,

      • token_address - The token address,

      • receiver - The receiver address,

      • total_coins - The total locked amount,

      • first_unlock_bps - The first unlock percentage,

      • first_unlock_epoch - The first unlock epoch,

      • stable_unlock_interval - The stable unlock interval,

      • stable_unlock_periods - The number of stable unlock periods.

    Claim leased tokens

    public entry fun claim(sender: &signer, token_address: address, amount: u128)

    • Function: Claim unlocked tokens.

    • Parameters:

      • sender - The signer,

      • token_address - The token address,

      • amount - The claim amount.

    Locking & Distribution
    struct FungibleStore has key {
        /// Address of the metadata object
        metadata: Object<Metadata>,
        /// Token balance
        balance: Aggregator<u128>,
        /// Freeze option
        frozen: bool,
    }
    struct FungibleAsset {
        // Associated token metadata
        metadata: Object<Metadata>,
        // Token amount
        amount: u128,
    }
    name: string::utf8(b"Endless Coin"),
    symbol: string::utf8(EDS_SYMBOL)
    decimals: 8,
    icon_uri: "https://www.endless.link/eds-icon.svg"
    project_uri: "https://www.endless.link"
    struct UnlockInfo has drop {
        address: address,
        unlocked: u128,
        unlock_list: vector<UnlockAt>,
    }

    On-Chain Multisig

    With K-of-N multisig authentication, there are a total of N signers for the account, and at least K of those N signatures must be used to authenticate a transaction.

    Endless support two method of MultiSig:

    • On-Chain K-of-N multisig

    • Off-Chain K-of-N multisig

    Here we describe the operations the On-Chain K-of-N multisig and compares with Off-Chain at the end.

    On-Chain K-of-N multisig

    Step 1: Pick an SDK

    Install your preferred SDK from the below list:

    • TypeScript SDK


    Step 2: Run the example

    Clone the endless-ts-sdk repo and build it:

    Navigate to the Typescript examples directory:

    Install the necessary dependencies:

    Run the example:

    Process flow

    1

    First, we will generate accounts for 4 owner accounts and fund them:

    2

    First we invoke view function 0x1::multisig_account::get_next_multisig_account_address to get multisig account address

    The Multisig Account addresss is derived from owner1 account address.

    We build a transaction to create a new Multisig Account, via invoke "0x1::multisig_account::create_with_owners()" and append owner2

    Pros and Cons

    On-Chain Multisig

    On-Chain multisig implement versatile MultiSig based on Move module, which could expand to support different key schemas in one trasaction, and more expandable features, eg. adding Weight to each account to support Weighted-Threashold multisig.

    On-Chain multisig account is created on "0x1::multisig_account" module, and dedicated multisig account. it means we cannot convert any pre-exists account with Ed25519 keypair, into an multisig account.

    any k-N multisig transaction, go through K transaction committment, K-1 for seperate approve of each owner, 1 transaction is for any owner execute the transaction. the whole process is gas consumption, compared with Off-Chain multisig.

    Off-Chain Multisig

    Off-Chain is built on the Endless Account authentication key and off-chain multisig service(Dapp). With well-designed Dapp user interface, users can easily collaborate to sign transactions, while the Dapp handles transaction submission to the Endless network, delivering a smoother operational experience.

    Compared to on-chain multisig, off-chain multisig significantly reduces gas consumption.

    Due to the structural invariance of Endless Account, off-chain multisig is less scalable and less versatile.

    For more details, please refer to the following resources:

    • : A tutorial demonstrating off-chain multisig.

    ,
    owner3
    the owner of multisig account, set
    Threshold
    to 2.

    owner1, the signer of transaction of "create multisig account", also is the owner of multisig account.

    output like below:

    3

    Any operation on multisig account is done via interact with "0x1::multisig_account" move module by transaction, eg:

    • create and submit a new transfer EDS multisig transaction

    • vote Appprove for this transaction

    • vote Reject for this transaction

    • execute the transfer EDS multisig transaction

    we create a new EDS transfer transaction, from multisig account to recipient, detailed step as below:

    1. generate transaction payload

    2. invoke "0x1::multisig_account::create_transaction" and pass serialized data of payload as input parameter

    3. any owner sign and submit the transaction

    create_transaction upload serialized transaction on Endless chain, and get transactionId(value is 1).

    owners could refer transactionId to vote.

    4

    Threshold of multisig is 2, means any transaction, at least 2 owners vote for "Approve" to make tranaction validate. eg:

    • if 2 owners vote for a transcation, one for "Approve", one for "Reject", the transaction execute successfully.

    • if 3 owners vote for a transcation, two for "Approve", one for "Reject", the transaction execute with failure.

    simular output as below:

    vote count As shown in the code above, owner1 voted in favor, owner3 voted against.

    owner2 executed the transaction, effectively casting a vote in favor.

    The vote count is 2 in favor, and this transaction can be executed, for the multi-signature governance module

    The transfer transaction fails due to multisig account balance is zero.

    5

    0x1::multisig_account module provide owner management functions as add_owner and remove_owner.

    we re-use the same process flow: owner1 voted in favor, owner3 voted against, and owner2 executed the transaction

    when execute multisig transaction, we can set transaction payload as new MultiSig(AccountAddress.fromString(multisigAddress)), only specify the multisig account address in the parameters, instead of set transfer traction payload like Step 2. It works because in Step 6. we already upload the AddingOwnerToMultisigAccount to multisig_account module.

    specifing the multisig account address in the parameters, indicates that we are specifying the next transaction in the queue within the multisig_account module to be executed, i.e., the AddingOwnerToMultisigAccount transaction.

    output as below:

    6

    We also can modify multisign threshold. At first we set threshold to 2 when create multisign account. now we set threshold to 3

    After re-set threshold, we invoke view function num_signatures_required with paramter of multisigAddress to fetch updated threashold

    onchain_multisig
    Your-First-Mulitisig
    Endless Multisig Dapp
    const rejectAndApprove = async (aprroveOwner: Account, rejectOwner: Account, transactionId: number): Promise<void> => {
      const rejectTx = await endless.transaction.build.simple({
        sender: aprroveOwner.accountAddress,
        data: {
          function: "0x1::multisig_account::reject_transaction",
          functionArguments: [multisigAddress, transactionId],
        },
      });
    
      const rejectSenderAuthenticator = endless.transaction.sign({ signer: aprroveOwner, transaction: rejectTx });
      const rejectTxResponse = await endless.transaction.submit.simple({
        senderAuthenticator: rejectSenderAuthenticator,
        transaction: rejectTx,
      });
      await endless.waitForTransaction({ transactionHash: rejectTxResponse.hash });
    
      console.log("reject_transaction:", scan(rejectTxResponse.hash));
    
      const approveTx = await endless.transaction.build.simple({
        sender: rejectOwner.accountAddress,
        data: {
          function: "0x1::multisig_account::approve_transaction",
          functionArguments: [multisigAddress, transactionId],
        },
      });
    
      const approveSenderAuthenticator = endless.transaction.sign({ signer: rejectOwner, transaction: approveTx });
      const approveTxResponse = await endless.transaction.submit.simple({
        senderAuthenticator: approveSenderAuthenticator,
        transaction: approveTx,
      });
      await endless.waitForTransaction({ transactionHash: approveTxResponse.hash });
    
      console.log("approve_transaction:", scan(approveTxResponse.hash));
    };
    transfer but fail:
    Creating a multisig transaction to transfer coins...
    create_transaction: https://scan.endless.link/txn/9hamxKBTUEVvKzeA94oxx8aLq62rZJ9AY6qaPaV8WNqL
    reject_transaction: https://scan.endless.link/txn/cEpCKXL2SwTiJr6fL1hNQwvTACY1dh5ZGVWSqbLRPMR
    approve_transaction: https://scan.endless.link/txn/2YZPh5THyvaazUN7RjYjV3z3kC8GhFWZ6hAYxBrpt4Qf
    const executeMultiSigTransferTransaction = async () => {
      const rawTransaction = await generateRawTransaction({
        endlessConfig: config,
        sender: owner2.accountAddress,
        payload: transactionPayload,
      });
    
      const transaction = new SimpleTransaction(rawTransaction);
    
      const owner2Authenticator = endless.transaction.sign({ signer: owner2, transaction });
      const transferTransactionReponse = await endless.transaction.submit.simple({
        senderAuthenticator: owner2Authenticator,
        transaction,
      });
      await endless.waitForTransaction({ transactionHash: transferTransactionReponse.hash });
    
      console.log("executeMultiSigTransferTransaction:", scan(transferTransactionReponse.hash));
    };
    const createAddingAnOwnerToMultiSigAccountTransaction = async () => {
      // Generate a transaction payload as it is one of the input arguments create_transaction expects
      const addOwnerTransactionPayload = await generateTransactionPayload({
        multisigAddress,
        function: "0x1::multisig_account::add_owner",
        functionArguments: [owner4.accountAddress],
        endlessConfig: config,
      });
    
      // Build create_transaction transaction
      const createAddOwnerTransaction = await endless.transaction.build.simple({
        sender: owner2.accountAddress,
        data: {
          function: "0x1::multisig_account::create_transaction",
          functionArguments: [multisigAddress, addOwnerTransactionPayload.multiSig.transaction_payload.bcsToBytes()],
        },
      });
      // Owner 2 signs the transaction
      const createAddOwnerTxAuthenticator = endless.transaction.sign({
        signer: owner2,
        transaction: createAddOwnerTransaction,
      });
      // Submit the transaction to chain
      const createAddOwnerTxResponse = await endless.transaction.submit.simple({
        senderAuthenticator: createAddOwnerTxAuthenticator,
        transaction: createAddOwnerTransaction,
      });
      await endless.waitForTransaction({ transactionHash: createAddOwnerTxResponse.hash });
    
      console.log("create_transaction:", scan(createAddOwnerTxResponse.hash));
    };
    const executeAddingAnOwnerToMultiSigAccountTransaction = async () => {
      const multisigTxExecution3 = await generateRawTransaction({
        endlessConfig: config,
        sender: owner2.accountAddress,
        payload: new TransactionPayloadMultiSig(new MultiSig(AccountAddress.fromString(multisigAddress))),
      });
    
      const transaction = new SimpleTransaction(multisigTxExecution3);
    
      const owner2Authenticator3 = endless.transaction.sign({
        signer: owner2,
        transaction,
      });
      const multisigTxExecution3Reponse = await endless.transaction.submit.simple({
        senderAuthenticator: owner2Authenticator3,
        transaction,
      });
      await endless.waitForTransaction({ transactionHash: multisigTxExecution3Reponse.hash });
    
      console.log("Execution:", scan(multisigTxExecution3Reponse.hash));
    };
    const createChangeSignatureThresholdTransaction = async () => {
      const changeSigThresholdPayload = await generateTransactionPayload({
        multisigAddress,
        function: "0x1::multisig_account::update_signatures_required",
        functionArguments: [3],
        endlessConfig: config,
      });
    
      // Build create_transaction transaction
      const changeSigThresholdTx = await endless.transaction.build.simple({
        sender: owner2.accountAddress,
        data: {
          function: "0x1::multisig_account::create_transaction",
          functionArguments: [multisigAddress, changeSigThresholdPayload.multiSig.transaction_payload.bcsToBytes()],
        },
      });
    
      // Owner 2 signs the transaction
      const changeSigThresholdAuthenticator = endless.transaction.sign({
        signer: owner2,
        transaction: changeSigThresholdTx,
      });
      // Submit the transaction to chain
      const changeSigThresholdResponse = await endless.transaction.submit.simple({
        senderAuthenticator: changeSigThresholdAuthenticator,
        transaction: changeSigThresholdTx,
      });
      await endless.waitForTransaction({ transactionHash: changeSigThresholdResponse.hash });
    
      console.log("changeSigThreshold:", scan(changeSigThresholdResponse.hash));
    };
    const getSignatureThreshold = async (): Promise<void> => {
      const multisigAccountResource = await endless.getAccountResource<{ num_signatures_required: number }>({
        accountAddress: multisigAddress,
        resourceType: "0x1::multisig_account::MultisigAccount",
      });
      console.log("Signature Threshold:", multisigAccountResource.num_signatures_required);
    };
    Threshold:
    changeSigThreshold: https://scan.endless.link/txn/CNLtLWBVBSS4wVPtvCiF5NQ4R55HEC883D2KpDqdcsrs
    reject_transaction: https://scan.endless.link/txn/C8rqvfJFBRjZxeUypqmX14qgUh8YXKHubh7YJmW3Phmk
    approve_transaction: https://scan.endless.link/txn/9BtopY8JLjHnMr1JNsRsjGDa5M6boUiPmtE8jGpQXPQg
    changeSigThreshold Execution: https://scan.endless.link/txn/8MUyc1d17YPSGtjyyCyRRyAr5FqcgNnRA8y9VDk8dwhC
    Signature Threshold: 3
    
    Multisig setup and transactions complete.
    git clone https://github.com/endless-labs/endless-ts-sdk.git
    cd endless-ts-sdk
    pnpm install
    pnpm build
    cd examples/typescript/endless
    pnpm install
    pnpm run onchain_multisig
    owner1 balance: 1000000000
    owner2 balance: 1000000000
    owner3 balance: 1000000000
    owner4 balance: 1000000000
    const payload: InputViewFunctionData = {
    function: "0x1::multisig_account::get_next_multisig_account_address",
    functionArguments: [owner1.accountAddress.toString()],
    };
    [multisigAddress] = await endless.view<[string]>({ payload });
    const createMultisig = await endless.transaction.build.simple({
        sender: owner1.accountAddress,
        data: {
            function: "0x1::multisig_account::create_with_owners",
            functionArguments: [
            [owner2.accountAddress, owner3.accountAddress],
            2,
            ["Example"],
            [new MoveString("SDK").bcsToBytes()],
            ],
        },
    });
    
    const owner1Authenticator = endless.transaction.sign({ signer: owner1, transaction: createMultisig });
    
    const res = await endless.transaction.submit.simple({
        senderAuthenticator: owner1Authenticator,
        transaction: createMultisig,
    });
    create_with_owners: https://scan.endless.link/txn/D5jRQVcV8B67UpFjjF74XAXyT7xu6uXbQD9WxJJG7enu
    Multisig Account Address: FRPXTpbeaWqALuqLrGMiypiN9MVzmh1NAG7hn4oQTd3y
    const createMultiSigTransferTransaction = async () => {
      console.log("Creating a multisig transaction to transfer coins...");
    
      transactionPayload = await generateTransactionPayload({
        multisigAddress,
        function: "0x1::endless_account::transfer",
        functionArguments: [recipient.accountAddress, 1_000_000],
        endlessConfig: config,
      });
    
      // Build create_transaction transaction
      const createMultisigTx = await endless.transaction.build.simple({
        sender: owner2.accountAddress,
        data: {
          function: "0x1::multisig_account::create_transaction",
          functionArguments: [multisigAddress, transactionPayload.multiSig.transaction_payload.bcsToBytes()],
        },
      });
    
      // Owner 2 signs the transaction
      const createMultisigTxAuthenticator = endless.transaction.sign({ signer: owner2, transaction: createMultisigTx });
    
      // Submit the transaction to chain
      const createMultisigTxResponse = await endless.transaction.submit.simple({
        senderAuthenticator: createMultisigTxAuthenticator,
        transaction: createMultisigTx,
      });
      await endless.waitForTransaction({ transactionHash: createMultisigTxResponse.hash });
    
      console.log("create_transaction:", scan(createMultisigTxResponse.hash));
    };
    executeMultiSigTransferTransaction: https://scan.endless.link/txn/DfA8L2PC3zP3WLGTBaXQRMrg39sfqcoDrXTpGzE6zupF
    recipient balance: 0
    adding an owner:
    create_transaction: https://scan.endless.link/txn/Ear1cyydwSbYm1g7LAXPFJkSzGCgNAVEDoR1wvR3anDD
    reject_transaction: https://scan.endless.link/txn/AdwWLqhdmSsQzkHBHYsGySFatdSVPGjzsigYrKRSebL9
    approve_transaction: https://scan.endless.link/txn/4mgS4uRf2FBvBeyMATPLkXvW25gGHnEH6HftiASkfcK5
    Execution: https://scan.endless.link/txn/B1VbZ3gVxo9n5oBevN4sTQzEnovrEn1SnSCMayWCRP8g
    Number of Owners: 4

    Randomness

    Randomization is an important feature in blockchain. The application of randomization results, such as generated random numbers, can be achieved similarly to, e.g., the election of block nodes, while trusted random numbers can provide support for numerous DApps, such as lotto, games, etc.

    In many other chains, the implementation of trusted random number generation requires the use of off-chain random number generation services, such as Ethereum, which requires off-chain beacons such as Chainlink or drand to provide external randomness.

    The disadvantages of relying on external beacons are that the randomness is too expensive or too slow to be generated for DApps that require higher speed random numbers; in addition, the external randomness must be sent to the contract through transactions, which complicates DApp development and user interaction.

    Reliable Randomness

    If a sequence is to be identified as random, it has to possess the following qualities:

    • Unpredictable β€” The result must be unknowable ahead of time.

    • Unbiased β€” Each outcome must be equally possible.

    • Provable β€” The result must be independently verifiable.

    • Tamper-proof β€” The process of generating randomness must be resistant to manipulation by any entity.

    Weighted Threshold VUF Essentials

    Here we discuss threshold VUF with an idealized key generation algorithm. The advantages of this approach, rather than immediately considering VUF keying using a DKG, is that it lets us focus on the VUF security and avoid any nuances due to the key generation phase.

    An -threshold VUF scheme for a finite message space is a tuple of polynomial time computable algorithms with the following properties:

    1. Setup.

      (public parameter) consists of a generator and a random generator . Let be the weights of the participants, be the total weights, and be the threshold.

    2. KeyGen, , .

      The key generation algorithm takes the public parameters then outputs:


    • Unpredictable depends on below:

      • outcomes aggregated signature, which is the income of VUF output. The procedure takes place in every block's 1st transaction (Block Metadata transaction), which means not any User or Validator could predict randomness in User Transaction of the current block.

    • Provable depends on below:

    PVSS Process

    Endless PVSS procedure, described as below:

    1. Public Parameter Setup

      • define groups and , generator , in group and in group ; as numbers of validators, threshold

      • denote pp (public parameter) = , the following functions will take pp as default input parameter.


    The Endless chain has built-in provision for trusted random number generation, which can better enhance the security of services based on this type of service. At the implementation level, Endless utilizes the Weighted Publicly Verifiable Secret Sharing (wPVSS) algorithm, which can be run efficiently by each verifying node and which is aggregatable to help reduce communication overhead. Meanwhile, there is a communication-efficient aggregatable weighted distributed key generation (wDKG) generation protocol on Endless. Finally, Endless has a novel Weighted Verifiable Random Function (wVRF), which the verifying node evaluates each round, with a constant amount of communication per verifying node, rather than a linear relationship with the amount pledged by the verifier.

    API Usage

    Endless framework module provide a series of function randomness APIs:

    • randomness::u8_integer() returns a u8 random number, evenly samples an 8-bit from Endless Randomness

    • randomness::u16_integer()

    • ...

    • randomness::u8_range(min_incl: u8, max_excl: u8)

    By repeatedly calling these functions, multiple objects can be safely sampled to generate multiple "unbiased" random numbers.

    Demonstration

    Endless Roll

    Here we build a lottery system and pick a random winner from n participants. In the centralized world with a trusted server, the roll algorithm is the backend simply calls a random integer sampling function (random.randint(0, n-1) in Python, or Math.floor(Math.random() * n) in JS).

    In Endless chain, we build a lottery move module:

    • let winner_idx = endless_framework::randomness::u64_range(0, n); is the randomness API call that returns a u64 integer in range [0, n) uniformly at random.

    • #[randomness] enables the move compiler to check the outermost of the call stack of decide_winner is private.

    Functions using randomness should be defined with private, to avoid Test and Abort attacks. decide_winner is an entry function, instead of public entry.

    Security Considerations

    Test and Abort Attack

    If decide_winner() were accidentally marked public, malicious players can deploy their own contract to:

    1. call decide_winner();

    2. read the lottery result (assuming the lottery module has some getter functions for the result);

    3. abort if the result is not in their favor. By repeatedly calling their own contract until a txn succeeds, malicious users can bias the uniform distribution of the winner (dApp developer’s initial design).

    Under Gas Attack

    Users should also put prevention of Undergassed Attack into security considerations when writing business logic.

    Imagine such a dApp. It defines a private entry function for a user to:

    1. toss a coin (gas cost: 9), then

    2. get a reward (gas cost: 10) if coin=1, or do some cleanup (gas cost: 100) otherwise.

    A malicious user can control its account balance, so it covers at most 108 gas units (or set transaction parameter max_gas=108), and the cleanup branch (total gas cost: 110) will always abort with an out-of-gas error. The user then repeatedly calls the entry function until it gets the reward.

    Here are some ideas of how to prevent undergassing attacks generally:

    • Make your entry function gas independent from the randomness outcome. The simplest example is to not β€œact” on the randomness outcome, i.e., read it and store it for later. Note that calling any other functions can have variable gas costs. For example, when calling randomness to decide which player should win, and then depositing the winnings to the winner might seem like a fixed gas cost. But, 0x1::coin::transfer / 0x1::fungible_asset::transfer can have a variable cost based on the user’s on-chain state.

    • If your dApp involves a trusted admin/admin group, only allow the trusted to execute randomness transactions (i.e., require an admin signer).

    • Make the path that is most beneficial have the highest gas (as the attacker can only abort paths with gas above a threshold he chooses). NOTE: that this can be tricky to get right, and the gas schedule can change, and is even harder to get right when there are more than 2 possible outcomes.

    Random, but not a secret

    Please keep in mind that random returned from calling the randomness API is randomness, but not a secret. The original seed in each block lies in 0x1::randomness::PerBlockRandomness which is public on the Endless chain.

    Users should not try to do the following:

    • Use the randomness API to generate an asymmetric key pair, discard the private key, then think the public key is safe.

    • Use the randomness API to shuffle some opened cards, veil them, and think no one knows the permutation.

    Non-reproducible β€” The process of generating randomness cannot be reproduced unless the original sequence is preserved.

    a secret key sk:=hask := h^{a}sk:=ha, where aaa is a random number in group G\mathbb{G}G
  • a vector of secret signing keys {sk1,…,skn}\{sk_1, \ldots, sk_n\}{sk1​,…,skn​}, using H:{0,1}βˆ—β†’GH:\{0, 1\}^{*} \to \mathbb{G}H:{0,1}βˆ—β†’G.

  • (aj)j∈[W](a_{j})_{j \in [W]}(aj​)j∈[W]​ is derived from ShamirShare(a,t,Wa, t, Wa,t,W)

  • a vector of verification keys {vk1,⋯ ,vkn}\{vk_1, \cdots, vk_n\}{vk1​,β‹―,vkn​}, where vki:=(hri,⋯ ,hriasi+wi)vk_{i} := (h^{r_{i}}, \cdots ,h^{r_{i}a_{s_{i}+w_{i}}})vki​:=(hri​,β‹―,hri​asi​+wi​​)

  • a vector of public keys equal to verification keys, i.e., pki:=vki,i∈[n]pk_{i} := vk_{i}, i \in [n]pki​:=vki​,i∈[n].

  • The public key size of a party in our weighted VUF is proportional to its weight, i.e., the public key vkivk_{i}vki​ of party iii contains wi+1w_{i} + 1wi​+1 group elements.

  • ShareSign(m,ski)β†’Οƒi(m,sk_{i}) β†’ \sigma_{i}(m,ski​)β†’Οƒi​.

    The partial signing algorithm is a possibly randomized algorithm that takes as input a message mmm and a secret key share skisk_iski​. It generates a signature share Οƒi\sigma_{i}Οƒi​.

  • ShareVerify(m,Οƒi,vki)β†’0/1(m, Οƒ_i, vk_i) \to 0/1(m,Οƒi​,vki​)β†’0/1.

    The signature share verification function is a deterministic algorithm that takes as input a message mmm, a public key share vkivk_ivki​, and a signature share Οƒi\sigma_iΟƒi​. It outputs 1 (accept) or 0 (reject).

    The function asserts e(hri,Οƒi)=?e(h,H(m))e(h^{r_{i}}, \sigma_{i}) \stackrel{?}{=} e(h, H(m))e(hri​,Οƒi​)=?e(h,H(m))

  • Aggregate(S,(Οƒi,i)i∈S)β†’Οƒ/βŠ₯Aggregate(S, {(Οƒ_i, i)}_{i \in S}) β†’ Οƒ/βŠ₯Aggregate(S,(Οƒi​,i)i∈S​)β†’Οƒ/βŠ₯.

    The signature share combining algorithm takes as input the public key pkpkpk, a vector of public key shares (pk1,⋯ ,pkn)(pk_1, \cdots, pk_n)(pk1​,β‹―,pkn​), a message mmm, and a set SSS of t+1t + 1t+1 signature shares (Οƒi,i)(\sigma_i, i)(Οƒi​,i) (with corresponding indices). It outputs either a signature Οƒ\sigmaΟƒ or βŠ₯.

  • Derive(m,vk,Οƒ)→ρ(m, vk, \sigma) β†’ \rho(m,vk,Οƒ)→ρ.

    The output derivation algorithm is a deterministic algorithm that takes as input a public key pkpkpk, a message mmm, and a signature σ\sigmaσ, and outputs the VUF output hohoho and the proof π\piπ.

  • Verify(m,pk,ρ,Ο€),pk:=vki,i∈[n](m, pk, \rho, \pi), pk :={vk}_{i}, i \in [n](m,pk,ρ,Ο€),pk:=vki​,i∈[n].

    The signature verification algorithm is a deterministic algorithm that takes as input a public key pkpkpk, a message mmm, and a VUF output hohoho and proof Ο€\piΟ€.

    • verify Ο€=?(S,Οƒii∈S)\pi \stackrel{?}{=} (S, {\sigma_{i}}_{i \in S})Ο€=?(S,Οƒi​i∈S​)

    • run Derive of Step 6: Derive(S,{Οƒi,vki}i∈S,m)→ρ′Derive(S, \{\sigma_{i}, vk_{i}\}_{i \in S}, m) \to \rho'Derive(S,{Οƒi​,vki​}i∈S​,m)→ρ′

    • check if , and outputs 1 (accept) or 0 (reject).

  • Pr[Pr[Pr[ShareVerify(m,ShareSign(m,ski),pki)(m, ShareSign(m,sk_i), pk_i)(m,ShareSign(m,ski​),pki​) = 1] = 1$$

  • Pr[Pr[Pr[Verify(m, Aggregate({(Οƒi,i)}i∈S),pk)=1:Οƒi=ShareSign(m,ski)βˆ€i∈S]=1βˆ’negl(Ξ»)(\{(\sigma_{i}, i)\}_{i \in S}), pk) = 1 : \sigma_i = ShareSign(m,sk_i) βˆ€i \in S] = 1 βˆ’ negl(Ξ»)({(Οƒi​,i)}i∈S​),pk)=1:Οƒi​=ShareSign(m,ski​)βˆ€i∈S]=1βˆ’negl(Ξ»)

  • Tamper-proof depends on below:

    • Pr[ShareVerify(m,Οƒi,pki)=1]=1Pr[ShareVerify(m, Οƒ_i, pk_i) = 1] = 1Pr[ShareVerify(m,Οƒi​,pki​)=1]=1

    • Pr[Verify(m,pk,Οƒ)=1]=1Pr[Verify(m, pk, \sigma) = 1] = 1Pr[Verify(m,pk,Οƒ)=1]=1

  • Unbiased depends on below:

    • VUF output hohoho is unbiased if

      • derivation algorithm is unbiased

      • input(m,pk,Οƒ)(m, pk, \sigma)(m,pk,Οƒ) is unbiased, e.g., use hash56(m) as input in the derivation function.

  • PVSS KeyGen(i)β†’(dki,eki)KeyGen(i) \to (dk_{i}, ek_{i})KeyGen(i)β†’(dki​,eki​)
    • the key generation protocol generates decryption key dkidk_{i}dki​, and the corresponding encryption key ekiek_{i}eki​

    • validator_{i}, indexed by i,i∈ni, i \in ni,i∈n privately keeps dkidk_{i}dki​, and publishes ekiek_{i}eki​

    • denote ek=[eki]i∈nek = [ek_i]_{i \in n}ek=[eki​]i∈n​

  • PVSS Share(ek,s)β†’trxShare(ek, s) \to trxShare(ek,s)β†’trx

    • leader node takes sss (secret) as input and generates secret shares s1,s2,⋯ ,sns_1, s_2, \cdots, s_ns1​,s2​,β‹―,sn​

    • leader node encrypts sis_isi​ share with encryption key ekiek_{i}eki​, outputs encrypted cic_{i}ci​ and proof Ο€i\pi_{i}Ο€i​

    • leader node generates commitment comcomcom for secret shares {si},i∈n\{s_{i}\}, i \in n{si​},i∈n

    • leader node commits transaction trx, taking () as PVSS transcript

    • commit transaction actually is , Step. 5 will aggregate these separate into one trx

  • PVSS Verify(ek,trx)β†’true/false(ek, trx) \to true/false(ek,trx)β†’true/false

    • assert (com,ci)β†’true/false(com, c_{i}) \to true/false(com,ci​)β†’true/false

    • assert (ci,eki,Ο€i)β†’true/false(c_{i}, ek_{i}, \pi_{i}) \to true/false(ci​,eki​,Ο€i​)β†’true/false

    check if comcomcom is a commitment to valid shares of some secret sss and the cic_{i}ci​ values consist of encryptions of valid shares of the same secret

  • PVSS Agg(trx1,trx2)β†’trx(trx_{1}, trx_{2}) \to trx(trx1​,trx2​)β†’trx

    • public function Aggregate that anyone can use to aggregate two PVSS transcripts into a single transcript.

    • repeat to call Agg() to aggregate all trxitrx_{i}trxi​ into one trx, to save nodes broadcast effort, also save space.

  • PVSS DecShare(trx,i,dki)β†’si(trx, i, dk_{i}) \to s_{i}(trx,i,dki​)β†’si​

    • take as input aggregated PVSS transcript trx, index iii, i-th decryption key dkidk_{i}dki​, outputs cic_{i}ci​

  • PVSS Recon(ek,trx,ci,{dki}i∈S)β†’s(ek, trx, c_{i}, \{dk_{i}\}_{i \in S}) \to s(ek,trx,ci​,{dki​}i∈S​)β†’s

    • each validator node decrypts cic_{i}ci​ with owned dkidk_{i}dki​ to get its share sis_{i}si​, and publishes sis_{i}si​ with corresponding NIZK proof ziz_{i}zi​

    • anyone could verify sis_{i}si​ with proof ziz_{i}zi​, and if shares collected combined weight greater than threshold ttt, the original secret sss could be recovered.

  • returns a u8 random number with specified minimal and maximum bound
  • ...

  • randomness::u256_range(min_incl: u256, max_excl: u256)

  • (n,t)(n, t)(n,t)
    M\mathbb {M}M
    βˆ‘=(KeyGen,ShareSign,ShareVerify,Verify,Aggregate)\sum=(KeyGen, ShareSign, ShareVerify, Verify, Aggregate)βˆ‘=(KeyGen,ShareSign,ShareVerify,Verify,Aggregate)
    (1k,n,w,t)β†’pp(1^{k}, n, w, t) \to pp(1k,n,w,t)β†’pp
    pppppp
    h∈Gh \in \mathbb{G}h∈G
    H:{0,1}βˆ—β†’GH:\{0, 1\}^{*} \to \mathbb{G}H:{0,1}βˆ—β†’G
    w=[w1,⋯ ,wn]w = [w_{1},\cdots,w_{n}]w=[w1​,β‹―,wn​]
    W:=βˆ‘i∈[n]wi\mathbb{W} := \sum_{i \in [n]}{w_{i}}W:=βˆ‘i∈[n]​wi​
    ttt
    (pp)β†’sk(pp) \to sk(pp)β†’sk
    {vki}i∈[n]\{vk_{i}\}_{i \in [n]}{vki​}i∈[n]​
    {ski}i∈[n]\{sk_{i}\}_{i \in [n]}{ski​}i∈[n]​
    Aggregate(S,(Οƒi,i)i∈S)β†’ΟƒAggregate(S, {(Οƒ_i, i)}_{i \in S}) β†’ ΟƒAggregate(S,(Οƒi​,i)i∈S​)β†’Οƒ
    GGG
    G^\hat{G}G^
    ggg
    hhh
    GGG
    g^\hat gg^​
    G^\hat{G}G^
    nnn
    ttt
    (g,h,g^,n,t)(g, h, \hat{g}, n, t)(g,h,g^​,n,t)
    randomness.move
    module module_owner::lottery {
        // ...
     
        struct LotteryState {
            players: vector<address>,
            winner_idx: std::option::Option<u64>,
        }
     
        fun load_lottery_state_mut(): &mut Lottery {
            // authentication check 
            // return global borrow_mut LotteryState
            // ...
        }
     
        #[randomness]
        entry fun decide_winner() {
            let lottery_state = load_lottery_state_mut();
            let n = vector::length(&lottery_state.players);
            let winner_idx = endless_framework::randomness::u64_range(0, n);
            lottery_state.winner_idx = std::option::some(winner_idx);
        }
    }
    ho==ρ′ho == \rho'ho==ρ′
    com,{ci,Ο€i}com, \{c_{i}, \pi_{i}\}com,{ci​,Ο€i​}
    {trx1,trx2,⋯ ,trxi}\{trx_{1}, trx_{2}, \cdots, trx_{i}\}{trx1​,trx2​,β‹―,trxi​}
    trxitrx_{i}trxi​