diff --git a/lib/accounts.ts b/lib/accounts.ts index e9858d6..ae14e88 100644 --- a/lib/accounts.ts +++ b/lib/accounts.ts @@ -2,38 +2,21 @@ import { Abi, Account, AllowArray, - ArraySignatureType, - CairoOption, - CairoOptionVariant, Call, CallData, Contract, DeployAccountContractPayload, DeployContractResponse, - GetTransactionReceiptResponse, - InvocationsSignerDetails, InvokeFunctionResponse, RPC, - RawCalldata, - Signature, UniversalDetails, - V2InvocationsSignerDetails, - V3InvocationsSignerDetails, - hash, num, - shortString, - stark, - transaction, uint256, } from "starknet"; import { manager } from "./manager"; -import { ensureSuccess } from "./receipts"; -import { LegacyArgentSigner, LegacyKeyPair, LegacyMultisigSigner, LegacyStarknetKeyPair } from "./signers/legacy"; -import { ArgentSigner, KeyPair, RawSigner, randomStarknetKeyPair } from "./signers/signers"; +import { KeyPair } from "./signers/signers"; import { ethAddress, strkAddress } from "./tokens"; -export const VALID = BigInt(shortString.encodeShortString("VALID")); - export class ArgentAccount extends Account { // Increase the gas limit by 30% to avoid failures due to gas estimation being too low with tx v3 and transactions the use escaping override async deployAccount( @@ -79,21 +62,6 @@ export interface ArgentWallet { owner: KeyPair; } -export interface ArgentWalletWithGuardian extends ArgentWallet { - guardian: KeyPair; -} - -export interface LegacyArgentWallet { - account: ArgentAccount; - accountContract: Contract; - owner: LegacyKeyPair; - guardian: LegacyKeyPair; -} - -export interface ArgentWalletWithGuardianAndBackup extends ArgentWalletWithGuardian { - guardianBackup: KeyPair; -} - export const deployer = (() => { if (manager.isDevnet) { const devnetAddress = "0x64b48806902a367c8598f4f95c305e8c1a1acba5f082d294a43793113115691"; @@ -133,251 +101,6 @@ export function setDefaultTransactionVersionV3(account: ArgentAccount): ArgentAc console.log("Deployer:", deployer.address); -export async function deployOldAccount( - owner = new LegacyStarknetKeyPair(), - guardian = new LegacyStarknetKeyPair(), - salt = num.toHex(randomStarknetKeyPair().privateKey), -): Promise { - const proxyClassHash = await manager.declareFixtureContract("Proxy"); - const oldArgentAccountClassHash = await manager.declareFixtureContract("OldArgentAccount"); - - const constructorCalldata = CallData.compile({ - implementation: oldArgentAccountClassHash, - selector: hash.getSelectorFromName("initialize"), - calldata: CallData.compile({ owner: owner.publicKey, guardian: guardian.publicKey }), - }); - - const contractAddress = hash.calculateContractAddressFromHash(salt, proxyClassHash, constructorCalldata, 0); - - const account = new Account(manager, contractAddress, owner); - account.signer = new LegacyMultisigSigner([owner, guardian]); - - await fundAccount(account.address, 1e16, "ETH"); // 0.01 ETH - - const { transaction_hash } = await account.deployAccount({ - classHash: proxyClassHash, - constructorCalldata, - contractAddress, - addressSalt: salt, - }); - await manager.waitForTransaction(transaction_hash); - const accountContract = await manager.loadContract(account.address); - accountContract.connect(account); - return { account, accountContract, owner, guardian }; -} - -async function deployAccountInner(params: DeployAccountParams): Promise< - DeployAccountParams & { - account: Account; - classHash: string; - owner: KeyPair; - guardian?: KeyPair; - salt: string; - transactionHash: string; - } -> { - const finalParams = { - ...params, - classHash: params.classHash ?? (await manager.declareLocalContract("ArgentAccount")), - salt: params.salt ?? num.toHex(randomStarknetKeyPair().privateKey), - owner: params.owner ?? randomStarknetKeyPair(), - useTxV3: params.useTxV3 ?? false, - selfDeploy: params.selfDeploy ?? false, - }; - const guardian = finalParams.guardian - ? finalParams.guardian.signerAsOption - : new CairoOption(CairoOptionVariant.None); - const constructorCalldata = CallData.compile({ owner: finalParams.owner.signer, guardian }); - - const { classHash, salt } = finalParams; - const contractAddress = hash.calculateContractAddressFromHash(salt, classHash, constructorCalldata, 0); - const fundingCall = finalParams.useTxV3 - ? await fundAccountCall(contractAddress, finalParams.fundingAmount ?? 1e16, "STRK") // 0.01 STRK - : await fundAccountCall(contractAddress, finalParams.fundingAmount ?? 1e18, "ETH"); // 1 ETH - const calls = fundingCall ? [fundingCall] : []; - - const transactionVersion = finalParams.useTxV3 ? RPC.ETransactionVersion.V3 : RPC.ETransactionVersion.V2; - const signer = new ArgentSigner(finalParams.owner, finalParams.guardian); - const account = new ArgentAccount(manager, contractAddress, signer, "1", transactionVersion); - - let transactionHash; - if (finalParams.selfDeploy) { - const response = await deployer.execute(calls); - await manager.waitForTransaction(response.transaction_hash); - const { transaction_hash } = await account.deploySelf({ classHash, constructorCalldata, addressSalt: salt }); - transactionHash = transaction_hash; - } else { - const udcCalls = deployer.buildUDCContractPayload({ classHash, salt, constructorCalldata, unique: false }); - const { transaction_hash } = await deployer.execute([...calls, ...udcCalls]); - transactionHash = transaction_hash; - } - - await manager.waitForTransaction(transactionHash); - return { ...finalParams, account, transactionHash }; -} - -export type DeployAccountParams = { - useTxV3?: boolean; - classHash?: string; - owner?: KeyPair; - guardian?: KeyPair; - salt?: string; - fundingAmount?: number | bigint; - selfDeploy?: boolean; -}; - -export async function deployAccount( - params: DeployAccountParams = {}, -): Promise { - params.guardian ||= randomStarknetKeyPair(); - const { account, owner, transactionHash } = await deployAccountInner(params); - const accountContract = await manager.loadContract(account.address); - accountContract.connect(account); - return { account, accountContract, owner, guardian: params.guardian, transactionHash }; -} - -export async function deployAccountWithoutGuardian( - params: Omit = {}, -): Promise { - const { account, owner, transactionHash } = await deployAccountInner(params); - const accountContract = await manager.loadContract(account.address); - accountContract.connect(account); - return { account, accountContract, owner, transactionHash }; -} - -export async function deployAccountWithGuardianBackup( - params: DeployAccountParams & { guardianBackup?: KeyPair } = {}, -): Promise { - const guardianBackup = params.guardianBackup ?? randomStarknetKeyPair(); - - const wallet = (await deployAccount(params)) as ArgentWalletWithGuardianAndBackup & { transactionHash: string }; - await wallet.accountContract.change_guardian_backup(guardianBackup.compiledSignerAsOption); - - wallet.account.signer = new ArgentSigner(wallet.owner, guardianBackup); - wallet.guardianBackup = guardianBackup; - wallet.accountContract.connect(wallet.account); - return wallet; -} - -export async function deployLegacyAccount(classHash: string) { - const owner = new LegacyStarknetKeyPair(); - const guardian = new LegacyStarknetKeyPair(); - const salt = num.toHex(owner.privateKey); - const constructorCalldata = CallData.compile({ owner: owner.publicKey, guardian: guardian.publicKey }); - const contractAddress = hash.calculateContractAddressFromHash(salt, classHash, constructorCalldata, 0); - await fundAccount(contractAddress, 1e15, "ETH"); // 0.001 ETH - const account = new Account(manager, contractAddress, owner, "1"); - account.signer = new LegacyArgentSigner(owner, guardian); - - const { transaction_hash } = await account.deploySelf({ - classHash, - constructorCalldata, - addressSalt: salt, - }); - await manager.waitForTransaction(transaction_hash); - - const accountContract = await manager.loadContract(account.address); - accountContract.connect(account); - return { account, accountContract, owner, guardian }; -} - -export async function upgradeAccount( - accountToUpgrade: Account, - newClassHash: string, - calldata: RawCalldata = [], -): Promise { - const { transaction_hash } = await accountToUpgrade.execute( - { - contractAddress: accountToUpgrade.address, - entrypoint: "upgrade", - calldata: CallData.compile({ implementation: newClassHash, calldata }), - }, - undefined, - { maxFee: 1e14 }, - ); - return await ensureSuccess(await manager.waitForTransaction(transaction_hash)); -} - -export async function executeWithCustomSig( - account: ArgentAccount, - transactions: AllowArray, - signature: ArraySignatureType, - transactionsDetail: UniversalDetails = {}, -): Promise { - const signer = new (class extends RawSigner { - public async signRaw(messageHash: string): Promise { - return signature; - } - })(); - const newAccount = new ArgentAccount( - manager, - account.address, - signer, - account.cairoVersion, - account.transactionVersion, - ); - - return await newAccount.execute(transactions, undefined, transactionsDetail); -} - -export async function getSignerDetails(account: ArgentAccount, calls: Call[]): Promise { - const newAccount = new ArgentAccount( - manager, - account.address, - account.signer, - account.cairoVersion, - account.transactionVersion, - ); - const customSigner = new (class extends RawSigner { - public signerDetails?: InvocationsSignerDetails; - public async signTransaction(calls: Call[], signerDetails: InvocationsSignerDetails): Promise { - this.signerDetails = signerDetails; - throw Error("Should not execute"); - } - public async signRaw(messageHash: string): Promise { - throw Error("Not implemented"); - } - })(); - newAccount.signer = customSigner; - try { - await newAccount.execute(calls, undefined); - throw Error("Should not execute"); - } catch (customError) { - return customSigner.signerDetails!; - } -} - -export function calculateTransactionHash(transactionDetail: InvocationsSignerDetails, calls: Call[]): string { - const compiledCalldata = transaction.getExecuteCalldata(calls, transactionDetail.cairoVersion); - let transactionHash; - if (Object.values(RPC.ETransactionVersion2).includes(transactionDetail.version as any)) { - const transactionDetailV2 = transactionDetail as V2InvocationsSignerDetails; - transactionHash = hash.calculateInvokeTransactionHash({ - ...transactionDetailV2, - senderAddress: transactionDetailV2.walletAddress, - compiledCalldata, - }); - } else if (Object.values(RPC.ETransactionVersion3).includes(transactionDetail.version as any)) { - const transactionDetailV3 = transactionDetail as V3InvocationsSignerDetails; - transactionHash = hash.calculateInvokeTransactionHash({ - ...transactionDetailV3, - senderAddress: transactionDetailV3.walletAddress, - compiledCalldata, - nonceDataAvailabilityMode: stark.intDAM(transactionDetailV3.nonceDataAvailabilityMode), - feeDataAvailabilityMode: stark.intDAM(transactionDetailV3.feeDataAvailabilityMode), - }); - } else { - throw Error("unsupported transaction version"); - } - return transactionHash; -} - -export async function fundAccount(recipient: string, amount: number | bigint, token: "ETH" | "STRK") { - const call = await fundAccountCall(recipient, amount, token); - const response = await deployer.execute(call ? [call] : []); - await manager.waitForTransaction(response.transaction_hash); -} - export async function fundAccountCall( recipient: string, amount: number | bigint, diff --git a/lib/index.ts b/lib/index.ts index 3116f82..79e104b 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -10,17 +10,11 @@ export * from "./contracts"; export * from "./devnet"; export * from "./expectations"; export * from "./manager"; -export * from "./multisig"; export * from "./openZeppelinAccount"; -export * from "./outsideExecution"; export * from "./receipts"; -export * from "./recovery"; export * from "./signers/legacy"; -export * from "./signers/secp256"; export * from "./signers/signers"; -export * from "./signers/webauthn"; export * from "./tokens"; -export * from "./udc"; export * from "./upgrade"; export type Constructor = new (...args: any[]) => T; diff --git a/lib/multisig.ts b/lib/multisig.ts deleted file mode 100644 index 5855bdc..0000000 --- a/lib/multisig.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { Account, CallData, Contract, GetTransactionReceiptResponse, RPC, hash, num } from "starknet"; -import { - ArgentAccount, - KeyPair, - LegacyMultisigSigner, - MultisigSigner, - deployer, - fundAccount, - fundAccountCall, - manager, - randomLegacyMultisigKeyPairs, - randomStarknetKeyPair, - randomStarknetKeyPairs, - sortByGuid, -} from "."; - -export interface MultisigWallet { - account: Account; - accountContract: Contract; - keys: KeyPair[]; - threshold: bigint; - receipt: GetTransactionReceiptResponse; -} - -export type DeployMultisigParams = { - threshold: number; - signersLength?: number; - keys?: KeyPair[]; - useTxV3?: boolean; - classHash?: string; - salt?: string; - fundingAmount?: number | bigint; - selfDeploy?: boolean; - selfDeploymentIndexes?: number[]; -}; - -export async function deployMultisig(params: DeployMultisigParams): Promise { - const finalParams = { - ...params, - classHash: params.classHash ?? (await manager.declareLocalContract("ArgentMultisigAccount")), - salt: params.salt ?? num.toHex(randomStarknetKeyPair().privateKey), - useTxV3: params.useTxV3 ?? false, - selfDeploy: params.selfDeploy ?? false, - selfDeploymentIndexes: params.selfDeploymentIndexes ?? [0], - }; - - if (params.selfDeploymentIndexes && !finalParams.selfDeploy) { - throw new Error("selfDeploymentIndexes can only be used with selfDeploy"); - } - - if (!params.keys && !finalParams.signersLength) { - throw new Error("Fill in one of 'keys' or 'signersLength'"); - } - const keys = params.keys ?? sortedKeyPairs(finalParams.signersLength!); - const signers = keysToSigners(keys); - const constructorCalldata = CallData.compile({ threshold: finalParams.threshold, signers }); - - const { classHash, salt, selfDeploymentIndexes } = finalParams; - const accountAddress = hash.calculateContractAddressFromHash(salt, classHash, constructorCalldata, 0); - - const fundingCall = finalParams.useTxV3 - ? await fundAccountCall(accountAddress, finalParams.fundingAmount ?? 1e16, "STRK") // 0.01 STRK - : await fundAccountCall(accountAddress, finalParams.fundingAmount ?? 1e15, "ETH"); // 0.001 ETH - const calls = fundingCall ? [fundingCall] : []; - - const transactionVersion = finalParams.useTxV3 ? RPC.ETransactionVersion.V3 : RPC.ETransactionVersion.V2; - - let transactionHash; - if (finalParams.selfDeploy) { - const response = await deployer.execute(calls); - await manager.waitForTransaction(response.transaction_hash); - - const selfDeploymentSigner = new MultisigSigner(keys.filter((_, i) => selfDeploymentIndexes.includes(i))); - const account = new Account(manager, accountAddress, selfDeploymentSigner, "1", transactionVersion); - - const { transaction_hash } = await account.deploySelf({ classHash, constructorCalldata, addressSalt: salt }); - transactionHash = transaction_hash; - } else { - const udcCalls = deployer.buildUDCContractPayload({ classHash, salt, constructorCalldata, unique: false }); - const { transaction_hash } = await deployer.execute([...calls, ...udcCalls]); - transactionHash = transaction_hash; - } - - const receipt = await manager.waitForTransaction(transactionHash); - const signer = new MultisigSigner(keys.slice(0, finalParams.threshold)); - const account = new ArgentAccount(manager, accountAddress, signer, "1", transactionVersion); - const accountContract = await manager.loadContract(account.address); - accountContract.connect(account); - return { account, accountContract, keys, receipt, threshold: BigInt(finalParams.threshold) }; -} - -export async function deployMultisig1_3( - params: Omit = {}, -): Promise { - return deployMultisig({ ...params, threshold: 1, signersLength: 3 }); -} - -export async function deployMultisig1_1( - params: Omit = {}, -): Promise { - return deployMultisig({ ...params, threshold: 1, signersLength: 1 }); -} - -const sortedKeyPairs = (length: number) => sortByGuid(randomStarknetKeyPairs(length)); - -const keysToSigners = (keys: KeyPair[]) => keys.map(({ signer }) => signer); - -export async function deployLegacyMultisig(classHash: string, threshold = 1) { - const keys = randomLegacyMultisigKeyPairs(threshold); - const signersPublicKeys = keys.map((key) => key.publicKey); - const salt = num.toHex(randomStarknetKeyPair().privateKey); - const constructorCalldata = CallData.compile({ threshold, signers: signersPublicKeys }); - const contractAddress = hash.calculateContractAddressFromHash(salt, classHash, constructorCalldata, 0); - await fundAccount(contractAddress, 1e15, "ETH"); // 0.001 ETH - const deploySigner = new LegacyMultisigSigner([keys[0]]); - const account = new Account(manager, contractAddress, deploySigner, "1"); - - const { transaction_hash } = await account.deploySelf({ classHash, constructorCalldata, addressSalt: salt }); - await manager.waitForTransaction(transaction_hash); - - const signers = new LegacyMultisigSigner(keys); - account.signer = signers; - const accountContract = await manager.loadContract(account.address); - accountContract.connect(account); - return { account, accountContract, deploySigner, signers }; -} diff --git a/lib/outsideExecution.ts b/lib/outsideExecution.ts deleted file mode 100644 index 348c66a..0000000 --- a/lib/outsideExecution.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { Call, CallData, hash, num, RawArgs, SignerInterface, typedData } from "starknet"; -import { manager } from "./"; - -const typesRev0 = { - StarkNetDomain: [ - { name: "name", type: "felt" }, - { name: "version", type: "felt" }, - { name: "chainId", type: "felt" }, - ], - OutsideExecution: [ - { name: "caller", type: "felt" }, - { name: "nonce", type: "felt" }, - { name: "execute_after", type: "felt" }, - { name: "execute_before", type: "felt" }, - { name: "calls_len", type: "felt" }, - { name: "calls", type: "OutsideCall*" }, - ], - OutsideCall: [ - { name: "to", type: "felt" }, - { name: "selector", type: "felt" }, - { name: "calldata_len", type: "felt" }, - { name: "calldata", type: "felt*" }, - ], -}; - -const typesRev1 = { - StarknetDomain: [ - { name: "name", type: "shortstring" }, - { name: "version", type: "shortstring" }, - { name: "chainId", type: "shortstring" }, - { name: "revision", type: "shortstring" }, - ], - OutsideExecution: [ - { name: "Caller", type: "ContractAddress" }, - { name: "Nonce", type: "felt" }, - { name: "Execute After", type: "u128" }, - { name: "Execute Before", type: "u128" }, - { name: "Calls", type: "Call*" }, - ], - Call: [ - { name: "To", type: "ContractAddress" }, - { name: "Selector", type: "selector" }, - { name: "Calldata", type: "felt*" }, - ], -}; - -function getDomain(chainId: string, revision: typedData.TypedDataRevision) { - if (revision == typedData.TypedDataRevision.Active) { - // WARNING! Version and revision are encoded as numbers in the StarkNetDomain type and not as shortstring - // This is due to a bug in the Braavos implementation, and has been kept for compatibility - return { - name: "Account.execute_from_outside", - version: "2", - chainId: chainId, - revision: "1", - }; - } - return { - name: "Account.execute_from_outside", - version: "1", - chainId: chainId, - }; -} - -export interface OutsideExecution { - caller: string; - nonce: num.BigNumberish; - execute_after: num.BigNumberish; - execute_before: num.BigNumberish; - calls: OutsideCall[]; -} - -export interface OutsideCall { - to: string; - selector: num.BigNumberish; - calldata: RawArgs; -} - -export function getOutsideCall(call: Call): OutsideCall { - return { - to: call.contractAddress, - selector: hash.getSelectorFromName(call.entrypoint), - calldata: call.calldata ?? [], - }; -} - -export function getTypedDataHash( - outsideExecution: OutsideExecution, - accountAddress: num.BigNumberish, - chainId: string, - revision: typedData.TypedDataRevision, -): string { - return typedData.getMessageHash(getTypedData(outsideExecution, chainId, revision), accountAddress); -} - -export function getTypedData( - outsideExecution: OutsideExecution, - chainId: string, - revision: typedData.TypedDataRevision, -) { - if (revision == typedData.TypedDataRevision.Active) { - return { - types: typesRev1, - primaryType: "OutsideExecution", - domain: getDomain(chainId, revision), - message: { - Caller: outsideExecution.caller, - Nonce: outsideExecution.nonce, - "Execute After": outsideExecution.execute_after, - "Execute Before": outsideExecution.execute_before, - Calls: outsideExecution.calls.map((call) => { - return { - To: call.to, - Selector: call.selector, - Calldata: call.calldata, - }; - }), - }, - }; - } - - return { - types: typesRev0, - primaryType: "OutsideExecution", - domain: getDomain(chainId, revision), - message: { - ...outsideExecution, - calls_len: outsideExecution.calls.length, - calls: outsideExecution.calls.map((call) => { - return { - ...call, - calldata_len: call.calldata.length, - calldata: call.calldata, - }; - }), - }, - }; -} - -export async function getOutsideExecutionCall( - outsideExecution: OutsideExecution, - accountAddress: string, - signer: SignerInterface, - revision: typedData.TypedDataRevision, - chainId?: string, -): Promise { - chainId = chainId ?? (await manager.getChainId()); - const currentTypedData = getTypedData(outsideExecution, chainId, revision); - const signature = await signer.signMessage(currentTypedData, accountAddress); - return { - contractAddress: accountAddress, - entrypoint: revision == typedData.TypedDataRevision.Active ? "execute_from_outside_v2" : "execute_from_outside", - calldata: CallData.compile({ ...outsideExecution, signature }), - }; -} diff --git a/lib/recovery.ts b/lib/recovery.ts deleted file mode 100644 index 6424df8..0000000 --- a/lib/recovery.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { expect } from "chai"; -import { CairoCustomEnum, Contract, hash } from "starknet"; -import { RawSigner } from "."; - -export const ESCAPE_SECURITY_PERIOD = 7n * 24n * 60n * 60n; // 7 days -export const ESCAPE_EXPIRY_PERIOD = 2n * 7n * 24n * 60n * 60n; // 14 days -export const MAX_U64 = 2n ** 64n - 1n; - -export enum EscapeStatus { - None, - NotReady, - Ready, - Expired, -} - -export const ESCAPE_TYPE_NONE = new CairoCustomEnum({ - None: {}, - Guardian: undefined, - Owner: undefined, -}); - -export const ESCAPE_TYPE_GUARDIAN = new CairoCustomEnum({ - None: undefined, - Guardian: {}, - Owner: undefined, -}); - -export const ESCAPE_TYPE_OWNER = new CairoCustomEnum({ - None: undefined, - Guardian: undefined, - Owner: {}, -}); - -export const signChangeOwnerMessage = async ( - accountAddress: string, - currentOwnerGuid: bigint, - newOwner: RawSigner, - chainId: string, -) => { - const messageHash = await getChangeOwnerMessageHash(accountAddress, currentOwnerGuid, chainId); - return newOwner.signRaw(messageHash); -}; - -export const getChangeOwnerMessageHash = async (accountAddress: string, currentOwnerGuid: bigint, chainId: string) => { - const changeOwnerSelector = hash.getSelectorFromName("change_owner"); - return hash.computeHashOnElements([changeOwnerSelector, chainId, accountAddress, currentOwnerGuid]); -}; - -export async function hasOngoingEscape(accountContract: Contract): Promise { - const escape = await accountContract.get_escape(); - return escape.escape_type != 0n && escape.ready_at != 0n && escape.new_signer != 0n; -} - -export async function getEscapeStatus(accountContract: Contract): Promise { - // StarknetJs parsing is broken so we do it manually - const result = (await accountContract.call("get_escape_and_status", undefined, { parseResponse: false })) as string[]; - const result_len = result.length; - expect(result_len).to.be.oneOf([4, 6]); - const status = Number(result[result_len - 1]); - expect(status).to.be.lessThan(4, `Unknown status ${status}`); - return status; -} diff --git a/lib/signers/cairo0-sha256.patch b/lib/signers/cairo0-sha256.patch deleted file mode 100644 index 4dc4e56..0000000 --- a/lib/signers/cairo0-sha256.patch +++ /dev/null @@ -1,31 +0,0 @@ -commit d77431b967d84de3a902fbfea5cf8e1ac972f6de -Author: Yoav Gaziel -Date: Thu Nov 16 11:10:28 2023 +0200 - - add external entrypoint in main.cairo - -diff --git a/src/main.cairo b/src/main.cairo -new file mode 100644 -index 0000000..16c4e00 ---- /dev/null -+++ b/src/main.cairo -@@ -0,0 +1,19 @@ -+%lang starknet -+from starkware.cairo.common.alloc import alloc -+from starkware.cairo.common.math import split_int -+from starkware.cairo.common.memcpy import memcpy -+from starkware.cairo.common.cairo_builtins import BitwiseBuiltin, HashBuiltin -+from src.sha256 import sha256, finalize_sha256 -+ -+@view -+func sha256_cairo0{bitwise_ptr: BitwiseBuiltin*, pedersen_ptr: HashBuiltin*, range_check_ptr}( -+ data_len: felt, data: felt*, data_len_no_padding: felt -+) -> (result_len: felt, result: felt*) { -+ alloc_locals; -+ let (local sha256_ptr_start: felt*) = alloc(); -+ let sha256_ptr = sha256_ptr_start; -+ let sha256_ptr_end = sha256_ptr_start; -+ let (hash) = sha256{sha256_ptr=sha256_ptr}(data, data_len_no_padding); -+ finalize_sha256(sha256_ptr_start=sha256_ptr_start, sha256_ptr_end=sha256_ptr_end); -+ return (8, hash); -+} diff --git a/lib/signers/secp256.ts b/lib/signers/secp256.ts deleted file mode 100644 index 9c52dfc..0000000 --- a/lib/signers/secp256.ts +++ /dev/null @@ -1,229 +0,0 @@ -import * as utils from "@noble/curves/abstract/utils"; -import { p256 as secp256r1 } from "@noble/curves/p256"; -import { secp256k1 } from "@noble/curves/secp256k1"; -import { Signature as EthersSignature, Wallet } from "ethers"; -import { CairoCustomEnum, CallData, hash, num, shortString, uint256 } from "starknet"; -import { KeyPair, SignerType, signerTypeToCustomEnum } from "../signers/signers"; - -export type NormalizedSecpSignature = { r: bigint; s: bigint; yParity: boolean }; - -export function normalizeSecpR1Signature(signature: { - r: bigint; - s: bigint; - recovery: number; -}): NormalizedSecpSignature { - return normalizeSecpSignature(secp256r1, signature); -} - -export function normalizeSecpK1Signature(signature: { - r: bigint; - s: bigint; - recovery: number; -}): NormalizedSecpSignature { - return normalizeSecpSignature(secp256k1, signature); -} - -export function normalizeSecpSignature( - curve: typeof secp256r1 | typeof secp256k1, - signature: { r: bigint; s: bigint; recovery: number }, -): NormalizedSecpSignature { - let s = signature.s; - let yParity = signature.recovery !== 0; - if (s > curve.CURVE.n / 2n) { - s = curve.CURVE.n - s; - yParity = !yParity; - } - return { r: signature.r, s, yParity }; -} - -export class EthKeyPair extends KeyPair { - pk: bigint; - allowLowS?: boolean; - - constructor(pk?: string | bigint, allowLowS?: boolean) { - super(); - - if (pk == undefined) { - pk = Wallet.createRandom().privateKey; - } - if (typeof pk === "string") { - pk = BigInt(pk); - } - this.pk = pk; - this.allowLowS = allowLowS; - } - - public get address(): bigint { - return BigInt(new Wallet("0x" + padTo32Bytes(num.toHex(this.pk))).address); - } - - public get guid(): bigint { - return BigInt(hash.computePoseidonHash(shortString.encodeShortString("Secp256k1 Signer"), this.address)); - } - - public get storedValue(): bigint { - throw new Error("Not implemented yet"); - } - - public get signer(): CairoCustomEnum { - return signerTypeToCustomEnum(SignerType.Secp256k1, { signer: this.address }); - } - - public async signRaw(messageHash: string): Promise { - const signature = normalizeSecpK1Signature( - secp256k1.sign(padTo32Bytes(messageHash), this.pk, { lowS: this.allowLowS }), - ); - - return CallData.compile([ - signerTypeToCustomEnum(SignerType.Secp256k1, { - pubkeyHash: this.address, - r: uint256.bnToUint256(signature.r), - s: uint256.bnToUint256(signature.s), - y_parity: signature.yParity, - }), - ]); - } -} - -export class Eip191KeyPair extends KeyPair { - pk: string; - - constructor(pk?: string | bigint) { - super(); - this.pk = pk ? "0x" + padTo32Bytes(num.toHex(pk)) : Wallet.createRandom().privateKey; - } - - public get address() { - return BigInt(new Wallet(this.pk).address); - } - - public get guid(): bigint { - return BigInt(hash.computePoseidonHash(shortString.encodeShortString("Eip191 Signer"), this.address)); - } - - public get storedValue(): bigint { - throw new Error("Not implemented yet"); - } - - public get signer(): CairoCustomEnum { - return signerTypeToCustomEnum(SignerType.Eip191, { signer: this.address }); - } - - public async signRaw(messageHash: string): Promise { - const ethSigner = new Wallet(this.pk); - messageHash = "0x" + padTo32Bytes(messageHash); - const ethersSignature = EthersSignature.from(ethSigner.signMessageSync(num.hexToBytes(messageHash))); - - const signature = normalizeSecpK1Signature({ - r: BigInt(ethersSignature.r), - s: BigInt(ethersSignature.s), - recovery: ethersSignature.yParity ? 1 : 0, - }); - - return CallData.compile([ - signerTypeToCustomEnum(SignerType.Eip191, { - ethAddress: this.address, - r: uint256.bnToUint256(signature.r), - s: uint256.bnToUint256(signature.s), - y_parity: signature.yParity, - }), - ]); - } -} - -export class EstimateEip191KeyPair extends KeyPair { - readonly address: bigint; - - constructor(address: bigint) { - super(); - this.address = address; - } - - public get privateKey(): string { - throw new Error("EstimateEip191KeyPair does not have a private key"); - } - - public get guid(): bigint { - throw new Error("Not implemented yet"); - } - - public get storedValue(): bigint { - throw new Error("Not implemented yet"); - } - - public get signer(): CairoCustomEnum { - return signerTypeToCustomEnum(SignerType.Eip191, { signer: this.address }); - } - - public async signRaw(messageHash: string): Promise { - return CallData.compile([ - signerTypeToCustomEnum(SignerType.Eip191, { - ethAddress: this.address, - r: uint256.bnToUint256("0x1556a70d76cc452ae54e83bb167a9041f0d062d000fa0dcb42593f77c544f647"), - s: uint256.bnToUint256("0x1643d14dbd6a6edc658f4b16699a585181a08dba4f6d16a9273e0e2cbed622da"), - y_parity: false, - }), - ]); - } -} - -export class Secp256r1KeyPair extends KeyPair { - pk: bigint; - private allowLowS?: boolean; - - constructor(pk?: string | bigint, allowLowS?: boolean) { - super(); - this.pk = BigInt(pk ? `${pk}` : Wallet.createRandom().privateKey); - this.allowLowS = allowLowS; - } - - public get publicKey() { - const publicKey = secp256r1.getPublicKey(this.pk).slice(1); - return uint256.bnToUint256("0x" + utils.bytesToHex(publicKey)); - } - - public get guid(): bigint { - return BigInt( - hash.computePoseidonHashOnElements([ - shortString.encodeShortString("Secp256r1 Signer"), - this.publicKey.low, - this.publicKey.high, - ]), - ); - } - - public get storedValue(): bigint { - throw new Error("Not implemented yet"); - } - - public get signer() { - return signerTypeToCustomEnum(SignerType.Secp256r1, { signer: this.publicKey }); - } - - public async signRaw(messageHash: string): Promise { - messageHash = padTo32Bytes(messageHash); - const signature = normalizeSecpR1Signature(secp256r1.sign(messageHash, this.pk, { lowS: this.allowLowS })); - return CallData.compile([ - signerTypeToCustomEnum(SignerType.Secp256r1, { - pubkey: this.publicKey, - r: uint256.bnToUint256(signature.r), - s: uint256.bnToUint256(signature.s), - y_parity: signature.yParity, - }), - ]); - } -} - -export function padTo32Bytes(hexString: string): string { - if (hexString.startsWith("0x")) { - hexString = hexString.slice(2); - } - if (hexString.length < 64) { - hexString = "0".repeat(64 - hexString.length) + hexString; - } - return hexString; -} - -export const randomEthKeyPair = () => new EthKeyPair(); -export const randomEip191KeyPair = () => new Eip191KeyPair(); -export const randomSecp256r1KeyPair = () => new Secp256r1KeyPair(); diff --git a/lib/signers/webauthn.ts b/lib/signers/webauthn.ts deleted file mode 100644 index f38a1de..0000000 --- a/lib/signers/webauthn.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { concatBytes } from "@noble/curves/abstract/utils"; -import { p256 as secp256r1 } from "@noble/curves/p256"; -import { BinaryLike, createHash } from "crypto"; -import { - ArraySignatureType, - BigNumberish, - CairoCustomEnum, - CallData, - Uint256, - hash, - shortString, - uint256, -} from "starknet"; -import { KeyPair, SignerType, normalizeSecpR1Signature, signerTypeToCustomEnum } from ".."; - -const buf2hex = (buffer: ArrayBuffer, prefix = true) => - `${prefix ? "0x" : ""}${[...new Uint8Array(buffer)].map((x) => x.toString(16).padStart(2, "0")).join("")}`; - -const normalizeTransactionHash = (transactionHash: string) => transactionHash.replace(/^0x/, "").padStart(64, "0"); - -const buf2base64url = (buffer: ArrayBuffer) => - buf2base64(buffer).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); - -const buf2base64 = (buffer: ArrayBuffer) => btoa(String.fromCharCode(...new Uint8Array(buffer))); - -const hex2buf = (hex: string) => - Uint8Array.from( - hex - .replace(/^0x/, "") - .match(/.{1,2}/g)! - .map((byte) => parseInt(byte, 16)), - ); - -const toCharArray = (value: string) => CallData.compile(value.split("").map(shortString.encodeShortString)); - -interface WebauthnSigner { - origin: BigNumberish[]; - rp_id_hash: Uint256; - pubkey: Uint256; -} - -interface WebauthnSignature { - cross_origin: boolean; - client_data_json_outro: BigNumberish[]; - flags: number; - sign_count: number; - ec_signature: { r: Uint256; s: Uint256; y_parity: boolean }; - sha256_implementation: CairoCustomEnum; -} - -export class WebauthnOwner extends KeyPair { - pk: Uint8Array; - rpIdHash: Uint256; - - constructor( - pk?: string, - public rpId = "localhost", - public origin = "http://localhost:5173", - ) { - super(); - this.pk = pk ? hex2buf(normalizeTransactionHash(pk)) : secp256r1.utils.randomPrivateKey(); - this.rpIdHash = uint256.bnToUint256(buf2hex(sha256(rpId))); - } - - public get publicKey() { - return secp256r1.getPublicKey(this.pk).slice(1); - } - - public get guid(): bigint { - const rpIdHashAsU256 = this.rpIdHash; - const publicKeyAsU256 = uint256.bnToUint256(buf2hex(this.publicKey)); - const originBytes = toCharArray(this.origin); - const elements = [ - shortString.encodeShortString("Webauthn Signer"), - originBytes.length, - ...originBytes, - rpIdHashAsU256.low, - rpIdHashAsU256.high, - publicKeyAsU256.low, - publicKeyAsU256.high, - ]; - return BigInt(hash.computePoseidonHashOnElements(elements)); - } - - public get storedValue(): bigint { - throw new Error("Not implemented yet"); - } - - public get signer(): CairoCustomEnum { - const signer: WebauthnSigner = { - origin: toCharArray(this.origin), - rp_id_hash: this.rpIdHash, - pubkey: uint256.bnToUint256(buf2hex(this.publicKey)), - }; - return signerTypeToCustomEnum(SignerType.Webauthn, signer); - } - - public async signRaw(messageHash: string): Promise { - const webauthnSigner = this.signer.variant.Webauthn; - const webauthnSignature = await this.signHash(messageHash); - return CallData.compile([signerTypeToCustomEnum(SignerType.Webauthn, { webauthnSigner, webauthnSignature })]); - } - - public async signHash(transactionHash: string): Promise { - const flags = "0b00000101"; // present and verified - const signCount = 0; - const authenticatorData = concatBytes(sha256(this.rpId), new Uint8Array([Number(flags), 0, 0, 0, signCount])); - - const sha256Impl = 0; - const challenge = buf2base64url(hex2buf(`${normalizeTransactionHash(transactionHash)}0${sha256Impl}`)); - const crossOrigin = false; - const extraJson = ""; // = `,"extraField":"random data"}`; - const clientData = JSON.stringify({ type: "webauthn.get", challenge, origin: this.origin, crossOrigin }); - const clientDataJson = extraJson ? clientData.replace(/}$/, extraJson) : clientData; - const clientDataHash = sha256(new TextEncoder().encode(clientDataJson)); - - const signedHash = sha256(concatBytes(authenticatorData, clientDataHash)); - - const signature = normalizeSecpR1Signature(secp256r1.sign(signedHash, this.pk)); - - // console.log(` - // let transaction_hash = ${transactionHash}; - // let pubkey = ${buf2hex(this.publicKey)}; - // let signer = new_webauthn_signer(:origin, :rp_id_hash, :pubkey); - // let signature = WebauthnSignature { - // cross_origin: ${crossOrigin}, - // client_data_json_outro: ${extraJson ? `${JSON.stringify(extraJson)}.into_bytes()` : "array![]"}.span(), - // flags: ${flags}, - // sign_count: ${signCount}, - // ec_signature: Signature { - // r: 0x${r.toString(16)}, - // s: 0x${s.toString(16)}, - // y_parity: ${recovery !== 0}, - // }, - // sha256_implementation: Sha256Implementation::Cairo${sha256Impl}, - // };`); - - return { - cross_origin: crossOrigin, - client_data_json_outro: CallData.compile(toCharArray(extraJson)), - flags: Number(flags), - sign_count: signCount, - ec_signature: { - r: uint256.bnToUint256(signature.r), - s: uint256.bnToUint256(signature.s), - y_parity: signature.yParity, - }, - sha256_implementation: new CairoCustomEnum({ - Cairo0: sha256Impl ? undefined : {}, - Cairo1: sha256Impl ? {} : undefined, - }), - }; - } -} - -function sha256(message: BinaryLike) { - return createHash("sha256").update(message).digest(); -} - -export const randomWebauthnOwner = () => new WebauthnOwner(); diff --git a/lib/udc.ts b/lib/udc.ts deleted file mode 100644 index fe8abe1..0000000 --- a/lib/udc.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { CallData, RawCalldata } from "starknet"; -import { deployer, manager } from "."; - -export const udcAddress = "0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf"; - -export async function deployContractUDC(classHash: string, salt: string, calldata: RawCalldata) { - const unique = 0n; //false - - const udcContract = await manager.loadContract(udcAddress); - - udcContract.connect(deployer); - - const deployCall = udcContract.populate("deployContract", CallData.compile([classHash, salt, unique, calldata])); - const { transaction_hash } = await udcContract.deployContract(deployCall.calldata); - - const transaction_response = await manager.waitForTransaction(transaction_hash); - - return transaction_response.events?.[0].from_address; -}