|
| 1 | +//! Configurable Ed25519 verification criteria. |
| 2 | +//! |
| 3 | +//! Ed25519 "signature validity" is not a single definition: implementations |
| 4 | +//! differ on cofactored vs. cofactorless verification, whether non-canonical |
| 5 | +//! point encodings are accepted, and whether small-order points are rejected. |
| 6 | +//! These divergences are catalogued in Henry de Valence's |
| 7 | +//! ["It's 255:19AM. Do you know what your validation criteria are?"][blog]. |
| 8 | +//! |
| 9 | +//! [`VerificationCriteria`] exposes those divergences as independent knobs so a |
| 10 | +//! caller can select the exact variant they need. Two named presets ship today — |
| 11 | +//! [`zip215`] (the [ZIP-215] criteria specified by [SIMD-0376]) and |
| 12 | +//! [`dalek_verify_strict`] — and the knobs are designed so that other well-known |
| 13 | +//! profiles (e.g. libsodium, RFC 8032 / FIPS 186-5) can be added as presets in |
| 14 | +//! follow-ups without changing the verifier. |
| 15 | +//! |
| 16 | +//! [blog]: https://hdevalence.ca/blog/2020-10-04-its-25519am/ |
| 17 | +//! [ZIP-215]: https://zips.z.cash/zip-0215 |
| 18 | +//! [SIMD-0376]: https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0376-verify-strict.md |
| 19 | +//! [`zip215`]: VerificationCriteria::zip215 |
| 20 | +//! [`dalek_verify_strict`]: VerificationCriteria::dalek_verify_strict |
| 21 | +
|
| 22 | +/// Independent Ed25519 validation knobs. |
| 23 | +/// |
| 24 | +/// Each field toggles one decision point from the "255:19AM" taxonomy. Fields |
| 25 | +/// are public so callers can compose arbitrary combinations, typically by |
| 26 | +/// starting from a preset and overriding a single knob: |
| 27 | +/// |
| 28 | +/// ``` |
| 29 | +/// use solana_ed25519_verify::VerificationCriteria; |
| 30 | +/// |
| 31 | +/// let strict_s = VerificationCriteria { |
| 32 | +/// reject_small_order_a: true, |
| 33 | +/// ..VerificationCriteria::zip215() |
| 34 | +/// }; |
| 35 | +/// ``` |
| 36 | +#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 37 | +pub struct VerificationCriteria { |
| 38 | + /// Use the cofactored verification equation `[8](S·B − H·A) == [8]R`. |
| 39 | + /// |
| 40 | + /// When `false`, the cofactorless equation `S·B − H·A == R` is used, which |
| 41 | + /// rejects mixed-order points that the cofactored equation tolerates. The |
| 42 | + /// cofactored form costs one extra multiplication by the cofactor 8, which |
| 43 | + /// the verifier performs as three `sol_curve_group_op` additions. |
| 44 | + pub cofactored: bool, |
| 45 | + /// Reject public keys whose compressed `y`-coordinate is `>= p` (a |
| 46 | + /// non-canonical encoding of a reduced point). |
| 47 | + pub require_canonical_a: bool, |
| 48 | + /// Reject signature `R` values whose compressed `y`-coordinate is `>= p`. |
| 49 | + pub require_canonical_r: bool, |
| 50 | + /// Reject public keys that lie in the small-order (torsion) subgroup. |
| 51 | + /// |
| 52 | + /// Costs a multiplication by the cofactor 8 (three `sol_curve_group_op` |
| 53 | + /// additions) when enabled. |
| 54 | + pub reject_small_order_a: bool, |
| 55 | + /// Reject signature `R` values that lie in the small-order subgroup. |
| 56 | + /// |
| 57 | + /// Costs a multiplication by the cofactor 8 (three `sol_curve_group_op` |
| 58 | + /// additions) when enabled. |
| 59 | + pub reject_small_order_r: bool, |
| 60 | + /// Reject signatures whose scalar `S` is not in canonical `[0, L)` form. |
| 61 | + pub require_canonical_s: bool, |
| 62 | +} |
| 63 | + |
| 64 | +impl VerificationCriteria { |
| 65 | + /// [ZIP-215] verification, as specified for Solana by [SIMD-0376]. |
| 66 | + /// |
| 67 | + /// Cofactored equation with a canonical `S` requirement; non-canonical point |
| 68 | + /// encodings and small-order points are accepted (cofactor multiplication |
| 69 | + /// makes them indistinguishable from the identity contribution). This is |
| 70 | + /// backward compatible with `ed25519_dalek::verify_strict`: every signature |
| 71 | + /// dalek accepts is accepted here. |
| 72 | + /// |
| 73 | + /// [ZIP-215]: https://zips.z.cash/zip-0215 |
| 74 | + /// [SIMD-0376]: https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0376-verify-strict.md |
| 75 | + pub const fn zip215() -> Self { |
| 76 | + Self { |
| 77 | + cofactored: true, |
| 78 | + require_canonical_a: false, |
| 79 | + require_canonical_r: false, |
| 80 | + reject_small_order_a: false, |
| 81 | + reject_small_order_r: false, |
| 82 | + require_canonical_s: true, |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + /// The criteria enforced by `ed25519_dalek::VerifyingKey::verify_strict`. |
| 87 | + /// |
| 88 | + /// Cofactorless verification with canonical `S`, canonical `R`, and |
| 89 | + /// small-order rejection for both `A` and `R`. Mirrors ed25519-dalek 2.x |
| 90 | + /// exactly, including the detail that a non-canonically encoded public key |
| 91 | + /// `A` is *not* rejected — dalek's `VerifyingKey::from_bytes` decompresses |
| 92 | + /// `A` (reducing `y` modulo `p`) without a canonicity check, and |
| 93 | + /// `verify_strict` only re-encodes and compares `R`. Every signature this |
| 94 | + /// preset accepts is accepted by dalek's `verify_strict`, and vice versa. |
| 95 | + pub const fn dalek_verify_strict() -> Self { |
| 96 | + Self { |
| 97 | + cofactored: false, |
| 98 | + require_canonical_a: false, |
| 99 | + require_canonical_r: true, |
| 100 | + reject_small_order_a: true, |
| 101 | + reject_small_order_r: true, |
| 102 | + require_canonical_s: true, |
| 103 | + } |
| 104 | + } |
| 105 | +} |
| 106 | + |
| 107 | +impl Default for VerificationCriteria { |
| 108 | + fn default() -> Self { |
| 109 | + Self::zip215() |
| 110 | + } |
| 111 | +} |
0 commit comments