darkfi_sdk/crypto/
util.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2025 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use darkfi_serial::ReadExt;
20use halo2_gadgets::poseidon::primitives as poseidon;
21use pasta_curves::{
22    group::ff::{FromUniformBytes, PrimeField},
23    pallas,
24};
25use std::io::Cursor;
26use subtle::CtOption;
27
28use crate::{
29    error::{ContractError, GenericResult},
30    hex::{decode_hex_arr, hex_from_iter},
31};
32
33#[inline]
34fn hash_to_field_elem<F: FromUniformBytes<64>>(persona: &[u8], vals: &[&[u8]]) -> F {
35    let mut hasher = blake2b_simd::Params::new().hash_length(64).personal(persona).to_state();
36
37    for v in vals {
38        hasher.update(v);
39    }
40
41    F::from_uniform_bytes(hasher.finalize().as_array())
42}
43
44/// Hash a slice of values together with a prefix `persona` using BLAKE2b
45/// and return a `pallas::Scalar` element from the digest.
46pub fn hash_to_scalar(persona: &[u8], vals: &[&[u8]]) -> pallas::Scalar {
47    hash_to_field_elem(persona, vals)
48}
49
50/// Hash a slice of values together with a prefix `persona` using BLAKE2b
51/// and return a `pallas::Scalar` element from the digest.
52pub fn hash_to_base(persona: &[u8], vals: &[&[u8]]) -> pallas::Base {
53    hash_to_field_elem(persona, vals)
54}
55
56/// Converts from pallas::Base to pallas::Scalar (aka $x \pmod{r_\mathbb{P}}$).
57///
58/// This requires no modular reduction because Pallas' base field is smaller than its
59/// scalar field.
60pub fn fp_mod_fv(val: pallas::Base) -> pallas::Scalar {
61    pallas::Scalar::from_repr(val.to_repr()).unwrap()
62}
63
64/// Converts from pallas::Scalar to pallas::Base (aka $x \pmod{r_\mathbb{P}}$).
65///
66/// This call is unsafe and liable to fail. Use with caution.
67/// The Pallas scalar field is bigger than the field we're converting to here.
68pub fn fv_mod_fp_unsafe(val: pallas::Scalar) -> CtOption<pallas::Base> {
69    pallas::Base::from_repr(val.to_repr())
70}
71
72/// Wrapper around poseidon in `halo2_gadgets`
73pub fn poseidon_hash<const N: usize>(messages: [pallas::Base; N]) -> pallas::Base {
74    // TODO: it's possible to make this function simply take a slice, by using the lower level
75    // sponge defined in halo2 lib. Simply look how the function hash() is defined.
76    // Why is this needed? Simply put we are often working with dynamic data such as Python
77    // or with other interpreted environments. We don't always know the length of input data
78    // at compile time.
79    poseidon::Hash::<_, poseidon::P128Pow5T3, poseidon::ConstantLength<N>, 3, 2>::init()
80        .hash(messages)
81}
82
83pub fn fp_to_u64(value: pallas::Base) -> Option<u64> {
84    let repr = value.to_repr();
85    if !repr[8..].iter().all(|&b| b == 0u8) {
86        return None
87    }
88    let mut cur = Cursor::new(&repr[0..8]);
89    let uint = ReadExt::read_u64(&mut cur).ok()?;
90    Some(uint)
91}
92
93// Not allowed to implement external traits for external crates
94pub trait FieldElemAsStr: PrimeField<Repr = [u8; 32]> {
95    fn to_string(&self) -> String {
96        // We reverse repr since it is little endian encoded
97        "0x".to_string() + &hex_from_iter(self.to_repr().iter().cloned().rev())
98    }
99
100    fn from_str(hex: &str) -> GenericResult<Self> {
101        if hex.len() != 33 * 2 {
102            return Err(ContractError::HexFmtErr)
103        }
104
105        let hex = hex.strip_prefix("0x").ok_or(ContractError::HexFmtErr)?;
106
107        let mut bytes = decode_hex_arr(hex)?;
108        bytes.reverse();
109
110        Option::from(Self::from_repr(bytes)).ok_or(ContractError::HexFmtErr)
111    }
112}
113
114impl FieldElemAsStr for pallas::Base {}
115impl FieldElemAsStr for pallas::Scalar {}
116
117#[test]
118fn test_fp_to_u64() {
119    use super::pasta_prelude::Field;
120
121    let fp = pallas::Base::from(u64::MAX);
122    assert_eq!(fp_to_u64(fp), Some(u64::MAX));
123    assert_eq!(fp_to_u64(fp + pallas::Base::ONE), None);
124}
125
126#[test]
127fn test_fp_to_str() {
128    use self::FieldElemAsStr;
129    let fpstr = "0x227ae0da79929f3e23f8d5bc9992f5f140f5198932378731e1b49b67fdc296c8";
130    assert_eq!(pallas::Base::from_str(fpstr).unwrap().to_string(), fpstr);
131
132    let fpstr = "0x000000000000000000000000000000000000000000000000ffffffffffffffff";
133    let fp = pallas::Base::from(u64::MAX);
134    assert_eq!(fp.to_string(), fpstr);
135    assert_eq!(pallas::Base::from_str(fpstr).unwrap(), fp);
136}