Skip to content

Commit 64fb616

Browse files
committed
add PlutoAttestationVerifier contract
1 parent bcfaf40 commit 64fb616

File tree

2 files changed

+246
-8
lines changed

2 files changed

+246
-8
lines changed

src/Verifier.sol

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

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+
function setUp() public {
33+
verifier = new PlutoAttestationVerifier(0xF2E3878C9aB6A377D331E252F6bF3673d8e87323);
34+
}
35+
36+
/**
37+
* @dev Example function demonstrating attestation verification
38+
*/
39+
function test_verifyExampleAttestation() public {
40+
// Create the proof data array
41+
string[] memory keys = new string[](2);
42+
string[] memory values = new string[](2);
43+
keys[0] = "a";
44+
keys[1] = "c";
45+
values[0] = "10";
46+
values[1] = "\"d\"";
47+
48+
PlutoAttestationVerifier.ProofData[] memory proofData =
49+
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)