Skip to content

Commit

Permalink
feat: stark sig auth (#467)
Browse files Browse the repository at this point in the history
* feat: stark sig auth

* chore: test stark sig auth

* feat: propose digest

* chore: starknetjs test scripts

* feat: verify propose sig

* chore: cleaned up stark sig test

* chore: removed: dead code

* feat: vote and update proposal sig auth

* chore: typescript linting + style

* chore: update yarn lock

* chore: tests for vote and update proposal starknet sig authentication

* feat: compute domain hash

* chore: account inteface

* chore: updated tests

* feat: use er165 to check account and sig

* chore: updated tests

* chore: formatting

* chore: type hash comments

* refactor: cleanup contracts and test

* fix: revert if salt used on propose and update

* chore: some comments

* chore: cleaned up test

* chore: .env example

* chore: updated lint settings

* chore: formatting

---------

Co-authored-by: Orlando <[email protected]>
  • Loading branch information
Orland0x and Orlando authored Aug 16, 2023
1 parent 831369d commit f9e623c
Show file tree
Hide file tree
Showing 22 changed files with 4,098 additions and 15 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NETWORK_URL=
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ethereum/lib
18 changes: 18 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"parser": "@typescript-eslint/parser",
"extends": [
"plugin:@typescript-eslint/recommended"
],
"plugins": [
"prettier",
"@typescript-eslint"
],
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"rules": {
"prettier/prettier": "error",
"@typescript-eslint/no-explicit-any": "off"
}
}
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"semi": true,
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2
}
11 changes: 11 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { JestConfigWithTsJest } from 'ts-jest';

const jestConfig: JestConfigWithTsJest = {
// [...]
// Replace `ts-jest` with the preset you want to use
// from the above list
preset: 'ts-jest',
roots: ['<rootDir>/starknet/tests'],
};

export default jestConfig;
24 changes: 23 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,27 @@
"description": "Cairo 1 implementation of the Snapshot X Protocol",
"repository": "https://github.com/snapshot-labs/sx-starknet-2.git",
"author": "Snapshot Labs",
"license": "MIT"
"license": "MIT",
"scripts": {
"format:ts": "eslint . --ext .ts --fix",
"test:stark-sig-auth": "jest -c jest.config.ts"
},
"devDependencies": {
"@types/jest": "^29.5.3",
"@types/node": "^20.4.5",
"@typescript-eslint/parser": "^6.2.1",
"dotenv": "^16.3.1",
"eslint": "^8.46.0",
"eslint-plugin-prettier": "^5.0.0",
"fs": "^0.0.1-security",
"jest": "^29.6.2",
"prettier": "^3.0.0",
"starknet": "^5.14.1",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
"typescript": "^5.1.6"
},
"dependencies": {
"@typescript-eslint/eslint-plugin": "^6.2.1"
}
}
3 changes: 2 additions & 1 deletion starknet/Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ version = "0.1.0"
# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest

[[target.starknet-contract]]
allowed-libfuncs-list.name = "all"
allowed-libfuncs-list.name = "audited"
casm = true

[dependencies]
starknet = ">=2.1.0-rc1"
Expand Down
2 changes: 2 additions & 0 deletions starknet/src/authenticators.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ mod eth_tx;

mod eth_sig;

mod stark_sig;

mod stark_tx;
172 changes: 172 additions & 0 deletions starknet/src/authenticators/stark_sig.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
use starknet::ContractAddress;
use sx::types::{Strategy, IndexedStrategy, Choice};

#[starknet::interface]
trait IStarkSigAuthenticator<TContractState> {
fn authenticate_propose(
ref self: TContractState,
signature: Array<felt252>,
target: ContractAddress,
author: ContractAddress,
execution_strategy: Strategy,
user_proposal_validation_params: Array<felt252>,
metadata_URI: Array<felt252>,
salt: felt252,
account_type: felt252
);
fn authenticate_vote(
ref self: TContractState,
signature: Array<felt252>,
target: ContractAddress,
voter: ContractAddress,
proposal_id: u256,
choice: Choice,
user_voting_strategies: Array<IndexedStrategy>,
metadata_URI: Array<felt252>,
account_type: felt252
);
fn authenticate_update_proposal(
ref self: TContractState,
signature: Array<felt252>,
target: ContractAddress,
author: ContractAddress,
proposal_id: u256,
execution_strategy: Strategy,
metadata_URI: Array<felt252>,
salt: felt252,
account_type: felt252
);
}

