coetus’s blog

Blockchain の Improvement Proposal などの和訳をしています. 管理人の学習用途のため正確な意味は原文を参照してください

EIP-137 - Ethereum Domain Name Service - Specification

  • 著者:Nick Johnson
  • ステータス:Final
  • タイプ:Standards Track
  • カテゴリ:ERC
  • 作成日:2016-04-04

要約

このドラフトの EIP は Ethereum Name Service の詳細を記述する。提案のプロトコルと ABI の定義は、短い、人間が読める名前を、サービスやリソースのIDに解決する。これによって、ユーザと開発者が読めるようになり、名前も覚えやすくなる。根底にあるリソース(コントラクトやアドレスが付いたコンテンツのデータなど)が変更されたとき、名前の更新ができるようになる。

ドメイン名の目標は、安定した、人間に読めるIDの提供であり、それがネットワークリソースを定義するために使われるようになることである。この方法で、'vitalik.vallet' や 'www.mysite.swarm' といった、ユーザが覚えられる文字列を入力して、適切なリソースにリンクすることが出来る。名前とリソースの間のマッピングは時間経過によって変更されるかもしれない。ドメイン名の変更無しで、ユーザはウォレットを変更し、ユーザはホストを変更し、swarm のドキュメントは新しいバージョンにアップデートされるだろう。更に、ドメインは単一のリソースを指す必要がない。様々なレコードの種類によって、同一ドメインが別々のリソースを指すことが出来るようになる。例えば、ブラウザは 'mysite.swarm' を A (アドレス) のレコードをフェッチすることによって、サーバの IP アドレスに解決するだろう。一方で、メールのクライアントは、MX (mail exchanger) レコードをフェッチすることによって、同一のアドレスをメールサーバに解決するかもしれない。

動機

Ethereum 上の名前解決の既存の仕様実装は基本的な機能を提供するが、長期的な有益性を著しく制限するいくつかの欠点を持ち合わせている。

  • 全ての名前のための、単一のグローバルな名前空間が、単一の「中央集権化された」リゾルバと一緒に存在する。
  • 委譲やサブネーム/サブドメインのサポートが制限されているか存在しない。
  • 一つのレコードの種類だけであり、複数のレコードのコピーとドメインを結びつけることはサポートしない。
  • 単一のグローバルな実装のせいで、複数の異なる name allocation のシステムはサポートしない。
  • 責任の合成:名前解決、登録、whois情報

これらの特徴が含むことを許可するユースケース

  • サブネームやサブドメインのサポート - 例:live.mysite.tld, forum.mysite.tld.
  • 単一の名前の下で複数のサービス、Swarm でホスティングされた DApp、Whisper のアドレス、メールサーバ。
  • DNS レコードタイプのサポート。ブロックチェーンが「レガシー」な名前をホスティングすることを許可する。これは Mist のような Ethereum クライアントが伝統的なウェブサイトのアドレスや email アドレスのためのメールサーバの解決をブロックチェーン名から行うのを許可する。
  • DNS ゲートウェイは、Domain Name Service 経由で ENS ドメインを expose しているが、レガシークライアントがブロックチェーンサービスを解決・接続する、より簡単な方法を提供する。

最初の 2 つのユースケースは、とりわけ、今日の DNS 下のインターネットでどこでも観測することが出来る。私達は、これらが Ethereum プラットフォームが発展・成熟するのに役立ち続けるネームサービスの基礎的な機能になることを信じている。

このドキュメントの標準的な部分では、提案されたシステムの実装を規定しない。目的は、一貫した名前解決を促進するため、異なるリゾルバの実装が遵守することの出来るプロトコルをドキュメント化することにある。付録として、リゾルバのコントラクトやライブラリの参考実装を提供している。これは、実例としてのみ扱われるべきである。

同様にこのドキュメントは、どのようにドメインが登録され、更新されるべきか、また、名前の所有者が与えられたドメインに対して責任を負うことを、どのようにすればシステムが分かるかについて、規定しようとはしない。ドメインの登録はレジストラの責任であり、トップレベルドメインの中で必ず様々な違いが生じてくる政治的な問題である。

仕様

概要

ENS システムは、以下の 3 つのメインパートからなる。

レジストリは、任意の登録済みの名前から担当するリゾルバへのマッピングを提供する単一のコントラクトであり、名前の所有者が、リゾルバのアドレスを設定し、潜在的に異なる所有者を持つサブドメインを親ドメイン上に作成することを許可する。

ゾルバは、コントラクトのアドレス、コンテンツのハッシュや、必要に応じて IP アドレスを返却するなどの、リソースの名前解決を実行する。リゾルバの仕様は、本 EIP で定義され別の EIP で拡張されるものであり、仕様ではリゾルバが異なる種類のレコードを解決するサポートをするために実装すると思われるメソッドを定義する。

レジストラは、システムのユーザに対してドメインの名前を確保する責任を負っており、ENS を更新できる唯一のエンティティである。即ち、ENS レジストリ上のノードの所有者は、ノードのレジストラである。レジストラは、コントラクトや外部に所持されたアカウントであると思わるが、最低でも、ルートやトップレベルのレジストラコントラクトとして実装されているであろうということが期待されている。

ENS 上での名前解決は 2 ステップのプロセスからなる。はじめに、ENS レジストリは、解決したい名前を以下で述べる手順を用いてハッシュ化した後に、そのハッシュ値を用いて呼び出される。次に、リクエストされたリソースに適切な方法を用いてリゾルバが呼ばれる。そうしてリゾルバは求値の結果を返却する。

例として、'beercoin.eth' に紐付いたトークコントラクトのアドレスを見つける場合を考える。はじめに、リゾルバを取得する:

