diff --git a/.tool-versions b/.tool-versions index 6594d5c8..9eb3f527 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -scarb 2.6.3 +scarb 2.7.0 starknet-foundry 0.20.0 diff --git a/Scarb.lock b/Scarb.lock index 7183d2e2..ed3b689d 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -1,10 +1,19 @@ # Code generated by scarb DO NOT EDIT. version = 1 +[[package]] +name = "alexandria_bytes" +version = "0.1.0" +source = "git+https://github.com/piniom/quaireaux.git?rev=b51ccc3#b51ccc3045b87d521ce4ec0ff28775c3a94468c4" +dependencies = [ + "alexandria_data_structures", + "alexandria_math", +] + [[package]] name = "alexandria_data_structures" version = "0.2.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=cairo-v2.6.0#946e6e2f9d390ad9f345882a352c0dd6f02ef3ad" +source = "git+https://github.com/piniom/quaireaux.git?rev=b51ccc3#b51ccc3045b87d521ce4ec0ff28775c3a94468c4" dependencies = [ "alexandria_encoding", ] @@ -12,8 +21,9 @@ dependencies = [ [[package]] name = "alexandria_encoding" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=cairo-v2.6.0#946e6e2f9d390ad9f345882a352c0dd6f02ef3ad" +source = "git+https://github.com/piniom/quaireaux.git?rev=b51ccc3#b51ccc3045b87d521ce4ec0ff28775c3a94468c4" dependencies = [ + "alexandria_bytes", "alexandria_math", "alexandria_numeric", ] @@ -21,7 +31,7 @@ dependencies = [ [[package]] name = "alexandria_math" version = "0.2.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=cairo-v2.6.0#946e6e2f9d390ad9f345882a352c0dd6f02ef3ad" +source = "git+https://github.com/piniom/quaireaux.git?rev=b51ccc3#b51ccc3045b87d521ce4ec0ff28775c3a94468c4" dependencies = [ "alexandria_data_structures", ] @@ -29,20 +39,30 @@ dependencies = [ [[package]] name = "alexandria_merkle_tree" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?tag=cairo-v2.6.0#946e6e2f9d390ad9f345882a352c0dd6f02ef3ad" +source = "git+https://github.com/piniom/quaireaux.git?rev=b51ccc3#b51ccc3045b87d521ce4ec0ff28775c3a94468c4" [[package]] name = "alexandria_numeric" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=cairo-v2.6.0#946e6e2f9d390ad9f345882a352c0dd6f02ef3ad" +source = "git+https://github.com/piniom/quaireaux.git?rev=b51ccc3#b51ccc3045b87d521ce4ec0ff28775c3a94468c4" dependencies = [ "alexandria_math", + "alexandria_searching", +] + +[[package]] +name = "alexandria_searching" +version = "0.1.0" +source = "git+https://github.com/piniom/quaireaux.git?rev=b51ccc3#b51ccc3045b87d521ce4ec0ff28775c3a94468c4" +dependencies = [ + "alexandria_data_structures", ] [[package]] name = "argent" version = "0.1.0" dependencies = [ + "alexandria_data_structures", "alexandria_encoding", "alexandria_math", "alexandria_merkle_tree", @@ -52,10 +72,91 @@ dependencies = [ [[package]] name = "openzeppelin" -version = "0.12.0" -source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.12.0#0697004db74502ce49900edef37331dd03531356" +version = "0.15.0-rc.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?rev=a13bae3#a13bae3390a7114a8c14c0352c6898eff5f4f5d7" +dependencies = [ + "openzeppelin_access", + "openzeppelin_account", + "openzeppelin_governance", + "openzeppelin_introspection", + "openzeppelin_presets", + "openzeppelin_security", + "openzeppelin_token", + "openzeppelin_upgrades", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_access" +version = "0.15.0-rc.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?rev=a13bae3#a13bae3390a7114a8c14c0352c6898eff5f4f5d7" +dependencies = [ + "openzeppelin_introspection", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_account" +version = "0.15.0-rc.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?rev=a13bae3#a13bae3390a7114a8c14c0352c6898eff5f4f5d7" +dependencies = [ + "openzeppelin_introspection", + "openzeppelin_token", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_governance" +version = "0.15.0-rc.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?rev=a13bae3#a13bae3390a7114a8c14c0352c6898eff5f4f5d7" +dependencies = [ + "openzeppelin_access", + "openzeppelin_introspection", +] + +[[package]] +name = "openzeppelin_introspection" +version = "0.15.0-rc.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?rev=a13bae3#a13bae3390a7114a8c14c0352c6898eff5f4f5d7" + +[[package]] +name = "openzeppelin_presets" +version = "0.15.0-rc.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?rev=a13bae3#a13bae3390a7114a8c14c0352c6898eff5f4f5d7" +dependencies = [ + "openzeppelin_access", + "openzeppelin_account", + "openzeppelin_introspection", + "openzeppelin_token", + "openzeppelin_upgrades", +] + +[[package]] +name = "openzeppelin_security" +version = "0.15.0-rc.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?rev=a13bae3#a13bae3390a7114a8c14c0352c6898eff5f4f5d7" + +[[package]] +name = "openzeppelin_token" +version = "0.15.0-rc.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?rev=a13bae3#a13bae3390a7114a8c14c0352c6898eff5f4f5d7" +dependencies = [ + "openzeppelin_account", + "openzeppelin_governance", + "openzeppelin_introspection", +] + +[[package]] +name = "openzeppelin_upgrades" +version = "0.15.0-rc.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?rev=a13bae3#a13bae3390a7114a8c14c0352c6898eff5f4f5d7" + +[[package]] +name = "openzeppelin_utils" +version = "0.15.0-rc.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?rev=a13bae3#a13bae3390a7114a8c14c0352c6898eff5f4f5d7" [[package]] name = "snforge_std" -version = "0.20.0" -source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.20.0#423eecf7847469e353258321274394b9155d24eb" +version = "0.27.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.27.0#2d99b7c00678ef0363881ee0273550c44a9263de" diff --git a/Scarb.toml b/Scarb.toml index d07a2074..5a4e7a9f 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -3,6 +3,8 @@ name = "argent" version = "0.1.0" cairo-version = "2.6.3" +[[target.lib]] + [[target.starknet-contract]] sierra = true casm = true @@ -10,12 +12,13 @@ allowed-libfuncs-list.name = "audited" build-external-contracts = ["openzeppelin::presets::account::AccountUpgradeable"] [dependencies] -starknet = "2.6.3" -alexandria_math = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "cairo-v2.6.0" } -alexandria_encoding = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "cairo-v2.6.0" } -alexandria_merkle_tree = { git = "https://github.com/keep-starknet-strange/alexandria.git", tag="cairo-v2.6.0" } -snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.20.0" } -openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.12.0" } +starknet = "2.7.0" +alexandria_math = { git = "https://github.com/piniom/quaireaux.git", rev = "b51ccc3" } +alexandria_encoding = { git = "https://github.com/piniom/quaireaux.git", rev = "b51ccc3" } +alexandria_merkle_tree = { git = "https://github.com/piniom/quaireaux.git", rev = "b51ccc3" } +alexandria_data_structures = { git = "https://github.com/piniom/quaireaux.git", rev = "b51ccc3" } +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.27.0" } +openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", rev = "a13bae3" } [tool.fmt] max-line-length = 120 diff --git a/src/account/interface.cairo b/src/account/interface.cairo index 10717035..994614af 100644 --- a/src/account/interface.cairo +++ b/src/account/interface.cairo @@ -35,7 +35,7 @@ trait IArgentAccount { self: @TContractState, class_hash: felt252, contract_address_salt: felt252, - threshold: usize, + new_threshold: usize, signers: Array ) -> felt252; fn get_name(self: @TContractState) -> felt252; diff --git a/src/external_recovery/external_recovery.cairo b/src/external_recovery/external_recovery.cairo index 965a4bab..bdab1a88 100644 --- a/src/external_recovery/external_recovery.cairo +++ b/src/external_recovery/external_recovery.cairo @@ -31,7 +31,7 @@ mod external_recovery_component { use super::{IExternalRecoveryCallback, get_escape_call_hash}; /// Minimum time for the escape security period - const MIN_ESCAPE_PERIOD: u64 = consteval_int!(60 * 10); // 10 minutes; + const MIN_ESCAPE_PERIOD: u64 = 60 * 10; // 10 minutes; #[storage] struct Storage { diff --git a/src/introspection/src5.cairo b/src/introspection/src5.cairo index c16c6c1a..6146230e 100644 --- a/src/introspection/src5.cairo +++ b/src/introspection/src5.cairo @@ -5,23 +5,23 @@ mod src5_component { }; use argent::introspection::interface::{ISRC5, ISRC5Legacy}; use argent::introspection::interface::{SRC5_INTERFACE_ID, SRC5_INTERFACE_ID_OLD}; - use argent::outside_execution::interface::{ - ERC165_OUTSIDE_EXECUTION_INTERFACE_ID_REV_0, ERC165_OUTSIDE_EXECUTION_INTERFACE_ID_REV_1 - }; + use argent::outside_execution::interface::{ERC165_OUTSIDE_EXECUTION_INTERFACE_ID_REV_2}; #[storage] struct Storage {} #[embeddable_as(SRC5Impl)] - impl SRC5> of ISRC5> { - fn supports_interface(self: @ComponentState, interface_id: felt252) -> bool { + impl SRC5< + TContractState, +HasComponent + > of ISRC5> { + fn supports_interface( + self: @ComponentState, interface_id: felt252 + ) -> bool { if interface_id == SRC5_INTERFACE_ID { true } else if interface_id == SRC5_ACCOUNT_INTERFACE_ID { true - } else if interface_id == ERC165_OUTSIDE_EXECUTION_INTERFACE_ID_REV_0 { - true - } else if interface_id == ERC165_OUTSIDE_EXECUTION_INTERFACE_ID_REV_1 { + } else if interface_id == ERC165_OUTSIDE_EXECUTION_INTERFACE_ID_REV_2 { true } else if interface_id == SRC5_INTERFACE_ID_OLD { true @@ -36,8 +36,12 @@ mod src5_component { } #[embeddable_as(SRC5LegacyImpl)] - impl SRC5Legacy> of ISRC5Legacy> { - fn supportsInterface(self: @ComponentState, interfaceId: felt252) -> felt252 { + impl SRC5Legacy< + TContractState, +HasComponent + > of ISRC5Legacy> { + fn supportsInterface( + self: @ComponentState, interfaceId: felt252 + ) -> felt252 { if self.supports_interface(interfaceId) { 1 } else { diff --git a/src/mocks/future_argent_multisig.cairo b/src/mocks/future_argent_multisig.cairo index 7426ebf7..9f5889c5 100644 --- a/src/mocks/future_argent_multisig.cairo +++ b/src/mocks/future_argent_multisig.cairo @@ -111,7 +111,7 @@ mod MockFutureArgentMultisig { self: @ContractState, class_hash: felt252, contract_address_salt: felt252, - threshold: usize, + new_threshold: usize, signers: Array ) -> felt252 { panic_with_felt252('argent/deploy-not-available') diff --git a/src/mocks/mock_dapp.cairo b/src/mocks/mock_dapp.cairo index df32cfcd..50b1f7fb 100644 --- a/src/mocks/mock_dapp.cairo +++ b/src/mocks/mock_dapp.cairo @@ -1,5 +1,6 @@ /// @dev 🚨 This smart contract is a mock implementation and is not meant for actual deployment or use in any live environment. It is solely for testing, educational, or demonstration purposes. Any interactions with this contract will not have real-world consequences or effects on blockchain networks. Please refrain from relying on the functionality of this contract for any production. 🚨 use starknet::ContractAddress; +use starknet::storage::Map; #[starknet::interface] trait IMockDapp { @@ -14,11 +15,11 @@ trait IMockDapp { #[starknet::contract] mod MockDapp { - use starknet::{get_caller_address, ContractAddress}; + use starknet::{get_caller_address, ContractAddress, storage::Map}; #[storage] struct Storage { - stored_number: LegacyMap, + stored_number: Map, } #[abi(embed_v0)] diff --git a/src/mocks/mock_erc20.cairo b/src/mocks/mock_erc20.cairo index ce446083..7c2d0da2 100644 --- a/src/mocks/mock_erc20.cairo +++ b/src/mocks/mock_erc20.cairo @@ -17,12 +17,12 @@ trait IErc20Mock { #[starknet::contract] mod Erc20Mock { - use starknet::{info::{get_caller_address}, ContractAddress}; + use starknet::{info::{get_caller_address}, ContractAddress, storage::Map}; #[storage] struct Storage { - balances: LegacyMap, - allowances: LegacyMap<(ContractAddress, ContractAddress), u256>, + balances: Map, + allowances: Map<(ContractAddress, ContractAddress), u256>, } #[abi(embed_v0)] diff --git a/src/outside_execution/interface.cairo b/src/outside_execution/interface.cairo index 76b99cb2..5c47170d 100644 --- a/src/outside_execution/interface.cairo +++ b/src/outside_execution/interface.cairo @@ -2,16 +2,11 @@ use hash::{HashStateExTrait, HashStateTrait}; use pedersen::PedersenTrait; use starknet::{ContractAddress, get_contract_address, get_tx_info, account::Call}; -// Interface ID for revision 0 of the OutsideExecute interface -// see https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-9.md -const ERC165_OUTSIDE_EXECUTION_INTERFACE_ID_REV_0: felt252 = - 0x68cfd18b92d1907b8ba3cc324900277f5a3622099431ea85dd8089255e4181; - -// Interface ID for revision 1 of the OutsideExecute interface +// Interface ID for revision 2 of the OutsideExecute interface // see https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-9.md // calculated using https://github.com/ericnordelo/src5-rs -const ERC165_OUTSIDE_EXECUTION_INTERFACE_ID_REV_1: felt252 = - 0x1d1144bb2138366ff28d8e9ab57456b1d332ac42196230c3a602003c89872; +const ERC165_OUTSIDE_EXECUTION_INTERFACE_ID_REV_2: felt252 = + 0x11807fbf461e989e437c2a77b6683f3e5d886f83ba27dade7b341aeb5b1def1; /// @notice As defined in SNIP-9 https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-9.md /// @param caller Only the address specified here will be allowed to call `execute_from_outside` @@ -24,7 +19,7 @@ const ERC165_OUTSIDE_EXECUTION_INTERFACE_ID_REV_1: felt252 = #[derive(Copy, Drop, Serde)] struct OutsideExecution { caller: ContractAddress, - nonce: felt252, + nonce: (felt252, u128), execute_after: u64, execute_before: u64, calls: Span @@ -33,38 +28,41 @@ struct OutsideExecution { /// @notice get_outside_execution_message_hash_rev_* is not part of the standard interface #[starknet::interface] trait IOutsideExecution { - /// @notice This function allows anyone to submit a transaction on behalf of the account as long as they have the relevant signatures - /// @param outside_execution The parameters of the transaction to execute - /// @param signature A valid signature on the Eip712 message encoding of `outside_execution` - /// @notice This function does not allow reentrancy. A call to `__execute__` or `execute_from_outside` cannot trigger another nested transaction to `execute_from_outside`. - fn execute_from_outside( - ref self: TContractState, outside_execution: OutsideExecution, signature: Array - ) -> Array>; - - /// @notice Outside execution using SNIP-12 Rev 1 - fn execute_from_outside_v2( + /// @notice This function allows anyone to submit a transaction on behalf of the account as long + /// as they have the relevant signatures @param outside_execution The parameters of the + /// transaction to execute @param signature A valid signature on the Eip712 message encoding of + /// `outside_execution` + /// @notice This function does not allow reentrancy. A call to `__execute__` or + /// `execute_from_outside` cannot trigger another nested transaction to `execute_from_outside`. + fn execute_from_outside_v3( ref self: TContractState, outside_execution: OutsideExecution, signature: Span ) -> Array>; /// Get the status of a given nonce, true if the nonce is available to use - fn is_valid_outside_execution_nonce(self: @TContractState, nonce: felt252) -> bool; + fn is_valid_outside_execution_v3_nonce(self: @TContractState, nonce: (felt252, u128)) -> bool; - /// Get the message hash for some `OutsideExecution` rev 0 following Eip712. Can be used to know what needs to be signed - fn get_outside_execution_message_hash_rev_0(self: @TContractState, outside_execution: OutsideExecution) -> felt252; + /// Get the message hash for some `OutsideExecution` rev 2 following Eip712. Can be used to know + /// what needs to be signed + fn get_outside_execution_message_hash_rev_2( + self: @TContractState, outside_execution: OutsideExecution + ) -> felt252; - /// Get the message hash for some `OutsideExecution` rev 1 following Eip712. Can be used to know what needs to be signed - fn get_outside_execution_message_hash_rev_1(self: @TContractState, outside_execution: OutsideExecution) -> felt252; + fn get_outside_execution_v3_channel_nonce(self: @TContractState, channel: felt252) -> u128; } -/// This trait must be implemented when using the component `outside_execution_component` (This is enforced by the compiler) +/// This trait must be implemented when using the component `outside_execution_component` (This is +/// enforced by the compiler) trait IOutsideExecutionCallback { /// @notice Callback performed after checking the OutsideExecution is valid /// @dev Make the correct access control checks in this callback - /// @param calls The calls to be performed + /// @param calls The calls to be performed /// @param outside_execution_hash The hash of OutsideExecution /// @param signature The signature that the user gave for this transaction #[inline(always)] fn execute_from_outside_callback( - ref self: TContractState, calls: Span, outside_execution_hash: felt252, signature: Span, + ref self: TContractState, + calls: Span, + outside_execution_hash: felt252, + signature: Span, ) -> Array>; } diff --git a/src/outside_execution/outside_execution.cairo b/src/outside_execution/outside_execution.cairo index ebbff7b2..dd6a3fdc 100644 --- a/src/outside_execution/outside_execution.cairo +++ b/src/outside_execution/outside_execution.cairo @@ -3,18 +3,25 @@ #[starknet::component] mod outside_execution_component { use argent::outside_execution::{ - outside_execution_hash::{OffChainMessageOutsideExecutionRev0, OffChainMessageOutsideExecutionRev1}, + outside_execution_hash::{OffChainMessageOutsideExecutionRev2}, interface::{OutsideExecution, IOutsideExecutionCallback, IOutsideExecution} }; use hash::{HashStateTrait, HashStateExTrait}; - use openzeppelin::security::reentrancyguard::{ReentrancyGuardComponent, ReentrancyGuardComponent::InternalImpl}; + use openzeppelin::security::reentrancyguard::{ + ReentrancyGuardComponent, ReentrancyGuardComponent::InternalImpl + }; use pedersen::PedersenTrait; - use starknet::{get_caller_address, get_contract_address, get_block_timestamp, get_tx_info, account::Call}; + use starknet::{ + get_caller_address, get_contract_address, get_block_timestamp, get_tx_info, account::Call, + storage::Map + }; + use core::starknet::storage_access::StorePacking; + use core::integer::bitwise; #[storage] struct Storage { /// Keeps track of used nonces for outside transactions (`execute_from_outside`) - outside_nonces: LegacyMap, + outside_nonces: Map, } #[event] @@ -29,34 +36,37 @@ mod outside_execution_component { +Drop, impl ReentrancyGuard: ReentrancyGuardComponent::HasComponent, > of IOutsideExecution> { - fn execute_from_outside( - ref self: ComponentState, outside_execution: OutsideExecution, signature: Array - ) -> Array> { - let hash = outside_execution.get_message_hash_rev_0(); - self.assert_valid_outside_execution(outside_execution, hash, signature.span()) - } - - fn execute_from_outside_v2( - ref self: ComponentState, outside_execution: OutsideExecution, signature: Span + fn execute_from_outside_v3( + ref self: ComponentState, + outside_execution: OutsideExecution, + signature: Span ) -> Array> { let hash = outside_execution.get_message_hash_rev_1(); self.assert_valid_outside_execution(outside_execution, hash, signature) } - fn get_outside_execution_message_hash_rev_0( + fn get_outside_execution_message_hash_rev_2( self: @ComponentState, outside_execution: OutsideExecution ) -> felt252 { - outside_execution.get_message_hash_rev_0() + outside_execution.get_message_hash_rev_1() } - fn get_outside_execution_message_hash_rev_1( - self: @ComponentState, outside_execution: OutsideExecution - ) -> felt252 { - outside_execution.get_message_hash_rev_1() + fn is_valid_outside_execution_v3_nonce( + self: @ComponentState, nonce: (felt252, u128) + ) -> bool { + let (channel, mask) = nonce; + if mask == 0_u128 { + return false; + } + + let current_mask = self.outside_nonces.read(channel); + (current_mask & mask) == 0 } - fn is_valid_outside_execution_nonce(self: @ComponentState, nonce: felt252) -> bool { - !self.outside_nonces.read(nonce) + fn get_outside_execution_v3_channel_nonce( + self: @ComponentState, channel: felt252 + ) -> u128 { + self.outside_nonces.read(channel) } } @@ -83,17 +93,20 @@ mod outside_execution_component { let block_timestamp = get_block_timestamp(); assert( - outside_execution.execute_after < block_timestamp && block_timestamp < outside_execution.execute_before, + outside_execution.execute_after < block_timestamp + && block_timestamp < outside_execution.execute_before, 'argent/invalid-timestamp' ); - let nonce = outside_execution.nonce; - assert(!self.outside_nonces.read(nonce), 'argent/duplicated-outside-nonce'); - self.outside_nonces.write(nonce, true); + let (channel, mask) = outside_execution.nonce; + let current_mask = self.outside_nonces.read(channel); + let (and, _, or) = bitwise(current_mask, mask); + assert(mask != 0 && and == 0, 'argent/invalid-outside-nonce'); + self.outside_nonces.write(channel, or); let mut state = self.get_contract_mut(); - let result = state.execute_from_outside_callback(outside_execution.calls, outside_tx_hash, signature); + let result = state + .execute_from_outside_callback(outside_execution.calls, outside_tx_hash, signature); reentrancy_guard.end(); result } } } - diff --git a/src/outside_execution/outside_execution_hash.cairo b/src/outside_execution/outside_execution_hash.cairo index 073a5363..35f09409 100644 --- a/src/outside_execution/outside_execution_hash.cairo +++ b/src/outside_execution/outside_execution_hash.cairo @@ -1,7 +1,7 @@ use argent::offchain_message::{ interface::{ - StarkNetDomain, StarknetDomain, StructHashStarkNetDomain, IOffChainMessageHashRev0, IStructHashRev0, - IOffChainMessageHashRev1, IStructHashRev1 + StarkNetDomain, StarknetDomain, StructHashStarkNetDomain, IOffChainMessageHashRev1, + IStructHashRev1 }, precalculated_hashing::get_message_hash_rev_1_with_precalc }; @@ -13,109 +13,43 @@ use starknet::{get_tx_info, get_contract_address, account::Call}; const MAINNET_FIRST_HADES_PERMUTATION: (felt252, felt252, felt252) = ( - 2727651893633223888261849279042022325174182119102281398572272198960815727249, - 729016093840936084580216898033636860729342953928695140840860652272753125883, - 2792630223211151632174198306610141883878913626231408099903852589995722964080 + 996915192477314844232397706210079185628598843828924621070706959145689833980, + 2154834376955455672602400038844399406202405733270731961376124532261347854986, + 1129673910579930509220015777859527444962253764479830284363060876794734318472, ); const SEPOLIA_FIRST_HADES_PERMUTATION: (felt252, felt252, felt252) = ( - 3580606761507954093996364807837346681513890124685758374532511352257317798951, - 3431227198346789440159663709265467470274870120429209591243179659934705045436, - 974062396530052497724701732977002885691473732259823426261944148730229556466 + 2648373253270285159769360603517540536782489473878572730211506916518063798474, + 604219299452944139991089465374505793098487122536238208890268251872611859633, + 2075519913841617027120337084019352091900417009471098949739064814802390776233, ); - -const OUTSIDE_CALL_TYPE_HASH_REV_0: felt252 = - selector!("OutsideCall(to:felt,selector:felt,calldata_len:felt,calldata:felt*)"); - -const OUTSIDE_EXECUTION_TYPE_HASH_REV_0: felt252 = +const OUTSIDE_EXECUTION_TYPE_HASH_REV_2: felt252 = selector!( - "OutsideExecution(caller:felt,nonce:felt,execute_after:felt,execute_before:felt,calls_len:felt,calls:OutsideCall*)OutsideCall(to:felt,selector:felt,calldata_len:felt,calldata:felt*)" + "\"OutsideExecution\"(\"Caller\":\"ContractAddress\",\"Nonce\":\"(felt,u128)\",\"Execute After\":\"u128\",\"Execute Before\":\"u128\",\"Calls\":\"Call*\")\"Call\"(\"To\":\"ContractAddress\",\"Selector\":\"selector\",\"Calldata\":\"felt*\")" ); -const OUTSIDE_EXECUTION_TYPE_HASH_REV_1: felt252 = +const CALL_TYPE_HASH_REV_2: felt252 = selector!( - "\"OutsideExecution\"(\"Caller\":\"ContractAddress\",\"Nonce\":\"felt\",\"Execute After\":\"u128\",\"Execute Before\":\"u128\",\"Calls\":\"Call*\")\"Call\"(\"To\":\"ContractAddress\",\"Selector\":\"selector\",\"Calldata\":\"felt*\")" + "\"Call\"(\"To\":\"ContractAddress\",\"Selector\":\"selector\",\"Calldata\":\"felt*\")" ); -const CALL_TYPE_HASH_REV_1: felt252 = - selector!("\"Call\"(\"To\":\"ContractAddress\",\"Selector\":\"selector\",\"Calldata\":\"felt*\")"); - -impl StructHashOutsideExecutionRev0 of IStructHashRev0 { - fn get_struct_hash_rev_0(self: @OutsideExecution) -> felt252 { - let self = *self; - let mut state = PedersenTrait::new(0); - let mut calls_span = self.calls; - let calls_len = self.calls.len().into(); - let calls_hash = loop { - match calls_span.pop_front() { - Option::Some(call) => state = state.update((call.get_struct_hash_rev_0())), - Option::None => { break state.update(calls_len).finalize(); }, - } - }; - - PedersenTrait::new(0) - .update_with(OUTSIDE_EXECUTION_TYPE_HASH_REV_0) - .update_with(self.caller) - .update_with(self.nonce) - .update_with(self.execute_after) - .update_with(self.execute_before) - .update_with(calls_len) - .update_with(calls_hash) - .update_with(7) - .finalize() - } -} - -impl StructHashCallRev0 of IStructHashRev0 { - fn get_struct_hash_rev_0(self: @Call) -> felt252 { - let mut state = PedersenTrait::new(0); - let mut calldata_span = *self.calldata; - let calldata_len = calldata_span.len().into(); - let calldata_hash = loop { - match calldata_span.pop_front() { - Option::Some(item) => state = state.update(*item), - Option::None => { break state.update(calldata_len).finalize(); }, - } - }; - - PedersenTrait::new(0) - .update_with(OUTSIDE_CALL_TYPE_HASH_REV_0) - .update_with(*self.to) - .update_with(*self.selector) - .update_with(calldata_len) - .update_with(calldata_hash) - .update_with(5) - .finalize() - } -} - -impl OffChainMessageOutsideExecutionRev0 of IOffChainMessageHashRev0 { - fn get_message_hash_rev_0(self: @OutsideExecution) -> felt252 { - let domain = StarkNetDomain { - name: 'Account.execute_from_outside', version: 1, chain_id: get_tx_info().unbox().chain_id, - }; - - PedersenTrait::new(0) - .update_with('StarkNet Message') - .update_with(domain.get_struct_hash_rev_0()) - .update_with(get_contract_address()) - .update_with((*self).get_struct_hash_rev_0()) - .update(4) - .finalize() - } -} - -impl StructHashCallRev1 of IStructHashRev1 { +impl StructHashCallRev2 of IStructHashRev1 { fn get_struct_hash_rev_1(self: @Call) -> felt252 { poseidon_hash_span( - array![CALL_TYPE_HASH_REV_1, (*self.to).into(), *self.selector, poseidon_hash_span(*self.calldata)].span() + array![ + CALL_TYPE_HASH_REV_2, + (*self.to).into(), + *self.selector, + poseidon_hash_span(*self.calldata) + ] + .span() ) } } -impl StructHashOutsideExecutionRev1 of IStructHashRev1 { +impl StructHashOutsideExecutionRev2 of IStructHashRev1 { fn get_struct_hash_rev_1(self: @OutsideExecution) -> felt252 { let self = *self; let mut calls_span = self.calls; @@ -124,11 +58,15 @@ impl StructHashOutsideExecutionRev1 of IStructHashRev1 { while let Option::Some(call) = calls_span.pop_front() { hashed_calls.append(call.get_struct_hash_rev_1()); }; + + let (nonce_channel, nonce_mask) = self.nonce; + poseidon_hash_span( array![ - OUTSIDE_EXECUTION_TYPE_HASH_REV_1, + OUTSIDE_EXECUTION_TYPE_HASH_REV_2, self.caller.into(), - self.nonce, + nonce_channel, + nonce_mask.into(), self.execute_after.into(), self.execute_before.into(), poseidon_hash_span(hashed_calls.span()), @@ -138,10 +76,12 @@ impl StructHashOutsideExecutionRev1 of IStructHashRev1 { } } -impl OffChainMessageOutsideExecutionRev1 of IOffChainMessageHashRev1 { +impl OffChainMessageOutsideExecutionRev2 of IOffChainMessageHashRev1 { fn get_message_hash_rev_1(self: @OutsideExecution) -> felt252 { - // Version is a felt instead of a shortstring in SNIP-9 due to a mistake in the Braavos contracts and has been copied for compatibility. - // Revision will also be a felt instead of a shortstring for all SNIP12-rev1 signatures because of the same issue + // Version is a felt instead of a shortstring in SNIP-9 due to a mistake in the Braavos + // contracts and has been copied for compatibility. + // Revision will also be a felt instead of a shortstring for all SNIP22-rev2 signatures + // because of the same issue let chain_id = get_tx_info().unbox().chain_id; if chain_id == 'SN_MAIN' { @@ -150,7 +90,9 @@ impl OffChainMessageOutsideExecutionRev1 of IOffChainMessageHashRev1, + _signer_non_stark: Map, /// Current account guardian _guardian: felt252, /// Current account backup guardian _guardian_backup: felt252, - _guardian_backup_non_stark: LegacyMap, + _guardian_backup_non_stark: Map, /// The ongoing escape, if any _escape: LegacyEscape, /// The following 4 fields are used to limit the number of escapes the account will pay for diff --git a/src/presets/multisig_account.cairo b/src/presets/multisig_account.cairo index 10ea224f..b105e008 100644 --- a/src/presets/multisig_account.cairo +++ b/src/presets/multisig_account.cairo @@ -17,7 +17,6 @@ mod ArgentMultisigAccount { }; use openzeppelin::security::reentrancyguard::ReentrancyGuardComponent; use starknet::{get_tx_info, get_execution_info, get_contract_address, VALIDATED, account::Call, ClassHash}; - const NAME: felt252 = 'ArgentMultisig'; const VERSION: Version = Version { major: 0, minor: 2, patch: 0 }; @@ -148,7 +147,7 @@ mod ArgentMultisigAccount { } #[abi(embed_v0)] - impl ArgentAccountImpl of IArgentAccount { + impl ArgentMultisigAccountImpl of IArgentAccount { fn __validate_declare__(self: @ContractState, class_hash: felt252) -> felt252 { panic_with_felt252('argent/declare-not-available') // Not implemented yet } @@ -157,7 +156,7 @@ mod ArgentMultisigAccount { self: @ContractState, class_hash: felt252, contract_address_salt: felt252, - threshold: usize, + new_threshold: usize, signers: Array ) -> felt252 { let tx_info = get_tx_info().unbox(); diff --git a/src/presets/user_account.cairo b/src/presets/user_account.cairo index b5ec988d..cf912fe4 100644 --- a/src/presets/user_account.cairo +++ b/src/presets/user_account.cairo @@ -165,7 +165,7 @@ mod ArgentUserAccount { self: @ContractState, class_hash: felt252, contract_address_salt: felt252, - threshold: usize, + new_threshold: usize, signers: Array ) -> felt252 { let tx_info = get_tx_info().unbox(); diff --git a/src/recovery/interface.cairo b/src/recovery/interface.cairo index 604ef822..c58a0488 100644 --- a/src/recovery/interface.cairo +++ b/src/recovery/interface.cairo @@ -57,7 +57,7 @@ enum EscapeStatus { /// @param ready_at when the escape can be completed /// @param target_signers the signers to escape /// @param new_signers the new signers to be set after the security period -#[derive(Drop, Serde, starknet::StorePacking)] +#[derive(Drop, Serde)] struct Escape { ready_at: u64, target_signers: Array, @@ -68,7 +68,7 @@ struct Escape { /// @param is_enabled The escape is enabled /// @param security_period Time it takes for the escape to become ready after being triggered /// @param expiry_period The escape will be ready and can be completed for this duration -#[derive(Drop, Copy, Serde, starknet::StorePacking)] +#[derive(Drop, Copy, Serde)] struct EscapeEnabled { is_enabled: bool, security_period: u64, @@ -218,10 +218,3 @@ impl U256TryIntoLegacyEscapeType of TryInto { } } } - -impl OptionDefault of Default> { - #[inline(always)] - fn default() -> Option { - Option::None - } -} diff --git a/src/session/interface.cairo b/src/session/interface.cairo index f09a8afb..2fbcf5ea 100644 --- a/src/session/interface.cairo +++ b/src/session/interface.cairo @@ -10,14 +10,18 @@ use starknet::{get_tx_info, get_contract_address, ContractAddress}; /// @param allowed_methods_root The root of the merkle tree of the allowed methods /// @param metadata_hash The hash of the metadata JSON string of the session /// @param session_key_guid The GUID of the session key +/// @param guardian_key_guid The GUID of the session key #[derive(Drop, Serde, Copy)] struct Session { expires_at: u64, - allowed_methods_root: felt252, + allowed_policies_root: felt252, metadata_hash: felt252, session_key_guid: felt252, + guardian_key_guid: felt252, } + + /// @notice Session Token struct contains the session struct, relevant signatures and merkle proofs /// @param session The session struct /// @param cache_authorization Flag indicating whether to cache the authorization signature for the session @@ -35,6 +39,27 @@ struct SessionToken { proofs: Span>, } +/// @param scope_hash Commitment computed as `poseidon(domain_separator_hash, type_hash)` +/// @param typed_data_hash Encoded data for the primary type as a single `felt252` +#[derive(Drop, Serde, Copy)] +struct TypedData { + scope_hash: felt252, + typed_data_hash: felt252, +} + +#[derive(Drop, Serde, Copy)] +struct DetailedTypedData { + domain_hash: felt252, + type_hash: felt252, + params: Span, +} + +#[derive(Drop, Serde, Copy)] +enum Policy { + Call: Call, + TypedData: TypedData, +} + /// This trait has to be implemented when using the component `session_component` (This is enforced by the compiler) #[starknet::interface] diff --git a/src/session/session.cairo b/src/session/session.cairo index 640a83bd..20ed7838 100644 --- a/src/session/session.cairo +++ b/src/session/session.cairo @@ -12,15 +12,15 @@ mod session_component { use argent::utils::{asserts::{assert_no_self_call, assert_only_self}, serialization::full_deserialize}; use hash::{HashStateExTrait, HashStateTrait}; use poseidon::PoseidonTrait; - use starknet::{account::Call, get_contract_address, VALIDATED, get_block_timestamp}; + use starknet::{account::Call, get_contract_address, VALIDATED, get_block_timestamp, storage::Map}; #[storage] struct Storage { /// A map of session hashes to a boolean indicating if the session has been revoked. - revoked_session: LegacyMap, + revoked_session: Map, /// A map of (owner_guid, guardian_guid, session_hash) to a len of authorization signature - valid_session_cache: LegacyMap<(felt252, felt252, felt252), u32>, + valid_session_cache: Map<(felt252, felt252, felt252), u32>, } const SESSION_MAGIC: felt252 = 'session-token'; @@ -167,7 +167,7 @@ mod session_component { fn assert_valid_session_calls(token: @SessionToken, mut calls: Span) { assert((*token.proofs).len() == calls.len(), 'session/unaligned-proofs'); - let merkle_root = *token.session.allowed_methods_root; + let merkle_root = *token.session.allowed_policies_root; let mut merkle_tree: MerkleTree = MerkleTreeImpl::new(); let mut proofs = *token.proofs; while let Option::Some(call) = calls diff --git a/src/session/session_hash.cairo b/src/session/session_hash.cairo index 1671b969..ef360141 100644 --- a/src/session/session_hash.cairo +++ b/src/session/session_hash.cairo @@ -1,8 +1,11 @@ use argent::offchain_message::{ - interface::{StarknetDomain, StructHashStarknetDomain, IMerkleLeafHash, IStructHashRev1, IOffChainMessageHashRev1,}, + interface::{ + StarknetDomain, StructHashStarknetDomain, IMerkleLeafHash, IStructHashRev1, + IOffChainMessageHashRev1, + }, precalculated_hashing::get_message_hash_rev_1_with_precalc }; -use argent::session::interface::Session; +use argent::session::interface::{Session, TypedData, Policy}; use hash::{HashStateExTrait, HashStateTrait}; use poseidon::{hades_permutation, poseidon_hash_span, HashState}; use starknet::{get_contract_address, get_tx_info, account::Call}; @@ -27,13 +30,37 @@ const SESSION_TYPE_HASH_REV_1: felt252 = selector!( "\"Session\"(\"Expires At\":\"timestamp\",\"Allowed Methods\":\"merkletree\",\"Metadata\":\"string\",\"Session Key\":\"felt\")" ); +const DATA_TYPE_HASH_REV_1: felt252 = + selector!("\"Allowed Type\"(\"Scope Hash\":\"felt\",\"Typed Data Hash\":\"felt\")"); const ALLOWED_METHOD_HASH_REV_1: felt252 = - selector!("\"Allowed Method\"(\"Contract Address\":\"ContractAddress\",\"selector\":\"selector\")"); + selector!( + "\"Allowed Method\"(\"Contract Address\":\"ContractAddress\",\"selector\":\"selector\")" + ); + +const ALLOWED_DATA_TYPE_HASH_REV_1: felt252 = + selector!("\"Allowed Type\"(\"Scope Hash\":\"felt\")"); impl MerkleLeafHash of IMerkleLeafHash { fn get_merkle_leaf(self: @Call) -> felt252 { - poseidon_hash_span(array![ALLOWED_METHOD_HASH_REV_1, (*self.to).into(), *self.selector].span()) + poseidon_hash_span( + array![ALLOWED_METHOD_HASH_REV_1, (*self.to).into(), *self.selector].span() + ) + } +} + +impl MerkleLeafHashTypedData of IMerkleLeafHash { + fn get_merkle_leaf(self: @TypedData) -> felt252 { + poseidon_hash_span(array![ALLOWED_DATA_TYPE_HASH_REV_1, *self.scope_hash].span()) + } +} + +impl MerkleLeafHashPolicy of IMerkleLeafHash { + fn get_merkle_leaf(self: @Policy) -> felt252 { + match self { + Policy::Call(call) => call.get_merkle_leaf(), + Policy::TypedData(typed_data) => typed_data.get_merkle_leaf(), + } } } @@ -44,15 +71,26 @@ impl StructHashSession of IStructHashRev1 { array![ SESSION_TYPE_HASH_REV_1, self.expires_at.into(), - self.allowed_methods_root, + self.allowed_policies_root, self.metadata_hash, - self.session_key_guid + self.session_key_guid, + self.guardian_key_guid ] .span() ) } } + +impl StructHashTypedData of IStructHashRev1 { + fn get_struct_hash_rev_1(self: @TypedData) -> felt252 { + let self = *self; + poseidon_hash_span( + array![DATA_TYPE_HASH_REV_1, self.scope_hash, self.typed_data_hash].span() + ) + } +} + impl OffChainMessageHashSessionRev1 of IOffChainMessageHashRev1 { fn get_message_hash_rev_1(self: @Session) -> felt252 { let chain_id = get_tx_info().unbox().chain_id; @@ -62,7 +100,9 @@ impl OffChainMessageHashSessionRev1 of IOffChainMessageHashRev1 { if chain_id == 'SN_SEPOLIA' { return get_message_hash_rev_1_with_precalc(SEPOLIA_FIRST_HADES_PERMUTATION, *self); } - let domain = StarknetDomain { name: 'SessionAccount.session', version: '1', chain_id, revision: 1, }; + let domain = StarknetDomain { + name: 'SessionAccount.session', version: '1', chain_id, revision: 1, + }; poseidon_hash_span( array![ 'StarkNet Message', diff --git a/src/signer/webauthn.cairo b/src/signer/webauthn.cairo index 55cea7d6..a4c67a98 100644 --- a/src/signer/webauthn.cairo +++ b/src/signer/webauthn.cairo @@ -1,39 +1,35 @@ use alexandria_encoding::base64::Base64UrlEncoder; -use alexandria_math::sha256::{sha256}; use argent::signer::signer_signature::{WebauthnSigner}; use argent::utils::array_ext::ArrayExtTrait; -use argent::utils::bytes::{SpanU8TryIntoU256, SpanU8TryIntoFelt252, u32s_to_u256, u32s_to_u8s, u256_to_u8s}; +use argent::utils::bytes::{ + SpanU8TryIntoU256, SpanU8TryIntoFelt252, u32s_to_u256, u32s_typed_to_u256, u32s_to_u8s, + u256_to_u8s, ArrayU8Ext, u256_to_byte_array, u32s_to_byte_array +}; use argent::utils::hashing::{sha256_cairo0}; use starknet::secp256_trait::Signature; +use core::sha256::compute_sha256_byte_array; /// @notice The webauthn signature that needs to be validated /// @param cross_origin From the client data JSON +/// @param top_origin From the client data JSON /// @param client_data_json_outro The rest of the JSON contents coming after the 'crossOrigin' value /// @param flags From authenticator data /// @param sign_count From authenticator data /// @param ec_signature The signature as {r, s, y_parity} -/// @param sha256_implementation The implementation of the sha256 hash +/// @param sha256_implementation The implementation of the sha256 hash #[derive(Drop, Copy, Serde, PartialEq)] struct WebauthnSignature { - cross_origin: bool, client_data_json_outro: Span, flags: u8, sign_count: u32, ec_signature: Signature, - sha256_implementation: Sha256Implementation, -} - -#[derive(Drop, Copy, Serde, PartialEq)] -enum Sha256Implementation { - Cairo0, - Cairo1, } /// Example data: /// 0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000 /// <--------------------------------------------------------------><><------> /// rpIdHash (32 bytes) ^ sign count (4 bytes) -/// flags (1 byte) | +/// flags (1 byte) | /// Memory layout: https://www.w3.org/TR/webauthn/#sctn-authenticator-data fn verify_authenticator_flags(flags: u8) { // rpIdHash is verified with the signature over the authenticator @@ -41,7 +37,8 @@ fn verify_authenticator_flags(flags: u8) { // Verify that the User Present bit of the flags in authData is set. assert!((flags & 0b00000001) == 0b00000001, "webauthn/nonpresent-user"); - // If user verification is required for this signature, verify that the User Verified bit of the flags in authData is set. + // If user verification is required for this signature, verify that the User Verified bit of the + // flags in authData is set. assert!((flags & 0b00000100) == 0b00000100, "webauthn/unverified-user"); // Allowing attested credential data and extension data if present @@ -52,17 +49,14 @@ fn verify_authenticator_flags(flags: u8) { /// {"type":"webauthn.get","challenge":"3q2-7_8","origin":"http://localhost:5173","crossOrigin":false} /// Spec: https://www.w3.org/TR/webauthn/#dictdef-collectedclientdata /// Encoding spec: https://www.w3.org/TR/webauthn/#clientdatajson-verification -fn encode_client_data_json(hash: felt252, signature: WebauthnSignature, origin: Span) -> Span { +fn encode_client_data_json( + hash: felt252, signature: WebauthnSignature, origin: Span +) -> Span { let mut json = client_data_json_intro(); - json.append_all(encode_challenge(hash, signature.sha256_implementation)); + json.append_all(encode_challenge(hash)); json.append_all(array!['"', ',', '"', 'o', 'r', 'i', 'g', 'i', 'n', '"', ':', '"'].span()); json.append_all(origin); - json.append_all(array!['"', ',', '"', 'c', 'r', 'o', 's', 's', 'O', 'r', 'i', 'g', 'i', 'n', '"', ':'].span()); - if signature.cross_origin { - json.append_all(array!['t', 'r', 'u', 'e'].span()); - } else { - json.append_all(array!['f', 'a', 'l', 's', 'e'].span()); - } + json.append('"'); if !signature.client_data_json_outro.is_empty() { assert!(*signature.client_data_json_outro.at(0) == ',', "webauthn/invalid-json-outro"); json.append_all(signature.client_data_json_outro); @@ -72,46 +66,29 @@ fn encode_client_data_json(hash: felt252, signature: WebauthnSignature, origin: json.span() } -fn encode_challenge(hash: felt252, sha256_implementation: Sha256Implementation) -> Span { +fn encode_challenge(hash: felt252) -> Span { let mut bytes = u256_to_u8s(hash.into()); - let last_byte = match sha256_implementation { - Sha256Implementation::Cairo0 => 0, - Sha256Implementation::Cairo1 => 1, - }; - bytes.append(last_byte); - assert!(bytes.len() == 33, "webauthn/invalid-challenge-length"); // remove '=' signs if this assert fails - Base64UrlEncoder::encode(bytes).span() + assert!(bytes.len() == 32, "webauthn/invalid-challenge-length"); + let result = Base64UrlEncoder::encode(bytes).span(); + // The trailing '=' are ommited as specified in: + // https://www.w3.org/TR/webauthn-2/#sctn-dependencies + assert!(result.len() == 44, "webauthn/invalid-challenge-encoding"); + result.slice(0, 43) } -fn encode_authenticator_data(signature: WebauthnSignature, rp_id_hash: u256) -> Array { - let mut bytes = u256_to_u8s(rp_id_hash); - bytes.append(signature.flags); - bytes.append_all(u32s_to_u8s(array![signature.sign_count.into()].span())); +fn encode_authenticator_data(signature: WebauthnSignature, rp_id_hash: u256) -> ByteArray { + let mut bytes = u256_to_byte_array(rp_id_hash); + bytes.append_byte(signature.flags); + bytes.append(@u32s_to_byte_array(array![signature.sign_count].span())); bytes } -fn get_webauthn_hash_cairo0(hash: felt252, signer: WebauthnSigner, signature: WebauthnSignature) -> Option { - let client_data_json = encode_client_data_json(hash, signature, signer.origin); - let client_data_hash = u32s_to_u8s(sha256_cairo0(client_data_json)?); - let mut message = encode_authenticator_data(signature, signer.rp_id_hash.into()); - message.append_all(client_data_hash); - Option::Some(u32s_to_u256(sha256_cairo0(message.span())?)) -} - -fn get_webauthn_hash_cairo1(hash: felt252, signer: WebauthnSigner, signature: WebauthnSignature) -> u256 { +fn get_webauthn_hash(hash: felt252, signer: WebauthnSigner, signature: WebauthnSignature) -> u256 { let client_data_json = encode_client_data_json(hash, signature, signer.origin); - let client_data_hash = sha256(client_data_json.snapshot.clone()).span(); + let client_data_hash = compute_sha256_byte_array(@client_data_json.into_byte_array()).span(); let mut message = encode_authenticator_data(signature, signer.rp_id_hash.into()); - message.append_all(client_data_hash); - sha256(message).span().try_into().expect('webauthn/invalid-hash') -} - -fn get_webauthn_hash(hash: felt252, signer: WebauthnSigner, signature: WebauthnSignature) -> u256 { - match signature.sha256_implementation { - Sha256Implementation::Cairo0 => get_webauthn_hash_cairo0(hash, signer, signature) - .expect('webauthn/sha256-cairo0-failed'), - Sha256Implementation::Cairo1 => get_webauthn_hash_cairo1(hash, signer, signature), - } + message.append(@u32s_to_byte_array(client_data_hash)); + u32s_typed_to_u256(@compute_sha256_byte_array(@message)) } fn client_data_json_intro() -> Array { diff --git a/src/signer_storage/signer_list.cairo b/src/signer_storage/signer_list.cairo index e4bc8573..3a100f7d 100644 --- a/src/signer_storage/signer_list.cairo +++ b/src/signer_storage/signer_list.cairo @@ -2,10 +2,11 @@ mod signer_list_component { use argent::signer::signer_signature::Signer; use argent::signer_storage::interface::ISignerList; + use starknet::storage::Map; #[storage] struct Storage { - signer_list: LegacyMap, + signer_list: Map, } #[event] diff --git a/src/utils/array_store.cairo b/src/utils/array_store.cairo index 0ae08594..fe983b29 100644 --- a/src/utils/array_store.cairo +++ b/src/utils/array_store.cairo @@ -4,11 +4,11 @@ use starknet::{SyscallResult, storage_access::{Store, StorageBaseAddress}}; // Can store up to 255 felt252 impl StoreFelt252Array of Store> { fn read(address_domain: u32, base: StorageBaseAddress) -> SyscallResult> { - StoreFelt252Array::read_at_offset(address_domain, base, 0) + Self::read_at_offset(address_domain, base, 0) } fn write(address_domain: u32, base: StorageBaseAddress, value: Array) -> SyscallResult<()> { - StoreFelt252Array::write_at_offset(address_domain, base, 0, value) + Self::write_at_offset(address_domain, base, 0, value) } fn read_at_offset(address_domain: u32, base: StorageBaseAddress, mut offset: u8) -> SyscallResult> { diff --git a/src/utils/bytes.cairo b/src/utils/bytes.cairo index c53e9794..2ad9f57b 100644 --- a/src/utils/bytes.cairo +++ b/src/utils/bytes.cairo @@ -101,6 +101,75 @@ fn u256_to_u8s(word: u256) -> Array { ] } +fn u256_to_byte_array(word: u256) -> ByteArray { + let (rest, byte_32) = integer::u128_safe_divmod(word.low, 0x100); + let (rest, byte_31) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_30) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_29) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_28) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_27) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_26) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_25) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_24) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_23) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_22) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_21) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_20) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_19) = integer::u128_safe_divmod(rest, 0x100); + let (byte_17, byte_18) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_16) = integer::u128_safe_divmod(word.high, 0x100); + let (rest, byte_15) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_14) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_13) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_12) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_11) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_10) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_9) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_8) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_7) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_6) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_5) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_4) = integer::u128_safe_divmod(rest, 0x100); + let (rest, byte_3) = integer::u128_safe_divmod(rest, 0x100); + let (byte_1, byte_2) = integer::u128_safe_divmod(rest, 0x100); + let mut output: ByteArray = ""; + + output.append_byte(byte_1.try_into().unwrap()); + output.append_byte(byte_2.try_into().unwrap()); + output.append_byte(byte_3.try_into().unwrap()); + output.append_byte(byte_4.try_into().unwrap()); + output.append_byte(byte_5.try_into().unwrap()); + output.append_byte(byte_6.try_into().unwrap()); + output.append_byte(byte_7.try_into().unwrap()); + output.append_byte(byte_8.try_into().unwrap()); + output.append_byte(byte_9.try_into().unwrap()); + output.append_byte(byte_10.try_into().unwrap()); + output.append_byte(byte_11.try_into().unwrap()); + output.append_byte(byte_12.try_into().unwrap()); + output.append_byte(byte_13.try_into().unwrap()); + output.append_byte(byte_14.try_into().unwrap()); + output.append_byte(byte_15.try_into().unwrap()); + output.append_byte(byte_16.try_into().unwrap()); + output.append_byte(byte_17.try_into().unwrap()); + output.append_byte(byte_18.try_into().unwrap()); + output.append_byte(byte_19.try_into().unwrap()); + output.append_byte(byte_20.try_into().unwrap()); + output.append_byte(byte_21.try_into().unwrap()); + output.append_byte(byte_22.try_into().unwrap()); + output.append_byte(byte_23.try_into().unwrap()); + output.append_byte(byte_24.try_into().unwrap()); + output.append_byte(byte_25.try_into().unwrap()); + output.append_byte(byte_26.try_into().unwrap()); + output.append_byte(byte_27.try_into().unwrap()); + output.append_byte(byte_28.try_into().unwrap()); + output.append_byte(byte_29.try_into().unwrap()); + output.append_byte(byte_30.try_into().unwrap()); + output.append_byte(byte_31.try_into().unwrap()); + output.append_byte(byte_32.try_into().unwrap()); + + output +} + #[generate_trait] impl ByteArrayExt of ByteArrayExtTrait { fn into_bytes(self: ByteArray) -> Array { @@ -115,6 +184,20 @@ impl ByteArrayExt of ByteArrayExtTrait { } } +#[generate_trait] +impl ArrayU8Ext of ArrayU8ExtTrait { + fn into_byte_array(self: Span) -> ByteArray { + let mut output: ByteArray = ""; + let len = self.len(); + let mut i = 0; + while i != len { + output.append_byte(*self[i]); + i += 1; + }; + output + } +} + // Accepts felt252 for efficiency as it's the type of retdata but all values are expected to fit u32 fn u32s_to_u256(arr: Span) -> u256 { assert!(arr.len() == 8, "u32s_to_u256: input must be 8 elements long"); @@ -131,6 +214,21 @@ fn u32s_to_u256(arr: Span) -> u256 { u256 { high, low } } +fn u32s_typed_to_u256(arr: @[u32; 8]) -> u256 { + let [arr0, arr1, arr2, arr3, arr4, arr5, arr6, arr7] = arr; + let arr: Array = array![ + (*arr0).into(), + (*arr1).into(), + (*arr2).into(), + (*arr3).into(), + (*arr4).into(), + (*arr5).into(), + (*arr6).into(), + (*arr7).into(), + ]; + u32s_to_u256(arr.span()) +} + // Accepts felt252 for efficiency as it's the type of retdata but all values are expected to fit u32 fn u32s_to_u8s(mut words: Span) -> Span { let mut output = array![]; @@ -147,6 +245,22 @@ fn u32s_to_u8s(mut words: Span) -> Span { }; output.span() } +fn u32s_to_byte_array(mut words: Span) -> ByteArray { + let mut output: ByteArray = ""; + while let Option::Some(word) = words + .pop_front() { + let word: u32 = (*word).try_into().unwrap(); + let (rest, byte_4) = integer::u32_safe_divmod(word, 0x100); + let (rest, byte_3) = integer::u32_safe_divmod(rest, 0x100); + let (byte_1, byte_2) = integer::u32_safe_divmod(rest, 0x100); + output.append_byte(byte_1.try_into().unwrap()); + output.append_byte(byte_2.try_into().unwrap()); + output.append_byte(byte_3.try_into().unwrap()); + output.append_byte(byte_4.try_into().unwrap()); + }; + output +} + // Takes an array of u8s and returns an array of u32s, padding the end with 0s if necessary fn u8s_to_u32s_pad_end(mut bytes: Span) -> Array { let mut output = array![]; diff --git a/src/utils/transaction_version.cairo b/src/utils/transaction_version.cairo index 6aeb1a68..2ac4f10e 100644 --- a/src/utils/transaction_version.cairo +++ b/src/utils/transaction_version.cairo @@ -1,11 +1,11 @@ use starknet::{SyscallResultTrait, get_execution_info, get_tx_info, get_caller_address}; const TX_V1: felt252 = 1; // INVOKE -const TX_V1_ESTIMATE: felt252 = consteval_int!(0x100000000000000000000000000000000 + 1); // 2**128 + TX_V1 +const TX_V1_ESTIMATE: felt252 = 0x100000000000000000000000000000000 + 1; // 2**128 + TX_V1 const TX_V2: felt252 = 2; // DECLARE -const TX_V2_ESTIMATE: felt252 = consteval_int!(0x100000000000000000000000000000000 + 2); // 2**128 + TX_V2 +const TX_V2_ESTIMATE: felt252 = 0x100000000000000000000000000000000 + 2; // 2**128 + TX_V2 const TX_V3: felt252 = 3; -const TX_V3_ESTIMATE: felt252 = consteval_int!(0x100000000000000000000000000000000 + 3); // 2**128 + TX_V3 +const TX_V3_ESTIMATE: felt252 = 0x100000000000000000000000000000000 + 3; // 2**128 + TX_V3 const DA_MODE_L1: u32 = 0; const DA_MODE_L2: u32 = 1; diff --git a/tests/test_comp_src5.cairo b/tests/test_comp_src5.cairo index df086149..03e14313 100644 --- a/tests/test_comp_src5.cairo +++ b/tests/test_comp_src5.cairo @@ -6,9 +6,7 @@ use argent::introspection::interface::ISRC5Legacy; use argent::introspection::interface::{SRC5_INTERFACE_ID, SRC5_INTERFACE_ID_OLD}; use argent::introspection::src5::src5_component; use argent::mocks::src5_mocks::SRC5Mock; -use argent::outside_execution::interface::{ - ERC165_OUTSIDE_EXECUTION_INTERFACE_ID_REV_0, ERC165_OUTSIDE_EXECUTION_INTERFACE_ID_REV_1 -}; +use argent::outside_execution::interface::{ERC165_OUTSIDE_EXECUTION_INTERFACE_ID_REV_2}; const UNSUPORTED_INTERFACE_ID: felt252 = 0xffffffff; @@ -26,8 +24,14 @@ fn COMPONENT_STATE() -> ComponentState { fn test_introspection_account_id() { let mut component = COMPONENT_STATE(); assert!(component.supports_interface(SRC5_ACCOUNT_INTERFACE_ID), "should support account"); - assert!(component.supports_interface(SRC5_ACCOUNT_INTERFACE_ID_OLD_1), "should support account old 1"); - assert!(component.supports_interface(SRC5_ACCOUNT_INTERFACE_ID_OLD_2), "should support account old 2"); + assert!( + component.supports_interface(SRC5_ACCOUNT_INTERFACE_ID_OLD_1), + "should support account old 1" + ); + assert!( + component.supports_interface(SRC5_ACCOUNT_INTERFACE_ID_OLD_2), + "should support account old 2" + ); } #[test] @@ -40,8 +44,9 @@ fn test_introspection_src5_id() { #[test] fn test_introspection_outside_execution_id() { let mut component = COMPONENT_STATE(); - assert!(component.supports_interface(ERC165_OUTSIDE_EXECUTION_INTERFACE_ID_REV_0), "should support"); - assert!(component.supports_interface(ERC165_OUTSIDE_EXECUTION_INTERFACE_ID_REV_1), "should support"); + assert!( + component.supports_interface(ERC165_OUTSIDE_EXECUTION_INTERFACE_ID_REV_2), "should support" + ); } #[test] @@ -54,12 +59,23 @@ fn test_unsuported_interface_id() { fn test_introspection_legacy_method() { let mut component = COMPONENT_STATE(); assert_eq!(component.supportsInterface(SRC5_ACCOUNT_INTERFACE_ID), 1, "should support account"); - assert_eq!(component.supportsInterface(SRC5_ACCOUNT_INTERFACE_ID_OLD_1), 1, "should support account old 1"); - assert_eq!(component.supportsInterface(SRC5_ACCOUNT_INTERFACE_ID_OLD_2), 1, "should support account old 2"); + assert_eq!( + component.supportsInterface(SRC5_ACCOUNT_INTERFACE_ID_OLD_1), + 1, + "should support account old 1" + ); + assert_eq!( + component.supportsInterface(SRC5_ACCOUNT_INTERFACE_ID_OLD_2), + 1, + "should support account old 2" + ); assert_eq!(component.supportsInterface(SRC5_INTERFACE_ID), 1, "should support src5"); assert_eq!(component.supportsInterface(SRC5_INTERFACE_ID_OLD), 1, "should support src5 old"); - assert_eq!(component.supportsInterface(ERC165_OUTSIDE_EXECUTION_INTERFACE_ID_REV_0), 1, "should support"); - assert_eq!(component.supportsInterface(ERC165_OUTSIDE_EXECUTION_INTERFACE_ID_REV_1), 1, "should support"); + assert_eq!( + component.supportsInterface(ERC165_OUTSIDE_EXECUTION_INTERFACE_ID_REV_2), + 1, + "should support" + ); assert_eq!(component.supportsInterface(UNSUPORTED_INTERFACE_ID), 0, "should not support"); } diff --git a/tests/webauthn/test_webauthn_validation.cairo b/tests/webauthn/test_webauthn_validation.cairo index de2654b0..7c285854 100644 --- a/tests/webauthn/test_webauthn_validation.cairo +++ b/tests/webauthn/test_webauthn_validation.cairo @@ -12,7 +12,8 @@ fn new_webauthn_signer(origin: ByteArray, rp_id_hash: u256, pubkey: u256) -> Web fn localhost_rp() -> (ByteArray, u256) { let origin = "http://localhost:5173"; - let rp_id_hash = 0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763; // sha256("localhost") + let rp_id_hash = + 0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763; // sha256("localhost") (origin, rp_id_hash) } @@ -22,7 +23,8 @@ fn valid_signer() -> (felt252, WebauthnSigner, WebauthnSignature) { let pubkey = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296; let signer = new_webauthn_signer(:origin, :rp_id_hash, :pubkey); let signature = WebauthnSignature { - cross_origin: false, + cross_origin: Option::Some(false), + top_origin: Option::None, client_data_json_outro: array![].span(), flags: 0b00000101, sign_count: 0, @@ -52,7 +54,8 @@ fn test_is_valid_webauthn_signature_with_extra_json() { let pubkey = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296; let signer = new_webauthn_signer(:origin, :rp_id_hash, :pubkey); let signature = WebauthnSignature { - cross_origin: true, + cross_origin: Option::Some(true), + top_origin: Option::Some(array![].span()), client_data_json_outro: ",\"extraField\":\"random data\"}".into_bytes().span(), flags: 0b00010101, sign_count: 42,