Cleanup themis (#742)

* Cleanup themis

* Speed up seeding fee-payers
* Add utility functions

* Remove BN variant of themis
This commit is contained in:
Greg Fitzgerald 2020-10-27 16:57:09 -06:00 committed by GitHub
parent cb9a94142a
commit 6906200174
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 65 additions and 1802 deletions

108
Cargo.lock generated
View File

@ -196,19 +196,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
[[package]]
name = "bn"
version = "0.4.4"
source = "git+https://github.com/garious/bn?rev=5c35c737ffabac9921310f53f48725216d59cbf1#5c35c737ffabac9921310f53f48725216d59cbf1"
dependencies = [
"borsh",
"byteorder",
"crunchy",
"lazy_static",
"rand",
"rustc-hex",
]
[[package]]
name = "borsh"
version = "0.7.1"
@ -477,12 +464,6 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
[[package]]
name = "cpuid-bool"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
[[package]]
name = "crc32fast"
version = "1.2.1"
@ -623,19 +604,6 @@ dependencies = [
"subtle 2.2.3",
]
[[package]]
name = "curve25519-dalek"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d85653f070353a16313d0046f173f70d1aadd5b42600a14de626f0dfb3473a5"
dependencies = [
"byteorder",
"digest 0.8.1",
"rand_core",
"subtle 2.2.3",
"zeroize",
]
[[package]]
name = "curve25519-dalek"
version = "2.1.0"
@ -650,6 +618,19 @@ dependencies = [
"zeroize",
]
[[package]]
name = "curve25519-dalek"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d85653f070353a16313d0046f173f70d1aadd5b42600a14de626f0dfb3473a5"
dependencies = [
"byteorder",
"digest 0.8.1",
"rand_core",
"subtle 2.2.3",
"zeroize",
]
[[package]]
name = "derivative"
version = "2.1.1"
@ -746,7 +727,7 @@ dependencies = [
"ed25519",
"rand",
"serde",
"sha2 0.8.2",
"sha2",
"zeroize",
]
@ -756,20 +737,6 @@ version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
[[package]]
name = "elgamal_bn"
version = "0.1.0"
source = "git+https://github.com/garious/elgamal_bn?rev=ba9bdcdb6cdd6fb8e74d0b8bc1b918bcd1b543a9#ba9bdcdb6cdd6fb8e74d0b8bc1b918bcd1b543a9"
dependencies = [
"bn",
"borsh",
"clear_on_drop",
"rand",
"rand_core",
"rustc-hex",
"sha2 0.9.1",
]
[[package]]
name = "elgamal_ristretto"
version = "0.2.4"
@ -781,7 +748,7 @@ dependencies = [
"curve25519-dalek 2.1.0 (git+https://github.com/garious/curve25519-dalek?rev=60efef3553d6bf3d7f3b09b5f97acd54d72529ff)",
"rand_core",
"serde",
"sha2 0.8.2",
"sha2",
"solana-sdk",
"zkp",
]
@ -1393,7 +1360,7 @@ dependencies = [
"digest 0.8.1",
"hmac-drbg",
"rand",
"sha2 0.8.2",
"sha2",
"subtle 2.2.3",
"typenum",
]
@ -2105,12 +2072,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc-hex"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
[[package]]
name = "rustc_version"
version = "0.2.3"
@ -2329,19 +2290,6 @@ dependencies = [
"opaque-debug 0.2.3",
]
[[package]]
name = "sha2"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2933378ddfeda7ea26f48c555bdad8bb446bf8a3d17832dc83e380d444cfb8c1"
dependencies = [
"block-buffer 0.9.0",
"cfg-if 0.1.10",
"cpuid-bool",
"digest 0.9.0",
"opaque-debug 0.3.0",
]
[[package]]
name = "sha3"
version = "0.9.1"
@ -2552,7 +2500,7 @@ dependencies = [
"rustc_version",
"serde",
"serde_derive",
"sha2 0.8.2",
"sha2",
"solana-frozen-abi-macro",
"solana-logger",
"thiserror",
@ -2654,7 +2602,7 @@ dependencies = [
"serde",
"serde_bytes",
"serde_derive",
"sha2 0.8.2",
"sha2",
"solana-frozen-abi",
"solana-frozen-abi-macro",
"solana-logger",
@ -2773,7 +2721,7 @@ dependencies = [
"serde_bytes",
"serde_derive",
"serde_json",
"sha2 0.8.2",
"sha2",
"sha3",
"solana-crate-features",
"solana-frozen-abi",
@ -2941,22 +2889,6 @@ dependencies = [
"thiserror",
]
[[package]]
name = "spl-themis-bn"
version = "0.1.0"
dependencies = [
"bincode",
"bn",
"borsh",
"elgamal_bn",
"getrandom",
"num-derive",
"num-traits",
"rand",
"solana-program",
"thiserror",
]
[[package]]
name = "spl-themis-ristretto"
version = "0.1.0"
@ -3238,7 +3170,7 @@ dependencies = [
"pbkdf2",
"rand",
"rustc-hash",
"sha2 0.8.2",
"sha2",
"unicode-normalization",
]

View File

@ -10,12 +10,10 @@ members = [
"token/cli",
"token/program",
"token/program-v3",
"themis/program_bn",
"themis/program_ristretto",
]
exclude = [
"shared-memory/client",
"token/perf-monitor",
"themis/client_bn",
"themis/client_ristretto",
]

View File

@ -56,7 +56,6 @@ done
# Run client tests
_ cargo test --manifest-path=shared-memory/client/Cargo.toml -- --nocapture
_ cargo test --manifest-path=token/perf-monitor/Cargo.toml -- --nocapture
_ cargo test --manifest-path=themis/client_bn/Cargo.toml -- --nocapture
_ cargo test --manifest-path=themis/client_ristretto/Cargo.toml -- --nocapture

View File

@ -12,7 +12,6 @@ fi
workspace_crates=(
Cargo.toml
shared-memory/client/Cargo.toml
themis/client_bn/Cargo.toml
themis/client_ristretto/Cargo.toml
token/perf-monitor/Cargo.toml
)

View File

@ -1,34 +0,0 @@
[package]
name = "spl-themis-bn-client"
version = "0.1.0"
description = "SPL THEMIS client"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana-program-library"
license = "Apache-2.0"
edition = "2018"
exclude = ["js/**"]
[dependencies]
bincode = "1.3"
borsh = "0.7.1"
bn = {git = "https://github.com/garious/bn", rev = "5c35c737ffabac9921310f53f48725216d59cbf1", default-features = false, features = ["borsh"]}
elgamal_bn = { git = "https://github.com/garious/elgamal_bn", rev = "ba9bdcdb6cdd6fb8e74d0b8bc1b918bcd1b543a9" }
futures = "0.3"
solana-banks-client = "1.4.3"
solana-cli-config = "1.4.3"
solana-sdk = "1.4.3"
spl-themis-bn = { version = "0.1.0", path = "../program_bn", features = ["exclude_entrypoint"]}
tarpc = { version = "0.21.1", features = ["full"] }
tokio = "0.2"
url = "2.1"
[dev-dependencies]
separator = "0.4.1"
solana-banks-server = "1.4.3"
solana-bpf-loader-program = "1.4.3"
solana_rbpf = "=0.1.32"
solana-runtime = "1.4.3"
[lib]
crate-type = ["cdylib", "lib"]

View File

@ -1,10 +0,0 @@
use std::process::Command;
fn main() {
println!("cargo:warning=(not a warning) Building BPF themis program");
Command::new("cargo")
.arg("build-bpf")
.status()
.expect("Failed to build BPF themis program")
.success();
}

View File

@ -1,37 +0,0 @@
//! Themis client
use bn::Fr;
use solana_banks_client::start_tcp_client;
use solana_cli_config::{Config, CONFIG_FILE};
use solana_sdk::signature::read_keypair_file;
use spl_themis_bn_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![Fr::new(1u64.into()).unwrap(), Fr::new(2u64.into()).unwrap()];
let sender_keypair = read_keypair_file(&config.keypair_path).unwrap();
test_e2e(
&mut banks_client,
sender_keypair,
policies,
1_000,
Fr::new(3u64.into()).unwrap(),
)
.await
.unwrap();
});
}

View File

@ -1,357 +0,0 @@
//! Themis client
use bn::{Fr, Group, G1};
use elgamal_bn::{/*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,
};
use spl_themis_bn::{
instruction,
state::generate_keys, // recover_scalar, User},
};
use std::{io, time::Instant};
//use tarpc::context;
/// For a single user, create interactions, calculate the aggregate, submit a proof, and verify it.
async fn run_user_workflow(
mut client: BanksClient,
sender_keypair: Keypair,
(_sk, pk): (SecretKey, PublicKey),
interactions: Vec<(G1, G1)>,
policies_pubkey: Pubkey,
_expected_scalar_aggregate: Fr,
) -> io::Result<u64> {
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(&sender_pubkey, &user_pubkey, sol_to_lamports(0.001), pk);
let msg = Message::new(&ixs, Some(&sender_keypair.pubkey()));
let recent_blockhash = client.get_recent_blockhash().await?;
let tx = Transaction::new(&[&sender_keypair, &user_keypair], msg, recent_blockhash);
let tx_size = bincode::serialize(&tx).unwrap().len();
assert!(
tx_size <= 1200,
"transaction over 1200 bytes: {} bytes",
tx_size
);
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(&user_pubkey, &policies_pubkey, interactions);
let msg = Message::new(&[ix], Some(&sender_keypair.pubkey()));
let recent_blockhash = client.get_recent_blockhash().await?;
let tx = Transaction::new(&[&sender_keypair, &user_keypair], msg, recent_blockhash);
let tx_size = bincode::serialize(&tx).unwrap().len();
assert!(
tx_size <= 1200,
"transaction over 1200 bytes: {} bytes",
tx_size
);
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 = G1::one();
//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) =
((G1::one(), G1::one()), Fr::new(0.into()).unwrap());
//sk.prove_correct_decryption_no_Merlin(&ciphertext, &decrypted_aggregate).unwrap();
let ix = instruction::submit_proof_decryption(
&user_pubkey,
decrypted_aggregate,
announcement_g,
announcement_ctx,
response,
);
let msg = Message::new(&[ix], Some(&sender_keypair.pubkey()));
let recent_blockhash = client.get_recent_blockhash().await?;
let tx = Transaction::new(&[&sender_keypair, &user_keypair], msg, recent_blockhash);
let tx_size = bincode::serialize(&tx).unwrap().len();
assert!(
tx_size <= 1200,
"transaction over 1200 bytes: {} bytes",
tx_size
);
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,
sender_keypair: Keypair,
policies: Vec<Fr>,
num_users: u64,
expected_scalar_aggregate: Fr,
) -> 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(
&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(
&policies_pubkey,
policies_slice,
));
let msg = Message::new(&ixs, Some(&sender_keypair.pubkey()));
let recent_blockhash = client.get_recent_blockhash().await?;
let tx = Transaction::new(&[&sender_keypair, &policies_keypair], msg, recent_blockhash);
let tx_size = bincode::serialize(&tx).unwrap().len();
assert!(
tx_size <= 1200,
"transaction over 1200 bytes: {} bytes",
tx_size
);
client
.process_transaction_with_commitment(tx, CommitmentLevel::Recent)
.await
.unwrap();
// Send feepayer_keypairs some SOL
let feepayers: Vec<_> = (0..num_users).map(|_| Keypair::new()).collect();
for feepayers in feepayers.chunks(20) {
println!("Seeding feepayer accounts...");
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 recent_blockhash = client.get_recent_blockhash().await.unwrap();
let tx = Transaction::new(&[&sender_keypair], msg, recent_blockhash);
let tx_size = bincode::serialize(&tx).unwrap().len();
assert!(
tx_size <= 1200,
"transaction over 1200 bytes: {} bytes",
tx_size
);
client
.process_transaction_with_commitment(tx, 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(&G1::one()).points)
.collect();
let futures: Vec<_> = feepayers
.into_iter()
.map(move |feepayer_keypair| {
run_user_workflow(
client.clone(),
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::<u64>();
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, KeyedAccount},
account_info::AccountInfo,
genesis_config::create_genesis_config,
instruction::InstructionError,
program_error::ProgramError,
};
use spl_themis_bn::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],
) -> Result<(), InstructionError> {
// Copy all the accounts into a HashMap to ensure there are no duplicates
let mut accounts: HashMap<Pubkey, Account> = 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<AccountInfo> = 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);
bank.add_builtin_program("Themis", spl_themis_bn::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![Fr::new(1u64.into()).unwrap(), Fr::new(2u64.into()).unwrap()];
test_e2e(
&mut banks_client,
sender_keypair,
policies,
10,
Fr::new(3u64.into()).unwrap(),
)
.await
.unwrap();
});
}
}

View File

@ -1,390 +0,0 @@
use bn::{Fr, Group, G1};
use borsh::BorshSerialize;
use elgamal_bn::ciphertext::Ciphertext;
use separator::Separatable;
use solana_bpf_loader_program::{
create_vm,
serialization::{deserialize_parameters, serialize_parameters},
};
use solana_rbpf::vm::{EbpfVm, InstructionMeter};
use solana_runtime::process_instruction::{
ComputeBudget, ComputeMeter, Executor, InvokeContext, Logger, ProcessInstruction,
};
use solana_sdk::{
account::{Account as SolanaAccount, KeyedAccount},
bpf_loader,
entrypoint::SUCCESS,
instruction::{CompiledInstruction, InstructionError},
message::Message,
pubkey::Pubkey,
};
use spl_themis_bn::{
instruction::ThemisInstruction,
state::{generate_keys, /*recover_scalar,*/ Policies, User},
};
use std::{cell::RefCell, fs::File, io::Read, path::PathBuf, rc::Rc, sync::Arc};
fn load_program(name: &str) -> Vec<u8> {
let mut path = PathBuf::new();
path.push("../../target/bpfel-unknown-unknown/release");
path.push(name);
path.set_extension("so");
let mut file = File::open(path).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],
) -> Result<u64, InstructionError> {
let mut program_account = SolanaAccount::default();
program_account.data = load_program("spl_themis_bn");
let loader_id = bpf_loader::id();
let mut invoke_context = MockInvokeContext::default();
let executable = EbpfVm::<solana_bpf_loader_program::BPFError>::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, &parameter_bytes).unwrap();
Ok(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![Fr::new(1u64.into()).unwrap(), Fr::new(2u64.into()).unwrap()];
//let scalars = vec![
// Fr::new(1u64.into()).unwrap(),
// Fr::new(1u64.into()).unwrap(),
// Fr::new(1u64.into()).unwrap(),
// Fr::new(1u64.into()).unwrap(),
// Fr::new(1u64.into()).unwrap(),
// Fr::new(1u64.into()).unwrap(),
// Fr::new(1u64.into()).unwrap(),
// Fr::new(1u64.into()).unwrap(),
// Fr::new(1u64.into()).unwrap(),
// Fr::new(1u64.into()).unwrap(), //10
// Fr::new(2u64.into()).unwrap(),
// Fr::new(2u64.into()).unwrap(),
// Fr::new(2u64.into()).unwrap(),
// Fr::new(2u64.into()).unwrap(),
// Fr::new(2u64.into()).unwrap(),
// Fr::new(2u64.into()).unwrap(),
// Fr::new(2u64.into()).unwrap(),
// Fr::new(2u64.into()).unwrap(),
// Fr::new(2u64.into()).unwrap(),
// Fr::new(2u64.into()).unwrap(), // 2 * 10
// Fr::new(1u64.into()).unwrap(),
// Fr::new(1u64.into()).unwrap(),
// Fr::new(1u64.into()).unwrap(),
// Fr::new(1u64.into()).unwrap(),
// Fr::new(1u64.into()).unwrap(),
// Fr::new(1u64.into()).unwrap(),
// Fr::new(1u64.into()).unwrap(),
// Fr::new(1u64.into()).unwrap(),
// Fr::new(1u64.into()).unwrap(),
// Fr::new(1u64.into()).unwrap(), //10
// Fr::new(2u64.into()).unwrap(),
// Fr::new(2u64.into()).unwrap(),
// Fr::new(2u64.into()).unwrap(),
// Fr::new(2u64.into()).unwrap(),
// Fr::new(2u64.into()).unwrap(),
// Fr::new(2u64.into()).unwrap(),
// Fr::new(2u64.into()).unwrap(),
// Fr::new(2u64.into()).unwrap(),
// Fr::new(2u64.into()).unwrap(),
// Fr::new(2u64.into()).unwrap(), // 2 * 10
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
// Fr::new(0u64.into()).unwrap(),
//];
let num_scalars = scalars.len();
let (sk, pk) = generate_keys();
let encrypted_interactions: Vec<_> = (0..num_scalars)
.map(|i| (i as u8, pk.encrypt(&G1::one()).points))
.collect();
let policies_account = SolanaAccount::new_ref(
0,
Policies {
is_initialized: true,
num_scalars: num_scalars as u8,
scalars: scalars.clone(),
}
.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, &parameter_accounts[..], &instruction_data).unwrap();
// Create user account
let user_key = Pubkey::new_unique();
let user_account =
SolanaAccount::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, &parameter_accounts[..], &instruction_data).unwrap();
// 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, &parameter_accounts[..], &instruction_data).unwrap();
// 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 = Fr::new(3u64.into()).unwrap();
//assert_eq!(scalar_aggregate, expected_scalar_aggregate);
let (announcement, response) = sk
.prove_correct_decryption_no_Merlin(&ciphertext, &decrypted_aggregate)
.unwrap();
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, &parameter_accounts[..], &instruction_data).unwrap();
const BASELINE_NEW_POLICIES_COUNT: u64 = 80_000; // last known 75,796 @ 128, 4,675 @ 2
const BASELINE_INITIALIZE_USER_COUNT: u64 = 22_000; // last known 19,868
const BASELINE_CALCULATE_AGGREGATE_COUNT: u64 = 15_000_000; // last known 13,061,884
const BASELINE_PROOF_DECRYPTION_COUNT: u64 = 60_000_000; // last known 13,167,140
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);
}
// Mock InvokeContext
#[derive(Debug)]
struct MockInvokeContext {
pub key: Pubkey,
pub logger: MockLogger,
pub compute_meter: MockComputeMeter,
compute_budget: ComputeBudget,
}
impl Default for MockInvokeContext {
fn default() -> Self {
Self {
key: Pubkey::default(),
logger: MockLogger::default(),
compute_meter: MockComputeMeter::default(),
compute_budget: ComputeBudget {
max_invoke_depth: 10,
..ComputeBudget::default()
},
}
}
}
impl InvokeContext for MockInvokeContext {
fn push(&mut self, _key: &Pubkey) -> Result<(), InstructionError> {
Ok(())
}
fn pop(&mut self) {}
fn verify_and_update(
&mut self,
_message: &Message,
_instruction: &CompiledInstruction,
_accounts: &[Rc<RefCell<SolanaAccount>>],
) -> Result<(), InstructionError> {
Ok(())
}
fn get_caller(&self) -> Result<&Pubkey, InstructionError> {
Ok(&self.key)
}
fn get_programs(&self) -> &[(Pubkey, ProcessInstruction)] {
&[]
}
fn get_logger(&self) -> Rc<RefCell<dyn Logger>> {
Rc::new(RefCell::new(self.logger.clone()))
}
fn get_compute_budget(&self) -> &ComputeBudget {
&self.compute_budget
}
fn get_compute_meter(&self) -> Rc<RefCell<dyn ComputeMeter>> {
Rc::new(RefCell::new(self.compute_meter.clone()))
}
fn add_executor(&mut self, _pubkey: &Pubkey, _executor: Arc<dyn Executor>) {}
fn get_executor(&mut self, _pubkey: &Pubkey) -> Option<Arc<dyn Executor>> {
None
}
fn record_instruction(&self, _: &solana_sdk::instruction::Instruction) {
todo!()
}
fn is_feature_active(&self, _: &solana_sdk::pubkey::Pubkey) -> bool {
true
}
}
#[derive(Debug, Default, Clone)]
struct MockComputeMeter {}
impl ComputeMeter for MockComputeMeter {
fn consume(&mut self, _amount: u64) -> Result<(), InstructionError> {
Ok(())
}
fn get_remaining(&self) -> u64 {
u64::MAX
}
}
#[derive(Debug, Default, Clone)]
struct MockLogger {}
impl Logger for MockLogger {
fn log_enabled(&self) -> bool {
true
}
fn log(&mut self, message: &str) {
println!("{}", message);
}
}
struct TestInstructionMeter {}
impl InstructionMeter for TestInstructionMeter {
fn consume(&mut self, _amount: u64) {}
fn get_remaining(&self) -> u64 {
u64::MAX
}
}

