[zk-token-sdk] Add option to create proof context state in the proof verification program (#29996)

* extend verifiable trait

* add PodBool

* implement ZkProofData trait

* add proof context program to zk-token-proof program

* update tests  for close account

* add close account instruction

* reorganize tests

* complete tests

* clean up and add docs

* clean up pod

* add proof program state

* update tests

* move proof program tests as separate module

* clippy

* cargo sort

* cargo fmt

* re-organize visibility

* add context state description

* update maintainer reference

* change `VerifyProofData` and `ProofContextState` to pod

* add tests for mixing proof types

* add tests for self owned context state accounts

* cargo fmt

* remove unnecessary scoping and add comments on scopes

* re-organize proof instructions

* clippy

* update zk-token-proof-test to 1.16.0

* upgrade spl-token-2022 to 0.6.1

* reoganize proof type

* cargo lock

* remove ZkProofContext trait
This commit is contained in:
samkim-crypto 2023-03-16 07:35:20 +09:00 committed by GitHub
parent a4ad0c75fc
commit 2d58bb287d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1531 additions and 270 deletions

15
Cargo.lock generated
View File

@ -7082,6 +7082,17 @@ dependencies = [
"solana-zk-token-sdk 1.16.0",
]
[[package]]
name = "solana-zk-token-proof-program-tests"
version = "1.16.0"
dependencies = [
"bytemuck",
"solana-program-runtime",
"solana-program-test",
"solana-sdk 1.16.0",
"solana-zk-token-sdk 1.16.0",
]
[[package]]
name = "solana-zk-token-sdk"
version = "1.15.1"
@ -7236,9 +7247,9 @@ dependencies = [
[[package]]
name = "spl-token-2022"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67fcd758e8d22c5fce17315015f5ff319604d1a6e57a73c72795639dba898890"
checksum = "0043b590232c400bad5ee9eb983ced003d15163c4c5d56b090ac6d9a57457b47"
dependencies = [
"arrayref",
"bytemuck",

View File

@ -62,6 +62,7 @@ members = [
"programs/stake",
"programs/vote",
"programs/zk-token-proof",
"programs/zk-token-proof-tests",
"pubsub-client",
"quic-client",
"rayon-threadlimit",
@ -360,7 +361,7 @@ spl-associated-token-account = "=1.1.3"
spl-instruction-padding = "0.1"
spl-memo = "=3.0.1"
spl-token = "=3.5.0"
spl-token-2022 = "=0.6.0"
spl-token-2022 = "=0.6.1"
static_assertions = "1.1.0"
stream-cancel = "0.8.1"
strum = "0.24"

View File

@ -6406,9 +6406,9 @@ dependencies = [
[[package]]
name = "spl-token-2022"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67fcd758e8d22c5fce17315015f5ff319604d1a6e57a73c72795639dba898890"
checksum = "0043b590232c400bad5ee9eb983ced003d15163c4c5d56b090ac6d9a57457b47"
dependencies = [
"arrayref",
"bytemuck",

View File

@ -0,0 +1,15 @@
[package]
name = "solana-zk-token-proof-program-tests"
authors = ["Solana Labs Maintainers <maintainers@solanalabs.com>"]
repository = "https://github.com/solana-labs/solana"
version = "1.16.0"
license = "Apache-2.0"
edition = "2021"
publish = false
[dev-dependencies]
bytemuck = { version = "1.11.0", features = ["derive"] }
solana-program-runtime = { path = "../../program-runtime", version = "=1.16.0" }
solana-program-test = { path = "../../program-test", version = "=1.16.0" }
solana-sdk = { path = "../../sdk", version = "=1.16.0" }
solana-zk-token-sdk = { path = "../../zk-token-sdk", version = "=1.16.0" }

View File

@ -0,0 +1,788 @@
use {
bytemuck::Pod,
solana_program_test::*,
solana_sdk::{
instruction::InstructionError,
signature::Signer,
signer::keypair::Keypair,
system_instruction,
transaction::{Transaction, TransactionError},
},
solana_zk_token_sdk::{
encryption::elgamal::ElGamalKeypair, instruction::*, zk_token_proof_instruction::*,
zk_token_proof_program, zk_token_proof_state::ProofContextState,
},
std::mem::size_of,
};
const VERIFY_INSTRUCTION_TYPES: [ProofInstruction; 6] = [
ProofInstruction::VerifyCloseAccount,
ProofInstruction::VerifyWithdraw,
ProofInstruction::VerifyWithdrawWithheldTokens,
ProofInstruction::VerifyTransfer,
ProofInstruction::VerifyTransferWithFee,
ProofInstruction::VerifyPubkeyValidity,
];
#[tokio::test]
async fn test_close_account() {
let elgamal_keypair = ElGamalKeypair::new_rand();
let zero_ciphertext = elgamal_keypair.public.encrypt(0_u64);
let success_proof_data = CloseAccountData::new(&elgamal_keypair, &zero_ciphertext).unwrap();
let incorrect_keypair = ElGamalKeypair {
public: ElGamalKeypair::new_rand().public,
secret: ElGamalKeypair::new_rand().secret,
};
let fail_proof_data = CloseAccountData::new(&incorrect_keypair, &zero_ciphertext).unwrap();
test_verify_proof_without_context(
ProofInstruction::VerifyCloseAccount,
&success_proof_data,
&fail_proof_data,
)
.await;
test_verify_proof_with_context(
ProofInstruction::VerifyCloseAccount,
size_of::<ProofContextState<CloseAccountProofContext>>(),
&success_proof_data,
&fail_proof_data,
)
.await;
test_close_context_state(
ProofInstruction::VerifyCloseAccount,
size_of::<ProofContextState<CloseAccountProofContext>>(),
&success_proof_data,
)
.await;
}
#[tokio::test]
async fn test_withdraw_withheld_tokens() {
let elgamal_keypair = ElGamalKeypair::new_rand();
let destination_keypair = ElGamalKeypair::new_rand();
let amount: u64 = 0;
let withdraw_withheld_authority_ciphertext = elgamal_keypair.public.encrypt(amount);
let success_proof_data = WithdrawWithheldTokensData::new(
&elgamal_keypair,
&destination_keypair.public,
&withdraw_withheld_authority_ciphertext,
amount,
)
.unwrap();
let incorrect_keypair = ElGamalKeypair {
public: ElGamalKeypair::new_rand().public,
secret: ElGamalKeypair::new_rand().secret,
};
let fail_proof_data = WithdrawWithheldTokensData::new(
&incorrect_keypair,
&destination_keypair.public,
&withdraw_withheld_authority_ciphertext,
amount,
)
.unwrap();
test_verify_proof_without_context(
ProofInstruction::VerifyWithdrawWithheldTokens,
&success_proof_data,
&fail_proof_data,
)
.await;
test_verify_proof_with_context(
ProofInstruction::VerifyWithdrawWithheldTokens,
size_of::<ProofContextState<WithdrawWithheldTokensProofContext>>(),
&success_proof_data,
&fail_proof_data,
)
.await;
test_close_context_state(
ProofInstruction::VerifyWithdrawWithheldTokens,
size_of::<ProofContextState<WithdrawWithheldTokensProofContext>>(),
&success_proof_data,
)
.await;
}
#[tokio::test]
async fn test_transfer() {
let source_keypair = ElGamalKeypair::new_rand();
let dest_pubkey = ElGamalKeypair::new_rand().public;
let auditor_pubkey = ElGamalKeypair::new_rand().public;
let spendable_balance: u64 = 0;
let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance);
let transfer_amount: u64 = 0;
let success_proof_data = TransferData::new(
transfer_amount,
(spendable_balance, &spendable_ciphertext),
&source_keypair,
(&dest_pubkey, &auditor_pubkey),
)
.unwrap();
let incorrect_keypair = ElGamalKeypair {
public: ElGamalKeypair::new_rand().public,
secret: ElGamalKeypair::new_rand().secret,
};
let fail_proof_data = TransferData::new(
transfer_amount,
(spendable_balance, &spendable_ciphertext),
&incorrect_keypair,
(&dest_pubkey, &auditor_pubkey),
)
.unwrap();
test_verify_proof_without_context(
ProofInstruction::VerifyTransfer,
&success_proof_data,
&fail_proof_data,
)
.await;
test_verify_proof_with_context(
ProofInstruction::VerifyTransfer,
size_of::<ProofContextState<TransferProofContext>>(),
&success_proof_data,
&fail_proof_data,
)
.await;
test_close_context_state(
ProofInstruction::VerifyTransfer,
size_of::<ProofContextState<TransferProofContext>>(),
&success_proof_data,
)
.await;
}
#[tokio::test]
async fn test_transfer_with_fee() {
let source_keypair = ElGamalKeypair::new_rand();
let destination_pubkey = ElGamalKeypair::new_rand().public;
let auditor_pubkey = ElGamalKeypair::new_rand().public;
let withdraw_withheld_authority_pubkey = ElGamalKeypair::new_rand().public;
let spendable_balance: u64 = 120;
let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance);
let transfer_amount: u64 = 0;
let fee_parameters = FeeParameters {
fee_rate_basis_points: 400,
maximum_fee: 3,
};
let success_proof_data = TransferWithFeeData::new(
transfer_amount,
(spendable_balance, &spendable_ciphertext),
&source_keypair,
(&destination_pubkey, &auditor_pubkey),
fee_parameters,
&withdraw_withheld_authority_pubkey,
)
.unwrap();
let incorrect_keypair = ElGamalKeypair {
public: ElGamalKeypair::new_rand().public,
secret: ElGamalKeypair::new_rand().secret,
};
let fail_proof_data = TransferWithFeeData::new(
transfer_amount,
(spendable_balance, &spendable_ciphertext),
&incorrect_keypair,
(&destination_pubkey, &auditor_pubkey),
fee_parameters,
&withdraw_withheld_authority_pubkey,
)
.unwrap();
test_verify_proof_without_context(
ProofInstruction::VerifyTransferWithFee,
&success_proof_data,
&fail_proof_data,
)
.await;
test_verify_proof_with_context(
ProofInstruction::VerifyTransferWithFee,
size_of::<ProofContextState<TransferWithFeeProofContext>>(),
&success_proof_data,
&fail_proof_data,
)
.await;
test_close_context_state(
ProofInstruction::VerifyTransferWithFee,
size_of::<ProofContextState<TransferWithFeeProofContext>>(),
&success_proof_data,
)
.await;
}
#[tokio::test]
async fn test_withdraw() {
let elgamal_keypair = ElGamalKeypair::new_rand();
let current_balance: u64 = 77;
let current_ciphertext = elgamal_keypair.public.encrypt(current_balance);
let withdraw_amount: u64 = 55;
let success_proof_data = WithdrawData::new(
withdraw_amount,
&elgamal_keypair,
current_balance,
&current_ciphertext,
)
.unwrap();
let incorrect_keypair = ElGamalKeypair {
public: ElGamalKeypair::new_rand().public,
secret: ElGamalKeypair::new_rand().secret,
};
let fail_proof_data = WithdrawData::new(
withdraw_amount,
&incorrect_keypair,
current_balance,
&current_ciphertext,
)
.unwrap();
test_verify_proof_without_context(
ProofInstruction::VerifyWithdraw,
&success_proof_data,
&fail_proof_data,
)
.await;
test_verify_proof_with_context(
ProofInstruction::VerifyWithdraw,
size_of::<ProofContextState<WithdrawProofContext>>(),
&success_proof_data,
&fail_proof_data,
)
.await;
test_close_context_state(
ProofInstruction::VerifyWithdraw,
size_of::<ProofContextState<WithdrawProofContext>>(),
&success_proof_data,
)
.await;
}
#[tokio::test]
async fn test_pubkey_validity() {
let elgamal_keypair = ElGamalKeypair::new_rand();
let success_proof_data = PubkeyValidityData::new(&elgamal_keypair).unwrap();
let incorrect_keypair = ElGamalKeypair {
public: ElGamalKeypair::new_rand().public,
secret: ElGamalKeypair::new_rand().secret,
};
let fail_proof_data = PubkeyValidityData::new(&incorrect_keypair).unwrap();
test_verify_proof_without_context(
ProofInstruction::VerifyPubkeyValidity,
&success_proof_data,
&fail_proof_data,
)
.await;
test_verify_proof_with_context(
ProofInstruction::VerifyPubkeyValidity,
size_of::<ProofContextState<PubkeyValidityProofContext>>(),
&success_proof_data,
&fail_proof_data,
)
.await;
test_close_context_state(
ProofInstruction::VerifyPubkeyValidity,
size_of::<ProofContextState<PubkeyValidityProofContext>>(),
&success_proof_data,
)
.await;
}
async fn test_verify_proof_without_context<T, U>(
proof_instruction: ProofInstruction,
success_proof_data: &T,
fail_proof_data: &T,
) where
T: Pod + ZkProofData<U>,
U: Pod,
{
let mut context = ProgramTest::default().start_with_context().await;
let client = &mut context.banks_client;
let payer = &context.payer;
let recent_blockhash = context.last_blockhash;
// verify a valid proof (wihtout creating a context account)
let instructions = vec![proof_instruction.encode_verify_proof(None, success_proof_data)];
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&payer.pubkey()),
&[payer],
recent_blockhash,
);
client.process_transaction(transaction).await.unwrap();
// try to verify an invalid proof (without creating a context account)
let instructions = vec![proof_instruction.encode_verify_proof(None, fail_proof_data)];
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&payer.pubkey()),
&[payer],
recent_blockhash,
);
let err = client
.process_transaction(transaction)
.await
.unwrap_err()
.unwrap();
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::InvalidInstructionData)
);
// try to verify a valid proof, but with a wrong proof type
for wrong_instruction_type in VERIFY_INSTRUCTION_TYPES {
if proof_instruction == wrong_instruction_type {
continue;
}
let instruction =
vec![wrong_instruction_type.encode_verify_proof(None, success_proof_data)];
let transaction = Transaction::new_signed_with_payer(
&instruction,
Some(&payer.pubkey()),
&[payer],
recent_blockhash,
);
let err = client
.process_transaction(transaction)
.await
.unwrap_err()
.unwrap();
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::InvalidInstructionData)
);
}
}
async fn test_verify_proof_with_context<T, U>(
instruction_type: ProofInstruction,
space: usize,
success_proof_data: &T,
fail_proof_data: &T,
) where
T: Pod + ZkProofData<U>,
U: Pod,
{
let mut context = ProgramTest::default().start_with_context().await;
let rent = context.banks_client.get_rent().await.unwrap();
let client = &mut context.banks_client;
let payer = &context.payer;
let recent_blockhash = context.last_blockhash;
let context_state_account = Keypair::new();
let context_state_authority = Keypair::new();
let context_state_info = ContextStateInfo {
context_state_account: &context_state_account.pubkey(),
context_state_authority: &context_state_authority.pubkey(),
};
// try to create proof context state with an invalid proof
let instructions = vec![
system_instruction::create_account(
&payer.pubkey(),
&context_state_account.pubkey(),
rent.minimum_balance(space),
space as u64,
&zk_token_proof_program::id(),
),
instruction_type.encode_verify_proof(Some(context_state_info), fail_proof_data),
];
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&payer.pubkey()),
&[payer, &context_state_account],
recent_blockhash,
);
let err = client
.process_transaction(transaction)
.await
.unwrap_err()
.unwrap();
assert_eq!(
err,
TransactionError::InstructionError(1, InstructionError::InvalidInstructionData)
);
// try to create proof context state with incorrect account data length
let instructions = vec![
system_instruction::create_account(
&payer.pubkey(),
&context_state_account.pubkey(),
rent.minimum_balance(space),
(space.checked_sub(1).unwrap()) as u64,
&zk_token_proof_program::id(),
),
instruction_type.encode_verify_proof(Some(context_state_info), success_proof_data),
];
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&payer.pubkey()),
&[payer, &context_state_account],
recent_blockhash,
);
let err = client
.process_transaction(transaction)
.await
.unwrap_err()
.unwrap();
assert_eq!(
err,
TransactionError::InstructionError(1, InstructionError::InvalidAccountData)
);
// try to create proof context state with insufficient rent
let instructions = vec![
system_instruction::create_account(
&payer.pubkey(),
&context_state_account.pubkey(),
rent.minimum_balance(space).checked_sub(1).unwrap(),
space as u64,
&zk_token_proof_program::id(),
),
instruction_type.encode_verify_proof(Some(context_state_info), success_proof_data),
];
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&payer.pubkey()),
&[payer, &context_state_account],
recent_blockhash,
);
let err = client
.process_transaction(transaction)
.await
.unwrap_err()
.unwrap();
assert_eq!(
err,
TransactionError::InsufficientFundsForRent { account_index: 1 },
);
// try to create proof context state with an invalid `ProofType`
for wrong_instruction_type in VERIFY_INSTRUCTION_TYPES {
if instruction_type == wrong_instruction_type {
continue;
}
let instructions = vec![
system_instruction::create_account(
&payer.pubkey(),
&context_state_account.pubkey(),
rent.minimum_balance(space),
space as u64,
&zk_token_proof_program::id(),
),
wrong_instruction_type
.encode_verify_proof(Some(context_state_info), success_proof_data),
];
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&payer.pubkey()),
&[payer, &context_state_account],
recent_blockhash,
);
let err = client
.process_transaction(transaction)
.await
.unwrap_err()
.unwrap();
assert_eq!(
err,
TransactionError::InstructionError(1, InstructionError::InvalidInstructionData)
);
}
// successfully create a proof context state
let instructions = vec![
system_instruction::create_account(
&payer.pubkey(),
&context_state_account.pubkey(),
rent.minimum_balance(space),
space as u64,
&zk_token_proof_program::id(),
),
instruction_type.encode_verify_proof(Some(context_state_info), success_proof_data),
];
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&payer.pubkey()),
&[payer, &context_state_account],
recent_blockhash,
);
client.process_transaction(transaction).await.unwrap();
// try overwriting the context state
let instructions =
vec![instruction_type.encode_verify_proof(Some(context_state_info), success_proof_data)];
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&payer.pubkey()),
&[payer],
recent_blockhash,
);
let err = client
.process_transaction(transaction)
.await
.unwrap_err()
.unwrap();
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::AccountAlreadyInitialized)
);
// self-owned context state account
let context_state_account_and_authority = Keypair::new();
let context_state_info = ContextStateInfo {
context_state_account: &context_state_account_and_authority.pubkey(),
context_state_authority: &context_state_account_and_authority.pubkey(),
};
let instructions = vec![
system_instruction::create_account(
&payer.pubkey(),
&context_state_account_and_authority.pubkey(),
rent.minimum_balance(space),
space as u64,
&zk_token_proof_program::id(),
),
instruction_type.encode_verify_proof(Some(context_state_info), success_proof_data),
];
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&payer.pubkey()),
&[payer, &context_state_account_and_authority],
recent_blockhash,
);
client.process_transaction(transaction).await.unwrap();
}
async fn test_close_context_state<T, U>(
instruction_type: ProofInstruction,
space: usize,
success_proof_data: &T,
) where
T: Pod + ZkProofData<U>,
U: Pod,
{
let mut context = ProgramTest::default().start_with_context().await;
let rent = context.banks_client.get_rent().await.unwrap();
let client = &mut context.banks_client;
let payer = &context.payer;
let recent_blockhash = context.last_blockhash;
let context_state_account = Keypair::new();
let context_state_authority = Keypair::new();
let context_state_info = ContextStateInfo {
context_state_account: &context_state_account.pubkey(),
context_state_authority: &context_state_authority.pubkey(),
};
let destination_account = Keypair::new();
// create a proof context state
let instructions = vec![
system_instruction::create_account(
&payer.pubkey(),
&context_state_account.pubkey(),
rent.minimum_balance(space),
space as u64,
&zk_token_proof_program::id(),
),
instruction_type.encode_verify_proof(Some(context_state_info), success_proof_data),
];
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&payer.pubkey()),
&[payer, &context_state_account],
recent_blockhash,
);
client.process_transaction(transaction).await.unwrap();
// try to close context state with incorrect authority
let incorrect_authority = Keypair::new();
let instruction = close_context_state(
ContextStateInfo {
context_state_account: &context_state_account.pubkey(),
context_state_authority: &incorrect_authority.pubkey(),
},
&destination_account.pubkey(),
);
let transaction = Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer, &incorrect_authority],
recent_blockhash,
);
let err = client
.process_transaction(transaction)
.await
.unwrap_err()
.unwrap();
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::InvalidAccountOwner)
);
// successfully close proof context state
let instruction = close_context_state(
ContextStateInfo {
context_state_account: &context_state_account.pubkey(),
context_state_authority: &context_state_authority.pubkey(),
},
&destination_account.pubkey(),
);
let transaction = Transaction::new_signed_with_payer(
&[instruction.clone()],
Some(&payer.pubkey()),
&[payer, &context_state_authority],
recent_blockhash,
);
client.process_transaction(transaction).await.unwrap();
// create and close proof context in a single transaction
let instructions = vec![
system_instruction::create_account(
&payer.pubkey(),
&context_state_account.pubkey(),
0_u64, // do not deposit rent
space as u64,
&zk_token_proof_program::id(),
),
instruction_type.encode_verify_proof(Some(context_state_info), success_proof_data),
close_context_state(
ContextStateInfo {
context_state_account: &context_state_account.pubkey(),
context_state_authority: &context_state_authority.pubkey(),
},
&destination_account.pubkey(),
),
];
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&payer.pubkey()),
&[payer, &context_state_account, &context_state_authority],
recent_blockhash,
);
client.process_transaction(transaction).await.unwrap();
// close proof context state with owner as destination
let instructions = vec![
system_instruction::create_account(
&payer.pubkey(),
&context_state_account.pubkey(),
0_u64,
space as u64,
&zk_token_proof_program::id(),
),
instruction_type.encode_verify_proof(Some(context_state_info), success_proof_data),
close_context_state(
ContextStateInfo {
context_state_account: &context_state_account.pubkey(),
context_state_authority: &context_state_authority.pubkey(),
},
&context_state_authority.pubkey(),
),
];
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&payer.pubkey()),
&[payer, &context_state_account, &context_state_authority],
recent_blockhash,
);
client.process_transaction(transaction).await.unwrap();
// try close account with itself as destination
let instructions = vec![
system_instruction::create_account(
&payer.pubkey(),
&context_state_account.pubkey(),
0_u64,
space as u64,
&zk_token_proof_program::id(),
),
instruction_type.encode_verify_proof(Some(context_state_info), success_proof_data),
close_context_state(
ContextStateInfo {
context_state_account: &context_state_account.pubkey(),
context_state_authority: &context_state_authority.pubkey(),
},
&context_state_account.pubkey(),
),
];
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&payer.pubkey()),
&[payer, &context_state_account, &context_state_authority],
recent_blockhash,
);
let err = client
.process_transaction(transaction)
.await
.unwrap_err()
.unwrap();
assert_eq!(
err,
TransactionError::InstructionError(2, InstructionError::InvalidInstructionData)
);
// close self-owned proof context accounts
let context_state_account_and_authority = Keypair::new();
let context_state_info = ContextStateInfo {
context_state_account: &context_state_account_and_authority.pubkey(),
context_state_authority: &context_state_account_and_authority.pubkey(),
};
let instructions = vec![
system_instruction::create_account(
&payer.pubkey(),
&context_state_account_and_authority.pubkey(),
0_u64,
space as u64,
&zk_token_proof_program::id(),
),
instruction_type.encode_verify_proof(Some(context_state_info), success_proof_data),
close_context_state(context_state_info, &context_state_account.pubkey()),
];
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&payer.pubkey()),
&[payer, &context_state_account_and_authority],
recent_blockhash,
);
client.process_transaction(transaction).await.unwrap();
}