#[starknet::contract]
mod StarkSigAuthenticator {
use super::IStarkSigAuthenticator;
use starknet::{ContractAddress, info};
use core::array::{ArrayTrait, SpanTrait};
use serde::Serde;
use sx::space::space::{ISpaceDispatcher, ISpaceDispatcherTrait};
use sx::types::{Strategy, IndexedStrategy, UserAddress, Choice};
use sx::utils::stark_eip712;

#[storage]
struct Storage {
_domain_hash: felt252,
_used_salts: LegacyMap::<(ContractAddress, felt252), bool>
}

#[external(v0)]
impl StarkSigAuthenticator of IStarkSigAuthenticator<ContractState> {
fn authenticate_propose(
ref self: ContractState,
signature: Array<felt252>,
target: ContractAddress,
author: ContractAddress,
execution_strategy: Strategy,
user_proposal_validation_params: Array<felt252>,
metadata_URI: Array<felt252>,
salt: felt252,
account_type: felt252
) {
assert(!self._used_salts.read((author, salt)), 'Salt Already Used');

stark_eip712::verify_propose_sig(
self._domain_hash.read(),
signature,
target,
author,
@execution_strategy,
user_proposal_validation_params.span(),
metadata_URI.span(),
salt,
account_type
);

self._used_salts.write((author, salt), true);
ISpaceDispatcher {
contract_address: target
}
.propose(
UserAddress::Starknet(author),
execution_strategy,
user_proposal_validation_params,
metadata_URI
);
}

fn authenticate_vote(
ref self: ContractState,
signature: Array<felt252>,
target: ContractAddress,
voter: ContractAddress,
proposal_id: u256,
choice: Choice,
user_voting_strategies: Array<IndexedStrategy>,
metadata_URI: Array<felt252>,
account_type: felt252
) {
// No need to check salts here, as double voting is prevented by the space itself.

stark_eip712::verify_vote_sig(
self._domain_hash.read(),
signature,
target,
voter,
proposal_id,
choice,
user_voting_strategies.span(),
metadata_URI.span(),
account_type
);

ISpaceDispatcher {
contract_address: target
}
.vote(
UserAddress::Starknet(voter),
proposal_id,
choice,
user_voting_strategies,
metadata_URI
);
}

fn authenticate_update_proposal(
ref self: ContractState,
signature: Array<felt252>,
target: ContractAddress,
author: ContractAddress,
proposal_id: u256,
execution_strategy: Strategy,
metadata_URI: Array<felt252>,
salt: felt252,
account_type: felt252
) {
assert(!self._used_salts.read((author, salt)), 'Salt Already Used');

stark_eip712::verify_update_proposal_sig(
self._domain_hash.read(),
signature,
target,
author,
proposal_id,
@execution_strategy,
metadata_URI.span(),
salt,
account_type
);

self._used_salts.write((author, salt), true);
ISpaceDispatcher {
contract_address: target
}
.update_proposal(
UserAddress::Starknet(author), proposal_id, execution_strategy, metadata_URI
);
}
}
#[constructor]
fn constructor(ref self: ContractState, name: felt252, version: felt252) {
// TODO: store domain hash in stark_eip712 component once syntax is live.
self._domain_hash.write(stark_eip712::get_domain_hash(name, version));
}
}
5 changes: 5 additions & 0 deletions starknet/src/interfaces.cairo
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod i_voting_strategy;
mod i_execution_strategy;
mod i_proposal_validation_strategy;
mod i_account;

