bridge: add secp check instruction (#41)

* bridge: add secp check instruction

* solana: update to secp solana upstream

* solana: iteration on secp

* solana: fix secp instruction

serialization indices were off and secp ix data was serialized twice

* solana: optimize ix serialization

* agent: send multiple chunks of signatures

* doc: update protocol spec

* solana: store signatures in siginfo; reconstruct signed VAA in webinterface

* solana: reformat

* solana: add rustfmt config
This commit is contained in:
Hendrik Hofstadt 2020-10-01 16:42:45 +02:00 committed by GitHub
parent 3701d16b84
commit 25533f0264
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1313 additions and 7972 deletions

View File

@ -31,42 +31,18 @@ There are multiple ways to measure whether enough validators have approved a dec
#### Multiple signatures - MultiSig #### Multiple signatures - MultiSig
The most simple solution is by using a *MultiSig* mechanism. This means that each guardian would sign a message The most simple solution is by using a *MultiSig* mechanism. This means that each guardian would sign a message
and submit it to a smart contract on-chain with reference to a *decision* that the guardians need to make (e.g. a transfer). and submit it via a P2P gossip network.
Since a transaction itself is already signed, we can simplify this to using the transaction itself as proof.
Said smart contract will count the number of guardians that have submitted a transaction for a *decision*. Once the consensus threshold has been reached, a guardian will aggregate all signatures into a VAA and execute/submit it
Once the consensus threshold has been reached, the contract will execute the action the guardians have agreed on. on the chain.
The issue with this schema is that it requires at least `n=2/3*m+1` transactions for `m` validators. On Ethereum for The downside here is that gas costs increase with larger guardian sets bringing verification costs to
example one such transaction would cost `21k+20k+x` gas (base + `SSTORE` \[to track the tx] + additional compute). `(5k+5k)*n` (`ECRECOVER+GTXDATANONZERO*72`).
With `n` txs and 20 guardians threshold (`2/3m+1`) the cost would be `n*(41k+x)` which is `820k+20x`.
At a gas price of `50 Gwei` this would mean total tx costs of `0.041 ETH` at `x=0`. At an ETH price of `300$` that To prevent lagging and complex gas price handling by validators or relayers, we always submit VAAs to Solana where txs
means costs of `12.3$`. are negligibly cheap. In the case of a Solana -> ETH transfer. Guardians would publish a signed VAA on Solana and a user
or independently paid relayer would publish said VAA on Ethereum, paying for gas costs. This mechanism is similar to a
These prices will require the guardians to charge significant fees. If these fees are not covered by the user, bridge check issued by the guardians (a VAA) which can be used on another chain to claim assets.
transactions would stall and time out.
There are a couple of other issues with this concept:
1. There is no way for the Solana Bridge program to verify whether the guardians have actually unlocked the tokens on
the foreign chain.
2. Users cannot cover gas costs themselves because transactions are not "portable". I.e. the require serialized nonces.
If a guardian submits a transaction with nonce 20 to the user but in the meantime issues another transaction with the
same nonce, the user tx will be invalid even though the Solana program might successfully verify the tx (as it does not
know the state of ETH).
There is an alternative way by using portable ECDSA signatures that approve an action i.e. a transfer. The guardians
could submit all of those signatures to the lock proposal and the user or another participant in the network could relay
them to Ethereum.
That way the Solana program can verify that the signatures and signed action are valid, being sure that if there is a
quorum (i.e. enough signatures), the user could use these signatures to trigger the execution of the signed action on
the foreign chain.
The downside here is that this makes tracking and synchronizing guardian changes highly complex and further increases
gas costs by about `(5k+5k)*n` (`ECRECOVER+GTXDATANONZERO*72`) for the additional `ecrecover` calls that need to be made.
However since all signatures can be aggregate into one tx, we'll save `(n-1)*21k` leading to an effective gas saving of
`~10k*n`. Still, transfers would be considerably expensive applying the aforementioned assumptions.
#### Threshold signatures #### Threshold signatures
@ -114,7 +90,7 @@ A great overview can be found [here](https://github.com/Turing-Chain/TSSKit-Thre
#### Design choices #### Design choices
For transfers we implement a Schnorr-Threshold signature schema based on the implementation from Chainlink. For transfers we implement a simple MultiSig schema.
We'll create a portable "action blob" with a threshold signature to allow anyone to relay action approvals We'll create a portable "action blob" with a threshold signature to allow anyone to relay action approvals
between chains. We call this structure: **VAA** (Verifiable Action Approval). between chains. We call this structure: **VAA** (Verifiable Action Approval).
@ -159,8 +135,6 @@ set.
ID: `0x01` ID: `0x01`
Size: `32 byte`
Payload: Payload:
``` ```
@ -176,8 +150,6 @@ desynchronization between the any of the chains in the system.
ID: `0x10` ID: `0x10`
Size: `75 byte`
Payload: Payload:
``` ```
@ -195,9 +167,6 @@ uint256 amount
#### Transfer of assets Foreign Chain -> Root Chain #### Transfer of assets Foreign Chain -> Root Chain
If this is the first time the asset is transferred to the root chain, the user inititates a `CreateWrapped` instruction
on the root chain to initialize the wrapped asset.
The user creates a token account for the wrapped asset on the root chain. The user creates a token account for the wrapped asset on the root chain.
The user sends a chain native asset to the bridge on the foreign chain using the `Lock` function. The user sends a chain native asset to the bridge on the foreign chain using the `Lock` function.
@ -210,7 +179,7 @@ They check for the validity, parse it and will then initiate a threshold signatu
produced VAA (`Transfer`) testifying that they have seen a foreign lockup. They will post this VAA on the root chain produced VAA (`Transfer`) testifying that they have seen a foreign lockup. They will post this VAA on the root chain
using the `SubmitVAA` instruction. using the `SubmitVAA` instruction.
This instruction will either mint a new wrapped assetor released tokens from custody. This instruction will either mint a new wrapped asset or release tokens from custody.
Custody is used for Solana-native tokens that have previously been transferred to a foreign chain, minting will be used Custody is used for Solana-native tokens that have previously been transferred to a foreign chain, minting will be used
to create new units of a wrapped foreign-chain asset. to create new units of a wrapped foreign-chain asset.
@ -233,19 +202,14 @@ Guardians will pick up the **LockProposal** once it has enough confirmations on
full confirmation (i.e. the max lockup, currently 32 slots), but can be changed to a different commitment levels full confirmation (i.e. the max lockup, currently 32 slots), but can be changed to a different commitment levels
on each guardian's discretion. on each guardian's discretion.
They check for the validity of the tx, parse it and will initiate an off-chain threshold signature ceremony which will They check for the validity of the tx, parse it and will initiate an off-chain signature aggregation ceremony which will
output a **VAA** that can be used with a foreign chain smart contract to reclaim an unwrapped local asset or mint a output a **VAA** that can be used with a foreign chain smart contract to reclaim an unwrapped local asset or mint a
wrapped `spl-token`. wrapped `spl-token`.
This VAA will be posted on Solana by one of the guardians using the `SubmitVAA` instruction and will be stored in the This VAA will be posted on Solana by one of the guardians using the `SubmitVAA` instruction and will be stored in the
`LockProposal`. `LockProposal`.
Depending on whether the fees are sufficient for **guardians** or **relayers** to cover the foreign chain fees, they The user can then get the VAA from the `LockProposal` and submit it on the foreign chain.
will also post the VAA on the foreign chain, completing the transfer.
If no fee or an insufficient fee is specified, the user can pick up the VAA from the `LockProposal` and submit it on the foreign chain themselves.
VAAs for conducting transfers to a foreign chain are submitted using `FinalizeTransfer`.
### Fees ### Fees
@ -254,11 +218,6 @@ TODO \o/
### Config changes ### Config changes
#### Guardian set changes #### Guardian set changes
Since we use a *TSS* (Threshold signature scheme) for VAAs, changes to the guardian list are finalized by setting a
new aggregate public key that's derived from a distributed key generation ("DKG") ceremony of the new guardian set.
This new public key is set via a VAA with the `UPDATE_GUARDIANS` action that is signed by the previous guardians.
The guardians need to make sure that the sets are synchronized between all chains. The guardians need to make sure that the sets are synchronized between all chains.
If the guardian set is changed, the guardian must also be replaced on all foreign chains. Therefore we If the guardian set is changed, the guardian must also be replaced on all foreign chains. Therefore we
conduct these changes via VAAs that are universally valid on all chains. conduct these changes via VAAs that are universally valid on all chains.
@ -269,4 +228,4 @@ chains.
If all VAAs issued by the previous guardian set would immediately become invalid once a new guardian set takes over, that would If all VAAs issued by the previous guardian set would immediately become invalid once a new guardian set takes over, that would
lead to some payments being "stuck". Therefore we track a list of previous guardian sets. VAAs issued by old lead to some payments being "stuck". Therefore we track a list of previous guardian sets. VAAs issued by old
guardian sets stay valid for one day from the time that the change happens. guardian sets stay valid for one day from the time that the change happens in the default configuration.

View File

@ -24,6 +24,17 @@ Pokes a `TransferOutProposal` so it is reprocessed by the guardians.
| ----- | ------ | ------------ | ------ | --------- | ----- | ------- | | ----- | ------ | ------------ | ------ | --------- | ----- | ------- |
| 0 | proposal | TransferOutProposal | | ✅ | | ✅ | | 0 | proposal | TransferOutProposal | | ✅ | | ✅ |
#### VerifySignatures
Checks secp checks (in the previous instruction) and stores results.
| Index | Name | Type | signer | writeable | empty | derived |
| ----- | ------ | ------------ | ------ | --------- | ----- | ------- |
| 0 | bridge_p | BridgeProgram | | | | |
| 1 | instructions | Sysvar | | | | ✅ |
| 2 | sig_status | SignatureState | | ✅ | | |
| 3 | guardian_set | GuardianSet | | | | ✅ |
#### TransferOut #### TransferOut
Burns a wrapped asset `token` from `sender` on the Solana chain. Burns a wrapped asset `token` from `sender` on the Solana chain.
@ -37,12 +48,13 @@ Parameters:
| 0 | bridge_p | BridgeProgram | | | | | | 0 | bridge_p | BridgeProgram | | | | |
| 1 | sys | SystemProgram | | | | | | 1 | sys | SystemProgram | | | | |
| 2 | token_program | SplToken | | | | | | 2 | token_program | SplToken | | | | |
| 3 | clock | Sysvar | | | | ✅ | | 3 | rent | Sysvar | | | | ✅ |
| 4 | token_account | TokenAccount | | ✅ | | | | 4 | clock | Sysvar | | | | ✅ |
| 5 | bridge | BridgeConfig | | | | | | 5 | token_account | TokenAccount | | ✅ | | |
| 6 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ | | 6 | bridge | BridgeConfig | | | | |
| 7 | token | WrappedAsset | | ✅ | | ✅ | | 7 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ |
| 8 | payer | Account | ✅ | | | | | 8 | token | WrappedAsset | | ✅ | | ✅ |
| 9 | payer | Account | ✅ | | | |
#### TransferOutNative #### TransferOutNative
@ -56,13 +68,14 @@ The transfer proposal will be tracked at a new account `proposal` where a VAA wi
| 0 | bridge_p | BridgeProgram | | | | | | 0 | bridge_p | BridgeProgram | | | | |
| 1 | sys | SystemProgram | | | | | | 1 | sys | SystemProgram | | | | |
| 2 | token_program | SplToken | | | | | | 2 | token_program | SplToken | | | | |
| 3 | clock | Sysvar | | | | ✅ | | 3 | rent | Sysvar | | | | ✅ |
| 4 | token_account | TokenAccount | | ✅ | | | | 4 | clock | Sysvar | | | | ✅ |
| 5 | bridge | BridgeConfig | | | | | | 5 | token_account | TokenAccount | | ✅ | | |
| 6 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ | | 6 | bridge | BridgeConfig | | | | |
| 7 | token | Mint | | ✅ | | | | 7 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ |
| 8 | payer | Account | ✅ | | | | | 8 | token | Mint | | ✅ | | |
| 9 | custody_account | TokenAccount | | ✅ | opt | ✅ | | 9 | payer | Account | ✅ | | | |
| 10 | custody_account | TokenAccount | | ✅ | opt | ✅ |
#### EvictTransferOut #### EvictTransferOut
@ -88,20 +101,6 @@ Deletes a `ClaimedVAA` after the `VAA_EXPIRATION_TIME` to free up space on chain
| 3 | bridge | BridgeConfig | | | | | | 3 | bridge | BridgeConfig | | | | |
| 4 | claim | ClaimedVAA | | ✅ | | ✅ | | 4 | claim | ClaimedVAA | | ✅ | | ✅ |
#### CreateWrappedAsset
Creates a new `WrappedAsset` to be used to create accounts and later receive transfers on chain.
| Index | Name | Type | signer | writeable | empty | derived |
| ----- | -------- | ------------------- | ------ | --------- | ----- | ------- |
| 0 | bridge_p | BridgeProgram | | | | |
| 1 | sys | SystemProgram | | | | |
| 2 | token_program | SplToken | | | | |
| 3 | bridge | BridgeConfig | | | | |
| 4 | payer | Account | ✅ | | | |
| 5 | wrapped_mint | WrappedAsset | | | ✅ | ✅ |
| 6 | wrapped_meta_account | WrappedAssetMeta | | ✅ | ✅ | ✅ |
#### SubmitVAA #### SubmitVAA
Submits a VAA signed by the guardians to perform an action. Submits a VAA signed by the guardians to perform an action.
@ -114,11 +113,13 @@ All require:
| ----- | ------------ | ------------ | ------ | --------- | ----- | ------- | | ----- | ------------ | ------------ | ------ | --------- | ----- | ------- |
| 0 | bridge_p | BridgeProgram | | | | | | 0 | bridge_p | BridgeProgram | | | | |
| 1 | sys | SystemProgram | | | | | | 1 | sys | SystemProgram | | | | |
| 2 | clock | Sysvar | | | | ✅ | | 2 | rent | Sysvar | | | | ✅ |
| 3 | bridge | BridgeConfig | | | | | | 3 | clock | Sysvar | | | | ✅ |
| 4 | guardian_set | GuardianSet | | | | | | 4 | bridge | BridgeConfig | | | | |
| 5 | claim | ExecutedVAA | | ✅ | ✅ | ✅ | | 5 | guardian_set | GuardianSet | | | | |
| 6 | payer | Account | ✅ | | | | | 6 | claim | ExecutedVAA | | ✅ | ✅ | ✅ |
| 7 | sig_info | SigState | | | ✅ | |
| 8 | payer | Account | ✅ | | | |
followed by: followed by:
@ -126,31 +127,31 @@ followed by:
| Index | Name | Type | signer | writeable | empty | derived | | Index | Name | Type | signer | writeable | empty | derived |
| ----- | ------------ | ------------------- | ------ | --------- | ----- | ------- | | ----- | ------------ | ------------------- | ------ | --------- | ----- | ------- |
| 7 | guardian_set_new | GuardianSet | | ✅ | ✅ | ✅ | | 9 | guardian_set_new | GuardianSet | | ✅ | ✅ | ✅ |
##### Transfer: Ethereum (native) -> Solana (wrapped) ##### Transfer: Ethereum (native) -> Solana (wrapped)
| Index | Name | Type | signer | writeable | empty | derived | | Index | Name | Type | signer | writeable | empty | derived |
| ----- | ------------ | ------------ | ------ | --------- | ----- | ------- | | ----- | ------------ | ------------ | ------ | --------- | ----- | ------- |
| 7 | token_program | SplToken | | | | | | 9 | token_program | SplToken | | | | |
| 8 | token | WrappedAsset | | | | ✅ | | 10 | token | WrappedAsset | | | | ✅ |
| 9 | destination | TokenAccount | | ✅ | | | | 11 | destination | TokenAccount | | ✅ | | |
| 10 | wrapped_meta | WrappedMeta | | ✅ | opt | ✅ | | 12 | wrapped_meta | WrappedMeta | | ✅ | opt | ✅ |
##### Transfer: Ethereum (wrapped) -> Solana (native) ##### Transfer: Ethereum (wrapped) -> Solana (native)
| Index | Name | Type | signer | writeable | empty | derived | | Index | Name | Type | signer | writeable | empty | derived |
| ----- | ------------ | ------------ | ------ | --------- | ----- | ------- | | ----- | ------------ | ------------ | ------ | --------- | ----- | ------- |
| 7 | token_program | SplToken | | | | | | 9 | token_program | SplToken | | | | |
| 8 | token | Mint | | | | ✅ | | 10 | token | Mint | | | | ✅ |
| 9 | destination | TokenAccount | | ✅ | opt | | | 11 | destination | TokenAccount | | ✅ | opt | |
| 10 | custody_src | TokenAccount | | ✅ | | ✅ | | 12 | custody_src | TokenAccount | | ✅ | | ✅ |
##### Transfer: Solana (any) -> Ethereum (any) ##### Transfer: Solana (any) -> Ethereum (any)
| Index | Name | Type | signer | writeable | empty | derived | | Index | Name | Type | signer | writeable | empty | derived |
| ----- | ------------ | ------------------- | ------ | --------- | ----- | ------- | | ----- | ------------ | ------------------- | ------ | --------- | ----- | ------- |
| 7 | out_proposal | TransferOutProposal | | ✅ | | ✅ | | 9 | out_proposal | TransferOutProposal | | ✅ | | ✅ |
## Accounts ## Accounts

686
solana/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -3,3 +3,5 @@ members = ["agent", "bridge", "cli"]
[patch.crates-io] [patch.crates-io]
solana-sdk = { git="https://github.com/solana-labs/solana", branch="master" } solana-sdk = { git="https://github.com/solana-labs/solana", branch="master" }
solana-client = { git="https://github.com/solana-labs/solana", branch="master" }
solana-account-decoder = { git="https://github.com/solana-labs/solana", branch="master" }

1038
solana/agent/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -9,11 +9,10 @@ tonic = "0.3.0"
tokio = { version = "0.2", features = ["rt-threaded", "time", "stream", "fs", "macros", "uds"] } tokio = { version = "0.2", features = ["rt-threaded", "time", "stream", "fs", "macros", "uds"] }
prost = "0.6" prost = "0.6"
prost-types = "0.6" prost-types = "0.6"
solana-sdk = { version = "1.3.3" } solana-sdk = { version = "1.3.11" }
solana-client = { version = "1.3.3" } solana-client = { version = "1.3.11" }
solana-faucet = "1.3.3" solana-faucet = "1.3.11"
solana-transaction-status = "1.3.3" spl-token = "=2.0.3"
spl-token = { package = "spl-token", git = "https://github.com/solana-labs/solana-program-library" }
wormhole-bridge = { path = "../bridge" } wormhole-bridge = { path = "../bridge" }
primitive-types = { version = "0.7.2" } primitive-types = { version = "0.7.2" }
hex = "0.4.2" hex = "0.4.2"
@ -26,6 +25,7 @@ log ="0.4.8"
serde_derive = "1.0.103" serde_derive = "1.0.103"
serde_json = "1.0.57" serde_json = "1.0.57"
bs58 = "0.3.1" bs58 = "0.3.1"
byteorder = "1.3.4"
[build-dependencies] [build-dependencies]
tonic-build = { version = "0.3.0", features = ["prost"] } tonic-build = { version = "0.3.0", features = ["prost"] }

View File

@ -1,37 +1,41 @@
use std::env; use std::env;
use std::fs::File;
use std::mem::size_of;
use std::rc::Rc;
use std::str::FromStr;
use std::sync::mpsc::RecvError;
use std::thread::sleep;
use solana_client::client_error::ClientError; use std::{io::Write, mem::size_of};
use solana_client::rpc_client::RpcClient;
use solana_client::rpc_config::RpcSendTransactionConfig; use std::str::FromStr;
use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt};
use solana_client::{
client_error::ClientError, rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig,
};
use solana_sdk::commitment_config::{CommitmentConfig, CommitmentLevel}; use solana_sdk::commitment_config::{CommitmentConfig, CommitmentLevel};
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::instruction::Instruction; use solana_sdk::instruction::Instruction;
use solana_sdk::program_error::ProgramError;
use solana_sdk::pubkey::Pubkey; use solana_sdk::{
use solana_sdk::signature::{read_keypair_file, write_keypair_file, Keypair, Signer}; pubkey::Pubkey,
use solana_sdk::transaction::Transaction; signature::{read_keypair_file, write_keypair_file, Keypair, Signature, Signer},
use solana_transaction_status::UiTransactionEncoding; system_instruction::create_account,
use spl_token::state::Account; transaction::Transaction,
use tokio::stream::Stream; };
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio::time::Duration;
use tonic::{transport::Server, Code, Request, Response, Status}; use tonic::{transport::Server, Code, Request, Response, Status};
use service::agent_server::{Agent, AgentServer};
use service::{ use service::{
lockup_event::Event, Empty, LockupEvent, LockupEventNew, LockupEventVaaPosted, agent_server::{Agent, AgentServer},
SubmitVaaRequest, SubmitVaaResponse, WatchLockupsRequest, lockup_event::Event,
Empty, LockupEvent, LockupEventNew, LockupEventVaaPosted, SubmitVaaRequest, SubmitVaaResponse,
WatchLockupsRequest,
};
use spl_bridge::{
instruction::{post_vaa, verify_signatures, VerifySigPayload, CHAIN_ID_SOLANA},
state::{Bridge, GuardianSet, SignatureState, TransferOutProposal},
vaa::VAA,
}; };
use spl_bridge::instruction::{post_vaa, CHAIN_ID_SOLANA};
use spl_bridge::state::{Bridge, TransferOutProposal};
use crate::monitor::{ProgramNotificationMessage, PubsubClient}; use crate::monitor::PubsubClient;
mod monitor; mod monitor;
@ -47,6 +51,12 @@ pub struct AgentImpl {
key: Keypair, key: Keypair,
} }
pub struct SignatureItem {
signature: [u8; 64 + 1],
key: [u8; 20],
index: u8,
}
#[tonic::async_trait] #[tonic::async_trait]
impl Agent for AgentImpl { impl Agent for AgentImpl {
async fn submit_vaa( async fn submit_vaa(
@ -56,42 +66,58 @@ impl Agent for AgentImpl {
// Hack to clone keypair // Hack to clone keypair
let b = self.key.to_bytes(); let b = self.key.to_bytes();
let key = Keypair::from_bytes(&b).unwrap(); let key = Keypair::from_bytes(&b).unwrap();
let bridge = self.bridge.clone();
let ix = match post_vaa(&self.bridge, &key.pubkey(), request.get_ref().vaa.clone()) {
Ok(v) => v,
Err(e) => {
return Err(Status::new(
Code::InvalidArgument,
format!("could not create instruction: {}", e),
));
}
};
let mut transaction = Transaction::new_with_payer(&[ix], Some(&key.pubkey()));
let rpc_url = self.rpc_url.clone(); let rpc_url = self.rpc_url.clone();
// we need to spawn an extra thread because tokio does not allow nested runtimes // we need to spawn an extra thread because tokio does not allow nested runtimes
std::thread::spawn(move || { std::thread::spawn(move || {
let rpc = RpcClient::new(rpc_url); let rpc = RpcClient::new(rpc_url);
let (recent_blockhash, fee_calculator) = match rpc.get_recent_blockhash() {
let sig_key = solana_sdk::signature::Keypair::new();
let mut vaa = match VAA::deserialize(&request.get_ref().vaa) {
Ok(v) => v, Ok(v) => v,
Err(e) => { Err(e) => {
return Err(Status::new( return Err(Status::new(
Code::Unavailable, Code::InvalidArgument,
format!("could not fetch recent blockhash: {}", e), format!("could not parse VAA: {}", e),
)); ));
} }
}; };
transaction.sign(&[&key], recent_blockhash); let verify_txs = pack_sig_verification_txs(&rpc, &bridge, &vaa, &key, &sig_key)?;
match rpc.send_and_confirm_transaction_with_spinner_and_config(
&transaction, // Strip signatures
CommitmentConfig { vaa.signatures = Vec::new();
commitment: CommitmentLevel::Single, let ix = match post_vaa(
}, &bridge,
RpcSendTransactionConfig { &key.pubkey(),
skip_preflight: true, &sig_key.pubkey(),
}, vaa.serialize().unwrap(),
) { ) {
Ok(v) => v,
Err(e) => {
return Err(Status::new(
Code::InvalidArgument,
format!("could not create post_vaa instruction: {}", e),
));
}
};
let mut transaction2 = Transaction::new_with_payer(&[ix], Some(&key.pubkey()));
for (mut tx, signers) in verify_txs {
match sign_and_send(&rpc, &mut tx, signers) {
Ok(_) => (),
Err(e) => {
return Err(Status::new(
Code::Unavailable,
format!("tx sending failed: {}", e),
));
}
};
}
match sign_and_send(&rpc, &mut transaction2, vec![&key]) {
Ok(s) => Ok(Response::new(SubmitVaaResponse { Ok(s) => Ok(Response::new(SubmitVaaResponse {
signature: s.to_string(), signature: s.to_string(),
})), })),
@ -115,15 +141,15 @@ impl Agent for AgentImpl {
async fn watch_lockups( async fn watch_lockups(
&self, &self,
req: Request<WatchLockupsRequest>, _req: Request<WatchLockupsRequest>,
) -> Result<Response<Self::WatchLockupsStream>, Status> { ) -> Result<Response<Self::WatchLockupsStream>, Status> {
let (mut tx, mut rx) = mpsc::channel(1); let (mut tx, rx) = mpsc::channel(1);
let url = self.url.clone(); let url = self.url.clone();
let bridge = self.bridge.clone(); let bridge = self.bridge.clone();
let rpc_url = self.rpc_url.clone(); let rpc_url = self.rpc_url.clone();
tokio::spawn(async move { tokio::spawn(async move {
let rpc = RpcClient::new(rpc_url.to_string()); let _rpc = RpcClient::new(rpc_url.to_string());
let sub = PubsubClient::program_subscribe(&url, &bridge).unwrap(); let sub = PubsubClient::program_subscribe(&url, &bridge).unwrap();
// looping and sending our response using stream // looping and sending our response using stream
loop { loop {
@ -225,6 +251,176 @@ impl Agent for AgentImpl {
} }
} }
fn pack_sig_verification_txs<'a>(
rpc: &RpcClient,
bridge: &Pubkey,
vaa: &VAA,
sender_keypair: &'a Keypair,
sign_keypair: &'a Keypair,
) -> Result<Vec<(Transaction, Vec<&'a Keypair>)>, Status> {
// Load guardian set
let bridge_key = Bridge::derive_bridge_id(bridge).unwrap();
let guardian_key =
Bridge::derive_guardian_set_id(bridge, &bridge_key, vaa.guardian_set_index).unwrap();
let guardian_account = rpc
.get_account_with_commitment(
&guardian_key,
CommitmentConfig {
commitment: CommitmentLevel::Single,
},
)
.unwrap()
.value
.unwrap_or_default();
let data = guardian_account.data;
let guardian_set: &GuardianSet = Bridge::unpack_immutable(data.as_slice()).unwrap();
// Map signatures to guardian set
let mut signature_items: Vec<SignatureItem> = Vec::new();
for s in vaa.signatures.iter() {
let mut item = SignatureItem {
signature: [0; 64 + 1],
key: [0; 20],
index: s.index,
};
item.signature[0..32].copy_from_slice(&s.r);
item.signature[32..64].copy_from_slice(&s.s);
item.signature[64] = s.v;
item.key = guardian_set.keys[s.index as usize];
signature_items.push(item);
}
let vaa_hash = match vaa.body_hash() {
Ok(v) => v,
Err(e) => {
return Err(Status::new(
Code::InvalidArgument,
format!("could get vaa body hash: {}", e),
));
}
};
let vaa_body = match vaa.signature_body() {
Ok(v) => v,
Err(e) => {
return Err(Status::new(
Code::InvalidArgument,
format!("could get vaa body: {}", e),
));
}
};
let mut verify_txs: Vec<(Transaction, Vec<&Keypair>)> = Vec::new();
for (tx_index, chunk) in signature_items.chunks(6).enumerate() {
let mut secp_payload = Vec::new();
let mut signature_status = [-1i8; 20];
let data_offset = 1 + chunk.len() * 11;
let message_offset = data_offset + chunk.len() * 85;
// 1 number of signatures
secp_payload.write_u8(chunk.len() as u8);
let secp_ix_index = if tx_index == 0 { 1u8 } else { 0u8 };
// Secp signature info description (11 bytes * n)
for (i, s) in chunk.iter().enumerate() {
secp_payload.write_u16::<LittleEndian>((data_offset + 85 * i) as u16);
secp_payload.write_u8(secp_ix_index);
secp_payload.write_u16::<LittleEndian>((data_offset + 85 * i + 65) as u16);
secp_payload.write_u8(secp_ix_index);
secp_payload.write_u16::<LittleEndian>(message_offset as u16);
secp_payload.write_u16::<LittleEndian>(vaa_body.len() as u16);
secp_payload.write_u8(secp_ix_index);
signature_status[s.index as usize] = i as i8;
}
// Write signatures and addresses
for s in chunk.iter() {
secp_payload.write(&s.signature);
secp_payload.write(&s.key);
}
// Write body
secp_payload.write(&vaa_body);
let secp_ix = Instruction {
program_id: solana_sdk::secp256k1_program::id(),
data: secp_payload,
accounts: vec![],
};
let payload = VerifySigPayload {
signers: signature_status,
hash: vaa_hash,
};
let verify_ix = match verify_signatures(
&bridge,
&sign_keypair.pubkey(),
vaa.guardian_set_index,
&payload,
) {
Ok(v) => v,
Err(e) => {
return Err(Status::new(
Code::InvalidArgument,
format!("could not create verify instruction: {}", e),
));
}
};
if tx_index == 0 {
// Instruction for creating the signature status account
let min_sig_rent = rpc
.get_minimum_balance_for_rent_exemption(size_of::<SignatureState>())
.unwrap();
let create_ix = create_account(
&sender_keypair.pubkey(),
&sign_keypair.pubkey(),
min_sig_rent,
size_of::<SignatureState>() as u64,
bridge,
);
verify_txs.push((
Transaction::new_with_payer(
&[create_ix, secp_ix, verify_ix],
Some(&sender_keypair.pubkey()),
),
vec![sender_keypair, sign_keypair],
))
} else {
verify_txs.push((
Transaction::new_with_payer(&[secp_ix, verify_ix], Some(&sender_keypair.pubkey())),
vec![sender_keypair],
))
}
}
Ok(verify_txs)
}
fn sign_and_send(
rpc: &RpcClient,
tx: &mut Transaction,
keys: Vec<&Keypair>,
) -> Result<Signature, ClientError> {
let (recent_blockhash, _fee_calculator) = rpc.get_recent_blockhash()?;
tx.sign(&keys, recent_blockhash);
rpc.send_and_confirm_transaction_with_spinner_and_config(
&tx,
CommitmentConfig {
commitment: CommitmentLevel::Single,
},
RpcSendTransactionConfig {
skip_preflight: false,
preflight_commitment: Some(CommitmentLevel::SingleGossip),
},
)
}
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();

View File

@ -1,6 +1,6 @@
use std::str::FromStr;
use std::{ use std::{
marker::PhantomData, marker::PhantomData,
str::FromStr,
sync::{ sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
mpsc::{channel, Receiver}, mpsc::{channel, Receiver},
@ -11,14 +11,16 @@ use std::{
use bs58; use bs58;
use log::*; use log::*;
use serde::{de::DeserializeOwned, de::Error, Deserialize, Deserializer, Serialize}; use serde::{
de::{DeserializeOwned, Error},
Deserialize, Deserializer, Serialize,
};
use serde_json::{ use serde_json::{
json, json,
value::Value::{Number, Object}, value::Value::{Number, Object},
Map, Value, Map, Value,
}; };
use solana_sdk::account::Account;
use solana_sdk::account_info::AccountInfo;
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use thiserror::Error; use thiserror::Error;
use tungstenite::{client::AutoStream, connect, Message, WebSocket}; use tungstenite::{client::AutoStream, connect, Message, WebSocket};

2340
solana/bridge/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -19,8 +19,8 @@ default = ["solana-sdk/default", "spl-token/default"]
num-derive = "0.2" num-derive = "0.2"
num-traits = "0.2" num-traits = "0.2"
remove_dir_all = "=0.5.0" remove_dir_all = "=0.5.0"
solana-sdk = { version = "1.3.3", default-features = false, optional = true } solana-sdk = { version = "1.3.11", default-features = false, optional = true }
spl-token = { package = "spl-token", git = "https://github.com/solana-labs/solana-program-library", default-features = false, optional = true } spl-token = { version = "=2.0.3", default-features = false, optional = true }
thiserror = "1.0" thiserror = "1.0"
byteorder = "1.3.4" byteorder = "1.3.4"
zerocopy = "0.3.0" zerocopy = "0.3.0"

View File

@ -100,6 +100,9 @@ pub enum Error {
/// VAA for this transfer has already been submitted /// VAA for this transfer has already been submitted
#[error("VAAAlreadySubmitted")] #[error("VAAAlreadySubmitted")]
VAAAlreadySubmitted, VAAAlreadySubmitted,
/// Mismatching guardian set
#[error("GuardianSetMismatch")]
GuardianSetMismatch,
} }
impl From<Error> for ProgramError { impl From<Error> for ProgramError {

View File

@ -38,6 +38,7 @@ impl PrintProgramError for Error {
Error::VAATooLong => info!("Error: VAATooLong"), Error::VAATooLong => info!("Error: VAATooLong"),
Error::CannotWrapNative => info!("Error: CannotWrapNative"), Error::CannotWrapNative => info!("Error: CannotWrapNative"),
Error::VAAAlreadySubmitted => info!("Error: VAAAlreadySubmitted"), Error::VAAAlreadySubmitted => info!("Error: VAAAlreadySubmitted"),
Error::GuardianSetMismatch => info!("Error: GuardianSetMismatch"),
} }
} }
} }

View File

@ -1,10 +1,8 @@
#![allow(clippy::too_many_arguments)] #![allow(clippy::too_many_arguments)]
//! Instruction types //! Instruction types
use std::io::{Cursor, Read, Write};
use std::mem::size_of; use std::mem::size_of;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use primitive_types::U256; use primitive_types::U256;
use solana_sdk::{ use solana_sdk::{
instruction::{AccountMeta, Instruction}, instruction::{AccountMeta, Instruction},
@ -12,11 +10,13 @@ use solana_sdk::{
pubkey::Pubkey, pubkey::Pubkey,
}; };
use crate::error::Error; use crate::{
use crate::error::Error::VAATooLong; instruction::BridgeInstruction::{
use crate::instruction::BridgeInstruction::{Initialize, PokeProposal, PostVAA, TransferOut}; Initialize, PokeProposal, PostVAA, TransferOut, VerifySignatures,
use crate::state::{AssetMeta, Bridge, BridgeConfig}; },
use crate::vaa::{VAABody, VAA}; state::{AssetMeta, Bridge, BridgeConfig},
vaa::{VAABody, VAA},
};
/// chain id of this chain /// chain id of this chain
pub const CHAIN_ID_SOLANA: u8 = 1; pub const CHAIN_ID_SOLANA: u8 = 1;
@ -75,6 +75,14 @@ pub struct TransferOutPayloadRaw {
pub nonce: u32, pub nonce: u32,
} }
#[derive(Clone, Copy, Debug)]
pub struct VerifySigPayload {
/// hash of the VAA
pub hash: [u8; 32],
/// instruction indices of signers (-1 for missing)
pub signers: [i8; MAX_LEN_GUARDIAN_KEYS],
}
/// Instructions supported by the SwapInfo program. /// Instructions supported by the SwapInfo program.
#[repr(C)] #[repr(C)]
pub enum BridgeInstruction { pub enum BridgeInstruction {
@ -126,6 +134,9 @@ pub enum BridgeInstruction {
/// Pokes a proposal with no valid VAAs attached so guardians reprocess it. /// Pokes a proposal with no valid VAAs attached so guardians reprocess it.
PokeProposal(), PokeProposal(),
/// Verifies signature instructions
VerifySignatures(VerifySigPayload),
} }
impl BridgeInstruction { impl BridgeInstruction {
@ -157,16 +168,22 @@ impl BridgeInstruction {
PostVAA(payload) PostVAA(payload)
} }
5 => PokeProposal(), 5 => PokeProposal(),
6 => {
let payload: &VerifySigPayload = unpack(input)?;
VerifySignatures(*payload)
}
_ => return Err(ProgramError::InvalidInstructionData), _ => return Err(ProgramError::InvalidInstructionData),
}) })
} }
/// Serializes a BridgeInstruction into a byte buffer. /// Serializes a BridgeInstruction into a byte buffer.
pub fn serialize(self: Self) -> Result<Vec<u8>, ProgramError> { pub fn serialize(self: Self) -> Result<Vec<u8>, ProgramError> {
let mut output = vec![0u8; size_of::<BridgeInstruction>()]; let mut output = Vec::with_capacity(size_of::<BridgeInstruction>());
match self { match self {
Self::Initialize(payload) => { Self::Initialize(payload) => {
output.resize(size_of::<InitializePayload>() + 1, 0);
output[0] = 0; output[0] = 0;
#[allow(clippy::cast_ptr_alignment)] #[allow(clippy::cast_ptr_alignment)]
let value = unsafe { let value = unsafe {
@ -175,6 +192,7 @@ impl BridgeInstruction {
*value = payload; *value = payload;
} }
Self::TransferOut(payload) => { Self::TransferOut(payload) => {
output.resize(size_of::<TransferOutPayloadRaw>() + 1, 0);
output[0] = 1; output[0] = 1;
#[allow(clippy::cast_ptr_alignment)] #[allow(clippy::cast_ptr_alignment)]
let value = unsafe { let value = unsafe {
@ -193,21 +211,32 @@ impl BridgeInstruction {
}; };
} }
Self::PostVAA(payload) => { Self::PostVAA(payload) => {
output.resize(1, 0);
output[0] = 2; output[0] = 2;
#[allow(clippy::cast_ptr_alignment)] #[allow(clippy::cast_ptr_alignment)]
let value = output.extend_from_slice(&payload);
unsafe { &mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut VAAData) };
*value = payload;
} }
Self::EvictTransferOut() => { Self::EvictTransferOut() => {
output.resize(1, 0);
output[0] = 3; output[0] = 3;
} }
Self::EvictClaimedVAA() => { Self::EvictClaimedVAA() => {
output.resize(1, 0);
output[0] = 4; output[0] = 4;
} }
Self::PokeProposal() => { Self::PokeProposal() => {
output.resize(1, 0);
output[0] = 5; output[0] = 5;
} }
Self::VerifySignatures(payload) => {
output.resize(size_of::<VerifySigPayload>() + 1, 0);
output[0] = 6;
#[allow(clippy::cast_ptr_alignment)]
let value = unsafe {
&mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut VerifySigPayload)
};
*value = payload;
}
} }
Ok(output) Ok(output)
} }
@ -280,6 +309,7 @@ pub fn transfer_out(
AccountMeta::new_readonly(*program_id, false), AccountMeta::new_readonly(*program_id, false),
AccountMeta::new_readonly(solana_sdk::system_program::id(), false), AccountMeta::new_readonly(solana_sdk::system_program::id(), false),
AccountMeta::new_readonly(spl_token::id(), false), AccountMeta::new_readonly(spl_token::id(), false),
AccountMeta::new_readonly(solana_sdk::sysvar::rent::id(), false),
AccountMeta::new_readonly(solana_sdk::sysvar::clock::id(), false), AccountMeta::new_readonly(solana_sdk::sysvar::clock::id(), false),
AccountMeta::new(*token_account, false), AccountMeta::new(*token_account, false),
AccountMeta::new(bridge_key, false), AccountMeta::new(bridge_key, false),
@ -301,11 +331,40 @@ pub fn transfer_out(
}) })
} }
/// Creates a 'VerifySignatures' instruction.
#[cfg(not(target_arch = "bpf"))]
pub fn verify_signatures(
program_id: &Pubkey,
signature_acc: &Pubkey,
guardian_set_id: u32,
p: &VerifySigPayload,
) -> Result<Instruction, ProgramError> {
let data = BridgeInstruction::VerifySignatures(*p).serialize()?;
let bridge_key = Bridge::derive_bridge_id(program_id)?;
let guardian_set_key =
Bridge::derive_guardian_set_id(program_id, &bridge_key, guardian_set_id)?;
let accounts = vec![
AccountMeta::new_readonly(*program_id, false),
AccountMeta::new_readonly(solana_sdk::sysvar::instructions::id(), false),
AccountMeta::new(*signature_acc, false),
AccountMeta::new_readonly(guardian_set_key, false),
];
Ok(Instruction {
program_id: *program_id,
accounts,
data,
})
}
/// Creates a 'PostVAA' instruction. /// Creates a 'PostVAA' instruction.
#[cfg(not(target_arch = "bpf"))] #[cfg(not(target_arch = "bpf"))]
pub fn post_vaa( pub fn post_vaa(
program_id: &Pubkey, program_id: &Pubkey,
payer: &Pubkey, payer: &Pubkey,
signature_key: &Pubkey,
v: VAAData, v: VAAData,
) -> Result<Instruction, ProgramError> { ) -> Result<Instruction, ProgramError> {
let mut data = v.clone(); let mut data = v.clone();
@ -322,10 +381,12 @@ pub fn post_vaa(
let mut accounts = vec![ let mut accounts = vec![
AccountMeta::new_readonly(*program_id, false), AccountMeta::new_readonly(*program_id, false),
AccountMeta::new_readonly(solana_sdk::system_program::id(), false), AccountMeta::new_readonly(solana_sdk::system_program::id(), false),
AccountMeta::new_readonly(solana_sdk::sysvar::rent::id(), false),
AccountMeta::new_readonly(solana_sdk::sysvar::clock::id(), false), AccountMeta::new_readonly(solana_sdk::sysvar::clock::id(), false),
AccountMeta::new(bridge_key, false), AccountMeta::new(bridge_key, false),
AccountMeta::new(guardian_set_key, false), AccountMeta::new(guardian_set_key, false),
AccountMeta::new(claim_key, false), AccountMeta::new(claim_key, false),
AccountMeta::new(*signature_key, false),
AccountMeta::new(*payer, true), AccountMeta::new(*payer, true),
]; ];
@ -390,7 +451,7 @@ pub fn poke_proposal(
) -> Result<Instruction, ProgramError> { ) -> Result<Instruction, ProgramError> {
let data = BridgeInstruction::PokeProposal().serialize()?; let data = BridgeInstruction::PokeProposal().serialize()?;
let mut accounts = vec![AccountMeta::new(*transfer_proposal, false)]; let accounts = vec![AccountMeta::new(*transfer_proposal, false)];
Ok(Instruction { Ok(Instruction {
program_id: *program_id, program_id: *program_id,

View File

@ -7,5 +7,4 @@ pub mod error_program;
pub mod instruction; pub mod instruction;
pub mod processor; pub mod processor;
pub mod state; pub mod state;
pub mod syscalls;
pub mod vaa; pub mod vaa;

View File

@ -1,32 +1,53 @@
//! Program instruction processing logic //! Program instruction processing logic
#![cfg(feature = "program")] #![cfg(feature = "program")]
use std::borrow::Borrow; use std::{borrow::Borrow, cell::RefCell, io::Write, mem::size_of, slice::Iter};
use std::cell::RefCell;
use std::mem::size_of;
use std::slice::Iter;
use byteorder::ByteOrder;
use num_traits::AsPrimitive; use num_traits::AsPrimitive;
use primitive_types::U256; use primitive_types::U256;
use solana_sdk::clock::Clock; use sha3::Digest;
use solana_sdk::hash::Hasher;
#[cfg(target_arch = "bpf")] #[cfg(target_arch = "bpf")]
use solana_sdk::program::invoke_signed; use solana_sdk::program::invoke_signed;
use solana_sdk::rent::Rent;
use solana_sdk::system_instruction::{create_account, SystemInstruction};
use solana_sdk::sysvar::Sysvar;
use solana_sdk::{ use solana_sdk::{
account_info::next_account_info, account_info::AccountInfo, entrypoint::ProgramResult, info, account_info::{next_account_info, AccountInfo},
instruction::Instruction, program_error::ProgramError, pubkey::Pubkey, clock::Clock,
entrypoint::ProgramResult,
hash::Hasher,
info,
instruction::Instruction,
program_error::ProgramError,
pubkey::Pubkey,
rent::Rent,
system_instruction::{create_account, SystemInstruction},
sysvar::Sysvar,
}; };
use spl_token::state::Mint; use spl_token::{pack::Pack, state::Mint};
use crate::error::Error; use crate::{
use crate::instruction::BridgeInstruction::*; error::Error,
use crate::instruction::{BridgeInstruction, TransferOutPayload, VAAData, CHAIN_ID_SOLANA}; instruction::{
use crate::instruction::{MAX_LEN_GUARDIAN_KEYS, MAX_VAA_SIZE}; BridgeInstruction, BridgeInstruction::*, TransferOutPayload, VAAData, VerifySigPayload,
use crate::state::*; CHAIN_ID_SOLANA, MAX_LEN_GUARDIAN_KEYS, MAX_VAA_SIZE,
use crate::vaa::{BodyTransfer, BodyUpdateGuardianSet, VAABody, VAA}; },
state::*,
vaa::{BodyTransfer, BodyUpdateGuardianSet, VAABody, VAA},
};
/// SigInfo contains metadata about signers in a VerifySignature ix
struct SigInfo {
/// index of the signer in the guardianset
signer_index: u8,
/// index of the signature in the secp instruction
sig_index: u8,
}
struct SecpInstructionPart<'a> {
address: &'a [u8],
signature: &'a [u8],
msg_offset: u16,
msg_size: u16,
}
/// Instruction processing logic /// Instruction processing logic
impl Bridge { impl Bridge {
@ -64,6 +85,11 @@ impl Bridge {
Self::process_poke(program_id, accounts) Self::process_poke(program_id, accounts)
} }
VerifySignatures(p) => {
info!("Instruction: VerifySignatures");
Self::process_verify_signatures(program_id, accounts, &p)
}
_ => panic!(""), _ => panic!(""),
} }
} }
@ -155,6 +181,158 @@ impl Bridge {
Ok(()) Ok(())
} }
/// Processes signature verifications
pub fn process_verify_signatures(
program_id: &Pubkey,
accounts: &[AccountInfo],
payload: &VerifySigPayload,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
next_account_info(account_info_iter)?; // Bridge program
let instruction_accounts = next_account_info(account_info_iter)?;
let sig_info = next_account_info(account_info_iter)?;
let guardian_set_info = next_account_info(account_info_iter)?;
let guardian_set: GuardianSet = Self::guardian_set_deserialize(guardian_set_info)?;
let mut sig_state_data = sig_info.data.borrow_mut();
let mut sig_state: &mut SignatureState = Self::unpack_unchecked(&mut sig_state_data)?;
if sig_state.is_initialized {
if sig_state.guardian_set_index != guardian_set.index {
return Err(Error::GuardianSetMismatch.into());
}
if sig_state.hash != payload.hash {
return Err(ProgramError::InvalidArgument);
}
} else {
sig_state.is_initialized = true;
sig_state.guardian_set_index = guardian_set.index;
sig_state.hash = payload.hash;
}
let sig_infos: Vec<SigInfo> = payload
.signers
.iter()
.enumerate()
.filter_map(|(i, p)| {
if *p == -1 {
return None;
}
return Some(SigInfo {
sig_index: *p as u8,
signer_index: i as u8,
});
})
.collect();
let current_instruction = solana_sdk::sysvar::instructions::load_current_index(
&instruction_accounts.try_borrow_data()?,
);
if current_instruction == 0 {
return Err(ProgramError::InvalidInstructionData);
}
// The previous ix must be a secp verification instruction
let secp_ix_index = (current_instruction - 1) as u8;
let secp_ix = solana_sdk::sysvar::instructions::load_instruction_at(
secp_ix_index as usize,
&instruction_accounts.try_borrow_data()?,
)
.map_err(|_| ProgramError::InvalidAccountData)?;
// Check that the instruction is actually for the secp program
if secp_ix.program_id != solana_sdk::secp256k1_program::id() {
return Err(ProgramError::InvalidArgument);
}
let secp_data_len = secp_ix.data.len();
if secp_data_len < 2 {
return Err(ProgramError::InvalidAccountData);
}
let sig_len = secp_ix.data[0];
let mut index = 1;
let mut secp_ixs: Vec<SecpInstructionPart> = Vec::with_capacity(sig_len as usize);
for i in 0..sig_len {
let sig_offset = byteorder::LE::read_u16(&secp_ix.data[index..index + 2]) as usize;
index += 2;
let sig_ix = secp_ix.data[index];
index += 1;
let address_offset = byteorder::LE::read_u16(&secp_ix.data[index..index + 2]) as usize;
index += 2;
let address_ix = secp_ix.data[index];
index += 1;
let msg_offset = byteorder::LE::read_u16(&secp_ix.data[index..index + 2]);
index += 2;
let msg_size = byteorder::LE::read_u16(&secp_ix.data[index..index + 2]);
index += 2;
let msg_ix = secp_ix.data[index];
index += 1;
if address_ix != secp_ix_index || msg_ix != secp_ix_index || sig_ix != secp_ix_index {
return Err(ProgramError::InvalidArgument);
}
let address: &[u8] = &secp_ix.data[address_offset..address_offset + 20];
let signature: &[u8] = &secp_ix.data[sig_offset..sig_offset + 65];
// Make sure that all messages are equal
if i > 0 {
if msg_offset != secp_ixs[0].msg_offset || msg_size != secp_ixs[0].msg_size {
return Err(ProgramError::InvalidArgument);
}
}
secp_ixs.push(SecpInstructionPart {
address,
signature,
msg_offset,
msg_size,
});
}
if sig_infos.len() != secp_ixs.len() {
return Err(ProgramError::InvalidArgument);
}
// Check message
let message = &secp_ix.data[secp_ixs[0].msg_offset as usize
..(secp_ixs[0].msg_offset + secp_ixs[0].msg_size) as usize];
let mut h = sha3::Keccak256::default();
if let Err(_) = h.write(message) {
return Err(ProgramError::InvalidArgument);
};
let msg_hash: [u8; 32] = h.finalize().into();
if msg_hash != payload.hash {
return Err(ProgramError::InvalidArgument);
}
// Check addresses
for s in sig_infos {
if s.signer_index > guardian_set.len_keys {
return Err(ProgramError::InvalidArgument);
}
if s.sig_index + 1 > sig_len {
return Err(ProgramError::InvalidArgument);
}
let key = guardian_set.keys[s.signer_index as usize];
// Check key in ix
if key != secp_ixs[s.sig_index as usize].address {
return Err(ProgramError::InvalidArgument);
}
sig_state.signatures[s.signer_index as usize]
.copy_from_slice(secp_ixs[s.sig_index as usize].signature);
}
Ok(())
}
/// Transfers a wrapped asset out /// Transfers a wrapped asset out
pub fn process_transfer_out( pub fn process_transfer_out(
program_id: &Pubkey, program_id: &Pubkey,
@ -166,6 +344,7 @@ impl Bridge {
next_account_info(account_info_iter)?; // Bridge program next_account_info(account_info_iter)?; // Bridge program
next_account_info(account_info_iter)?; // System program next_account_info(account_info_iter)?; // System program
next_account_info(account_info_iter)?; // Token program next_account_info(account_info_iter)?; // Token program
next_account_info(account_info_iter)?; // Rent sysvar
let clock_info = next_account_info(account_info_iter)?; let clock_info = next_account_info(account_info_iter)?;
let sender_account_info = next_account_info(account_info_iter)?; let sender_account_info = next_account_info(account_info_iter)?;
let bridge_info = next_account_info(account_info_iter)?; let bridge_info = next_account_info(account_info_iter)?;
@ -223,6 +402,7 @@ impl Bridge {
accounts, accounts,
&bridge.config.token_program, &bridge.config.token_program,
sender_account_info.key, sender_account_info.key,
mint_info.key,
t.amount, t.amount,
)?; )?;
@ -256,6 +436,7 @@ impl Bridge {
next_account_info(account_info_iter)?; // Bridge program next_account_info(account_info_iter)?; // Bridge program
next_account_info(account_info_iter)?; // System program next_account_info(account_info_iter)?; // System program
next_account_info(account_info_iter)?; // Token program next_account_info(account_info_iter)?; // Token program
next_account_info(account_info_iter)?; // Rent sysvar
let clock_info = next_account_info(account_info_iter)?; let clock_info = next_account_info(account_info_iter)?;
let sender_account_info = next_account_info(account_info_iter)?; let sender_account_info = next_account_info(account_info_iter)?;
let bridge_info = next_account_info(account_info_iter)?; let bridge_info = next_account_info(account_info_iter)?;
@ -368,10 +549,12 @@ impl Bridge {
// Load VAA processing default accounts // Load VAA processing default accounts
next_account_info(account_info_iter)?; // Bridge program next_account_info(account_info_iter)?; // Bridge program
next_account_info(account_info_iter)?; // System program next_account_info(account_info_iter)?; // System program
next_account_info(account_info_iter)?; // Rent sysvar
let clock_info = next_account_info(account_info_iter)?; let clock_info = next_account_info(account_info_iter)?;
let bridge_info = next_account_info(account_info_iter)?; let bridge_info = next_account_info(account_info_iter)?;
let guardian_set_info = next_account_info(account_info_iter)?; let guardian_set_info = next_account_info(account_info_iter)?;
let claim_info = next_account_info(account_info_iter)?; let claim_info = next_account_info(account_info_iter)?;
let sig_info = next_account_info(account_info_iter)?;
let payer_info = next_account_info(account_info_iter)?; let payer_info = next_account_info(account_info_iter)?;
let mut bridge = Bridge::bridge_deserialize(bridge_info)?; let mut bridge = Bridge::bridge_deserialize(bridge_info)?;
@ -401,9 +584,29 @@ impl Bridge {
return Err(Error::GuardianSetExpired.into()); return Err(Error::GuardianSetExpired.into());
} }
// Verify VAA signature // Verify sig state
if !vaa.verify(&guardian_set.keys[..guardian_set.len_keys as usize]) { let mut sig_state_data = sig_info.data.borrow_mut();
return Err(Error::InvalidVAASignature.into()); let sig_state: &SignatureState = Self::unpack(&mut sig_state_data)?;
// Verify that signatures were made using the correct set
if sig_state.guardian_set_index != guardian_set.index {
return Err(Error::GuardianSetMismatch.into());
}
let hash = vaa.body_hash()?;
if sig_state.hash != hash {
return Err(ProgramError::InvalidAccountData);
}
// Check quorum
if (sig_state
.signatures
.iter()
.filter(|v| v.iter().filter(|v| **v != 0).count() != 0)
.count() as u8)
< (((guardian_set.len_keys / 4) * 3) + 1)
{
return Err(ProgramError::InvalidArgument);
} }
let payload = vaa.payload.as_ref().ok_or(Error::InvalidVAAAction)?; let payload = vaa.payload.as_ref().ok_or(Error::InvalidVAAAction)?;
@ -428,6 +631,7 @@ impl Bridge {
vaa, vaa,
&v, &v,
vaa_data, vaa_data,
sig_info.key,
) )
} else { } else {
Self::process_vaa_transfer( Self::process_vaa_transfer(
@ -635,6 +839,7 @@ impl Bridge {
vaa: &VAA, vaa: &VAA,
b: &BodyTransfer, b: &BodyTransfer,
vaa_data: VAAData, vaa_data: VAAData,
sig_account: &Pubkey,
) -> ProgramResult { ) -> ProgramResult {
info!("posting VAA"); info!("posting VAA");
let proposal_info = next_account_info(account_info_iter)?; let proposal_info = next_account_info(account_info_iter)?;
@ -673,6 +878,7 @@ impl Bridge {
// Stop byte // Stop byte
proposal.vaa[vaa_data.len()] = 0xff; proposal.vaa[vaa_data.len()] = 0xff;
proposal.vaa_time = vaa.timestamp; proposal.vaa_time = vaa.timestamp;
proposal.signature_account = *sig_account;
Ok(()) Ok(())
} }
@ -686,11 +892,13 @@ impl Bridge {
accounts: &[AccountInfo], accounts: &[AccountInfo],
token_program_id: &Pubkey, token_program_id: &Pubkey,
token_account: &Pubkey, token_account: &Pubkey,
mint_account: &Pubkey,
amount: U256, amount: U256,
) -> Result<(), ProgramError> { ) -> Result<(), ProgramError> {
let ix = spl_token::instruction::burn( let ix = spl_token::instruction::burn(
token_program_id, token_program_id,
token_account, token_account,
mint_account,
&Self::derive_bridge_id(program_id)?, &Self::derive_bridge_id(program_id)?,
&[], &[],
amount.as_u64(), amount.as_u64(),
@ -769,7 +977,7 @@ impl Bridge {
mint: &Pubkey, mint: &Pubkey,
payer: &Pubkey, payer: &Pubkey,
) -> Result<(), ProgramError> { ) -> Result<(), ProgramError> {
Self::check_and_create_account::<spl_token::state::Account>( Self::check_and_create_account::<[u8; spl_token::state::Account::LEN]>(
program_id, program_id,
accounts, accounts,
account, account,
@ -777,7 +985,6 @@ impl Bridge {
token_program, token_program,
&Self::derive_custody_seeds(bridge, mint), &Self::derive_custody_seeds(bridge, mint),
)?; )?;
info!("bababu");
info!(token_program.to_string().as_str()); info!(token_program.to_string().as_str());
let ix = spl_token::instruction::initialize_account( let ix = spl_token::instruction::initialize_account(
token_program, token_program,
@ -799,7 +1006,7 @@ impl Bridge {
asset: &AssetMeta, asset: &AssetMeta,
decimals: u8, decimals: u8,
) -> Result<(), ProgramError> { ) -> Result<(), ProgramError> {
Self::check_and_create_account::<Mint>( Self::check_and_create_account::<[u8; spl_token::state::Mint::LEN]>(
program_id, program_id,
accounts, accounts,
mint, mint,
@ -810,9 +1017,8 @@ impl Bridge {
let ix = spl_token::instruction::initialize_mint( let ix = spl_token::instruction::initialize_mint(
token_program, token_program,
mint, mint,
&Self::derive_bridge_id(program_id)?,
None, None,
Some(&Self::derive_bridge_id(program_id)?),
0,
decimals, decimals,
)?; )?;
invoke_signed(&ix, accounts, &[]) invoke_signed(&ix, accounts, &[])

View File

@ -1,18 +1,16 @@
//! Bridge transition types //! Bridge transition types
use std::io::{Cursor, Read, Write};
use std::mem::size_of; use std::mem::size_of;
use std::ops::Deref;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use primitive_types::U256; use primitive_types::U256;
use solana_sdk::pubkey::{PubkeyError, MAX_SEED_LEN};
use solana_sdk::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; use solana_sdk::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};
use zerocopy::AsBytes; use zerocopy::AsBytes;
use crate::error::Error; use crate::{
use crate::instruction::{ForeignAddress, VAAData, MAX_LEN_GUARDIAN_KEYS, MAX_VAA_SIZE}; error::Error,
use crate::vaa::BodyTransfer; instruction::{ForeignAddress, MAX_LEN_GUARDIAN_KEYS, MAX_VAA_SIZE},
vaa::BodyTransfer,
};
/// fee rate as a ratio /// fee rate as a ratio
#[repr(C)] #[repr(C)]
@ -73,6 +71,8 @@ pub struct TransferOutProposal {
pub lockup_time: u32, pub lockup_time: u32,
/// times the proposal has been poked /// times the proposal has been poked
pub poke_counter: u8, pub poke_counter: u8,
/// Account where signatures are stored
pub signature_account: Pubkey,
/// Is `true` if this structure has been initialized. /// Is `true` if this structure has been initialized.
pub is_initialized: bool, pub is_initialized: bool,
@ -178,19 +178,42 @@ impl IsInitialized for Bridge {
} }
} }
/// Signature state
#[repr(C)]
#[derive(Clone, Copy)]
pub struct SignatureState {
/// signatures of validators
pub signatures: [[u8; 65]; MAX_LEN_GUARDIAN_KEYS],
/// hash of the data
pub hash: [u8; 32],
/// index of the guardian set
pub guardian_set_index: u32,
/// Is `true` if this structure has been initialized.
pub is_initialized: bool,
}
impl IsInitialized for SignatureState {
fn is_initialized(&self) -> bool {
self.is_initialized
}
}
/// Implementation of serialization functions /// Implementation of serialization functions
impl Bridge { impl Bridge {
/// Deserializes a spl_token `Account`. /// Deserializes a spl_token `Account`.
pub fn token_account_deserialize( pub fn token_account_deserialize(
info: &AccountInfo, info: &AccountInfo,
) -> Result<spl_token::state::Account, Error> { ) -> Result<spl_token::state::Account, Error> {
Ok(*spl_token::state::unpack(&mut info.data.borrow_mut()) Ok(spl_token::pack::Pack::unpack(&mut info.data.borrow_mut())
.map_err(|_| Error::ExpectedAccount)?) .map_err(|_| Error::ExpectedAccount)?)
} }
/// Deserializes a spl_token `Mint`. /// Deserializes a spl_token `Mint`.
pub fn mint_deserialize(info: &AccountInfo) -> Result<spl_token::state::Mint, Error> { pub fn mint_deserialize(info: &AccountInfo) -> Result<spl_token::state::Mint, Error> {
Ok(*spl_token::state::unpack(&mut info.data.borrow_mut()) Ok(spl_token::pack::Pack::unpack(&mut info.data.borrow_mut())
.map_err(|_| Error::ExpectedToken)?) .map_err(|_| Error::ExpectedToken)?)
} }

View File

@ -1,47 +0,0 @@
use solana_sdk::program_error::ProgramError;
use crate::error::Error;
#[repr(C)]
pub struct EcrecoverInput {
pub r: [u8; 32],
pub s: [u8; 32],
pub v: u8,
pub message: [u8; 32],
}
#[repr(C)]
pub struct EcrecoverOutput {
pub address: [u8; 20],
}
impl EcrecoverInput {
pub fn new(r: [u8; 32], s: [u8; 32], v: u8, message: [u8; 32]) -> EcrecoverInput {
EcrecoverInput { r, s, v, message }
}
}
/// Verify an ETH optimized Schnorr signature
///
/// @param input - Input for signature verification
//#[cfg(target_arch = "bpf")]
#[inline]
pub fn sol_syscall_ecrecover(input: &EcrecoverInput) -> Result<EcrecoverOutput, Error> {
let mut output = EcrecoverOutput { address: [0; 20] };
let res = unsafe {
sol_ecrecover(
input as *const _ as *const u8,
(&mut output) as *mut _ as *mut u8,
)
};
if res == 1 {
Ok(output)
} else {
Err(Error::InvalidVAASignature)
}
}
//#[cfg(target_arch = "bpf")]
extern "C" {
fn sol_ecrecover(input: *const u8, output: *mut u8) -> u64;
}

View File

@ -3,11 +3,9 @@ use std::io::{Cursor, Read, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use primitive_types::U256; use primitive_types::U256;
use sha3::Digest; use sha3::Digest;
use solana_sdk::program_error::ProgramError;
use crate::error::Error; use crate::{error::Error, state::AssetMeta};
use crate::error::Error::InvalidVAAFormat;
use crate::state::AssetMeta;
use crate::syscalls::{sol_syscall_ecrecover, EcrecoverInput, EcrecoverOutput};
pub type ForeignAddress = [u8; 32]; pub type ForeignAddress = [u8; 32];
@ -42,50 +40,19 @@ impl VAA {
}; };
} }
pub fn verify(&self, guardian_keys: &[[u8; 20]]) -> bool { pub fn body_hash(&self) -> Result<[u8; 32], ProgramError> {
let body = match self.signature_body() { let body = match self.signature_body() {
Ok(v) => v, Ok(v) => v,
Err(_) => { Err(_) => {
return false; return Err(ProgramError::InvalidArgument);
} }
}; };
let mut h = sha3::Keccak256::default(); let mut h = sha3::Keccak256::default();
if let Err(_) = h.write(body.as_slice()) { if let Err(_) = h.write(body.as_slice()) {
return false; return Err(ProgramError::InvalidArgument);
}; };
let hash = h.finalize().into(); Ok(h.finalize().into())
// Check quorum
if self.signatures.len() < (((guardian_keys.len() / 4) * 3) + 1 as usize) {
return false;
}
let mut last_index: i16 = -1;
for sig in self.signatures.iter() {
// Prevent multiple sinatures by the same guardian
if sig.index as i16 <= last_index {
return false;
}
last_index = sig.index as i16;
let ecrecover_input = EcrecoverInput::new(sig.r, sig.s, sig.v, hash);
let res = match sol_syscall_ecrecover(&ecrecover_input) {
Ok(v) => v,
Err(_) => {
return false;
}
};
if sig.index >= guardian_keys.len() as u8 {
return false;
}
if res.address != guardian_keys[sig.index as usize] {
return false;
}
}
true
} }
pub fn serialize(&self) -> Result<Vec<u8>, Error> { pub fn serialize(&self) -> Result<Vec<u8>, Error> {
@ -136,7 +103,7 @@ impl VAA {
let len_sig = rdr.read_u8()?; let len_sig = rdr.read_u8()?;
let mut sigs: Vec<Signature> = Vec::with_capacity(len_sig as usize); let mut sigs: Vec<Signature> = Vec::with_capacity(len_sig as usize);
for i in 0..len_sig { for _i in 0..len_sig {
let mut sig = Signature::default(); let mut sig = Signature::default();
sig.index = rdr.read_u8()?; sig.index = rdr.read_u8()?;
@ -302,8 +269,10 @@ mod tests {
use hex; use hex;
use primitive_types::U256; use primitive_types::U256;
use crate::state::AssetMeta; use crate::{
use crate::vaa::{BodyTransfer, BodyUpdateGuardianSet, Signature, VAABody, VAA}; state::AssetMeta,
vaa::{BodyTransfer, BodyUpdateGuardianSet, Signature, VAABody, VAA},
};
#[test] #[test]
fn serialize_deserialize_vaa_transfer() { fn serialize_deserialize_vaa_transfer() {

3230
solana/cli/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,15 +8,14 @@ edition = "2018"
[dependencies] [dependencies]
clap = "2.33.0" clap = "2.33.0"
solana-clap-utils = { version = "1.3.3"} solana-clap-utils = { version = "1.3.11"}
solana-cli-config = { version = "1.3.3" } solana-cli-config = { version = "1.3.11" }
solana-logger = { version = "1.3.3" } solana-logger = { version = "1.3.11" }
solana-sdk = { version = "1.3.3" } solana-sdk = { version = "1.3.11" }
solana-client = { version = "=1.3.3" } solana-client = { version = "1.3.11" }
solana-faucet = "1.3.3" solana-faucet = "1.3.11"
solana-transaction-status = "1.3.3" solana-account-decoder = { version = "1.3.11" }
solana-account-decoder = { version = "1.3.3" } spl-token = "=2.0.3"
spl-token = { package = "spl-token", git = "https://github.com/solana-labs/solana-program-library" }
wormhole-bridge = { path = "../bridge" } wormhole-bridge = { path = "../bridge" }
primitive-types = {version ="0.7.2"} primitive-types = {version ="0.7.2"}
hex = "0.4.2" hex = "0.4.2"

View File

@ -1,14 +1,15 @@
use crate::CommmandResult; use std::{error, net::SocketAddr, thread::sleep, time::Duration};
use solana_client::rpc_client::RpcClient; use solana_client::rpc_client::RpcClient;
use solana_faucet::faucet::request_airdrop_transaction; use solana_faucet::faucet::request_airdrop_transaction;
use solana_sdk::hash::Hash; use solana_sdk::{
use solana_sdk::pubkey::Pubkey; hash::Hash,
use solana_sdk::signature::{Signature, Signer, SignerError}; pubkey::Pubkey,
use solana_sdk::transaction::Transaction; signature::{Signature, Signer, SignerError},
use std::error; transaction::Transaction,
use std::net::SocketAddr; };
use std::thread::sleep;
use std::time::Duration; use crate::CommmandResult;
// Quick and dirty Keypair that assumes the client will do retries but not update the // Quick and dirty Keypair that assumes the client will do retries but not update the
// blockhash. If the client updates the blockhash, the signature will be invalid. // blockhash. If the client updates the blockhash, the signature will be invalid.

View File

@ -1,7 +1,4 @@
use std::fmt::Display; use std::{fmt::Display, mem::size_of, net::ToSocketAddrs, process::exit};
use std::net::{SocketAddr, ToSocketAddrs};
use std::str::FromStr;
use std::{mem::size_of, process::exit};
use clap::{ use clap::{
crate_description, crate_name, crate_version, value_t, value_t_or_exit, App, AppSettings, Arg, crate_description, crate_name, crate_version, value_t, value_t_or_exit, App, AppSettings, Arg,
@ -10,16 +7,13 @@ use clap::{
use hex; use hex;
use primitive_types::U256; use primitive_types::U256;
use solana_account_decoder::{parse_token::TokenAccountType, UiAccountData}; use solana_account_decoder::{parse_token::TokenAccountType, UiAccountData};
use solana_clap_utils::input_parsers::value_of;
use solana_clap_utils::input_validators::is_derivation;
use solana_clap_utils::{ use solana_clap_utils::{
input_parsers::{keypair_of, pubkey_of}, input_parsers::{keypair_of, pubkey_of, value_of},
input_validators::{is_amount, is_keypair, is_pubkey_or_keypair, is_url}, input_validators::{is_amount, is_keypair, is_pubkey_or_keypair, is_url},
}; };
use solana_client::client_error::ClientError; use solana_client::{
use solana_client::rpc_config::RpcSendTransactionConfig; rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig, rpc_request::TokenAccountsFilter,
use solana_client::{rpc_client::RpcClient, rpc_request::TokenAccountsFilter}; };
use solana_sdk::system_instruction::create_account;
use solana_sdk::{ use solana_sdk::{
commitment_config::CommitmentConfig, commitment_config::CommitmentConfig,
native_token::*, native_token::*,
@ -32,11 +26,11 @@ use spl_token::{
self, self,
instruction::*, instruction::*,
native_mint, native_mint,
pack::Pack,
state::{Account, Mint}, state::{Account, Mint},
}; };
use spl_bridge::instruction::*; use spl_bridge::{instruction::*, state::*};
use spl_bridge::state::*;
use crate::faucet::request_and_confirm_airdrop; use crate::faucet::request_and_confirm_airdrop;
@ -79,7 +73,8 @@ fn command_deploy_bridge(
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?; let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
check_fee_payer_balance( check_fee_payer_balance(
config, config,
minimum_balance_for_rent_exemption + fee_calculator.calculate_fee(&transaction.message()), minimum_balance_for_rent_exemption
+ fee_calculator.calculate_fee(&transaction.message(), None),
)?; )?;
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash); transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
Ok(Some(transaction)) Ok(Some(transaction))
@ -92,7 +87,10 @@ fn command_poke_proposal(config: &Config, bridge: &Pubkey, proposal: &Pubkey) ->
let mut transaction = Transaction::new_with_payer(&[ix], Some(&config.fee_payer.pubkey())); let mut transaction = Transaction::new_with_payer(&[ix], Some(&config.fee_payer.pubkey()));
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?; let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?; check_fee_payer_balance(
config,
fee_calculator.calculate_fee(&transaction.message(), None),
)?;
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash); transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
Ok(Some(transaction)) Ok(Some(transaction))
} }
@ -127,14 +125,14 @@ fn command_lock_tokens(
decimals: 0, decimals: 0,
} }
} }
Err(e) => AssetMeta { Err(_e) => AssetMeta {
address: token.to_bytes(), address: token.to_bytes(),
chain: CHAIN_ID_SOLANA, chain: CHAIN_ID_SOLANA,
decimals: 0, decimals: 0,
}, },
}; };
let mut instructions = vec![ let instructions = vec![
approve( approve(
&spl_token::id(), &spl_token::id(),
&account, &account,
@ -169,27 +167,8 @@ fn command_lock_tokens(
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?; let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
check_fee_payer_balance( check_fee_payer_balance(
config, config,
minimum_balance_for_rent_exemption + fee_calculator.calculate_fee(&transaction.message()), minimum_balance_for_rent_exemption
)?; + fee_calculator.calculate_fee(&transaction.message(), None),
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
Ok(Some(transaction))
}
fn command_submit_vaa(config: &Config, bridge: &Pubkey, vaa: &[u8]) -> CommmandResult {
println!("Submitting VAA");
let minimum_balance_for_rent_exemption = config
.rpc_client
.get_minimum_balance_for_rent_exemption(size_of::<Mint>())?;
let ix = post_vaa(bridge, &config.owner.pubkey(), vaa.to_vec())?;
let mut transaction = Transaction::new_with_payer(&[ix], Some(&config.fee_payer.pubkey()));
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
check_fee_payer_balance(
config,
minimum_balance_for_rent_exemption + fee_calculator.calculate_fee(&transaction.message()),
)?; )?;
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash); transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
Ok(Some(transaction)) Ok(Some(transaction))
@ -231,7 +210,7 @@ fn command_create_token(config: &Config, decimals: u8) -> CommmandResult {
let minimum_balance_for_rent_exemption = config let minimum_balance_for_rent_exemption = config
.rpc_client .rpc_client
.get_minimum_balance_for_rent_exemption(size_of::<Mint>())?; .get_minimum_balance_for_rent_exemption(Mint::LEN)?;
let mut transaction = Transaction::new_with_payer( let mut transaction = Transaction::new_with_payer(
&[ &[
@ -239,15 +218,14 @@ fn command_create_token(config: &Config, decimals: u8) -> CommmandResult {
&config.fee_payer.pubkey(), &config.fee_payer.pubkey(),
&token.pubkey(), &token.pubkey(),
minimum_balance_for_rent_exemption, minimum_balance_for_rent_exemption,
size_of::<Mint>() as u64, Mint::LEN as u64,
&spl_token::id(), &spl_token::id(),
), ),
initialize_mint( initialize_mint(
&spl_token::id(), &spl_token::id(),
&token.pubkey(), &token.pubkey(),
&config.owner.pubkey(),
None, None,
Some(&config.owner.pubkey()),
0,
decimals, decimals,
)?, )?,
], ],
@ -257,7 +235,8 @@ fn command_create_token(config: &Config, decimals: u8) -> CommmandResult {
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?; let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
check_fee_payer_balance( check_fee_payer_balance(
config, config,
minimum_balance_for_rent_exemption + fee_calculator.calculate_fee(&transaction.message()), minimum_balance_for_rent_exemption
+ fee_calculator.calculate_fee(&transaction.message(), None),
)?; )?;
transaction.sign( transaction.sign(
&[&config.fee_payer, &config.owner, &token], &[&config.fee_payer, &config.owner, &token],
@ -272,7 +251,7 @@ fn command_create_account(config: &Config, token: Pubkey) -> CommmandResult {
let minimum_balance_for_rent_exemption = config let minimum_balance_for_rent_exemption = config
.rpc_client .rpc_client
.get_minimum_balance_for_rent_exemption(size_of::<Account>())?; .get_minimum_balance_for_rent_exemption(Account::LEN)?;
let mut transaction = Transaction::new_with_payer( let mut transaction = Transaction::new_with_payer(
&[ &[
@ -280,7 +259,7 @@ fn command_create_account(config: &Config, token: Pubkey) -> CommmandResult {
&config.fee_payer.pubkey(), &config.fee_payer.pubkey(),
&account.pubkey(), &account.pubkey(),
minimum_balance_for_rent_exemption, minimum_balance_for_rent_exemption,
size_of::<Account>() as u64, Account::LEN as u64,
&spl_token::id(), &spl_token::id(),
), ),
initialize_account( initialize_account(
@ -296,7 +275,8 @@ fn command_create_account(config: &Config, token: Pubkey) -> CommmandResult {
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?; let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
check_fee_payer_balance( check_fee_payer_balance(
config, config,
minimum_balance_for_rent_exemption + fee_calculator.calculate_fee(&transaction.message()), minimum_balance_for_rent_exemption
+ fee_calculator.calculate_fee(&transaction.message(), None),
)?; )?;
transaction.sign( transaction.sign(
&[&config.fee_payer, &config.owner, &account], &[&config.fee_payer, &config.owner, &account],
@ -314,10 +294,11 @@ fn command_assign(config: &Config, account: Pubkey, new_owner: Pubkey) -> Commma
); );
let mut transaction = Transaction::new_with_payer( let mut transaction = Transaction::new_with_payer(
&[set_owner( &[spl_token::instruction::set_authority(
&spl_token::id(), &spl_token::id(),
&account, &account,
&new_owner, Some(&new_owner),
AuthorityType::AccountOwner,
&config.owner.pubkey(), &config.owner.pubkey(),
&[], &[],
)?], )?],
@ -325,7 +306,10 @@ fn command_assign(config: &Config, account: Pubkey, new_owner: Pubkey) -> Commma
); );
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?; let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?; check_fee_payer_balance(
config,
fee_calculator.calculate_fee(&transaction.message(), None),
)?;
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash); transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
Ok(Some(transaction)) Ok(Some(transaction))
} }
@ -361,7 +345,10 @@ fn command_transfer(
); );
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?; let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?; check_fee_payer_balance(
config,
fee_calculator.calculate_fee(&transaction.message(), None),
)?;
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash); transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
Ok(Some(transaction)) Ok(Some(transaction))
} }
@ -373,12 +360,19 @@ fn command_burn(config: &Config, source: Pubkey, ui_amount: f64) -> CommmandResu
.rpc_client .rpc_client
.get_token_account_balance_with_commitment(&source, config.commitment_config)? .get_token_account_balance_with_commitment(&source, config.commitment_config)?
.value; .value;
let source_account = config
.rpc_client
.get_account_with_commitment(&source, config.commitment_config)?
.value
.unwrap_or_default();
let data = source_account.data.to_vec();
let mint_pubkey = Account::unpack_from_slice(&data)?.mint;
let amount = spl_token::ui_amount_to_amount(ui_amount, source_token_balance.decimals); let amount = spl_token::ui_amount_to_amount(ui_amount, source_token_balance.decimals);
let mut transaction = Transaction::new_with_payer( let mut transaction = Transaction::new_with_payer(
&[burn( &[burn(
&spl_token::id(), &spl_token::id(),
&source, &source,
&mint_pubkey,
&config.owner.pubkey(), &config.owner.pubkey(),
&[], &[],
amount, amount,
@ -387,7 +381,10 @@ fn command_burn(config: &Config, source: Pubkey, ui_amount: f64) -> CommmandResu
); );
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?; let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?; check_fee_payer_balance(
config,
fee_calculator.calculate_fee(&transaction.message(), None),
)?;
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash); transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
Ok(Some(transaction)) Ok(Some(transaction))
} }
@ -422,7 +419,10 @@ fn command_mint(
); );
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?; let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?; check_fee_payer_balance(
config,
fee_calculator.calculate_fee(&transaction.message(), None),
)?;
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash); transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
Ok(Some(transaction)) Ok(Some(transaction))
} }
@ -453,7 +453,10 @@ fn command_wrap(config: &Config, sol: f64) -> CommmandResult {
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?; let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
check_owner_balance(config, lamports)?; check_owner_balance(config, lamports)?;
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?; check_fee_payer_balance(
config,
fee_calculator.calculate_fee(&transaction.message(), None),
)?;
transaction.sign( transaction.sign(
&[&config.fee_payer, &config.owner, &account], &[&config.fee_payer, &config.owner, &account],
recent_blockhash, recent_blockhash,
@ -486,7 +489,10 @@ fn command_unwrap(config: &Config, address: Pubkey) -> CommmandResult {
); );
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?; let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?; check_fee_payer_balance(
config,
fee_calculator.calculate_fee(&transaction.message(), None),
)?;
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash); transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
Ok(Some(transaction)) Ok(Some(transaction))
} }
@ -1148,12 +1154,6 @@ fn main() {
&config, &bridge, account, token, amount, chain, recipient, nonce, &config, &bridge, account, token, amount, chain, recipient, nonce,
) )
} }
("postvaa", Some(arg_matches)) => {
let bridge = pubkey_of(arg_matches, "bridge").unwrap();
let vaa_string: String = value_of(arg_matches, "vaa").unwrap();
let vaa = hex::decode(vaa_string).unwrap();
command_submit_vaa(&config, &bridge, vaa.as_slice())
}
("poke", Some(arg_matches)) => { ("poke", Some(arg_matches)) => {
let bridge = pubkey_of(arg_matches, "bridge").unwrap(); let bridge = pubkey_of(arg_matches, "bridge").unwrap();
let proposal = pubkey_of(arg_matches, "proposal").unwrap(); let proposal = pubkey_of(arg_matches, "proposal").unwrap();
@ -1189,6 +1189,7 @@ fn main() {
RpcSendTransactionConfig { RpcSendTransactionConfig {
// TODO: move to https://github.com/solana-labs/solana/pull/11792 // TODO: move to https://github.com/solana-labs/solana/pull/11792
skip_preflight: true, skip_preflight: true,
preflight_commitment: None,
}, },
)?; )?;
println!("Signature: {}", signature); println!("Signature: {}", signature);

1
solana/rustfmt.toml Normal file
View File

@ -0,0 +1 @@
merge_imports = true

View File

@ -1,722 +1,3 @@
Index: Cargo.lock
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- Cargo.lock (revision c8b40da7abf11c1d880436b0b3ed51261900a986)
+++ Cargo.lock (revision d0901065ee3d9b73483be7d8101ef124a2ee7dda)
@@ -274,12 +274,22 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
dependencies = [
- "block-padding",
+ "block-padding 0.1.5",
"byte-tools",
"byteorder",
"generic-array 0.12.3",
]
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "block-padding 0.2.1",
+ "generic-array 0.14.3",
+]
+
[[package]]
name = "block-padding"
version = "0.1.5"
@@ -289,6 +299,12 @@
"byte-tools",
]
+[[package]]
+name = "block-padding"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
+
[[package]]
name = "bs58"
version = "0.3.1"
@@ -633,6 +649,12 @@
"lazy_static",
]
+[[package]]
+name = "crunchy"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+
[[package]]
name = "crypto-mac"
version = "0.7.0"
@@ -682,7 +704,7 @@
checksum = "5d85653f070353a16313d0046f173f70d1aadd5b42600a14de626f0dfb3473a5"
dependencies = [
"byteorder",
- "digest",
+ "digest 0.8.1",
"rand_core 0.5.1",
"subtle 2.2.2",
"zeroize",
@@ -725,6 +747,15 @@
"generic-array 0.12.3",
]
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array 0.14.3",
+]
+
[[package]]
name = "dir-diff"
version = "0.3.2"
@@ -1316,7 +1347,18 @@
checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695"
dependencies = [
"crypto-mac",
- "digest",
+ "digest 0.8.1",
+]
+
+[[package]]
+name = "hmac-drbg"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b"
+dependencies = [
+ "digest 0.8.1",
+ "generic-array 0.12.3",
+ "hmac",
]
[[package]]
@@ -1750,6 +1792,12 @@
"ws",
]
+[[package]]
+name = "keccak"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
+
[[package]]
name = "kernel32-sys"
version = "0.2.2"
@@ -1818,6 +1866,22 @@
"libc",
]
+[[package]]
+name = "libsecp256k1"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962"
+dependencies = [
+ "arrayref",
+ "crunchy",
+ "digest 0.8.1",
+ "hmac-drbg",
+ "rand 0.7.3",
+ "sha2",
+ "subtle 2.2.2",
+ "typenum",
+]
+
[[package]]
name = "linked-hash-map"
version = "0.5.3"
@@ -2146,6 +2210,12 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
[[package]]
name = "openssl"
version = "0.10.29"
@@ -3098,10 +3168,10 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
dependencies = [
- "block-buffer",
- "digest",
+ "block-buffer 0.7.3",
+ "digest 0.8.1",
"fake-simd",
- "opaque-debug",
+ "opaque-debug 0.2.3",
]
[[package]]
@@ -3116,10 +3186,22 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69"
dependencies = [
- "block-buffer",
- "digest",
+ "block-buffer 0.7.3",
+ "digest 0.8.1",
"fake-simd",
- "opaque-debug",
+ "opaque-debug 0.2.3",
+]
+
+[[package]]
+name = "sha3"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809"
+dependencies = [
+ "block-buffer 0.9.0",
+ "digest 0.9.0",
+ "keccak",
+ "opaque-debug 0.3.0",
]
[[package]]
@@ -3376,12 +3458,16 @@
name = "solana-bpf-loader-program"
version = "1.4.0"
dependencies = [
+ "arrayref",
"bincode",
"byteorder",
+ "hex",
+ "libsecp256k1",
"num-derive 0.3.0",
"num-traits",
"rand 0.7.3",
"rustversion",
+ "sha3",
"solana-runtime",
"solana-sdk 1.4.0",
"solana_rbpf",
Index: programs/bpf_loader/Cargo.toml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- programs/bpf_loader/Cargo.toml (revision c8b40da7abf11c1d880436b0b3ed51261900a986)
+++ programs/bpf_loader/Cargo.toml (revision d0901065ee3d9b73483be7d8101ef124a2ee7dda)
@@ -17,6 +17,10 @@
solana-sdk = { path = "../../sdk", version = "1.4.0" }
solana_rbpf = "=0.1.28"
thiserror = "1.0"
+libsecp256k1 = "0.3.5"
+sha3 = "0.9.1"
+arrayref = "0.3.6"
+hex = "0.4.2"
[dev-dependencies]
rand = "0.7.3"
Index: programs/bpf_loader/src/crypto.rs
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- programs/bpf_loader/src/crypto.rs (revision d0901065ee3d9b73483be7d8101ef124a2ee7dda)
+++ programs/bpf_loader/src/crypto.rs (revision d0901065ee3d9b73483be7d8101ef124a2ee7dda)
@@ -0,0 +1,171 @@
+pub extern crate secp256k1;
+
+use std::io::Write;
+
+use num_traits::AsPrimitive;
+use secp256k1::curve::{Affine, ECMULT_CONTEXT, ECMULT_GEN_CONTEXT, ECMultGenContext, Field, Jacobian, Scalar};
+use sha3::Digest;
+
+use self::secp256k1::{Error, PublicKey};
+
+pub enum SchnorrError {
+ InvalidPubKey
+}
+
+#[repr(C)]
+pub struct EcrecoverInput {
+ pub signature: [u8; 65],
+ pub message: [u8; 32],
+}
+
+#[repr(C)]
+pub struct EcrecoverOutput {
+ pub address: [u8; 20],
+}
+
+#[repr(C)]
+pub struct SchnorrifyInput {
+ message: [u8; 32],
+ addr: [u8; 20],
+ signature: [u8; 32],
+ pub_key: [u8; 64],
+}
+
+impl SchnorrifyInput {
+ pub fn verify(&self) -> bool {
+ let sig = SchnorrSignature {
+ address: self.addr,
+ signature: self.signature,
+ };
+
+ let pub_key = SchnorrPublicKey::deserialize(&self.pub_key);
+
+ match sig.verify_signature(&self.message, &pub_key) {
+ Ok(res) => res,
+ Err(_) => false
+ }
+ }
+}
+
+pub struct SchnorrPublicKey {
+ x: Field,
+ y: Field,
+}
+
+pub struct SchnorrSignature {
+ pub address: [u8; 20],
+ pub signature: [u8; 32],
+}
+
+impl SchnorrPublicKey {
+ pub fn deserialize(d: &[u8; 64]) -> SchnorrPublicKey {
+ let mut x = Field::default();
+ let mut y = Field::default();
+ x.set_b32(array_ref![d,0,32]);
+ y.set_b32(array_ref![d,32,32]);
+ x.normalize();
+ y.normalize();
+ SchnorrPublicKey {
+ x,
+ y,
+ }
+ }
+}
+
+impl SchnorrSignature {
+ pub fn verify_signature(&self, msg: &[u8; 32], pub_key: &SchnorrPublicKey) -> Result<bool, SchnorrError> {
+ let mut af = Affine::default();
+ af.set_xy(&pub_key.x, &pub_key.y);
+ let mut pubkey_j: Jacobian = Jacobian::default();
+ pubkey_j.set_ge(&af);
+
+ // Verify that the pubkey is a curve point
+ let mut elem = Affine::default();
+ if !elem.set_xo_var(&pub_key.x, pub_key.y.is_odd()) {
+ return Err(SchnorrError::InvalidPubKey);
+ }
+ elem.y.normalize();
+
+ // Make sure that the ordinates are equal
+ if elem.y.b32() != pub_key.y.b32() {
+ return Err(SchnorrError::InvalidPubKey);
+ }
+
+ // Generate the challenge
+ let mut h = sha3::Keccak256::default();
+ h.write(&pub_key.x.b32()); // pub key x coordinate
+ h.write(&[(pub_key.y.is_odd()).as_()]); // y parity
+ h.write(msg); // msg
+ h.write(&self.address); // nonceTimesGeneratorAddress
+ let challenge = h.finalize();
+
+ let mut e = Scalar::default();
+ e.set_b32(array_ref![challenge,0,32]);
+
+ let mut s = Scalar::default();
+ s.set_b32(&self.signature);
+
+ // Calculate s x G + e x P
+ let mut k: Jacobian = Jacobian::default();
+ ECMULT_CONTEXT.ecmult(&mut k, &pubkey_j, &e, &s);
+
+ let r = jacobian_to_normalized_affine(&k);
+
+ // Generate Ethereum address from calculated point
+ let eth_addr = affine_to_eth_address(&r);
+
+ // Verify that addr(k) == sig.address
+ Ok(eth_addr == self.address)
+ }
+}
+
+fn affine_to_eth_address(a: &Affine) -> [u8; 20] {
+ let mut h = sha3::Keccak256::default();
+ h.write(a.x.b32().as_ref()); // result key x coordinate
+ h.write(a.y.b32().as_ref()); // result key y coordinate
+
+ let out = h.finalize();
+ let mut out_addr = [0; 20];
+ out_addr.copy_from_slice(&out[12..]);
+
+ out_addr
+}
+
+fn jacobian_to_normalized_affine(j: &Jacobian) -> Affine {
+ let mut r = Affine::default();
+ r.set_gej(j);
+ r.x.normalize();
+ r.y.normalize();
+
+ r
+}
+
+#[cfg(test)]
+mod tests {
+ use std::io::Write;
+
+ use hex;
+
+ use crate::crypto::{SchnorrPublicKey, SchnorrSignature};
+
+ #[test]
+ fn verify_signature() {
+ let msggg = hex::decode("0194fdc2fa2ffcc041d3ff12045b73c86e4ff95ff662a5eee82abdf44a2d0b75").unwrap();
+ let siggg = hex::decode("ee5884a66454baca985f4453c05394214a75dc38956ea39f12cc429f081aae4b").unwrap();
+ let addr = hex::decode("9addd8a38fea7e1b94550e5bc249309a633dfa63").unwrap();
+ let pb = hex::decode("ae92ce7553993f04400c6976f8cd4540ae076bf0131eec8b35ae0ff9fc577a901de834d0f62ae6ecbeec2124595b06bce078b8133b4dda3855cf346feb2b2ca2").unwrap();
+
+ let sig = SchnorrSignature {
+ address: *array_ref![addr,0,20],
+ signature: *array_ref![siggg,0,32],
+ };
+
+ let pub_key = SchnorrPublicKey::deserialize(array_ref![pb,0,64]);
+
+ let msg = array_ref![msggg,0,32];
+ match sig.verify_signature(msg, &pub_key) {
+ Ok(res) => assert!(res, "signature should be valid"),
+ Err(err) => assert!(false, "signature verification failed")
+ }
+ }
+}
Index: programs/bpf_loader/src/lib.rs
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- programs/bpf_loader/src/lib.rs (revision c8b40da7abf11c1d880436b0b3ed51261900a986)
+++ programs/bpf_loader/src/lib.rs (revision 745a97685408ff4249d96333a7885cd214639b70)
@@ -4,6 +4,10 @@
pub mod deprecated;
pub mod serialization;
pub mod syscalls;
+pub mod crypto;
+
+#[macro_use]
+extern crate arrayref;
use crate::{
bpf_verifier::VerifierError,
@@ -65,7 +69,7 @@
) -> Result<(EbpfVm<'a, BPFError>, MemoryRegion), EbpfError<BPFError>> {
let mut vm = EbpfVm::new(None)?;
vm.set_verifier(bpf_verifier::check)?;
- vm.set_max_instruction_count(100_000)?;
+ vm.set_max_instruction_count(1_000_000)?;
vm.set_elf(&prog)?;
let heap_region = syscalls::register_syscalls(&mut vm, parameter_accounts, invoke_context)?;
Index: programs/bpf_loader/src/syscalls.rs
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- programs/bpf_loader/src/syscalls.rs (revision c8b40da7abf11c1d880436b0b3ed51261900a986)
+++ programs/bpf_loader/src/syscalls.rs (revision d0901065ee3d9b73483be7d8101ef124a2ee7dda)
@@ -1,10 +1,23 @@
-use crate::{alloc, BPFError};
-use alloc::Alloc;
+use std::{
+ alloc::Layout,
+ cell::{RefCell, RefMut},
+ convert::TryFrom,
+ mem::{align_of, size_of},
+ rc::Rc,
+ slice::from_raw_parts_mut,
+ str::{from_utf8, Utf8Error},
+};
+
+use secp256k1::{Error, PublicKey, RecoveryId, Signature};
+use sha3::Digest;
use solana_rbpf::{
- ebpf::{EbpfError, SyscallObject, ELF_INSN_DUMP_OFFSET, MM_HEAP_START},
- memory_region::{translate_addr, MemoryRegion},
+ ebpf::{EbpfError, ELF_INSN_DUMP_OFFSET, MM_HEAP_START, SyscallObject},
EbpfVm,
+ memory_region::{MemoryRegion, translate_addr},
};
+use thiserror::Error as ThisError;
+
+use alloc::Alloc;
use solana_runtime::message_processor::MessageProcessor;
use solana_sdk::{
account::Account,
@@ -18,16 +31,16 @@
program_error::ProgramError,
pubkey::{Pubkey, PubkeyError},
};
-use std::{
- alloc::Layout,
- cell::{RefCell, RefMut},
- convert::TryFrom,
- mem::{align_of, size_of},
- rc::Rc,
- slice::from_raw_parts_mut,
- str::{from_utf8, Utf8Error},
-};
-use thiserror::Error as ThisError;
+
+use crate::{alloc, BPFError};
+/// Program heap allocators are intended to allocate/free from a given
+/// chunk of memory. The specific allocator implementation is
+/// selectable at build-time.
+/// Only one allocator is currently supported
+
+/// Simple bump allocator, never frees
+use crate::allocator_bump::BPFAllocator;
+use crate::crypto::{EcrecoverInput, EcrecoverOutput, SchnorrifyInput};
/// Error definitions
#[derive(Debug, ThisError)]
@@ -53,20 +66,13 @@
#[error("Unaligned pointer")]
UnalignedPointer,
}
+
impl From<SyscallError> for EbpfError<BPFError> {
fn from(error: SyscallError) -> Self {
EbpfError::UserError(error.into())
}
}
-/// Program heap allocators are intended to allocate/free from a given
-/// chunk of memory. The specific allocator implementation is
-/// selectable at build-time.
-/// Only one allocator is currently supported
-
-/// Simple bump allocator, never frees
-use crate::allocator_bump::BPFAllocator;
-
/// Default program heap size, allocators
/// are expected to enforce this
const DEFAULT_HEAP_SIZE: usize = 32 * 1024;
@@ -114,6 +120,16 @@
)?;
}
+ // Signature verification
+ vm.register_syscall_with_context_ex(
+ "sol_verify_ethschnorr",
+ Box::new(SyscallSchorrify {}),
+ )?;
+ vm.register_syscall_with_context_ex(
+ "sol_ecrecover",
+ Box::new(SyscallEcrecover {}),
+ )?;
+
// Memory allocator
let heap = vec![0_u8; DEFAULT_HEAP_SIZE];
let heap_region = MemoryRegion::new_from_slice(&heap, MM_HEAP_START);
@@ -255,6 +271,7 @@
pub struct SyscallLog {
logger: Rc<RefCell<dyn Logger>>,
}
+
impl SyscallObject<BPFError> for SyscallLog {
fn call(
&mut self,
@@ -284,6 +301,7 @@
pub struct SyscallLogU64 {
logger: Rc<RefCell<dyn Logger>>,
}
+
impl SyscallObject<BPFError> for SyscallLogU64 {
fn call(
&mut self,
@@ -318,6 +336,7 @@
pub struct SyscallSolAllocFree {
allocator: BPFAllocator,
}
+
impl SyscallObject<BPFError> for SyscallSolAllocFree {
fn call(
&mut self,
@@ -380,6 +399,87 @@
Ok(0)
}
+/// Verify a ETH optimized Schnorr Signature
+pub struct SyscallEcrecover {
+}
+
+impl SyscallObject<BPFError> for SyscallEcrecover {
+ fn call(
+ &mut self,
+ input: u64,
+ output: u64,
+ _arg3: u64,
+ _arg4: u64,
+ _arg5: u64,
+ ro_regions: &[MemoryRegion],
+ _rw_regions: &[MemoryRegion],
+ ) -> Result<u64, EbpfError<BPFError>> {
+ let input = translate_type!(
+ EcrecoverInput,
+ input,
+ ro_regions
+ )?;
+
+ let mut output = translate_type_mut!(
+ EcrecoverOutput,
+ output,
+ ro_regions
+ )?;
+
+ let signature = match secp256k1::Signature::parse_slice(&input.signature[..64]) {
+ Ok(v) => v,
+ Err(_) => {
+ return Ok(0);
+ }
+ };
+
+ let recovery_id = match secp256k1::RecoveryId::parse(input.signature[64]) {
+ Ok(v) => v,
+ Err(_) => { return Ok(0); }
+ };
+
+ match secp256k1::recover(&secp256k1::Message::parse(&input.message), &signature,
+ &recovery_id) {
+ Ok(v) => {
+ let mut addr = [0u8; 20];
+ addr.copy_from_slice(&sha3::Keccak256::digest(&v.serialize()[1..])[12..]);
+ output.address = addr;
+ }
+ Err(_) => {
+ return Ok(0);
+ }
+ }
+
+ Ok(1)
+ }
+}
+
+/// Verify a ETH optimized Schnorr Signature
+pub struct SyscallSchorrify {
+}
+
+impl SyscallObject<BPFError> for SyscallSchorrify {
+ fn call(
+ &mut self,
+ addr: u64,
+ _arg2: u64,
+ _arg3: u64,
+ _arg4: u64,
+ _arg5: u64,
+ ro_regions: &[MemoryRegion],
+ _rw_regions: &[MemoryRegion],
+ ) -> Result<u64, EbpfError<BPFError>> {
+ let input = translate_type!(
+ SchnorrifyInput,
+ addr,
+ ro_regions
+ )?;
+ let res = input.verify();
+
+ Ok(res as u64)
+ }
+}
+
// Cross-program invocation syscalls
struct AccountReferences<'a> {
@@ -422,6 +522,7 @@
callers_keyed_accounts: &'a [KeyedAccount<'a>],
invoke_context: Rc<RefCell<&'a mut dyn InvokeContext>>,
}
+
impl<'a> SyscallProcessInstruction<'a> for SyscallProcessInstructionRust<'a> {
fn get_context_mut(&self) -> Result<RefMut<&'a mut dyn InvokeContext>, EbpfError<BPFError>> {
self.invoke_context
@@ -443,7 +544,7 @@
ix.accounts.len(),
ro_regions
)?
- .to_vec();
+ .to_vec();
let data = translate_slice!(u8, ix.data.as_ptr(), ix.data.len(), ro_regions)?.to_vec();
Ok(Instruction {
program_id: ix.program_id,
@@ -558,6 +659,7 @@
}
}
}
+
impl<'a> SyscallObject<BPFError> for SyscallProcessInstructionRust<'a> {
fn call(
&mut self,
@@ -633,6 +735,7 @@
callers_keyed_accounts: &'a [KeyedAccount<'a>],
invoke_context: Rc<RefCell<&'a mut dyn InvokeContext>>,
}
+
impl<'a> SyscallProcessInstruction<'a> for SyscallProcessSolInstructionC<'a> {
fn get_context_mut(&self) -> Result<RefMut<&'a mut dyn InvokeContext>, EbpfError<BPFError>> {
self.invoke_context
@@ -770,6 +873,7 @@
}
}
}
+
impl<'a> SyscallObject<BPFError> for SyscallProcessSolInstructionC<'a> {
fn call(
&mut self,
@@ -821,10 +925,10 @@
}
if account.is_signer && // If message indicates account is signed
- !( // one of the following needs to be true:
- keyed_account.signer_key().is_some() // Signed in the parent instruction
- || signers.contains(&account.pubkey) // Signed by the program
- ) {
+ !( // one of the following needs to be true:
+ keyed_account.signer_key().is_some() // Signed in the parent instruction
+ || signers.contains(&account.pubkey) // Signed by the program
+ ) {
return Err(SyscallError::PrivilegeEscalation.into());
}
}
@@ -1042,7 +1146,7 @@
assert_eq!(string, "Gaggablaghblagh!");
Ok(42)
})
- .unwrap()
+ .unwrap()
);
}
@@ -1074,7 +1178,7 @@
&[ro_region],
&[rw_region],
)
- .unwrap();
+ .unwrap();
}
#[test]
Index: fetch-spl.sh Index: fetch-spl.sh
IDEA additional info: IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
@ -754,3 +35,64 @@ Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
Ok(TokenAccountType::Account(UiTokenAccount { Ok(TokenAccountType::Account(UiTokenAccount {
mint: account.mint.to_string(), mint: account.mint.to_string(),
owner: account.owner.to_string(), owner: account.owner.to_string(),
Index: programs/bpf_loader/src/bpf_verifier.rs
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- programs/bpf_loader/src/bpf_verifier.rs (revision 6563726f227414164c32a0373fa32b0f87fbd4e8)
+++ programs/bpf_loader/src/bpf_verifier.rs (date 1600862128332)
@@ -58,7 +58,7 @@
if prog.len() % ebpf::INSN_SIZE != 0 {
return Err(VerifierError::ProgramLengthNotMultiple.into());
}
- if prog.len() > ebpf::PROG_MAX_SIZE {
+ if prog.len() > ebpf::PROG_MAX_SIZE * 2 {
return Err(VerifierError::ProgramTooLarge(prog.len() / ebpf::INSN_SIZE).into());
}
--- core/src/rpc.rs
+++ core/src/rpc.rs
@@ -2210,6 +2210,10 @@ impl RpcSol for RpcSolImpl {
return Err(RpcCustomError::TransactionSignatureVerificationFailure.into());
}
+ if let Err(e) = transaction.verify_precompiles() {
+ return Err(RpcCustomError::TransactionPrecompileVerificationFailure(e).into());
+ }
+
if meta.health.check() != RpcHealthStatus::Ok {
return Err(RpcCustomError::RpcNodeUnhealthy.into());
}
--- core/src/rpc_error.rs
+++ core/src/rpc_error.rs
@@ -7,6 +7,7 @@ const JSON_RPC_SERVER_ERROR_2: i64 = -32002;
const JSON_RPC_SERVER_ERROR_3: i64 = -32003;
const JSON_RPC_SERVER_ERROR_4: i64 = -32004;
const JSON_RPC_SERVER_ERROR_5: i64 = -32005;
+const JSON_RPC_SERVER_ERROR_6: i64 = -32006;
pub enum RpcCustomError {
BlockCleanedUp {
@@ -22,6 +23,7 @@ pub enum RpcCustomError {
slot: Slot,
},
RpcNodeUnhealthy,
+ TransactionPrecompileVerificationFailure(solana_sdk::transaction::TransactionError),
}
impl From<RpcCustomError> for Error {
@@ -58,6 +60,11 @@ impl From<RpcCustomError> for Error {
message: "RPC node is unhealthy".to_string(),
data: None,
},
+ RpcCustomError::TransactionPrecompileVerificationFailure(e) => Self {
+ code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_6),
+ message: format!("Transaction precompile verification failure {:?}", e),
+ data: None,
+ },
}
}
}

View File

@ -11,7 +11,7 @@ RUN rustup component add rustfmt
WORKDIR /usr/src/solana WORKDIR /usr/src/solana
RUN git clone https://github.com/solana-labs/solana --branch master && \ RUN git clone https://github.com/solana-labs/solana --branch master && \
cd solana && git checkout e2d66cf7 cd solana && git checkout 5dcf3480986a87cc9c80788c1d8ccd8f0cb44a8d
ADD *.patch . ADD *.patch .

View File

@ -76,13 +76,24 @@ function TransferProposals() {
let executeVAA = async (v: LockupWithStatus) => { let executeVAA = async (v: LockupWithStatus) => {
let wh = WormholeFactory.connect(BRIDGE_ADDRESS, signer) let wh = WormholeFactory.connect(BRIDGE_ADDRESS, signer)
let vaa = v.vaa; let vaa = new Buffer(v.vaa);
for (let i = vaa.length; i > 0; i--) { for (let i = vaa.length; i > 0; i--) {
if (vaa[i] == 0xff) { if (vaa[i] == 0xff) {
vaa = vaa.slice(0, i) vaa = vaa.slice(0, i)
break break
} }
} }
let signatures = await b.fetchSignatureStatus(v.signatureAccount);
let sigData = Buffer.of(...signatures.reduce((previousValue, currentValue) => {
previousValue.push(currentValue.index)
previousValue.push(...currentValue.signature)
return previousValue
}, new Array<number>()))
vaa = Buffer.concat([vaa.slice(0, 5), Buffer.of(signatures.length), sigData, vaa.slice(6)])
message.loading({content: "Signing transaction...", key: "eth_tx", duration: 1000},) message.loading({content: "Signing transaction...", key: "eth_tx", duration: 1000},)
let tx = await wh.submitVAA(vaa) let tx = await wh.submitVAA(vaa)
message.loading({content: "Waiting for transaction to be mined...", key: "eth_tx", duration: 1000}) message.loading({content: "Waiting for transaction to be mined...", key: "eth_tx", duration: 1000})

View File

@ -5,12 +5,14 @@ const WRAPPED_MASTER = "e78A0F7E598Cc8b0Bb87894B0F60dD2a88d6a8Ab"
const SOLANA_BRIDGE_PROGRAM = new PublicKey("Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"); const SOLANA_BRIDGE_PROGRAM = new PublicKey("Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o");
const TOKEN_PROGRAM = new PublicKey("TokenSVp5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"); const TOKEN_PROGRAM = new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
const SOLANA_HOST = "http://localhost:8899";
export { export {
BRIDGE_ADDRESS, BRIDGE_ADDRESS,
TOKEN_PROGRAM, TOKEN_PROGRAM,
WRAPPED_MASTER, WRAPPED_MASTER,
SOLANA_BRIDGE_PROGRAM SOLANA_BRIDGE_PROGRAM,
SOLANA_HOST
} }

View File

@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import * as solanaWeb3 from '@solana/web3.js'; import * as solanaWeb3 from '@solana/web3.js';
import {SOLANA_HOST} from "../config";
const ClientContext = React.createContext<solanaWeb3.Connection>(new solanaWeb3.Connection("http://localhost:8899")); const ClientContext = React.createContext<solanaWeb3.Connection>(new solanaWeb3.Connection(SOLANA_HOST));
export default ClientContext export default ClientContext

View File

@ -5,7 +5,7 @@ import assert from "assert";
// @ts-ignore // @ts-ignore
import * as BufferLayout from 'buffer-layout' import * as BufferLayout from 'buffer-layout'
import {Token} from "@solana/spl-token"; import {Token} from "@solana/spl-token";
import {TOKEN_PROGRAM} from "../config"; import {SOLANA_HOST, TOKEN_PROGRAM} from "../config";
import * as bs58 from "bs58"; import * as bs58 from "bs58";
export interface AssetMeta { export interface AssetMeta {
@ -26,9 +26,15 @@ export interface Lockup {
vaa: Uint8Array, vaa: Uint8Array,
vaaTime: number, vaaTime: number,
pokeCounter: number, pokeCounter: number,
signatureAccount: PublicKey,
initialized: boolean, initialized: boolean,
} }
export interface Signature {
signature: number[],
index: number,
}
export const CHAIN_ID_SOLANA = 1; export const CHAIN_ID_SOLANA = 1;
class SolanaBridge { class SolanaBridge {
@ -95,6 +101,7 @@ class SolanaBridge {
{pubkey: this.programID, isSigner: false, isWritable: false}, {pubkey: this.programID, isSigner: false, isWritable: false},
{pubkey: solanaWeb3.SystemProgram.programId, isSigner: false, isWritable: false}, {pubkey: solanaWeb3.SystemProgram.programId, isSigner: false, isWritable: false},
{pubkey: this.tokenProgram, isSigner: false, isWritable: false}, {pubkey: this.tokenProgram, isSigner: false, isWritable: false},
{pubkey: solanaWeb3.SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false},
{pubkey: solanaWeb3.SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false}, {pubkey: solanaWeb3.SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false},
{pubkey: tokenAccount, isSigner: false, isWritable: true}, {pubkey: tokenAccount, isSigner: false, isWritable: true},
{pubkey: configKey, isSigner: false, isWritable: false}, {pubkey: configKey, isSigner: false, isWritable: false},
@ -171,11 +178,47 @@ class SolanaBridge {
} }
} }
// fetchSignatureStatus fetches the signatures for a VAA
async fetchSignatureStatus(
signatureStatus: PublicKey,
): Promise<Signature[]> {
let signatureInfo = await this.connection.getAccountInfo(signatureStatus);
console.log(signatureStatus.toBase58())
if (signatureInfo == null || signatureInfo.lamports == 0) {
throw new Error("not found")
} else {
const dataLayout = BufferLayout.struct([
BufferLayout.blob(20 * 65, 'signaturesRaw'),
]);
let rawSignatureInfo = dataLayout.decode(signatureInfo?.data);
let signatures: Signature[] = [];
for (let i = 0; i < 20; i++) {
let data = rawSignatureInfo.signaturesRaw.slice(65 * i, 65 * (i + 1));
let empty = true;
for (let v of data) {
if (v != 0) {
empty = false;
break
}
}
if (empty) continue;
signatures.push({
signature: data,
index: i,
})
}
return signatures;
}
}
// fetchAssetMeta fetches the AssetMeta for an SPL token // fetchAssetMeta fetches the AssetMeta for an SPL token
async fetchTransferProposals( async fetchTransferProposals(
tokenAccount: PublicKey, tokenAccount: PublicKey,
): Promise<Lockup[]> { ): Promise<Lockup[]> {
let accountRes = await fetch("http://localhost:8899", { let accountRes = await fetch(SOLANA_HOST, {
method: "POST", method: "POST",
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@ -186,7 +229,7 @@ class SolanaBridge {
"method": "getProgramAccounts", "method": "getProgramAccounts",
"params": [this.programID.toString(), { "params": [this.programID.toString(), {
"commitment": "single", "commitment": "single",
"filters": [{"dataSize": 1152}, { "filters": [{"dataSize": 1184}, {
"memcmp": { "memcmp": {
"offset": 33, "offset": 33,
"bytes": tokenAccount.toString() "bytes": tokenAccount.toString()
@ -210,7 +253,9 @@ class SolanaBridge {
BufferLayout.blob(1001, 'vaa'), BufferLayout.blob(1001, 'vaa'),
BufferLayout.seq(BufferLayout.u8(), 3), // 4 byte alignment because a u32 is following BufferLayout.seq(BufferLayout.u8(), 3), // 4 byte alignment because a u32 is following
BufferLayout.u32('vaaTime'), BufferLayout.u32('vaaTime'),
BufferLayout.u32('lockupTime'),
BufferLayout.u8('pokeCounter'), BufferLayout.u8('pokeCounter'),
BufferLayout.blob(32, 'signatureAccount'),
BufferLayout.u8('initialized'), BufferLayout.u8('initialized'),
]); ]);
@ -230,6 +275,7 @@ class SolanaBridge {
toChain: parsedAccount.toChain, toChain: parsedAccount.toChain,
vaa: parsedAccount.vaa, vaa: parsedAccount.vaa,
vaaTime: parsedAccount.vaaTime, vaaTime: parsedAccount.vaaTime,
signatureAccount: new PublicKey(parsedAccount.signatureAccount),
pokeCounter: parsedAccount.pokeCounter pokeCounter: parsedAccount.pokeCounter
}) })
} }