View File

@ -13,6 +13,7 @@ use solana_sdk::{
signature::{Keypair, Signer},
system_instruction,
transaction::Transaction,
transport,
};
use spl_themis_ristretto::{
instruction,
@ -21,6 +22,32 @@ use spl_themis_ristretto::{
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<Transaction>,
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<Result<_, _>> to Result<Vec<_>>
}
/// For a single user, create interactions, calculate the aggregate, submit a proof, and verify it.
async fn run_user_workflow(
mut client: BanksClient,
@ -44,15 +71,10 @@ async fn run_user_workflow(
sol_to_lamports(0.001),
pk,
);
let msg = Message::new(&ixs, Some(&sender_keypair.pubkey()));
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);
let tx_size = bincode::serialize(&tx).unwrap().len();
assert!(
tx_size <= 1200,
"transaction over 1200 bytes: {} bytes",
tx_size
);
assert_transaction_size(&tx);
client
.process_transaction_with_commitment(tx, CommitmentLevel::Recent)
.await
@ -68,15 +90,10 @@ async fn run_user_workflow(
&policies_pubkey,
interactions,
);
let msg = Message::new(&[ix], Some(&sender_keypair.pubkey()));
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);
let tx_size = bincode::serialize(&tx).unwrap().len();
assert!(
tx_size <= 1200,
"transaction over 1200 bytes: {} bytes",
tx_size
);
assert_transaction_size(&tx);
client
.process_transaction_with_commitment(tx, CommitmentLevel::Recent)
.await
@ -110,7 +127,6 @@ async fn run_user_workflow(
(RISTRETTO_BASEPOINT_POINT, RISTRETTO_BASEPOINT_POINT),
0u64.into(),
);
//sk.prove_correct_decryption_no_Merlin(&ciphertext, &decrypted_aggregate).unwrap();
let ix = instruction::submit_proof_decryption(
program_id,
@ -120,15 +136,10 @@ async fn run_user_workflow(
announcement_ctx,
response,
);
let msg = Message::new(&[ix], Some(&sender_keypair.pubkey()));
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);
let tx_size = bincode::serialize(&tx).unwrap().len();
assert!(
tx_size <= 1200,
"transaction over 1200 bytes: {} bytes",
tx_size
);
assert_transaction_size(&tx);
client
.process_transaction_with_commitment(tx, CommitmentLevel::Recent)
.await
@ -174,43 +185,33 @@ pub async fn test_e2e(
policies_slice,
));
let msg = Message::new(&ixs, Some(&sender_keypair.pubkey()));
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);
let tx_size = bincode::serialize(&tx).unwrap().len();
assert!(
tx_size <= 1200,
"transaction over 1200 bytes: {} bytes",
tx_size
);
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();
for feepayers in feepayers.chunks(20) {
println!("Seeding feepayer accounts...");
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 recent_blockhash = client.get_recent_blockhash().await.unwrap();
let tx = Transaction::new(&[&sender_keypair], msg, recent_blockhash);
let tx_size = bincode::serialize(&tx).unwrap().len();
assert!(
tx_size <= 1200,
"transaction over 1200 bytes: {} bytes",
tx_size
);
client
.process_transaction_with_commitment(tx, CommitmentLevel::Recent)
.await
.unwrap();
}
assert_transaction_size(&tx);
tx
}).collect();
process_transactions_with_commitment(client, txs, CommitmentLevel::Recent)
.await
.unwrap();
println!("Starting benchmark...");
let now = Instant::now();

