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

Init validations #1

Merged
merged 3 commits into from
Oct 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Empty file.
131 changes: 131 additions & 0 deletions Scarb.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,137 @@
# Code generated by scarb DO NOT EDIT.
version = 1

[[package]]
name = "openzeppelin"
version = "0.17.0"
source = "registry+https://scarbs.xyz/"
checksum = "sha256:7e77855aaba0825a2a12cad72d52d85380a9fab732007754b3c5d98908918ce7"
dependencies = [
"openzeppelin_access",
"openzeppelin_account",
"openzeppelin_finance",
"openzeppelin_governance",
"openzeppelin_introspection",
"openzeppelin_merkle_tree",
"openzeppelin_presets",
"openzeppelin_security",
"openzeppelin_token",
"openzeppelin_upgrades",
"openzeppelin_utils",
]

[[package]]
name = "openzeppelin_access"
version = "0.17.0"
source = "registry+https://scarbs.xyz/"
checksum = "sha256:541bb8fdf1ad17fe0d275b00acb9f0d7f56ea5534741e21535ac3fda2c600281"
dependencies = [
"openzeppelin_introspection",
"openzeppelin_utils",
]

[[package]]
name = "openzeppelin_account"
version = "0.17.0"
source = "registry+https://scarbs.xyz/"
checksum = "sha256:c4e11609fdd1f4c3d3004cd1468711bd2ea664739c9e59a4b270567fe4c23ee3"
dependencies = [
"openzeppelin_introspection",
"openzeppelin_utils",
]

[[package]]
name = "openzeppelin_finance"
version = "0.17.0"
source = "registry+https://scarbs.xyz/"
checksum = "sha256:9adcbec76ee8ed08be8d87c6af6014aa7080d67578816f5ba77f4376b25bc165"
dependencies = [
"openzeppelin_access",
"openzeppelin_token",
]

[[package]]
name = "openzeppelin_governance"
version = "0.17.0"
source = "registry+https://scarbs.xyz/"
checksum = "sha256:b7e0142d88d69a8c367aea8c9dc7f659f27372551efc23f39a0cf71a189c1302"
dependencies = [
"openzeppelin_access",
"openzeppelin_introspection",
]

[[package]]
name = "openzeppelin_introspection"
version = "0.17.0"
source = "registry+https://scarbs.xyz/"
checksum = "sha256:892433a4a1ea0fc9cf7cdb01e06ddc2782182abcc188e4ea5dd480906d006cf8"

[[package]]
name = "openzeppelin_merkle_tree"
version = "0.17.0"
source = "registry+https://scarbs.xyz/"
checksum = "sha256:3c338fa07cbaba8034051a42967816800abe535ef7d709a929175616603dccf9"

[[package]]
name = "openzeppelin_presets"
version = "0.17.0"
source = "registry+https://scarbs.xyz/"
checksum = "sha256:0a39e0effff133ab7fb003961ee2986438ee09b53608ce0d71aca24459879597"
dependencies = [
"openzeppelin_access",
"openzeppelin_account",
"openzeppelin_finance",
"openzeppelin_introspection",
"openzeppelin_token",
"openzeppelin_upgrades",
]

[[package]]
name = "openzeppelin_security"
version = "0.17.0"
source = "registry+https://scarbs.xyz/"
checksum = "sha256:6e2dee39d87f9ddec2ad37e33e80cf0d8b6c6927fd7950f220dbc2baea658d43"

[[package]]
name = "openzeppelin_token"
version = "0.17.0"
source = "registry+https://scarbs.xyz/"
checksum = "sha256:77997a7e217b69674c34b402dc0c7b2210540db66a56087572679c31896eaabb"
dependencies = [
"openzeppelin_account",
"openzeppelin_governance",
"openzeppelin_introspection",
]

[[package]]
name = "openzeppelin_upgrades"
version = "0.17.0"
source = "registry+https://scarbs.xyz/"
checksum = "sha256:a0fa5934f2924e1e85ec8f8c5b7dcd95c25295c029d3a745ba87b3191146004d"

[[package]]
name = "openzeppelin_utils"
version = "0.17.0"
source = "registry+https://scarbs.xyz/"
checksum = "sha256:36d93e353f42fd6b824abcd8b4b51c3f5d02c893c5f886ae81403b0368aa5fde"

[[package]]
name = "rosettacontracts"
version = "0.1.0"
dependencies = [
"openzeppelin",
"snforge_std",
]

[[package]]
name = "snforge_scarb_plugin"
version = "0.31.0"
source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.31.0#72ea785ca354e9e506de3e5d687da9fb2c1b3c67"

[[package]]
name = "snforge_std"
version = "0.31.0"
source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.31.0#72ea785ca354e9e506de3e5d687da9fb2c1b3c67"
dependencies = [
"snforge_scarb_plugin",
]
5 changes: 4 additions & 1 deletion Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,8 @@ sierra = true


