Skip to content
Merged
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