Evaluation Engines
TrustWeave Evaluation Engines provide a pluggable, tamper-proof system for evaluating contract conditions with cryptographic integrity verification.
Overview
Evaluation Engines are pluggable components that implement domain-specific logic for evaluating contract conditions. They provide:
- Pluggable Architecture: Register custom engines for different domains (insurance, finance, supply chain, etc.)
- Tamper Detection: Cryptographic hashing ensures engines haven’t been modified
- Version Control: Track engine versions for compatibility
- Type Safety: Strongly-typed interfaces for condition evaluation
Why Evaluation Engines?
Traditional contract execution systems have hardcoded evaluation logic, making them:
- Inflexible for different domains
- Vulnerable to tampering
- Difficult to update without breaking existing contracts
TrustWeave’s Evaluation Engine framework solves these problems by:
- Allowing domain-specific engines (parametric insurance, rule engines, etc.)
- Cryptographically protecting engine implementations
- Enabling engine updates while maintaining contract integrity
Architecture
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
┌─────────────────────────────────────────────────────────┐
│ Contract Execution │
│ ┌───────────────────────────────────────────────────┐ │
│ │ DefaultSmartContractService │ │
│ │ - Extracts engine ID from ExecutionModel │ │
│ │ - Verifies engine integrity │ │
│ │ - Delegates to registered engine │ │
│ └───────────────────┬───────────────────────────────┘ │
└──────────────────────┼──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ EvaluationEngines │
│ ┌───────────────────────────────────────────────────┐ │
│ │ - Thread-safe engine storage │ │
│ │ - Hash verification │ │
│ │ - Engine lookup (operator overloads) │ │
│ └───────────────────┬───────────────────────────────┘ │
└──────────────────────┼──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ ContractEvaluationEngine │
│ ┌───────────────────────────────────────────────────┐ │
│ │ - engineId: String │ │
│ │ - version: String │ │
│ │ - implementationHash: String │ │
│ │ - evaluateCondition() │ │
│ │ - evaluateConditions() │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Core Components
ContractEvaluationEngine Interface
The main interface that all evaluation engines must implement:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface ContractEvaluationEngine {
val engineId: String
val version: String
val implementationHash: String
val supportedConditionTypes: Set<ConditionType>
suspend fun evaluateCondition(
condition: ContractCondition,
inputData: JsonElement,
context: EvaluationContext
): Boolean
suspend fun evaluateConditions(
conditions: List<ContractCondition>,
inputData: JsonElement,
context: EvaluationContext
): Map<String, Boolean>
}
Why JsonElement for Input Data?
The evaluation engine API uses JsonElement for inputData rather than Any or generic types. This design decision is based on several key considerations:
1. Architectural Consistency
TrustWeave is built on a JSON-first architecture:
- Verifiable Credentials use JSON-LD format
- DID Documents are JSON
- Blockchain anchors store JSON payloads
- Contract data (
contractData) isJsonElement - Execution context (
ExecutionContext.triggerData) isJsonElement
Using JsonElement maintains consistency across the entire framework, making the API predictable and easy to understand.
2. Real-World Data Sources
All external data sources in TrustWeave are JSON-native:
- Earth Observation APIs (ESA, NASA, Planet) → JSON responses
- Weather APIs (NOAA, Weather.com) → JSON responses
- IoT Sensors → JSON payloads (MQTT, HTTP)
- Financial APIs → JSON market data
- Verifiable Credentials → JSON-LD format
Since all data arrives as JSON, JsonElement eliminates unnecessary conversion overhead.
3. Type Safety with Flexibility
JsonElement provides a balance between type safety and flexibility:
1
2
3
4
5
6
7
8
9
10
11
// Compile-time: Ensures it's structured JSON
val inputData: JsonElement = buildJsonObject {
put("floodDepthCm", 75.0)
put("timestamp", Instant.now().toString())
}
// Runtime: Safe, explicit access with null handling
val inputObj = inputData as? JsonObject
?: throw IllegalArgumentException("Expected JSON object")
val value = inputObj["floodDepthCm"]?.jsonPrimitive?.content?.toDoubleOrNull()
This is safer than Any, which provides no compile-time guarantees and requires extensive runtime type checking.
4. Serialization & Integrity
JsonElement integrates seamlessly with kotlinx.serialization and enables integrity verification:
1
2
3
4
5
// Direct serialization support
val json = Json.encodeToJsonElement(executionModel)
// Can hash for tamper detection
val inputHash = DigestUtils.sha256DigestMultibase(inputData)
Using Any would require conversion to JSON before hashing, adding complexity and potential for errors.
5. Performance
JsonElement offers optimal performance:
- Zero-copy parsing: kotlinx.serialization parses JSON efficiently
- Lazy evaluation: Only accessed fields are parsed
- Efficient memory: Tree structure is memory-efficient
- Native serialization: No conversion overhead
6. Developer Experience
JsonElement provides a clear, explicit API:
1
2
3
4
5
6
// Clear and explicit
val data = buildJsonObject {
put("temperature", 35.5)
put("humidity", 0.65)
}
engine.evaluateCondition(condition, data, context)
Compared to Any, which is unclear about accepted types and requires documentation to understand what’s supported.
7. Extensibility
JsonElement supports any JSON structure without breaking changes:
1
2
3
4
5
6
7
8
9
10
11
12
// Engine can handle any JSON schema
class CustomEngine : BaseEvaluationEngine() {
override suspend fun evaluateCondition(
condition: ContractCondition,
inputData: JsonElement, // Flexible for any JSON structure
context: EvaluationContext
): Boolean {
// Can parse and validate any JSON schema
val obj = inputData as? JsonObject ?: return false
// Handle different data structures
}
}
8. Framework Integration
JsonElement works seamlessly with TrustWeave’s execution flow:
1
2
3
4
5
6
7
8
9
10
// Data flows naturally through the system
val executionContext = ExecutionContext(
triggerData = buildJsonObject { ... } // JsonElement
)
// Passed directly to evaluation
evaluateConditions(contract, executionContext.triggerData) // JsonElement
// Engine receives it directly
engine.evaluateCondition(condition, inputData, context) // JsonElement
No conversion layers needed - data flows naturally from external sources through the framework to engines.
Conclusion
JsonElement is the optimal choice because it:
- ✅ Maintains architectural consistency
- ✅ Matches real-world data formats
- ✅ Provides type safety with flexibility
- ✅ Enables integrity verification
- ✅ Offers optimal performance
- ✅ Improves developer experience
- ✅ Supports extensibility
- ✅ Integrates seamlessly with the framework
Using Any would reduce type safety, add conversion complexity, and break consistency with TrustWeave’s JSON-first architecture.
BaseEvaluationEngine
Base class that provides automatic hash calculation:
1
2
3
4
5
6
abstract class BaseEvaluationEngine : ContractEvaluationEngine {
// implementationHash is computed automatically from class file bytecode
final override val implementationHash: String by lazy {
computeImplementationHash()
}
}
EvaluationEngines
Thread-safe collection for managing evaluation engines with operator overloads:
1
2
3
4
5
6
7
8
9
10
11
12
13
interface EvaluationEngines {
operator fun plusAssign(engine: ContractEvaluationEngine) // engines += engine
operator fun minusAssign(engineId: String) // engines -= "engine-id"
operator fun get(engineId: String): ContractEvaluationEngine? // engines["engine-id"]
operator fun contains(engineId: String): Boolean // "engine-id" in engines
fun verify(engineId: String, expectedHash: String): Boolean
val keys: Set<String>
val size: Int
fun clear()
}
// Factory function
fun EvaluationEngines(): EvaluationEngines
Example Usage:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
val engines = EvaluationEngines()
// Register using += operator
engines += ParametricInsuranceEngine()
// Get using [] operator
val engine = engines["parametric-insurance"]
// Check if registered using 'in' operator
if ("parametric-insurance" in engines) {
// Engine is registered
}
// Verify integrity
engines.verify("parametric-insurance", expectedHash)
Creating a Custom Engine
Step 1: Extend BaseEvaluationEngine
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.example.contracts.engines
import com.trustweave.contract.evaluation.*
import com.trustweave.contract.models.*
class MyCustomEngine : BaseEvaluationEngine() {
override val engineId: String = "my-custom-engine"
override val version: String = "1.0.0"
override val supportedConditionTypes: Set<ConditionType> = setOf(
ConditionType.THRESHOLD,
ConditionType.RANGE
)
override suspend fun evaluateCondition(
condition: ContractCondition,
inputData: JsonElement,
context: EvaluationContext
): Boolean {
return when (condition.conditionType) {
ConditionType.THRESHOLD -> evaluateThreshold(condition, inputData)
ConditionType.RANGE -> evaluateRange(condition, inputData)
else -> throw UnsupportedOperationException(
"Condition type ${condition.conditionType} not supported"
)
}
}
private fun evaluateThreshold(
condition: ContractCondition,
inputData: JsonElement
): Boolean {
// Your implementation
TODO()
}
private fun evaluateRange(
condition: ContractCondition,
inputData: JsonElement
): Boolean {
// Your implementation
TODO()
}
}
Step 2: Register the Engine
1
2
3
4
5
6
7
8
val engines = EvaluationEngines()
val engine = MyCustomEngine()
// Register using += operator
engines += engine
// Engine hash is computed automatically
println("Engine hash: ${engine.implementationHash}")
Step 3: Use in Contracts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
val contract = trustWeave.contracts.draft(
request = ContractDraftRequest(
executionModel = ExecutionModel.Parametric(
triggerType = TriggerType.EarthObservation,
evaluationEngine = "my-custom-engine"
// engineHash will be added during bindContract()
),
terms = ContractTerms(
conditions = listOf(
ContractCondition(
id = "condition-1",
description = "Value exceeds threshold",
conditionType = ConditionType.THRESHOLD,
expression = "$.value >= 50"
)
),
obligations = listOf(...)
),
// ... other fields
)
).getOrThrow()
Engine Hash Calculation
The engine hash is computed from the compiled class file bytecode using SHA-256:
1
2
3
4
// Hash is computed automatically by BaseEvaluationEngine
// The hash is computed from class file bytecode using SHA-256
// Returns: "uABC123..." (multibase-encoded)
val hash = engine.implementationHash
Hash Verification Flow
- During Binding: Engine hash is computed and stored in the contract credential
- During Execution: Current engine hash is compared with stored hash
- If Mismatch:
SecurityExceptionis thrown - engine may have been tampered with
1
2
3
4
5
6
7
8
9
// During contract binding (automatic via extension function)
val executionModelWithHash = contract.executionModel.withEngineHash(engines)
// During contract execution (automatic in DefaultSmartContractService)
val engineRef = contract.executionModel.toEngineReference()
val engine = engines.require(engineRef.engineId!!)
engineRef.expectedHash?.let { expectedHash ->
engines.verifyOrThrow(engineRef.engineId, expectedHash)
}
Tamper Protection
The evaluation engine framework provides multiple layers of tamper protection:
1. Cryptographic Hashing
Engine implementations are hashed using SHA-256, and the hash is:
- Stored in the contract’s
ExecutionModel - Included in the signed credential
- Verified before each evaluation
2. Credential Signing
The executionModel (including engineHash) is part of the credential subject, which is:
- Cryptographically signed by the issuer
- Anchored to blockchain for immutability
- Verified during credential verification
3. Runtime Verification
Before evaluating conditions, the system:
- Retrieves the engine from the registry
- Computes the current engine hash
- Compares with the hash stored in the contract
- Throws
SecurityExceptionif mismatch detected
Example: Parametric Insurance Engine
The ParametricInsuranceEngine is included as an example implementation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
val engines = EvaluationEngines()
val engine = ParametricInsuranceEngine()
engines += engine
// Create parametric insurance contract
val contract = trustWeave.contracts.draft(
request = ContractDraftRequest(
executionModel = ExecutionModel.Parametric(
triggerType = TriggerType.EarthObservation,
evaluationEngine = "parametric-insurance"
),
terms = ContractTerms(
conditions = listOf(
ContractCondition(
id = "flood-threshold",
description = "Flood depth >= 50cm",
conditionType = ConditionType.THRESHOLD,
expression = "$.floodDepthCm >= 50"
)
),
obligations = listOf(...)
)
)
)
// Bind contract (engine hash is captured)
val bound = trustWeave.contracts.bindContract(
contractId = contract.id,
issuerDid = issuerDid,
issuerKeyId = issuerKeyId
)
// Execute contract (engine integrity is verified)
val result = trustWeave.contracts.executeContract(
contract = bound.contract,
executionContext = ExecutionContext(
triggerData = buildJsonObject {
put("floodDepthCm", 75.0)
}
)
)
Expression Formats
Different engines may support different expression formats. The ParametricInsuranceEngine supports:
Threshold Expressions
1
2
3
4
5
6
$.path >= value
$.path <= value
$.path == value
$.path > value
$.path < value
$.path != value
Examples:
$.floodDepthCm >= 50$.temperature <= 30$.rainfallMm == 100
Range Expressions
1
$.path >= min && $.path <= max
Example:
$.temperature >= 20 && $.temperature <= 30
Comparison Expressions
1
2
3
$.path1 >= $.path2
$.path1 <= $.path2
$.path1 == $.path2
Example:
$.currentValue > $.previousValue
Best Practices
1. Engine Versioning
Always increment version when making breaking changes:
1
2
3
override val version: String = "1.0.0" // Initial version
override val version: String = "1.1.0" // Minor update
override val version: String = "2.0.0" // Breaking change
2. Condition Type Support
Only declare support for condition types you actually implement:
1
2
3
override val supportedConditionTypes: Set<ConditionType> = setOf(
ConditionType.THRESHOLD // Only declare what you support
)
3. Error Handling
Provide clear error messages for invalid expressions:
1
2
3
4
5
6
7
8
if (expression.contains(">=")) {
// Parse threshold
} else {
throw IllegalArgumentException(
"Invalid expression: $expression. " +
"Expected format: \$.path >= value"
)
}
4. Testing
Test engines thoroughly before registration:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
fun `test threshold evaluation`() {
val engine = MyEngine()
val condition = ContractCondition(
id = "test",
description = "Test",
conditionType = ConditionType.THRESHOLD,
expression = "$.value >= 50"
)
val inputData = buildJsonObject { put("value", 75.0) }
val result = runBlocking {
engine.evaluateCondition(condition, inputData, context)
}
assertTrue(result)
}
Integration with TrustWeave
Evaluation engines integrate seamlessly with TrustWeave’s contract system:
Contract Creation
1
2
3
4
5
6
7
8
9
val contract = trustWeave.contracts.draft(
request = ContractDraftRequest(
executionModel = ExecutionModel.Parametric(
triggerType = TriggerType.EarthObservation,
evaluationEngine = "parametric-insurance"
),
// ... other fields
)
)
Contract Binding
During binding, the engine hash is automatically captured:
1
2
3
4
5
6
val bound = trustWeave.contracts.bindContract(
contractId = contract.id,
issuerDid = issuerDid,
issuerKeyId = issuerKeyId
)
// bound.contract.executionModel.engineHash contains the hash
Contract Execution
During execution, engine integrity is automatically verified:
1
2
3
4
5
6
7
val result = trustWeave.contracts.executeContract(
contract = contract,
executionContext = ExecutionContext(
triggerData = inputData
)
)
// Engine integrity is verified automatically
Security Considerations
- Engine Registration: Only register engines from trusted sources
- Hash Verification: Always verify engine hashes before evaluation
- Version Compatibility: Check engine versions for compatibility
- Expression Validation: Validate expressions before evaluation
- Input Sanitization: Sanitize input data to prevent injection attacks
Troubleshooting
Engine Not Found
1
Evaluation engine 'my-engine' is not registered
Solution: Register the engine before creating contracts:
1
2
val engines = EvaluationEngines()
engines += MyEngine()
Integrity Check Failed
1
Evaluation engine 'my-engine' integrity check failed
Solution: The engine has been modified. Re-register the original engine or update the contract.
Version Mismatch
1
Evaluation engine version mismatch. Expected: 1.0.0, Actual: 2.0.0
Solution: Use the correct engine version or update the contract to use the new version.
See Also
- Smart Contracts for contract lifecycle and execution models
- Verifiable Credentials for credential issuance and verification
- Blockchain Anchoring for anchoring concepts
- API Reference for complete API documentation