Skip to content

Commit

Permalink
fix: address feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
sarahschwartz committed Sep 10, 2024
1 parent a4b7552 commit c8c3e06
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 67 deletions.
6 changes: 5 additions & 1 deletion code/webauthn/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,13 @@ npm run compile
npm run transfer
```

From the logged output of the `transfer` script, copy the account address after "SC Account deployed on address" and save it to your `.env` file:
From the logged output of the `transfer` script, copy the private key after "SC Account owner pk:"
and the account address after "SC Account deployed on address"
and save them to the `ACCOUNT_PK` and `ACCOUNT_ADDRESS` variables your `.env` file.

```env
ACCOUNT_ADDRESS=0x...
ACCOUNT_PK=0x...
```

You'll come back to this later.
Expand Down Expand Up @@ -70,6 +73,7 @@ Your final `.env` file should look like this:
WALLET_PRIVATE_KEY=0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110
RECEIVER_ACCOUNT=0xa61464658AfeAf65CccaaFD3a512b69A83B77618
ACCOUNT_ADDRESS=0x<YOUR_ACCOUNT_ADDRESS_HERE>
ACCOUNT_PK=0x<YOUR_ACCOUNT_PRIVATE_KEY_HERE>
NEW_R1_OWNER_PUBLIC_KEY=0x<YOUR_PUB_KEY_HERE>
```

Expand Down
1 change: 1 addition & 0 deletions code/webauthn/contracts/.env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
WALLET_PRIVATE_KEY=
RECEIVER_ACCOUNT=
ACCOUNT_ADDRESS=
ACCOUNT_PK=
NEW_R1_OWNER_PUBLIC_KEY=
97 changes: 39 additions & 58 deletions code/webauthn/contracts/contracts/Account.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';
import '@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol';
// to call non-view function of system contracts
import '@matterlabs/zksync-contracts/l2/system-contracts/libraries/SystemContractsCaller.sol';
import "@openzeppelin/contracts/utils/Base64.sol";

