Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions contracts/src/povw/IPovwAccounting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ struct PendingEpoch {
}

interface IPovwAccounting {
/// @notice Error indicating that a provided signature is invalid.
/// @dev This error is used when the signature provided for a work log update does not
/// correctly authenticate the update.
error InvalidSignature();

/// @notice Event emitted during the finalization of an epoch.
/// @dev This event is emitted in some block after the end of the epoch, when the finalizeEpoch
/// function is called. Note that this is no later than the first time that updateWorkLog
Expand Down Expand Up @@ -94,6 +99,7 @@ interface IPovwAccounting {
bytes32 updatedCommit,
uint64 updateValue,
address valueRecipient,
bytes calldata signature,
bytes calldata seal
) external;

Expand Down
43 changes: 43 additions & 0 deletions contracts/src/povw/PovwAccounting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/U
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {IZKC} from "zkc/interfaces/IZKC.sol";
import {IPovwAccounting, WorkLogUpdate, Journal, PendingEpoch} from "./IPovwAccounting.sol";
import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol";
import {WorkLogUpdateLibrary} from "./WorkLogUpdateLibrary.sol";

bytes32 constant EMPTY_LOG_ROOT = hex"b26927f749929e8484785e36e7ec93d5eeae4b58182f76f1e760263ab67f540c";

Expand All @@ -26,6 +28,7 @@ struct PendingEpochStorage {

contract PovwAccounting is IPovwAccounting, Initializable, EIP712Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
using SafeCast for uint256;
using WorkLogUpdateLibrary for WorkLogUpdate;

/// @dev The version of the contract, with respect to upgrades.
uint64 public constant VERSION = 1;
Expand Down Expand Up @@ -104,6 +107,7 @@ contract PovwAccounting is IPovwAccounting, Initializable, EIP712Upgradeable, Ow
bytes32 updatedCommit,
uint64 updateValue,
address valueRecipient,
bytes calldata signature,
bytes calldata seal
) public {
uint64 currentEpoch = TOKEN.getCurrentEpoch().toUint64();
Expand All @@ -123,6 +127,8 @@ contract PovwAccounting is IPovwAccounting, Initializable, EIP712Upgradeable, Ow
updateValue: updateValue,
valueRecipient: valueRecipient
});
verifySignature(update, workLogId, signature);

Journal memory journal = Journal({update: update, eip712Domain: _domainSeparatorV4()});
VERIFIER.verify(seal, LOG_UPDATER_ID, sha256(abi.encode(journal)));

Expand Down Expand Up @@ -150,4 +156,41 @@ contract PovwAccounting is IPovwAccounting, Initializable, EIP712Upgradeable, Ow
}
return commit;
}

function verifySignature(WorkLogUpdate memory update, address signer, bytes calldata signature) public view {
// Check if signer is a contract
uint256 codeSize;
assembly {
codeSize := extcodesize(signer)
}
bytes32 eip712Digest = update.eip712Digest();
bytes32 hash = _hashTypedDataV4(eip712Digest);
if (codeSize > 0) {
// Signer is a contract, try IERC1271
try IERC1271(signer).isValidSignature(hash, signature) returns (bytes4 magicValue) {
if (magicValue == 0x1626ba7e) {
return; // valid signature
}
} catch {
revert InvalidSignature();
}
} else {
// Signer is likely an EOA, try ECDSA
bytes32 r;
bytes32 s;
uint8 v;
if (signature.length == 65) {
assembly {
r := calldataload(add(signature.offset, 0))
s := calldataload(add(signature.offset, 32))
v := byte(0, calldataload(add(signature.offset, 64)))
}
address recovered = ecrecover(hash, v, r, s);
if (recovered == signer) {
return;
}
}
revert InvalidSignature();
}
}
}
30 changes: 30 additions & 0 deletions contracts/src/povw/WorkLogUpdateLibrary.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2025 RISC Zero, Inc.
//
// Use of this source code is governed by the Business Source License
// as found in the LICENSE-BSL file.
pragma solidity ^0.8.26;

import {WorkLogUpdate} from "./IPovwAccounting.sol";

library WorkLogUpdateLibrary {
string constant WORK_LOG_UPDATE_TYPE =
"WorkLogUpdate(address workLogId,bytes32 initialCommit,bytes32 updatedCommit,uint64 updateValue,address valueRecipient)";

bytes32 constant WORK_LOG_UPDATE_TYPEHASH = keccak256(abi.encodePacked(WORK_LOG_UPDATE_TYPE));

/// @notice Computes the EIP-712 digest for the given WorkLogUpdate.
/// @param update The WorkLogUpdate to compute the digest for.
/// @return The EIP-712 digest of the WorkLogUpdate.
function eip712Digest(WorkLogUpdate memory update) internal pure returns (bytes32) {
return keccak256(
abi.encode(
WORK_LOG_UPDATE_TYPEHASH,
update.workLogId,
update.initialCommit,
update.updatedCommit,
update.updateValue,
update.valueRecipient
)
);
}
}
4 changes: 2 additions & 2 deletions crates/boundless-cli/src/commands/povw/submit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,14 +170,14 @@ impl PovwSubmit {

// Sign and prove the authorized work log update.
tracing::info!("Proving work log update");
let prove_info = prover
let (prove_info, signature) = prover
.prove_update(receipt, work_log_signer)
.await
.context("Failed to prove authorized log update")?;

tracing::info!("Sending work log update transaction");
let tx_result = povw_accounting
.update_work_log(&prove_info.receipt)
.update_work_log(&prove_info.receipt, &signature)
.context("Failed to construct update transaction")?
.send()
.await
Expand Down
Binary file modified crates/povw/elfs/boundless-povw-log-updater.bin
Binary file not shown.
Binary file modified crates/povw/elfs/boundless-povw-log-updater.iid
Binary file not shown.
8 changes: 0 additions & 8 deletions crates/povw/log-updater/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,6 @@ fn main() {
// Convert the input to the Solidity struct and verify the EIP-712 signature, using the work
// log ID as the authenticating party.
let update = WorkLogUpdate::from_log_builder_journal(input.update, input.value_recipient);
update
.verify_signature(
update.workLogId,
&input.signature,
input.contract_address,
input.chain_id,
)
.expect("failed to verify signature on work log update");

// Write the journal, including the EIP-712 domain hash for the verifying contract.
let journal = Journal {
Expand Down
6 changes: 6 additions & 0 deletions crates/povw/src/contracts/artifacts/IPovwAccounting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ struct PendingEpoch {
}

interface IPovwAccounting {
/// @notice Error indicating that a provided signature is invalid.
/// @dev This error is used when the signature provided for a work log update does not
/// correctly authenticate the update.
error InvalidSignature();

/// @notice Event emitted during the finalization of an epoch.
/// @dev This event is emitted in some block after the end of the epoch, when the finalizeEpoch
/// function is called. Note that this is no later than the first time that updateWorkLog
Expand Down Expand Up @@ -94,6 +99,7 @@ interface IPovwAccounting {
bytes32 updatedCommit,
uint64 updateValue,
address valueRecipient,
bytes calldata signature,
bytes calldata seal
) external;

Expand Down
Loading
Loading