Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add remote sign for attestation #59

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .env.local
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind moving these changes to: https://github.com/Layr-Labs/hello-world-avs/blob/master/.env.example ?

We're trying to make the deployments more streamlined for local and holesky, so we removed .env.local

Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# local or remote
SIGNER_TYPE=local

PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
RPC_URL=http://127.0.0.1:8545
CONTRACT_ADDRESS=0x84eA74d481Ee0A5332c457a4d796187F6Ba67fEB
Expand All @@ -13,4 +16,7 @@ HOLESKY_CONTRACT_ADDRESS=0x3361953F4a9628672dCBcDb29e91735fb1985390
HOLESKY_DELEGATION_MANAGER_ADDRESS=0xA44151489861Fe9e3055d95adC98FbD462B948e7
HOLESKY_STAKE_REGISTRY_ADDRESS=0xBDACD5998989Eec814ac7A0f0f6596088AA2a270
HOLESKY_AVS_DIRECTORY_ADDRESS=0x055733000064333CaDDbC92763c58BF0192fFeBf
HOLESKY_WS_RPC_URL=wss://holesky.drpc.org
HOLESKY_WS_RPC_URL=wss://holesky.drpc.org

REMOTE_SIGNER_URL="fill me in if you want to use remote signer"
OPERATOR_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
106 changes: 93 additions & 13 deletions operator/index.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: there were a LOT of changes to index.ts. It might be easier to start with a fresh PR?

Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,41 @@ import { delegationABI } from "./abis/delegationABI";
import { contractABI } from './abis/contractABI';
import { registryABI } from './abis/registryABI';
import { avsDirectoryABI } from './abis/avsDirectoryABI';
import { RemoteSigner } from "./remoteSigner";
dotenv.config();

const remoteSignerUrl = process.env.REMOTE_SIGNER_URL!;
const operatorAddress = process.env.OPERATOR_ADDRESS!;
const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
const signer = new RemoteSigner(operatorAddress, provider, remoteSignerUrl);
let address = "";

const delegationManagerAddress = process.env.DELEGATION_MANAGER_ADDRESS!;
const contractAddress = process.env.CONTRACT_ADDRESS!;
const stakeRegistryAddress = process.env.STAKE_REGISTRY_ADDRESS!;
const avsDirectoryAddress = process.env.AVS_DIRECTORY_ADDRESS!;

const delegationManager = new ethers.Contract(delegationManagerAddress, delegationABI, wallet);
const contract = new ethers.Contract(contractAddress, contractABI, wallet);
const registryContract = new ethers.Contract(stakeRegistryAddress, registryABI, wallet);
const avsDirectory = new ethers.Contract(avsDirectoryAddress, avsDirectoryABI, wallet);
const signerType = process.env.SIGNER_TYPE!;

let delegationManager: ethers.Contract;
let contract: ethers.Contract;
let registryContract: ethers.Contract;
let avsDirectory: ethers.Contract;

