Ethereum specializations for ProofPack.
$ dotnet add package Zipwire.ProofPack.EthereumEthereum integration for ProofPack: verifiable data exchange with EAS attestation support.
This package extends the core ProofPack library to support:
Note: This package provides complete EAS attestation verification along with Ethereum-based signing and verification (ES256K). Supports Base Sepolia and other EAS-enabled networks.
dotnet add package Zipwire.ProofPack.Ethereum
using Zipwire.ProofPack.Ethereum;
// Create a signer with your Ethereum private key
var signer = new ES256KJwsSigner(privateKey);
// Sign a ProofPack envelope (see core library for envelope creation)
var signed = await signer.SignAsync(header, payload);
// Verify a signed envelope
var verifier = new ES256KJwsVerifier(expectedSignerAddress);
var result = await verifier.VerifyAsync(signed);
This package provides comprehensive EAS attestation verification:
using Zipwire.ProofPack.Ethereum;
// Configure EAS networks
var networkConfig = new EasNetworkConfiguration(
"Base Sepolia",
"base-sepolia-provider",
"https://sepolia.base.org",
loggerFactory);
// Create attestation verifier
var verifier = new EasAttestationVerifier(new[] { networkConfig });
// Create factory with the verifier
var factory = new AttestationVerifierFactory(verifier);
// Use with AttestedMerkleExchangeReader
var verificationContext = AttestedMerkleExchangeVerificationContext.WithAttestationVerifierFactory(
maxAge: TimeSpan.FromDays(30),
resolveJwsVerifier: (algorithm, signerAddresses) =>
{
return algorithm switch
{
"ES256K" => new ES256KJwsVerifier(signerAddresses.First()),
"RS256" => new DefaultRsaVerifier(publicKey),
_ => null
};
},
signatureRequirement: JwsSignatureRequirement.All,
hasValidNonce: nonce => Task.FromResult(true),
attestationVerifierFactory: factory);
var reader = new AttestedMerkleExchangeReader();
var result = await reader.ReadAsync(jwsJson, verificationContext);
if (result.IsValid)
{
// Verify recipient matches expected wallet
var expectedRecipient = "0x1234567890123456789012345678901234567890"; // User's wallet
var attestedRecipient = result.Document.Attestation.Eas.To;
if (attestedRecipient != null && attestedRecipient != expectedRecipient)
{
Console.WriteLine($"❌ Recipient verification failed: Expected {expectedRecipient}, Got {attestedRecipient}");
// Handle recipient mismatch
}
else
{
Console.WriteLine($"✅ Recipient verification passed: {attestedRecipient ?? "None specified"}");
// Use the verified document
}
}
This package supports hierarchical delegation verification via the IsDelegate schema. Enable one entity (verified by Zipwire) to delegate authority to another, with verifiable chain of custody and Merkle root binding.
The IsDelegate pattern enables:
using Zipwire.ProofPack.Ethereum;
// Configure the networks where attestations are stored
var networkConfig = new EasNetworkConfiguration(
networkId: "Base Sepolia",
rpcProviderName: "alchemy", // or your preferred provider
rpcEndpoint: "wss://base-sepolia.g.alchemy.com/v2/YOUR_API_KEY",
loggerFactory: loggerFactory);
// Define which root attestations are trusted
// (e.g., Zipwire identity schema with Zipwire attester)
var acceptedRoot = new AcceptedRoot
{
SchemaUid = "0x1234567890abcdef...", // IsAHuman schema UID
Attesters = new[] { "0xZipwireMasterAddress" }
};
// Create the IsDelegate verifier
var isDelegateConfig = new IsDelegateVerifierConfig
{
AcceptedRoots = new[] { acceptedRoot }, // Trusted roots
DelegationSchemaUid = "0x5678abcdef...", // Delegation schema UID
MaxDepth = 32 // Prevent infinite chains
};
var verifier = new IsDelegateAttestationVerifier(
new[] { networkConfig },
isDelegateConfig);
When a proof pack's attestation locator uses a delegation schema, the reader automatically routes to the IsDelegate verifier:
// Create verifier factory with IsDelegate verifier
var factory = new AttestationVerifierFactory(isDelegateVerifier);
// Configure routing to recognize delegation schema
var routingConfig = new AttestationRoutingConfig
{
DelegationSchemaUid = isDelegateConfig.DelegationSchemaUid
};
// Create verification context with routing
var verificationContext = AttestedMerkleExchangeVerificationContext.WithAttestationVerifierFactory(
maxAge: TimeSpan.FromDays(30),
resolveJwsVerifier: (algorithm, signerAddresses) =>
algorithm == "ES256K" ? new ES256KJwsVerifier(signerAddresses.First()) : null,
signatureRequirement: JwsSignatureRequirement.All,
hasValidNonce: nonce => Task.FromResult(true),
attestationVerifierFactory: factory,
routingConfig: routingConfig);
// Read and verify the proof pack
var reader = new AttestedMerkleExchangeReader();
var result = await reader.ReadAsync(jwsJson, verificationContext);
if (result.IsValid)
{
Console.WriteLine($"✅ Proof verified. Issued by: {result.Document.Attestation.Eas.From}");
Console.WriteLine($" Acting as: {result.Document.Attestation.Eas.To}");
Console.WriteLine($" Merkle root: {result.Document.MerkleTree.Root}");
}
else
{
Console.WriteLine($"❌ Verification failed: {result.Message}");
}
You can use EAS GraphQL instead of RPC: pass IsDelegateVerifierOptions with Chains (or Lookup) and call VerifyByWalletAsync. The verifier fetches all IsDelegate leaves for the wallet and returns the first valid chain.
using Zipwire.ProofPack.Ethereum;
// config: same IsDelegateVerifierConfig as above (AcceptedRoots, DelegationSchemaUid,
// PreferredSubjectSchemas, SchemaPayloadValidators, MaxDepth)
// Chain names only (built-in easscan.org endpoints)
var verifier = new IsDelegateAttestationVerifier(
new IsDelegateVerifierOptions { Chains = new[] { "base-sepolia", "base" } },
config);
var result = await verifier.VerifyByWalletAsync(actingWallet, merkleRoot);
// Or explicit lookup (e.g. custom URLs or tests)
var lookup = EasGraphQLLookup.Create(new[] { "base-sepolia" });
var verifier2 = new IsDelegateAttestationVerifier(
new IsDelegateVerifierOptions { Lookup = lookup },
config);
var result2 = await verifier2.VerifyByWalletAsync(actingWallet, merkleRoot, networkId: "base-sepolia");
What's happening here:
AcceptedRoots tells the verifier which root attesters/schemas are trusted at the top of the chain.PreferredSubjectSchemas and SchemaPayloadValidators define how the subject attestation (e.g. PrivateData) is validated and that its Merkle root matches the proof.GetAttestationAsync (no RPC).VerifyByWalletAsync: return values and behavior
No IsDelegate attestations found for the address
Returns a failed AttestationResult: IsValid: false, Message: "No delegation attestations found for wallet", ReasonCode: "MISSING_ATTESTATION", AttestationUid empty.
One or more valid chains
The verifier tries each leaf (each IsDelegate attestation for the wallet) in the order returned by the lookup. It returns as soon as one chain validates successfully. You get a successful AttestationResult with: IsValid: true, Message (success message from the walk), AttestationUid (the leaf attestation UID that was verified), ReasonCode: "VALID", Attester (root attester address).
Multiple valid chains
Only the first valid chain is returned. Order is determined by the lookup (e.g. GraphQL). The verifier does not aggregate or return multiple results.
First chain invalid, others valid
If the first leaf’s chain fails (e.g. revoked, expired, wrong root), the verifier does not stop: it tries the next leaf until one succeeds. If all fail, it returns the result of the last failed attempt (single failure with the last chain’s reason).
The IsDelegate verifier validates:
MaxDepthIf you need to support both EAS (single-attestation) and IsDelegate (chain-based) schemas in the same application:
// Create both verifiers
var easVerifier = new EasAttestationVerifier(new[] { networkConfig });
var isDelegateVerifier = new IsDelegateAttestationVerifier(
new[] { networkConfig },
isDelegateConfig);
// Register both in the factory
var factory = new AttestationVerifierFactory(easVerifier, isDelegateVerifier);
// Configure routing for both schemas
var routingConfig = new AttestationRoutingConfig
{
DelegationSchemaUid = isDelegateConfig.DelegationSchemaUid,
PrivateDataSchemaUid = "0x....." // Your private data schema UID
};
// The reader will automatically route to the correct verifier based on schema
var verificationContext = AttestedMerkleExchangeVerificationContext.WithAttestationVerifierFactory(
// ... other parameters ...
attestationVerifierFactory: factory,
routingConfig: routingConfig);
MIT — see LICENSE