Galactic Bridge

Intro to Galactic Bridge project

Whats is it?

Galactic bridge is a protocol that allows users to bridge multiple blockchain tokens into internet computer. Galactic bridge v1 is mainly focused on Solana native token and SPL tokens. It will enable seamless bi-directional value exchange between Solana and ICP. In next versions it could re-use the core logic and amend support for additional blockchains and assets.

Technical Overview

Galactic Bridge is using similar approach to ckETH and ckBTC. However, there are some differences caused by the Signature incompatibility. ICP can sign payload using tECDSA, where as Solana account generation is based on EdDSA. Because, of these specifics we cannot have a Solana account controlled by ICP Canister. We are solving this problem by implementing a Solana treasury Program that executes withdraws only after a valid tECDSA signed payload is provided and verified.

Main technologies used:

  • Chain-key cryptography

  • Https Outcall

  • ICP Canisters

  • SNS*

System Components

There are 4 main components in Galactic Bridge v1:

  • ICP Minter and Ledger Canisters

  • Solana Treasury Program

  • Solana RPC provider

  • Frontend Application

ICP Minter and Ledger Canister

Mint

Mint method manually generates Chain-key Sol asset. This functionality will become part of solana scrapper.

dfx canister call minter mint "(principal \"$USER_PRINCIPAL\", 1_000)"

dfx canister call ledger icrc1_balance_of "(record { owner = principal \"$USER_PRINCIPAL\" })"

Burn

Mint method manually removes Chain-key Sol asset. This functionality will become part of the withdraw flow.

dfx canister call ledger icrc2_approve "(record {
    spender = record { owner = principal \"$(dfx canister id minter)\" };
    amount = 1_000_000;
  })" --identity $USER_PRINCIPAL_NAME

dfx canister call minter burn "(1_000)" --identity $USER_PRINCIPAL_NAME

dfx canister call ledger icrc1_balance_of "(record { owner = principal \"$USER_PRINCIPAL\" })"

dfx canister call ledger icrc1_total_supply

Key Functions

get_address

Returns Threshold ECDSA address ("ecdsa_public_key") in 3 formats:

  1. compressed public key (size: 33 bytes, generated from icp)

  2. uncompressed public key (size: 64 bytes, generated from compressed version via "libsecp256k1" library)

  3. ethereum address (size: 20 bytes)

dfx canister call minter get_address

sign message

Signs a predefined message with "sign_with_ecdsa":

    let coupon = Coupon {
        address: "9gVndQ5SdugdFfGzyuKmePLRJZkCreKZ2iUTEg4agR5g".to_string(),
        amount: 10_000_000,
    };

Returns:

  1. coupon in json format

  2. hex of coupon

  3. signature

dfx canister call minter sign

verify

Input:

  1. signature

  2. message

  3. compressed key pair (via "k256" library)

Return: true/false

Example:

dfx canister call minter verify \
  '("fff722f41eb1cae151458c2d9acf16695984d1376a6a0a6ab56a385204f889370aec600906f278c5d9522d1df16ab50940827f96d7f62f61cd2ba33b28f2b7df", "{\"address\":\"9gVndQ5SdugdFfGzyuKmePLRJZkCreKZ2iUTEg4agR5g\",\"amount\":10000000}", "0269b3e4f4295275d99217c8d4f31a872d7af55c671fde7b0ed293650f9d1a4115")'

y_parity / recovery id

Input:

  1. signature

  2. message

  3. compressed public key (via "k256" library)

Return: Recovery id -> 0/1

dfx canister call minter y_parity \
  '("fff722f41eb1cae151458c2d9acf16695984d1376a6a0a6ab56a385204f889370aec600906f278c5d9522d1df16ab50940827f96d7f62f61cd2ba33b28f2b7df", "{\"address\":\"9gVndQ5SdugdFfGzyuKmePLRJZkCreKZ2iUTEg4agR5g\",\"amount\":10000000}", "0269b3e4f4295275d99217c8d4f31a872d7af55c671fde7b0ed293650f9d1a4115")'

