From 1c24a7f88895ab32dc69fce31ede3a45ccc16c20 Mon Sep 17 00:00:00 2001 From: lonerapier Date: Mon, 30 Jun 2025 17:00:20 +0530 Subject: [PATCH 1/2] add `PlutoAttestationVerifier` contract --- src/Verifier.sol | 166 ++++++++++++++++++++++++++++++++++++++++++++ test/Verifier.t.sol | 62 ++++++++++++++--- 2 files changed, 220 insertions(+), 8 deletions(-) diff --git a/src/Verifier.sol b/src/Verifier.sol index 8f02849..40662b9 100644 --- a/src/Verifier.sol +++ b/src/Verifier.sol @@ -90,3 +90,169 @@ contract Verifier is Ownable { return true; } } + +contract PlutoAttestationVerifier { + struct ProofData { + string key; + string value; + } + + struct AttestationInput { + string version; + string scriptRaw; + string issuedAt; + string nonce; + string sessionId; + ProofData[] data; + } + + struct AttestationSignature { + bytes32 digest; + uint8 v; + bytes32 r; + bytes32 s; + address expectedSigner; + } + + Verifier public verifier; + + constructor(address notaryAddress) { + verifier = new Verifier(notaryAddress); + } + + /** + * @dev Calculate script hash from version and script content + */ + function calculateScriptHash(string memory version, string memory scriptRaw) public pure returns (bytes32) { + return keccak256(abi.encodePacked(version, scriptRaw)); + } + + /** + * @dev Calculate session hash from all session components + */ + function calculateSessionHash( + string memory version, + string memory issuedAt, + string memory nonce, + string memory sessionId, + ProofData[] memory data + ) public pure returns (bytes32) { + // Sort the data array by key (simple bubble sort for demonstration) + ProofData[] memory sortedData = new ProofData[](data.length); + for (uint256 i = 0; i < data.length; i++) { + sortedData[i] = data[i]; + } + + // Bubble sort by key + for (uint256 i = 0; i < sortedData.length; i++) { + for (uint256 j = 0; j < sortedData.length - 1 - i; j++) { + if (keccak256(bytes(sortedData[j].key)) > keccak256(bytes(sortedData[j + 1].key))) { + ProofData memory temp = sortedData[j]; + sortedData[j] = sortedData[j + 1]; + sortedData[j + 1] = temp; + } + } + } + + // Build the hash incrementally + bytes memory hashData = abi.encodePacked(version, issuedAt, nonce, sessionId); + + for (uint256 i = 0; i < sortedData.length; i++) { + hashData = abi.encodePacked(hashData, sortedData[i].key, sortedData[i].value); + } + + return keccak256(hashData); + } + + /** + * @dev Calculate digest from session and script hashes + */ + function calculateDigest(bytes32 sessionHash, bytes32 scriptHash) public pure returns (bytes32) { + // reportData = sessionHash + scriptHash (64 bytes) + bytes memory reportData = abi.encodePacked(sessionHash, scriptHash); + return keccak256(reportData); + } + + /** + * @dev Verify complete attestation by calculating hashes and checking signature + */ + function verifyAttestation(AttestationInput memory input, AttestationSignature memory signature) + public + returns (bool) + { + // Calculate script hash + bytes32 scriptHash = calculateScriptHash(input.version, input.scriptRaw); + + // Calculate session hash + bytes32 sessionHash = + calculateSessionHash(input.version, input.issuedAt, input.nonce, input.sessionId, input.data); + + // Calculate digest + bytes32 digest = calculateDigest(sessionHash, scriptHash); + + // Verify the digest matches + if (digest != signature.digest) { + return false; + } + + // Call the signature verification contract + bool success = verifier.verifyNotarySignature( + signature.digest, signature.v, signature.r, signature.s, signature.expectedSigner, scriptHash, sessionHash + ); + + if (!success) { + return false; + } + + return success; + } + + /** + * @dev Batch verify multiple attestations + */ + function verifyMultipleAttestations(AttestationInput[] memory inputs, AttestationSignature[] memory signatures) + public + returns (bool[] memory) + { + require(inputs.length == signatures.length, "Array length mismatch"); + + bool[] memory results = new bool[](inputs.length); + + for (uint256 i = 0; i < inputs.length; i++) { + results[i] = verifyAttestation(inputs[i], signatures[i]); + } + + return results; + } + + /** + * @dev Get calculated hashes for debugging + */ + function getCalculatedHashes(AttestationInput memory input) + public + pure + returns (bytes32 scriptHash, bytes32 sessionHash, bytes32 digest) + { + scriptHash = calculateScriptHash(input.version, input.scriptRaw); + sessionHash = calculateSessionHash(input.version, input.issuedAt, input.nonce, input.sessionId, input.data); + digest = calculateDigest(sessionHash, scriptHash); + } + + /** + * @dev Helper function to create ProofData array from parallel arrays + */ + function createProofDataArray(string[] memory keys, string[] memory values) + public + pure + returns (ProofData[] memory) + { + require(keys.length == values.length, "Array length mismatch"); + + ProofData[] memory proofData = new ProofData[](keys.length); + for (uint256 i = 0; i < keys.length; i++) { + proofData[i] = ProofData(keys[i], values[i]); + } + + return proofData; + } +} diff --git a/test/Verifier.t.sol b/test/Verifier.t.sol index e84064d..48415fb 100644 --- a/test/Verifier.t.sol +++ b/test/Verifier.t.sol @@ -2,26 +2,72 @@ pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; -import {Verifier} from "../src/Verifier.sol"; +import {Verifier, PlutoAttestationVerifier} from "../src/Verifier.sol"; contract VerifierTest is Test { Verifier public verifier; function setUp() public { - verifier = new Verifier(0xfdf07A5dCfa7b74f4c28DAb23eaD8B1c43Be801F); + verifier = new Verifier(0xF2E3878C9aB6A377D331E252F6bF3673d8e87323); } function test_isValidSignature() public { // TEST vector from web-prover @ githash 2dc768e818d6f9fef575a88a2ceb80c0ed11974f - address signer = 0xfdf07A5dCfa7b74f4c28DAb23eaD8B1c43Be801F; - bytes32 digest = bytes32(0xe45537be7b5cd288c9c46b7e027b4f5a66202146012f792c1b1cabb65828994b); - bytes32 r = bytes32(0x36e820b3524e9ffffe0b4ee49e4131cc362fd161821c1dfc8757dc6186f31c96); - bytes32 s = bytes32(0x416e537065673e3028eca37cf3cbe805a3d2fafbc47235fee5e89df5f0509a9c); + address signer = 0xF2E3878C9aB6A377D331E252F6bF3673d8e87323; + bytes32 digest = bytes32(0x3858f7da505d328a26770f8cac2170c0e937261dc451e34707dd8b2600b3a63e); + bytes32 r = bytes32(0xcd21eef84a7686c71e6c3cc801b4cc6883d3e9e4ba0da78ab1245897f6bcbe43); + bytes32 s = bytes32(0x6985c20ecd47b70c007f95412c0231b20d16a56001d7d214e427acf6b5615e22); uint8 v = 27; - bytes32 value = 0x8452c9b9140222b08593a26daa782707297be9f7b3e8281d7b4974769f19afd0; - bytes32 manifest = 0x7df909980a1642d0370a4a510422201ce525da6b319a7b9e9656771fa7336d5a; + bytes32 value = 0x0e38baef3358f6094095731571734ed4e83492afd88025e0e929d3de25286a60; + bytes32 manifest = 0xdd2a3dcaa72abdb5de17624afbf7f4216fa72a4998c82383617818ce80bb03b6; assertEq(verifier.verifyNotarySignature(digest, v, r, s, signer, manifest, value), true); } } + +// Example usage contract showing how to use the verifier +contract PlutoAttestationExample is Test { + PlutoAttestationVerifier public verifier; + + function setUp() public { + verifier = new PlutoAttestationVerifier(0xF2E3878C9aB6A377D331E252F6bF3673d8e87323); + } + + /** + * @dev Example function demonstrating attestation verification + */ + function test_verifyExampleAttestation() public { + // Create the proof data array + string[] memory keys = new string[](2); + string[] memory values = new string[](2); + keys[0] = "a"; + keys[1] = "c"; + values[0] = "10"; + values[1] = "\"d\""; + + PlutoAttestationVerifier.ProofData[] memory proofData = verifier.createProofDataArray(keys, values); + + // Create the attestation input + PlutoAttestationVerifier.AttestationInput memory input = PlutoAttestationVerifier.AttestationInput({ + version: "v1", + scriptRaw: "import { createSession } from '@plutoxyz/automation';\nconst session = await createSession();\nawait session.prove('bank_balance', { a: 10, c: 'd' });", + issuedAt: "2025-06-30T10:45:20Z", + nonce: "0x7b830a98e58e284b", + sessionId: "f4c38687-6fe0-40b8-8c05-e4a085856b05", + data: proofData + }); + + // Create the signature struct + PlutoAttestationVerifier.AttestationSignature memory signature = PlutoAttestationVerifier.AttestationSignature({ + digest: 0x088965a798b565d02f3ae18aa703609c645668d438969198166e4d9215a77f30, + v: 27, + r: 0x44a1b7809a7903ea087e7b5ce4092c24020134c5c3ea008656853f6d6da51b54, + s: 0x1e8b4bcbb716639b038d613257eac91d8edfaa1a5daa924307ce6cf4490b1edb, + expectedSigner: 0xF2E3878C9aB6A377D331E252F6bF3673d8e87323 + }); + + bool success = verifier.verifyAttestation(input, signature); + assertEq(success, true); + } +} From 257ab957e8174cae41459043f5fc4b86b6f6e0b1 Mon Sep 17 00:00:00 2001 From: lonerapier Date: Thu, 3 Jul 2025 12:54:36 +0530 Subject: [PATCH 2/2] remove sorting --- src/Verifier.sol | 21 ++------------------- test/Verifier.t.sol | 8 ++++---- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/src/Verifier.sol b/src/Verifier.sol index 40662b9..1d912de 100644 --- a/src/Verifier.sol +++ b/src/Verifier.sol @@ -137,28 +137,11 @@ contract PlutoAttestationVerifier { string memory sessionId, ProofData[] memory data ) public pure returns (bytes32) { - // Sort the data array by key (simple bubble sort for demonstration) - ProofData[] memory sortedData = new ProofData[](data.length); - for (uint256 i = 0; i < data.length; i++) { - sortedData[i] = data[i]; - } - - // Bubble sort by key - for (uint256 i = 0; i < sortedData.length; i++) { - for (uint256 j = 0; j < sortedData.length - 1 - i; j++) { - if (keccak256(bytes(sortedData[j].key)) > keccak256(bytes(sortedData[j + 1].key))) { - ProofData memory temp = sortedData[j]; - sortedData[j] = sortedData[j + 1]; - sortedData[j + 1] = temp; - } - } - } - // Build the hash incrementally bytes memory hashData = abi.encodePacked(version, issuedAt, nonce, sessionId); - for (uint256 i = 0; i < sortedData.length; i++) { - hashData = abi.encodePacked(hashData, sortedData[i].key, sortedData[i].value); + for (uint256 i = 0; i < data.length; i++) { + hashData = abi.encodePacked(hashData, data[i].key, data[i].value); } return keccak256(hashData); diff --git a/test/Verifier.t.sol b/test/Verifier.t.sol index 48415fb..9de56d9 100644 --- a/test/Verifier.t.sol +++ b/test/Verifier.t.sol @@ -41,10 +41,10 @@ contract PlutoAttestationExample is Test { // Create the proof data array string[] memory keys = new string[](2); string[] memory values = new string[](2); - keys[0] = "a"; - keys[1] = "c"; - values[0] = "10"; - values[1] = "\"d\""; + keys[0] = "c"; + keys[1] = "a"; + values[0] = "\"d\""; + values[1] = "10"; PlutoAttestationVerifier.ProofData[] memory proofData = verifier.createProofDataArray(keys, values);