初めまして。
CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です!
ERCについてひたすらまとめたり、スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。
以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください!
https://twitter.com/cardene777
https://chaldene.net/
https://qiita.com/cardene
https://zenn.dev/heku
https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58
https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4
また、これからも情報を見逃したくないという方はぜひ以下の購読ボタンを教えてください。
更新があった時、登録しているメールアドレス宛に通知が飛ぶようになります。
今回はSolanaについての第3回の記事になります。
Solanaのプログラム派生アドレス(Program Derived Addresses)についてできる限り詳しくまとめていきます。
PDAとは?
プログラム派生アドレス(PDAs: Program Derived Addresses)は、Solanaブロックチェーン上で、特定のプログラムによって生成される特殊なアドレスです。
Solanaのブロックチェーン上でプログラム間のコミュニケーションや、特定のアカウントに対する権限の委譲を可能にします。
これらのアドレスは、プログラムが直接管理するためのアカウントを指すもので、通常のウォレットアドレス(人が持つ秘密鍵に紐づいたアドレス)とは異なります。
PDAsは、スマートコントラクト(プログラム)が特定のルールやロジックに従ってアクセス権を持つアカウントを作成する際に使用されます。
PDAの特徴
秘密鍵が存在しない
PDAには対応する秘密鍵が存在せず、その生成プロセスはある入力(例えば、プログラムID、追加のシード値)から決定論的に行われます。
プログラムによる制御
PDAは特定のプログラムによってのみアクセス可能であり、そのプログラムのロジックに基づいて操作されます。
特定のプログラムによって、PDAに保持されているアカウントを変更することもできます。
クロスプログラム呼び出し
PDAは、クロスプログラム呼び出し(CPI)を介して呼び出される指示内でプログラムによるプログラムアドレスの署名を可能にします。
決定論的生成
PDAsはプログラムIDと追加のシード情報(任意のデータ)を組み合わせることにより、再現可能な方法で生成されます。
これにより、プログラムは同じ入力から常に同じPDAを導出できます。
プログラムIDと追加のシード情報の組み合わせをsha256ハッシュ関数に渡し、ed25519楕円曲線上に存在する公開鍵が生成されるか確認します。
最大50%の確率で楕円曲線上に存在する公開鍵が取得でき、
bump
という追加の入力値を与えて楕円曲線上に存在しないアドレスを取得します。bump
値は255
から始まり、楕円曲線上に存在しないアドレスが見つかるまで254
、253
と値を1つずつ下げていきます。
PDAの用途
PDAsは、プログラムが特定のロジックに基づいて独自の状態や資産を管理するために使用されます。
例えば、以下のような用途があります。
アカウントへの権限委譲
プログラムはPDAを介して、あるアカウントに対する権限を得て、その権限を後で別のアカウントへと移譲することができます。
これは、PDAを使用してトランザクションに署名することにより、権限移譲トランザクションの署名者としてプログラムが機能するため可能です。
マルチシグウォレット
複数のユーザーによる承認が必要な取引を管理するためのアカウント。
DAO(分散型自律組織)
DAOのメンバーシップや投票権を管理するためのアカウント。
デジタル資産の発行
トークンやNFT(ノンファンジブルトークン)など、プログラムが発行・管理するデジタル資産。
DeFiアプリケーションや分散型取引所
多くのDeFiアプリケーションでは、特定のイベントが発生するまで資産をエスクロー代理人に移転する必要があります。
PDAは、資産を勝者に移転することを含む、このような操作を実現するために不可欠です。
エスクロー代理人(Escrow Agent)は、取引の両当事者間で資産や金銭を一時的に保管し、契約条件が満たされたことを確認した後で、それらを指定された当事者に移転する役割を担います。
エスクローサービスは、取引が公正かつ透明に行われることを保証し、特に信頼関係が未確立の当事者間での取引において、リスクを軽減するために使用されます。エスクロー代理人の役割は、主に以下の通りです。
契約条件の確認
取引の両当事者が合意に至った契約条件を確認し、これらが適切に文書化されていることを保証します。
資産の保管
契約条件が満たされるまで、資金、不動産の権利書、株式、またはその他の資産を安全に保管します。
条件の履行の監視
契約条件が両当事者によって満たされたかどうかを監視し、必要なすべての検証作業を行います。
資産の移転
契約条件が満たされたことが確認された場合、エスクロー代理人は資産を売り手から買い手に、または合意に基づいて指定された当事者に移転します。
エスクローサービスは、不動産取引、オンラインマーケットプレイス、法的紛争の解決、投資取引、そしてデジタル通貨やトークンの取引など、多岐にわたる分野で利用されています。
ブロックチェーン技術とスマートコントラクトを活用することで、エスクローサービスはより透明性が高く、自動化されたプロセスを提供することができ、取引の信頼性と効率性を大きく向上させることが可能です。
具体例
賭け事
2人のユーザーがゲームの結果に賭ける場合、彼らはそれぞれの賭けの資産を、合意を履行する中間者に移転する必要があります。
PDAを使用すると、この中間者プログラムが資産を勝者に移転する権限を持つことができます。
分散型取引所
一致する売買注文間で資産を移転する。
オークション
資産を勝者に移転する。
ゲームや予測市場
賞金を集めて、勝者に再分配する。
実装例
SolanaでPDAを生成するプロセスは、プログラム自体と特定のシード情報を組み合わせることで行われます。
以下は、RustでのPDA生成の例です。
use solana_program::{
pubkey::Pubkey,
program_error::ProgramError,
};
// プログラムIDとシードからPDAを生成
let seeds = &["特定のシード情報".as_bytes(), &[特定のバイト]];
let (pda, _bump_seed) = Pubkey::find_program_address(seeds, &プログラムID);
このコードスニペットでは、find_program_address
関数を使用してPDAを生成しています。
ここでseeds
は、PDAを一意にするためのシード情報を含む配列です。
このシードには、文字列や他のアカウントの公開鍵など、任意の情報を使用できます。プログラムID
は、PDAを生成するプログラムの公開鍵(アドレス)です。
この関数は、生成されたPDAと使用されたバンプシード(PDAの生成に使用される追加のシード値)を返します。
bump
を持つプログラムは、PDAを必要とする命令に署名できます。
PDAを使用するプログラムは、invoke_signed
関数を通じて、PDAが必要とする命令に「署名」することができます。
これにより、プログラムはPDAの権限を持つかのようにトランザクションを実行できます。
PDAの生成に使用されたシードとbumpは、PDAを検証するために必要です。
開発者は、これらの情報を利用してPDAの正当性を確認し、プログラムが正しいPDAを使用していることを保証します。
プログラムアドレスの秘密鍵
プログラムアドレスは、一般的な暗号通貨のアドレスや公開鍵とは異なり、特定のプロパティを持っています。
ed25519曲線とプログラムアドレス
ed25519曲線は、デジタル署名などの暗号学的操作に使用される楕円曲線暗号(ECC)の一種です。
通常の公開鍵やアドレスは、この曲線上の点として生成され、それぞれに対応するプライベートキーが存在します。
プログラムアドレスは、このed25519曲線上には存在しません。
これは、プログラムアドレスが特定の計算手順(例えば、create_program_address
関数)を用いて生成されるため、通常のキーペアとは異なり、対応するプライベートキーが存在しないことを意味します。
プログラムアドレスの特徴
プログラムアドレスには有効なプライベートキーが存在しないため、通常の方法でデジタル署名を生成することはできません。
これは、プログラムアドレスが不正アクセスから保護されるべき資産やデータを持つスマートコントラクトやプログラムに紐付けられている場合に、セキュリティ上の利点を提供します。
しかし、プログラムアドレスはプログラムによって「署名者」として使用されることができます。
これは、Solanaのランタイムが特定の操作(例えば、資産の移転やステートの更新)を実行する時に、プログラムアドレスが関連するプログラムによって生成されたものであることを認識し、検証する能力に基づいています。
プログラムアドレスは、プログラムがブロックチェーン上で特定のアクションを代行するために使用される特別なアドレスです。
プライベートキーを持たないため、不正な署名生成が不可能であり、この性質によってセキュリティが強化されます。
また、Solanaプラットフォームは、プログラムが自身のアドレスを「署名者」として使用することを許可することで、プログラムがブロックチェーン上でより複雑な操作を安全に実行できるようにしています。
ハッシュベースで生成されるアドレス
Solanaにおけるプログラムアドレス(Program Addresses)の生成方法に関する説明です。
プログラムアドレスは、特定のシード(データの集合)とプログラムIDを用いて、256ビットの原像計算困難ハッシュ関数を使って決定論的に導出されます。
SHA-256は、セキュアハッシュアルゴリズム(Secure Hash Algorithm)ファミリーの1つであり、暗号学的ハッシュ関数の中で広く使用されています。
このアルゴリズムは、任意の長さのメッセージを入力として受け取り、固定長(256ビット、すなわち32バイト)のハッシュ値を出力します。
SHA-256は、データの整合性を検証したり、デジタル署名の作成に使用されるなど、様々なセキュリティ関連のアプリケーションで利用されています。主な特徴
固定長出力
SHA-256は、入力データの長さに関係なく、常に256ビットのハッシュ値を生成します。
耐衝突性
異なる二つのメッセージから同じハッシュ値を生成することは計算上非常に困難です。
これを「衝突耐性」と言います。
高速性
モダンなコンピュータ上で高速に計算でき、大量のデータを扱う場合でも効率的です。
決定性
同じ入力に対しては、常に同じハッシュ値が生成されます。
用途
デジタル署名
デジタル署名の作成時に、メッセージのハッシュ値を暗号化して署名を生成します。
受信者は、同じハッシュ関数を使用してメッセージのハッシュ値を計算し、公開鍵を使って署名を復号化して、二つのハッシュ値が一致するかを検証します。
データ整合性の検証
データが変更されていないことを確認するために、ファイルやメッセージのハッシュ値を計算し、元のハッシュ値と比較します。
ブロックチェーン
ビットコインを含む多くの暗号通貨で、トランザクションの整合性を保証し、ブロックのリンクを確立するためにSHA-256が使用されます。
SHA-256の計算プロセス
SHA-256は、以下のステップでハッシュ値を計算します。
メッセージの準備
入力データをビット列に変換し、長さをビット単位で追加します(パディング)。
メッセージスケジュールの生成
メッセージを512ビットのブロックに分割し、それぞれのブロックから64ワードのメッセージスケジュールを生成します。
圧縮関数の実行
初期ハッシュ値とメッセージスケジュールを入力として、圧縮関数を64ラウンド実行します。
ハッシュ値の生成
最終ラウンド後のハッシュ値を結合して、最終的な256ビットのハッシュ値を生成します。
SHA-256はそのセキュリティと効率のため、世界中のさまざまな技術分野で信頼されています。
これらのアドレスは、ed25519曲線上に存在しないことが保証されており、これにより関連するプライベートキーが存在しないことが確実になります。
プログラムアドレスの生成
シードとプログラムIDの利用
プログラムアドレスは、シードとプログラムIDからハッシュ関数を通じて生成されます。
このプロセスは、アドレスがed25519曲線上に存在しないように確認しながら実行されます。
ed25519
ed25519は、公開鍵暗号システムにおける楕円曲線暗号(ECC)の一種で、高いセキュリティレベルと効率的な計算能力を提供します。
ed25519は、特にデジタル署名の生成と検証に使用されることが多く、暗号通貨やセキュアな通信プロトコルなど、セキュリティが重要な多くのアプリケーションで採用されています。ed25519の特徴
高速な署名と検証
ed25519は署名の生成と検証が非常に高速で、リソースが限られている環境でも効率的に動作します。
堅牢なセキュリティ
128ビットのセキュリティレベルを提供し、現在知られている攻撃手法に対して強固な耐性を持っています。
短い鍵と署名
ed25519の公開鍵と署名はそれぞれ256ビットと512ビットの長さであり、他の多くの公開鍵暗号方式と比較して短く、通信のオーバーヘッドを減らすことができます。
耐衝突性
異なるメッセージに対して同じ署名が生成される可能性が非常に低いです。
楕円曲線とは
楕円曲線暗号では、特定の楕円曲線上の点の乗算を基本演算として使用します。
ed25519では、Edwards曲線という特定の形式の楕円曲線が使用され、この曲線は安全性と計算効率の良いバランスを提供します。具体例
署名の生成と検証のプロセスは以下の通りです。
鍵生成
秘密鍵から公開鍵を生成します。公開鍵は、楕円曲線上の特定の点として表されます。
署名生成
秘密鍵とメッセージから署名を生成します。署名は、メッセージのハッシュ値と秘密鍵を用いた楕円曲線上の点の計算によって行われます。
署名検証
公開鍵とメッセージ、署名を使用して、署名が正当であるかを検証します。
署名がメッセージと公開鍵に対応している場合、検証は成功します。
使用例
デジタル署名
ドキュメントやトランザクションに署名し、その真正性と完全性を保証します。
セキュアな通信
SSHやTLSなどのセキュアな通信プロトコルで、通信の両端点間の身元確認とデータの暗号化に使用されます。
暗号通貨
ビットコインやイーサリアムなどの多くの暗号通貨で、トランザクションの署名に使用され、資産の安全な移転を保証します。
ed25519は、そのセキュリティと効率の高さから、暗号学的に安全なデジタル署名の生成と検証に広く利用されています。
エラーハンドリング
アドレスが曲線上に見つかった場合、エラーが返されます。
与えられたシードとプログラムIDの組み合わせに対して、この状況が発生する確率は約50/50です。
bumpシードの利用
有効なプログラムアドレスが曲線外に見つからない場合、異なるシードセットやbumpシード(追加の8ビットシード)を使用して、有効なアドレスを見つけることができます。
プログラムアドレスの派生
決定論的アドレス
プログラムは任意の数のアドレスをシードを用いて決定論的に導出することができます。
これにより、アドレスがどのように使用されるかを象徴的に識別することが可能になります。
アドレスの生成関数
create_program_address
関数とfind_program_address
関数は、シードとプログラムIDを基にプログラムアドレスを生成します。
特にfind_program_address
は、有効なオフカーブプログラムアドレスとそのbumpシードを見つけるために使用されます。
pub fn create_with_seed(
base: &Pubkey,
seed: &str,
program_id: &Pubkey,
) -> Result<Pubkey, SystemError> {
if seed.len() > MAX_ADDRESS_SEED_LEN {
return Err(SystemError::MaxSeedLengthExceeded);
}
Ok(Pubkey::new(
hashv(&[base.as_ref(), seed.as_ref(), program_id.as_ref()]).as_ref(),
))
}
この関数は、あるベースの公開鍵(base
)、シード文字列(seed
)、およびプログラムの公開鍵(program_id
)を使用して、新しい公開鍵(Pubkey
)を生成します。
この新しい公開鍵は、通常、アカウントやプログラムアドレスの生成に使われます。
パラメータ
base: &Pubkey
新しい公開鍵の生成に使われるベースとなる公開鍵です。
seed: &str
新しい公開鍵を生成する際に、追加のエントロピー(ランダム性)を提供するための文字列です。
program_id: &Pubkey
新しい公開鍵の生成に関連するプログラムの公開鍵です。
処理の流れ
シード長の検証
まず、与えられたシードの長さが定義された最大長(`MAX_ADDRESS_SEED_LEN`)を超えていないかを検証します。
もし最大長を超えている場合は、`MaxSeedLengthExceeded`エラーを返して処理を終了します。
公開鍵の生成
与えられた
base
、seed
、program_id
を元に、hashv
関数を使って一意のハッシュ値を計算します。このハッシュ値は、新しい公開鍵の生成に使用されます。
hashv関数は、与えられた入力値のベクトルに対してハッシュを計算し、その結果を返します。
新しい公開鍵の返却
最後に、計算されたハッシュ値から新しい
Pubkey
オブジェクトを生成し、これを関数の結果として返します。
戻り値
成功した場合、新しく生成された公開鍵(Pubkey
)を含むOk
が返されます。
エラーが発生した場合(例: シードの長さが最大を超えている場合)、Err
が返され、それには具体的なSystemError
が含まれます。
/// Generate a derived program address
/// * seeds, symbolic keywords used to derive the key
/// * program_id, program that the address is derived for
pub fn create_program_address(
seeds: &[&[u8]],
program_id: &Pubkey,
) -> Result<Pubkey, PubkeyError>
/// Find a valid off-curve derived program address and its bump seed
/// * seeds, symbolic keywords used to derive the key
/// * program_id, program that the address is derived for
pub fn find_program_address(
seeds: &[&[u8]],
program_id: &Pubkey,
) -> Option<(Pubkey, u8)> {
let mut bump_seed = [std::u8::MAX];
for _ in 0..std::u8::MAX {
let mut seeds_with_bump = seeds.to_vec();
seeds_with_bump.push(&bump_seed);
if let Ok(address) = create_program_address(&seeds_with_bump, program_id) {
return Some((address, bump_seed[0]));
}
bump_seed[0] -= 1;
}
None
}
プログラムが特定のアドレスを「所有」することを可能にし、他のアカウントとのやり取りを行う時の関数についての解説です。
create_program_address
関数
目的
シード(シンボリックキーワード)とプログラムIDを使用して、派生プログラムアドレスを生成します。
引数
seeds
アドレス生成のためのシードの配列。
これらはバイト列(
&[u8]
)のスライスとして渡されます。
program_id
このアドレスが派生する元となるプログラムの公開鍵(
Pubkey
)。
戻り値
成功すると派生したプログラムアドレス(
Pubkey
)を返します。アドレスがed25519曲線上に存在する(有効な秘密鍵が存在する)場合にはエラー(
PubkeyError
)を返します。
find_program_address
関数
目的
シードとプログラムIDを使用して、ed25519曲線上にない有効な派生プログラムアドレスとそのバンプシードを見つけ出します。
引数
seeds
アドレス生成のためのシードの配列。
program_id
このアドレスが派生する元となるプログラムの公開鍵。
プロセス
bump_seed
(バンプシード)を初期値(std::u8::MAX
、つまり255)から開始し、0までデクリメントしながらループします。各イテレーションで、現在のバンプシードをシード配列に追加し、
create_program_address
を呼び出して派生アドレスを試みます。有効な派生アドレスが見つかった場合(つまり、アドレスが曲線上にない場合)、そのアドレスと使用されたバンプシードをタプルとして返します。
戻り値
成功すると、有効な派生プログラムアドレスとそのバンプシードのタプルを返します。有効なアドレスが見つからない場合は`None`を返します。
これらの関数は、特定のプログラムがSolanaブロックチェーン上で他のアカウントとの間でセキュアにやり取りするための基盤を提供します。
特に、プログラムが特定のアドレスを「所有」する必要がある場合や、資産のエスクローなど、特定のロジックを実行するために使用されます。
衝突のリスクと対策
衝突の可能性
シードが連続してハッシュされるため、特定のプログラムIDに対してプログラムアドレスの衝突が発生する可能性があります。
プログラム開発者は、シード間で衝突が起こらないように注意深く選択する必要があります。
衝突回避策
ハッシュ衝突に対して脆弱なシードスキームの場合、シード間にセパレーターを挿入することが一般的な対策となります。
これにより、シードの組み合わせによる衝突のリスクを減らすことができます。
Solanaプラットフォームにおいて、プログラムアドレスの生成と管理は、セキュリティと効率性を確保するために重要なプロセスです。
これにより、プログラム間の安全な通信とアセットの管理が可能になります。
プログラムアドレスの使用
PDAを利用することで、プログラムは自身のアドレスを生成し、そのアドレスを用いてトランザクションを実行することができます。
ここでは、そのプロセスを説明するいくつかのコードスニペットが示されています。
アドレス生成してエスクローアカウントにTransfer
// deterministically derive the escrow key
let escrow_pubkey = create_program_address(&[&["escrow"]], &escrow_program_id);
// construct a transfer message using that key
let message = Message::new(vec![
token_instruction::transfer(&alice_pubkey, &escrow_pubkey, 1),
]);
// process the message which transfer one 1 token to the escrow
client.send_and_confirm_message(&[&alice_keypair], &message);
このコードは、Solanaプラットフォーム上でトークンをエスクローアカウントに転送するプロセスを示しています。
クライアント(ユーザーやプログラム)は、create_program_address
関数を使用して目的のアドレス(この場合はエスクローアドレス)を生成し、そのアドレスを使ってトランザクションを行います。
エスクローキーの生成
let escrow_pubkey = create_program_address(&[&["escrow"]], &escrow_program_id);
この行で、create_program_address
関数を呼び出しています。
関数には&[&["escrow"]]
(シードとして使用されるキーワードのリスト)と&escrow_program_id
(エスクロープログラムのID)の2つの引数が渡されます。
この関数は、与えられたシードとプログラムIDから派生したプログラムアドレス(ここではescrow_pubkey
として保持)を生成します。
このアドレスは「楕円曲線上にない」ことが保証されており、それによりプライベートキーが存在しないため、通常の方法ではアクセスできません。
トランザクションメッセージの構築
let message = Message::new(vec![
token_instruction::transfer(&alice_pubkey, &escrow_pubkey, 1),
]);
次に、Message::new
関数を使用して、トークン転送のためのメッセージを構築します。
このメッセージは、token_instruction::transfer
命令を含む配列(ベクター)から成り立っています。
この命令は、Aliceのアカウント(alice_pubkey
)からエスクローアカウント(escrow_pubkey
)へ1トークンを転送するよう指示します。
メッセージの送信と確認
client.send_and_confirm_message(&[&alice_keypair], &message);
最後に、client.send_and_confirm_message
関数を呼び出して、先ほど構築したメッセージをブロックチェーンに送信し、トランザクションの実行を確認します。
この関数は、署名に使用するキーペアのリスト(この例ではalice_keypair
のみ)と、送信するメッセージを引数に取ります。
このプロセスにより、クライアントはプログラムによって生成されたエスクローアドレスに安全にトークンを転送でき、トランザクションはブロックチェーン上で確認されます。
このような操作は、特にDeFiアプリケーションやスマートコントラクトが関わるトランザクションで広く利用される手法です。
トークンのTransfer
fn transfer_one_token_from_escrow(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult {
// User supplies the destination
let alice_pubkey = keyed_accounts[1].unsigned_key();
// Deterministically derive the escrow pubkey.
let escrow_pubkey = create_program_address(&[&["escrow"]], program_id);
// Create the transfer instruction
let instruction = token_instruction::transfer(&escrow_pubkey, &alice_pubkey, 1);
// The runtime deterministically derives the key from the currently
// executing program ID and the supplied keywords.
// If the derived address matches a key marked as signed in the instruction
// then that key is accepted as signed.
invoke_signed(&instruction, accounts, &[&["escrow"]])
}
Solanaのスマートコントラクト(プログラム)が自身のアドレスから、別のアドレスへトークンを転送するプロセスを実装しています。
この例では、エスクロープログラムがエスクローアカウントからAliceのアカウントへ1トークンを転送する場合を想定しています。
関数の概要
transfer_one_token_from_escrow
は、プログラムIDとアカウントの情報を引数に取り、プログラムの実行結果を表すProgramResult
を返します。
処理の流れ
宛先の指定
Aliceの公開鍵(
alice_pubkey
)は、関数に渡されたアカウント情報(keyed_accounts
)から取得されます。
エスクローアドレスの導出
create_program_address
関数を使って、シードescrow
と現在実行中のプログラムIDから、エスクローアドレス(escrow_pubkey
)を決定的に導出します。
転送命令の作成
token_instruction::transfer
関数を使用して、エスクローアドレスからAliceのアドレスへ1トークンを転送する命令(instruction
)を作成します。
命令の実行
invoke_signed
関数を使用して、作成した転送命令を実行します。この関数は、プログラムがエスクローアドレスの「署名者」として振る舞うことを可能にします。
プログラムアドレスにはプライベートキーが存在しないため、このメカニズムを使ってプログラムが自身でトランザクションを「署名」することができます。
注意点
create_program_address
関数で生成されるアドレスが必ずしも楕円曲線上にない有効なプログラムアドレスである保証はありません。
このため、有効なアドレスを生成するためには、異なるシードやバンプシードを試す必要があります(この例ではシードescrow2
が有効なプログラムアドレスを生成しない可能性があると仮定しています)。
この機能により、プログラムは特定の条件下で他のアカウントにトークンを転送する能力を持ち、デジタル資産の自動管理やエスクローサービスの実装など、多様なアプリケーションが可能になります。
エスクローアドレスにトークンをTransfer
// find the escrow key and valid bump seed
let (escrow_pubkey2, escrow_bump_seed) = find_program_address(&[&["escrow2"]], &escrow_program_id);
// construct a transfer message using that key
let message = Message::new(vec![
token_instruction::transfer(&alice_pubkey, &escrow_pubkey2, 1),
]);
// process the message which transfer one 1 token to the escrow
client.send_and_confirm_message(&[&alice_keypair], &message);
このコードは、Solanaプログラムでプログラム派生アドレス(PDA)を使用して、あるアカウントから別のエスクローアカウントへトークンを転送する一連のプロセスを示しています。
具体的には、「escrow2
」というシードを用いて、有効なエスクローアドレスを見つけ出し、そのアドレスを使用してトークンを転送する方法を説明しています。
エスクローアドレスとバンプシードの検索
let (escrow_pubkey2, escrow_bump_seed) = find_program_address(&[&["escrow2"]], &escrow_program_id);
find_program_address
関数を使って、シードescrow2
とプログラムID(escrow_program_id
)から、有効なプログラムアドレス(escrow_pubkey2
)とバンプシード(escrow_bump_seed
)を見つけます。
この関数は、指定されたシードからPDAを生成し、そのPDAが楕円曲線上になく、実際に使用可能なアドレスであることを保証します。
トランザクションメッセージの構築
let message = Message::new(vec![
token_instruction::transfer(&alice_pubkey, &escrow_pubkey2, 1),
]);
生成されたエスクローアドレス(escrow_pubkey2
)を使用して、Aliceの公開鍵(alice_pubkey
)からエスクローアドレスへ1トークンを転送するトランザクションメッセージを構築します。
トランザクションの送信と確認
client.send_and_confirm_message(&[&alice_keypair], &message);
クライアントは、生成されたメッセージをSolanaネットワークに送信し、トランザクションが正常に処理されたことを確認します。
このプロセスにより、指定されたトークンがAliceからエスクローアカウントへ安全に転送されます。
バンプシードについて
バンプシードは、有効なPDAを見つけるためにfind_program_address
関数が内部的に使用する追加のシードです。
この値を変更することで、同じシードとプログラムIDの組み合わせから異なるPDAを生成することが可能になります。
これは、特定のシードが楕円曲線上にあるアドレスを生成した場合に、別の有効なアドレスを見つけ出すために役立ちます。
このコードは、SolanaのスマートコントラクトやdApp開発において、PDAを利用してセキュアなトランザクションを行う一般的な方法を示しています。
PDAは、プログラムが自身または他のプログラムのために特定のアクションを「代理実行」する時に特に役立ちます。
エスクローアカウントからトークンTransfer
fn transfer_one_token_from_escrow2(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult {
// User supplies the destination
let alice_pubkey = keyed_accounts[1].unsigned_key();
// Iteratively derive the escrow pubkey
let (escrow_pubkey2, bump_seed) = find_program_address(&[&["escrow2"]], program_id);
// Create the transfer instruction
let instruction = token_instruction::transfer(&escrow_pubkey2, &alice_pubkey, 1);
// Include the generated bump seed to the list of all seeds
invoke_signed(&instruction, accounts, &[&["escrow2", &[bump_seed]]])
}
この関数は、エスクローアカウントから別のアカウント(この例ではAliceのアカウント)に1トークンを転送するために使用されます。
宛先の指定
let alice_pubkey = keyed_accounts[1].unsigned_key();
トークンの転送先としてAliceの公開鍵を取得します。
この公開鍵は、関数に渡されたアカウント情報のリスト(accounts
)から取得されます。
エスクローアドレスの導出
let (escrow_pubkey2, bump_seed) = find_program_address(&[&["escrow2"]], program_id);
find_program_address
関数を使用して、シードescrow2
とプログラムIDからエスクローアドレス(escrow_pubkey2
)とバンプシード(bump_seed
)を導出します。
このアドレスは、トークンを保持しているエスクローアカウントのアドレスです。
転送命令の作成
let instruction = token_instruction::transfer(&escrow_pubkey2, &alice_pubkey, 1);
エスクローアカウントからAliceのアカウントへ1トークンを転送する命令を作成します。
署名付き命令の実行
invoke_signed(&instruction, accounts, &[&["escrow2", &[bump_seed]]])
invoke_signed
関数を使用して、上記で作成した転送命令を実行します。
この関数は、命令に署名するために、導出されたエスクローアドレスを使用します。invoke_signed
は、特定のシード(ここではescrow2
とバンプシード)を使って、プログラムがエスクローアドレスの「所有者」として行動できるようにします。
コンピュートコストとの関係
find_program_address
関数は、有効なプログラムアドレスを見つけるためにcreate_program_address
を複数回呼び出す必要があるため、オンチェーンで使用すると計算コストがかかります。
計算コストを削減するための推奨される方法は、find_program_address
をオフチェーン(例えば、クライアントのアプリケーションやスクリプト内で)で実行し、その結果として得られたバンプシードをプログラムに渡すことです。
これにより、プログラムは必要な計算を行わずに、直接有効なエスクローアドレスを使用できるようになります。
このプロセスを通じて、Solanaのスマートコントラクトは、特定のロジックに従ってトークンを安全に転送することが可能になります。
これは、DeFiアプリケーションやその他の分散型アプリケーションにおける自動化されたアセット管理の重要な部分を形成します。
署名者を必要とする処理
Solanaブロックチェーン上でプログラムを実行する時、特定のアドレスがプログラムによって生成されたプログラム派生アドレス(Program Derived Address、PDA)であることをランタイム(実行環境)が認識するためのメカニズムについて説明します。
PDAの特性
create_program_address
やfind_program_address
関数を使用して生成されるアドレスは、見た目上他の公開鍵(アドレス)と区別がつきません。
これらのアドレスは、通常の公開鍵と同様にSolanaブロックチェーン上で使用できます。
署名要求のある命令
一部の命令では、トランザクションの実行者(signer
)が必要です。
つまり、トランザクションを有効にするためには、その命令を発行したアカウントのデジタル署名が必要になります。
しかし、PDAは実際のプライベートキーを持たないため、通常の方法で署名を生成することはできません。
ランタイムによる検証
PDAがプログラムに属することをランタイムが認識する唯一の方法は、そのPDAを生成する際に使用されたシード(seed
)情報をプログラムが提供することです。
命令を実行する時、ランタイムは内部的にcreate_program_address
関数を呼び出し、その結果(生成されたアドレス)と命令に含まれるアドレスを比較します。
この比較によって、提供されたアドレスが実際に指定されたシードとプログラムIDから生成されたPDAであることを検証します。
検証プロセスの意義
このプロセスにより、プログラムは自身が「所有する」PDAに対して、署名者(signer
)としての権限を持つことができます。
これにより、PDAを使用して特定の操作(例えば、資産の移転やステートの更新)を実行できるようになります。
重要なのは、この検証プロセスによって、PDAがプログラムに紐付いていることがSolanaのランタイムによって保証される点です。
結果として、PDAはプログラムがブロックチェーン上でより複雑なロジックやアセットの管理を行うための強力なツールとなります。
これにより、DeFiアプリケーション、スマートコントラクト、その他多くの分散型アプリケーションの開発において、新たな可能性が開かれます。
参考資料
https://solanacookbook.com/ja/core-concepts/pdas.html
https://solana.com/docs/core/cpi#program-derived-addresses
最後に
今回はSolanaのPDAについてまとめました。
これからもSolanaについてまとめていきつつ、他のブロックチェーンやサービスについてもまとめていきます。
情報を見逃したくないという方はぜひ以下の購読ボタンを教えてください。
更新があった時、登録しているメールアドレス宛に通知が飛ぶようになります。
また、拡散もしてくれると嬉しいです🙇♂️