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_supplyKey Functions
get_address
Returns Threshold ECDSA address ("ecdsa_public_key") in 3 formats:
compressed public key (size: 33 bytes, generated from icp)
uncompressed public key (size: 64 bytes, generated from compressed version via "libsecp256k1" library)
ethereum address (size: 20 bytes)
dfx canister call minter get_addresssign message
Signs a predefined message with "sign_with_ecdsa":
let coupon = Coupon {
address: "9gVndQ5SdugdFfGzyuKmePLRJZkCreKZ2iUTEg4agR5g".to_string(),
amount: 10_000_000,
};Returns:
coupon in json format
hex of coupon
signature
dfx canister call minter signverify
Input:
signature
message
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:
signature
message
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_idSolana 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
depositfunction with the desired amount and their ICP address.Inputs:
Context ctx: Provides access to relevant accounts and program information.
payer: This
Signeraccount represents the party initiating the deposit and providing the funds.treasury: This
SystemAccountrepresents the account that will receive the deposited lamports.system_program: This
Programaccount 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
DepositEventafter 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_icpinput.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:
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.
Transfer:
Uses the
transferinstruction from the system program to transfer lamports from the payer's account to the treasury account.
Event Emission:
Emits a
DepositEventwith the receiver's ICP address and the deposited amount.
Success:
Returns
Ok(())to signal successful deposit.
Withdrawing SOL:
Users can withdraw SOL from the treasury by calling
withdrawfunction 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:
Validation:
Checks if the treasury has enough lamports for the withdrawal.
Calls
utils::verifyto verify:Message hash matches the generated hash from verify_data.
Signature validity using provided Ethereum public key.
Signer Seeds:
Prepares seeds for treasury PDA (Program Derived Account).
Transfer:
Uses
transferwith signer seeds to transfer lamports from treasury to receiver.
Success:
Returns
Ok(())if successful.
Signature verification
This function
utils::verifywhich is used inwithdrawbefore 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 frommsg_addressandmsg_amount.InvalidSignature: The signature doesn't match the provided Ethereum public key.
Algorithm Flow:
Message Construction:
Creates a formatted string message containing
msg_addressandmsg_amount.
Hashing:
Calculates the hash of the constructed message.
Data Hash Validation:
Compares the provided message bytes (
msg) with the hash of the constructed message. If they don't match, it throws anInvalidDataHasherror, indicating a mismatch between the signed data and the claimed withdrawal details.
Signature Verification:
Attempts to recover the public key from the signature (
sig) usingsolana_program::secp256k1_recover::secp256k1_recoverfunction.If the recovered public key matches the pubkey embeded in the program, it signifies a valid signature.
Validation Result:
If no match is found for the recovered public key and the pubkey embeded in the program, it throws an
InvalidSignatureerror, 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 functionDeposit data contains
amountandaddress_icpSolana Program emits
DepositEventICP Minter canister fetches
DepositEventICP Minter
mintChain-key Soltoaddress_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 signatureUser A uses the returned data to invoke
withdraw in Solana Treasury Program
Withdraw Algorithm Solana
Algorithm Flow:
Validation:
Checks if the treasury has enough lamports for the withdrawal.
Calls
utils::verifyto verify:Message hash matches the generated hash from verify_data.
Signature validity using provided Ethereum public key.
Signer Seeds:
Prepares seeds for treasury PDA (Program Derived Account).
Transfer:
Uses
transferwith signer seeds to transfer lamports from treasury to receiver.
Success:
Returns
Ok(())if successful.
Signature Verification Solana
Algorithm Flow:
Message Construction:
Creates a formatted string message containing
msg_addressandmsg_amount.
Hashing:
Calculates the hash of the constructed message.
Data Hash Validation:
Compares the provided message bytes (
msg) with the hash of the constructed message. If they don't match, it throws anInvalidDataHasherror, indicating a mismatch between the signed data and the claimed withdrawal details.
Signature Verification:
Attempts to recover the public key from the signature (
sig) usingsolana_program::secp256k1_recover::secp256k1_recoverfunction.If the recovered public key matches the pubkey embeded in the program, it signifies a valid signature.
Validation Result:
If no match is found for the recovered public key and the pubkey embeded in the program, it throws an
InvalidSignatureerror, indicating the signature doesn't correspond to the provided public key.If data and signature verification are successful, the function returns
Ok(()).
Useful Links
Last updated