次世代電子契約及び分散型応用ソフトウェア基盤#02

01

歴史
 分散型の電子貨幣の概念は、不動産登記簿等の別の応用と同様に、数十年間考えられてきたものである。1980年代及び1990年代の匿名電子貨幣手続きは大部分をChaumの不視化(Chaumian blinding)として知られている基本暗号要素(cryptographic primitive)に依存しており、高度な私事権確保性能を有する電子貨幣を提供したが、集中型の仲介者に依存していたため概して大きな支持を得ることはなかった。1998年、Wei Daiのb-moneyがパズルを解く計算及び分散型の合意を通して貨幣を作ろうという発想を導入した最初の提案となった。しかしながら、この提案には実際に分散型の合意を実現する方法の詳細に関して不備があった。2005年、Hal Finneyは「再使用可能な仕事証明(proof of work)」という概念を導入した。これはb-moneyの発想とAdam Backの計算の困難なHashcashパズルを組み合わせ、暗号貨幣(cryptocurrency)という概念を形作った仕組みであったが、これも又、背部で信用のある計算に依存していたため理想にまでは届かない不十分なものであった。
 貨幣は先願制度を応用したものであり、即ち、多くの場合において取引の順序が極めて重要であるから、分散型の貨幣を実現するには分散型の合意形成を可能にする方法が必要となる。長年に亘ってByzantine耐故障(Byzantine-fault-tolerant)の多数人合意形成方式に関する十分な研究がされてきたにも拘らず、提案されていた全ての手続きは問題の半分しか解決していなかった。Bitcoin以前の全ての貨幣手続きはこの問題に直面し、これを解決することができなかった。これらの手続きは全ての参加者が既知であるということを前提としており、「もしN人が参加しており、悪意のある人間がN/4人より少ないのならば、攻撃があっても耐えることができる」という形での安全性の余地を提供する。しかしながら、問題なのは匿名環境においてはこのような安全性の余地はSybil攻撃(Sybil attack)に対して脆弱であるということであり、即ち、単一の攻撃者であってもサーバ或いはボットネット上に数千の擬似的なノードを作成することができ、ネットワークを一方的に占有するためにこれらのノードを使用することができる。
 Satoshiが齎した革新的な発想は、非常に単純な分散型の合意形成手続きを繋ぎ合わせるというものである。ノードは仕事証明という機構を通して手続きに参加する権利を獲得し、10分毎に取引を組み込んだブロックを生成し、永遠に成長するブロック鎖を作っていく。多くの計算能力を有するノードはそれに比例する影響力を有するが、ネットワーク全体の計算能力を合わせたものよりも多くの計算能力を用意するのは100万個の擬似的なノードを用意するよりも遥かに困難である。Bitcoinのブロック鎖は粗雑であり、単純であるものの、目的の達成のためにはそれで十分であるということが分かってきており、これから5年を掛けて世界中の200を超える通貨や手続きの基盤となることだろう。


状態遷移系(state transition system)としてのBitcoin
 Bitcoinの元帳は技術的な観点から見ると状態遷移系と見做すことができる。即ち、既存の全てのBitcoinに対する、所有者であるという地位から構成される「状態」と、現在の状態及び取引を引数として取り、結果たる新しい状態を返す「状態遷移関数(state transition function)」から成る。例えば、標準的な銀行系においては、状態は貸借対照表であり、取引は口座Aから口座BへのXドルの移動要求であり、状態遷移関数は口座AからXドルを控除し、口座BにXドルを付加する。当初から口座Aの残高がXドル未満であった場合には、状態遷移関数は失敗する。従って、状態遷移関数は次のように形式的に定義することができる。

APPLY(S,TX) -> S' or ERROR

 上に示した銀行系の例の場合は次のようになる。

APPLY({ Alice: $50, Bob: $50 },"send $20 from Alice to Bob") = { Alice: $30, Bob: $70 }

 Bitcoinにおける「状態」は(技術的には「未使用の取引出力(unspent transaction outputs:UTXO)」と言われる)鋳造されたけれども未使用の、全てのBitcoinの集まりである。未使用の取引出力は夫々が額面価格と所有者を有する(所有者は20バイトの口座番号(address)によって表される。これは公開鍵を素にしたものである)。取引には1つ以上の取引入力(transaction input)が含まれ、更には、1つ以上の取引出力(transaction output)が含まれる。取引入力の夫々には既存の未使用の取引出力への参照と、所有者を表す口座番号に対応する秘密鍵による署名が含まれる。取引出力の夫々は新しい状態の一部として追加されることになる新しい未使用の取引出力である。
 状態遷移関数APPLY(S,TX) -> S'は大まかには次のように定義することができる。
  1.TXの取引入力の夫々について
    ・参照している取引出力がSに含まれない場合には、ERRORを返す。
    ・署名が参照している取引出力の所有者によるものでない場合には、ERRORを返す。
  2.TXの全ての取引入力の額面価格の合計がTXの全ての取引出力の額面価格の合計より小さい場合には、ERRORを返す。
  3.SからTXの取引入力が参照している全ての取引出力を取り除き、更にTXの全ての取引出力を追加したものを返す。
 1の1つ目の処理は支払い人が使用済みの取引出力を使用することができないようにし、2つ目の処理は他人の所有する取引出力を使用することができないようにする。2の処理は取引の前後で存在する額面価格が変わらないようにする。この手続きは決済の際には次のように使用される。AliceはBobに11.7BTC送金したいものとすると、Aliceは先ず自己の所有する未使用の取引出力の中から合計すると11.7BTC以上となるようなものの集まりを探し出す。現実的には合計すると丁度11.7BTCとなるようなものはないだろうから、例えば、最も11.7BTCに近いものが6BTC+4BTC+2BTC=12BTCであったとすると、Aliceは次にこれら3つを参照する取引入力と2つの取引出力から成る取引を作成する。1つ目の取引出力の額面価格は11.7BTCであり、口座番号はBobのものとなる。2つ目の取引出力は差し引き0.3BTCの「釣り銭」となり、所有者はAlice自身となる。

