Alert Source Discuss
🚧 Stagnant Standards Track: ERC

ERC-918: 可挖矿代币标准

Authors Jay Logelin <jlogelin@alumni.harvard.edu>, Infernal_toast <admin@0xbitcoin.org>, Michael Seiler <mgs33@cornell.edu>, Brandon Grill <bg2655@columbia.edu>
Created 2018-03-07

简述

一种标准化可挖矿代币的规范,该代币使用工作量证明算法进行分发。

摘要

本规范描述了一种最初将代币锁定在代币合约中,并使用类似水龙头的 mint() 函数缓慢分配代币的方法。此 mint() 函数使用工作量证明算法,以最大限度地减少 gas 费用并控制分配速率。此外,可挖矿代币的标准化将催生标准化的 CPU 和 GPU 代币挖矿软件、代币挖矿池以及代币挖矿生态系统中的其他外部工具。

动机

通过 ICO 模型及其衍生物进行的代币分发容易受到人为参与者的非法行为的影响。此外,新的代币项目是中心化的,因为单个实体必须处理和控制所有初始代币和所有筹集到的 ICO 资金。通过通过“首次挖矿发行”(或 IMO)分发代币,代币合约的所有权不再属于部署者,并且部署者“只是另一个用户”。因此,利用挖矿代币分发模型的投资者风险敞口显着降低。本标准旨在独立存在,从而最大程度地实现与 ERC20、ERC721 和其他标准的互操作性。

规范

接口

通用行为规范包括一个定义代币铸造操作的主函数、一个用于发行多个代币的可选合并铸造操作、用于获取挑战码、挖矿难度、挖矿目标和当前奖励的 getter,以及最终的 Mint 事件,该事件将在成功解决方案验证和代币发行后发出。至少,合约必须遵守此接口(保存可选的合并操作)。建议合约与下面描述的更具行为定义的抽象合约进行交互,以便利用更明确的构造,从而允许通过重写的阶段性函数进行更轻松的外部实现。(参见下面的“抽象合约”)

interface ERC918  {
   
   function mint(uint256 nonce) public returns (bool success);

   function getAdjustmentInterval() public view returns (uint);

   function getChallengeNumber() public view returns (bytes32);

   function getMiningDifficulty() public view returns (uint);

   function getMiningTarget() public view returns (uint);

   function getMiningReward() public view returns (uint);
   
   function decimals() public view returns (uint8);

   event Mint(address indexed from, uint rewardAmount, uint epochCount, bytes32 newChallengeNumber);
}

抽象合约(可选)

抽象合约遵守 EIP918 接口,并通过引入代币挖矿和铸造的 4 个内部阶段来扩展行为定义:hash、reward、epoch 和 adjust difficulty,所有这些都在 mint() 操作期间调用。这种构造在过于通用而无法使用与为多个挖矿实现类型提供充足空间之间取得了平衡。

字段

adjustmentInterval

难度调整之间的时间量(以秒为单位)。

bytes32 public adjustmentInterval;

challengeNumber

当前的挑战码。预计在铸造新的奖励后会生成一个新的挑战码。

bytes32 public challengeNumber;

difficulty

当前的挖矿难度,应通过 _adjustDifficulty 铸造阶段进行调整

uint public difficulty;

tokensMinted

已铸造代币总数的累积计数器,通常在 _reward 阶段修改

uint public tokensMinted;

epochCount

已挖矿的“区块”数

uint public epochCount;

挖矿操作

mint

返回一个标志,指示成功的哈希摘要验证,以及奖励分配给 msg.sender。为了防止 MiTM 攻击,建议摘要包括最近的以太坊区块哈希和 msg.sender 的地址。验证后,mint 函数计算并向发送者提供挖矿奖励,并在合约的供应量上执行内部会计操作。

mint 操作作为一个公共函数存在,该函数调用 4 个单独的阶段,表示为函数 hash、_reward、_newEpoch 和 _adjustDifficulty。为了创建最灵活的实现,同时遵守必要的合约协议,建议代币实现者覆盖内部方法,允许基本合约通过 mint 处理它们的执行。

