Skip to content
Open
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
51 changes: 51 additions & 0 deletions la-utils/la-transactions/handlers/signEip7702Auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { signAuthorization } from "../primitive/signAuthorization";
import { toEthAddress } from "../../la-pkp/toEthAddress";

/**
* Handler function for EIP-7702 authorization signing using PKP
* This function provides a high-level interface for generating EIP-7702 compliant authorization tuples using PKP
*
* @param {Object} params - The parameters object
* @param {string} params.pkpPublicKey - The PKP's public key
* @param {string} params.targetAddress - The address being authorized
* @param {string|number} params.chainId - The chain ID (0 for universal deployment)
* @param {string|number} params.nonce - Optional nonce value (defaults to 0 for new accounts)
* @returns {Promise<{
* chainId: number,
* address: string,
* nonce: number,
* yParity: number,
* r: string,
* s: string,
* signer: string
* }>} The authorization tuple components and signer address
*/
export const signEip7702Auth = async ({
pkpPublicKey,
targetAddress,
chainId = 0,
nonce = 0,
}: {
pkpPublicKey: string;
targetAddress: string;
chainId?: string | number;
nonce?: string | number;
}) => {
// Get the signer's address from PKP
const signerAddress = toEthAddress(pkpPublicKey);

// Generate the authorization tuple
const authTuple = await signAuthorization({
sigName: "eip7702-auth",
pkpPublicKey,
chainId,
target: targetAddress,
nonce,
});

// Return authorization tuple with signer address
return {
...authTuple,
signer: signerAddress,
};
};
68 changes: 68 additions & 0 deletions la-utils/la-transactions/handlers/signEip7702AuthViem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { concatHex, encodeAbiParameters, type Hex } from "viem";
import { privateKeyToAccount } from "viem/accounts";

/**
* Signs an EIP-7702 authorization using a private key via Viem
* This function provides a direct interface to sign EIP-7702 authorizations using Viem's primitives and a private key
*
* @param {Object} params - The parameters object
* @param {Hex} params.privateKey - The private key to sign with (must be in 0x format)
* @param {string} params.targetAddress - The address being authorized
* @param {bigint} params.chainId - The chain ID (defaults to 1n for Ethereum mainnet)
* @param {bigint} params.nonce - The nonce value (defaults to 0n)
* @returns {Promise<{
* chainId: number,
* address: string,
* nonce: number,
* yParity: number,
* r: string,
* s: string,
* signer: string
* }>} The authorization tuple components and signer address
*/
export const signEip7702AuthViem = async ({
privateKey,
targetAddress,
chainId = 1n,
nonce = 0n,
}: {
privateKey: Hex;
targetAddress: string;
chainId?: bigint;
nonce?: bigint;
}) => {
const wallet = privateKeyToAccount(privateKey);

const message = {
chainId,
target: targetAddress as Hex,
nonce,
};

// Sign using Viem's signMessage with EIP-7702 format
const signature = await wallet.signMessage({
message: concatHex([
"0x05", // MAGIC prefix for EIP-7702
encodeAbiParameters(
[{ type: "uint256" }, { type: "address" }, { type: "uint64" }],
[message.chainId, message.target, message.nonce]
),
]),
});

// Extract r, s, v components from the signature
const r = `0x${signature.slice(2, 66)}`;
const s = `0x${signature.slice(66, 130)}`;
const v = parseInt(signature.slice(130, 132), 16);

// Return in the same format as PKP auth tuple
return {
chainId: Number(chainId),
address: targetAddress,
nonce: Number(nonce),
yParity: v - 27, // Convert v to yParity
r,
s,
signer: wallet.address
};
};
210 changes: 210 additions & 0 deletions la-utils/la-transactions/primitive/signAuthorization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
// Validate inputs according to EIP-7702 spec
/** Maximum value for 256-bit unsigned integers (2^256 - 1)
* Used to validate:
* - signature components (r,s)
* - chainId
*/
const MAX_UINT256 = BigInt(
"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
);

/** Maximum value for 64-bit unsigned integers (2^64 - 1)
* Used to validate:
* - nonce value
*/
const MAX_UINT64 = BigInt("0xffffffffffffffff");

/** Maximum value for 8-bit unsigned integers (2^8 - 1)
* Used to validate:
* - signature yParity (v) value
*/
const MAX_UINT8 = BigInt("0xff");