独自暗号貨幣を作ろう 第9回

第1回 第2回 第3回 第4回 第5回 第6回 第7回 第8回


<微修正1>
これまで、額面価格を保持する型はulong型としてきましたが、将来的に自動会計処理等を実行できるようにしようと思うと、負の額面価格も扱えた方が都合が良いので、long型に変更することにします。

CurrencyUnitクラス、Creacoinクラス、Yuminaクラス及びTransactionOutputクラスのulongを全てlongに変更します。それ程大きな変更ではないので、コードは示しません。


<取引入力への署名>
取引入力への署名を実装します。取引入力への署名は取引入力のみで独立して実行できるものではなく、取引入力を格納している取引(の全ての取引入力と取引出力)やその取引の全ての取引入力が参照している全ての取引出力も必要になります。そのため、取引入力への署名はTransactionInputクラスではなく、TransferTransactionクラスで行うものとします(新しい貨幣を生成する取引には1つも取引入力が存在せず、1つ以上の取引入力が格納される取引は(少なくとも現時点では)貨幣を移動する取引のみであることに注意)。

Bitcoinの場合、取引入力への署名は下図のように行われます。
f:id:pizyumina:20140617022816p:plain
CREACOINも同様に署名を行うことにします。CREACOINの場合、上図のTXID及びOutputIndexNumberはTransactionInputクラスのprevTxHash及びprevTxOutputIndexです。TX1のScriptはprevTxHash及びprevTxOutputIndexによって参照されるTransactionOutputクラスのreceiverPubKeyHashです。TX2 templateのScript及びAmountはTransactionOutputクラスのreceiverPubKeyHash及びamountです。

TransferTransactionクラスは1つ以上の取引入力と1つ以上の取引出力を有しており、ある取引入力の署名を作成するに際して、どの取引入力と取引出力を署名対象として用いるかは様々考えられますが、取り敢えずは、全ての取引入力の署名のために全ての取引入力と全ての取引出力を署名対象として用いることにします(将来的に別の方法を追加するかもしれません)。

先ず、署名を行うデータを作成する必要があります。取引入力と取引出力の上述の項目(を結合したもの)に署名するので、それらを直列化するための情報をTransactionInputクラス及びTransactionOutputクラスに追加します。
TransactionInputクラスのStreamInfoToSignプロパティが上図のTXID及びOutputIndexNumberに対応し、TransactionOutputクラスのStreamInfoToSignPrevプロパティがTX1のScriptに対応し、StreamInfoToSignプロパティがTX2 templateのScript及びAmountに対応します。

public class TransactionInput<TxidHashType, PubKeyType> : SHAREDDATA
    where TxidHashType : HASHBASE
    where PubKeyType : DSAPUBKEYBASE
{
(中略)
    public Func<ReaderWriter, IEnumerable<MainDataInfomation>> StreamInfoToSign
    {
        get
        {
            return (msrw) => new MainDataInfomation[]{
                new MainDataInfomation(typeof(TxidHashType), null, () => prevTxHash, (o) => { throw new NotSupportedException("tx_in_si_to_sign"); }),
                new MainDataInfomation(typeof(int), () => prevTxOutputIndex, (o) => { throw new NotSupportedException("tx_in_si_to_sign"); }),
            };
        }
    }
(中略)
}
 
public class TransactionOutput<PubKeyHashType> : SHAREDDATA where PubKeyHashType : HASHBASE
{
(中略)
    public Func<ReaderWriter, IEnumerable<MainDataInfomation>> StreamInfoToSign
    {
        get
        {
            return (msrw) => new MainDataInfomation[]{
                new MainDataInfomation(typeof(PubKeyHashType), null, () => receiverPubKeyHash, (o) => { throw new NotSupportedException("tx_out_si_to_sign"); }),
                new MainDataInfomation(typeof(long), () => amount.rawAmount, (o) => { throw new NotSupportedException("tx_out_si_to_sign"); }),
            };
        }
    }
 
    public Func<ReaderWriter, IEnumerable<MainDataInfomation>> StreamInfoToSignPrev
    {
        get
        {
            return (msrw) => new MainDataInfomation[]{
                new MainDataInfomation(typeof(PubKeyHashType), null, () => receiverPubKeyHash, (o) => { throw new NotSupportedException("tx_out_si_to_sign_prev"); }),
            };
        }
    }
}