View File

@ -12,7 +12,7 @@ use solana_sdk::{
transaction::Transaction,
transport,
};
use spl_themis_ristretto_client::test_e2e;
use spl_themis_ristretto_client::{test_e2e, process_transactions_with_commitment};
use std::{
fs::{remove_dir_all, File},
io::Read,
@ -34,23 +34,6 @@ fn load_program(name: &str) -> Vec<u8> {
program
}
// TODO: Add this to BanksClient
async fn process_transactions_with_commitment(
client: &mut BanksClient,
transactions: Vec<Transaction>,
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<Result<_, _>> to Result<Vec<_>>
}
async fn create_program_account_with_commitment(
client: &mut BanksClient,
loader_id: &Pubkey,

View File

@ -1,26 +0,0 @@
[package]
name = "spl-themis-bn"
version = "0.1.0"
description = "Solana Program Library THEMIS"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana-program-library"
license = "Apache-2.0"
edition = "2018"
[features]
exclude_entrypoint = []
[dependencies]
bincode = "1.3"
borsh = "0.7.1"
bn = {git = "https://github.com/garious/bn", rev = "5c35c737ffabac9921310f53f48725216d59cbf1", default-features = false, features = ["borsh"]}
elgamal_bn = { git = "https://github.com/garious/elgamal_bn", rev = "ba9bdcdb6cdd6fb8e74d0b8bc1b918bcd1b543a9" }
getrandom = { version = "0.1.15", features = ["dummy"] }
num-derive = "0.3"
num-traits = "0.2"
rand = "0.7.0"
solana-program = "1.4.3"
thiserror = "1.0"
[lib]
crate-type = ["cdylib", "lib"]

View File

@ -1,2 +0,0 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -1 +0,0 @@
F3FWeYPjD1jeR6UykMj1GRbCcmoxtJnDiPuFdTLRGvb6

View File

@ -1,15 +0,0 @@
//! Program entrypoint
use solana_program::{
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey,
};
entrypoint!(process_instruction);
fn process_instruction<'a>(
program_id: &Pubkey,
accounts: &'a [AccountInfo<'a>],
instruction_data: &[u8],
) -> ProgramResult {
crate::processor::process_instruction(program_id, accounts, instruction_data)?;
Ok(())
}