get_ledger_id

dfx canister call minter get_ledger_id

Solana Treasury Program

It implements the main business logic related to Solana. The main functions supported by the program are:

Depositing SOL:

  • Users can deposit SOL by calling the deposit function with the desired amount and their ICP address.

    Inputs:

    • Context ctx: Provides access to relevant accounts and program information.

      • payer: This Signer account represents the party initiating the deposit and providing the funds.

      • treasury: This SystemAccount represents the account that will receive the deposited lamports.

      • system_program: This Program account represents the Solana system program, which provides functionalities like transferring lamports between accounts.

    • DepositData data: Contains the deposit details:

      • amount: The amount of lamports to deposit (u64).

      • address_icp: The ICP address to receive Chain-key Sol(String).

    Event Emission:

    • Emit a DepositEvent after a successful deposit.

    • This event provides information about the transaction to interested parties, enabling integration with other applications or monitoring systems.

      Fields within DepositEvent:

      • address_icp: This field stores the receiver's ICP address obtained from the data.address_icp input.

      • amount: This field holds the deposited amount (transfer_amount) transferred from the payer to the treasury.

    Outputs:

    • Result<()>: Indicates success (Ok()) or an error (Err(DepositError)).

    • DepositEvent: An emitted event containing:

      • address_icp: The ICP address to receive Chain-key Sol.

      • amount: The deposited amount.

    Algorithm Flow:

    1. Validation:

      • Checks if the payer is a signer. If not, returns DepositError::PayerNotSigner.

      • Verifies if the payer has enough lamports to cover the transfer. If not, returns DepositError::PayerInsufficientAmount.

    2. Transfer:

      • Uses the transfer instruction from the system program to transfer lamports from the payer's account to the treasury account.

    3. Event Emission:

      • Emits a DepositEvent with the receiver's ICP address and the deposited amount.

    4. Success:

      • Returns Ok(()) to signal successful deposit.

Withdrawing SOL:

  • Users can withdraw SOL from the treasury by calling withdraw function providing a signed message containing the withdrawal amount, receiver Solana address, a valid signature, and a valid data hash.

    Inputs:

    • Context ctx: Provides access to relevant accounts and program information.

    • WithdrawData data: Contains data for withdrawal:

      • message: A hashed message for verification (Vec).

      • signature: A signature over the message ([u8; 64]).

      • verify_data: Information for verification:

        • address: The recipient's address (String).

        • amount: The withdrawal amount in Ethereum format (u64).

    Outputs:

    • Result<()>: Indicates success (Ok()) or errors:

      • WithdrawError: Treasury-related issues.

      • ValidationError: Data or signature validation failures.

    Algorithm Flow:

    1. Validation:

      • Checks if the treasury has enough lamports for the withdrawal.

      • Calls utils::verify to verify:

        • Message hash matches the generated hash from verify_data.

        • Signature validity using provided Ethereum public key.

    2. Signer Seeds:

      • Prepares seeds for treasury PDA (Program Derived Account).

    3. Transfer:

      • Uses transfer with signer seeds to transfer lamports from treasury to receiver.

    4. Success:

      • Returns Ok(()) if successful.