TransferTransactionクラスに上記のデータを纏め上げるためのStreamInfoToSignメソッド(とStreamInfoToSignInnerメソッド)を追加します。全ての取引入力と全ての取引出力を結合しなければならないことに注意してください。
更に署名対象のバイト配列を返すGetBytesToSignメソッドを追加します。このメソッドの内部では基底クラス(SHAREDDATA抽象クラス)で実装されているToBinaryMainDataメソッドにStreamInfoToSignメソッドの戻り値を渡して署名対象のバイト配列を生成しています。
これら2つのメソッドは全ての取引入力が参照している全ての取引出力を引数として取ることに注意してください。どのようにして取引入力が参照している取引出力を取得するかは別に考えなければなりません。又、引数として渡された夫々の取引出力が本当に対応する夫々の取引入力によって参照されているか検証は行っていません。あくまでも、夫々の取引入力が参照している正しい取引出力が夫々引数として渡されることを前提としています。

最後に、実際に署名を実行するSignメソッドを追加します。GetBytesToSignメソッドを使って署名対象のバイト配列を取得し、全ての取引入力に対して署名を実行し、SetSenderSigメソッドを使って適切な場所に格納します。このメソッドは全ての取引入力が参照している全ての取引出力を引数として取るだけではなく、全ての取引入力の公開鍵に対応する全ての秘密鍵も引数として取ることに注意してください。上の2つのメソッドと同様に、引数として渡された夫々の取引出力や秘密鍵は正しいものであることを前提としています。

public class TransferTransaction<TxidHashType, PubKeyHashType, PubKeyType> : Transaction<TxidHashType, PubKeyHashType>
    where TxidHashType : HASHBASE
    where PubKeyHashType : HASHBASE
    where PubKeyType : DSAPUBKEYBASE
{
(中略)
    private Func<ReaderWriter, IEnumerable<MainDataInfomation>> StreamInfoToSign(TransactionOutput<PubKeyHashType>[] prevTxOutputs) { return (msrw) => StreamInfoToSignInner(prevTxOutputs); }
    private IEnumerable<MainDataInfomation> StreamInfoToSignInner(TransactionOutput<PubKeyHashType>[] prevTxOutputs)
    {
        if (Version == 0)
        {
            for (int i = 0; i < inputs.Length; i++)
            {
                foreach (var mdi in inputs[i].StreamInfoToSign(null))
                    yield return mdi;
                foreach (var mdi in prevTxOutputs[i].StreamInfoToSignPrev(null))
                    yield return mdi;
            }
            for (int i = 0; i < outputs.Length; i++)
                foreach (var mdi in outputs[i].StreamInfoToSign(null))
                    yield return mdi;
        }
        else
            throw new NotSupportedException("transfer_tx_mdi_sign");
    }
 
    public byte[] GetBytesToSign(TransactionOutput<PubKeyHashType>[] prevTxOutputs)
    {
        if (prevTxOutputs.Length != inputs.Length)
            throw new ArgumentException("inputs_and_prev_outputs");
 
        return ToBinaryMainData(StreamInfoToSign(prevTxOutputs));
    }
 
    public void Sign(TransactionOutput<PubKeyHashType>[] prevTxOutputs, DSAPRIVKEYBASE[] privKeys)
    {
        if (prevTxOutputs.Length != inputs.Length)
            throw new ArgumentException("inputs_and_prev_outputs");
        if (privKeys.Length != inputs.Length)
            throw new ArgumentException("inputs_and_priv_keys");
 
        byte[] bytesToSign = GetBytesToSign(prevTxOutputs);
 
        for (int i = 0; i < inputs.Length; i++)
            inputs[i].SetSenderSig(privKeys[i].Sign(bytesToSign));
    }
}

独自暗号貨幣を作ろう 第8回

第1回 第2回 第3回 第4回 第5回 第6回 第7回


電子署名
電子署名の枠組みを実装します。
Bitcoinと同様に楕円曲線DSAを使用します。但し、secp256k1ではなく、取り敢えずは普通の(.NET Frameworkが提供している)楕円曲線DSAにします。

普通の楕円曲線DSAによる署名と署名検証を実行する関数を拡張メソッドとして実装します。署名で用いる暗号学的要約関数はSHA-256とします。

public static byte[] SignEcdsaSha256(this byte[] data, byte[] privKey)
{
    using (ECDsaCng dsa = new ECDsaCng(CngKey.Import(privKey, CngKeyBlobFormat.EccPrivateBlob)))
    {
        dsa.HashAlgorithm = CngAlgorithm.Sha256;
 
        return dsa.SignData(data);
    }
}
 
public static bool VerifyEcdsa(this byte[] data, byte[] signature, byte[] pubKey)
{
    using (ECDsaCng dsa = new ECDsaCng(CngKey.Import(pubKey, CngKeyBlobFormat.EccPublicBlob)))
        return dsa.VerifyData(data, signature);
}


後からsecp256k1に差し替えることができるように、鍵対を保持し、署名と署名検証を行うための抽象クラスを作成します。公開鍵と秘密鍵は別々のクラスで保持することにします。

public abstract class DSAPUBKEYBASE : SHAREDDATA
{
    public DSAPUBKEYBASE() : base(null) { }
 
