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

第1回
第2回第3回

<新しい貨幣を生成する取引>
Bitcoin型の暗号貨幣においては、(全ての種類の)取引には1つ以上の取引入力(transaction input)と、1つ以上の取引出力(transaction output)が含まれます。
取引入力はどの取引出力を使用する(spend)かを表し、使用しようとしている取引出力の所有者の電子署名(digital signature、注:digital signatureと電子署名(electronic signature)は異なる概念ですが、本連載記事では同一視します)を含みます。
取引出力は貨幣の所有者(貨幣を保持する口座番号(address))とその額面価格を表します。
ある取引出力を使用する際には新たに作成した取引入力にその取引出力を結び付けます。当然ながら、既に取引入力が結び付けられている取引出力を別の新たに作成された取引入力に結び付けることはできません(もしこれが可能ならば、貨幣が二重使用(double-spend)できるということになります)。
即ち、Bitcoin型の暗号貨幣においては、貨幣の実体は未使用の取引出力(unspent transaction output:UXTO)です

CREACOINでも、基本的にはBitcoin型の取引入力及び取引出力の考え方を踏襲しますが、新しい貨幣を生成する取引には取引入力が1つも含まれないものとします。特に必要ではない為です。

(因みに、余談ですが、Bitcoinの、新しい貨幣を生成する取引には唯1つの取引入力が含まれます。新しい貨幣を生成する取引は(未使用の)取引出力を使用しようとしている訳ではないので、(新しい貨幣を生成する取引の)取引入力の、どの取引出力を使用するかを表す部分には無意味な無視できる値が格納されます。又、これから生成される貨幣に既に所有者が存在している筈もないので、(新しい貨幣を生成する取引の)取引入力には電子署名も必要ない筈なのですが、本来電子署名が格納される部分には追加の仕事証明の解(extra nonce)が格納されます。これは2バイト以上100バイト以下の任意の値であり、仕事証明の手続きの為に使用されます。尚、将来の拡張でもっと別のデータも格納するようにしようという話があります)。

取引出力を表すTransactionOutputクラスを作ります。
receiverPubKeyHashが貨幣の所有者を表します。Bitcoin型の暗号貨幣は貨幣の所有権が本人性と結び付かず、貨幣の所有者は(追加の情報がなければ本人性と結び付かない)口座番号として表されます。口座番号は楕円曲線DSAの公開鍵の暗号学的要約値です。対応する秘密鍵を知っている者しか有効な署名を作成することはできませんので、貨幣の所有権を間接的に言明する口座を表章するものとして利用することができます。
amountが貨幣の額面価格を表します。
口座番号を計算するのにどの暗号学的要約関数(又は、それらの組み合わせ)を採用するかは、未だ確定させないでおきます。

public class TransactionOutput<PubKeyHashType> : SHAREDDATA where PubKeyHashType : HASHBASE
{
    public TransactionOutput() : base(null) { }
 
    public TransactionOutput(PubKeyHashType _receiverPubKeyHash, CurrencyUnit _amount)
        : base(null)
    {
        receiverPubKeyHash = _receiverPubKeyHash;
        amount = _amount;
    }
 
    public PubKeyHashType receiverPubKeyHash { get; private set; }
    public CurrencyUnit amount { get; private set; }
 
    protected override Func<ReaderWriter, IEnumerable<MainDataInfomation>> StreamInfo
    {
        get
        {
            return (msrw) => new MainDataInfomation[]{
                new MainDataInfomation(typeof(PubKeyHashType), null, () => receiverPubKeyHash, (o) => receiverPubKeyHash = (PubKeyHashType)o),
                new MainDataInfomation(typeof(ulong), () => amount.rawAmount, (o) => amount = new CurrencyUnit((ulong)o)),
            };
        }
    }
}


今回は新しい貨幣を生成する取引を実装しますが、1つ以上の取引出力が必要なのは全ての種類の取引で共通しているので、共通する部分はTransaction抽象クラスに実装します。夫々の種類の取引に特有な部分だけを夫々の具象クラスで実装します。

Transaction抽象クラスを修正します。
outputsが1つ以上の取引出力を表します。

public abstract class Transaction<TxidHashType, PubKeyHashType> : TXBLOCKBASE<TxidHashType>
    where TxidHashType : HASHBASE
    where PubKeyHashType : HASHBASE
{
    public Transaction(int? _version) : base(_version) { }
 
    public Transaction(int? _version, TransactionOutput<PubKeyHashType>[] _outputs)
        : base(_version)
    {
        if (_outputs.Length == 0)
            throw new InvalidDataException("tx_outputs_empty");
 
        outputs = _outputs;
    }
 
    public TransactionOutput<PubKeyHashType>[] outputs { get; private set; }
 
    protected override Func<ReaderWriter, IEnumerable<MainDataInfomation>> StreamInfo
    {
        get
        {
            return (msrw) => new MainDataInfomation[]{
                new MainDataInfomation(typeof(TransactionOutput<PubKeyHashType>[]), null, null, () => outputs, (o) => outputs = (TransactionOutput<PubKeyHashType>[])o),
            };
        }
    }
}


新しい貨幣を生成する取引を表すCoinbaseTransactionクラスを実装します。
とは言え、新しい貨幣を生成する取引は1つ以上の取引出力しか含まないので、Transaction抽象クラスを継承(して、幾つかのプロパティをオーバーライド)するだけです(Bitcoinの場合、もう少し追加の機能があったりするのですが、取り敢えずは単純なものを考えましょう。必要なら後で追加実装すれば良いでしょう)。

public class CoinbaseTransaction<TxidHashType, PubKeyHashType> : Transaction<TxidHashType, PubKeyHashType>
    where TxidHashType : HASHBASE
    where PubKeyHashType : HASHBASE
{
    public CoinbaseTransaction() : base(0) { }
 
    public CoinbaseTransaction(TransactionOutput<PubKeyHashType>[] _outputs) : base(0, _outputs) { }
 
    protected override Func<ReaderWriter, IEnumerable<MainDataInfomation>> StreamInfo
    {
        get
        {
            if (Version == 0)
                return base.StreamInfo;
            else
                throw new NotSupportedException("coinbase_tx_main_data_info");
        }
    }
 
    public override bool IsVersioned { get { return true; } }
}