Skip to content

Hashing Functions

The curve package provides several cryptographic hash functions used in Starknet. Understanding when to use each hash function is crucial for correct implementation.

Hash Function Overview

Starknet uses specialized hash functions optimized for zero-knowledge proofs:

  • Pedersen Hash: Legacy hash function, widely used in existing contracts
  • Poseidon Hash: Modern, more efficient hash function recommended for new implementations
  • Starknet Keccak: Specialized variant of Keccak for selector computation

Pedersen Hash Functions

Pedersen

Computes the Pedersen hash of two field elements.

Signature:
func Pedersen(a, b *felt.Felt) *felt.Felt
Parameters:
  • a - First felt element to hash
  • b - Second felt element to hash
Returns:
  • *felt.Felt - The resulting Pedersen hash
When to Use:
  • Maintaining compatibility with existing contracts
  • Working with legacy systems that use Pedersen
  • Computing hashes for older Starknet protocols
Usage Example:
package main
 
import (
	"fmt"
 
	"github.com/NethermindEth/juno/core/felt"
	"github.com/NethermindEth/starknet.go/curve"
)
 
func main() {
	// Hash two field elements
	a := new(felt.Felt).SetUint64(123)
	b := new(felt.Felt).SetUint64(456)
 
	hash := curve.Pedersen(a, b)
	fmt.Printf("Pedersen hash: %s\n", hash.String())
 
	// Chaining hashes (common pattern)
	c := new(felt.Felt).SetUint64(789)
	chainedHash := curve.Pedersen(hash, c)
	fmt.Printf("Chained hash: %s\n", chainedHash.String())
}

PedersenArray

Computes the Pedersen hash of multiple field elements.

Signature:
func PedersenArray(felts ...*felt.Felt) *felt.Felt
Parameters:
  • felts - Variadic number of felt elements to hash
Returns:
  • *felt.Felt - The resulting Pedersen hash of all elements
When to Use:
  • Hashing arrays or lists of values
  • Computing cumulative hashes
  • Working with variable-length data
Usage Example:
package main
 
import (
	"fmt"
 
	"github.com/NethermindEth/juno/core/felt"
	"github.com/NethermindEth/starknet.go/curve"
)
 
func main() {
	// Hash multiple elements
	elements := []*felt.Felt{
		new(felt.Felt).SetUint64(100),
		new(felt.Felt).SetUint64(200),
		new(felt.Felt).SetUint64(300),
		new(felt.Felt).SetUint64(400),
	}
 
	hash := curve.PedersenArray(elements...)
	fmt.Printf("Array hash: %s\n", hash.String())
 
	// Hash empty array (returns hash of zero)
	emptyHash := curve.PedersenArray()
	fmt.Printf("Empty array hash: %s\n", emptyHash.String())
}

HashPedersenElements

Calculates the Pedersen hash of a list of big integers.

Signature:
func HashPedersenElements(elems []*big.Int) *big.Int
Parameters:
  • elems - Slice of big.Int pointers to be hashed
Returns:
  • *big.Int - The hash of the elements
When to Use:
  • Working with big.Int instead of felt.Felt
  • Integrating with code that uses big.Int
  • Computing hashes for numerical data
Usage Example:
package main
 
import (
	"fmt"
	"math/big"
 
	"github.com/NethermindEth/starknet.go/curve"
)
 
func main() {
	// Hash big integers
	elements := []*big.Int{
		big.NewInt(123782376),
		big.NewInt(213984),
		big.NewInt(128763521321),
	}
 
	hash := curve.HashPedersenElements(elements)
	fmt.Printf("Hash result: %s\n", hash.String())
	fmt.Printf("Hash (hex): 0x%x\n", hash)
 
	// Empty array handling
	emptyHash := curve.HashPedersenElements([]*big.Int{})
	fmt.Printf("Empty hash: 0x%x\n", emptyHash)
}

ComputeHashOnElements

Computes the Pedersen hash on elements with length encoding.