    public DSAPUBKEYBASE(byte[] _pubKey) : base(null) { pubKey = _pubKey; }
 
    public byte[] pubKey { get; private set; }
 
    public abstract bool Verify(byte[] data, byte[] signature);
 
    protected override Func<ReaderWriter, IEnumerable<MainDataInfomation>> StreamInfo
    {
        get
        {
            return (msrw) => new MainDataInfomation[]{
                new MainDataInfomation(typeof(byte[]), null, () => pubKey, (o) => pubKey = (byte[])o),
            };
        }
    }
    public Func<ReaderWriter, IEnumerable<MainDataInfomation>> PublicStreamInfo { get { return StreamInfo; } }
}
 
public abstract class DSAPRIVKEYBASE : SHAREDDATA
{
    public DSAPRIVKEYBASE() : base(null) { }
 
    public DSAPRIVKEYBASE(byte[] _privKey) : base(null) { privKey = _privKey; }
 
    public byte[] privKey { get; private set; }
 
    public abstract byte[] Sign(byte[] data);
 
    protected override Func<ReaderWriter, IEnumerable<MainDataInfomation>> StreamInfo
    {
        get
        {
            return (msrw) => new MainDataInfomation[]{
                new MainDataInfomation(typeof(byte[]), null, () => privKey, (o) => privKey = (byte[])o),
            };
        }
    }
    public Func<ReaderWriter, IEnumerable<MainDataInfomation>> PublicStreamInfo { get { return StreamInfo; } }
}
 
public abstract class DSAKEYPAIRBASE<DsaPubKeyType, DsaPrivKeyType> : SHAREDDATA
    where DsaPubKeyType : DSAPUBKEYBASE
    where DsaPrivKeyType : DSAPRIVKEYBASE
{
    public DSAKEYPAIRBASE() : base(null) { }
 
    public DsaPubKeyType pubKey { get; protected set; }
    public DsaPrivKeyType privKey { get; protected set; }
 
    protected override Func<ReaderWriter, IEnumerable<MainDataInfomation>> StreamInfo { get { return (msrw) => StreamInfoInner(msrw); } }
    private IEnumerable<MainDataInfomation> StreamInfoInner(ReaderWriter msrw)
    {
        if (pubKey == null)
            pubKey = Activator.CreateInstance(typeof(DsaPubKeyType)) as DsaPubKeyType;
        if (privKey == null)
            privKey = Activator.CreateInstance(typeof(DsaPrivKeyType)) as DsaPrivKeyType;
 
        foreach (var mdi in pubKey.PublicStreamInfo(null))
            yield return mdi;
        foreach (var mdi in privKey.PublicStreamInfo(null))
            yield return mdi;
    }
}


DSAPUBKEYBASE抽象クラスのVerify抽象メソッドで署名検証を行います。
DSAPRIVKEYBASE抽象クラスのSign抽象メソッドで署名を行います。

普通の楕円曲線DSAを実装します。

public class Ecdsa256PubKey : DSAPUBKEYBASE
{
    public Ecdsa256PubKey() : base() { }
 
    public Ecdsa256PubKey(byte[] _pubKey) : base(_pubKey) { }
 
    public override bool Verify(byte[] data, byte[] signature)
    {
        return data.VerifyEcdsa(signature, pubKey);
    }
}
 
public class Ecdsa256PrivKey : DSAPRIVKEYBASE
{
    public Ecdsa256PrivKey() : base() { }
 
    public Ecdsa256PrivKey(byte[] _privKey) : base(_privKey) { }
 
    public override byte[] Sign(byte[] data)
    {
        return data.SignEcdsaSha256(privKey);
    }
}
 
public class Ecdsa256KeyPair : DSAKEYPAIRBASE<Ecdsa256PubKey, Ecdsa256PrivKey>
{
    public Ecdsa256KeyPair() : this(false) { }
 
    public Ecdsa256KeyPair(bool isCreate)
    {
        if (isCreate)
        {
            CngKey ck = CngKey.Create(CngAlgorithm.ECDsaP256, null, new CngKeyCreationParameters { ExportPolicy = CngExportPolicies.AllowPlaintextExport });
 
            pubKey = new Ecdsa256PubKey(ck.Export(CngKeyBlobFormat.EccPublicBlob));
            privKey = new Ecdsa256PrivKey(ck.Export(CngKeyBlobFormat.EccPrivateBlob));
        }
    }
}

独自暗号貨幣を作ろう 第7回

第1回 第2回 第3回 第4回 第5回 第6回


<暗号学的要約関数>
暗号学的要約関数を実装します。

必要になるものを考えます。
 1.ノード識別子(ノード識別子はノード情報の暗号学的要約値)
 2.口座番号(口座番号は楕円曲線DSAの公開鍵又はその暗号学的要約値)
 3.取引識別子(取引識別子は取引の暗号学的要約値)
 4.ブロック識別子(ブロック識別子はブロックの暗号学的要約値)

ノード識別子はP2Pネットワーク上でノードを識別するための値です。これはSHA-256暗号学的要約値で良いでしょう。
口座番号は口座を識別するための値です。Bitcoinと同様にSHA-256暗号学的要約値のRIPEMD160暗号学的要約値とします。

