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
//! An sUILD is a "signed ULID". It's like a ULID, but works better in distributed systems.
//!
//! # The Problem
//!
//! Normal ULIDs have two parts:
//! 1) A 48-bit timestamp
//! 2) A random 80-bit suffix.
//!
//! Together, these *should* be globally unique. However, in a distributed
//! system composed of peers of varying trustworthiness, things can go wrong.
//! Malicious peers are free to assign their own ULIDs which conflict with those
//! that already exist in other systems. This could be used as a form of Denial of
//! Service attack, if the attacker can cause their ULIDs to supersede or replace an
//! existing ULID.
//!
//! We need something like ULIDs, but with the following properties:
//!
//! 1) Malicious users can not (easily) cause duplicate ULIDs to enter the system.
//! 2) System administrators can not modify the ULID for a message.
//!
//! # The Solution
//!
//! sULIDs solve each of the above needs.
//!
//! 1) The "random" 80 bits of a ULID are replaced by 80 bits derived from
//! a cryptographic signature. It is non-trivial to generate a signature that
//! has a collision on these bits.
//! 2) The timestamp portion of the ULID is part of the signed payload, so an
//! admin can not change the timestamp without breaking the sULID/signature relationship.
//!
//! Additionally, the payload signed by sULIDs contains a blake3 hash of the content being signed.
//! If the content is large, systems can take advantage of blake3 "verified streaming" to
//! verify content bytes as they are being fetched.
//!
//! # Example
//!
//! ```
//! # use std::time::SystemTime;
//! # use signed_ulid::{UnsignedPayload, Sulid};
//! # use ed25519_dalek::{SigningKey};
//! #
//! # pub fn blake3hash(bytes: &[u8]) -> blake3::Hash {
//! # let mut hasher = blake3::Hasher::new();
//! # hasher.update(bytes);
//! # hasher.finalize()
//! # }
//! #
//! # pub fn random_secret() -> SigningKey {
//! # use getrandom::SysRng;
//! # use rand_core::UnwrapErr;
//! # let mut prng = UnwrapErr(SysRng);
//! # SigningKey::generate(&mut prng)
//! # }
//! # let secret = random_secret();
//! #
//! let message = "This message will be signed and given an sULID.";
//! let app_context = b"my-app".to_vec();
//!
//! let signed = Sulid::sign(
//! &secret,
//! UnsignedPayload {
//! timestamp: SystemTime::now(),
//! message_hash: blake3hash(message.as_bytes()),
//! message_length: message.as_bytes().len() as u64,
//! app_metadata: app_context,
//! },
//! );
//!
//! println!("Generated sULID: {}", signed.sulid);
//! assert!(signed.is_valid());
//! ```
//!
//! This crate doesn't dictate how you serialize the [`SignedPayload`], only that you must be able to
//! reconstruct it to validate that the sULID and signature are in agreement. For example, the above
//! message and `SignedPayload` might be serialized into plaintext, with an inline message, like this:
//!
//! ```text
//! id: 01M10D78ZBGZNVWHA60G8P95W1
//! by: WchunVqWZD7TfVoYkM1BPCzDpsrKTyN8ur2aZxWwbjQ
//! sig: 46kq3wNwQTjZKH9PjSWJeX9a7SwMkSni2t7hxorRDmgNXqrkYPey7WodyLs1npHBsFcdFGCJcV7dHF2hJtWPcpKR
//!
//! This message will be signed and given an sULID.
//! ```
//!
//! You can reconstructed the `SignedPayload` fields `sulid`, `public_key`, and
//! `signature` directly from the first 3 lines.
//!
//! The message, which begins after the empty line, can be used to recalculate
//! `message_hash` and `message_length`.
//!
//! And `app_metadata` in this case is just hard-coded by our application to
//! distinguish it from other signing schemes. But, it could be extended to allow
//! more (signed!) fields in the header.
use ;
// re-export, since its types are part of our public api.
pub use ed25519_dalek;
use ;
use Ulid;
pub use DecodeError as UlidDecodeError;
use crate;
/// A "signed" ULID, whose "random" portion is generated by a cryptographic signature.
///
// public impl
// private impl
/// Provides the canonical .to_string() form of sULIDs
/// Passed to [`Sulid::sign`] to create an sULID.
/// Output of [`Sulid::sign`], also used to verify sULIDs.
///
/// This contains the generated sULID and the necessary context to validate it.
/// The sULID generated by [`Sulid::sign`] will always be valid, so no need to revalidate it.
///
/// However, if you want to validate an untrusted sULID, construct this SignedPayload and check
/// [`SignedPayload::is_valid()`]