这个面向外部的函数由矿工调用,以验证挑战摘要、计算奖励、 填充统计信息、更改 epoch 变量并根据需要调整解决方案难度。完成后, 在返回布尔成功标志之前,会发出一个 Mint 事件。

contract AbstractERC918 is EIP918Interface {

    // the amount of time between difficulty adjustments
    uint public adjustmentInterval;
     
    // generate a new challenge number after a new reward is minted
    bytes32 public challengeNumber;
    
    // the current mining target
    uint public miningTarget;

    // cumulative counter of the total minted tokens
    uint public tokensMinted;

    // number of blocks per difficulty readjustment
    uint public blocksPerReadjustment;

    //number of 'blocks' mined
    uint public epochCount;
   
    /*
     * Externally facing mint function that is called by miners to validate challenge digests, calculate reward,
     * populate statistics, mutate epoch variables and adjust the solution difficulty as required. Once complete,
     * a Mint event is emitted before returning a success indicator.
     **/
    function mint(uint256 nonce) public returns (bool success) {
        require(msg.sender != address(0));

        // perform the hash function validation
        hash(nonce);
        
        // calculate the current reward
        uint rewardAmount = _reward();
        
        // increment the minted tokens amount
        tokensMinted += rewardAmount;
        
        epochCount = _epoch();

        //every so often, readjust difficulty. Don't readjust when deploying
        if(epochCount % blocksPerReadjustment == 0){
            _adjustDifficulty();
        }
       
        // send Mint event indicating a successful implementation
        emit Mint(msg.sender, rewardAmount, epochCount, challengeNumber);
        
        return true;
    }
}
Mint 事件

成功验证和奖励后,mint 方法会分派一个 Mint 事件,指示奖励地址、奖励金额、epoch 计数和最新的挑战码。

event Mint(address indexed from, uint reward_amount, uint epochCount, bytes32 newChallengeNumber);

hash

公共接口函数 hash,旨在在实现中被覆盖以定义哈希算法和验证。返回已验证的摘要

function hash(uint256 nonce) public returns (bytes32 digest);

_reward

内部接口函数 _reward,旨在在实现中被覆盖以计算和分配奖励金额。奖励金额必须由此方法返回。

function _reward() internal returns (uint);

_newEpoch

内部接口函数 _newEpoch,旨在在实现中被覆盖以定义一个切入点,用于改变挖矿变量,为下一阶段的挖矿做准备。

function _newEpoch(uint256 nonce) internal returns (uint);

_adjustDifficulty

内部接口函数 _adjustDifficulty,旨在在实现中被覆盖以根据需要调整挖矿的难度(通过字段 difficulty)

function _adjustDifficulty() internal returns (uint);

getAdjustmentInterval

难度调整操作之间的时间量(以秒为单位)。

function getAdjustmentInterval() public view returns (uint);

getChallengeNumber

最近的以太坊区块哈希,用于防止预先挖掘未来的区块。

function getChallengeNumber() public view returns (bytes32);

getMiningDifficulty

PoW 解决方案的摘要所需的位数,通常在奖励生成期间自动调整。

function getMiningDifficulty() public view returns (uint)

getMiningReward

返回当前奖励金额。根据算法的不同,通常会在每个奖励时代划分奖励,因为会挖掘代币以提供稀缺性。

function getMiningReward() public view returns (uint)

示例挖矿函数

一个用 python 编写的通用挖矿函数,用于查找 keccak256 挖矿代币的有效 nonce,如下所示:

def generate_nonce():
  myhex =  b'%064x' % getrandbits(32*8)
  return codecs.decode(myhex, 'hex_codec')
  
def mine(challenge, public_address, difficulty):
  while True:
    nonce = generate_nonce()
    hash1 = int(sha3.keccak_256(challenge+public_address+nonce).hexdigest(), 16)
    if hash1 < difficulty:
      return nonce, hash1

