#[cfg(not(target_os = "solana"))]
use {
crate::error::TokenError,
solana_sdk_ids::sysvar,
solana_zk_elgamal_proof_interface::{
instruction::ProofInstruction,
proof_data::{
BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data,
CiphertextCiphertextEqualityProofData, CiphertextCommitmentEqualityProofData,
},
},
spl_token_confidential_transfer_proof_extraction::instruction::{
process_proof_location, ProofLocation,
},
};
#[cfg(feature = "serde")]
use {
crate::serialization::{
aeciphertext_fromstr, elgamalciphertext_fromstr, elgamalpubkey_fromstr,
},
serde::{Deserialize, Serialize},
};
use {
crate::{
check_program_account,
extension::confidential_transfer::DecryptableBalance,
instruction::{encode_instruction, TokenInstruction},
},
alloc::{vec, vec::Vec},
bytemuck::{Pod, Zeroable},
num_enum::{IntoPrimitive, TryFromPrimitive},
solana_address::Address,
solana_instruction::{AccountMeta, Instruction},
solana_program_error::ProgramError,
solana_zk_sdk_pod::encryption::{
auth_encryption::PodAeCiphertext,
elgamal::{PodElGamalCiphertext, PodElGamalPubkey},
},
};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Debug, TryFromPrimitive, IntoPrimitive)]
#[repr(u8)]
pub enum ConfidentialMintBurnInstruction {
InitializeMint,
RotateSupplyElGamalPubkey,
UpdateDecryptableSupply,
Mint,
Burn,
ApplyPendingBurn,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
#[repr(C)]
pub struct InitializeMintData {
#[cfg_attr(feature = "serde", serde(with = "elgamalpubkey_fromstr"))]
pub supply_elgamal_pubkey: PodElGamalPubkey,
#[cfg_attr(feature = "serde", serde(with = "aeciphertext_fromstr"))]
pub decryptable_supply: PodAeCiphertext,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
#[repr(C)]
pub struct RotateSupplyElGamalPubkeyData {
#[cfg_attr(feature = "serde", serde(with = "elgamalpubkey_fromstr"))]
pub new_supply_elgamal_pubkey: PodElGamalPubkey,
pub proof_instruction_offset: i8,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
#[repr(C)]
pub struct UpdateDecryptableSupplyData {
#[cfg_attr(feature = "serde", serde(with = "aeciphertext_fromstr"))]
pub new_decryptable_supply: PodAeCiphertext,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
#[repr(C)]
pub struct MintInstructionData {
#[cfg_attr(feature = "serde", serde(with = "aeciphertext_fromstr"))]
pub new_decryptable_supply: PodAeCiphertext,
#[cfg_attr(feature = "serde", serde(with = "elgamalciphertext_fromstr"))]
pub mint_amount_auditor_ciphertext_lo: PodElGamalCiphertext,
#[cfg_attr(feature = "serde", serde(with = "elgamalciphertext_fromstr"))]
pub mint_amount_auditor_ciphertext_hi: PodElGamalCiphertext,
pub equality_proof_instruction_offset: i8,
pub ciphertext_validity_proof_instruction_offset: i8,
pub range_proof_instruction_offset: i8,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
#[repr(C)]
pub struct BurnInstructionData {
#[cfg_attr(feature = "serde", serde(with = "aeciphertext_fromstr"))]
pub new_decryptable_available_balance: DecryptableBalance,
#[cfg_attr(feature = "serde", serde(with = "elgamalciphertext_fromstr"))]
pub burn_amount_auditor_ciphertext_lo: PodElGamalCiphertext,
#[cfg_attr(feature = "serde", serde(with = "elgamalciphertext_fromstr"))]
pub burn_amount_auditor_ciphertext_hi: PodElGamalCiphertext,
pub equality_proof_instruction_offset: i8,
pub ciphertext_validity_proof_instruction_offset: i8,
pub range_proof_instruction_offset: i8,
}
pub fn initialize_mint(
token_program_id: &Address,
mint: &Address,
supply_elgamal_pubkey: &PodElGamalPubkey,
decryptable_supply: &DecryptableBalance,
) -> Result<Instruction, ProgramError> {
check_program_account(token_program_id)?;
let accounts = vec![AccountMeta::new(*mint, false)];
Ok(encode_instruction(
token_program_id,
accounts,
TokenInstruction::ConfidentialMintBurnExtension,
ConfidentialMintBurnInstruction::InitializeMint,
&InitializeMintData {
supply_elgamal_pubkey: *supply_elgamal_pubkey,
decryptable_supply: *decryptable_supply,
},
))
}
#[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "solana"))]
pub fn rotate_supply_elgamal_pubkey(
token_program_id: &Address,
mint: &Address,
authority: &Address,
multisig_signers: &[&Address],
new_supply_elgamal_pubkey: &PodElGamalPubkey,
ciphertext_equality_proof: ProofLocation<CiphertextCiphertextEqualityProofData>,
) -> Result<Vec<Instruction>, ProgramError> {
check_program_account(token_program_id)?;
let mut accounts = vec![AccountMeta::new(*mint, false)];
let mut expected_instruction_offset = 1;
let mut proof_instructions = vec![];
let proof_instruction_offset = process_proof_location(
&mut accounts,
&mut expected_instruction_offset,
&mut proof_instructions,
ciphertext_equality_proof,
true,
ProofInstruction::VerifyCiphertextCiphertextEquality,
)?;
accounts.push(AccountMeta::new_readonly(
*authority,
multisig_signers.is_empty(),
));
for multisig_signer in multisig_signers.iter() {
accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
}
let mut instructions = vec![encode_instruction(
token_program_id,
accounts,
TokenInstruction::ConfidentialMintBurnExtension,
ConfidentialMintBurnInstruction::RotateSupplyElGamalPubkey,
&RotateSupplyElGamalPubkeyData {
new_supply_elgamal_pubkey: *new_supply_elgamal_pubkey,
proof_instruction_offset,
},
)];
instructions.extend(proof_instructions);
Ok(instructions)
}
#[cfg(not(target_os = "solana"))]
pub fn update_decryptable_supply(
token_program_id: &Address,
mint: &Address,
authority: &Address,
multisig_signers: &[&Address],
new_decryptable_supply: &DecryptableBalance,
) -> Result<Instruction, ProgramError> {
check_program_account(token_program_id)?;
let mut accounts = vec![
AccountMeta::new(*mint, false),
AccountMeta::new_readonly(*authority, multisig_signers.is_empty()),
];
for multisig_signer in multisig_signers.iter() {
accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
}
Ok(encode_instruction(
token_program_id,
accounts,
TokenInstruction::ConfidentialMintBurnExtension,
ConfidentialMintBurnInstruction::UpdateDecryptableSupply,
&UpdateDecryptableSupplyData {
new_decryptable_supply: *new_decryptable_supply,
},
))
}
#[derive(Clone, Copy)]
pub struct MintSplitContextStateAccounts<'a> {
pub equality_proof: &'a Address,
pub ciphertext_validity_proof: &'a Address,
pub range_proof: &'a Address,
pub authority: &'a Address,
}
#[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "solana"))]
pub fn inner_confidential_mint(
token_program_id: &Address,
token_account: &Address,
mint: &Address,
mint_amount_auditor_ciphertext_lo: &PodElGamalCiphertext,
mint_amount_auditor_ciphertext_hi: &PodElGamalCiphertext,
authority: &Address,
multisig_signers: &[&Address],
equality_proof_location: ProofLocation<CiphertextCommitmentEqualityProofData>,
ciphertext_validity_proof_location: ProofLocation<
BatchedGroupedCiphertext3HandlesValidityProofData,
>,
range_proof_location: ProofLocation<BatchedRangeProofU128Data>,
new_decryptable_supply: &DecryptableBalance,
) -> Result<Instruction, ProgramError> {
check_program_account(token_program_id)?;
let mut accounts = vec![
AccountMeta::new(*token_account, false),
AccountMeta::new(*mint, false),
];
if equality_proof_location.is_instruction_offset()
|| ciphertext_validity_proof_location.is_instruction_offset()
|| range_proof_location.is_instruction_offset()
{
accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false));
}
let equality_proof_instruction_offset = match equality_proof_location {
ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
proof_instruction_offset.into()
}
ProofLocation::ContextStateAccount(context_state_account) => {
accounts.push(AccountMeta::new_readonly(*context_state_account, false));
0
}
};
let ciphertext_validity_proof_instruction_offset = match ciphertext_validity_proof_location {
ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
proof_instruction_offset.into()
}
ProofLocation::ContextStateAccount(context_state_account) => {
accounts.push(AccountMeta::new_readonly(*context_state_account, false));
0
}
};
let range_proof_instruction_offset = match range_proof_location {
ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
proof_instruction_offset.into()
}
ProofLocation::ContextStateAccount(context_state_account) => {
accounts.push(AccountMeta::new_readonly(*context_state_account, false));
0
}
};
accounts.push(AccountMeta::new_readonly(
*authority,
multisig_signers.is_empty(),
));
for multisig_signer in multisig_signers.iter() {
accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
}
Ok(encode_instruction(
token_program_id,
accounts,
TokenInstruction::ConfidentialMintBurnExtension,
ConfidentialMintBurnInstruction::Mint,
&MintInstructionData {
new_decryptable_supply: *new_decryptable_supply,
mint_amount_auditor_ciphertext_lo: *mint_amount_auditor_ciphertext_lo,
mint_amount_auditor_ciphertext_hi: *mint_amount_auditor_ciphertext_hi,
equality_proof_instruction_offset,
ciphertext_validity_proof_instruction_offset,
range_proof_instruction_offset,
},
))
}
#[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "solana"))]
pub fn confidential_mint_with_split_proofs(
token_program_id: &Address,
token_account: &Address,
mint: &Address,
mint_amount_auditor_ciphertext_lo: &PodElGamalCiphertext,
mint_amount_auditor_ciphertext_hi: &PodElGamalCiphertext,
authority: &Address,
multisig_signers: &[&Address],
equality_proof_location: ProofLocation<CiphertextCommitmentEqualityProofData>,
ciphertext_validity_proof_location: ProofLocation<
BatchedGroupedCiphertext3HandlesValidityProofData,
>,
range_proof_location: ProofLocation<BatchedRangeProofU128Data>,
new_decryptable_supply: &DecryptableBalance,
) -> Result<Vec<Instruction>, ProgramError> {
let mut instructions = vec![inner_confidential_mint(
token_program_id,
token_account,
mint,
mint_amount_auditor_ciphertext_lo,
mint_amount_auditor_ciphertext_hi,
authority,
multisig_signers,
equality_proof_location,
ciphertext_validity_proof_location,
range_proof_location,
new_decryptable_supply,
)?];
let mut expected_instruction_offset = 1;
if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
equality_proof_location
{
let proof_instruction_offset: i8 = proof_instruction_offset.into();
if proof_instruction_offset != expected_instruction_offset {
return Err(TokenError::InvalidProofInstructionOffset.into());
}
instructions.push(
ProofInstruction::VerifyCiphertextCommitmentEquality
.encode_verify_proof(None, proof_data),
);
expected_instruction_offset += 1;
}
if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
ciphertext_validity_proof_location
{
let proof_instruction_offset: i8 = proof_instruction_offset.into();
if proof_instruction_offset != expected_instruction_offset {
return Err(TokenError::InvalidProofInstructionOffset.into());
}
instructions.push(
ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity
.encode_verify_proof(None, proof_data),
);
expected_instruction_offset += 1;
}
if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
range_proof_location
{
let proof_instruction_offset: i8 = proof_instruction_offset.into();
if proof_instruction_offset != expected_instruction_offset {
return Err(TokenError::InvalidProofInstructionOffset.into());
}
instructions.push(
ProofInstruction::VerifyBatchedRangeProofU128.encode_verify_proof(None, proof_data),
);
}
Ok(instructions)
}
#[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "solana"))]
pub fn inner_confidential_burn(
token_program_id: &Address,
token_account: &Address,
mint: &Address,
new_decryptable_available_balance: &DecryptableBalance,
burn_amount_auditor_ciphertext_lo: &PodElGamalCiphertext,
burn_amount_auditor_ciphertext_hi: &PodElGamalCiphertext,
authority: &Address,
multisig_signers: &[&Address],
equality_proof_location: ProofLocation<CiphertextCommitmentEqualityProofData>,
ciphertext_validity_proof_location: ProofLocation<
BatchedGroupedCiphertext3HandlesValidityProofData,
>,
range_proof_location: ProofLocation<BatchedRangeProofU128Data>,
) -> Result<Instruction, ProgramError> {
check_program_account(token_program_id)?;
let mut accounts = vec![
AccountMeta::new(*token_account, false),
AccountMeta::new(*mint, false),
];
if equality_proof_location.is_instruction_offset()
|| ciphertext_validity_proof_location.is_instruction_offset()
|| range_proof_location.is_instruction_offset()
{
accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false));
}
let equality_proof_instruction_offset = match equality_proof_location {
ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
proof_instruction_offset.into()
}
ProofLocation::ContextStateAccount(context_state_account) => {
accounts.push(AccountMeta::new_readonly(*context_state_account, false));
0
}
};
let ciphertext_validity_proof_instruction_offset = match ciphertext_validity_proof_location {
ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
proof_instruction_offset.into()
}
ProofLocation::ContextStateAccount(context_state_account) => {
accounts.push(AccountMeta::new_readonly(*context_state_account, false));
0
}
};
let range_proof_instruction_offset = match range_proof_location {
ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
proof_instruction_offset.into()
}
ProofLocation::ContextStateAccount(context_state_account) => {
accounts.push(AccountMeta::new_readonly(*context_state_account, false));
0
}
};
accounts.push(AccountMeta::new_readonly(
*authority,
multisig_signers.is_empty(),
));
for multisig_signer in multisig_signers.iter() {
accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
}
Ok(encode_instruction(
token_program_id,
accounts,
TokenInstruction::ConfidentialMintBurnExtension,
ConfidentialMintBurnInstruction::Burn,
&BurnInstructionData {
new_decryptable_available_balance: *new_decryptable_available_balance,
burn_amount_auditor_ciphertext_lo: *burn_amount_auditor_ciphertext_lo,
burn_amount_auditor_ciphertext_hi: *burn_amount_auditor_ciphertext_hi,
equality_proof_instruction_offset,
ciphertext_validity_proof_instruction_offset,
range_proof_instruction_offset,
},
))
}
#[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "solana"))]
pub fn confidential_burn_with_split_proofs(
token_program_id: &Address,
token_account: &Address,
mint: &Address,
new_decryptable_available_balance: &DecryptableBalance,
burn_amount_auditor_ciphertext_lo: &PodElGamalCiphertext,
burn_amount_auditor_ciphertext_hi: &PodElGamalCiphertext,
authority: &Address,
multisig_signers: &[&Address],
equality_proof_location: ProofLocation<CiphertextCommitmentEqualityProofData>,
ciphertext_validity_proof_location: ProofLocation<
BatchedGroupedCiphertext3HandlesValidityProofData,
>,
range_proof_location: ProofLocation<BatchedRangeProofU128Data>,
) -> Result<Vec<Instruction>, ProgramError> {
let mut instructions = vec![inner_confidential_burn(
token_program_id,
token_account,
mint,
new_decryptable_available_balance,
burn_amount_auditor_ciphertext_lo,
burn_amount_auditor_ciphertext_hi,
authority,
multisig_signers,
equality_proof_location,
ciphertext_validity_proof_location,
range_proof_location,
)?];
let mut expected_instruction_offset = 1;
if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
equality_proof_location
{
let proof_instruction_offset: i8 = proof_instruction_offset.into();
if proof_instruction_offset != expected_instruction_offset {
return Err(TokenError::InvalidProofInstructionOffset.into());
}
instructions.push(
ProofInstruction::VerifyCiphertextCommitmentEquality
.encode_verify_proof(None, proof_data),
);
expected_instruction_offset += 1;
}
if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
ciphertext_validity_proof_location
{
let proof_instruction_offset: i8 = proof_instruction_offset.into();
if proof_instruction_offset != expected_instruction_offset {
return Err(TokenError::InvalidProofInstructionOffset.into());
}
instructions.push(
ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity
.encode_verify_proof(None, proof_data),
);
expected_instruction_offset += 1;
}
if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
range_proof_location
{
let proof_instruction_offset: i8 = proof_instruction_offset.into();
if proof_instruction_offset != expected_instruction_offset {
return Err(TokenError::InvalidProofInstructionOffset.into());
}
instructions.push(
ProofInstruction::VerifyBatchedRangeProofU128.encode_verify_proof(None, proof_data),
);
}
Ok(instructions)
}
pub fn apply_pending_burn(
token_program_id: &Address,
mint: &Address,
authority: &Address,
multisig_signers: &[&Address],
) -> Result<Instruction, ProgramError> {
check_program_account(token_program_id)?;
let mut accounts = vec![
AccountMeta::new(*mint, false),
AccountMeta::new_readonly(*authority, multisig_signers.is_empty()),
];
for multisig_signer in multisig_signers.iter() {
accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
}
Ok(encode_instruction(
token_program_id,
accounts,
TokenInstruction::ConfidentialMintBurnExtension,
ConfidentialMintBurnInstruction::ApplyPendingBurn,
&(),
))
}