Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(relayer): implement poseidon hash #186

Merged
merged 29 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ad528ca
rm ibc_token_addresses endpoint
rnbguy Jan 11, 2025
095702d
use poseidon hash key for ibc denom trace
rnbguy Jan 11, 2025
9c59dcf
poseidon hash impl
rnbguy Jan 11, 2025
05f1ae3
poseidon tests in cairo
rnbguy Jan 11, 2025
e3f5aa1
test LocalKeyBuilderImpl
rnbguy Jan 11, 2025
d7e79e2
hash PrefixedDenom directly
rnbguy Jan 11, 2025
435214f
refactor
rnbguy Jan 11, 2025
7d0f3ed
test write-finish equivalence with digest
rnbguy Jan 11, 2025
7d4eb69
use PoseidonState::digest
rnbguy Jan 11, 2025
369d002
statically compute hades round_constants
rnbguy Jan 12, 2025
7cd437d
add test for round_constants
rnbguy Jan 12, 2025
8f902af
rm redundant field
rnbguy Jan 12, 2025
7e13c0f
rm redundant generic
rnbguy Jan 12, 2025
151914b
rm pre-computed round_keys
rnbguy Jan 12, 2025
9726e3f
add comment to ref impl
rnbguy Jan 12, 2025
01d32c7
rename to RATE_PLUS_1
rnbguy Jan 12, 2025
5ce64b2
refactor and rename
rnbguy Jan 12, 2025
a7c1a0a
rename to Poseidon3Hasher
rnbguy Jan 12, 2025
bbc93af
rename file
rnbguy Jan 12, 2025
add5b3d
refactor
rnbguy Jan 12, 2025
15266ab
add comment
rnbguy Jan 13, 2025
280d48a
update poseidon hashing strategy
rnbguy Jan 14, 2025
a6f754d
Merge branch 'main' into rano/poseidon-hash
rnbguy Jan 14, 2025
56a3213
change hashing strategy for PrefixedDenom
rnbguy Jan 14, 2025
aeefb02
add test for PrefixedDenom
rnbguy Jan 14, 2025
49b5c62
fix poseidon test
rnbguy Jan 14, 2025
a758687
move poseidon impl to sep crate
rnbguy Jan 14, 2025
4e47548
rm redundant tests
rnbguy Jan 15, 2025
6a281b5
rename
rnbguy Jan 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub mod TokenTransferComponent {
use starknet::ContractAddress;
use starknet::storage::{
Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess,
StoragePointerWriteAccess, Vec, VecTrait, MutableVecTrait
StoragePointerWriteAccess
};
use starknet::{get_contract_address, get_caller_address};
use starknet_ibc_apps::transfer::types::{
Expand All @@ -36,7 +36,6 @@ pub mod TokenTransferComponent {
salt: felt252,
ibc_token_key_to_address: Map<felt252, ContractAddress>,
ibc_token_address_to_key: Map<ContractAddress, felt252>,
token_addresses: Vec<ContractAddress>,
}

#[event]
Expand Down Expand Up @@ -283,10 +282,6 @@ pub mod TokenTransferComponent {

address
}

fn ibc_token_addresses(self: @ComponentState<TContractState>) -> Array<ContractAddress> {
self.read_ibc_token_addresses()
}
}

// -----------------------------------------------------------
Expand Down Expand Up @@ -781,8 +776,6 @@ pub mod TokenTransferComponent {
) {
let denom_key = denom.key();

self.append_ibc_token_address(token_address);

self.write_ibc_token_key_to_address(denom_key, token_address);

self.write_ibc_token_address_to_key(token_address, denom_key);
Expand Down Expand Up @@ -846,19 +839,6 @@ pub mod TokenTransferComponent {
self.ibc_token_key_to_address.read(token_key)
}

fn read_ibc_token_addresses(
self: @ComponentState<TContractState>
) -> Array<ContractAddress> {
let mut addresses = array![];
for i in 0
..self
.token_addresses
.len() {
addresses.append(self.token_addresses.at(i).read());
};
addresses
}

fn read_ibc_token_key(
self: @ComponentState<TContractState>, token_address: ContractAddress
) -> felt252 {
Expand All @@ -880,12 +860,6 @@ pub mod TokenTransferComponent {
self.salt.write(salt);
}

fn append_ibc_token_address(
ref self: ComponentState<TContractState>, token_address: ContractAddress,
) {
self.token_addresses.append().write(token_address);
}

fn write_ibc_token_key_to_address(
ref self: ComponentState<TContractState>,
token_key: felt252,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,5 @@ pub trait ITransferQuery<TContractState> {
/// ```
/// Hashing the denom is delegated to the client as it is more cost-efficient.
fn ibc_token_address(self: @TContractState, token_key: felt252) -> ContractAddress;

/// Return the contract addresses of all IBC tokens.
fn ibc_token_addresses(self: @TContractState) -> Array<ContractAddress>;
}

11 changes: 6 additions & 5 deletions cairo-contracts/packages/apps/src/transfer/types.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,12 @@ impl PrefixedDenomDisplay of Display<PrefixedDenom> {
impl PrefixedDenomKeyImpl of ComputeKey<PrefixedDenom> {
fn key(self: @PrefixedDenom) -> felt252 {
let mut key_builder = LocalKeyBuilderImpl::init();
let mut trace_path_span = self.trace_path.span();
while let Option::Some(path) = trace_path_span.pop_front() {
key_builder.append_serde(path);
};
key_builder.append_serde(self.base);
// let mut trace_path_span = self.trace_path.span();
// while let Option::Some(path) = trace_path_span.pop_front() {
// key_builder.append_serde(path);
// };
// key_builder.append_serde(self.base);
key_builder.append_serde(self);
rnbguy marked this conversation as resolved.
Show resolved Hide resolved
key_builder.key()
}
}
Expand Down
59 changes: 59 additions & 0 deletions cairo-contracts/packages/utils/src/utils.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,62 @@ pub impl RemotePathBuilderImpl of RemotePathBuilderTrait {
self.path
}
}

#[cfg(test)]
mod tests {
use core::hash::HashStateTrait;
use core::poseidon::{PoseidonTrait, poseidon_hash_span};
use super::LocalKeyBuilderImpl;

#[test]
fn test_poseidon_hash() {
// https://github.com/starkware-libs/cairo/blob/dff35c09bfaa1ae0969c48ce4e103bad46d5fe50/corelib/src/poseidon.cairo#L128

let data = array![1, 2];
let hash = poseidon_hash_span(data.span());
assert_eq!(hash, 0x0371cb6995ea5e7effcd2e174de264b5b407027a75a231a70c2c8d196107f0e7);
}

#[test]
fn test_poseidon_update() {
// https://github.com/starkware-libs/cairo/blob/dff35c09bfaa1ae0969c48ce4e103bad46d5fe50/corelib/src/poseidon.cairo#L99

let mut state = PoseidonTrait::new();
state = state.update(1);
state = state.update(2);
let hash = state.finalize();
assert_eq!(hash, 0x0371cb6995ea5e7effcd2e174de264b5b407027a75a231a70c2c8d196107f0e7);
}

#[derive(Drop, Serde)]
pub struct Foo {
pub foo: Array<ByteArray>,
}

fn direct_key(data: @Foo) -> felt252 {
let mut key_builder = LocalKeyBuilderImpl::init();
key_builder.append_serde(data);
key_builder.key()
}

fn manual_key(data: @Foo) -> felt252 {
let mut key_builder = LocalKeyBuilderImpl::init();
let mut data_span = data.foo.span();
while let Option::Some(value) = data_span.pop_front() {
key_builder.append_serde(value);
};
key_builder.key()
}

#[test]
fn test_struct_key() {
// depending on how you serialize the struct, the key will be different
let value = Foo { foo: array!["hello", "world"] };
assert_eq!(
direct_key(@value), 0x562cad83e4f09c5813d3d8cc79e67b24d7d979531160e90affd52d4ddf745fe
);
assert_eq!(
manual_key(@value), 0x6c667cf271320ba416f4956bd4c0a532920206fe281247da5c6e7feeec8aa61
);
}
}
30 changes: 21 additions & 9 deletions relayer/crates/starknet-integration-tests/src/tests/ics20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ use starknet::core::types::{Felt, U256};
use starknet::macros::{selector, short_string};
use tracing::info;

use super::poseidon::PoseidonState;
use crate::contexts::bootstrap::StarknetBootstrap;

#[test]
Expand Down Expand Up @@ -409,22 +410,33 @@ fn test_starknet_ics20_contract() -> Result<(), Error> {
balance_cosmos_a_step_1.quantity + transfer_quantity
);

// TODO(rano): we should use the poseidon hash of the ibc denom to get the token address
let ics20_token_address: Felt = {
let ibc_prefixed_denom = PrefixedDenom {
trace_path: vec![TracePrefix {
port_id: ics20_port.to_string(),
channel_id: starknet_channel_id.channel_id.clone(),
}],
base: Denom::Hosted(denom_cosmos.to_string()),
};

// https://github.com/informalsystems/ibc-starknet/blob/e64a8ecaa708c5c5150b058b6c9bbe1ba9f54d51/cairo-contracts/packages/utils/src/utils.cairo#L36
let ibc_prefixed_denom_key = PoseidonState::default()
.update(PoseidonState::update_slice(
&cairo_encoding.encode(&ibc_prefixed_denom)?,
))
.finish();

let calldata = cairo_encoding.encode(&product![ibc_prefixed_denom_key])?;

let ics20_token_address = {
let output = starknet_chain
.call_contract(
&ics20_contract_address,
&selector!("ibc_token_addresses"),
&vec![],
&selector!("ibc_token_address"),
&calldata,
)
.await?;

let addresses: Vec<Felt> = cairo_encoding.decode(&output)?;

assert_eq!(addresses.len(), 1);

addresses[0]
cairo_encoding.decode(&output)?
};

info!("ics20 token address: {:?}", ics20_token_address);
Expand Down
1 change: 1 addition & 0 deletions relayer/crates/starknet-integration-tests/src/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod erc20;
pub mod ics20;
pub mod light_client;
pub mod poseidon;
pub mod update_clients;
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use starknet::core::types::Felt;

// References:
// https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/cairo/common/poseidon_utils.py

pub struct HadesPermutate<
const DIM: usize,
const FULL_ROUNDS: usize,
const PARTIAL_ROUNDS: usize,
const N_ROUNDS: usize,
> {
pub mds: [[i64; DIM]; DIM],
pub round_keys: [[&'static str; DIM]; N_ROUNDS],
}

impl<
const DIM: usize,
const FULL_ROUNDS: usize,
const PARTIAL_ROUNDS: usize,
const N_ROUNDS: usize,
> HadesPermutate<DIM, FULL_ROUNDS, PARTIAL_ROUNDS, N_ROUNDS>
{
// Perform matrix multiplication in the field.
fn matrix_multiply(matrix: &[[i64; DIM]; DIM], vector: &[Felt; DIM]) -> [Felt; DIM] {
matrix.map(|row| {
row.iter()
.zip(vector.iter())
.map(|(&m, v)| Felt::from(m) * v)
.sum()
})
}

// Perform a single round of the Poseidon hash function.
fn hades_round(
&self,
values: [Felt; DIM],
is_full_round: bool,
round_idx: usize,
) -> [Felt; DIM] {
// Add-Round Key
let mut values = core::array::from_fn(|i| {
values[i] + Felt::from_dec_str(self.round_keys[round_idx][i]).unwrap()
});

// Perform the cube operation (x^3) in the field.
fn cube(x: Felt) -> Felt {
x * x * x
}

// SubWords
if is_full_round {
values = values.map(cube);
} else {
values[DIM - 1] = cube(values[DIM - 1]);
}

// MixLayer
Self::matrix_multiply(&self.mds, &values)
}

// Perform the full Poseidon permutation.
pub fn hades_permutation(&self, mut values: [Felt; DIM]) -> [Felt; DIM] {
let mut round_idx = 0;

// Apply R_F/2 full rounds
for _ in 0..(FULL_ROUNDS / 2) {
values = self.hades_round(values, true, round_idx);
round_idx += 1;
}

// Apply R_P partial rounds
for _ in 0..PARTIAL_ROUNDS {
values = self.hades_round(values, false, round_idx);
round_idx += 1;
}

// Apply R_F/2 full rounds
for _ in 0..(FULL_ROUNDS / 2) {
values = self.hades_round(values, true, round_idx);
round_idx += 1;
}

values
}
}
Loading
Loading