오프체인 데이터 온체인 수집을 위한 Trustus EIP712 기반 솔루션



Trustus는 "오프체인 데이터 온체인에 액세스하기 위한 신뢰를 최소화한 방법"입니다. 프로젝트에 대한 자세한 내용은 https://github.com/ZeframLou/trustus에서 확인할 수 있습니다.

Trustus는 기본적으로 Solidity로 작성된 하나의 스마트 계약입니다. 계약의 주요 부분은 입력된 구조화된 날짜의 해싱 및 서명을 위한 EIP712 표준의 구현입니다. Trustus는 1) 계약에 데이터를 보낼 수 있는 화이트리스트 주소와 2) EIP712 부분을 허용합니다. 즉, 구체적이고 미리 정의된 데이터 구조 및 서명에 오프체인 데이터(예: NFT 가격 피드)를 가져올 수 있습니다. 그런 다음 V, R, S 출력 값 및 추가 원시 데이터를 사용하여 기본 계약에서 스마트 계약 기능을 호출할 수 있습니다(이전에 가져온 Trustus 추상 계약에서 verifyPacket 수정자를 적용했습니다).

트러스트 계약의 견고성 코드(_verifyPacket 함수)는 ecrecover로 이더리움 주소를 전달하고 복구하는 원시 데이터와 V,R,S 값을 기반으로 해싱 작업을 재생합니다. 그런 다음 화이트리스트 주소(이전에 _setIsTrusted 함수로 설정됨)와 비교하고 복구된 주소가 화이트리스트에 없는 경우 추가 데이터 수집을 허용하거나 되돌릴 수 있습니다.

"Caviat"는 우리가 필요한 JavaScript 부분(적절한 데이터 구조를 공식화하려는 경우)이 설명서나 일부 예제에 제공되지 않는다는 것입니다. 단 한 문장: "서버는 데이터 패킷을 형식화하기 위해 Trustus에서 사용하는 특정 표준을 구현하고 신뢰할 수 있는 서버에서 시작된 데이터 패킷을 확인하는 ECDSA 서명을 제공해야 합니다."

그렇기 때문에 이번 포스트에서는 Trustus 계약을 보완하기 위해 JS 부분을 제공할 것입니다. 원래 Trustus 계약과 여기서 사용하는 계약의 유일한 차이점은 내 버전에서 struct에 정의된 페이로드가 바이트가 아니라 단위라는 것입니다.

이 모든 코드는 ConsenSys Mesh & Protocol Labs에서 지원하는 커뮤니티 뱅킹 및 자산 관리 fluidNFT 프로젝트를 위해 개발되었습니다. 당신이 intrested 경우에 당신은 우리의 프로젝트를 방문할 수 있습니다

이 프로세스의 모든 힌트에 대해 Medium의 Tnx @apurbapokharel도 제공합니다!

import React, { useState, useEffect } from "react";
import Web3 from "web3";
import Main from "./contracts/Main.json";
var ethUtil = require('ethereumjs-util');
var sigUtil = require('eth-sig-util');


