Liquidのペグインアドレスの作り方 


こんにちは。力道山が刺された日まであと５日なので、力道山の「リキド」に掛けてLiquidのペグインアドレスの作り方について書きます！

## 前提知識
### Liquid
Blockstreamが出しているBitcoinのサイドチェーンで、メインとなる通貨はL-BTCです。
ペグイン・ペグアウトという形で、BitconのメインネットとLiquidのliquidv1との間で資金を移動することが出来ます。
liquidv1は15の取引所等から構成されるフェデレーションが運営するプライベートチェーンで、Bitcoinメインネットと比較すると、1分ごとにフェデレーションメンバーが2/3のフェデレーションの同意を取ってブロックを作成する、チェーンの分岐が起こらない、ブロックが2承認で確定する、秘匿取引やチェーン上でのアセットの作成の機能があるなどの特徴があります。

ElementsというBlockchainを構築するためのプラットフォーム上でLiquidは作られています。Elementsの上で動くバージョンのBitcoindもあり、多くの部分で同じコードが使われています。

#### ペグイン
Liquid側で作成したアドレス宛にBitcoinのメインネット上で送金することで、送金されたBTCがメインネット上でロックされ、Liquidのliquidv1ネットワーク上で同額のL-BTCが発生します。このプロセスをペグイン、ペグインに使われるアドレスをペグインアドレスと呼びます。

ペグインは誰でも実行可能です。

#### ペグインアドレス
フェデレーションのメンバーと送金者が同意した場合のみアンロック可能なマルチシグのアドレスです。

#### ペグアウト
L-BTCをLiquidのliquidv1ネットワークから、フェデレーションメンバーが管理するPAKリストに登録されているxpubから派生するBitcoinアドレス宛に送金すると、送金したL-BTCがliquid1上から消滅し、同額のBTCがメインネットでアンロックされて指定のアドレスに送金されます。このプロセスをペグアウトと呼びます。PAKリストにアドレスを登録するにはフェデレーションのメンバーになる必要があります。

ほとんどの人はペグアウトできません！


