はじめに
『DApps開発入門』という本や色々記事を書いているかるでねです。
今回は、パスワードを使って秘密鍵のように取引データへ安全な署名を行い、より直感的で管理しやすい認証体験を実現しようとする仕組みを提案しているERC6327についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。
概要
Elastic Signature(以下、ES)は、人が覚えやすい秘密情報を使ってデータへ署名できる仕組みです。 ここでいう「秘密情報」は、一般的な暗号資約で使われる固定長の秘密鍵ではなく、ユーザーが自由に決められるパスワードのようなものです。 ESの大きな特徴は、秘密情報そのものを保存せず、ゼロ知識証明(秘密を明かさずに知っていることだけを証明する仕組み)を使って完全にオンチェーンで検証できる点です。
ユーザーは必要に応じて好きなタイミングで秘密情報を変更でき、秘密情報の長さも制限されません。 つまり、従来の秘密鍵のように「一度流出したら終わり」という状況を避けることができます。 特に、ブロックチェーンに慣れていないユーザーにとって、秘密鍵よりもパスワードの方が理解しやすいという点が大きなメリットになります。
ESは、この仕組みをスマートコントラクトで扱えるようにするための標準インターフェースを定義するものです。 これにより、ESを利用した署名検証や操作の許可が、あらゆるDappやウォレットから安全に利用できるようになります。
動機
ESが実現しようとしているのは、「変更可能な秘密鍵」という新しい概念です。 ブロックチェーンを利用する時、多くの人がつまずくポイントは秘密鍵の扱いです。 秘密鍵は非常に強力ですが、紛失・流出すると取り返しがつかず、初心者にとっては心理的なハードルが高いものです。
これまでの対策としては、秘密鍵を預かるサービス(カストディアル型ウォレット)がありましたが、これらはサービス提供者に依存してしまいます。 つまり、 decentralization(完全な分散性)とは相性がよくありません。
ESは、ゼロ知識証明を使うことで「ユーザー自身が管理でき、しかも秘密鍵のように扱う必要のない署名方式」を実現します。 ユーザーは秘密情報(パスワード)をただ覚えておくだけでよく、スマートコントラクト側はユーザーがその秘密情報を「知っている」という事実だけを安全に検証できます。
この仕組みによって、オンボーディング(初めてのDapp利用時の手順)が簡単になり、非技術ユーザーでも分散的な環境を直接扱えるようになります。 さらに、秘密情報をいつでも変更できるため、安全性に対する不安も軽減できます。
利用例
ESは、従来の秘密鍵の代わりになるものではなく、秘密鍵に追加して使える署名方式です。 つまり「どちらか一方」という選択ではなく、両方を組み合わせて安全性を高めることができます。
DeFiアプリでの利用
DeFiアプリにESを組み込むと、送金など重要な操作を行う時に、ユーザーへ追加のパスワード入力を求めることができます。 もし秘密鍵が漏洩したとしても、ESで設定したパスワードを知らない限り不正操作はできません。 二段階認証のような役割を果たし、セキュリティを大幅に強化できます。
スマートコントラクトウォレットでの利用
Account Abstraction(ERC4337)のようなスマートコントラクトウォレットでは、柔軟に署名方式を追加できます。
ERC4337については以下の記事を参考にしてください。
そこにESを組み込むことで、以下のような使い方が可能になり、ブロックチェーン初心者でもストレスなくDappを使えるようになります。
- 初回利用時にパスワードを設定するだけでよい
- 秘密鍵を保管する必要がない
- 秘密情報はいつでも変更可能
特に、「パスワードで使える分散ウォレット」という形は、既存のWebサービスに近い感覚で利用できるため、ユーザーの心理的な抵抗を大きく下げる効果があります。
仕様
ES(Elastic Signature)は、パスワードのような任意長の秘密情報を使い、その秘密を公開せずにゼロ知識証明で「知っていること」だけを示す仕組みです。 この署名方式では、パスワードは保存されず、オンチェーンで完全に検証できます。 パスワードは任意のタイミングで変更でき、従来の秘密鍵と併せて利用することでセキュリティを向上できます。
ESの検証には Verifier(検証者)、Requester(検証要求者)、Prover(証明生成者)の三者が関与します。
全体の流れは次のハッシュ計算を土台としています。
| 名称 | 説明 |
|---|---|
pwdhash |
パスワードのハッシュ。 |
datahash |
トランザクション対象データのハッシュ。 |
fullhash |
datahash と既知の変数(nonce や chainid など)から計算されるハッシュ。 |
expiration |
証明が有効な期限(タイムスタンプ)。 |
allhash |
fullhash と pwdhash から計算されるハッシュ。 |
各者の役割は以下のとおりです。
Verifier(検証者)
- Requesterが提供した
datahashをもとにfullhashを計算する。 - 対象ユーザーのアドレス(EOA またはスマートコントラクトウォレット)から
pwdhashを取得する。 - Proverが生成し、Requesterが提出した
allhashが正しいか検証する。
Requester(要求者)
datahashを生成し、証明の期限expirationを決める。- Prover から受け取った
proofおよびallhashをVerifierに提出する。
Prover(証明生成者)
datahash、expiration、nonce、chainidなどの既知の変数を使ってproofとallhashを生成する。
必要条件
- 既知の変数は全参加者に共有されている必要がある。
nonceを含める必要がある。chainidを含める必要がある。- Verifier 固有の変数を含めてもよい。
- 公開ステートメントには
pwdhash・fullhash・allhashをそれぞれ反映する必要がある。 fullhashの計算方法はVerifierとProverが合意しておく必要がある。datahashの計算方法も明確に定義される必要がある。
IElasticSignature インターフェース
pragma solidity ^0.8.0;
interface IElasticSignature {
event SetPassword(address indexed user, uint indexed pwdhash);
event Verified(address indexed user, uint indexed nonce);
function pwdhashOf(address user) external view returns (uint);
function resetPassword(
uint[8] memory proof1,
uint expiration1,
uint allhash1,
uint[8] memory proof2,
uint pwdhash2,
uint expiration2,
uint allhash2
) external;
function verify(
address user,
uint[8] memory proof,
uint datahash,
uint expiration,
uint allhash
) external;
}イベント
SetPassword
event SetPassword(address indexed user, uint indexed pwdhash);ユーザーが自身のパスワードハッシュを設定または再設定した時に発行されるイベント。 ユーザーのアドレスに紐づけて、パスワードを変更したことを通知します。 EOAだけでなく、スマートコントラクトウォレットでも利用できます。
パラメータ
-
user- パスワードハッシュを設定したユーザーアドレス。
-
pwdhash- 設定されたパスワードのハッシュ。
Verified
event Verified(address indexed user, uint indexed nonce);ユーザーが提出した proof の検証に成功した時に発行されるイベント。
パスワードによる認証が成功した際に、新しい nonce を発行し、再利用されないように管理します。
nonce はゼロ知識証明における再利用防止のための重要な要素です。
パラメータ
-
user- 検証の対象となったユーザーアドレス。
-
nonce- 新しく発行された
nonce。
- 新しく発行された
関数
pwdhashOf
function pwdhashOf(address user) external view returns (uint);指定したユーザーアドレスに登録されている pwdhash を取得する関数で
Verifier が proofの検証を行う時に、対象ユーザーに対応するパスワードハッシュを取り出すために使われます。
引数
user- パスワードハッシュを取得したいユーザーのアドレス。
戻り値
pwdhash- 指定アドレスのパスワードハッシュ。
resetPassword
function resetPassword(
uint[8] memory proof1,
uint expiration1,
uint allhash1,
uint[8] memory proof2,
uint pwdhash2,
uint expiration2,
uint allhash2
) external;古いパスワードから新しいパスワードへ安全に更新するための関数。
1つ目の proof(proof1)で旧パスワードを知っていることを証明し、2つ目の proof(proof2)で新しいパスワードに対応する正しい証明を提示します。
この2段階の検証により、パスワード変更が正当なユーザーによって行われていることを保証します。
引数
-
proof1- 旧パスワードで生成した
proof。
- 旧パスワードで生成した
-
expiration1- 旧パスワードの証明が有効である期限。
-
allhash1- 旧パスワードから生成された
allhash。
- 旧パスワードから生成された
-
proof2- 新パスワードで生成した
proof。
- 新パスワードで生成した
-
pwdhash2- 新しいパスワードのハッシュ。
-
expiration2- 新パスワードの証明が有効である期限。
-
allhash2- 新パスワードで生成した
allhash。
- 新パスワードで生成した
verify
function verify(
address user,
uint[8] memory proof,
uint datahash,
uint expiration,
uint allhash
) external;他のコントラクトから呼び出され、ユーザーが生成した proof を検証する関数。
トランザクションを発行する側(例:DeFi アプリ)が datahash を計算し、ユーザーが提示した proof と allhash をこの関数へ渡します。
Verifierは内部で fullhash を再計算し、allhash が正しいかをチェックします。
期限切れ(expiration 超過)や不正な allhash であれば検証は失敗します。
成功時には Verified イベントが発行され、新しい nonce を更新します。
引数
-
user- 検証対象のユーザーアドレス。
-
proof- ユーザーがパスワードをもとに生成した証明。
-
datahash- 操作対象データのハッシュ。
-
expiration- 証明が有効である期限。
-
allhashproofとともに生成される公開ステートメント。
補足
pwdhash をコントラクトに保存する理由
ESでは、各ユーザーのパスワードそのものはどこにも保存しませんが、パスワードをハッシュ化した値である pwdhash はオンチェーンに保存します。
コントラクト側は「このアドレスのユーザーは、こういう pwdhash を持っている」という情報だけを持ち、それをもとにゼロ知識証明の検証を行います。
こうしておくことで、VerifierとなるESコントラクトは、Proverが提出した proof(証明)や allhash(まとめたハッシュ)が、登録済みの pwdhash と整合しているかどうかを判断できます。
パスワードそのものは一切開示されず、ハッシュと証明だけを頼りに「本物のユーザーが操作した」と確認できるようになっています。
zkサーキットとハッシュの全体像
この図は、ユーザーの操作から zkサーキット、そしてVerifierによる検証までのデータの流れを表しています。
- 左側にユーザーがいて、「
input」と書かれたブロックに操作内容が入ります。 例えば、トークンの種類(token)、送金・出金量(amount)などです。 - これらの入力データから
datahashが計算されます。 図ではtoken・amountなどがまとまってdatahashになっています。 - さらに
expiration(有効期限)、chainId(チェーン ID)、nonce(使い捨てカウンタ)などの値を組み合わせてfullhashが作られます。 これは、「どのチェーンで」、「いつまで有効な」、「どの操作か」を一意に表すハッシュです。 - 一方で、ユーザーのウォレットアドレスとパスワード(
pwd)がzkサーキットに入力されます。 図の上部には「wallet address (…) → pwdhash」と書かれており、このアドレスに対応するpwdhashがコントラクトに保存されていることを表しています。 - zk サーキットは、
addresspwdfullhashを入力として、proof(ゼロ知識証明)pwdhash(パスワードハッシュ)allhash(fullhashとpwdhashをまとめたハッシュ) を出力します。
- 右側では、Verifierコントラクトがすでに保存している
pwdhashと、Prover が渡してきたproof、allhash、fullhashを使ってverifyを行います。 ここで、「このユーザーは登録されているpwdhashに対応するパスワードを知っていて、そのパスワードでこのfullhashを署名した」という事実を検証します。
この図が示しているポイントは、「ユーザーの入力はトランザクション内容とパスワードに分かれ、それらが zkサーキットの中で結びつき、最終的に fullhash・pwdhash・allhash と proof が生成される」という流れです。
Poseidonを使ったzkサーキット内部の計算
この図は、zkサーキットの内部ロジックを、ハッシュ関数 Poseidon を使ってどのように構成しているかを示しています。
Poseidon は、ゼロ知識証明に向いたハッシュ関数で、回路内で計算しやすいように設計されています。
この図では「Poseidon 1」、「Poseidon 2」として2段階のハッシュ処理になっています。
図の流れを整理すると以下のようになります。
-
入力(
input)として、以下のデータがzkサーキットに入ります。passwordaddressfullhash
-
Poseidon 1では、
passwordとaddressからpwdhashを計算します。 図の破線は、password とaddressが**Poseidon 1**に流れ込み、その出力がpwdhash` として右側に出ていることを示しています。 -
Poseidon 2では、Poseidon 1の出力
pwdhashとfullhashを組み合わせてallhashを計算します。 図中の緑色の矢印が、fullhashとpwdhashが Poseidon 2 に入力され、右側にall hashが出力される流れを表しています。 -
zkサーキット全体としては、以下を出力します。
proof(ゼロ知識証明)all hashpwdhashfullhash
fullhashは入力の一部でもありますが、出力側にも含めることで、Verifier が「どのトランザクションに対する証明なのか」を明確に扱えるようにしています。
この図が強調しているのは、pwdhash と allhash がサーキット内部のハッシュ計算から一貫して導かれており、Verifier は「Poseidon でこう計算されているはずだ」という前提で検証する、という点です。
つまり、Prover が勝手な allhash を作ることはできず、パスワードとアドレス、fullhash の組み合わせに従った値しか出せない構造になっています。
ワークフロー(User → Requester → ES コントラクト)

この図は、実際のフローを「誰が何をどこへ渡すか」という視点で描いたものです。 User(prover)、Third-party contract(requester)、ES contract(verifier)の三者が登場します。
-
左側のUser(prover)は、例えば「
withdrawal(出金)」操作をしようとします。 ここではtokenの種類とamount(数量)が入力として扱われています。 -
Userは、自分のパスワードと出金内容を使ってzkサーキットを実行していかを生成します。
proofallhash
図ではZKブロックから
proofとallhashが出力されています。 -
Userは、
withdrawalの内容とともに、proofとallhashをThird-party contract(requester)に送ります。 Third-party contractは、例えばDeFiアプリのコントラクトのようなイメージです。 -
Requesterコントラクトは、受け取った
token・amountなどからdatahashを計算し、さらにユーザーアドレスを組み合わせて「どのユーザーがどの操作をしたのか」を表すハッシュを作ります。 図の中央部分では、以下を右側のES contractに渡しています。datahashuser addressと、User から来たproofallhash
-
ES contract(verifier)は、あらかじめ保存しているユーザーの
pwdhashと、Requester から渡されたdatahashからfullhashを計算します。 そして、以下を使って検証を行います。fullhashpwdhashproofallhash
図の右端では、ES contract に
fullhashとpwdhash、そこにproofとallhashが入力されている形で表現されています。 -
検証に成功すれば、「この出金操作は、登録済みパスワードを知っているユーザーによって署名された」とみなすことができます。 これにより、秘密鍵が知られているだけでは不十分で、パスワードによるES署名も満たしている場合にのみ資金移動が行われるようにできます。
互換性
ESによる署名検証は、すでに存在している従来の署名検証の仕組みと衝突しないように設計されています。
ここで強調されているのは、ESが扱うのは「パスワードベースの署名」だけであり、Externally Owned Account(EOA)の秘密鍵を使った従来の署名方式とは別レイヤーの仕組みだという点です。 そのため、既存のEOA署名検証ロジックを変更したり置き換えたりする必要はなく、追加の署名方式として並行して使うことができます。
言い換えると、既存のコントラクトやウォレットがEOA署名による検証を続けながら、必要に応じてESを組み込んでいくことができる設計になっています。
参考実装
pragma solidity ^0.8.0;
import "../interfaces/IElasticSignature.sol";
import "./verifier.sol";
contract ZKPass is IElasticSignature {
Verifier verifier = new Verifier();
mapping(address => uint) public pwdhashOf;
mapping(address => uint) public nonceOf;
constructor() {
}
function resetPassword(
uint[8] memory proof1,
uint expiration1,
uint allhash1,
uint[8] memory proof2,
uint pwdhash2,
uint expiration2,
uint allhash2
) public override {
uint nonce = nonceOf[msg.sender];
if (nonce == 0) {
//init password
pwdhashOf[msg.sender] = pwdhash2;
nonceOf[msg.sender] = 1;
verify(msg.sender, proof2, 0, expiration2, allhash2);
} else {
//reset password
// check old pwdhash
verify(msg.sender, proof1, 0, expiration1, allhash1);
// check new pwdhash
pwdhashOf[msg.sender] = pwdhash2;
verify(msg.sender, proof2, 0, expiration2, allhash2);
}
emit SetPassword(msg.sender, pwdhash2);
}
function verify(
address user,
uint[8] memory proof,
uint datahash,
uint expiration,
uint allhash
) public override {
require(
block.timestamp < expiration,
"ZKPass::verify: expired"
);
uint pwdhash = pwdhashOf[user];
require(
pwdhash != 0,
"ZKPass::verify: user not exist"
);
uint nonce = nonceOf[user];
uint fullhash = uint(keccak256(abi.encodePacked(expiration, block.chainid, nonce, datahash))) / 8; // 256b->254b
require(
verifyProof(proof, pwdhash, fullhash, allhash),
"ZKPass::verify: verify proof fail"
);
nonceOf[user] = nonce + 1;
emit Verified(user, nonce);
}
/////////// util ////////////
function verifyProof(
uint[8] memory proof,
uint pwdhash,
uint fullhash, //254b
uint allhash
) internal view returns (bool) {
return
verifier.verifyProof(
[proof[0], proof[1]],
[[proof[2], proof[3]], [proof[4], proof[5]]],
[proof[6], proof[7]],
[pwdhash, fullhash, allhash]
);
}
}verifier.sol はsnarkjsによって自動生成されます。
ソースコード circuit.circom は以下です。
pragma circom 2.0.0;
include "../../node_modules/circomlib/circuits/poseidon.circom";
template Main() {
signal input in[3];
signal output out[3];
component poseidon1 = Poseidon(2);
component poseidon2 = Poseidon(2);
poseidon1.inputs[0] <== in[0]; //pwd
poseidon1.inputs[1] <== in[1]; //address
out[0] <== poseidon1.out; //pwdhash
poseidon2.inputs[0] <== poseidon1.out;
poseidon2.inputs[1] <== in[2]; //fullhash
out[1] <== in[2]; //fullhash
out[2] <== poseidon2.out; //allhash
}
component main = Main();セキュリティ
セキュリティの説明では、「pwdhash が公開される」という前提を置いたうえで、総当たり攻撃(パスワードのハッシュから元のパスワードを推測する攻撃)にどれくらい耐えられるかを具体的な数字で示しています。
pwdhash が公開されることによるリスク
ESではパスワードそのものは保存されませんが、そのハッシュ値である pwdhash はオンチェーンに公開されます。
ハッシュ関数として Poseidon を使うことを前提に、例えばGPU(RTX 3090)で1 秒あたり1億回(100Mhash/s)のハッシュ計算ができると仮定し、「どの程度の長さ・文字種のパスワードなら現実的に安全か」を見積もっています。
想定されるクラック時間の目安
提示されている条件と推定クラック時間を表にすると次のようになります。
| パスワード条件 | 推定クラック時間 |
|---|---|
| 8文字(数字のみ) | 1 秒 |
| 8文字(数字 + 英字) | 25 日 |
| 8文字(数字 + 英字 + 記号) | 594 日 |
| 12文字(数字のみ) | 10,000 秒 |
| 12文字(数字 + 英字) | 1,023,042 年 |
| 12文字(数字 + 英字 + 記号) | 116,586,246 年 |
ここで示されているのは、あくまでハッシュレート 100Mhash/s 程度の環境で総当たり攻撃を行った場合の「目安」です。 文字種が増えるほど、同じ文字数でも総当たりに必要な組み合わせ数が急激に増えることがわかります。
秘密鍵との比較
次に、パスワードの強度と秘密鍵の強度の比較が示されています。
- 秘密鍵のクラック難易度は 2^256。
- 40文字のパスワード(数字 + 英字 + 記号)の総当たり難易度は 92^40。
ここで「92」というのは、「数字 + 英字 + 記号」を合わせた時に想定している文字集合のサイズです。
そして、
92^40 > 2^256
となるため、40文字のパスワード(数字 + 英字 + 記号)であれば、理論上は一般的な 256bit 秘密鍵よりも総当たり攻撃に強い、という結論を示しています。
つまり、pwdhash が公開されている前提でも、十分に長く、かつ多様な文字種を使ったパスワードを選べば、秘密鍵と同等以上の総当たり耐性を持たせることができる、という考え方になっています。
引用
George (@JXRow), "ERC-6327: Elastic Signature [DRAFT]," Ethereum Improvement Proposals, no. 6327, January 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6327.
最後に
今回は「パスワードを使って秘密鍵のように取引データへ安全な署名を行い、より直感的で管理しやすい認証体験を実現しようとする仕組みを提案しているERC6327」についてまとめてきました! いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!



