🌊

Descriptor wallet で Multisig

2021/03/27に公開

bitcoin wallet、時代は Descriptor

bitcoin wallet は Descriptor の時代に突入しました。
ということで、Descriptor Wallet の実装 Bitcoin Dev Kit(BDK) を使って、Mulltisig に lock された bitcoin を unlock してみようと思います。
Desciptor 対応している wallet はこちらから確認できます。
Wallets Supporting Output Descriptors

Descriptor とは

Descriptor とは、Output descriptor や Output Script descriptor とも呼ばれ、その名の通り Output Script を表現するある形式のことです。
まだ BIP にはなっていませんが、このコンセプトが機能し、標準化できそうであれば BIP になるのではないでしょうか。
wallet は従来 key を保管していました。多くの BIP32 対応している wallet では、すべての key を決定的に導出する素になる mnemonic が実質的には key です。
そのため、mnemonic をバックアップすることがもとめられ、BIP32 対応している wallet であれば mnemonic をもとに復元することができます。
しかし、BIP32 は key の導出方法しか定義していないので、アドレスや Script の情報を表現する力はありません。
通常の P2WPKH であれば、key から Script の導出は決定的なので、key まで導出できれば復元可能ですが、Multisig など Script が複雑なものになった場合、mnemonic だけでは同じ状態を復元できません。
そこで Script を組み立てるのに必要な情報を表現する形式の標準として提案され、Bitcoin Core の wallet でも実装されているのが Descriptor です。
Core が実装する Descriptor の仕様はこちら
次の文字列は、P2WSH 2-of-3 Multisig output を表現する Descriptor です。

wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))

BDK で Multisig を unlock する

今回は、WSH の 2-of-2 Multisig に lock します。
そして、それを unlock する transaction を作成します。
2つの key は Alice と Bob がそれぞれ1つずつもっているものとします。
Alice の Desciptor は次のように書くことができます。

"wsh(multi(2,tprv8ZgxMBicQKsPdc8QKZCWq74PDg5s8QFKbMvDCJ439k7LkuzrJc2WQJneeBWkHK8U9zLyw5sq4S3ZJwPwG67fut7Pe4XTjWF9yCMsnJioXiK/84h/1h/0h/0/*,[f18b357e/84h/1h/1h]tpubDCee5uGtrRdeeWSRuee72eroAMS2XBFFYoHxVQHCiEL78WGiPopfJd2Whg5dE8fW7NrXk23yYf6n5MGtoS2avzAeLmZ9jQDSAoqXg1VRdiK/0/*))"

1番目の xpriv key が Alice の extended private key(xprv) で、2番目の xpub は Bob の extended public key(xpub) です。Alice は Bob の private key はわかりませんので、public key になります。
xprv に続いて、BIP32 の derivation path を指定することで、どの key で lock するかを指定します。
Descriptor は pkh()wsh() といった SCRIPT と、key や derivation path を表現する KEY から構成されます。(ADDRHEX もある)
KEY は optional で冒頭に key に関する情報をつけくわえることができ、[] で囲います。
Bob の xpub はそのような形式で、xpub が derive された元の xprv の finger print と元の xprv から xpub の derivation path が示されています。

Multisig アドレスを生成する

Descriptor をもとに、BDK で wallet を作成します。

Alice

let descriptor = "wsh(multi(2,tprv8ZgxMBicQKsPdc8QKZCWq74PDg5s8QFKbMvDCJ439k7LkuzrJc2WQJneeBWkHK8U9zLyw5sq4S3ZJwPwG67fut7Pe4XTjWF9yCMsnJioXiK/84h/1h/0h/0/*,[f18b357e/84h/1h/1h]tpubDCee5uGtrRdeeWSRuee72eroAMS2XBFFYoHxVQHCiEL78WGiPopfJd2Whg5dE8fW7NrXk23yYf6n5MGtoS2avzAeLmZ9jQDSAoqXg1VRdiK/0/*))";
let change_descriptor = "wsh(multi(2,tprv8ZgxMBicQKsPdc8QKZCWq74PDg5s8QFKbMvDCJ439k7LkuzrJc2WQJneeBWkHK8U9zLyw5sq4S3ZJwPwG67fut7Pe4XTjWF9yCMsnJioXiK/84h/1h/0h/1/*,[f18b357e/84h/1h/1h]tpubDCee5uGtrRdeeWSRuee72eroAMS2XBFFYoHxVQHCiEL78WGiPopfJd2Whg5dE8fW7NrXk23yYf6n5MGtoS2avzAeLmZ9jQDSAoqXg1VRdiK/1/*))";

