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

Commit ba60c3d

Browse files
committed
create ElGamal registry program
1 parent eef0666 commit ba60c3d

File tree

7 files changed

+473
-0
lines changed

7 files changed

+473
-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: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
use {
2+
crate::{get_elgamal_registry_address, id},
3+
solana_program::{
4+
instruction::{AccountMeta, Instruction},
5+
program_error::ProgramError,
6+
pubkey::Pubkey,
7+
system_program, 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. `[writeable, signer]` The funding account (must be a system account)
21+
/// 1. `[writable]` The account to be created
22+
/// 2. `[]` The wallet address (will also be the owner address for the
23+
/// registry account)
24+
/// 3. `[]` System program
25+
/// 4. `[]` Instructions sysvar if `VerifyPubkeyValidity` is included in the
26+
/// same transaction or context state account if `VerifyPubkeyValidity`
27+
/// is pre-verified into a context state account.
28+
/// 5. `[]` (Optional) Record account if the accompanying proof is to be
29+
/// read from a record account.
30+
CreateRegistry {
31+
/// Relative location of the `ProofInstruction::PubkeyValidityProof`
32+
/// instruction to the `CreateElGamalRegistry` instruction in the
33+
/// transaction. If the offset is `0`, then use a context state account
34+
/// for the proof.
35+
proof_instruction_offset: i8,
36+
},
37+
/// Update an ElGamal public key registry with a new ElGamal public key.
38+
///
39+
/// 0. `[writable]` The ElGamal registry account
40+
/// 1. `[]` Instructions sysvar if `VerifyPubkeyValidity` is included in the
41+
/// same transaction or context state account if `VerifyPubkeyValidity`
42+
/// is pre-verified into a context state account.
43+
/// 2. `[]` (Optional) Record account if the accompanying proof is to be
44+
/// read from a record account.
45+
/// 3. `[signer]` The owner of the ElGamal public key registry
46+
UpdateRegistry {
47+
/// Relative location of the `ProofInstruction::PubkeyValidityProof`
48+
/// instruction to the `UpdateElGamalRegistry` instruction in the
49+
/// transaction. If the offset is `0`, then use a context state account
50+
/// for the proof.
51+
proof_instruction_offset: i8,
52+
},
53+
}
54+
55+
impl RegistryInstruction {
56+
/// Unpacks a byte buffer into a `RegistryInstruction`
57+
pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
58+
let (&tag, rest) = input
59+
.split_first()
60+
.ok_or(ProgramError::InvalidInstructionData)?;
61+
62+
Ok(match tag {
63+
0 => {
64+
let proof_instruction_offset =
65+
*rest.first().ok_or(ProgramError::InvalidInstructionData)?;
66+
Self::CreateRegistry {
67+
proof_instruction_offset: proof_instruction_offset as i8,
68+
}
69+
}
70+
1 => {
71+
let proof_instruction_offset =
72+
*rest.first().ok_or(ProgramError::InvalidInstructionData)?;
73+
Self::UpdateRegistry {
74+
proof_instruction_offset: proof_instruction_offset as i8,
75+
}
76+
}
77+
_ => return Err(ProgramError::InvalidInstructionData),
78+
})
79+
}
80+
81+
/// Packs a `RegistryInstruction` into a byte buffer.
82+
pub fn pack(&self) -> Vec<u8> {
83+
let mut buf = vec![];
84+
match self {
85+
Self::CreateRegistry {
86+
proof_instruction_offset,
87+
} => {
88+
buf.push(0);
89+
buf.extend_from_slice(&proof_instruction_offset.to_le_bytes());
90+
}
91+
Self::UpdateRegistry {
92+
proof_instruction_offset,
93+
} => {
94+
buf.push(1);
95+
buf.extend_from_slice(&proof_instruction_offset.to_le_bytes());
96+
}
97+
};
98+
buf
99+
}
100+
}
101+
102+
/// Create a `RegistryInstruction::CreateRegistry` instruction
103+
pub fn create_registry(
104+
funding_address: &Pubkey,
105+
owner_address: &Pubkey,
106+
proof_location: ProofLocation<PubkeyValidityProofData>,
107+
) -> Result<Vec<Instruction>, ProgramError> {
108+
let elgamal_registry_address = get_elgamal_registry_address(owner_address, &id());
109+
110+
let mut accounts = vec![
111+
AccountMeta::new(*funding_address, true),
112+
AccountMeta::new(elgamal_registry_address, false),
113+
AccountMeta::new_readonly(*owner_address, false),
114+
AccountMeta::new_readonly(system_program::id(), false),
115+
];
116+
let proof_instruction_offset = proof_instruction_offset(&mut accounts, proof_location);
117+
118+
let registry_instruction = Instruction {
119+
program_id: id(),
120+
accounts,
121+
data: RegistryInstruction::CreateRegistry {
122+
proof_instruction_offset,
123+
}
124+
.pack(),
125+
};
126+
append_zk_elgamal_proof(registry_instruction, proof_location)
127+
}
128+
129+
/// Create a `RegistryInstruction::UpdateRegistry` instruction
130+
pub fn update_registry(
131+
registry_account: &Pubkey,
132+
owner: &Pubkey,
133+
proof_location: ProofLocation<PubkeyValidityProofData>,
134+
) -> Result<Vec<Instruction>, ProgramError> {
135+
let mut accounts = vec![AccountMeta::new(*registry_account, false)];
136+
let proof_instruction_offset = proof_instruction_offset(&mut accounts, proof_location);
137+
accounts.push(AccountMeta::new_readonly(*owner, true));
138+
139+
let registry_instruction = Instruction {
140+
program_id: id(),
141+
accounts,
142+
data: RegistryInstruction::UpdateRegistry {
143+
proof_instruction_offset,
144+
}
145+
.pack(),
146+
};
147+
append_zk_elgamal_proof(registry_instruction, proof_location)
148+
}
149+
150+
/// Takes a `ProofLocation`, updates the list of accounts, and returns a
151+
/// suitable proof location
152+
fn proof_instruction_offset(
153+
accounts: &mut Vec<AccountMeta>,
154+
proof_location: ProofLocation<PubkeyValidityProofData>,
155+
) -> i8 {
156+
match proof_location {
157+
ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => {
158+
accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false));
159+
if let ProofData::RecordAccount(record_address, _) = proof_data {
160+
accounts.push(AccountMeta::new_readonly(*record_address, false));
161+
}
162+
proof_instruction_offset.into()
163+
}
164+
ProofLocation::ContextStateAccount(context_state_account) => {
165+
accounts.push(AccountMeta::new_readonly(*context_state_account, false));
166+
0
167+
}
168+
}
169+
}
170+
171+
/// Takes a `RegistryInstruction` and appends the pubkey validity proof
172+
/// instruction
173+
fn append_zk_elgamal_proof(
174+
registry_instruction: Instruction,
175+
proof_data_location: ProofLocation<PubkeyValidityProofData>,
176+
) -> Result<Vec<Instruction>, ProgramError> {
177+
let mut instructions = vec![registry_instruction];
178+
179+
if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
180+
proof_data_location
181+
{
182+
let proof_instruction_offset: i8 = proof_instruction_offset.into();
183+
if proof_instruction_offset != 1 {
184+
return Err(ProgramError::InvalidArgument);
185+
}
186+
match proof_data {
187+
ProofData::InstructionData(data) => instructions
188+
.push(ProofInstruction::VerifyPubkeyValidity.encode_verify_proof(None, data)),
189+
ProofData::RecordAccount(address, offset) => instructions.push(
190+
ProofInstruction::VerifyPubkeyValidity
191+
.encode_verify_proof_from_account(None, address, offset),
192+
),
193+
}
194+
}
195+
Ok(instructions)
196+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
mod entrypoint;
2+
pub mod instruction;
3+
pub mod processor;
4+
pub mod state;
5+
6+
use solana_program::pubkey::Pubkey;
7+
8+
/// Seed for the ElGamal registry program-derived address
9+
pub const REGISTRY_ADDRESS_SEED: &[u8] = "elgamal-registry".as_bytes();
10+
11+
/// Derives the ElGamal registry account address and seed for the given wallet
12+
/// address
13+
pub fn get_elgamal_registry_address_and_bump_seed(
14+
wallet_address: &Pubkey,
15+
program_id: &Pubkey,
16+
) -> (Pubkey, u8) {
17+
Pubkey::find_program_address(
18+
&[REGISTRY_ADDRESS_SEED, &wallet_address.to_bytes()],
19+
program_id,
20+
)
21+
}
22+
23+
/// Derives the ElGamal registry account address for the given wallet address
24+
pub fn get_elgamal_registry_address(wallet_address: &Pubkey, program_id: &Pubkey) -> Pubkey {
25+
get_elgamal_registry_address_and_bump_seed(wallet_address, program_id).0
26+
}
27+
28+
solana_program::declare_id!("regVYJW7tcT8zipN5YiBvHsvR5jXW1uLFxaHSbugABg");

0 commit comments

Comments
 (0)