Verify EIP191 signatures in SignatureVerifier

Not really an EIP191-compliant method, as it only supports version E, but this is what we currently use in nucypher
pull/1029/head
David Núñez 2019-06-17 14:46:55 +02:00
parent 29a53c4392
commit ba5da0dfec
3 changed files with 118 additions and 3 deletions

View File

@ -87,4 +87,60 @@ library SignatureVerifier {
return toAddress(_publicKey) == recover(hash(_message, _algorithm), _signature);
}
/**
* @notice Hash message according to EIP191 signature specification
* @dev It always assumes Keccak256 is used as hashing algorithm
* @dev Only supports version E
* @param _message Message to sign
**/
function hashEIP191(
bytes memory _message
)
internal
pure
returns (bytes32 result)
{
require(_message.length > 0, "Empty message not allowed");
// Header for Version E as defined by EIP191. First byte ('E') is the version
bytes25 header = "Ethereum Signed Message:\n";
// Compute text-encoded length of message
uint256 length = _message.length;
uint256 digits = 0;
while (length != 0) {
digits++;
length /= 10;
}
bytes memory lengthAsText = new bytes(digits);
length = _message.length;
uint256 index = digits - 1;
while (length != 0) {
lengthAsText[index--] = byte(uint8(48 + length % 10));
length /= 10;
}
return keccak256(abi.encodePacked(byte(0x19), header, lengthAsText, _message));
}
/**
* @notice Verify EIP191 signature
* @dev It always assumes Keccak256 is used as hashing algorithm
* @param _message Signed message
* @param _signature Signature of message hash
* @param _publicKey secp256k1 public key in uncompressed format without prefix byte (64 bytes)
**/
function verifyEIP191(
bytes memory _message,
bytes memory _signature,
bytes memory _publicKey
)
internal
pure
returns (bool)
{
require(_publicKey.length == 64);
return toAddress(_publicKey) == recover(hashEIP191(_message), _signature);
}
}

View File

@ -43,6 +43,28 @@ contract SignatureVerifierMock {
return SignatureVerifier.verify(_message, _signature, _publicKey, _algorithm);
}
function verifyEIP191(
bytes memory _message,
bytes memory _signature,
bytes memory _publicKey
)
public
pure
returns (bool)
{
return SignatureVerifier.verifyEIP191(_message, _signature, _publicKey);
}
function hashEIP191(
bytes memory _message
)
public
pure
returns (bytes32)
{
return SignatureVerifier.hashEIP191(_message);
}
}

View File

@ -21,14 +21,16 @@ import os
import pytest
from cryptography.hazmat.backends.openssl import backend
from cryptography.hazmat.primitives import hashes
from eth_account.account import Account
from eth_account.messages import encode_defunct
from eth_tester.exceptions import TransactionFailed
from eth_utils import to_normalized_address
from eth_utils import to_normalized_address, to_checksum_address
from umbral.keys import UmbralPrivateKey
from umbral.signing import Signer
from nucypher.crypto.api import keccak_digest
from nucypher.crypto.utils import get_signature_recovery_value
from nucypher.crypto.api import keccak_digest, verify_eip_191
from nucypher.crypto.utils import get_signature_recovery_value, canonical_address_from_umbral_key
ALGORITHM_KECCAK256 = 0
ALGORITHM_SHA256 = 1
@ -144,3 +146,38 @@ def test_verify(testerchain, signature_verifier):
recoverable_signature,
umbral_pubkey_bytes[1:],
ALGORITHM_SHA256).call()
@pytest.mark.slow
def test_verify_eip191(testerchain, signature_verifier):
message = os.urandom(100)
# Generate Umbral key
umbral_privkey = UmbralPrivateKey.gen_key()
umbral_pubkey = umbral_privkey.get_pubkey()
umbral_pubkey_bytes = umbral_pubkey.to_bytes(is_compressed=False)
# Produce EIP191 signature
signable_message = encode_defunct(primitive=message)
signature = Account.sign_message(signable_message=signable_message,
private_key=umbral_privkey.to_bytes())
signature = bytes(signature.signature)
# Off-line verify, just in case
checksum_address = to_checksum_address(canonical_address_from_umbral_key(umbral_pubkey))
assert verify_eip_191(address=checksum_address,
message=message,
signature=signature)
# Verify signature
assert signature_verifier.functions.verifyEIP191(message,
signature,
umbral_pubkey_bytes[1:]).call()
# Check that the hash-based method also works independently
hash = signature_verifier.functions.hashEIP191(message).call()
eip191_header = "\x19Ethereum Signed Message:\n"+str(len(message))
assert hash == keccak_digest(eip191_header.encode() + message)
address = signature_verifier.functions.recover(hash, signature).call()
assert address == checksum_address