Cleanup themis (#742)
* Cleanup themis * Speed up seeding fee-payers * Add utility functions * Remove BN variant of themis
This commit is contained in:
parent
cb9a94142a
commit
6906200174
|
@ -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",
|
||||
]
|
||||
|
||||
|
|
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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"]
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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, ¶meter_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, ¶meter_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, ¶meter_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, ¶meter_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, ¶meter_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
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"]
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -1 +0,0 @@
|
|||
F3FWeYPjD1jeR6UykMj1GRbCcmoxtJnDiPuFdTLRGvb6
|
|
@ -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(())
|
||||
}
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
}
|
||||
}
|
|
@ -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");
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue