Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 9ef1a74

Browse files
committed
create ElGamal registry program
1 parent 1b80f81 commit 9ef1a74

File tree

7 files changed

+324
-0
lines changed

7 files changed

+324
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ members = [
7575
"token/confidential-transfer/proof-extraction",
7676
"token/confidential-transfer/proof-generation",
7777
"token/confidential-transfer/proof-tests",
78+
"token/confidential-transfer/elgamal-registry",
7879
"token/client",
7980
"utils/cgen",
8081
"utils/test-client",
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[package]
2+
name = "spl-elgamal-registry"
3+
version = "0.1.0"
4+
description = "Solana ElGamal Registry"
5+
authors = ["Solana Labs Maintainers <[email protected]>"]
6+
repository = "https://github.com/solana-labs/solana-program-library"
7+
license = "Apache-2.0"
8+
edition = "2021"
9+
10+
[features]
11+
no-entrypoint = []
12+
test-sbf = []
13+
14+
[dependencies]
15+
bytemuck = { version = "1.18.0", features = ["derive"] }
16+
solana-program = "2.0.3"
17+
solana-zk-sdk = "2.0.3"
18+
spl-pod = { version = "0.4.0", path = "../../../libraries/pod" }
19+
spl-token-confidential-transfer-proof-extraction = { version = "0.1.0", path = "../proof-extraction" }
20+
21+
[lib]
22+
crate-type = ["cdylib", "lib"]
23+
24+
[package.metadata.docs.rs]
25+
targets = ["x86_64-unknown-linux-gnu"]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//! Program entrypoint
2+
3+
#![cfg(all(target_os = "solana", not(feature = "no-entrypoint")))]
4+
5+
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey};
6+
7+
solana_program::entrypoint!(process_instruction);
8+
fn process_instruction(
9+
program_id: &Pubkey,
10+
accounts: &[AccountInfo],
11+
instruction_data: &[u8],
12+
) -> ProgramResult {
13+
crate::processor::process_instruction(program_id, accounts, instruction_data)
14+
}
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
use {
2+
crate::id,
3+
solana_program::{
4+
instruction::{AccountMeta, Instruction},
5+
program_error::ProgramError,
6+
pubkey::{Pubkey, PUBKEY_BYTES},
7+
sysvar,
8+
},
9+
solana_zk_sdk::zk_elgamal_proof_program::{
10+
instruction::ProofInstruction, proof_data::PubkeyValidityProofData,
11+
},
12+
spl_token_confidential_transfer_proof_extraction::{ProofData, ProofLocation},
13+
};
14+
15+
#[derive(Clone, Debug, PartialEq)]
16+
#[repr(u8)]
17+
pub enum RegistryInstruction {
18+
/// Initialize an ElGamal public key registry.
19+
///
20+
/// 0. `[writable]` The account to initialize
21+
/// 1. `[]` Instructions sysvar if `VerifyPubkeyValidity` is included in the
22+
/// same transaction or context state account if `VerifyPubkeyValidity`
23+
/// is pre-verified into a context state account.
24+
/// 2. `[]` (Optional) Record account if the accompanying proof is to be
25+
/// read from a record account.
26+
CreateRegistry {
27+
/// The owner of the ElGamal registry account
28+
owner: Pubkey,
29+
/// Relative location of the `ProofInstruction::PubkeyValidityProof`
30+
/// instruction to the `CreateElGamalRegistry` instruction in the
31+
/// transaction. If the offset is `0`, then use a context state account
32+
/// for the proof.
33+
proof_instruction_offset: i8,
34+
},
35+
/// Update an ElGamal public key registry with a new ElGamal public key.
36+
///
37+
/// 0. `[writable]` The account to initialize
38+
/// 1. `[signer]` The owner of the ElGamal public key registry
39+
/// 2. `[]` Instructions sysvar if `VerifyPubkeyValidity` is included in the
40+
/// same transaction or context state account if `VerifyPubkeyValidity`
41+
/// is pre-verified into a context state account.
42+
/// 3. `[]` (Optional) Record account if the accompanying proof is to be
43+
/// read from a record account.
44+
UpdateRegistry {
45+
/// Relative location of the `ProofInstruction::PubkeyValidityProof`
46+
/// instruction to the `UpdateElGamalRegistry` instruction in the
47+
/// transaction. If the offset is `0`, then use a context state account
48+
/// for the proof.
49+
proof_instruction_offset: i8,
50+
},
51+
}
52+
53+
impl RegistryInstruction {
54+
/// Unpacks a byte buffer into a `RegistryInstruction`
55+
pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
56+
let (&tag, rest) = input
57+
.split_first()
58+
.ok_or(ProgramError::InvalidInstructionData)?;
59+
60+
Ok(match tag {
61+
0 => {
62+
let owner = rest
63+
.get(..PUBKEY_BYTES)
64+
.and_then(|x| Pubkey::try_from(x).ok())
65+
.ok_or(ProgramError::InvalidInstructionData)?;
66+
let proof_instruction_offset =
67+
*rest.first().ok_or(ProgramError::InvalidInstructionData)?;
68+
Self::CreateRegistry {
69+
owner,
70+
proof_instruction_offset: proof_instruction_offset as i8,
71+
}
72+
}
73+
2 => {
74+
let proof_instruction_offset =
75+
*rest.first().ok_or(ProgramError::InvalidInstructionData)?;
76+
Self::UpdateRegistry {
77+
proof_instruction_offset: proof_instruction_offset as i8,
78+
}
79+
}
80+
_ => return Err(ProgramError::InvalidInstructionData),
81+
})
82+
}
83+
84+
/// Packs a `RegistryInstruction` into a byte buffer.
85+
pub fn pack(&self) -> Vec<u8> {
86+
let mut buf = vec![];
87+
match self {
88+
Self::CreateRegistry {
89+
owner,
90+
proof_instruction_offset,
91+
} => {
92+
buf.push(0);
93+
buf.extend_from_slice(owner.as_ref());
94+
buf.extend_from_slice(&proof_instruction_offset.to_le_bytes());
95+
}
96+
Self::UpdateRegistry {
97+
proof_instruction_offset,
98+
} => {
99+
buf.push(1);
100+
buf.extend_from_slice(&proof_instruction_offset.to_le_bytes());
101+
}
102+
};
103+
buf
104+
}
105+
}
106+
107+
/// Create a `RegistryInstruction::CreateRegistry` instruction
108+
pub fn create_registry(
109+
registry_account: &Pubkey,
110+
owner: &Pubkey,
111+
proof_location: ProofLocation<PubkeyValidityProofData>,
112+
) -> Result<Vec<Instruction>, ProgramError> {
113+
let mut accounts = vec![AccountMeta::new(*registry_account, false)];
114+
let proof_instruction_offset = proof_instruction_offset(&mut accounts, proof_location);
115+
116+
let registry_instruction = Instruction {
117+
program_id: id(),
118+
accounts,
119+
data: RegistryInstruction::CreateRegistry {
120+
owner: *owner,
121+
proof_instruction_offset,
122+
}
123+
.pack(),
124+
};
125+
append_zk_elgamal_proof(registry_instruction, proof_location)
126+
}
127+
128+
/// Create a `RegistryInstruction::UpdateRegistry` instruction
129+
pub fn update_registry(
130+
registry_account: &Pubkey,
131+
owner: &Pubkey,
132+
proof_location: ProofLocation<PubkeyValidityProofData>,
133+
) -> Result<Vec<Instruction>, ProgramError> {
134+
let mut accounts = vec![
135+
AccountMeta::new(*registry_account, false),
136+
AccountMeta::new_readonly(*owner, true),
137+
];
138+
let proof_instruction_offset = proof_instruction_offset(&mut accounts, proof_location);
139+
140+
let registry_instruction = Instruction {
141+
program_id: id(),
142+
accounts,
143+
data: RegistryInstruction::UpdateRegistry {
144+
proof_instruction_offset,
145+
}
146+
.pack(),
147+
};
148+
append_zk_elgamal_proof(registry_instruction, proof_location)
149+
}
150+
151+
/// Takes a `ProofLocation`, updates the list of accounts, and returns a
152+
/// suitable proof location
153+
fn proof_instruction_offset(
154+
accounts: &mut Vec<AccountMeta>,
155+
proof_location: ProofLocation<PubkeyValidityProofData>,
156+
) -> i8 {
157+
match proof_location {
158+
ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => {
159+
accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false));
160+
if let ProofData::RecordAccount(record_address, _) = proof_data {
161+
accounts.push(AccountMeta::new_readonly(*record_address, false));
162+
}
163+
proof_instruction_offset.into()
164+
}
165+
ProofLocation::ContextStateAccount(context_state_account) => {
166+
accounts.push(AccountMeta::new_readonly(*context_state_account, false));
167+
0
168+
}
169+
}
170+
}
171+
172+
/// Takes a `RegistryInstruction` and appends the pubkey validity proof
173+
/// instruction
174+
fn append_zk_elgamal_proof(
175+
registry_instruction: Instruction,
176+
proof_data_location: ProofLocation<PubkeyValidityProofData>,
177+
) -> Result<Vec<Instruction>, ProgramError> {
178+
let mut instructions = vec![registry_instruction];
179+
180+
if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
181+
proof_data_location
182+
{
183+
let proof_instruction_offset: i8 = proof_instruction_offset.into();
184+
if proof_instruction_offset != 1 {
185+
return Err(ProgramError::InvalidArgument);
186+
}
187+
match proof_data {
188+
ProofData::InstructionData(data) => instructions
189+
.push(ProofInstruction::VerifyPubkeyValidity.encode_verify_proof(None, data)),
190+
ProofData::RecordAccount(address, offset) => instructions.push(
191+
ProofInstruction::VerifyPubkeyValidity
192+
.encode_verify_proof_from_account(None, address, offset),
193+
),
194+
}
195+
}
196+
Ok(instructions)
197+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
mod entrypoint;
2+
pub mod error;
3+
pub mod instruction;
4+
pub mod processor;
5+
pub mod state;
6+
7+
solana_program::declare_id!("regVYJW7tcT8zipN5YiBvHsvR5jXW1uLFxaHSbugABg");
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use {
2+
crate::{instruction::RegistryInstruction, state::ElGamalRegistry},
3+
solana_program::{
4+
account_info::{next_account_info, AccountInfo},
5+
entrypoint::ProgramResult,
6+
program_error::ProgramError,
7+
pubkey::Pubkey,
8+
},
9+
solana_zk_sdk::zk_elgamal_proof_program::proof_data::pubkey_validity::{
10+
PubkeyValidityProofContext, PubkeyValidityProofData,
11+
},
12+
spl_pod::bytemuck::pod_from_bytes_mut,
13+
spl_token_confidential_transfer_proof_extraction::verify_and_extract_context,
14+
};
15+
16+
/// Instruction processor
17+
pub fn process_instruction(
18+
_program_id: &Pubkey,
19+
accounts: &[AccountInfo],
20+
input: &[u8],
21+
) -> ProgramResult {
22+
let instruction = RegistryInstruction::unpack(input)?;
23+
let account_info_iter = &mut accounts.iter();
24+
let registry_account_info = next_account_info(account_info_iter)?;
25+
let registry_account_data = &mut registry_account_info.data.borrow_mut();
26+
let registry_account = pod_from_bytes_mut::<ElGamalRegistry>(registry_account_data)?;
27+
28+
let proof_instruction_offset = match instruction {
29+
RegistryInstruction::CreateRegistry {
30+
owner,
31+
proof_instruction_offset,
32+
} => {
33+
// set the owner; ElGamal pubkey is set after the zkp verification below
34+
registry_account.owner = owner;
35+
proof_instruction_offset
36+
}
37+
RegistryInstruction::UpdateRegistry {
38+
proof_instruction_offset,
39+
} => {
40+
// check the owner; ElGamal pubkey is set after the zkp verification below
41+
let owner_info = next_account_info(account_info_iter)?;
42+
validate_owner(owner_info, &registry_account.owner)?;
43+
proof_instruction_offset
44+
}
45+
};
46+
// zero-knowledge proof certifies that the supplied ElGamal public key is valid
47+
let proof_context = verify_and_extract_context::<
48+
PubkeyValidityProofData,
49+
PubkeyValidityProofContext,
50+
>(account_info_iter, proof_instruction_offset as i64, None)?;
51+
registry_account.elgamal_pubkey = proof_context.pubkey;
52+
53+
Ok(())
54+
}
55+
56+
fn validate_owner(owner_info: &AccountInfo, expected_owner: &Pubkey) -> ProgramResult {
57+
if expected_owner != owner_info.key {
58+
return Err(ProgramError::InvalidAccountOwner);
59+
}
60+
if !owner_info.is_signer {
61+
return Err(ProgramError::MissingRequiredSignature);
62+
}
63+
Ok(())
64+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use {
2+
bytemuck::{Pod, Zeroable},
3+
solana_program::pubkey::Pubkey,
4+
solana_zk_sdk::encryption::pod::elgamal::PodElGamalPubkey,
5+
};
6+
7+
/// ElGamal public key registry. It contains an ElGamal public key that is
8+
/// associated with a wallet account, but independent of any specific mint.
9+
#[repr(C)]
10+
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
11+
pub struct ElGamalRegistry {
12+
/// The owner of the registry
13+
pub owner: Pubkey,
14+
/// The ElGamal public key associated with an account
15+
pub elgamal_pubkey: PodElGamalPubkey,
16+
}

0 commit comments

Comments
 (0)