var node = namehash("beercoin.eth");
var resolver = ens.resolver(node);

次に、リゾルバにコントラクトのアドレスを尋ねる:

var address = resolver.addr(node);

namehash の手順は名前そのものに依存するため、これは事前計算をしてコントラクト内に挿入することが出来る。その結果、文字列操作が不要となり、生の名前上での要素数に関わらず O(1) で ENS レコードの検索ができる。

名前のシンタックス

ENS の名前は、以下のシンタックスに従う必要がある:

<domain> ::= <label> | <domain> "." <label>
<label> ::= any valid string label per \[UTS46\](http://unicode.org/reports/tr46/)

要するに、名前はドット区切りの label 列からなる。各々の label は UTS46 で、オプションを transitional=false, useSTD3AsciiRules=true として記述された valid な正規化された label である必要がある。Javascript の実装用に、名前の正規化とチェックが出来るライブラリが利用できる。

名前に UpperCase と lowercase が使用できる一方で、ハッシュ化する前に UTS46 の正規化でラベルに case 変換を施す。よって、case が異なる 2 つの名前だが同一のスペルとなるものは、同じ namehash を生成する。

label と domain は任意の長さを持ちうるが、レガシーな DNS との互換性のために、label はそれぞれ 64 文字以下で、全体の ENS の名前は 255 文字以下になるように制限することが推奨されている。同様の理由で、label の先頭と末尾にハイフンは置かず、先頭に数字を置かないようにすることが推奨されている。

namehash アルゴリズム

名前は ENS で利用される前に、'namehash' アルゴリズムを用いてハッシュ化される。このアルゴリズム再帰的に名前のコンポーネントをハッシュ化し、任意の valid な入力の domain に対して、ユニークで固定長の文字列を生成する。namehash の出力は 'node' として参照される。

namehash アルゴリズム擬似コードは以下である:

def namehash(name):
  if name == '':
    return '\0' * 32
  else:
    label, _, remainder = name.partition('.')
    return sha3(namehash(remainder) + sha3(label))

略式では、名前は label に分割され、それぞれのラベルがハッシュ化される。その後、最後の要素で始め、前の出力は label のハッシュと連結してからもう一度ハッシュ化される。最初の要素は '0' のバイトを 32 個連結したものとする。したがって、'mysite.swarm' は、以下のように処理される:

node = '\0' * 32
node = sha3(node + sha3('swarm'))
node = sha3(node + sha3('mysite'))

実装は、namehash 用の以下のテストベクターに適合する必要がある:

namehash('') = 0x0000000000000000000000000000000000000000000000000000000000000000
namehash('eth') = 0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae
namehash('foo.eth') = 0xde9b09fd7c5f901e23a3f19fecc54828e9c848539801e86591bd9801b019f84f

レジストリの仕様

ENS レジストリコントラクトは、以下の関数を公開する:

function owner(bytes32 node) constant returns (address);

特定のノードの所有者 (レジストラ) を返す。

function resolver(bytes32 node) constant returns (address);

特定のノードのリゾルバを返す。

function ttl(bytes32 node) constant returns (uint64);

ノードの time-to-live (TTL) を返す。これは、ノードの情報をキャッシュ化するための最大の期間である。

function setOwner(bytes32 node, address owner);

ノードの所有権を別のレジストラに転送する。この関数は node の現在の所有者だけが呼び出すことが出来る。関数の呼び出しが成功したら、Transfer(bytes32 indexed, address) のイベントがロギングされる。

function setSubnodeOwner(bytes32 node, bytes32 label, address owner);

新規のノードを sha3(node, label) として作成し、その所有者を owner に付与する。既にノードが存在していた場合、そのノードの所有者を新しい所有者に更新する。この関数は node の現在の所有者だけが呼び出すことが出来る。関数の呼び出しが成功したら、NewOwner(bytes32 indexed, bytes32 indexed, address) のイベントがロギングされる。

function setResolver(bytes32 node, address resolver);

node にリゾルバのアドレスを設定する。この関数は node の現在の所有者だけが呼び出すことが出来る。関数の呼び出しが成功したら、NewResolver(bytes32 indexed, address) のイベントがロギングされる。

function setTTL(bytes32 node, uint64 ttl);

ノードに TTL の設定をする。ノードの TTL は、レジストリ内の 'owner' と 'resolver' に適用され、同様に、関連するリゾルバが返却した任意の情報に対しても適用される。

ゾルバの仕様

ゾルバは、ここで定義されるレコードの種類の任意の部分集合を実装することができる。レコードの種類の仕様は、複数の関数を提供するリゾルバを必要とし、リゾルバはそれらの全てを実装するか、どれも実装しないかの何れかでなければならない。リゾルバは例外を投げるフォールバック関数を定義しなければならない。

ゾルバは、一つの必ず実装が必要な関数を持つ:

function supportsInterface(bytes4 interfaceID) constant returns (bool)

surrportsInterface 関数は EIP 165 でドキュメント化されており、リゾルバが、与えられた 4 バイトの ID によって定義されたインターフェースを実装しているときに true を返す。インターフェースの ID は、インターフェースによって提供された関数の、関数シグネチャのハッシュの XOR からなっている。リゾルバが supportsInterface() に対して true を返す場合、そのインターフェース定義された関数を実装しなければならない。

supportsInterface0x01ffc9a7 に対して常に true を返す。これは supportsInterface 自身の interface ID になる。

現在のところ、標準化されているリゾルバのインターフェースは以下のテーブルで定義されている。

以下のインターフェースが定義される:

Interface 名 Interface ハッシュ 仕様
addr 0x3b3b57de コントラクトのアドレス
name 0x691f3431 #181
ABI 0x2203ab56 #205
pubkey 0xc8690233 #619

このレジストリに登録される新規のインターフェースを EIP に定義することになる。

Contract Address インターフェース

コントラクトのアドレスのリソース機能を持つことが期待されるリゾルバは、以下の関数を提供しなければならない。

function addr(bytes32 node) constant returns (address);

ゾルバが addr 検索をサポートしているのに、リクエストされたノードがアドレスのレコードqを持っていない場合、リゾルバは、必ずゼロ番地を返さなければならない。

addr レコードを解決するクライアントは必ず zero return value のチェックをして、定義されたリゾルバを持たない名前と同様に扱わなければならない - 即ち、アドレスに資金を送金したりやり取りすることを拒否しなければならない。これを実行することに失敗すれば、ユーザが間違って 0 番地に送金する結果になりうる。

アドレスへの変更は以下のイベントのトリガーにならなければならない:

event AddrChanged(bytes32 indexed node, address a);

付録 A: レジストリの実装

contract ENS {
    struct Record {
        address owner;
        address resolver;
        uint64 ttl;
    }

    mapping(bytes32=>Record) records;

    event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner);
    event Transfer(bytes32 indexed node, address owner);
    event NewResolver(bytes32 indexed node, address resolver);

    modifier only_owner(bytes32 node) {
        if(records[node].owner != msg.sender) throw;
        _
    }

    function ENS(address owner) {
        records[0].owner = owner;
    }

    function owner(bytes32 node) constant returns (address) {
        return records[node].owner;
    }

    function resolver(bytes32 node) constant returns (address) {
        return records[node].resolver;
    }

    function ttl(bytes32 node) constant returns (uint64) {
        return records[node].ttl;
    }

    function setOwner(bytes32 node, address owner) only_owner(node) {
        Transfer(node, owner);
        records[node].owner = owner;
    }

    function setSubnodeOwner(bytes32 node, bytes32 label, address owner) only_owner(node) {
        var subnode = sha3(node, label);
        NewOwner(node, label, owner);
        records[subnode].owner = owner;
    }

    function setResolver(bytes32 node, address resolver) only_owner(node) {
        NewResolver(node, resolver);
        records[node].resolver = resolver;
    }

    function setTTL(bytes32 node, uint64 ttl) only_owner(node) {
        NewTTL(node, ttl);
        records[node].ttl = ttl;
    }
}