暗号貨幣の実装のために使用できる仕事証明の方式としては、(PrimecoinやRiecoinで採用されている)素数を用いたものもありますが、CREACOINはBitcoinと同様の、暗号学的要約関数を用いるものを採用することにします。

ブロック識別子は暗号貨幣の安全性に係わってくるものであり、どの暗号学的要約関数を採用するかは非常に重要です。
又、ブロック識別子は仕事証明に係わってくるものでもあります。採掘者が有効な新しいブロックを作成するためには、ブロックの暗号学的要約値が目標値(target)以下となるような仕事証明の解(nonce)を発見しなければなりません。暗号学的要約関数は与えられた入力値に対して擬似無作為な出力値を返すので、実際に仕事証明の解を発見するには候補に対して虱潰しにブロックの暗号学的要約値が目標値以下となるか調べていかなければなりません。
即ち、採掘者は仕事証明の解(の候補)を変えながらブロックの暗号学的要約値を(通常は)途方もない回数計算する必要があり、どの暗号学的要約関数を採用するかは採掘の態様に密接に係わってくる要素の1つです。

そんな訳で、ブロック識別子の計算のためにどの暗号学的要約関数を採用するかに関しては様々な議論がある訳ですが、(議論の詳細な説明は省いて)CREACOINでは複数の異なる暗号学的要約関数を繋げて作った連鎖型の暗号学的要約関数を採用することにします。
この種の暗号学的要約関数の中で、暗号貨幣において利用されているものとしては、(Quarkで採用されている)Quark、(Darkcoinで採用されている)X11等が有名ですが、CREACOINではSHA-3公募の第2選考を通過した14種の暗号学的要約関数を繋げて作った連鎖型の暗号学的要約関数を採用したいと思います。これをX11に倣ってX14と呼ぶことにしましょう。

SHA-3公募の第2選考を通過した14種の暗号学的要約関数とは、次の14種です。
 1.BLAKE
 2.BlueMidnightWish
 3.Grøstl
 4.Skein
 5.JH
 6.Keccak
 7.Luffa
 8.CubeHash
 9.SHAvite
 10.SIMD
 11.ECHO
 12.Fugue
 13.Hamsi
 14.Shabal
これらの512ビット版を上記の順番で繋げ、最後に結果として生じる512ビットの暗号学的要約値の左256ビットのみを取り出す操作を加えたものをX14とします。

当初はX14を採用する予定でしたが、更にSHA-1を加えると2ちゃんねるのトリップ検索を行うことが可能です。暗号学的要約値を用いる仕事証明では、仕事証明の解を求める計算は取引の発生を確実にするという唯一の目的のために行われているものであり、ある意味無駄の多い浪費的な計算です。その計算を他の用途のためにも用いることができるというのは非常に貴重であって、価値のあることです。そこで、CREACOINはSHA-3公募の第2選考を通過した14種の暗号学的要約関数とSHA-1暗号学的要約関数を繋げて作った連鎖型の暗号学的要約関数を採用したいと思います。

取引識別子はSHA-256暗号学的要約値のSHA-256暗号学的要約値にしましょう。

それではコードを書きます。先ずは、夫々の暗号学的要約(値を計算する)関数を拡張メソッドとして実装します。
SHA-1とSHA-256とRIPEMD-160は.NET Frameworkの実装を利用し、SHA-3公募の第2選考を通過した14種の暗号学的要約関数はHashLibの実装を利用することにします。

private static HashAlgorithm haSha1 = null;
private static HashAlgorithm haSha256 = null;
private static HashAlgorithm haRipemd160 = null;
private static IHash haBlake512 = null;
private static IHash haBmw512 = null;
private static IHash haGroestl512 = null;
private static IHash haSkein512 = null;
private static IHash haJh512 = null;
private static IHash haKeccak512 = null;
private static IHash haLuffa512 = null;
private static IHash haCubehash512 = null;
private static IHash haShavite512 = null;
private static IHash haSimd512 = null;
private static IHash haEcho512 = null;
private static IHash haFugue512 = null;
private static IHash haHamsi512 = null;
private static IHash haShabal512 = null;

public static byte[] ComputeSha1(this byte[] bytes)
{
    if (haSha1 == null)
        haSha1 = new SHA1CryptoServiceProvider();
 
    return haSha1.ComputeHash(bytes);
}
 
public static byte[] ComputeSha256(this byte[] bytes)
{
    if (haSha256 == null)
        haSha256 = HashAlgorithm.Create("SHA-256");
 
    return haSha256.ComputeHash(bytes);
}
 
public static byte[] ComputeRipemd160(this byte[] bytes)
{
    if (haRipemd160 == null)
        haRipemd160 = HashAlgorithm.Create("RIPEMD-160");
 
    return haRipemd160.ComputeHash(bytes);
}
 
public static byte[] ComputeBlake512(this byte[] bytes)
{
    if (haBlake512 == null)
        haBlake512 = HashFactory.Crypto.SHA3.CreateBlake512();
 
    return haBlake512.ComputeBytes(bytes).GetBytes();
}
 
public static byte[] ComputeBmw512(this byte[] bytes)
{
    if (haBmw512 == null)
        haBmw512 = HashFactory.Crypto.SHA3.CreateBlueMidnightWish512();
 
    return haBmw512.ComputeBytes(bytes).GetBytes();
}
 
