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

refactor: update schnorr implementation #129

Merged
merged 33 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
424c701
update public key identity
alexbarnsley Jul 17, 2024
a36c950
add signature library
alexbarnsley Jul 17, 2024
356a9eb
transactions should be version 1
alexbarnsley Jul 17, 2024
db561bc
add expiration to transaction in builder
alexbarnsley Jul 17, 2024
eac4a47
use new signature signing and verification
alexbarnsley Jul 17, 2024
41d8986
test for signature
alexbarnsley Jul 17, 2024
35676b2
fix for asset deserialization
alexbarnsley Jul 17, 2024
5164d33
update tests
alexbarnsley Jul 17, 2024
1060d2f
add btc dependency
alexbarnsley Jul 17, 2024
92fc0b6
add btclib dependency to setup
alexbarnsley Jul 17, 2024
b0c2a84
update message signing and verifying
alexbarnsley Jul 24, 2024
17036c5
test real-life transfer signature
alexbarnsley Jul 24, 2024
a569009
fix recipient_id serialization
alexbarnsley Jul 24, 2024
f347309
Merge remote-tracking branch 'origin/feat/mainsail' into refactor/upd…
alexbarnsley Jul 25, 2024
edb69a9
wip
alexbarnsley Jul 25, 2024
03258eb
fix: transfer serialisation and test
ItsANameToo Jul 25, 2024
5bf0109
update serializers
alexbarnsley Jul 26, 2024
9d43619
update serializer tests
alexbarnsley Jul 26, 2024
2e9b276
remove tests for unused tx types
alexbarnsley Jul 26, 2024
8351ff1
update vote builder to include many votes/unvotes
alexbarnsley Jul 26, 2024
696e8ed
vote deserializer
alexbarnsley Jul 26, 2024
f30272c
delegate registration deserializer
alexbarnsley Jul 26, 2024
05ce981
update test for delegate resignation
alexbarnsley Jul 26, 2024
1015057
wip
ItsANameToo Jul 26, 2024
91d6baf
fix: delegate reg
ItsANameToo Jul 26, 2024
1652749
fix: transfer test
ItsANameToo Jul 26, 2024
272dc22
fix: vote tests
ItsANameToo Jul 26, 2024
8c66600
fix: multipayment test
ItsANameToo Jul 26, 2024
4da48a9
refactor: types to support python 3.8 and up
ItsANameToo Jul 26, 2024
a337547
wip
ItsANameToo Jul 26, 2024
d9901f2
fix: multipayment ser/der
ItsANameToo Jul 26, 2024
8e33db2
refactor: use pubkey pubkeyxonly
ItsANameToo Jul 26, 2024
4260a0f
fix: musig registration test
ItsANameToo Jul 26, 2024
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
2 changes: 1 addition & 1 deletion crypto/identity/address.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from Cryptodome.Hash import RIPEMD160, keccak
from coincurve import PrivateKey, PublicKey

def get_checksum_address(address):
def get_checksum_address(address: bytes) -> str:
"""Get checksum address

Args:
Expand Down
11 changes: 8 additions & 3 deletions crypto/identity/private_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@


class PrivateKey(object):

def __init__(self, private_key):
self.private_key = PvtKey.from_hex(private_key)
self.public_key = hexlify(self.private_key.public_key.format()).decode()
Expand All @@ -19,8 +18,14 @@ def sign(self, message):
Returns:
str: signature of the signed message
"""
signature = self.private_key.sign(message)
return hexlify(signature).decode()
from crypto.transactions.signature import Signature

signature = Signature.sign(
hexlify(message),
self.private_key.to_hex()
)

return signature.encode()