付録 B: サンプルのリゾルバ実装

組み込みのリゾル

最も単純なあり得るリゾルバの実装は、contract address resource profile の実装によって、自分自身のネームリゾルバとして振る舞うコントラクトである。

contract DoSomethingUseful {
    // Other code

    function addr(bytes32 node) constant returns (address) {
        return this;
    }

    function supportsInterface(bytes4 interfaceID) constant returns (bool) {
        return interfaceID == 0x3b3b57de || interfaceID == 0x01ffc9a7;
    }

    function() {
        throw;
    }
}

そのようなコントラクトは、最も単純なユースケースで、別に分けられたリゾルコントラクトの需要を排除して ENS レジストリに直接挿入されうる。しかしながら、未知の関数呼び出し上で '例外を送出する' 要件は、何らかの種類のコントラクトの通常実行に鑑賞するかも知れない。

スタンドアローンのリゾル

contract address profile を実装する基本的なリゾルバはであり、所有者のみがレコードを更新できる:

contract Resolver {
    event AddrChanged(bytes32 indexed node, address a);

    address owner;
    mapping(bytes32=>address) addresses;

    modifier only_owner() {
        if(msg.sender != owner) throw;
        _
    }

    function Resolver() {
        owner = msg.sender;
    }

    function addr(bytes32 node) constant returns(address) {
        return addresses[node];    
    }

    function setAddr(bytes32 node, address addr) only_owner {
        addresses[node] = addr;
        AddrChanged(node, addr);
    }

    function supportsInterface(bytes4 interfaceID) constant returns (bool) {
        return interfaceID == 0x3b3b57de || interfaceID == 0x01ffc9a7;
    }

    function() {
        throw;
    }
}

このコントラクトをデプロイした後、名前解決のためにこのコントラクトを参照するために ENS レジストリを更新することによって使用する。その後、解決したい contract address を設定するために同じノードに setAddr() の呼び出す。

パブリックリゾル

上記のリゾルバ同様に、このコントラクトは contract address profile のみをサポートする。しかし、誰がエンティティを更新することを許可されるべきかを決定するために ENS レジストリを使用する。

contract PublicResolver {
    event AddrChanged(bytes32 indexed node, address a);
    event ContentChanged(bytes32 indexed node, bytes32 hash);

    ENS ens;
    mapping(bytes32=>address) addresses;

    modifier only_owner(bytes32 node) {
        if(ens.owner(node) != msg.sender) throw;
        _
    }

    function PublicResolver(address ensAddr) {
        ens = ENS(ensAddr);
    }

    function addr(bytes32 node) constant returns (address ret) {
        ret = addresses[node];
    }

    function setAddr(bytes32 node, address addr) only_owner(node) {
        addresses[node] = addr;
        AddrChanged(node, addr);
    }

    function supportsInterface(bytes4 interfaceID) constant returns (bool) {
        return interfaceID == 0x3b3b57de || interfaceID == 0x01ffc9a7;
    }

    function() {
        throw;
    }
}

付録 C: サンプルのレジストラ実装

このレジストラは、リクエストを送った最初のユーザが名前をノーコストで登録することを許可する。

