Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

import { DeployLinkHandleEntrypointScript } from "./_DeployLinkHandleEntrypoint.s.sol";
import { HonkVerifier } from "../../test/fixtures/handleCommand/HonkVerifier.sol";

contract DeployLinkHandleEntrypointTwitterScript is DeployLinkHandleEntrypointScript {
function _deployHonkVerifier() internal override returns (address) {
return address(new HonkVerifier());
}

function _dkimRegistry() internal pure override returns (address) {
// sepolia always valid dkim registry
return 0xc4f628496b8c474096650C8f9023954643cC614F;
}

function _keyName() internal pure override returns (string memory) {
return "com.twitter";
}
}
Comment thread
benceharomi marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

import { DeployLinkHandleEntrypointScript } from "./_DeployLinkHandleEntrypoint.s.sol";
import { HonkVerifier } from "../../test/fixtures/handleCommand/HonkVerifier.sol";

contract DeployLinkHandleEntrypointTwitterScript is DeployLinkHandleEntrypointScript {
function _getHonkVerifierAddress() internal override returns (address) {
return address(new HonkVerifier());
}

function _getDkimRegistryAddress() internal pure override returns (address) {
// sepolia always valid dkim registry
return 0xc4f628496b8c474096650C8f9023954643cC614F;
}

function _getPlatformName() internal pure override returns (string memory) {
return "x";
}

function _getRecordName() internal pure override returns (string memory) {
return "com.twitter";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

import { Script, console } from "forge-std/Script.sol";
import { LinkHandleEntrypoint } from "../../src/entrypoints/LinkHandleEntrypoint.sol";
import { LinkHandleCommandVerifier } from "../../src/verifiers/LinkHandleCommandVerifier.sol";

abstract contract DeployLinkHandleEntrypointScript is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");

vm.startBroadcast(deployerPrivateKey);

console.log("\n=== Step 1: Deploy LinkHandleCommandVerifier ===");
LinkHandleCommandVerifier commandVerifier =
new LinkHandleCommandVerifier(_getHonkVerifierAddress(), _getDkimRegistryAddress());
console.log("LinkHandleCommandVerifier deployed at:", address(commandVerifier));

console.log("\n=== Step 2: Deploy LinkHandleEntrypoint ===");
LinkHandleEntrypoint entrypoint =
new LinkHandleEntrypoint(address(commandVerifier), _getRecordName(), _getPlatformName());
console.log("LinkHandleEntrypoint deployed at:", address(entrypoint));

vm.stopBroadcast();

console.log("\n=== Deployment Complete ===");
console.log("HONK_VERIFIER=", _getHonkVerifierAddress());
console.log("DKIM_REGISTRY=", _getDkimRegistryAddress());
console.log("LINK_HANDLE_COMMAND_VERIFIER=", address(commandVerifier));
console.log("RECORD_NAME=", _getRecordName());
console.log("PLATFORM_NAME=", _getPlatformName());
console.log("LINK_HANDLE_ENTRYPOINT=", address(entrypoint));
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/**
* @notice The HonkVerifier address.
* @dev TODO: ideally ths would be pure and would only return the address of the HonkVerifier deployed by the
* registry. Currently this is deployed inside this function. Remove it once the registry deploys the
* HonkVerifier.
* @return The address of the HonkVerifier
*/
function _getHonkVerifierAddress() internal virtual returns (address);

/**
* @notice The DKIM registry address.
* @dev Example (sepolia): return 0xe24c24Ab94c93D5754De1cbE61b777e47cc57723;
* Example (sepolia always valid): return 0xc4f628496b8c474096650C8f9023954643cC614F;
* @return The address of the DKIM registry
*/
function _getDkimRegistryAddress() internal pure virtual returns (address);

/**
* @notice The platform name in the command (e.g. "x").
* @return The platform name used in the command
*/
function _getPlatformName() internal pure virtual returns (string memory);

/**
* @notice ENS text record name (e.g. "com.twitter") — the key in setText(node, key, value).
* @return The record name for the text record
*/
function _getRecordName() internal pure virtual returns (string memory);
}
27 changes: 0 additions & 27 deletions script/DeployLinkHandleVerifier.s.sol

This file was deleted.

2 changes: 1 addition & 1 deletion src/entrypoints/LinkEmailEntrypoint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { LinkEmailCommand } from "../verifiers/LinkEmailCommandVerifier.sol";
* @dev The verifier can be updated via the entrypoint function.
*/
contract LinkEmailEntrypoint is LinkTextRecordEntrypoint {
constructor(address verifier) LinkTextRecordEntrypoint(verifier, "email") { }
constructor(address verifier) LinkTextRecordEntrypoint(verifier, "email", "email") { }

/**
* @inheritdoc LinkTextRecordEntrypoint
Expand Down
14 changes: 13 additions & 1 deletion src/entrypoints/LinkHandleEntrypoint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,19 @@ import { LinkHandleCommand } from "../verifiers/HandleVerifier.sol";
* @dev The verifier can be updated via the entrypoint function.
*/
contract LinkHandleEntrypoint is LinkTextRecordEntrypoint {
constructor(address verifier) LinkTextRecordEntrypoint(verifier, "com.twitter") { }
/**
* @notice Constructor
* @param verifier The LinkHandleCommandVerifier contract address
* @param recordName ENS text record name (e.g. "com.twitter") — the key in setText(node, key, value)
* @param platformName The platform name in the command (e.g. "Twitter")
*/
constructor(
address verifier,
string memory recordName,
string memory platformName
)
LinkTextRecordEntrypoint(verifier, recordName, platformName)
{ }

/**
* @inheritdoc LinkTextRecordEntrypoint
Expand Down
19 changes: 14 additions & 5 deletions src/entrypoints/LinkTextRecordEntrypoint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { IVerifier } from "../interfaces/IVerifier.sol";
import { EnsUtils } from "../utils/EnsUtils.sol";

struct TextRecord {
string platformName;
string ensName;
string value;
bytes32 nullifier;
Expand All @@ -20,7 +21,8 @@ struct TextRecord {
abstract contract LinkTextRecordEntrypoint is IEntryPoint, ITextRecordVerifier {
using EnsUtils for bytes;

bytes32 private immutable _KEY;
bytes32 private immutable _recordNameHash;
bytes32 private immutable _platformNameHash;

// link text record command verifier
address public immutable VERIFIER;
Expand All @@ -34,9 +36,12 @@ abstract contract LinkTextRecordEntrypoint is IEntryPoint, ITextRecordVerifier {
error InvalidCommand();
error NullifierUsed();

constructor(address verifier, string memory keyName) {
/// @param recordName ENS text record name (e.g. "com.twitter") — the key in setText(node, key, value)
/// @param platformName Platform name in the command (e.g. "x")
constructor(address verifier, string memory recordName, string memory platformName) {
VERIFIER = verifier;
_KEY = keccak256(bytes(keyName));
_recordNameHash = keccak256(bytes(recordName));
_platformNameHash = bytes(platformName).length == 0 ? bytes32(0) : keccak256(bytes(platformName));
}

/**
Expand All @@ -47,6 +52,10 @@ abstract contract LinkTextRecordEntrypoint is IEntryPoint, ITextRecordVerifier {
function entrypoint(bytes memory data) external {
TextRecord memory record = _extractTextRecord(data);

if (_platformNameHash != bytes32(0) && keccak256(bytes(record.platformName)) != _platformNameHash) {
revert InvalidCommand();
}

if (_isUsed[record.nullifier]) {
revert NullifierUsed();
}
Expand Down Expand Up @@ -81,8 +90,8 @@ abstract contract LinkTextRecordEntrypoint is IEntryPoint, ITextRecordVerifier {
* @inheritdoc ITextRecordVerifier
*/
function verifyTextRecord(bytes32 node, string memory key, string memory value) external view returns (bool) {
// this verifier only supports this specific text record
if (keccak256(bytes(key)) != _KEY) {
// this verifier only supports this specific text record (key must match constructor recordName)
if (keccak256(bytes(key)) != _recordNameHash) {
revert UnsupportedKey();
}
string memory storedTextRecord = textRecord[node];
Expand Down
17 changes: 13 additions & 4 deletions src/verifiers/LinkEmailCommandVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import { TextRecord } from "../entrypoints/LinkTextRecordEntrypoint.sol";
/**
* @notice Enum representing the indices of command parameters in the command template
* @dev Used to specify which parameter to extract from the command string
* @param ENS_NAME = 0
* @param PLATFORM_NAME = 0
* @param ENS_NAME = 1
*/
enum CommandParamIndex {
PLATFORM_NAME,
ENS_NAME
}

Expand Down Expand Up @@ -79,6 +81,11 @@ contract LinkEmailCommandVerifier is EmailAuthVerifier {
PublicInputs memory publicInputs = _unpackPublicInputs(publicInputsFields);
return LinkEmailCommand({
textRecord: TextRecord({
platformName: string(
CommandUtils.extractCommandParamByIndex(
_getTemplate(), publicInputs.maskedCommand, uint256(CommandParamIndex.PLATFORM_NAME)
)
),
// ensName is extracted from the command
ensName: string(
CommandUtils.extractCommandParamByIndex(
Expand All @@ -95,8 +102,10 @@ contract LinkEmailCommandVerifier is EmailAuthVerifier {
}

function _getMaskedCommand(LinkEmailCommand memory command) private pure returns (string memory) {
bytes[] memory commandParams = new bytes[](1);
commandParams[0] = abi.encode(command.textRecord.ensName);
bytes[] memory commandParams = new bytes[](2);
commandParams[0] = abi.encode(command.textRecord.platformName);
commandParams[1] = abi.encode(command.textRecord.ensName);

return CommandUtils.computeExpectedCommand(commandParams, _getTemplate(), 0);
}

Expand All @@ -105,7 +114,7 @@ contract LinkEmailCommandVerifier is EmailAuthVerifier {

template[0] = "Link";
template[1] = "my";
template[2] = "email";
template[2] = CommandUtils.STRING_MATCHER;
template[3] = "to";
template[4] = CommandUtils.STRING_MATCHER;

Expand Down
16 changes: 12 additions & 4 deletions src/verifiers/LinkHandleCommandVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import { HandleVerifier, LinkHandleCommand, PublicInputs, TextRecord } from "./H
/**
* @notice Enum representing the indices of command parameters in the command template
* @dev Used to specify which parameter to extract from the command string
* @param ENS_NAME = 0
* @param PLATFORM_NAME = 0
* @param ENS_NAME = 1
*/
enum CommandParamIndex {
PLATFORM_NAME,
ENS_NAME
}

Expand Down Expand Up @@ -65,6 +67,11 @@ contract LinkHandleCommandVerifier is HandleVerifier {
PublicInputs memory publicInputs = _unpackPublicInputs(publicInputsFields);
return LinkHandleCommand({
textRecord: TextRecord({
platformName: string(
CommandUtils.extractCommandParamByIndex(
_getTemplate(), publicInputs.command, uint256(CommandParamIndex.PLATFORM_NAME)
)
),
// ensName is extracted from the command
ensName: string(
CommandUtils.extractCommandParamByIndex(
Expand All @@ -81,8 +88,9 @@ contract LinkHandleCommandVerifier is HandleVerifier {
}

function _getCommand(LinkHandleCommand memory command) private pure returns (string memory) {
bytes[] memory commandParams = new bytes[](1);
commandParams[0] = abi.encode(command.textRecord.ensName);
bytes[] memory commandParams = new bytes[](2);
commandParams[0] = abi.encode(command.textRecord.platformName);
commandParams[1] = abi.encode(command.textRecord.ensName);

return CommandUtils.computeExpectedCommand(commandParams, _getTemplate(), 0);
}
Expand All @@ -92,7 +100,7 @@ contract LinkHandleCommandVerifier is HandleVerifier {

template[0] = "Link";
template[1] = "my";
template[2] = "x";
template[2] = CommandUtils.STRING_MATCHER;
template[3] = "handle";
template[4] = "to";
template[5] = CommandUtils.STRING_MATCHER;
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/TestFixtures.sol
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ library TestFixtures {

command = LinkEmailCommand({
textRecord: TextRecord({
platformName: "email",
ensName: "zkfriendly.eth",
value: "thezdev1@gmail.com",
nullifier: hex"0CEF36D2E53A61D038B0F46466F7C1E4E5D62636FC3ACAB471C8AC6A0558F705"
Expand Down
30 changes: 4 additions & 26 deletions test/fixtures/handleCommand/HandleCommandTestFixture.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Vm } from "forge-std/Vm.sol";
import { LinkHandleCommand, PublicInputs } from "../../../src/verifiers/HandleVerifier.sol";
import { ClaimHandleCommand } from "../../../src/verifiers/ClaimHandleCommandVerifier.sol";
import { TextRecord } from "../../../src/entrypoints/LinkTextRecordEntrypoint.sol";
import { TestStringUtils } from "../../utils/TestStringUtils.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";

address constant _VM_ADDR = address(uint160(uint256(keccak256("hevm cheat code"))));
Expand All @@ -24,7 +25,7 @@ library HandleCommandTestFixture {
_getExpectedPublicInputs(string.concat(path, "files/claimX/expected_public_inputs.json"));

command = ClaimHandleCommand({
target: Strings.parseAddress(_getLastWord(expectedPublicInputs.command)),
target: Strings.parseAddress(TestStringUtils.getNthWord(expectedPublicInputs.command, -1)),
proof: abi.encodePacked(_getProofFieldsFromBinary(string.concat(path, "files/claimX/proof"))),
publicInputs: expectedPublicInputs
});
Expand All @@ -40,7 +41,8 @@ library HandleCommandTestFixture {

command = LinkHandleCommand({
textRecord: TextRecord({
ensName: _getLastWord(expectedPublicInputs.command),
platformName: TestStringUtils.getNthWord(expectedPublicInputs.command, 2),
ensName: TestStringUtils.getNthWord(expectedPublicInputs.command, -1),
value: expectedPublicInputs.handle,
nullifier: expectedPublicInputs.emailNullifier
}),
Expand Down Expand Up @@ -128,28 +130,4 @@ library HandleCommandTestFixture {
}
return publicInputs;
}

function _getLastWord(string memory input) private pure returns (string memory) {
bytes memory strBytes = bytes(input);
uint256 len = strBytes.length;
uint256 start = len;

// Iterate backwards to find the last space character
for (uint256 i = len; i > 0; i--) {
if (strBytes[i - 1] == 0x20) {
// 0x20 is the ASCII for space
start = i;
break;
}
}

// Copy the last word to a new bytes array
uint256 wordLen = len - start;
bytes memory lastWordBytes = new bytes(wordLen);
for (uint256 i = 0; i < wordLen; i++) {
lastWordBytes[i] = strBytes[start + i];
}

return string(lastWordBytes);
}
}
Loading
Loading