Skip to content

zeta-chain/protocol-contracts-solana

Folders and files

NameName
Last commit message
Last commit date

Latest commit

b09affd · Aug 7, 2024

History

28 Commits
Aug 7, 2024
Jun 18, 2024
Aug 7, 2024
Aug 7, 2024
Jul 2, 2024
Aug 7, 2024
Jun 20, 2024
Jun 20, 2024
Jun 18, 2024
Aug 7, 2024
Jul 2, 2024
Jun 22, 2024
Jun 20, 2024
Jun 22, 2024

Repository files navigation

Introduction

This repository hosts the smart contracts (program) on Solana network to support ZetaChain cross-chain functionality. Specifically, it consists of a single program deployed (to be deployed; show address here) which allows the following two actions:

  1. Users on Solana network can send SOL or selected SPL tokens to the program to deposit into ZetaChain and optionally invoke a ZetaChain EVM contract.
  2. Allows contracts on ZetaChain EVM to withdraw SOL and SPL tokens to users on Solana;
  3. (TO DO) In the withdraw above, optionally allow a contract on ZetaChain EVM to withdraw SOL/SPL tokens and call a user specified contract (program) with parameters.

Authentication and Authorization

Anyone can deposit and remote invoke ZetaChain contracts.

Only ZetaChain TSS account can make withdraw or withdrawAndCall transactions on the program. The ZetaChain TSS account is a collection of Observer/KeySigners which uses ECDSA TSS (Threshold Signature Scheme) to sign outbound transactions. The TSS address will appear in this program a single ECDSA secp256k1 address; but it does not have a single private key, rather its private key consists of multiple key shares and they collectively sign a message in a KeySign MPC ceremony. The program authenticates via verifying the TSS signature and is authorized by ZetaChain to perform outbound transactions as part of ZetaChain cross-chain machinery.

The ZetaChain TSS is on ECDSA secp256k1 curve; But Solana native digital signature scheme is EdDSA Ed25519 curve. Therefore the program uses custom logic to verify the TSS ECDSA signature (like alternative authentication in smart contract wallet); the native transaction signer (fee payer on Solana) does not carry authorization and it's only used to build the transaction and pay tx fees. There are no restrictions on who the native transaction signer/fee payer is. The following code excerpt is for authenticating TSS signature in the contract itself, using the Rust secp256k1 library bundled with solana:

let address = recover_eth_address(&message_hash, recovery_id, &signature)?; // ethereum address is the last 20 Bytes of the hashed pubkey
msg!("recovered address {:?}", address);
if address != pda.tss_address {
msg!("ECDSA signature error");
return err!(Errors::TSSAuthenticationFailed);
}

The function recover_eth_address is implemented in the gateway program:

fn recover_eth_address(
message_hash: &[u8; 32],
recovery_id: u8,
signature: &[u8; 64],
) -> Result<[u8; 20]> {
let pubkey = secp256k1_recover(message_hash, recovery_id, signature)
.map_err(|_| ProgramError::InvalidArgument)?;
// pubkey is 64 Bytes, uncompressed public secp256k1 public key
let h = hash(pubkey.to_bytes().as_slice()).to_bytes();
let address = &h.as_slice()[12..32]; // ethereum address is the last 20 Bytes of the hashed pubkey
msg!("recovered address {:?}", address);
let mut eth_address = [0u8; 20];
eth_address.copy_from_slice(address);
Ok(eth_address)
}

The TSS signature is a ECDSA secp256k1 signature; its public key therefore address (Ethereum compatible hashing from pubkey) is therefore verifiable using the secp256k1_recover function. Alternatively, Solana runtime also privides a program to provide this verification service via CPI; see proposal 48 which might be more cost efficient.

In both withdraw and withdrawAndCall instructions, the ECDSA signed message_hash must commit to the nonce, amount, and to address. See the check in these instructions like:

let mut concatenated_buffer = Vec::new();
concatenated_buffer.extend_from_slice(&nonce.to_be_bytes());
concatenated_buffer.extend_from_slice(&amount.to_be_bytes());
concatenated_buffer.extend_from_slice(&ctx.accounts.to.key().to_bytes());
require!(message_hash == hash(&concatenated_buffer[..]).to_bytes(), Errors::MessageHashMismatch);
The commitment of nonce in the signature prevents replay of the TSS ECDSA signed message. Also (to be added in #6) a chain id of Solana should be added to the commitment of the message hash, so that the signature cannot be replayed on other blockchains that potentially uses similar authentication (say in TON).

Build and Test Instructions

Prerequisites: a recent version of rust compiler and cargo package manger must be installed. The program is built with the anchor framework so it needs to be installed as well; see installation

Please install compatible Solana tools and anchor-cli before build, otherwise the program will not be built successfully

$ solana-install init 1.18.15

$ cargo install --git https://github.com/coral-xyz/anchor --tag v0.30.0 anchor-cli --locked

To show the installed versions of the tools

$ cargo-build-sbf --version
solana-cargo-build-sbf 1.18.15
platform-tools v1.41
rustc 1.75.0

$ anchor --version
anchor-cli 0.30.0

To build (this will require at least 2GB disk space)

$ anchor build

To run the tests

$ anchor test

Relevant Account and Addresses

The Gateway program derive a PDA (Program Derived Address) with seeds b"meta" and canonical bump. This PDA account/address actually holds the SOL balance of the Gateway program. For SPL tokens, the program stores the SPL token in PDA derived ATAs. For each SPL token (different mint account), the program creates ATA from PDA and the Mint (standard way of deriving ATA in Solana SPL program).

The PDA account itself is a data account that holds Gateway program state, namely the following data types

pub struct Pda {
nonce: u64, // ensure that each signature can only be used once
tss_address: [u8; 20], // 20 bytes address format of ethereum
authority: Pubkey,
}

The nonce is incremented on each successful withdraw transaction, and it's used to prevent replay of signed ECDSA messages. The tss_address is the TSS address of ZetaChain (20Bytes, Ethereum style). authority is the one who can update the TSS address stored in PDA account.

The initialize instruction sets nonce to 0.