Bitcoin ウォレットを把握する

# 概要
Bitcoin のウォレット[^1]を多様な形（エントロピー、ニーモニック、シード、拡張鍵、など）で把握するためのツールを作成してみました。

[^1]:『鍵・アドレスの管理』、『アドレスの状況監視（残高・入出金履歴）』、『トランザクションデータの生成・署名～P2Pネットワークへのブロードキャスト（出金）』の３つの機能を持つソフトウェアや機器をウォレットと呼ぶことが多いですが、本稿でいうウォレットは **暗号資産の持ち主$\approx$秘密鍵の持ち主**の大原則にもとづいた狭義のウォレット（ ＝ 鍵・アドレスのセット）を意味します。

- Bitcoin アドレスの生成過程を確認・理解する。（コメント少ないですがソースは1000行程度なので PowerShell に精通している方なら読み解けると思います）。
- 別のウォレットアプリに引っ越すために必要な情報（拡張鍵など）を取得する。
- HD ウォレット版の Brain Wallet を作成する。
- powershell のクラス作成のサンプルとして利用する。

### 動作環境

下記の環境にて動作確認しています。
- Windows PowerShell 5.1
- PowerShell 7.X

### 長所
-  **Window標準のPowerShellを利用しているため外部のライブラリを必要としません。** Windows 10 の PC があれば動きます。
     (PowerShell 7系 の場合は RIPEMD160 の Nugetパッケージが必要です。) 

### 短所
ビットコイン以外の仮想通貨には対応していません。

### 備考
- ~~ビットコインアドレスの残高を照会する関数 `GetBalance()` においてネットワークアクセスを行ないます。その他の処理はローカルのみで動作します。~~ 
**BitcoinWallet.ps1 はネットワークへのアクセスは一切行ないません**（V02.27で`GetBalance()`関数を BitcoinWallet から削除し、BitcoinTransaction.ps1 に移動）。
- 念のため Windows Sandbox 内でネットワークを無効化して使用されることをお薦めします。（ご使用のPCがマルウェアに感染しているリスクを考慮）
デスクトップに下記の内容のテキストファイルに .wsb の拡張子を付けて保存し、ダブルクリックするとネットワークを無効化した状態で Sandbox を立ち上げることができます。

```NoNetwork.wsb
<Configuration>
  <LogonCommand>
    <Command>powershell -Command "Set-ExecutionPolicy -Scope CurrentUser RemoteSigned"</Command>
  </LogonCommand>
  <MappedFolders>
    <MappedFolder>
      <HostFolder>C:\Users\{ユーザID}\Desktop\{読み書き可能フォルダ名}</HostFolder>
    </MappedFolder>
    <MappedFolder>
      <HostFolder>C:\Users\{ユーザID}\Desktop\{参照専用フォルダ名}</HostFolder>
      <ReadOnly>true</ReadOnly>
    </MappedFolder>
  </MappedFolders>
  <MemoryInMB>4096</MemoryInMB>
  <Networking>Disable</Networking>
</Configuration>
```

# コード
### 関数・クラスの定義

