Skip to main content

brine_ed25519/
lib.rs

1#![no_std]
2
3mod curve;
4pub mod hasher;
5mod scalar;
6
7use crate::curve::multiscalar_multiply_edwards;
8use crate::hasher::Hasher;
9use crate::scalar::scalar_from_bytes_mod_order_wide_into;
10pub use solana_address::Address;
11use solana_program_error::ProgramError;
12
13pub type Signature = [u8; 64];
14
15/// Negated compressed base point (-G). Identical to G with the sign bit
16/// (bit 7 of the last byte) flipped. Used so that the signature check
17/// `R == sB - kA` can be rewritten as a single multiscalar multiplication:
18/// `msm([s, k], [-G, A]) == -R`.
19const NEG_G: [u8; 32] = [
20    88, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
21    102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 230,
22];
23
24/// The hash implementation used by [`verify`]: the `sol_sha512` syscall
25/// (SIMD-0512) on Solana targets, the in-program `FastSha512` when the
26/// `fast-sha512` feature opts out of the syscall, and the software `sha2`
27/// implementation on host builds.
28#[cfg(all(
29    any(target_arch = "bpf", target_os = "solana"),
30    not(feature = "fast-sha512")
31))]
32type DefaultHasher = crate::hasher::Sha512Syscall;
33#[cfg(all(
34    any(target_arch = "bpf", target_os = "solana"),
35    feature = "fast-sha512"
36))]
37type DefaultHasher = crate::hasher::FastSha512;
38#[cfg(not(any(target_arch = "bpf", target_os = "solana")))]
39type DefaultHasher = crate::hasher::Sha512;
40
41/// Verify an ed25519 signature over a vectored message.
42///
43/// Point encodings are validated according to RFC 8032. This uses the
44/// RFC-permitted cofactorless verification equation, so canonical small-order
45/// points are not rejected. Use [`verify_strict`] when that additional check
46/// is required.
47///
48/// On Solana targets the challenge hash `H(R || A || M)` is computed via the
49/// `sol_sha512` syscall
50/// ([SIMD-0512](https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0512-sha512-syscall.md)),
51/// which requires the `enable_sha512_syscall` feature gate
52/// (`s512oDwgx8hjMnaQjXfqqrZroVj4HvC6TkN3iSSWXCh`) to be active on the
53/// target cluster. On clusters without the gate (or other SVM runtimes
54/// without the syscall), build with the `fast-sha512` feature to hash
55/// in-program instead. Host builds always hash in software.
56#[inline(always)]
57pub fn verify(pubkey: &Address, sig: &Signature, messages: &[&[u8]]) -> Result<(), ProgramError> {
58    verify_with_hasher::<DefaultHasher>(pubkey, sig, messages)
59}
60
61/// Strictly verify an ed25519 signature, rejecting small-order public keys and
62/// `R` values. This matches the Solana ed25519 precompile's point checks.
63#[inline(always)]
64pub fn verify_strict(
65    pubkey: &Address,
66    sig: &Signature,
67    messages: &[&[u8]],
68) -> Result<(), ProgramError> {
69    verify_with_hasher_strict::<DefaultHasher>(pubkey, sig, messages)
70}
71
72/// Verify a signature using the provided hash implementation and RFC 8032
73/// point decoding rules, without additional small-order rejection.
74#[inline(always)]
75pub fn verify_with_hasher<H: Hasher>(
76    pubkey: &Address,
77    sig: &Signature,
78    messages: &[&[u8]],
79) -> Result<(), ProgramError> {
80    // SAFETY: first 32 bytes of [u8; 64] is a valid [u8; 32].
81    let sig_r: &[u8; 32] = unsafe { &*(sig.as_ptr() as *const [u8; 32]) };
82    // SAFETY: Address is #[repr(transparent)] over [u8; 32].
83    let pubkey_bytes: &[u8; 32] = unsafe { &*(pubkey as *const Address as *const [u8; 32]) };
84
85    validate_point_encodings(pubkey_bytes, sig_r)?;
86
87    let challenge = challenge::<H>(sig_r, pubkey, messages);
88
89    verify_prehashed_unchecked(pubkey, sig, &challenge)
90}
91
92/// Strictly verify a signature using the provided hash implementation.
93#[inline(always)]
94pub fn verify_with_hasher_strict<H: Hasher>(
95    pubkey: &Address,
96    sig: &Signature,
97    messages: &[&[u8]],
98) -> Result<(), ProgramError> {
99    // SAFETY: first 32 bytes of [u8; 64] is a valid [u8; 32].
100    let sig_r: &[u8; 32] = unsafe { &*(sig.as_ptr() as *const [u8; 32]) };
101    // SAFETY: Address is #[repr(transparent)] over [u8; 32].
102    let pubkey_bytes: &[u8; 32] = unsafe { &*(pubkey as *const Address as *const [u8; 32]) };
103
104    validate_points_strict(pubkey_bytes, sig_r)?;
105
106    let challenge = challenge::<H>(sig_r, pubkey, messages);
107
108    verify_prehashed_unchecked(pubkey, sig, &challenge)
109}
110
111/// Verify an ed25519 signature using a precomputed challenge hash `H(R || A || M)`.
112/// This is useful in cases where the challenge hash needs to be computed off-chain
113/// or pre-computed on-chain for efficiency reasons.
114/// Point encodings are validated according to RFC 8032. Use
115/// [`verify_prehashed_strict`] when canonical small-order points must also be
116/// rejected.
117///
118/// # Safety (validation delegated to the MSM syscall)
119///
120/// The following checks are intentionally omitted because the Solana
121/// `sol_curve_multiscalar_mul` syscall already performs them internally
122/// (see `agave/curves/curve25519/src/edwards.rs` and `scalar.rs`):
123///
124/// - **Point decompression / on-curve check** for both `pubkey` and the
125///   constant `-G`: the syscall calls `CompressedEdwardsY::decompress()`
126///   on every point and returns failure if decompression fails.
127/// - **Scalar canonicality** of `s` (the upper half of the signature):
128///   the syscall calls `Scalar::from_canonical_bytes()` on every scalar
129///   and returns failure if the scalar is non-canonical.
130///
131/// If any of those checks fail the MSM returns `None`, which we map to
132/// `ProgramError::InvalidArgument`.
133#[inline(always)]
134#[allow(non_snake_case)]
135pub fn verify_prehashed(
136    pubkey: &Address,
137    sig: &Signature,
138    challenge: &[u8; 64],
139) -> Result<(), ProgramError> {
140    // SAFETY: first 32 bytes of [u8; 64] is a valid [u8; 32].
141    let sig_r: &[u8; 32] = unsafe { &*(sig.as_ptr() as *const [u8; 32]) };
142    // SAFETY: Address is #[repr(transparent)] over [u8; 32].
143    let pubkey_bytes: &[u8; 32] = unsafe { &*(pubkey as *const Address as *const [u8; 32]) };
144
145    validate_point_encodings(pubkey_bytes, sig_r)?;
146    verify_prehashed_unchecked(pubkey, sig, challenge)
147}
148
149#[inline(always)]
150#[allow(non_snake_case)]
151fn verify_prehashed_unchecked(
152    pubkey: &Address,
153    sig: &Signature,
154    challenge: &[u8; 64],
155) -> Result<(), ProgramError> {
156    // SAFETY: [u8; 64] has the same layout as [[u8; 32]; 2].
157    let (sig_r, sig_s): &([u8; 32], [u8; 32]) = unsafe { &*(sig as *const [u8; 64] as *const _) };
158
159    // SAFETY: Address is #[repr(transparent)] over [u8; 32].
160    let pubkey_bytes: &[u8; 32] = unsafe { &*(pubkey as *const Address as *const [u8; 32]) };
161
162    // Build the [[u8; 32]; 2] scalar array in place so that the reduced `k`
163    // limbs can be written straight into the MSM input slot instead of
164    // materializing through a separate 32-byte stack temporary.
165    let mut scalars: [[u8; 32]; 2] = [*sig_s, [0u8; 32]];
166    scalar_from_bytes_mod_order_wide_into(challenge, &mut scalars[1]);
167
168    let points = [NEG_G, *pubkey_bytes];
169
170    // msm([s, k], [-G, A]) = s*(-G) + k*A = k*A - s*G = -(s*G - k*A) = -R
171    let neg_R =
172        multiscalar_multiply_edwards(&scalars, &points).ok_or(ProgramError::InvalidArgument)?;
173
174    // Negate sig_R (flip sign bit) to compare against -R.
175    let mut neg_sig_R = *sig_r;
176    neg_sig_R[31] ^= 0x80;
177
178    if neg_R == neg_sig_R {
179        Ok(())
180    } else {
181        Err(ProgramError::InvalidArgument)
182    }
183}
184
185/// Strictly verify a signature using a precomputed challenge hash.
186#[inline(always)]
187#[allow(non_snake_case)]
188pub fn verify_prehashed_strict(
189    pubkey: &Address,
190    sig: &Signature,
191    challenge: &[u8; 64],
192) -> Result<(), ProgramError> {
193    // SAFETY: first 32 bytes of [u8; 64] is a valid [u8; 32].
194    let sig_r: &[u8; 32] = unsafe { &*(sig.as_ptr() as *const [u8; 32]) };
195    // SAFETY: Address is #[repr(transparent)] over [u8; 32].
196    let pubkey_bytes: &[u8; 32] = unsafe { &*(pubkey as *const Address as *const [u8; 32]) };
197
198    validate_points_strict(pubkey_bytes, sig_r)?;
199    verify_prehashed_unchecked(pubkey, sig, challenge)
200}
201
202#[inline(always)]
203fn challenge<H: Hasher>(sig_r: &[u8; 32], pubkey: &Address, messagev: &[&[u8]]) -> [u8; 64] {
204    let mut hasher = H::new();
205    hasher.update(sig_r);
206    hasher.update(pubkey.as_ref());
207
208    for message in messagev {
209        hasher.update(message);
210    }
211
212    hasher.finalize()
213}
214
215#[cfg(test)]
216const G: [u8; 32] = [
217    88, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
218    102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
219];
220
221/// Canonical encodings of the eight small-order points.
222const EIGHT_TORSION: [[u8; 32]; 8] = [
223    [
224        0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
225        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
226        0x00, 0x00,
227    ],
228    [
229        0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67,
230        0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac,
231        0x03, 0x7a,
232    ],
233    [
234        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
235        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
236        0x00, 0x80,
237    ],
238    [
239        0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98,
240        0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53,
241        0xfc, 0x05,
242    ],
243    [
244        0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
245        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
246        0xff, 0x7f,
247    ],
248    [
249        0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98,
250        0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53,
251        0xfc, 0x85,
252    ],
253    [
254        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
255        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
256        0x00, 0x00,
257    ],
258    [
259        0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67,
260        0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac,
261        0x03, 0xfa,
262    ],
263];
264
265#[inline(always)]
266fn is_canonical_small_order(point: &[u8; 32]) -> bool {
267    EIGHT_TORSION.contains(point)
268}
269
270/// Apply the RFC 8032 point-encoding checks not provided by dalek's field
271/// decoding: the encoded y-coordinate must be less than p = 2^255 - 19, and
272/// x = 0 may not be encoded with its sign bit set.
273#[inline(always)]
274fn is_canonical_point_encoding(point: &[u8; 32]) -> bool {
275    const TOP: u64 = 0x7fff_ffff_ffff_ffff;
276    const LOW_P: u64 = 0xffff_ffff_ffff_ffed;
277
278    // Almost every valid encoding is decided by the final byte alone. Only
279    // y values near zero or p need the remaining limbs.
280    let final_byte = point[31];
281    match final_byte & 0x7f {
282        0x00 if final_byte & 0x80 != 0 => {
283            // The only invalid encoding in this range is negative zero at
284            // y = 1. Check it only when its top byte makes it possible.
285            point[0] != 1 || point[1..31].iter().any(|&byte| byte != 0)
286        }
287        0x7f => {
288            // Parse little-endian limbs only at the field boundary. The byte
289            // array need not be u64-aligned.
290            let src = point.as_ptr() as *const u64;
291            // SAFETY: point covers four possibly unaligned u64-sized slots.
292            let encoded_top = u64::from_le(unsafe { core::ptr::read_unaligned(src.add(3)) });
293            let top = encoded_top & TOP;
294            if top != TOP {
295                return true;
296            }
297
298            let middle_high = u64::from_le(unsafe { core::ptr::read_unaligned(src.add(2)) });
299            if middle_high != u64::MAX {
300                return true;
301            }
302
303            let middle_low = u64::from_le(unsafe { core::ptr::read_unaligned(src.add(1)) });
304            if middle_low != u64::MAX {
305                return true;
306            }
307
308            let low = u64::from_le(unsafe { core::ptr::read_unaligned(src) });
309            low < LOW_P && !(final_byte & 0x80 != 0 && low == LOW_P - 1)
310        }
311        _ => true,
312    }
313}
314
315#[inline(always)]
316fn is_negative_zero(point: &[u8; 32]) -> bool {
317    // RFC 8032 also rejects x = 0 with the x-sign bit set. On edwards25519,
318    // x = 0 occurs at y = 1 and y = p - 1.
319    if point[31] & 0x80 == 0 {
320        return false;
321    }
322
323    match point[31] & 0x7f {
324        0x00 => point[0] == 1 && point[1..31].iter().all(|&byte| byte == 0),
325        0x7f => point[0] == 0xec && point[1..31].iter().all(|&byte| byte == 0xff),
326        _ => false,
327    }
328}
329
330#[inline(always)]
331fn validate_point_encodings(pubkey: &[u8; 32], sig_r: &[u8; 32]) -> Result<(), ProgramError> {
332    // The public key is decompressed by the curve implementation, whose field
333    // decoder accepts non-canonical y values, so enforce the full RFC encoding
334    // rules here. R is compared byte-for-byte with the canonical MSM output;
335    // that comparison already rejects y >= p, leaving only negative zero to
336    // reject explicitly.
337    if !is_canonical_point_encoding(pubkey) || is_negative_zero(sig_r) {
338        Err(ProgramError::InvalidArgument)
339    } else {
340        Ok(())
341    }
342}
343
344#[inline(always)]
345fn validate_points_strict(pubkey: &[u8; 32], sig_r: &[u8; 32]) -> Result<(), ProgramError> {
346    validate_point_encodings(pubkey, sig_r)?;
347
348    if is_canonical_small_order(pubkey) || is_canonical_small_order(sig_r) {
349        Err(ProgramError::InvalidArgument)
350    } else {
351        Ok(())
352    }
353}
354
355#[cfg(test)]
356mod tests {
357    use super::*;
358    use crate::hasher::{Hasher, Sha512};
359    use curve25519_dalek::constants;
360
361    fn plus_p(y: u8, sign: u8) -> [u8; 32] {
362        let mut bytes = [0xffu8; 32];
363        bytes[0] = 0xed + y;
364        bytes[31] = 0x7f | sign;
365        bytes
366    }
367
368    fn forged_signature() -> [u8; 64] {
369        let mut sig = [0u8; 64];
370        sig[..32].copy_from_slice(&G);
371        sig[32] = 1;
372        sig
373    }
374
375    #[test]
376    fn test_small_order() {
377        assert!(!is_canonical_small_order(&G));
378        for (expected, point) in EIGHT_TORSION.iter().zip(constants::EIGHT_TORSION) {
379            assert_eq!(*expected, point.compress().to_bytes());
380            assert!(is_canonical_small_order(expected));
381        }
382    }
383
384    #[test]
385    fn test_canonical_point_encoding_boundaries() {
386        assert!(is_canonical_point_encoding(&G));
387        assert!(is_canonical_point_encoding(&NEG_G));
388
389        // p - 1 is the largest field element, with a canonical zero sign.
390        assert!(is_canonical_point_encoding(&EIGHT_TORSION[4]));
391
392        // Every y from p through 2^255 - 1 is non-canonical, independently
393        // of the x-sign bit.
394        for offset in 0..19 {
395            for sign in [0x00u8, 0x80] {
396                assert!(!is_canonical_point_encoding(&plus_p(offset, sign)));
397            }
398        }
399
400        // x = 0 cannot carry a set sign bit. Its two possible y-coordinates
401        // are 1 and p - 1.
402        for i in [0, 4] {
403            let mut negative_zero = EIGHT_TORSION[i];
404            negative_zero[31] |= 0x80;
405            assert!(!is_canonical_point_encoding(&negative_zero));
406        }
407    }
408
409    #[test]
410    fn test_noncanonical_alias_rejected() {
411        let sig = forged_signature();
412        let pubkey = Address::from(plus_p(1, 0));
413
414        assert_eq!(
415            verify(&pubkey, &sig, &[b"anything"]),
416            Err(ProgramError::InvalidArgument)
417        );
418        assert_eq!(
419            verify_strict(&pubkey, &sig, &[b"anything"]),
420            Err(ProgramError::InvalidArgument)
421        );
422    }
423
424    #[test]
425    fn test_noncanonical_r_rejected_by_canonical_result_comparison() {
426        let pubkey = Address::from(G);
427        let challenge = [0u8; 64];
428
429        for offset in 0..19 {
430            for sign in [0x00u8, 0x80] {
431                let mut sig = [0u8; 64];
432                sig[..32].copy_from_slice(&plus_p(offset, sign));
433
434                assert_eq!(
435                    verify_prehashed(&pubkey, &sig, &challenge),
436                    Err(ProgramError::InvalidArgument),
437                    "offset={offset}, sign={sign:#x}"
438                );
439            }
440        }
441    }
442
443    #[test]
444    fn test_strict_rejects_canonical_small_order_key() {
445        let sig = forged_signature();
446        let pubkey = Address::from(EIGHT_TORSION[0]);
447
448        // RFC 8032 permits the cofactorless equation used by verify(), which
449        // this canonical identity key and forged signature satisfy.
450        assert!(verify(&pubkey, &sig, &[b"anything"]).is_ok());
451        assert_eq!(
452            verify_strict(&pubkey, &sig, &[b"anything"]),
453            Err(ProgramError::InvalidArgument)
454        );
455    }
456
457    #[test]
458    fn test_negative_zero_r_rejected() {
459        let pubkey = Address::from(G);
460        let mut r = EIGHT_TORSION[0];
461        r[31] |= 0x80;
462        let mut sig = [0u8; 64];
463        sig[..32].copy_from_slice(&r);
464        sig[32] = 1;
465        let mut challenge = [0u8; 64];
466        challenge[0] = 1;
467
468        assert_eq!(
469            verify_prehashed(&pubkey, &sig, &challenge),
470            Err(ProgramError::InvalidArgument)
471        );
472        assert_eq!(
473            verify_prehashed_strict(&pubkey, &sig, &challenge),
474            Err(ProgramError::InvalidArgument)
475        );
476    }
477
478    #[test]
479    fn test_base_point() {
480        let base_point = constants::ED25519_BASEPOINT_POINT;
481        let compressed = base_point.compress();
482        let bytes = compressed.to_bytes();
483        assert_eq!(bytes, G);
484    }
485
486    #[test]
487    fn test_hello_world() {
488        let pubkey = Address::from([
489            73, 73, 170, 112, 75, 235, 154, 81, 203, 8, 44, 245, 233, 18, 204, 136, 162, 9, 233,
490            49, 154, 201, 171, 175, 47, 6, 223, 101, 105, 80, 95, 166,
491        ]);
492        let sig: [u8; 64] = [
493            164, 121, 89, 242, 88, 29, 80, 177, 104, 20, 102, 176, 48, 133, 68, 8, 105, 33, 58, 86,
494            28, 108, 198, 140, 160, 219, 62, 184, 154, 181, 140, 33, 35, 102, 183, 203, 111, 33,
495            55, 170, 180, 138, 92, 196, 185, 201, 122, 167, 15, 112, 9, 228, 226, 112, 111, 10,
496            142, 73, 85, 43, 81, 152, 204, 13,
497        ];
498
499        assert!(verify(&pubkey, &sig, &[b"hello world"]).is_ok());
500        assert!(verify(&pubkey, &sig, &[b"not the right message"]).is_err());
501    }
502
503    #[test]
504    fn test_hello_world_with_hasher() {
505        let pubkey = Address::from([
506            73, 73, 170, 112, 75, 235, 154, 81, 203, 8, 44, 245, 233, 18, 204, 136, 162, 9, 233,
507            49, 154, 201, 171, 175, 47, 6, 223, 101, 105, 80, 95, 166,
508        ]);
509        let sig: [u8; 64] = [
510            164, 121, 89, 242, 88, 29, 80, 177, 104, 20, 102, 176, 48, 133, 68, 8, 105, 33, 58, 86,
511            28, 108, 198, 140, 160, 219, 62, 184, 154, 181, 140, 33, 35, 102, 183, 203, 111, 33,
512            55, 170, 180, 138, 92, 196, 185, 201, 122, 167, 15, 112, 9, 228, 226, 112, 111, 10,
513            142, 73, 85, 43, 81, 152, 204, 13,
514        ];
515
516        assert!(verify_with_hasher::<Sha512>(&pubkey, &sig, &[b"hello world"]).is_ok());
517        assert!(verify_with_hasher::<Sha512>(&pubkey, &sig, &[b"not the right message"]).is_err());
518    }
519
520    #[test]
521    fn test_error_invalid_public_key() {
522        let pubkey = Address::from(EIGHT_TORSION[0]);
523        let sig: [u8; 64] = [
524            164, 121, 89, 242, 88, 29, 80, 177, 104, 20, 102, 176, 48, 133, 68, 8, 105, 33, 58, 86,
525            28, 108, 198, 140, 160, 219, 62, 184, 154, 181, 140, 33, 35, 102, 183, 203, 111, 33,
526            55, 170, 180, 138, 92, 196, 185, 201, 122, 167, 15, 112, 9, 228, 226, 112, 111, 10,
527            142, 73, 85, 43, 81, 152, 204, 13,
528        ];
529
530        assert_eq!(
531            verify_strict(&pubkey, &sig, &[b"hello world"]),
532            Err(ProgramError::InvalidArgument)
533        );
534    }
535
536    #[test]
537    fn test_error_invalid_signature() {
538        let pubkey = Address::from([
539            73, 73, 170, 112, 75, 235, 154, 81, 203, 8, 44, 245, 233, 18, 204, 136, 162, 9, 233,
540            49, 154, 201, 171, 175, 47, 6, 223, 101, 105, 80, 95, 166,
541        ]);
542        let sig: [u8; 64] = [
543            164, 121, 89, 242, 88, 29, 80, 177, 104, 20, 102, 176, 48, 133, 68, 8, 105, 33, 58, 86,
544            28, 108, 198, 140, 160, 219, 62, 184, 154, 181, 140, 33, 35, 102, 183, 203, 111, 33,
545            55, 170, 180, 138, 92, 196, 185, 201, 122, 167, 15, 112, 9, 228, 226, 112, 111, 10,
546            142, 73, 85, 43, 81, 152, 204, 13,
547        ];
548
549        assert_eq!(
550            verify(&pubkey, &sig, &[b"not the right message"]),
551            Err(ProgramError::InvalidArgument)
552        );
553    }
554
555    #[test]
556    fn test_hello_worldv() {
557        let pubkey = Address::from([
558            73, 73, 170, 112, 75, 235, 154, 81, 203, 8, 44, 245, 233, 18, 204, 136, 162, 9, 233,
559            49, 154, 201, 171, 175, 47, 6, 223, 101, 105, 80, 95, 166,
560        ]);
561        let sig: [u8; 64] = [
562            164, 121, 89, 242, 88, 29, 80, 177, 104, 20, 102, 176, 48, 133, 68, 8, 105, 33, 58, 86,
563            28, 108, 198, 140, 160, 219, 62, 184, 154, 181, 140, 33, 35, 102, 183, 203, 111, 33,
564            55, 170, 180, 138, 92, 196, 185, 201, 122, 167, 15, 112, 9, 228, 226, 112, 111, 10,
565            142, 73, 85, 43, 81, 152, 204, 13,
566        ];
567
568        let messagev: &[&[u8]] = &[b"hello", b" ", b"world"];
569        let bad_messagev: &[&[u8]] = &[b"hello", b" ", b"there"];
570
571        assert!(verify(&pubkey, &sig, messagev).is_ok());
572        assert!(verify(&pubkey, &sig, bad_messagev).is_err());
573    }
574
575    #[test]
576    fn test_vector_1() {
577        let pubkey = Address::from([
578            0xd7, 0x5a, 0x98, 0x01, 0x82, 0xb1, 0x0a, 0xb7, 0xd5, 0x4b, 0xfe, 0xd3, 0xc9, 0x64,
579            0x07, 0x3a, 0x0e, 0xe1, 0x72, 0xf3, 0xda, 0xa6, 0x23, 0x25, 0xaf, 0x02, 0x1a, 0x68,
580            0xf7, 0x07, 0x51, 0x1a,
581        ]);
582
583        let sig: [u8; 64] = [
584            0xe5, 0x56, 0x43, 0x00, 0xc3, 0x60, 0xac, 0x72, 0x90, 0x86, 0xe2, 0xcc, 0x80, 0x6e,
585            0x82, 0x8a, 0x84, 0x87, 0x7f, 0x1e, 0xb8, 0xe5, 0xd9, 0x74, 0xd8, 0x73, 0xe0, 0x65,
586            0x22, 0x49, 0x01, 0x55, 0x5f, 0xb8, 0x82, 0x15, 0x90, 0xa3, 0x3b, 0xac, 0xc6, 0x1e,
587            0x39, 0x70, 0x1c, 0xf9, 0xb4, 0x6b, 0xd2, 0x5b, 0xf5, 0xf0, 0x59, 0x5b, 0xbe, 0x24,
588            0x65, 0x51, 0x41, 0x43, 0x8e, 0x7a, 0x10, 0x0b,
589        ];
590
591        assert!(verify(&pubkey, &sig, &[b""]).is_ok());
592        assert!(verify(&pubkey, &sig, &[b"not the right message"]).is_err());
593    }
594
595    #[test]
596    fn test_vector_2() {
597        let pubkey = Address::from([
598            0x3d, 0x40, 0x17, 0xc3, 0xe8, 0x43, 0x89, 0x5a, 0x92, 0xb7, 0x0a, 0xa7, 0x4d, 0x1b,
599            0x7e, 0xbc, 0x9c, 0x98, 0x2c, 0xcf, 0x2e, 0xc4, 0x96, 0x8c, 0xc0, 0xcd, 0x55, 0xf1,
600            0x2a, 0xf4, 0x66, 0x0c,
601        ]);
602
603        let sig: [u8; 64] = [
604            0x92, 0xa0, 0x09, 0xa9, 0xf0, 0xd4, 0xca, 0xb8, 0x72, 0x0e, 0x82, 0x0b, 0x5f, 0x64,
605            0x25, 0x40, 0xa2, 0xb2, 0x7b, 0x54, 0x16, 0x50, 0x3f, 0x8f, 0xb3, 0x76, 0x22, 0x23,
606            0xeb, 0xdb, 0x69, 0xda, 0x08, 0x5a, 0xc1, 0xe4, 0x3e, 0x15, 0x99, 0x6e, 0x45, 0x8f,
607            0x36, 0x13, 0xd0, 0xf1, 0x1d, 0x8c, 0x38, 0x7b, 0x2e, 0xae, 0xb4, 0x30, 0x2a, 0xee,
608            0xb0, 0x0d, 0x29, 0x16, 0x12, 0xbb, 0x0c, 0x00,
609        ];
610
611        assert!(verify(&pubkey, &sig, &[b"r"]).is_ok());
612        assert!(verify(&pubkey, &sig, &[b"not the right message"]).is_err());
613    }
614
615    #[test]
616    fn test_vector_3() {
617        let pubkey = Address::from([
618            0xfc, 0x51, 0xcd, 0x8e, 0x62, 0x18, 0xa1, 0xa3, 0x8d, 0xa4, 0x7e, 0xd0, 0x02, 0x30,
619            0xf0, 0x58, 0x08, 0x16, 0xed, 0x13, 0xba, 0x33, 0x03, 0xac, 0x5d, 0xeb, 0x91, 0x15,
620            0x48, 0x90, 0x80, 0x25,
621        ]);
622
623        let sig: [u8; 64] = [
624            0x62, 0x91, 0xd6, 0x57, 0xde, 0xec, 0x24, 0x02, 0x48, 0x27, 0xe6, 0x9c, 0x3a, 0xbe,
625            0x01, 0xa3, 0x0c, 0xe5, 0x48, 0xa2, 0x84, 0x74, 0x3a, 0x44, 0x5e, 0x36, 0x80, 0xd7,
626            0xdb, 0x5a, 0xc3, 0xac, 0x18, 0xff, 0x9b, 0x53, 0x8d, 0x16, 0xf2, 0x90, 0xae, 0x67,
627            0xf7, 0x60, 0x98, 0x4d, 0xc6, 0x59, 0x4a, 0x7c, 0x15, 0xe9, 0x71, 0x6e, 0xd2, 0x8d,
628            0xc0, 0x27, 0xbe, 0xce, 0xea, 0x1e, 0xc4, 0x0a,
629        ];
630
631        let message: &[u8] = &[0xaf, 0x82];
632
633        assert!(verify(&pubkey, &sig, &[message]).is_ok());
634        assert!(verify(&pubkey, &sig, &[b"not the right message"]).is_err());
635    }
636
637    #[test]
638    fn test_prehashed() {
639        let pubkey = Address::from([
640            0xfc, 0x51, 0xcd, 0x8e, 0x62, 0x18, 0xa1, 0xa3, 0x8d, 0xa4, 0x7e, 0xd0, 0x02, 0x30,
641            0xf0, 0x58, 0x08, 0x16, 0xed, 0x13, 0xba, 0x33, 0x03, 0xac, 0x5d, 0xeb, 0x91, 0x15,
642            0x48, 0x90, 0x80, 0x25,
643        ]);
644
645        let sig: [u8; 64] = [
646            0x62, 0x91, 0xd6, 0x57, 0xde, 0xec, 0x24, 0x02, 0x48, 0x27, 0xe6, 0x9c, 0x3a, 0xbe,
647            0x01, 0xa3, 0x0c, 0xe5, 0x48, 0xa2, 0x84, 0x74, 0x3a, 0x44, 0x5e, 0x36, 0x80, 0xd7,
648            0xdb, 0x5a, 0xc3, 0xac, 0x18, 0xff, 0x9b, 0x53, 0x8d, 0x16, 0xf2, 0x90, 0xae, 0x67,
649            0xf7, 0x60, 0x98, 0x4d, 0xc6, 0x59, 0x4a, 0x7c, 0x15, 0xe9, 0x71, 0x6e, 0xd2, 0x8d,
650            0xc0, 0x27, 0xbe, 0xce, 0xea, 0x1e, 0xc4, 0x0a,
651        ];
652
653        let message = &[0xaf, 0x82];
654        let challenge = Sha512::hashv(&[sig[..32].as_ref(), pubkey.as_ref(), message.as_ref()]);
655        let wrong_challenge = [0u8; 64];
656
657        assert!(verify_prehashed(&pubkey, &sig, &challenge).is_ok());
658        assert!(verify_prehashed(&pubkey, &sig, &wrong_challenge).is_err());
659    }
660
661    #[test]
662    fn test_challenge_hashv() {
663        let sig_r: [u8; 32] = [
664            164, 121, 89, 242, 88, 29, 80, 177, 104, 20, 102, 176, 48, 133, 68, 8, 105, 33, 58, 86,
665            28, 108, 198, 140, 160, 219, 62, 184, 154, 181, 140, 33,
666        ];
667        let pubkey = Address::from([
668            73, 73, 170, 112, 75, 235, 154, 81, 203, 8, 44, 245, 233, 18, 204, 136, 162, 9, 233,
669            49, 154, 201, 171, 175, 47, 6, 223, 101, 105, 80, 95, 166,
670        ]);
671        let messagev: &[&[u8]] = &[b"hello", b" ", b"world"];
672
673        let expected = Sha512::hashv(&[sig_r.as_ref(), pubkey.as_ref(), b"hello", b" ", b"world"]);
674
675        assert_eq!(challenge::<Sha512>(&sig_r, &pubkey, messagev), expected);
676    }
677}