# Designing Core Components for API ## Data structures three primary data structures that form the foundational components: `Round`, `SnapShot` and `SnapShotData`. At this stage, we have not factored in considerations related to Authentication and Security. ### Round Each round has several snapshots that are considered with block finalization of various chains. ```Rust! struct Round { round: usize, snapshots: Vec<SnapShot>, // candidate snapshots committed_snapshots: Option<Vec<SnapshotId>>, // this may replaced by index of snapshot // Metadata roundstatus: RoundStatus, } type SnapshotId = String; enum RoundStatus { NotStarted, // Round hasn't begun yet InProgress, // At least one snapshot started commit period Complete // All snapshot commit time period end. } ``` Having multiple candidates could be useful for proof generation even when some chains have not been finalized. For instance, if there are two blocks at the same height, we can proceed by generating two separate snapshots that each contain one of the candidate blocks. I anticipate that in most scenarios, we can expect the same snapshot in both the `snapshot` and `committed_snapshot` fields. Please remember that if we have a limit on the number of assets we can handle in a single snapshot, we might need to commit several snapshots to `SummaSC` to cover all types of assets. ### Snapshot I believe the system will be managing Summa on a snapshot basis. After consolidating the `chain_hashes`, we could generate a unique `id` for each snapshot ```Rust! struct SnapShot { id: String, round_num: usize, status: SnapShotStatus, timestamp: Instant, // can be called snapshot time `t` chain_hashes: Vec<ChainHash>, // the order of ChainHash might important if we use this for metatdata of the balances on the snapshot. // data for committing to the `SummaSC` round_info: Option<RoundInfo>, } enum SnapShotStatus { Initializing, // newly created and waiting for calculations, commit hash and others. Initialized, // Ready to commit, most of evaluataion be done, except generating proofs. Submitted, // For commit, sent Tx to the `SummaSC` wait for confirmation. CommitFailed, // The commit transaction reverted. Committed, // Should be shared Signed Account Data to each user, may can start generating proofs internally. FinalizeFailed, // There's a possibility that proof generation could fail after a commitment has been made. Finalized, // commit period closed and ready to share proofs } struct ChainHash { name: String, blockhash: String, timestamp: usize, // block time finalized: bool, // This flag may differently calculated by policy of CEX } struct RoundInfo { // possible to change `[u8; 32]` instead `String` commit_hash: String, eth_block_number: u64, signing_pubkey: String, commitment_time_period: u64, // usdc_block_hash: String, // more generalize data type for this. asset_hashes: Vec<String>, } impl SnapShot { pub fn new(round_id: usize, snapshot_id: String, chainhashes: Vec<ChainHash>) -> Self { // Self // starting with `Initializing` status. } pub fn generate_roundinfo(&self, commit_hash: String, eth_block_number: u64, signing_pubkey: String, commitperiod: usize) -> Result<(), Err> { // This function is responsible for updating the round information with `mst commitment` in V1. // // The existence of a `SnapShotData` instance signifies that all necessary computations for `mst_commitment` are complete, and it's ready to be used here. // // Initialize `SnapShotData` let mut asset_hashes = vec![]; for asset in self.chain_hashes { asset_hash.push(asset.blockhash) } self.round_info = RoundInfo { // ... asset_hashes } } pub fn get_roundinfo(&self) -> RoundInfo { // for generating `startRound` transaction return self.RoundInfo } // For inputs, should fetch data from `SnapShotData` pub fn get_public_input(&self, assets: &Vec<Asset>, signatures: &Vec<String>, account_data_hashes: &Vec<String>) { // Verify that the names in `ChainHashes` match the names in the `assets`. // Other verificaion steps are in here. i.e verifying signatures with account_data_hashes // return data for `verifyProofOfSolvency` tx // struct PublicInput { // bytes32[] cexBTCPubKey; // address[] cexETHPubKey; // uint[] cexBTCBalances; // uint[] cexETHBalances; // bytes[] signatures; // bytes32 mstRoot; // bytes32[] accountDataHash; // bytes32 btcBlockHash; // uint ethBlockNumber; // uint timestamp; // uint round; // } // the PublicInput not specified yet let public_input = PublicInput { // Asset data from assets // Asset1_block_hash // Asset2_block_hash // Asset1_PubKey // Asset2_PubKey // Asset1_Balances // Asset2_Balances signatures, account_data_hashes, mst_root: self.round_info.commit_hash, eth_block_number: self.round_info.eth_block_number, timestamp: self.timestamp, round: self.round_num, } } } ``` In the `roundinfo`, It should be included root of mst for V1. And includes block hashes. ```sequence Title: CEX operator flow for SummaSC participant SnapShotData participant SnapShot participant Signer participant SummaSC SnapShot->SnapShotData: generate_roundinfo() SnapShotData->SnapShot: assets, commit_hash, entries SnapShot->Signer: get_round_info() Note over Signer: generate `startRound` tx Signer->SummaSC: startRound SnapShot->SnapShotData: generate_proofs() Note over SnapShotData: Proof generation time \nmay increase with more users. SnapShot->SnapShotData: get_all_proofs() SnapShotData->SnapShot: proofs SnapShot->Signer: get_public_info() Signer->SummaSC: verifyProofOfSolvency ``` Note that this workflow may change if proof of inclusion is involved. The signer can be internal or external of the `SnapShot` instance. ### SnapShotData The actual data can be managed directly within `SnapShotData`. This component will continue running even after the `SnapShot` cycle is complete. However, for the early version of the Backend, it might be a good option to place this inside the `SnapShot`. This component role can be like these - Aggregating User Data - Signing Data - Generating Proofs For simplicity, the `entries` and `proofs` can be stored for the user as well as for the contract. ```Rust! struct SnapShotData<const N_ASSETS: usize> { snapshot_id: String, commit_hash: String, // i.e root of MST at V1 entries: HashMap<Name, Entry<N_ASSETS>>, assets: Vec<Asset> // the Name in `proofs` can be use for user and contracts. // the proofs may not be as Option, can be initalized proofs: Option<HashMap<Name, Proof>>, // can be define additional data for optimization } type Name = String; // this structure may change in future struct Asset { name: String, pubkeys: Vec<String>, balances: Vec<BigUint>, } // Consider storing this data in a database for improved efficiency and faster data retrieval. struct SignedAccountData<const N_ASSETS: usize> { // for signature inputs hash: String, username: String, balances: [BigUint; N_ASSETS], // metadata are same for all users meta_data: Vec<u8>, // i.e contract address, round_id etc.. } struct Proof { // depends on the circuit // The type T can be about a User or a Contract } impl SnapShotData<const N_ASSETS: usize> { fn new(commit_hash: &str, snapshot_id: u64, entries: Vec<Entry>, assets: Vec<Asset>) -> SnapShotData { // this can be consider MstInclusion & Solvency Circuit // For the Solvency Circuit, we have to store panu } // for SnapShot operator fn generate_proofs(&self) { // running backend for generating proofs for each users // may consider KGZ for e in entries { // the proof for each user and also generate for contract with specified name self.proofs.insert(e.username, proof) } } // for serving `SAD` to user fn get_sad(&self, username: &str) -> SignedAccountData<N_ASSETS> { // ... let balances = self.entries[username].balances; let message = create_message_from_entry(self.entries[username]; let hash = ethers.sign_message(message); SignedAccountData { hash, username: username.to_string(), balances, meta_data: vec![], // placeholder, } } // for serving proof by name fn get_proof(&self, name: &str) -> Proof {} } ``` A `SnapshotData` instance can be created once the `Snapshot` is initialized. #### Prepare entries - initializing snapshot There are two approaches for checking identity inclusion. - Merkle sum tree - KGZ commitment Certainly, we might need a wrapper to handle the two types of inclusion-checking schemes: Merkle Sum Trees and KGZ. This wrapper would maintain all entries in memory (or a database), waiting until data is flushed to the proof generator. If the private key for signing is not important, the `SignedEntry` may not need in `PrepareEntries`. It is because I expect that more than half of Signed message does not use in the commit phase at least. There is more efficient in generating a signed message when calling `get_entry`. ```Rust! struct PrepareEntries<const N_ASSETS: usize> { snapshotid: String, entries: Vec<SignedEntry<N_ASSETS>>, #[cfg(mode = "mst")] depth: Option<usize> } enum CsvForm { Path(String), Bytes(Vec<u8>) } impl PrepareEntries { fn new(snapshotid: String) -> Result<(), ()> { // depth will be calculated in here } // update entries with csv file path pub fn from_csv(&self, snapshotid: String, source: CsvForm) -> Result<(), ()> { match CsvForm { ... } } pub fn flush() -> Self { // depends on which inclusion return proper type } } ``` this method could be bound `Snapshot` or `SnapshotData` ## Interaction I presume that users can retrieve their data(i.e SAD, proofs and others) from the CEX through its web platform. In this section, I'm outlining middleware modules for the development of a higher-level API in the future. ### Verification There are two verification steps that take place on the user side, both of which occur off-chain. - Verification for Signed Account Data(SAD) - Verify `hash` with public key - Verification for Proofs - Verify `leaf_hash` with balances. - Verify `timestamp` in `RoundInfo`. Firstly, if the user believes their `SAD` is accurate, they can opt to commit it to the `SummaSC`, and they should retain the `SAD` for later verification of the `leaf_hash`. Secondly, when the user receives the proof for their round, they need to verify the `leaf_hash` and `timestamp` in the `RoundInfo`. To verify the `leaf_hash`, the user requires the balances from the `SAD` of the round that matches the one in the `RoundInfo`. Additionally, they need to ensure that the `timestamp` in the `RoundInfo` corresponds to the nearest block for that timestamp. For this, the user need API key(s) that query on chain data. #### SAD & Commit The user is required to store their entry, which is the Signed Account Data generated by the CEX. They must also manage their entries and proofs for verifying it accuracy and committing it to on-chain. ```Rust! struct CommittedEntry { txhash: Option<String>; contract_addres: String, // This field might not be necessary once the message specification is defined round_id: u64, signed_entry_hash: String, signed_entry: SignedEntry } struct MyEntries { // Entries unverified_entries: Vec<SignedEntry>, verified_entries: Vec<SignedEntry>, pending_entries: Vec<CommittedEntry>, // send tx for committing, but not included in finalized block. committed_entries: Vec<CommittedEntry>, } impl MyEntries { pub fn verify_entry(unverified_entry: SignedEntry) { // verify signature, if valid then move it to `verified_entries` } pub fn generate_commit(entry: SignedEntry) -> { // if possible, we can generate raw transaction for committing to target round // or return data for generating commit transaction to High level API(i.e metamask) } } ``` #### Proofs After committing phase, the CEX should be finished generating proofs for serving data to users. ```Rust! // Serving proof data from CEX to users struct ProofData { snapshot_id: String, contract_address: String, username: String, // this may different text inside the proofdata. proofdata: Vec<u8>, // these fields below, would be possible to get parsing proofdata, it depends on proofdata specified } ``` User can store all proofs on their device. ```Rust! struct ProofData { round_info: proof_data: Vec<u8>, // Includ public inputs } struct MyProofs { // TBD } ```