From f991163ec36c6860cb022bf4eda0bc7aa11709e7 Mon Sep 17 00:00:00 2001 From: OkoliEvans Date: Sun, 30 Jun 2024 12:16:29 +0100 Subject: [PATCH 01/10] draft PR for issue 191 --- Scarb.lock | 4 + .../account_abstraction/.gitignore | 1 + .../account_abstraction/Scarb.toml | 14 ++ .../src/account_abstraction.cairo | 133 ++++++++++++++++++ .../account_abstraction/src/lib.cairo | 1 + src/advanced-concepts/account-abstraction.md | 40 ++++++ 6 files changed, 193 insertions(+) create mode 100644 listings/advanced-concepts/account_abstraction/.gitignore create mode 100644 listings/advanced-concepts/account_abstraction/Scarb.toml create mode 100644 listings/advanced-concepts/account_abstraction/src/account_abstraction.cairo create mode 100644 listings/advanced-concepts/account_abstraction/src/lib.cairo create mode 100644 src/advanced-concepts/account-abstraction.md diff --git a/Scarb.lock b/Scarb.lock index a055e8a9..c48cd663 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -1,6 +1,10 @@ # Code generated by scarb DO NOT EDIT. version = 1 +[[package]] +name = "account_abstraction" +version = "0.1.0" + [[package]] name = "advanced_factory" version = "0.1.0" diff --git a/listings/advanced-concepts/account_abstraction/.gitignore b/listings/advanced-concepts/account_abstraction/.gitignore new file mode 100644 index 00000000..eb5a316c --- /dev/null +++ b/listings/advanced-concepts/account_abstraction/.gitignore @@ -0,0 +1 @@ +target diff --git a/listings/advanced-concepts/account_abstraction/Scarb.toml b/listings/advanced-concepts/account_abstraction/Scarb.toml new file mode 100644 index 00000000..83a29d49 --- /dev/null +++ b/listings/advanced-concepts/account_abstraction/Scarb.toml @@ -0,0 +1,14 @@ +[package] +name = "account_abstraction" +version = "0.1.0" +edition = "2023_11" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[dependencies] +starknet.workspace = true + +[scripts] +test.workspace = true + +[[target.starknet-contract]] diff --git a/listings/advanced-concepts/account_abstraction/src/account_abstraction.cairo b/listings/advanced-concepts/account_abstraction/src/account_abstraction.cairo new file mode 100644 index 00000000..ae338cd1 --- /dev/null +++ b/listings/advanced-concepts/account_abstraction/src/account_abstraction.cairo @@ -0,0 +1,133 @@ +use starknet::secp256_trait::{ + Secp256PointTrait, Signature as Secp256Signature, recover_public_key, is_signature_entry_valid +}; +use starknet::secp256r1::Secp256r1Point; +use starknet::secp256k1::Secp256k1Point; +use starknet::{ EthAddress, eth_signature::is_eth_signature_valid }; +use core::traits::TryInto; + + +const SECP256R1_SIGNER_TYPE: felt252 = 'Secp256r1 Signer'; +const SECP256K1_SIGNER_TYPE: felt252 = 'Secp256k1 Signer'; +const SECP_256_R1_HALF: u256 = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551 / 2; +const SECP_256_K1_HALF: u256 = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 / 2; + + + +#[derive(Drop, Copy, PartialEq, Serde, Default)] +enum SignerType { + #[default] + Secp256r1, + Secp256k1, +} + +#[derive(Drop, Copy, Serde)] +enum SignerSignature { + Secp256r1: (Secp256r1Signer, Secp256Signature), + Secp256k1: (Secp256k1Signer, Secp256Signature), +} + +#[derive(Drop, Copy, Serde)] +enum Signer { + Secp256r1: Secp256r1Signer, + Secp256k1: Secp256k1Signer, +} + +#[derive(Drop, Copy, Serde, PartialEq)] +struct Secp256r1Signer { + pubkey: NonZero +} + +#[derive(Drop, Copy, PartialEq)] +struct Secp256k1Signer { + pubkey_hash: EthAddress +} + + +// To ensure the pubkey hash is not zero +impl Secp256k1SignerSerde of Serde { + #[inline(always)] + fn serialize(self: @Secp256k1Signer, ref output: Array) { + self.pubkey_hash.serialize(ref output); + } + + #[inline(always)] + fn deserialize(ref serialized: Span) -> Option { + let pubkey_hash = Serde::::deserialize(ref serialized)?; + assert(pubkey_hash.address != 0, 'zero pub key hash' ); + Option::Some(Secp256k1Signer { pubkey_hash }) + } +} + +// To check if secp256k1 and secp256r1 signatures are valid +trait Secp256SignatureTrait { + fn is_valid_signature(self: SignerSignature, hash: felt252) -> bool; + fn signer(self: SignerSignature) -> Signer; +} + +impl Secp256SignatureImpl of Secp256SignatureTrait { + #[inline(always)] + fn is_valid_signature(self: SignerSignature, hash: felt252) -> bool { + match self { + SignerSignature::Secp256r1(( + signer, signature + )) => is_valid_secp256r1_signature(hash.into(), signer, signature), + SignerSignature::Secp256k1(( + signer, signature + )) => is_valid_secp256k1_signature(hash.into(), signer.pubkey_hash.into(), signature), + } + } + + #[inline(always)] + fn signer(self: SignerSignature) -> Signer { + match self { + SignerSignature::Secp256k1((signer, _)) => Signer::Secp256k1(signer), + SignerSignature::Secp256r1((signer, _)) => Signer::Secp256r1(signer), + } + } +} + +// To validate secp256k1 signature +#[inline(always)] +fn is_valid_secp256k1_signature(hash: u256, pubkey_hash: EthAddress, signature: Secp256Signature) -> bool { + assert(signature.s <= SECP_256_K1_HALF, 'malleable signature'); + is_eth_signature_valid(hash, signature, pubkey_hash).is_ok() +} + +// To validate secp256r1 signature +#[inline(always)] +fn is_valid_secp256r1_signature(hash: u256, signer: Secp256r1Signer, signature: Secp256Signature) -> bool { + assert(is_signature_entry_valid::(signature.s), 'invalid s-value'); + assert(is_signature_entry_valid::(signature.r), 'invalid r-value'); + assert(signature.s <= SECP_256_R1_HALF, 'malleable signature'); + let recovered_pubkey = recover_public_key::(hash, signature).expect('invalid sign format'); + let (recovered_signer, _) = recovered_pubkey.get_coordinates().expect('invalid sig format'); + recovered_signer == signer.pubkey.into() +} + +// impl to convert signer type into felt252 using into() +impl SignerTypeIntoFelt252 of Into { + #[inline(always)] + fn into(self: SignerType) -> felt252 { + match self { + SignerType::Secp256k1 => 1, + SignerType::Secp256r1 => 2, + } + } +} + + +// impl to convert u256 type into SignerType using try_into() +impl U256TryIntoSignerType of TryInto { + #[inline(always)] + fn try_into(self: u256) -> Option { + if self == 1 { + Option::Some(SignerType::Secp256k1) + } else if self == 2 { + Option::Some(SignerType::Secp256r1) + } else { + Option::None + } + } +} + diff --git a/listings/advanced-concepts/account_abstraction/src/lib.cairo b/listings/advanced-concepts/account_abstraction/src/lib.cairo new file mode 100644 index 00000000..50d5caab --- /dev/null +++ b/listings/advanced-concepts/account_abstraction/src/lib.cairo @@ -0,0 +1 @@ +mod account_abstraction; \ No newline at end of file diff --git a/src/advanced-concepts/account-abstraction.md b/src/advanced-concepts/account-abstraction.md new file mode 100644 index 00000000..3c33100f --- /dev/null +++ b/src/advanced-concepts/account-abstraction.md @@ -0,0 +1,40 @@ +# Account Abstraction + +### Understanding Ethereum Account System + +Traditionally, there are two types of account on Ethereum: Externally Owned Accounts known as EOAs and smart contract Accounts. + +EOAs are normal accounts used by individuals, they have private keys and can sign transactions. Smart contract accounts do not have private keys, therefore they cannot initiate or sign transactions. All transactions on Ethereum must be initiated by an EOA. + +Ethereum accounts have many challenges such as: + +i. Key Management: Users must secure and never lose their seed phrase and private keys, otherwise they risk losing access to their accounts and assets forever. + +Also, once a thief gains access to your private keys or seed phrase, he gains complete access to your account and its assets. + +ii. Bad User Experience: The account system used on Ethereum makes it difficult for newbies to use crypto applications as they are always complicated to use. +Lack of account recovery options also makes it unappealing to users. + +iii. Lack of Flexibility: Ethereum account system restricts custom transaction validation schemes, limiting potential enhancements on access control and security. + + +### What is Account Abstraction? + +Account Abstraction is the concept of modifying accounts and enhancing transactions on blockchain networks. +Account Abstraction replaces EOAs with a broader model where all accounts are smart contracts, each with its own unique rules and behaviors. + +Account Abstraction makes it possible for innovative account management system such as custom signature types, session keys, 2 Factor Authentication (2FA), fingerprint or facial recognition account signing, ability to pay for gas using other tokens such as USDT, ability to recover accounts without seed phrase, email recovery of accounts and so on. + +### The Most Important Concepts + +i. **Account:** All accounts are smart contracts that can hold assets and execute transactions on Starknet protocol, these account contracts however must implement some specific methods outlined in SNIP-6. + +ii. **Signers:** These are responsible for digitally signing transactions and provide the authorization needed to initiate transactions. +Digital signatures are cryptographic proofs that transactions are authorized by corresponding accounts. + +In summary, Starknet accounts are normal blockchain accounts that hold assets and initiate transactions onchain, while signers provide the authorization required to ensure that transactions originating from these accounts are secure, valid and executed. + +To implement custom validation method on Starknet you have to ensure that the contract contains these three methods: `is_valid_signature`, `__validate__` and `__execute__`. These are the building block for account contracts on Starknet as contained in the SNIP-6. + + + From 60e7a6e7ce0c7c3b8164e1cb73b687498fded5fa Mon Sep 17 00:00:00 2001 From: OkoliEvans Date: Tue, 16 Jul 2024 12:52:26 +0100 Subject: [PATCH 02/10] edited custom-signature.md file --- Scarb.lock | 8 ++-- .../account_abstraction/src/lib.cairo | 1 - .../.gitignore | 0 .../Scarb.toml | 2 +- .../src/custom_signature.cairo} | 0 .../custom_signature_validation/src/lib.cairo | 1 + src/advanced-concepts/account-abstraction.md | 40 ------------------- .../custom_signature_validation.md | 17 ++++++++ 8 files changed, 23 insertions(+), 46 deletions(-) delete mode 100644 listings/advanced-concepts/account_abstraction/src/lib.cairo rename listings/advanced-concepts/{account_abstraction => custom_signature_validation}/.gitignore (100%) rename listings/advanced-concepts/{account_abstraction => custom_signature_validation}/Scarb.toml (87%) rename listings/advanced-concepts/{account_abstraction/src/account_abstraction.cairo => custom_signature_validation/src/custom_signature.cairo} (100%) create mode 100644 listings/advanced-concepts/custom_signature_validation/src/lib.cairo delete mode 100644 src/advanced-concepts/account-abstraction.md create mode 100644 src/advanced-concepts/custom_signature_validation.md diff --git a/Scarb.lock b/Scarb.lock index c48cd663..4499ad1c 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -1,10 +1,6 @@ # Code generated by scarb DO NOT EDIT. version = 1 -[[package]] -name = "account_abstraction" -version = "0.1.0" - [[package]] name = "advanced_factory" version = "0.1.0" @@ -67,6 +63,10 @@ dependencies = [ "snforge_std", ] +[[package]] +name = "custom_signature_validation" +version = "0.1.0" + [[package]] name = "custom_type_serde" version = "0.1.0" diff --git a/listings/advanced-concepts/account_abstraction/src/lib.cairo b/listings/advanced-concepts/account_abstraction/src/lib.cairo deleted file mode 100644 index 50d5caab..00000000 --- a/listings/advanced-concepts/account_abstraction/src/lib.cairo +++ /dev/null @@ -1 +0,0 @@ -mod account_abstraction; \ No newline at end of file diff --git a/listings/advanced-concepts/account_abstraction/.gitignore b/listings/advanced-concepts/custom_signature_validation/.gitignore similarity index 100% rename from listings/advanced-concepts/account_abstraction/.gitignore rename to listings/advanced-concepts/custom_signature_validation/.gitignore diff --git a/listings/advanced-concepts/account_abstraction/Scarb.toml b/listings/advanced-concepts/custom_signature_validation/Scarb.toml similarity index 87% rename from listings/advanced-concepts/account_abstraction/Scarb.toml rename to listings/advanced-concepts/custom_signature_validation/Scarb.toml index 83a29d49..3b7e2452 100644 --- a/listings/advanced-concepts/account_abstraction/Scarb.toml +++ b/listings/advanced-concepts/custom_signature_validation/Scarb.toml @@ -1,5 +1,5 @@ [package] -name = "account_abstraction" +name = "custom_signature_validation" version = "0.1.0" edition = "2023_11" diff --git a/listings/advanced-concepts/account_abstraction/src/account_abstraction.cairo b/listings/advanced-concepts/custom_signature_validation/src/custom_signature.cairo similarity index 100% rename from listings/advanced-concepts/account_abstraction/src/account_abstraction.cairo rename to listings/advanced-concepts/custom_signature_validation/src/custom_signature.cairo diff --git a/listings/advanced-concepts/custom_signature_validation/src/lib.cairo b/listings/advanced-concepts/custom_signature_validation/src/lib.cairo new file mode 100644 index 00000000..1e39ca20 --- /dev/null +++ b/listings/advanced-concepts/custom_signature_validation/src/lib.cairo @@ -0,0 +1 @@ +mod custom_signature; \ No newline at end of file diff --git a/src/advanced-concepts/account-abstraction.md b/src/advanced-concepts/account-abstraction.md deleted file mode 100644 index 3c33100f..00000000 --- a/src/advanced-concepts/account-abstraction.md +++ /dev/null @@ -1,40 +0,0 @@ -# Account Abstraction - -### Understanding Ethereum Account System - -Traditionally, there are two types of account on Ethereum: Externally Owned Accounts known as EOAs and smart contract Accounts. - -EOAs are normal accounts used by individuals, they have private keys and can sign transactions. Smart contract accounts do not have private keys, therefore they cannot initiate or sign transactions. All transactions on Ethereum must be initiated by an EOA. - -Ethereum accounts have many challenges such as: - -i. Key Management: Users must secure and never lose their seed phrase and private keys, otherwise they risk losing access to their accounts and assets forever. - -Also, once a thief gains access to your private keys or seed phrase, he gains complete access to your account and its assets. - -ii. Bad User Experience: The account system used on Ethereum makes it difficult for newbies to use crypto applications as they are always complicated to use. -Lack of account recovery options also makes it unappealing to users. - -iii. Lack of Flexibility: Ethereum account system restricts custom transaction validation schemes, limiting potential enhancements on access control and security. - - -### What is Account Abstraction? - -Account Abstraction is the concept of modifying accounts and enhancing transactions on blockchain networks. -Account Abstraction replaces EOAs with a broader model where all accounts are smart contracts, each with its own unique rules and behaviors. - -Account Abstraction makes it possible for innovative account management system such as custom signature types, session keys, 2 Factor Authentication (2FA), fingerprint or facial recognition account signing, ability to pay for gas using other tokens such as USDT, ability to recover accounts without seed phrase, email recovery of accounts and so on. - -### The Most Important Concepts - -i. **Account:** All accounts are smart contracts that can hold assets and execute transactions on Starknet protocol, these account contracts however must implement some specific methods outlined in SNIP-6. - -ii. **Signers:** These are responsible for digitally signing transactions and provide the authorization needed to initiate transactions. -Digital signatures are cryptographic proofs that transactions are authorized by corresponding accounts. - -In summary, Starknet accounts are normal blockchain accounts that hold assets and initiate transactions onchain, while signers provide the authorization required to ensure that transactions originating from these accounts are secure, valid and executed. - -To implement custom validation method on Starknet you have to ensure that the contract contains these three methods: `is_valid_signature`, `__validate__` and `__execute__`. These are the building block for account contracts on Starknet as contained in the SNIP-6. - - - diff --git a/src/advanced-concepts/custom_signature_validation.md b/src/advanced-concepts/custom_signature_validation.md new file mode 100644 index 00000000..ae9cdf0b --- /dev/null +++ b/src/advanced-concepts/custom_signature_validation.md @@ -0,0 +1,17 @@ +# Custom Signature Validation Scheme + +Account Abstraction on Starknet supports various signature schemes. This means that signature schemes on Starknet are not limited to just one, any standard signature scheme can be validated, for example Starknet signature, Secp256k1, Secp256r1, Eip191 et al are some of the custom signatures that can be validated on Starknet currently. + +### The Concepts of Accounts and Signers + +i. **Account:** All accounts are smart contracts that can hold assets and execute transactions on Starknet protocol, these account contracts however must implement some specific methods outlined in SNIP-6. + +ii. **Signers:** These are responsible for digitally signing transactions and provide the authorization needed to initiate transactions. +Digital signatures are cryptographic proofs that transactions are authorized by corresponding accounts. + +In summary, Starknet accounts are normal blockchain accounts that hold assets and initiate transactions onchain, while signers provide the authorization required to ensure that transactions originating from these accounts are secure, valid and executed. + +To implement custom validation method on Starknet you have to ensure that the contract contains these three methods: `is_valid_signature`, `__validate__` and `__execute__`. These are the building block for account contracts on Starknet as contained in the SNIP-6. + + + From cd066bb24ec6f48de7a7b4b51d71af4c1630509d Mon Sep 17 00:00:00 2001 From: OkoliEvans Date: Tue, 16 Jul 2024 12:55:00 +0100 Subject: [PATCH 03/10] edited custom-signature.md file --- Scarb.lock | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Scarb.lock b/Scarb.lock index 7509ed71..4084bbd1 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -135,6 +135,10 @@ dependencies = [ "openzeppelin", ] +[[package]] +name = "simple_storage" +version = "0.1.0" + [[package]] name = "simple_vault" version = "0.1.0" From cbbb89fed8ef09adb34534d02b88592f583ac363 Mon Sep 17 00:00:00 2001 From: OkoliEvans Date: Sun, 21 Jul 2024 13:12:27 +0100 Subject: [PATCH 04/10] added more details to custom signature scheme --- .../src/custom_signature.cairo | 2 ++ .../custom_signature_validation.md | 29 ++++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/listings/advanced-concepts/custom_signature_validation/src/custom_signature.cairo b/listings/advanced-concepts/custom_signature_validation/src/custom_signature.cairo index ae338cd1..bc61e9ae 100644 --- a/listings/advanced-concepts/custom_signature_validation/src/custom_signature.cairo +++ b/listings/advanced-concepts/custom_signature_validation/src/custom_signature.cairo @@ -1,3 +1,4 @@ +// ANCHOR: custom_signature_scheme use starknet::secp256_trait::{ Secp256PointTrait, Signature as Secp256Signature, recover_public_key, is_signature_entry_valid }; @@ -131,3 +132,4 @@ impl U256TryIntoSignerType of TryInto { } } +// ANCHOR_END: custom_signature_scheme \ No newline at end of file diff --git a/src/advanced-concepts/custom_signature_validation.md b/src/advanced-concepts/custom_signature_validation.md index ae9cdf0b..7b8d4e88 100644 --- a/src/advanced-concepts/custom_signature_validation.md +++ b/src/advanced-concepts/custom_signature_validation.md @@ -1,17 +1,38 @@ # Custom Signature Validation Scheme -Account Abstraction on Starknet supports various signature schemes. This means that signature schemes on Starknet are not limited to just one, any standard signature scheme can be validated, for example Starknet signature, Secp256k1, Secp256r1, Eip191 et al are some of the custom signatures that can be validated on Starknet currently. +Account Abstraction is a native feature on Starknet, this makes it possible for anyone to implement custom signature schemes. The implication is that signature schemes on Starknet are not limited to just one, any standard signature scheme can be validated, for example Starknet signature, Secp256k1, Secp256r1, Eip191 etc are some of the custom signatures that can be validated on Starknet currently. ### The Concepts of Accounts and Signers -i. **Account:** All accounts are smart contracts that can hold assets and execute transactions on Starknet protocol, these account contracts however must implement some specific methods outlined in SNIP-6. +i. **Account:** All accounts are smart contracts that can hold assets and execute transactions on Starknet, these account contracts however must implement some specific methods outlined in SNIP-6. For further reading: [Account contract](https://starknet-by-example.voyager.online/advanced-concepts/account_abstraction/account_contract.html). ii. **Signers:** These are responsible for digitally signing transactions and provide the authorization needed to initiate transactions. Digital signatures are cryptographic proofs that transactions are authorized by corresponding accounts. -In summary, Starknet accounts are normal blockchain accounts that hold assets and initiate transactions onchain, while signers provide the authorization required to ensure that transactions originating from these accounts are secure, valid and executed. +### Signature validation on Starknet -To implement custom validation method on Starknet you have to ensure that the contract contains these three methods: `is_valid_signature`, `__validate__` and `__execute__`. These are the building block for account contracts on Starknet as contained in the SNIP-6. +On Starknet transactions are signed offchain, which means that the signature process happens outside the blockchain. The signed transaction is then submitted to Starknet network for verification and execution. Read more: [Starknet-js docs](https://www.starknetjs.com/docs/guides/signature/) +All Account contracts on Starknet must implement the SNIP-6 standard as mentioned earlier. The methods implemented in the SNIP-6 standard provide means to move offchain signatures onchain and execute them. +`is_valid_signature` returns true if the signature is valid, `__validate__` validates the signature and marks them as 'VALIDATED', while `__execute__` executes the validated transaction. Sample implementation of SNIP-6 standard: [Sample SNIP-6 Implementation](https://starknet-by-example.voyager.online/advanced-concepts/account_abstraction/account_contract.html#minimal-account-contract-executing-transactions) + +On Ethereum, EOAs directly sign transactions onchain using their private keys. This onchain signing of transactions is more secure and straightforward but less flexible. Offchain signing employed on Starknet gives room for more flexibility aside the ability to implement custom signature schemes, however, care must be taken to validate all signatures meticulously to ensure that: + +a. the message has not been altered. +b. the signer owns the private key corresponding to the public key. + + +In summary, Starknet accounts are normal blockchain accounts that hold assets and initiate transactions onchain, while signers provide the authorization required to ensure that transactions originating from these accounts are secure and valid. + +Digital signatures are a fundamental aspect of modern cryptography, used to verify the authenticity and integrity of digital messages or transactions. They are based on public-key cryptography, where a pair of keys (a public key and a private key) are used to create and verify signatures. +Private keys are kept secret and secure by the owner, and are used to sign the message or transaction, while the public key can be used by anyone to verify the signature. + +### Custom signature validation sample + +The example below shows a sample implementation of `Secp256r1` and `Secp256k1` signature schemes: + +```rust +{{#rustdoc_include ../../listings/advanced-concepts/custom_signature_validation/src/custom_signature.cairo:custom_signature_scheme}} +``` From bd156317a090e037c260497800fb0097670df41a Mon Sep 17 00:00:00 2001 From: OkoliEvans Date: Wed, 24 Jul 2024 12:37:43 +0100 Subject: [PATCH 05/10] made some adjustments based on PR review --- .../custom_signature_validation.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/advanced-concepts/custom_signature_validation.md b/src/advanced-concepts/custom_signature_validation.md index 7b8d4e88..bb6a95dc 100644 --- a/src/advanced-concepts/custom_signature_validation.md +++ b/src/advanced-concepts/custom_signature_validation.md @@ -1,12 +1,15 @@ # Custom Signature Validation Scheme -Account Abstraction is a native feature on Starknet, this makes it possible for anyone to implement custom signature schemes. The implication is that signature schemes on Starknet are not limited to just one, any standard signature scheme can be validated, for example Starknet signature, Secp256k1, Secp256r1, Eip191 etc are some of the custom signatures that can be validated on Starknet currently. +Digital signatures are a fundamental aspect of modern cryptography, used to verify the authenticity and integrity of digital messages or transactions. They are based on public-key cryptography, where a pair of keys (a public key and a private key) are used to create and verify signatures. +Private keys are kept secret and secure by the owner, and are used to sign the message or transaction, while the public key can be used by anyone to verify the signature. + +Account Abstraction is a native feature on Starknet, this makes it possible for anyone to implement custom signature schemes. The implication is that signature schemes on Starknet are not limited to just one, any standard signature scheme can be validated, for example Starknet signature, Secp256k1, Secp256r1, Eip191 etc are some of the custom signatures that can be validated on Starknet currently. ### The Concepts of Accounts and Signers i. **Account:** All accounts are smart contracts that can hold assets and execute transactions on Starknet, these account contracts however must implement some specific methods outlined in SNIP-6. For further reading: [Account contract](https://starknet-by-example.voyager.online/advanced-concepts/account_abstraction/account_contract.html). -ii. **Signers:** These are responsible for digitally signing transactions and provide the authorization needed to initiate transactions. +ii. **Signers:** These are responsible for digitally signing transactions and provide the authorization needed to initiate transactions. Digital signatures are cryptographic proofs that transactions are authorized by corresponding accounts. ### Signature validation on Starknet @@ -17,18 +20,14 @@ All Account contracts on Starknet must implement the SNIP-6 standard as mentione `is_valid_signature` returns true if the signature is valid, `__validate__` validates the signature and marks them as 'VALIDATED', while `__execute__` executes the validated transaction. Sample implementation of SNIP-6 standard: [Sample SNIP-6 Implementation](https://starknet-by-example.voyager.online/advanced-concepts/account_abstraction/account_contract.html#minimal-account-contract-executing-transactions) - -On Ethereum, EOAs directly sign transactions onchain using their private keys. This onchain signing of transactions is more secure and straightforward but less flexible. Offchain signing employed on Starknet gives room for more flexibility aside the ability to implement custom signature schemes, however, care must be taken to validate all signatures meticulously to ensure that: +On Ethereum, only **one** signature scheme is used for signing messages and transactions, and also for signature authentications: the Elliptic Curve Digital Signature Algorithm (ECDSA). That means that no other signature algorithms can be validated on Ethereum, making it more secure but less flexible. +Custom signature validation employed on Starknet gives room for more flexibility, however, care must be taken to validate all signatures meticulously to ensure that: a. the message has not been altered. b. the signer owns the private key corresponding to the public key. - In summary, Starknet accounts are normal blockchain accounts that hold assets and initiate transactions onchain, while signers provide the authorization required to ensure that transactions originating from these accounts are secure and valid. -Digital signatures are a fundamental aspect of modern cryptography, used to verify the authenticity and integrity of digital messages or transactions. They are based on public-key cryptography, where a pair of keys (a public key and a private key) are used to create and verify signatures. -Private keys are kept secret and secure by the owner, and are used to sign the message or transaction, while the public key can be used by anyone to verify the signature. - ### Custom signature validation sample The example below shows a sample implementation of `Secp256r1` and `Secp256k1` signature schemes: From d531ed6cd845aae6c1a36a86e6bb300820f25cb1 Mon Sep 17 00:00:00 2001 From: OkoliEvans Date: Tue, 13 Aug 2024 21:39:02 +0100 Subject: [PATCH 06/10] Edited draft PR on Custom signature validation --- .../src/custom_signature.cairo | 12 +++-- .../src/simple_account.cairo | 51 +++++++++++++++++++ .../custom_signature_validation.md | 29 ++++++----- 3 files changed, 76 insertions(+), 16 deletions(-) create mode 100644 listings/advanced-concepts/custom_signature_validation/src/simple_account.cairo diff --git a/listings/advanced-concepts/custom_signature_validation/src/custom_signature.cairo b/listings/advanced-concepts/custom_signature_validation/src/custom_signature.cairo index bc61e9ae..310c15e0 100644 --- a/listings/advanced-concepts/custom_signature_validation/src/custom_signature.cairo +++ b/listings/advanced-concepts/custom_signature_validation/src/custom_signature.cairo @@ -24,7 +24,7 @@ enum SignerType { #[derive(Drop, Copy, Serde)] enum SignerSignature { - Secp256r1: (Secp256r1Signer, Secp256Signature), + Secp256r1: (Secp256r1Signer, Secp2newAccount56Signature), Secp256k1: (Secp256k1Signer, Secp256Signature), } @@ -64,8 +64,10 @@ impl Secp256k1SignerSerde of Serde { trait Secp256SignatureTrait { fn is_valid_signature(self: SignerSignature, hash: felt252) -> bool; fn signer(self: SignerSignature) -> Signer; -} - + } + + // ANCHOR: is_valid_signature +// To check if secp256k1 and secp256r1 signatures are valid impl Secp256SignatureImpl of Secp256SignatureTrait { #[inline(always)] fn is_valid_signature(self: SignerSignature, hash: felt252) -> bool { @@ -78,7 +80,8 @@ impl Secp256SignatureImpl of Secp256SignatureTrait { )) => is_valid_secp256k1_signature(hash.into(), signer.pubkey_hash.into(), signature), } } - + + #[inline(always)] fn signer(self: SignerSignature) -> Signer { match self { @@ -105,6 +108,7 @@ fn is_valid_secp256r1_signature(hash: u256, signer: Secp256r1Signer, signature: let (recovered_signer, _) = recovered_pubkey.get_coordinates().expect('invalid sig format'); recovered_signer == signer.pubkey.into() } +// ANCHOR_END: is_valid_signature // impl to convert signer type into felt252 using into() impl SignerTypeIntoFelt252 of Into { diff --git a/listings/advanced-concepts/custom_signature_validation/src/simple_account.cairo b/listings/advanced-concepts/custom_signature_validation/src/simple_account.cairo new file mode 100644 index 00000000..0f32f6eb --- /dev/null +++ b/listings/advanced-concepts/custom_signature_validation/src/simple_account.cairo @@ -0,0 +1,51 @@ + +#[starknet::contract] +mod SimpleAccount { + + +// ANCHOR: validate +fn __validate__(ref self: ContractState, calls: Array) -> felt252 { + let exec_info = get_execution_info().unbox(); + let tx_info = exec_info.tx_info.unbox(); + assert_only_protocol(exec_info.caller_address); + assert_correct_invoke_version(tx_info.version); + assert(tx_info.paymaster_data.is_empty(), 'unsupported-paymaster'); + if self.session.is_session(tx_info.signature) { + self.session.assert_valid_session(calls.span(), tx_info.transaction_hash, tx_info.signature,); + } else { + self + .assert_valid_calls_and_signature( + calls.span(), + tx_info.transaction_hash, + tx_info.signature, + is_from_outside: false, + account_address: exec_info.contract_address, + ); + } + VALIDATED +} +// ANCHOR_END: validate + +// ANCHOR: execute +fn __execute__(ref self: ContractState, calls: Array) -> Array> { + self.reentrancy_guard.start(); + let exec_info = get_execution_info().unbox(); + let tx_info = exec_info.tx_info.unbox(); + assert_only_protocol(exec_info.caller_address); + assert_correct_invoke_version(tx_info.version); + let signature = tx_info.signature; + if self.session.is_session(signature) { + let session_timestamp = *signature[1]; + // can call unwrap safely as the session has already been deserialized + let session_timestamp_u64 = session_timestamp.try_into().unwrap(); + assert(session_timestamp_u64 >= exec_info.block_info.unbox().block_timestamp, 'session/expired'); + } + + let retdata = execute_multicall(calls.span()); + + self.emit(TransactionExecuted { hash: tx_info.transaction_hash, response: retdata.span() }); + self.reentrancy_guard.end(); + retdata +} +// ANCHOR_END: execute +} \ No newline at end of file diff --git a/src/advanced-concepts/custom_signature_validation.md b/src/advanced-concepts/custom_signature_validation.md index bb6a95dc..9e0a42a0 100644 --- a/src/advanced-concepts/custom_signature_validation.md +++ b/src/advanced-concepts/custom_signature_validation.md @@ -1,37 +1,42 @@ # Custom Signature Validation Scheme -Digital signatures are a fundamental aspect of modern cryptography, used to verify the authenticity and integrity of digital messages or transactions. They are based on public-key cryptography, where a pair of keys (a public key and a private key) are used to create and verify signatures. -Private keys are kept secret and secure by the owner, and are used to sign the message or transaction, while the public key can be used by anyone to verify the signature. +Digital signatures are a fundamental aspect of modern cryptography used to verify the authenticity and integrity of digital messages or transactions. They are based on public-key cryptography, where a pair of keys (a public key and a private key) are used to create and verify signatures. -Account Abstraction is a native feature on Starknet, this makes it possible for anyone to implement custom signature schemes. The implication is that signature schemes on Starknet are not limited to just one, any standard signature scheme can be validated, for example Starknet signature, Secp256k1, Secp256r1, Eip191 etc are some of the custom signatures that can be validated on Starknet currently. +Private keys are kept secret and secured by the owner. They are used to sign data such as messages or transactions, which can be verified by anyone with the public key. + +Account Abstraction is a native feature on Starknet, this makes it possible for anyone to implement custom signature schemes and use it to validate transactions with the implementation of the signature validation logic. ### The Concepts of Accounts and Signers -i. **Account:** All accounts are smart contracts that can hold assets and execute transactions on Starknet, these account contracts however must implement some specific methods outlined in SNIP-6. For further reading: [Account contract](https://starknet-by-example.voyager.online/advanced-concepts/account_abstraction/account_contract.html). +i. **Account:** All accounts are smart contracts that can hold assets and execute transactions on Starknet, these account contracts however must implement some specific methods outlined in [SNIP-6.](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-6.md) + +ii. **Signers:** These are responsible for digitally signing transactions and also provide the authorization needed to initiate transactions. Transaction signing is done offchain on Starknet. -ii. **Signers:** These are responsible for digitally signing transactions and provide the authorization needed to initiate transactions. Digital signatures are cryptographic proofs that transactions are authorized by corresponding accounts. ### Signature validation on Starknet On Starknet transactions are signed offchain, which means that the signature process happens outside the blockchain. The signed transaction is then submitted to Starknet network for verification and execution. Read more: [Starknet-js docs](https://www.starknetjs.com/docs/guides/signature/) -All Account contracts on Starknet must implement the SNIP-6 standard as mentioned earlier. The methods implemented in the SNIP-6 standard provide means to move offchain signatures onchain and execute them. +All Account contracts on Starknet must implement the SNIP-6 standard as mentioned earlier. The methods outlined in the standard provide means to move offchain signatures onchain and execute them. + +`is_valid_signature` returns true if the signature is valid, `__validate__` validates the signature and marks them as 'VALIDATED', while `__execute__` executes the validated transaction. Sample implementation of SNIP-6 standard: -`is_valid_signature` returns true if the signature is valid, `__validate__` validates the signature and marks them as 'VALIDATED', while `__execute__` executes the validated transaction. Sample implementation of SNIP-6 standard: [Sample SNIP-6 Implementation](https://starknet-by-example.voyager.online/advanced-concepts/account_abstraction/account_contract.html#minimal-account-contract-executing-transactions) +```rust +{{#rustdoc_include ../../listings/advanced-concepts/simple_account/src/simple_account.cairo}} +``` -On Ethereum, only **one** signature scheme is used for signing messages and transactions, and also for signature authentications: the Elliptic Curve Digital Signature Algorithm (ECDSA). That means that no other signature algorithms can be validated on Ethereum, making it more secure but less flexible. -Custom signature validation employed on Starknet gives room for more flexibility, however, care must be taken to validate all signatures meticulously to ensure that: +On Ethereum, only **one** signature scheme is used: ECDSA. It makes Ethereum more secure but less flexible. +Custom signature validation used on Starknet gives room for more flexibility, however, care must be taken to validate all signatures meticulously to ensure that: a. the message has not been altered. b. the signer owns the private key corresponding to the public key. -In summary, Starknet accounts are normal blockchain accounts that hold assets and initiate transactions onchain, while signers provide the authorization required to ensure that transactions originating from these accounts are secure and valid. ### Custom signature validation sample -The example below shows a sample implementation of `Secp256r1` and `Secp256k1` signature schemes: +The example below shows a sample validation of `Secp256r1` and `Secp256k1` signature schemes: ```rust -{{#rustdoc_include ../../listings/advanced-concepts/custom_signature_validation/src/custom_signature.cairo:custom_signature_scheme}} +{{#rustdoc_include ../../listings/advanced-concepts/custom_signature_validation/src/custom_signature.cairo:is_valid_signature}} ``` From badf4dbdf5fbb669b3786925ebea25a667ecf2d6 Mon Sep 17 00:00:00 2001 From: Julio <30329843+julio4@users.noreply.github.com> Date: Thu, 21 Nov 2024 16:37:59 +0700 Subject: [PATCH 07/10] Prepare release 2.8.2 (#257) * dep: fix patch to latest shikijs for cairo hl * fix(app): correct sidebar placement * fix(app): responsive content centering * fix(app): responsive content centering * doc: branch guidelines * fix(app): top section nav links --- CONTRIBUTING.md | 6 + package.json | 8 + patches/vocs.patch | 1416 ++------------------------------------------ pnpm-lock.yaml | 135 +++-- routes.ts | 4 - styles.css | 95 +-- 6 files changed, 183 insertions(+), 1481 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1ac94b34..0543b466 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,12 @@ When contributing to this repository, please first discuss the change you wish t Join the telegram channel: https://t.me/StarknetByExample +The release branch is `main`. The development branch is `dev` and is considered stable (but not released yet). +When you want to contribute, please create a new branch from `dev` and open a pull request to merge your changes back into `dev`. +You should never open a pull request to merge your changes directly into `main`. + +The `dev` branch is deployed at https://starknet-by-example-dev.voyager.online/ + Please note we have a code of conduct, please follow it in all your interactions with the project. ## Table of Contents diff --git a/package.json b/package.json index 032ce81a..4d683183 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,14 @@ "pnpm": { "patchedDependencies": { "vocs": "patches/vocs.patch" + }, + "overrides": { + "vocs>shiki": "^1.23.0", + "vocs>@shikijs/core": "^1.23.0", + "vocs>@shikijs/types": "^1.23.0", + "vocs>@shikijs/rehype": "^1.23.0", + "vocs>@shikijs/twoslash": "^1.23.0", + "vocs>@shikijs/transformers": "^1.23.0" } } } diff --git a/patches/vocs.patch b/patches/vocs.patch index 0bd0899a..db1208c9 100644 --- a/patches/vocs.patch +++ b/patches/vocs.patch @@ -1,1394 +1,46 @@ -diff --git a/_lib/vite/plugins/cairo.json b/_lib/vite/plugins/cairo.json -new file mode 100644 -index 0000000000000000000000000000000000000000..9078642c2c1dc8b472469c269619b1898734459c ---- /dev/null -+++ b/_lib/vite/plugins/cairo.json -@@ -0,0 +1,996 @@ -+{ -+ "credits": "Pulled from: https://raw.githubusercontent.com/starkware-libs/cairo/main/vscode-cairo/syntaxes/cairo.tmLanguage.json. This grammar is heavily modified Rust grammar from VSCode repository: https://github.com/microsoft/vscode/blob/11f415d4d77f7739c202905ccd02e27774146b75/extensions/rust/syntaxes/rust.tmLanguage.json", -+ "name": "Cairo", -+ "scopeName": "source.cairo", -+ "patterns": [ -+ { -+ "comment": "boxed slice literal", -+ "begin": "(<)(\\[)", -+ "beginCaptures": { -+ "1": { -+ "name": "punctuation.brackets.angle.cairo" -+ }, -+ "2": { -+ "name": "punctuation.brackets.square.cairo" -+ } -+ }, -+ "end": ">", -+ "endCaptures": { -+ "0": { -+ "name": "punctuation.brackets.angle.cairo" -+ } -+ }, -+ "patterns": [ -+ { -+ "include": "#block-comments" -+ }, -+ { -+ "include": "#comments" -+ }, -+ { -+ "include": "#gtypes" -+ }, -+ { -+ "include": "#lvariables" -+ }, -+ { -+ "include": "#punctuation" -+ }, -+ { -+ "include": "#types" -+ } -+ ] -+ }, -+ { -+ "comment": "modules", -+ "match": "(mod)\\s+([a-z][A-Za-z0-9_]*)", -+ "captures": { -+ "1": { -+ "name": "storage.type.cairo" -+ }, -+ "2": { -+ "name": "entity.name.module.cairo" -+ } -+ } -+ }, -+ { -+ "comment": "use statements", -+ "name": "meta.use.cairo", -+ "begin": "\\b(use)\\s", -+ "beginCaptures": { -+ "1": { -+ "name": "keyword.other.cairo" -+ } -+ }, -+ "end": ";", -+ "endCaptures": { -+ "0": { -+ "name": "punctuation.semi.cairo" -+ } -+ }, -+ "patterns": [ -+ { -+ "include": "#block-comments" -+ }, -+ { -+ "include": "#comments" -+ }, -+ { -+ "include": "#keywords" -+ }, -+ { -+ "include": "#namespaces" -+ }, -+ { -+ "include": "#punctuation" -+ }, -+ { -+ "include": "#types" -+ }, -+ { -+ "include": "#lvariables" -+ } -+ ] -+ }, -+ { -+ "include": "#block-comments" -+ }, -+ { -+ "include": "#comments" -+ }, -+ { -+ "include": "#attributes" -+ }, -+ { -+ "include": "#lvariables" -+ }, -+ { -+ "include": "#constants" -+ }, -+ { -+ "include": "#gtypes" -+ }, -+ { -+ "include": "#functions" -+ }, -+ { -+ "include": "#types" -+ }, -+ { -+ "include": "#keywords" -+ }, -+ { -+ "include": "#macros" -+ }, -+ { -+ "include": "#namespaces" -+ }, -+ { -+ "include": "#punctuation" -+ }, -+ { -+ "include": "#strings" -+ }, -+ { -+ "include": "#variables" -+ } -+ ], -+ "repository": { -+ "comments": { -+ "patterns": [ -+ { -+ "comment": "documentation comments", -+ "name": "comment.line.documentation.cairo", -+ "match": "(///).*$", -+ "captures": { -+ "1": { -+ "name": "punctuation.definition.comment.cairo" -+ } -+ } -+ }, -+ { -+ "comment": "line comments", -+ "name": "comment.line.double-slash.cairo", -+ "match": "(//).*$", -+ "captures": { -+ "1": { -+ "name": "punctuation.definition.comment.cairo" -+ } -+ } -+ } -+ ] -+ }, -+ "block-comments": { -+ "patterns": [ -+ { -+ "comment": "empty block comments", -+ "name": "comment.block.cairo", -+ "match": "/\\*\\*/" -+ }, -+ { -+ "comment": "block documentation comments", -+ "name": "comment.block.documentation.cairo", -+ "begin": "/\\*\\*", -+ "end": "\\*/", -+ "patterns": [ -+ { -+ "include": "#block-comments" -+ } -+ ] -+ }, -+ { -+ "comment": "block comments", -+ "name": "comment.block.cairo", -+ "begin": "/\\*(?!\\*)", -+ "end": "\\*/", -+ "patterns": [ -+ { -+ "include": "#block-comments" -+ } -+ ] -+ } -+ ] -+ }, -+ "constants": { -+ "patterns": [ -+ { -+ "comment": "ALL CAPS constants", -+ "name": "constant.other.caps.cairo", -+ "match": "\\b[A-Z]{2}[A-Z0-9_]*\\b" -+ }, -+ { -+ "comment": "constant declarations", -+ "match": "\\b(const)\\s+([A-Z][A-Za-z0-9_]*)\\b", -+ "captures": { -+ "1": { -+ "name": "storage.type.cairo" -+ }, -+ "2": { -+ "name": "constant.other.caps.cairo" -+ } -+ } -+ }, -+ { -+ "comment": "decimal integers and floats", -+ "name": "constant.numeric.decimal.cairo", -+ "match": "\\b\\d[\\d_]*(\\.?)[\\d_]*(?:(E|e)([+-]?)([\\d_]+))?(f32|f64|i128|i16|i32|i64|i8|isize|u128|u16|u32|u64|u8|usize)?\\b", -+ "captures": { -+ "1": { -+ "name": "punctuation.separator.dot.decimal.cairo" -+ }, -+ "2": { -+ "name": "keyword.operator.exponent.cairo" -+ }, -+ "3": { -+ "name": "keyword.operator.exponent.sign.cairo" -+ }, -+ "4": { -+ "name": "constant.numeric.decimal.exponent.mantissa.cairo" -+ }, -+ "5": { -+ "name": "entity.name.type.numeric.cairo" -+ } -+ } -+ }, -+ { -+ "comment": "hexadecimal integers", -+ "name": "constant.numeric.hex.cairo", -+ "match": "\\b0x[\\da-fA-F_]+(i128|i16|i32|i64|i8|isize|u128|u16|u32|u64|u8|usize)?\\b", -+ "captures": { -+ "1": { -+ "name": "entity.name.type.numeric.cairo" -+ } -+ } -+ }, -+ { -+ "comment": "octal integers", -+ "name": "constant.numeric.oct.cairo", -+ "match": "\\b0o[0-7_]+(i128|i16|i32|i64|i8|isize|u128|u16|u32|u64|u8|usize)?\\b", -+ "captures": { -+ "1": { -+ "name": "entity.name.type.numeric.cairo" -+ } -+ } -+ }, -+ { -+ "comment": "binary integers", -+ "name": "constant.numeric.bin.cairo", -+ "match": "\\b0b[01_]+(i128|i16|i32|i64|i8|isize|u128|u16|u32|u64|u8|usize)?\\b", -+ "captures": { -+ "1": { -+ "name": "entity.name.type.numeric.cairo" -+ } -+ } -+ }, -+ { -+ "comment": "booleans", -+ "name": "constant.language.bool.cairo", -+ "match": "\\b(true|false)\\b" -+ } -+ ] -+ }, -+ "escapes": { -+ "comment": "escapes: ASCII, byte, Unicode, quote, regex", -+ "name": "constant.character.escape.cairo", -+ "match": "(\\\\)(?:(?:(x[0-7][\\da-fA-F])|(u(\\{)[\\da-fA-F]{4,6}(\\}))|.))", -+ "captures": { -+ "1": { -+ "name": "constant.character.escape.backslash.cairo" -+ }, -+ "2": { -+ "name": "constant.character.escape.bit.cairo" -+ }, -+ "3": { -+ "name": "constant.character.escape.unicode.cairo" -+ }, -+ "4": { -+ "name": "constant.character.escape.unicode.punctuation.cairo" -+ }, -+ "5": { -+ "name": "constant.character.escape.unicode.punctuation.cairo" -+ } -+ } -+ }, -+ "attributes": { -+ "comment": "attributes", -+ "name": "meta.attribute.cairo", -+ "begin": "(#)(\\!?)(\\[)", -+ "beginCaptures": { -+ "1": { -+ "name": "punctuation.definition.attribute.cairo" -+ }, -+ "3": { -+ "name": "punctuation.brackets.attribute.cairo" -+ } -+ }, -+ "end": "\\]", -+ "endCaptures": { -+ "0": { -+ "name": "punctuation.brackets.attribute.cairo" -+ } -+ }, -+ "patterns": [ -+ { -+ "include": "#block-comments" -+ }, -+ { -+ "include": "#comments" -+ }, -+ { -+ "include": "#keywords" -+ }, -+ { -+ "include": "#punctuation" -+ }, -+ { -+ "include": "#strings" -+ }, -+ { -+ "include": "#gtypes" -+ }, -+ { -+ "include": "#types" -+ } -+ ] -+ }, -+ "functions": { -+ "patterns": [ -+ { -+ "comment": "pub as a function", -+ "match": "\\b(pub)(\\()", -+ "captures": { -+ "1": { -+ "name": "keyword.other.cairo" -+ }, -+ "2": { -+ "name": "punctuation.brackets.round.cairo" -+ } -+ } -+ }, -+ { -+ "comment": "function definition", -+ "name": "meta.function.definition.cairo", -+ "begin": "\\b(fn)\\s+([A-Za-z0-9_]+)((\\()|(<))", -+ "beginCaptures": { -+ "1": { -+ "name": "keyword.other.fn.cairo" -+ }, -+ "2": { -+ "name": "entity.name.function.cairo" -+ }, -+ "4": { -+ "name": "punctuation.brackets.round.cairo" -+ }, -+ "5": { -+ "name": "punctuation.brackets.angle.cairo" -+ } -+ }, -+ "end": "\\{|;", -+ "endCaptures": { -+ "0": { -+ "name": "punctuation.brackets.curly.cairo" -+ } -+ }, -+ "patterns": [ -+ { -+ "include": "#block-comments" -+ }, -+ { -+ "include": "#comments" -+ }, -+ { -+ "include": "#keywords" -+ }, -+ { -+ "include": "#lvariables" -+ }, -+ { -+ "include": "#constants" -+ }, -+ { -+ "include": "#gtypes" -+ }, -+ { -+ "include": "#functions" -+ }, -+ { -+ "include": "#macros" -+ }, -+ { -+ "include": "#namespaces" -+ }, -+ { -+ "include": "#punctuation" -+ }, -+ { -+ "include": "#strings" -+ }, -+ { -+ "include": "#types" -+ }, -+ { -+ "include": "#variables" -+ } -+ ] -+ }, -+ { -+ "comment": "function/method calls, chaining", -+ "name": "meta.function.call.cairo", -+ "begin": "([A-Za-z0-9_]+)(\\()", -+ "beginCaptures": { -+ "1": { -+ "name": "entity.name.function.cairo" -+ }, -+ "2": { -+ "name": "punctuation.brackets.round.cairo" -+ } -+ }, -+ "end": "\\)", -+ "endCaptures": { -+ "0": { -+ "name": "punctuation.brackets.round.cairo" -+ } -+ }, -+ "patterns": [ -+ { -+ "include": "#block-comments" -+ }, -+ { -+ "include": "#comments" -+ }, -+ { -+ "include": "#attributes" -+ }, -+ { -+ "include": "#keywords" -+ }, -+ { -+ "include": "#lvariables" -+ }, -+ { -+ "include": "#constants" -+ }, -+ { -+ "include": "#gtypes" -+ }, -+ { -+ "include": "#functions" -+ }, -+ { -+ "include": "#macros" -+ }, -+ { -+ "include": "#namespaces" -+ }, -+ { -+ "include": "#punctuation" -+ }, -+ { -+ "include": "#strings" -+ }, -+ { -+ "include": "#types" -+ }, -+ { -+ "include": "#variables" -+ } -+ ] -+ }, -+ { -+ "comment": "function/method calls with turbofish", -+ "name": "meta.function.call.cairo", -+ "begin": "([A-Za-z0-9_]+)(?=::<.*>\\()", -+ "beginCaptures": { -+ "1": { -+ "name": "entity.name.function.cairo" -+ } -+ }, -+ "end": "\\)", -+ "endCaptures": { -+ "0": { -+ "name": "punctuation.brackets.round.cairo" -+ } -+ }, -+ "patterns": [ -+ { -+ "include": "#block-comments" -+ }, -+ { -+ "include": "#comments" -+ }, -+ { -+ "include": "#attributes" -+ }, -+ { -+ "include": "#keywords" -+ }, -+ { -+ "include": "#lvariables" -+ }, -+ { -+ "include": "#constants" -+ }, -+ { -+ "include": "#gtypes" -+ }, -+ { -+ "include": "#functions" -+ }, -+ { -+ "include": "#lifetimes" -+ }, -+ { -+ "include": "#macros" -+ }, -+ { -+ "include": "#namespaces" -+ }, -+ { -+ "include": "#punctuation" -+ }, -+ { -+ "include": "#strings" -+ }, -+ { -+ "include": "#types" -+ }, -+ { -+ "include": "#variables" -+ } -+ ] -+ } -+ ] -+ }, -+ "keywords": { -+ "patterns": [ -+ { -+ "comment": "control flow keywords", -+ "name": "keyword.control.cairo", -+ "match": "\\b(break|continue|do|else|for|if|loop|match|return|try|while|yield)\\b" -+ }, -+ { -+ "comment": "storage keywords", -+ "name": "keyword.other.cairo storage.type.cairo", -+ "match": "\\b(extern|let|macro|mod)\\b" -+ }, -+ { -+ "comment": "const keyword", -+ "name": "storage.modifier.cairo", -+ "match": "\\b(const)\\b" -+ }, -+ { -+ "comment": "type keyword", -+ "name": "keyword.declaration.type.cairo storage.type.cairo", -+ "match": "\\b(type)\\b" -+ }, -+ { -+ "comment": "enum keyword", -+ "name": "keyword.declaration.enum.cairo storage.type.cairo", -+ "match": "\\b(enum)\\b" -+ }, -+ { -+ "comment": "trait keyword", -+ "name": "keyword.declaration.trait.cairo storage.type.cairo", -+ "match": "\\b(trait)\\b" -+ }, -+ { -+ "comment": "struct keyword", -+ "name": "keyword.declaration.struct.cairo storage.type.cairo", -+ "match": "\\b(struct)\\b" -+ }, -+ { -+ "comment": "storage modifiers", -+ "name": "storage.modifier.cairo", -+ "match": "\\b(ref|static)\\b" -+ }, -+ { -+ "comment": "other keywords", -+ "name": "keyword.other.cairo", -+ "match": "\\b(as|dyn|move|impl|implicits|in|nopanic|of|priv|pub|static_assert|typeof|unsafe|use|where|with)\\b" -+ }, -+ { -+ "comment": "fn", -+ "name": "keyword.other.fn.cairo", -+ "match": "\\bfn\\b" -+ }, -+ { -+ "comment": "crate", -+ "name": "keyword.other.crate.cairo", -+ "match": "\\bcrate\\b" -+ }, -+ { -+ "comment": "mut", -+ "name": "storage.modifier.mut.cairo", -+ "match": "\\bmut\\b" -+ }, -+ { -+ "comment": "logical operators", -+ "name": "keyword.operator.logical.cairo", -+ "match": "(\\^|\\||\\|\\||&&|<<|>>|!)(?!=)" -+ }, -+ { -+ "comment": "logical AND, borrow references", -+ "name": "keyword.operator.borrow.and.cairo", -+ "match": "&(?![&=])" -+ }, -+ { -+ "comment": "assignment operators", -+ "name": "keyword.operator.assignment.cairo", -+ "match": "(\\+=|-=|\\*=|/=|%=|\\^=|&=|\\|=|<<=|>>=)" -+ }, -+ { -+ "comment": "single equal", -+ "name": "keyword.operator.assignment.equal.cairo", -+ "match": "(?])=(?!=|>)" -+ }, -+ { -+ "comment": "comparison operators", -+ "name": "keyword.operator.comparison.cairo", -+ "match": "(=(=)?(?!>)|!=|<=|(?=)" -+ }, -+ { -+ "comment": "math operators", -+ "name": "keyword.operator.math.cairo", -+ "match": "(([+%]|(\\*(?!\\w)))(?!=))|(-(?!>))|(/(?!/))" -+ }, -+ { -+ "comment": "less than, greater than (special case)", -+ "match": "(?:\\b|(?:(\\))|(\\])|(\\})))[ \\t]+([<>])[ \\t]+(?:\\b|(?:(\\()|(\\[)|(\\{)))", -+ "captures": { -+ "1": { -+ "name": "punctuation.brackets.round.cairo" -+ }, -+ "2": { -+ "name": "punctuation.brackets.square.cairo" -+ }, -+ "3": { -+ "name": "punctuation.brackets.curly.cairo" -+ }, -+ "4": { -+ "name": "keyword.operator.comparison.cairo" -+ }, -+ "5": { -+ "name": "punctuation.brackets.round.cairo" -+ }, -+ "6": { -+ "name": "punctuation.brackets.square.cairo" -+ }, -+ "7": { -+ "name": "punctuation.brackets.curly.cairo" -+ } -+ } -+ }, -+ { -+ "comment": "namespace operator", -+ "name": "keyword.operator.namespace.cairo", -+ "match": "::" -+ }, -+ { -+ "comment": "desnap", -+ "match": "(\\*)(?=\\w+)", -+ "captures": { -+ "1": { -+ "name": "keyword.operator.desnap.cairo" -+ } -+ } -+ }, -+ { -+ "comment": "snap", -+ "name": "keyword.operator.snap.cairo", -+ "match": "@" -+ }, -+ { -+ "comment": "dot access", -+ "name": "keyword.operator.access.dot.cairo", -+ "match": "\\.(?!\\.)" -+ }, -+ { -+ "comment": "ranges, range patterns", -+ "name": "keyword.operator.range.cairo", -+ "match": "\\.{2}(=|\\.)?" -+ }, -+ { -+ "comment": "colon", -+ "name": "keyword.operator.key-value.cairo", -+ "match": ":(?!:)" -+ }, -+ { -+ "comment": "dashrocket, skinny arrow", -+ "name": "keyword.operator.arrow.skinny.cairo", -+ "match": "->" -+ }, -+ { -+ "comment": "hashrocket, fat arrow", -+ "name": "keyword.operator.arrow.fat.cairo", -+ "match": "=>" -+ }, -+ { -+ "comment": "dollar macros", -+ "name": "keyword.operator.macro.dollar.cairo", -+ "match": "\\$" -+ }, -+ { -+ "comment": "question mark operator, questionably sized, macro kleene matcher", -+ "name": "keyword.operator.question.cairo", -+ "match": "\\?" -+ } -+ ] -+ }, -+ "interpolations": { -+ "comment": "curly brace interpolations", -+ "name": "meta.interpolation.cairo", -+ "match": "({)[^\"{}]*(})", -+ "captures": { -+ "1": { -+ "name": "punctuation.definition.interpolation.cairo" -+ }, -+ "2": { -+ "name": "punctuation.definition.interpolation.cairo" -+ } -+ } -+ }, -+ "macros": { -+ "patterns": [ -+ { -+ "comment": "macros", -+ "name": "meta.macro.cairo", -+ "match": "(([a-z_][A-Za-z0-9_]*!)|([A-Z_][A-Za-z0-9_]*!))", -+ "captures": { -+ "2": { -+ "name": "entity.name.function.macro.cairo" -+ }, -+ "3": { -+ "name": "entity.name.type.macro.cairo" -+ } -+ } -+ } -+ ] -+ }, -+ "namespaces": { -+ "patterns": [ -+ { -+ "comment": "namespace (non-type, non-function path segment)", -+ "match": "(?", -+ "endCaptures": { -+ "0": { -+ "name": "punctuation.brackets.angle.cairo" -+ } -+ }, -+ "patterns": [ -+ { -+ "include": "#block-comments" -+ }, -+ { -+ "include": "#comments" -+ }, -+ { -+ "include": "#keywords" -+ }, -+ { -+ "include": "#lvariables" -+ }, -+ { -+ "include": "#punctuation" -+ }, -+ { -+ "include": "#types" -+ }, -+ { -+ "include": "#variables" -+ } -+ ] -+ }, -+ { -+ "comment": "primitive types", -+ "name": "entity.name.type.primitive.cairo", -+ "match": "\\b(bool|never)\\b" -+ }, -+ { -+ "comment": "trait declarations", -+ "match": "\\b(trait)\\s+(_?[A-Z][A-Za-z0-9_]*)\\b", -+ "captures": { -+ "1": { -+ "name": "keyword.declaration.trait.cairo storage.type.cairo" -+ }, -+ "2": { -+ "name": "entity.name.type.trait.cairo" -+ } -+ } -+ }, -+ { -+ "comment": "struct declarations", -+ "match": "\\b(struct)\\s+(_?[A-Z][A-Za-z0-9_]*)\\b", -+ "captures": { -+ "1": { -+ "name": "keyword.declaration.struct.cairo storage.type.cairo" -+ }, -+ "2": { -+ "name": "entity.name.type.struct.cairo" -+ } -+ } -+ }, -+ { -+ "comment": "enum declarations", -+ "match": "\\b(enum)\\s+(_?[A-Z][A-Za-z0-9_]*)\\b", -+ "captures": { -+ "1": { -+ "name": "keyword.declaration.enum.cairo storage.type.cairo" -+ }, -+ "2": { -+ "name": "entity.name.type.enum.cairo" -+ } -+ } -+ }, -+ { -+ "comment": "type declarations", -+ "match": "\\b(type)\\s+(_?[A-Z][A-Za-z0-9_]*)\\b", -+ "captures": { -+ "1": { -+ "name": "keyword.declaration.type.cairo storage.type.cairo" -+ }, -+ "2": { -+ "name": "entity.name.type.declaration.cairo" -+ } -+ } -+ }, -+ { -+ "comment": "types", -+ "name": "entity.name.type.cairo", -+ "match": "\\b_?[A-Z][A-Za-z0-9_]*\\b(?!!)" -+ } -+ ] -+ }, -+ "gtypes": { -+ "patterns": [ -+ { -+ "comment": "option types", -+ "name": "entity.name.type.option.cairo", -+ "match": "\\b(Some|None)\\b" -+ }, -+ { -+ "comment": "result types", -+ "name": "entity.name.type.result.cairo", -+ "match": "\\b(Ok|Err)\\b" -+ } -+ ] -+ }, -+ "punctuation": { -+ "patterns": [ -+ { -+ "comment": "comma", -+ "name": "punctuation.comma.cairo", -+ "match": "," -+ }, -+ { -+ "comment": "curly braces", -+ "name": "punctuation.brackets.curly.cairo", -+ "match": "[{}]" -+ }, -+ { -+ "comment": "parentheses, round brackets", -+ "name": "punctuation.brackets.round.cairo", -+ "match": "[()]" -+ }, -+ { -+ "comment": "semicolon", -+ "name": "punctuation.semi.cairo", -+ "match": ";" -+ }, -+ { -+ "comment": "square brackets", -+ "name": "punctuation.brackets.square.cairo", -+ "match": "[\\[\\]]" -+ }, -+ { -+ "comment": "angle brackets", -+ "name": "punctuation.brackets.angle.cairo", -+ "match": "(?]" -+ } -+ ] -+ }, -+ "strings": { -+ "patterns": [ -+ { -+ "comment": "double-quoted byte array strings", -+ "name": "string.quoted.double.cairo", -+ "begin": "(\")", -+ "beginCaptures": { -+ "1": { -+ "name": "punctuation.definition.string.bytearray.cairo" -+ } -+ }, -+ "end": "\"", -+ "endCaptures": { -+ "0": { -+ "name": "punctuation.definition.string.bytearray.cairo" -+ } -+ }, -+ "patterns": [ -+ { -+ "include": "#escapes" -+ }, -+ { -+ "include": "#interpolations" -+ } -+ ] -+ }, -+ { -+ "comment": "single-quoted short strings", -+ "name": "string.quoted.single.cairo", -+ "begin": "(')", -+ "beginCaptures": { -+ "1": { -+ "name": "punctuation.definition.string.short.cairo" -+ } -+ }, -+ "end": "'", -+ "endCaptures": { -+ "0": { -+ "name": "punctuation.definition.string.short.cairo" -+ } -+ }, -+ "patterns": [ -+ { -+ "include": "#escapes" -+ }, -+ { -+ "include": "#interpolations" -+ } -+ ] -+ } -+ ] -+ }, -+ "lvariables": { -+ "patterns": [ -+ { -+ "comment": "super", -+ "name": "variable.language.super.cairo", -+ "match": "\\bsuper\\b" -+ } -+ ] -+ }, -+ "variables": { -+ "patterns": [ -+ { -+ "comment": "variables", -+ "name": "variable.other.cairo", -+ "match": "\\b(? [ -- remarkDirective, -- remarkInferFrontmatter, -- remarkFrontmatter, -- remarkMdxFrontmatter, -- remarkGfm, -- remarkLinks, -- remarkBlogPosts, -- remarkCallout, -- remarkCode, -- remarkCodeGroup, -- remarkDetails, -- remarkFilename, -- remarkSponsors, -- remarkSteps, -- remarkStrongBlock, -- remarkSubheading, -- remarkTwoslash, -- remarkAuthors, -- ...(markdown?.remarkPlugins || []), -+ remarkDirective, -+ remarkInferFrontmatter, -+ remarkFrontmatter, -+ remarkMdxFrontmatter, -+ remarkGfm, -+ remarkLinks, -+ remarkBlogPosts, -+ remarkCallout, -+ remarkCode, -+ remarkCodeGroup, -+ remarkDetails, -+ remarkFilename, -+ remarkSponsors, -+ remarkSteps, -+ remarkStrongBlock, -+ remarkSubheading, -+ remarkTwoslash, -+ remarkAuthors, -+ ...(markdown?.remarkPlugins || []), - ]; - const defaultThemes = { -- dark: 'github-dark-dimmed', -- light: 'github-light', -+ dark: "github-dark-dimmed", -+ light: "github-light", - }; --export const getRehypePlugins = ({ markdown, rootDir = '', twoslash = {}, } = {}) => [ -- rehypeSlug, -- [ -- rehypeShiki, -- { -- transformers: [ -- transformerLineNumbers(), -- transformerNotationDiff(), -- transformerNotationFocus(), -- transformerNotationHighlight(), -- transformerNotationWordHighlight(), -- transformerNotationInclude({ rootDir }), -- transformerEmptyLine(), -- transformerTagLine(), -- transformerTitle(), -- twoslash !== false -- ? transformerTwoslash({ -- explicitTrigger: true, -- renderer: twoslashRenderer(), -- twoslasher, -- twoslashOptions: { -- ...twoslash, -- customTags: [ -- 'allowErrors', -- ...(defaultTwoslashOptions.customTags ?? []), -- ...(twoslash.customTags ?? []), -- ], -- compilerOptions: { -- ...(twoslash.compilerOptions ?? {}), -- ...defaultTwoslashOptions.compilerOptions, -- }, -- }, -- }) -- : null, -- transformerSplitIdentifiers(), -- ].filter(Boolean), -- themes: defaultThemes, -- ...markdown?.code, -- }, -- ], -- [ -- rehypeInlineShiki, -- { -- themes: defaultThemes, -- ...markdown?.code, -- }, -- ], -- rehypeShikiDisplayNotation, -- [ -- rehypeAutolinkHeadings, -- { -- behavior: 'append', -- content() { -- return [h('div', { dataAutolinkIcon: true })]; -- }, -- }, -- ], -- ...(markdown?.rehypePlugins || []), -+export const getRehypePlugins = ({ -+ markdown, -+ rootDir = "", -+ twoslash = {}, -+} = {}) => [ -+ rehypeSlug, -+ [ -+ // Patch -+ rehypeShikiFromHighlighter, -+ highlighter, -+ // Patch end -+ { -+ transformers: [ -+ transformerLineNumbers(), -+ transformerNotationDiff(), -+ transformerNotationFocus(), -+ transformerNotationHighlight(), -+ transformerNotationWordHighlight(), -+ transformerNotationInclude({ rootDir }), -+ transformerEmptyLine(), -+ transformerTagLine(), -+ transformerTitle(), -+ twoslash !== false -+ ? transformerTwoslash({ -+ explicitTrigger: true, -+ renderer: twoslashRenderer(), -+ twoslasher, -+ twoslashOptions: { -+ ...twoslash, -+ customTags: [ -+ "allowErrors", -+ ...(defaultTwoslashOptions.customTags ?? []), -+ ...(twoslash.customTags ?? []), -+ ], -+ compilerOptions: { -+ ...(twoslash.compilerOptions ?? {}), -+ ...defaultTwoslashOptions.compilerOptions, -+ }, -+ }, -+ }) -+ : null, -+ transformerSplitIdentifiers(), -+ ].filter(Boolean), -+ themes: defaultThemes, -+ ...markdown?.code, -+ }, -+ ], -+ [ -+ rehypeInlineShiki, -+ { -+ langs, -+ themes: defaultThemes, -+ ...markdown?.code, -+ }, -+ ], -+ rehypeShikiDisplayNotation, -+ [ -+ rehypeAutolinkHeadings, -+ { -+ behavior: "append", -+ content() { -+ return [h("div", { dataAutolinkIcon: true })]; -+ }, -+ }, -+ ], -+ ...(markdown?.rehypePlugins || []), - ]; - export async function mdx() { -- const { config } = await resolveVocsConfig(); -- const { markdown, rootDir, twoslash } = config; -- const remarkPlugins = getRemarkPlugins({ markdown }); -- const rehypePlugins = getRehypePlugins({ markdown, rootDir, twoslash }); -- return [ -- mdxPlugin({ -- providerImportSource: 'vocs/mdx-react', -- remarkPlugins, -- rehypePlugins, -- }), -- ]; -+ const { config } = await resolveVocsConfig(); -+ const { markdown, rootDir, twoslash } = config; -+ const remarkPlugins = getRemarkPlugins({ markdown }); -+ const rehypePlugins = getRehypePlugins({ markdown, rootDir, twoslash }); -+ return [ -+ mdxPlugin({ -+ providerImportSource: "vocs/mdx-react", -+ remarkPlugins, -+ rehypePlugins, -+ }), -+ ]; - } - //# sourceMappingURL=mdx.js.map diff --git a/_lib/vite/plugins/rehype/inline-shiki.js b/_lib/vite/plugins/rehype/inline-shiki.js -index 1e2a7426a5a80afa1bdff24388a8849b49e04f2e..20332be7836e5ca932f59747c3de697430b1d711 100644 +index 1e2a7426a5a80afa1bdff24388a8849b49e04f2e..a8394cddd3968f6c153eed58481a80fb5a8e1b09 100644 --- a/_lib/vite/plugins/rehype/inline-shiki.js +++ b/_lib/vite/plugins/rehype/inline-shiki.js -@@ -1,30 +1,51 @@ --import { bundledLanguages, getSingletonHighlighter } from 'shiki'; --import { visit } from 'unist-util-visit'; -+import { bundledLanguages, getSingletonHighlighter } from "shiki"; -+import { visit } from "unist-util-visit"; - const inlineShikiRegex = /(.*){:(.*)}$/; - let promise; - export const rehypeInlineShiki = function (options = {}) { -- const themeNames = ('themes' in options ? Object.values(options.themes) : [options.theme]).filter(Boolean); -- const langs = options.langs || Object.keys(bundledLanguages); -- return async function (tree) { -- if (!promise) -- promise = getSingletonHighlighter({ -- themes: themeNames, -- langs, -- }); -- const highlighter = await promise; -- return visit(tree, 'element', (node, index, parent) => { +@@ -13,16 +13,33 @@ export const rehypeInlineShiki = function (options = {}) { + }); + const highlighter = await promise; + return visit(tree, 'element', (node, index, parent) => { - if (node.tagName !== 'code') - return; -- const match = node.children[0]?.value?.match(inlineShikiRegex); ++ if (node.tagName !== "code") return; ++ // ignore math ++ const classes = Array.isArray(node.properties.className) ++ ? node.properties.className ++ : []; ++ const languageMath = classes.includes("language-math"); ++ const mathDisplay = classes.includes("math-display"); ++ const mathInline = classes.includes("math-inline"); ++ if (languageMath || mathDisplay || mathInline) { ++ return; ++ } ++ + const match = node.children[0]?.value?.match(inlineShikiRegex); - if (!match) - return; - const [, code, lang] = match; - const hast = highlighter.codeToHast(code, { ...options, lang }); -- const inlineCode = hast.children[0].children[0]; ++ let hast; ++ if (match) { ++ const [, code, lang] = match; ++ hast = highlighter.codeToHast(code, { ...options, lang }); ++ } else { ++ if (!node.children[0] || node.children[0].type !== "text") return; ++ const code = node.children[0].value; ++ hast = highlighter.codeToHast(code, { ++ ...options, ++ lang: "cairo", ++ }); ++ } + const inlineCode = hast.children[0].children[0]; - if (!inlineCode) - return; -- parent?.children.splice(index ?? 0, 1, inlineCode); -+ const themeNames = ( -+ "themes" in options ? Object.values(options.themes) : [options.theme] -+ ).filter(Boolean); -+ const langs = options.langs || Object.keys(bundledLanguages); -+ return async function (tree) { -+ if (!promise) -+ promise = getSingletonHighlighter({ -+ themes: themeNames, -+ langs, -+ }); -+ const highlighter = await promise; -+ return visit(tree, "element", (node, index, parent) => { -+ if (node.tagName !== "code") return; -+ // ignore math -+ const classes = Array.isArray(node.properties.className) -+ ? node.properties.className -+ : []; -+ const languageMath = classes.includes("language-math"); -+ const mathDisplay = classes.includes("math-display"); -+ const mathInline = classes.includes("math-inline"); -+ if (languageMath || mathDisplay || mathInline) { -+ return; -+ } -+ -+ const match = node.children[0]?.value?.match(inlineShikiRegex); -+ if (match) { -+ const [, code, lang] = match; -+ const hast = highlighter.codeToHast(code, { ...options, lang }); -+ const inlineCode = hast.children[0].children[0]; -+ if (!inlineCode) return; -+ parent?.children.splice(index ?? 0, 1, inlineCode); -+ } else { -+ if (!node.children[0] || node.children[0].type !== "text") return; -+ const code = node.children[0].value; -+ const hast = highlighter.codeToHast(code, { -+ ...options, -+ lang: "Cairo", ++ if (!inlineCode) return; + parent?.children.splice(index ?? 0, 1, inlineCode); }); -- }; -+ const inlineCode = hast.children[0].children[0]; -+ if (!inlineCode) return; -+ parent?.children.splice(index ?? 0, 1, inlineCode); -+ } -+ }); -+ }; - }; - //# sourceMappingURL=inline-shiki.js.map + }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7c6d19f9..575a437a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,9 +4,17 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + vocs>shiki: ^1.23.0 + vocs>@shikijs/core: ^1.23.0 + vocs>@shikijs/types: ^1.23.0 + vocs>@shikijs/rehype: ^1.23.0 + vocs>@shikijs/twoslash: ^1.23.0 + vocs>@shikijs/transformers: ^1.23.0 + patchedDependencies: vocs: - hash: p6z7kxjkom4q2uemolyyrxbtiy + hash: 7wumpnts656yvepr4seo2mjn34 path: patches/vocs.patch importers: @@ -33,7 +41,7 @@ importers: version: 5.6.3 vocs: specifier: 1.0.0-alpha.62 - version: 1.0.0-alpha.62(patch_hash=p6z7kxjkom4q2uemolyyrxbtiy)(@types/node@22.8.6)(@types/react-dom@18.3.1)(@types/react@18.3.12)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.24.3)(typescript@5.6.3) + version: 1.0.0-alpha.62(patch_hash=7wumpnts656yvepr4seo2mjn34)(@types/node@22.8.6)(@types/react-dom@18.3.1)(@types/react@18.3.12)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.24.3)(typescript@5.6.3) devDependencies: '@types/react-dom': specifier: ^18.3.1 @@ -981,26 +989,26 @@ packages: cpu: [x64] os: [win32] - '@shikijs/core@1.22.2': - resolution: {integrity: sha512-bvIQcd8BEeR1yFvOYv6HDiyta2FFVePbzeowf5pPS1avczrPK+cjmaxxh0nx5QzbON7+Sv0sQfQVciO7bN72sg==} + '@shikijs/core@1.23.1': + resolution: {integrity: sha512-NuOVgwcHgVC6jBVH5V7iblziw6iQbWWHrj5IlZI3Fqu2yx9awH7OIQkXIcsHsUmY19ckwSgUMgrqExEyP5A0TA==} - '@shikijs/engine-javascript@1.22.2': - resolution: {integrity: sha512-iOvql09ql6m+3d1vtvP8fLCVCK7BQD1pJFmHIECsujB0V32BJ0Ab6hxk1ewVSMFA58FI0pR2Had9BKZdyQrxTw==} + '@shikijs/engine-javascript@1.23.1': + resolution: {integrity: sha512-i/LdEwT5k3FVu07SiApRFwRcSJs5QM9+tod5vYCPig1Ywi8GR30zcujbxGQFJHwYD7A5BUqagi8o5KS+LEVgBg==} - '@shikijs/engine-oniguruma@1.22.2': - resolution: {integrity: sha512-GIZPAGzQOy56mGvWMoZRPggn0dTlBf1gutV5TdceLCZlFNqWmuc7u+CzD0Gd9vQUTgLbrt0KLzz6FNprqYAxlA==} + '@shikijs/engine-oniguruma@1.23.1': + resolution: {integrity: sha512-KQ+lgeJJ5m2ISbUZudLR1qHeH3MnSs2mjFg7bnencgs5jDVPeJ2NVDJ3N5ZHbcTsOIh0qIueyAJnwg7lg7kwXQ==} - '@shikijs/rehype@1.22.2': - resolution: {integrity: sha512-A0RHgiYR5uiHvddwHehBN9j8PhOvfT6/GebSTWrapur6M+fD/4i3mlfUv7aFK4b+4GQ1R42L8fC5N98whZjNcg==} + '@shikijs/rehype@1.23.1': + resolution: {integrity: sha512-PH5bpMDEc4nBP62Ci3lUqkxBWRTm8cdE+eY9er5QD50jAWQxhXcc1Aeax1AlyrASrtjTwCkI22M6N9iSn5p+bQ==} - '@shikijs/transformers@1.22.2': - resolution: {integrity: sha512-8f78OiBa6pZDoZ53lYTmuvpFPlWtevn23bzG+azpPVvZg7ITax57o/K3TC91eYL3OMJOO0onPbgnQyZjRos8XQ==} + '@shikijs/transformers@1.23.1': + resolution: {integrity: sha512-yQ2Cn0M9i46p30KwbyIzLvKDk+dQNU+lj88RGO0XEj54Hn4Cof1bZoDb9xBRWxFE4R8nmK63w7oHnJwvOtt0NQ==} - '@shikijs/twoslash@1.22.2': - resolution: {integrity: sha512-4R3A7aH/toZgtlveXHKk01nIsvn8hjAfPJ1aT550zcV4qK6vK/tfaEyYtaljOaY1wig2l5+8sKjNSEz3PcSiEw==} + '@shikijs/twoslash@1.23.1': + resolution: {integrity: sha512-Qj/+CGAF6TdcRjPDQn1bxyKD8ejnV7VJLqCHzob1uCbwQlJTI5z0gUVAgpqS55z4vdV1Mrx2IpCTl9glhC0l3A==} - '@shikijs/types@1.22.2': - resolution: {integrity: sha512-NCWDa6LGZqTuzjsGfXOBWfjS/fDIbDdmVDug+7ykVe1IKT4c1gakrvlfFYp5NhAXH/lyqLM8wsAPo5wNy73Feg==} + '@shikijs/types@1.23.1': + resolution: {integrity: sha512-98A5hGyEhzzAgQh2dAeHKrWW4HfCMeoFER2z16p5eJ+vmPeF6lZ/elEne6/UCU551F/WqkopqRsr1l2Yu6+A0g==} '@shikijs/vscode-textmate@9.3.0': resolution: {integrity: sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==} @@ -1375,6 +1383,9 @@ packages: electron-to-chromium@1.5.50: resolution: {integrity: sha512-eMVObiUQ2LdgeO1F/ySTXsvqvxb6ZH2zPGaMYsWzRDdOddUa77tdmI0ltg+L16UpbWdhPmuF3wIQYyQq65WfZw==} + emoji-regex-xs@1.0.0: + resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} + emoji-regex@10.4.0: resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} @@ -2042,8 +2053,8 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} - oniguruma-to-js@0.4.3: - resolution: {integrity: sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ==} + oniguruma-to-es@0.4.1: + resolution: {integrity: sha512-rNcEohFz095QKGRovP/yqPIKc+nP+Sjs4YTHMv33nMePGKrq/r2eu9Yh4646M5XluGJsUnmwoXuiXE69KDs+fQ==} ora@7.0.1: resolution: {integrity: sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==} @@ -2282,8 +2293,14 @@ packages: regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - regex@4.4.0: - resolution: {integrity: sha512-uCUSuobNVeqUupowbdZub6ggI5/JZkYyJdDogddJr60L764oxC2pMZov1fQ3wM9bdyzUILDG+Sqx6NAKAz9rKQ==} + regex-recursion@4.2.1: + resolution: {integrity: sha512-QHNZyZAeKdndD1G3bKAbBEKOSSK4KOHQrAJ01N1LJeb0SoH4DJIeFhp0uUpETgONifS4+P3sOgoA1dhzgrQvhA==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@5.0.2: + resolution: {integrity: sha512-/pczGbKIQgfTMRV0XjABvc5RzLqQmwqxLHdQao2RTXPk+pmTXB2P0IaUHYdYyk412YLwUIkaeMd5T+RzVgTqnQ==} rehype-autolink-headings@7.1.0: resolution: {integrity: sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==} @@ -2379,8 +2396,8 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shiki@1.22.2: - resolution: {integrity: sha512-3IZau0NdGKXhH2bBlUk4w1IHNxPh6A5B2sUpyY+8utLu2j/h1QpFkAaUA1bAMxOWWGtTWcAh531vnS4NJKS/lA==} + shiki@1.23.1: + resolution: {integrity: sha512-8kxV9TH4pXgdKGxNOkrSMydn1Xf6It8lsle0fiqxf7a1149K1WGtdOu3Zb91T5r1JpvRPxqxU3C2XdZZXQnrig==} signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -3489,49 +3506,49 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.24.3': optional: true - '@shikijs/core@1.22.2': + '@shikijs/core@1.23.1': dependencies: - '@shikijs/engine-javascript': 1.22.2 - '@shikijs/engine-oniguruma': 1.22.2 - '@shikijs/types': 1.22.2 + '@shikijs/engine-javascript': 1.23.1 + '@shikijs/engine-oniguruma': 1.23.1 + '@shikijs/types': 1.23.1 '@shikijs/vscode-textmate': 9.3.0 '@types/hast': 3.0.4 hast-util-to-html: 9.0.3 - '@shikijs/engine-javascript@1.22.2': + '@shikijs/engine-javascript@1.23.1': dependencies: - '@shikijs/types': 1.22.2 + '@shikijs/types': 1.23.1 '@shikijs/vscode-textmate': 9.3.0 - oniguruma-to-js: 0.4.3 + oniguruma-to-es: 0.4.1 - '@shikijs/engine-oniguruma@1.22.2': + '@shikijs/engine-oniguruma@1.23.1': dependencies: - '@shikijs/types': 1.22.2 + '@shikijs/types': 1.23.1 '@shikijs/vscode-textmate': 9.3.0 - '@shikijs/rehype@1.22.2': + '@shikijs/rehype@1.23.1': dependencies: - '@shikijs/types': 1.22.2 + '@shikijs/types': 1.23.1 '@types/hast': 3.0.4 hast-util-to-string: 3.0.1 - shiki: 1.22.2 + shiki: 1.23.1 unified: 11.0.5 unist-util-visit: 5.0.0 - '@shikijs/transformers@1.22.2': + '@shikijs/transformers@1.23.1': dependencies: - shiki: 1.22.2 + shiki: 1.23.1 - '@shikijs/twoslash@1.22.2(typescript@5.6.3)': + '@shikijs/twoslash@1.23.1(typescript@5.6.3)': dependencies: - '@shikijs/core': 1.22.2 - '@shikijs/types': 1.22.2 + '@shikijs/core': 1.23.1 + '@shikijs/types': 1.23.1 twoslash: 0.2.12(typescript@5.6.3) transitivePeerDependencies: - supports-color - typescript - '@shikijs/types@1.22.2': + '@shikijs/types@1.23.1': dependencies: '@shikijs/vscode-textmate': 9.3.0 '@types/hast': 3.0.4 @@ -3929,6 +3946,8 @@ snapshots: electron-to-chromium@1.5.50: {} + emoji-regex-xs@1.0.0: {} + emoji-regex@10.4.0: {} emoji-regex@8.0.0: {} @@ -5011,9 +5030,11 @@ snapshots: dependencies: mimic-fn: 2.1.0 - oniguruma-to-js@0.4.3: + oniguruma-to-es@0.4.1: dependencies: - regex: 4.4.0 + emoji-regex-xs: 1.0.0 + regex: 5.0.2 + regex-recursion: 4.2.1 ora@7.0.1: dependencies: @@ -5260,7 +5281,15 @@ snapshots: regenerator-runtime@0.14.1: {} - regex@4.4.0: {} + regex-recursion@4.2.1: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@5.0.2: + dependencies: + regex-utilities: 2.3.0 rehype-autolink-headings@7.1.0: dependencies: @@ -5467,12 +5496,12 @@ snapshots: shebang-regex@3.0.0: {} - shiki@1.22.2: + shiki@1.23.1: dependencies: - '@shikijs/core': 1.22.2 - '@shikijs/engine-javascript': 1.22.2 - '@shikijs/engine-oniguruma': 1.22.2 - '@shikijs/types': 1.22.2 + '@shikijs/core': 1.23.1 + '@shikijs/engine-javascript': 1.23.1 + '@shikijs/engine-oniguruma': 1.23.1 + '@shikijs/types': 1.23.1 '@shikijs/vscode-textmate': 9.3.0 '@types/hast': 3.0.4 @@ -5766,7 +5795,7 @@ snapshots: '@types/node': 22.8.6 fsevents: 2.3.3 - vocs@1.0.0-alpha.62(patch_hash=p6z7kxjkom4q2uemolyyrxbtiy)(@types/node@22.8.6)(@types/react-dom@18.3.1)(@types/react@18.3.12)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.24.3)(typescript@5.6.3): + vocs@1.0.0-alpha.62(patch_hash=7wumpnts656yvepr4seo2mjn34)(@types/node@22.8.6)(@types/react-dom@18.3.1)(@types/react@18.3.12)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.24.3)(typescript@5.6.3): dependencies: '@floating-ui/react': 0.26.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@hono/node-server': 1.13.4(hono@3.12.12) @@ -5781,9 +5810,9 @@ snapshots: '@radix-ui/react-navigation-menu': 1.2.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-popover': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-tabs': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@shikijs/rehype': 1.22.2 - '@shikijs/transformers': 1.22.2 - '@shikijs/twoslash': 1.22.2(typescript@5.6.3) + '@shikijs/rehype': 1.23.1 + '@shikijs/transformers': 1.23.1 + '@shikijs/twoslash': 1.23.1(typescript@5.6.3) '@vanilla-extract/css': 1.16.0 '@vanilla-extract/dynamic': 2.1.2 '@vanilla-extract/vite-plugin': 3.9.5(@types/node@22.8.6)(vite@5.4.10(@types/node@22.8.6)) @@ -5823,7 +5852,7 @@ snapshots: remark-mdx-frontmatter: 4.0.0 remark-parse: 11.0.0 serve-static: 1.16.2 - shiki: 1.22.2 + shiki: 1.23.1 tailwindcss: 3.4.14 toml: 3.0.0 twoslash: 0.2.12(typescript@5.6.3) diff --git a/routes.ts b/routes.ts index 1969da23..d8348c7f 100644 --- a/routes.ts +++ b/routes.ts @@ -7,7 +7,6 @@ const config: Sidebar = [ }, { text: "Getting Started", - link: "/getting-started/basics/storage", items: [ // { // text: "Local environment setup" @@ -98,7 +97,6 @@ const config: Sidebar = [ }, { text: "Components", - link: "/components/how_to", items: [ { text: "Components How-To", @@ -120,7 +118,6 @@ const config: Sidebar = [ }, { text: "Applications", - link: "/applications/upgradeable_contract", items: [ { text: " Upgradeable Contract", @@ -174,7 +171,6 @@ const config: Sidebar = [ }, { text: "Advanced concepts", - link: "/advanced-concepts/write_to_any_slot", items: [ { text: "Writing to any storage slot", diff --git a/styles.css b/styles.css index 2ba7b331..4aeb1128 100644 --- a/styles.css +++ b/styles.css @@ -60,48 +60,47 @@ section.vocs_Sidebar_level } /* Make sidebar collapsible */ -.vocs_DocsLayout_gutterLeft { - top: 0; - left: 0; - bottom: 0; -} -.vocs_DocsLayout_gutterLeft { - margin-left: 0; - transition: margin 0.3s ease; -} -.sidebar_hidden .vocs_DocsLayout_gutterLeft { - margin-left: calc(var(--vocs_DocsLayout_leftGutterWidth) * -1); -}; - -.vocs_DocsLayout_content_withSidebar { - margin-left: var(--vocs_DocsLayout_leftGutterWidth) -} -.sidebar_hidden .vocs_DocsLayout_content_withSidebar { - margin-left: 0 !important; -} -.vocs_DocsLayout_content_withSidebar { - transition: margin 300ms ease !important; - max-width: unset; -} - -.vocs_DesktopTopNav_logo a { - margin-top: 2px !important; -} - -.vocs_Sidebar_logo { - padding-top: 0px !important; -} - -.sidebar_toggle { - position: absolute; - top: 0; - right: calc(var(--vocs-topNav_height) * -1); - height: var(--vocs-topNav_height); - width: var(--vocs-topNav_height); -} - -.sidebar_toggle button { - padding: var(--vocs-space_8); +@media screen and (min-width: 1080px) { + .vocs_DocsLayout_gutterLeft { + margin-left: 0; + transition: margin 0.3s ease; + width: var(--vocs-sidebar_width); + } + .sidebar_hidden .vocs_DocsLayout_gutterLeft { + margin-left: calc(var(--vocs-sidebar_width) * -1); + } + + .vocs_DocsLayout_content_withSidebar { + margin-left: var(--vocs-sidebar_width); + } + .sidebar_hidden .vocs_DocsLayout_content_withSidebar { + margin-left: 0 !important; + } + .vocs_DocsLayout_content_withSidebar { + margin-left: var(--vocs-sidebar_width); + transition: margin 300ms ease !important; + max-width: unset; + } + + .vocs_DesktopTopNav_logo a { + margin-top: 2px !important; + } + + .vocs_Sidebar_logo { + padding-top: 0px !important; + } + + .sidebar_toggle { + position: absolute; + top: 0; + right: calc(var(--vocs-topNav_height) * -1); + height: var(--vocs-topNav_height); + width: var(--vocs-topNav_height); + } + + .sidebar_toggle button { + padding: var(--vocs-space_8); + } } /* END sidebar */ @@ -120,3 +119,15 @@ section.vocs_Sidebar_level font-size: var(--vocs-fontSize_20); padding-bottom: 0.875em; } + +.vocs_DesktopTopNav_logoWrapper { + left: 22px; + justify-content: start; +} + +/* Force show theme switcher */ +@media screen and (max-width: 1280px) { + .vocs_DesktopTopNav_hideCompact { + display: block; + } +} From d62e621753e29596cd8e0c9a306be75830cc8bda Mon Sep 17 00:00:00 2001 From: OkoliEvans Date: Fri, 22 Nov 2024 10:02:21 +0100 Subject: [PATCH 08/10] code upgraded to the new Markdown format --- .../custom_signature_validation/Scarb.toml | 2 +- .../src/custom_signature.cairo | 48 +++++----- .../custom_signature_validation/src/lib.cairo | 2 +- .../src/simple_account.cairo | 90 ++++++++++--------- .../custom_validation_signature_scheme.md | 40 +++++++++ 5 files changed, 115 insertions(+), 67 deletions(-) create mode 100644 pages/advanced-concepts/account_abstraction/custom_validation_signature_scheme.md diff --git a/listings/advanced-concepts/custom_signature_validation/Scarb.toml b/listings/advanced-concepts/custom_signature_validation/Scarb.toml index 3b7e2452..4117f002 100644 --- a/listings/advanced-concepts/custom_signature_validation/Scarb.toml +++ b/listings/advanced-concepts/custom_signature_validation/Scarb.toml @@ -1,6 +1,6 @@ [package] name = "custom_signature_validation" -version = "0.1.0" +version.workspace = true edition = "2023_11" # See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html diff --git a/listings/advanced-concepts/custom_signature_validation/src/custom_signature.cairo b/listings/advanced-concepts/custom_signature_validation/src/custom_signature.cairo index 310c15e0..3b5b2be6 100644 --- a/listings/advanced-concepts/custom_signature_validation/src/custom_signature.cairo +++ b/listings/advanced-concepts/custom_signature_validation/src/custom_signature.cairo @@ -4,15 +4,16 @@ use starknet::secp256_trait::{ }; use starknet::secp256r1::Secp256r1Point; use starknet::secp256k1::Secp256k1Point; -use starknet::{ EthAddress, eth_signature::is_eth_signature_valid }; +use starknet::{EthAddress, eth_signature::is_eth_signature_valid}; use core::traits::TryInto; const SECP256R1_SIGNER_TYPE: felt252 = 'Secp256r1 Signer'; const SECP256K1_SIGNER_TYPE: felt252 = 'Secp256k1 Signer'; -const SECP_256_R1_HALF: u256 = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551 / 2; -const SECP_256_K1_HALF: u256 = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 / 2; - +const SECP_256_R1_HALF: u256 = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551 + / 2; +const SECP_256_K1_HALF: u256 = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 + / 2; #[derive(Drop, Copy, PartialEq, Serde, Default)] @@ -24,7 +25,7 @@ enum SignerType { #[derive(Drop, Copy, Serde)] enum SignerSignature { - Secp256r1: (Secp256r1Signer, Secp2newAccount56Signature), + Secp256r1: (Secp256r1Signer, Secp256Signature), Secp256k1: (Secp256k1Signer, Secp256Signature), } @@ -39,7 +40,7 @@ struct Secp256r1Signer { pubkey: NonZero } -#[derive(Drop, Copy, PartialEq)] +#[derive(Drop, Copy, PartialEq)] struct Secp256k1Signer { pubkey_hash: EthAddress } @@ -55,19 +56,19 @@ impl Secp256k1SignerSerde of Serde { #[inline(always)] fn deserialize(ref serialized: Span) -> Option { let pubkey_hash = Serde::::deserialize(ref serialized)?; - assert(pubkey_hash.address != 0, 'zero pub key hash' ); + assert(pubkey_hash.address != 0, 'zero pub key hash'); Option::Some(Secp256k1Signer { pubkey_hash }) } } -// To check if secp256k1 and secp256r1 signatures are valid +// ANCHOR: is_valid_signature +///@notice Check if secp256k1 and secp256r1 signatures are valid trait Secp256SignatureTrait { fn is_valid_signature(self: SignerSignature, hash: felt252) -> bool; fn signer(self: SignerSignature) -> Signer; - } - - // ANCHOR: is_valid_signature -// To check if secp256k1 and secp256r1 signatures are valid +} + +///@notice Check if secp256k1 and secp256r1 signatures are valid impl Secp256SignatureImpl of Secp256SignatureTrait { #[inline(always)] fn is_valid_signature(self: SignerSignature, hash: felt252) -> bool { @@ -80,8 +81,8 @@ impl Secp256SignatureImpl of Secp256SignatureTrait { )) => is_valid_secp256k1_signature(hash.into(), signer.pubkey_hash.into(), signature), } } - - + + #[inline(always)] fn signer(self: SignerSignature) -> Signer { match self { @@ -91,20 +92,25 @@ impl Secp256SignatureImpl of Secp256SignatureTrait { } } -// To validate secp256k1 signature +///@notice To validate secp256k1 signature #[inline(always)] -fn is_valid_secp256k1_signature(hash: u256, pubkey_hash: EthAddress, signature: Secp256Signature) -> bool { +fn is_valid_secp256k1_signature( + hash: u256, pubkey_hash: EthAddress, signature: Secp256Signature +) -> bool { assert(signature.s <= SECP_256_K1_HALF, 'malleable signature'); is_eth_signature_valid(hash, signature, pubkey_hash).is_ok() } -// To validate secp256r1 signature +///@notice To validate secp256r1 signature #[inline(always)] -fn is_valid_secp256r1_signature(hash: u256, signer: Secp256r1Signer, signature: Secp256Signature) -> bool { - assert(is_signature_entry_valid::(signature.s), 'invalid s-value'); +fn is_valid_secp256r1_signature( + hash: u256, signer: Secp256r1Signer, signature: Secp256Signature +) -> bool { + assert(is_signature_entry_valid::(signature.s), 'invalid s-value'); assert(is_signature_entry_valid::(signature.r), 'invalid r-value'); assert(signature.s <= SECP_256_R1_HALF, 'malleable signature'); - let recovered_pubkey = recover_public_key::(hash, signature).expect('invalid sign format'); + let recovered_pubkey = recover_public_key::(hash, signature) + .expect('invalid sign format'); let (recovered_signer, _) = recovered_pubkey.get_coordinates().expect('invalid sig format'); recovered_signer == signer.pubkey.into() } @@ -135,5 +141,5 @@ impl U256TryIntoSignerType of TryInto { } } } +// ANCHOR_END: custom_signature_scheme -// ANCHOR_END: custom_signature_scheme \ No newline at end of file diff --git a/listings/advanced-concepts/custom_signature_validation/src/lib.cairo b/listings/advanced-concepts/custom_signature_validation/src/lib.cairo index 1e39ca20..b51fb9c6 100644 --- a/listings/advanced-concepts/custom_signature_validation/src/lib.cairo +++ b/listings/advanced-concepts/custom_signature_validation/src/lib.cairo @@ -1 +1 @@ -mod custom_signature; \ No newline at end of file +mod custom_signature; diff --git a/listings/advanced-concepts/custom_signature_validation/src/simple_account.cairo b/listings/advanced-concepts/custom_signature_validation/src/simple_account.cairo index 0f32f6eb..1fd8fb20 100644 --- a/listings/advanced-concepts/custom_signature_validation/src/simple_account.cairo +++ b/listings/advanced-concepts/custom_signature_validation/src/simple_account.cairo @@ -1,51 +1,53 @@ - #[starknet::contract] mod SimpleAccount { - - -// ANCHOR: validate -fn __validate__(ref self: ContractState, calls: Array) -> felt252 { - let exec_info = get_execution_info().unbox(); - let tx_info = exec_info.tx_info.unbox(); - assert_only_protocol(exec_info.caller_address); - assert_correct_invoke_version(tx_info.version); - assert(tx_info.paymaster_data.is_empty(), 'unsupported-paymaster'); - if self.session.is_session(tx_info.signature) { - self.session.assert_valid_session(calls.span(), tx_info.transaction_hash, tx_info.signature,); - } else { - self - .assert_valid_calls_and_signature( - calls.span(), - tx_info.transaction_hash, - tx_info.signature, - is_from_outside: false, - account_address: exec_info.contract_address, - ); + // ANCHOR: validate + fn __validate__(ref self: ContractState, calls: Array) -> felt252 { + let exec_info = get_execution_info().unbox(); + let tx_info = exec_info.tx_info.unbox(); + assert_only_protocol(exec_info.caller_address); + assert_correct_invoke_version(tx_info.version); + assert(tx_info.paymaster_data.is_empty(), 'unsupported-paymaster'); + if self.session.is_session(tx_info.signature) { + self + .session + .assert_valid_session(calls.span(), tx_info.transaction_hash, tx_info.signature,); + } else { + self + .assert_valid_calls_and_signature( + calls.span(), + tx_info.transaction_hash, + tx_info.signature, + is_from_outside: false, + account_address: exec_info.contract_address, + ); + } + VALIDATED } - VALIDATED -} -// ANCHOR_END: validate + // ANCHOR_END: validate -// ANCHOR: execute -fn __execute__(ref self: ContractState, calls: Array) -> Array> { - self.reentrancy_guard.start(); - let exec_info = get_execution_info().unbox(); - let tx_info = exec_info.tx_info.unbox(); - assert_only_protocol(exec_info.caller_address); - assert_correct_invoke_version(tx_info.version); - let signature = tx_info.signature; - if self.session.is_session(signature) { - let session_timestamp = *signature[1]; - // can call unwrap safely as the session has already been deserialized - let session_timestamp_u64 = session_timestamp.try_into().unwrap(); - assert(session_timestamp_u64 >= exec_info.block_info.unbox().block_timestamp, 'session/expired'); - } + // ANCHOR: execute + fn __execute__(ref self: ContractState, calls: Array) -> Array> { + self.reentrancy_guard.start(); + let exec_info = get_execution_info().unbox(); + let tx_info = exec_info.tx_info.unbox(); + assert_only_protocol(exec_info.caller_address); + assert_correct_invoke_version(tx_info.version); + let signature = tx_info.signature; + if self.session.is_session(signature) { + let session_timestamp = *signature[1]; + // can call unwrap safely as the session has already been deserialized + let session_timestamp_u64 = session_timestamp.try_into().unwrap(); + assert( + session_timestamp_u64 >= exec_info.block_info.unbox().block_timestamp, + 'session/expired' + ); + } - let retdata = execute_multicall(calls.span()); + let retdata = execute_multicall(calls.span()); - self.emit(TransactionExecuted { hash: tx_info.transaction_hash, response: retdata.span() }); - self.reentrancy_guard.end(); - retdata + self.emit(TransactionExecuted { hash: tx_info.transaction_hash, response: retdata.span() }); + self.reentrancy_guard.end(); + retdata + } + // ANCHOR_END: execute } -// ANCHOR_END: execute -} \ No newline at end of file diff --git a/pages/advanced-concepts/account_abstraction/custom_validation_signature_scheme.md b/pages/advanced-concepts/account_abstraction/custom_validation_signature_scheme.md new file mode 100644 index 00000000..76d3fe89 --- /dev/null +++ b/pages/advanced-concepts/account_abstraction/custom_validation_signature_scheme.md @@ -0,0 +1,40 @@ +# Custom Signature Validation Scheme + +Digital signatures are a fundamental aspect of modern cryptography used to verify the authenticity and integrity of digital messages or transactions. They are based on public-key cryptography, where a pair of keys (a public key and a private key) are used to create and verify signatures. + +Private keys are kept secret and secured by the owner. They are used to sign data such as messages or transactions, which can be verified by anyone with the public key. + +Account Abstraction is a native feature on Starknet, this makes it possible for anyone to implement custom signature schemes and use it to validate transactions with the implementation of the signature validation logic. + +### The Concepts of Accounts and Signers + +i. **Account:** All accounts are smart contracts that can hold assets and execute transactions on Starknet, these account contracts however must implement some specific methods outlined in [SNIP-6.](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-6.md) + +ii. **Signers:** These are responsible for digitally signing transactions and also provide the authorization needed to initiate transactions. Transaction signing is done offchain on Starknet. + +Digital signatures are cryptographic proofs that transactions are authorized by corresponding accounts. + +### Signature validation on Starknet + +On Starknet transactions are signed offchain, which means that the signature process happens outside the blockchain. The signed transaction is then submitted to Starknet network for verification and execution. Read more: [Starknet-js docs](https://www.starknetjs.com/docs/guides/signature/) + +All Account contracts on Starknet must implement the SNIP-6 standard as already stated. The methods outlined in the standard provide means to move offchain signatures onchain and execute them. More details below: + +```cairo +// [!include ~/listings/advanced-concepts/simple_account/src/simple_account.cairo] +``` + +On Ethereum, only **one** signature scheme is used: ECDSA. It makes Ethereum more secure but less flexible. +Custom signature validation used on Starknet gives room for more flexibility, however, care must be taken to validate all signatures meticulously to ensure that: + +a. the message has not been altered. +b. the signer owns the private key corresponding to the public key. + + +### Custom signature validation sample + +The example below shows a sample validation of `Secp256r1` and `Secp256k1` signature schemes: + +```cairo +// [!include ~/listings/advanced-concepts/custom_signature_validation/src/custom_signature.cairo:is_valid_signature] +``` \ No newline at end of file From f7438dcc41e3f840fdf96a07b36a444437fee5e7 Mon Sep 17 00:00:00 2001 From: OkoliEvans Date: Fri, 22 Nov 2024 10:16:12 +0100 Subject: [PATCH 09/10] code upgraded to the new Markdown format --- .../custom_signature_validation.md | 42 ------------------- 1 file changed, 42 deletions(-) delete mode 100644 src/advanced-concepts/custom_signature_validation.md diff --git a/src/advanced-concepts/custom_signature_validation.md b/src/advanced-concepts/custom_signature_validation.md deleted file mode 100644 index 9e0a42a0..00000000 --- a/src/advanced-concepts/custom_signature_validation.md +++ /dev/null @@ -1,42 +0,0 @@ -# Custom Signature Validation Scheme - -Digital signatures are a fundamental aspect of modern cryptography used to verify the authenticity and integrity of digital messages or transactions. They are based on public-key cryptography, where a pair of keys (a public key and a private key) are used to create and verify signatures. - -Private keys are kept secret and secured by the owner. They are used to sign data such as messages or transactions, which can be verified by anyone with the public key. - -Account Abstraction is a native feature on Starknet, this makes it possible for anyone to implement custom signature schemes and use it to validate transactions with the implementation of the signature validation logic. - -### The Concepts of Accounts and Signers - -i. **Account:** All accounts are smart contracts that can hold assets and execute transactions on Starknet, these account contracts however must implement some specific methods outlined in [SNIP-6.](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-6.md) - -ii. **Signers:** These are responsible for digitally signing transactions and also provide the authorization needed to initiate transactions. Transaction signing is done offchain on Starknet. - -Digital signatures are cryptographic proofs that transactions are authorized by corresponding accounts. - -### Signature validation on Starknet - -On Starknet transactions are signed offchain, which means that the signature process happens outside the blockchain. The signed transaction is then submitted to Starknet network for verification and execution. Read more: [Starknet-js docs](https://www.starknetjs.com/docs/guides/signature/) - -All Account contracts on Starknet must implement the SNIP-6 standard as mentioned earlier. The methods outlined in the standard provide means to move offchain signatures onchain and execute them. - -`is_valid_signature` returns true if the signature is valid, `__validate__` validates the signature and marks them as 'VALIDATED', while `__execute__` executes the validated transaction. Sample implementation of SNIP-6 standard: - -```rust -{{#rustdoc_include ../../listings/advanced-concepts/simple_account/src/simple_account.cairo}} -``` - -On Ethereum, only **one** signature scheme is used: ECDSA. It makes Ethereum more secure but less flexible. -Custom signature validation used on Starknet gives room for more flexibility, however, care must be taken to validate all signatures meticulously to ensure that: - -a. the message has not been altered. -b. the signer owns the private key corresponding to the public key. - - -### Custom signature validation sample - -The example below shows a sample validation of `Secp256r1` and `Secp256k1` signature schemes: - -```rust -{{#rustdoc_include ../../listings/advanced-concepts/custom_signature_validation/src/custom_signature.cairo:is_valid_signature}} -``` From df9a60e5832b697886cbbb2ccce83a5876b88570 Mon Sep 17 00:00:00 2001 From: OkoliEvans Date: Fri, 22 Nov 2024 11:27:31 +0100 Subject: [PATCH 10/10] code upgraded to the new Markdown format --- .../src/simple_account.cairo | 53 ------------------- 1 file changed, 53 deletions(-) delete mode 100644 listings/advanced-concepts/custom_signature_validation/src/simple_account.cairo diff --git a/listings/advanced-concepts/custom_signature_validation/src/simple_account.cairo b/listings/advanced-concepts/custom_signature_validation/src/simple_account.cairo deleted file mode 100644 index 1fd8fb20..00000000 --- a/listings/advanced-concepts/custom_signature_validation/src/simple_account.cairo +++ /dev/null @@ -1,53 +0,0 @@ -#[starknet::contract] -mod SimpleAccount { - // ANCHOR: validate - fn __validate__(ref self: ContractState, calls: Array) -> felt252 { - let exec_info = get_execution_info().unbox(); - let tx_info = exec_info.tx_info.unbox(); - assert_only_protocol(exec_info.caller_address); - assert_correct_invoke_version(tx_info.version); - assert(tx_info.paymaster_data.is_empty(), 'unsupported-paymaster'); - if self.session.is_session(tx_info.signature) { - self - .session - .assert_valid_session(calls.span(), tx_info.transaction_hash, tx_info.signature,); - } else { - self - .assert_valid_calls_and_signature( - calls.span(), - tx_info.transaction_hash, - tx_info.signature, - is_from_outside: false, - account_address: exec_info.contract_address, - ); - } - VALIDATED - } - // ANCHOR_END: validate - - // ANCHOR: execute - fn __execute__(ref self: ContractState, calls: Array) -> Array> { - self.reentrancy_guard.start(); - let exec_info = get_execution_info().unbox(); - let tx_info = exec_info.tx_info.unbox(); - assert_only_protocol(exec_info.caller_address); - assert_correct_invoke_version(tx_info.version); - let signature = tx_info.signature; - if self.session.is_session(signature) { - let session_timestamp = *signature[1]; - // can call unwrap safely as the session has already been deserialized - let session_timestamp_u64 = session_timestamp.try_into().unwrap(); - assert( - session_timestamp_u64 >= exec_info.block_info.unbox().block_timestamp, - 'session/expired' - ); - } - - let retdata = execute_multicall(calls.span()); - - self.emit(TransactionExecuted { hash: tx_info.transaction_hash, response: retdata.span() }); - self.reentrancy_guard.end(); - retdata - } - // ANCHOR_END: execute -}