def to_hex(self):
"""Returns a private key in hex format
Expand Down
7 changes: 2 additions & 5 deletions crypto/identity/public_key.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
from binascii import hexlify, unhexlify
from binascii import hexlify

from coincurve import PublicKey as PubKey
from coincurve import PublicKeyXOnly as PubKey

from crypto.identity.private_key import PrivateKey


class PublicKey(object):

def __init__(self, public_key):
public_key = unhexlify(public_key.encode())
self.public_key = PubKey(public_key)

def to_hex(self):
Expand Down
25 changes: 12 additions & 13 deletions crypto/transactions/builder/base.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import hashlib
from binascii import hexlify, unhexlify

from crypto.configuration.fee import get_fee
from crypto.constants import HTLC_LOCK_EXPIRATION_TYPE, TRANSACTION_TYPE_GROUP
from crypto.identity.private_key import PrivateKey
from crypto.identity.public_key import PublicKey
from crypto.schnorr import schnorr
from crypto.transactions.signature import Signature
from crypto.transactions.transaction import Transaction
from crypto.utils.message import Message


class BaseTransactionBuilder(object):

Expand All @@ -19,7 +16,8 @@ def __init__(self):
self.transaction.nonce = getattr(self, 'nonce', None)
self.transaction.typeGroup = getattr(self, 'typeGroup', 1)
self.transaction.signatures = getattr(self, 'signatures', None)
self.transaction.version = getattr(self, 'version', 2)
self.transaction.version = getattr(self, 'version', 1)
self.transaction.expiration = getattr(self, 'expiration', 0)
if self.transaction.type != 0:
self.transaction.amount = getattr(self, 'amount', 0)

Expand All @@ -29,16 +27,17 @@ def to_dict(self):
def to_json(self):
return self.transaction.to_json()

def schnorr_sign(self, passphrase):
def sign(self, passphrase):
"""Sign the transaction using the given passphrase

Args:
passphrase (str): passphrase associated with the account sending this transaction
"""
self.transaction.senderPublicKey = PublicKey.from_passphrase(passphrase)
msg = hashlib.sha256(self.transaction.to_bytes(False, True, False)).digest()

msg = self.transaction.to_bytes(False, True, False)
secret = unhexlify(PrivateKey.from_passphrase(passphrase).to_hex())
self.transaction.signature = hexlify(schnorr.bcrypto410_sign(msg, secret))
self.transaction.signature = Signature.sign(msg, secret)
self.transaction.id = self.transaction.get_id()

def second_sign(self, passphrase):
Expand All @@ -47,9 +46,9 @@ def second_sign(self, passphrase):
Args:
passphrase (str): 2nd passphrase associated with the account sending this transaction
"""
msg = hashlib.sha256(self.transaction.to_bytes(False, True, False)).digest()
msg = self.transaction.to_bytes(False, True, False)
secret = unhexlify(PrivateKey.from_passphrase(passphrase).to_hex())
self.transaction.signSignature = hexlify(schnorr.bcrypto410_sign(msg, secret))
self.transaction.signSignature = Signature.sign(msg, secret)
self.transaction.id = self.transaction.get_id()

def multi_sign(self, passphrase, index):
Expand All @@ -58,16 +57,16 @@ def multi_sign(self, passphrase, index):

index = len(self.transaction.signatures) if index == -1 else index

msg = hashlib.sha256(self.transaction.to_bytes()).digest()
msg = self.transaction.to_bytes()
secret = unhexlify(PrivateKey.from_passphrase(passphrase).to_hex())
signature = hexlify(schnorr.bcrypto410_sign(msg, secret))
signature = hexlify(Signature.sign(msg, secret).encode())

index_formatted = hex(index).replace('x', '')
self.transaction.signatures.append(index_formatted + signature.decode())

def schnorr_verify(self):
return self.transaction.verify_schnorr()

def schnorr_verify_second(self, secondPublicKey):
return self.transaction.verify_schnorr_secondsig(secondPublicKey)

Expand Down
11 changes: 6 additions & 5 deletions crypto/transactions/builder/vote.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@


class Vote(BaseTransactionBuilder):

transaction_type = TRANSACTION_VOTE

def __init__(self, vote, fee=None):
"""Create a second signature registration transaction
def __init__(self, votes: list[str] | None, unvotes: list[str] | None, fee: int | None = None):
"""Create a vote transaction

Args:
vote (str): address of a delegate you want to vote
vote (str, optional): address of a delegate you want to vote
unvote (str, optional): address of a delegate you want to unvote
fee (int, optional): fee used for the transaction (default is already set)
"""
super().__init__()

self.transaction.asset['votes'] = [vote]
self.transaction.asset['votes'] = votes
self.transaction.asset['unvotes'] = unvotes

if fee:
self.transaction.fee = fee
Expand Down
20 changes: 2 additions & 18 deletions crypto/transactions/deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,7 @@ def deserialize(self):
handled_transaction = self._handle_transaction_type(asset_offset, transaction)
transaction.amount = handled_transaction.amount
transaction.version = handled_transaction.version
if transaction.version == 2:
transaction = self._handle_version_two(transaction)
else:
raise Exception('should this ever happen?')
transaction.id = transaction.get_id()

return transaction

Expand All @@ -79,18 +76,5 @@ def _handle_transaction_type(self, asset_offset, transaction):
# this attribute is actually a specific deserializer that we want to use
deserializer = attribute
break
return deserializer(self.serialized, asset_offset, transaction).deserialize()

def _handle_version_two(self, transaction):
"""Handle deserialization for version two