一旦找到 nonce 和 hash1,这些将用于调用智能合约的 mint() 函数,以获得代币奖励。

合并挖矿扩展(可选)

为了提供对合并挖掘多个代币的支持,可以选择实现合并挖矿扩展作为 ERC918 标准的一部分。重要的是要注意,如果基本合约在使用奖励时使用 tx.origin 而不是 msg.sender,则以下函数只能正常工作。如果不是,则奖励代币将发送到调用合约,而不是最终用户。

/**
 * @title ERC-918 可挖矿代币标准,可选的合并挖矿功能
 * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-918.md
 * 
 */
contract ERC918Merged is AbstractERC918 {
    /*
     * @notice 面向外部的合并函数,由矿工调用以验证挑战摘要、计算奖励、
     * 填充统计信息、更改状态变量并根据需要调整解决方案难度。此外,
     * 合并函数接受一个目标代币地址数组,用于合并奖励。完成后,
     * 在返回成功指示器之前,会发出一个 Mint 事件。
     *
     * @param _nonce 解决方案 nonce
     **/
    function merge(uint256 _nonce, address[] _mineTokens) public returns (bool) {
      for (uint i = 0; i < _mineTokens.length; i++) {
        address tokenAddress = _mineTokens[i];
        ERC918Interface(tokenAddress).mint(_nonce);
      }
    }

    /*
     * @notice 面向外部的合并函数,保留用于与先前定义向后兼容
     *
     * @param _nonce 解决方案 nonce
     * @param _challenge_digest keccak256 编码的挑战码 + 消息发送者 + 解决方案 nonce
     **/
     function merge(uint256 _nonce, bytes32 _challenge_digest, address[] _mineTokens) public returns (bool) {
       //挑战摘要必须与预期匹配
       bytes32 digest = keccak256( abi.encodePacked(challengeNumber, msg.sender, _nonce) );
       require(digest == _challenge_digest, "Challenge digest does not match expected digest on token contract [ ERC918Merged.mint() ]");
       return merge(_nonce, _mineTokens);
     }
}

委托铸造扩展(可选)

为了方便第三方铸造提交范例,例如矿工将解决方案提交给矿池运营商和/或系统的情况,可以使用委托铸造扩展来允许矿池帐户代表用户提交解决方案,从而使矿工可以避免直接支付以太坊交易费用。这通过链下挖掘帐户打包和签署标准化的 mint 解决方案包并将其发送给矿池或第三方以进行提交来执行。

ERC918 可挖矿 Mint 数据包元数据应使用以下架构准备:

{
    "title": "Mineable Mint Packet Metadata",
    "type": "object",
    "properties": {
        "nonce": {
            "type": "string",
            "description": "Identifies the target solution nonce",  //标识目标解决方案 nonce
        },
        "origin": {
            "type": "string",
            "description": "Identifies the original user that mined the solution nonce", //标识挖掘解决方案 nonce 的原始用户
        },
        "signature": {
            "type": "string",
            "description": "The signed hash of tightly packed variables sha3('delegatedMintHashing(uint256,address)')+nonce+origin_account", //紧密打包变量的签名哈希 sha3('delegatedMintHashing(uint256,address)')+nonce+origin_account
        }
    }
}

在 JavaScript 客户端上准备可挖矿的 mint 数据包将如下所示:

function prepareDelegatedMintTxn(nonce, account) {
  var functionSig = web3.utils.sha3("delegatedMintHashing(uint256,address)").substring(0,10)
  var data = web3.utils.soliditySha3( functionSig, nonce, account.address )
  var sig = web3.eth.accounts.sign(web3.utils.toHex(data), account.privateKey )
  // prepare the mint packet
  var packet = {}
  packet.nonce = nonce
  packet.origin = account.address
  packet.signature = sig.signature
  // deliver resulting JSON packet to pool or third party
  var mineableMintPacket = JSON.stringify(packet, null, 4)
  /* todo: send mineableMintPacket to submitter */
  ...
}