View File

@ -3,26 +3,114 @@
use {
bytemuck::Pod,
solana_program_runtime::{ic_msg, invoke_context::InvokeContext},
solana_sdk::instruction::{InstructionError, TRANSACTION_LEVEL_STACK_HEIGHT},
solana_zk_token_sdk::zk_token_proof_instruction::*,
solana_sdk::{
instruction::{InstructionError, TRANSACTION_LEVEL_STACK_HEIGHT},
system_program,
},
solana_zk_token_sdk::{
zk_token_proof_instruction::*,
zk_token_proof_program::id,
zk_token_proof_state::{ProofContextState, ProofContextStateMeta},
},
std::result::Result,
};
fn verify<T: Pod + Verifiable>(invoke_context: &mut InvokeContext) -> Result<(), InstructionError> {
fn process_verify_proof<T, U>(invoke_context: &mut InvokeContext) -> Result<(), InstructionError>
where
T: Pod + ZkProofData<U>,
U: Pod,
{
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
let instruction_data = instruction_context.get_instruction_data();
let instruction = ProofInstruction::decode_data::<T>(instruction_data);
let proof = instruction.ok_or_else(|| {
let proof_data = ProofInstruction::proof_data::<T, U>(instruction_data).ok_or_else(|| {
ic_msg!(invoke_context, "invalid proof data");
InstructionError::InvalidInstructionData
})?;
proof.verify().map_err(|err| {
ic_msg!(invoke_context, "proof verification failed: {:?}", err);
proof_data.verify_proof().map_err(|err| {
ic_msg!(invoke_context, "proof_verification failed: {:?}", err);
InstructionError::InvalidInstructionData
})
})?;
// create context state if accounts are provided with the instruction
if instruction_context.get_number_of_instruction_accounts() > 0 {
let context_state_authority = *instruction_context
.try_borrow_instruction_account(transaction_context, 1)?
.get_key();
let mut proof_context_account =
instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
if *proof_context_account.get_owner() != id() {
return Err(InstructionError::InvalidAccountOwner);
}
let proof_context_state_meta =
ProofContextStateMeta::try_from_bytes(proof_context_account.get_data())?;
if proof_context_state_meta.proof_type != ProofType::Uninitialized.into() {
return Err(InstructionError::AccountAlreadyInitialized);
}
let context_state_data = ProofContextState::encode(
&context_state_authority,
T::PROOF_TYPE,
proof_data.context_data(),
);
if proof_context_account.get_data().len() != context_state_data.len() {
return Err(InstructionError::InvalidAccountData);
}
proof_context_account.set_data(context_state_data)?;
}
Ok(())
}
fn process_close_proof_context(invoke_context: &mut InvokeContext) -> Result<(), InstructionError> {
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
let owner_pubkey = {
let owner_account =
instruction_context.try_borrow_instruction_account(transaction_context, 2)?;
if !owner_account.is_signer() {
return Err(InstructionError::MissingRequiredSignature);
}
*owner_account.get_key()
}; // done with `owner_account`, so drop it to prevent a potential double borrow
let proof_context_account_pubkey = *instruction_context
.try_borrow_instruction_account(transaction_context, 0)?
.get_key();
let destination_account_pubkey = *instruction_context
.try_borrow_instruction_account(transaction_context, 1)?
.get_key();
if proof_context_account_pubkey == destination_account_pubkey {
return Err(InstructionError::InvalidInstructionData);
}
let mut proof_context_account =
instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
let proof_context_state_meta =
ProofContextStateMeta::try_from_bytes(proof_context_account.get_data())?;
let expected_owner_pubkey = proof_context_state_meta.context_state_authority;
if owner_pubkey != expected_owner_pubkey {
return Err(InstructionError::InvalidAccountOwner);
}
let mut destination_account =
instruction_context.try_borrow_instruction_account(transaction_context, 1)?;
destination_account.checked_add_lamports(proof_context_account.get_lamports())?;
proof_context_account.set_lamports(0)?;
proof_context_account.set_data_length(0)?;
proof_context_account.set_owner(system_program::id().as_ref())?;
Ok(())
}
pub fn process_instruction(invoke_context: &mut InvokeContext) -> Result<(), InstructionError> {
@ -40,32 +128,39 @@ pub fn process_instruction(invoke_context: &mut InvokeContext) -> Result<(), Ins
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
let instruction_data = instruction_context.get_instruction_data();
let instruction = ProofInstruction::decode_type(instruction_data);
let instruction = ProofInstruction::instruction_type(instruction_data)
.ok_or(InstructionError::InvalidInstructionData)?;
match instruction.ok_or(InstructionError::InvalidInstructionData)? {
match instruction {
ProofInstruction::CloseContextState => {
ic_msg!(invoke_context, "CloseContextState");
process_close_proof_context(invoke_context)
}
ProofInstruction::VerifyCloseAccount => {
ic_msg!(invoke_context, "VerifyCloseAccount");
verify::<CloseAccountData>(invoke_context)
process_verify_proof::<CloseAccountData, CloseAccountProofContext>(invoke_context)
}
ProofInstruction::VerifyWithdraw => {
ic_msg!(invoke_context, "VerifyWithdraw");
verify::<WithdrawData>(invoke_context)
process_verify_proof::<WithdrawData, WithdrawProofContext>(invoke_context)
}
ProofInstruction::VerifyWithdrawWithheldTokens => {
ic_msg!(invoke_context, "VerifyWithdrawWithheldTokens");
verify::<WithdrawWithheldTokensData>(invoke_context)
process_verify_proof::<WithdrawWithheldTokensData, WithdrawWithheldTokensProofContext>(
invoke_context,
)
}
ProofInstruction::VerifyTransfer => {
ic_msg!(invoke_context, "VerifyTransfer");
verify::<TransferData>(invoke_context)
process_verify_proof::<TransferData, TransferProofContext>(invoke_context)
}
ProofInstruction::VerifyTransferWithFee => {
ic_msg!(invoke_context, "VerifyTransferWithFee");
verify::<TransferWithFeeData>(invoke_context)
process_verify_proof::<TransferWithFeeData, TransferWithFeeProofContext>(invoke_context)
}
ProofInstruction::VerifyPubkeyValidity => {
ic_msg!(invoke_context, "VerifyPubkeyValidity");
verify::<PubkeyValidityData>(invoke_context)
process_verify_proof::<PubkeyValidityData, PubkeyValidityProofContext>(invoke_context)
}
}
}

View File

@ -1,19 +1,21 @@
use {
crate::zk_token_elgamal::pod,
bytemuck::{Pod, Zeroable},
};
#[cfg(not(target_os = "solana"))]
use {
crate::{
encryption::elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
errors::ProofError,
instruction::Verifiable,
sigma_proofs::zero_balance_proof::ZeroBalanceProof,
transcript::TranscriptProtocol,
},
merlin::Transcript,
std::convert::TryInto,
};
use {
crate::{
instruction::{ProofType, ZkProofData},
zk_token_elgamal::pod,
},
bytemuck::{Pod, Zeroable},
};
/// This struct includes the cryptographic proof *and* the account data information needed to verify
/// the proof
@ -25,14 +27,21 @@ use {
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct CloseAccountData {
/// The context data for the close account proof
pub context: CloseAccountProofContext,
/// Proof that the source account available balance is zero
pub proof: CloseAccountProof, // 96 bytes
}
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct CloseAccountProofContext {
/// The source account ElGamal pubkey
pub pubkey: pod::ElGamalPubkey, // 32 bytes
/// The source account available balance in encrypted form
pub ciphertext: pod::ElGamalCiphertext, // 64 bytes
/// Proof that the source account available balance is zero
pub proof: CloseAccountProof, // 96 bytes
}
#[cfg(not(target_os = "solana"))]
@ -44,25 +53,32 @@ impl CloseAccountData {
let pod_pubkey = pod::ElGamalPubkey(keypair.public.to_bytes());
let pod_ciphertext = pod::ElGamalCiphertext(ciphertext.to_bytes());
let mut transcript = CloseAccountProof::transcript_new(&pod_pubkey, &pod_ciphertext);
let proof = CloseAccountProof::new(keypair, ciphertext, &mut transcript);
Ok(CloseAccountData {
let context = CloseAccountProofContext {
pubkey: pod_pubkey,
ciphertext: pod_ciphertext,
proof,
})
};
let mut transcript = CloseAccountProof::transcript_new(&pod_pubkey, &pod_ciphertext);
let proof = CloseAccountProof::new(keypair, ciphertext, &mut transcript);
Ok(CloseAccountData { context, proof })
}
}
#[cfg(not(target_os = "solana"))]
impl Verifiable for CloseAccountData {
fn verify(&self) -> Result<(), ProofError> {
let mut transcript = CloseAccountProof::transcript_new(&self.pubkey, &self.ciphertext);
impl ZkProofData<CloseAccountProofContext> for CloseAccountData {
const PROOF_TYPE: ProofType = ProofType::CloseAccount;
let pubkey = self.pubkey.try_into()?;
let ciphertext = self.ciphertext.try_into()?;
fn context_data(&self) -> &CloseAccountProofContext {
&self.context
}
#[cfg(not(target_os = "solana"))]
fn verify_proof(&self) -> Result<(), ProofError> {
let mut transcript =
CloseAccountProof::transcript_new(&self.context.pubkey, &self.context.ciphertext);
let pubkey = self.context.pubkey.try_into()?;
let ciphertext = self.context.ciphertext.try_into()?;
self.proof.verify(&pubkey, &ciphertext, &mut transcript)
}
}
@ -127,11 +143,11 @@ mod test {
// general case: encryption of 0
let ciphertext = keypair.public.encrypt(0_u64);
let close_account_data = CloseAccountData::new(&keypair, &ciphertext).unwrap();
assert!(close_account_data.verify().is_ok());
assert!(close_account_data.verify_proof().is_ok());
// general case: encryption of > 0
let ciphertext = keypair.public.encrypt(1_u64);
let close_account_data = CloseAccountData::new(&keypair, &ciphertext).unwrap();
assert!(close_account_data.verify().is_err());
assert!(close_account_data.verify_proof().is_err());
}
}

View File

@ -5,6 +5,7 @@ pub mod transfer_with_fee;
pub mod withdraw;
pub mod withdraw_withheld;
use num_derive::{FromPrimitive, ToPrimitive};
#[cfg(not(target_os = "solana"))]
use {
crate::{
@ -17,14 +18,35 @@ use {
curve25519_dalek::scalar::Scalar,
};
pub use {
close_account::CloseAccountData, pubkey_validity::PubkeyValidityData, transfer::TransferData,
transfer_with_fee::TransferWithFeeData, withdraw::WithdrawData,
withdraw_withheld::WithdrawWithheldTokensData,
bytemuck::Pod,
close_account::{CloseAccountData, CloseAccountProofContext},
pubkey_validity::{PubkeyValidityData, PubkeyValidityProofContext},
transfer::{TransferData, TransferProofContext},
transfer_with_fee::{FeeParameters, TransferWithFeeData, TransferWithFeeProofContext},
withdraw::{WithdrawData, WithdrawProofContext},
withdraw_withheld::{WithdrawWithheldTokensData, WithdrawWithheldTokensProofContext},
};
#[cfg(not(target_os = "solana"))]
pub trait Verifiable {
fn verify(&self) -> Result<(), ProofError>;
#[derive(Clone, Copy, Debug, FromPrimitive, ToPrimitive, PartialEq, Eq)]
#[repr(u8)]
pub enum ProofType {
/// Empty proof type used to distinguish if a proof context account is initialized
Uninitialized,
CloseAccount,
Withdraw,
WithdrawWithheldTokens,
Transfer,
TransferWithFee,
PubkeyValidity,
}
pub trait ZkProofData<T: Pod> {
const PROOF_TYPE: ProofType;
fn context_data(&self) -> &T;
#[cfg(not(target_os = "solana"))]
fn verify_proof(&self) -> Result<(), ProofError>;
}
#[cfg(not(target_os = "solana"))]

View File

@ -1,19 +1,21 @@
use {
crate::zk_token_elgamal::pod,
bytemuck::{Pod, Zeroable},
};
#[cfg(not(target_os = "solana"))]
use {
crate::{
encryption::elgamal::{ElGamalKeypair, ElGamalPubkey},
errors::ProofError,
instruction::Verifiable,
sigma_proofs::pubkey_proof::PubkeySigmaProof,
transcript::TranscriptProtocol,
},
merlin::Transcript,
std::convert::TryInto,
};
use {
crate::{
instruction::{ProofType, ZkProofData},
zk_token_elgamal::pod,
},
bytemuck::{Pod, Zeroable},
};
/// This struct includes the cryptographic proof *and* the account data information needed to
/// verify the proof
@ -24,34 +26,45 @@ use {
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct PubkeyValidityData {
/// The public key to be proved
pub pubkey: pod::ElGamalPubkey,
/// The context data for the public key validity proof
pub context: PubkeyValidityProofContext,
/// Proof that the public key is well-formed
pub proof: PubkeyValidityProof, // 64 bytes
}
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct PubkeyValidityProofContext {
/// The public key to be proved
pub pubkey: pod::ElGamalPubkey, // 32 bytes
}
#[cfg(not(target_os = "solana"))]
impl PubkeyValidityData {
pub fn new(keypair: &ElGamalKeypair) -> Result<Self, ProofError> {
let pod_pubkey = pod::ElGamalPubkey(keypair.public.to_bytes());
let mut transcript = PubkeyValidityProof::transcript_new(&pod_pubkey);
let context = PubkeyValidityProofContext { pubkey: pod_pubkey };
let mut transcript = PubkeyValidityProof::transcript_new(&pod_pubkey);
let proof = PubkeyValidityProof::new(keypair, &mut transcript);
Ok(PubkeyValidityData {
pubkey: pod_pubkey,
proof,
})
Ok(PubkeyValidityData { context, proof })
}
}
#[cfg(not(target_os = "solana"))]
impl Verifiable for PubkeyValidityData {
fn verify(&self) -> Result<(), ProofError> {
let mut transcript = PubkeyValidityProof::transcript_new(&self.pubkey);
let pubkey = self.pubkey.try_into()?;
impl ZkProofData<PubkeyValidityProofContext> for PubkeyValidityData {
const PROOF_TYPE: ProofType = ProofType::PubkeyValidity;
fn context_data(&self) -> &PubkeyValidityProofContext {
&self.context
}
#[cfg(not(target_os = "solana"))]
fn verify_proof(&self) -> Result<(), ProofError> {
let mut transcript = PubkeyValidityProof::transcript_new(&self.context.pubkey);
let pubkey = self.context.pubkey.try_into()?;
self.proof.verify(&pubkey, &mut transcript)
}
}
@ -100,6 +113,6 @@ mod test {
let keypair = ElGamalKeypair::new_rand();
let pubkey_validity_data = PubkeyValidityData::new(&keypair).unwrap();
assert!(pubkey_validity_data.verify().is_ok());
assert!(pubkey_validity_data.verify_proof().is_ok());
}
}

View File

@ -1,7 +1,3 @@
use {
crate::zk_token_elgamal::pod,
bytemuck::{Pod, Zeroable},
};
#[cfg(not(target_os = "solana"))]
use {
crate::{
@ -12,7 +8,7 @@ use {
pedersen::{Pedersen, PedersenCommitment, PedersenOpening},
},
errors::ProofError,
instruction::{combine_lo_hi_ciphertexts, split_u64, Role, Verifiable},
instruction::{combine_lo_hi_ciphertexts, split_u64, Role},
range_proof::RangeProof,
sigma_proofs::{
equality_proof::CtxtCommEqualityProof, validity_proof::AggregatedValidityProof,
@ -23,6 +19,13 @@ use {
merlin::Transcript,
std::convert::TryInto,
};
use {
crate::{
instruction::{ProofType, ZkProofData},
zk_token_elgamal::pod,
},
bytemuck::{Pod, Zeroable},
};
#[cfg(not(target_os = "solana"))]
const TRANSFER_SOURCE_AMOUNT_BITS: usize = 64;
@ -42,22 +45,29 @@ lazy_static::lazy_static! {
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferData {
/// Group encryption of the low 16 bits of the transfer amount
pub ciphertext_lo: pod::TransferAmountEncryption,
/// Group encryption of the high 48 bits of the transfer amount
pub ciphertext_hi: pod::TransferAmountEncryption,
/// The public encryption keys associated with the transfer: source, dest, and auditor
pub transfer_pubkeys: pod::TransferPubkeys,
/// The final spendable ciphertext after the transfer
pub new_source_ciphertext: pod::ElGamalCiphertext,
/// The context data for the transfer proof
pub context: TransferProofContext,
/// Zero-knowledge proofs for Transfer
pub proof: TransferProof,
}
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferProofContext {
/// Group encryption of the low 16 bits of the transfer amount
pub ciphertext_lo: pod::TransferAmountEncryption, // 128 bytes
/// Group encryption of the high 48 bits of the transfer amount
pub ciphertext_hi: pod::TransferAmountEncryption, // 128 bytes
/// The public encryption keys associated with the transfer: source, dest, and auditor
pub transfer_pubkeys: pod::TransferPubkeys, // 96 bytes
/// The final spendable ciphertext after the transfer
pub new_source_ciphertext: pod::ElGamalCiphertext, // 64 bytes
}
#[cfg(not(target_os = "solana"))]
impl TransferData {
#[allow(clippy::too_many_arguments)]
@ -116,6 +126,13 @@ impl TransferData {
let pod_ciphertext_hi: pod::TransferAmountEncryption = ciphertext_hi.into();
let pod_new_source_ciphertext: pod::ElGamalCiphertext = new_source_ciphertext.into();
let context = TransferProofContext {
ciphertext_lo: pod_ciphertext_lo,
ciphertext_hi: pod_ciphertext_hi,
transfer_pubkeys: pod_transfer_pubkeys,
new_source_ciphertext: pod_new_source_ciphertext,
};
let mut transcript = TransferProof::transcript_new(
&pod_transfer_pubkeys,
&pod_ciphertext_lo,
@ -133,18 +150,12 @@ impl TransferData {
&mut transcript,
);
Ok(Self {
ciphertext_lo: pod_ciphertext_lo,
ciphertext_hi: pod_ciphertext_hi,
transfer_pubkeys: pod_transfer_pubkeys,
new_source_ciphertext: pod_new_source_ciphertext,
proof,
})
Ok(Self { context, proof })
}
/// Extracts the lo ciphertexts associated with a transfer data
fn ciphertext_lo(&self, role: Role) -> Result<ElGamalCiphertext, ProofError> {
let ciphertext_lo: TransferAmountEncryption = self.ciphertext_lo.try_into()?;
let ciphertext_lo: TransferAmountEncryption = self.context.ciphertext_lo.try_into()?;
let handle_lo = match role {
Role::Source => Some(ciphertext_lo.source_handle),
@ -165,7 +176,7 @@ impl TransferData {
/// Extracts the lo ciphertexts associated with a transfer data
fn ciphertext_hi(&self, role: Role) -> Result<ElGamalCiphertext, ProofError> {
let ciphertext_hi: TransferAmountEncryption = self.ciphertext_hi.try_into()?;
let ciphertext_hi: TransferAmountEncryption = self.context.ciphertext_hi.try_into()?;
let handle_hi = match role {
Role::Source => Some(ciphertext_hi.source_handle),
@ -201,21 +212,27 @@ impl TransferData {
}
}
#[cfg(not(target_os = "solana"))]
impl Verifiable for TransferData {
fn verify(&self) -> Result<(), ProofError> {
impl ZkProofData<TransferProofContext> for TransferData {
const PROOF_TYPE: ProofType = ProofType::Transfer;
fn context_data(&self) -> &TransferProofContext {
&self.context
}
#[cfg(not(target_os = "solana"))]
fn verify_proof(&self) -> Result<(), ProofError> {
// generate transcript and append all public inputs
let mut transcript = TransferProof::transcript_new(
&self.transfer_pubkeys,
&self.ciphertext_lo,
&self.ciphertext_hi,
&self.new_source_ciphertext,
&self.context.transfer_pubkeys,
&self.context.ciphertext_lo,
&self.context.ciphertext_hi,
&self.context.new_source_ciphertext,
);
let ciphertext_lo = self.ciphertext_lo.try_into()?;
let ciphertext_hi = self.ciphertext_hi.try_into()?;
let transfer_pubkeys = self.transfer_pubkeys.try_into()?;
let new_spendable_ciphertext = self.new_source_ciphertext.try_into()?;
let ciphertext_lo = self.context.ciphertext_lo.try_into()?;
let ciphertext_hi = self.context.ciphertext_hi.try_into()?;
let transfer_pubkeys = self.context.transfer_pubkeys.try_into()?;
let new_spendable_ciphertext = self.context.new_source_ciphertext.try_into()?;
self.proof.verify(
&ciphertext_lo,
@ -537,7 +554,7 @@ mod test {
)
.unwrap();
assert!(transfer_data.verify().is_ok());
assert!(transfer_data.verify_proof().is_ok());
// Case 2: transfer max amount
@ -558,7 +575,7 @@ mod test {
)
.unwrap();
assert!(transfer_data.verify().is_ok());
assert!(transfer_data.verify_proof().is_ok());
// Case 3: general success case
@ -578,7 +595,7 @@ mod test {
)
.unwrap();
assert!(transfer_data.verify().is_ok());
assert!(transfer_data.verify_proof().is_ok());
// Case 4: invalid destination or auditor pubkey
let spendable_balance: u64 = 0;
@ -598,7 +615,7 @@ mod test {
)
.unwrap();
assert!(transfer_data.verify().is_err());
assert!(transfer_data.verify_proof().is_err());
// auditor pubkey invalid
let dest_pk = ElGamalKeypair::new_rand().public;
@ -612,7 +629,7 @@ mod test {
)
.unwrap();
assert!(transfer_data.verify().is_err());
assert!(transfer_data.verify_proof().is_err());
}
#[test]

View File

@ -1,7 +1,3 @@
use {
crate::zk_token_elgamal::pod,
bytemuck::{Pod, Zeroable},
};
#[cfg(not(target_os = "solana"))]
use {
crate::{
@ -14,7 +10,7 @@ use {
errors::ProofError,
instruction::{
combine_lo_hi_ciphertexts, combine_lo_hi_commitments, combine_lo_hi_openings,
combine_lo_hi_u64, split_u64, transfer::TransferAmountEncryption, Role, Verifiable,
combine_lo_hi_u64, split_u64, transfer::TransferAmountEncryption, Role,
},
range_proof::RangeProof,
sigma_proofs::{
@ -29,6 +25,13 @@ use {
std::convert::TryInto,
subtle::{ConditionallySelectable, ConstantTimeGreater},
};
use {
crate::{
instruction::{ProofType, ZkProofData},
zk_token_elgamal::pod,
},
bytemuck::{Pod, Zeroable},
};
#[cfg(not(target_os = "solana"))]
const MAX_FEE_BASIS_POINTS: u64 = 10_000;
@ -61,31 +64,38 @@ lazy_static::lazy_static! {
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferWithFeeData {
/// Group encryption of the low 16 bites of the transfer amount
pub ciphertext_lo: pod::TransferAmountEncryption,
/// Group encryption of the high 48 bits of the transfer amount
pub ciphertext_hi: pod::TransferAmountEncryption,
/// The public encryption keys associated with the transfer: source, dest, and auditor
pub transfer_with_fee_pubkeys: pod::TransferWithFeePubkeys,
/// The final spendable ciphertext after the transfer,
pub new_source_ciphertext: pod::ElGamalCiphertext,
// transfer fee encryption of the low 16 bits of the transfer fee amount
pub fee_ciphertext_lo: pod::FeeEncryption,
// transfer fee encryption of the hi 32 bits of the transfer fee amount
pub fee_ciphertext_hi: pod::FeeEncryption,
// fee parameters
pub fee_parameters: pod::FeeParameters,
/// The context data for the transfer with fee proof
pub context: TransferWithFeeProofContext,
// transfer fee proof
pub proof: TransferWithFeeProof,
}
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferWithFeeProofContext {
/// Group encryption of the low 16 bites of the transfer amount
pub ciphertext_lo: pod::TransferAmountEncryption, // 128 bytes
/// Group encryption of the high 48 bits of the transfer amount
pub ciphertext_hi: pod::TransferAmountEncryption, // 128 bytes
/// The public encryption keys associated with the transfer: source, dest, and auditor
pub transfer_with_fee_pubkeys: pod::TransferWithFeePubkeys, // 128 bytes
/// The final spendable ciphertext after the transfer,
pub new_source_ciphertext: pod::ElGamalCiphertext, // 64 bytes
// transfer fee encryption of the low 16 bits of the transfer fee amount
pub fee_ciphertext_lo: pod::FeeEncryption, // 96 bytes
// transfer fee encryption of the hi 32 bits of the transfer fee amount
pub fee_ciphertext_hi: pod::FeeEncryption, // 96 bytes
// fee parameters
pub fee_parameters: pod::FeeParameters, // 10 bytes
}
#[cfg(not(target_os = "solana"))]
impl TransferWithFeeData {
pub fn new(
@ -173,6 +183,16 @@ impl TransferWithFeeData {
let pod_fee_ciphertext_lo: pod::FeeEncryption = fee_ciphertext_lo.to_pod();
let pod_fee_ciphertext_hi: pod::FeeEncryption = fee_ciphertext_hi.to_pod();
let context = TransferWithFeeProofContext {
ciphertext_lo: pod_ciphertext_lo,
ciphertext_hi: pod_ciphertext_hi,
transfer_with_fee_pubkeys: pod_transfer_with_fee_pubkeys,
new_source_ciphertext: pod_new_source_ciphertext,
fee_ciphertext_lo: pod_fee_ciphertext_lo,
fee_ciphertext_hi: pod_fee_ciphertext_hi,
fee_parameters: fee_parameters.into(),
};
let mut transcript = TransferWithFeeProof::transcript_new(
&pod_transfer_with_fee_pubkeys,
&pod_ciphertext_lo,
@ -196,21 +216,12 @@ impl TransferWithFeeData {
&mut transcript,
);
Ok(Self {
ciphertext_lo: pod_ciphertext_lo,
ciphertext_hi: pod_ciphertext_hi,
transfer_with_fee_pubkeys: pod_transfer_with_fee_pubkeys,
new_source_ciphertext: pod_new_source_ciphertext,
fee_ciphertext_lo: pod_fee_ciphertext_lo,
fee_ciphertext_hi: pod_fee_ciphertext_hi,
fee_parameters: fee_parameters.into(),
proof,
})
Ok(Self { context, proof })
}
/// Extracts the lo ciphertexts associated with a transfer-with-fee data
fn ciphertext_lo(&self, role: Role) -> Result<ElGamalCiphertext, ProofError> {
let ciphertext_lo: TransferAmountEncryption = self.ciphertext_lo.try_into()?;
let ciphertext_lo: TransferAmountEncryption = self.context.ciphertext_lo.try_into()?;
let handle_lo = match role {
Role::Source => Some(ciphertext_lo.source_handle),
@ -231,7 +242,7 @@ impl TransferWithFeeData {
/// Extracts the lo ciphertexts associated with a transfer-with-fee data
fn ciphertext_hi(&self, role: Role) -> Result<ElGamalCiphertext, ProofError> {
let ciphertext_hi: TransferAmountEncryption = self.ciphertext_hi.try_into()?;
let ciphertext_hi: TransferAmountEncryption = self.context.ciphertext_hi.try_into()?;
let handle_hi = match role {
Role::Source => Some(ciphertext_hi.source_handle),
@ -252,7 +263,7 @@ impl TransferWithFeeData {
/// Extracts the lo fee ciphertexts associated with a transfer_with_fee data
fn fee_ciphertext_lo(&self, role: Role) -> Result<ElGamalCiphertext, ProofError> {
let fee_ciphertext_lo: FeeEncryption = self.fee_ciphertext_lo.try_into()?;
let fee_ciphertext_lo: FeeEncryption = self.context.fee_ciphertext_lo.try_into()?;
let fee_handle_lo = match role {
Role::Source => None,
@ -275,7 +286,7 @@ impl TransferWithFeeData {
/// Extracts the hi fee ciphertexts associated with a transfer_with_fee data
fn fee_ciphertext_hi(&self, role: Role) -> Result<ElGamalCiphertext, ProofError> {
let fee_ciphertext_hi: FeeEncryption = self.fee_ciphertext_hi.try_into()?;
let fee_ciphertext_hi: FeeEncryption = self.context.fee_ciphertext_hi.try_into()?;
let fee_handle_hi = match role {
Role::Source => None,
@ -329,26 +340,32 @@ impl TransferWithFeeData {
}
}
#[cfg(not(target_os = "solana"))]
impl Verifiable for TransferWithFeeData {
fn verify(&self) -> Result<(), ProofError> {
impl ZkProofData<TransferWithFeeProofContext> for TransferWithFeeData {
const PROOF_TYPE: ProofType = ProofType::TransferWithFee;
fn context_data(&self) -> &TransferWithFeeProofContext {
&self.context
}
#[cfg(not(target_os = "solana"))]
fn verify_proof(&self) -> Result<(), ProofError> {
let mut transcript = TransferWithFeeProof::transcript_new(
&self.transfer_with_fee_pubkeys,
&self.ciphertext_lo,
&self.ciphertext_hi,
&self.new_source_ciphertext,
&self.fee_ciphertext_lo,
&self.fee_ciphertext_hi,
&self.context.transfer_with_fee_pubkeys,
&self.context.ciphertext_lo,
&self.context.ciphertext_hi,
&self.context.new_source_ciphertext,
&self.context.fee_ciphertext_lo,
&self.context.fee_ciphertext_hi,
);
let ciphertext_lo = self.ciphertext_lo.try_into()?;
let ciphertext_hi = self.ciphertext_hi.try_into()?;
let pubkeys_transfer_with_fee = self.transfer_with_fee_pubkeys.try_into()?;
let new_source_ciphertext = self.new_source_ciphertext.try_into()?;
let ciphertext_lo = self.context.ciphertext_lo.try_into()?;
let ciphertext_hi = self.context.ciphertext_hi.try_into()?;
let pubkeys_transfer_with_fee = self.context.transfer_with_fee_pubkeys.try_into()?;
let new_source_ciphertext = self.context.new_source_ciphertext.try_into()?;
let fee_ciphertext_lo = self.fee_ciphertext_lo.try_into()?;
let fee_ciphertext_hi = self.fee_ciphertext_hi.try_into()?;
let fee_parameters = self.fee_parameters.into();
let fee_ciphertext_lo = self.context.fee_ciphertext_lo.try_into()?;
let fee_ciphertext_hi = self.context.fee_ciphertext_hi.try_into()?;
let fee_parameters = self.context.fee_parameters.into();
self.proof.verify(
&ciphertext_lo,
@ -886,7 +903,7 @@ mod test {
)
.unwrap();
assert!(fee_data.verify().is_ok());
assert!(fee_data.verify_proof().is_ok());
// Case 2: transfer max amount
let spendable_balance: u64 = u64::max_value();
@ -910,7 +927,7 @@ mod test {
)
.unwrap();
assert!(fee_data.verify().is_ok());
assert!(fee_data.verify_proof().is_ok());
// Case 3: general success case
let spendable_balance: u64 = 120;
@ -933,7 +950,7 @@ mod test {
)
.unwrap();
assert!(fee_data.verify().is_ok());
assert!(fee_data.verify_proof().is_ok());
// Case 4: invalid destination, auditor, or withdraw authority pubkeys
let spendable_balance: u64 = 120;
@ -961,7 +978,7 @@ mod test {
)
.unwrap();
assert!(fee_data.verify().is_err());
assert!(fee_data.verify_proof().is_err());
// auditor pubkey invalid
let destination_pubkey: ElGamalPubkey = ElGamalKeypair::new_rand().public;
@ -978,7 +995,7 @@ mod test {
)
.unwrap();
assert!(fee_data.verify().is_err());
assert!(fee_data.verify_proof().is_err());
// withdraw authority invalid
let destination_pubkey: ElGamalPubkey = ElGamalKeypair::new_rand().public;
@ -995,6 +1012,6 @@ mod test {
)
.unwrap();
assert!(fee_data.verify().is_err());
assert!(fee_data.verify_proof().is_err());
}
}

View File

@ -1,7 +1,3 @@
use {
crate::zk_token_elgamal::pod,
bytemuck::{Pod, Zeroable},
};
#[cfg(not(target_os = "solana"))]
use {
crate::{
@ -10,7 +6,6 @@ use {
pedersen::{Pedersen, PedersenCommitment},
},
errors::ProofError,
instruction::Verifiable,
range_proof::RangeProof,
sigma_proofs::equality_proof::CtxtCommEqualityProof,
transcript::TranscriptProtocol,
@ -18,6 +13,13 @@ use {
merlin::Transcript,
std::convert::TryInto,
};
use {
crate::{
instruction::{ProofType, ZkProofData},
zk_token_elgamal::pod,
},
bytemuck::{Pod, Zeroable},
};
#[cfg(not(target_os = "solana"))]
const WITHDRAW_AMOUNT_BIT_LENGTH: usize = 64;
@ -32,15 +34,22 @@ const WITHDRAW_AMOUNT_BIT_LENGTH: usize = 64;
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct WithdrawData {
/// The context data for the withdraw proof
pub context: WithdrawProofContext, // 128 bytes
/// Range proof
pub proof: WithdrawProof, // 736 bytes
}
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct WithdrawProofContext {
/// The source account ElGamal pubkey
pub pubkey: pod::ElGamalPubkey, // 32 bytes
/// The source account available balance *after* the withdraw (encrypted by
/// `source_pk`
pub final_ciphertext: pod::ElGamalCiphertext, // 64 bytes
/// Range proof
pub proof: WithdrawProof, // 736 bytes
}
#[cfg(not(target_os = "solana"))]
@ -64,24 +73,33 @@ impl WithdrawData {
let pod_pubkey = pod::ElGamalPubkey(keypair.public.to_bytes());
let pod_final_ciphertext: pod::ElGamalCiphertext = final_ciphertext.into();
let context = WithdrawProofContext {
pubkey: pod_pubkey,
final_ciphertext: pod_final_ciphertext,
};
let mut transcript = WithdrawProof::transcript_new(&pod_pubkey, &pod_final_ciphertext);
let proof = WithdrawProof::new(keypair, final_balance, &final_ciphertext, &mut transcript);
Ok(Self {
pubkey: pod_pubkey,
final_ciphertext: pod_final_ciphertext,
proof,
})
Ok(Self { context, proof })
}
}
#[cfg(not(target_os = "solana"))]
impl Verifiable for WithdrawData {
fn verify(&self) -> Result<(), ProofError> {
let mut transcript = WithdrawProof::transcript_new(&self.pubkey, &self.final_ciphertext);
impl ZkProofData<WithdrawProofContext> for WithdrawData {
const PROOF_TYPE: ProofType = ProofType::Withdraw;
let elgamal_pubkey = self.pubkey.try_into()?;
let final_balance_ciphertext = self.final_ciphertext.try_into()?;
fn context_data(&self) -> &WithdrawProofContext {
&self.context
}
#[cfg(not(target_os = "solana"))]
fn verify_proof(&self) -> Result<(), ProofError> {
let mut transcript =
WithdrawProof::transcript_new(&self.context.pubkey, &self.context.final_ciphertext);
let elgamal_pubkey = self.context.pubkey.try_into()?;
let final_balance_ciphertext = self.context.final_ciphertext.try_into()?;
self.proof
.verify(&elgamal_pubkey, &final_balance_ciphertext, &mut transcript)
}
@ -200,7 +218,7 @@ mod test {
&current_ciphertext,
)
.unwrap();
assert!(data.verify().is_ok());
assert!(data.verify_proof().is_ok());
// generate and verify proof with wrong balance
let wrong_balance: u64 = 99;
@ -211,6 +229,6 @@ mod test {
&current_ciphertext,
)
.unwrap();
assert!(data.verify().is_err());
assert!(data.verify_proof().is_err());
}
}

View File

@ -1,7 +1,3 @@
use {
crate::zk_token_elgamal::pod,
bytemuck::{Pod, Zeroable},
};
#[cfg(not(target_os = "solana"))]
use {
crate::{
@ -10,13 +6,19 @@ use {
pedersen::PedersenOpening,
},
errors::ProofError,
instruction::Verifiable,
sigma_proofs::equality_proof::CtxtCtxtEqualityProof,
transcript::TranscriptProtocol,
},
merlin::Transcript,
std::convert::TryInto,
};
use {
crate::{
instruction::{ProofType, ZkProofData},
zk_token_elgamal::pod,
},
bytemuck::{Pod, Zeroable},
};
/// This struct includes the cryptographic proof *and* the account data information needed to verify
/// the proof
@ -28,17 +30,23 @@ use {
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct WithdrawWithheldTokensData {
pub withdraw_withheld_authority_pubkey: pod::ElGamalPubkey,
pub destination_pubkey: pod::ElGamalPubkey,
pub withdraw_withheld_authority_ciphertext: pod::ElGamalCiphertext,
pub destination_ciphertext: pod::ElGamalCiphertext,
pub context: WithdrawWithheldTokensProofContext,
pub proof: WithdrawWithheldTokensProof,
}
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct WithdrawWithheldTokensProofContext {
pub withdraw_withheld_authority_pubkey: pod::ElGamalPubkey, // 32 bytes
pub destination_pubkey: pod::ElGamalPubkey, // 32 bytes
pub withdraw_withheld_authority_ciphertext: pod::ElGamalCiphertext, // 64 bytes
pub destination_ciphertext: pod::ElGamalCiphertext, // 64 bytes
}
#[cfg(not(target_os = "solana"))]
impl WithdrawWithheldTokensData {
pub fn new(
@ -58,6 +66,13 @@ impl WithdrawWithheldTokensData {
pod::ElGamalCiphertext(withdraw_withheld_authority_ciphertext.to_bytes());
let pod_destination_ciphertext = pod::ElGamalCiphertext(destination_ciphertext.to_bytes());
let context = WithdrawWithheldTokensProofContext {
withdraw_withheld_authority_pubkey: pod_withdraw_withheld_authority_pubkey,
destination_pubkey: pod_destination_pubkey,
withdraw_withheld_authority_ciphertext: pod_withdraw_withheld_authority_ciphertext,
destination_ciphertext: pod_destination_ciphertext,
};
let mut transcript = WithdrawWithheldTokensProof::transcript_new(
&pod_withdraw_withheld_authority_pubkey,
&pod_destination_pubkey,
@ -74,32 +89,34 @@ impl WithdrawWithheldTokensData {
&mut transcript,
);
Ok(Self {
withdraw_withheld_authority_pubkey: pod_withdraw_withheld_authority_pubkey,
destination_pubkey: pod_destination_pubkey,
withdraw_withheld_authority_ciphertext: pod_withdraw_withheld_authority_ciphertext,
destination_ciphertext: pod_destination_ciphertext,
proof,
})
Ok(Self { context, proof })
}
}
#[cfg(not(target_os = "solana"))]
impl Verifiable for WithdrawWithheldTokensData {
fn verify(&self) -> Result<(), ProofError> {
impl ZkProofData<WithdrawWithheldTokensProofContext> for WithdrawWithheldTokensData {
const PROOF_TYPE: ProofType = ProofType::WithdrawWithheldTokens;
fn context_data(&self) -> &WithdrawWithheldTokensProofContext {
&self.context
}
#[cfg(not(target_os = "solana"))]
fn verify_proof(&self) -> Result<(), ProofError> {
let mut transcript = WithdrawWithheldTokensProof::transcript_new(
&self.withdraw_withheld_authority_pubkey,
&self.destination_pubkey,
&self.withdraw_withheld_authority_ciphertext,
&self.destination_ciphertext,
&self.context.withdraw_withheld_authority_pubkey,
&self.context.destination_pubkey,
&self.context.withdraw_withheld_authority_ciphertext,
&self.context.destination_ciphertext,
);
let withdraw_withheld_authority_pubkey =
self.withdraw_withheld_authority_pubkey.try_into()?;
let destination_pubkey = self.destination_pubkey.try_into()?;
let withdraw_withheld_authority_ciphertext =
self.withdraw_withheld_authority_ciphertext.try_into()?;
let destination_ciphertext = self.destination_ciphertext.try_into()?;
self.context.withdraw_withheld_authority_pubkey.try_into()?;
let destination_pubkey = self.context.destination_pubkey.try_into()?;
let withdraw_withheld_authority_ciphertext = self
.context
.withdraw_withheld_authority_ciphertext
.try_into()?;
let destination_ciphertext = self.context.destination_ciphertext.try_into()?;
self.proof.verify(
&withdraw_withheld_authority_pubkey,
@ -210,7 +227,7 @@ mod test {
)
.unwrap();
assert!(withdraw_withheld_tokens_data.verify().is_ok());
assert!(withdraw_withheld_tokens_data.verify_proof().is_ok());
let amount: u64 = 55;
let withdraw_withheld_authority_ciphertext =
@ -224,7 +241,7 @@ mod test {
)
.unwrap();
assert!(withdraw_withheld_tokens_data.verify().is_ok());
assert!(withdraw_withheld_tokens_data.verify_proof().is_ok());
let amount = u64::max_value();
let withdraw_withheld_authority_ciphertext =
@ -238,6 +255,6 @@ mod test {
)
.unwrap();
assert!(withdraw_withheld_tokens_data.verify().is_ok());
assert!(withdraw_withheld_tokens_data.verify_proof().is_ok());
}
}

View File

@ -37,3 +37,4 @@ pub mod instruction;
pub mod zk_token_elgamal;
pub mod zk_token_proof_instruction;
pub mod zk_token_proof_program;
pub mod zk_token_proof_state;

View File

@ -1,5 +1,10 @@
pub use bytemuck::{Pod, Zeroable};
use std::fmt;
use {
crate::zk_token_proof_instruction::ProofType,
num_traits::{FromPrimitive, ToPrimitive},
solana_program::instruction::InstructionError,
std::fmt,
};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Pod, Zeroable)]
#[repr(transparent)]
@ -29,6 +34,22 @@ impl From<PodU64> for u64 {
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Pod, Zeroable)]
#[repr(transparent)]
pub struct PodProofType(u8);
impl From<ProofType> for PodProofType {
fn from(proof_type: ProofType) -> Self {
Self(ToPrimitive::to_u8(&proof_type).unwrap())
}
}
impl TryFrom<PodProofType> for ProofType {
type Error = InstructionError;
fn try_from(pod: PodProofType) -> Result<Self, Self::Error> {
FromPrimitive::from_u8(pod.0).ok_or(Self::Error::InvalidAccountData)
}
}
#[derive(Clone, Copy, Pod, Zeroable, PartialEq, Eq)]
#[repr(transparent)]
pub struct CompressedRistretto(pub [u8; 32]);

View File

@ -1,18 +1,41 @@
///! Instructions provided by the ZkToken Proof program
pub use crate::instruction::*;
use {
bytemuck::{bytes_of, Pod},
bytemuck::bytes_of,
num_derive::{FromPrimitive, ToPrimitive},
num_traits::{FromPrimitive, ToPrimitive},
solana_program::instruction::Instruction,
solana_program::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
},
};
#[derive(Clone, Copy, Debug, FromPrimitive, ToPrimitive, PartialEq, Eq)]
#[repr(u8)]
pub enum ProofInstruction {
/// Verify a `CloseAccountData` struct
/// Close a zero-knowledge proof context state.
///
/// Accounts expected by this instruction:
/// 0. `[writable]` The proof context account to close
/// 1. `[writable]` The destination account for lamports
/// 2. `[signer]` The context account's owner
///
/// Data expected by this instruction:
/// None
///
CloseContextState,
/// Verify a close account zero-knowledge proof.
///
/// This instruction can be configured to optionally create a proof context state account.
///
/// Accounts expected by this instruction:
///
/// * Creating a proof context account
/// 0. `[writable]` The proof context account
/// 1. `[]` The proof context account owner
///
/// * Otherwise
/// None
///
/// Data expected by this instruction:
@ -20,9 +43,17 @@ pub enum ProofInstruction {
///
VerifyCloseAccount,
/// Verify a `WithdrawData` struct
/// Verify a withdraw zero-knowledge proof.
///
/// This instruction can be configured to optionally create a proof context state account.
///
/// Accounts expected by this instruction:
///
/// * Creating a proof context account
/// 0. `[writable]` The proof context account
/// 1. `[]` The proof context account owner
///
/// * Otherwise
/// None
///
/// Data expected by this instruction:
@ -30,9 +61,17 @@ pub enum ProofInstruction {
///
VerifyWithdraw,
/// Verify a `WithdrawWithheldTokensData` struct
/// Verify a withdraw withheld tokens zero-knowledge proof.
///
/// This instruction can be configured to optionally create a proof context state account.
///
/// Accounts expected by this instruction:
///
/// * Creating a proof context account
/// 0. `[writable]` The proof context account
/// 1. `[]` The proof context account owner
///
/// * Otherwise
/// None
///
/// Data expected by this instruction:
@ -40,9 +79,17 @@ pub enum ProofInstruction {
///
VerifyWithdrawWithheldTokens,
/// Verify a `TransferData` struct
/// Verify a transfer zero-knowledge proof.
///
/// This instruction can be configured to optionally create a proof context state account.
///
/// Accounts expected by this instruction:
///
/// * Creating a proof context account
/// 0. `[writable]` The proof context account
/// 1. `[]` The proof context account owner
///
/// * Otherwise
/// None
///
/// Data expected by this instruction:
@ -50,9 +97,17 @@ pub enum ProofInstruction {
///
VerifyTransfer,
/// Verify a `TransferWithFeeData` struct
/// Verify a transfer with fee zero-knowledge proof.
///
/// This instruction can be configured to optionally create a proof context state account.
///
/// Accounts expected by this instruction:
///
/// * Creating a proof context account
/// 0. `[writable]` The proof context account
/// 1. `[]` The proof context account owner
///
/// * Otherwise
/// None
///
/// Data expected by this instruction:
@ -60,9 +115,17 @@ pub enum ProofInstruction {
///
VerifyTransferWithFee,
/// Verify a `PubkeyValidityData` struct
/// Verify a pubkey validity zero-knowledge proof.
///
/// This instruction can be configured to optionally create a proof context state account.
///
/// Accounts expected by this instruction:
///
/// * Creating a proof context account
/// 0. `[writable]` The proof context account
/// 1. `[]` The proof context account owner
///
/// * Otherwise
/// None
///
/// Data expected by this instruction:
@ -71,50 +134,124 @@ pub enum ProofInstruction {
VerifyPubkeyValidity,
}
/// Pubkeys associated with a context state account to be used as parameters to functions.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ContextStateInfo<'a> {
pub context_state_account: &'a Pubkey,
pub context_state_authority: &'a Pubkey,
}
/// Create a `CloseContextState` instruction.
pub fn close_context_state(
context_state_info: ContextStateInfo,
destination_account: &Pubkey,
) -> Instruction {
let accounts = vec![
AccountMeta::new(*context_state_info.context_state_account, false),
AccountMeta::new(*destination_account, false),
AccountMeta::new_readonly(*context_state_info.context_state_authority, true),
];
let data = vec![ToPrimitive::to_u8(&ProofInstruction::CloseContextState).unwrap()];
Instruction {
program_id: crate::zk_token_proof_program::id(),
accounts,
data,
}
}
/// Create a `VerifyCloseAccount` instruction.
pub fn verify_close_account(
context_state_info: Option<ContextStateInfo>,
proof_data: &CloseAccountData,
) -> Instruction {
ProofInstruction::VerifyCloseAccount.encode_verify_proof(context_state_info, proof_data)
}
/// Create a `VerifyWithdraw` instruction.
pub fn verify_withdraw(
context_state_info: Option<ContextStateInfo>,
proof_data: &WithdrawData,
) -> Instruction {
ProofInstruction::VerifyWithdraw.encode_verify_proof(context_state_info, proof_data)
}
/// Create a `VerifyWithdrawWithheldTokens` instruction.
pub fn verify_withdraw_withheld_tokens(
context_state_info: Option<ContextStateInfo>,
proof_data: &WithdrawWithheldTokensData,
) -> Instruction {
ProofInstruction::VerifyWithdrawWithheldTokens
.encode_verify_proof(context_state_info, proof_data)
}
/// Create a `VerifyTransfer` instruction.
pub fn verify_transfer(
context_state_info: Option<ContextStateInfo>,
proof_data: &TransferData,
) -> Instruction {
ProofInstruction::VerifyTransfer.encode_verify_proof(context_state_info, proof_data)
}
/// Create a `VerifyTransferWithFee` instruction.
pub fn verify_transfer_with_fee(
context_state_info: Option<ContextStateInfo>,
proof_data: &TransferWithFeeData,
) -> Instruction {
ProofInstruction::VerifyTransferWithFee.encode_verify_proof(context_state_info, proof_data)
}
/// Create a `VerifyPubkeyValidity` instruction.
pub fn verify_pubkey_validity(
context_state_info: Option<ContextStateInfo>,
proof_data: &PubkeyValidityData,
) -> Instruction {
ProofInstruction::VerifyPubkeyValidity.encode_verify_proof(context_state_info, proof_data)
}
impl ProofInstruction {
pub fn encode<T: Pod>(&self, proof: &T) -> Instruction {
pub fn encode_verify_proof<T, U>(
&self,
context_state_info: Option<ContextStateInfo>,
proof_data: &T,
) -> Instruction
where
T: Pod + ZkProofData<U>,
U: Pod,
{
let accounts = if let Some(context_state_info) = context_state_info {
vec![
AccountMeta::new(*context_state_info.context_state_account, false),
AccountMeta::new_readonly(*context_state_info.context_state_authority, false),
]
} else {
vec![]
};
let mut data = vec![ToPrimitive::to_u8(self).unwrap()];
data.extend_from_slice(bytes_of(proof));
data.extend_from_slice(bytes_of(proof_data));
Instruction {
program_id: crate::zk_token_proof_program::id(),
accounts: vec![],
accounts,
data,
}
}
pub fn decode_type(input: &[u8]) -> Option<Self> {
input.first().and_then(|x| FromPrimitive::from_u8(*x))
pub fn instruction_type(input: &[u8]) -> Option<Self> {
input
.first()
.and_then(|instruction| FromPrimitive::from_u8(*instruction))
}
pub fn decode_data<T: Pod>(input: &[u8]) -> Option<&T> {
if input.is_empty() {
None
} else {
bytemuck::try_from_bytes(&input[1..]).ok()
}
pub fn proof_data<T, U>(input: &[u8]) -> Option<&T>
where
T: Pod + ZkProofData<U>,
U: Pod,
{
input
.get(1..)
.and_then(|data| bytemuck::try_from_bytes(data).ok())
}
}
pub fn verify_close_account(proof_data: &CloseAccountData) -> Instruction {
ProofInstruction::VerifyCloseAccount.encode(proof_data)
}
pub fn verify_withdraw(proof_data: &WithdrawData) -> Instruction {
ProofInstruction::VerifyWithdraw.encode(proof_data)
}
pub fn verify_withdraw_withheld_tokens(proof_data: &WithdrawWithheldTokensData) -> Instruction {
ProofInstruction::VerifyWithdrawWithheldTokens.encode(proof_data)
}
pub fn verify_transfer(proof_data: &TransferData) -> Instruction {
ProofInstruction::VerifyTransfer.encode(proof_data)
}
pub fn verify_transfer_with_fee(proof_data: &TransferWithFeeData) -> Instruction {
ProofInstruction::VerifyTransferWithFee.encode(proof_data)
}
pub fn verify_pubkey_validity(proof_data: &PubkeyValidityData) -> Instruction {
ProofInstruction::VerifyPubkeyValidity.encode(proof_data)
}

View File

@ -0,0 +1,72 @@
use {
crate::{zk_token_elgamal::pod::PodProofType, zk_token_proof_instruction::ProofType},
bytemuck::{bytes_of, Pod, Zeroable},
num_traits::ToPrimitive,
solana_program::{
instruction::{InstructionError, InstructionError::InvalidAccountData},
pubkey::Pubkey,
},
std::mem::size_of,
};
/// The proof context account state
#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(C)]
pub struct ProofContextState<T: Pod> {
/// The proof context authority that can close the account
pub context_state_authority: Pubkey,
/// The proof type for the context data
pub proof_type: PodProofType,
/// The proof context data
pub proof_context: T,
}
// `bytemuck::Pod` cannot be derived for generic structs unless the struct is marked
// `repr(packed)`, which may cause unnecessary complications when referencing its fields. Directly
// mark `ProofContextState` as `Zeroable` and `Pod` since since none of its fields has an alignment
// requirement greater than 1 and therefore, guaranteed to be `packed`.
unsafe impl<T: Pod> Zeroable for ProofContextState<T> {}
unsafe impl<T: Pod> Pod for ProofContextState<T> {}
impl<T: Pod> ProofContextState<T> {
pub fn encode(
context_state_authority: &Pubkey,
proof_type: ProofType,
proof_context: &T,
) -> Vec<u8> {
let mut buf = Vec::with_capacity(size_of::<Self>());
buf.extend_from_slice(context_state_authority.as_ref());
buf.push(ToPrimitive::to_u8(&proof_type).unwrap());
buf.extend_from_slice(bytes_of(proof_context));
buf
}
/// Interpret a slice as a `ProofContextState`.
///
/// This function requires a generic parameter. To access only the generic-independent fields
/// in `ProofContextState` without a generic parameter, use
/// `ProofContextStateMeta::try_from_bytes` instead.
pub fn try_from_bytes(input: &[u8]) -> Result<&Self, InstructionError> {
bytemuck::try_from_bytes(input).map_err(|_| InvalidAccountData)
}
}
/// The `ProofContextState` without the proof context itself. This struct exists to facilitate the
/// decoding of generic-independent fields in `ProofContextState`.
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
#[repr(C)]
pub struct ProofContextStateMeta {
/// The proof context authority that can close the account
pub context_state_authority: Pubkey,
/// The proof type for the context data
pub proof_type: PodProofType,
}
impl ProofContextStateMeta {
pub fn try_from_bytes(input: &[u8]) -> Result<&Self, InstructionError> {
input
.get(..size_of::<ProofContextStateMeta>())
.and_then(|data| bytemuck::try_from_bytes(data).ok())
.ok_or(InvalidAccountData)
}
}