Signature:
func ComputeHashOnElements(elems []*big.Int) *big.Int
Parameters:
  • elems - Slice of big.Int pointers to be hashed
Returns:
  • *big.Int - The hash including the length
When to Use:
  • When you need to include array length in the hash
  • Computing hashes for contract calldata
  • Ensuring hash integrity with variable-length data

Difference from HashPedersenElements: This function appends the length of the array to the elements before hashing, providing additional integrity.

Usage Example:
package main
 
import (
	"fmt"
	"math/big"
 
	"github.com/NethermindEth/starknet.go/curve"
)
 
func main() {
	elements := []*big.Int{
		big.NewInt(100),
		big.NewInt(200),
		big.NewInt(300),
	}
 
	// Hash with length encoding
	hash := curve.ComputeHashOnElements(elements)
	fmt.Printf("Hash with length: 0x%x\n", hash)
 
	// Compare with HashPedersenElements
	hashWithoutLength := curve.HashPedersenElements(elements)
	fmt.Printf("Hash without length: 0x%x\n", hashWithoutLength)
 
	// The results will be different because ComputeHashOnElements
	// includes the array length (3) in the hash computation
}

Poseidon Hash Functions

Poseidon

Computes the Poseidon hash of two field elements.

Signature:
func Poseidon(a, b *felt.Felt) *felt.Felt
Parameters:
  • a - First felt element to hash
  • b - Second felt element to hash
Returns:
  • *felt.Felt - The resulting Poseidon hash
When to Use:
  • New implementations (recommended)
  • Performance-critical applications
  • Modern Starknet contracts
Advantages over Pedersen:
  • Faster computation
  • More efficient in zero-knowledge proofs
  • Better suited for STARK-friendly operations
Usage Example:
package main
 
import (
	"fmt"
 
	"github.com/NethermindEth/juno/core/felt"
	"github.com/NethermindEth/starknet.go/curve"
)
 
func main() {
	// Hash two elements with Poseidon
	a := new(felt.Felt).SetUint64(123)
	b := new(felt.Felt).SetUint64(456)
 
	hash := curve.Poseidon(a, b)
	fmt.Printf("Poseidon hash: %s\n", hash.String())
 
	// Compare with Pedersen (different results)
	pedersenHash := curve.Pedersen(a, b)
	fmt.Printf("Pedersen hash: %s\n", pedersenHash.String())
	fmt.Printf("Hashes are different: %t\n", hash.String() != pedersenHash.String())
}

PoseidonArray

Computes the Poseidon hash of multiple field elements.

Signature:
func PoseidonArray(felts ...*felt.Felt) *felt.Felt
Parameters:
  • felts - Variadic number of felt elements to hash
Returns:
  • *felt.Felt - The resulting Poseidon hash of all elements
When to Use:
  • Hashing multiple values efficiently
  • Modern contract implementations
  • Performance-critical array hashing
Usage Example:
package main
 
import (
	"fmt"
	"time"
 
	"github.com/NethermindEth/juno/core/felt"
	"github.com/NethermindEth/starknet.go/curve"
)
 
func main() {
	// Create large array for performance comparison
	elements := make([]*felt.Felt, 100)
	for i := range elements {
		elements[i] = new(felt.Felt).SetUint64(uint64(i))
	}
 
	// Time Poseidon hashing
	start := time.Now()
	poseidonHash := curve.PoseidonArray(elements...)
	poseidonTime := time.Since(start)
	fmt.Printf("Poseidon hash: %s\n", poseidonHash.String())
	fmt.Printf("Poseidon time: %v\n", poseidonTime)
 
	// Time Pedersen hashing (for comparison)
	start = time.Now()
	pedersenHash := curve.PedersenArray(elements...)
	pedersenTime := time.Since(start)
	fmt.Printf("Pedersen hash: %s\n", pedersenHash.String())
	fmt.Printf("Pedersen time: %v\n", pedersenTime)
}

Starknet Keccak

StarknetKeccak

Computes the Starknet Keccak hash of a byte slice.

