Skip to content

Commit

Permalink
docs: outline tutorial
Browse files Browse the repository at this point in the history
  • Loading branch information
sarahschwartz committed Sep 12, 2024
1 parent e83b67c commit 6f40762
Show file tree
Hide file tree
Showing 31 changed files with 1,506 additions and 490 deletions.
12 changes: 10 additions & 2 deletions code/webauthn/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# Sign Transactions With Webauthn Example

Note: this code is not secure. It is just intended as a proof of concept to demonstrate how signing transactions using secp256r1 signatures is possible.

This demo requires Google Chrome and a device that supports webauthn.

## Running Locally
Expand All @@ -19,6 +17,16 @@ Run a local in-memory node with `era_test_node`:
era_test_node run
```

### Deploying the Contracts

Deploy the AAFactory, paymaster, and NFT contracts:

```shell
npm run deploy
```

Once deployed, update the addresses in `frontend/utils/constants.ts`.

### Deploying a New Smart Account

Go to the frontend folder and start a development server:
Expand Down
12 changes: 10 additions & 2 deletions code/webauthn/contracts/contracts/Account.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ contract Account is IAccount, IERC1271 {
// state variable for account owner
address public owner;

// ANCHOR: r1Owner
// the secp256r1 public key of the owner
bytes public r1Owner;
// ANCHOR_END: r1Owner

// P256Verify precompile address
address constant P256 = 0x0000000000000000000000000000000000000100;
Expand Down Expand Up @@ -205,20 +207,23 @@ contract Account is IAccount, IERC1271 {
r1Owner = _r1Owner;
}

// ANCHOR: validateWebAuthnSignature
function validateWebAuthnSignature(bytes memory webauthnSignature, bytes32 txHash) private view returns (bool valid) {
if (r1Owner.length == 0) {
return false;
}
bytes32[2] memory pubKey = abi.decode(r1Owner, (bytes32[2]));
valid = _validateWebAuthnSignature(txHash, webauthnSignature, pubKey);
}
// ANCHOR_END: validateWebAuthnSignature

// ANCHOR: _validateWebAuthnSignature
function _validateWebAuthnSignature(
bytes32 txHash,
bytes memory webauthnSignature,
bytes32[2] memory pubKey
) public view returns (bool valid) {
(bytes memory authenticatorData, bytes memory clientData, bytes32[2] memory rs) = _decodeWebauthnSignature(
(bytes memory authenticatorData, bytes memory clientData, bytes32[2] memory rs) = _decodeWebAuthnSignature(
webauthnSignature
);

Expand All @@ -245,13 +250,15 @@ contract Account is IAccount, IERC1271 {

valid = callVerifier(message, rs, pubKey);
}
// ANCHOR_END: _validateWebAuthnSignature

function _decodeWebauthnSignature(
function _decodeWebAuthnSignature(
bytes memory webauthnSignature
) private pure returns (bytes memory authenticatorData, bytes memory clientData, bytes32[2] memory rs) {
(authenticatorData, clientData, rs) = abi.decode(webauthnSignature, (bytes, bytes, bytes32[2]));
}

// ANCHOR: callVerifier
function callVerifier(bytes32 hash, bytes32[2] memory rs, bytes32[2] memory pubKey) internal view returns (bool) {
/**
* Prepare the input format
Expand All @@ -272,6 +279,7 @@ contract Account is IAccount, IERC1271 {

return abi.decode(output, (bool));
}
// ANCHOR_END: callVerifier

function extractChallengeFromClientData(bytes memory clientDataJSON) public pure returns (string memory) {
bytes memory challengeSlice = slice(clientDataJSON, 36, 58);
Expand Down
66 changes: 36 additions & 30 deletions code/webauthn/contracts/deploy/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { utils, Wallet, Provider } from 'zksync-ethers';
import * as ethers from 'ethers';
import type { HardhatRuntimeEnvironment } from 'hardhat/types';
import { Deployer } from '@matterlabs/hardhat-zksync-deploy';

// load env file
import dotenv from 'dotenv';
import { ethers } from 'ethers';
dotenv.config();

const DEPLOYER_PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || '';
Expand All @@ -14,45 +13,52 @@ export default async function (hre: HardhatRuntimeEnvironment) {
const provider = new Provider(hre.network.config.url);
const wallet = new Wallet(DEPLOYER_PRIVATE_KEY, provider);
const deployer = new Deployer(hre, wallet);
await deploy(deployer, wallet);

// deploy the AA Factory & test deploying an account
await deployAAFactory(deployer);
// deploy the NFT contract
await deployMyNFT(deployer);
// deploy the Paymaster contract
await deployPaymaster(deployer, wallet);
}

export async function deploy(deployer: Deployer, wallet: Wallet) {
async function deployAAFactory(deployer: Deployer) {
const factoryArtifact = await deployer.loadArtifact('AAFactory');
const aaArtifact = await deployer.loadArtifact('Account');

const factory = await deployer.deploy(factoryArtifact, [utils.hashBytecode(aaArtifact.bytecode)], undefined, [
aaArtifact.bytecode,
]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
] as any);
const factoryAddress = await factory.getAddress();
console.log(`AA factory address: ${factoryAddress}`);
}

const aaFactory = new ethers.Contract(factoryAddress, factoryArtifact.abi, wallet);

const owner = Wallet.createRandom();
console.log('SC Account owner pk: ', owner.privateKey);
async function deployMyNFT(deployer: Deployer) {
const artifact = await deployer.loadArtifact('MyNFT');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const constructorArguments: any[] = [];
const contract = await deployer.deploy(artifact, constructorArguments);
console.log('NFT CONTRACT ADDRESS: ', await contract.getAddress());

const salt = ethers.ZeroHash;
const tx = await aaFactory.deployAccount(salt, owner.address);
const tx = await contract.mintZeek();
await tx.wait();
console.log('DONE MINTING');
}

async function deployPaymaster(deployer: Deployer, wallet: Wallet) {
const artifact = await deployer.loadArtifact('GeneralPaymaster');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const constructorArguments: any[] = [];
const contract = await deployer.deploy(artifact, constructorArguments);
const paymasterAddress = await contract.getAddress();
console.log('PAYMASTER CONTRACT ADDRESS: ', paymasterAddress);

const abiCoder = new ethers.AbiCoder();
const accountAddress = utils.create2Address(
factoryAddress,
await aaFactory.aaBytecodeHash(),
salt,
abiCoder.encode(['address'], [owner.address])
);

console.log(`SC Account deployed on address ${accountAddress}`);

console.log('Funding smart contract account with some ETH');
await (
await wallet.sendTransaction({
to: accountAddress,
value: ethers.parseEther('100'),
})
).wait();
console.log(`Done!`);
return { accountAddress, owner };
const tx = await wallet.sendTransaction({
to: paymasterAddress,
value: ethers.parseEther('10'),
});

await tx.wait();
console.log('DONE DEPLOYING & FUNDING PAYMASTER');
}
69 changes: 0 additions & 69 deletions code/webauthn/contracts/deploy/deployAndTransfer.ts

This file was deleted.

25 changes: 0 additions & 25 deletions code/webauthn/contracts/deploy/deployMyNFT.ts

This file was deleted.

31 changes: 0 additions & 31 deletions code/webauthn/contracts/deploy/deployPaymaster.ts

This file was deleted.

60 changes: 0 additions & 60 deletions code/webauthn/contracts/deploy/registerR1Owner.ts

This file was deleted.

Loading

0 comments on commit 6f40762

Please sign in to comment.