はじめに(Introduction)
以下を翻訳します。
ERC-1271: Standard Signature Validation Method for Contracts
ERC-1271: コントラクトのための標準的な署名検証メソッド
Standard way to verify a signature when the account is a smart contract
スマートコントラクトがアカウントの場合の署名検証の標準的な方法
著者 Francisco Giordano (@frangio), Matt Condon (@shrugs), Philippe Castonguay (@PhABC), Amir Bandeali (@abandeali1), Jorge Izquierdo (@izqui), Bertrand Masius (@catageek)
作成日 2018-07-25
要旨(Abstract)
外部所有アカウント(EOA)は、関連する秘密鍵でメッセージに署名することができますが、現在のところコントラクトはできません。
指定のコントラクトに代わって署名が有効かどうかを検証するための標準的な方法を提案します。
これは、署名コントラクト上の isValidSignature(hash, signature)
関数の実装により可能になり、この関数を呼び出すことで署名を検証できます。
動悸(Motivation)
資産の移転権限の検証やその他の目的で、署名済みメッセージを利用したいコントラクトは今後多数あると考えられます。
これらのコントラクトが外部所有アカウント(EOA)以外、つまりコントラクト所有者をサポートできるようにするには、指定の署名がコントラクト自身に代わって有効か無効かを示す標準的な仕組みが必要です。
署名が求められるアプリケーションの一例としては、オフチェーンオーダーブックを持つ分散型取引所があります。そこでは買い/売り注文が署名済みメッセージとなります。
このようなアプリケーションでは、EOAが特定の資産を買いたい/売りたいという意思と、取引を成立させる明示的な許可をスマートコントラクトに与えるため、注文に署名します。
しかし、コントラクトの場合は秘密鍵を持たないため通常の署名ができません。そこでこの提案が必要になるのです。
仕様(Specification)
この文書における「MUST」、「MUST NOT」、「REQUIRED」、「SHALL」、「SHALL NOT」、「SHOULD」、「SHOULD NOT」、「RECOMMENDED」、「MAY」、「OPTIONAL」というキーワードは、RFC 2119で説明されているとおりに解釈されるものとします。
pragma solidity ^0.5.0;
contract ERC1271 {
// bytes4(keccak256("isValidSignature(bytes32,bytes)")
bytes4 constant internal MAGICVALUE = 0x1626ba7e;
/**
* @dev Should return whether the signature provided is valid for the provided hash
* @param _hash Hash of the data to be signed
* @param _signature Signature byte array associated with _hash
*
* MUST return the bytes4 magic value 0x1626ba7e when function passes.
* MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5)
* MUST allow external calls
*/
function isValidSignature(
bytes32 _hash,
bytes memory _signature)
public
view
returns (bytes4 magicValue);
}
isValidSignature
は、与えられた署名を検証するために任意のメソッドを呼び出すことができます。これは状況に依存する可能性があります(例えば時間ベースやステートベース)。また、EOAに依存する可能性もあります(例えばスマートウォレット内の署名者の権限レベル)。さらに、署名スキームに依存する可能性もあります(例えばECDSA、多重署名、BLSなど)。
この関数は、メッセージに署名したいコントラクト(例えばスマートコントラクトウォレット、DAO、多重署名ウォレットなど)によって実装されるべきです。コントラクトの署名をサポートしたいアプリケーションは、署名者がコントラクトの場合、このメソッドを呼び出す必要があります。
理論的根拠(Rationale)
提案された関数名は適切だと考えています。承認された署名者が特定のデータに対して適切な署名を提供した場合、署名コントラクトはその署名を「有効」と見なすからです。
したがって、署名済みのアクションメッセージは、署名者がスマートウォレットに代わって特定のアクションを実行する権限を持っている場合にのみ有効となります。
署名されたハッシュを署名から簡単に分離するために、2 つの引数が提供されています。
コントラクトでは、EIP-712 など、標準ではない特定のハッシュ関数が期待される可能性もあるため、簡略化するために、ハッシュ化されていないメッセージの代わりに bytes32 ハッシュが使用されます。
isValidSignature()
はステートを変更できないようにする必要があります。これは、GasToken
の作成や同様の攻撃ベクトルを防ぐためです。
繰り返しになりますが、これは機能の実装面を簡素化し、より適切な標準化を図るとともに、オフチェーンでのコントラクトクエリを可能にするためです。
ブール値の代わりに特定の戻り値を返すことが求められています。これは署名の検証をより厳密かつ単純にするためです。
後方互換性(Backwards Compatibility)
この提案は、このメソッドがコントラクトベースの署名に特化しているため、EOAの署名ではなく、以前の署名検証の作業との後方互換性があります。
参考実装(Reference Implementation)
署名コントラクトの実装例:
/**
* @notice Verifies that the signer is the owner of the signing contract.
*/
function isValidSignature(
bytes32 _hash,
bytes calldata _signature
) external override view returns (bytes4) {
// Validate signatures
if (recoverSigner(_hash, _signature) == owner) {
return 0x1626ba7e;
} else {
return 0xffffffff;
}
}
/**
* @notice Recover the signer of hash, assuming it's an EOA account
* @dev Only for EthSign signatures
* @param _hash Hash of message that was signed
* @param _signature Signature encoded as (bytes32 r, bytes32 s, uint8 v)
*/
function recoverSigner(
bytes32 _hash,
bytes memory _signature
) internal pure returns (address signer) {
require(_signature.length == 65, "SignatureValidator#recoverSigner: invalid signature length");
// Variables are not scoped in Solidity.
uint8 v = uint8(_signature[64]);
bytes32 r = _signature.readBytes32(0);
bytes32 s = _signature.readBytes32(32);
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
//
// Source OpenZeppelin
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/cryptography/ECDSA.sol
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
revert("SignatureValidator#recoverSigner: invalid signature 's' value");
}
if (v != 27 && v != 28) {
revert("SignatureValidator#recoverSigner: invalid signature 'v' value");
}
// Recover ECDSA signer
signer = ecrecover(_hash, v, r, s);
// Prevent signer from being 0x0
require(
signer != address(0x0),
"SignatureValidator#recoverSigner: INVALID_SIGNER"
);
return signer;
}
外部署名コントラクトで isValidSignature() 関数を呼び出すコントラクトの実装例。
function callERC1271isValidSignature(
address _addr,
bytes32 _hash,
bytes calldata _signature
) external view {
bytes4 result = IERC1271Wallet(_addr).isValidSignature(_hash, _signature);
require(result == 0x1626ba7e, "INVALID_SIGNATURE");
}
セキュリティに関する考慮事項(Security Considerations)
isValidSignature()関数の呼び出しに対してガス制限が設けられていないため、一部の実装ではガスの大量消費が発生する、可能性があります。
そのため、外部コントラクトでこのメソッドを呼び出す際に、ガス量を固定値で設定しないことが重要です。固定値を設定すると、特定の署名の検証ができなくなる、可能性があるためです。
また、このメソッドを実装する各コントラクトは、渡された署名が確かに有効であることを保証する責任があります。そうでない場合、壊滅的な結果が予想されます。
著作権(Copyright)
著作権およびその他の権利はCC0により放棄されています。
引用(Citation)
この文書を引用する場合は以下のように記載してください:
Francisco Giordano (@frangio), Matt Condon (@shrugs), Philippe Castonguay (@PhABC), Amir Bandeali (@abandeali1), Jorge Izquierdo (@izqui), Bertrand Masius (@catageek), "ERC-1271: Standard Signature Validation Method for Contracts," Ethereum Improvement Proposals, no. 1271, July 2018. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-1271.
まとめ(Conclusion)
コントラクトアドレスに対応する署名はできないので、コントラクトが署名値の正当性を証明する関数(isValidSignature
)を用意したということがわかりました。
ただ、ERC-165(supportsInterface
)を装備してないのが疑問です。