Skip to main content
Attested decrypt allows authorized user to request a decryption attestation and submit plaintext back on chain. Only handles that are allowed with e.allow() or e.reveal() can be used in decryption.

Getting Started

Take this example contract:
import {euint256, e, inco} from "@inco/lightning/src/Lib.sol";
import {DecryptionAttestation} from "@inco/lightning/src/lightning-parts/DecryptionAttester.types.sol";

contract TestAttestedDecrypt {
    euint256 randomNumber;
    uint256 decryptedNumber;

    constructor(address owner) payable {
        require(msg.value == inco.getFee(), "Fee not paid");
        randomNumber = e.rand();
        e.allow(randomNumber, address(this));
        e.allow(randomNumber, owner);
    }

    function GetHandle() external returns (euint256) {
        return randomNumber;
    }

    function SubmitDecryption(
        DecryptionAttestation memory decryption,
        bytes[] memory signatures
    ) external {
        require(
            inco.incoVerifier().isValidDecryptionAttestation(decryption, signatures),
            "Invalid Signature"
        );
        require(euint256.unwrap(randomNumber) == decryption.handle, "Handle mismatch");

        decryptedNumber = uint256(decryption.value);
    }
}
This contract creates a random 256bit integer and grants owner the ownership of the handle, allowing the owner to perform computations on it as well as decrypt it. In order to decrypt the random integer, the owner can now request a decryption with attestation from covalidator and that’s done completely offchain.

Basic Decryption

Here’s how to decrypt a handle using a wallet client:
import { getViemChain, supportedChains, type HexString } from '@inco/js';
import { Lightning } from '@inco/js/lite';
import { createWalletClient, custom } from 'viem'

// Connect to Metamask or other wallet provider
const walletClient = createWalletClient({
  chain: getViemChain(supportedChains.baseSepolia),
  transport: custom(window.ethereum!)
})

const zap = await Lightning.latest('testnet', supportedChains.baseSepolia);
const results = await zap.attestedDecrypt(
  walletClient,
  ["0x<your_handle>" as HexString]
);
const plaintext = results[0].plaintext.value;
It’s also possible to decrypt multiple handles in a single request by passing an array of handles:
const results = await zap.attestedDecrypt(
  walletClient,
  [
    "0x<your_handle_1>" as HexString,
    "0x<your_handle_2>" as HexString
  ]
);
const plaintext1 = results[0].plaintext.value;
const plaintext2 = results[1].plaintext.value;

Understanding Reencryption vs Decryption

When using attested decrypt, you have two main options:
  • Decryption: Returns the plaintext value directly (as shown in the Basic Decryption examples above)
  • Reencryption: Instead of returning plaintext, the decrypted value is re-encrypted with a different public key. This allows you to share decrypted data with third parties (delegates) without exposing the plaintext, or to decrypt it locally using your own keypair.
Reencryption provides an additional layer of security by ensuring that even if the attested decrypt response is intercepted, the actual plaintext value remains encrypted and can only be decrypted by the intended recipient.

Reencryption for Delegates

You can request that the decryption be reencrypted for a delegate using their public key. This returns an encrypted decryption attestation that only the delegate can decrypt:
import { generateSecp256k1Keypair } from '@inco/js/lite';

// Generate a keypair for the delegate
const delegateKeypair = generateSecp256k1Keypair();
const delegatePubKey = delegateKeypair.encodePublicKey();

// Request reencryption for the delegate
const encryptedResults = await zap.attestedDecrypt(
  walletClient,
  ["0x<your_handle>" as HexString],
  delegatePubKey
);

// The delegate can decrypt using their keypair
const encryptedAttestation = encryptedResults[0].encryptedPlaintext;
// The delegate would decrypt this using their private key

Reencrypt and Decrypt Locally

You can also request reencryption and decrypt it immediately using a keypair you control:
import { generateSecp256k1Keypair } from '@inco/js/lite';

// Generate your own keypair
const keypair = generateSecp256k1Keypair();
const pubKey = keypair.encodePublicKey();

// Request reencryption and decrypt locally
const results = await zap.attestedDecrypt(
  walletClient,
  ["0x<your_handle>" as HexString],
  pubKey,
  keypair
);

const plaintext = results[0].plaintext.value;

Session Key Decryption

For applications that need to perform decryption without requiring the user to sign each request, you can use session keys with allowance vouchers:
import { generateSecp256k1Keypair } from '@inco/js/lite';

// Generate an ephemeral keypair for the session
const ephemeralKeypair = generateSecp256k1Keypair();

const defaultSessionVerifier = '0xc34569efc25901bdd6b652164a2c8a7228b23005';

// Grant a session key allowance voucher (done once, typically by the user)
const expiresAt = new Date(Date.now() + 3600000); // 1 hour from now
const voucher = await zap.grantSessionKeyAllowanceVoucher(
  walletClient,
  ephemeralKeypair.encodePublicKey(), // grantee address derived from keypair
  expiresAt,
  defaultSessionVerifier // or a custom session verifier
);

// Now decrypt using the session key (no wallet signature needed)
const results = await zap.attestedDecryptWithVoucher(
  ephemeralKeypair,
  voucher,
  ["0x<your_handle>" as HexString]
);

const plaintext = results[0].plaintext.value;
Session key decryption also supports reencryption:
// Reencrypt for a delegate using session key
const delegateKeypair = generateSecp256k1Keypair();
const encryptedResults = await zap.attestedDecryptWithVoucher(
  ephemeralKeypair,
  voucher,
  ["0x<your_handle>" as HexString],
  delegateKeypair.encodePublicKey()
);

// Or reencrypt and decrypt locally
const results = await zap.attestedDecryptWithVoucher(
  ephemeralKeypair,
  voucher,
  ["0x<your_handle>" as HexString],
  keypair.encodePublicKey(),
  keypair
);

Retry Configuration

All decryption methods support optional retry configuration:
const backoffConfig = {
  maxRetries: 5,
  initialDelay: 1000,
  maxDelay: 10000
};

const results = await zap.attestedDecrypt(
  walletClient,
  ["0x<your_handle>" as HexString],
  backoffConfig
);
If you wish to perform a computation on a handle before decrypting it in a single request, check out Attested Compute .

Attested decrypt with Allowance Voucher/Session Key

Attested decrypt can also be called with a session key instead of a wallet provider using Allowance Voucher, allowing someone else to request decryption on your behalf.