LoginSignup
5
1

More than 3 years have passed since last update.

カタパルトのDragonでリプレイプロテクションが実装されたのでトランザクションの署名とハッシュの算出が変わった

Posted at

はじめに

カタパルトのDragonが公開されました。

変更箇所の一つに、リプレイプロテクションの実装があります。

チェンジログには、以下のような記載があります。

Prevent transactions from being replayed on different networks by prepending the network generation hash to transaction data prior to signing and verifying.
署名および検証の前に、ネットワーク生成ハッシュをトランザクションデータに付加することによって、トランザクションが異なるネットワークで再生されないようにします。

どうやら、トランザクションの署名をする際には、network generation hashが必要になるみたいです。

network generation hashは、おそらくネメシスブロックのジェネレーションハッシュのことであると推測されます。

ジェネレーションハッシュ

ジェネレーションハッシュは以下の資料にあるように、ブロックの生成に必要な値です。

導出方法とか、何に使われているのかは、よく調べていません。

ネメシスブロックのジェネレーションハッシュの探しかた

ネメシスブロックの情報をRESTに問い合わせます。generationHashと書いてある箇所がそうです。

image.png

ネットワーク毎に異なるので、プライベートネットワークを使っている場合は注意しましょう。

ジェネレーションハッシュをどう使うか

この段階で分かっているのは、トランザクションに含めることだけです。

よくわかんないので、従来通りトランスファートランザクションをなげました。

2019-06-01 09:28:24.303162 0x00007f1ce47e8700: <warning> (subscribers::SubscriptionManager.cpp@101) rejected tx 7D1CDD44C55F847A67A876D2025521A82BFA138DE495EE184326AA8CE01E4E95 due to result Failure_Signature_Not_Verifiable (deadline 99919702895) 
2019-06-01 09:28:24.303637 0x00007f1ce47e8700: <warning> (consumers::StatelessValidationConsumer.cpp@47) stateless transaction validation failed: Failure_Signature_Not_Verifiable 
2019-06-01 09:28:24.326207 0x00007f1ce3fe7700: <warning> (consumers::ReclaimMemoryInspector.cpp@32) consumer aborted at position 0 while processing 1 txes [7D1CDD44] from Remote_Push due to Failure_Signature_Not_Verifiable 

Failure_Signature_Not_Verifiableと出ました。つまり、署名対象データが変わったということが推測されます。

後で分かったことなのですが、トランザクションハッシュの計算方法も変わっていました。

署名とハッシュの算出について参考にした箇所

いろいろ探し回って、それっぽいにおいがする箇所を見つけました。

ジェネレーションハッシュをどのように使うかは、次でまとめます。

署名対象データ

これまで

項目 サイズ 意味
version 2byte(LE) 0390
type 2byte(LE) 5441
fee 8byte(LE) 手数料
deadline 8byte(LE) 期限
受取人 25byte 受取人アドレス
msg size 2byte(LE) メッセージバイト数+1
num mosaics 1byte モザイクの種類数
msg payload 1byte + 任意byte メッセージタイプ+メッセージ
mosaics array(8byte(LE), 8byte(LE)) モザイクID+量

これから

先頭に追加です

項目 サイズ 意味
nemesisGenerationHash 32byte ネメシスブロックのジェネレーションハッシュ
あとは同じ

ハッシュ計算データ

これまで

項目 サイズ 意味
署名の先頭32byte 32byte 署名のR
公開鍵 32byte 署名者の公開鍵
version 2byte(LE) 0390
type 2byte(LE) 5441
fee 8byte(LE) 手数料
deadline 8byte(LE) 期限
受取人 25byte 受取人アドレス
msg size 2byte(LE) メッセージバイト数+1
num mosaics 1byte モザイクの種類数
msg payload 1byte + 任意byte メッセージタイプ+メッセージ
mosaics array(8byte(LE), 8byte(LE)) モザイクID+量

これから

あいだに追加です

