def test_sia_custom(): e = SiaBinaryEncoder() # a class that provides a custom encoding logic for its types, # required in order to be able to encode Python objects class Answer(SiaBinaryObjectEncoderBase): def __init__(self, number=0): self._number = number def sia_binary_encode(self, encoder): if self._number == 42: return encoder.add(True) return encoder.add(False) # when we add our objects they will be encoded # using the method as provided by its type e.add(Answer()) e.add(Answer(42)) # this works for slices and arrays as well e.add_array([Answer(5), Answer(2)]) # the result is a single bytearray jsass.equals(e.data, b'\x00\x01\x00\x00') # everything has limits, so do types, # that is what this test is about # no integer can be negative jsass.raises(IntegerOutOfRange, lambda _: e.add(-1))
def _binary_encode_data(self): """ Default Binary encoding of a Transaction Data, can be overriden if required. """ encoder = SiaBinaryEncoder() encoder.add_all( self.coin_inputs, self.coin_outputs, self.blockstake_inputs, self.blockstake_outputs, self.miner_fees, self.data, ) return encoder.data
def test_sia_basic_encoding(): e = SiaBinaryEncoder() # 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 jsass.equals( e.data, b'\x00\x01\x00\x00\x00\x00\x00\x00\x00a\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x03\x00\x00\x00\x00\x00\x00\x00foo\x03\x00\x00\x00\x00\x00\x00\x00123' )
def _id_new(self, specifier=None, index=None): encoder = SiaBinaryEncoder() if specifier != None: encoder.add_array(specifier) encoder.add_array(self._id_input_compute()) if index != None: encoder.add_int(index) hash = blake2b(encoder.data) return Hash(value=hash)
def sia_binary_encode(self, encoder): """ Encode this Condition according to the Sia Binary Encoding format. """ encoder.add_array(bytes([self.ctype])) data_enc = SiaBinaryEncoder() self.sia_binary_encode_data(data_enc) encoder.add_slice(data_enc.data)
def sia_binary_encode(self, encoder): """ Encode this Fulfillment according to the Sia Binary Encoding format. """ encoder.add_array(bytearray([int(self.ftype)])) data_enc = SiaBinaryEncoder() self.sia_binary_encode_data(data_enc) encoder.add_slice(data_enc.data)
def _custom_unlockhash_getter(self): e = RivineBinaryEncoder() self.sia_binary_encode_data(e) # need to encode again to add the length data = e.data e = SiaBinaryEncoder() e.add_slice(data) hash = jscrypto.blake2b(e.data) return UnlockHash(uhtype=UnlockHashType.ATOMIC_SWAP, uhhash=hash)
def from_unlockhash(cls, unlockhash): """ Create an ERC20 Address from a TFT Address (type: UnlockHash). """ if isinstance(unlockhash, str): raise TypeError("unlockhash has to be already decoded from str before calling this func") e = SiaBinaryEncoder() unlockhash.sia_binary_encode(e) hash = jscrypto.blake2b(e.data) return cls(value=jsarr.slice_array(hash, Hash.SIZE-ERC20Address.SIZE))
def test_sia_types(): e = SiaBinaryEncoder() # in the sia_basic test we saw we can # serialise anything using the add method. # by default strings, byte arrays and iterateable objects # are encoded as slices. # # 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]) # a single byte can be added as well e.add_byte(6) e.add_byte('4') e.add_byte(b'2') # the result is a single bytearray jsass.equals(e.data, b'\x00\x01\x01\x0642')
def _binary_encode_data(self): encoder = SiaBinaryEncoder() encoder.add_array(self._nonce.value) encoder.add_all( self.mint_fulfillment, self.coin_outputs, self.miner_fees, self.data, ) return encoder.data
def binary_encode(self): """ Binary encoding of a Transaction, overriden to specify the version correctly """ if self._legacy: return jsarr.concat(bytearray([TransactionVersion.LEGACY.__int__()]), self._binary_encode_data()) encoder = SiaBinaryEncoder() encoder.add_array(bytearray([TransactionVersion.STANDARD.__int__()])) encoder.add_slice(self._binary_encode_data()) return encoder.data
def unlockhash(self): """ Return the unlock hash generated from this public key. """ e = SiaBinaryEncoder() self.sia_binary_encode(e) # need to encode again to add the length data = e.data e = SiaBinaryEncoder() e.add_slice(data) hash = jscrypto.blake2b(e.data) return UnlockHash(uhtype=UnlockHashType.PUBLIC_KEY, uhhash=hash)
def _legacy_signature_hash_input_get(self, *extra_objects): e = SiaBinaryEncoder() # encode extra objects if exists if extra_objects: e.add_all(*extra_objects) # encode coin inputs for ci in self.coin_inputs: e.add_all(ci.parentid, ci.fulfillment.public_key.unlockhash) # encode coin outputs e.add(len(self.coin_outputs)) for co in self.coin_outputs: e.add_all(co.value, co.condition.unlockhash) # encode blockstake inputs for bsi in self.blockstake_inputs: e.add_all(bsi.parentid, bsi.fulfillment.public_key.unlockhash) # encode blockstake outputs e.add(len(self.blockstake_outputs)) for bso in self.blockstake_outputs: e.add_all(bso.value, bso.condition.unlockhash) # encode miner fees e.add_slice(self.miner_fees) # encode custom data e.add(self.data) # return the encoded data return e.data
def _binary_encode_data(self): if not self._legacy: return super()._binary_encode_data() # encoding was slightly different in legacy transactions (v0) # (NOTE: we only support the subset of v0 transactions that are actually active on the tfchain network) encoder = SiaBinaryEncoder() # > encode coin inputs encoder.add_int(len(self.coin_inputs)) for ci in self.coin_inputs: encoder.add(ci.parentid) encoder.add_array(bytearray([1])) # FulfillmentTypeSingleSignature sub_encoder = SiaBinaryEncoder() sub_encoder.add(ci.fulfillment.public_key) encoder.add_slice(sub_encoder.data) encoder.add(ci.fulfillment.signature) # > encode coin outputs encoder.add_int(len(self.coin_outputs)) for co in self.coin_outputs: encoder.add_all(co.value, co.condition.unlockhash) # > encode block stake inputs encoder.add_int(len(self._blockstake_inputs)) for bsi in self._blockstake_inputs: encoder.add(bsi.parentid) encoder.add_array(bytearray([1])) # FulfillmentTypeSingleSignature sub_encoder = SiaBinaryEncoder() sub_encoder.add(bsi.fulfillment.public_key) encoder.add_slice(sub_encoder.data) encoder.add(bsi.fulfillment.signature) # > encode block stake outputs encoder.add_int(len(self._blockstake_outputs)) for bso in self.blockstake_outputs: encoder.add_all(bso.value, bso.condition.unlockhash) # > encode miner fees and arbitrary data encoder.add_all(self.miner_fees, self.data) return encoder.data
def _signature_hash_input_get(self, *extra_objects): e = SiaBinaryEncoder() # encode the transaction version e.add_byte(self.version.__int__()) # encode the specifier e.add_array(TransactionV129._SPECIFIER) # encode nonce e.add_array(self._nonce.value) # extra objects if any if extra_objects: e.add_all(*extra_objects) # encode coin outputs e.add_slice(self.coin_outputs) # encode miner fees e.add_slice(self.miner_fees) # encode custom data e.add(self.data) # return the encoded data return e.data
def test_sia_encoded(obj, expected): test_encoded(SiaBinaryEncoder(), obj, expected)
def _signature_hash_input_get(self, *extra_objects): if self._legacy: return self._legacy_signature_hash_input_get(*extra_objects) e = SiaBinaryEncoder() # encode the transaction version e.add_byte(self.version.__int__()) # encode extra objects if exists if extra_objects: e.add_all(*extra_objects) # encode the number of coins inputs e.add(len(self.coin_inputs)) # encode coin inputs parent_ids for ci in self.coin_inputs: e.add(ci.parentid) # encode coin outputs e.add_slice(self.coin_outputs) # encode the number of blockstake inputs e.add(len(self.blockstake_inputs)) # encode blockstake inputs parent_ids for bsi in self.blockstake_inputs: e.add(bsi.parentid) # encode blockstake outputs e.add_slice(self.blockstake_outputs) # encode miner fees e.add_slice(self.miner_fees) # encode custom data e.add(self.data) # return the encoded data return e.data