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

Eth account support #361

Merged
merged 66 commits into from
Jun 29, 2022
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
8561dc7
Create separate execute function
JulissaDantes Jun 10, 2022
7676f32
Add is_valid_eth_signature to account library
JulissaDantes Jun 10, 2022
99446d5
Add eth_execute to account library
JulissaDantes Jun 10, 2022
1e2f521
Create eth account mock and test
JulissaDantes Jun 10, 2022
2b7b91a
Add missing dependencies
JulissaDantes Jun 10, 2022
c72c4cd
Create TestEthSigner
JulissaDantes Jun 10, 2022
b6e4701
Update used private key
JulissaDantes Jun 10, 2022
2f737db
Update implicit parameters
JulissaDantes Jun 10, 2022
a92e1de
Update execute parameters
JulissaDantes Jun 14, 2022
e507109
Update all implicit arguments
JulissaDantes Jun 14, 2022
8e0c04d
Update signature values and hash
JulissaDantes Jun 14, 2022
199d272
Update variable name
JulissaDantes Jun 14, 2022
f9cd939
Merge branch 'main' into eth-account
JulissaDantes Jun 14, 2022
2601297
Update documentation
JulissaDantes Jun 15, 2022
75f75c5
Fix merge error
JulissaDantes Jun 15, 2022
4092021
Improve format
JulissaDantes Jun 15, 2022
2e7e85a
Update tests/utils.py
JulissaDantes Jun 17, 2022
6219d02
Update docs/Account.md
JulissaDantes Jun 17, 2022
121f87d
Update docs/Account.md
JulissaDantes Jun 17, 2022
12fbf29
Rename test and fix documentation
JulissaDantes Jun 17, 2022
a88b9bd
Add documentation
JulissaDantes Jun 21, 2022
0394607
Update src/openzeppelin/account/library.cairo
JulissaDantes Jun 21, 2022
b10dbaf
Update src/openzeppelin/account/library.cairo
JulissaDantes Jun 21, 2022
e5a2f97
Update src/openzeppelin/account/library.cairo
JulissaDantes Jun 21, 2022
e62486d
Update src/openzeppelin/account/library.cairo
JulissaDantes Jun 21, 2022
2880487
Update tests/utils.py
JulissaDantes Jun 21, 2022
9d47a8c
Update tests/utils.py
JulissaDantes Jun 21, 2022
c7c685f
Update tests/mocks/eth_account.cairo
JulissaDantes Jun 21, 2022
dd74a2b
Create eth account preset
JulissaDantes Jun 21, 2022
09faeb0
Merge branch 'eth-account' of https://github.com/JulissaDantes/cairo-…
JulissaDantes Jun 21, 2022
3b71400
Create signers module
JulissaDantes Jun 22, 2022
e868715
use assert_revert to test nonce
JulissaDantes Jun 22, 2022
69ed42b
Add test for valid signature
JulissaDantes Jun 23, 2022
586ba4f
use internal hash
JulissaDantes Jun 23, 2022
43dea0f
Update validity test
JulissaDantes Jun 23, 2022
7204bf6
Update docs/Account.md
JulissaDantes Jun 24, 2022
3572442
Update docs/Account.md
JulissaDantes Jun 24, 2022
c7b2f73
Update tests/signers.py
JulissaDantes Jun 24, 2022
f357988
Update src/openzeppelin/account/library.cairo
JulissaDantes Jun 24, 2022
e002076
Merge branch 'main' into eth-account
JulissaDantes Jun 24, 2022
0123dae
Fix after merge
JulissaDantes Jun 24, 2022
397756c
Improve tests
JulissaDantes Jun 24, 2022
042c4fd
Update account library
JulissaDantes Jun 24, 2022
0171e96
Update Account.md
JulissaDantes Jun 24, 2022
d9ab11e
update format
JulissaDantes Jun 24, 2022
3e8e82d
Update src/openzeppelin/account/library.cairo
JulissaDantes Jun 27, 2022
656d460
Update tests/access/test_Ownable.py
JulissaDantes Jun 27, 2022
a195736
Update src/openzeppelin/account/library.cairo
JulissaDantes Jun 27, 2022
8cc4308
Update eth test
JulissaDantes Jun 27, 2022
866254a
Update Account.md
JulissaDantes Jun 27, 2022
cfa1713
Update test
JulissaDantes Jun 27, 2022
9dc3c31
Update tests/signers.py
JulissaDantes Jun 27, 2022
7c9291b
Fix typo
JulissaDantes Jun 27, 2022
860f756
Merge branch 'eth-account' of https://github.com/JulissaDantes/cairo-…
JulissaDantes Jun 27, 2022
b73307a
Update signers
JulissaDantes Jun 27, 2022
0d0bc1a
Update test
JulissaDantes Jun 28, 2022
47b4785
Update docs/Account.md
JulissaDantes Jun 29, 2022
346cd79
Update docs/Account.md
JulissaDantes Jun 29, 2022
650e578
Update tests/signers.py
JulissaDantes Jun 29, 2022
fd745fa
Update docs/Account.md
JulissaDantes Jun 29, 2022
78a7b7b
Merge branch 'main' into eth-account
JulissaDantes Jun 29, 2022
668a25f
Merge branch 'eth-account' of https://github.com/JulissaDantes/cairo-…
JulissaDantes Jun 29, 2022
7d78282
update test
JulissaDantes Jun 29, 2022
4e583e5
Update documenation for Account
JulissaDantes Jun 29, 2022
4302136
Update docs/Account.md
martriay Jun 29, 2022
2c121ed
Update docs/Account.md
JulissaDantes Jun 29, 2022
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
51 changes: 50 additions & 1 deletion docs/Account.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,13 @@ If utilizing multicall, send multiple transactions with the `send_transactions`
)
```

### TestEthSigner utility

The `TestEthSigner` class in [utils.py](../tests/utils.py) is used to perform transactions on a given Account with a secp256k1 curve key pair, crafting the transaction and managing nonces. It differs from the `TestSigner` implementation by:
* not using the public key but its derived address instead (the last 20 bytes of the keccak256 hash of the public key and adding `0x` to the beginning)
* using keccak to hash transactions
* signing the message with a secp256k1 curve address

## Account entrypoint

`__execute__` acts as a single entrypoint for all user interaction with any contract, including managing the account contract itself. That's why if you want to change the public key controlling the Account, you would send a transaction targeting the very Account contract:
Expand Down Expand Up @@ -386,6 +393,49 @@ response_len: felt
response: felt*
```

