レッスン2 — Alloyの基本型と署名
問い
Alloy で Ethereum を扱うときの最初の道具立て — アドレス / U256 / B256 / 署名。型システムが「これは Address、これは uint256」と区別してくれるので、誤って混ぜることがない。本レッスンで Alloy 基本型を見て、Signer で鍵を作る。
原理(最小モデル)
- 3 primitive 型.
Address(20 バイト、コントラクト or EOA)/U256(256 ビット符号なし整数、wei 金額)/B256(32 バイト、ハッシュやスロットキー)。 .parse::<Address>(). 文字列 →Address、checksum 検証込み、不正ならErr。U256リテラル.U256::from(1_000_000)(u64 から)/"1000000".parse()?(文字列から)/parse_ether("1")?(ETH 単位)。Signerトレイト. 鍵を持って署名を作る抽象、PrivateKeySigner::random()で新規鍵、.address()で公開アドレス取得。- Ledger / AWS KMS 等もSigner で抽象化. 後の Inside Alloy で詳細、ここでは「Signer = 署名できる何か」で十分。
具体例 + ステップで組み立てる
Alloyの基本型と署名
最初に Alloy を直接触る。Alloyは「EthereumをRustで扱うためのライブラリ群」で、Rethも内部でこれを活用している。
1. プロジェクトの準備
cargo new hello_alloy
cd hello_alloy
Cargo.toml の [dependencies] に追記する。
[dependencies]
alloy = { version = "1.0", features = ["full"] }
tokio = { version = "1", features = ["full"] }
eyre = "0.6"
Tip: バージョンは執筆時点のもの。実際は最新版を crates.io で確認する。
eyreは読みやすいエラー出力向けのクレートである。
2. メッセージに署名する — 実例
これは alloy-rs/examples の sign_message.rs 全体:
//! Example of signing a message with a signer.
use alloy::signers::{local::PrivateKeySigner, Signer};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// ランダムなシグナーを作る
let signer = PrivateKeySigner::random();
// 任意でチェーンIDを設定(EIP-155 リプレイ攻撃防止)
let signer = signer.with_chain_id(Some(1337));
// 署名するメッセージ
let message = b"hello";
// 非同期に署名
let signature = signer.sign_message(message).await?;
println!("Signature produced by {}: {:?}", signer.address(), signature);
println!("Signature recovered address: {}", signature.recover_address_from_msg(&message[..])?);
Ok(())
}
src/main.rs にコピーして cargo run する。ランダム生成シグナーのアドレス、署名、検証で復元したアドレス(一致するはず)が表示される。
sequenceDiagram
participant Signer as PrivateKeySigner
participant Msg as メッセージバイト
participant Hash as EIP-191 ハッシュ
participant Sig as 署名
participant Verify as recover_address_from_msg
Signer->>Msg: "hello" を取得
Msg->>Hash: prefix + keccak256
Hash->>Sig: sign(privkey, hash)
Sig-->>Verify: 署名 + 元メッセージ
Verify->>Hash: prefix付きで再ハッシュ
Verify-->>Signer: 復元したアドレス
3. このコードが教えること
PrivateKeySigner::random()
セキュアRNGで新しい鍵ペア生成。本番資金には絶対に使わないこと — テストと学習用。本番では環境変数、暗号化キーストア、ハードウェアウォレットから読み込む。
with_chain_id(Some(1337))
EIP-155 はチェーンIDを署名に含めるため、チェーンAで署名したtxをチェーンBへ再送できなくする。本番では必須。 1337 はローカルAnvilの典型的チェーンIDである。
sign_message(message).await
EIP-191 ("Ethereum signed message" プレフィックス)を実装。JSON-RPCの personal_sign や window.ethereum.request("personal_sign", ...) が返すものと同じ。async なのは、ハードウェアウォレット(Ledger/Trezor)が応答に時間がかかるため — ローカルシグナーも同じインターフェースで差し替え可能。
signature.recover_address_from_msg(&message[..])
検証側。署名と元メッセージから、署名者のアドレスを復元。これが 「Sign in with Ethereum」の作り方 — サーバーがnonceを発行、ユーザーが署名、サーバーがアドレスを復元。パスワード不要。
4. address! マクロ
use alloy::primitives::address;
let recipient = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
address! は 手続きマクロ で、コンパイル時に走る。1桁タイポや長さミスは コンパイル時に失敗 し、送信時の実行時エラーにしない。Expertティアで内部実装を正確に扱う。
なぜ「型」が重要なのか
Solidityも address 型を持ちますが、Rustの型システムはより厳格:
U256を期待する関数にu64を渡すとコンパイル時に止まるAddressは[u8; 20]互換ではなく、専用の型AddressとB256を混同するとコンパイルエラー- これが「金融資産を扱うコードの安全性」につながります
練習
例を改造して:
- 同じメッセージ を 異なる2つのチェーンID で署名 — 署名は異なるはず
recover_address_from_msgを 改ざんされたメッセージ に対して呼ぶ — 復元アドレスが一致しない。それがEIP-191の改ざん耐性
次は Result・Option・? — Provider に触れる前に押さえるべきエラーハンドリングの語彙。
まとめ(3行)
- 3 primitive =
Address20 バイト /U256256 ビット /B25632 バイト、型システムが「これは Address、これは wei」を区別。 .parse()で文字列 → 型、U256::from()で数値 → U256、parse_ether("1")で ETH 単位、お金は f64 でなく U256。Signer= 署名できる何か、PrivateKeySigner::random()で新規鍵、.address()で公開アドレス、次は Result/Option/?。