contract FIFSRegistrar {
    ENS ens;
    bytes32 rootNode;

    function FIFSRegistrar(address ensAddr, bytes32 node) {
        ens = ENS(ensAddr);
        rootNode = node;
    }

    function register(bytes32 subnode, address owner) {
        var node = sha3(rootNode, subnode);
        var currentOwner = ens.owner(node);
        if(currentOwner != 0 && currentOwner != msg.sender)
            throw;

        ens.setSubnodeOwner(rootNode, subnode, owner);
    }
}

EIP-101 - Serenity Currency and Crypto Abstraction

  • 著者:Vitalik Buterin
  • ステータス:Draft
  • タイプ:Standards Track
  • カテゴリ:Core
  • 作成日:2015-11-15

仕様

  1. アカウントはたった 2 つのフィールドを RLP エンコーディング内に持つ:codestorage である。
  2. Ether はもはやアカウントオブジェクトに直接格納されない。そのかわり、アドレス 0 に、私達はすべての ether の持ち金を含むコントラクトを先にマイニングする。eth.getBalance コマンドは web3 で適切にリマッピングされる。
  3. msg.value はもはや opcode として存在しない。
  4. トランザクションはたった 4 つのフィールドを持つようになる:to, startgas, data, code である。
  5. RLP の妥当性検証の傍らに、to フィールドが 20 byte の長さであること、startgas が整数であること、code が空または to のアドレスのハッシュ値になるかを検証する。他に制約は無く、全て検証を通る。しかしながら、ブロックの gas の制限は残っているので、マイナーは junk データを含めないように disincentivize される。
  6. gas は code 内のバイト列に対して、data と同じレートだけ請求される。
  7. トランザクションが送信されたとき、もし受け取りのアカウントがもはや存在していないとき、アカウントが作られ、その code はトランザクションの code に設定される。そうでなければ code は無視される。
  8. tx.gas opcode は、0x5c のインデックスにある既存の msg.gas に沿って追加される。この新規の opcode で、トランザクションは、トランザクションに割り当てられたオリジナルの gas の量にアクセスできるようになる。

注釈として、シーケンス番号/ナンスの増加や ether である ECRECOVER は、bottom-level の仕様としては今やどこにもない(NOTE: ether は Casper PoS の特権ロールを持ち続ける)。新規のモデルの下で既存の機能の再現するため、私達は以下を行う。

単純なユーザアカウントは、以下のデフォルトの標準化されたコードを持つ:

# データは下記のスキーマに従うものとする:
# bytes 0-31: v (ECDSA 署名)
# bytes 32-63: r (ECDSA 署名)
# bytes 64-95: s (ECDSA 署名)
# bytes 96-127: シーケンス番号 (フォーマルには "ナンス" と呼ばれる)
# bytes 128-159: gasprice
# bytes 172-191: to
# bytes 192+: data

# トランザクションの署名のハッシュを取得
~mstore(0, msg.gas)
~calldatacopy(32, 96, ~calldatasize() - 96)
h = sha3(96, ~calldatasize() - 96)
# sender を取得するために ECRECOVER コントラクトを呼ぶ
~call(5000, 3, \[h, ~calldataload(0), ~calldataload(32), ~calldataload(64)\], 128, ref(addr), 32)
# sender の正当性をチェックする
assert addr == 0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1
# シーケンス番号の正当性をチェックする
assert ~calldataload(96) == self.storage\[-1\]
# シーケンス番号をインクリメントする
self.storage\[-1\] += 1
# sub-call を作成し、出力を破棄する
~call(msg.gas - 50000, ~calldataload(160), 192, ~calldatasize() - 192, 0, 0)
# gas を支払う
~call(40000, 0, \[SEND, block.coinbase, ~calldataload(128) * (tx.gas - msg.gas + 50000)\], 96, 0, 0)

これは本質的に署名とナンスをチェックする実装であり、両方のチェックが通れば、全ての残りの gas - 50000 が実際の所望の呼び出しに送られ、そして最終的に gas に支払われる。

マイナーはトランザクションを受け取ると、以下のアルゴリズムに従うことが出来る:

  1. 最大 50000 gas のコードを実行する。この限界値を超える恐れのある operation や call があると、実行は停止する。
  2. この operation を見ることに際して、最後には 50000 の gas を費やすことを保証する(静的な gas 消費が十分小さいことをチェックするか gas の上限のパラメータとして msg.gas - 50000 の call をチェックするかによって)。
  3. パターンマッチを行い、gas の支払いコードは最終的にちょうど上記のコードと同じになることを保証する。

この処理は、トランザクションを含めるのに価値が足るかどうかを知る前に、マイナーの浪費が高々 50000 gas であることを保証する。また、とても一般的であるので、ユーザが新しい暗号(例:ed25519, Lamport)、リング署名、疑似-native マルチシグなどを実験することが出来る。理論的には、ある人が有効な署名タイプが、領収書(?)の有効なマークルブランチであるアカウントを作成して、疑似 native な alarm clock を作成することさえ出来る。

もし誰かが非ゼロの値を持つトランザクションを発行したい場合、現在の msg.sender の手法の代わりに、3 ステップのプロセスにコンパイルする:

  1. 呼び出しのちょうど前の外側のスコープで、所望の金額のために小切手を作成する ether コントラクトを呼び出す。
  2. 内側のスコープで、コントラクトが msg.value の opcode を、関数が呼び出される任意の場所で使用する。そして、関数呼び出しの最初にコントラクトに小切手を現金化させ、標準化されたアドレス上で現金化された金額をメモリ上に格納する。

原理