let alice_wallet = Wallet::new(
    &descriptor,
    Some(&change_descriptor),
    Network::Regtest,
    MemoryDatabase::default(),
    ElectrumBlockchain::from(Client::new("127.0.0.1:50001"))
)?;

Bob

let descriptor = "wsh(multi(2,[3dac5985/84h/1h/0h]tpubDCfUf4BCGu7zFFYDcEKEz3mqDrAqZxmiRjk8mNwXcGWuqEAfxP2ku3uwEQNq3fra8C5E3dWj1wE6PZCW5g37kEZtovvMxsxjswCW31MZhFi/0/*,tprv8ZgxMBicQKsPeq2pQSD2QotT6PMJmZgbuGFbu4dNi8R22T9Rrnp8XTY34WmVZN3WKKaVWhnnQ5FdkgNNTQeyfDreEj3KrDJWBkVMAJgw8Tq/84h/1h/1h/0/*))";
let change_descriptor = "wsh(multi(2,[3dac5985/84h/1h/0h]tpubDCfUf4BCGu7zFFYDcEKEz3mqDrAqZxmiRjk8mNwXcGWuqEAfxP2ku3uwEQNq3fra8C5E3dWj1wE6PZCW5g37kEZtovvMxsxjswCW31MZhFi/1/*,tprv8ZgxMBicQKsPeq2pQSD2QotT6PMJmZgbuGFbu4dNi8R22T9Rrnp8XTY34WmVZN3WKKaVWhnnQ5FdkgNNTQeyfDreEj3KrDJWBkVMAJgw8Tq/84h/1h/1h/1/*))";

let bob_wallet = Wallet::new(
    &descriptor,
    Some(&change_descriptor),
    Network::Regtest,
    MemoryDatabase::default(),
    ElectrumBlockchain::from(Client::new("127.0.0.1:50001"))
)?;

Multisig に lock する

このように Wallet を生成すると、Alice と Bob で同じ Multisig address を導出することができます。
このアドレスに、適当に lock します。
この Descriptor では、最初の address は bcrt1qwjkwnfy88apa9a7axhma6k24uw8em4zn36xw7ntgtlazm3avw8tsw40jr2 になります。

let alice_addr = wallet.get_new_address();
let bob_addr = wallet.get_new_address();

assert_eq!(alice_addr, bob_addr);

Multisig を unlock する

bcrt1qwjkwnfy88apa9a7axhma6k24uw8em4zn36xw7ntgtlazm3avw8tsw40jr2 から unlock して bcrt1qj5skjfedlrnfv8ttccjt59r46278rw7wx2ls2z に 1000 satoshi を lock するとします。

Alice signs the PSBT

let satoshi = 1000;
let address = String::from("bcrt1qj5skjfedlrnfv8ttccjt59r46278rw7wx2ls2z");
let lock_into = Address::from_str(address).unwrap();

let (tx, _details) = {
    let mut tx_builder = alice_wallet.build_tx();
    tx_builder.add_recipient(lock_into.script_pubkey(), satoshi);
    tx_builder.finish().unwrap()
};

let (alice_signed_psbt, _finalized) = alice_wallet.sign(tx, None).unwrap();

Bob signs PSBT

let (signed_psbt, _finalized) = bob_wallet.sign(alice_signed_psbt, None).unwrap();

Serialization

16進数表記にシリアライズする場合。

use bdk::bitcoin::consensus::encode::{serialize_hex, deserialize};

let tx = signed_psbt.extract_tx();
println!("Signed Hex Tx: {:?}", &serialize_hex(&tx));

Further readings

bitcoin/descriptors.md at master · bitcoin/bitcoin
bitcoin/release-notes.md at 0.21 · bitcoin/bitcoin
Rethinking Wallet Architecture: Native Descriptor Wallets - YouTube
What's Coming To The Bitcoin Core Wallet in 0.21
Descriptors in the wild :: Bitcoin Dev Kit

フィードバックお待ちしています!

Discussion