From 55f4afb16e72f58403257c60745fe36fb4ecf7dd Mon Sep 17 00:00:00 2001 From: Jay Logelin Date: Thu, 27 Sep 2018 18:19:11 -0300 Subject: [PATCH] Automatically merged updates to draft EIP(s) 918 Hi, I'm a bot! This change was automatically merged because: - It only modifies existing Draft or Last Call EIP(s) - The PR was approved or written by at least one author of each modified EIP - The build is passing --- EIPS/eip-918.md | 290 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 245 insertions(+), 45 deletions(-) diff --git a/EIPS/eip-918.md b/EIPS/eip-918.md index c54ab4cb..cb25cb06 100644 --- a/EIPS/eip-918.md +++ b/EIPS/eip-918.md @@ -1,7 +1,7 @@ --- eip: 918 title: Mineable Token Standard -author: Jay Logelin , Infernal_toast , Michael Seiler +author: Jay Logelin , Infernal_toast , Michael Seiler , Brandon Grill type: Standards Track category: ERC status: Draft @@ -25,20 +25,30 @@ Token distribution via the ICO model and it's derivatives is susceptible to illi #### Interface The general behavioral specification includes a primary function that defines the token minting operation, an optional merged minting operation for issuing multiple tokens, getters for challenge number, mining difficulty, mining target and current reward, and finally a Mint event, to be emitted upon successful solution validation and token issuance. At a minimum, contracts must adhere to this interface (save the optional merge operation). It is recommended that contracts interface with the more behaviorally defined Abstract Contract described below, in order to leverage a more defined construct, allowing for easier external implementations via overridden phased functions. (see 'Abstract Contract' below) -``` js -contract EIP918Interface { - function mint(uint256 nonce) public returns (bool success); - function merge(uint256 nonce, address[] mineTokens) public returns (bool) {} - 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 hash(uint256 nonce) public returns (bytes32 digest); - function _reward() internal returns (uint); - function _epoch() internal returns (uint); - function _adjustDifficulty() internal returns (uint); - event Mint(address indexed from, uint reward_amount, uint epochCount, bytes32 newChallengeNumber); +``` solidity +contract 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 hash(uint256 _nonce, address _minter) public returns (bytes32 digest); + + function _reward(address _minter) internal returns (uint); + + function _epoch() internal returns (uint); + + function _adjustDifficulty() internal returns (uint); + + event Mint(address indexed from, uint rewardAmount, uint epochCount, bytes32 newChallengeNumber); } ``` @@ -51,35 +61,35 @@ The Abstract Contract adheres to the EIP918 Interface and extends behavioral def #### adjustmentInterval The amount of time between difficulty adjustments in seconds. -``` js +``` solidity bytes32 public adjustmentInterval; ``` #### challengeNumber The current challenge number. It is expected tha a new challenge number is generated after a new reward is minted. -``` js +``` solidity bytes32 public challengeNumber; ``` #### difficulty The current mining difficulty which should be adjusted via the \_adjustDifficulty minting phase -``` js +``` solidity uint public difficulty; ``` #### tokensMinted Cumulative counter of the total minted tokens, usually modified during the \_reward phase -``` js +``` solidity uint public tokensMinted; ``` #### epochCount Number of 'blocks' mined -``` js +``` solidity uint public epochCount; ``` @@ -95,7 +105,7 @@ This externally facing function is called by miners to validate challenge digest populate statistics, mutate epoch variables and adjust the solution difficulty as required. Once complete, a Mint event is emitted before returning a boolean success flag. -``` js +``` solidity contract AbstractERC918 is EIP918Interface { // the amount of time between difficulty adjustments @@ -152,25 +162,15 @@ contract AbstractERC918 is EIP918Interface { Upon successful verification and reward the mint method dispatches a Mint Event indicating the reward address, the reward amount, the epoch count and newest challenge number. -``` js +``` solidity event Mint(address indexed from, uint reward_amount, uint epochCount, bytes32 newChallengeNumber); ``` -#### merge - -*Optional* - -Operationally similar to mint, except the merge function offers a list of token target addresses intended to be used to merge multiple token rewards. - -``` js -function merge(uint256 nonce, address[] mineTokens) public returns (bool success); -``` - #### hash Public interface function hash, meant to be overridden in implementation to define hashing algorithm and validation. Returns the validated digest -``` js +``` solidity function hash(uint256 nonce) public returns (bytes32 digest); ``` @@ -178,7 +178,7 @@ function hash(uint256 nonce) public returns (bytes32 digest); Internal interface function \_reward, meant to be overridden in implementation to calculate and allocate the reward amount. The reward amount must be returned by this method. -``` js +``` solidity function _reward() internal returns (uint); ``` @@ -186,7 +186,7 @@ function _reward() internal returns (uint); Internal interface function \_newEpoch, meant to be overridden in implementation to define a cutpoint for mutating mining variables in preparation for the next phase of mine. -``` js +``` solidity function _newEpoch(uint256 nonce) internal returns (uint); ``` @@ -194,7 +194,7 @@ function _newEpoch(uint256 nonce) internal returns (uint); Internal interface function \_adjustDifficulty, meant to be overridden in implementation to adjust the difficulty (via field difficulty) of the mining as required -``` js +``` solidity function _adjustDifficulty() internal returns (uint); ``` @@ -202,7 +202,7 @@ function _adjustDifficulty() internal returns (uint); The amount of time, in seconds, between difficulty adjustment operations. -``` js +``` solidity function getAdjustmentInterval() public view returns (uint); ``` @@ -210,7 +210,7 @@ function getAdjustmentInterval() public view returns (uint); Recent ethereum block hash, used to prevent pre-mining future blocks. -``` js +``` solidity function getChallengeNumber() public view returns (bytes32); ``` @@ -218,7 +218,7 @@ function getChallengeNumber() public view returns (bytes32); The number of digits that the digest of the PoW solution requires which typically auto adjusts during reward generation. -``` js +``` solidity function getMiningDifficulty() public view returns (uint) ``` @@ -226,16 +226,20 @@ function getMiningDifficulty() public view returns (uint) Return the current reward amount. Depending on the algorithm, typically rewards are divided every reward era as tokens are mined to provide scarcity. -``` js +``` solidity function getMiningReward() public view returns (uint) ``` ### Example mining function -A general mining function written in python for finding a valid nonce for mined token 0xbitcoin, is as follows: -``` +A general mining function written in python for finding a valid nonce for keccak256 mined token, is as follows: +``` python +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_random_number() + nonce = generate_nonce() hash1 = int(sha3.keccak_256(challenge+public_address+nonce).hexdigest(), 16) if hash1 < difficulty: return nonce, hash1 @@ -243,9 +247,184 @@ def mine(challenge, public_address, difficulty): Once the nonce and hash1 are found, these are used to call the mint() function of the smart contract to receive a reward of tokens. +### Merged Mining Extension (Optional) +In order to provide support for merge mining multiple tokens, an optional merged mining extension can be implemented as part of the ERC918 standard. It is important to note that the following function will only properly work if the base contracts use tx.origin instead of msg.sender when applying rewards. If not the rewarded tokens will be sent to the calling contract and not the end user. + +``` solidity +/** + * @title ERC-918 Mineable Token Standard, optional merged mining functionality + * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-918.md + * + */ +contract ERC918Merged is AbstractERC918 { + /* + * @notice Externally facing merge function that is called by miners to validate challenge digests, calculate reward, + * populate statistics, mutate state variables and adjust the solution difficulty as required. Additionally, the + * merge function takes an array of target token addresses to be used in merged rewards. Once complete, + * a Mint event is emitted before returning a success indicator. + * + * @param _nonce the solution 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 Externally facing merge function kept for backwards compatability with previous definition + * + * @param _nonce the solution nonce + * @param _challenge_digest the keccak256 encoded challenge number + message sender + solution nonce + **/ + function merge(uint256 _nonce, bytes32 _challenge_digest, address[] _mineTokens) public returns (bool) { + //the challenge digest must match the expected + 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); + } +} +``` + +### Delegated Minting Extension (Optional) +In order to facilitate a third party minting submission paradigm, such as the case of miners submitting solutions to a pool operator and/or system, a delegated minting extension can be used to allow pool accounts submit solutions on the behalf of a user, so the miner can avoid directly paying Ethereum transaction costs. This is performed by an offchain mining account packaging and signing a standardized mint solution packet and sending it to a pool or 3rd party to be submitted. + +The ERC918 Mineable Mint Packet Metadata should be prepared using following schema: +``` solidity +{ + "title": "Mineable Mint Packet Metadata", + "type": "object", + "properties": { + "nonce": { + "type": "string", + "description": "Identifies the target solution nonce", + }, + "origin": { + "type": "string", + "description": "Identifies the original user that mined the solution nonce", + }, + "signature": { + "type": "string", + "description": "The signed hash of tightly packed variables sha3('delegatedMintHashing(address,uint256)')+nonce+origin_account", + } + } +} +``` +The preparation of a mineable mint packet on a javascript client would appear as follows: + +``` solidity +const ethUtil = require("ethereumjs-util") + +function prepareDelegatedMintTxn(nonce, address, privateKey) { + var functionSig = web3.sha3("delegatedMintHashing(address,uint256)").substring(0,10) + var hashOf = "0x" + bytes4ToHex(functionSig) + uint256ToHex(nonce) +addressToHex(accounts[1]) + var data = ethUtil.sha3(hashOf) + var signature = ethUtil.ecsign(data, new Buffer(privateKey, 'hex')) + var sig = ethUtil.toRpcSig(signature.v, signature.r, signature.s) + // prepare the mint packet + var packet = {} + packet.nonce = nonce; + packet.origin = address; + packet.signature = sig; + // deliver resulting JSON packet to pool or third party submitter + var mineableMintPacket = JSON.stringify(packet, null, 4) + /* todo: send mineableMintPacket to submitter */ +} +``` +Once the packet is prepared and formatted it can then be routed to a third party that will submit the transaction to the contract's deletedMint() function, thereby paying for the transaction gas and receiving the resulting tokens. The pool/third party must then manually payback the minted tokens minus fees to the original minter. + +The following code sample exemplifies third party packet relaying: +``` solidity +//received by minter +var mineableMintPacket = ... +var packet = JSON.parse(mineableMintPacket) +erc918MineableToken.delegatedMint(packet.nonce, packet.origin, packet.signature) +``` +The Extension expands upon ERC918 realized here as a sub-contract: +``` js +contract ERC918DelegatedMint is AbstractERC918 { + /** + * @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 eliptical 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) internal pure returns (bytes32) { + /* "0xb548f23d": delegatedMintHashing(address,uint256) */ + return keccak256( abi.encodePacked( bytes4(0xb548f23d), _nonce, _origin) ); + } +} +``` + +### Mineable Token Metadata (Optional) +In order to provide for richer and potentially mutable metadata for a particular Mineable Token, it is more viable to offer an off-chain reference to said data. This requires the implementation of a single interface method 'metadataURI()' that returns a JSON string encoded with the string fields symbol, name, description, website, image, and type. + +Solidity interface for Mineable Token Metadata: +``` solidity +/** + * @title ERC-918 Mineable Token Standard, optional metadata extension + * @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); +} +``` + +Mineable Token Metadata JSON schema definition: +``` solidity +{ + "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", + }, + "image": { + "type": "string", + "description": "Identifies the Mineable Token's image URI", + }, + "type": { + "type": "string", + "description": "Identifies the Mineable Token's hash algorithm ( ie.keccak256 ) used to encode the solution", + } + } +} +``` + ### Rationale -A keccak256 algorithm does not have to be used, but it is recommended since it is a cost effective one-way algorithm to perform in the EVM and simple to perform in solidity. The nonce is the solution that miners try to find and so it is part of the hashing algorithm. A challengeNumber is also part of the hash so that future blocks cannot be mined since it acts like a random piece of data that is not revealed until a mining round starts. The msg.sender address is part of the hash so that a nonce solution is valid only for a particular Ethereum account and so the solution is not susceptible to man-in-the-middle attacks. This also allows pools to operate without being easily cheated by the miners since pools can force miners to mine using the pool's address in the hash algo. +The solidity keccak256 algorithm does not have to be used, but it is recommended since it is a cost effective one-way algorithm to perform in the EVM and simple to perform in solidity. The nonce is the solution that miners try to find and so it is part of the hashing algorithm. A challengeNumber is also part of the hash so that future blocks cannot be mined since it acts like a random piece of data that is not revealed until a mining round starts. The msg.sender address is part of the hash so that a nonce solution is valid only for a particular Ethereum account and so the solution is not susceptible to man-in-the-middle attacks. This also allows pools to operate without being easily cheated by the miners since pools can force miners to mine using the pool's address in the hash algo. The economics of transferring electricity and hardware into mined token assets offers a flourishing community of decentralized miners the option to be involved in the Ethereum token economy directly. By voting with hashpower, an economically pegged asset to real-world resources, miners are incentivized to participate in early token trade to revamp initial costs, providing a bootstrapped stimulus mechanism between miners and early investors. @@ -253,8 +432,29 @@ One community concern for mined tokens has been around energy use without a func ### Backwards Compatibility +Earlier versions of this standard incorporated a redundant 'challenge_digest' parameter on the mint() function that hash-encoded the packed variables challengeNumber, msg.sender and nonce. It was decided that this could be removed from the standard to help minimize processing and thereby gas usage during mint operations. However, in the name of interoperability with existing mining programs and pool software the following contract can be added to the inheritance tree: -Backwards incompatibilities are not introduced. +``` solidity +/** + * @title ERC-918 Mineable Token Standard, optional backwards compatibility function + * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-918.md + * + */ +contract ERC918BackwardsCompatible is AbstractERC918 { + + /* + * @notice Externally facing mint function kept for backwards compatability with previous mint() definition + * @param _nonce the solution nonce + * @param _challenge_digest the keccak256 encoded challenge number + message sender + solution nonce + **/ + function mint(uint256 _nonce, bytes32 _challenge_digest) public returns (bool success) { + //the challenge digest must match the expected + 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); + } +} +``` ### Test Cases (Test cases for an implementation are mandatory for EIPs that are affecting consensus changes. Other EIPs can choose to include links to test cases if applicable.)