From 29517476c3c6a73d97bd86122de3425533b27f2a Mon Sep 17 00:00:00 2001 From: MSG <59928086+MSghais@users.noreply.github.com> Date: Mon, 3 Feb 2025 07:07:41 +0100 Subject: [PATCH] Feat/aa nostr (#457) * init nostr AA * continue building * start fix stuff etc * fixing stuff3 * fix aa --- .../src/indexer/tip-service.indexer.ts | 23 +- .../interfaces/tip-claim.interface.ts | 14 +- .../afk/.snfoundry_cache/.prev_tests_failed | 6 +- onchain/cairo/afk/src/account.cairo | 1 + .../cairo/afk/src/account/nostr_account.cairo | 597 ++++++++++++++++++ onchain/cairo/afk/src/bip340.cairo | 50 +- onchain/cairo/afk/src/lib.cairo | 4 + 7 files changed, 657 insertions(+), 38 deletions(-) create mode 100644 onchain/cairo/afk/src/account.cairo create mode 100644 onchain/cairo/afk/src/account/nostr_account.cairo diff --git a/apps/nestjs-indexer/src/indexer/tip-service.indexer.ts b/apps/nestjs-indexer/src/indexer/tip-service.indexer.ts index f0aa5cb91..76d97863e 100644 --- a/apps/nestjs-indexer/src/indexer/tip-service.indexer.ts +++ b/apps/nestjs-indexer/src/indexer/tip-service.indexer.ts @@ -105,9 +105,14 @@ export class TipServiceIndexer { } private getAddress(addressFelt: IFieldElement) { - return validateAndParseAddress( - `0x${FieldElement.toBigInt(addressFelt).toString(16)}`, - ) as ContractAddress; + try { + return validateAndParseAddress( + `0x${FieldElement.toBigInt(addressFelt).toString(16)}`, + ) as ContractAddress; + } catch (error) { + this.logger.error(error); + } + } private getU256ToHex(lowFelt: IFieldElement, highFelt: IFieldElement) { @@ -259,12 +264,12 @@ export class TipServiceIndexer { amountLow, amountHigh, contractAddressFelt, + gasTokenAddressFelt, gasAmountLow, gasAmountHigh, - gasTokenAddressFelt, ] = event.data; - const amount = this.uint256ToAmount(amountLow, amountHigh); + // const amount = this.uint256ToAmount(amountLow, amountHigh); const tokenAddress = this.getAddress(contractAddressFelt); const gasAmount = this.uint256ToAmount(gasAmountLow, gasAmountHigh); const gasTokenAddress = this.getAddress(gasTokenAddressFelt); @@ -275,7 +280,7 @@ export class TipServiceIndexer { sender, nostrRecipient, starknetRecipient, - amount, + // amount, tokenAddress, gasTokenAddress, gasAmount, @@ -294,8 +299,6 @@ export class TipServiceIndexer { transaction: starknet.ITransaction, ) { try { - - } catch (error) { this.logger.error(error); const commonTxData = this.getTxData(header, transaction); /* eslint-disable @typescript-eslint/no-unused-vars */ @@ -328,6 +331,10 @@ export class TipServiceIndexer { }; await this.tipService.updateCancel(data); + + } catch (error) { + this.logger.error(error); + } } diff --git a/apps/nestjs-indexer/src/services/tip-service/interfaces/tip-claim.interface.ts b/apps/nestjs-indexer/src/services/tip-service/interfaces/tip-claim.interface.ts index cc139bb4d..15b632843 100644 --- a/apps/nestjs-indexer/src/services/tip-service/interfaces/tip-claim.interface.ts +++ b/apps/nestjs-indexer/src/services/tip-service/interfaces/tip-claim.interface.ts @@ -5,11 +5,11 @@ export interface TipClaim { blockTimestamp: Date; transactionHash: string; depositId: string; - sender: string; - nostrRecipient: string; - starknetRecipient: string; - amount: number; - tokenAddress: string; - gasTokenAddress: string; - gasAmount: number; + sender?: string; + nostrRecipient?: string; + starknetRecipient?: string; + amount?: number; + tokenAddress?: string; + gasTokenAddress?: string; + gasAmount?: number; } diff --git a/onchain/cairo/afk/.snfoundry_cache/.prev_tests_failed b/onchain/cairo/afk/.snfoundry_cache/.prev_tests_failed index 94718de52..ea8e178df 100644 --- a/onchain/cairo/afk/.snfoundry_cache/.prev_tests_failed +++ b/onchain/cairo/afk/.snfoundry_cache/.prev_tests_failed @@ -1,6 +1,6 @@ afk::bip340::tests::test_20 -afk::social::deposit::tests::claim_incorrect_gas_amount -afk::social::deposit::tests::deposit_claim_gas_fee -afk::social::deposit::tests::deposit_claim afk::bip340::tests::test_generate_sign_and_verify +afk::social::deposit::tests::deposit_claim afk::social::deposit::tests::deposit_with_known_starknet_recipient +afk::social::deposit::tests::deposit_claim_gas_fee +afk::social::deposit::tests::claim_incorrect_gas_amount diff --git a/onchain/cairo/afk/src/account.cairo b/onchain/cairo/afk/src/account.cairo new file mode 100644 index 000000000..ac7fe4fb1 --- /dev/null +++ b/onchain/cairo/afk/src/account.cairo @@ -0,0 +1 @@ +pub mod afk_id; diff --git a/onchain/cairo/afk/src/account/nostr_account.cairo b/onchain/cairo/afk/src/account/nostr_account.cairo new file mode 100644 index 000000000..dab8ef195 --- /dev/null +++ b/onchain/cairo/afk/src/account/nostr_account.cairo @@ -0,0 +1,597 @@ +use starknet::account::Call; +use starknet::{ContractAddress, get_caller_address, get_contract_address}; +// contract_address_const}; +use afk::social::profile::NostrProfile; +use afk::social::request::SocialRequest; +use afk::social::transfer::Transfer; + +#[starknet::interface] +pub trait INostrAccount { + fn get_public_key(self: @TContractState) -> u256; + fn get_nostr_public_key(self: @TContractState) -> u256; + fn get_starknet_public_key(self: @TContractState) -> ContractAddress; + fn init_nostr_account(ref self: TContractState); + fn set_vrf_contract_address(ref self: TContractState, vrf_contract_address: ContractAddress); + + // fn sign_message(ref self: TContractState, message: Array) -> Array; + // fn sign_nostr_event(ref self: TContractState, message: SocialRequest) -> Array; + + // fn handle_transfer(ref self: TContractState, request: SocialRequest); + // fn __execute__(self: @TContractState, calls: Array) -> Array>; +// fn __validate__(self: @TContractState, calls: Array) -> felt252; +// fn is_valid_signature(self: @TContractState, hash: felt252, signature: Array) -> +// felt252; +} + +#[starknet::interface] +pub trait ISRC6 { + fn __execute__(self: @TState, calls: Array) -> Array>; + fn __validate__(self: @TState, calls: Array) -> felt252; + fn is_valid_signature(self: @TState, hash: felt252, signature: Array) -> felt252; +} + + +#[starknet::contract(account)] +pub mod NostrAccount { + use afk::bip340; + use afk::bip340::{verify, verify_sig, sign, generate_keypair}; + + use afk::pedersen::{pedersen_commit, verify_commitment, hash_to_curve}; + use core::hash::{HashStateTrait, HashStateExTrait,}; + use core::poseidon::PoseidonTrait; + + use afk::tokens::erc20::{IERC20Dispatcher, IERC20DispatcherTrait}; + use afk::utils::{ + MIN_TRANSACTION_VERSION, QUERY_OFFSET, execute_calls, // is_valid_stark_signature + }; + use core::num::traits::Zero; + use starknet::account::Call; + use starknet::storage::{ + StoragePointerReadAccess, StoragePointerWriteAccess, StoragePathEntry, Map + }; + use starknet::{get_caller_address, get_contract_address, get_tx_info, ContractAddress}; + use super::ISRC6; + + use afk::social::request::{ + SocialRequest, SocialRequestImpl, SocialRequestTrait, Encode, Signature + }; + use afk::social::transfer::Transfer; + use super::{INostrAccountDispatcher, INostrAccountDispatcherTrait}; + use core::ecdsa::check_ecdsa_signature; + use core::byte_array::ByteArrayTrait; + use core::ec::stark_curve::GEN_X; + use core::ec::stark_curve::GEN_Y; + use core::ec::stark_curve::ORDER; + use core::ec::{EcPoint, ec_point_unwrap}; + use starknet::SyscallResultTrait; + use starknet::{secp256k1::{Secp256k1Point}, secp256_trait::{Secp256Trait, Secp256PointTrait}}; + + #[storage] + struct Storage { + // #[key] + public_key: u256, + // #[key] + nostr_public_key: u256, + nostr_point_public_key: Secp256k1Point, + // #[key] + starknet_address:ContractAddress, + private_key:u256, + signature_salt:felt252, + password:ByteArray, + address_recovery:ContractAddress, + transfers: Map, + nostr_accounts_public_keys: Map, + nostr_accounts_private_keys: Map, + vrf_contract_address:ContractAddress + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + AccountCreated: AccountCreated, + } + + #[derive(Drop, starknet::Event)] + struct AccountCreated { + #[key] + public_key: ContractAddress, + } + + #[derive(Drop, starknet::Event)] + struct NostrAccountCreated { + #[key] + public_key: u256, + } + + #[constructor] + fn constructor( + ref self: ContractState, + // public_key: u256, + public_key: ContractAddress, + vrf_contract_address:ContractAddress + ) { + // self.public_key.write(public_key); + self.starknet_address.write(public_key); + self.vrf_contract_address.write(vrf_contract_address); + + // Generate a Salt for Pedersen commitment + // Generate public and private key + // Saved Private key with Pedersen commitment with the signature of the Starknet account + self.emit(AccountCreated { public_key: public_key }); + + } + + #[abi(embed_v0)] + impl NostrAccount of super::INostrAccount { + fn get_public_key(self: @ContractState) -> u256 { + self.public_key.read() + } + + fn get_nostr_public_key(self: @ContractState) -> u256 { + self.nostr_public_key.read() + } + + fn get_starknet_public_key(self: @ContractState) -> ContractAddress { + self.starknet_address.read() + } + + fn set_vrf_contract_address(ref self: ContractState, vrf_contract_address: ContractAddress) { + if !self.starknet_address.read().is_zero() { + assert!(get_caller_address() == self.starknet_address.read(), "invalid caller"); + } + self.vrf_contract_address.write(vrf_contract_address); + } + + fn init_nostr_account(ref self: ContractState) { + assert!(self.private_key.read().is_zero(), "account already initialized"); + if !self.starknet_address.read().is_zero() { + assert!(get_caller_address() == self.starknet_address.read(), "invalid caller"); + } + let (private_key, public_key_point) = generate_keypair(self.vrf_contract_address.read()); + + // let public_key:u256 = public_key_point.try_into().unwrap(); + // Save private key with Pedersen commitment with the signature of the Starknet account + let H: EcPoint = hash_to_curve().unwrap(); + // TODO + // add random salt with signature and save its + let salt: felt252 = 228282189421094; + + let commitment = pedersen_commit(private_key, salt, H); + let is_valid = verify_commitment(commitment, private_key, salt, H); + assert(is_valid, 'The commitment is not valid'); + + self.private_key.write(private_key); + // self.nostr_public_key.write(public_key); + self.nostr_point_public_key.write(public_key_point); + // Convert secp256k1 point to u256 + // let public_key: u256 = public_key_point.try_into().unwrap(); + // self.nostr_public_key.write(public_key); + // self.starknet_address.write(get_caller_address()); + } + + // fn handle_transfer(ref self: ContractState, request: SocialRequest) { + // // TODO: is this check necessary + // assert!(request.public_key == self.public_key.read(), "wrong sender"); + + // let erc20 = IERC20Dispatcher { contract_address: request.content.token_address }; + // assert!(erc20.symbol() == request.content.token, "wrong token"); + + // let recipient = INostrAccountDispatcher { + // contract_address: request.content.recipient_address + // }; + + // assert!( + // recipient.get_public_key() == request.content.recipient.public_key, + // "wrong recipient" + // ); + + // if let Option::Some(id) = request.verify() { + // assert!(!self.transfers.read(id), "double spend"); + // self.transfers.entry(id).write(true); + // erc20.transfer(request.content.recipient_address, request.content.amount); + // } else { + // panic!("can't verify signature"); + // } + // } + } + + #[abi(embed_v0)] + impl ISRC6Impl of ISRC6 { + fn __execute__(self: @ContractState, calls: Array) -> Array> { + assert!(get_caller_address().is_zero(), "invalid caller"); + + // Check tx version + let tx_info = get_tx_info().unbox(); + let tx_version: u256 = tx_info.version.into(); + // Check if tx is a query + if (tx_version >= QUERY_OFFSET) { + assert!(QUERY_OFFSET + MIN_TRANSACTION_VERSION <= tx_version, "invalid tx version"); + } else { + assert!(MIN_TRANSACTION_VERSION <= tx_version, "invalid tx version"); + } + + execute_calls(calls) + } + + fn __validate__(self: @ContractState, calls: Array) -> felt252 { + let tx_info = get_tx_info().unbox(); + self._is_valid_signature(tx_info.transaction_hash, tx_info.signature) + } + + fn is_valid_signature( + self: @ContractState, hash: felt252, signature: Array + ) -> felt252 { + self._is_valid_signature(hash, signature.span()) + } + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + fn _is_valid_signature( + self: @ContractState, hash: felt252, signature: Span + ) -> felt252 { + + let is_valid_length = signature.len() == 2_u32; + // assert(is_valid_length, 'Account: Incorrect tx signature'); + + if !is_valid_length { + return 'INVALID_LENGTH'; + } + + let account_address:felt252 = self.starknet_address.read().try_into().unwrap(); + let is_valid = check_ecdsa_signature( + hash, account_address, *signature.at(0_u32), *signature.at(1_u32) + ); + if is_valid { + return starknet::VALIDATED; + } + // assert(is_valid, 'INVALIDATED'); + 0 + } + + fn _is_valid_signature_nostr( + self: @ContractState, + hash: felt252, + signature: Span + ) -> felt252 { + let mut signature = signature; + let r: u256 = Serde::deserialize(ref signature).expect('invalid signature format'); + let s: u256 = Serde::deserialize(ref signature).expect('invalid signature format'); + + let hash: u256 = hash.into(); + let mut hash_as_ba = Default::default(); + hash_as_ba.append_word(hash.high.into(), 16); + hash_as_ba.append_word(hash.low.into(), 16); + + let public_key = self.nostr_public_key.read(); + + if bip340::verify(public_key, r, s, hash_as_ba) { + starknet::VALIDATED + } else { + 0 + } + } + + } +} +#[cfg(test)] +mod tests { + use afk::tokens::erc20::{ERC20, IERC20Dispatcher, IERC20DispatcherTrait}; + use core::array::SpanTrait; + use core::traits::Into; + use snforge_std::{ + declare, ContractClass, ContractClassTrait, spy_events, SpyOn, EventSpy, EventFetcher, + Event, EventAssertions, cheat_transaction_hash_global, cheat_signature_global, + stop_cheat_transaction_hash_global, stop_cheat_signature_global + }; + use starknet::{ + ContractAddress, get_caller_address, get_contract_address, contract_address_const, VALIDATED + }; + use super::super::profile::NostrProfile; + + use super::super::request::{SocialRequest, Signature, Encode}; + use super::super::transfer::Transfer; + use super::{ + INostrAccountDispatcher, INostrAccountDispatcherTrait, INostrAccountSafeDispatcher, + INostrAccountSafeDispatcherTrait + }; + + use super::{ISRC6Dispatcher, ISRC6DispatcherTrait}; + // Sepolia + const VRF_CONTRACT_ADDRESS: ContractAddress = + 0x00be3edf412dd5982aa102524c0b8a0bcee584c5a627ed1db6a7c36922047257; + fn declare_account() -> ContractClass { + declare("NostrAccount").unwrap() + } + + fn declare_erc20() -> ContractClass { + declare("ERC20").unwrap() + } + + fn deploy_account(class: ContractClass, public_key: ContractAddress, vrf_contract_address: ContractAddress) -> INostrAccountDispatcher { + let mut calldata = array![]; + public_key.serialize(ref calldata); + vrf_contract_address.serialize(ref calldata); + + let address = class.precalculate_address(@calldata); + + let mut spy = spy_events(SpyOn::One(address)); + + let (contract_address, _) = class.deploy(@calldata).unwrap(); + + spy.fetch_events(); + + assert(spy.events.len() == 1, 'there should be one event'); + + // TODO: deserialize event instead of manual decoding + let (_, event) = spy.events.at(0); + assert(event.keys.at(0) == @selector!("AccountCreated"), 'wrong event name'); + + let event_key = u256 { + low: (*event.keys.at(1)).try_into().unwrap(), + high: (*event.keys.at(2)).try_into().unwrap() + }; + + assert(event_key == public_key, 'wrong public key'); + + INostrAccountDispatcher { contract_address } + } + + fn deploy_erc20( + class: ContractClass, + name: felt252, + symbol: felt252, + initial_supply: u256, + recipient: ContractAddress + ) -> IERC20Dispatcher { + let mut calldata = array![]; + + name.serialize(ref calldata); + symbol.serialize(ref calldata); + (2 * initial_supply).serialize(ref calldata); + recipient.serialize(ref calldata); + 18_u8.serialize(ref calldata); + + let (contract_address, _) = class.deploy(@calldata).unwrap(); + + IERC20Dispatcher { contract_address } + } + + // Constants + fn OWNER() -> ContractAddress { + // 'owner'.try_into().unwrap() + 123.try_into().unwrap() + } + + fn RECIPIENT() -> ContractAddress { + 'recipient'.try_into().unwrap() + } + + fn request_fixture_custom_classes( + erc20_class: ContractClass, account_class: ContractClass + ) -> ( + SocialRequest, + INostrAccountDispatcher, + INostrAccountDispatcher, + IERC20Dispatcher + ) { + // sender private key: 70aca2a9ab722bd56a9a1aadae7f39bc747c7d6735a04d677e0bc5dbefa71d47 + // just for testing, do not use for anything else + let nostr_sender_public_key = + 0xd6f1cf53f9f52d876505164103b1e25811ec4226a17c7449576ea48b00578171_u256; + + let sender_public_key: ContractAddress = OWNER(); + let sender = deploy_account(account_class, sender_public_key, VRF_CONTRACT_ADDRESS); + + // recipient private key: + // 59a772c0e643e4e2be5b8bac31b2ab5c5582b03a84444c81d6e2eec34a5e6c35 // just for testing, do + // not use for anything else + // let recipient_public_key = + // 0x5b2b830f2778075ab3befb5a48c9d8138aef017fab2b26b5c31a2742a901afcc_u256; + let recipient_public_key: ContractAddress = RECIPIENT(); + let recipient = deploy_account(account_class, recipient_public_key, VRF_CONTRACT_ADDRESS); + + let joyboy_public_key = + 0x84603b4e300840036ca8cc812befcc8e240c09b73812639d5cdd8ece7d6eba40; + + let erc20 = deploy_erc20(erc20_class, 'USDC token', 'USDC', 100, + sender.contract_address); + + let transfer = Transfer { + amount: 1, + token: erc20.symbol(), + token_address: erc20.contract_address, + joyboy: NostrProfile { + public_key: joyboy_public_key, relays: array!["wss://relay.joyboy.community.com"] + }, + recipient: NostrProfile { public_key: recipient_public_key, relays: array![] }, + recipient_address: recipient.contract_address + }; + + // for test data see: https://replit.com/@maciejka/WanIndolentKilobyte-2 + + let request = SocialRequest { + public_key: nostr_sender_public_key, + created_at: 1716285235_u64, + kind: 1_u16, + tags: "[]", + content: transfer, + sig: Signature { + r: 0x3570a9a0c92c180bd4ac826c887e63844b043e3b65da71a857d2aa29e7cd3a4e_u256, + s: 0x1c0c0a8b7a8330b6b8915985c9cd498a407587213c2e7608e7479b4ef966605f_u256, + } + }; + + (request, sender, recipient, erc20) + } + + fn request_fixture() -> ( + SocialRequest, + INostrAccountDispatcher, + INostrAccountDispatcher, + IERC20Dispatcher + ) { + let erc20_class = declare_erc20(); + let account_class = declare_account(); + request_fixture_custom_classes(erc20_class, account_class) + } + + #[test] + #[fork("Sepolia")] + fn init_nostr_account() { + // let public_key: u256 = 45; + let public_key: ContractAddress = OWNER(); + let account = deploy_account(declare_account(), public_key, VRF_CONTRACT_ADDRESS); + account.init_nostr_account(); + // assert!(account.get_public_key() == public_key, "wrong public_key"); + } + + #[test] + fn test_get_public_key() { + // let public_key: u256 = 45; + let public_key: ContractAddress = OWNER(); + + // let account = deploy_account(declare_account(), public_key); + let account = deploy_account(declare_account(), public_key, VRF_CONTRACT_ADDRESS); + + assert!(account.get_public_key() == public_key, "wrong public_key"); + } + + #[test] + fn successful_transfer() { + let (request, sender, _, _) = request_fixture(); + // sender.handle_transfer(request); + } + + #[test] + #[should_panic(expected: "can't verify signature")] + fn incorrect_signature() { + let (request, sender, _, _) = request_fixture(); + + let request = SocialRequest { + sig: Signature { + r: 0x2570a9a0c92c180bd4ac826c887e63844b043e3b65da71a857d2aa29e7cd3a4e_u256, + s: 0x1c0c0a8b7a8330b6b8915985c9cd498a407587213c2e7608e7479b4ef966605f_u256, + }, + ..request, + }; + + // sender.handle_transfer(request); + } + + #[test] + #[should_panic(expected: "wrong sender")] + fn wrong_sender() { + let (request, sender, _, _) = request_fixture(); + + let request = SocialRequest { public_key: 123_u256, ..request, }; + + // sender.handle_transfer(request); + } + + #[test] + #[should_panic(expected: "wrong recipient")] + fn wrong_recipient() { + let (request, sender, _, _) = request_fixture(); + + // let content = request.content.clone(); + + let request = SocialRequest { + content: Transfer { + recipient_address: sender.contract_address, ..request.content.clone() + }, + ..request, + }; + + // sender.handle_transfer(request); + } + + #[test] + #[should_panic(expected: "wrong token")] + fn wrong_token() { + let erc20_class = declare_erc20(); + let account_class = declare_account(); + + let dai = deploy_erc20(erc20_class, 'DAI token', 'DAI', 100, 21.try_into().unwrap()); + + let (request, sender, _, _) = request_fixture_custom_classes(erc20_class, account_class); + + let request = SocialRequest { + content: Transfer { token_address: dai.contract_address, ..request.content.clone() }, + ..request, + }; + + // sender.handle_transfer(request); + } + + // #[test] + // #[should_panic(expected: "double spend")] + // fn double_transfer() { + // let erc20_class = declare_erc20(); + // let account_class = declare_account(); + // let (request, sender, _, _) = request_fixture_custom_classes(erc20_class, account_class); + // let (request2, _, _, _) = request_fixture_custom_classes(erc20_class, account_class); + + // sender.handle_transfer(request); + // sender.handle_transfer(request2); + // } + + #[test] + fn is_valid_signature() { + let public_key: ContractAddress = OWNER(); + + let account_class = declare_account(); + let account = deploy_account(account_class, public_key, VRF_CONTRACT_ADDRESS); + + let account = ISRC6Dispatcher { contract_address: account.contract_address }; + + let hash = 0x6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89; + + let r: u256 = 0x49ae3fa614e2877877a90987726f1b48387bef1f66de78e5075659040cbbf612; + let s: u256 = 0x11259ae25e0743ac7490df3fef875ea291c7b99cf2295e44aabd677107b9c53a; + + let mut signature = Default::default(); + r.serialize(ref signature); + s.serialize(ref signature); + + assert!(account.is_valid_signature(hash, signature.clone()) == starknet::VALIDATED); + + let invalid_hash = 0x5a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89; + + assert!(account.is_valid_signature(invalid_hash, signature) != starknet::VALIDATED); + } + + #[test] + fn validate_transaction() { + let public_key: ContractAddress = OWNER(); + + let account_class = declare_account(); + let account = deploy_account(account_class, public_key, VRF_CONTRACT_ADDRESS); + + let account = ISRC6Dispatcher { contract_address: account.contract_address }; + + let hash = 0x6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89; + + let r: u256 = 0x49ae3fa614e2877877a90987726f1b48387bef1f66de78e5075659040cbbf612; + let s: u256 = 0x11259ae25e0743ac7490df3fef875ea291c7b99cf2295e44aabd677107b9c53a; + + let mut signature = Default::default(); + r.serialize(ref signature); + s.serialize(ref signature); + + cheat_transaction_hash_global(hash); + cheat_signature_global(signature.span()); + + assert!(account.__validate__(Default::default()) == starknet::VALIDATED); + + let invalid_hash = 0x5a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89; + cheat_transaction_hash_global(invalid_hash); + + assert!(account.__validate__(Default::default()) != starknet::VALIDATED); + + stop_cheat_transaction_hash_global(); + stop_cheat_signature_global(); + } +} + + diff --git a/onchain/cairo/afk/src/bip340.cairo b/onchain/cairo/afk/src/bip340.cairo index f328bb8f7..7ceb42fb3 100644 --- a/onchain/cairo/afk/src/bip340.cairo +++ b/onchain/cairo/afk/src/bip340.cairo @@ -166,7 +166,7 @@ pub fn verify(px: u256, rx: u256, s: u256, m: ByteArray) -> bool { !(Rx == 0 && Ry == 0) && Ry % 2 == 0 && Rx == rx } -fn count_digits(mut num: u256) -> (u32, felt252) { +pub fn count_digits(mut num: u256) -> (u32, felt252) { let BASE: u256 = 16_u256; let mut count: u32 = 0; while num > 0 { @@ -176,12 +176,12 @@ fn count_digits(mut num: u256) -> (u32, felt252) { let res: felt252 = count.try_into().unwrap(); (count, res) } -fn linkedStarknetAddress_to_bytes(linkedStarknetAddress: LinkedStarknetAddress) -> ByteArray { +pub fn linkedStarknetAddress_to_bytes(linkedStarknetAddress: LinkedStarknetAddress) -> ByteArray { let mut ba: ByteArray = ""; ba.append_word(linkedStarknetAddress.starknet_address.into(), 1_u32); ba } -fn claim_to_bytes(claim: Claim) -> ByteArray { +pub fn claim_to_bytes(claim: Claim) -> ByteArray { let mut ba: ByteArray = ""; ba.append_word(claim.deposit_id.into(), 1_u32); ba.append_word(claim.starknet_recipient.into(), 1_u32); @@ -192,7 +192,7 @@ fn claim_to_bytes(claim: Claim) -> ByteArray { ba.append_word(gas_felt252, gas_count); ba } -fn transfer_to_bytes(transfer: Transfer) -> ByteArray { +pub fn transfer_to_bytes(transfer: Transfer) -> ByteArray { let mut ba: ByteArray = ""; // Encode amount (u256 to felt252 conversion) let (amount_count, amount_count_felt252) = count_digits(transfer.amount); @@ -230,7 +230,7 @@ fn transfer_to_bytes(transfer: Transfer) -> ByteArray { ba } -fn encodeSocialRequest, impl CDrop: Drop>( +pub fn encodeSocialRequest, impl CDrop: Drop>( request: SocialRequest ) -> ByteArray { let mut ba: ByteArray = ""; @@ -268,7 +268,9 @@ fn encodeSocialRequest, impl CDrop: Drop>( } /// Generates a key pair (private key, public key) for Schnorr signatures -fn generate_keypair( +// Highly senstive data +// Take care of the result +pub fn generate_keypair( vrf_contract_address: ContractAddress ) -> (core::felt252, core::starknet::secp256k1::Secp256k1Point) { // vrf address @@ -291,12 +293,19 @@ fn generate_keypair( /// Generates a nonce and corresponding R point for signature -fn generate_nonce_point() -> (u256, Secp256k1Point) { +pub fn generate_nonce_point(vrf_contract_address: ContractAddress) -> (u256, Secp256k1Point) { + // pub fn generate_nonce_point(vrf_contract_address: ContractAddress) -> (u256, Secp256k1Point) { let G = Secp256Trait::::get_generator_point(); - let nonce: u256 = 0x46952909012476409278523962123414653_u256; // VRF needed - let R = G.mul(nonce).unwrap_syscall(); + let vrf_provider = IVrfProviderDispatcher { contract_address: vrf_contract_address }; + let caller = get_caller_address(); + let source = Source::Nonce(caller); + let nonce: felt252 = vrf_provider.consume_random(source); + let nonce_u256: u256 = nonce.try_into().unwrap(); + // let nonce: u256 = 0x46952909012476409278523962123414653_u256; // VRF needed + // let R = G.mul(nonce).unwrap_syscall(); + let R = G.mul(nonce_u256).unwrap_syscall(); - (nonce, R) + (nonce_u256, R) } /// Computes the challenge hash e using Poseidon @@ -307,8 +316,8 @@ fn compute_challenge(R: u256, public_key: Secp256k1Point, message: ByteArray) -> hash_challenge(rx, px, message) } -fn sign(private_key: u256, message: ByteArray) -> SchnorrSignature { - let (nonce, R) = generate_nonce_point(); +pub fn sign(private_key: u256, message: ByteArray, vrf_contract_address: ContractAddress) -> SchnorrSignature { + let (nonce, R) = generate_nonce_point(vrf_contract_address); let G = Secp256Trait::::get_generator_point(); let public_key = G.mul(private_key).unwrap_syscall(); let (s_G_x, _s_G_y) = public_key.get_coordinates().unwrap_syscall(); @@ -324,11 +333,11 @@ fn sign(private_key: u256, message: ByteArray) -> SchnorrSignature { } /// Verifies a Schnorr signature -fn verify_sig(public_key: Secp256k1Point, message: ByteArray, signature: SchnorrSignature) -> bool { +pub fn verify_sig(public_key: Secp256k1Point, message: ByteArray, signature: SchnorrSignature, vrf_contract_address: ContractAddress) -> bool { let G = Secp256Trait::::get_generator_point(); let e = compute_challenge(signature.r, public_key, message); let n = Secp256Trait::::get_curve_size(); - let (_nonce, R) = generate_nonce_point(); + let (_nonce, R) = generate_nonce_point(vrf_contract_address); // Check that s is within valid range if (signature.s).into() >= n { return false; @@ -369,9 +378,10 @@ mod tests { ba } } - const CONTRACT_ADDRESS: felt252 = - 0x00be3edf412dd5982aa102524c0b8a0bcee584c5a627ed1db6a7c36922047257; + // const CONTRACT_ADDRESS: felt252 = + // 0x00be3edf412dd5982aa102524c0b8a0bcee584c5a627ed1db6a7c36922047257; + const CONTRACT_ADDRESS: ContractAddress = 0x00be3edf412dd5982aa102524c0b8a0bcee584c5a627ed1db6a7c36922047257; // test data adapted from: https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv #[test] @@ -605,10 +615,10 @@ mod tests { // Sign message let private_key_u256: u256 = private_key.into(); - let signature = sign(private_key_u256, message.clone()); + let signature = sign(private_key_u256, message.clone(), vrf_provider.contract_address); // Verify signature - let is_valid = verify_sig(public_key, message, signature); + let is_valid = verify_sig(public_key, message, signature, vrf_provider.contract_address); assert!(is_valid); } @@ -627,10 +637,10 @@ mod tests { // Sign message let private_key_u256: u256 = private_key.into(); - let signature = sign(private_key_u256, message.clone()); + let signature = sign(private_key_u256, message.clone(), vrf_provider.contract_address); // Verify signature - let is_valid = verify_sig(public_key, message, signature); + let is_valid = verify_sig(public_key, message, signature, vrf_provider.contract_address); assert!(is_valid); } diff --git a/onchain/cairo/afk/src/lib.cairo b/onchain/cairo/afk/src/lib.cairo index 186656418..bc4099244 100644 --- a/onchain/cairo/afk/src/lib.cairo +++ b/onchain/cairo/afk/src/lib.cairo @@ -38,6 +38,10 @@ pub mod types { pub mod tap_types; } +pub mod account { + pub mod nostr_account; +} + pub mod tokens { pub mod erc20; pub mod erc20_mintable;