diff --git a/.tool-versions b/.tool-versions index 045dc3e..2060b90 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -scarb 2.7.0 +scarb 2.8.3 diff --git a/Scarb.toml b/Scarb.toml index c7c6fd9..c4a290d 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -6,10 +6,10 @@ version = "0.3.0" sierra = true [dependencies] -starknet = "2.7.0" +starknet = "2.8.2" [dev-dependencies] -cairo_test = "2.7.0" +cairo_test = "2.8.2" [scripts] SimpleStruct = "npx ts-node scripts/v0/SimpleStruct.ts" @@ -20,6 +20,7 @@ StructWithMerkletree = "npx ts-node scripts/v0/StructWithMerkletree.ts" StructWithMerkletreeV1 = "npx ts-node scripts/v1/StructWithMerkletree.ts" StructWithU256 = "npx ts-node scripts/v0/StructWithU256.ts" StructWithU256V1 = "npx ts-node scripts/v1/StructWithU256.ts" +StructWithByteArray = "npx ts-node scripts/v1/StructWithByteArray.ts" format = "scarb fmt && npx prettier --write ." ## TODO ADD ALL STANDARD INTERFACES \ No newline at end of file diff --git a/scripts/v1/StructWithByteArray.ts b/scripts/v1/StructWithByteArray.ts new file mode 100644 index 0000000..7a63db7 --- /dev/null +++ b/scripts/v1/StructWithByteArray.ts @@ -0,0 +1,53 @@ +import { byteArray, shortString, StarknetDomain, TypedData, typedData, TypedDataRevision } from "starknet"; + +const types = { + StarknetDomain: [ + { name: "name", type: "shortstring" }, + { name: "version", type: "shortstring" }, + { name: "chainId", type: "shortstring" }, + { name: "revision", type: "shortstring" }, + ], + StructWithByteArray: [ + { name: "some_felt252", type: "felt" }, + { name: "some_byte_array", type: "string" }, + ], +}; + +interface StructWithByteArray { + some_felt252: string; + some_byte_array: string; +} + + +function getDomain(chainId: string): StarknetDomain { + return { + name: "dappName", + version: shortString.encodeShortString("1"), + chainId, + revision: TypedDataRevision.ACTIVE, + }; +} + +function getTypedDataHash(myStruct: StructWithByteArray, chainId: string, owner: bigint): string { + console.log(JSON.stringify(getTypedData(myStruct, chainId))); + return typedData.getMessageHash(getTypedData(myStruct, chainId), owner); +} + +// Needed to reproduce the same structure as: +// https://github.com/0xs34n/starknet.js/blob/1a63522ef71eed2ff70f82a886e503adc32d4df9/__mocks__/typedDataStructArrayExample.json +function getTypedData(myStruct: StructWithByteArray, chainId: string): TypedData { + return { + types, + primaryType: "StructWithByteArray", + domain: getDomain(chainId), + message: { ...myStruct }, + }; +} + +console.log(byteArray.byteArrayFromString("Some long message that exceeds 31 characters")); +const structWithByteArray: StructWithByteArray = { + some_felt252: "712", + some_byte_array: "Some long message that exceeds 31 characters", +}; + +console.log(`test test_valid_hash ${getTypedDataHash(structWithByteArray, "0", 420n)};`); diff --git a/src/examples/v1/struct_with_byte_array.cairo b/src/examples/v1/struct_with_byte_array.cairo new file mode 100644 index 0000000..438a387 --- /dev/null +++ b/src/examples/v1/struct_with_byte_array.cairo @@ -0,0 +1,68 @@ +use starknet::{get_tx_info, get_caller_address}; +use poseidon::PoseidonTrait; +use hash::{HashStateTrait, HashStateExTrait}; +use off_chain_signature::interfaces::{IOffChainMessageHash, IStructHash, v1::StarknetDomain}; + +const STRUCT_WITH_U256_TYPE_HASH: felt252 = + selector!("\"StructWithByteArray\"(\"some_felt252\":\"felt\",\"some_byte_array\":\"string\")"); + +#[derive(Drop)] +struct StructWithByteArray { + some_felt252: felt252, + some_byte_array: ByteArray, +} + +impl OffChainMessageHashStructWithByteArray of IOffChainMessageHash { + fn get_message_hash(self: @StructWithByteArray) -> felt252 { + let domain = StarknetDomain { + name: 'dappName', version: '1', chain_id: get_tx_info().unbox().chain_id, revision: 1 + }; + let mut state = PoseidonTrait::new(); + state = state.update_with('StarkNet Message'); + state = state.update_with(domain.get_struct_hash()); + // This can be a field within the struct, it doesn't have to be get_caller_address(). + state = state.update_with(get_caller_address()); + state = state.update_with(self.get_struct_hash()); + // Hashing with the amount of elements being hashed + state.finalize() + } +} + +impl StructHashStructWithByteArray of IStructHash { + fn get_struct_hash(self: @StructWithByteArray) -> felt252 { + let mut state = PoseidonTrait::new(); + state = state.update_with(STRUCT_WITH_U256_TYPE_HASH); + state = state.update_with(*self.some_felt252); + state = state.update_with(self.some_byte_array.get_struct_hash()); + state.finalize() + } +} + +impl StructHashU256 of IStructHash { + fn get_struct_hash(self: @ByteArray) -> felt252 { + let mut state = PoseidonTrait::new(); + let mut output = array![]; + Serde::serialize(self, ref output); + for e in output.span() { + state = state.update_with(*e); + }; + state.finalize() + } +} + +#[cfg(test)] +mod tests { + use super::{StructWithByteArray, IOffChainMessageHash}; + use starknet::testing::set_caller_address; + + #[test] + fn test_valid_hash() { + // This value was computed using StarknetJS + let message_hash = 0x3e75d70054b3e7d181949eaab082b5dab03903f08952959d46e4755b7055b5; + let simple_struct = StructWithByteArray { + some_felt252: 712, some_byte_array: "Some long message that exceeds 31 characters" + }; + set_caller_address(420.try_into().unwrap()); + assert_eq!(simple_struct.get_message_hash(), message_hash); + } +} diff --git a/src/lib.cairo b/src/lib.cairo index 67d7ccd..2ba1962 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -12,5 +12,6 @@ mod examples { mod struct_with_array; mod struct_with_merkletree; mod struct_with_u256; + mod struct_with_byte_array; } }