View File

@ -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<ThemisError> for ProgramError {
fn from(e: ThemisError) -> Self {
ProgramError::Custom(e as u32)
}
}
impl<T> DecodeError<T> for ThemisError {
fn type_of() -> &'static str {
"ThemisError"
}
}
impl PrintProgramError for ThemisError {
fn print<E>(&self)
where
E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
{
match self {
ThemisError::InvalidInstruction => println!("Error: Invalid instruction"),
ThemisError::AccountInUse => println!("Error: Account in use"),
}
}
}

View File

@ -1,235 +0,0 @@
//! Instruction types
use crate::state::{Policies, User};
use bn::{Fr, G1};
use borsh::{BorshDeserialize, BorshSerialize};
use elgamal_bn::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, Fr)>,
},
/// 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, (G1, G1))>,
},
/// Submit proof decryption
///
/// Accounts expected by this instruction:
///
/// 0. `[writable, signer]` The user account
SubmitProofDecryption {
/// plaintext
plaintext: G1,
/// (announcement_g, announcement_ctx)
announcement: Box<(G1, G1)>,
/// response
response: Fr,
},
/// Request a payment
///
/// Accounts expected by this instruction:
///
/// 0. `[writable, signer]` The user account
RequestPayment {
/// Encrypted aggregate
encrypted_aggregate: Box<(G1, G1)>,
/// Decrypted aggregate
decrypted_aggregate: G1,
/// Proof correct decryption
proof_correct_decryption: G1,
},
}
impl ThemisInstruction {
pub fn serialize(&self) -> Result<Vec<u8>, ProgramError> {
self.try_to_vec()
.map_err(|_| ProgramError::AccountDataTooSmall)
}
pub(crate) fn deserialize(data: &[u8]) -> Result<Self, ProgramError> {
Self::try_from_slice(&data).map_err(|_| ProgramError::InvalidInstructionData)
}
}
/// Return an `InitializeUserAccount` instruction.
fn initialize_user_account(user_pubkey: &Pubkey, public_key: PublicKey) -> Instruction {
let data = ThemisInstruction::InitializeUserAccount { public_key };
let accounts = vec![AccountMeta::new(*user_pubkey, false)];
Instruction {
program_id: crate::id(),
accounts,
data: data.serialize().unwrap(),
}
}
/// Return two instructions that create and initialize a user account.
pub fn create_user_account(
from: &Pubkey,
user_pubkey: &Pubkey,
lamports: u64,
public_key: PublicKey,
) -> Vec<Instruction> {
let space = User::default().try_to_vec().unwrap().len() as u64;
vec![
system_instruction::create_account(from, user_pubkey, lamports, space, &crate::id()),
initialize_user_account(user_pubkey, public_key),
]
}
/// Return an `InitializePoliciesAccount` instruction.
fn initialize_policies_account(policies_pubkey: &Pubkey, num_scalars: u8) -> Instruction {
let data = ThemisInstruction::InitializePoliciesAccount { num_scalars };
let accounts = vec![AccountMeta::new(*policies_pubkey, false)];
Instruction {
program_id: crate::id(),
accounts,
data: data.serialize().unwrap(),
}
}
/// Return two instructions that create and initialize a policies account.
pub fn create_policies_account(
from: &Pubkey,
policies_pubkey: &Pubkey,
lamports: u64,
num_scalars: u8,
) -> Vec<Instruction> {
let space = Policies::new(num_scalars).try_to_vec().unwrap().len() as u64;
vec![
system_instruction::create_account(from, policies_pubkey, lamports, space, &crate::id()),
initialize_policies_account(policies_pubkey, num_scalars),
]
}
/// Return an `InitializePoliciesAccount` instruction.
pub fn store_policies(policies_pubkey: &Pubkey, scalars: Vec<(u8, Fr)>) -> Instruction {
let data = ThemisInstruction::StorePolicies { scalars };
let accounts = vec![AccountMeta::new(*policies_pubkey, true)];
Instruction {
program_id: crate::id(),
accounts,
data: data.serialize().unwrap(),
}
}
/// Return a `SubmitInteractions` instruction.
pub fn submit_interactions(
user_pubkey: &Pubkey,
policies_pubkey: &Pubkey,
encrypted_interactions: Vec<(u8, (G1, G1))>,
) -> Instruction {
let data = ThemisInstruction::SubmitInteractions {
encrypted_interactions,
};
let accounts = vec![
AccountMeta::new(*user_pubkey, true),
AccountMeta::new_readonly(*policies_pubkey, false),
];
Instruction {
program_id: crate::id(),
accounts,
data: data.serialize().unwrap(),
}
}
/// Return a `SubmitProofDecryption` instruction.
pub fn submit_proof_decryption(
user_pubkey: &Pubkey,
plaintext: G1,
announcement_g: G1,
announcement_ctx: G1,
response: Fr,
) -> 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: crate::id(),
accounts,
data: data.serialize().unwrap(),
}
}
/// Return a `RequestPayment` instruction.
pub fn request_payment(
user_pubkey: &Pubkey,
encrypted_aggregate: (G1, G1),
decrypted_aggregate: G1,
proof_correct_decryption: G1,
) -> 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: crate::id(),
accounts,
data: data.serialize().unwrap(),
}
}