public static byte[] ComputeGroestl512(this byte[] bytes)
{
    if (haGroestl512 == null)
        haGroestl512 = HashFactory.Crypto.SHA3.CreateGroestl512();
 
    return haGroestl512.ComputeBytes(bytes).GetBytes();
}
 
public static byte[] ComputeSkein512(this byte[] bytes)
{
    if (haSkein512 == null)
        haSkein512 = HashFactory.Crypto.SHA3.CreateSkein512();
 
    return haSkein512.ComputeBytes(bytes).GetBytes();
}
 
public static byte[] ComputeJh512(this byte[] bytes)
{
    if (haJh512 == null)
        haJh512 = HashFactory.Crypto.SHA3.CreateJH512();
 
    return haJh512.ComputeBytes(bytes).GetBytes();
}
 
public static byte[] ComputeKeccak512(this byte[] bytes)
{
    if (haKeccak512 == null)
        haKeccak512 = HashFactory.Crypto.SHA3.CreateKeccak512();
 
    return haKeccak512.ComputeBytes(bytes).GetBytes();
}
 
public static byte[] ComputeLuffa512(this byte[] bytes)
{
    if (haLuffa512 == null)
        haLuffa512 = HashFactory.Crypto.SHA3.CreateLuffa512();
 
    return haLuffa512.ComputeBytes(bytes).GetBytes();
}
 
public static byte[] ComputeCubehash512(this byte[] bytes)
{
    if (haCubehash512 == null)
        haCubehash512 = HashFactory.Crypto.SHA3.CreateCubeHash512();
 
    return haCubehash512.ComputeBytes(bytes).GetBytes();
}
 
public static byte[] ComputeShavite512(this byte[] bytes)
{
    if (haShavite512 == null)
        haShavite512 = HashFactory.Crypto.SHA3.CreateSHAvite3_512();
 
    return haShavite512.ComputeBytes(bytes).GetBytes();
}
 
public static byte[] ComputeSimd512(this byte[] bytes)
{
    if (haSimd512 == null)
        haSimd512 = HashFactory.Crypto.SHA3.CreateSIMD512();
 
    return haSimd512.ComputeBytes(bytes).GetBytes();
}
 
public static byte[] ComputeEcho512(this byte[] bytes)
{
    if (haEcho512 == null)
        haEcho512 = HashFactory.Crypto.SHA3.CreateEcho512();
 
    return haEcho512.ComputeBytes(bytes).GetBytes();
}
 
public static byte[] ComputeFugue512(this byte[] bytes)
{
    if (haFugue512 == null)
        haFugue512 = HashFactory.Crypto.SHA3.CreateFugue512();
 
    return haFugue512.ComputeBytes(bytes).GetBytes();
}
 
public static byte[] ComputeHamsi512(this byte[] bytes)
{
    if (haHamsi512 == null)
        haHamsi512 = HashFactory.Crypto.SHA3.CreateHamsi512();
 
    return haHamsi512.ComputeBytes(bytes).GetBytes();
}
 
public static byte[] ComputeShabal512(this byte[] bytes)
{
    if (haShabal512 == null)
        haShabal512 = HashFactory.Crypto.SHA3.CreateShabal512();
 
    return haShabal512.ComputeBytes(bytes).GetBytes();
}


次に、暗号学的要約値を保持するSha256Hashクラス、Sha256Sha256Hashクラス、Sha256Ripemd160Hashクラス及びX14Hashクラスを作ります(X14Hashクラスは今のところ使用予定はありません)。

public class Sha256Hash : HASHBASE
{
    public Sha256Hash() : base() { }
 
    public Sha256Hash(string stringHash) : base(stringHash) { }
 
    public Sha256Hash(byte[] data) : base(data) { }
 
    public override int SizeBit { get { return 256; } }
 
    protected override byte[] ComputeHash(byte[] data) { return data.ComputeSha256(); }
}

public class Sha256Sha256Hash : HASHBASE
{
    public Sha256Sha256Hash() : base() { }
 
    public Sha256Sha256Hash(string stringHash) : base(stringHash) { }
 
    public Sha256Sha256Hash(byte[] data) : base(data) { }
 
    public override int SizeBit { get { return 256; } }
 
    protected override byte[] ComputeHash(byte[] data) { return data.ComputeSha256().ComputeSha256(); }
}
 
public class Sha256Ripemd160Hash : HASHBASE
{
    public Sha256Ripemd160Hash() : base() { }
 
    public Sha256Ripemd160Hash(string stringHash) : base(stringHash) { }
 
    public Sha256Ripemd160Hash(byte[] data) : base(data) { }
 
    public override int SizeBit { get { return 160; } }
 
    protected override byte[] ComputeHash(byte[] data) { return data.ComputeSha256().ComputeRipemd160(); }
}
 
public class X14Hash : HASHBASE
{
    public X14Hash() : base() { }
 
    public X14Hash(string stringHash) : base(stringHash) { }
 
    public X14Hash(byte[] data) : base(data) { }
 
    public override int SizeBit { get { return 256; } }
 
