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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
//! `crypto_sign` equivalent (`docs/dstu-crypto-project.md` "Mapping onto the libsodium API",
//! `docs/TASKS.md` T-48) - a libsodium-ergonomics wrapper over `hazmat::dstu4145::signature`. The first
//! module in the high-level layer D-09 planned but never built (`docs/release-readiness.md` step
//! 4) - this session's shape for it: `SigningKey`/`VerifyingKey`/`Signature`, `ed25519-dalek`-style
//! naming (`docs/DECISIONS.md` D-04's addendum cites that crate's convention).
//!
//! Two departures from `hazmat::dstu4145::signature`'s raw API, both documented in `docs/DECISIONS.md`
//! D-46:
//! - **The ephemeral nonce is derived deterministically** from `(d, message)` via
//! `hazmat::kupyna_kmac` (an RFC-6979-style adaptation, not a literal port - RFC 6979 is
//! HMAC-specific, `hazmat::kupyna_kmac`'s construction is not HMAC). No RNG dependency anywhere
//! in this module, unlike Bouncy Castle's `DSTU4145Signer` (which uses `SecureRandom`) - a
//! deliberate, user-confirmed deviation from the reference, matching Ed25519/libsodium's own
//! misuse-resistant signing design rather than the DSA-family default of caller-supplied
//! randomness (whose reuse is a real-world catastrophic key-recovery class: PS3, several Bitcoin
//! wallet thefts).
//! - **`sign`/`verify` take a raw `message: &[u8]`, not a pre-computed digest** - this module
//! hashes it internally with Kupyna-256 (`hazmat::kupyna::Kupyna256`), matching libsodium's own
//! `crypto_sign(message, ...)` ergonomics. `hazmat::dstu4145::signature` itself stays
//! digest-agnostic (its own doc comment's stated design), unaffected by this choice.
//!
//! **Large/streamed messages (`docs/TASKS.md` T-113): `sign_digest`/`verify_digest`.** DSTU 4145 signs
//! a hash of the message, not a domain-separated multi-part construction (`docs/pseudocode/
//! dstu4145.md` §5.9/§9/§10: `h ← hash_to_field(H(T))`) - so there is no "streaming signer" to
//! build, only a need to let the hash itself be computed incrementally. `sign`/`verify` above
//! still take the whole message and hash it with one `Kupyna256::digest` call, which needs it all
//! in memory at once; `sign_digest`/`verify_digest` instead take an already-computed 32-byte
//! Kupyna-256 digest directly, so a caller with a large or streamed message can hash it themselves
//! via `hazmat::kupyna::Kupyna256Hasher::{new, update, finalize}` (already `no_std`-compatible,
//! bounded memory regardless of message size) and pass the result in. `sign`/`verify` are now thin
//! wrappers over these two.
//!
//! **Keypair generation (`docs/TASKS.md` T-122): [`SigningKey::generate`].** `from_bytes` above only
//! ever *validates* a caller-supplied `d` - until this method existed there was no way to obtain a
//! valid `d` through the public API at all, without reaching into `hazmat` internals
//! (`curve163::order()` isn't part of this module's own surface). `#[cfg(any(feature = "std",
//! feature = "getrandom"))]`-gated (needs `crate::randombytes`, `docs/TASKS.md` T-123/`docs/DECISIONS.md`
//! D-74), matching every other `crypto_*` module's own `Key::generate` convention
//! (`crypto_secretbox`/`crypto_auth`/`crypto_kdf`/`crypto_stream`/`crypto_secretstream`).
//!
//! `VerifyingKey::to_uncompressed_bytes`/`from_uncompressed_bytes` use a plain 42-byte `x || y`
//! encoding, **not** the DSTU 4145 standard's own compressed point encoding (official text
//! §6.9/§6.10, `DSTU4145PointEncoder.java` in Bouncy Castle) - that encoding isn't implemented
//! anywhere in this project yet (`docs/pseudocode/dstu4145.md`'s existing note lists it as future
//! work, unrelated to sign/verify itself). Anyone needing interoperable, spec-compliant public-key
//! serialization must wait for that, tracked separately in `docs/TASKS.md`.
//!
//! # Example
//!
//! A signature proves a message came from whoever holds the signing key and hasn't been altered
//! since - unlike [`crate::crypto_secretbox`], it does not hide the message's contents, only
//! attests to its origin and integrity. Both the success path and a rejected forgery are shown
//! below (`docs/TASKS.md` T-120's own requirement - a signature example that only shows the happy path
//! doesn't demonstrate the primitive actually does what it claims).
//!
//! ```rust
//! use dstu_core::crypto_sign::SigningKey;
//!
//! # if cfg!(miri) { return; } // several Point::scalar_multiply calls - minutes each under Miri's
//! # // interpreter (docs/TASKS.md T-100/T-156/D-113); type-checked normally, just not executed
//! # // there. `cargo test` (not Miri) still runs this doctest for real every push.
//! let signing_key = SigningKey::generate().expect("OS CSPRNG should not fail");
//! let verifying_key = signing_key.verifying_key(); // safe to share/publish
//!
//! let message = b"a message whose origin and integrity matter";
//! let signature = signing_key.sign(message);
//! assert!(verifying_key.verify(message, &signature));
//!
//! // A different message, or a signature from a different key, must fail to verify.
//! assert!(!verifying_key.verify(b"a different message", &signature));
//! let other_key = SigningKey::generate().expect("OS CSPRNG should not fail");
//! assert!(!other_key.verifying_key().verify(message, &signature));
//! ```
use crate;
use crateFieldElement;
use crateScalar;
use cratesignature;
use crateKupyna256;
use crateKupyna256Kmac;
use Zeroize;
/// One-byte curve identifier for tagged, self-describing serialization of a
/// [`crate::crypto_sign::VerifyingKey`] or [`crate::crypto_sign257::VerifyingKey`] - lives here
/// (not duplicated in `crypto_sign257`, and not in `uacrypt` or any language binding) so every
/// caller shares the same tag numbering, the D-118 lesson (`crypto_secretstream`'s wire-format
/// validation) already learned once about not letting each binding re-hand-roll a parser
/// (`docs/DECISIONS.md` D-186 Decision 1). Values `3` and above are reserved for the other 8
/// `DSTU4145NamedCurves.java` curve sizes, if any are ever implemented (`docs/TASKS.md` T-199).
///
/// This tags *keys/signatures a caller serializes themselves* (e.g. `uacrypt sign-pubkey`'s output
/// file) - `crypto_sign::VerifyingKey`/`crypto_sign257::VerifyingKey`'s own `to_uncompressed_bytes`
/// stay untagged fixed-width encodings (42/66 bytes), matching how they already worked before this
/// enum existed; a caller that wants a self-describing blob prepends the matching [`CurveId`] byte
/// itself, exactly as `uacrypt`'s own `sign-pubkey`/`sign-pubkey257`/`verify` commands do.
/// A DSTU 4145 signature, `r || s` (21 bytes each, 42 total - `hazmat::dstu4145::signature`'s own
/// byte convention).
/// A DSTU 4145 private key. Signing needs no RNG (see the module doc) - only key generation from
/// external entropy is the caller's concern, same posture as `hazmat::kalyna_ccm`'s nonce
/// (`docs/DECISIONS.md` D-40): this module takes `d` as given rather than generating it.
;
/// A DSTU 4145 public key `Q = -d*G` (`hazmat::dstu4145::signature`'s module doc / `docs/DECISIONS.md`
/// D-25's follow-up entry on the sign convention).
;
/// Deterministic ephemeral-nonce derivation (`docs/DECISIONS.md` D-46): `e = reduce_mod_n(KMAC(key =
/// zero-padded d, message = hash || counter))`, retried with an incremented `counter` on the
/// ~`2^-163`-probability chance of a zero result or a hazmat-level degenerate rejection. `d`'s
/// 21-byte big-endian value is left-padded with zeros to `Kupyna256Kmac`'s required 32-byte key
/// length (`hazmat::kupyna_kmac`'s key length must equal its `mac_len`) - an embedding, not a
/// truncation, so no information about `d` is lost.