Signature verification

  • This function utils::verify which is used in withdraw before transfering lamports, verifies the integrity and authenticity of a withdrawal request in a Solana program.

    Inputs:

    • eth_pubkey: Ethereum public key which is hardcoded in the Treasury program, associated with the withdrawal request ([u8; 64]).

    • msg: The message bytes representing the withdrawal information (Vec).

    • sig: The signature over the message ([u8; 64]).

    • msg_address: The recipient's address for the withdrawal (String).

    • msg_amount: The withdrawal amount in Ethereum format (u64).

    Outputs:

    • Result<()>: Indicates success (Ok()) or error (Err(ValidationError)):

      • InvalidDataHash: The message hash doesn't match the data derived from msg_address and msg_amount.

      • InvalidSignature: The signature doesn't match the provided Ethereum public key.

    Algorithm Flow:

    1. Message Construction:

      • Creates a formatted string message containing msg_address and msg_amount.

    2. Hashing:

      • Calculates the hash of the constructed message.

    3. Data Hash Validation:

      • Compares the provided message bytes (msg) with the hash of the constructed message. If they don't match, it throws an InvalidDataHash error, indicating a mismatch between the signed data and the claimed withdrawal details.

    4. Signature Verification:

      • Attempts to recover the public key from the signature (sig) using solana_program::secp256k1_recover::secp256k1_recover function.

      • If the recovered public key matches the pubkey embeded in the program, it signifies a valid signature.

    5. Validation Result:

      • If no match is found for the recovered public key and the pubkey embeded in the program, it throws an InvalidSignature error, indicating the signature doesn't correspond to the provided public key.

      • If data and signature verification are successful, the function returns Ok(()).

Frontend Application

Simplified UI that enables seamless wallet connection and Mint/Burn of ICP Chain-key Sol tokens.

Novelty and Algorithms

There are 2 main user flows in the protocol:

  • Deposit Solana tokens and Mint Chain-key Sol

  • Burn Chain-key Sol and Withdraw Solana

Deposit Solana Tokens and Mint Chain-key Sol

Deposit Algorithm is straight forward:

  • User A deposits Solana tokens in Solana Treasury Program using deposit function

  • Deposit data contains amount and address_icp

  • Solana Program emits DepositEvent

  • ICP Minter canister fetches DepositEvent

  • ICP Minter mint Chain-key Sol to address_icp

Burn Chain-key Sol and Withdraw Solana

Burn and Withdaw algorithm is the novel part that we have researched and developed. Verifying the tECDSA signature on the Solana Treasury program side is allowing us to control the treasury funds. This approach is universal and can be used for EVM and non-EVM chains that support Secp256k1.

  • User A gives icrc2_approve to Ledger Canister

  • User A executed burn transaction

  • Canister coupon with sign_with_ecdsa

let coupon = Coupon {
        address: "9gVndQ5SdugdFfGzyuKmePLRJZkCreKZ2iUTEg4agR5g".to_string(),
        amount: 10_000_000,
    };
returns 
        1 coupon in json format
        2 hex of coupon
        3 signature
  • User A uses the returned data to invoke withdraw in Solana Treasury Program

Withdraw Algorithm Solana

Algorithm Flow:

  1. Validation:

    • Checks if the treasury has enough lamports for the withdrawal.

    • Calls utils::verify to verify:

      • Message hash matches the generated hash from verify_data.

      • Signature validity using provided Ethereum public key.

  2. Signer Seeds:

    • Prepares seeds for treasury PDA (Program Derived Account).

  3. Transfer:

    • Uses transfer with signer seeds to transfer lamports from treasury to receiver.

  4. Success:

    • Returns Ok(()) if successful.

Signature Verification Solana

Algorithm Flow:

  1. Message Construction:

    • Creates a formatted string message containing msg_address and msg_amount.

  2. Hashing:

    • Calculates the hash of the constructed message.

  3. Data Hash Validation:

    • Compares the provided message bytes (msg) with the hash of the constructed message. If they don't match, it throws an InvalidDataHash error, indicating a mismatch between the signed data and the claimed withdrawal details.

  4. Signature Verification:

    • Attempts to recover the public key from the signature (sig) using solana_program::secp256k1_recover::secp256k1_recover function.

    • If the recovered public key matches the pubkey embeded in the program, it signifies a valid signature.

  5. Validation Result:

    • If no match is found for the recovered public key and the pubkey embeded in the program, it throws an InvalidSignature error, indicating the signature doesn't correspond to the provided public key.

    • If data and signature verification are successful, the function returns Ok(()).

Last updated