Skip to main content

rns_crypto/
stamp_value.rs

1//! Fractional stamp values (upstream `RNS.Stamps.stamp_value` since rgit
2//! `78fb90ea`).
3//!
4//! A stamp's value is the base-2 logarithm of how unlikely its stamp product
5//! `SHA-256(workblock || stamp)` is, quantized down onto a 1/256 grid. The
6//! integer part is the leading zero bit count (the legacy value) and the
7//! fraction is read off the next 33 bits of the product through the
8//! authoritative mantissa table [`TAB`]. Values are carried as exact
9//! fixed-point [`StampValue`]s; conversion to `f64` only happens where a value
10//! leaves the process, as upstream reports them as floats.
11
12use alloc::format;
13use alloc::string::String;
14use core::fmt;
15
16use crate::sha256;
17
18/// Significand bits of the normalized mantissa (upstream `MANTISSA_BITS`).
19pub const MANTISSA_BITS: u32 = 33;
20
21/// Fractional bits of the fixed-point representation (`COST_FP_BITS`).
22pub const COST_FP_BITS: u32 = 8;
23
24/// Fixed-point scale: a value of 1.0 is `COST_FP` (`COST_FP`).
25pub const COST_FP: u32 = 1 << COST_FP_BITS;
26
27/// SHA-256 of the comma-joined decimal [`TAB`] entries (`TAB_DIGEST`).
28pub const TAB_DIGEST: &str = "55ba3ce1df224895b0dfdc102eb4359fc2f5a7efefee68df0cc90c8a8ac24f48";
29
30/// SHA-256 pinning the table digest and parameters (`PRM_DIGEST`).
31pub const PRM_DIGEST: &str = "21436fc98e76a12cad7c52ebeb6ab76c2ae93b4cd768949df7fdd4e2f98c378d";
32
33/// Fixed-point stamp target mantissa table,
34/// `TAB[f] = floor(2^33 * 2^(f/256))` for `f = 0..=255`. Copied verbatim from
35/// upstream, which declares the values authoritative: they must not be
36/// recomputed. The tests pin them to [`TAB_DIGEST`].
37pub const TAB: [u64; COST_FP as usize] = [
38    0x200000000,
39    0x201635f4b,
40    0x202c7b53f,
41    0x2042d0287,
42    0x2059347ce,
43    0x206fa85c2,
44    0x20862bd0d,
45    0x209cbee5e,
46    0x20b361a62,
47    0x20ca141c7,
48    0x20e0d653b,
49    0x20f7a856e,
50    0x210e8a30e,
51    0x21257becc,
52    0x213c7d958,
53    0x21538f363,
54    0x216ab0d9f,
55    0x2181e28bc,
56    0x21992456e,
57    0x21b076467,
58    0x21c7d865a,
59    0x21df4abfb,
60    0x21f6cd5ff,
61    0x220e6051a,
62    0x222603a02,
63    0x223db756b,
64    0x22557b80d,
65    0x226d5029e,
66    0x2285355d5,
67    0x229d2b269,
68    0x22b531914,
69    0x22cd48a8e,
70    0x22e57078f,
71    0x22fda90d2,
72    0x2315f2711,
73    0x232e4cb06,
74    0x2346b7d6d,
75    0x235f33f02,
76    0x2377c1080,
77    0x23905f2a5,
78    0x23a90e62d,
79    0x23c1cebd6,
80    0x23daa045f,
81    0x23f383087,
82    0x240c7710c,
83    0x24257c6af,
84    0x243e9322f,
85    0x2457bb44f,
86    0x2470f4dce,
87    0x248a3ff70,
88    0x24a39c9f6,
89    0x24bd0ae23,
90    0x24d68acbc,
91    0x24f01c683,
92    0x2509bfc3e,
93    0x252374eb2,
94    0x253d3bea3,
95    0x255714cda,
96    0x2570ffa1b,
97    0x258afc72e,
98    0x25a50b4dc,
99    0x25bf2c3ec,
100    0x25d95f527,
101    0x25f3a4957,
102    0x260dfc146,
103    0x262865dbd,
104    0x2642e1f89,
105    0x265d70775,
106    0x26781164c,
107    0x2692c4cdc,
108    0x26ad8abf2,
109    0x26c86345b,
110    0x26e34e6e7,
111    0x26fe4c463,
112    0x27195cda0,
113    0x27348036e,
114    0x274fb669c,
115    0x276aff7fd,
116    0x27865b862,
117    0x27a1ca89d,
118    0x27bd4c982,
119    0x27d8e1be3,
120    0x27f48a095,
121    0x28104586c,
122    0x282c1443e,
123    0x2847f64e1,
124    0x2863ebb2a,
125    0x287ff47f0,
126    0x289c10c0c,
127    0x28b840854,
128    0x28d483da3,
129    0x28f0dacd1,
130    0x290d456b8,
131    0x2929c3c32,
132    0x294655e1a,
133    0x2962fbd4d,
134    0x297fb5aa6,
135    0x299c83702,
136    0x29b96533f,
137    0x29d65b03b,
138    0x29f364ed3,
139    0x2a1082fe8,
140    0x2a2db5459,
141    0x2a4afbd07,
142    0x2a6856ad3,
143    0x2a85c5e9e,
144    0x2aa34994b,
145    0x2ac0e1bbd,
146    0x2ade8e6d6,
147    0x2afc4fb7c,
148    0x2b1a25a92,
149    0x2b38104ff,
150    0x2b560fba9,
151    0x2b7423f75,
152    0x2b924d14b,
153    0x2bb08b213,
154    0x2bcede2b5,
155    0x2bed4641b,
156    0x2c0bc372e,
157    0x2c2a55cd9,
158    0x2c48fd607,
159    0x2c67ba3a3,
160    0x2c868c699,
161    0x2ca573fd7,
162    0x2cc47104a,
163    0x2ce3838e1,
164    0x2d02aba89,
165    0x2d21e9633,
166    0x2d413cccf,
167    0x2d60a5f4e,
168    0x2d8024ea1,
169    0x2d9fb9bba,
170    0x2dbf6478c,
171    0x2ddf2530b,
172    0x2dfefbf2a,
173    0x2e1ee8cde,
174    0x2e3eebd1d,
175    0x2e5f050dd,
176    0x2e7f34914,
177    0x2e9f7a6ba,
178    0x2ebfd6ac8,
179    0x2ee049635,
180    0x2f00d29fb,
181    0x2f2172715,
182    0x2f4228e7d,
183    0x2f62f612e,
184    0x2f83da026,
185    0x2fa4d4c5f,
186    0x2fc5e66d9,
187    0x2fe70f092,
188    0x30084ea87,
189    0x3029a55ba,
190    0x304b13329,
191    0x306c983d7,
192    0x308e348c4,
193    0x30afe82f3,
194    0x30d1b3368,
195    0x30f395b26,
196    0x31158fb31,
197    0x3137a148f,
198    0x3159ca845,
199    0x317c0b75a,
200    0x319e642d6,
201    0x31c0d4bc1,
202    0x31e35d322,
203    0x3205fda05,
204    0x3228b6172,
205    0x324b86a75,
206    0x326e6f619,
207    0x32917056b,
208    0x32b489979,
209    0x32d7bb34e,
210    0x32fb053fb,
211    0x331e67c8f,
212    0x3341e2e19,
213    0x3365769aa,
214    0x338923054,
215    0x33ace8329,
216    0x33d0c633c,
217    0x33f4bd1a0,
218    0x3418ccf6b,
219    0x343cf5db1,
220    0x346137d89,
221    0x348593008,
222    0x34aa07647,
223    0x34ce9515e,
224    0x34f33c266,
225    0x3517fca78,
226    0x353cd6aaf,
227    0x3561ca426,
228    0x3586d77fa,
229    0x35abfe747,
230    0x35d13f32b,
231    0x35f699cc4,
232    0x361c0e531,
233    0x36419cd93,
234    0x366745709,
235    0x368d082b6,
236    0x36b2e51bc,
237    0x36d8dc53e,
238    0x36feede5f,
239    0x372519e44,
240    0x374b60614,
241    0x3771c16f3,
242    0x37983d209,
243    0x37bed387e,
244    0x37e584b7a,
245    0x380c50c28,
246    0x383337bb0,
247    0x385a39b3f,
248    0x388156bff,
249    0x38a88ef1f,
250    0x38cfe25ca,
251    0x38f751131,
252    0x391edb280,
253    0x394680aea,
254    0x396e41b9d,
255    0x39961e5cd,
256    0x39be16aab,
257    0x39e62ab6b,
258    0x3a0e5a940,
259    0x3a36a6561,
260    0x3a5f0e101,
261    0x3a8791d59,
262    0x3ab031b9f,
263    0x3ad8edd0c,
264    0x3b01c62d9,
265    0x3b2abae3f,
266    0x3b53cc07b,
267    0x3b7cf9ac7,
268    0x3ba643e60,
269    0x3bcfaac83,
270    0x3bf92e66f,
271    0x3c22ced63,
272    0x3c4c8c29e,
273    0x3c7666762,
274    0x3ca05dcf1,
275    0x3cca7248c,
276    0x3cf4a3f78,
277    0x3d1ef2ef9,
278    0x3d495f454,
279    0x3d73e90cf,
280    0x3d9e905b1,
281    0x3dc955443,
282    0x3df437dcc,
283    0x3e1f38396,
284    0x3e4a566ed,
285    0x3e759291b,
286    0x3ea0ecb6d,
287    0x3ecc64f31,
288    0x3ef7fb5b3,
289    0x3f23b0044,
290    0x3f4f83033,
291    0x3f7b746d2,
292    0x3fa784571,
293    0x3fd3b2d65,
294];
295
296/// Largest representable value: a stamp product of zero has value 256.
297const MAX_FP: u32 = 256 * COST_FP;
298
299/// A stamp value in fixed point, in 1/256 steps.
300#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
301pub struct StampValue(u32);
302
303impl StampValue {
304    pub const ZERO: StampValue = StampValue(0);
305
306    /// Value from its fixed-point representation, clamped to the maximum.
307    pub const fn from_fp(fp: u32) -> Self {
308        StampValue(if fp > MAX_FP { MAX_FP } else { fp })
309    }
310
311    /// Value of a whole number of bits, such as a legacy integer value.
312    pub const fn from_whole(value: u32) -> Self {
313        StampValue::from_fp(value.saturating_mul(COST_FP))
314    }
315
316    /// Value read from a float, floored onto the grid. Values written by this
317    /// implementation or upstream lie on the grid exactly; non-finite or
318    /// negative input reads as zero.
319    pub fn from_f64(value: f64) -> Self {
320        if !value.is_finite() || value <= 0.0 {
321            return StampValue::ZERO;
322        }
323        let fp = value * COST_FP as f64;
324        if fp >= MAX_FP as f64 {
325            StampValue(MAX_FP)
326        } else {
327            StampValue(fp as u32)
328        }
329    }
330
331    /// Fixed-point representation.
332    pub const fn fp(self) -> u32 {
333        self.0
334    }
335
336    /// Whole bits of value: the legacy leading-zero value (`cost_int`).
337    pub const fn whole(self) -> u32 {
338        self.0 >> COST_FP_BITS
339    }
340
341    /// The value as upstream reports it (`cost_from_fp`).
342    pub fn as_f64(self) -> f64 {
343        f64::from(self.0) / f64::from(COST_FP)
344    }
345
346    /// Whether the value reaches `cost`, as upstream compares a reported
347    /// value with a required cost (`value < get_link_cost()`). Validity of
348    /// a stamp itself is decided against the target, see
349    /// [`StampCost::admits`].
350    pub const fn meets(self, cost: StampCost) -> bool {
351        self.0 >= cost.0
352    }
353}
354
355impl fmt::Display for StampValue {
356    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357        fmt::Display::fmt(&self.as_f64(), f)
358    }
359}
360
361/// Global hard cap on stamp costs (`Transport.STAMP_COST_CAP`).
362pub const STAMP_COST_CAP: u32 = 100;
363
364const MAX_COST_FP: u32 = STAMP_COST_CAP * COST_FP;
365
366const _: () = assert!((MAX_FP - MAX_COST_FP) >> COST_FP_BITS >= MANTISSA_BITS);
367
368/// A stamp cost in fixed point, in 1/256 steps, clamped to
369/// [`STAMP_COST_CAP`]. Since rgit `a8cc6cb2` the default costs are
370/// fractional; a cost becomes a target through [`TAB`] exactly as a value is
371/// read through it.
372#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
373pub struct StampCost(u32);
374
375impl StampCost {
376    pub const ZERO: StampCost = StampCost(0);
377
378    /// Cost from its fixed-point representation, clamped to the cap.
379    pub const fn from_fp(fp: u32) -> Self {
380        StampCost(if fp > MAX_COST_FP { MAX_COST_FP } else { fp })
381    }
382
383    /// Cost from its fixed-point representation, or `None` above the cap,
384    /// for costs read off the wire.
385    pub const fn from_fp_checked(fp: u32) -> Option<Self> {
386        if fp > MAX_COST_FP {
387            None
388        } else {
389            Some(StampCost(fp))
390        }
391    }
392
393    /// Cost of a whole number of bits, such as a configured integer cost.
394    pub const fn from_whole(cost: u32) -> Self {
395        StampCost::from_fp(cost.saturating_mul(COST_FP))
396    }
397
398    /// Cost read from a float, rounded to the nearest grid point and clamped
399    /// (`cost_to_fp`). Non-finite or negative input reads as zero.
400    pub fn from_f64(cost: f64) -> Self {
401        if !cost.is_finite() || cost <= 0.0 {
402            return StampCost::ZERO;
403        }
404        // floor(cost * COST_FP + 0.5); truncation floors positive input.
405        let fp = cost * COST_FP as f64 + 0.5;
406        if fp >= MAX_COST_FP as f64 {
407            StampCost(MAX_COST_FP)
408        } else {
409            StampCost(fp as u32)
410        }
411    }
412
413    /// Fixed-point representation.
414    pub const fn fp(self) -> u32 {
415        self.0
416    }
417
418    /// Whole bits of the cost (`cost_int`).
419    pub const fn whole(self) -> u32 {
420        self.0 >> COST_FP_BITS
421    }
422
423    /// The cost as upstream reports it (`cost_from_fp`).
424    pub fn as_f64(self) -> f64 {
425        f64::from(self.0) / f64::from(COST_FP)
426    }
427
428    /// The stamp target for this cost (`target_from_fp`) as a big-endian
429    /// 256-bit number, or `None` for a zero cost, whose target is `2^256`
430    /// and admits every product.
431    pub fn target(self) -> Option<[u8; 32]> {
432        if self.0 == 0 {
433            return None;
434        }
435        // i, f = divmod(256*COST_FP - fp, COST_FP);
436        // target = (TAB[f] << i) >> MANTISSA_BITS. With fp >= 1, i <= 255
437        // and TAB[f] < 2^34, so the target fits in 256 bits.
438        let rest = MAX_FP - self.0;
439        let exponent = rest >> COST_FP_BITS;
440        let mantissa = TAB[(rest & (COST_FP - 1)) as usize];
441        // The cap keeps the exponent at 156 or more, so the shift is left.
442        let shift = exponent - MANTISSA_BITS;
443        let (limb, offset) = ((shift / 64) as usize, shift % 64);
444        let mut limbs = [0u64; 4];
445        limbs[limb] = mantissa << offset;
446        if offset > 0 && limb + 1 < limbs.len() {
447            limbs[limb + 1] = mantissa >> (64 - offset);
448        }
449        let mut target = [0u8; 32];
450        for (i, limb) in limbs.iter().rev().enumerate() {
451            target[i * 8..i * 8 + 8].copy_from_slice(&limb.to_be_bytes());
452        }
453        Some(target)
454    }
455
456    /// Whether a stamp product meets this cost: the product, read as a
457    /// big-endian integer, is at most the target (`stamp_valid`).
458    pub fn admits(self, product: &[u8; 32]) -> bool {
459        match self.target() {
460            None => true,
461            Some(target) => product[..] <= target[..],
462        }
463    }
464}
465
466impl fmt::Display for StampCost {
467    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
468        fmt::Display::fmt(&self.as_f64(), f)
469    }
470}
471
472/// A stamp value or cost as upstream's `RNS.prettycost` prints it (rgit
473/// `21170877`): rounded onto the cost grid and clamped like
474/// [`StampCost::from_f64`] (`quantize_cost`), with three decimals. Rust's
475/// `{:.3}` rounds the grid's exact ties half to even, as Python does.
476pub fn pretty_cost(value: f64) -> String {
477    format!("{:.3}", StampCost::from_f64(value).as_f64())
478}
479
480/// Value of a 256-bit stamp product.
481pub fn value_of_product(product: &[u8; 32]) -> StampValue {
482    let lz = leading_zeros(product);
483    if lz == 256 {
484        return StampValue(MAX_FP);
485    }
486    // The normalized mantissa is the leading one followed by MANTISSA_BITS
487    // bits. Bits past the end of the product read as zero, which matches
488    // upstream left-shifting short products.
489    let start = (lz / 8) as usize;
490    let mut window = [0u8; 16];
491    let available = (32 - start).min(16);
492    window[..available].copy_from_slice(&product[start..start + available]);
493    let aligned = u128::from_be_bytes(window) << (lz % 8);
494    let mantissa = (aligned >> (128 - (MANTISSA_BITS + 1))) as u64;
495    // Right-bisecting TAB counts the grid points at or below the mantissa,
496    // which floors the value onto the grid without ever over-valuing it.
497    let climb = TAB.partition_point(|&entry| entry <= mantissa) as u32;
498    StampValue((lz + 1) * COST_FP - climb)
499}
500
501/// Value of `stamp` over `workblock` (`stamp_value`).
502pub fn stamp_value(workblock: &[u8], stamp: &[u8]) -> StampValue {
503    let mut input = alloc::vec::Vec::with_capacity(workblock.len() + stamp.len());
504    input.extend_from_slice(workblock);
505    input.extend_from_slice(stamp);
506    value_of_product(&sha256::sha256(&input))
507}
508
509/// Leading zero bits of a 256-bit product (`stamp_value_legacy`).
510pub fn leading_zeros(product: &[u8; 32]) -> u32 {
511    let mut count = 0u32;
512    for &byte in product {
513        if byte == 0 {
514            count += 8;
515        } else {
516            count += byte.leading_zeros();
517            break;
518        }
519    }
520    count
521}
522
523#[cfg(test)]
524mod tests {
525    use super::*;
526    use alloc::format;
527    use alloc::string::String;
528    use alloc::vec::Vec;
529
530    fn hex(bytes: &[u8]) -> String {
531        bytes.iter().map(|b| format!("{b:02x}")).collect()
532    }
533
534    fn unhex(s: &str) -> Vec<u8> {
535        (0..s.len())
536            .step_by(2)
537            .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
538            .collect()
539    }
540
541    #[test]
542    fn table_matches_upstream_digests() {
543        assert_eq!(TAB[0], 1 << MANTISSA_BITS);
544        assert!(TAB.windows(2).all(|w| w[1] > w[0]));
545        let joined: Vec<String> = TAB.iter().map(|v| format!("{v}")).collect();
546        let tab_digest = hex(&sha256::sha256(joined.join(",").as_bytes()));
547        assert_eq!(tab_digest, TAB_DIGEST);
548        let params = format!(
549            "master|{TAB_DIGEST}|{MANTISSA_BITS}|{}|{COST_FP_BITS}|{COST_FP}|",
550            TAB.len()
551        );
552        assert_eq!(hex(&sha256::sha256(params.as_bytes())), PRM_DIGEST);
553    }
554
555    /// `(stamp, value * 256)` pairs computed by upstream rgit `78fb90ea`
556    /// `RNS.Stamps.stamp_value` over the workblock below, spanning leading
557    /// zero counts 0 to 16.
558    const UPSTREAM_WORKBLOCK: &[u8] = b"rns-rs fractional stamp vectors";
559    const UPSTREAM_VECTORS: &[(&str, u32)] = &[
560        ("af5570f5a1810b7af78caf4bc70a", 236),
561        ("cd2662154e6d76b2b2b92e70c0ca", 668),
562        ("cd04a4754498e06db5a13c5f371f", 88),
563        ("d5688a52d55a02ec4aea5ec1eadf", 112),
564        ("8005f02d43fa06e7d0585fb64c96", 276),
565        ("5dee4dd60ff8d0ba9900fe91e90e", 188),
566        ("14ac577cdb2ef6d986078b4054cc", 886),
567        ("a3eb8db89fc5123ccfd49585059f", 160),
568        ("4c0e071832d527694adea57b50dd", 349),
569        ("5924513516a5993435ec4a240610", 111),
570        ("7a42e3892368f826928202014a6c", 620),
571        ("561f627b4213258dc8863498bb9b", 742),
572        ("42f28a46039f894d3a0179d09085", 577),
573        ("1d7968ffc995d6a495071668f4a9", 730),
574        ("539b4c4c41a13f1f0452f5a35a67", 393),
575        ("ea676268c71eb961cae5f1a68dc8", 597),
576        ("e9a5f5201eb3c3c856e0a224527a", 511),
577        ("6a339ff6defc6e73cf17dea3ea81", 392),
578        ("0db5549dd341d8a11a21a653574f", 438),
579        ("dd677f19feae7d8e78334c0096d7", 1125),
580        ("76a872016c45a5edd4d933a2e45c", 1613),
581        ("23768e05b10a7f0335d3e35a9d30", 1477),
582        ("dc9684c3cd2e0715b1f151fda6d7", 1975),
583        ("d279f9164d2bf5ad26307e1a1f86", 2268),
584        ("d6050af84e83c2183c1a3005633f", 2693),
585        ("e6570450c0e7e92eb09bb2fb732c", 2426),
586        ("3e5bdd07714a45a792e880eeef89", 3027),
587        ("1d726593f982f447e8a65d82f51b", 4271),
588        ("cd3ff1775c55c55f6c5494b8eaa1", 3266),
589        ("f15bb2b358c7de7fbbd7a5280908", 3426),
590    ];
591
592    #[test]
593    fn values_match_upstream_vectors() {
594        for &(stamp, fp) in UPSTREAM_VECTORS {
595            let stamp = unhex(stamp);
596            assert_eq!(stamp_value(UPSTREAM_WORKBLOCK, &stamp).fp(), fp);
597        }
598    }
599
600    #[test]
601    fn value_stays_within_its_legacy_integer_step() {
602        for i in 0u32..4096 {
603            let product = sha256::sha256(&i.to_be_bytes());
604            let legacy = leading_zeros(&product);
605            let value = value_of_product(&product);
606            assert_eq!(value.whole(), legacy);
607            assert!(value.fp() >= legacy * COST_FP);
608            assert!(value.fp() < (legacy + 1) * COST_FP);
609            assert!(value.meets(StampCost::from_whole(legacy)));
610            assert!(!value.meets(StampCost::from_whole(legacy + 1)));
611        }
612    }
613
614    #[test]
615    fn value_is_monotone_in_the_product() {
616        // Smaller products are less likely and must never be worth less.
617        let mut product = [0u8; 32];
618        product[3] = 0x01;
619        let mut last = value_of_product(&product);
620        for top in (0x01u8..=0xff).step_by(3) {
621            product[4] = top;
622            let value = value_of_product(&product);
623            assert!(value <= last);
624            last = value;
625        }
626    }
627
628    #[test]
629    fn extreme_products() {
630        assert_eq!(value_of_product(&[0u8; 32]).as_f64(), 256.0);
631        assert_eq!(value_of_product(&[0xff; 32]).fp(), 0);
632        let mut one = [0u8; 32];
633        one[31] = 1;
634        // Shorter than the mantissa window: bits past the end read as zero,
635        // so the mantissa is exactly TAB[0]. An exact grid match floors one
636        // step below the ceiling, as in upstream.
637        assert_eq!(value_of_product(&one).fp(), 256 * COST_FP - 1);
638    }
639
640    /// `(cost, cost_to_fp(cost), target_from_fp(fp))` from upstream rgit
641    /// `a8cc6cb2`, including its fractional default costs.
642    const UPSTREAM_TARGETS: &[(f64, u32, &str)] = &[
643        (
644            23.5,
645            6016,
646            "0000016a09e66780000000000000000000000000000000000000000000000000",
647        ),
648        (
649            25.0,
650            6400,
651            "0000008000000000000000000000000000000000000000000000000000000000",
652        ),
653        (
654            19.8,
655            5069,
656            "0000125e85711800000000000000000000000000000000000000000000000000",
657        ),
658        (
659            19.2,
660            4915,
661            "00001bdf69c3f000000000000000000000000000000000000000000000000000",
662        ),
663        (
664            0.01,
665            3,
666            "fdedd1b480000000000000000000000000000000000000000000000000000000",
667        ),
668        (
669            1.0,
670            256,
671            "8000000000000000000000000000000000000000000000000000000000000000",
672        ),
673        (
674            8.0,
675            2048,
676            "0100000000000000000000000000000000000000000000000000000000000000",
677        ),
678        (
679            99.99,
680            25597,
681            "0000000000000000000000001021681438000000000000000000000000000000",
682        ),
683        (
684            100.0,
685            25600,
686            "0000000000000000000000001000000000000000000000000000000000000000",
687        ),
688    ];
689
690    /// `(fp, target_from_fp(fp))` from upstream, around step boundaries.
691    const UPSTREAM_FP_TARGETS: &[(u32, &str)] = &[
692        (
693            1,
694            "ff4ecb5940000000000000000000000000000000000000000000000000000000",
695        ),
696        (
697            255,
698            "8058d7d2c0000000000000000000000000000000000000000000000000000000",
699        ),
700        (
701            257,
702            "7fa765aca0000000000000000000000000000000000000000000000000000000",
703        ),
704        (
705            8447,
706            "000000008058d7d2c00000000000000000000000000000000000000000000000",
707        ),
708        (
709            8448,
710            "0000000080000000000000000000000000000000000000000000000000000000",
711        ),
712        (
713            8455,
714            "000000007d98c9e6200000000000000000000000000000000000000000000000",
715        ),
716    ];
717
718    #[test]
719    fn costs_and_targets_match_upstream() {
720        for &(cost, fp, target) in UPSTREAM_TARGETS {
721            let cost = StampCost::from_f64(cost);
722            assert_eq!(cost.fp(), fp);
723            assert_eq!(hex(&cost.target().unwrap()), target);
724        }
725        for &(fp, target) in UPSTREAM_FP_TARGETS {
726            assert_eq!(hex(&StampCost::from_fp(fp).target().unwrap()), target);
727        }
728        assert_eq!(StampCost::ZERO.target(), None);
729        assert!(StampCost::ZERO.admits(&[0xff; 32]));
730        assert_eq!(
731            StampCost::from_f64(1000.0),
732            StampCost::from_whole(STAMP_COST_CAP)
733        );
734        assert_eq!(StampCost::from_f64(-1.0), StampCost::ZERO);
735        assert_eq!(StampCost::from_f64(f64::NAN), StampCost::ZERO);
736        // Rounded to nearest, not floored like a value.
737        assert_eq!(StampCost::from_f64(19.8).fp(), 5069);
738        assert_eq!(StampValue::from_f64(19.8).fp(), 5068);
739    }
740
741    #[test]
742    fn whole_cost_targets_are_leading_zero_counts() {
743        for i in 0u32..4096 {
744            let product = sha256::sha256(&i.to_be_bytes());
745            let lz = leading_zeros(&product);
746            for cost in [0, 1, lz.saturating_sub(1), lz, lz + 1, 8, 100] {
747                let cost = StampCost::from_whole(cost);
748                assert_eq!(cost.admits(&product), lz >= cost.whole(), "{i} {cost}");
749            }
750        }
751    }
752
753    #[test]
754    fn target_agrees_with_the_value_off_the_boundary() {
755        // A product meets a cost exactly when its value does, except for a
756        // product equal to the target itself, which the target admits.
757        for i in 0u32..4096 {
758            let product = sha256::sha256(&i.to_le_bytes());
759            let value = value_of_product(&product);
760            for fp in [1, 37, 255, 256, 300, value.fp(), value.fp() + 1, 6016] {
761                let cost = StampCost::from_fp(fp);
762                assert_eq!(cost.admits(&product), value.meets(cost), "{i} {fp}");
763            }
764        }
765        let cost = StampCost::from_fp(6016);
766        let target = cost.target().unwrap();
767        assert!(cost.admits(&target));
768        assert!(!value_of_product(&target).meets(cost));
769    }
770
771    #[test]
772    fn float_conversions_round_trip_on_the_grid() {
773        for fp in [0, 1, 128, 255, 256, 23 * 256 + 187, MAX_FP] {
774            let value = StampValue::from_fp(fp);
775            assert_eq!(StampValue::from_f64(value.as_f64()), value);
776        }
777        assert_eq!(StampValue::from_f64(8.5).fp(), 8 * 256 + 128);
778        assert_eq!(StampValue::from_f64(-1.0), StampValue::ZERO);
779        assert_eq!(StampValue::from_f64(f64::NAN), StampValue::ZERO);
780        assert_eq!(StampValue::from_f64(1e9).fp(), MAX_FP);
781        assert_eq!(StampValue::from_whole(24).as_f64(), 24.0);
782        assert_eq!(format!("{}", StampValue::from_fp(8 * 256 + 128)), "8.5");
783        assert_eq!(format!("{}", StampValue::from_whole(8)), "8");
784    }
785
786    #[test]
787    fn stamp_cost_displays_the_cost_not_its_fixed_point() {
788        // Upstream 2ef855c8 logs `cost_from_fp(stamp_cost)`; the fixed-point
789        // integer (4915 for the 19.2 link request cost) is not a cost.
790        let cost = StampCost::from_fp(4915);
791        assert_eq!(format!("{cost}"), "19.19921875");
792        assert_eq!(format!("{}", StampCost::from_whole(8)), "8");
793        assert_eq!(format!("{}", StampCost::from_fp(8 * COST_FP + 128)), "8.5");
794    }
795
796    #[test]
797    fn pretty_cost_matches_upstream_prettycost() {
798        // Outputs of RNS.prettycost at rgit 6927c3c5.
799        assert_eq!(pretty_cost(23.5), "23.500");
800        assert_eq!(pretty_cost(24.73046875), "24.730");
801        assert_eq!(pretty_cost(19.2), "19.199");
802        assert_eq!(pretty_cost(0.0625), "0.062");
803        assert_eq!(pretty_cost(0.1875), "0.188");
804        assert_eq!(pretty_cost(256.0), "100.000");
805        assert_eq!(pretty_cost(-1.0), "0.000");
806    }
807}