# 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
}
```