独自暗号貨幣を作ろう 第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));
    }
}