-
Notifications
You must be signed in to change notification settings - Fork 3
refactor with pinocchio framework #2
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
Changes from all commits
1f252e1
23a41c2
072b222
a2bff48
6ee189b
2ba055e
e04fb09
b11e9ae
3be55e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| [package] | ||
| name = "solana-ed25519-verify" | ||
| version = "4.0.0" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: we can start with v1 or v0.1 |
||
| edition = "2021" | ||
|
|
||
| [lib] | ||
| crate-type = ["rlib"] | ||
|
|
||
| [features] | ||
| default = [] | ||
| # Enables the client-side constructor for invoking the standalone program. | ||
| instruction = ["dep:solana-instruction", "dep:solana-pubkey"] | ||
|
|
||
| [dependencies] | ||
| solana-curve25519 = { workspace = true } | ||
| solana-instruction = { workspace = true, optional = true } | ||
| solana-program-error = { workspace = true } | ||
| solana-pubkey = { workspace = true, optional = true } | ||
| solana-sha512-hasher = { workspace = true } | ||
|
|
||
| [dev-dependencies] | ||
| ed25519-dalek = { workspace = true } | ||
| solana-ed25519-verify = { path = ".", features = ["instruction"] } | ||
| solana-pubkey = { workspace = true } | ||
|
|
||
| [lints] | ||
| workspace = true | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| #![no_std] | ||
|
|
||
| //! Stateless Ed25519 verification utilities for Solana programs. | ||
| //! | ||
| //! This crate contains the reusable verifier used by | ||
| //! `solana-ed25519-program`. Programs can also depend on it directly to verify | ||
| //! Ed25519 signatures without invoking the standalone verifier program. | ||
| //! | ||
| //! The verifier performs ZIP-215 verification with canonical `S`. | ||
|
|
||
| #[cfg(feature = "instruction")] | ||
| extern crate alloc; | ||
|
|
||
| #[cfg(feature = "instruction")] | ||
| pub mod program; | ||
| mod scalar; | ||
| mod verifier; | ||
|
|
||
| #[cfg(feature = "instruction")] | ||
| pub use program::ed25519_verify_instruction; | ||
| pub use verifier::Ed25519Verifier; | ||
|
|
||
| pub const PUBKEY_SERIALIZED_SIZE: usize = 32; | ||
| pub const SIGNATURE_SERIALIZED_SIZE: usize = 64; |
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: can we call this file instruction.rs? That'll line up better with other clients |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| extern crate alloc; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: is this line needed since it's already enabled in lib.rs? |
||
|
|
||
| use { | ||
| crate::{PUBKEY_SERIALIZED_SIZE, SIGNATURE_SERIALIZED_SIZE}, | ||
| alloc::{vec, vec::Vec}, | ||
| solana_instruction::Instruction, | ||
| solana_pubkey::Pubkey, | ||
| }; | ||
|
|
||
| /// Constructs an on-chain instruction to invoke `solana-ed25519-program`. | ||
| pub fn ed25519_verify_instruction( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: let's just call this "verify" |
||
| program_id: &Pubkey, | ||
| public_key: &[u8; PUBKEY_SERIALIZED_SIZE], | ||
| signature: &[u8; SIGNATURE_SERIALIZED_SIZE], | ||
| message: &[u8], | ||
| ) -> Instruction { | ||
| let mut data = | ||
| Vec::with_capacity(PUBKEY_SERIALIZED_SIZE + SIGNATURE_SERIALIZED_SIZE + message.len()); | ||
| data.extend_from_slice(public_key); | ||
| data.extend_from_slice(signature); | ||
| data.extend_from_slice(message); | ||
|
|
||
| Instruction::new_with_bytes(*program_id, &data, vec![]) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| use { | ||
| crate::{scalar, PUBKEY_SERIALIZED_SIZE, SIGNATURE_SERIALIZED_SIZE}, | ||
| solana_curve25519::{ | ||
| edwards::{ | ||
| multiply_edwards, multiscalar_multiply_edwards, subtract_edwards, PodEdwardsPoint, | ||
| }, | ||
| scalar::PodScalar, | ||
| }, | ||
| solana_program_error::ProgramError, | ||
| }; | ||
|
|
||
| const ED25519_BASEPOINT_COMPRESSED: PodEdwardsPoint = PodEdwardsPoint([ | ||
| 0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, | ||
| 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, | ||
| ]); | ||
| /// Identity point of the Edwards curve, in compressed form. | ||
| pub(crate) const EDWARDS_IDENTITY_COMPRESSED_BYTES: [u8; PUBKEY_SERIALIZED_SIZE] = [ | ||
| 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
| ]; | ||
| const EDWARDS_IDENTITY_COMPRESSED: PodEdwardsPoint = | ||
| PodEdwardsPoint(EDWARDS_IDENTITY_COMPRESSED_BYTES); | ||
| const EIGHT_SCALAR: PodScalar = PodScalar([ | ||
| 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
| ]); | ||
|
|
||
| /// Stateless, zero-allocation Ed25519 verifier. | ||
| #[derive(Debug, Clone, Copy, Default)] | ||
| pub struct Ed25519Verifier; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the future, we should add a bunch of configuration options. For example, I am thinking we should support different variations organized in https://hdevalence.ca/blog/2020-10-04-its-25519am/). But let's do this in follow-ups since this PR is already quite big. |
||
|
|
||
| impl Ed25519Verifier { | ||
| /// Initializes a new verifier. | ||
| pub const fn new() -> Self { | ||
| Self | ||
| } | ||
|
Comment on lines
+33
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I'm always a fan of less code rather than more code, and this doesn't seem to give very much |
||
|
|
||
| /// Performs ZIP-215 Ed25519 verification for one signature. | ||
| /// | ||
| /// Uses the cofactored equation `[8](S*B - H(R || A || M)*A) == [8]R`. | ||
| /// The combined multiply-add minus `R` is performed first, then multiplied | ||
| /// by 8 and compared with the identity, matching the ed25519-zebra batch | ||
| /// verification shape. Canonical `S` is still required. | ||
| pub fn verify_signature( | ||
| &self, | ||
| signature: &[u8; SIGNATURE_SERIALIZED_SIZE], | ||
| public_key: &[u8; PUBKEY_SERIALIZED_SIZE], | ||
| message: &[u8], | ||
| ) -> Result<(), ProgramError> { | ||
| let (r_bytes, s_bytes) = signature.split_at(32); | ||
| let r_bytes: &[u8; 32] = r_bytes.try_into().unwrap(); | ||
| let s_bytes: &[u8; 32] = s_bytes.try_into().unwrap(); | ||
| if !scalar::is_canonical_scalar(s_bytes) { | ||
| return Err(ProgramError::InvalidArgument); | ||
| } | ||
|
|
||
| let r_point = PodEdwardsPoint(*r_bytes); | ||
| let public_key_point = PodEdwardsPoint(*public_key); | ||
|
|
||
| let challenge = compute_challenge(r_bytes, public_key, message); | ||
| let minus_challenge = scalar::negate(&challenge); | ||
| let lhs = multiscalar_multiply_edwards( | ||
| &[PodScalar(*s_bytes), PodScalar(minus_challenge)], | ||
| &[ED25519_BASEPOINT_COMPRESSED, public_key_point], | ||
| ) | ||
| .ok_or(ProgramError::InvalidArgument)?; | ||
| let difference = subtract_edwards(&lhs, &r_point).ok_or(ProgramError::InvalidArgument)?; | ||
| let difference_cofactored = | ||
| multiply_edwards(&EIGHT_SCALAR, &difference).ok_or(ProgramError::InvalidArgument)?; | ||
|
|
||
| if difference_cofactored != EDWARDS_IDENTITY_COMPRESSED { | ||
| return Err(ProgramError::InvalidArgument); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| fn compute_challenge(signature_r: &[u8; 32], public_key: &[u8; 32], message: &[u8]) -> [u8; 32] { | ||
| let digest = solana_sha512_hasher::hashv(&[signature_r, public_key, message]).to_bytes(); | ||
| scalar::reduce_wide(&digest) | ||
| } | ||
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 for future work: v2 is getting removed, so we should use v3