[dependencies]
starknet = "2.6.4"
starknet = "2.8.2"
openzeppelin = "0.17.0"

[dev-dependencies]
snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.31.0" }
121 changes: 105 additions & 16 deletions src/accounts/base.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ pub type EthPublicKey = starknet::secp256k1::Secp256k1Point;
#[starknet::interface]
pub trait IRosettaAccount<TState> {
fn __execute__(self: @TState, calls: Array<felt252>) -> Array<Span<felt252>>;
fn __validate__(self: @TState, calls: Array<Call>) -> felt252;
fn __validate__(self: @TState, calls: Array<felt252>) -> felt252;
fn is_valid_signature(self: @TState, hash: felt252, signature: Array<felt252>) -> felt252;
fn supports_interface(self: @TState, interface_id: felt252) -> bool;
fn __validate_declare__(self: @TState, class_hash: felt252) -> felt252;
Expand All @@ -20,52 +20,141 @@ pub trait IRosettaAccount<TState> {
#[starknet::contract(account)]
mod RosettaAccount {
use super::EthPublicKey;
use starknet::{EthAddress, get_execution_info, get_contract_address};
use core::num::traits::Zero;
use starknet::{
EthAddress, get_execution_info, get_contract_address, get_caller_address, get_tx_info
};
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
use rosettacontracts::accounts::utils::{is_valid_eth_signature, Secp256k1PointStorePacking};

pub mod Errors {
pub const INVALID_CALLER: felt252 = 'Rosetta: invalid caller';
pub const INVALID_SIGNATURE: felt252 = 'Rosetta: invalid signature';
pub const INVALID_TX_VERSION: felt252 = 'Rosetta: invalid tx version';
pub const UNAUTHORIZED: felt252 = 'Rosetta: unauthorized';
}


#[storage]
struct Storage {
ethereum_address: EthAddress
ethereum_address: EthAddress,
ethereum_public_key: EthPublicKey
}

#[constructor]
fn constructor(ref self: ContractState) {}
fn constructor(ref self: ContractState, eth_account: EthAddress) {
self.ethereum_address.write(eth_account);
}

#[abi(embed_v0)]
impl AccountImpl of super::IRosettaAccount<ContractState> {
fn __execute__(self: @TState, calls: Array<felt252>) -> Array<Span<felt252>> {}
// Instead of Array<Call> we use Array<felt252> since we pass different values to the
// parameter
fn __execute__(self: @ContractState, calls: Array<felt252>) -> Array<Span<felt252>> {
let sender = get_caller_address();
assert(sender.is_zero(), Errors::INVALID_CALLER);
// TODO: Check tx version

fn __validate__(self: @TState, calls: Array<Call>) -> felt252 {}
// TODO: Exec calls
}

fn is_valid_signature(self: @TState, hash: felt252, signature: Array<felt252>) -> felt252 {}
fn __validate__(self: @ContractState, calls: Array<felt252>) -> felt252 {
// TODO: check if validations enough
self.validate_transaction()
}

fn supports_interface(self: @TState, interface_id: felt252) -> bool {}
fn is_valid_signature(
self: @ContractState, hash: felt252, signature: Array<felt252>
) -> felt252 {
if self._is_valid_signature(hash, signature.span()) {
starknet::VALIDATED
} else {
0
}
}

fn __validate_declare__(self: @TState, class_hash: felt252) -> felt252 {}
fn supports_interface(self: @ContractState, interface_id: felt252) -> bool {
true
}

fn __validate_declare__(self: @ContractState, class_hash: felt252) -> felt252 {
// TODO: check if validations enough
self.validate_transaction()
}

fn __validate_deploy__(
self: @TState,
self: @ContractState,
class_hash: felt252,
contract_address_salt: felt252,
public_key: EthPublicKey
) -> felt252 {}
) -> felt252 {
// TODO: check if validations enough
self.validate_transaction()
}

fn get_public_key(self: @TState) -> EthPublicKey {}
fn get_public_key(self: @ContractState) -> EthPublicKey {
self.ethereum_public_key.read()
}

// We dont need that function
fn set_public_key(
ref self: TState, new_public_key: EthPublicKey, signature: Span<felt252>
ref self: ContractState, new_public_key: EthPublicKey, signature: Span<felt252>
) {}

fn isValidSignature(self: @TState, hash: felt252, signature: Array<felt252>) -> felt252 {
fn isValidSignature(
self: @ContractState, hash: felt252, signature: Array<felt252>
) -> felt252 {
self.is_valid_signature(hash, signature)
}

fn getPublicKey(self: @TState) -> EthPublicKey {
fn getPublicKey(self: @ContractState) -> EthPublicKey {
self.get_public_key()
}

fn setPublicKey(ref self: TState, newPublicKey: EthPublicKey, signature: Span<felt252>) {
// We dont need that function
fn setPublicKey(
ref self: ContractState, newPublicKey: EthPublicKey, signature: Span<felt252>
) {
self.set_public_key(newPublicKey, signature)
}
}

#[generate_trait]
impl InternalImpl of InternalTrait {
fn initializer(ref self: ContractState, ethPubKey: EthPublicKey) {
// Write pubkey to storage
self._set_public_key(ethPubKey);
}

fn assert_only_self(self: @ContractState) {
let caller = get_caller_address();
let self = get_contract_address();
assert(self == caller, Errors::UNAUTHORIZED);
}

// Overwrites ethereum public key. We may remove that function since we only need to
// write during initialization.
fn _set_public_key(ref self: ContractState, new_public_key: EthPublicKey) {
self.ethereum_public_key.write(new_public_key);
}

/// Validates the signature for the current transaction.
/// Returns the short string `VALID` if valid, otherwise it reverts.
fn validate_transaction(self: @ContractState) -> felt252 {
let tx_info = get_tx_info().unbox();
let tx_hash = tx_info.transaction_hash;
let signature = tx_info.signature;
assert(self._is_valid_signature(tx_hash, signature), Errors::INVALID_SIGNATURE);
starknet::VALIDATED
}

/// Returns whether the given signature is valid for the given hash
/// using the account's current public key.
fn _is_valid_signature(
self: @ContractState, hash: felt252, signature: Span<felt252>
) -> bool {
let public_key: EthPublicKey = self.ethereum_public_key.read();
is_valid_eth_signature(hash, public_key, signature)
}
}
}
77 changes: 77 additions & 0 deletions src/accounts/utils.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use starknet::secp256_trait;
use rosettacontracts::accounts::base::{EthPublicKey};

#[derive(Copy, Drop, Serde)]
pub struct EthSignature {
pub r: u256,
pub s: u256,
}

pub fn is_valid_eth_signature(
msg_hash: felt252, public_key: EthPublicKey, signature: Span<felt252>
) -> bool {
let mut signature = signature;
let signature: EthSignature = Serde::deserialize(ref signature)
.expect('Signature: Invalid format.');

secp256_trait::is_valid_signature(msg_hash.into(), signature.r, signature.s, public_key)
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts for Cairo v0.17.0 (account/utils/secp256k1.cairo)

use core::fmt::{Formatter, Error};
use starknet::SyscallResultTrait;
use starknet::secp256_trait::{Secp256Trait, Secp256PointTrait};
use starknet::secp256k1::Secp256k1Point;
use starknet::storage_access::StorePacking;

/// Packs a Secp256k1Point into a (felt252, felt252).
///
/// The packing is done as follows:
/// - First felt contains x.low (x being the x-coordinate of the point).
/// - Second felt contains x.high and the parity bit, at the least significant bits (2 * x.high +
/// parity).
pub impl Secp256k1PointStorePacking of StorePacking<Secp256k1Point, (felt252, felt252)> {
fn pack(value: Secp256k1Point) -> (felt252, felt252) {
let (x, y) = value.get_coordinates().unwrap_syscall();

let parity = y % 2;
let xhigh_and_parity = 2 * x.high.into() + parity.try_into().unwrap();

(x.low.into(), xhigh_and_parity)
}

fn unpack(value: (felt252, felt252)) -> Secp256k1Point {
let (xlow, xhigh_and_parity) = value;
let xhigh_and_parity: u256 = xhigh_and_parity.into();

let x = u256 {
low: xlow.try_into().unwrap(), high: (xhigh_and_parity / 2).try_into().unwrap(),
};
let parity = xhigh_and_parity % 2 == 1;

// Expects parity odd to be true
Secp256Trait::secp256_ec_get_point_from_x_syscall(x, parity)
.unwrap_syscall()
.expect('Secp256k1Point: Invalid point.')
}
}

pub impl Secp256k1PointPartialEq of PartialEq<Secp256k1Point> {
#[inline(always)]
fn eq(lhs: @Secp256k1Point, rhs: @Secp256k1Point) -> bool {
(*lhs).get_coordinates().unwrap_syscall() == (*rhs).get_coordinates().unwrap_syscall()
}
#[inline(always)]
fn ne(lhs: @Secp256k1Point, rhs: @Secp256k1Point) -> bool {
!(lhs == rhs)
}
}

pub impl DebugSecp256k1Point of core::fmt::Debug<Secp256k1Point> {
fn fmt(self: @Secp256k1Point, ref f: Formatter) -> Result<(), Error> {
let (x, y) = (*self).get_coordinates().unwrap_syscall();
write!(f, "({x:?},{y:?})")
}
}
Loading
Loading