spl_elgamal_registry/
instruction.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
use {
    crate::{get_elgamal_registry_address, id},
    solana_program::{
        instruction::{AccountMeta, Instruction},
        program_error::ProgramError,
        pubkey::Pubkey,
        system_program, sysvar,
    },
    solana_zk_sdk::zk_elgamal_proof_program::{
        instruction::ProofInstruction, proof_data::PubkeyValidityProofData,
    },
    spl_token_confidential_transfer_proof_extraction::instruction::{ProofData, ProofLocation},
};

#[derive(Clone, Debug, PartialEq)]
#[repr(u8)]
pub enum RegistryInstruction {
    /// Initialize an ElGamal public key registry.
    ///
    /// 0. `[writable]` The account to be created
    /// 1. `[signer]` The wallet address (will also be the owner address for the
    ///    registry account)
    /// 2. `[]` System program
    /// 3. `[]` Instructions sysvar if `VerifyPubkeyValidity` is included in the
    ///    same transaction or context state account if `VerifyPubkeyValidity`
    ///    is pre-verified into a context state account.
    /// 4. `[]` (Optional) Record account if the accompanying proof is to be
    ///    read from a record account.
    CreateRegistry {
        /// Relative location of the `ProofInstruction::PubkeyValidityProof`
        /// instruction to the `CreateElGamalRegistry` instruction in the
        /// transaction. If the offset is `0`, then use a context state account
        /// for the proof.
        proof_instruction_offset: i8,
    },
    /// Update an ElGamal public key registry with a new ElGamal public key.
    ///
    /// 0. `[writable]` The ElGamal registry account
    /// 1. `[]` Instructions sysvar if `VerifyPubkeyValidity` is included in the
    ///    same transaction or context state account if `VerifyPubkeyValidity`
    ///    is pre-verified into a context state account.
    /// 2. `[]` (Optional) Record account if the accompanying proof is to be
    ///    read from a record account.
    /// 3. `[signer]` The owner of the ElGamal public key registry
    UpdateRegistry {
        /// Relative location of the `ProofInstruction::PubkeyValidityProof`
        /// instruction to the `UpdateElGamalRegistry` instruction in the
        /// transaction. If the offset is `0`, then use a context state account
        /// for the proof.
        proof_instruction_offset: i8,
    },
}

impl RegistryInstruction {
    /// Unpacks a byte buffer into a `RegistryInstruction`
    pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
        let (&tag, rest) = input
            .split_first()
            .ok_or(ProgramError::InvalidInstructionData)?;

        Ok(match tag {
            0 => {
                let proof_instruction_offset =
                    *rest.first().ok_or(ProgramError::InvalidInstructionData)?;
                Self::CreateRegistry {
                    proof_instruction_offset: proof_instruction_offset as i8,
                }
            }
            1 => {
                let proof_instruction_offset =
                    *rest.first().ok_or(ProgramError::InvalidInstructionData)?;
                Self::UpdateRegistry {
                    proof_instruction_offset: proof_instruction_offset as i8,
                }
            }
            _ => return Err(ProgramError::InvalidInstructionData),
        })
    }

    /// Packs a `RegistryInstruction` into a byte buffer.
    pub fn pack(&self) -> Vec<u8> {
        let mut buf = vec![];
        match self {
            Self::CreateRegistry {
                proof_instruction_offset,
            } => {
                buf.push(0);
                buf.extend_from_slice(&proof_instruction_offset.to_le_bytes());
            }
            Self::UpdateRegistry {
                proof_instruction_offset,
            } => {
                buf.push(1);
                buf.extend_from_slice(&proof_instruction_offset.to_le_bytes());
            }
        };
        buf
    }
}

/// Create a `RegistryInstruction::CreateRegistry` instruction
pub fn create_registry(
    owner_address: &Pubkey,
    proof_location: ProofLocation<PubkeyValidityProofData>,
) -> Result<Vec<Instruction>, ProgramError> {
    let elgamal_registry_address = get_elgamal_registry_address(owner_address, &id());

    let mut accounts = vec![
        AccountMeta::new(elgamal_registry_address, false),
        AccountMeta::new_readonly(*owner_address, true),
        AccountMeta::new_readonly(system_program::id(), false),
    ];
    let proof_instruction_offset = proof_instruction_offset(&mut accounts, proof_location);

    let mut instructions = vec![Instruction {
        program_id: id(),
        accounts,
        data: RegistryInstruction::CreateRegistry {
            proof_instruction_offset,
        }
        .pack(),
    }];
    append_zk_elgamal_proof(&mut instructions, proof_location)?;
    Ok(instructions)
}

/// Create a `RegistryInstruction::UpdateRegistry` instruction
pub fn update_registry(
    owner_address: &Pubkey,
    proof_location: ProofLocation<PubkeyValidityProofData>,
) -> Result<Vec<Instruction>, ProgramError> {
    let elgamal_registry_address = get_elgamal_registry_address(owner_address, &id());

    let mut accounts = vec![AccountMeta::new(elgamal_registry_address, false)];
    let proof_instruction_offset = proof_instruction_offset(&mut accounts, proof_location);
    accounts.push(AccountMeta::new_readonly(*owner_address, true));

    let mut instructions = vec![Instruction {
        program_id: id(),
        accounts,
        data: RegistryInstruction::UpdateRegistry {
            proof_instruction_offset,
        }
        .pack(),
    }];
    append_zk_elgamal_proof(&mut instructions, proof_location)?;
    Ok(instructions)
}

/// Takes a `ProofLocation`, updates the list of accounts, and returns a
/// suitable proof location
fn proof_instruction_offset(
    accounts: &mut Vec<AccountMeta>,
    proof_location: ProofLocation<PubkeyValidityProofData>,
) -> i8 {
    match proof_location {
        ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => {
            accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false));
            if let ProofData::RecordAccount(record_address, _) = proof_data {
                accounts.push(AccountMeta::new_readonly(*record_address, false));
            }
            proof_instruction_offset.into()
        }
        ProofLocation::ContextStateAccount(context_state_account) => {
            accounts.push(AccountMeta::new_readonly(*context_state_account, false));
            0
        }
    }
}

/// Takes a `RegistryInstruction` and appends the pubkey validity proof
/// instruction
fn append_zk_elgamal_proof(
    instructions: &mut Vec<Instruction>,
    proof_data_location: ProofLocation<PubkeyValidityProofData>,
) -> Result<(), ProgramError> {
    if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
        proof_data_location
    {
        let proof_instruction_offset: i8 = proof_instruction_offset.into();
        if proof_instruction_offset != 1 {
            return Err(ProgramError::InvalidArgument);
        }
        match proof_data {
            ProofData::InstructionData(data) => instructions
                .push(ProofInstruction::VerifyPubkeyValidity.encode_verify_proof(None, data)),
            ProofData::RecordAccount(address, offset) => instructions.push(
                ProofInstruction::VerifyPubkeyValidity
                    .encode_verify_proof_from_account(None, address, offset),
            ),
        }
    }
    Ok(())
}