changed a year ago
Published Linked with GitHub

Pedersen hash for trie-key generation for Besu

  • Besu calls pedersen hash with Address32 and TrieIndex32 both are 32bytes
  • What is passed to JNI is the byte slice where values are in the range [-127,128] and each decimal - we can think of it as [0,256] represent one byte
  • In rust we need to unpack this make the 64 bytes and compute pedersen hash with this
  • What does pedersen hash receives:
/// Pedersen hash receives an address and a trie index and returns a hash calculated this way:
/// H( constant \|| address_low \|| address_high \|| trie_index_low \|| trie_index_high)
/// where constant = 2 + 256*64
/// address_low = first 16 bytes of the address
/// address_high = last 16 bytes of the address
/// trie_index_low = first 16 bytes of the trie index
/// trie_index_high = last 16 bytes of the trie index
/// The result is a 32bytes hash
/// TODO: make the parameters prettier
  • pedersen hash needs to return a serialized point to base field
  • that means we do scalar mul of 5 elements and that's a point. Now we take this point, get the base field "bytes" per this algorithm:
    pub fn to_bytes(&self) -> [u8; 32] {
        // We assume that internally this point is "correct"
        //
        // We serialise a correct point by serialising the x co-ordinate times sign(y)
        let affine = self.0.into_affine();
        let x = if is_positive(affine.y) {
            affine.x
        } else {
            -affine.x
        };
        let mut bytes = [0u8; 32];
        x.serialize(&mut bytes[..]).expect("serialisation failed");

        // reverse bytes to big endian, for interopability
        bytes.reverse();

        bytes
    }

So this is the final result of the pedersen hash, and we return these 32bytes to Java. We cut the last byte in Java and that's our stem.

Pedersen commitment for trie commitments:

  • we pass arbitrary number of 32bytes to JNI (scalar fields)
  • rust unpacks this, gets each scalar
  • comuptes pedersen commitemnt based on the number of scalars
  • we serialize the point to base field per this algorithm:
    let base_field = point.map_to_field();

    let mut bytes = [0u8; 32];
    base_field
        .serialize(&mut bytes[..])
        .expect("could not serialise point into a 32 byte array");
    Fr::from_le_bytes_mod_order(&bytes)

where map_to_field is:

    pub fn map_to_field(&self) -> Fq {
        self.0.x / self.0.y
    }

so we return the scalar here (Fr) so it can be reused in for the pedersen commitment in parent node.

Conclusion:

Since we are returning Fq for trie-key pedersen and Fr for trie commitment pedersen we need 2 separate functions for this.

Possible upgrade:

We may need to return the point without serializing to Fr for pedersen commitment in the trie too since this will maybe used for the proof generation. But we are focusing on correct pedersen commitment stored in the trie so it can be recomputed in the parent node. In case we need a point too(without serializing to Fr), we can add one more return parameter to JNI.

Select a repo