これによって、大きく一般性が上昇する。特にある分野において:

  1. アカウントをセキュアにするために使われる暗号のアルゴリズム(完全に自由に Lamport 署名でアカウントをセキュアにすることで、Ethereum が量子安全であるということを十分に言うことが出来る)。ナンスを上昇させる手法は、今やアカウントの所有者の一部にもオープンに見直され、k-paralleizaable nonce techniques や UTXO のスキーマなどを実験することができる。
  2. ehter と sub-token がコントラクトが同様に扱えるという特別なの利益と同時に、ether の抽象レベルを引き上げること。
  3. マルチシグといったカスタムポリシーをもつアカウントのために必要な間接レベルを削減すること。

これは、根底の Ethereum のプロトコルをかなり単純化して綺麗にし、最小のコンセンサス実装の複雑度を削減する。

実装

近日公開

EIP-100 - Change difficulty adjustment to target mean block time including uncles

  • 著者:Vitalik Buterin
  • タイプ:Standards Track
  • カテゴリ:Core
  • ステータス:Final
  • 作成日:2016-04-28

仕様

現在では、ブロックの難易度計算の公式は以下のロジックを含む:

adj_factor = max(1 - ((timestamp - parent.timestamp) // 10), -99)
child_diff = int(max(parent.difficulty + (parent.difficulty // BLOCK\_DIFF\_FACTOR) * adj_factor, min(parent.difficulty, MIN_DIFF)))
...

block.number >= BYZANTIUM_FORK_BLKNUM で、この1行目を次に変更する:

adj_factor = max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99)

原理

この新しい公式によって、難易度調整アルゴリズムが uncle ブロックを含む生成されたブロックを加味した一定な平均レートを目指すようになることが保証される。uncle ブロックの割合を操作することで上方にコントロールされない、かなり予測可能な発行レートを保証する:

adj_factor = max(1 + len(parent.uncles) - ((timestamp - parent.timestamp) // 9), -99)

k 個の uncle ブロックを持つブロックと、ちょうど同じタイムスタンプを持つ k+1 個のブロック列が等価であると見なすことと、上式が(~3/4194304 の許容範囲で)数学的に同じであることはかなり容易にわかる。これは恐らく望ましい結果を実現するのに最も簡単な方法だろう。しかし、正確な公式はヘッダだけではなくブロック全体に依存しているため、私達は代わりにほぼ同等の効果を実現するがブロックヘッダのみに依存する利点を持つ近似式を使用している(空のハッシュに対して uncle ブロックのハッシュを確認できる)。

分母を 10 から 9 に変更することでブロック生成の時間間隔はおおよそ変わらないことを保証している (実際、現在の 7% の uncle ブロックの割合の場合に ~3%程度減少するはずである)。

リファレンス

EIP-55 - Mixed-case checksum address encoding

  • 著者:Vitalik Buterin
  • タイプ:Standards Track
  • カテゴリ:ERC
  • ステータス:Final
  • 作成日:2016-01-14

仕様

コード:

from ethereum import utils

def checksum_encode(addr): # 入力として 20-byte のバイナリのアドレスを受け取る
  o = ''
  v = utils.big_endian_to_int(utils.sha3(addr.hex()))
  for i, c in enumerate(addr.hex())
    if c in '0123456789':
      o += c
    else:
      o += c.upper() if (v & (2**(255 - 4*i))) else c.lower()
  return '0x'+o

def test(addrstr):
  assert(addrstr == checksum_encode(bytes.fromhex(addrstr[2:])))

test('アドレス(略)')
...

英語で、アドレスを 16 進数表現に変換する。しかし i 番目の数字が文字のとき(即ち abcdef のうちの一つである)、もし lowercase の 16 進数アドレスのハッシュの 4*i 番目のビットが 1 なら、uppercase で出力され、そうでなければ lowercase で出力される。

原理

利益:

  • ミックスしたケースを受理できる沢山の 16 進数パーサとの後方互換性があり、時間を超えて簡単に導入可能である。
  • 長さは 40 文字に保持される。
  • 平均してアドレスあたり 15 回のチェックがある。ミスタイピングでランダムに生成されるアドレスがチェックをすり抜ける純粋な確率は 0.0247% である。これは ICAP の ~50倍 の改善であるが、4 バイトのチェックコードほど良くはない。

実装

JSによる実装 (略)

Keccak256 への入力は lowercase の 16 進数文字列になる(即ち、ASCII でエンコードされた 16 進数になる)。


(テストケース・対応状況・リファレンスを省略)

EIP-20 - ERC-20 Token Standard

  • 著者:Fabian Vogelsteller
  • タイプ:Standards Track
  • カテゴリ:ERC
  • ステータス:Final
  • 作成日:2015-11-19

簡潔な要約

トークンのための標準インターフェース

要約

以下の標準で、スマートコントラクト内のトークンのための標準 API の実装を可能にする。この標準はトークン移転のための基本的な機能を提供する。トークンが承認され、別の on-chain のサードパーティによって使われることも同様に可能にする。

動機

他のアプリケーション(ウォレットや分散取引所)によって Ethereum 上の任意のトークンを再利用できる標準インターフェースの需要による。

仕様

トーク

メソッド

注釈

  • 以下の仕様は Solidity 0.4.17 (以上) のシンタックスを用いている。
  • 呼び出し元は returns (bool success) から、必ず false が返る場合の処理をしなければならない。呼び出し元は、戻り値が false にならないという仮定を置いてはならない!

name

トークンの名前を返す - 例: "MyToken".

オプション:このメソッドはユーザビリティを向上させることが出来る。しかし、インターフェースや他のコントラクトは、この値が存在するという仮定をおいてはならない。

function name() public view returns (string)

symbol

トークンのシンボルを返す - 例: "HIX".

オプション:このメソッドはユーザビリティを向上させることが出来る。しかし、インターフェースや他のコントラクトは、この値が存在するという仮定をおいてはならない。

function symbol() public view returns (string)

decimals

トークンが使用している小数点数を返す。例えば、8であれば、100000000で割った値がユーザに認識できるトークンの形式になる。

オプション:このメソッドはユーザビリティを向上させることが出来る。しかし、インターフェースや他のコントラクトは、この値が存在するという仮定をおいてはならない。

function decimals() public view returns (uint8)

totalSupply

合計の token supply を返す。

function totalSupply() public view returns (uint256)

balanceOf

_ownerアドレスを持つ別のアカウントの残高を返す。

function balanceOf(address _owner) public view returns (uint256 balance)

transfer

_valueトークンを _to のアドレスに送金する。また Transfer イベントを実行しなければならない。もし _from のアカウント残高が支払うのに十分なトークンを持っていなかった場合、関数は throw を投げなければならない。

注:0 の値の送金は通常の送金として取り扱われ、Transfer イベントが実行されなければならない。

function transfer(address _to, uint256 _value) public returns (bool success)

transferFrom

_valueトークンをアドレス _from からアドレス _to に送金する。また Transfer イベントを実行しなければならない。

transferFrom メソッドは引き出しのワークフローとして使われ、あなたに代わってコントラクトにトークンを送金させる。これは、例えばあなたに代わってコントラクトにこーくんを送金させたり、sub-currencies で手数料を支払うといったことに使うことが出来る。この関数は、_from アカウントが何らかのメカニズムを通してメッセージの送信者を故意に認可することなしで throw をしなければならない。

注:0 の値の送金は通常の送金として取り扱われ、Transfer イベントが実行されなければならない。

function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)

approve

あなたのアカウントから _spender に、最大 _value の量まで、複数回の引き出しを許可する。もしこの関数が再び呼ばれたら、現在の費用を _value で上書きする。

注:ある人がここで書いていたり、ここで議論しているような攻撃ベクターを抑制するために、クライアントは同じ費用負担者(?)のために別の値にセットする前に、費用をはじめ 0 にセットする。今までにデプロイしていたコントラクトとの後方互換性のために、コントラクト自体がそれを強制すべきではない。

function approve(address _spender, uint256 _value) public returns (bool success)

allowance

_spender が、まだ _owner からの引き出しを許可されている金額を返す。

function allowance(address _owner, address _spender) public view returns (uint256 remaining)

Events

Transfer

トークン(値が 0 であるものを含む)が送金された時にトリガーしなければならない。

新規のトークンを作るトークコントラクトは、トークンが作成された時に _from アドレスを 0x0 にセットして、Transfer イベントをトリガーする必要がある。

event Transfer(address indexed \_from, address indexed \_to, uint256 _value)

Approval

approve(address _spender, uint256 _value) への任意の成功の呼び出しに対してトリガーしなければならない。

event Approval(address indexed \_owner, address indexed \_spender, uint256 _value)

実装

すでにたくさんの ERC20-準拠のトークンが Ethereum ネットワーク上にデプロイされている。様々なチームによって、異なるトレードオフ(gas の仕様を抑えるものからセキュリティを向上させるものまで)を持つ異なる実装が記述されてきた。

実装例は以下で入手可能

歴史

この標準に関連した歴史的なリンク:

EIP-8 - devp2p Forward Compatibility Requirements for Homestead

  • 作成日:2015-12-18
  • カテゴリ:Networking
  • タイプ:Standards Track
  • ステータス:Final

要約

このEIPは、devp2p Wire Protocol, RLPx Discovery Protocol と RLPx TCP Transport Protocol の実装のための新しい前方互換性の要件を導入する。EIP-8を実装するクライアントは、ポステルの法則(訳注:通信における設計原則)に従って振る舞う:

送信するものに関しては厳密に、受信するものに関しては寛容に

仕様

devp2p Wire Protocol の実装は、HELLOパケット(訳注:自分自身が生きていることを示すために送信するパケット)のバージョン番号を無視しなければならない。HELLOパケットの送信時に、サポートされているバージョン要素が最も高い devp2p バージョンにセットされるべきである。実装は、HELLOパケットの末尾で追加のリスト要素もまた無視しなければならない。

同様に、RLPx Discovery Protocol の実装は、ping パケットのバージョン番号の検証、パケットにおいて追加のリスト要素を無視、また、パケット内の最初のRLPの値の後にある任意のデータを無視、をしてはならない。不明なパケットのタイプをもつ Discovery Packet はサイレントに捨てられる。Discovery Packet の最大サイズは依然として 1280 バイトになる。

最後に、RLPx TCP Transport Protocol の実装は、暗号キー確立のハンドシェイクパケットのための新しいエンコーディングを受け入れるべきである。EIP-8 形式の RLPx auth-packet を受け取ったら、返答の ack-packet は以下のルールを使って送信されるべきである。

auth-bodyack-body の中にある RLP データをデコードすることは auth-vsnack-vsn のミスマッチ、追加の任意のリスト要素とリストの後の任意の trailing データを無視するだろう。移行期間の間は(即ち、古いフォーマットが役割を終えるまで)、実装は少なくとも 100 byte のジャンクデータを auth-body に詰める必要がある。パケットのサイズをバラけさせるために [100, 300] の範囲のランダムな量のデータを詰めることが推奨されている。

    auth-vsn         = 4
    auth-size        = big-endian 16-bit integer でエンコードされた enc-auth-body のサイズ
    auth-body        = rlp.list(sig, initiator-pubk, initiator-nonce, auth-vsn)
    enc-auth-body    = ecies.encrypt(recipient-pubk, auth-body, auth-size)
    auth-packet      = auth-size || enc-auth-body
    
    ack-vsn          = 4
    ack-size         = big-endian 16-bit integer でエンコードされた enc-ack-body のサイズ
    ack-body         = rlp.list(recipient-ephemeral-pubk, recipient-nonce, ack-vsn)
    enc-ack-body     = ecies.encrypt(initiator-pubk, ack-body, ack-size)
    ack-packet       = ack-size || enc-ack-body
    
    ここで
    
    X || Y
        X と Y の結合を示す
    X[:N]
        X の N-byte のプリフィックスを示す
    rlp.list(X, Y, Z, ...)
        [X, Y, Z, ...] を RLP のリストとして再帰的にエンコードされたものを示す。
    sha3(MESSAGE)
        Ethereum で使用されている Keccak256 のハッシュ関数
    ecies.encrypt(PUBKEY, MESSAGE, AUTHDATA)
        RLPx によって使用されている非対称の認証付き暗号関数
        AUTHDATA 出力の暗号文の一部ではないが、メッセージタグを生成する前に HMAC-256 に書かれた認証済みデータ

動機

devp2p protocol への変更は、古いバージョンを動かしているクライアントが、バージョン番号やHELLO (pingの通信, RLPxハンドシェイク) パケットの構造がローカルで期待しているものとマッチしない場合に通信を拒否するため、デプロイが困難である。

前方互換性の要件を Homestead コンセンサスのアップグレードの一部として導入することで、Ethereum ネットワーク上で使われているすべてのクライアントのソフトウェアが、将来のネットワークプロトコルのアップグレードに(後方互換性がメンテナンスされる限り)対処出来ることが保証される。

原理

提案された変更はポステルの法則(プロトコルスタックに通じている堅牢性原則としても知られる)を適用することで前方互換性に対処する。この手法のメリットと適用可能性はRFP761のオリジナルの適用から繰り返し研究されてきた。最近の見解については、The Robustness Principle Reconsidered (Eric Allman, 2011)を参照せよ。

devp2p Wire Protocol への変更

すべてのクライアントは現在のところ以下のようなステートメントを含む。

# pydevp2p/p2p_protocol.py
if data\['version'\] != proto.version:
    log.debug('incompatible network protocols', peer=proto.peer,
        expected=proto.version, received=data\['version'\])
    return proto.send_disconnect(reason=reasons.incompatibel\_p2p\_version)

これらのチェックによって、HELLOパケットのバージョンや構造を変更することが出来なくなる。これらをやめることで新規のプロトコルバージョンに移行することが出来る:新規のバージョンを実装しているクライアントが、より高いバージョンと追加されているかもしれないリストの要素とを添えたパケットを単純に送信する。

  • そのようなパケットが低いバージョンのノードによって受信されたら、盲目的に、リモートの端点は後方互換だとみなして古いハンドシェイクを元に返答するだろう。
  • パケットが同一のバージョンのノードによって受信されたら、プロトコルの新機能が使われるだろう。
  • パケットがより高いバージョンのノードによって受信されたら、後方互換のロジックを有効にし、接続を落とすことが出来る。

RLPx Discovery Protocol への変更

ルールをデコードしているDiscovery Packet の緩和は、現在の実用を大部分体系化する。最も多い既存の実装は、リストの要素数について気にせず(例外はgo-ethereum)、バージョンのミスマッチを起こすノードをリジェクトしない。しかしながらこの挙動は仕様によって保証されていない。

適用された場合、この変更によって devp2p の HELLOパケットの変更と同様の方法で、プロトコルの変更のデプロイが可能になる。より古いクライアントは、追加の要素を無視し、ネットワークのマジョリティが新しいプロトコルに移行した場合でさえも、処理を続行することが出来る。

RLPx TCP ハンドシェイクの変更

RLPx v5 の変更(チャンクされたパケット、鍵導出の変更)に対する論議はある程度弱まってきた。なぜなら、v4 ハンドシェイクエンコーディングが、バージョン番号を追加するための唯一の一方の帯域内の方向(\?)を提供するからである。即ち、ナンスのランダムな部分を短くする。たとえ RLPx v5 ハンドシェイクの提案が受理されたとしても、ハンドシェイクパケットは、既知のレイアウトで固定長のサイズである ECIES の暗号文であるので、将来のアップグレードは困難である。

私はハンドシェイクパケットに以下の変更を提案する:

  • 平文のヘッダとして暗号文の長さを追加する。
  • ハンドシェイクの body を RLP でエンコーディングする。
  • トークンフラグ(未使用)の代わりに、両方のパケットにバージョン番号を追加する。
  • 一時的な公開鍵のハッシュを取り除く(冗長である)

これらの変更のおかげで、他のプロトコルのために記述されているのと同様の方法、即ちリストの要素を追加し、バージョンを突き合わせることによって、RLPx TCP トランスポートプロトコルのアップグレードが可能になる。

これが RLPx ハンドシェイクパケットに対する最初の変更であるので、我々はすべての現在使われていないフィールドを取り除く機会を得ることが出来る。

RLP リストの後ろにデータを追加することが許可(実際には要求)されている。なぜなら、古いフォーマットと区別するために、ハンドシェイクパケットの拡張が必要だからである。クライアントは下記のような擬似コードで両方のフォーマットを同時に扱うためにロジックを実行する。

packet = read(307, connection)
if decrypt(packet) {
    // process as old format
} else {
    size = unpack\_16bit\_big_endian(packet)
    packet += read(size - 307 \+ 2, connection)
    if !decrypt(packet) {
        // error
    }
    // process as new format
}

平文サイズのプレフィックスは、このドキュメントにおいて最も議論の呼ぶ観点であるかもしれない。そのプレフィックスは、ネットワークレベルで RLPx 接続をフィルタリングしたり特定しようとする敵の助けになるという議論がされてきた。

どのくらい敵が出資したがるかというのは大きな問題である。長さのランダム化の推奨に従った場合、ピュアなパターンベースのパケット認識は成功しづらい。

  • 典型的なファイアウォールのオペレータにとって、最初の 2 byte が [300, 600] の範囲の整数値の形になるすべての接続をブロックすることは、侵略的すぎる可能性が高い。ポートベースのブロックは、RLPx のトラフィックをフィルタするのにより効果的な指標だろう。
  • 多くの基準を関連付ける余裕がある攻撃者にとって、インディケーターセットに追加するため、サイズのプレフィックスが認識を容易にする。しかしながら、そのような攻撃社もまた RLPx Discovery のトラフィックを読んだり、トラフィック参加することが期待されているだろう。これは、フォーマットが何であれ、RLPx TCP 接続をブロック出来るのに十分だろう。

後方互換

この EIP には後方互換性がある。すべての妥当なバージョン 4 のパケットは依然として受理される。

実装

go-ethereum libweb3core pydevp2p

テストベクター

devp2p Base Protocol

devp2p のHELLOパケットは version 22 を告知しており、少しの追加のリスト要素を含む:

(略)

RLPx Discovery Protocol

実装は以下のエンコードされた Discovery Packet が妥当であるとして受理する必要がある。パケットは secp256k1 ノード鍵を用いて署名される。

(略)

version 4 の ping パケットについて、追加のリスト要素:

(略)

version 555 の ping パケットについて、追加のリスト要素と追加のランダムデータ:

(略)

pong パケット、追加のリスト要素と追加のランダムデータ。

(略)

findnode パケット、追加のリスト要素と追加のランダムデータ

(略)

neighbours パケット、追加のリスト要素と追加のランダムデータ

RLPx ハンドシェイク

このテストベクターにおいて、ノード A はノード B と接続を開始する。すべてのパケットに含まれる値は以下で与えられる:

Static(無期) Key A: (略)
Static(無期) Key B: (略)
Ephermeral(一時的な) Key A: (略)
Ephermeral(一時的な) Key A: (略)
Nonce A: (略)
Nonce B: (略)

(Auth_1) RLPx v4 のフォーマット (A から B へ送信):

(略)

(Auth_2) version 4 の EIP-8 のフォーマットで、追加のリスト要素は無い (A から B への送信):

(略)

(後略)

EIP-7 - DELEGATECALL

  • 作成: 2015-11-15
  • カテゴリ: Core
  • タイプ: Standards Track
  • ステータス: Final

ハードフォーク

Homestead (EIP-606)

パラメータ

  • アクティベーション
    • Block >= 1,150,000 : メインネット
    • Block >= 494,000 : モダン
    • Block >= 0 : 未来のテストネット

概要

新規の opcode である DELEGATECALL0xf4 に追加する。送信者と値を、親のスコープから子のスコープへ伝播させることを除いて、アイデア上で CALLLCODE に似ている。即ち、作成された呼び出しは、元の呼び出しと同一の送信者と値を持つ。

仕様

DELEGATECALL: 0xf4 は 6 つの operand を取る。

  • gas : コードが実行に使用する gas の量
  • to : 実行されるコードの送信先アドレス
  • in_offset : 入力のメモリに入るオフセット
  • in_size : バイトでの入力のサイズ
  • out_offset : 出力のメモリに入るオフセット
  • out_size : 出力のためのスクラッチパッドのサイズ

gas に関する注釈

  • 基本的な報酬は与えられない。gas は呼び出された側が受け取った総量になる。
  • CALLCODE のように、アカウント生成は決して起こらない。なので、前払いの gas のコストは常に schedule.callGas+gas になる。
  • 未使用の gas は通常通り払い戻される。

送信者に関する注釈

  • CALLERVALUE は、呼び出された側の環境で、ちょうど呼び出し元の環境のように振る舞う。

他の注釈

  • 1024 の深さの上限は今まで通り保持される。

原理

送信者と値を親のスコープから子のスコープへ伝播することは、子スコープのコードが(少ない gas であるのとコールスタックの深さが上昇していることを除いて)親スコープのコードと本質的に同じ環境で実行されるので、コントラクトが別のアドレスをコードの変更可能なソースとして保存し、そのアドレスへの呼び出しを"通り抜ける"ことがはるかに容易になる。

ユースケース1: 約 3m の gas barrier を取得するためにコードを分割する。

~calldatacopy(0, 0, ~calldatasize())
if ~calldataload(0) < 2**253:
    ~delegate_call(msg.gas - 10000, $ADDR1, 0, ~calldatasize(), ~calldatasize(), 10000)
    ~return(~calldatasize(), 10000)
elif ~calldataload(0) < 2**253 * 2:
    ~delegate_call(msg.gas - 10000, $ADDR2, 0, ~calldatasize(), ~calldatasize(), 10000)
    ~return(~calldatasize(), 10000)
...

ユースケース2: コントラクトのコードを保存するための変更可能なアドレス

if ~calldataload(0) / 2**224 == 0x12345678 and self.owner == msg.sender:
    self.delegate = ~calldataload(4)
else:
    ~delegate_call(msg.gas - 10000, self.delegate, 0, ~calldatasize(), ~calldatasize(), 10000)
    ~return(~calldatasize(), 10000)

これらのメソッドに呼ばれる子スコープの関数は、今や自由にmsg.sendermsg.valueを参照することが出来る。

考えられる議論

  • 送信者をコールデータの最初の20バイトに固定するだけで、この機能を複製出来る。しかしながら、これはコードがデリゲートされたコントラクトのために特別にコンパイルされる必要があり、デリゲートされた生のコンテキストでは同時に使えないことを意味する。