View File

@ -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 = "exclude_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!("F3FWeYPjD1jeR6UykMj1GRbCcmoxtJnDiPuFdTLRGvb6");

View File

@ -1,142 +0,0 @@
//! Themis program
use crate::{
error::ThemisError,
instruction::ThemisInstruction,
state::{Policies, User},
};
use bn::{Fr, G1};
use elgamal_bn::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, Fr)>,
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, (G1, G1))],
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: G1,
announcement: (G1, G1),
response: Fr,
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: (G1, G1),
decrypted_aggregate: G1,
proof_correct_decryption: G1,
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<'a>(
_program_id: &Pubkey,
account_infos: &'a [AccountInfo<'a>],
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,
)
}
}
}

View File

@ -1,344 +0,0 @@
#![allow(missing_docs)]
use bn::{Fr, Group, G1};
use borsh::{BorshDeserialize, BorshSerialize};
use elgamal_bn::{ciphertext::Ciphertext, private::SecretKey, public::PublicKey};
use rand::thread_rng;
use solana_program::program_error::ProgramError;
type Points = (G1, G1);
#[derive(Default, BorshSerialize, BorshDeserialize)]
pub struct Policies {
pub is_initialized: bool,
pub num_scalars: u8,
pub scalars: Vec<Fr>,
}
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, ProgramError> {
Self::try_from_slice(&data).map_err(|_| ProgramError::InvalidAccountData)
}
pub fn new(num_scalars: u8) -> Self {
Self {
is_initialized: true,
num_scalars,
scalars: vec![Fr::zero(); num_scalars as usize],
}
}
/// Useful for testing
pub fn new_with_scalars(scalars: Vec<Fr>) -> 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: G1,
pub proof_correct_decryption: G1,
pub valid: bool,
}
impl PaymentRequest {
fn new(
encrypted_aggregate: Points,
decrypted_aggregate: G1,
proof_correct_decryption: G1,
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: &[Fr],
) -> Points {
for &(i, (x, y)) in ciphertexts {
aggregate_x = x * scalars[i as usize] + aggregate_x;
aggregate_y = y * scalars[i as usize] + 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<PaymentRequest>,
}
impl Default for User {
fn default() -> Self {
Self {
encrypted_aggregate: (G1::zero(), G1::zero()),
public_key: PublicKey::from(G1::zero()),
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, ProgramError> {
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: &[Fr]) -> bool {
self.encrypted_aggregate = inner_product(self.encrypted_aggregate, interactions, &policies);
true
}
pub fn submit_proof_decryption(
&mut self,
plaintext: G1,
announcement_g: G1,
announcement_ctx: G1,
response: Fr,
) -> 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,
)
.is_ok();
true
}
pub fn request_payment(
&mut self,
encrypted_aggregate: Points,
decrypted_aggregate: G1,
proof_correct_decryption: G1,
) -> 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: G1, k: u32) -> Fr {
for i in 0..2u64.pow(k) {
let scalar = Fr::new(i.into()).unwrap();
if G1::one() * scalar == point {
return scalar;
}
}
panic!("Encrypted scalar too long");
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
fn test_policy_contract(policies: &[Fr], expected_scalar_aggregate: Fr) {
let (sk, pk) = generate_keys();
let interactions: Vec<_> = (0..policies.len())
.map(|i| (i as u8, pk.encrypt(&G1::one()).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)
.unwrap();
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![Fr::new(1u64.into()).unwrap(), Fr::new(2u64.into()).unwrap()];
test_policy_contract(&policies, Fr::new(3u64.into()).unwrap());
}
#[test]
fn test_policy_contract_128ads() {
let policies = vec![
Fr::new(1u64.into()).unwrap(),
Fr::new(1u64.into()).unwrap(),
Fr::new(1u64.into()).unwrap(),
Fr::new(1u64.into()).unwrap(),
Fr::new(1u64.into()).unwrap(),
Fr::new(1u64.into()).unwrap(),
Fr::new(1u64.into()).unwrap(),
Fr::new(1u64.into()).unwrap(),
Fr::new(1u64.into()).unwrap(),
Fr::new(1u64.into()).unwrap(), //10
Fr::new(2u64.into()).unwrap(),
Fr::new(2u64.into()).unwrap(),
Fr::new(2u64.into()).unwrap(),
Fr::new(2u64.into()).unwrap(),
Fr::new(2u64.into()).unwrap(),
Fr::new(2u64.into()).unwrap(),
Fr::new(2u64.into()).unwrap(),
Fr::new(2u64.into()).unwrap(),
Fr::new(2u64.into()).unwrap(),
Fr::new(2u64.into()).unwrap(), // 2 * 10
Fr::new(1u64.into()).unwrap(),
Fr::new(1u64.into()).unwrap(),
Fr::new(1u64.into()).unwrap(),
Fr::new(1u64.into()).unwrap(),
Fr::new(1u64.into()).unwrap(),
Fr::new(1u64.into()).unwrap(),
Fr::new(1u64.into()).unwrap(),
Fr::new(1u64.into()).unwrap(),
Fr::new(1u64.into()).unwrap(),
Fr::new(1u64.into()).unwrap(), //10
Fr::new(2u64.into()).unwrap(),
Fr::new(2u64.into()).unwrap(),
Fr::new(2u64.into()).unwrap(),
Fr::new(2u64.into()).unwrap(),
Fr::new(2u64.into()).unwrap(),
Fr::new(2u64.into()).unwrap(),
Fr::new(2u64.into()).unwrap(),
Fr::new(2u64.into()).unwrap(),
Fr::new(2u64.into()).unwrap(),
Fr::new(2u64.into()).unwrap(), // 2 * 10
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
Fr::new(0u64.into()).unwrap(),
];
test_policy_contract(&policies, Fr::new(60u64.into()).unwrap());
}
}