zkApp programmability is not yet available on the Mina Mainnet, but zkApps can now be deployed on Berkeley Testnet.
Keccak (SHA-3)
Keccak is a flexible cryptographic hash function that provides more security than traditional SHA hash algorithms.
What is Keccak?
Developed by a team of cryptographers from Belgium, Keccak was the winning submission for the National Institute of Standards and Technology (NIST) SHA-3 competition. Keccak was standardized as SHA-3. Keccak is used in many different applications, including Ethereum.
Specifically, Keccak is used in the Ethereum ecosystem to hash addresses, transactions, and blocks. It is also used to hash the state trie, the data structure that stores the state of the Ethereum blockchain. Because of the common usage of Keccak in Ethereum, it is a key component of o1js that you can use to verify Ethereum transactions and blocks in o1js.
Keccak and Poseidon
As an o1js developer, you are likely familar with the Poseidon zero knowledge native hash function. Poseidon operates over the native Pallas base field and uses parameters generated specifically for Mina which makes Poseidon the most efficient hash function available in o1js.
In contrast, Keccak is a hash function that requires binary arithmetic. It operates over binary data and is not native to most zero knowledge proofs. For this reason, Keccak is not as efficient as Poseidon, but it is still very useful for verifying Ethereum transactions and blocks. So, when you choose what hash function to use, important considerations include the use case and the data that needs to be hashed.
Basic usage
Keccak is available in the following configurations that are available under the Hash
namespace in o1js:
Hash.SHA3_256
: NIST SHA3 hash function with output size of 256 bits.Hash.SHA3_385
: NIST SHA3 hash function with output size of 384 bits.Hash.SHA3_512
: NIST SHA3 hash function with output size of 512 bits.Hash.Keccak256
: pre-NIST Keccak hash function with output size of 256 bits. This configuration used to hash blocks, transactions, and more in Ethereum.Hash.Keccak384
: pre-NIST Keccak hash function with output size of 384 bits.Hash.Keccak512
: pre-NIST Keccak hash function with output size of 512 bits.
Because Keccak operates over binary data instead of native Field elements like Poseidon, o1js uses the Bytes
type. Bytes
is a fixed-length array of bytes that can be used to represent binary data.
Under the hood, Bytes
is represented as an array of UInt8
elements.
To use Bytes
, you must extend the Bytes
class and specify the length of bytes:
// This creates a Bytes class that represents 16 bytes
class Bytes16 extends Bytes(16) {}
To initiate your Bytes16
class with a value, you can use from
, fromHex
, or fromString
.
// `.from` accepts an array of `number`, `bigint`, `UInt8` elements or a `Uint8Array` or `Bytes`
let bytes = Bytes16.from(new Array(16).fill(0));
// converts a hex string to bytes
bytes = Bytes16.fromHex('646f67');
// converts a string to bytes
bytes = Bytes16.fromString('dog');
// print the contents of `bytes` to the console
bytes.bytes.forEach((b) => console.log(b.toBigInt()));
// [100n, 111n, 103n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n]
If the input array is smaller than the length of the Bytes
class, it will be padded with zeros.
You can also convert a Bytes
object back to a hex string using toHex
.
class Bytes3 extends Bytes(3) {}
let bytes = Bytes3.fromHex('646f67');
let hex = bytes.toHex();
console.log('hex', hex);
// 646f67
Now that you know how to use Bytes
as input, you can use it to hash data using Keccak:
// define a preimage
let preimage = 'The quick brown fox jumps over the lazy dog';
// create a Bytes class that represents 43 bytes
class Bytes43 extends Bytes(43) {}
// convert the preimage to bytes
let preimageBytes = Bytes43.fromString(preimage);
// hash the preimage
let hash = Hash.SHA3_256.hash(preimageBytes);
console.log(hash.toHex());
//69070dda01975c8c120c3aada1b282394e7f032fa9cf32f4cb2259a0897dfc04
See the o1js repository for a hashing example that uses SHA-256 and Keccak.
Bytes - API reference
// creates a Bytes class that represents n bytes (n is 32 in this example)
let n = 32;
class BytesN extends Bytes(n) {}
// initiate an instance from a hex string
let bytes = BytesN.fromHex('646f67');
// initiate an instance from a string
bytes = BytesN.fromString('dog');
// initiate an instance from an array of numbers
bytes = BytesN.from([100, 111, 103]);
// initiate an instance from an array of bigints
bytes = BytesN.from([100n, 111n, 103n]);
// initiate an instance from an array of UInt8 elements
bytes = BytesN.from([UInt8(100), UInt8(111), UInt8(103)]);
// initiate an instance from a Uint8Array
bytes = BytesN.from(new Uint8Array([100, 111, 103]));
// initiate an instance from another Bytes instance
bytes = BytesN.from(BytesN.fromHex('646f67'));
// convert the bytes to a hex string
const hex = bytes.toHex();
Keccak - API reference
// https://keccak.team/keccak.html hash function, pre-NIST specification
// hash bytes using Keccak256 with output size of 256 bits, mainly used in Ethereum
Hash.Keccak256.hash(bytes);
// hash bytes using Keccak384 with output size of 384 bits
Hash.Keccak384.hash(bytes);
// hash bytes using Keccak512 with output size of 512 bits
Keccak.Keccak512.hash(bytes);
// https://csrc.nist.gov/pubs/fips/202/final hash function, official NIST specification
// hash bytes using SHA3_256 with output size of 256 bits
Hash.SHA3_256.hash(bytes);
// hash bytes using SHA3_384 with output size of 384 bits
Hash.SHA3_384.hash(bytes);
// hash bytes using SHA3_512 with output size of 512 bits
Hash.SHA3_512.hash(bytes);