const App = () => {
  const [storageValue, setStorageValue] = useState("");
  const [myWeb3, setMyWeb3] = useState(null);
  const [accounts, setAccounts] = useState(null);
  const [contract, setContract] = useState(null); 


  const init= async () => {
    const web3 = new Web3(window.ethereum);    
    setMyWeb3(web3);

    const _accounts = await web3.eth.getAccounts();
    setAccounts(_accounts[0]); 

    const instance = new web3.eth.Contract(
      Main.abi,
      "0x3779277C9EE5f957fE90027E37bf60828c028ecF"
    );
    setContract(instance);
    const response = await instance.methods.get_recovered().call();
    setStorageValue(response);
  };

  const signData = async () => {
    var milsec_deadline = Date.now() / 1000 + 100;
    var deadline = parseInt(String(milsec_deadline).slice(0, 10));
    console.log(deadline);
    var request =
      "0x0000000000000000000000000000000000000000000000000000000000000001";
    var payload = 122;

    myWeb3.currentProvider.sendAsync(
      {
        method: "net_version",
        params: [],
        jsonrpc: "2.0",
      },
      function (err, result) {
        const netId = result.result;

        const msgParams = JSON.stringify({
          types: {
            EIP712Domain: [
              { name: "name", type: "string" },
              { name: "version", type: "string" },
              { name: "chainId", type: "uint256" },
              { name: "verifyingContract", type: "address" },
            ],
            VerifyPacket: [
              { name: "request", type: "bytes32" },
              { name: "deadline", type: "uint256" },
              { name: "payload", type: "uint256" },
            ],
          },
          primaryType: "VerifyPacket",
          domain: {
            name: "Trustus",
            version: "1",
            chainId: netId,
            verifyingContract: "0x3779277C9EE5f957fE90027E37bf60828c028ecF",
          },
          message: {
            request: request,
            deadline: deadline,
            payload: payload,
          },
        });

        var params = [accounts, msgParams];
        console.dir(params);
        var method = "eth_signTypedData_v3";

        myWeb3.currentProvider.sendAsync(
          {
            method,
            params,
            accounts,
          },
          async function (err, result) {
            if (err) return console.dir(err);
            if (result.error) {
              alert(result.error.message);
            }
            if (result.error) return console.error("ERROR", result);            

            const recovered = sigUtil.recoverTypedSignature({
              data: JSON.parse(msgParams),
              sig: result.result,
            });

            if (
              ethUtil.toChecksumAddress(recovered) ===
              ethUtil.toChecksumAddress(accounts)
            ) {
              alert("Successfully ecRecovered signer as " + accounts);
            } else {
              alert(
                "Failed to verify signer when comparing " +
                  result +
                  " to " +
                  accounts
              );
            }

            //getting r s v from a signature
            const signature = result.result.substring(2);
            const r = "0x" + signature.substring(0, 64);
            const s = "0x" + signature.substring(64, 128);
            const v = parseInt(signature.substring(128, 130), 16);
            console.log("r:", r);
            console.log("s:", s);
            console.log("v:", v);
            console.log("signer:", accounts);

            await contract.methods
              .proba(request, [v, r, s, request, deadline, payload])
              .send({ from: accounts });
          }
        );
      }
    );
  };

  const setRendered = async () => {
    // Get the value from the contract to prove it worked.
    const response = await contract.methods.get_recovered().call();
    console.log(response);
    setStorageValue(response);
  };

  const setUser = async () => {
    await contract.methods
      .setTrusted(accounts, true)
      .send({ from: accounts });
  };

  useEffect(() => {  
    init();
  }, []);


  return (
    <div className="App">
      <h1>Implementation of EIP 712 standard</h1>
      <h2>The recovred address is: {storageValue}</h2>
      <button className={style.universalBtn} onClick={() => signData()}>
        Press to sign
      </button>
      <button className={style.universalBtn} onClick={() => setRendered()}>
        Set rendered address
      </button>
      <button className={style.universalBtn} onClick={() => setUser()}>
        Add trusted address
      </button>
    </div>
  );
}


export default App;


그리고 여기에 우리가 Trustus 추상 계약을 가져오는 주요 스마트 계약이 있습니다.

// SPDX-License-Identifier: MIT

import "./Trustus.sol";

pragma solidity ^0.8.4;

contract Main is Trustus {



    function proba(bytes32 _request, TrustusPacket calldata _packet) public verifyPacket(_request, _packet) returns (bool) {
        return true;
    }


    function setTrusted(address _signer, bool _isTrusted) public {
        _setIsTrusted (_signer, _isTrusted);
    }

}


Trustus 계약의 약간 수정된 버전입니다.

// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.4;
/// @title Trustus
/// @author zefram.eth
/// @notice Trust-minimized method for accessing offchain data onchain

