Skip to main content

Overview

alt text

https://github.com/stackup-wallet/userop.js/blob/main/README.md

https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/core/BasePaymaster.sol

https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/core/EntryPoint.sol

https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/core/BaseAccount.sol

https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/utils/Exec.sol

factory

https://github.com/erc6900/reference-implementation/blob/develop/test/mocks/SingleSignerFactoryFixture.sol

1. UserOp calldata 数据 调用 wallet 合约的方法

在以太坊智能合约开发中,UserOperationcallData 字段通常用于指定要调用的合约方法及其参数。在大多数情况下,callData 会包含一个合约方法的编码数据,这个方法通常是钱包合约中的方法。

UserOperation 结构

UserOperation 是一种常见的结构体,用于表示用户操作。假设你有以下结构:

struct UserOperation {
uint256 nonce;
bytes callData;
// 其他字段...
}

callData 字段

callData 是一个 bytes 类型的字段,包含要调用的合约方法及其参数的编码数据。通常,callData 是通过 abi.encodeWithSelectorabi.encodeWithSignature 编码的。

为什么 callData 通常是钱包合约的方法

在许多钱包合约中,callData 被设计为调用钱包合约内的方法。这是因为钱包合约通常需要执行特定的逻辑,如转账、调用其他合约等。因此,callData 被用来指定这些操作。

示例

假设你有一个钱包合约 Wallet,其中有一个方法 execute

contract Wallet {
function execute(address to, uint256 value, bytes calldata data) external {
// 执行逻辑...
}
}

你可以使用 abi.encodeWithSelector 来编码对 execute 方法的调用数据:

bytes memory callData = abi.encodeWithSelector(
Wallet.execute.selector,
toAddress,
value,
data
);

然后将这个 callData 放入 UserOperation 中:

UserOperation memory userOp;
userOp.nonce = someNonce;
userOp.callData = callData;

其他合约的方法

虽然 callData 通常是钱包合约的方法,但它也可以是任何合约的方法。这取决于你的具体需求和设计。例如,如果你需要调用另一个合约的方法,你可以编码那个合约的方法数据并将其放入 callData

示例:调用另一个合约的方法

假设你有另一个合约 Token,其中有一个方法 transfer

contract Token {
function transfer(address to, uint256 value) external {
// 转账逻辑...
}
}

你可以编码对 transfer 方法的调用数据:

bytes memory callData = abi.encodeWithSelector(
Token.transfer.selector,
recipientAddress,
amount
);

然后将这个 callData 放入 UserOperation 中:

UserOperation memory userOp;
userOp.nonce = someNonce;
userOp.callData = callData;

总结

UserOperationcallData 字段通常是钱包合约的方法,但它也可以是任何合约的方法。这取决于你的具体需求和设计。通过使用 abi.encodeWithSelectorabi.encodeWithSignature,你可以编码任意合约方法的调用数据,并将其放入 callData 字段中。

2. UserOp calldata 数据 调用 非 wallet 合约的方法

这是一个很好的问题。在以太坊智能合约中,函数调用的执行是由 callData 和目标合约地址共同决定的。callData 包含了函数选择器和参数,而目标合约地址则指定了哪个合约应该执行这个调用。

函数选择器和 callData

函数选择器是一个 4 字节的哈希值,表示你要调用的合约方法。它是通过对方法签名(如 transfer(address,uint256))进行 Keccak-256 哈希计算并取前 4 个字节得到的。

例如,Token.transfer.selector 计算如下:

bytes4 selector = bytes4(keccak256("transfer(address,uint256)"));

callData 的构造

当你构造 callData 时,包含了函数选择器和参数:

bytes memory callData = abi.encodeWithSelector(
Token.transfer.selector,
recipientAddress,
amount
);

目标合约地址

要执行这个调用,你还需要指定目标合约的地址。例如,假设 tokenAddressToken 合约的地址:

address tokenAddress = 0x1234567890abcdef1234567890abcdef12345678;

执行调用

在 Solidity 合约中,你可以使用 call 方法来执行这个调用:

(bool success, bytes memory returnData) = tokenAddress.call(callData);
require(success, "Call failed");

这里的 call 方法会将 callData 发送到 tokenAddress,并执行 Token 合约中的 transfer 方法。

示例

假设你有一个钱包合约 Wallet,其中有一个方法 execute 用于执行任意合约的方法调用:

