Skip to content

refactor: legacy second signature support #175

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: refactor/eip-155-support
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions crypto/identity/private_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ def sign(self, message: bytes) -> bytes:

return bytes([der[64]]) + der[0:64]

def sign_to_ecdsa(self, message: bytes) -> bytes:
"""Sign a message with this private key object in ECDSA format

Args:
message (bytes): bytes data you want to sign

Returns:
bytes: signature of the signed message
"""

message_hash = bytes.fromhex(keccak.new(data=message, digest_bits=256).hexdigest())

der = self.private_key.sign_recoverable(message_hash, hasher=None)

return der[0:64] + bytes([der[64]])

def to_hex(self):
"""Returns a private key in hex format

Expand Down
7 changes: 7 additions & 0 deletions crypto/transactions/builder/abstract_transaction_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ def sign(self, passphrase: str):
self.transaction.data['hash'] = self.transaction.get_id()
return self

def legacy_second_sign(self, passphrase: str, second_passphrase: str):
self.sign(passphrase)

self.transaction.legacy_second_sign(PrivateKey.from_passphrase(second_passphrase))

return self

def verify(self):
return self.transaction.verify()

Expand Down
9 changes: 9 additions & 0 deletions crypto/transactions/types/abstract_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ def sign(self, private_key: PrivateKey):

return self

def legacy_second_sign(self, second_private_key: PrivateKey):
transaction_hash = TransactionUtils.to_buffer(self.data, skip_signature=True).decode()

message = bytes.fromhex(transaction_hash)

self.data['legacySecondSignature'] = second_private_key.sign_to_ecdsa(message).hex()

return self

def recover_sender(self):
signature_with_recid = self._get_signature()
if not signature_with_recid:
Expand Down
9 changes: 4 additions & 5 deletions crypto/utils/transaction_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,18 @@ def to_buffer(cls, transaction: dict, skip_signature: bool = False) -> bytes:
fields.append(cls.to_be_array(int(transaction['v']) + (Network.get_network().chain_id() * 2 + 35)))
fields.append(bytes.fromhex(transaction['r']))
fields.append(bytes.fromhex(transaction['s']))

if 'legacySecondSignature' in transaction and transaction['legacySecondSignature']:
fields.append(bytes.fromhex(transaction['legacySecondSignature']))
else:
# Push chainId + 0s for r and s
fields.append(cls.to_be_array(Network.get_network().chain_id()))
fields.append(cls.to_be_array(0))
fields.append(cls.to_be_array(0))

# TODO: second signature handling

encoded = RlpEncoder.encode(fields)

hash_input = encoded

return hash_input.encode()
return encoded.encode()

@classmethod
def to_hash(cls, transaction: dict, skip_signature: bool = False) -> str:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"data": {
"nonce": "1",
"gasPrice": "5000000000",
"gasLimit": "21000",
"to": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22",
"value": "100000000",
"data": "",
"network": 11812,
"v": 0,
"r": "a1f79cb40a4bb409d6cebd874002ceda3ec0ccb614c1d8155f5c2f7f798135f9",
"s": "2d2ef517aaf6feed747385e260c206f46b2ce9d6b2a585427a111685a097bd79",
"legacySecondSignature": "094b33b2d10c4d48cff9a9b10f79990c9a902a762b00d2f2a4d2ddad7823b59332b2da193265068c67cefea9ca8bc84e47acec406f3300a0a10b77a56ea8c18801",
"senderPublicKey": "0243333347c8cbf4e3cbc7a96964181d02a2b0c854faa2fef86b4b8d92afcf473d",
"from": "0x1E6747BEAa5B4076a6A98D735DF8c35a70D18Bdd",
"hash": "a39435ec5de418e77479856d06a653efc171afe43e091472af22ee359eeb83be"
},
"serialized": "f8ad0185012a05f200825208946f0182a0cc707b055322ccf6d4cb6a5aff1aeb228405f5e10080825c6ba0a1f79cb40a4bb409d6cebd874002ceda3ec0ccb614c1d8155f5c2f7f798135f9a02d2ef517aaf6feed747385e260c206f46b2ce9d6b2a585427a111685a097bd79b841094b33b2d10c4d48cff9a9b10f79990c9a902a762b00d2f2a4d2ddad7823b59332b2da193265068c67cefea9ca8bc84e47acec406f3300a0a10b77a56ea8c18801"
}
6 changes: 6 additions & 0 deletions tests/transactions/builder/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ def passphrase():

return 'found lobster oblige describe ready addict body brave live vacuum display salute lizard combine gift resemble race senior quality reunion proud tell adjust angle'

@pytest.fixture
def second_passphrase():
"""Second Passphrase used for tests"""

return 'gold favorite math anchor detect march purpose such sausage crucial reform novel connect misery update episode invite salute barely garbage exclude winner visa cruise'

@pytest.fixture
def validator_public_key():
"""BLS Public used for validator tests"""
Expand Down
28 changes: 28 additions & 0 deletions tests/transactions/builder/test_transfer_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,34 @@ def test_it_should_sign_it_with_a_passphrase(passphrase, load_transaction_fixtur
assert builder.transaction.data['hash'] == fixture['data']['hash']
assert builder.verify()

def test_it_should_sign_with_a_legacy_second_signature(passphrase, second_passphrase, load_transaction_fixture):
fixture = load_transaction_fixture('transactions/transfer-legacy-second-signature')

builder = (
TransferBuilder
.new()
.gas_price(fixture['data']['gasPrice'])
.nonce(fixture['data']['nonce'])
.gas_limit(fixture['data']['gasLimit'])
.to(fixture['data']['to'])
.value(fixture['data']['value'])
.legacy_second_sign(passphrase, second_passphrase)
)

assert builder.transaction.data['gasPrice'] == int(fixture['data']['gasPrice'])
assert builder.transaction.data['gasLimit'] == int(fixture['data']['gasLimit'])
assert builder.transaction.data['nonce'] == fixture['data']['nonce']
assert builder.transaction.data['to'] == fixture['data']['to']
assert builder.transaction.data['value'] == int(fixture['data']['value'])
assert builder.transaction.data['v'] == fixture['data']['v']
assert builder.transaction.data['r'] == fixture['data']['r']
assert builder.transaction.data['s'] == fixture['data']['s']
assert builder.transaction.data['legacySecondSignature'] == fixture['data']['legacySecondSignature']

assert builder.transaction.serialize().hex() == fixture['serialized']
assert builder.transaction.data['hash'] == fixture['data']['hash']
assert builder.verify()

def test_it_should_handle_unit_converter(passphrase, address):
builder = (
TransferBuilder
Expand Down