const signAndRespondToTask = async (taskIndex: number, taskCreatedBlock: number, taskName: string) => {
const message = `Hello, ${taskName}`;
const messageHash = ethers.utils.solidityKeccak256(["string"], [message]);
const messageBytes = ethers.utils.arrayify(messageHash);
const signature = await wallet.signMessage(messageBytes);

let signature = "";
if (signerType === "local") {
console.log("Using local private key to sign message")
const messageBytes = ethers.utils.arrayify(messageHash);
signature = await wallet.signMessage(messageBytes);
} else if (signerType === "remote") {
console.log("Using remote signer to sign message")
signature = await signer.signMessage(messageHash);
}

console.log(
`Signing and responding to task ${taskIndex}`
Expand All @@ -41,7 +56,7 @@ const signAndRespondToTask = async (taskIndex: number, taskCreatedBlock: number,
const registerOperator = async () => {
console.log("check")
const tx1 = await delegationManager.registerAsOperator({
earningsReceiver: await wallet.address,
earningsReceiver: address,
delegationApprover: "0x0000000000000000000000000000000000000000",
stakerOptOutWindowBlocks: 0
}, "");
Expand All @@ -60,22 +75,24 @@ const registerOperator = async () => {

// Calculate the digest hash using the avsDirectory's method
const digestHash = await avsDirectory.calculateOperatorAVSRegistrationDigestHash(
wallet.address,
contract.address,
salt,
address,
contract.address,
salt,
expiry
);

// // Sign the digest hash with the operator's private key
// TODO(shrimalmadhur): I am not completely sure about how to make this work with remote signer
// as the signDigest function is not available on the remote signer.
const signingKey = new ethers.utils.SigningKey(process.env.PRIVATE_KEY!);
const signature = signingKey.signDigest(digestHash);

// // Encode the signature in the required format
operatorSignature.signature = ethers.utils.joinSignature(signature);

const tx2 = await registryContract.registerOperatorWithSignature(
operatorSignature,
wallet.address
address
);
await tx2.wait();
console.log("Operator registered on AVS successfully");
Expand All @@ -93,6 +110,19 @@ const monitorNewTasks = async () => {
};

const main = async () => {
if (signerType === "local") {
address = wallet.address;
delegationManager = new ethers.Contract(delegationManagerAddress, delegationABI, wallet);
contract = new ethers.Contract(contractAddress, contractABI, wallet);
registryContract = new ethers.Contract(stakeRegistryAddress, registryABI, wallet);
avsDirectory = new ethers.Contract(avsDirectoryAddress, avsDirectoryABI, wallet);
} else {
address = await signer.getAddress();
delegationManager = new ethers.Contract(delegationManagerAddress, delegationABI, signer);
contract = new ethers.Contract(contractAddress, contractABI, signer);
registryContract = new ethers.Contract(stakeRegistryAddress, registryABI, signer);
avsDirectory = new ethers.Contract(avsDirectoryAddress, avsDirectoryABI, signer);
}
await registerOperator();
monitorNewTasks().catch((error) => {
console.error("Error monitoring tasks:", error);
Expand All @@ -101,4 +131,54 @@ const main = async () => {

main().catch((error) => {
console.error("Error in main function:", error);
});
});

interface JsonRpcRequest {
jsonrpc: string;
method: string;
params: any[];
id: number;
}

interface JsonRpcResponse {
jsonrpc: string;
result?: any;
error?: {
code: number;
message: string;
};
id: number;
}

async function callJsonRpcEndpoint(
url: string,
method: string,
params: any[] = []
): Promise<any> {
const request: JsonRpcRequest = {
jsonrpc: "2.0",
method,
params,
id: 1, // You might want to generate a unique id for each request
};

const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(request),
});

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const jsonResponse: JsonRpcResponse = await response.json();

if (jsonResponse.error) {
throw new Error(`JSON-RPC error: ${jsonResponse.error.message}`);
}

return jsonResponse.result;
}
115 changes: 115 additions & 0 deletions operator/remoteSigner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { Signer, providers, utils } from 'ethers';

export class RemoteSigner extends Signer {
private readonly address: string;
readonly provider: providers.Provider;
private readonly remoteSigningEndpoint: string;

constructor(address: string, provider: providers.Provider, remoteSigningEndpoint: string) {
super();
this.address = address;
this.provider = provider;
this.remoteSigningEndpoint = remoteSigningEndpoint;
}

async getAddress(): Promise<string> {
return this.address;
}

async signMessage(message: string | utils.Bytes): Promise<string> {
if (typeof(message) === "string") {
return this.signMessageHash(message);
} else {
const messageHash = utils.solidityKeccak256(["string"], [message])
return this.signMessageHash(messageHash);
}
}

async signTransaction(transaction: utils.Deferrable<providers.TransactionRequest>): Promise<string> {
// Implement the logic to send the transaction to your remote signing service
// and return the signed transaction
const tx = {
from: transaction.from,
to: transaction.to,
value: transaction.value,
gas: transaction.gasLimit?.toString(),
maxPriorityFeePerGas: transaction.maxPriorityFeePerGas?.toString(),
maxFeePerGas: transaction.maxFeePerGas?.toString(),
nonce: transaction.nonce,
data: transaction.data,
}
const signedTransaction = await callJsonRpcEndpoint(
this.remoteSigningEndpoint,
"eth_signTransaction",
[tx]
);

return signedTransaction;
}

connect(provider: providers.Provider): Signer {
return new RemoteSigner(this.address, provider, this.remoteSigningEndpoint);
}

private async signMessageHash(messageHash: string): Promise<string> {
// Implement the logic to send the message hash to your remote signing service
// and return the signature
const signature = await callJsonRpcEndpoint(
this.remoteSigningEndpoint,
"eth_sign",
[this.address, messageHash]
);

return signature;
}
}

interface JsonRpcRequest {
jsonrpc: string;
method: string;
params: any[];
id: number;
}

interface JsonRpcResponse {
jsonrpc: string;
result?: any;
error?: {
code: number;
message: string;
};
id: number;
}

async function callJsonRpcEndpoint(
url: string,
method: string,
params: any[] = []
): Promise<any> {
const request: JsonRpcRequest = {
jsonrpc: "2.0",
method,
params,
id: 1, // You might want to generate a unique id for each request
};

const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(request),
});

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const jsonResponse: JsonRpcResponse = await response.json();

if (jsonResponse.error) {
throw new Error(`JSON-RPC error: ${jsonResponse.error.message}`);
}

return jsonResponse.result;
}