Signature:
func StarknetKeccak(b []byte) *felt.Felt
Parameters:
  • b - Byte slice to hash
Returns:
  • *felt.Felt - The resulting Starknet Keccak hash
When to Use:
  • Computing function selectors
  • Hashing string data
  • Computing entry point selectors for contracts
Usage Example:
package main
 
import (
	"fmt"
 
	"github.com/NethermindEth/starknet.go/curve"
)
 
func main() {
	// Compute function selector
	functionName := "transfer"
	selector := curve.StarknetKeccak([]byte(functionName))
	fmt.Printf("Function selector for '%s': %s\n", functionName, selector.String())
 
	// Compute entry point selector
	entryPoint := "constructor"
	epSelector := curve.StarknetKeccak([]byte(entryPoint))
	fmt.Printf("Entry point selector for '%s': %s\n", entryPoint, epSelector.String())
 
	// Hash arbitrary data
	data := []byte("Hello, Starknet!")
	hash := curve.StarknetKeccak(data)
	fmt.Printf("Data hash: %s\n", hash.String())
}

Choosing the Right Hash Function

Use Pedersen When:

  • Working with legacy contracts
  • Maintaining compatibility with existing systems
  • Following Cairo 0 patterns
  • Required by contract specifications

Use Poseidon When:

  • Building new applications (recommended)
  • Performance is critical
  • Working with Cairo 1+ contracts
  • Optimizing gas costs

Use Starknet Keccak When:

  • Computing selectors (function names, entry points)
  • Hashing string or byte data
  • Following Starknet conventions for naming

Performance Comparison

package main
 
import (
	"fmt"
	"time"
 
	"github.com/NethermindEth/juno/core/felt"
	"github.com/NethermindEth/starknet.go/curve"
)
 
func main() {
	a := new(felt.Felt).SetUint64(12345)
	b := new(felt.Felt).SetUint64(67890)
	iterations := 10000
 
	// Benchmark Pedersen
	start := time.Now()
	for i := 0; i < iterations; i++ {
		_ = curve.Pedersen(a, b)
	}
	pedersenTime := time.Since(start)
 
	// Benchmark Poseidon
	start = time.Now()
	for i := 0; i < iterations; i++ {
		_ = curve.Poseidon(a, b)
	}
	poseidonTime := time.Since(start)
 
	fmt.Printf("Pedersen: %v for %d iterations\n", pedersenTime, iterations)
	fmt.Printf("Poseidon: %v for %d iterations\n", poseidonTime, iterations)
	fmt.Printf("Poseidon is %.2fx faster\n", float64(pedersenTime)/float64(poseidonTime))
}

Common Patterns

Computing Contract Address Hash

// Example: Hashing constructor calldata
constructorCalldata := []*big.Int{
	big.NewInt(1000), // initial supply
	big.NewInt(18),   // decimals
}
 
calldataHash := curve.ComputeHashOnElements(constructorCalldata)
fmt.Printf("Constructor calldata hash: 0x%x\n", calldataHash)

Merkle Tree Construction

// Hash pairs of leaves to build Merkle tree
leaf1 := new(felt.Felt).SetUint64(100)
leaf2 := new(felt.Felt).SetUint64(200)
 
// Use Poseidon for efficiency
parent := curve.Poseidon(leaf1, leaf2)
fmt.Printf("Parent node: %s\n", parent.String())

State Commitment

// Compute state root from multiple values
stateValues := []*felt.Felt{
	new(felt.Felt).SetUint64(1),
	new(felt.Felt).SetUint64(2),
	new(felt.Felt).SetUint64(3),
}
 
stateRoot := curve.PoseidonArray(stateValues...)
fmt.Printf("State root: %s\n", stateRoot.String())

Security Considerations

  1. Hash Function Selection: Use Poseidon for new code unless compatibility requires Pedersen
  2. Input Validation: Ensure inputs are valid field elements
  3. Length Encoding: Use ComputeHashOnElements when array length matters
  4. Collision Resistance: All provided hash functions are cryptographically secure

Related Resources