Skip to content

Commit

Permalink
rust-arkworks hash_to_curve reference fn upgrade
Browse files Browse the repository at this point in the history
It was transferred to appropriate location, relevant dependepcies
updated, code was improved and reworked.
Find below some rationale on the most discussionable choice
of the rework.

## Rationale
`from_random_bytes` might be preferred way that will be found
in the actual hash_to_curve functions, but it doesn't let us be generic
in this reference example. `[&u8]` interface is broad and makes too few
assumptions to be sure it's able to preserve properties of our function.
Adding that behind broad interface only shallow trait definition, it's
hard to be sure in its behavior in different contexts. These design
choices could be justified to discourage its usage out of context
of particular (curve) implementation.

That said, devs using the trait should be seeking standardized
primitives, and delve into all the details down the chain
of calls when it's unavoidable.

I feel like input randomness could be damaged in subtle ways using
`from_random_bytes` without thorough understanding of particular
implementation. Future updates can damage it too
in non-obvious way as crate(s) isn't quite stable yet.

So as far as it's just an example *let's go with naive but transparent
approach.* Complexity of production implemetation can grow to very
significant level as seen at
<https://docs.rs/ark-ec/0.4.2/ark_ec/hashing/curve_maps/swu/trait.SWUConfig.html>.
  • Loading branch information