    protected override byte[] ComputeHash(byte[] data)
    {
        return data.ComputeBlake512().ComputeBmw512().ComputeGroestl512().ComputeSkein512().ComputeJh512().ComputeKeccak512().ComputeLuffa512().ComputeCubehash512().ComputeShavite512().ComputeSimd512().ComputeEcho512().ComputeFugue512().ComputeHamsi512().ComputeShabal512().Decompose(0, SizeByte);
    }
}


最後に、X15クラスを作ります。
SHA-1を連鎖の途中に入れると情報量が160ビットになってしまうので、計算方法を少し工夫しました。
f:id:pizyumina:20140611164028p:plain
tripKeyにトリップキーが格納され、tripにトリップが格納されます。

public class X15Hash : HASHBASE
{
    public X15Hash() : base() { }
 
    public X15Hash(string stringHash) : base(stringHash) { }
 
    public X15Hash(byte[] data) : base(data) { }
 
    public string tripKey { get; private set; }
    public string trip { get; private set; }
 
    public override int SizeBit { get { return 256; } }
 
    protected override byte[] ComputeHash(byte[] data)
    {
        tripKey = Convert.ToBase64String(data.ComputeSha1());
 
        byte[] sha1base64shiftjissha1 = Encoding.GetEncoding("Shift-JIS").GetBytes(tripKey).ComputeSha1();
 
        tripKey += "#";
        trip = "◆" + Convert.ToBase64String(sha1base64shiftjissha1).Substring(0, 12).Replace('+', '.');
 
        byte[] blake512 = data.ComputeBlake512();
        byte[] bmw512 = data.ComputeBmw512();
        byte[] groestl512 = data.ComputeGroestl512();
        byte[] skein512 = data.ComputeSkein512();
        byte[] jh512 = data.ComputeJh512();
        byte[] keccak512 = data.ComputeKeccak512();
        byte[] luffa512 = data.ComputeLuffa512();
 
        byte[] cubehash512 = sha1base64shiftjissha1.Combine(blake512).ComputeCubehash512();
        byte[] shavite512 = bmw512.Combine(groestl512).ComputeShavite512();
        byte[] simd512 = skein512.Combine(jh512).ComputeSimd512();
        byte[] echo512 = keccak512.Combine(luffa512).ComputeEcho512();
 
        byte[] fugue512 = cubehash512.Combine(shavite512).ComputeFugue512();
        byte[] hamsi512 = simd512.Combine(echo512).ComputeHamsi512();
 
        byte[] shabal512 = fugue512.Combine(hamsi512).ComputeShabal512();
 
        return shavite512.Decompose(0, SizeByte);
    }
}

独自暗号貨幣を作ろう 第6回

第1回 第2回 第3回 第4回 第5回

<塵埃取引出力規制>
塵埃取引(dust transaction、額面価格が極めて小額であるような取引出力を有する取引)によるブロック鎖の肥大化(ブロック鎖の保持に必要な記憶領域が深刻な速度で増大していくこと)を阻止するために、Bitcoinでは額面価格が5460satoshi未満の取引出力は認められません。

CREACOINにも同様の制限を設けることにします。

額面価格が0.1yumina未満の取引出力を塵埃取引出力と定義し、1つ以上の塵埃取引出力を含む取引を塵埃取引と定義し、塵埃取引は無効とします。無効な取引は正当なノード(honest node)には認められず、正常なネットワーク(正当なノードが多数を占めるネットワーク)においては棄却されます。

塵埃取引の無効化をどの部分で(どのクラスで)実装するかは難しいところですが、Transaction抽象クラスで実装することにしましょう。

Transaction抽象クラスにdustTxout静的フィールドを追加し、IsValid仮想プロパティをオーバーライドします。

public static CurrencyUnit dustTxout = new Yumina(0.1m);

public override bool IsValid
{
    get
    {
        if (!base.IsValid)
            return false;
 
        if (Version == 0)
            return outputs.All((e) => e.amount.rawAmount >= dustTxout.rawAmount);
        else
            throw new NotSupportedException("tx_is_valid_not_supported");
    }
}

独自暗号貨幣を作ろう 第5回

第1回 第2回 第3回 第4回


<貨幣を移動する取引>
貨幣を移動する取引は1つ以上の取引入力と、1つ以上の取引出力を含みます(Bitcoinの場合、もう少し追加の機能があったりするのですが、取り敢えずは単純なものを考えましょう。必要なら後で追加実装すれば良いでしょう)。

貨幣を移動する取引によって、夫々の取引入力が参照している夫々の取引出力(の口座)から貨幣を移動する取引の中に含まれる夫々の取引出力(の口座)に貨幣が移動します。
要するに、貨幣をある口座から別の口座に移動したい場合には、貨幣を移動する取引を新たに作成します。そして、それをネットワーク全体に送信し、採掘者による仕事証明を経て、最終的にブロック鎖の中に含まれることによって確定的な取引となります(実際には、ブロック鎖分岐が発生する可能性があるので、この段階でも未だ完全に確定的な取引とはなっていません)。