### `is_eth_valid_signature`

Returns `TRUE` if a given signature in the secp256k1 curve is valid, otherwise it reverts. In the future it will return `FALSE` if a given signature is invalid (for more info please check [this issue](https://github.com/OpenZeppelin/cairo-contracts/issues/327)).

Parameters:

```cairo
hash: felt
JulissaDantes marked this conversation as resolved.
Show resolved Hide resolved
signature_len: felt
signature: felt*
```

Returns:

```cairo
is_valid: felt
```

> returns `TRUE` if a given signature is valid. Otherwise, reverts. In the future it will return `FALSE` if a given signature is invalid (for more info please check [this issue](https://github.com/OpenZeppelin/cairo-contracts/issues/327)).

### `eth_execute`

This follows the same idea as the vanilla version of `execute` with the sole difference that signature verification is on the secp256k1 curve.

Parameters:

```cairo
call_array_len: felt
call_array: AccountCallArray*
calldata_len: felt
calldata: felt*
nonce: felt
```

> Note that the current signature scheme expects a 7-element array like `[sig_v, uint256_sig_r_low, uint256_sig_r_high, uint256_sig_s_low, uint256_sig_s_high, uint256_hash_low, uint256_hash_high]` given that the parameters of the verification are bigger than a felt.

Returns:

```cairo
response_len: felt
response: felt*
```

## Account differentiation with ERC165

Certain contracts like ERC721 require a means to differentiate between account contracts and non-account contracts. For a contract to declare itself as an account, it should implement [ERC165](https://eips.ethereum.org/EIPS/eip-165) as proposed in [#100](https://github.com/OpenZeppelin/cairo-contracts/discussions/100). To be in compliance with ERC165 specifications, the idea is to calculate the XOR of `IAccount`'s EVM selectors (not StarkNet selectors). The resulting magic value of `IAccount` is 0x50b70dcb.
Expand All @@ -400,7 +450,6 @@ Currently, there's only a single library/preset Account scheme, but we're lookin

* multisig
* guardian logic like in [Argent's account](https://github.com/argentlabs/argent-contracts-starknet/blob/de5654555309fa76160ba3d7393d32d2b12e7349/contracts/ArgentAccount.cairo)
* [Ethereum signatures](https://github.com/OpenZeppelin/cairo-contracts/issues/161)
JulissaDantes marked this conversation as resolved.
Show resolved Hide resolved

## L1 escape hatch mechanism

Expand Down
5 changes: 3 additions & 2 deletions src/openzeppelin/account/Account.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

%lang starknet

from starkware.cairo.common.cairo_builtins import HashBuiltin, SignatureBuiltin
from starkware.cairo.common.cairo_builtins import HashBuiltin, SignatureBuiltin, BitwiseBuiltin

from openzeppelin.account.library import Account, AccountCallArray

Expand Down Expand Up @@ -95,7 +95,8 @@ func __execute__{
syscall_ptr : felt*,
pedersen_ptr : HashBuiltin*,
range_check_ptr,
ecdsa_ptr: SignatureBuiltin*
ecdsa_ptr: SignatureBuiltin*,
bitwise_ptr: BitwiseBuiltin*
JulissaDantes marked this conversation as resolved.
Show resolved Hide resolved
}(
call_array_len: felt,
call_array: AccountCallArray*,
Expand Down
128 changes: 106 additions & 22 deletions src/openzeppelin/account/library.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
from starkware.cairo.common.registers import get_fp_and_pc
from starkware.starknet.common.syscalls import get_contract_address
from starkware.cairo.common.signature import verify_ecdsa_signature
from starkware.cairo.common.cairo_builtins import HashBuiltin, SignatureBuiltin
from starkware.cairo.common.cairo_builtins import HashBuiltin, SignatureBuiltin, BitwiseBuiltin
from starkware.cairo.common.alloc import alloc
from starkware.cairo.common.uint256 import Uint256
from starkware.cairo.common.memcpy import memcpy
from starkware.cairo.common.bool import TRUE
from starkware.starknet.common.syscalls import call_contract, get_caller_address, get_tx_info

from starkware.cairo.common.cairo_secp.signature import verify_eth_signature_uint256
from openzeppelin.introspection.ERC165 import ERC165

from openzeppelin.utils.constants import IACCOUNT_ID
Expand Down Expand Up @@ -141,12 +142,47 @@ namespace Account:
return (is_valid=TRUE)
end

func is_valid_eth_signature{
syscall_ptr : felt*,
pedersen_ptr : HashBuiltin*,
range_check_ptr,
bitwise_ptr: BitwiseBuiltin*
JulissaDantes marked this conversation as resolved.
Show resolved Hide resolved
}(
signature_len: felt,
signature: felt*
) -> (is_valid: felt):
alloc_locals
let (_public_key) = get_public_key()
let (__fp__, _) = get_fp_and_pc()

# This interface expects a signature pointer and length to make
# no assumption about signature validation schemes.
# But this implementation does, and it expects a the sig_v, sig_r,
# sig_s, and hash elements.
let sig_v: felt = signature[0]
local sig_r : Uint256 = Uint256(low=signature[1], high=signature[2])
local sig_s : Uint256 = Uint256(low=signature[3], high=signature[4])
local msg_hash : Uint256 = Uint256(low=signature[5], high=signature[6])
JulissaDantes marked this conversation as resolved.
Show resolved Hide resolved

let (local keccak_ptr : felt*) = alloc()

with keccak_ptr:
verify_eth_signature_uint256(
msg_hash=msg_hash,
r=sig_r,
s=sig_s,
v=sig_v,
eth_address=_public_key)
end

return (is_valid=TRUE)
end

func execute{
syscall_ptr : felt*,
pedersen_ptr : HashBuiltin*,
range_check_ptr,
ecdsa_ptr: SignatureBuiltin*
bitwise_ptr: BitwiseBuiltin*
}(
call_array_len: felt,
call_array: AccountCallArray*,
Expand All @@ -163,32 +199,47 @@ namespace Account:

let (__fp__, _) = get_fp_and_pc()
let (tx_info) = get_tx_info()
let (_current_nonce) = Account_current_nonce.read()
let (local ecdsa_ptr : SignatureBuiltin*) = alloc()
with ecdsa_ptr:
# validate transaction
let (is_valid) = is_valid_signature(tx_info.transaction_hash, tx_info.signature_len, tx_info.signature)
JulissaDantes marked this conversation as resolved.
Show resolved Hide resolved
with_attr error_message("Account: invalid signature"):
assert is_valid = TRUE
end
end

return unsafe_execute(call_array_len, call_array, calldata_len, calldata, nonce)
end

# validate nonce
with_attr error_message("Account: nonce is invalid"):
assert _current_nonce = nonce
func eth_execute{
syscall_ptr : felt*,
pedersen_ptr : HashBuiltin*,
range_check_ptr,
bitwise_ptr: BitwiseBuiltin*
}(
call_array_len: felt,
call_array: AccountCallArray*,
calldata_len: felt,
calldata: felt*,
nonce: felt
) -> (response_len: felt, response: felt*):
alloc_locals

let (caller) = get_caller_address()
with_attr error_message("Account: no reentrant call"):
assert caller = 0
JulissaDantes marked this conversation as resolved.
Show resolved Hide resolved
end

# TMP: Convert `AccountCallArray` to 'Call'.
let (calls : Call*) = alloc()
_from_call_array_to_call(call_array_len, call_array, calldata, calls)
let calls_len = call_array_len
let (__fp__, _) = get_fp_and_pc()
let (tx_info) = get_tx_info()

# validate transaction
let (is_valid) = is_valid_signature(tx_info.transaction_hash, tx_info.signature_len, tx_info.signature)
# validate transaction
let (is_valid) = is_valid_eth_signature(tx_info.signature_len, tx_info.signature)
with_attr error_message("Account: invalid signature"):
JulissaDantes marked this conversation as resolved.
Show resolved Hide resolved
assert is_valid = TRUE
end

# bump nonce
Account_current_nonce.write(_current_nonce + 1)

# execute call
let (response : felt*) = alloc()
let (response_len) = _execute_list(calls_len, calls, response)

return (response_len=response_len, response=response)

return unsafe_execute(call_array_len, call_array, calldata_len, calldata, nonce)
end

func _execute_list{syscall_ptr: felt*}(
Expand Down Expand Up @@ -240,4 +291,37 @@ namespace Account:
_from_call_array_to_call(call_array_len - 1, call_array + AccountCallArray.SIZE, calldata, calls + Call.SIZE)
return ()
end

func unsafe_execute{
JulissaDantes marked this conversation as resolved.
Show resolved Hide resolved
syscall_ptr : felt*,
pedersen_ptr : HashBuiltin*,
range_check_ptr,
bitwise_ptr: BitwiseBuiltin*
}(
call_array_len: felt,
call_array: AccountCallArray*,
calldata_len: felt,
calldata: felt*,
nonce: felt
) -> (response_len: felt, response: felt*):
alloc_locals
let (_current_nonce) = Account_current_nonce.read()
# validate nonce
with_attr error_message("Account: nonce is invalid"):
JulissaDantes marked this conversation as resolved.
Show resolved Hide resolved
assert _current_nonce = nonce
end
# bump nonce
Account_current_nonce.write(_current_nonce + 1)
JulissaDantes marked this conversation as resolved.
Show resolved Hide resolved

# TMP: Convert `AccountCallArray` to 'Call'.
let (calls : Call*) = alloc()
_from_call_array_to_call(call_array_len, call_array, calldata, calls)
let calls_len = call_array_len

# execute call
let (response : felt*) = alloc()
let (response_len) = _execute_list(calls_len, calls, response)

return (response_len=response_len, response=response)
end
end
Loading