独自暗号貨幣を作ろう 第5回
<貨幣を移動する取引>
貨幣を移動する取引は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)の取引出力を参照していたり、全ての取引入力の額面価格の総和が全ての取引出力の額面価格の総和より小さかったりすると、それは無効な取引です。
即ち、取引が有効であるか無効であるかを判定できる必要があるのですが、そのための枠組みは今後考えていくことになるかと思います。