//! Digest computation utilities for BLS signature consensus.
//!
//! Provides functions to compute digests for TaskResponse used in BLS
//! signature aggregation and on-chain verification.
use alloy::{
primitives::{keccak256, FixedBytes},
sol_types::SolValue,
};
use crate::newton_prover_task_manager::INewtonProverTaskManager::TaskResponse;
/// Computes the consensus digest for a TaskResponse.
///
/// This is the digest that operators BLS-sign. Must match the on-chain
/// `TaskLib.computeConsensusDigest` exactly.
pub fn compute_consensus_digest(task_response: &TaskResponse) -> FixedBytes<32> {
keccak256(TaskResponse::abi_encode(task_response))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::newton_prover_task_manager::{INewtonPolicy, NewtonMessage};
use alloy::primitives::{Address, Bytes, B256, U256};
fn create_test_task_response() -> TaskResponse {
TaskResponse {
taskId: B256::ZERO,
policyClient: Address::ZERO,
policyId: B256::ZERO,
policyAddress: Address::ZERO,
intent: NewtonMessage::Intent {
from: Address::ZERO,
to: Address::ZERO,
value: U256::ZERO,
data: Bytes::new(),
chainId: U256::from(1),
functionSignature: Bytes::new(),
},
intentSignature: Bytes::new(),
evaluationResult: Bytes::from(vec![1]),
policyTaskData: NewtonMessage::PolicyTaskData {
policyId: B256::ZERO,
policyAddress: Address::ZERO,
policy: Bytes::new(),
policyData: vec![
NewtonMessage::PolicyData {
wasmArgs: Bytes::new(),
data: Bytes::from(b"{\"price\": 100}".to_vec()),
policyDataAddress: Address::ZERO,
expireBlock: 1000,
},
NewtonMessage::PolicyData {
wasmArgs: Bytes::new(),
data: Bytes::from(b"{\"balance\": 50}".to_vec()),
policyDataAddress: Address::ZERO,
expireBlock: 1000,
},
],
},
policyConfig: INewtonPolicy::PolicyConfig {
policyParams: Bytes::new(),
expireAfter: 0,
},
initializationTimestamp: U256::ZERO,
}
}
#[test]
fn consensus_digest_differs_when_data_differs() {
let response1 = create_test_task_response();
let mut response2 = response1.clone();
response2.policyTaskData.policyData[0].data = Bytes::from(b"{\"price\": 200}".to_vec());
let digest1 = compute_consensus_digest(&response1);
let digest2 = compute_consensus_digest(&response2);
assert_ne!(digest1, digest2, "Digests should differ when policy data differs");
}
#[test]
fn digest_is_deterministic() {
let response = create_test_task_response();
let digest1 = compute_consensus_digest(&response);
let digest2 = compute_consensus_digest(&response);
assert_eq!(digest1, digest2, "Same input should produce same digest");
}
/// Empty-policyData shape fixture: no policyData entries, empty evaluationResult.
fn create_empty_policy_data_response() -> TaskResponse {
TaskResponse {
taskId: B256::ZERO,
policyClient: Address::ZERO,
policyId: B256::ZERO,
policyAddress: Address::ZERO,
intent: NewtonMessage::Intent {
from: Address::ZERO,
to: Address::ZERO,
value: U256::ZERO,
data: Bytes::new(),
chainId: U256::from(1),
functionSignature: Bytes::new(),
},
intentSignature: Bytes::new(),
evaluationResult: Bytes::new(),
policyTaskData: NewtonMessage::PolicyTaskData {
policyId: B256::ZERO,
policyAddress: Address::ZERO,
policy: Bytes::new(),
policyData: vec![],
},
policyConfig: INewtonPolicy::PolicyConfig {
policyParams: Bytes::new(),
expireAfter: 0,
},
initializationTimestamp: U256::ZERO,
}
}
// GOLDEN: exact ABI encoding of the standard fixture. Pins the wire bytes that
// feed keccak256 -> must match on-chain TaskLib.computeConsensusDigest input.
#[test]
fn golden_abi_encode_standard_fixture() {
const EXPECTED: &str = "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000560000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e7b227072696365223a203130307d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f7b2262616c616e6365223a2035307d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
let encoded = TaskResponse::abi_encode(&create_test_task_response());
assert_eq!(
hex::encode(&encoded),
EXPECTED,
"ABI encoding drifted for standard fixture"
);
}
// GOLDEN: keccak256 of the standard fixture's ABI encoding = the consensus digest.
#[test]
fn golden_consensus_digest_standard_fixture() {
const EXPECTED: &str = "0xb7987f030d21ecbf8e26e095626fef9fdfb569ec6fe6b8b7b82c33647f94f988";
let digest = compute_consensus_digest(&create_test_task_response());
assert_eq!(
format!("{digest:#x}"),
EXPECTED,
"consensus digest drifted for standard fixture"
);
}
// GOLDEN: empty-policyData shape. Pins both the ABI bytes and the digest.
#[test]
fn golden_empty_policy_data_fixture() {
const EXPECTED_ABI: &str = "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
const EXPECTED_DIGEST: &str = "0xb22059d6f4f46ce62c65f54d48e5d4e578d2f69f13ef4a3c711958890e708f4d";
let fixture = create_empty_policy_data_response();
let encoded = TaskResponse::abi_encode(&fixture);
assert_eq!(
hex::encode(&encoded),
EXPECTED_ABI,
"ABI encoding drifted for empty-policyData fixture"
);
let digest = compute_consensus_digest(&fixture);
assert_eq!(
format!("{digest:#x}"),
EXPECTED_DIGEST,
"consensus digest drifted for empty-policyData fixture"
);
}
#[test]
fn digest_handles_empty_policy_data() {
let response = TaskResponse {
taskId: B256::ZERO,
policyClient: Address::ZERO,
policyId: B256::ZERO,
policyAddress: Address::ZERO,
intent: NewtonMessage::Intent {
from: Address::ZERO,
to: Address::ZERO,
value: U256::ZERO,
data: Bytes::new(),
chainId: U256::from(1),
functionSignature: Bytes::new(),
},
intentSignature: Bytes::new(),
evaluationResult: Bytes::new(),
policyTaskData: NewtonMessage::PolicyTaskData {
policyId: B256::ZERO,
policyAddress: Address::ZERO,
policy: Bytes::new(),
policyData: vec![],
},
policyConfig: INewtonPolicy::PolicyConfig {
policyParams: Bytes::new(),
expireAfter: 0,
},
initializationTimestamp: U256::ZERO,
};
let _ = compute_consensus_digest(&response);
}
}