秘密鍵とブラウザ、そして0.1円で実現するブロックチェーン存在証明

ブロックチェーンを利用した存在証明についてご紹介します。
今回はSymbolを使用して、約80行ほどのJavaScriptでファイルのハッシュ値をブロックチェーンに記録してみます。


#### 存在証明とは
データの要約値となるハッシュ値をブロックチェーンに記録することで、そのデータを取り込んだブロックの`タイムスタンプ`、そのデータを記録した`署名者`の情報が改竄されずに後から検証可能な状態にできます。データ全体を保存するのではなく要約値のみ保存するのでプライバシー情報がブロックチェーンに記録されることはありません。


:::note alert
今回使用するスクリプトは秘密鍵を直接使用します。
テストネットなどで使用するか、あるいは1XYMなど少額の手数料費しか持ち合わせていないアカウントでご利用ください。
:::

### 必要なもの
- 秘密鍵
- ブラウザ（ネット環境）
- 0.0225XYM(現在約0.1円)

### ノード選択
以下のノード一覧から同期しているブロック高に注意してエンドポイントを選択してください。
(Height列の httpのリンクをクリック)

#### mainnet
https://symbolnodes.org/nodes/

#### testnet
https://symbolnodes.org/nodes_testnet/

### スクリプト

ブラウザでノードのURLをクリックしたらF12キーを押して開発者コンソールを開き、以下のスクリプトをコピーペーストします。
PRIVATE_KEYには各自1XYM程度を事前に送信しておいたアカウントの秘密鍵を指定してください。

```js
const PRIVATE_KEY = "{ここに秘密鍵を入力}";

const node = window.origin;
const res = await fetch(node + "/network/properties");
const json = await res.json();
const networkId = json.network.identifier;
const epochAdjustment = Number(json.network.epochAdjustment.replace("s",""));

const bundle = await import("https://www.unpkg.com/symbol-sdk@3.2.3/dist/bundle.web.js");
const core = bundle.core;
const sym  = bundle.symbol;
const chain = new sym.SymbolFacade(networkId);

async function sha256(data) {
    const hashBuffer = await crypto.subtle.digest('SHA-256', data);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}

function clog(signedHash){
    console.log(node + "/transactionStatus/" + signedHash);
    console.log(node + "/transactions/confirmed/" + signedHash);
    if(networkId === "mainnet"){
        console.log("https://symbol.fyi/transactions/" + signedHash);
    }else{
        console.log("https://testnet.symbol.fyi/transactions/" + signedHash);
    }
}

function setMaxFee(tx,multiplier){
    return new sym.models.Amount(BigInt(tx.size * multiplier))
}

function createDeadline(secs = 7200) {
    const value = ((Math.trunc(Date.now() / 1000) + secs) - epochAdjustment) * 1000
    return BigInt(value);
}

const prikey = new core.PrivateKey(PRIVATE_KEY)
const kpair = new sym.KeyPair(prikey);
const pubkey = kpair.publicKey;
const address = chain.network.publicKeyToAddress(kpair.publicKey);

const input = document.createElement('input');
input.type = 'file';
input.addEventListener('change', (event) => {
    const file = event.target.files[0]; // 選択された最初のファイル
    const reader = new FileReader();
    reader.onload = async (event) => {

        const arrayBuffer = event.target.result;
        const apos = await sha256(arrayBuffer)
        const messageData = new Uint8Array([
            0x00,
            ...new TextEncoder().encode(apos),
        ]);

        const tx = chain.transactionFactory.create({
            type: 'transfer_transaction_v1',
            signerPublicKey: pubkey,
            deadline:createDeadline(),
            recipientAddress: address,
            message:messageData,
        });
        tx.fee = setMaxFee(tx,100);

        const signature = chain.signTransaction(kpair,tx);
        const requestBody = sym.SymbolTransactionFactory.attachSignature(tx, signature);
        const res = await fetch(
          new URL('/transactions', node),
          {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: requestBody,
          }
        );
        clog(chain.hashTransaction(tx).toString());            
    };
    reader.readAsArrayBuffer(file);
});
input.click();
```

### 存在証明実行

スクリプトを実行すると、ファイル選択ダイアログが表示されるので存在証明したいファイルを選択します。
リンクが３つ表示されるので、一番下のエクスプローラーのリンクをクリックしてブロックチェーンに記録されたことを確認してください。

![スクリーンショット 2024-12-07 0.59.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3633677/897f1c5e-3aaf-6f91-40a7-3cf320fe5622.png)


### 検証

OSのファイルハッシュ値を出力するコマンドで、ハッシュ値が正しいかどうかを確認します。

#### Windows
```
certutil -hashfile {ファイル名} SHA256
```

#### Mac
```
shasum -a 256 {ファイル名}
```

Linux
```
sha256sum {ファイル名}
```