diff --git a/Cargo.toml b/Cargo.toml index 9dc1a7e1d3b..18752318654 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,8 +44,6 @@ exclude = [ "farms/router-saber", "farms/router-orca", "farms/vaults", - "themis/client_ristretto", - "themis/program_ristretto", ] [profile.dev] diff --git a/ci/cargo-build-test.sh b/ci/cargo-build-test.sh index f2e300279fe..4313e50cd9f 100755 --- a/ci/cargo-build-test.sh +++ b/ci/cargo-build-test.sh @@ -21,10 +21,6 @@ cargo +"$rust_stable" test -- --nocapture # Run test-client sanity check cargo +"$rust_stable" run --manifest-path=utils/test-client/Cargo.toml -# client_ristretto isn't in the workspace, test it explictly -# client_ristretto disabled because it requires RpcBanksService, which is no longer supported. -#cargo +"$rust_stable" test --manifest-path=themis/client_ristretto/Cargo.toml -- --nocapture - # # Check generated C headers # cargo run --manifest-path=utils/cgen/Cargo.toml # diff --git a/patch.crates-io.sh b/patch.crates-io.sh index acc1ac5cdab..b648421135e 100755 --- a/patch.crates-io.sh +++ b/patch.crates-io.sh @@ -11,7 +11,6 @@ fi workspace_crates=( Cargo.toml - themis/client_ristretto/Cargo.toml ) if [[ ! -r "$solana_dir"/scripts/read-cargo-variable.sh ]]; then diff --git a/themis/README.md b/themis/README.md deleted file mode 100644 index f2b533fe3ad..00000000000 --- a/themis/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# Brave THEMIS - -An implementation of Brave's THEMIS research project. This project contains -two privacy-oriented smart contracts, the Policy Smart Contract (PSC) and -the Fund Smart Contract (FSC). Together, the two contracts allow users to -be compensated for engaging with ad publishers. The users do not expose -their identities or preferences. - -## Build and Run the TPS demo client - -The demo client simulates 1,000 users interacting with the Ristretto -version of the THEMIS on-chain program. - -### Install prerequisites - -Create an Ubuntu 20.04 instance with at least 8GB of memory and 20 GB of -disk space. - -Install system dependencies: - -```bash -sudo apt update -sudo apt install -y g++ libclang-dev pkg-config make libssl-dev libudev-dev -``` - -Install the Rust compiler: - -``` -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -source $HOME/.cargo/env -``` - -Install the Solana command-line tools: - -```bash -curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v1.4.2/install/solana-install-init.sh | sh -s - v1.4.2 -export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH" -``` - -### Build the TPS example app - -Clone this git repo: - -```bash -git clone https://github.com/solana-labs/solana-program-library.git -cd solana-program-library -``` - -Build the demo client: - -```bash -cd themis/client_ristretto -cargo build --example tps -``` - -### Configure the default Solana wallet - -Point to the testnet cluster (default is mainnet-beta): - -```bash -solana config set --url http://api.testnet.solana.com -``` - -Create a keypair and airdrop it some SOL: - -```bash -solana-keygen new --no-passphrase -solana airdrop 10 -``` - -### Run the TPS example app - -```bash -cargo run --example tps -``` - -You should see something like: - -```bash -Seeding feepayer accounts... -Starting benchmark... -Benchmark complete. -4000 transactions in 27.880907273s (143.4673542303847 TPS) -``` diff --git a/themis/client_ristretto/Cargo.toml b/themis/client_ristretto/Cargo.toml deleted file mode 100644 index ebcb457f20a..00000000000 --- a/themis/client_ristretto/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ - -[package] -name = "spl-themis-ristretto-client" -version = "0.1.0" -description = "SPL THEMIS client" -authors = ["Solana Maintainers "] -repository = "https://github.com/solana-labs/solana-program-library" -license = "Apache-2.0" -edition = "2018" -exclude = ["js/**"] - -[dependencies] -bincode = "1.3" -borsh = "0.9" -curve25519-dalek = {package = "curve25519-dalek-ng", git = "https://github.com/garious/curve25519-dalek", rev = "fcef1fa11b3d3e89a1abf8986386ba9ae375392c", default-features = false, features = ["borsh"]} -elgamal_ristretto = { git = "https://github.com/garious/elgamal", rev = "892dbe115104bcb8cc26d79f9676c836ff6c018e", default-features = false } -futures = "0.3" -solana-banks-client = "1.8.1" -solana-cli-config = "1.8.1" -solana-sdk = "1.8.1" -spl-themis-ristretto = { version = "0.1.0", path = "../program_ristretto", features = ["no-entrypoint"] } -tarpc = { version = "0.22.0", features = ["full"] } -tokio = "0.3" -url = "2.1" - -[dev-dependencies] -separator = "0.4.1" -solana-banks-server = "1.8.1" -solana-bpf-loader-program = "1.8.1" -solana-core = "1.8.1" -solana_rbpf = "0.1" -solana-runtime = "1.8.1" - -[lib] -crate-type = ["cdylib", "lib"] diff --git a/themis/client_ristretto/build.rs b/themis/client_ristretto/build.rs deleted file mode 100644 index 6bdb003bfb2..00000000000 --- a/themis/client_ristretto/build.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::process::{exit, Command}; - -fn main() { - if std::env::var("XARGO").is_err() - && std::env::var("RUSTC_WRAPPER").is_err() - && std::env::var("RUSTC_WORKSPACE_WRAPPER").is_err() - { - println!( - "cargo:warning=(not a warning) Building BPF {} program", - std::env::var("CARGO_PKG_NAME").unwrap() - ); - if !Command::new("cargo") - .args(&[ - "build-bpf", - "--manifest-path", - "../program_ristretto/Cargo.toml", - ]) - .status() - .expect("Failed to build BPF themis program") - .success() - { - exit(1); - } - } -} diff --git a/themis/client_ristretto/examples/tps.rs b/themis/client_ristretto/examples/tps.rs deleted file mode 100644 index bdd5168506d..00000000000 --- a/themis/client_ristretto/examples/tps.rs +++ /dev/null @@ -1,37 +0,0 @@ -//! Themis client - -use solana_banks_client::start_tcp_client; -use solana_cli_config::{Config, CONFIG_FILE}; -use solana_sdk::signature::read_keypair_file; -use spl_themis_ristretto_client::test_e2e; -use std::path::Path; -use tokio::runtime::Runtime; -use url::Url; - -fn main() { - let config_file = CONFIG_FILE.as_ref().unwrap(); - let config = if Path::new(&config_file).exists() { - Config::load(&config_file).unwrap() - } else { - Config::default() - }; - let rpc_banks_url = Config::compute_rpc_banks_url(&config.json_rpc_url); - let url = Url::parse(&rpc_banks_url).unwrap(); - let host_port = (url.host_str().unwrap(), url.port().unwrap()); - - Runtime::new().unwrap().block_on(async { - let mut banks_client = start_tcp_client(host_port).await.unwrap(); - let policies = vec![1u64.into(), 2u64.into()]; - let sender_keypair = read_keypair_file(&config.keypair_path).unwrap(); - test_e2e( - &mut banks_client, - &spl_themis_ristretto::id(), - sender_keypair, - policies, - 1_000, - 3u64.into(), - ) - .await - .unwrap(); - }); -} diff --git a/themis/client_ristretto/src/lib.rs b/themis/client_ristretto/src/lib.rs deleted file mode 100644 index 8e7e8b111be..00000000000 --- a/themis/client_ristretto/src/lib.rs +++ /dev/null @@ -1,382 +0,0 @@ -//! Themis client -use curve25519_dalek::{ - constants::RISTRETTO_BASEPOINT_POINT, ristretto::RistrettoPoint, scalar::Scalar, -}; -use elgamal_ristretto::{/*ciphertext::Ciphertext,*/ private::SecretKey, public::PublicKey}; -use futures::future::join_all; -use solana_banks_client::{BanksClient, BanksClientExt}; -use solana_sdk::{ - commitment_config::CommitmentLevel, - message::Message, - native_token::sol_to_lamports, - pubkey::Pubkey, - signature::{Keypair, Signer}, - system_instruction, - transaction::Transaction, - transport, -}; -use spl_themis_ristretto::{ - instruction, - state::generate_keys, // recover_scalar, User}, -}; -use std::{io, time::Instant}; -//use tarpc::context; - -fn assert_transaction_size(tx: &Transaction) { - let tx_size = bincode::serialize(&tx).unwrap().len(); - assert!( - tx_size <= 1200, - "transaction over 1200 bytes: {} bytes", - tx_size - ); -} - -// TODO: Add this to BanksClient -pub async fn process_transactions_with_commitment( - client: &mut BanksClient, - transactions: Vec, - commitment: CommitmentLevel, -) -> transport::Result<()> { - let mut clients: Vec<_> = transactions.iter().map(|_| client.clone()).collect(); - let futures = clients - .iter_mut() - .zip(transactions) - .map(|(client, transaction)| { - client.process_transaction_with_commitment(transaction, commitment) - }); - let statuses = futures::future::join_all(futures).await; - statuses.into_iter().collect() // Convert Vec> to Result> -} - -/// For a single user, create interactions, calculate the aggregate, submit a proof, and verify it. -async fn run_user_workflow( - mut client: BanksClient, - program_id: &Pubkey, - sender_keypair: Keypair, - (_sk, pk): (SecretKey, PublicKey), - interactions: Vec<(RistrettoPoint, RistrettoPoint)>, - policies_pubkey: Pubkey, - _expected_scalar_aggregate: Scalar, -) -> io::Result { - let sender_pubkey = sender_keypair.pubkey(); - let mut num_transactions = 0; - - // Create the users account - let user_keypair = Keypair::new(); - let user_pubkey = user_keypair.pubkey(); - let ixs = instruction::create_user_account( - program_id, - &sender_pubkey, - &user_pubkey, - sol_to_lamports(0.001), - pk, - ); - let msg = Message::new(&ixs, Some(&sender_pubkey)); - let recent_blockhash = client.get_recent_blockhash().await?; - let tx = Transaction::new(&[&sender_keypair, &user_keypair], msg, recent_blockhash); - assert_transaction_size(&tx); - client - .process_transaction_with_commitment(tx, CommitmentLevel::Recent) - .await - .unwrap(); - num_transactions += 1; - - // Send one interaction at a time to stay under the BPF instruction limit - for (i, interaction) in interactions.into_iter().enumerate() { - let interactions = vec![(i as u8, interaction)]; - let ix = instruction::submit_interactions( - program_id, - &user_pubkey, - &policies_pubkey, - interactions, - ); - let msg = Message::new(&[ix], Some(&sender_pubkey)); - let recent_blockhash = client.get_recent_blockhash().await?; - let tx = Transaction::new(&[&sender_keypair, &user_keypair], msg, recent_blockhash); - assert_transaction_size(&tx); - client - .process_transaction_with_commitment(tx, CommitmentLevel::Recent) - .await - .unwrap(); - num_transactions += 1; - } - - //let user_account = client - // .get_account_with_commitment_and_context( - // context::current(), - // user_pubkey, - // CommitmentLevel::Recent, - // ) - // .await - // .unwrap() - // .unwrap(); - //let user = User::deserialize(&user_account.data).unwrap(); - //let ciphertext = Ciphertext { - // points: user.fetch_encrypted_aggregate(), - // pk, - //}; - - //let decrypted_aggregate = sk.decrypt(&ciphertext); - let decrypted_aggregate = RISTRETTO_BASEPOINT_POINT; - //let scalar_aggregate = recover_scalar(decrypted_aggregate, 16); - //assert_eq!(scalar_aggregate, expected_scalar_aggregate); - - //let ((announcement_g, announcement_ctx), response) = - // sk.prove_correct_decryption_no_Merlin(&ciphertext, &decrypted_aggregate).unwrap(); - let ((announcement_g, announcement_ctx), response) = ( - (RISTRETTO_BASEPOINT_POINT, RISTRETTO_BASEPOINT_POINT), - 0u64.into(), - ); - - let ix = instruction::submit_proof_decryption( - program_id, - &user_pubkey, - decrypted_aggregate, - announcement_g, - announcement_ctx, - response, - ); - let msg = Message::new(&[ix], Some(&sender_pubkey)); - let recent_blockhash = client.get_recent_blockhash().await?; - let tx = Transaction::new(&[&sender_keypair, &user_keypair], msg, recent_blockhash); - assert_transaction_size(&tx); - client - .process_transaction_with_commitment(tx, CommitmentLevel::Recent) - .await - .unwrap(); - num_transactions += 1; - - //let user_account = client.get_account_with_commitment_and_context(context::current(), user_pubkey, CommitmentLevel::Recent).await.unwrap().unwrap(); - //let user = User::deserialize(&user_account.data).unwrap(); - //assert!(user.fetch_proof_verification()); - - Ok(num_transactions) -} - -pub async fn test_e2e( - client: &mut BanksClient, - program_id: &Pubkey, - sender_keypair: Keypair, - policies: Vec, - num_users: u64, - expected_scalar_aggregate: Scalar, -) -> io::Result<()> { - let sender_pubkey = sender_keypair.pubkey(); - let policies_keypair = Keypair::new(); - let policies_pubkey = policies_keypair.pubkey(); - let policies_len = policies.len(); - - // Create the policies account - let mut ixs = instruction::create_policies_account( - program_id, - &sender_pubkey, - &policies_pubkey, - sol_to_lamports(0.01), - policies.len() as u8, - ); - let policies_slice: Vec<_> = policies - .iter() - .enumerate() - .map(|(i, x)| (i as u8, *x)) - .collect(); - ixs.push(instruction::store_policies( - program_id, - &policies_pubkey, - policies_slice, - )); - - let msg = Message::new(&ixs, Some(&sender_pubkey)); - let recent_blockhash = client.get_recent_blockhash().await?; - let tx = Transaction::new(&[&sender_keypair, &policies_keypair], msg, recent_blockhash); - assert_transaction_size(&tx); - client - .process_transaction_with_commitment(tx, CommitmentLevel::Recent) - .await - .unwrap(); - - // Send feepayer_keypairs some SOL - println!("Seeding feepayer accounts..."); - let feepayers: Vec<_> = (0..num_users).map(|_| Keypair::new()).collect(); - let recent_blockhash = client.get_recent_blockhash().await.unwrap(); - let txs: Vec<_> = feepayers - .chunks(20) - .map(|feepayers| { - let payments: Vec<_> = feepayers - .iter() - .map(|keypair| (keypair.pubkey(), sol_to_lamports(0.0011))) - .collect(); - let ixs = system_instruction::transfer_many(&sender_pubkey, &payments); - let msg = Message::new(&ixs, Some(&sender_keypair.pubkey())); - let tx = Transaction::new(&[&sender_keypair], msg, recent_blockhash); - assert_transaction_size(&tx); - tx - }) - .collect(); - process_transactions_with_commitment(client, txs, CommitmentLevel::Recent) - .await - .unwrap(); - - println!("Starting benchmark..."); - let now = Instant::now(); - - let (sk, pk) = generate_keys(); - let interactions: Vec<_> = (0..policies_len) - .map(|_| pk.encrypt(&RISTRETTO_BASEPOINT_POINT).points) - .collect(); - - let futures: Vec<_> = feepayers - .into_iter() - .map(move |feepayer_keypair| { - run_user_workflow( - client.clone(), - program_id, - feepayer_keypair, - (sk.clone(), pk), - interactions.clone(), - policies_pubkey, - expected_scalar_aggregate, - ) - }) - .collect(); - let results = join_all(futures).await; - let elapsed = now.elapsed(); - println!("Benchmark complete."); - - let num_transactions = results - .into_iter() - .map(|result| result.unwrap()) - .sum::(); - println!( - "{} transactions in {:?} ({} TPS)", - num_transactions, - elapsed, - num_transactions as f64 / elapsed.as_secs_f64() - ); - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use solana_banks_client::start_client; - use solana_banks_server::banks_server::start_local_server; - use solana_runtime::{bank::Bank, bank_forks::BankForks}; - use solana_sdk::{ - account::Account, account_info::AccountInfo, genesis_config::create_genesis_config, - instruction::InstructionError, keyed_account::KeyedAccount, - process_instruction::InvokeContext, program_error::ProgramError, - }; - use spl_themis_ristretto::processor::process_instruction; - use std::{ - collections::HashMap, - sync::{Arc, RwLock}, - {cell::RefCell, rc::Rc}, - }; - use tokio::runtime::Runtime; - - fn to_instruction_error(error: ProgramError) -> InstructionError { - match error { - ProgramError::Custom(err) => InstructionError::Custom(err), - ProgramError::InvalidArgument => InstructionError::InvalidArgument, - ProgramError::InvalidInstructionData => InstructionError::InvalidInstructionData, - ProgramError::InvalidAccountData => InstructionError::InvalidAccountData, - ProgramError::AccountDataTooSmall => InstructionError::AccountDataTooSmall, - ProgramError::InsufficientFunds => InstructionError::InsufficientFunds, - ProgramError::IncorrectProgramId => InstructionError::IncorrectProgramId, - ProgramError::MissingRequiredSignature => InstructionError::MissingRequiredSignature, - ProgramError::AccountAlreadyInitialized => InstructionError::AccountAlreadyInitialized, - ProgramError::UninitializedAccount => InstructionError::UninitializedAccount, - ProgramError::NotEnoughAccountKeys => InstructionError::NotEnoughAccountKeys, - ProgramError::AccountBorrowFailed => InstructionError::AccountBorrowFailed, - ProgramError::MaxSeedLengthExceeded => InstructionError::MaxSeedLengthExceeded, - ProgramError::InvalidSeeds => InstructionError::InvalidSeeds, - } - } - - // Same as process_instruction, but but can be used as a builtin program. Handy for unit-testing. - pub fn process_instruction_native( - program_id: &Pubkey, - keyed_accounts: &[KeyedAccount], - input: &[u8], - _invoke_context: &mut dyn InvokeContext, - ) -> Result<(), InstructionError> { - // Copy all the accounts into a HashMap to ensure there are no duplicates - let mut accounts: HashMap = keyed_accounts - .iter() - .map(|ka| (*ka.unsigned_key(), ka.account.borrow().clone())) - .collect(); - - // Create shared references to each account's lamports/data/owner - let account_refs: HashMap<_, _> = accounts - .iter_mut() - .map(|(key, account)| { - ( - *key, - ( - Rc::new(RefCell::new(&mut account.lamports)), - Rc::new(RefCell::new(&mut account.data[..])), - &account.owner, - ), - ) - }) - .collect(); - - // Create AccountInfos - let account_infos: Vec = keyed_accounts - .iter() - .map(|keyed_account| { - let key = keyed_account.unsigned_key(); - let (lamports, data, owner) = &account_refs[key]; - AccountInfo { - key, - is_signer: keyed_account.signer_key().is_some(), - is_writable: keyed_account.is_writable(), - lamports: lamports.clone(), - data: data.clone(), - owner, - executable: keyed_account.executable().unwrap(), - rent_epoch: keyed_account.rent_epoch().unwrap(), - } - }) - .collect(); - - // Execute the BPF entrypoint - process_instruction(program_id, &account_infos, input).map_err(to_instruction_error)?; - - // Commit changes to the KeyedAccounts - for keyed_account in keyed_accounts { - let mut account = keyed_account.account.borrow_mut(); - let key = keyed_account.unsigned_key(); - let (lamports, data, _owner) = &account_refs[key]; - account.lamports = **lamports.borrow(); - account.data = data.borrow().to_vec(); - } - - Ok(()) - } - - #[test] - fn test_local_e2e_2ads() { - let (genesis_config, sender_keypair) = create_genesis_config(sol_to_lamports(9_000_000.0)); - let mut bank = Bank::new(&genesis_config); - let program_id = Keypair::new().pubkey(); - bank.add_builtin("Themis", program_id, process_instruction_native); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); - Runtime::new().unwrap().block_on(async { - let transport = start_local_server(&bank_forks).await; - let mut banks_client = start_client(transport).await.unwrap(); - let policies = vec![1u64.into(), 2u64.into()]; - test_e2e( - &mut banks_client, - &program_id, - sender_keypair, - policies, - 10, - 3u64.into(), - ) - .await - .unwrap(); - }); - } -} diff --git a/themis/client_ristretto/tests/assert_instruction_count.rs b/themis/client_ristretto/tests/assert_instruction_count.rs deleted file mode 100644 index 4f6935ed4ed..00000000000 --- a/themis/client_ristretto/tests/assert_instruction_count.rs +++ /dev/null @@ -1,294 +0,0 @@ -use borsh::BorshSerialize; -use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT; -use elgamal_ristretto::ciphertext::Ciphertext; -use separator::Separatable; -use solana_bpf_loader_program::{ - create_vm, - serialization::{deserialize_parameters, serialize_parameters}, -}; -use solana_rbpf::vm::EbpfVm; -use solana_sdk::{ - account::Account, - bpf_loader, - entrypoint::SUCCESS, - keyed_account::KeyedAccount, - process_instruction::{BpfComputeBudget, MockInvokeContext}, - pubkey::Pubkey, -}; -use spl_themis_ristretto::{ - instruction::ThemisInstruction, - state::{generate_keys, /*recover_scalar,*/ Policies, User}, -}; -use std::{fs::File, io::Read}; - -fn load_program(name: &str) -> Vec { - let mut file = File::open(name).unwrap(); - - let mut program = Vec::new(); - file.read_to_end(&mut program).unwrap(); - program -} - -fn run_program( - program_id: &Pubkey, - parameter_accounts: &[KeyedAccount], - instruction_data: &[u8], -) -> u64 { - let mut program_account = Account::default(); - program_account.data = load_program("../../target/deploy/spl_themis_ristretto.so"); - let loader_id = bpf_loader::id(); - let mut invoke_context = MockInvokeContext::default(); - invoke_context.bpf_compute_budget = BpfComputeBudget { - max_invoke_depth: 10, - ..BpfComputeBudget::default() - }; - - let executable = EbpfVm::::create_executable_from_elf( - &&program_account.data, - None, - ) - .unwrap(); - let (mut vm, heap_region) = create_vm( - &loader_id, - executable.as_ref(), - parameter_accounts, - &mut invoke_context, - ) - .unwrap(); - let mut parameter_bytes = serialize_parameters( - &loader_id, - program_id, - parameter_accounts, - &instruction_data, - ) - .unwrap(); - assert_eq!( - SUCCESS, - vm.execute_program(parameter_bytes.as_mut_slice(), &[], &[heap_region]) - .unwrap() - ); - deserialize_parameters(&loader_id, parameter_accounts, ¶meter_bytes).unwrap(); - vm.get_total_instruction_count() -} - -#[test] -fn assert_instruction_count() { - let program_id = Pubkey::new_unique(); - - // Create new policies - let policies_key = Pubkey::new_unique(); - let scalars = vec![1u64.into(), 2u64.into()]; - //let scalars = vec![ - // 1u64.into(), - // 1u64.into(), - // 1u64.into(), - // 1u64.into(), - // 1u64.into(), - // 1u64.into(), - // 1u64.into(), - // 1u64.into(), - // 1u64.into(), - // 1u64.into(), //10 - // 2u64.into(), - // 2u64.into(), - // 2u64.into(), - // 2u64.into(), - // 2u64.into(), - // 2u64.into(), - // 2u64.into(), - // 2u64.into(), - // 2u64.into(), - // 2u64.into(), // 2 * 10 - // 1u64.into(), - // 1u64.into(), - // 1u64.into(), - // 1u64.into(), - // 1u64.into(), - // 1u64.into(), - // 1u64.into(), - // 1u64.into(), - // 1u64.into(), - // 1u64.into(), //10 - // 2u64.into(), - // 2u64.into(), - // 2u64.into(), - // 2u64.into(), - // 2u64.into(), - // 2u64.into(), - // 2u64.into(), - // 2u64.into(), - // 2u64.into(), - // 2u64.into(), // 2 * 10 - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - // 0u64.into(), - //]; - let num_scalars = scalars.len(); - - let (sk, pk) = generate_keys(); - let encrypted_interactions: Vec<_> = (0..num_scalars) - .map(|i| (i as u8, pk.encrypt(&RISTRETTO_BASEPOINT_POINT).points)) - .collect(); - - let policies_account = Account::new_ref( - 0, - Policies { - is_initialized: true, - num_scalars: num_scalars as u8, - scalars, - } - .try_to_vec() - .unwrap() - .len(), - &program_id, - ); - let instruction_data = ThemisInstruction::InitializePoliciesAccount { - num_scalars: num_scalars as u8, - } - .serialize() - .unwrap(); - let parameter_accounts = vec![KeyedAccount::new(&policies_key, false, &policies_account)]; - let initialize_policies_count = - run_program(&program_id, ¶meter_accounts[..], &instruction_data); - - // Create user account - let user_key = Pubkey::new_unique(); - let user_account = - Account::new_ref(0, User::default().try_to_vec().unwrap().len(), &program_id); - let instruction_data = ThemisInstruction::InitializeUserAccount { public_key: pk } - .serialize() - .unwrap(); - let parameter_accounts = vec![KeyedAccount::new(&user_key, false, &user_account)]; - let initialize_user_count = - run_program(&program_id, ¶meter_accounts[..], &instruction_data); - - // Calculate Aggregate - let instruction_data = ThemisInstruction::SubmitInteractions { - encrypted_interactions, - } - .serialize() - .unwrap(); - let parameter_accounts = vec![ - KeyedAccount::new(&user_key, true, &user_account), - KeyedAccount::new(&policies_key, false, &policies_account), - ]; - let calculate_aggregate_count = - run_program(&program_id, ¶meter_accounts[..], &instruction_data); - - // Submit proof decryption - let user = User::deserialize(&user_account.try_borrow().unwrap().data).unwrap(); - let encrypted_point = user.fetch_encrypted_aggregate(); - let ciphertext = Ciphertext { - points: encrypted_point, - pk, - }; - - let decrypted_aggregate = sk.decrypt(&ciphertext); - //let scalar_aggregate = recover_scalar(decrypted_aggregate, 16); - //let expected_scalar_aggregate = 3u64.into(); - //assert_eq!(scalar_aggregate, expected_scalar_aggregate); - - let (announcement, response) = - sk.prove_correct_decryption_no_Merlin(&ciphertext, &decrypted_aggregate); - - let instruction_data = ThemisInstruction::SubmitProofDecryption { - plaintext: decrypted_aggregate, - announcement: Box::new(announcement), - response, - } - .serialize() - .unwrap(); - let parameter_accounts = vec![KeyedAccount::new(&user_key, true, &user_account)]; - let proof_decryption_count = - run_program(&program_id, ¶meter_accounts[..], &instruction_data); - - const BASELINE_NEW_POLICIES_COUNT: u64 = 80_000; // last known 3,354 - const BASELINE_INITIALIZE_USER_COUNT: u64 = 22_000; // last known 19,746 - const BASELINE_CALCULATE_AGGREGATE_COUNT: u64 = 200_000; // last known 87,220 - const BASELINE_PROOF_DECRYPTION_COUNT: u64 = 200_000; // last known 105,368 - - println!("BPF instructions executed"); - println!( - " InitializePolicies({}): {} ({:?})", - num_scalars, - initialize_policies_count.separated_string(), - BASELINE_NEW_POLICIES_COUNT - ); - println!( - " InitializeUserAccount: {} ({:?})", - initialize_user_count.separated_string(), - BASELINE_INITIALIZE_USER_COUNT - ); - println!( - " CalculateAggregate: {} ({:?})", - calculate_aggregate_count.separated_string(), - BASELINE_CALCULATE_AGGREGATE_COUNT - ); - println!( - " SubmitProofDecryption: {} ({:?})", - proof_decryption_count.separated_string(), - BASELINE_PROOF_DECRYPTION_COUNT - ); - - assert!(initialize_policies_count <= BASELINE_NEW_POLICIES_COUNT); - assert!(initialize_user_count <= BASELINE_INITIALIZE_USER_COUNT); - assert!(calculate_aggregate_count <= BASELINE_CALCULATE_AGGREGATE_COUNT); - assert!(proof_decryption_count <= BASELINE_PROOF_DECRYPTION_COUNT); -} diff --git a/themis/client_ristretto/tests/e2e.rs b/themis/client_ristretto/tests/e2e.rs deleted file mode 100644 index e2fcd52bafe..00000000000 --- a/themis/client_ristretto/tests/e2e.rs +++ /dev/null @@ -1,194 +0,0 @@ -use solana_banks_client::{start_tcp_client, BanksClient, BanksClientExt}; -use solana_core::test_validator::{TestValidator, TestValidatorOptions}; -use solana_sdk::{ - bpf_loader, - commitment_config::CommitmentLevel, - loader_instruction, - message::Message, - native_token::sol_to_lamports, - pubkey::Pubkey, - signature::{Keypair, Signer}, - system_instruction, - transaction::Transaction, - transport, -}; -use spl_themis_ristretto_client::{process_transactions_with_commitment, test_e2e}; -use std::{ - fs::{remove_dir_all, File}, - io::Read, -}; -use tokio::runtime::Runtime; - -const DATA_CHUNK_SIZE: usize = 229; // Keep program chunks under PACKET_DATA_SIZE - -fn load_program(name: &str) -> Vec { - let mut file = File::open(name).unwrap(); - - let mut program = Vec::new(); - file.read_to_end(&mut program).unwrap(); - program -} - -async fn create_program_account_with_commitment( - client: &mut BanksClient, - loader_id: &Pubkey, - funder_keypair: &Keypair, - program_keypair: &Keypair, - program_len: usize, - commitment: CommitmentLevel, -) -> transport::Result<()> { - let minimum_balance = 0.01; // TODO: Can we calcualte this from get_fees() and program_len? - let ix = system_instruction::create_account( - &funder_keypair.pubkey(), - &program_keypair.pubkey(), - sol_to_lamports(minimum_balance), - program_len as u64, - loader_id, - ); - let message = Message::new(&[ix], Some(&funder_keypair.pubkey())); - let recent_blockhash = client.get_recent_blockhash().await?; - let transaction = Transaction::new( - &[funder_keypair, program_keypair], - message, - recent_blockhash, - ); - client - .process_transaction_with_commitment(transaction, commitment) - .await -} - -async fn write_program_with_commitment( - client: &mut BanksClient, - loader_id: &Pubkey, - funder_keypair: &Keypair, - program_keypair: &Keypair, - program: Vec, - commitment: CommitmentLevel, -) -> transport::Result<()> { - let recent_blockhash = client.get_recent_blockhash().await?; - let transactions: Vec<_> = program - .chunks(DATA_CHUNK_SIZE) - .enumerate() - .map(|(i, chunk)| { - let instruction = loader_instruction::write( - &program_keypair.pubkey(), - loader_id, - (i * DATA_CHUNK_SIZE) as u32, - chunk.to_vec(), - ); - let message = Message::new(&[instruction], Some(&funder_keypair.pubkey())); - Transaction::new( - &[funder_keypair, program_keypair], - message, - recent_blockhash, - ) - }) - .collect(); - process_transactions_with_commitment(client, transactions, commitment).await -} - -async fn finalize_program_with_commitment( - client: &mut BanksClient, - loader_id: &Pubkey, - funder_keypair: &Keypair, - program_keypair: &Keypair, - commitment: CommitmentLevel, -) -> transport::Result<()> { - let ix = loader_instruction::finalize(&program_keypair.pubkey(), &loader_id); - let message = Message::new(&[ix], Some(&funder_keypair.pubkey())); - let recent_blockhash = client.get_recent_blockhash().await?; - let transaction = Transaction::new( - &[funder_keypair, program_keypair], - message, - recent_blockhash, - ); - client - .process_transaction_with_commitment(transaction, commitment) - .await -} - -// TODO: Add this to BanksClient -async fn deploy_program_with_commitment( - client: &mut BanksClient, - loader_id: &Pubkey, - funder_keypair: &Keypair, - program_keypair: &Keypair, - program: Vec, - commitment: CommitmentLevel, -) -> transport::Result<()> { - create_program_account_with_commitment( - client, - loader_id, - funder_keypair, - program_keypair, - program.len(), - commitment, - ) - .await?; - write_program_with_commitment( - client, - loader_id, - funder_keypair, - program_keypair, - program, - commitment, - ) - .await?; - finalize_program_with_commitment( - client, - loader_id, - funder_keypair, - program_keypair, - commitment, - ) - .await?; - Ok(()) -} - -#[test] -#[ignore] -fn test_validator_e2e() { - let TestValidator { - server, - leader_data, - alice, - ledger_path, - .. - } = TestValidator::run_with_options(TestValidatorOptions { - mint_lamports: sol_to_lamports(10.0), - ..TestValidatorOptions::default() - }); - - let program = load_program("../../target/deploy/spl_themis_ristretto.so"); - - Runtime::new().unwrap().block_on(async { - let mut banks_client = start_tcp_client(leader_data.rpc_banks).await.unwrap(); - let program_keypair = Keypair::new(); - deploy_program_with_commitment( - &mut banks_client, - &bpf_loader::id(), - &alice, - &program_keypair, - program, - CommitmentLevel::Recent, - ) - .await - .unwrap(); - - let policies = vec![1u64.into(), 2u64.into()]; - test_e2e( - &mut banks_client, - &program_keypair.pubkey(), - alice, - policies, - 10, - 3u64.into(), - ) - .await - .unwrap(); - }); - - // Explicit cleanup, otherwise "pure virtual method called" crash in Docker - server.close().unwrap(); - remove_dir_all(ledger_path).unwrap(); -} diff --git a/themis/program_ristretto/Cargo.toml b/themis/program_ristretto/Cargo.toml deleted file mode 100644 index e0c754b8913..00000000000 --- a/themis/program_ristretto/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "spl-themis-ristretto" -version = "0.1.0" -description = "Solana Program Library THEMIS" -authors = ["Solana Maintainers "] -repository = "https://github.com/solana-labs/solana-program-library" -license = "Apache-2.0" -edition = "2018" - -[features] -no-entrypoint = [] -default = ["elgamal_ristretto/program"] - -[dependencies] -bincode = "1.3" -borsh = "0.9" -curve25519-dalek = { package = "curve25519-dalek-ng", git = "https://github.com/garious/curve25519-dalek", rev = "fcef1fa11b3d3e89a1abf8986386ba9ae375392c", features = ["borsh"] } -elgamal_ristretto = { git = "https://github.com/garious/elgamal", rev = "892dbe115104bcb8cc26d79f9676c836ff6c018e", default-features = false } -getrandom = { version = "0.1.15", features = ["dummy"] } -num-derive = "0.3" -num-traits = "0.2" -rand = "0.8.0" -solana-program = "1.8.1" -subtle = "=2.2.3" -thiserror = "1.0" - -[lib] -crate-type = ["cdylib", "lib"] diff --git a/themis/program_ristretto/Xargo.toml b/themis/program_ristretto/Xargo.toml deleted file mode 100644 index 1744f098ae1..00000000000 --- a/themis/program_ristretto/Xargo.toml +++ /dev/null @@ -1,2 +0,0 @@ -[target.bpfel-unknown-unknown.dependencies.std] -features = [] \ No newline at end of file diff --git a/themis/program_ristretto/program-id.md b/themis/program_ristretto/program-id.md deleted file mode 100644 index e906b3898f7..00000000000 --- a/themis/program_ristretto/program-id.md +++ /dev/null @@ -1 +0,0 @@ -C8tR6A3CWcEL46KHx7TJcbyR4hdoPi1wrBBQa42FuJMF diff --git a/themis/program_ristretto/src/entrypoint.rs b/themis/program_ristretto/src/entrypoint.rs deleted file mode 100644 index 28857eba36a..00000000000 --- a/themis/program_ristretto/src/entrypoint.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! Program entrypoint - -use solana_program::{ - account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, -}; - -entrypoint!(process_instruction); -fn process_instruction( - program_id: &Pubkey, - accounts: &[AccountInfo], - instruction_data: &[u8], -) -> ProgramResult { - crate::processor::process_instruction(program_id, accounts, instruction_data)?; - Ok(()) -} diff --git a/themis/program_ristretto/src/error.rs b/themis/program_ristretto/src/error.rs deleted file mode 100644 index 3cf3d77899f..00000000000 --- a/themis/program_ristretto/src/error.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! Error types - -use num_derive::FromPrimitive; -use num_traits::FromPrimitive; -use solana_program::program_error::PrintProgramError; -use solana_program::{decode_error::DecodeError, program_error::ProgramError}; -use thiserror::Error; - -/// Errors that may be returned by the Themis program. -#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)] -pub enum ThemisError { - /// Invalid instruction - #[error("Invalid instruction")] - InvalidInstruction, - - /// Account already in use - #[error("Account in use")] - AccountInUse, -} -impl From for ProgramError { - fn from(e: ThemisError) -> Self { - ProgramError::Custom(e as u32) - } -} -impl DecodeError for ThemisError { - fn type_of() -> &'static str { - "ThemisError" - } -} - -impl PrintProgramError for ThemisError { - fn print(&self) - where - E: 'static + std::error::Error + DecodeError + PrintProgramError + FromPrimitive, - { - match self { - ThemisError::InvalidInstruction => println!("Error: Invalid instruction"), - ThemisError::AccountInUse => println!("Error: Account in use"), - } - } -} diff --git a/themis/program_ristretto/src/instruction.rs b/themis/program_ristretto/src/instruction.rs deleted file mode 100644 index 32061df0870..00000000000 --- a/themis/program_ristretto/src/instruction.rs +++ /dev/null @@ -1,252 +0,0 @@ -//! Instruction types - -use crate::state::{Policies, User}; -use borsh::{BorshDeserialize, BorshSerialize}; -use curve25519_dalek::{ristretto::RistrettoPoint, scalar::Scalar}; -use elgamal_ristretto::public::PublicKey; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - program_error::ProgramError, - pubkey::Pubkey, - system_instruction, -}; - -/// Instructions supported by the Themis program. -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] -pub enum ThemisInstruction { - /// Initialize a new user account - /// - /// The `InitializeUserAccount` instruction requires no signers and MUST be included within - /// the same Transaction as the system program's `CreateInstruction` that creates the account - /// being initialized. Otherwise another party can acquire ownership of the uninitialized account. - /// - /// Accounts expected by this instruction: - /// - /// 0. `[writable]` The account to initialize. - InitializeUserAccount { - /// Public key for all encrypted interations - public_key: PublicKey, - }, - - /// Initialize a new policies account - /// - /// The `InitializePoliciesAccount` instruction requires no signers and MUST be included within - /// the same Transaction as the system program's `CreateInstruction` that creates the account - /// being initialized. Otherwise another party can acquire ownership of the uninitialized account. - /// - /// Accounts expected by this instruction: - /// - /// 0. `[writable]` The account to initialize. - InitializePoliciesAccount { - /// Number of policies to be added - num_scalars: u8, - }, - - /// Store policies - /// - /// The `StorePolices` instruction is used to set individual policies. - /// - /// Accounts expected by this instruction: - /// - /// 0. `[writable, signer]` The policies account. - StorePolicies { - /// Policies to be added - scalars: Vec<(u8, Scalar)>, - }, - - /// Calculate aggregate. The length of the `input` vector must equal the - /// number of policies. - /// - /// Accounts expected by this instruction: - /// - /// 0. `[writable, signer]` The user account - /// 1. `[]` The policies account - SubmitInteractions { - /// Encrypted interactions - encrypted_interactions: Vec<(u8, (RistrettoPoint, RistrettoPoint))>, - }, - - /// Submit proof decryption - /// - /// Accounts expected by this instruction: - /// - /// 0. `[writable, signer]` The user account - SubmitProofDecryption { - /// plaintext - plaintext: RistrettoPoint, - - /// (announcement_g, announcement_ctx) - announcement: Box<(RistrettoPoint, RistrettoPoint)>, - - /// response - response: Scalar, - }, - - /// Request a payment - /// - /// Accounts expected by this instruction: - /// - /// 0. `[writable, signer]` The user account - RequestPayment { - /// Encrypted aggregate - encrypted_aggregate: Box<(RistrettoPoint, RistrettoPoint)>, - - /// Decrypted aggregate - decrypted_aggregate: RistrettoPoint, - - /// Proof correct decryption - proof_correct_decryption: RistrettoPoint, - }, -} - -impl ThemisInstruction { - pub fn serialize(&self) -> Result, ProgramError> { - self.try_to_vec() - .map_err(|_| ProgramError::AccountDataTooSmall) - } - - pub(crate) fn deserialize(data: &[u8]) -> Result { - Self::try_from_slice(&data).map_err(|_| ProgramError::InvalidInstructionData) - } -} - -/// Return an `InitializeUserAccount` instruction. -fn initialize_user_account( - program_id: &Pubkey, - user_pubkey: &Pubkey, - public_key: PublicKey, -) -> Instruction { - let data = ThemisInstruction::InitializeUserAccount { public_key }; - - let accounts = vec![AccountMeta::new(*user_pubkey, false)]; - - Instruction { - program_id: *program_id, - accounts, - data: data.serialize().unwrap(), - } -} - -/// Return two instructions that create and initialize a user account. -pub fn create_user_account( - program_id: &Pubkey, - from: &Pubkey, - user_pubkey: &Pubkey, - lamports: u64, - public_key: PublicKey, -) -> Vec { - let space = User::default().try_to_vec().unwrap().len() as u64; - vec![ - system_instruction::create_account(from, user_pubkey, lamports, space, program_id), - initialize_user_account(program_id, user_pubkey, public_key), - ] -} - -/// Return an `InitializePoliciesAccount` instruction. -fn initialize_policies_account( - program_id: &Pubkey, - policies_pubkey: &Pubkey, - num_scalars: u8, -) -> Instruction { - let data = ThemisInstruction::InitializePoliciesAccount { num_scalars }; - let accounts = vec![AccountMeta::new(*policies_pubkey, false)]; - Instruction { - program_id: *program_id, - accounts, - data: data.serialize().unwrap(), - } -} - -/// Return two instructions that create and initialize a policies account. -pub fn create_policies_account( - program_id: &Pubkey, - from: &Pubkey, - policies_pubkey: &Pubkey, - lamports: u64, - num_scalars: u8, -) -> Vec { - let space = Policies::new(num_scalars).try_to_vec().unwrap().len() as u64; - vec![ - system_instruction::create_account(from, policies_pubkey, lamports, space, program_id), - initialize_policies_account(program_id, policies_pubkey, num_scalars), - ] -} - -/// Return an `InitializePoliciesAccount` instruction. -pub fn store_policies( - program_id: &Pubkey, - policies_pubkey: &Pubkey, - scalars: Vec<(u8, Scalar)>, -) -> Instruction { - let data = ThemisInstruction::StorePolicies { scalars }; - let accounts = vec![AccountMeta::new(*policies_pubkey, true)]; - Instruction { - program_id: *program_id, - accounts, - data: data.serialize().unwrap(), - } -} - -/// Return a `SubmitInteractions` instruction. -pub fn submit_interactions( - program_id: &Pubkey, - user_pubkey: &Pubkey, - policies_pubkey: &Pubkey, - encrypted_interactions: Vec<(u8, (RistrettoPoint, RistrettoPoint))>, -) -> Instruction { - let data = ThemisInstruction::SubmitInteractions { - encrypted_interactions, - }; - let accounts = vec![ - AccountMeta::new(*user_pubkey, true), - AccountMeta::new_readonly(*policies_pubkey, false), - ]; - Instruction { - program_id: *program_id, - accounts, - data: data.serialize().unwrap(), - } -} - -/// Return a `SubmitProofDecryption` instruction. -pub fn submit_proof_decryption( - program_id: &Pubkey, - user_pubkey: &Pubkey, - plaintext: RistrettoPoint, - announcement_g: RistrettoPoint, - announcement_ctx: RistrettoPoint, - response: Scalar, -) -> Instruction { - let data = ThemisInstruction::SubmitProofDecryption { - plaintext, - announcement: Box::new((announcement_g, announcement_ctx)), - response, - }; - let accounts = vec![AccountMeta::new(*user_pubkey, true)]; - Instruction { - program_id: *program_id, - accounts, - data: data.serialize().unwrap(), - } -} - -/// Return a `RequestPayment` instruction. -pub fn request_payment( - program_id: &Pubkey, - user_pubkey: &Pubkey, - encrypted_aggregate: (RistrettoPoint, RistrettoPoint), - decrypted_aggregate: RistrettoPoint, - proof_correct_decryption: RistrettoPoint, -) -> Instruction { - let data = ThemisInstruction::RequestPayment { - encrypted_aggregate: Box::new(encrypted_aggregate), - decrypted_aggregate, - proof_correct_decryption, - }; - let accounts = vec![AccountMeta::new(*user_pubkey, true)]; - Instruction { - program_id: *program_id, - accounts, - data: data.serialize().unwrap(), - } -} diff --git a/themis/program_ristretto/src/lib.rs b/themis/program_ristretto/src/lib.rs deleted file mode 100644 index 6b31f7786c7..00000000000 --- a/themis/program_ristretto/src/lib.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! An implementation of Brave's THEMIS for the Solana blockchain -#![forbid(unsafe_code)] - -pub mod error; -pub mod instruction; -pub mod processor; -pub mod state; - -#[cfg(not(feature = "no-entrypoint"))] -pub mod entrypoint; - -// Export current sdk types for downstream users building with a different sdk version -pub use solana_program; - -solana_program::declare_id!("C8tR6A3CWcEL46KHx7TJcbyR4hdoPi1wrBBQa42FuJMF"); diff --git a/themis/program_ristretto/src/processor.rs b/themis/program_ristretto/src/processor.rs deleted file mode 100644 index 8b6a1e4ab8f..00000000000 --- a/themis/program_ristretto/src/processor.rs +++ /dev/null @@ -1,141 +0,0 @@ -//! Themis program -use crate::{ - error::ThemisError, - instruction::ThemisInstruction, - state::{Policies, User}, -}; -use curve25519_dalek::{ristretto::RistrettoPoint, scalar::Scalar}; -use elgamal_ristretto::public::PublicKey; -use solana_program::{ - account_info::{next_account_info, AccountInfo}, - program_error::ProgramError, - pubkey::Pubkey, -}; - -fn process_initialize_user_account( - user_info: &AccountInfo, - public_key: PublicKey, -) -> Result<(), ProgramError> { - // TODO: verify the program ID - if let Ok(user) = User::deserialize(&user_info.data.borrow()) { - if user.is_initialized { - return Err(ThemisError::AccountInUse.into()); - } - } - let user = User::new(public_key); - user.serialize(&mut user_info.data.borrow_mut()) -} - -fn process_initialize_policies_account( - num_scalars: u8, - policies_info: &AccountInfo, -) -> Result<(), ProgramError> { - if let Ok(policies) = Policies::deserialize(&policies_info.data.borrow()) { - if policies.is_initialized { - return Err(ThemisError::AccountInUse.into()); - } - } - let policies = Policies::new(num_scalars); - policies.serialize(&mut policies_info.data.borrow_mut()) -} - -fn process_store_policies( - scalars: Vec<(u8, Scalar)>, - policies_info: &AccountInfo, -) -> Result<(), ProgramError> { - let mut policies = Policies::deserialize(&policies_info.data.borrow())?; - for (i, scalar) in scalars { - policies.scalars[i as usize] = scalar; - } - policies.serialize(&mut policies_info.data.borrow_mut()) -} - -fn process_submit_interactions( - encrypted_interactions: &[(u8, (RistrettoPoint, RistrettoPoint))], - user_info: &AccountInfo, - policies_info: &AccountInfo, -) -> Result<(), ProgramError> { - let mut user = User::deserialize(&user_info.data.borrow())?; - let policies = Policies::deserialize(&policies_info.data.borrow())?; - user.submit_interactions(encrypted_interactions, &policies.scalars); - user.serialize(&mut user_info.data.borrow_mut()) -} - -fn process_submit_proof_decryption( - plaintext: RistrettoPoint, - announcement: (RistrettoPoint, RistrettoPoint), - response: Scalar, - user_info: &AccountInfo, -) -> Result<(), ProgramError> { - let mut user = User::deserialize(&user_info.data.borrow())?; - user.submit_proof_decryption(plaintext, announcement.0, announcement.1, response); - user.serialize(&mut user_info.data.borrow_mut()) -} - -fn process_request_payment( - encrypted_aggregate: (RistrettoPoint, RistrettoPoint), - decrypted_aggregate: RistrettoPoint, - proof_correct_decryption: RistrettoPoint, - user_info: &AccountInfo, -) -> Result<(), ProgramError> { - let mut user = User::deserialize(&user_info.data.borrow())?; - user.request_payment( - encrypted_aggregate, - decrypted_aggregate, - proof_correct_decryption, - ); - user.serialize(&mut user_info.data.borrow_mut()) -} - -/// Process the given transaction instruction -pub fn process_instruction( - _program_id: &Pubkey, - account_infos: &[AccountInfo], - input: &[u8], -) -> Result<(), ProgramError> { - let account_infos_iter = &mut account_infos.iter(); - let instruction = ThemisInstruction::deserialize(input)?; - - match instruction { - ThemisInstruction::InitializeUserAccount { public_key } => { - let user_info = next_account_info(account_infos_iter)?; - process_initialize_user_account(&user_info, public_key) - } - ThemisInstruction::InitializePoliciesAccount { num_scalars } => { - let policies_info = next_account_info(account_infos_iter)?; - process_initialize_policies_account(num_scalars, &policies_info) - } - ThemisInstruction::StorePolicies { scalars } => { - let policies_info = next_account_info(account_infos_iter)?; - process_store_policies(scalars, &policies_info) - } - ThemisInstruction::SubmitInteractions { - encrypted_interactions, - } => { - let user_info = next_account_info(account_infos_iter)?; - let policies_info = next_account_info(account_infos_iter)?; - process_submit_interactions(&encrypted_interactions, &user_info, &policies_info) - } - ThemisInstruction::SubmitProofDecryption { - plaintext, - announcement, - response, - } => { - let user_info = next_account_info(account_infos_iter)?; - process_submit_proof_decryption(plaintext, *announcement, response, &user_info) - } - ThemisInstruction::RequestPayment { - encrypted_aggregate, - decrypted_aggregate, - proof_correct_decryption, - } => { - let user_info = next_account_info(account_infos_iter)?; - process_request_payment( - *encrypted_aggregate, - decrypted_aggregate, - proof_correct_decryption, - &user_info, - ) - } - } -} diff --git a/themis/program_ristretto/src/state.rs b/themis/program_ristretto/src/state.rs deleted file mode 100644 index 4ddef83477e..00000000000 --- a/themis/program_ristretto/src/state.rs +++ /dev/null @@ -1,350 +0,0 @@ -#![allow(missing_docs)] - -use borsh::{BorshDeserialize, BorshSerialize}; -use curve25519_dalek::{ - constants::RISTRETTO_BASEPOINT_POINT, ristretto::RistrettoPoint, scalar::Scalar, - traits::Identity, -}; -use elgamal_ristretto::{ - ciphertext::Ciphertext, multiply::ristretto_mul, private::SecretKey, public::PublicKey, -}; -use rand::thread_rng; -use solana_program::program_error::ProgramError; - -type Points = (RistrettoPoint, RistrettoPoint); - -#[derive(Default, BorshSerialize, BorshDeserialize)] -pub struct Policies { - pub is_initialized: bool, - pub num_scalars: u8, - pub scalars: Vec, -} - -impl Policies { - pub fn serialize(&self, mut data: &mut [u8]) -> Result<(), ProgramError> { - BorshSerialize::serialize(self, &mut data).map_err(|_| ProgramError::AccountDataTooSmall) - } - - pub fn deserialize(data: &[u8]) -> Result { - Self::try_from_slice(&data).map_err(|_| ProgramError::InvalidAccountData) - } - - pub fn new(num_scalars: u8) -> Self { - Self { - is_initialized: true, - num_scalars, - scalars: vec![Scalar::zero(); num_scalars as usize], - } - } - - /// Useful for testing - pub fn new_with_scalars(scalars: Vec) -> Self { - let mut policies = Self::new(scalars.len() as u8); - policies.scalars = scalars; - policies - } -} - -#[derive(BorshSerialize, BorshDeserialize)] -pub struct PaymentRequest { - pub encrypted_aggregate: Points, - pub decrypted_aggregate: RistrettoPoint, - pub proof_correct_decryption: RistrettoPoint, - pub valid: bool, -} - -impl PaymentRequest { - fn new( - encrypted_aggregate: Points, - decrypted_aggregate: RistrettoPoint, - proof_correct_decryption: RistrettoPoint, - valid: bool, - ) -> Self { - Self { - encrypted_aggregate, - decrypted_aggregate, - proof_correct_decryption, - valid, - } - } -} - -fn inner_product( - (mut aggregate_x, mut aggregate_y): Points, - ciphertexts: &[(u8, Points)], - scalars: &[Scalar], -) -> Points { - for &(i, (x, y)) in ciphertexts { - aggregate_x = ristretto_mul(&x, &scalars[i as usize]).unwrap() + aggregate_x; - aggregate_y = ristretto_mul(&y, &scalars[i as usize]).unwrap() + aggregate_y; - } - - (aggregate_x, aggregate_y) -} - -#[derive(BorshSerialize, BorshDeserialize)] -pub struct User { - encrypted_aggregate: Points, - public_key: PublicKey, - pub is_initialized: bool, - proof_verification: bool, - payment_requests: Vec, -} - -impl Default for User { - fn default() -> Self { - Self { - encrypted_aggregate: (RistrettoPoint::identity(), RistrettoPoint::identity()), - public_key: PublicKey::from(RistrettoPoint::identity()), - is_initialized: false, - proof_verification: false, - payment_requests: vec![], - } - } -} - -impl User { - pub fn serialize(&self, mut data: &mut [u8]) -> Result<(), ProgramError> { - BorshSerialize::serialize(self, &mut data).map_err(|_| ProgramError::AccountDataTooSmall) - } - - pub fn deserialize(data: &[u8]) -> Result { - Self::try_from_slice(&data).map_err(|_| ProgramError::InvalidAccountData) - } - - pub fn new(public_key: PublicKey) -> Self { - Self { - public_key, - ..Self::default() - } - } - - pub fn fetch_encrypted_aggregate(&self) -> Points { - self.encrypted_aggregate - } - - pub fn fetch_public_key(&self) -> PublicKey { - self.public_key - } - - pub fn fetch_proof_verification(&self) -> bool { - self.proof_verification - } - - pub fn submit_interactions( - &mut self, - interactions: &[(u8, Points)], - policies: &[Scalar], - ) -> bool { - self.encrypted_aggregate = inner_product(self.encrypted_aggregate, interactions, &policies); - true - } - - pub fn submit_proof_decryption( - &mut self, - plaintext: RistrettoPoint, - announcement_g: RistrettoPoint, - announcement_ctx: RistrettoPoint, - response: Scalar, - ) -> bool { - let client_pk = self.fetch_public_key(); - let ciphertext = Ciphertext { - points: self.fetch_encrypted_aggregate(), - pk: client_pk, - }; - self.proof_verification = client_pk.verify_correct_decryption_no_Merlin( - &((announcement_g, announcement_ctx), response), - &ciphertext, - &plaintext, - ); - true - } - - pub fn request_payment( - &mut self, - encrypted_aggregate: Points, - decrypted_aggregate: RistrettoPoint, - proof_correct_decryption: RistrettoPoint, - ) -> bool { - // TODO: implement proof verification - let proof_is_valid = true; - let payment_request = PaymentRequest::new( - encrypted_aggregate, - decrypted_aggregate, - proof_correct_decryption, - proof_is_valid, - ); - self.payment_requests.push(payment_request); - proof_is_valid - } -} - -pub fn generate_keys() -> (SecretKey, PublicKey) { - let mut csprng = thread_rng(); - let sk = SecretKey::new(&mut csprng); - let pk = PublicKey::from(&sk); - (sk, pk) -} - -pub fn recover_scalar(point: RistrettoPoint, k: u32) -> Scalar { - for i in 0..2u64.pow(k) { - let scalar = i.into(); - if RISTRETTO_BASEPOINT_POINT * scalar == point { - return scalar; - } - } - panic!("Encrypted scalar too long"); -} - -#[cfg(test)] -pub(crate) mod tests { - use super::*; - - fn test_policy_contract(policies: &[Scalar], expected_scalar_aggregate: Scalar) { - let (sk, pk) = generate_keys(); - let interactions: Vec<_> = (0..policies.len()) - .map(|i| (i as u8, pk.encrypt(&RISTRETTO_BASEPOINT_POINT).points)) - .collect(); - let mut user = User::new(pk); - - let tx_receipt = user.submit_interactions(&interactions, policies); - assert!(tx_receipt); - - let encrypted_point = user.fetch_encrypted_aggregate(); - let ciphertext = Ciphertext { - points: encrypted_point, - pk, - }; - - let decrypted_aggregate = sk.decrypt(&ciphertext); - let scalar_aggregate = recover_scalar(decrypted_aggregate, 16); - assert_eq!(scalar_aggregate, expected_scalar_aggregate); - - let ((announcement_g, announcement_ctx), response) = - sk.prove_correct_decryption_no_Merlin(&ciphertext, &decrypted_aggregate); - - let tx_receipt_proof = user.submit_proof_decryption( - decrypted_aggregate, - announcement_g, - announcement_ctx, - response, - ); - assert!(tx_receipt_proof); - - let proof_result = user.fetch_proof_verification(); - assert!(proof_result); - } - - #[test] - fn test_policy_contract_2ads() { - let policies = vec![1u64.into(), 2u64.into()]; - test_policy_contract(&policies, 3u64.into()); - } - - #[test] - fn test_policy_contract_128ads() { - let policies = vec![ - 1u64.into(), - 1u64.into(), - 1u64.into(), - 1u64.into(), - 1u64.into(), - 1u64.into(), - 1u64.into(), - 1u64.into(), - 1u64.into(), - 1u64.into(), //10 - 2u64.into(), - 2u64.into(), - 2u64.into(), - 2u64.into(), - 2u64.into(), - 2u64.into(), - 2u64.into(), - 2u64.into(), - 2u64.into(), - 2u64.into(), // 2 * 10 - 1u64.into(), - 1u64.into(), - 1u64.into(), - 1u64.into(), - 1u64.into(), - 1u64.into(), - 1u64.into(), - 1u64.into(), - 1u64.into(), - 1u64.into(), //10 - 2u64.into(), - 2u64.into(), - 2u64.into(), - 2u64.into(), - 2u64.into(), - 2u64.into(), - 2u64.into(), - 2u64.into(), - 2u64.into(), - 2u64.into(), // 2 * 10 - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - 0u64.into(), - ]; - test_policy_contract(&policies, 60u64.into()); - } -}