-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[token-2022] Add elgamal registry account #7341
Conversation
2049427
to
d5890aa
Compare
d5890aa
to
4862464
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks pretty good overall! Mostly small things, let me know what you think
token/confidential-transfer/elgamal-registry/src/instruction.rs
Outdated
Show resolved
Hide resolved
token/confidential-transfer/elgamal-registry/src/instruction.rs
Outdated
Show resolved
Hide resolved
token/program-2022/src/extension/confidential_transfer/instruction.rs
Outdated
Show resolved
Hide resolved
// configure account using ElGamal registry | ||
let alice_account_keypair = Keypair::new(); | ||
let alice_token_account = alice_account_keypair.pubkey(); | ||
token | ||
.create_auxiliary_token_account_with_extension_space( | ||
&alice_account_keypair, | ||
&alice_token_account, | ||
vec![ExtensionType::ConfidentialTransferAccount], | ||
) | ||
.await | ||
.unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh gosh, this makes me realize something we haven't thought about yet. Should we make reallocate
on token-2022 permissionless? Otherwise, it's not possible to configure someone else's account.
If we want to go with the safer change, we can have configure_with_registry
optionally reallocate the account.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah completely missed that. It seems that if all accounts are to be rent-exempt, then there seems to be not much harm in making it permissionless? I'll think about it today and let's discuss tomorrow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is one slightly strange attack -- if we're competing in a trading environment, I could reallocate your token account to be as big as possible, which would count against the loaded data costs of your transaction, and try to make your transaction fail.
I'd probably go with special-casing this behavior and only reallocating for the confidential token account extension to be totally safe, but maybe I'm being overly cautious.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yes I forgot that there was a MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES
field. Yeah in this case, it seems safest to just make reallocation be permissionless for confidential extensions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I added the option to reallocate when configuring confidential transfer accounts with registry.
token/program-2022/src/extension/confidential_transfer/processor.rs
Outdated
Show resolved
Hide resolved
rust-toolchain.toml
Outdated
@@ -1,2 +1,2 @@ | |||
[toolchain] | |||
channel = "1.78.0" | |||
channel = "1.79.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this bumped intentionally?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, that accidentally got in. I will revert.
token/program-2022/src/extension/confidential_transfer/processor.rs
Outdated
Show resolved
Hide resolved
pub enum RegistryInstruction { | ||
/// Initialize an ElGamal public key registry. | ||
/// | ||
/// 0. `[writable, signer]` The funding account (must be a system account) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the other part of my comment got lost under the typo suggestion -- could we remove this and have the client function automatically transfer the rent-exempt lamports into the account so that the user does not need to pass a mutable signer to the program?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I updated the program so that the registry program just allocates and assigns ownership of the PDA account. Please take a look!
b96645c
to
8ea238e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking really close! Sorry, I did one last pass over the whole thing and found a couple more bits
/// Data expected by | ||
/// `ConfidentialTransferInstruction::ConfigureAccountWithRegistry` | ||
#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))] | ||
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] | ||
#[repr(C)] | ||
pub struct ConfigureAccountWithRegistryInstructionData { | ||
/// Reallocate token account if it is not large enough for the | ||
/// `ConfidentialTransfer` extension. | ||
pub reallocate_account: PodBool, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can probably use the presence of the payer and system program to signal that the caller is ok with doing a reallocation, making the instruction data here unnecessary. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that is a good idea! I made the instruction constructor logic this way, but I missed having that logic in the program.
|
||
let elgamal_registry_account_seeds: &[&[_]] = &[ | ||
REGISTRY_ADDRESS_SEED, | ||
&wallet_account_info.key.to_bytes(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: to_bytes()
creates a new array of 32 bytes, so it's better to use as_ref()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point!
current_extension_types.push(ExtensionType::ConfidentialTransferAccount); | ||
let needed_account_len = | ||
ExtensionType::try_calculate_account_len::<Account>(¤t_extension_types)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can you add a comment saying that ExtensionType::try_calculate_account_len
dedupes extension types, making this correct?
|
||
// reallocate | ||
msg!( | ||
"account needs realloc, +{:?} bytes", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: is there a reason to use the Debug
format string of {:?}
instead of the normal one of {}
? If not, let's go with the normal formatting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I modeled the function over the reallocate processor logic and used debug mode for consistency with that function.
Btw, I considered refactoring the process_reallocate
logic into sub-functions and recycling the logic for configuring confidential transfers, but I thought that would make things less readable. I can do the refactoring if you think that would be cleaner.
) -> ProgramResult { | ||
let mut current_extension_types = { | ||
let token_account = token_account_info.data.borrow(); | ||
let account = StateWithExtensions::<Account>::unpack(&token_account)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can you use PodStateWithExtensions::<PodAccount>
instead for better CU usage?
if reallocate { | ||
let payer_info = next_account_info(account_info_iter)?; | ||
let system_program_info = next_account_info(account_info_iter)?; | ||
reallocate_for_configure_account_with_registry( | ||
token_account_info, | ||
payer_info, | ||
system_program_info, | ||
)?; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can maybe make this processing logic more clever. Rather than always trying pull out the payer / system program info and then deciding to reallocate, the instruction can always try to reallocate, and only if extra lamports are needed, try to extract the payer and system program. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about this as well since that logic will be cleaner. The reason why I went with the current implementation is that if the user does not want to reallocate the account, then we would be consuming unnecessary compute units by unpacking the account and calculating the needed account length. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, that's a good point on the CUs... could you do a little test to see how many CUs the extra logic takes up? I'm hoping it's not too many, but there is a vector allocation, so who knows
…er-proof-extraction`
Co-authored-by: Jon C <[email protected]>
Co-authored-by: Jon C <[email protected]>
4b4f84a
to
7b77b83
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me, thanks! We can do the CU calculation with a separate piece of work, this can go in first
Problem
Currently, it is not possible to use associated token accounts for confidential transfers because in order to initialize a confidential transfer account, the owner must initialize it with an ElGamal public key.
It would be nice to have a special program "registry" program where a user can store an ElGamal public key that it can use for any mint with confidential transfers enabled.
Summary of Changes
CreateRegistry
instruction derives the PDA, invokes the system program to create the account, and then initializes the registry account. TheUpdateRegistry
instruction checks the owner signature and updates the old ElGamal key with a new key. I believe theUpdateRegistry
instruction does not need to check the PDA address, but please correct me if I am overlooking something 🙏 .program-2022
toconfidential-transfer-proof-extraction
module since this will be shared by both program-2022 and ElGamal registry program - eef0666ConfigureAccountWithRegistry
instruction in the confidential transfer extension which is identical to theConfigureAccount
instruction, but skips the signature and zk proof verification - 3104459. I believe the token program itself just need to check the provided registry account is owned by the registry program and does not need to necessarily check the validity of the PDA address, but please correct me if I am overlooking something 🙏 . Another thing to note is the fact that onConfigureAccountWithRegistry
instruction, the program initializes the confidential transfer account with default maximum pending credits counter and alsoAeCiphertext::default()
(all zero ciphertext). Unlike ElGamal ciphertexts, the all-zero symmetric ciphertext is not really valid, but I added this logic to simplify the program at the cost of increasing the complexity slightly on the client side (client can just interpret all zero ciphertext as encrypting 0). An alternative would be to add a valid zero-ciphertext field to the ElGamal registry account, but I thought this would make things a bit more complicated.ConfigureAccountWithRegistry
instruction in the token-client - 1a8dc29program-2022-test
- 4862464