contract Account is IAccount, IERC1271 {
// to get transaction hash
Expand All @@ -27,6 +28,9 @@ contract Account is IAccount, IERC1271 {
// maximum value for 's' in a secp256r1 signature
bytes32 constant lowSmax = 0x7fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a8;

// webauthn user flags
bytes1 constant AUTH_DATA_MASK = 0x05;

bytes4 constant EIP1271_SUCCESS_RETURN_VALUE = 0x1626ba7e;

modifier onlyBootloader() {
Expand All @@ -35,6 +39,11 @@ contract Account is IAccount, IERC1271 {
_;
}

modifier onlySelf() {
require(msg.sender == address(this), "Only the contract itself allowed to call this function");
_;
}

constructor(address _owner) {
owner = _owner;
}
Expand Down Expand Up @@ -118,11 +127,7 @@ contract Account is IAccount, IERC1271 {
}
}

function executeTransactionFromOutside(Transaction calldata _transaction) external payable {
bytes4 magic = _validateTransaction(bytes32(0), _transaction);
require(magic == ACCOUNT_VALIDATION_SUCCESS_MAGIC, 'NOT VALIDATED');
_executeTransaction(_transaction);
}
function executeTransactionFromOutside(Transaction calldata _transaction) external payable {}

function isValidSignature(bytes32 _hash, bytes memory _signature) public view override returns (bytes4 magic) {
magic = EIP1271_SUCCESS_RETURN_VALUE;
Expand Down Expand Up @@ -196,9 +201,7 @@ contract Account is IAccount, IERC1271 {
_transaction.processPaymasterInput();
}

// WARNING: This function is not safe. Anyone can call it and change the owner of the account.
// It is only used for testing purposes
function updateR1Owner(bytes memory _r1Owner) external {
function updateR1Owner(bytes memory _r1Owner) external onlySelf {
r1Owner = _r1Owner;
}

Expand Down Expand Up @@ -232,6 +235,11 @@ contract Account is IAccount, IERC1271 {
return false;
}

// check if the webauthn user flags are set
if (authenticatorData[32] & AUTH_DATA_MASK != AUTH_DATA_MASK) {
return false;
}

bytes32 clientDataHash = sha256(clientData);
bytes32 message = sha256(bytes.concat(authenticatorData, clientDataHash));

Expand Down Expand Up @@ -278,58 +286,31 @@ contract Account is IAccount, IERC1271 {
}
return result;
}
function txHashToBase64Url(bytes32 txHash) public pure returns (string memory) {
bytes memory alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';

// Convert txHash to hex string
bytes memory hexString = new bytes(66); // '0x' + 64 hex characters
hexString[0] = '0';
hexString[1] = 'x';
bytes memory hexChars = '0123456789abcdef';
for (uint256 i = 0; i < 32; i++) {
uint8 b = uint8(txHash[i]);
hexString[2 + i * 2] = hexChars[b >> 4];
hexString[3 + i * 2] = hexChars[b & 0x0f];
}

// Encode hex string to base64Url
bytes memory data = hexString;
uint256 fullGroups = data.length / 3;
uint256 resultLength = 4 * ((data.length + 2) / 3);

bytes memory result = new bytes(resultLength);
uint256 resultIndex = 0;
uint256 dataIndex = 0;

// Convert full groups
for (uint256 i = 0; i < fullGroups; i++) {
uint256 n = (uint256(uint8(data[dataIndex++])) << 16) |
(uint256(uint8(data[dataIndex++])) << 8) |
uint256(uint8(data[dataIndex++]));

result[resultIndex++] = alphabet[n >> 18];
result[resultIndex++] = alphabet[(n >> 12) & 0x3F];
result[resultIndex++] = alphabet[(n >> 6) & 0x3F];
result[resultIndex++] = alphabet[n & 0x3F];
}

// Handle padding
uint256 remainder = data.length % 3;
if (remainder > 0) {
uint256 n = 0;
for (uint256 i = 0; i < remainder; i++) {
n |= uint256(uint8(data[dataIndex++])) << (16 - 8 * i);
}

result[resultIndex++] = alphabet[n >> 18];
result[resultIndex++] = alphabet[(n >> 12) & 0x3F];

if (remainder == 2) {
result[resultIndex++] = alphabet[(n >> 6) & 0x3F];
}
function txHashToBase64Url(bytes32 txHash) public pure returns (string memory) {
// Convert txHash to hex string
bytes memory hexString = new bytes(66); // '0x' + 64 hex characters
hexString[0] = '0';
hexString[1] = 'x';
bytes memory hexChars = "0123456789abcdef";
for (uint256 i = 0; i < 32; i++) {
uint8 b = uint8(txHash[i]);
hexString[2 + i * 2] = hexChars[b >> 4];
hexString[3 + i * 2] = hexChars[b & 0x0f];
}

// Encode to base64
string memory base64String = Base64.encode(hexString);

// Convert to base64url (replace '+' with '-' and '/' with '_')
bytes memory base64Bytes = bytes(base64String);
for (uint256 i = 0; i < base64Bytes.length; i++) {
if (base64Bytes[i] == '+') base64Bytes[i] = '-';
if (base64Bytes[i] == '/') base64Bytes[i] = '_';
}

return string(slice(base64Bytes, 0, 58));
}
return string(slice(result, 0, 58));
}

fallback() external {
// fallback of default account shouldn't be called by bootloader under no circumstances
Expand Down
2 changes: 1 addition & 1 deletion code/webauthn/contracts/deploy/deployAndTransfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default async function (hre: HardhatRuntimeEnvironment) {
} as types.Eip712Meta,
value: ethers.parseEther(transferAmount),
gasPrice: await provider.getGasPrice(),
gasLimit: BigInt(20000000), // constant 20M since estimateGas() causes an error and this tx consumes more than 15M at most
gasLimit: BigInt(20000000),
data: '0x',
};
console.log(`ETH transfer tx 1: ${ethTransferTx}`);
Expand Down
42 changes: 35 additions & 7 deletions code/webauthn/contracts/deploy/registerR1Owner.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Wallet, Provider } from 'zksync-ethers';
import { Wallet, Provider, EIP712Signer, types, utils } from 'zksync-ethers';
import * as ethers from 'ethers';
import type { HardhatRuntimeEnvironment } from 'hardhat/types';

Expand All @@ -9,7 +9,7 @@ dotenv.config();
// load the values into .env file after deploying an account
const ACCOUNT_ADDRESS = process.env.ACCOUNT_ADDRESS;
const NEW_R1_OWNER_PUBLIC_KEY = process.env.NEW_R1_OWNER_PUBLIC_KEY;
const DEPLOYER_PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY;
const DEPLOYER_PRIVATE_KEY = process.env.ACCOUNT_PK;

export default async function (hre: HardhatRuntimeEnvironment) {
if (!ACCOUNT_ADDRESS || !NEW_R1_OWNER_PUBLIC_KEY || !DEPLOYER_PRIVATE_KEY) {
Expand All @@ -22,12 +22,40 @@ export default async function (hre: HardhatRuntimeEnvironment) {

// Load compiled contract info
const contractArtifact = await hre.artifacts.readArtifact('Account');
const contract = new ethers.Contract(ACCOUNT_ADDRESS, contractArtifact.abi, provider);
const data = contract.interface.encodeFunctionData('updateR1Owner', [NEW_R1_OWNER_PUBLIC_KEY]);
const transferAmount = '0';

// Initialize contract instance for interaction
const contract = new ethers.Contract(ACCOUNT_ADDRESS, contractArtifact.abi, wallet);
const ethTransferTx = {
from: ACCOUNT_ADDRESS,
to: ACCOUNT_ADDRESS,
chainId: (await provider.getNetwork()).chainId,
nonce: await provider.getTransactionCount(ACCOUNT_ADDRESS),
type: 113,
customData: {
gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT,
} as types.Eip712Meta,
value: ethers.parseEther(transferAmount),
gasPrice: await provider.getGasPrice(),
gasLimit: BigInt(20000000),
data,
};
console.log(`tx: ${ethTransferTx}`);
const signedTxHash = EIP712Signer.getSignedDigest(ethTransferTx);
console.log(`Signed tx hash: ${signedTxHash}`);
const signature = ethers.concat([ethers.Signature.from(wallet.signingKey.sign(signedTxHash)).serialized]);

console.log(`Signature: ${signature}`);
ethTransferTx.customData = {
...ethTransferTx.customData,
customSignature: signature,
};

// make the call
console.log('Registering new R1 Owner');
const sentTx = await provider.broadcastTransaction(types.Transaction.from(ethTransferTx).serialized);
await sentTx.wait();
console.log('Registration completed!');

const tx = await contract.updateR1Owner(NEW_R1_OWNER_PUBLIC_KEY);
await tx.wait();
console.log('R1 Owner updated successfully');
return;
}

0 comments on commit c8c3e06

Please sign in to comment.