use i_voting_strategy::{IVotingStrategy, IVotingStrategyDispatcher, IVotingStrategyDispatcherTrait};
use i_execution_strategy::{
Expand All @@ -10,3 +11,7 @@ use i_proposal_validation_strategy::{
IProposalValidationStrategy, IProposalValidationStrategyDispatcher,
IProposalValidationStrategyDispatcherTrait
};
use i_account::{
AccountABI, AccountABIDispatcher, AccountABIDispatcherTrait, AccountCamelABI,
AccountCamelABIDispatcher, AccountCamelABIDispatcherTrait
};
34 changes: 34 additions & 0 deletions starknet/src/interfaces/i_account.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use array::ArrayTrait;
use array::SpanTrait;
use starknet::account::Call;
use starknet::ContractAddress;

// Interfaces from OZ: https://github.com/OpenZeppelin/cairo-contracts/blob/cairo-2/src/account/interface.cairo
#[starknet::interface]
trait AccountABI<TState> {
fn __execute__(self: @TState, calls: Array<Call>) -> Array<Span<felt252>>;
fn __validate__(self: @TState, calls: Array<Call>) -> felt252;
fn __validate_declare__(self: @TState, class_hash: felt252) -> felt252;
fn __validate_deploy__(
self: @TState, class_hash: felt252, contract_address_salt: felt252, _public_key: felt252
) -> felt252;
fn set_public_key(ref self: TState, new_public_key: felt252);
fn get_public_key(self: @TState) -> felt252;
fn is_valid_signature(self: @TState, hash: felt252, signature: Array<felt252>) -> felt252;
fn supports_interface(self: @TState, interface_id: felt252) -> bool;
}

// Entry points case-convention is enforced by the protocol
#[starknet::interface]
trait AccountCamelABI<TState> {
fn __execute__(self: @TState, calls: Array<Call>) -> Array<Span<felt252>>;
fn __validate__(self: @TState, calls: Array<Call>) -> felt252;
fn __validate_declare__(self: @TState, classHash: felt252) -> felt252;
fn __validate_deploy__(
self: @TState, classHash: felt252, contractAddressSalt: felt252, _publicKey: felt252
) -> felt252;
fn setPublicKey(ref self: TState, newPublicKey: felt252);
fn getPublicKey(self: @TState) -> felt252;
fn isValidSignature(self: @TState, hash: felt252, signature: Array<felt252>) -> felt252;
fn supportsInterface(self: @TState, interfaceId: felt252) -> bool;
}
3 changes: 3 additions & 0 deletions starknet/src/utils.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ mod legacy_hash;
mod math;
mod merkle;

mod struct_hash;

mod single_slot_proof;

mod signatures;

mod stark_eip712;
39 changes: 39 additions & 0 deletions starknet/src/utils/constants.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,42 @@ const STRATEGY_TYPEHASH_LOW: u128 = 0x78d5506febfdb18580ea361801747e63;
// keccak256("IndexedStrategy(uint256 index,uint8[] params)")
const INDEXED_STRATEGY_TYPEHASH_HIGH: u128 = 0x894665428ec742c74109dc21d320d1ab;
const INDEXED_STRATEGY_TYPEHASH_LOW: u128 = 0x8b36195eec0090e913c01e7534729c74;


// ------ Stark Signature Constants ------
// H refers to the Starknet Keccak hash function

const STARKNET_MESSAGE: felt252 = 'StarkNet Message';

// H('StarkNetDomain(name:felt252,version:felt252,chainId:felt252,verifyingContract:ContractAddress)')
const DOMAIN_TYPEHASH: felt252 = 0xa9974a36dee531bbc36aad5eeab4ade4df5ad388a296bb14d28ad4e9bf2164;

// H('Propose(space:ContractAddress,author:ContractAddress,executionStrategy:Strategy,
// userProposalValidationParams:felt*,metadataURI:felt*,salt:felt252)Strategy(address:felt252,params:felt*)')
const PROPOSE_TYPEHASH: felt252 = 0x248246f9067bb8dd7f7661e894ff088ed3e08cb0957df6cf9c9044cc71fffcb;

// H('Vote(space:ContractAddress,voter:ContractAddress,proposalId:u256,choice:felt252,
// userVotingStrategies:IndexedStrategy*,metadataURI:felt*)IndexedStrategy(index:felt252,params:felt*)
// u256(low:felt252,high:felt252)')
const VOTE_TYPEHASH: felt252 = 0x3ef46c9599d94309c080fa67cc9f79a94483b2a3ac938a28bba717aca5e1983;

// H('UpdateProposal(space:ContractAddress,author:ContractAddress,proposalId:u256,executionStrategy:Strategy,
// metadataURI:felt*,salt:felt252)Strategy(address:felt252,params:felt*)u256(low:felt252,high:felt252)')
const UPDATE_PROPOSAL_TYPEHASH: felt252 =
0x2dc58602de2862bc8c8dfae763fd5d754f9f4d0b5e6268a403783b7d9164c67;

// H('Strategy(address:felt252,params:felt*)')
const STRATEGY_TYPEHASH: felt252 =
0x39154ec0efadcd0deffdfc2044cf45dd986d260e59c26d69564b50a18f40f6b;

// H('IndexedStrategy(index:felt252,params:felt*)')
const INDEXED_STRATEGY_TYPEHASH: felt252 =
0x1f464f3e668281a899c5f3fc74a009ccd1df05fd0b9331b0460dc3f8054f64c;

// H('u256(low:felt252,high:felt252)')
const U256_TYPEHASH: felt252 = 0x1094260a770342332e6a73e9256b901d484a438925316205b4b6ff25df4a97a;

// ------ ERC165 Interface Ids ------
// For more information, refer to: https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md
const ERC165_ACCOUNT_INTERFACE_ID: felt252 = 0xa66bd575; // snake
const ERC165_OLD_ACCOUNT_INTERFACE_ID: felt252 = 0x3943f10f; // camel
Loading

0 comments on commit f9e623c

Please sign in to comment.