Skip to content

Commit 1c24a7f

Browse files
committed
add PlutoAttestationVerifier contract
1 parent bcfaf40 commit 1c24a7f

2 files changed

Lines changed: 220 additions & 8 deletions

File tree

src/Verifier.sol

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,169 @@ contract Verifier is Ownable {
9090
return true;
9191
}
9292
}
93+
94+
contract PlutoAttestationVerifier {
95+
struct ProofData {
96+
string key;
97+
string value;
98+
}
99+
100+
struct AttestationInput {
101+
string version;
102+
string scriptRaw;
103+
string issuedAt;
104+
string nonce;
105+
string sessionId;
106+
ProofData[] data;
107+
}
108+
109+
struct AttestationSignature {
110+
bytes32 digest;
111+
uint8 v;
112+
bytes32 r;
113+
bytes32 s;
114+
address expectedSigner;
115+
}
116+
117+
Verifier public verifier;
118+
119+
constructor(address notaryAddress) {
120+
verifier = new Verifier(notaryAddress);
121+
}
122+
123+
/**
124+
* @dev Calculate script hash from version and script content
125+
*/
126+
function calculateScriptHash(string memory version, string memory scriptRaw) public pure returns (bytes32) {
127+
return keccak256(abi.encodePacked(version, scriptRaw));
128+
}
129+
130+
/**
131+
* @dev Calculate session hash from all session components
132+
*/
133+
function calculateSessionHash(
134+
string memory version,
135+
string memory issuedAt,
136+
string memory nonce,
137+
string memory sessionId,
138+
ProofData[] memory data
139+
) public pure returns (bytes32) {
140+
// Sort the data array by key (simple bubble sort for demonstration)
141+
ProofData[] memory sortedData = new ProofData[](data.length);
142+
for (uint256 i = 0; i < data.length; i++) {
143+
sortedData[i] = data[i];
144+
}
145+
146+
// Bubble sort by key
147+
for (uint256 i = 0; i < sortedData.length; i++) {
148+
for (uint256 j = 0; j < sortedData.length - 1 - i; j++) {
149+
if (keccak256(bytes(sortedData[j].key)) > keccak256(bytes(sortedData[j + 1].key))) {
150+
ProofData memory temp = sortedData[j];
151+
sortedData[j] = sortedData[j + 1];
152+
sortedData[j + 1] = temp;
153+
}
154+
}
155+
}
156+
157+
// Build the hash incrementally
158+
bytes memory hashData = abi.encodePacked(version, issuedAt, nonce, sessionId);
159+
160+
for (uint256 i = 0; i < sortedData.length; i++) {
161+
hashData = abi.encodePacked(hashData, sortedData[i].key, sortedData[i].value);
162+
}
163+
164+
return keccak256(hashData);
165+
}
166+
167+
/**
168+
* @dev Calculate digest from session and script hashes
169+
*/
170+
function calculateDigest(bytes32 sessionHash, bytes32 scriptHash) public pure returns (bytes32) {
171+
// reportData = sessionHash + scriptHash (64 bytes)
172+
bytes memory reportData = abi.encodePacked(sessionHash, scriptHash);
173+
return keccak256(reportData);
174+
}
175+
176+
/**
177+
* @dev Verify complete attestation by calculating hashes and checking signature
178+
*/
179+
function verifyAttestation(AttestationInput memory input, AttestationSignature memory signature)
180+
public
181+
returns (bool)
182+
{
183+
// Calculate script hash
184+
bytes32 scriptHash = calculateScriptHash(input.version, input.scriptRaw);
185+
186+
// Calculate session hash
187+
bytes32 sessionHash =
188+
calculateSessionHash(input.version, input.issuedAt, input.nonce, input.sessionId, input.data);
189+
190+
// Calculate digest
191+
bytes32 digest = calculateDigest(sessionHash, scriptHash);
192+
193+
// Verify the digest matches
194+
if (digest != signature.digest) {
195+
return false;
196+
}
197+
198+
// Call the signature verification contract
199+
bool success = verifier.verifyNotarySignature(
200+
signature.digest, signature.v, signature.r, signature.s, signature.expectedSigner, scriptHash, sessionHash
201+
);
202+
203+
if (!success) {
204+
return false;
205+
}
206+
207+
return success;
208+
}
209+
210+
/**
211+
* @dev Batch verify multiple attestations
212+
*/
213+
function verifyMultipleAttestations(AttestationInput[] memory inputs, AttestationSignature[] memory signatures)
214+
public
215+
returns (bool[] memory)
216+
{
217+
require(inputs.length == signatures.length, "Array length mismatch");
218+
219+
bool[] memory results = new bool[](inputs.length);
220+
221+
for (uint256 i = 0; i < inputs.length; i++) {
222+
results[i] = verifyAttestation(inputs[i], signatures[i]);
223+
}
224+
225+
return results;
226+
}
227+
228+
/**
229+
* @dev Get calculated hashes for debugging
230+
*/
231+
function getCalculatedHashes(AttestationInput memory input)
232+
public
233+
pure
234+
returns (bytes32 scriptHash, bytes32 sessionHash, bytes32 digest)
235+
{
236+
scriptHash = calculateScriptHash(input.version, input.scriptRaw);
237+
sessionHash = calculateSessionHash(input.version, input.issuedAt, input.nonce, input.sessionId, input.data);
238+
digest = calculateDigest(sessionHash, scriptHash);
239+
}
240+
241+
/**
242+
* @dev Helper function to create ProofData array from parallel arrays
243+
*/
244+
function createProofDataArray(string[] memory keys, string[] memory values)
245+
public
246+
pure
247+
returns (ProofData[] memory)
248+
{
249+
require(keys.length == values.length, "Array length mismatch");
250+
251+
ProofData[] memory proofData = new ProofData[](keys.length);
252+
for (uint256 i = 0; i < keys.length; i++) {
253+
proofData[i] = ProofData(keys[i], values[i]);
254+
}
255+
256+
return proofData;
257+
}
258+
}

test/Verifier.t.sol

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,72 @@
22
pragma solidity ^0.8.13;
33

44
import {Test, console} from "forge-std/Test.sol";
5-
import {Verifier} from "../src/Verifier.sol";
5+
import {Verifier, PlutoAttestationVerifier} from "../src/Verifier.sol";
66

77
contract VerifierTest is Test {
88
Verifier public verifier;
99

1010
function setUp() public {
11-
verifier = new Verifier(0xfdf07A5dCfa7b74f4c28DAb23eaD8B1c43Be801F);
11+
verifier = new Verifier(0xF2E3878C9aB6A377D331E252F6bF3673d8e87323);
1212
}
1313

1414
function test_isValidSignature() public {
1515
// TEST vector from web-prover @ githash 2dc768e818d6f9fef575a88a2ceb80c0ed11974f
16-
address signer = 0xfdf07A5dCfa7b74f4c28DAb23eaD8B1c43Be801F;
17-
bytes32 digest = bytes32(0xe45537be7b5cd288c9c46b7e027b4f5a66202146012f792c1b1cabb65828994b);
18-
bytes32 r = bytes32(0x36e820b3524e9ffffe0b4ee49e4131cc362fd161821c1dfc8757dc6186f31c96);
19-
bytes32 s = bytes32(0x416e537065673e3028eca37cf3cbe805a3d2fafbc47235fee5e89df5f0509a9c);
16+
address signer = 0xF2E3878C9aB6A377D331E252F6bF3673d8e87323;
17+
bytes32 digest = bytes32(0x3858f7da505d328a26770f8cac2170c0e937261dc451e34707dd8b2600b3a63e);
18+
bytes32 r = bytes32(0xcd21eef84a7686c71e6c3cc801b4cc6883d3e9e4ba0da78ab1245897f6bcbe43);
19+
bytes32 s = bytes32(0x6985c20ecd47b70c007f95412c0231b20d16a56001d7d214e427acf6b5615e22);
2020
uint8 v = 27;
2121

22-
bytes32 value = 0x8452c9b9140222b08593a26daa782707297be9f7b3e8281d7b4974769f19afd0;
23-
bytes32 manifest = 0x7df909980a1642d0370a4a510422201ce525da6b319a7b9e9656771fa7336d5a;
22+
bytes32 value = 0x0e38baef3358f6094095731571734ed4e83492afd88025e0e929d3de25286a60;
23+
bytes32 manifest = 0xdd2a3dcaa72abdb5de17624afbf7f4216fa72a4998c82383617818ce80bb03b6;
2424

2525
assertEq(verifier.verifyNotarySignature(digest, v, r, s, signer, manifest, value), true);
2626
}
2727
}
28+
29+
// Example usage contract showing how to use the verifier
30+
contract PlutoAttestationExample is Test {
31+
PlutoAttestationVerifier public verifier;
32+
33+
function setUp() public {
34+
verifier = new PlutoAttestationVerifier(0xF2E3878C9aB6A377D331E252F6bF3673d8e87323);
35+
}
36+
37+
/**
38+
* @dev Example function demonstrating attestation verification
39+
*/
40+
function test_verifyExampleAttestation() public {
41+
// Create the proof data array
42+
string[] memory keys = new string[](2);
43+
string[] memory values = new string[](2);
44+
keys[0] = "a";
45+
keys[1] = "c";
46+
values[0] = "10";
47+
values[1] = "\"d\"";
48+
49+
PlutoAttestationVerifier.ProofData[] memory proofData = verifier.createProofDataArray(keys, values);
50+
51+
// Create the attestation input
52+
PlutoAttestationVerifier.AttestationInput memory input = PlutoAttestationVerifier.AttestationInput({
53+
version: "v1",
54+
scriptRaw: "import { createSession } from '@plutoxyz/automation';\nconst session = await createSession();\nawait session.prove('bank_balance', { a: 10, c: 'd' });",
55+
issuedAt: "2025-06-30T10:45:20Z",
56+
nonce: "0x7b830a98e58e284b",
57+
sessionId: "f4c38687-6fe0-40b8-8c05-e4a085856b05",
58+
data: proofData
59+
});
60+
61+
// Create the signature struct
62+
PlutoAttestationVerifier.AttestationSignature memory signature = PlutoAttestationVerifier.AttestationSignature({
63+
digest: 0x088965a798b565d02f3ae18aa703609c645668d438969198166e4d9215a77f30,
64+
v: 27,
65+
r: 0x44a1b7809a7903ea087e7b5ce4092c24020134c5c3ea008656853f6d6da51b54,
66+
s: 0x1e8b4bcbb716639b038d613257eac91d8edfaa1a5daa924307ce6cf4490b1edb,
67+
expectedSigner: 0xF2E3878C9aB6A377D331E252F6bF3673d8e87323
68+
});
69+
70+
bool success = verifier.verifyAttestation(input, signature);
71+
assertEq(success, true);
72+
}
73+
}

0 commit comments

Comments
 (0)