1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! DSTU 4145-2002 sign/verify, transcribed from Bouncy Castle's `DSTU4145Signer`
//! (`docs/DECISIONS.md` D-02/D-14, `docs/pseudocode/dstu4145.md`) - built on `gf2m163`'s field
//! arithmetic, `curve163`'s point arithmetic, and `scalar`'s mod-`n` integer arithmetic.
//!
//! **The public key is `Q = -d*G`, not `d*G`** - see `docs/pseudocode/dstu4145.md`'s 2026-07-22
//! note and `docs/DECISIONS.md` D-25's follow-up entry for how this was found (Bouncy Castle's own
//! `DSTU4145KeyPairGenerator` negates explicitly; the sign/verify identity only closes under this
//! convention). Callers deriving `Q` from `d` (e.g. `g.scalar_multiply(d)`) must negate via
//! `Point::negate` - this module takes `Q` as given rather than computing it, so it can't enforce
//! that for you.
//!
//! `verify` is public-data-only throughout (`r`, `s`, `Q`, `G` are all public in DSTU 4145
//! verification) - ordinary branches and `==` are fine here, same posture as `curve163`'s
//! `double`/`add`. The `s*G + r*Q` combine step itself goes through `curve163::verify_combine`
//! (`docs/DECISIONS.md` D-108), whose default-profile body branches freely on point-at-infinity
//! and x-coordinate coincidence - safe specifically because none of its inputs are ever secret.
use ;
use FieldElement;
use Scalar;
/// Official text §5.9 ("Перетворення геш-коду на елемент основного поля"): given a hash-code
/// `(h_{L_H-1},...,h_0)`, compute `k = min(m, L_H)`, take `x_i = h_i` for `i = 0..k-1`, and zero
/// the rest. In byte terms (§5.1/§5.6's own big-endian convention, `h_0` is the *last* bit of the
/// hash's last byte): keep the hash's **last** `min(len, 21)` bytes as-is, masking the top byte to
/// its low 3 bits if a full 21 were taken (163 bits total) - **no byte reversal**, contrary to
/// what an earlier version of this function did (see `docs/DECISIONS.md` D-25's follow-up-of-a-
/// follow-up entry: re-deriving this against the official text is what caught it - the previous
/// version only produced the right answer when its caller manually pre-reversed the hash first,
/// an easy-to-forget, undocumented API footgun that happened to cancel out against how the
/// `gf2m163.json` vector's own source test constructs its input).
/// `truncate(y, n.bit_length() - 1)`: keeps the low 162 bits of a field element's integer value,
/// as `r`/`r'` (an ordinary integer from here on, not a field element - see the module doc).
/// Big-endian byte-array comparison - public magnitude check (`r < n`, `s < n`), fine as ordinary
/// branches per the module doc.
/// `verifySignature` (see the pseudocode doc). `hash` is an already-computed message digest (not
/// hashed again here - DSTU 4145 is digest-agnostic, see the pseudocode doc's note on this).
// r, s, q, g, h, n match the pseudocode doc's own names
/// `generateSignature` (see the pseudocode doc). `e` is the caller-supplied ephemeral scalar - a
/// nonce, like the IV/key parameters other `hazmat` primitives take explicitly (see the `hazmat`
/// module doc: no forced RNG dependency here). **`e` must be freshly random and secret for every
/// real signature** - reusing it, like reusing an IV, breaks the scheme (a fixed `e` is only
/// valid for reproducing the `gf2m163.json` KAT, per that vector's own note).
///
/// Returns `None` on any of the pseudocode's three degenerate-value rejections (`F_e == 0`,
/// `r == 0`, `s == 0`) - each has probability roughly `2^-163`, the same accepted-exception class
/// as ECDSA's own nonce-rejection loops, not a realistic caller path. Since `hazmat` cannot
/// generate a replacement `e` itself, the caller must retry with a fresh one.
// r, s, d, e, h match the pseudocode doc's own names