一旦准备好数据包并对其进行格式化,就可以将其路由到第三方,该第三方会将交易提交给合约的 delegatedMint() 函数,从而支付交易 gas 并接收生成的代币。然后,矿池/第三方必须手动将铸造的代币(减去费用)偿还给原始矿工。

以下代码示例说明了第三方数据包中继:

//received by minter  //由矿工接收
var mineableMintPacket = ...
var packet = JSON.parse(mineableMintPacket)
erc918MineableToken.delegatedMint(packet.nonce, packet.origin, packet.signature)

委托 Mint 扩展以 ERC918 为基础实现为子合约:

import 'openzeppelin-solidity/contracts/contracts/cryptography/ECDSA.sol';

contract ERC918DelegatedMint is AbstractERC918, ECDSA {
   /**
     * @notice Hash (keccak256) of the payload used by delegatedMint
     * @param _nonce the golden nonce
     * @param _origin the original minter
     * @param _signature the original minter's elliptical curve signature
     */
    function delegatedMint(uint256 _nonce, address _origin, bytes _signature) public returns (bool success) {
        bytes32 hashedTx = delegatedMintHashing(_nonce, _origin);
        address minter = recover(hashedTx, _signature);
        require(minter == _origin, "Origin minter address does not match recovered signature address [ AbstractERC918.delegatedMint() ]");
        require(minter != address(0), "Invalid minter address recovered from signature [ ERC918DelegatedMint.delegatedMint() ]");
        success = mintInternal(_nonce, minter);
    }

    /**
     * @notice Hash (keccak256) of the payload used by delegatedMint
     * @param _nonce the golden nonce
     * @param _origin the original minter
     */
    function delegatedMintHashing(uint256 _nonce, address _origin) public pure returns (bytes32) {
        /* "0x7b36737a": delegatedMintHashing(uint256,address) */
        return toEthSignedMessageHash(keccak256(abi.encodePacked( bytes4(0x7b36737a), _nonce, _origin)));
    }
}

可挖矿代币元数据(可选)

为了为特定的可挖矿代币提供更丰富且可能可变的元数据,提供对所述数据的链下引用更为可行。这需要实现一个接口方法“metadataURI()”,该方法返回一个 JSON 字符串,该字符串使用字符串字段 symbol、name、description、website、image 和 type 进行编码。

可挖矿代币元数据的 Solidity 接口:

/**
 * @title ERC-918 可挖矿代币标准,可选的元数据扩展
 * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-918.md
 * 
 */
interface ERC918Metadata is AbstractERC918 {
    /**
     * @notice A distinct Uniform Resource Identifier (URI) for a mineable asset.
     */
    function metadataURI() external view returns (string);
}

可挖矿代币元数据 JSON 模式定义:

{
    "title": "Mineable Token Metadata",
    "type": "object",
    "properties": {
        "symbol": {
            "type": "string",
            "description": "Identifies the Mineable Token's symbol", //标识可挖矿代币的符号
        },
        "name": {
            "type": "string",
            "description": "Identifies the Mineable Token's name", //标识可挖矿代币的名称
        },
        "description": {
            "type": "string",
            "description": "Identifies the Mineable Token's long description", //标识可挖矿代币的长描述
        },
        "website": {
            "type": "string",
            "description": "Identifies the Mineable Token's homepage URI", //标识可挖矿代币的主页 URI
        },
        "image": {
            "type": "string",
            "description": "Identifies the Mineable Token's image URI", //标识可挖矿代币的图像 URI
        },
        "type": {
            "type": "string",
            "description": "Identifies the Mineable Token's hash algorithm ( ie.keccak256 ) used to encode the solution", //标识用于编码解决方案的可挖矿代币的哈希算法(即 keccak256)
        }
    }
}

理由