それでは、先ず、未だ実装していなかった取引入力を表すTransactionInputクラスを実装します。
prevTxHashとprevTxOutputIndexで(未使用の)取引出力を参照します。prevTxHashが参照しようとしている取引出力を含む取引の暗号学的要約値を表し、prevTxOutputIndexがその取引の何番目の取引出力を参照しようとしているのかを表します。
senderSigが支払い人(取引前の貨幣の所有者)の署名です。支払い人以外は正当な書名を作成することができません。この署名によって支払い人が本当に支払いを実行しようとしているということを確認します。
senderPubKeyが支払い人の口座に結び付けられている楕円曲線DSAの公開鍵を表します。この公開鍵によって署名が正当なものであるかを検証します。この公開鍵の暗号学的要約値が参照している取引出力の口座番号に一致しなければなりません(一致していない場合は支払い人とは何の関係もない公開鍵だということになります)。

公開鍵は楕円曲線DSAの公開鍵(の予定)ですが、楕円曲線DSAにも幾つか種類があります。口座に結び付けられる(秘密鍵と)公開鍵(の鍵対)としてどの方式のものを採用するかは未だ確定させないでおきます。

public class TransactionInput<TxidHashType, PubKeyType> : SHAREDDATA
    where TxidHashType : HASHBASE
    where PubKeyType : DSAPUBKEYBASE
{
    public TransactionInput() : base(null) { }
 
    public TransactionInput(TxidHashType _prevTxHash, int _prevTxOutputIndex, PubKeyType _senderPubKey)
        : base(null)
    {
        prevTxHash = _prevTxHash;
        prevTxOutputIndex = _prevTxOutputIndex;
        senderPubKey = _senderPubKey;
    }
 
    public TxidHashType prevTxHash { get; private set; }
    public int prevTxOutputIndex { get; private set; }
    public byte[] senderSig { get; private set; }
    public PubKeyType senderPubKey { get; private set; }
 
    protected override Func<ReaderWriter, IEnumerable<MainDataInfomation>> StreamInfo
    {
        get
        {
            return (msrw) => new MainDataInfomation[]{
                new MainDataInfomation(typeof(TxidHashType), null, () => prevTxHash, (o) => prevTxHash = (TxidHashType)o),
                new MainDataInfomation(typeof(int), () => prevTxOutputIndex, (o) => prevTxOutputIndex = (int)o),
                new MainDataInfomation(typeof(byte[]), null, () => senderSig, (o) => senderSig = (byte[])o),
                new MainDataInfomation(typeof(PubKeyType), null, () => senderPubKey, (o) => senderPubKey = (PubKeyType)o),
            };
        }
    }
 
    public void SetSenderSig(byte[] sig) { senderSig = sig; }
}


SetSenderSigメソッドで支払い人の署名を設定できるようにしておきます。

貨幣を移動する取引を表すTransferTransactionクラスを作ります。
inputsが1つ以上の取引入力を表します。

public class TransferTransaction<TxidHashType, PubKeyHashType, PubKeyType> : Transaction<TxidHashType, PubKeyHashType>
    where TxidHashType : HASHBASE
    where PubKeyHashType : HASHBASE
    where PubKeyType : DSAPUBKEYBASE
{
    public TransferTransaction() : base(0) { }
 
    public TransferTransaction(TransactionInput<TxidHashType, PubKeyType>[] _inputs, TransactionOutput<PubKeyHashType>[] _outputs)
        : base(0, _outputs)
    {
        if (_inputs.Length == 0)
            throw new InvalidDataException("tx_inputs_empty");
 
        inputs = _inputs;
    }
 
    public TransactionInput<TxidHashType, PubKeyType>[] inputs { get; private set; }
 
    protected override Func<ReaderWriter, IEnumerable<MainDataInfomation>> StreamInfo
    {
        get
        {
            if (Version == 0)
                return (msrw) => base.StreamInfo(msrw).Concat(new MainDataInfomation[]{
                    new MainDataInfomation(typeof(TransactionInput<TxidHashType, PubKeyType>[]), null, null, () => inputs, (o) => inputs = (TransactionInput<TxidHashType, PubKeyType>[])o),
                });
            else
                throw new NotSupportedException("transfer_tx_main_data_info");
        }
    }
 
    public override bool IsVersioned { get { return true; } }
}


さて、これで一応貨幣を移動する取引が実装できた訳ですが、貨幣を移動する取引の何れかの取引入力が使用済み(spent)の取引出力を参照していたり、全ての取引入力の額面価格の総和が全ての取引出力の額面価格の総和より小さかったりすると、それは無効な取引です。
即ち、取引が有効であるか無効であるかを判定できる必要があるのですが、そのための枠組みは今後考えていくことになるかと思います。

2つの自然数列を結合したものの要素数が夫々の自然数列の要素数の和に等しいことの証明

Inductive nat_list : Set :=

NNil : nat_list
NCons : nat -> nat_list -> nat_list.

Fixpoint nlength (ls : nat_list) : nat :=
match ls with

NNil => O
NCons _ ls' => S (nlength ls')

end.

Fixpoint napp (ls1 ls2 : nat_list) : nat_list :=
match ls1 with

NNil => ls2
NCons n ls1' => NCons n (napp ls1' ls2 )

end.

Theorem nlength_napp : forall ls1 ls2 : nat_list, nlength (napp ls1 ls2 ) = plus (nlength ls1 ) (nlength ls2 ).
induction ls1.
simpl. reflexivity.
simpl. intros ls2. rewrite IHls1. reflexivity.
Qed.