abstract contract Trustus {

    /// -----------------------------------------------------------------------
    /// Structs
    /// -----------------------------------------------------------------------
     /// @param v Part of the ECDSA signature
    /// @param r Part of the ECDSA signature
    /// @param s Part of the ECDSA signature
    /// @param request Identifier for verifying the packet is what is desired
    /// , rather than a packet for some other function/contract
    /// @param deadline The Unix timestamp (in seconds) after which the packet
    /// should be rejected by the contract
    /// @param payload The payload of the packet

    struct TrustusPacket {
        uint8 v;
        bytes32 r;
        bytes32 s;
        bytes32 request;
        uint256 deadline;
        uint256 payload;
    }

    // ADDED (erase on the end of testing)
    address recovered;  

    /// -----------------------------------------------------------------------
    /// Errors
    /// -----------------------------------------------------------------------
    error Trustus__InvalidPacket();

    /// -----------------------------------------------------------------------
    /// Immutable parameters
    /// -----------------------------------------------------------------------

    /// @notice The chain ID used by EIP-712
    uint256 internal immutable INITIAL_CHAIN_ID;

    /// @notice The domain separator used by EIP-712
    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;

    /// -----------------------------------------------------------------------
    /// Storage variables
    /// -----------------------------------------------------------------------

    /// @notice Records whether an address is trusted as a packet provider
    /// @dev provider => value    
    mapping(address => bool) internal isTrusted;

    /// -----------------------------------------------------------------------
    /// Modifiers
    /// -----------------------------------------------------------------------
    /// @notice Verifies whether a packet is valid and returns the result.
    /// Will revert if the packet is invalid.
    /// @dev The deadline, request, and signature are verified.
    /// @param request The identifier for the requested payload
    /// @param packet The packet provided by the offchain data provider
    modifier verifyPacket(bytes32 request, TrustusPacket calldata packet) {
        if (!_verifyPacket(request, packet)) revert Trustus__InvalidPacket();
        _;
    }

    /// -----------------------------------------------------------------------
    /// Constructor
    /// -----------------------------------------------------------------------

    constructor() {
        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = _computeDomainSeparator();
    }

    // ADDED (erase on the end of testing)
    function get_recovered() public view returns (address) {
        return recovered;
    }

    /// -----------------------------------------------------------------------
    /// Packet verification
    /// -----------------------------------------------------------------------
    /// @notice Verifies whether a packet is valid and returns the result.
    /// @dev The deadline, request, and signature are verified.
    /// @param request The identifier for the requested payload
    /// @param packet The packet provided by the offchain data provider
    /// @return success True if the packet is valid, false otherwise

    function _verifyPacket(bytes32 request, TrustusPacket calldata packet)
        internal
        virtual
        returns (bool success)
    {
        // verify deadline
        if (block.timestamp > packet.deadline) return false;

        // verify request
        if (request != packet.request) return false;

        // verify signature
        address recoveredAddress = ecrecover(
            keccak256(
                abi.encodePacked(
                    "\x19\x01",
                    DOMAIN_SEPARATOR(),
                    keccak256(
                        abi.encode(
                            keccak256(
                                "VerifyPacket(bytes32 request,uint256 deadline,uint256 payload)"
                            ),
                            packet.request,
                            packet.deadline,
                            packet.payload
                        )
                    )
                )
            ),
            packet.v,
            packet.r,
            packet.s
        );


        /// Added to original Trustus for test
        recovered =  recoveredAddress;

        return (recoveredAddress != address(0)) && isTrusted[recoveredAddress];
    } 


    /// @notice Sets the trusted status of an offchain data provider.
    /// @param signer The data provider's ECDSA public key as an Ethereum address
    /// @param isTrusted_ The desired trusted status to set
    function _setIsTrusted(address signer, bool isTrusted_) internal virtual {
            isTrusted[signer] = isTrusted_;
        }

    /// -----------------------------------------------------------------------
    /// EIP-712 compliance
    /// -----------------------------------------------------------------------

    /// @notice The domain separator used by EIP-712
    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
            return
            block.chainid == INITIAL_CHAIN_ID
                ? INITIAL_DOMAIN_SEPARATOR
                : _computeDomainSeparator();
    }
    /// @notice Computes the domain separator used by EIP-712
    function _computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256(
                        "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
                    ),
                    keccak256("Trustus"),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }

}


좋은 웹페이지 즐겨찾기