不一定必须使用 solidity keccak256 算法,但建议使用,因为它是一种在 EVM 中执行的具有成本效益的单向算法,并且在 solidity 中易于执行。nonce 是矿工尝试找到的解决方案,因此它是哈希算法的一部分。challengeNumber 也是哈希的一部分,因此无法挖掘未来的区块,因为它就像一个随机数据,在挖矿回合开始之前不会显示。msg.sender 地址是哈希的一部分,因此 nonce 解决方案仅对特定的以太坊帐户有效,因此该解决方案不易受到中间人攻击。这也允许矿池在不被矿工轻易欺骗的情况下运行,因为矿池可以强制矿工在哈希算法中使用矿池的地址进行挖掘。

将电力和硬件转移到挖矿代币资产的经济学为蓬勃发展的去中心化矿工社区提供了直接参与以太坊代币经济的选择。通过用哈希算力投票,一种在经济上与真实世界资源挂钩的资产,矿工有动力参与早期代币交易以改造初始成本,从而在矿工和早期投资者之间提供了一种引导性刺激机制。

社区对挖掘代币的一个担忧是能源使用,而没有保护网络的功能。虽然代币挖掘不能保护网络,但它可以保护社区免受腐败,因为它提供了中心化 ICO 的替代方案。此外,首次挖矿发行可能持续不到一周、一天或一小时,届时所有代币都将被铸造。

向后兼容性

该标准的早期版本在 mint() 函数中包含了一个冗余的“challenge_digest”参数,该参数哈希编码了打包变量 challengeNumber、msg.sender 和 nonce。已决定可以从标准中删除它,以帮助最大限度地减少处理,从而减少 mint 操作期间的 gas 使用量。但是,为了与现有的挖矿程序和矿池软件实现互操作性,可以将以下合约添加到继承树中:

/**
 * @title ERC-918 可挖矿代币标准,可选的向后兼容性函数
 * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-918.md
 * 
 */
contract ERC918BackwardsCompatible is AbstractERC918 {

    /*
     * @notice 面向外部的 mint 函数,保留用于与之前的 mint() 定义向后兼容
     * @param _nonce 解决方案 nonce
     * @param _challenge_digest keccak256 编码的挑战码 + 消息发送者 + 解决方案 nonce
     **/
    function mint(uint256 _nonce, bytes32 _challenge_digest) public returns (bool success) {
        //挑战摘要必须与预期匹配
        bytes32 digest = keccak256( abi.encodePacked(challengeNumber, msg.sender, _nonce) );
        require(digest == _challenge_digest, "Challenge digest does not match expected digest on token contract [ AbstractERC918.mint() ]");
        success = mint(_nonce);
    }
}

测试用例

(对于影响共识变更的 EIP,测试用例是强制性的。其他 EIP 可以选择包含适用测试用例的链接。)

实现

简单示例: https://github.com/0xbitcoin/EIP918-Mineable-Token/blob/master/contracts/SimpleERC918.sol

复杂示例:

https://github.com/0xbitcoin/EIP918-Mineable-Token/blob/master/contracts/0xdogeExample.sol https://github.com/0xbitcoin/EIP918-Mineable-Token/blob/master/contracts/0xdogeExample2.sol https://github.com/0xbitcoin/EIP918-Mineable-Token/blob/master/contracts/0xBitcoinBase.sol

0xBitcoin 代币合约: https://etherscan.io/address/0xb6ed7644c69416d67b522e20bc294a9a9b405b31

MVI OpenCL 代币矿工 https://github.com/mining-visualizer/MVis-tokenminer/releases

PoWAdv 代币合约: https://etherscan.io/address/0x1a136ae98b49b92841562b6574d1f3f5b0044e4c

版权

版权及相关权利通过 CC0 放弃。

Citation

Please cite this document as:

Jay Logelin <jlogelin@alumni.harvard.edu>, Infernal_toast <admin@0xbitcoin.org>, Michael Seiler <mgs33@cornell.edu>, Brandon Grill <bg2655@columbia.edu>, "ERC-918: 可挖矿代币标准 [DRAFT]," Ethereum Improvement Proposals, no. 918, March 2018. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-918.