mirror of https://github.com/nucypher/nucypher.git
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 nucypherpull/1029/head
parent
29a53c4392
commit
ba5da0dfec
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue