|
| 1 | +use { |
| 2 | + crate::{scalar, PUBKEY_SERIALIZED_SIZE, SIGNATURE_SERIALIZED_SIZE}, |
| 3 | + solana_curve25519::{ |
| 4 | + edwards::{ |
| 5 | + multiply_edwards, multiscalar_multiply_edwards, subtract_edwards, PodEdwardsPoint, |
| 6 | + }, |
| 7 | + scalar::PodScalar, |
| 8 | + }, |
| 9 | + solana_program_error::ProgramError, |
| 10 | +}; |
| 11 | + |
| 12 | +const ED25519_BASEPOINT_COMPRESSED: PodEdwardsPoint = PodEdwardsPoint([ |
| 13 | + 0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, |
| 14 | + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, |
| 15 | +]); |
| 16 | +/// Identity point of the Edwards curve, in compressed form. |
| 17 | +pub(crate) const EDWARDS_IDENTITY_COMPRESSED_BYTES: [u8; PUBKEY_SERIALIZED_SIZE] = [ |
| 18 | + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 19 | + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 20 | +]; |
| 21 | +const EDWARDS_IDENTITY_COMPRESSED: PodEdwardsPoint = |
| 22 | + PodEdwardsPoint(EDWARDS_IDENTITY_COMPRESSED_BYTES); |
| 23 | +const EIGHT_SCALAR: PodScalar = PodScalar([ |
| 24 | + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 25 | + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 26 | +]); |
| 27 | + |
| 28 | +/// Stateless, zero-allocation Ed25519 verifier. |
| 29 | +#[derive(Debug, Clone, Copy, Default)] |
| 30 | +pub struct Ed25519Verifier; |
| 31 | + |
| 32 | +impl Ed25519Verifier { |
| 33 | + /// Initializes a new verifier. |
| 34 | + pub const fn new() -> Self { |
| 35 | + Self |
| 36 | + } |
| 37 | + |
| 38 | + /// Performs ZIP-215 Ed25519 verification for one signature. |
| 39 | + /// |
| 40 | + /// Uses the cofactored equation `[8](S*B - H(R || A || M)*A) == [8]R`. |
| 41 | + /// The combined multiply-add minus `R` is performed first, then multiplied |
| 42 | + /// by 8 and compared with the identity, matching the ed25519-zebra batch |
| 43 | + /// verification shape. Canonical `S` is still required. |
| 44 | + pub fn verify_signature( |
| 45 | + &self, |
| 46 | + signature: &[u8; SIGNATURE_SERIALIZED_SIZE], |
| 47 | + public_key: &[u8; PUBKEY_SERIALIZED_SIZE], |
| 48 | + message: &[u8], |
| 49 | + ) -> Result<(), ProgramError> { |
| 50 | + let (r_bytes, s_bytes) = signature.split_at(32); |
| 51 | + let r_bytes: &[u8; 32] = r_bytes.try_into().unwrap(); |
| 52 | + let s_bytes: &[u8; 32] = s_bytes.try_into().unwrap(); |
| 53 | + if !scalar::is_canonical_scalar(s_bytes) { |
| 54 | + return Err(ProgramError::InvalidArgument); |
| 55 | + } |
| 56 | + |
| 57 | + let r_point = PodEdwardsPoint(*r_bytes); |
| 58 | + let public_key_point = PodEdwardsPoint(*public_key); |
| 59 | + |
| 60 | + let challenge = compute_challenge(r_bytes, public_key, message); |
| 61 | + let minus_challenge = scalar::negate(&challenge); |
| 62 | + let lhs = multiscalar_multiply_edwards( |
| 63 | + &[PodScalar(*s_bytes), PodScalar(minus_challenge)], |
| 64 | + &[ED25519_BASEPOINT_COMPRESSED, public_key_point], |
| 65 | + ) |
| 66 | + .ok_or(ProgramError::InvalidArgument)?; |
| 67 | + let difference = subtract_edwards(&lhs, &r_point).ok_or(ProgramError::InvalidArgument)?; |
| 68 | + let difference_cofactored = |
| 69 | + multiply_edwards(&EIGHT_SCALAR, &difference).ok_or(ProgramError::InvalidArgument)?; |
| 70 | + |
| 71 | + if difference_cofactored != EDWARDS_IDENTITY_COMPRESSED { |
| 72 | + return Err(ProgramError::InvalidArgument); |
| 73 | + } |
| 74 | + |
| 75 | + Ok(()) |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +fn compute_challenge(signature_r: &[u8; 32], public_key: &[u8; 32], message: &[u8]) -> [u8; 32] { |
| 80 | + let digest = solana_sha512_hasher::hashv(&[signature_r, public_key, message]).to_bytes(); |
| 81 | + scalar::reduce_wide(&digest) |
| 82 | +} |
0 commit comments