/**
* Signs an EIP-7702 authorization tuple
* This function implements the authorization signing as specified in EIP-7702
*
* @param {Object} params - The parameters object
* @param {string} params.pkpPublicKey - The PKP's public key
* @param {string|number} params.chainId - The chain ID (0 for universal deployment)
* @param {string} params.target - The target address that will be authorized
* @param {string|number} params.nonce - The nonce value (must be < 2^64)
* @param {string} params.sigName - The name of the signature for tracking
* @returns {Promise<{chainId: number, address: string, nonce: number, yParity: number, r: string, s: string}>}
* The authorization tuple components as specified in EIP-7702
*/
export const signAuthorization = async ({
sigName,
pkpPublicKey,
chainId,
target,
nonce,
}: {
sigName: string;
pkpPublicKey: string;
chainId: string | number;
target: string;
nonce: string | number;
}) => {
console.log("=== Debug: Input Parameters ===");
console.log("pkpPublicKey:", pkpPublicKey);
console.log("chainId:", chainId);
console.log("target:", target);
console.log("nonce:", nonce);

console.log("\n=== Debug: Max Values ===");
console.log("MAX_UINT256 (hex):", MAX_UINT256.toString(16));
console.log("MAX_UINT64 (hex):", MAX_UINT64.toString(16));
console.log("MAX_UINT8 (hex):", MAX_UINT8.toString(16));

// Validate chainId
const numericChainId = BigInt(chainId);
console.log("numericChainId:", numericChainId);
if (numericChainId > MAX_UINT256) {
throw new Error("Chain ID must be less than 2^256");
}

// Validate nonce
const numericNonce = BigInt(nonce);
console.log("numericNonce:", numericNonce);
if (numericNonce > MAX_UINT64) {
throw new Error("Nonce must be less than 2^64");
}

// Validate target address
if (!target.startsWith("0x")) {
throw new Error("Target address must start with 0x");
}
const addressWithoutPrefix = target.slice(2);
console.log("addressWithoutPrefix:", addressWithoutPrefix);
if (addressWithoutPrefix.length !== 40) {
// 20 bytes = 40 hex chars
throw new Error("Target address must be 20 bytes long");
}
if (!/^[0-9a-fA-F]+$/.test(addressWithoutPrefix)) {
throw new Error("Target address must be a valid hex string");
}

// Format the PKP public key if needed
const pkForLit = pkpPublicKey.startsWith("0x")
? pkpPublicKey.slice(2)
: pkpPublicKey;

// Create the authorization message according to EIP-7702 spec
// MAGIC (0x05) || rlp([chain_id, address, nonce])
const MAGIC = "0x05";
const rlpEncoded = ethers.utils.RLP.encode([
ethers.utils.hexlify(chainId),
target,
ethers.utils.hexlify(nonce),
]);

console.log("\n=== Debug: Message Construction ===");
console.log("MAGIC:", MAGIC);
console.log("RLP encoded:", rlpEncoded);

const messageToSign = ethers.utils.concat([
ethers.utils.hexlify(MAGIC),
rlpEncoded,
]);

console.log("Message to sign:", messageToSign);

// Hash the message
const messageHash = ethers.utils.keccak256(messageToSign);
console.log("Message hash:", messageHash);

// Sign the hash using PKP
const sig = await Lit.Actions.signAndCombineEcdsa({
toSign: ethers.utils.arrayify(messageHash),
publicKey: pkForLit,
sigName,
});

console.log("\n=== Debug: Raw Signature ===");
console.log("Raw signature:", sig);

// Parse signature components
const parsedSig = JSON.parse(sig);
console.log("\n=== Debug: Parsed Signature Components ===");
console.log("v:", parsedSig.v);
console.log("r:", parsedSig.r);
console.log("s:", parsedSig.s);

// Validate signature components
const yParity = BigInt(parsedSig.v);
console.log("\n=== Debug: yParity Validation ===");
console.log("yParity:", yParity.toString());
console.log("MAX_UINT8:", MAX_UINT8.toString(16));

if (yParity > MAX_UINT8) {
throw new Error("y_parity must be less than 2^8");
}

// Add hex prefix for BigInt conversion and ensure exactly 64 characters (32 bytes)
const rHexRaw = parsedSig.r.replace(/^0+/, "");
const sHexRaw = parsedSig.s.replace(/^0+/, "");

// Take the last 64 characters to ensure correct length
const rHex = "0x" + rHexRaw.slice(-64);
const sHex = "0x" + sHexRaw.slice(-64);

console.log("\n=== Debug: r/s Hex Values ===");
console.log("Original r:", parsedSig.r);
console.log("Truncated rHex:", rHex);
console.log("Original s:", parsedSig.s);
console.log("Truncated sHex:", sHex);

try {
const r = BigInt(rHex);
console.log("\n=== Debug: r Value Detailed Comparison ===");
console.log("r decimal:", r.toString());
console.log("r hex:", r.toString(16));
console.log("r length in hex:", r.toString(16).length);
console.log("MAX_UINT256 decimal:", MAX_UINT256.toString());
console.log("MAX_UINT256 hex:", MAX_UINT256.toString(16));
console.log("MAX_UINT256 length in hex:", MAX_UINT256.toString(16).length);
console.log("Is r > MAX_UINT256?", r > MAX_UINT256);
console.log("Difference:", (r - MAX_UINT256).toString());

if (r > MAX_UINT256) {
throw new Error("r must be less than 2^256");
}

const s = BigInt(sHex);
console.log("\n=== Debug: s Value ===");
console.log("s decimal:", s.toString());
console.log("s hex:", s.toString(16));

if (s > MAX_UINT256) {
throw new Error("s must be less than 2^256");
}

// Additional EIP-2 validation for s value
const secp256k1n = BigInt(
"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
);
console.log("\n=== Debug: secp256k1n Validation ===");
console.log("secp256k1n/2:", (secp256k1n / BigInt(2)).toString(16));

if (s > secp256k1n / BigInt(2)) {
throw new Error(
"s value must be less than or equal to secp256k1n/2 as per EIP-2"
);
}

// Return the authorization tuple components
return {
chainId: Number(chainId),
address: target,
nonce: Number(nonce),
yParity: Number(yParity),
r: rHex,
s: sHex,
};
} catch (error) {
console.error("\n=== Debug: Error Details ===");
console.error("Error converting or validating r/s values:", error);
throw error;
}
};
7 changes: 5 additions & 2 deletions my-app/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import { readPKP } from "../scripts/utils/io";

(async () => {
const lit = await createLitService();
const pkp = readPKP();

console.log("🏃‍♂️ Running Lit Action...");
console.log("🏃‍♂️ Running EIP-7702 Authorization Example...");
const res = await lit.executeJs({
code: litActionCodeString,
params: {
pkpPublicKey: readPKP().pkpInfo.publicKey,
pkpPublicKey: pkp.pkpInfo.publicKey,
targetAddress: "0x1234567890123456789012345678901234567890", // Example target address
chainId: 0, // 0 for universal deployment
},
});

Expand Down
Loading