contract Wallet {
function execute(address to, uint256 value, bytes calldata data) external {
(bool success, bytes memory returnData) = to.call{value: value}(data);
require(success, "Call failed");
}
}

你可以使用这个 execute 方法来调用 Token 合约的 transfer 方法:

// 构造 callData
bytes memory callData = abi.encodeWithSelector(
Token.transfer.selector,
recipientAddress,
amount
);

// 调用 Wallet 合约的 execute 方法
wallet.execute(tokenAddress, 0, callData);

总结

  • 函数选择器: 是通过对方法签名进行 Keccak-256 哈希计算并取前 4 个字节得到的。
  • callData: 包含了函数选择器和参数。
  • 目标合约地址: 指定了哪个合约应该执行这个调用。
  • 调用执行: 使用 call 方法将 callData 发送到目标合约地址。

通过这种方式,Token.transfer.selectorcallData 可以在目标合约地址上找到并执行对应的方法。

3. passkeys 提取公钥 x,y

  1. 你提供的 JSON 数据是一个典型的 WebAuthn 注册请求,用于生成和注册新的公钥凭证。这个请求中没有直接体现出公钥的 ( x ) 和 ( y ) 坐标,因为这些坐标通常是在注册完成后由设备生成并返回的。

WebAuthn 注册流程

  1. 客户端生成注册请求:包含了挑战 (challenge)、RP(可信方)信息、用户信息、支持的公钥算法等。
  2. 设备生成公钥对:设备(如手机或安全密钥)生成公钥对,并将公钥(以及其他相关信息)发送回服务器。
  3. 服务器验证并存储公钥:服务器验证设备返回的响应,并存储公钥(包括 ( x ) 和 ( y ) 坐标)。

提取公钥 ( x ) 和 ( y ) 坐标

在 WebAuthn 注册完成后,设备会返回一个包含公钥的响应。这个响应通常是一个 AuthenticatorAttestationResponse 对象,其中包含了公钥信息。

以下是一个简化的示例,展示如何从 AuthenticatorAttestationResponse 中提取公钥 ( x ) 和 ( y ) 坐标:

// 假设我们已经进行了 WebAuthn 注册,并获得了响应
navigator.credentials.create({
publicKey: {
// 你提供的 JSON 数据
}
}).then((credential) => {
// 解析响应
const attestationResponse = credential.response;
const attestationObject = CBOR.decode(attestationResponse.attestationObject);
const authData = attestationObject.authData;

// 提取公钥信息
const publicKey = extractPublicKeyFromAuthData(authData);

// 提取 x 和 y 坐标
const x = publicKey.x;
const y = publicKey.y;

// 调用智能合约
addOwnerPublicKey(x, y);
}).catch((error) => {
console.error('Error during WebAuthn registration:', error);
});

function extractPublicKeyFromAuthData(authData) {
// 解析 authData 并提取公钥信息
// 这是一个简化的示例,实际解析可能更复杂
const publicKeyData = authData.slice(-65); // 公钥数据通常在 authData 的末尾
const x = publicKeyData.slice(1, 33); // x 坐标
const y = publicKeyData.slice(33, 65); // y 坐标

return {
x: '0x' + Buffer.from(x).toString('hex'),
y: '0x' + Buffer.from(y).toString('hex')
};
}

function addOwnerPublicKey(x, y) {
// 使用 Web3.js 调用智能合约
const web3 = new Web3(
https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID');


const contractAddress = '0xYourContractAddress';


const contractABI = [ /* Your contract ABI */ ];

const contract = new web3.eth.Contract(contractABI, contractAddress);

const accounts = await web3.eth.getAccounts();
const sender = accounts[0];

try {
await contract.methods.addOwnerPublicKey(x, y).send({ from: sender });
console.log('Owner public key added successfully');
} catch (error) {
console.error('Error adding owner public key:', error);
}
}

总结

  1. 注册请求:你提供的 JSON 数据是注册请求的参数。
  2. 设备生成公钥对:设备生成公钥对并返回响应。
  3. 提取公钥 ( x ) 和 ( y ) 坐标:从设备返回的响应中提取公钥 ( x ) 和 ( y ) 坐标。
  4. 调用智能合约:使用提取的公钥 ( x ) 和 ( y ) 坐标调用智能合约的 addOwnerPublicKey 函数。

https://webauthn.guide/

alt text ![

alt text](image-1.png)

参考

https://github.com/Vectorized/solady?tab=readme-ov-file