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

Validate new public key in Account #989

Merged
Merged
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed (Breaking)

- ERC721Component internal implementation to support transfer, mint, and burn flows going through an `_update` function (#978)
- ERC721Component implementations now require an ERC721HooksTrait implementation in scope.
- ERC1155Component implementations now require an ERC1155HooksTrait implementation in scope.
- ERC721Component implementations now require an ERC721HooksTrait implementation in scope (#978)
- ERC1155Component implementations now require an ERC1155HooksTrait implementation in scope (#982)
- AccountComponent, preset, and dispatcher now require a `signature` param in the public-key-setter functions (#989)

## 0.12.0 (2024-04-21)

Expand Down
41 changes: 37 additions & 4 deletions docs/modules/ROOT/pages/api/account.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ NOTE: {src5-component-required-note}
.PublicKeyImpl

* xref:#AccountComponent-get_public_key[`++get_public_key(self)++`]
* xref:#AccountComponent-set_public_key[`++set_public_key(self, new_public_key)++`]
* xref:#AccountComponent-set_public_key[`++set_public_key(self, new_public_key, signature)++`]

[.sub-index#AccountComponent-Embeddable-Impls-SRC6CamelOnlyImpl]
.SRC6CamelOnlyImpl
Expand All @@ -128,7 +128,7 @@ NOTE: {src5-component-required-note}
.PublicKeyCamelImpl

* xref:#AccountComponent-getPublicKey[`++getPublicKey(self)++`]
* xref:#AccountComponent-setPublicKey[`++setPublicKey(self, newPublicKey)++`]
* xref:#AccountComponent-setPublicKey[`++setPublicKey(self, newPublicKey, signature)++`]

.SRC5Impl
* xref:api/introspection.adoc#ISRC5-supports_interface[`supports_interface(self, interface_id: felt252)`]
Expand All @@ -141,6 +141,7 @@ NOTE: {src5-component-required-note}

* xref:#AccountComponent-initializer[`++initializer(self, public_key)++`]
* xref:#AccountComponent-assert_only_self[`++assert_only_self(self)++`]
* xref:#AccountComponent-assert_valid_new_owner[`++assert_valid_new_owner(self, current_owner, new_owner, signature)++`]
* xref:#AccountComponent-validate_transaction[`++validate_transaction(self)++`]
* xref:#AccountComponent-_set_public_key[`++_set_public_key(self, new_public_key)++`]
* xref:#AccountComponent-_is_valid_signature[`++_is_valid_signature(self, hash, signature)++`]
Expand Down Expand Up @@ -199,12 +200,31 @@ Returns the current public key of the account.

[.contract-item]
[[AccountComponent-set_public_key]]
==== `[.contract-item-name]#++set_public_key++#++(ref self: ContractState, new_public_key: felt252)++` [.item-kind]#external#
==== `[.contract-item-name]#++set_public_key++#++(ref self: ContractState, new_public_key: felt252, signature: Span<felt252>)++` [.item-kind]#external#

Sets a new public key for the account. Only accessible by the account calling itself through `\\__execute__`.

Requirements:

- The caller must be the contract itself.
- The signature must be valid for the new owner.

Emits both an {OwnerRemoved} and an {OwnerAdded} event.

[NOTE]
====
The message to be signed is computed in Cairo as follows:

```javascript
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm good call with using js. The Cairo highlighting looks a little bland here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, we should at some point improve the highlights extension.

let message_hash = PoseidonTrait::new()
.update('StarkNet Message')
.update('accept_ownership')
.update(get_contract_address().into())
.update(current_owner)
.finalize();
```
====

[.contract-item]
[[AccountComponent-isValidSignature]]
==== `[.contract-item-name]#++isValidSignature++#++(self: @ContractState, hash: felt252, signature: Array<felt252>) → felt252++` [.item-kind]#external#
Expand All @@ -219,7 +239,7 @@ See xref:AccountComponent-get_public_key[get_public_key].

[.contract-item]
[[AccountComponent-setPublicKey]]
==== `[.contract-item-name]#++setPublicKey++#++(ref self: ContractState, newPublicKey: felt252)++` [.item-kind]#external#
==== `[.contract-item-name]#++setPublicKey++#++(ref self: ContractState, newPublicKey: felt252, signature: Span<felt252>)++` [.item-kind]#external#

See xref:AccountComponent-set_public_key[set_public_key].

Expand All @@ -240,6 +260,19 @@ Emits an {OwnerAdded} event.

Validates that the caller is the account itself. Otherwise it reverts.

[.contract-item]
[[AccountComponent-assert_valid_new_owner]]
==== `[.contract-item-name]#++assert_valid_new_owner++#++(self: @ComponentState, current_owner: felt252, new_owner: felt252, signature: Span<felt252>)++` [.item-kind]#internal#

Validates that new owner accepted the ownership of the contract through a signature.
ericnordelo marked this conversation as resolved.
Show resolved Hide resolved

Requirements:

- The signature must be valid for the new owner.
ericnordelo marked this conversation as resolved.
Show resolved Hide resolved

WARNING: This function assumes that current_owner is the current owner of the contract, and
ericnordelo marked this conversation as resolved.
Show resolved Hide resolved
does not validate this assumption.

[.contract-item]
[[AccountComponent-validate_transaction]]
==== `[.contract-item-name]#++validate_transaction++#++(self: @ComponentState)++ → felt252` [.item-kind]#internal#
Expand Down
67 changes: 58 additions & 9 deletions src/account/account.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
/// The Account component enables contracts to behave as accounts.
#[starknet::component]
mod AccountComponent {
use core::hash::{HashStateExTrait, HashStateTrait};
use core::traits::Into;
use openzeppelin::account::interface;
use openzeppelin::account::utils::{MIN_TRANSACTION_VERSION, QUERY_VERSION, QUERY_OFFSET};
use openzeppelin::account::utils::{execute_calls, is_valid_stark_signature};
use openzeppelin::introspection::src5::SRC5Component::InternalTrait as SRC5InternalTrait;
use openzeppelin::introspection::src5::SRC5Component::SRC5;
use openzeppelin::introspection::src5::SRC5Component;
use poseidon::PoseidonTrait;
use starknet::account::Call;
use starknet::get_caller_address;
use starknet::get_contract_address;
Expand Down Expand Up @@ -155,11 +158,20 @@ mod AccountComponent {
/// Requirements:
///
/// - The caller must be the contract itself.
/// - The signature must be valid for the new owner.
///
/// Emits an `OwnerRemoved` event.
fn set_public_key(ref self: ComponentState<TContractState>, new_public_key: felt252) {
/// Emits both an `OwnerRemoved` and an `OwnerAdded` event.
fn set_public_key(
ref self: ComponentState<TContractState>,
new_public_key: felt252,
signature: Span<felt252>
) {
self.assert_only_self();
self.emit(OwnerRemoved { removed_owner_guid: self.Account_public_key.read() });

let current_owner = self.Account_public_key.read();
self.assert_valid_new_owner(current_owner, new_public_key, signature);

self.emit(OwnerRemoved { removed_owner_guid: current_owner });
self._set_public_key(new_public_key);
}
}
Expand Down Expand Up @@ -191,8 +203,12 @@ mod AccountComponent {
self.Account_public_key.read()
}

fn setPublicKey(ref self: ComponentState<TContractState>, newPublicKey: felt252) {
PublicKey::set_public_key(ref self, newPublicKey);
fn setPublicKey(
ref self: ComponentState<TContractState>,
newPublicKey: felt252,
signature: Span<felt252>
) {
PublicKey::set_public_key(ref self, newPublicKey, signature);
}
}

Expand All @@ -218,6 +234,31 @@ mod AccountComponent {
assert(self == caller, Errors::UNAUTHORIZED);
}

/// Validates that new owner accepted the ownership of the contract.
ericnordelo marked this conversation as resolved.
Show resolved Hide resolved
///
/// WARNING: This function assumes that current_owner is the current owner of the contract, and
ericnordelo marked this conversation as resolved.
Show resolved Hide resolved
/// does not validate this assumption.
///
/// Requirements:
///
/// - The signature must be valid for the new owner.
fn assert_valid_new_owner(
self: @ComponentState<TContractState>,
current_owner: felt252,
new_owner: felt252,
signature: Span<felt252>
) {
let message_hash = PoseidonTrait::new()
.update('StarkNet Message')
.update('accept_ownership')
.update(get_contract_address().into())
.update(current_owner)
.finalize();

let is_valid = is_valid_stark_signature(message_hash, new_owner, signature);
assert(is_valid, Errors::INVALID_SIGNATURE);
}

/// Validates the signature for the current transaction.
/// Returns the short string `VALID` if valid, otherwise it reverts.
fn validate_transaction(self: @ComponentState<TContractState>) -> felt252 {
Expand Down Expand Up @@ -300,17 +341,25 @@ mod AccountComponent {
PublicKey::get_public_key(self)
}

fn set_public_key(ref self: ComponentState<TContractState>, new_public_key: felt252) {
PublicKey::set_public_key(ref self, new_public_key);
fn set_public_key(
ref self: ComponentState<TContractState>,
new_public_key: felt252,
signature: Span<felt252>
) {
PublicKey::set_public_key(ref self, new_public_key, signature);
}

// IPublicKeyCamel
fn getPublicKey(self: @ComponentState<TContractState>) -> felt252 {
PublicKeyCamel::getPublicKey(self)
}

fn setPublicKey(ref self: ComponentState<TContractState>, newPublicKey: felt252) {
PublicKeyCamel::setPublicKey(ref self, newPublicKey);
fn setPublicKey(
ref self: ComponentState<TContractState>,
newPublicKey: felt252,
signature: Span<felt252>
) {
PublicKeyCamel::setPublicKey(ref self, newPublicKey, signature);
}

// ISRC5
Expand Down
7 changes: 4 additions & 3 deletions src/account/dual_account.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ struct DualCaseAccount {
}

trait DualCaseAccountABI {
fn set_public_key(self: @DualCaseAccount, new_public_key: felt252);
fn set_public_key(self: @DualCaseAccount, new_public_key: felt252, signature: Span<felt252>);
fn get_public_key(self: @DualCaseAccount) -> felt252;
fn is_valid_signature(
self: @DualCaseAccount, hash: felt252, signature: Array<felt252>
Expand All @@ -24,8 +24,9 @@ trait DualCaseAccountABI {
}

impl DualCaseAccountImpl of DualCaseAccountABI {
fn set_public_key(self: @DualCaseAccount, new_public_key: felt252) {
let args = array![new_public_key];
fn set_public_key(self: @DualCaseAccount, new_public_key: felt252, signature: Span<felt252>) {
let mut args = array![new_public_key];
args.append_serde(signature);

try_selector_with_fallback(
*self.contract_address, selectors::set_public_key, selectors::setPublicKey, args.span()
Expand Down
8 changes: 4 additions & 4 deletions src/account/interface.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ trait IDeployable<TState> {
#[starknet::interface]
trait IPublicKey<TState> {
fn get_public_key(self: @TState) -> felt252;
fn set_public_key(ref self: TState, new_public_key: felt252);
fn set_public_key(ref self: TState, new_public_key: felt252, signature: Span<felt252>);
}

#[starknet::interface]
Expand All @@ -46,7 +46,7 @@ trait ISRC6CamelOnly<TState> {
#[starknet::interface]
trait IPublicKeyCamel<TState> {
fn getPublicKey(self: @TState) -> felt252;
fn setPublicKey(ref self: TState, newPublicKey: felt252);
fn setPublicKey(ref self: TState, newPublicKey: felt252, signature: Span<felt252>);
}

//
Expand All @@ -73,14 +73,14 @@ trait AccountABI<TState> {

// IPublicKey
fn get_public_key(self: @TState) -> felt252;
fn set_public_key(ref self: TState, new_public_key: felt252);
fn set_public_key(ref self: TState, new_public_key: felt252, signature: Span<felt252>);

// ISRC6CamelOnly
fn isValidSignature(self: @TState, hash: felt252, signature: Array<felt252>) -> felt252;

// IPublicKeyCamel
fn getPublicKey(self: @TState) -> felt252;
fn setPublicKey(ref self: TState, newPublicKey: felt252);
fn setPublicKey(ref self: TState, newPublicKey: felt252, signature: Span<felt252>);
}

//
Expand Down
4 changes: 2 additions & 2 deletions src/presets/interfaces/account.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ trait AccountUpgradeableABI<TState> {

// IPublicKey
fn get_public_key(self: @TState) -> felt252;
fn set_public_key(ref self: TState, new_public_key: felt252);
fn set_public_key(ref self: TState, new_public_key: felt252, signature: Span<felt252>);

// ISRC6CamelOnly
fn isValidSignature(self: @TState, hash: felt252, signature: Array<felt252>) -> felt252;

// IPublicKeyCamel
fn getPublicKey(self: @TState) -> felt252;
fn setPublicKey(ref self: TState, newPublicKey: felt252);
fn setPublicKey(ref self: TState, newPublicKey: felt252, signature: Span<felt252>);

// IUpgradeable
fn upgrade(ref self: TState, new_class_hash: ClassHash);
Expand Down
Loading
Loading