こんにちは。力道山が刺された日まであと5日なので、力道山の「リキド」に掛けて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リストにアドレスを登録するにはフェデレーションのメンバーになる必要があります。
ほとんどの人はペグアウトできません!
ペグインアドレスの作成
ソースコードのsrc/wallet/rpcwallet.cpp
に、
UniValue getpeginaddress(const JSONRPCRequest& request)
という関数が定義されていて、ここにペグインアドレスの作成手順が書かれています。以下はその手順の日本語での説明です。
ベースとなるスクリプト
fedpegscript
というハードコードされているスクリプトがあり、これがペグインアドレスのベースになっています。
// 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それぞれに対して以下を実行します。
- HMAC SHA256をpubkeyとscriptPubKeyで計算して結果をtweakとする
-
secp256k1_ec_pubkey_tweak_add
で、pubkeyにtweakを足す(secp256k1_ec_pubkey_tweak_add
はsecp256k1上でpubkeyが表す点にG * tweakを足している) - 足した結果からpubkeyを新たに作成して、元のpubkeyと置き換える
Tweakedスクリプトからペグインアドレスを作成
ペグイントランザクションでは、P2SH Witness Programが使われているので、その形でアドレスを作成します。
- TweakedスクリプトのSHA256を計算して、Witness Programを作成
-
OP_0 + sWitness Programの長さ + Witness Program
でredeemScriptを作成 -
Hash160(redeeemScript)
で、BitcoinアドレスのPayloadを計算 - Version byte 5でPayloadをBase58Checkにエンコードしてアドレスを作成
サンプルコード
bitcoinjsとsecp256k1ライブラリを使うので、npm install bitcoinjs-lib secp256k1
等でインストールしてください。
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"),
})
以上です。ではまた。