darkfi_sdk/crypto/util.rs
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
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2024 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use darkfi_serial::ReadExt;
use halo2_gadgets::poseidon::primitives as poseidon;
use pasta_curves::{
group::ff::{FromUniformBytes, PrimeField},
pallas,
};
use std::io::Cursor;
use subtle::CtOption;
use crate::{
error::{ContractError, GenericResult},
hex::{decode_hex_arr, hex_from_iter},
};
#[inline]
fn hash_to_field_elem<F: FromUniformBytes<64>>(persona: &[u8], vals: &[&[u8]]) -> F {
let mut hasher = blake2b_simd::Params::new().hash_length(64).personal(persona).to_state();
for v in vals {
hasher.update(v);
}
F::from_uniform_bytes(hasher.finalize().as_array())
}
/// Hash a slice of values together with a prefix `persona` using BLAKE2b
/// and return a `pallas::Scalar` element from the digest.
pub fn hash_to_scalar(persona: &[u8], vals: &[&[u8]]) -> pallas::Scalar {
hash_to_field_elem(persona, vals)
}
/// Hash a slice of values together with a prefix `persona` using BLAKE2b
/// and return a `pallas::Scalar` element from the digest.
pub fn hash_to_base(persona: &[u8], vals: &[&[u8]]) -> pallas::Base {
hash_to_field_elem(persona, vals)
}
/// Converts from pallas::Base to pallas::Scalar (aka $x \pmod{r_\mathbb{P}}$).
///
/// This requires no modular reduction because Pallas' base field is smaller than its
/// scalar field.
pub fn fp_mod_fv(val: pallas::Base) -> pallas::Scalar {
pallas::Scalar::from_repr(val.to_repr()).unwrap()
}
/// Converts from pallas::Scalar to pallas::Base (aka $x \pmod{r_\mathbb{P}}$).
///
/// This call is unsafe and liable to fail. Use with caution.
/// The Pallas scalar field is bigger than the field we're converting to here.
pub fn fv_mod_fp_unsafe(val: pallas::Scalar) -> CtOption<pallas::Base> {
pallas::Base::from_repr(val.to_repr())
}
/// Wrapper around poseidon in `halo2_gadgets`
pub fn poseidon_hash<const N: usize>(messages: [pallas::Base; N]) -> pallas::Base {
// TODO: it's possible to make this function simply take a slice, by using the lower level
// sponge defined in halo2 lib. Simply look how the function hash() is defined.
// Why is this needed? Simply put we are often working with dynamic data such as Python
// or with other interpreted environments. We don't always know the length of input data
// at compile time.
poseidon::Hash::<_, poseidon::P128Pow5T3, poseidon::ConstantLength<N>, 3, 2>::init()
.hash(messages)
}
pub fn fp_to_u64(value: pallas::Base) -> Option<u64> {
let repr = value.to_repr();
if !repr[8..].iter().all(|&b| b == 0u8) {
return None
}
let mut cur = Cursor::new(&repr[0..8]);
let uint = ReadExt::read_u64(&mut cur).ok()?;
Some(uint)
}
// Not allowed to implement external traits for external crates
pub trait FieldElemAsStr: PrimeField<Repr = [u8; 32]> {
fn to_string(&self) -> String {
// We reverse repr since it is little endian encoded
"0x".to_string() + &hex_from_iter(self.to_repr().iter().cloned().rev())
}
fn from_str(hex: &str) -> GenericResult<Self> {
if hex.len() != 33 * 2 {
return Err(ContractError::HexFmtErr)
}
let hex = hex.strip_prefix("0x").ok_or(ContractError::HexFmtErr)?;
let mut bytes = decode_hex_arr(hex)?;
bytes.reverse();
Option::from(Self::from_repr(bytes)).ok_or(ContractError::HexFmtErr)
}
}
impl FieldElemAsStr for pallas::Base {}
impl FieldElemAsStr for pallas::Scalar {}
#[test]
fn test_fp_to_u64() {
use super::pasta_prelude::Field;
let fp = pallas::Base::from(u64::MAX);
assert_eq!(fp_to_u64(fp), Some(u64::MAX));
assert_eq!(fp_to_u64(fp + pallas::Base::ONE), None);
}
#[test]
fn test_fp_to_str() {
use self::FieldElemAsStr;
let fpstr = "0x227ae0da79929f3e23f8d5bc9992f5f140f5198932378731e1b49b67fdc296c8";
assert_eq!(pallas::Base::from_str(fpstr).unwrap().to_string(), fpstr);
let fpstr = "0x000000000000000000000000000000000000000000000000ffffffffffffffff";
let fp = pallas::Base::from(u64::MAX);
assert_eq!(fp.to_string(), fpstr);
assert_eq!(pallas::Base::from_str(fpstr).unwrap(), fp);
}