Allowance voucher (aka Session Key) allows owner of a ciphertext to delegate viewing/compute to someone else for a period of time completely off-chain.
Vouchers are like sessions and are revokable.
Getting Started
To create a voucher and grant Bob permission to decrypt on Alice’s behalf:
import { createWalletClient, custom } from 'viem'
import { privateKeyToAccount } from 'viem/accounts';
import { getViemChain, supportedChains, type SupportedChainId } from '@inco/js';
import { Lightning, generateSecp256k1Keypair } from '@inco/js/lite';
// Alice connects her wallet
const walletClient = createWalletClient({
chain: getViemChain(supportedChains.baseSepolia),
transport: custom(window.ethereum!)
})
// Alice creates an ephemeral keypair to use for the decryption request
const ephemeralKeypair = generateSecp256k1Keypair();
const ephemeralAccount = privateKeyToAccount(
`0x${ephemeralKeypair.kp.getPrivate('hex')}`,
);
const zap = await Lightning.latest("testnet", 84532);
// Default session verifier address
const sessionVerfier = "0xc34569efc25901bdd6b652164a2c8a7228b23005";
// Alice creates attested decrypt request and passes the voucher
const voucherWithSig = await zap.grantSessionKeyAllowanceVoucher(
walletClient,
ephemeralAccount.address,
new Date(Date.now() + 1000 * 60 * 60 * 24), // 1 day, in seconds
sessionVerfier,
);
Some specific use cases may require a custom session verifier logic, to for
example check whether a payment has been made in order to access secrets.
grantSessionKeyAllowanceVoucher() allows the owner to specify a custom session
verifier contract address with a custom canUseSession() function that can
either allow or deny access based on certain on-chain conditions.
Using the default session verifier will give the session key unverified access
to all user handles for the specified time period.DEFAULT_SESSION_VERIFIER: (Base Sepolia)
0xc34569efc25901bdd6b652164a2c8a7228b23005
import {ALLOWANCE_GRANTED_MAGIC_VALUE} from "@inco/lightning/src/Types.sol";
struct Session {
address decrypter;
uint256 expiresAt;
}
contract MyCustomSessionVerifier {
function canUseSession(
bytes32, /* handle */
address account, /* user trying to use the session */
bytes memory sharerArgData,
bytes memory /* requesterArgData */
) external view returns (bytes32) {
Session memory session = abi.decode(sharerArgData, (Session));
if (session.expiresAt >= block.timestamp && session.decrypter == account) {
return ALLOWANCE_GRANTED_MAGIC_VALUE;
}
return bytes32(0);
}
}
Attested Decrypt with Voucher/Session Key
The voucher and signature now allow Bob to decrypt any handle that Alice owns using the attestedDecryptWithVoucher() function:
import type { HexString } from '@inco/js';
// Bob calls the attestedDecryptWithVoucher function with voucher
const decrypted = await zap.attestedDecryptWithVoucher(
ephemeralKeypair,
voucherWithSig,
publicClient,
['0x...' as HexString]
);
const plaintext = decrypted[0].plaintext.value;
AttestedCompute with Voucher/Session Key
Bob can also perform attested compute on behalf of Alice using attestedComputeWithVoucher() to check if the value is equal to an expected value:
import type { AttestedComputeSupportedOps } from '@inco/js/lite';
import type { HexString } from '@inco/js';
const computed = await zap.attestedComputeWithVoucher(
ephemeralKeypair,
voucherWithSig,
publicClient,
'0x...' as HexString,
AttestedComputeSupportedOps.Eq,
100n
);
const plaintext = computed.plaintext.value;
SessionKey Reencrypt
Similarly, Bob can also perform reencryption on behalf of Alice using attestedDecryptWithVoucher() with a reencrypt public key. This is similar to standard attested decrypt reencryption.
import type { HexString } from '@inco/js';
// Generate a reencrypt keypair for Bob
const reencryptKeypair = generateSecp256k1Keypair();
const reencryptPubKey = reencryptKeypair.encodePublicKey();
// Bob performs reencryption on behalf of Alice
const reencrypted = await zap.attestedDecryptWithVoucher(
ephemeralKeypair,
voucherWithSig,
publicClient,
['0x...' as HexString],
reencryptPubKey
);
// Bob can decrypt the reencrypted data
const decrypted = await decrypt(
reencryptKeypair,
hexToBytes(reencrypted[0].encryptedPlaintext.ciphertext.value)
);
const plaintext = BigInt('0x' + Buffer.from(decrypted).toString('hex'));
Revoking a Voucher
In order to invalidate existing vouchers, you can call this function:
const txHash = await zap.updateActiveVouchersSessionNonce(walletClient);
Which will invalidate all existing vouchers, despite their expiration time.
If you want to selectively revoke access to specific users, your dApp would
need to handle the logic of reissuing new vouchers to users who still require
access.