[Solana] Wallet情報をrecoverする

2022.06.21

Introduction

Solanaとは、スマートコントラクト機能を備えた
パブリックブロックチェーンプラットフォームです(暗号通貨はSOL)。

ライブラリもいろいろ提供されており、
プログラムから情報を取得したり暗号資産の送受信もできます。
そういったシステムの特性上、Wallet/鍵の管理は重要です。
今回はプログラムからWalletにアクセスする方法について紹介します。

Wallet & Keypair

SolanaでSOLトークンを受信できるようにする場合、
Wallet(ウォレット)を作成する必要があります。
一般的にWalletは、暗号資産の所有権を管理するためのデバイスまたはアプリケーションです。
このWalletは特殊なハードウェアだったりコンピュータのファイルだったりします。
これとか。

Keypairは、(solana-keygenコマンドとかで)生成された秘密鍵とその暗号化された公開鍵です。
公開鍵(pubkey)は、Walletのアドレスとして使用します。
このアドレスは共有しても問題ありません。
(暗号資産の送受信に必要)
ブロックチェーンによってはアドレス情報からWallet情報を取得可能です。
※Wallet情報の変更やトークン引き出しは不可

秘密鍵は、暗号通貨の送信やWallet情報の変更するための署名に必要となります。
公開鍵と違い、秘密鍵は絶対に​​共有してはいけません。
秘密鍵が漏洩すると、トークン全部持っていかれます。
また、秘密鍵を紛失した場合は
そのアドレスのトークンが永久に失われるので取り扱いに注意しましょう。

mnemonic

他のブロックチェーンではシークレットリカバリフレーズとか
シードフレーズとよばれることもあります。
これはWalletアプリ自体を復元するためのキーワードです。
試しに、Solana CLIをつかって確認してみましょう。

% solana-keygen new --no-outfile
Generating a new keypair

For added security, enter a BIP39 passphrase

NOTE! This passphrase improves security of the recovery seed phrase NOT the
keypair file itself, which is stored as insecure plain text

BIP39 Passphrase (empty for none):

======================================================================
pubkey: XXXXXXXXXXXXXXXXXXXXXXXX
======================================================================
Save this seed phrase and your BIP39 passphrase to recover your new keypair:
ribbon grant banner error ahead pole vital top real feel tomato duck
======================================================================

solana-keygen newで新しいキーペアを作成しています。
公開鍵とmnemonic(ribbonとかgrantとかの12の単語)が表示されてます。
ちなみに、--language japaneseオプションをつけると日本語のmnemonicが生成されます。

では、プログラムからWalletやKeypair情報にアクセスしてみます。

Environment

  • OS : MacOS 12.4
  • Node : v18.2.0
  • yarn : 1.22.15

Setup

Solana CLI toolのインストールをします。

% sh -c "$(curl -sSfL https://release.solana.com/v1.10.26/install)"

%solana --version
solana-cli 1.10.25 (src:d64f6808; feat:965221688)

Try

まずはサンプルとなるキーペアを生成。

% solana-keygen new --no-bip39-passphrase -o ./test-walllet.json

Generating a new keypair

Wrote new keypair to ./test-walllet.json
===========================================================================
pubkey: <公開鍵>
===========================================================================
Save this seed phrase and your BIP39 passphrase to recover your new keypair:
< mnemonic >
===========================================================================

CLIでmnemonicから復元するには↓のコマンドを実行し、mnemonicを入力すれば復元可能です。

% solana-keygen recover 'prompt://?key=0/0' -o ./test-wallet-recover.json
[recover] seed phrase:

TypeScriptでKeypairのrecover

次はTypescriptでmnemonicやSecretKeyからKeypairを復元してみます。
まずは必要パッケージのインストールをします。

% yarn add @solana/web3.js bip39  bs58 @solana/wallet-adapter-wallets @solana/wallet-adapter-base ed25519-hd-key

そして確認用プログラム。npx ts-node test.tsで実行できます。

//test.ts
import { Keypair } from "@solana/web3.js";
import * as bip39 from "bip39";
import * as bs58 from "bs58";
import { derivePath } from "ed25519-hd-key";
import assert from "assert";

const derivePathStr = "m/44'/501'/0'/0'";


/////////////////////////////////////////////////////////
// these words is very important. do not share!!!!!!
/////////////////////////////////////////////////////////

//wallet アドレス。公開鍵
const address = '<public key>';

//秘密鍵(phantomの秘密鍵エクスポートで出せたりする)
const secret_key = '<secret key>';

//mnemonic
const mnemonic ="<secret recovery phrase>";

/////////////////////////////////////////////////////////


function restore_from_mnemonic(mnemonic:string) : Keypair {
    const seed = bip39.mnemonicToSeedSync(mnemonic);
    const seedBuffer = Buffer.from(seed).toString('hex');
    const derivedSeed = derivePath(derivePathStr, seedBuffer).key;
    const keypair = Keypair.fromSeed(derivedSeed);
    return keypair;
}


function restore_from_secret(secret_key:string) : Keypair {
    return Keypair.fromSecretKey(bs58.decode(secret_key));
}

//秘密鍵からKeypair復元
const keypair_from_secret = restore_from_secret(secret_key);
console.log(`from_secret.publicKey   : ${keypair_from_secret.publicKey.toBase58()}`);
console.log(`from_secret.secretKey   : ${bs58.encode(keypair_from_secret.secretKey)}`);

//mnemonicからKeypair復元
const keypair_from_mnemonic = restore_from_mnemonic(mnemonic);
console.log(`from_mnemonic.publicKey : ${keypair_from_mnemonic.publicKey.toBase58()}`);
console.log(`from_mnemonic.secretLey : ${bs58.encode(keypair_from_mnemonic.secretKey)}`);

//assert
assert(keypair_from_secret.publicKey.toBase58() == keypair_from_mnemonic.publicKey.toBase58());
assert(bs58.encode(keypair_from_secret.secretKey) == bs58.encode(keypair_from_mnemonic.secretKey));
assert(address == keypair_from_secret.publicKey.toBase58());
assert(secret_key == bs58.encode(keypair_from_secret.secretKey));

秘密鍵からKeypairを復元するにはKeypair.fromSecretKeyを使用。
mnemonicからの復元はmnemonicToSeedSync(非同期版のmnemonicToSeedもある)
を使います。

References