-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(crypto): change hash_to_point algorithm (BFT-404) (#80)
## What ❔ Changing the existing `hash_to_point` algorithm to the standard try-and-increment algorithm: * hash msg into 32 uniform random bytes * reduce the 32 bytes to base field element for the `x` coordinate * try to get the `y` coordinate by computing the square root of `x^3 + b` * if failed, iterate again with `x + 1` ## Why ❔ The existing hash to curve algorithm, which turns 32 bytes to `G1::compressed` and maps it into `G1::uncompressed` isn’t valid; it doesn’t produce a point with an unknown discrete log. ## Notes * The function changed to return the successful nonce, in addition to the point. It’s currently unused, but suppose to be used for onchain verification, so that it won't require a loop. From the benchmarks I've run, the avg number of iteration is around 2. (nonce=1) * Need to review the crypto security of this approach, along with the expected onchain gas usage, mainly for the square root. I already prepared a compatible Solidity implementation, but I haven’t checked gas yet. * Need to review whether 32 uniform random bytes, to be reduced to `x`, are sufficient, or that it should be extended to 64 bytes, given the onchain verification gas usage implications. * Need to review whether just picking [only] the positive square root is good enough. * The prime field modulus (`0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47` / `21888242871839275222246405745257275088696311157297823662689037894645226208583`) is defined in the base field type macro definition, but I found no way to access it, hence it is currently redefined in the implementation. This should be fixed. * `get_point_from_x` is already implemented for `G1Affine`, but it's private and unaccessible. This should be fixed. --------- Co-authored-by: Bruno França <[email protected]>
- Loading branch information
1 parent
d17c018
commit b77d70c
Showing
7 changed files
with
50 additions
and
23 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,46 @@ | ||
//! Hash operations. | ||
use ff_ce::{Field, PrimeField, SqrtField}; | ||
use num_bigint::BigUint; | ||
use num_traits::Num; | ||
use pairing::{ | ||
bn256::{G1Affine, G1Compressed}, | ||
EncodedPoint, | ||
bn256::{fq, Fq, FqRepr, G1Affine}, | ||
CurveAffine, | ||
}; | ||
use sha3::Digest as _; | ||
|
||
/// Hashes an arbitrary message and maps it to an elliptic curve point in G1. | ||
pub(crate) fn hash_to_g1(msg: &[u8]) -> G1Affine { | ||
for i in 0..256 { | ||
// Hash the message with the index as suffix. | ||
let bytes: [u8; 32] = sha3::Keccak256::new() | ||
.chain_update(msg) | ||
.chain_update((i as u32).to_be_bytes()) | ||
.finalize() | ||
.into(); | ||
pub(crate) fn hash_to_point(msg: &[u8]) -> (G1Affine, u8) { | ||
let hash: [u8; 32] = sha3::Keccak256::new().chain_update(msg).finalize().into(); | ||
|
||
// Try to get a G1 point from the hash. The probability that this works is around 1/8. | ||
let p = G1Compressed::from_fixed_bytes(bytes).into_affine(); | ||
if let Ok(p) = p { | ||
return p; | ||
let hash_num = BigUint::from_bytes_be(&hash); | ||
let prime_field_modulus = BigUint::from_str_radix( | ||
"30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", | ||
16, | ||
) | ||
.unwrap(); | ||
let x_num = hash_num % prime_field_modulus; | ||
let x_arr: [u64; 4] = x_num.to_u64_digits().try_into().unwrap(); | ||
let mut x = Fq::from_repr(FqRepr(x_arr)).unwrap(); | ||
|
||
for i in 0..255 { | ||
let p = get_point_from_x(x); | ||
if let Some(p) = p { | ||
return (p, i); | ||
} | ||
x.add_assign(&Fq::one()); | ||
} | ||
|
||
// It should be statistically infeasible to finish the loop without finding a point. | ||
unreachable!() | ||
} | ||
|
||
fn get_point_from_x(x: Fq) -> Option<G1Affine> { | ||
// Compute x^3 + b. | ||
let mut x3b = x; | ||
x3b.square(); | ||
x3b.mul_assign(&x); | ||
x3b.add_assign(&fq::B_COEFF); | ||
// Try find the square root. | ||
x3b.sqrt().map(|y| G1Affine::from_xy_unchecked(x, y)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters