diff --git a/Makefile b/Makefile index a8fbc311..78a295dc 100644 --- a/Makefile +++ b/Makefile @@ -26,3 +26,7 @@ deploy-katana: starkli deploy "$${account_class}" ${test_pubkey} --salt 0x1234 ${config}; \ starkli declare ${build}ERC20${sierra} ${config}; \ starkli deploy "$${erc20_class}" str:token str:tkn u256:1 ${katana_0} --salt 0x1234 ${config}; + +test-session: generate_artifacts + rm -rf ./account_sdk/log + cargo test --manifest-path account_sdk/Cargo.toml session diff --git a/account_sdk/src/abigen.rs b/account_sdk/src/abigen.rs index b5fd3aa9..4df025b1 100644 --- a/account_sdk/src/abigen.rs +++ b/account_sdk/src/abigen.rs @@ -7,6 +7,7 @@ pub mod account { type_aliases { openzeppelin::introspection::src5::SRC5::Event as SRC5Event; webauthn_session::session_component::Event as SessionEvent; + webauthn_session::signature::SessionSignature as SessionSignature; } ); } diff --git a/account_sdk/src/session_token/account.rs b/account_sdk/src/session_token/account.rs index 7fb4efbe..0cbdc18b 100644 --- a/account_sdk/src/session_token/account.rs +++ b/account_sdk/src/session_token/account.rs @@ -1,7 +1,8 @@ use async_trait::async_trait; +use cainome::cairo_serde::CairoSerde; use starknet::{ accounts::{ - Account, Call, ConnectedAccount, Declaration, Execution, ExecutionEncoder, + Account, Call as StarknetCall, ConnectedAccount, Declaration, Execution, ExecutionEncoder, LegacyDeclaration, RawDeclaration, RawExecution, RawLegacyDeclaration, }, core::types::{ @@ -11,16 +12,18 @@ use starknet::{ providers::Provider, signers::Signer, }; -use std::sync::Arc; +use std::{sync::Arc, vec}; -use super::{Session, SessionSignature}; +use crate::abigen::account::{CartridgeAccount, SessionSignature, SignatureProofs}; +use crate::session_token::Session; +use crate::session_token::SIGNATURE_TYPE; impl ExecutionEncoder for SessionAccount where P: Provider + Send, S: Signer + Send, { - fn encode_calls(&self, calls: &[Call]) -> Vec { + fn encode_calls(&self, calls: &[StarknetCall]) -> Vec { // analogous to SingleOwnerAccount::encode_calls for ExecutionEncoding::New let mut serialized = vec![calls.len().into()]; @@ -68,6 +71,7 @@ where address: FieldElement, chain_id: FieldElement, ) -> Self { + assert_eq!(session.permitted_calls().len(), 1); Self { provider, signer, @@ -76,6 +80,10 @@ where address, } } + + pub fn session(&mut self) -> &mut Session { + &mut self.session + } } #[cfg_attr(not(target_arch = "wasm32"), async_trait)] @@ -114,18 +122,34 @@ where .await .map_err(SignError::SignersPubkey)?; + let account = CartridgeAccount::new(self.address, &self); + let permited_calls = self.session.permitted_calls(); + let proof = account + .compute_proof(permited_calls, &0) + .call() + .await + .expect("computing proof failed"); + let root = account + .compute_root(&permited_calls[0], &proof) + .call() + .await + .expect("computing root failed"); + let signature = SessionSignature { + signature_type: SIGNATURE_TYPE, r: signature.r, s: signature.s, session_key: session_key.scalar(), session_expires: self.session.session_expires(), - root: self.session.root(), - proof_len: self.session.proof_len(), - proofs: self.session.proofs(), + root, + proofs: SignatureProofs { + single_proof_len: proof.len() as u32, + proofs_flat: proof, + }, session_token: self.session.session_token(), }; - Ok(signature.into()) + Ok(SessionSignature::cairo_serialize(&signature)) } async fn sign_declaration( @@ -144,7 +168,7 @@ where unimplemented!("sign_legacy_declaration") } - fn execute(&self, calls: Vec) -> Execution { + fn execute(&self, calls: Vec) -> Execution { Execution::new(calls, self) } diff --git a/account_sdk/src/session_token/mod.rs b/account_sdk/src/session_token/mod.rs index 5380f2de..0f7976e2 100644 --- a/account_sdk/src/session_token/mod.rs +++ b/account_sdk/src/session_token/mod.rs @@ -1,12 +1,12 @@ mod account; mod sequence; mod session; -mod signature; +#[cfg(test)] +mod test_utils; pub use account::SessionAccount; pub use sequence::CallSequence; pub use session::Session; -pub use signature::SessionSignature; use starknet::{core::types::FieldElement, macros::felt}; pub const SIGNATURE_TYPE: FieldElement = felt!("0x53657373696f6e20546f6b656e207631"); // 'Session Token v1' @@ -15,64 +15,183 @@ pub const SIGNATURE_TYPE: FieldElement = felt!("0x53657373696f6e20546f6b656e2076 mod tests { use std::time::Duration; + use cainome::cairo_serde::ContractAddress; use starknet::{ - accounts::{Account, Call, ConnectedAccount}, + accounts::{Account, ConnectedAccount}, macros::selector, signers::{LocalWallet, SigningKey}, }; use tokio::time::sleep; + use crate::session_token::SessionAccount; use crate::tests::{ deployment_test::create_account, - runners::{DevnetRunner, TestnetRunner}, + runners::{KatanaRunner, TestnetRunner}, + }; + use crate::{ + abigen::{ + self, + account::{Call, CartridgeAccount}, + }, + session_token::test_utils::create_session_account, }; use super::*; #[tokio::test] - async fn test_session_valid() { - let runner = DevnetRunner::load(); + async fn test_session_compute_proof() { + let runner = KatanaRunner::load(); let master = create_account(&runner.prefunded_single_owner_account().await).await; - let session_key = LocalWallet::from(SigningKey::from_random()); + let address = master.address(); + let account = CartridgeAccount::new(address, &master); - let session = Session::default(); - let (chain_id, address) = (master.chain_id(), master.address()); - let provider = *master.provider(); - let account = SessionAccount::new(provider, session_key, session, address, chain_id); + let cainome_address = cainome::cairo_serde::ContractAddress::from(address); - let calls = vec![Call { - to: address, + let call = abigen::account::Call { + to: cainome_address, selector: selector!("revoke_session"), - calldata: vec![felt!("0x2137")], - }]; + calldata: vec![FieldElement::from(0x2137u32)], + }; - sleep(Duration::from_secs(10)).await; - account.execute(calls.clone()).send().await.unwrap(); + let proof = account.compute_proof(&vec![call], &0).call().await.unwrap(); + + assert_eq!(proof, vec![]); } #[tokio::test] - async fn test_session_revoked() { - let runner = DevnetRunner::load(); + async fn test_session_compute_root() { + let runner = KatanaRunner::load(); + let master = create_account(&runner.prefunded_single_owner_account().await).await; + + let address = master.address(); + let account = CartridgeAccount::new(address, &master); + + let cainome_address = ContractAddress::from(address); + + let call = Call { + to: cainome_address, + selector: selector!("revoke_session"), + calldata: vec![FieldElement::from(0x2137u32)], + }; + + let root = account.compute_root(&call, &vec![]).call().await.unwrap(); + + assert_ne!(root, felt!("0x0")); + } + + #[tokio::test] + async fn test_session_valid() { + let runner = KatanaRunner::load(); let master = create_account(&runner.prefunded_single_owner_account().await).await; let session_key = LocalWallet::from(SigningKey::from_random()); - let session = Session::default(); + let mut session = Session::default(); + let cainome_address = ContractAddress::from(master.address()); + let permited_calls = vec![Call { + to: cainome_address, + selector: selector!("revoke_session"), + calldata: vec![FieldElement::from(0x2137u32)], + }]; + + session.set_permitted_calls(permited_calls); + let (chain_id, address) = (master.chain_id(), master.address()); let provider = *master.provider(); let account = SessionAccount::new(provider, session_key, session, address, chain_id); + let account = CartridgeAccount::new(address, &account); - let calls = vec![Call { - to: address, - selector: selector!("revoke_session"), - calldata: vec![felt!("0x2137")], - }]; + account + .revoke_session(&FieldElement::from(0x2137u32)) + .send() + .await + .unwrap(); + } + + #[tokio::test] + async fn test_session_revoked() { + let runner = KatanaRunner::load(); + let session_account = + create_session_account(&runner.prefunded_single_owner_account().await).await; + + let account = CartridgeAccount::new(session_account.address(), &session_account); + + account + .revoke_session(&FieldElement::from(0x2137u32)) + .send() + .await + .unwrap(); + + sleep(Duration::from_millis(100)).await; + + let result = account + .revoke_session(&FieldElement::from(0x2137u32)) + .send() + .await; - sleep(Duration::from_secs(10)).await; - account.execute(calls.clone()).send().await.unwrap(); - let result = account.execute(calls.clone()).send().await; + assert!(result.is_err(), "Session should be revoked"); + } - assert!(result.is_err()); + #[tokio::test] + async fn test_session_invalid_proof() { + let runner = KatanaRunner::load(); + let mut session_account = + create_session_account(&runner.prefunded_single_owner_account().await).await; + + let cainome_address = ContractAddress::from(session_account.address()); + session_account.session().set_permitted_calls(vec![Call { + to: cainome_address, + selector: selector!("validate_session"), + calldata: vec![FieldElement::from(0x2137u32)], + }]); + + let account = CartridgeAccount::new(session_account.address(), &session_account); + + let result = account + .revoke_session(&FieldElement::from(0x2137u32)) + .send() + .await; + + assert!(result.is_err(), "Session should be revoked"); + } + + #[tokio::test] + async fn test_session_many_allowed() { + let runner = KatanaRunner::load(); + let mut session_account = + create_session_account(&runner.prefunded_single_owner_account().await).await; + + let cainome_address = ContractAddress::from(session_account.address()); + session_account.session().set_permitted_calls(vec![ + Call { + to: cainome_address, + selector: selector!("revoke_session"), + calldata: vec![], + }, + Call { + to: cainome_address, + selector: selector!("validate_session"), + calldata: vec![], + }, + Call { + to: cainome_address, + selector: selector!("compute_root"), + calldata: vec![], + }, + Call { + to: cainome_address, + selector: selector!("not_yet_defined"), + calldata: vec![], + }, + ]); + + let account = CartridgeAccount::new(session_account.address(), &session_account); + + account + .revoke_session(&FieldElement::from(0x2137u32)) + .send() + .await + .unwrap(); } } diff --git a/account_sdk/src/session_token/session.rs b/account_sdk/src/session_token/session.rs index dd010df2..6bd586a8 100644 --- a/account_sdk/src/session_token/session.rs +++ b/account_sdk/src/session_token/session.rs @@ -1,12 +1,12 @@ use starknet::{core::types::FieldElement, macros::felt}; +use crate::abigen::account::Call; + #[derive(Clone)] pub struct Session { session_expires: u64, - root: FieldElement, - proof_len: u32, - proofs: Vec, session_token: Vec, + permitted_calls: Vec, } impl Session { @@ -14,20 +14,17 @@ impl Session { self.session_expires } - pub fn root(&self) -> FieldElement { - self.root - } - - pub fn proof_len(&self) -> u32 { - self.proof_len + pub fn session_token(&self) -> Vec { + self.session_token.clone() } - pub fn proofs(&self) -> Vec { - self.proofs.clone() + pub fn permitted_calls(&self) -> &Vec { + &self.permitted_calls } - pub fn session_token(&self) -> Vec { - self.session_token.clone() + #[cfg(test)] + pub fn set_permitted_calls(&mut self, calls: Vec) { + self.permitted_calls = calls; } } @@ -35,10 +32,8 @@ impl Default for Session { fn default() -> Self { Self { session_expires: u64::MAX, - root: felt!("0x0"), - proof_len: 1, - proofs: vec![felt!("44")], session_token: vec![felt!("0x2137")], + permitted_calls: vec![], } } } diff --git a/account_sdk/src/session_token/signature.rs b/account_sdk/src/session_token/signature.rs deleted file mode 100644 index e3057806..00000000 --- a/account_sdk/src/session_token/signature.rs +++ /dev/null @@ -1,30 +0,0 @@ -use starknet::core::types::FieldElement; - -#[derive(Clone)] -pub struct SessionSignature { - pub r: FieldElement, - pub s: FieldElement, - pub session_key: FieldElement, - pub session_expires: u64, - pub root: FieldElement, - pub proof_len: u32, - pub proofs: Vec, - pub session_token: Vec, -} - -impl Into> for SessionSignature { - fn into(self) -> Vec { - let mut result = vec![super::SIGNATURE_TYPE]; - result.push(self.r); - result.push(self.s); - result.push(self.session_key); - result.push(self.session_expires.into()); - result.push(self.root); - result.push(self.proof_len.into()); - result.push(self.proofs.len().into()); - result.extend(self.proofs); - result.push(self.session_token.len().into()); - result.extend(self.session_token); - result - } -} diff --git a/account_sdk/src/session_token/test_utils.rs b/account_sdk/src/session_token/test_utils.rs new file mode 100644 index 00000000..eb878d3a --- /dev/null +++ b/account_sdk/src/session_token/test_utils.rs @@ -0,0 +1,37 @@ +use starknet::{ + accounts::{Account, ConnectedAccount, SingleOwnerAccount}, + core::types::FieldElement, + macros::selector, + providers::{jsonrpc::HttpTransport, JsonRpcClient}, + signers::{LocalWallet, SigningKey}, +}; + +use crate::abigen::account::Call; +use crate::session_token::SessionAccount; +use crate::tests::deployment_test::create_account; + +use super::Session; + +pub async fn create_session_account<'a>( + from: &SingleOwnerAccount<&'a JsonRpcClient, LocalWallet>, +) -> SessionAccount<&'a JsonRpcClient, LocalWallet> { + let (provider, chain_id) = (from.provider(), from.chain_id()); + + let master = create_account(from).await; + let address = master.address(); + let cainome_address = cainome::cairo_serde::ContractAddress::from(address); + + let call = Call { + to: cainome_address, + selector: selector!("revoke_session"), + calldata: vec![FieldElement::from(0x2137u32)], + }; + + let mut session = Session::default(); + let permited_calls = vec![call]; + session.set_permitted_calls(permited_calls); + + let session_key = LocalWallet::from(SigningKey::from_random()); + + SessionAccount::new(provider, session_key, session, address, chain_id) +} diff --git a/cartridge_account/src/lib.cairo b/cartridge_account/src/lib.cairo index d004436d..705d1c72 100644 --- a/cartridge_account/src/lib.cairo +++ b/cartridge_account/src/lib.cairo @@ -116,7 +116,16 @@ mod Account { } fn __validate__(self: @ContractState, mut calls: Array) -> felt252 { - self.validate_transaction() + let signature_type = self.validate_transaction(); + let tx_info = get_tx_info().unbox(); + + if signature_type == starknet::VALIDATED { + starknet::VALIDATED + } else if signature_type == 'Session Token v1' { + SessionImpl::validate_session(self, tx_info.signature, calls.span()) + } else { + signature_type + } } fn is_valid_signature( @@ -242,20 +251,13 @@ mod Account { return starknet::VALIDATED; } - let signature_type = *signature.pop_front().unwrap(); - - if signature_type == 'Session Token v1' { - // TODO: replace this mock - let calls = array![Call { - to: get_caller_address(), - selector: 1485514293835613587393115454149572371807630552041622198027280818831729347259, - calldata: array![0x2137] - }]; - SessionImpl::validate_session(self, signature, calls.span()) - } else { + let signature_type = *signature.at(0); + + if signature_type == starknet::VALIDATED { assert(false, Errors::INVALID_SIGNATURE); } - starknet::VALIDATED + + signature_type } fn _set_public_key(ref self: ContractState, new_public_key: felt252) { diff --git a/src/session/src/hash.cairo b/src/session/src/hash.cairo index 39c9f076..7797a78d 100644 --- a/src/session/src/hash.cairo +++ b/src/session/src/hash.cairo @@ -3,7 +3,7 @@ use core::poseidon::{PoseidonImpl, PoseidonTrait}; use starknet::{contract_address::ContractAddress}; -use webauthn_session::signature::TxInfoSignature; +use webauthn_session::signature::SessionSignature; use webauthn_session::Call; // H('StarkNetDomain(chainId:felt)') @@ -17,7 +17,7 @@ const POLICY_TYPE_HASH: felt252 = 0x2f0026e78543f036f33e26a8f5891b88c58dc1e20cbb // https://github.com/starkware-libs/cairo/blob/1cd1d242883787392f38f5a775ab045b5e2201f3/corelib/src/hash.cairo#L4 // https://github.com/starkware-libs/cairo/blob/1cd1d242883787392f38f5a775ab045b5e2201f3/examples/hash_chain.cairo#L4 fn compute_session_hash( - signature: TxInfoSignature, chain_id: felt252, account: ContractAddress + signature: SessionSignature, chain_id: felt252, account: ContractAddress ) -> felt252 { let domain_hash = hash_domain(chain_id); let message_hash = hash_message( diff --git a/src/session/src/lib.cairo b/src/session/src/lib.cairo index 2755a71d..d8f53c88 100644 --- a/src/session/src/lib.cairo +++ b/src/session/src/lib.cairo @@ -1,4 +1,3 @@ -use webauthn_session::signature::SignatureProofsTrait; use alexandria_data_structures::array_ext::ArrayTraitExt; use core::box::BoxTrait; use core::array::SpanTrait; @@ -9,14 +8,14 @@ use option::OptionTrait; use array::ArrayTrait; use core::{TryInto, Into}; use starknet::{contract_address::ContractAddress}; +use alexandria_merkle_tree::merkle_tree::{Hasher, MerkleTree, poseidon::PoseidonHasherImpl, MerkleTreeTrait}; -use webauthn_session::signature::{TxInfoSignature, FeltSpanTryIntoSignature, SignatureProofs}; +use core::ecdsa::check_ecdsa_signature; + +use webauthn_session::signature::{SessionSignature, FeltSpanTryIntoSignature, SignatureProofs, SignatureProofsTrait}; use webauthn_session::hash::{compute_session_hash, compute_call_hash}; -use alexandria_merkle_tree::merkle_tree::{Hasher, MerkleTree, pedersen::PedersenHasherImpl, MerkleTreeTrait}; -use core::ecdsa::check_ecdsa_signature; - mod hash; mod signature; @@ -25,8 +24,12 @@ mod tests; #[starknet::interface] trait ISession { - fn validate_session(self: @TContractState, signature: Span, calls: Span); + fn validate_session_abi(self: @TContractState, signature: SessionSignature, calls: Span); + fn validate_session(self: @TContractState, signature: Span, calls: Span) -> felt252; fn revoke_session(ref self: TContractState, token: felt252); + + fn compute_proof(self: @TContractState, calls: Array, position: u64) -> Span; + fn compute_root(self: @TContractState, call: Call, proof: Span) -> felt252; } // Based on https://github.com/argentlabs/starknet-plugin-account/blob/3c14770c3f7734ef208536d91bbd76af56dc2043/contracts/plugins/SessionKey.cairo @@ -36,9 +39,10 @@ mod session_component { use super::check_policy; use starknet::info::{TxInfo, get_tx_info, get_block_timestamp}; use starknet::account::Call; - use webauthn_session::signature::{TxInfoSignature, FeltSpanTryIntoSignature, SignatureProofs, SignatureProofsTrait}; + use webauthn_session::signature::{SessionSignature, FeltSpanTryIntoSignature, SignatureProofs, SignatureProofsTrait}; use webauthn_session::hash::{compute_session_hash, compute_call_hash}; use ecdsa::check_ecdsa_signature; + use alexandria_merkle_tree::merkle_tree::{Hasher, MerkleTree, poseidon::PoseidonHasherImpl, MerkleTreeTrait}; #[storage] struct Storage { @@ -68,10 +72,15 @@ mod session_component { impl SessionImpl< TContractState, +HasComponent > of super::ISession> { - fn validate_session(self: @ComponentState, mut signature: Span, calls: Span) { - let sig: TxInfoSignature = Serde::::deserialize(ref signature).unwrap(); + fn validate_session_abi(self: @ComponentState, signature: SessionSignature, calls: Span) { + self.validate_signature(signature, calls).unwrap(); + } + + fn validate_session(self: @ComponentState, mut signature: Span, calls: Span) -> felt252 { + let sig: SessionSignature = Serde::::deserialize(ref signature).unwrap(); self.validate_signature(sig, calls).unwrap(); + starknet::VALIDATED } fn revoke_session( @@ -80,13 +89,39 @@ mod session_component { self.revoked.write(token, 1); self.emit(TokenRevoked { token: token }); } + + fn compute_proof(self: @ComponentState, mut calls: Array, position: u64) -> Span { + assert(calls.len() > 0, 'No calls provided'); + let mut merkle: MerkleTree = MerkleTreeTrait::new(); + + let mut leaves = array![]; + + // Hashing all the calls + loop { + let pub_key = match calls.pop_front() { + Option::Some(single) => { + leaves.append(compute_call_hash(@single)); + }, + Option::None(_) => { break; }, + }; + }; + + merkle.compute_proof(leaves.clone(), 0) + } + + fn compute_root(self: @ComponentState, call: Call, proof: Span) -> felt252 { + let mut merkle: MerkleTree = MerkleTreeTrait::new(); + let leaf = compute_call_hash(@call); + + merkle.compute_root(leaf, proof) + } } #[generate_trait] impl InternalImpl< TContractState, +HasComponent > of InternalTrait { - fn validate_signature(self: @ComponentState, signature: TxInfoSignature, calls: Span) -> Result<(), felt252> { + fn validate_signature(self: @ComponentState, signature: SessionSignature, calls: Span) -> Result<(), felt252> { if signature.proofs.len() != calls.len() { return Result::Err(Errors::LENGHT_MISMATCH); }; @@ -118,9 +153,9 @@ mod session_component { return Result::Err(Errors::SESSION_SIGNATURE_INVALID); } - // if check_policy(calls, signature.root, signature.proofs).is_err() { - // return Result::Err(Errors::POLICY_CHECK_FAILED); - // } + if check_policy(calls, signature.root, signature.proofs).is_err() { + return Result::Err(Errors::POLICY_CHECK_FAILED); + } Result::Ok(()) } @@ -128,7 +163,7 @@ mod session_component { } fn check_policy( - call_array: Array, root: felt252, proofs: SignatureProofs, + call_array: Span, root: felt252, proofs: SignatureProofs, ) -> Result<(), ()> { let mut i = 0_usize; loop { @@ -137,6 +172,7 @@ fn check_policy( } let leaf = compute_call_hash(call_array.at(i)); let mut merkle: MerkleTree = MerkleTreeTrait::new(); + if merkle.verify(root, leaf, proofs.at(i)) == false { break Result::Err(()); }; diff --git a/src/session/src/signature.cairo b/src/session/src/signature.cairo index d084b21b..9d6a5cc6 100644 --- a/src/session/src/signature.cairo +++ b/src/session/src/signature.cairo @@ -3,7 +3,8 @@ use core::Into; use debug::PrintTrait; #[derive(Copy, Drop, Serde)] -struct TxInfoSignature { +struct SessionSignature { + signature_type: felt252, r: felt252, s: felt252, session_key: felt252, @@ -13,16 +14,16 @@ struct TxInfoSignature { session_token: Span } -impl FeltSpanTryIntoSignature of TryInto, TxInfoSignature> { - // Convert a span of felts to TxInfoSignature struct +impl FeltSpanTryIntoSignature of TryInto, SessionSignature> { + // Convert a span of felts to SessionSignature struct // The layout of the span should look like: // [r, s, session_key, session_expires, root, proof_len, proofs_len, { proofs ... } , session_token_len, { session_token ... }] // ^-proofs_len-^ ^-session_token_len-^ // See details in the implementation - fn try_into(self: Span) -> Option { - let single_proof_len: usize = (*self[6]).try_into()?; - let total_proofs_len: usize = (*self[7]).try_into()?; - let session_token_offset: usize = 8 + total_proofs_len; + fn try_into(self: Span) -> Option { + let single_proof_len: usize = (*self[7]).try_into()?; + let total_proofs_len: usize = (*self[8]).try_into()?; + let session_token_offset: usize = 9 + total_proofs_len; let session_token_len: usize = (*self[session_token_offset]).try_into()?; if self.len() != session_token_offset + 1 + session_token_len { @@ -32,16 +33,17 @@ impl FeltSpanTryIntoSignature of TryInto, TxInfoSignature> { let session_token: Span = self .slice(session_token_offset + 1, session_token_len); - let proofs_flat: Span = self.slice(8, total_proofs_len); + let proofs_flat: Span = self.slice(9, total_proofs_len); let proofs = ImplSignatureProofs::try_new(proofs_flat, single_proof_len)?; Option::Some( - TxInfoSignature { - r: *self[1], - s: *self[2], - session_key: *self[3], - session_expires: (*self[4]).try_into()?, - root: *self[5], + SessionSignature { + signature_type: *self[0], + r: *self[2], + s: *self[3], + session_key: *self[4], + session_expires: (*self[5]).try_into()?, + root: *self[6], proofs: proofs, session_token: session_token } @@ -68,6 +70,9 @@ impl ImplSignatureProofs of SignatureProofsTrait { } fn len(self: SignatureProofs) -> usize { + if self.single_proof_len == 0 { + return 1; // When there is only one leaf, it is equal to the root, so the only proof is empty + } U32Div::div(self.proofs_flat.len(), self.single_proof_len) } diff --git a/src/session/src/tests/signature_deserialization.cairo b/src/session/src/tests/signature_deserialization.cairo index 21aa582b..18318a61 100644 --- a/src/session/src/tests/signature_deserialization.cairo +++ b/src/session/src/tests/signature_deserialization.cairo @@ -9,7 +9,7 @@ use array::ArrayTrait; use core::{TryInto, Into}; use starknet::{contract_address::ContractAddress}; -use webauthn_session::signature::{TxInfoSignature, FeltSpanTryIntoSignature, SignatureProofs}; +use webauthn_session::signature::{SessionSignature, FeltSpanTryIntoSignature, SignatureProofs}; use webauthn_session::hash::{compute_session_hash, compute_call_hash}; use alexandria_merkle_tree::merkle_tree::{Hasher, MerkleTree, pedersen::PedersenHasherImpl, MerkleTreeTrait}; @@ -33,7 +33,7 @@ fn test_session() { 0x2137, ].span(); - let deser: TxInfoSignature = Serde::::deserialize(ref sig).unwrap(); + let deser: SessionSignature = Serde::::deserialize(ref sig).unwrap(); assert(deser.r == 0x42, 'invalid r'); assert(deser.proofs.len() == 1, 'invalid proofs len'); }