def test_rivine_types(): e = encoder_rivine_get() # in the rivine_basic test we saw we can # serialise anything using the add method. # One can however also directly encode the specific type as desired, # which allows for example the encoding of an integer as a specific byte size. e.add_int8(1) e.add_int16(2) e.add_int24(3) e.add_int32(4) e.add_int64(5) # a single byte can be added as well e.add_byte(6) e.add_byte('4') e.add_byte(b'2') # array are like slices, but have no length prefix, # therefore this is only useful if there is a fixed amount of elements, # known by all parties e.add_array([False, True, True]) # the result is a single bytearray assert e.data == b'\x01\x02\x00\x03\x00\x00\x04\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x0642\x00\x01\x01'
def _signature_hash_input_get(self, *extra_objects): e = encoder_rivine_get() # encode the transaction version e.add_int8(self.version) # encode the specifier e.add_array(TransactionV210._SPECIFIER) # encode the public key e.add_all(self.public_key) # extra objects if any if extra_objects: e.add_all(*extra_objects) # encode coin inputs e.add(len(self.coin_inputs)) for ci in self.coin_inputs: e.add(ci.parentid) # encode registration and transaction fee e.add_all(self.registration_fee, self.transaction_fee) # encode refund coin output if self._refund_coin_output is None: e.add_int8(0) else: e.add_int8(1) e.add(self._refund_coin_output) # return data return e.data
def test_rivine_limits(): e = encoder_rivine_get() # everything has limits, so do types, # that is what this test is about # no integer can be negative with pytest.raises(IntegerOutOfRange): e.add(-1) with pytest.raises(IntegerOutOfRange): e.add_int64(-1) with pytest.raises(IntegerOutOfRange): e.add_int32(-1) with pytest.raises(IntegerOutOfRange): e.add_int24(-1) with pytest.raises(IntegerOutOfRange): e.add_int16(-1) with pytest.raises(IntegerOutOfRange): e.add_int8(-1) # integers have upper limits as well with pytest.raises(IntegerOutOfRange): e.add(1 << 64) with pytest.raises(IntegerOutOfRange): e.add_int64(1 << 64) with pytest.raises(IntegerOutOfRange): e.add_int32(1 << 32) with pytest.raises(IntegerOutOfRange): e.add_int24(1 << 24) with pytest.raises(IntegerOutOfRange): e.add_int16(1 << 16) with pytest.raises(IntegerOutOfRange): e.add_int8(1 << 8)
def _checksum(self): if self._type == UnlockHashType.NIL: return b'\x00'*UnlockHash._CHECKSUM_SIZE e = encoder_rivine_get() e.add_int8(int(self._type)) e.add(self._hash) return bytearray.fromhex(blake2_string(e.data))
def sia_binary_encode(self, encoder): """ Sia binary encoding uses the Rivine Encoder for binary-encoding a network address, as it is only supported for backwards compatibility. """ renc = encoder_rivine_get() self.rivine_binary_encode_data(renc) encoder.add_array(renc.data)
def sia_binary_encode(self, encoder): """ Sia binary encodes a BotMonthsAndFlagsData as a one-byte flag, uses the Rivine Encoder. See rivine_binary_encode for more information. """ e = encoder_rivine_get() self.rivine_binary_encode(e) encoder.add_array(e.data)
def rivine_binary_encode(self, encoder): """ Encode this Fulfillment according to the Rivine Binary Encoding format. """ encoder.add_int8(int(self.type)) data_enc = encoder_rivine_get() self.rivine_binary_encode_data(data_enc) encoder.add_slice(data_enc.data)
def unlockhash(self): e = encoder_rivine_get() self.sia_binary_encode_data(e) # need to encode again to add the length data = e.data e = encoder_sia_get() e.add_slice(data) hash = bytearray.fromhex(blake2_string(e.data)) return UnlockHash(type=UnlockHashType.ATOMIC_SWAP, hash=hash)
def test_rivine_basic_encoding(): e = encoder_rivine_get() # you can add integers, booleans, iterateble objects, strings, # bytes and byte arrays. Dictionaries and objects are not supported. e.add(False) e.add("a") e.add([1, True, "foo"]) e.add(b"123") # the result is a single bytearray assert e.data == b'\x00\x02a\x06\x01\x00\x00\x00\x00\x00\x00\x00\x01\x06foo\x06123'
def _binary_encode_data(self): e = encoder_rivine_get() # encode all properties e.add_all( self.address, self.value, self.transaction_fee, self.blockid, self.transactionid, ) # return encoded data return e.data
def _binary_encode_data(self): e = encoder_rivine_get() # get addresses and names addresses_to_add = self.addresses_to_add addresses_to_remove = self.addresses_to_remove names_to_add = self.names_to_add names_to_remove = self.names_to_remove addresses_to_add_length = len(addresses_to_add) addresses_to_remove_length = len(addresses_to_remove) names_to_add_length = len(names_to_add) names_to_remove_length = len(names_to_remove) # encode the identifier e.add_int32(self.botid) # encode bot binary encoding prefix (containing length and refund info) maf = BotMonthsAndFlagsData( number_of_months=self.number_of_months, has_addresses=(addresses_to_add_length > 0 or addresses_to_remove_length > 0), has_names=(names_to_add_length > 0 or names_to_remove_length > 0), has_refund=(self._refund_coin_output is not None), ) e.add(maf) # if we have addresses, encode it if maf.has_addresses: e.add_int8(addresses_to_add_length | (addresses_to_remove_length << 4)) e.add_array(addresses_to_add) e.add_array(addresses_to_remove) # if we have names, encode it if maf.has_names: e.add_int8(names_to_add_length | (names_to_remove_length << 4)) e.add_array(names_to_add) e.add_array(names_to_remove) # encode transaction fee and coin inputs e.add_all(self.transaction_fee, self.coin_inputs) # encode refund coin output, if defined if maf.has_refund: e.add(self._refund_coin_output) # encode the signature at the end e.add(self.signature) # return encoded data return e.data
def _binary_encode_data(self): e = encoder_rivine_get() # encode all easy properties e.add_all(self.public_key, self.signature, self.registration_fee, self.transaction_fee, self.coin_inputs) # encode the only "pointer" property if self._refund_coin_output is None: e.add_int8(0) else: e.add_int8(1) e.add(self._refund_coin_output) # return encoded data return e.data
def _signature_hash_input_get(self, *extra_objects): e = encoder_rivine_get() # encode the transaction version e.add_int8(self.version) # encode the specifier e.add_array(TransactionV145._SPECIFIER) # encode the botID e.add_int32(self.botid) # extra objects if any if extra_objects: e.add_all(*extra_objects) # encode addresses, names and number of months e.add_all(self.addresses_to_add, self.addresses_to_remove) e.add_all(self.names_to_add, self.names_to_remove) e.add_int8(self.number_of_months) # encode coin inputs e.add(len(self.coin_inputs)) for ci in self.coin_inputs: e.add(ci.parentid) # encode transaction fee e.add_all(self.transaction_fee) # encode refund coin output if self._refund_coin_output is None: e.add_int8(0) else: e.add_int8(1) e.add(self._refund_coin_output) # return data return e.data
def _binary_encode_data(self): e = encoder_rivine_get() # get names names = self.names names_length = len(names) info_value = names_length # get has refund status has_refund = False if self._refund_coin_output is not None: has_refund = True info_value |= 16 # encode sender bot info e.add_int32(self.sender_botid) e.add(self.sender_signature) # encode receiver bot info e.add_int32(self.receiver_botid) e.add(self.receiver_signature) # encode info value e.add_int8(info_value) # encode the names e.add_array(names) # encode transaction fee and coin inputs e.add_all(self.transaction_fee, self.coin_inputs) # encode refund coin output, if defined if has_refund: e.add(self._refund_coin_output) # return encoded data return e.data
def _signature_hash_input_get(self, *extra_objects): e = encoder_rivine_get() # encode the transaction version e.add_int8(self.version) # encode the specifier e.add_array(TransactionV146._SPECIFIER) # encode the bot identifiers e.add_int32(self.sender_botid) e.add_int32(self.receiver_botid) # extra objects if any if extra_objects: e.add_all(*extra_objects) # encode names e.add(self.names) # encode coin inputs e.add(len(self.coin_inputs)) for ci in self.coin_inputs: e.add(ci.parentid) # encode transaction fee e.add(self.transaction_fee) # encode refund coin output if self._refund_coin_output is None: e.add_int8(0) else: e.add_int8(1) e.add(self._refund_coin_output) # return data return e.data
def test_rivine_custom(): e = encoder_rivine_get() # a class that provides a custom encoding logic for its types, # required in order to be able to encode Python objects class Answer(BaseRivineObjectEncoder): def __init__(self, number=0): self._number = number def rivine_binary_encode(self, encoder): if self._number % 2 != 0: return encoder.add_int24(self._number - 1) return encoder.add_int24(self._number) # when we add our objects they will be encoded # using the method as provided by its type e.add(Answer()) e.add(Answer(43)) # this works for slices and arrays as well e.add_array([Answer(5), Answer(2)]) # the result is a single bytearray assert e.data == b'\x00\x00\x00\x2A\x00\x00\x04\x00\x00\x02\x00\x00'
def _binary_encode_data(self): e = encoder_rivine_get() # get addresses and names addresses = self.addresses names = self.names addresses_length = len(addresses) names_length = len(names) # encode bot binary encoding prefix (containing length and refund info) maf = BotMonthsAndFlagsData( number_of_months=self.number_of_months, has_addresses=(addresses_length>0), has_names=(names_length>0), has_refund=(self._refund_coin_output is not None), ) e.add(maf) # encode the address and name length e.add_int8(addresses_length | (names_length<< 4)) # encode all addresses and names e.add_array(addresses) e.add_array(names) # encode transaction fee and coin inputs e.add_all(self.transaction_fee, self.coin_inputs) # encode refund coin output, if defined if maf.has_refund: e.add(self._refund_coin_output) # encode the identification at the end e.add_all(self.public_key, self.signature) # return encoded data return e.data
def test_rivine_encoded(obj, expected): test_encoded(encoder_rivine_get(), obj, expected)