項目 サイズ 意味
署名の先頭32byte 32byte 署名のR
公開鍵 32byte 署名者の公開鍵
nemesisGenerationHash 32byte ネメシスブロックのジェネレーションハッシュ
version 2byte(LE) 0390
type 2byte(LE) 5441
fee 8byte(LE) 手数料
deadline 8byte(LE) 期限
受取人 25byte 受取人アドレス
msg size 2byte(LE) メッセージバイト数+1
num mosaics 1byte モザイクの種類数
msg payload 1byte + 任意byte メッセージタイプ+メッセージ
mosaics array(8byte(LE), 8byte(LE)) モザイクID+量

トランザクション投げるためのかんたんなコード

transfer2.js
process.env.PRIVATE_KEY = '25B3F54217340F7061D02676C4B928ADB4395EB70A2A52D2A11E2F4AE011B03E';
process.env.nemesisGenerationHash = '5ABBD9F7894EE7E5D4C3CDA934245396AEFCD1CB0426F265AC81F4F3450AB6DD';
process.env.NODE_URL = 'http://3.112.207.254:3000'

const nem2Sdk = require("nem2-sdk");
const jssha3 = require('js-sha3');
const nem2lib = require("nem2-library");
const request = require('request');

const sha3_256 = jssha3.sha3_256;

const Address = nem2Sdk.Address,
    Deadline = nem2Sdk.Deadline,
    Account = nem2Sdk.Account,
    UInt64 = nem2Sdk.UInt64,
    NetworkType = nem2Sdk.NetworkType,
    PlainMessage = nem2Sdk.PlainMessage,
    TransferTransaction = nem2Sdk.TransferTransaction,
    Mosaic = nem2Sdk.Mosaic,
    MosaicId = nem2Sdk.MosaicId,
    NamespaceId = nem2Sdk.NamespaceId,
    TransactionHttp = nem2Sdk.TransactionHttp,
    NetworkCurrencyMosaic = nem2Sdk.NetworkCurrencyMosaic;

const recipientAddress = Address.createFromRawAddress('SD5DT3-CH4BLA-BL5HIM-EKP2TA-PUKF4N-Y3L5HR-IR54');

const tx = TransferTransaction.create(
  Deadline.create(),
  recipientAddress,
  [NetworkCurrencyMosaic.createRelative(0)],
  PlainMessage.create(''),
  NetworkType.MIJIN_TEST,
  UInt64.fromUint(0)
)

const account = Account.createFromPrivateKey(process.env.PRIVATE_KEY, NetworkType.MIJIN_TEST);
const signedTransaction = account.sign(tx);
const txPayload = signedTransaction.payload

const txPayloadSigningBytes = process.env.nemesisGenerationHash + txPayload.substr(100*2);
const keypair = nem2lib.KeyPair.createKeyPairFromPrivateKeyString(process.env.PRIVATE_KEY);
const signatureByte = nem2lib.KeyPair.sign(keypair, txPayloadSigningBytes);
const signature = nem2lib.convert.uint8ToHex(signatureByte);

const signedTxPayload =
    txPayload.substr(0,4*2) +
    signature +
    txPayload.substr((4+64)*2);

const hashInputPayload = 
    signedTxPayload.substr(4*2,32*2) +
    signedTxPayload.substr((4+64)*2,32*2) +
    process.env.nemesisGenerationHash +
    signedTxPayload.substr((4+64+32)*2);
const signedTxHash = sha3_256.create().update(Buffer.from(hashInputPayload, 'hex')).hex().toUpperCase();
console.log(`signedTxHash: ${signedTxHash}`);

const ENDPOINT = process.env.NODE_URL;
request({
    url: `${ENDPOINT}/transaction`,
    method: 'PUT',
    headers: {
        'Content-Type':'application/json'
    },
    json: {"payload": signedTxPayload}
}, (error, response, body) => {
    console.log(body);
});

なげた

> node .\transfer2.js
signedTxHash: 957DA42927A7790F9F9C161B98B630568DE2707562AE5F6D58154600CE918332
{ message: 'packet 9 was pushed to the network via /transaction' }

image.png

おわりに

リプレイプロテクションが実装されました。

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1