Args:
transaction (object): Transaction resource object

Returns:
object: Transaction resource object of currently deserialized data
"""

transaction.id = sha256(unhexlify(transaction.serialize(False, True, False))).hexdigest()

return transaction
return deserializer(self.serialized, asset_offset, transaction).deserialize()
14 changes: 4 additions & 10 deletions crypto/transactions/deserializers/delegate_registration.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from binascii import hexlify, unhexlify

from binary.unsigned_integer.reader import read_bit8
from binascii import hexlify

from crypto.transactions.deserializers.base import BaseDeserializer

Expand All @@ -10,17 +8,13 @@ class DelegateRegistrationDeserializer(BaseDeserializer):
def deserialize(self):
starting_position = int(self.asset_offset / 2)

username_length = read_bit8(self.serialized, starting_position) & 0xff
start_index = self.asset_offset + 2
end_index = start_index + (username_length * 2)
username = hexlify(self.serialized)[start_index:end_index]
username = unhexlify(username)
validator_public_key = self.serialized[starting_position:starting_position + 48]

self.transaction.asset['delegate'] = {'username': username.decode()}
self.transaction.asset['validatorPublicKey'] = validator_public_key.hex()

self.transaction.parse_signatures(
hexlify(self.serialized).decode(),
self.asset_offset + (username_length + 1) * 2
self.asset_offset + (48 * 2)
)

return self.transaction
12 changes: 5 additions & 7 deletions crypto/transactions/deserializers/transfer.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
from binascii import hexlify, unhexlify

from base58 import b58encode_check

from binary.unsigned_integer.reader import read_bit32, read_bit64

from crypto.identity.address import get_checksum_address
from crypto.transactions.deserializers.base import BaseDeserializer


class TransferDeserializer(BaseDeserializer):

def deserialize(self):
starting_position = int(self.asset_offset / 2)

self.transaction.amount = read_bit64(self.serialized, offset=starting_position)
self.transaction.expiration = read_bit32(self.serialized, offset=starting_position + 8)

recipient_start_index = (int(self.asset_offset / 2) + 12) * 2
recipient_start_index = (int(self.asset_offset / 2) + 11) * 2
recipientId = hexlify(self.serialized)[recipient_start_index:recipient_start_index + 42]
self.transaction.recipientId = b58encode_check(unhexlify(recipientId)).decode()

self.transaction.recipientId = get_checksum_address(unhexlify(recipientId).hex())

self.transaction.parse_signatures(
hexlify(self.serialized).decode(),
self.asset_offset + (8 + 4 + 21) * 2
self.asset_offset + (8 + 4 + 20) * 2
)

return self.transaction
26 changes: 16 additions & 10 deletions crypto/transactions/deserializers/vote.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,36 @@

from crypto.transactions.deserializers.base import BaseDeserializer


class VoteDeserializer(BaseDeserializer):

def deserialize(self):
starting_position = int(self.asset_offset / 2)

vote_length = read_bit8(self.serialized, starting_position) & 0xff

self.transaction.asset['votes'] = []
self.transaction.asset['unvotes'] = []

vote_position = starting_position + 1

for index in range(vote_length):
starting_position = self.asset_offset + 2 + (index * 2 * 34)
vote = hexlify(self.serialized)[starting_position:starting_position + 2 * 34].decode()
if vote[1] == '1':
operator = '+'
else:
operator = '-'
vote = '{}{}'.format(operator, vote[2:])
vote = self.serialized[vote_position + (index * 33):vote_position + (index * 33) + 33].hex()

self.transaction.asset['votes'].append(vote)

unvote_position = vote_position + (vote_length * 33)

unvote_length = read_bit8(self.serialized, unvote_position) & 0xff

unvote_position += 1

for index in range(unvote_length):
unvote = self.serialized[unvote_position + (index * 33):unvote_position + (index * 33) + 33].hex()

self.transaction.asset['unvotes'].append(unvote)

self.transaction.parse_signatures(
hexlify(self.serialized).decode(),
self.asset_offset + 2 + (vote_length * 34 * 2)
self.asset_offset + 2 + (vote_length * 66) + 2 + (unvote_length * 66)
)

return self.transaction
2 changes: 1 addition & 1 deletion crypto/transactions/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def serialize(self, skip_signature=True, skip_second_signature=True, skip_multi_

bytes_data += write_bit8(0xff)

bytes_data += write_bit8(self.transaction.get('version') or 0x02)
bytes_data += write_bit8(self.transaction.get('version') or 0x01)
bytes_data += write_bit8(self.transaction.get('network') or network_config['version'])
bytes_data += write_bit32(self.transaction.get('typeGroup') or 0x01)
bytes_data += write_bit16(self.transaction.get('type'))
Expand Down
7 changes: 3 additions & 4 deletions crypto/transactions/serializers/delegate_registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@
from binary.unsigned_integer.writer import write_bit8

from crypto.transactions.serializers.base import BaseSerializer

from binary.hex.writer import write_high

class DelegateRegistrationSerializer(BaseSerializer):
"""Serializer handling delegate registration data
"""

def serialize(self):
delegate_bytes = hexlify(self.transaction['asset']['delegate']['username'].encode())
delegate_bytes = self.transaction['asset']['validatorPublicKey'].encode()

self.bytes_data += write_bit8(len(delegate_bytes) // 2)
self.bytes_data += unhexlify(delegate_bytes)
self.bytes_data += write_high(delegate_bytes)

return self.bytes_data
8 changes: 6 additions & 2 deletions crypto/transactions/serializers/multi_payment.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ def serialize(self):
self.bytes_data += write_bit16(len(self.transaction['asset']['payments']))

for payment in self.transaction['asset']['payments']:
recipient = payment['recipientId'][2:]

if type(recipient) is str:
recipient = recipient.encode()

self.bytes_data += write_bit64(payment['amount'])
recipientId = hexlify(b58decode_check(payment['recipientId']))
self.bytes_data += write_high(recipientId)
self.bytes_data += write_high(recipient)

return self.bytes_data
9 changes: 7 additions & 2 deletions crypto/transactions/serializers/transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ class TransferSerializer(BaseSerializer):
def serialize(self):
self.bytes_data += write_bit64(self.transaction['amount'])
self.bytes_data += write_bit32(self.transaction.get('expiration', 0))
recipientId = hexlify(b58decode_check(self.transaction['recipientId']))
self.bytes_data += write_high(recipientId)

recipient = self.transaction['recipientId'][2:]

if type(recipient) is str:
recipient = recipient.encode()

self.bytes_data += write_high(recipient)

return self.bytes_data
12 changes: 8 additions & 4 deletions crypto/transactions/serializers/vote.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@ class VoteSerializer(BaseSerializer):

def serialize(self):
vote_bytes = []
unvote_bytes = []

for vote in self.transaction['asset']['votes']:
if vote.startswith('+'):
vote_bytes.append('01{}'.format(vote[1::]))
else:
vote_bytes.append('00{}'.format(vote[1::]))
vote_bytes.append(vote)

for unvote in self.transaction['asset']['unvotes']:
unvote_bytes.append(unvote)

self.bytes_data += write_bit8(len(self.transaction['asset']['votes']))
self.bytes_data += unhexlify(''.join(vote_bytes))

self.bytes_data += write_bit8(len(self.transaction['asset']['unvotes']))
self.bytes_data += unhexlify(''.join(unvote_bytes))

return self.bytes_data
18 changes: 18 additions & 0 deletions crypto/transactions/signature.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from crypto.identity.private_key import PrivateKey
from btclib.ecc import ssa

class Signature:
@staticmethod
def verify(signature, message, publicKey: bytes):
# Remove leading byte ('02' / '03') from ECDSA key
if (len(publicKey) == 33):
publicKey = publicKey[1:]

return ssa.verify(message, publicKey, signature)

@staticmethod
def sign(message, privateKey: bytes | PrivateKey):
if isinstance(privateKey, PrivateKey):
privateKey = bytes.fromhex(privateKey.to_hex())

return ssa.sign(message, privateKey).serialize(False).hex()
Loading
Loading