## ペグインアドレスの作成
[ソースコード](https://github.com/ElementsProject/elements)の`src/wallet/rpcwallet.cpp`に、

```cpp
UniValue getpeginaddress(const JSONRPCRequest& request)
```

という関数が定義されていて、ここにペグインアドレスの作成手順が書かれています。以下はその手順の日本語での説明です。

### ベースとなるスクリプト
`fedpegscript`というハードコードされているスクリプトがあり、これがペグインアドレスのベースになっています。

```cpp
// src/chainparams.cpp
class CLiquidV1Params : public CChainParams {
  // 中略
  consensus.fedpegScript = StrHexToScriptWithDefault("745c87635b21020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b678172612102675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af992102896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d4821029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c2102a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc40102102f8a00b269f8c5e59c67d36db3cdc11b11b21f64b4bffb2815e9100d9aa8daf072103079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b2103111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2210318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa08401742103230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de121035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a62103bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c2103cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d175462103d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d4248282103ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a5f6702c00fb275522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb5368ae", default_script);
```

このスクリプトをデコードして、読みやすく整形すると以下のようになります。

```
OP_DEPTH 12
OP_EQUAL
OP_IF
  11     
  020e0338c96a8870479f2396c373... // pubkey
  …
  15
OP_ELSE
  4032
  OP_CHECKSEQUENCEVERIFY
  OP_DROP
  2
  03aab896d53a8e7d6433137bbba… // pubkey
  …
  3
OP_ENDIF
OP_CHECKMULTISIG
```
ペグイン実行時には、バグ対応の先頭要素1つと11の署名がスタックに積まれてこのスクリプトが実行されるので、`OP_IF`節が実行されて11-of-15のマルチシグスクリプトになります。
`OP_ELSE`節は、Liquidネットワークの非常時に非常用の鍵で資金を取り出すためのものっぽいです。

### Tweakedスクリプトの作成
上のスクリプトをベースにTweakedスクリプトを作成します。

まず、送金者のpubkeyからP2WPKHのWitness Programを`Hash160(pubkey)`で計算して、Native Witness ProgramのscriptPubKeyを、`OP_0 + Witness Programの長さ + Witness Program`で作ります。
その上で、then節に入っているpubkeyそれぞれに対して以下を実行します。

1. HMAC SHA256をpubkeyとscriptPubKeyで計算して結果をtweakとする
1. `secp256k1_ec_pubkey_tweak_add`で、pubkeyにtweakを足す（`secp256k1_ec_pubkey_tweak_add`はsecp256k1上でpubkeyが表す点にG * tweakを足している）
1. 足した結果からpubkeyを新たに作成して、元のpubkeyと置き換える

### Tweakedスクリプトからペグインアドレスを作成
ペグイントランザクションでは、P2SH Witness Programが使われているので、その形でアドレスを作成します。

1. TweakedスクリプトのSHA256を計算して、Witness Programを作成
1. `OP_0 + sWitness Programの長さ + Witness Program`でredeemScriptを作成
1. `Hash160(redeeemScript)`で、BitcoinアドレスのPayloadを計算
1. Version byte 5でPayloadをBase58Checkにエンコードしてアドレスを作成

## サンプルコード
bitcoinjsとsecp256k1ライブラリを使うので、```npm install bitcoinjs-lib secp256k1```等でインストールしてください。

```javascript
const Bit = require("bitcoinjs-lib")
const Secp256k1 = require("secp256k1")
const Crypto = require("crypto")

const fedPegScript = Buffer.from("745c87635b21020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b678172612102675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af992102896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d4821029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c2102a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc40102102f8a00b269f8c5e59c67d36db3cdc11b11b21f64b4bffb2815e9100d9aa8daf072103079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b2103111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2210318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa08401742103230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de121035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a62103bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c2103cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d175462103d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d4248282103ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a5f6702c00fb275522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb5368ae", "hex")
const pubkey = Buffer.from("0299a6050c058ab298209bb1cb0bf93b14b4ed884960d45194c7de0c9e5f5fa140", "hex")

function tweakFedPegScript(script, scriptPubKey) {
  let p = 0
  const st = {
    next: (n) => {
      if (!n) return script[p++]
      const slice = script.slice(p, p + n)
      p += n
      return slice
    },
  }
  const tweakedScript = Buffer.alloc(script.length)
  let inOP_IF = false
  let q = 0

  while(true) {
    const code = st.next()
    if (code === undefined) break

    if (0x01 <= code && code <= 0x4b) {
      tweakedScript[q++] = code
      let slice = st.next(code)

      // tweak if the slice is pubkey inside OP_IF clause
      if (code === 33 && inOP_IF) {
        const hmac = Crypto.createHmac("sha256", slice)
        const tweak = hmac.update(scriptPubKey)
        const tweakedPubkey = Secp256k1.publicKeyTweakAdd(slice, tweak.digest())
        slice = tweakedPubkey
      }
      const sliceBeg = q
      for(let i=0; i<slice.length; i++) {
        tweakedScript[sliceBeg+i] = slice[i]
      }
      q += slice.length
      continue
    }
    if (code === 0x63) {
      inOP_IF = true
    } else if (code === 0x67) {
      inOP_IF = false
    }
    tweakedScript[q++] = code
  }
  return tweakedScript
}

const p2wpkhWitProg = Bit.crypto.hash160(pubkey)
const scriptPubKey = Buffer.concat([
  Buffer.from([0, p2wpkhWitProg.length]),
  p2wpkhWitProg,
])
const tweakedScript = tweakFedPegScript(fedPegScript, scriptPubKey)
const witProg32 = Bit.crypto.sha256(tweakedScript)
const redeemScript = Buffer.concat([
  Buffer.from([0, witProg32.length]),
  witProg32,
])
const payload = Bit.crypto.hash160(redeemScript)
const PARENT_SCRIPT_ADDRSSS = 5
const mainchain_address = Bit.address.toBase58Check(payload, PARENT_SCRIPT_ADDRSSS)

console.log({
  mainchain_address: mainchain_address.toString("hex"),
  claim_script: scriptPubKey.toString("hex"),
})
```
以上です。ではまた。