[コード（ BitcoinWallet.ps1 ）を表示するにはここをクリック](http://earthdiver.net/download/BitcoinWallet.ps1)

### ワードリスト
[英語](http://earthdiver.net/download/wordlist.txt)
[日本語](http://earthdiver.net/download/wordlist_jp.txt)


# 実行例

### 事前準備
【PowerShell 7系の場合】
下記のコマンドで RIPEMD160 の Nugetパッケージを入手しアセンブリ参照を有効化します。

```
Install-Package RIPEMD160 -Source https://www.nuget.org/api/v2     # 初回のみ実行
```

【Windows PowerShell 5.1/PowerShell 7系 共通】
下記コマンドを実行して関数・クラスの定義を読み込みます（Execution Policy のエラーが出る場合は[こちら](https://qiita.com/earthdiver1/items/b3f529c81f5892972a72)を参照）。

```PowerShell:
. ./BitcoinWallet.ps1
```

ワードリストは PowerShell スクリプトと同じディレクトリに置いてください。

### HD ウォレット用のニーモニック、シード の生成

乱数のバイト配列[^3]（エントロピー）を引数として `GetMnemonic` 関数を実行することでBIP39のニーモニックコード（mnemonic code）[^4]が得られます。次にそれを引数として `PBKDF2` 関数を実行して HD ウォレットを生成するためのシード（seed）を生成します。

electrum のニーモニック生成には対応していませんが、electrum のニーモニックからのシード生成は PBKDF2 関数に指定する salt を（「electrum」+ パスフレーズ）とすることで対応可能です[^5]。

[^3]: 通常 16バイトまたは 32バイト（ニーモニックの単語数 12個 または 24個に対応）
[^4]: , シードフレーズ（seed phrase）、ニーモニックフレーズ(mnemonic phrase)など様々な呼び方があります。
[^5]: 非 ANSI 文字列に対する electrum のノーマライズ処理が BIP39 と異なるため、日本語のニーモニックではうまくいかない可能性があります。ちなみに、electrum の派生パスは、受取用アドレスが "m/0'/0/n"、お釣り用アドレスが "m/0'/1/n" となります。


##### スクリプト

```PowerShell:
echo "<<< BIP39 >>>"
$entropy   = @()                                                       # エントロピー（バイト配列形式）、16以上の4の倍数個
$entropy_h = "00000000000000000000000000000000"                        # エントロピー（HEX形式）、       32以上の8の倍数桁
$entropy_b = ""                                                        # エントロピー（２進数形式）、   128以上の32の倍数桁
$entropy_d = ""                                                        # エントロピー（サイコロ形式、各桁1～6の数字）
#$entropy_d = "12345612345612345612345612345612345612345612345612345"  # 　 50＋α回サイコロを振ること (128bit)、αは3程度
                                                                       # 　100＋α回サイコロを振ること (256bit)、αは6程度

$passphrase = ""

if ( $entropy ) {
   # dummy
} elseif ( $entropy_h ) {
   $entropy = h2i $entropy_h
} elseif ( $entropy_b ) {
   $entropy = b2i $entropy_b
} elseif ( $entropy_d ) {
   $nbits   = if ( $entropy_d.Length -lt 100 ) { 128 } else { 256 }
   $nbytes  = $nbits / 8
#  $SHA256  = New-Object Security.Cryptography.SHA256CryptoServiceProvider                               # for compatibility with coldcard, seedsigner, 
#  $entropy = $SHA256.ComputeHash( [Text.Encoding]::ASCII.GetBytes( $entropy_d ) )[0..($nbytes-1)]       #  krux, iancoleman, etc.
   $ndigits = [Math]::Ceiling( [bigint]::Log( [bigint]::Pow( 2, $nbits ) - 1, 6 ) )
   $n = 0
   do {
      if ( $entropy_d.Length -lt $n + $ndigits ) { throw "need more rolls" }
      $i = [bigint]::Zero
      foreach ( $c in $entropy_d.Substring( $n, $ndigits ).ToCharArray() ) {
         $digit = "123456".IndexOf( $c )
         if ( $digit -lt 0 ) { throw "invalid character '$c'" }
         $i = $i * 6 + $digit
      }
      $n++
   } while ( $i -ge [bigint]::Pow( 2, $nbits ) )
   $entropy = h2i ( $i.ToString( "x" ) -replace '^0' ).PadLeft( "0", $nbytes )
} else {
   # エントロピーの指定がない場合は乱数から生成（ワードの数を24個にするには $nbits を256に変更）
   $nbits   = 128
   $nbytes  = $nbits / 8
   $entropy = [byte[]]::new( $nbytes )
   [Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes( $entropy )
}
if ( -not $entropy_b ) { $entropy_b = i2b $entropy }
if ( -not $entropy_h ) { $entropy_h = i2h $entropy }

$mnemonic  = GetMnemonic $entropy

# 必要に応じて ニーモニック を直接設定してください。
#$mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"

if ( -not ( ValidateMnemonic $mnemonic ) ) { throw "invalid mnemonic phrase" }

$HMACSHA512 = New-Object Security.Cryptography.HMACSHA512
$seed = PBKDF2 $mnemonic "mnemonic$passphrase" 2048 64 $HMACSHA512    # electrum の場合は saltに "electrum$passphrase" を指定

echo "Entropy(Binary) : $entropy_b"
echo "Entropy(Hex)    : $entropy_h"
echo "BIP39 Mnemonic  : $mnemonic"
echo "BIP39Passphrase : $passphrase"
echo "Seed            : $seed"


echo "<<< BIP32 >>>"
$version        = "0488ade4"     # BIP32 private key
$depth          = "00"
$pFingerprint   = "00000000"     # 親の公開鍵のハッシュ値の冒頭4バイト、ルートの場合は "00000000"
$childnumber    = "00000000"
$HMACSHA512.Key = [Text.Encoding]::UTF8.GetBytes("Bitcoin seed")
$extendedkey    = i2h $HMACSHA512.ComputeHash( ( h2i $seed ) )
$privatekey     = "00" + $extendedkey.Substring( 0, 64 )
$chaincode      =        $extendedkey.Substring( 64 ,64 )
$serialized     = $version + $depth + $pFingerprint + $childnumber + $chaincode + $privatekey
$xprv           = Base58Check_Encode $serialized
echo "BIP32 Root Key  : $xprv"
```

##### 出力

```
<<< BIP39 >>>
Entropy(Binary) : 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Entropy(Hex)    : 00000000000000000000000000000000
BIP39 Mnemonic  : abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
BIP39Passphrase :
Seed            : 5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4
<<< BIP32 >>>
BIP32 Root Key  : xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu
```

### HD ウォレットの情報取得

様々な派生パス（derivation path）に対応するアドレスを出力します。
また、HD ウォレットの各アカウントの拡張秘密鍵・拡張公開鍵を出力したり、逆にインポートすることが可能です。

##### スクリプト

```PowerShell:
echo "<<< HD Wallets BIP44,49,84,86 >>>"
$seed = "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4"
$w = [HDWallet]::new( $seed )
echo "BIP32 Root Private Key  : $($w.GetExtendedPrivateKey())"

echo ""
echo "P2PKH (Legacy)"
$w.Derive(44,$true).Derive(0,$true).Derive(0,$true).Path
$w.Derive(44,$true).Derive(0,$true).Derive(0,$true).GetExtendedPrivateKey()
$w.Derive(44,$true).Derive(0,$true).Derive(0,$true).GetExtendedPublicKey()
$w.Derive(44,$true).Derive(0,$true).Derive(0,$true).Derive(0,$false).Derive(0,$false).Path
$w.Derive(44,$true).Derive(0,$true).Derive(0,$true).Derive(0,$false).Derive(0,$false).GetWIF()
$w.Derive(44,$true).Derive(0,$true).Derive(0,$true).Derive(0,$false).Derive(0,$false).GetAddressP2PKH()

echo ""
echo "P2SH-P2WPKH (Nested Segwit)"
$w.Derive(49,$true).Derive(0,$true).Derive(0,$true).Path
$w.Derive(49,$true).Derive(0,$true).Derive(0,$true).GetExtendedPrivateKey()
$w.Derive(49,$true).Derive(0,$true).Derive(0,$true).GetExtendedPrivateKey() | Set-Variable yprv
$w.Derive(49,$true).Derive(0,$true).Derive(0,$true).GetExtendedPublicKey()
$w.Derive(49,$true).Derive(0,$true).Derive(0,$true).Derive(0,$false).Derive(0,$false).Path
$w.Derive(49,$true).Derive(0,$true).Derive(0,$true).Derive(0,$false).Derive(0,$false).GetWIF()
$w.Derive(49,$true).Derive(0,$true).Derive(0,$true).Derive(0,$false).Derive(0,$false).GetAddressP2SHP2WPKH()

echo ""
echo "P2WPKH (Native Segwit)"
$w.Derive(84,$true).Derive(0,$true).Derive(0,$true).Path
$w.Derive(84,$true).Derive(0,$true).Derive(0,$true).GetExtendedPrivateKey()
$w.Derive(84,$true).Derive(0,$true).Derive(0,$true).GetExtendedPublicKey()
$w.Derive(84,$true).Derive(0,$true).Derive(0,$true).GetExtendedPublicKey() | Set-Variable zpub
$w.Derive(84,$true).Derive(0,$true).Derive(0,$true).Derive(0,$false).Derive(0,$false).Path
$w.Derive(84,$true).Derive(0,$true).Derive(0,$true).Derive(0,$false).Derive(0,$false).GetWIF()
$w.Derive(84,$true).Derive(0,$true).Derive(0,$true).Derive(0,$false).Derive(0,$false).GetAddressP2WPKH()

echo ""
echo "P2TR (Taproot)"
$w.Derive(86,$true).Derive(0,$true).Derive(0,$true).Path
$w.Derive(86,$true).Derive(0,$true).Derive(0,$true).GetExtendedPrivateKey()
$w.Derive(86,$true).Derive(0,$true).Derive(0,$true).GetExtendedPublicKey()
$w.Derive(86,$true).Derive(0,$true).Derive(0,$true).Derive(0,$false).Derive(0,$false).Path
$w.Derive(86,$true).Derive(0,$true).Derive(0,$true).Derive(0,$false).Derive(0,$false).GetWIF()
$w.Derive(86,$true).Derive(0,$true).Derive(0,$true).Derive(0,$false).Derive(0,$false).GetAddressP2TR()

echo ""
echo "BIP49拡張秘密鍵のインポート"
( $y = [HDWallet]::new() ).ImportExtendedKey( $yprv, "m/49'/0'/0'" )
$y.GetExtendedPrivateKey()
$y.GetExtendedPublicKey()
$y.Derive(0,$false).Derive(0,$false).Path
$y.Derive(0,$false).Derive(0,$false).GetWIF()
$y.Derive(0,$false).Derive(0,$false).GetAddressP2SHP2WPKH()

echo ""
echo "BIP84拡張公開鍵のインポート"
( $z = [HDWallet]::new() ).ImportExtendedKey( $zpub, "m/84'/0'/0'" )
$z.GetExtendedPrivateKey()
$z.GetExtendedPublicKey()
$z.Derive(0,$false).Derive(0,$false).Path
$z.Derive(0,$false).Derive(0,$false).GetWIF()
$z.Derive(0,$false).Derive(0,$false).GetAddressP2WPKH()
```

##### 出力

```
<<< HD Wallets BIP44,49,84,86 >>>
BIP32 Root Private Key  : xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu

P2PKH (Legacy)
m/44'/0'/0'
xprv9xpXFhFpqdQK3TmytPBqXtGSwS3DLjojFhTGht8gwAAii8py5X6pxeBnQ6ehJiyJ6nDjWGJfZ95WxByFXVkDxHXrqu53WCRGypk2ttuqncb
xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj
m/44'/0'/0'/0/0
L4p2b9VAf8k5aUahF1JCJUzZkgNEAqLfq8DDdQiyAprQAKSbu8hf
1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA

P2SH-P2WPKH (Nested Segwit)
m/49'/0'/0'
yprvAHwhK6RbpuS3dgCYHM5jc2ZvEKd7Bi61u9FVhYMpgMSuZS613T1xxQeKTffhrHY79hZ5PsskBjcc6C2V7DrnsMsNaGDaWev3GLRQRgV7hxF
ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP
m/49'/0'/0'/0/0
KyvHbRLNXfXaHuZb3QRaeqA5wovkjg4RuUpFGCxdH5UWc1Foih9o
37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf

P2WPKH (Native Segwit)
m/84'/0'/0'
zprvAdG4iTXWBoARxkkzNpNh8r6Qag3irQB8PzEMkAFeTRXxHpbF9z4QgEvBRmfvqWvGp42t42nvgGpNgYSJA9iefm1yYNZKEm7z6qUWCroSQnE
zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs
m/84'/0'/0'/0/0
KyZpNDKnfs94vbrwhJneDi77V6jF64PWPF8x5cdJb8ifgg2DUc9d
bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu

P2TR (Taproot)
m/86'/0'/0'
xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk
xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ
m/86'/0'/0'/0/0
KyRv5iFPHG7iB5E4CqvMzH3WFJVhbfYK4VY7XAedd9Ys69mEsPLQ
bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr

BIP49拡張秘密鍵のインポート
yprvAHwhK6RbpuS3dgCYHM5jc2ZvEKd7Bi61u9FVhYMpgMSuZS613T1xxQeKTffhrHY79hZ5PsskBjcc6C2V7DrnsMsNaGDaWev3GLRQRgV7hxF
ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP
m/49'/0'/0'/0/0
KyvHbRLNXfXaHuZb3QRaeqA5wovkjg4RuUpFGCxdH5UWc1Foih9o
37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf

BIP84拡張公開鍵のインポート
PrivateKey is unknown.

zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs
m/84'/0'/0'/0/0
PrivateKey is unknown.

bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu
```

### マルチシグアドレス（2-of-2）の導出例

マルチシグはセキュリティ観点で非常にお薦めです。

##### スクリプト

```PowerShell:
$HMACSHA512 = New-Object Security.Cryptography.HMACSHA512
$SHA256     = New-Object Security.Cryptography.SHA256CryptoServiceProvider

$mnemonic1   = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
$passphrase1 = ""
if ( -not ( ValidateMnemonic $mnemonic1 ) ) { throw "invalid mnemonic phrase (1)" }
$seed1   = PBKDF2 $mnemonic1 "mnemonic$passphrase1" 2048 64 $HMACSHA512
$wallet1 = [HDWallet]::new( $seed1 )
$fp1     = $wallet1.FingerPrint

write-Host "mnemonic1        : $mnemonic1"
Write-Host "seed1            : $seed1"

$mnemonic2   = "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong"
$passphrase2 = ""
if ( -not ( ValidateMnemonic $mnemonic2 ) ) { throw "invalid mnemonic phrase (2)" }
$seed2   = PBKDF2 $mnemonic2 "mnemonic$passphrase2" 2048 64 $HMACSHA512
$wallet2 = [HDWallet]::new( $seed2 )
$fp2     = $wallet2.FingerPrint
write-Host "mnemonic2        : $mnemonic2"
Write-Host "seed2            : $seed2"
Write-Host

# P2WSH (BIP-0011,0048,0067,0141)
$Zprv1   = $wallet1.Derive(48,$true).Derive(0,$true).Derive(0,$true).Derive(2,$true).GetExtendedPrivatekey()
$Zpub2   = $wallet2.Derive(48,$true).Derive(0,$true).Derive(0,$true).Derive(2,$true).GetExtendedPublickey()   # co-signer's extended public key
( $w1 = [HDWallet]::new() ).ImportExtendedKey( $Zprv1, "m/48'/0'/0'/2'" )
( $w2 = [HDWallet]::new() ).ImportExtendedKey( $Zpub2, "m/48'/0'/0'/2'" )
$xprv1   = $w1.GetExtendedPrivatekey( $false, $true )
$xpub1   = $w1.GetExtendedPublickey( $false, $true )
$xpub2   = $w2.GetExtendedPublickey( $false, $true )
$dscrptr = "wsh(sortedmulti(2,[$($fp1)/48h/0h/0h/2h]$($xpub1)/<0;1>/*,[$($fp2)/48h/0h/0h/2h]$($xpub2)/<0;1>/*))"
$dscrptr = descsum_create $dscrptr
$Zpub1   = $w1.GetExtendedPublickey()
$prvkey1 = $w1.Derive(0,$false).Derive(0,$false).PrivateKey
$pubkey1 = $w1.Derive(0,$false).Derive(0,$false).PublicKey
$pubkey2 = $w2.Derive(0,$false).Derive(0,$false).PublicKey
$pk = @( $pubkey1, $pubkey2 ) | Sort-Object
$witnessScript = "52" + "21" + $pk[0] + "21" + $pk[1] + "52" + "ae" # OP_2 PUSH(pubkey) PUSH(pubkey) OP_2 OP_CHECKMULTISIG
$scriptHash    = i2h $SHA256.ComputeHash( ( h2i $witnessScript ) )
$address = Bech32_Encode $scriptHash "bc" $false 0
Write-Host "P2WSH (Native Segwit)"
Write-Host " derivation path : m/48'/0'/0'/2'"
Write-Host "   xprv1         : $xprv1"
Write-Host "   Zprv1         : $Zprv1"
Write-Host "   Zpub1         : $Zpub1"
Write-Host "   Zpub2         : $Zpub2"
Write-Host "   output desc.  : $dscrptr"
Write-Host " derivation path : m/48'/0'/0'/2'/0/0"
Write-Host "   privatekey1   : $prvkey1"
Write-Host "   publickey1    : $pubkey1"
Write-Host "   publickey2    : $pubkey2"
Write-Host "   address       : $address"
Write-Host

# P2SH-P2WSH (BIP-0011,0048,0067,0141)
$Yprv1   = $wallet1.Derive(48,$true).Derive(0,$true).Derive(0,$true).Derive(1,$true).GetExtendedPrivatekey()
$Ypub2   = $wallet2.Derive(48,$true).Derive(0,$true).Derive(0,$true).Derive(1,$true).GetExtendedPublickey()  # co-signer's extended public key
( $w1 = [HDWallet]::new() ).ImportExtendedKey( $Yprv1, "m/48'/0'/0'/1'" )
( $w2 = [HDWallet]::new() ).ImportExtendedKey( $Ypub2, "m/48'/0'/0'/1'" )
$xprv1   = $w1.GetExtendedPrivatekey( $false, $true )
$xpub1   = $w1.GetExtendedPublickey( $false, $true )
$xpub2   = $w2.GetExtendedPublickey( $false, $true )
$dscrptr = "sh(wsh(sortedmulti(2,[$($fp1)/48h/0h/0h/1h]$($xpub1)/<0;1>/*,[$($fp2)/48h/0h/0h/1h]$($xpub2)/<0;1>/*)))"
$dscrptr = descsum_create $dscrptr
$Ypub1   = $w1.GetExtendedPublickey()
$prvkey1 = $w1.Derive(0,$false).Derive(0,$false).PrivateKey
$pubkey1 = $w1.Derive(0,$false).Derive(0,$false).PublicKey
$pubkey2 = $w2.Derive(0,$false).Derive(0,$false).PublicKey
$pk = @( $pubkey1, $pubkey2 ) | Sort-Object
$witnessScript = "52" + "21" + $pk[0] + "21" + $pk[1] + "52" + "ae"
$redeemScript = "0020" + ( i2h $SHA256.ComputeHash( ( h2i $witnessScript ) ) )  # 0020: witnessversion(0) + push32bytes
$scriptHash = HASH160 $redeemScript
$address = Base58Check_Encode ( "05" + $scriptHash ) 
Write-Host "P2SH-P2WSH (Nested Segwit)"
Write-Host " derivation path : m/48'/0'/0'/1'"
Write-Host "   xprv1         : $xprv1"
Write-Host "   Yprv1         : $Yprv1"
Write-Host "   Ypub1         : $Ypub1"
Write-Host "   Ypub2         : $Ypub2"
Write-Host "   output desc.  : $dscrptr"
Write-Host " derivation path : m/48'/0'/0'/1'/0/0"
Write-Host "   privatekey1   : $prvkey1"
Write-Host "   publickey1    : $pubkey1"
Write-Host "   publickey2    : $pubkey2"
Write-Host "   address       : $address"
Write-Host

# P2SH (BIP-0011,0016,0045,0067)
$xprv1   = $wallet1.Derive(45,$true).GetExtendedPrivatekey()
$xpub2   = $wallet2.Derive(45,$true).GetExtendedPublickey()  # co-signer's extended public key
( $w1 = [HDWallet]::new() ).ImportExtendedKey( $xprv1, "m/45'" )
( $w2 = [HDWallet]::new() ).ImportExtendedKey( $xpub2, "m/45'" )
$xpub1   = $w1.GetExtendedPublickey()
$dscrptr = "sh(sortedmulti(2,[$($fp1)/45h]$($xpub1)/<0;1>/*,[$($fp2)/45h]$($xpub2)/<0;1>/*))"
$dscrptr = descsum_create $dscrptr
$prvkey1 = $w1.Derive(0,$false).Derive(0,$false).PrivateKey                    # BlueWallet, Sparrow
$pubkey1 = $w1.Derive(0,$false).Derive(0,$false).PublicKey                     # BlueWallet, Sparrow
$pubkey2 = $w2.Derive(0,$false).Derive(0,$false).PublicKey                     # BlueWallet, Sparrow
#$prvkey1 = $w1.Derive(0,$false).Derive(0,$false).Derive(0,$false).PrivateKey   # Electrum (BIP-0045)
#$pubkey1 = $w1.Derive(0,$false).Derive(0,$false).Derive(0,$false).PublicKey    # Electrum (BIP-0045)
#$pubkey2 = $w2.Derive(0,$false).Derive(0,$false).Derive(0,$false).PublicKey    # Electrum (BIP-0045)
$pk = @( $pubkey1, $pubkey2 ) | Sort-Object
$redeemScript = "52" + "21" + $pk[0] + "21" + $pk[1] + "52" + "ae"
$scriptHash = HASH160 $redeemScript
$address = Base58Check_Encode ( "05" + $scriptHash ) 
Write-Host "P2SH (Legacy)"
Write-Host " derivation path : m/45'"
Write-Host "   xprv1         : $xprv1"
Write-Host "   xpub1         : $xpub1"
Write-Host "   xpub2         : $xpub2"
Write-Host "   output desc.  : $dscrptr"
Write-Host " derivation path : m/45'/0/0"
Write-Host "   privatekey1   : $prvkey1"
Write-Host "   publickey1    : $pubkey1"
Write-Host "   publickey2    : $pubkey2"
Write-Host "   address       : $address"
```

##### 出力

```
mnemonic1        : abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
seed1            : 5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4
mnemonic2        : zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong
seed2            : b6a6d8921942dd9806607ebc2750416b289adea669198769f2e15ed926c3aa92bf88ece232317b4ea463e84b0fcd3b53577812ee449ccc448eb45e6f544e25b6

P2WSH (Native Segwit)
 derivation path : m/48'/0'/0'/2'
   xprv1         : xprv9zktm1yWCFjfcYr7nqM9UqDK3Vhyf15f3iGgoGfuchETardTk1tPmdh1iKBs86XJiatoijkrXoxcKM2pwZwozGuyEkvectBcwU6RKRafo4D
   Zprv1         : ZprvAqKWVb3n4aNzjiPjQDPNj5k87E38kbkFCCxoHKjDkUpdKEpq1jbo6ssDZS4WgM43mLCQ6Gx6LM4DD5sigBvnjEPqouRuC7HbNeVagXvJcnz
   Zpub1         : Zpub74Jru6aftwwHxCUCWEvP6DgrfFsdA4U6ZRtQ5i8qJpMcC39yZGv3egBhQfV3MS9pZtH5z8iV5qWkJsK6ESs6mSzt4qvGhzJxPeeVS2e1zUG
   Zpub2         : Zpub75rAwNSrvDKNvAomunzgBvnBJs6VXV8LAmNrjAqPzjaygZtqWLxuuNoBQYAVY8hjNJpThXKKpei18636Q34ExQPyYg9wQwxo7PsgR4puq6J
   output desc.  : wsh(sortedmulti(2,[73c5da0a/48h/0h/0h/2h]xpub6DkFAXWQ2dHxq2vatrt9qyA3bXYU4ToWQwCHbf5XB2mSTexcHZCeKS1VZYcPoBd5X8yVcbXFHJR9R8UCVpt82VX1VhR28mCyxUFL4r6KFrf/<0;1>/*,[3f635a63/48h/0h/0h/2h]xpub6FHZCoNb3tg3o1GAJQxSwgFNF8mLRtTk2GgkF7n5rwzoxBhUEdFWa8cyZRHqytAzKZWsKz8627cQEMCCfR5GDSv6yXegqirpgDUX41Pxybr/<0;1>/*))#h5gmmm94
 derivation path : m/48'/0'/0'/2'/0/0
   privatekey1   : 070abb37477a5c4d8b19fb51193391b9dccff485e083926cb17e24500b77bfe1
   publickey1    : 03dc1953c2756c7c58d4f48ca1bbba767f414fd236bf4d662b67721ac626c514e0
   publickey2    : 0254689c4a585b8f3d9ddea8e9e68d0e8f49f75709fa6e7d075657573815958a81
   address       : bc1qkpcfs6lhqh3rx63we7435nf3hqpghkdtmmmvln0yrssv0ulhmngs92dens

P2SH-P2WSH (Nested Segwit)
 derivation path : m/48'/0'/0'/1'
   xprv1         : xprv9zktm1yWCFjfZsFLLAGfvizHJeXouxM4QQgBa6xqzJfHjqaoyF4DjsxM3Z2fjqeapN3s7MnFsbyxKUv6tdx8rkyuzsQ1QQwhhHFERbKagvT
   Yprv1         : YprvAWVFBvNrutqWqjbq7BXGxtRbCQiX4w29dnr5Gm8Gk5saR7xwzJc4T4UQsTwjJBXQTUEejRNwDUj1Kw9RuZX6oUnBhgCqPjEBrjakQ7n377y
   Ypub1         : Ypub6jUbbRukkGPp4DgJDD4HL2NKkSZ1UPk111mg59XtJRQZHvJ6XqvJzrntik9U4jCFQkgrBqevdKLPMdYZXU9KAGhKpMhW5XujwqiQ7Csmm4Z
   Ypub2         : Ypub6m1udhmwmXmu3q67JtfZ47n8GXzLFvUKS3CYsiwqMRMfCtJqb6TTXQLr6WNJoS197Tfddky5V6pnVhA9TxKShDZXC2f8aFu5qzKjnp1svff
   output desc.  : sh(wsh(sortedmulti(2,[73c5da0a/48h/0h/0h/1h]xpub6DkFAXWQ2dHxnMKoSBogHrw1rgNJKR4umdbnNVNTYeCGcduxWnNUHgGptqEQWPKRmeW4Zn4FHSbLMBKEWYaMDYu47Ytg6DdFnPNt8hwn5mE/<0;1>/*,[3f635a63/48h/0h/0h/1h]xpub6FHZCoNb3tg3mxjcXsQx1xLpNmod6woECf2fB4nQbe9NXbvha2ucpDpnGbTFF68KUMUr1hNQ9E5jVEvpT2kUkVmFVDrJawcbgXzDpJc2hkF/<0;1>/*)))#qjhtfzps
 derivation path : m/48'/0'/0'/1'/0/0
   privatekey1   : 19ac8a44091e75114e7d19a9c3865714d0eb25addacae7cb66b124ff5d8bb3ed
   publickey1    : 03abe5ccc0a6ddf20e02e27ca4829a4bcf288849f5d137db19559dd2ab23236dbe
   publickey2    : 02cb377cd72240ed59a548aa1c60d1103327f1812148296ccafbe9fe4b77c4dbe0
   address       : 377ZxuoEGhULpY6guCr44cojfMLsVKwyRC

P2SH (Legacy)
 derivation path : m/45'
   xprv1         : xprv9ukW2Usuz4v9W8vSjhtwZMqJeE3wq6pFCpozehginiMx1DXPM18rvqPenZdKsthKwqiXexB4Hcp1S1j4tJrFLhsgZdgvfSUtFK8XyS5BtWK
   xpub1         : xpub68jrRzQopSUSiczuqjRwvVn3CFtSEZY6a3jbT66LM3tvt1rXtYT7Udi8dt3m1qj3q8pKZjt7tqrSt7bRN4LD2vSVq1167PSA5AyM31FUHwU
   xpub2         : xpub68MXAZN5xcN3naSp29rp7gtiW8Srp3u1MegfWpt8aV7UuypzE4jxR3RDtr5DzHK9a2tDtDb7vfftKLfuX2Fi2E9PoHT1XxV6QQKKBPaPi23
   output desc.  : sh(sortedmulti(2,[73c5da0a/45h]xpub68jrRzQopSUSiczuqjRwvVn3CFtSEZY6a3jbT66LM3tvt1rXtYT7Udi8dt3m1qj3q8pKZjt7tqrSt7bRN4LD2vSVq1167PSA5AyM31FUHwU/<0;1>/*,[3f635a63/45h]xpub68MXAZN5xcN3naSp29rp7gtiW8Srp3u1MegfWpt8aV7UuypzE4jxR3RDtr5DzHK9a2tDtDb7vfftKLfuX2Fi2E9PoHT1XxV6QQKKBPaPi23/<0;1>/*))#3vxpfp4v
 derivation path : m/45'/0/0
   privatekey1   : 7b8b83e3e856dc692cffb134551e81f5f7274b34fb5e8a9dbd8997d88602f078
   publickey1    : 02928ce56c258522767df7385d9a5f4beaf599d310f52b510a4cf01c762749bb7a
   publickey2    : 03edb9dd0fa07a8ac85c9be2a27db8497768435f911bed34555ca1d62962a4a5de
   address       : 3Q6xNscq5ukuyYEBY2wiGATs6HrsWGmEnw
```

### 従来型の Brain Wallet

従来型[^6]の Brain Wallet は大変危険と言われています。少なくとも下記のような対策[^7]をとった方がよいでしょう。後述の HD ウォレット版の方がより安全で利便性も高いです。
- 出版物から拾った文言をそのまま使用しない（自分なりのアレンジを加える。例えば「我輩は猫である」⇒「我輩はnekoである」）。
- 出来るだけ長いパスフレーズを使用する。(同じフレーズの繰り返しでも可)
- 生成方法をアレンジする（ハッシュの回数を増やすなど。例：１回のみ　⇒ 100000回[^8]繰り返す）

[^6]: `https://brainwalletx.github.io/`、`https://bitaddress.org/`、`https://coinb.in/#newAddress` などで作成機能が提供されています。
[^7]: [こちら](https://keybase.io/warp)のWarpWalletでは別のアプローチで従来型の Brain Wallet がセキュアな形に発展されています。
[^8]: ハッシュ回数を増やすことには処理時間を長くして Brute force されにくくする効果もありますので 100000 回以上回してしまいましょう。

##### スクリプト

下記は危険な Brain Wallet の例です（上記の対策が講じられていません）。

```PowerShell:
echo "<<< Classic Brain Wallet >>>"

$BWPassphrase = "我輩は猫である"     # 暗記パスフレーズ
$bytes = [Text.Encoding]::UTF8.GetBytes( $BWPassphrase )
$NumOfHashings = 1                   # SHA-256 の適用回数
$hash = $bytes
$SHA256 = New-Object Security.Cryptography.SHA256CryptoServiceProvider
0..($NumOfHashings - 1) | % { $hash = $SHA256.ComputeHash( $hash ) }

$privatekey = i2h $hash
$wif        = GetWIF $privatekey
$publickey  = GetPublicKey $privatekey
$pubkeyHash = Hash160 $publickey
$address    = GetAddressP2PKH $publickey

echo "Passphrase        : $bwPassphrase"
echo "Private Key (Hex) : $privatekey"
echo "WIF Private Key   : $wif"
echo "Public Key        : $publickey"
echo "Public Key Hash   : $pubkeyHash"
echo "Compressed Address: $address"
```

注：Windows PowerShell 5.1 なら Shift-JIS、PowerShell 7系 なら BOM付きUTF-8 のエンコーディングでファイルに保存してください。

##### 出力

```
<<< Classic Brain Wallet >>>
Passphrase        : 我輩は猫である
Private Key (Hex) : 8afc1ea26b8e95f9bea48bd55b71ad63e8d1bda3e955e874c00e2f9ed004ae8f
WIF Private Key   : L1ssx4Wpd5LgAg8zuKUryY3xSrYuCosBiuzj4xLSK8hDrMAj3XJA
Public Key        : 03e995482ddcf3eb775e382d5a9ea420251b213ac209c13aed859d7f8dbebeb5a3
Public Key Hash   : 5e04286bdc5c7bf45d4e48c16dbbf2972591a9b6
Compressed Address: 19a7VvosV6mAaiH1qcx64T51zdsAzNyfDg
```

### HD ウォレット版の Brain Wallet

暗記パスフレーズのハッシュ値をエントロピーに用います[^9]。下記例では、従来型 Brain Wallet の項に記載した対策を反映しています。念のためにBIP39のパスフレーズ（PBKDF2 の salt に追加されるもの）も設定するとよいでしょう。生成方法をアレンジしすぎて忘れてしまったら意味がないので注意してください。

[^9]: 同様のコンセプトのサービスを提供しているサイトが[ここ](https://brain2bip.com/)にありますが、利用している bip39の Javascript ライブラリ（jsbip39.js）のバグの影響を受けているようです。

##### スクリプト

```PowerShell:
echo "<<< BIP39 >>>"

$BWPassphrase = "我輩はnekoである"                               # 暗記パスフレーズ

$SHA256  = New-Object Security.Cryptography.SHA256CryptoServiceProvider
$bytes = [Text.Encoding]::UTF8.GetBytes( $BWPassphrase * 2 )     # フレーズを２回繰り返し
$NumOfHashings = 100000                                          # SHA-256 の適用回数
$hash = $bytes
0..($NumOfHashings - 1) | % { $hash = $SHA256.ComputeHash( $hash ) }

$entropy    = $hash[0..15]
$entropy_b  = i2b $entropy
$entropy_h  = i2h $entropy

$mnemonic   = GetMnemonic $entropy
if ( -not ( ValidateMnemonic $mnemonic ) ) { throw "invalid mnemonic phrase" }

$passphrase = $BWPassphrase                                       # 念のため BIP39のパスフレーズも設定

$HMACSHA512 = New-Object Security.Cryptography.HMACSHA512
$seed       = PBKDF2 $mnemonic "mnemonic$passphrase" 2048 64 $HMACSHA512

echo "Entropy(Binary) : $entropy_b"
echo "Entropy(Hex)    : $entropy_h"
echo "BIP39 Mnemonic  : $mnemonic"
echo "BIP39Passphrase : $passphrase"
echo "Seed            : $seed"
```

注：Windows PowerShell 5.1 なら Shift-JIS、PowerShell 7系なら BOM付きUTF-8 のエンコーディングでファイルに保存してください。

##### 出力

```
<<< BIP39 >>>
Entropy(Binary) : 11110100100111001011000110101000101101011111000010010110000101011111010000000010100101010011001101101101111001100100000111000010
Entropy(Hex)    : f49cb1a8b5f09615f40295336de641c2
BIP39 Mnemonic  : virus tortoise health hip annual april source famous cricket hundred motor lunch
BIP39Passphrase : 我輩はnekoである
Seed            : 9a883797785c70651a49be513cfd57e61596961067e6e84b167f3b18f604da87dba44692bf328fdd044d8d245c389dedaae856b2cfa7436cbf1b662af3f33ff3
```

### 決定論的エントロピー（BIP-85）
簡単に言うと、1つのニーモニックから多数の子ニーモニック（子ウォレット）を作成する方法です。子ニーモニックは仮に失われても、親ニーモニックから同じものを再度導出することができます。一方、子ニーモニックから親ニーモニックや他の子ニーモニックを知ることはできません。親ニーモニックを知らない人には、別々の子ニーモニックはそれぞれが全く無関係のように見えます。用途としては、例えば以下のシナリオが考えられます。

1. 親ニーモニックを金属プレートに打刻し、安全な場所に保管します（ウォレットとしては使用しません）。
1. 子ニーモニックに紐付くウォレット（子ウォレット、複数可）の方を実際に使用します。

これにより、複数のウォレットを扱う場合のニーモニックの管理や新しいウォレット（＝別の子ウォレット）への移行が容易になります。応用例としては、１つのニーモニックからマルチシグに使用する複数のウォレットを生成したり、前述のHDウォレット版 Brain Wallet をカスタマイズすることなどが考えられます。

##### スクリプト
子ニーモニックに再度BIP-85を適用することで孫ニーモニックを作成することも可能です。
下記スクリプトは子世代のインデックス：3、孫世代のインデックス：1、ひい孫世代のインデックス：2009とした場合のひい孫ウォレットのニーモニック（12ワード）を導出する例です（だいぶマニアックになってきました）。

```PowerShell:
$wordList   = 0 # 1 for Japanese
$words      = 12
$mnemonic   = "install scatter logic circle pencil average fall shoe quantum disease suspect usage"
$passphrase = ""
$HMACSHA512 = New-Object Cryptography.HMACSHA512
$seed       = PBKDF2 $mnemonic "mnemonic$passphrase" 2048 64 $HMACSHA512
$ng         = 3
$indices    = @( 3, 1, 2009 )
for ( $i=0; $i -lt $ng; $i++ ) {
    $w    = [HDWallet]::new( $seed )
    $key  = $w.Derive( 83696968, 1 ).Derive( 39, 1 ).Derive( $wordList, 1 ).Derive( $words, 1 ).Derive( $indices[$i], 1 ).PrivateKey
    $HMACSHA512.Key = [Text.Encoding]::UTF8.GetBytes( "bip-entropy-from-k" )
    $entropy = ( $HMACSHA512.ComputeHash( ( h2i $key ) ) )[0..($words*4/3-1)]
    if ( $wordList -eq 1 ) {
        $new_mnemonic = GetMnemonic -j $entropy
    } else {
        $new_mnemonic = GetMnemonic    $entropy
    }
    $seed = PBKDF2 $new_mnemonic "mnemonic" 2048 64 $HMACSHA512
}
echo "original mnemonic code : $mnemonic"
echo "new      mnemonic code : $new_mnemonic"
```

##### 出力

```
original mnemonic code : install scatter logic circle pencil average fall shoe quantum disease suspect usage
new      mnemonic code : system allow lawn wagon dove abstract keep general explain weapon romance settle
```

### サイレントペイメント（BIP-352）
寄付金を募る場合などアドレスを公開すると、そのアドレスに送金された全ての取引内容が他人からも見えてしまいますが、BIP-352のサイレントペイメント（Silent Payments）の利用により受取人以外見えないようすることが出来ます。また、アドレスの使い回しは通常推奨されませんが、サイレントペイメントでは同一のアドレスに繰り返し送金しても実際の送金先は別個のTaprootアドレスとなります（mainnetの場合`sp1～`で始まるアドレスを使用します）。
2024年5月現在この機能を実装しているウォレットはほとんどありませんが、BlueWalletがサポートを予定しているようなので、他のウォレットも追従してくる可能性が高いと思います。

##### スクリプト
サイレントペイメントのアドレスを出力するサンプルコードです。
（ラベル番号0はお釣り専用アドレスです）

```PowerShell:
###########
$network    = "mainnet"
$mnemonic   = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
$passphrase = ""
$labels     = @( "", "0", "1", "2" )
###########
if ( -not ( ValidateMnemonic $mnemonic ) ) { throw "invalid mnemonic phrase" }
if ( $network -eq "mainnet" ) {
    $coinType = 0
    $hrp      = "sp"
} else {
    $coinType = 1
    $hrp      = "tsp"
}
$seed = PBKDF2 $mnemonic "mnemonic$passphrase" 2048 64 (New-Object Security.Cryptography.HMACSHA512)
$w = [HDWallet]::new( $seed )
$B_Spend_priv = $w.Derive(352,$true).Derive($coinType,$true).Derive(0,$true).Derive(0,$true).Derive(0,$false).PrivateKey
$B_Spend_pub  = $w.Derive(352,$true).Derive($coinType,$true).Derive(0,$true).Derive(0,$true).Derive(0,$false).PublicKey
$B_Scan_priv  = $w.Derive(352,$true).Derive($coinType,$true).Derive(0,$true).Derive(1,$true).Derive(0,$false).PrivateKey
$B_Scan_pub   = $w.Derive(352,$true).Derive($coinType,$true).Derive(0,$true).Derive(1,$true).Derive(0,$false).PublicKey
echo "spend_priv_key      : $B_Spend_priv"
echo " scan_priv_key      : $B_Scan_priv"
foreach ( $label in $labels ) {
    if ( $label -eq "" ) {
        $B_m_pub = $B_Spend_pub
    } else {
        $SHA256 = New-Object Cryptography.SHA256CryptoServiceProvider
        $tag    = [Text.Encoding]::UTF8.GetBytes( "BIP0352/Label" )
        $hash   = $SHA256.ComputeHash( $SHA256.ComputeHash( $tag ) * 2 + ( $B_Scan_priv + ([UInt32]$label).ToString( "x8" ) | h2i ) )
        $tweak  = [bigint]::new( $hash[31..0] + @(0x00) )
        $G      = [ECDSA]::new()
        $k      = [bigint]::Parse( "0" + $B_Spend_priv, "AllowHexSpecifier" )
        $B      = $G * $k
        $B_m    = $B + $G * $tweak
        if ( $B_m -eq $null ) { throw "The resulting address is invalid." }
        $prefix = if ( $B_m.Y.IsEven ) { "02" } else { "03" }
        $B_m_pub = $prefix + ( $B_m.X.ToString( "x64" ) -replace '^0(?=[0-9a-f]{64}$)' )
    }
    $silentAddress = Bech32_Encode ( $B_Scan_pub + $B_m_pub ) $hrp $true 0
    $comment = if ( $label -eq "0" ) { " # for change" } else { "" }
    echo "address (label:$($label.PadLeft( 4, ' ' ))): $silentAddress $comment"
}
```

##### 出力

```
spend_priv_key      : c88567742d5019d7ccc81f6e82cef8ef01997a6a3761cc9166036b580549539b
 scan_priv_key      : 78e7fd7d2b7a2c1456709d147021a122d2dccaafeada040cc1002083e2833b09
address (label:    ): sp1qqfqnnv8czppwysafq3uwgwvsc638hc8rx3hscuddh0xa2yd746s7xqh6yy9ncjnqhqxazct0fzh98w7lpkm5fvlepqec2yy0sxlq4j6ccc3h6t0g
address (label:   0): sp1qqfqnnv8czppwysafq3uwgwvsc638hc8rx3hscuddh0xa2yd746s7xqc7cztt86v30fp7s8rdq99v4vxl5vs4r6naeha9dgwu38c2aszwlqdmad77  # for change
address (label:   1): sp1qqfqnnv8czppwysafq3uwgwvsc638hc8rx3hscuddh0xa2yd746s7xqlh4r025th3htxgguxeck4qha04e0rhw33t8m2kpmqju28zzplwmgx2h7dd
address (label:   2): sp1qqfqnnv8czppwysafq3uwgwvsc638hc8rx3hscuddh0xa2yd746s7xqu7dz87allsc3jgt0wfc7taewfz3p45jp4fm7qaazx94ekyu97elqhn62v5
```

# トランザクションデータ出力

Hex トランザクションデータを出力する関数を作成してみました。
トランザクションについて理解したい方の参考になれば幸いです（testnetでご使用ください）。トランザクションの作成とそれに必要な署名の方法については下記の各サイト（最初の３つ以外は英語サイト）が参考になります（Taprootについては現状 BIP-340,341,342 を読み解くしかなさそう）。

- https://bc-2.jp/archive/season1/materials/0102_BitcoinのTransactionとは_修正版.pdf
- https://bc-2.jp/archive/season1/materials/0102_Transactionの作成.pdf
- https://bc-2.jp/archive/season1/materials/0204_Segwitの特徴と利用.pdf
- https://learnmeabitcoin.com/technical/transaction-data
- https://learnmeabitcoin.com/technical/ecdsa
- https://medium.com/@ottosch/manually-creating-and-signing-a-bitcoin-transaction-87fbbfe46032
- https://medium.com/@ottosch/creating-and-signing-a-segwit-transaction-from-scratch-ec98577b526a

P2Pネットワークへのブロードキャストには、[こちら](https://en.bitcoin.it/wiki/Transaction_broadcasting)に掲載のサイトが利用できます[^10]。

[^10]: 一部サイトは testnet に対応していないようです。

##### コード

[BitcoinTransaction.ps1](http://earthdiver.net/download/BitcoinTransaction.ps1)
- RawTXfromLegacyAddress（レガシーアドレスからの送金）
- RawTXfromSegwitAddress（Segwit v0 アドレスからの送金）
- RawTXfromTaprootAddress（Taproot アドレスからの送金）

注：これらの関数は addressFrom に指定されたアドレスの UTXO[^11] 情報を取得する目的で mempool.space, blockcypher.com にアクセスします。

[^11]: Unspent Transaction Output（未使用のトランザクション出力）の略。

##### 実行例

`RawTXfromSegwitAddress <WIF> <addressFrom> <addressTo> <amount> <fee> [<witnessScript>] [<addressChange>] [<memo>]`

※ addressFrom が P2SH, P2WSH（P2SH の入れ子になっている場合を含む）, P2TR の script path のアドレスの場合は、redeem/witness/tap script (unlocking script) の指定が必要です。これらに `single` を指定すると `PUSH(pubkey)` `OP_CHECKSIG` に置き換えられた後実行されます。マルチシグには非対応です（addressFrom へのマルチシグアドレスの指定不可）。
※ addressTo が `script:`で始まる場合はそれに続く文字列が scriptPubKey[^12]（locking script）と見なされます。
※ addressFrom の UTXO のうち、最も value の大きいアウトポイントが優先して消費されます（手数料の節約を優先）。

[^12]: Legacy の場合は P2SH、Segwitの場合は P2WSH、Taprootの場合は single-leaf script path の P2TR（key pathなし）となります。

```
PS> . ./BitcoinWallet.ps1
PS> . ./BitcoinTransaction.ps1
PS> RawTXfromSegwitAddress cPNcwavkjQKzPMt8Akpahkkr44HX4fEzqYs37XSGyKRrJKxMS27g tb1qsxk9kkdtyvfzmlg7lf0fd8sa8ttwxpngm9anw6 tb1pxmvm8hs9wlffw2ml7tpgd8r6qnu4txr7rrw3yxgsqxaf0vqq3jzsa7a730 10000 500 "" tb1q0e729ksqd9v9zgdzvjdfs835dgxs6kkcde86dn
02000000000101f7e6cb02f84018ef881888e44ce5ffc6f2a7d177af1b4326c0c151e76957b8e70100000000fdffffff02102700000000000022512036d9b3de0577d2972b7ff2c2869c7a04f955987e18dd12191001ba97b0008c8520e80600000000001600147e7ca2da0069585121a2649a981e346a0d0d5ad802483045022100b64137477eb2f19428775a2c22e9551952479ab46ee6ac24328895de70efbbef022060991cc57d2002424a0aac2cc0dad36d06ca37f3177bb120a8c9219de46d5e21012103f9a72977f996f9ef084f5cc611fa58d4b481f62753e2c511004917abb57c3d7b00000000
```

　
上記出力結果を Decoder で解析した結果（一部省略）
```
{
    "addresses": [
        "tb1q0e729ksqd9v9zgdzvjdfs835dgxs6kkcde86dn",
        "tb1qsxk9kkdtyvfzmlg7lf0fd8sa8ttwxpngm9anw6",
        "tb1pxmvm8hs9wlffw2ml7tpgd8r6qnu4txr7rrw3yxgsqxaf0vqq3jzsa7a730"
    ],
    "fees": 500,
    "inputs": [
        {
            "addresses": [
                "tb1qsxk9kkdtyvfzmlg7lf0fd8sa8ttwxpngm9anw6"
            ],
            "output_index": 1,
            "output_value": 463140,
            "prev_hash": "e7b85769e751c1c026431baf77d1a7f2c6ffe54ce4881888ef1840f802cbe6f7",
            "script_type": "pay-to-witness-pubkey-hash",
            "sequence": 4294967293,
            "witness": [
                "3045022100b64137477eb2f19428775a2c22e9551952479ab46ee6ac24328895de70efbbef022060991cc57d2002424a0aac2cc0dad36d06ca37f3177bb120a8c9219de46d5e2101",
                "03f9a72977f996f9ef084f5cc611fa58d4b481f62753e2c511004917abb57c3d7b"
            ]
        }
    ],
    "opt_in_rbf": true,
    "outputs": [
        {
            "addresses": [
                "tb1pxmvm8hs9wlffw2ml7tpgd8r6qnu4txr7rrw3yxgsqxaf0vqq3jzsa7a730"
            ],
            "script": "512036d9b3de0577d2972b7ff2c2869c7a04f955987e18dd12191001ba97b0008c85",
            "script_type": "pay-to-taproot",
            "value": 10000
        },
        {
            "addresses": [
                "tb1q0e729ksqd9v9zgdzvjdfs835dgxs6kkcde86dn"
            ],
            "script": "00147e7ca2da0069585121a2649a981e346a0d0d5ad8",
            "script_type": "pay-to-witness-pubkey-hash",
            "value": 452640
        }
    ],
}
```
　

クリエイティブ・コモンズ 表示 - 継承 4.0 国際
　
　
[備忘録](http://earthdiver.net/download/bitcoin-scripts.txt)