Skip to content

Conversation

@cienhub
Copy link

@cienhub cienhub commented Dec 5, 2025

Hey! This PR should be a fix for #13.
The bugs that I found are described inside the issue.

Please review these changes and let me know wdyt

We faced a specific problem with ATA derivation and these changes fixed the problem for us, so I'm pretty confident about them but I am more than willing to have your opinion on this @AmarildoGrembi

…nd modular arithmetic; update token account hash function for strict binary encoding.
Copy link
Member

@AmarildoGrembi AmarildoGrembi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @cienhub, sorry for the delay, and good job! I see only 2 issues that I would like you to improve if possible.

# Compute x² from the Ed25519 curve equation: x² = (y² - 1) / (d * y² + 1) mod Q
numerator = (y**2 - 1) % Q
denominator = (D * y**2 + 1) % Q
y_squared = y**2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

y**2 is computed before modular reduction. This creates unnecessarily large integers and diverges from standard field arithmetic used in Solana / ed25519-dalek.

suggestion:

y = y % Q
y_squared = (y * y) % Q
numerator   = (y_squared - 1) % Q
denominator = (D * y_squared + 1) % Q

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this is absolutely right, I'll keep your suggestion
Thanks

denominator = (D * y_squared + 1) % Q

# Compute the modular inverse of the denominator
denominator_inv = OpenSSL::BN.new(denominator).mod_inverse(Q).to_i rescue nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If mod_inverse fails, denominator_inv becomes nil, but execution continues.
This can cause false positives or runtime errors later.

suggestion:

denominator_inv =
  OpenSSL::BN.new(denominator).mod_inverse(Q).to_i rescue nil
return false unless denominator_inv

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually another way to write the same thing that is already implemented, wdyt?

denominator_inv = OpenSSL::BN.new(denominator).mod_inverse(Q).to_i rescue nil
return false if denominator_inv.nil?  # If inverse doesn't exist, it's off-curve

# Combine seeds and program ID with the PDA derivation logic
buffer = seeds.flatten.join + program_id + "ProgramDerivedAddress"
# strict encoding to binary
buffer = seeds.join.b + program_id.b + "ProgramDerivedAddress".b
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This now assumes:

  • seeds is a flat array
  • each seed is already a binary-safe string
    Are we sure about this?

Copy link
Author

@cienhub cienhub Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! In the current implementation, seeds are always binary strings from Base58.base58_to_binary() and pack('C'), so I would say that it's safe.
Also seeds is instanciated as a flat array and even when it is concatened with the nonce still remain a flat array

…ordinate calculations and ensuring correct field operations.
@cienhub
Copy link
Author

cienhub commented Dec 22, 2025

Hey @AmarildoGrembi! Thanks for taking the time to review it.
I have addressed your suggestion

Let me know what do you think

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants