def check_multisig_with_ECDSA_Secp256r1(engine: contracts.ApplicationEngine, public_keys: List[bytes], signatures: List[bytes]) -> bool: len_pub_keys = len(public_keys) len_sigs = len(signatures) if len_sigs == 0: raise ValueError("No signatures supplied") if len_pub_keys == 0: raise ValueError("No public keys supplied") if len_sigs > len_pub_keys: raise ValueError(f"Verification requires {len_sigs} public keys, got only {len_pub_keys}") message = engine.script_container.get_hash_data(settings.network.magic) engine.add_gas(len_pub_keys * CHECKSIG_PRICE * engine.exec_fee_factor) i = 0 j = 0 try: while i < len_sigs and j < len_pub_keys: if cryptography.verify_signature(message, signatures[i], public_keys[j], cryptography.ECCCurve.SECP256R1): i += 1 j += 1 if len_sigs - i > len_pub_keys - j: return False except cryptography.ECCException as e: return False return True
def register_candidate(self, engine: contracts.ApplicationEngine, public_key: cryptography.ECPoint) -> bool: """ Register a candidate for consensus node election. Args: engine: Application engine instance public_key: the candidate's public key Returns: True is succesfully registered. False otherwise. """ script_hash = to_script_hash( contracts.Contract.create_signature_redeemscript(public_key)) if not engine.checkwitness(script_hash): return False engine.add_gas(self.get_register_price(engine.snapshot)) storage_key = self.key_candidate + public_key storage_item = engine.snapshot.storages.try_get(storage_key, read_only=False) if storage_item is None: state = _CandidateState() state.registered = True storage_item = storage.StorageItem(state.to_array()) engine.snapshot.storages.put(storage_key, storage_item) else: state = storage_item.get(_CandidateState) state.registered = True self._candidates_dirty = True return True
def _storage_put_internal(engine: contracts.ApplicationEngine, context: storage.StorageContext, key: bytes, value: bytes, flags: storage.StorageFlags) -> None: if len(key) > MAX_STORAGE_KEY_SIZE: raise ValueError( f"Storage key length exceeds maximum of {MAX_STORAGE_KEY_SIZE}") if len(value) > MAX_STORAGE_VALUE_SIZE: raise ValueError( f"Storage value length exceeds maximum of {MAX_STORAGE_VALUE_SIZE}" ) if context.is_read_only: raise ValueError("Cannot persist to read-only storage context") storage_key = storage.StorageKey(context.script_hash, key) item = engine.snapshot.storages.try_get(storage_key, read_only=False) is_constant = storage.StorageFlags.CONSTANT in flags if item is None: new_data_len = len(key) + len(value) item = storage.StorageItem(b'', is_constant) engine.snapshot.storages.put(storage_key, item) else: if item.is_constant: raise ValueError("StorageItem is marked as constant") if len(value) <= len(item.value): new_data_len = 1 else: new_data_len = len(value) - len(item.value) engine.add_gas(new_data_len * STORAGE_PRICE) item.value = value item.is_constant = is_constant
def _check_multisig(engine: contracts.ApplicationEngine, stack_item: vm.StackItem, public_keys: List[bytes], signatures: List[bytes], curve: cryptography.ECCCurve) -> bool: len_pub_keys = len(public_keys) len_sigs = len(signatures) if len_sigs == 0: raise ValueError("No signatures supplied") if len_pub_keys == 0: raise ValueError("No public keys supplied") if len_sigs > len_pub_keys: raise ValueError( f"Verification requires {len_sigs} public keys, got only {len_pub_keys}" ) message = stackitem_to_hash_data(engine, stack_item) engine.add_gas(len_pub_keys * 1000000) i = 0 j = 0 try: while i < len_sigs and j < len_pub_keys: if cryptography.verify_signature(message, signatures[i], public_keys[j], curve): i += 1 j += 1 if len_sigs - i > len_pub_keys - j: return False except cryptography.ECCException as e: return False return True
def storage_put(engine: contracts.ApplicationEngine, context: storage.StorageContext, key: bytes, value: bytes) -> None: if len(key) > MAX_STORAGE_KEY_SIZE: raise ValueError( f"Storage key length exceeds maximum of {MAX_STORAGE_KEY_SIZE}") if len(value) > MAX_STORAGE_VALUE_SIZE: raise ValueError( f"Storage value length exceeds maximum of {MAX_STORAGE_VALUE_SIZE}" ) if context.is_read_only: raise ValueError("Cannot persist to read-only storage context") storage_key = storage.StorageKey(context.id, key) item = engine.snapshot.storages.try_get(storage_key, read_only=False) if item is None: new_data_len = len(key) + len(value) item = storage.StorageItem(b'') engine.snapshot.storages.put(storage_key, item) else: if len(value) == 0: new_data_len = 0 elif len(value) <= len(item.value): new_data_len = (len(value) - 1) // 4 + 1 elif len(item.value) == 0: new_data_len = len(value) else: new_data_len = (len(item.value) - 1) // 4 + 1 + len(value) - len( item.value) engine.add_gas(new_data_len * engine.STORAGE_PRICE) item.value = value
def invoke(self, engine: contracts.ApplicationEngine, version: int) -> None: """ Calls a contract function Reads the required arguments from the engine's stack and converts them to the appropiate contract function types Args: engine: the engine executing the smart contract version: which version of the smart contract to load Raises: ValueError: if the request contract version is not ValueError: if the function to be called does not exist on the contract ValueError: if trying to call a function without having the correct CallFlags """ if version != 0: raise ValueError(f"Native contract version {version} is not active" ) # type: ignore context = engine.current_context flags = contracts.CallFlags(context.call_flags) method = self._methods.get(context.ip, None) if method is None: raise ValueError( f"Method at IP \"{context.ip}\" does not exist on contract {self.service_name()}" ) if method.required_flags not in flags: raise ValueError( f"Method requires call flag: {method.required_flags} received: {flags}" ) engine.add_gas( method.cpu_price * contracts.PolicyContract().get_exec_fee_factor(engine.snapshot) + method.storage_price * contracts.PolicyContract().get_storage_price(engine.snapshot)) params: List[Any] = [] if method.add_engine: params.append(engine) if method.add_snapshot: params.append(engine.snapshot) for t in method.parameter_types: params.append( engine._stackitem_to_native(context.evaluation_stack.pop(), t)) if len(params) > 0: return_value = method.handler(*params) else: return_value = method.handler() if method.return_type is not None: context.evaluation_stack.push( engine._native_to_stackitem(return_value, type(return_value)))
def contract_create_with_data( self, engine: contracts.ApplicationEngine, nef_file: bytes, manifest: bytes, data: vm.StackItem) -> contracts.ContractState: if not isinstance(engine.script_container, payloads.Transaction): raise ValueError( "Cannot create contract without a Transaction script container" ) nef_len = len(nef_file) manifest_len = len(manifest) if (nef_len == 0 or nef_len > engine.MAX_CONTRACT_LENGTH or manifest_len == 0 or manifest_len > contracts.ContractManifest.MAX_LENGTH): raise ValueError("Invalid NEF or manifest length") engine.add_gas( max(engine.STORAGE_PRICE * (nef_len + manifest_len), self.get_minimum_deployment_fee(engine.snapshot))) nef = contracts.NEF.deserialize_from_bytes(nef_file) parsed_manifest = contracts.ContractManifest.from_json( json.loads(manifest.decode())) self.validate(nef.script, parsed_manifest.abi) sb = vm.ScriptBuilder() sb.emit(vm.OpCode.ABORT) sb.emit_push(engine.script_container.sender.to_array()) sb.emit_push(nef.checksum) sb.emit_push(parsed_manifest.name) hash_ = to_script_hash(sb.to_array()) existing_contract = engine.snapshot.contracts.try_get(hash_) if existing_contract is not None: raise ValueError("Contract already exists") contract = contracts.ContractState( self.get_next_available_id(engine.snapshot), nef, parsed_manifest, 0, hash_) if not contract.manifest.is_valid(hash_): raise ValueError("Error: invalid manifest") engine.snapshot.contracts.put(contract) method_descriptor = contract.manifest.abi.get_method("_deploy", 2) if method_descriptor is not None: engine.call_from_native(self.hash, hash_, method_descriptor.name, [data, vm.BooleanStackItem(False)]) msgrouter.interop_notify( self.hash, "Deploy", vm.ArrayStackItem(engine.reference_counter, vm.ByteStringStackItem( contract.hash.to_array()))) return contract
def contract_update(engine: contracts.ApplicationEngine, script: bytes, manifest: bytes) -> None: script_len = len(script) manifest_len = len(manifest) # TODO: In preview 4 revert back to # engine.add_gas(engine.STORAGE_PRICE * (script_len + manifest_len)) # They made a mistake in their storage price calculation logic where manifest size is never taken into account engine.add_gas(engine.STORAGE_PRICE * script_len) contract = engine.snapshot.contracts.try_get(engine.current_scripthash, read_only=True) if contract is None: raise ValueError("Can't find contract to update") if script_len == 0 or script_len > engine.MAX_CONTRACT_LENGTH: raise ValueError(f"Invalid script length: {script_len}") hash_ = to_script_hash(script) if hash_ == engine.current_scripthash or engine.snapshot.contracts.try_get(hash_) is not None: raise ValueError("Nothing to update") old_contract_has_storage = contract.has_storage contract = storage.ContractState(script, contract.manifest) contract.manifest.abi.contract_hash = hash_ engine.snapshot.contracts.put(contract) # migrate storage to new contract hash with blockchain.Blockchain().backend.get_snapshotview() as snapshot: if old_contract_has_storage: for key, value in snapshot.storages.find(engine.current_scripthash, b''): # delete the old storage snapshot.storages.delete(key) # update key to new contract hash key.contract = contract.script_hash() # now persist all data under new contract key snapshot.storages.put(key, value) snapshot.commit() engine.snapshot.contracts.delete(engine.current_scripthash) if manifest_len == 0 or manifest_len > contracts.ContractManifest.MAX_LENGTH: raise ValueError(f"Invalid manifest length: {manifest_len}") contract.manifest = contracts.ContractManifest.from_json(json.loads(manifest.decode())) if not contract.manifest.is_valid(contract.script_hash()): raise ValueError("Error: manifest does not match with script") if (not contract.has_storage and len(list(engine.snapshot.storages.find(contract.script_hash(), key_prefix=b''))) != 0): raise ValueError("Error: New contract does not support storage while old contract has existing storage")
def contract_update_with_data(self, engine: contracts.ApplicationEngine, nef_file: bytes, manifest: bytes, data: vm.StackItem) -> None: nef_len = len(nef_file) manifest_len = len(manifest) engine.add_gas(engine.STORAGE_PRICE * (nef_len + manifest_len)) contract = engine.snapshot.contracts.try_get(engine.calling_scripthash, read_only=False) if contract is None: raise ValueError("Can't find contract to update") if nef_len == 0: raise ValueError(f"Invalid NEF length: {nef_len}") # update contract contract.nef = contracts.NEF.deserialize_from_bytes(nef_file) if manifest_len == 0 or manifest_len > contracts.ContractManifest.MAX_LENGTH: raise ValueError(f"Invalid manifest length: {manifest_len}") manifest_new = contracts.ContractManifest.from_json( json.loads(manifest.decode())) if manifest_new.name != contract.manifest.name: raise ValueError("Error: cannot change contract name") if not contract.manifest.is_valid(contract.hash): raise ValueError("Error: manifest does not match with script") contract.manifest = manifest_new self.validate(contract.nef.script, contract.manifest.abi) contract.update_counter += 1 if len(nef_file) != 0: method_descriptor = contract.manifest.abi.get_method("_deploy", 2) if method_descriptor is not None: engine.call_from_native(self.hash, contract.hash, method_descriptor.name, [data, vm.BooleanStackItem(True)]) msgrouter.interop_notify( self.hash, "Update", vm.ArrayStackItem(engine.reference_counter, vm.ByteStringStackItem( contract.hash.to_array())))
def do_register(self, engine: contracts.ApplicationEngine, name: str, owner: types.UInt160) -> bool: if not self.is_available(engine.snapshot, name): raise ValueError( f"Registration failure - '{name}' is not available") if not engine.checkwitness(owner): raise ValueError("CheckWitness failed") engine.add_gas(self.get_price(engine.snapshot)) state = NameState( owner, name, (engine.snapshot.persisting_block.timestamp // 1000) + self.ONE_YEAR) self.mint(engine, state) engine.snapshot.storages.put( self.key_expiration + state.expiration.to_bytes(4, 'big') + name.encode(), storage.StorageItem(b'\x00')) return True
def contract_create(engine: contracts.ApplicationEngine, script: bytes, manifest: bytes) -> storage.ContractState: script_len = len(script) manifest_len = len(manifest) if (script_len == 0 or script_len > engine.MAX_CONTRACT_LENGTH or manifest_len == 0 or manifest_len > contracts.ContractManifest.MAX_LENGTH): raise ValueError("Invalid script or manifest length") engine.add_gas(engine.STORAGE_PRICE * (script_len + manifest_len)) hash_ = to_script_hash(script) contract = engine.snapshot.contracts.try_get(hash_) if contract is not None: raise ValueError("Contract already exists") contract = storage.ContractState(script, contracts.ContractManifest.from_json(json.loads(manifest.decode()))) if not contract.manifest.is_valid(hash_): raise ValueError("Error: manifest does not match with script") engine.snapshot.contracts.put(contract) return contract
def do_burn_gas(engine: contracts.ApplicationEngine, gas: int) -> None: if gas <= 0: raise ValueError("Burn gas cannot be called with negative value") engine.add_gas(gas)
def _request(self, engine: contracts.ApplicationEngine, url: str, filter: str, callback: str, user_data: vm.StackItem, gas_for_response: int) -> None: if len(url.encode('utf-8')) > self._MAX_URL_LENGTH or \ len(filter.encode('utf-8')) > self._MAX_FILTER_LEN or \ len(callback.encode('utf-8')) > self._MAX_CALLBACK_LEN or \ callback.startswith("_") or \ gas_for_response < 10000000: raise ValueError engine.add_gas(self.get_price(engine.snapshot)) engine.add_gas(gas_for_response) self._gas.mint(engine, self.hash, vm.BigInteger(gas_for_response), False) si_item_id = engine.snapshot.storages.get(self.key_request_id, read_only=False) item_id = vm.BigInteger(si_item_id.value) si_item_id.value = (item_id + 1).to_array() if contracts.ManagementContract().get_contract(engine.snapshot, engine.calling_scripthash) is None: raise ValueError oracle_request = OracleRequest(self._get_original_txid(engine), gas_for_response, url, filter, engine.calling_scripthash, callback, contracts.BinarySerializer.serialize(user_data, self._MAX_USER_DATA_LEN)) engine.snapshot.storages.put(self.key_request + int(item_id).to_bytes(8, 'little', signed=False), storage.StorageItem(oracle_request.to_array()) ) sk_id_list = self.key_id_list + self._get_url_hash(url) si_id_list = engine.snapshot.storages.try_get(sk_id_list, read_only=False) if si_id_list is None: si_id_list = storage.StorageItem(b'\x00') with serialization.BinaryReader(si_id_list.value) as reader: count = reader.read_var_int() id_list = [] for _ in range(count): id_list.append(reader.read_uint64()) id_list.append(item_id) if len(id_list) >= 256: raise ValueError("Oracle has too many pending responses for this url") with serialization.BinaryWriter() as writer: writer.write_var_int(len(id_list)) for id in id_list: writer.write_uint64(id) si_id_list.value = writer.to_array() engine.snapshot.storages.update(sk_id_list, si_id_list) state = vm.ArrayStackItem( engine.reference_counter, [vm.IntegerStackItem(item_id), vm.ByteStringStackItem(engine.calling_scripthash.to_array()), vm.ByteStringStackItem(url.encode()), vm.ByteStringStackItem(filter.encode()), ] ) msgrouter.interop_notify(self.hash, "OracleRequest", state)