待望のSymbolのChrome拡張機能「SSS Extension」を使ってみた


## 経緯
Symbolでの署名処理を外部に移譲できる「SSS Extension」という**最高**のChrome拡張機能が先日遂にリリースされました！
それらのハンズオンを進めているコミュニティのリーダー的ポジの方のツイートを拝見していたらわくわくが止まらず、雑な送金SPAをVue(Nuxt)で作ってみました！

## 大感謝
開発者の[いなたつさん](https://twitter.com/_inatatsu_csg_)
きっかけをくださった[ねむぐまさん](https://twitter.com/nembear)
大変参考になる[@nem_takanobuさんのQiita記事](https://qiita.com/nem_takanobu/items/4595dfb1666f49b17fe8)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277262/24d7ae7d-6ed2-63af-40b2-af89302c9865.png)

## この記事で扱うことと扱わないこと
- × テストネットアカウント開設
- × Nuxtプロジェクト作成
- ○ なんでSSS_Extensionが嬉しいのか
- ○ EEE_Extensionを用いたソースの記載
- ○ SSS_Extension使用時につまづいたポイント
- × 作成したSPAのデプロイ
（お恥ずかしながら、今回初めてテストネットアカウントを開設しました、、ドキュメントやQiita記事を読むだけで知った気になってました、、）

# 本題
お待たせしました、ぐだぐだ書いてすみません

## 作成したもの
[こちら](http://sss-extension-handson.s3-website-ap-northeast-1.amazonaws.com/)です！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277262/bcf45853-35c0-1116-93b2-1aab8ab3cf03.png)

本当に雑にJSで書かれたWebアプリをS3に配置しています！
無邪気に送金が出来るアプリが書けてしまうのはある意味恐ろしいまでありますね、、！
メインネットでチップをもらえるようにしたかったのですが、CORSでメインネットのノードにうまく繋げなかったことと、
そもそもいま完全に徹夜で作業をしているので、そんな状態でメインネットのアプリを書くべきではないかな、と自粛しました（笑）
秘密鍵での署名処理を拡張機能に移譲しているので、サーバ側を自分で用意する必要が無く、素晴らしいです。

## なんでSSS_Extensionが嬉しいのか
Symbolに限りませんが、ブロックチェーンで送金などをするには、
1. トランザクションを作成する（例：誰にいくら送りたい、こんなメッセージで！）
2. 自身の秘密鍵で署名（秘密鍵は誰にも知られてはならない、自分だけが署名できることで、このトランザクションはその秘密鍵の所有者によって行われたと証明できる）
3. ブロックチェーンにトランザクションを放流する（このトランザクションをみんな記録して！的な）
4. ノードらによって記録、承認される（Symbolではファイナライズでしっかり確定したりしてますね）

上記のようなフローとなります。
ここで問題なのは2の手順です。
ここでいう秘密鍵とは、**名前の通り秘密**でなくてはなりません。
この情報が他人にばれると、その秘密鍵の持ち主である＝トランザクションを発行可能＝資産を全て移動させるなどし放題、というまさにゲームオーバーな状況になります。

もう少し細かく見てみます
### ユースケース1：ゲームをクリアするとモザイクを配布するWebアプリ
今回の拡張機能で解決されるのはこのユースケースではありません。後述のユースケース2のほうですが記載します。
Webアプリに秘密鍵を書けない・書きたくない理由は、Webアプリのソースコードは全てクライアント（一般ユーザ）側にダウンロードされるからです。
如何に読みにくくしたところで、Webページを開く＝そのページを構成するHTML・JSのソースコードはクライアント側にダウンロードされてしまいます。
その秘密鍵を発見されれば、ゲームクリア時にプレゼントしたいモザイクであったり、その秘密鍵から生成されるアドレスの資産は全て抜き取り放題です。
こういった事態を避けるべく、バックエンドにAPIを用意し、バックエンド側にに秘密鍵を持たせ、Webアプリ側で条件を満たしたらそのAPIを呼び出し、バックエンド側で署名処理をするなどします。
こうすることでAPIの呼び出し先はバレてしまいますが、秘密鍵はバレないので安全に扱えます。

### ユースケース2：Webアプリでウォレットを作成する・送金機能を実装する
さて、今回のミソはこちらのユースケースです。
送金機能などを作りたい場合は、秘密鍵を用いてトランザクションに署名する必要があります。
秘密鍵を持ってさえいれば送金などが可能になるのに、Web上にその情報を記載したい！という人はいないと思います（入力内容が気付かないうちに外部に連携されていれば、そのページ作成者やネットワークを盗聴している人に秘密鍵の情報が盗まれてしまうかも、、）
今回これを解決してくれるのが、SSS_Extensionです。
拡張機能側でローカルに秘密鍵の情報を持たせて管理することで、**SSS_Extensionが安全ならば**Webアプリ側に秘密鍵に関する情報を渡さず、トランザクションに署名が出来るようになります！！
とってもセンシティブな署名処理を一つの拡張機能側に切り出せることで、うちのWebアプリでは秘密鍵の情報が一切不要ですよん！と気軽に送金機能を持たせたWebアプリが作れます！
めっちゃガバガバなフロー図で恐縮ですが、こんなイメージで署名部分を外だし出来ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277262/2a581bdf-6148-6d58-e886-a0ecc9103a16.png)
**SSS_Extensionが安全ならば**(このツール自体が秘密鍵を取るようなことが無ければ)という前提は入りますが、気にする必要があるツールが一つになるのは最高に嬉しいですし、素晴らしいツールです。

## EEE_Extensionを用いたソースの記載
やっとソースコードの話になるのですが、
- トランザクションの作成
- 署名をSSS_Extensionに依頼
- 署名が終わったトランザクションを放流
の3ステップです。

まずはWebアプリ上でSSS_Extensionを有効化してあげる必要があります。
Webアプリ上で右クリックし、**SSSと連携する**を押してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277262/8a5e2aec-a2c0-ac82-9e0c-05c191b8ac2a.png)

### トランザクションの作成
このステップはSSS_Extensionと関係ありません。従来のトランザクション作成です。
これまでのQiita記事などの記載と同じです。
```js
import * as symbol from 'symbol-sdk'
const EPOCH_ADJUSTMENT = 1637848847;
let tx = symbol.TransferTransaction.create(
  symbol.Deadline.create(EPOCH_ADJUSTMENT),
  symbol.Address.createFromRawAddress("TCVSR42OL2D2ELIDXLVXSDAZUEVBUTTLU7IJ3GI"), //これは自分がテストに使用したアドレスです
  [new symbol.Mosaic(new symbol.MosaicId('3A8416DB2D53B6C8'), symbol.UInt64.fromUint(1000000))],
  symbol.PlainMessage.create(this.message),
  symbol.NetworkType.TEST_NET,
  symbol.UInt64.fromUint(2000000)
);
```

### 署名をSSS_Extensionに依頼
以下だけです。とても分かり易い、、、
```js
window.SSS.setTransaction(tx)
let signedTx = await window.SSS.requestSign()
```
素晴らしいことに、下記画像のように署名が要求された旨・完了された旨が自動でウィンドウ右下に出てきてくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277262/6dda9505-80b2-d6e7-dd03-0940e2e9a813.png)

### 署名が終わったトランザクションを放流
署名が完了したら、次は署名したトランザクションをブロックチェーンに放流します。
```js
new symbol.TransactionHttp(NODE).announce(signedTx).subscribe(
  (success) => {
    console.log('成功', success)
  },
  (err) => {
    console.error(`失敗`, err)
  }
)
```
subscribeの記法についてはLintで怒られたりもしますが、動きますので一旦このまま、、（数日以内に直します。執筆中現在かなり眠くて深夜テンションなのでさぼれるところはさぼってます、、笑）

以上です。かなり開発者体験的に嬉しい、、簡単に署名してトランザクション流せる、、

## SSS_Extension使用時につまづいたポイント
ということで、幾つかこのアプリを作成時につまづいたポイントを紹介します。

### 拡張機能を呼び出してもUndefinedになってしまう
以下のようなconsole.logを呼び出しても、undefinedになってしまって拡張機能の呼び出しに苦戦しました。
```js
console.log("SSSの中身:" + JSON.stringify(window.SSS))
console.log("有効か否か" + JSON.stringify(window.isAllowedSSS()))
```
恐らくこれは拡張機能の読み込みが完了していなかったことが問題のようで、ボタンクリック時に呼び出すようにしたら問題無く使えました。
これはonReady系のイベントを監視したら直せそうですが、対症療法的に回避できたので一旦ヨシ！でいきます。

### トランザクション署名完了を待ってからうまいこと放流できない
署名をユーザがしてくれるのを待つ必要がありますが、JSは非同期処理言語です。
awaitやPromiseの概念を使って待たせる処理を記載しましょう。
`window.SSS.requestSign()`も、しっかりPromiseを返してくれるようです。
2パターンの方法があります。
- asyncの関数内に処理がある場合
awaitを記載することで処理を中断させ、署名されるまで次に進ませずにソースを一時停止できます。
先ほど示した例と同じですね！
```js
let signedTx = await window.SSS.requestSign()
new symbol.TransactionHttp(NODE).announce(signedTx).subscribe(
  (success) => {
    console.log('成功', success)
  },
  (err) => {
    console.error(`失敗`, err)
  }
)
```

- asyncの関数内に無く、awaitを使わずに処理完了を待ちたい
詳しくはPromiseの概念を調べていただければと思いますが、`.then`を付けることで、`.then`以前の処理が完了したら`.then`内の処理に進む、という記載ができます。（これを簡単に、分かり易く書けるようにしたのがasync/awaitです）
```js
window.SSS.requestSign().then((signedTx) => {
  new symbol.TransactionHttp(NODE).announce(signedTx).subscribe(
    (x) => {
      console.log('x', x)
    },
    (err) => {
      console.error(err)
    }
  )
  console.log(JSON.stringify(signedTx))
});
```
このように記載することで、署名が完了されたら`.then`以降の処理（トランザクションを放流）をさせることが出来ました。

## 最後に
このSSS_Extensionというアプリは大変すばらしいです。
開発者のいなたつさんには頭が上がりません。スーパーエンジニアです。
誰かがこういった基盤を作成してくれることで、一気にSymbolを使ったWebアプリが増えていくと思います。
これは本当に開発者参入のハードルを破壊する素晴らしい機能だと思っています。
自分もこの素晴らしさに魅入られて他の予定すっ飛ばしていきなり深夜1時くらいからハンズオンと記事執筆をしちゃっていました。
これを使ってWebアプリ型の送金機能を持たせたウォレットなんかも作ってみたいです！
ここまでお付き合いいただきありがとうございました！

署名後、SSS_Extensionが自動で閉じられたり、署名完了の旨が出たらより最高だな～と思っています！