skaunov committed Oct 2, 2023
1 parent f637f67 commit e83a9e8
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 46 deletions.
2 changes: 2 additions & 0 deletions rust-arkworks/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ k256 = {version = "0.11.3", features = ["arithmetic", "hash2curve", "expose-fiel
generic-array = { version = "0.14", default-features = false }
hex = "0.4.3"

[dev-dependencies]
bitvec = "*"
103 changes: 57 additions & 46 deletions rust-arkworks/examples/reference_hash_to_curve.rs
Original file line number Diff line number Diff line change
@@ -1,60 +1,71 @@
// use ark_ec::{AffineCurve, ProjectiveCurve};
use ark_ec::{AffineRepr, CurveGroup, CurveConfig, short_weierstrass::SWCurveConfig};
use tiny_keccak::{Hasher, Shake, Xof};
use elliptic_curve::hash2curve::{ExpandMsgXmd, GroupDigest};
use k256::AffinePoint;
use k256::sha2::Sha256;
use elliptic_curve::sec1::ToEncodedPoint;
// use ark_ec::short_weierstrass_jacobian::GroupAffine;
use k256::{ProjectivePoint, Secp256k1};
use ark_ff::{BigInteger, BigInt, fields::{Field, PrimeField}};
#![feature(iter_array_chunks)]

const fn digest_len(modulus_bit_size: u32) -> usize {(modulus_bit_size/8 + if modulus_bit_size % 8 != 0 {1} else {0}) as usize}
const fn bigint_len(bytearray_size: usize) -> usize {bytearray_size/8 + if bytearray_size % 8 != 0 {1} else {0}}
use ark_ec::{
short_weierstrass::{Affine, SWCurveConfig},
AffineRepr, CurveConfig,
};
use ark_ff::{
fields::{Field, PrimeField},
BigInteger,
};
use bitvec::prelude::*;
use tiny_keccak::{Hasher, Shake, Xof};

/// Kobi's hash_to_curve function, here for reference only
struct NaiveBytes(Vec<u8>);
impl Default for NaiveBytes {fn default() -> Self {Self(vec![0])}}
impl NaiveBytes {fn incement(&mut self) {
// TODO would be nice to make field private so that `unwrap` had no chance to panic
if self.0.iter().last().unwrap() == &u8::MAX {self.0.push(0);}
else {
self.0[self.0.len() - 1] += 1;
}
}}

pub fn _try_and_increment<C: CurveGroup + SWCurveConfig>(msg: &[u8]) -> C::Affine {
// pub fn _try_and_increment<C: SWCurveConfig>(msg: &[u8]) -> Affine<C> {
// `SWCurveConfig` chosen here just as a most general curve description, which can be tuned further with more appropriate to the task modules
let nonce = NaiveBytes::default();
///
/// WARNING: Not tested -- bugs expected. Don't use the reference example for practical cryptography.
pub fn _try_and_increment<C: SWCurveConfig>(msg: &[u8]) -> Affine<C> {
/* `SWCurveConfig` is chosen here just as a most general curve description, which can be
tuned further with more appropriate to the task modules.
Anyway if you really need to implement _hash to curve_, you should start from most close
implementation in the library that serves your particular needs, not from generic example. */
let mut nonce = NaiveBytes::default();
loop {
let mut h = Shake::v128();
h.update(&nonce.0);
h.update(msg.as_ref());
// let width_bits = C::Affine::MODULUS_BIT_SIZE /* + 1 */;
// let output_size = width_bits / 8 + if width_bits % 8 != 0 {1} else {0};


// as this one isn't intended to work with greater than 256 bits groups, checks for digest being long enough are omitted here
assert!(<<C as CurveGroup>::BaseField as Field>::BasePrimeField::MODULUS_BIT_SIZE <= 256u32); // just to be sure

let mut output_u8 = [0u8; digest_len(<<C as CurveGroup>::BaseField as Field>::BasePrimeField::MODULUS_BIT_SIZE)];
assert!(<C::BaseField as Field>::BasePrimeField::MODULUS_BIT_SIZE <= 256u32); // just to be sure

let mut output_u8 =
vec![0u8; digest_len(<C::BaseField as Field>::BasePrimeField::MODULUS_BIT_SIZE)];
h.squeeze(&mut output_u8);

// `from_bytes_be(sign: Sign, bytes: &[u8])`
// `assert_eq!(BigInt::from_bytes_be(Sign::Plus, b"A"),
// BigInt::parse_bytes(b"65", 10).unwrap());`

let output_u64 = output_u8.into_iter().chunk(8).map(|(i, chunk)| {chunk as u64});
// TODO check that `BigInt::new` is actually BE
if BigInt::new(output_u64).into() < <<C as CurveGroup>::BaseField as Field>::BasePrimeField::MODULUS {
if let Some(
result
) = ark_ec::short_weierstrass::Affine::get_point_from_x_unchecked(
// ) = ark_ec::models::short_weierstrass::Affine::get_point_from_x_unchecked(
<<C as CurveConfig>::BaseField as Field>::BasePrimeField::from_be_bytes_mod_order(&output_u8),

let output_bigint = <<<C as CurveConfig>::BaseField as Field>::BasePrimeField as PrimeField>::BigInt::from_bits_le(
bitvec::vec::BitVec::<u8, Lsb0>::from_vec(output_u8.clone()).into_iter().collect::<Vec<bool>>().as_slice()
);
if output_bigint < <<C::BaseField as Field>::BasePrimeField as PrimeField>::MODULUS {
if let Some(result) = Affine::get_point_from_x_unchecked(
<C as CurveConfig>::BaseField::from_base_prime_field(
<<<C as CurveConfig>::BaseField as Field>::BasePrimeField as PrimeField>::from_be_bytes_mod_order(&output_u8)
),
nonce.0.iter().last().unwrap() % 2 == 1
) {return result.into_group().into_affine();}
) {return result.clear_cofactor();}
}
// else {dbg!(nonce.0)}
nonce.increment();
}
}

/// Takes bit size and returns minimal number of bytes to fit these bits
const fn digest_len(modulus_bit_size: u32) -> usize {
(modulus_bit_size / 8 + if modulus_bit_size % 8 != 0 { 1 } else { 0 }) as usize
}

struct NaiveBytes(Vec<u8>);
impl Default for NaiveBytes {
fn default() -> Self {
Self(vec![0])
}
}
impl NaiveBytes {
fn increment(&mut self) {
// TODO would be nice to make field private so that `unwrap` had no chance to panic
if self.0.iter().last().unwrap() == &u8::MAX {
self.0.push(0);
} else {
*self.0.last_mut().unwrap() += 1;
}
}
}

0 comments on commit e83a9e8

Please sign in to comment.