Target Base Contract

Overview

TargetBase is a foundational contract that provides a signature-based authorization system for smart contracts. It enforces strict access controls by combining cryptographic signatures, smart account validation, and replay protection into a single, reusable module.

Application developers can extend TargetBase to build specialized contracts (e.g., DeFi protocols, marketplaces, or tokenized asset platforms) while inheriting all of its security guarantees.


Core Features

1. Master Key Authorization

  • A trusted master key signs all authorizations.

  • The master key can be rotated by the owner or a recovery key for security.

  • This ensures that only signatures from the current master key are valid.

2. Smart Account Enforcement

  • Only approved DelegatedAccount implementations can call protected functions.

  • Enforced using extcodehash checks:

    • EOAs are rejected (no direct access).

    • Contracts with incorrect or empty bytecode are rejected.

    • Only contracts with the registered DelegatedAccount code hash are accepted.

3. Replay Protection

  • Nonces: Each sender has a sequential nonce. Must match authData.nonce.

  • Authorization Hash Tracking: Each authorization (signature + data) is marked as used after execution.

  • This prevents signature reuse across different calls or chains.

4. Temporal Validation

  • Authorizations include an expiry timestamp.

  • Prevents attackers from replaying old signatures long after they were valid.

5. Emergency Recovery

  • Recovery Key: Dedicated address that can update the master key.

  • Nonce Reset: Owner can reset user nonces if accounts get stuck.


Authorization Workflow

AuthData

Every protected function must receive an AuthData struct as its first argument. It contains:

  • nonce → Sequential replay protection.

  • expiry → Timestamp after which the auth is invalid.

  • id → Execution hash for the overall operation.

  • executions → Array of execution steps for auditability.

  • result → ABI-encoded final result or function parameters.

  • sponsorExecutionFee → Indicates if fees are sponsored.

  • signature → ECDSA signature from the master key.

Authorization Verification (requireAuth)

When a protected function is called:

  1. Implementation Check → Caller must be approved DelegatedAccount.

  2. Nonce CheckauthData.nonce == nonces[msg.sender].

  3. Expiry Checkblock.timestamp <= authData.expiry.

  4. Hash Generation → Unique hash created from sender, nonce, expiry, result, selector.

  5. Replay Check → Hash must not already exist in usedAuthorizations.

  6. Signature Check → Recovered signer must match masterKey.

  7. State Update → Nonce incremented, hash marked used.

  8. Event EmittedAuthorizationVerified.


Extending TargetBase

Developers extend TargetBase by inheriting it in their own contracts and applying requireAuth to sensitive functions.

Example: RealEstateInvestment (Without extra parameters)

The RealEstateInvestment contract extends TargetBase to implement fractional real estate ownership.

Key extensions:

  • Inherits both TargetBase (for authorization) and ERC20 (for fractional tokens).

  • Defines domain-specific structs like PropertyInfo, PropertyAnalysisResponse, and InvestmentRecord.

  • Implements business logic:

    • submitPropertyAnalysis → Requires a signed analysis result.

    • purchaseTokens → Requires signed authorization for each investment.

  • All core operations use requireAuth(authData) to enforce:

    • Signature validation.

    • DelegatedAccount-only access.

    • Replay protection.

function submitPropertyAnalysis(AuthData calldata authData)
    external
    requireAuth(authData)
{
    // Decode AI analysis results
    PropertyAnalysisResponse memory analysis = abi.decode(authData.result, (PropertyAnalysisResponse));

    // Apply domain-specific validation rules
    if (analysis.confidence < MIN_CONFIDENCE) revert ConfidenceTooLow();
    if (keccak256(bytes(analysis.recommendation)) != keccak256("INVEST")) revert InvalidRecommendation();

    // Update property state & emit events
    property.totalValue = analysis.propertyValue;
    property.isActive = true;
    emit PropertyAnalyzed(...);
}

Example 2: TokenSale (With extra parameters)


contract TokenSale {
    IERC20 public token;

    constructor(address _token) {
        token = IERC20(_token);
    }

    function purchaseTokens(
        AuthData calldata authData,   // comes from krnl "authData_result"
        uint256 amount                // comes from krnl "parameters"
    ) external {
        // ✅ 1. Verify the AuthData
        require(verifyAuthData(authData), "Invalid AuthData");

        // ✅ 2. Process token purchase
        require(amount > 0, "Zero amount");
        require(token.balanceOf(address(this)) >= amount, "Insufficient liquidity");

        token.transfer(msg.sender, amount);
    }
}

From the DSL we wrote earlier in 2. Target Contract Details this is how the structure will look like with the extra parameter:

"function": "purchaseTokens((uint256,uint256,bytes32,(bytes32,bytes,bytes)[],bytes,bool,bytes),uint256)",
"authData_result": "${construct-authdata-evm.result}",
"parameters": [
  {
    "name": "amount",
    "type": "uint256",
    "value": "1000000000000000000"
  }
]
  • authData_result → fills the first argument (AuthData calldata authData)

  • parameters → supplies extra arguments (uint256 amount)

Benefits of Inheriting TargetBase

  • Security out of the box: No need to reimplement replay protection or signature verification.

  • Standardized authorization: Uniform way to authorize actions across contracts.

  • Extensible: Each new contract only needs to define its domain-specific logic.

  • Composable: Multiple TargetBase-derived contracts can interoperate securely.

Best Practices

  • AuthData as the first parameter

    • All functions requiring authorization must have AuthData calldata authData first.

    • Ensures deterministic ABI encoding.

  • Validate extra parameters

    • Never trust DSL inputs blindly.

    • Use require or custom errors for range, zero addresses, or business logic constraints.

    • Example:

      require(amount > 0, "Amount must be positive");
      require(recipient != address(0), "Invalid recipient");
  • Use events for auditability

    • Emit events for:

      • Authorization verified

      • Master key update

      • Property or token transactions

    • Enables off-chain monitoring & workflow debugging.

  • Fail fast and explicitly

    • Custom errors instead of generic require messages save gas and improve readability.

Last updated