Reencryption
At a high level, reencryption in SPF transforms a ciphertext decryptable with respect to the committee’s private key to a ciphertext decryptable by a particular user’s private key. What’s magical about reencryption is that we can accomplish this transformation without ever decrypting the ciphertext!
Reencryption is useful for users who worry about the possibility that a majority of the threshold committee may collude to decrypt network data that aren’t supposed to.
Today, our implementation relies on one-time pads. Currently, re-encryption is only available for web2 programs.
Ensure the client is initialized before using these functions.
How reencryption works
The standard decryption workflow requires a network round-trip to the SPF threshold committee:
- Request threshold decryption
- Wait for committee to decrypt
- Receive plaintext result
With reencryption, the final decryption happens locally:
- Generate one-time pad (OTP) keypair
- Request reencryption with public OTP
- Request threshold decryption of reencrypted ciphertext
- Locally decrypt with secret OTP (instant)
Generating OTP keypair
To generate a one-time pad keypair, use the generateOtp() function:
export async function generateOtpExample(): Promise<spf.OtpKeypair> {
// Generate OTP keypair
const { publicOtp, secretOtp } = await spf.generateOtp();
console.log("Public OTP:", publicOtp.length, "bytes");
console.log("Secret OTP:", secretOtp.length, "bytes");
// Keep secretOtp private! Only share publicOtp with SPF
return { publicOtp, secretOtp };
}
The function generates a keypair where the public OTP is FHE-encrypted and sent to SPF for reencryption, while the secret OTP is kept locally for final decryption.
Returns: Promise<OtpKeypair> - Object with publicOtp and secretOtp (both Uint8Array)
Security note: The secret OTP must remain private. Only share the public OTP with SPF.
Requesting reencryption
To request reencryption of a ciphertext using a one-time pad, use the requestReencryption() function:
export async function requestRecryptionExample(
signer: spf.PrivateKeySigner,
ciphertextId: spf.CiphertextId,
publicOtp: Uint8Array,
): Promise<spf.ReencryptHandle> {
// Request reencryption with the public OTP
const reencryptHandle = await spf.requestReencryption(
signer,
ciphertextId,
publicOtp,
);
console.log("Reencryption requested:", reencryptHandle);
return reencryptHandle;
}
This submits a reencryption request to SPF. The service reencrypts the ciphertext under the provided public OTP, enabling local decryption with the corresponding secret OTP. The signer must have decrypt access to the ciphertext.
Parameters:
signer:AnySigner- Signer for authentication (must have decrypt access)ciphertextId:CiphertextId- The ciphertext identifier to reencryptpublicOtp:Uint8Array- The public one-time pad bytes fromgenerateOtp()
Returns: Promise<ReencryptHandle> - The reencryption handle (branded hex string with 0x prefix)
Checking reencryption status
To check the status of a reencryption request, use the checkReencryptionStatus() function:
const status = await spf.checkReencryptionStatus(reencryptHandle);
if (status.status === "success") {
console.log("Reencrypted ciphertext ID:", status.payload.id);
} else if (status.status === "failed") {
console.error("Reencryption failed:", status.payload?.message);
}
Parameters:
reencryptHandle:ReencryptHandle- The reencryption handle fromrequestReencryption()
Returns: Promise<ReencryptionStatus> - Discriminated union with status field
Waiting for reencryption completion
For convenience, use waitForReencryption() to automatically poll until reencryption completes:
export async function waitForRecryptionExample(
reencryptHandle: spf.ReencryptHandle,
): Promise<spf.CiphertextId> {
// Wait for reencryption to complete
const reencryptedCtId = await spf.waitForReencryption(reencryptHandle);
console.log("Reencrypted ciphertext ID:", reencryptedCtId);
return reencryptedCtId;
}
This function polls checkReencryptionStatus() until reencryption succeeds or fails, then returns the reencrypted ciphertext ID.
Parameters:
reencryptHandle:ReencryptHandle- The reencryption handle fromrequestReencryption()signal:AbortSignal- Optional cancellation signal (useAbortSignal.timeout(ms)for timeout)
Returns: Promise<CiphertextId> - The reencrypted ciphertext identifier
Cancellation support
The waitForReencryption() function supports cancellation via AbortSignal:
// Wait with 30 second timeout
const reencryptedCtId = await spf.waitForReencryption(
reencryptHandle,
AbortSignal.timeout(30000),
);
Local OTP decryption
After reencryption completes, use the secret OTP to decrypt locally:
export async function otpDecryptExample(
signer: spf.PrivateKeySigner,
reencryptedCtId: spf.CiphertextId,
secretOtp: Uint8Array,
bitWidth: spf.BitWidth,
): Promise<bigint> {
// Request threshold decryption of the reencrypted ciphertext
const decryptHandle = await spf.requestDecryption(signer, reencryptedCtId);
// Get OTP-encrypted polynomial bytes (NOT parsed plaintext)
const otpEncryptedPoly = await spf.getPolynomialBytesForOtp(decryptHandle);
// Perform local OTP decryption with the secret OTP
const plaintext: bigint = await spf.otpDecrypt(
otpEncryptedPoly,
secretOtp,
bitWidth,
false,
);
console.log("Decrypted value:", plaintext);
return plaintext;
}
This workflow performs threshold decryption on the reencrypted ciphertext, retrieves the OTP-encrypted polynomial bytes, and locally decrypts using the secret OTP.
Key functions:
requestDecryption(): Request threshold decryption of the reencrypted ciphertext (returnsDecryptHandle)getPolynomialBytesForOtp(): Wait for threshold decryption and retrieve OTP-encrypted polynomial bytes (not parsed plaintext)otpDecrypt(): Perform instant local decryption using the secret OTP
getPolynomialBytesForOtp() parameters:
decryptHandle:DecryptHandle- The decrypt handle fromrequestDecryption()signal:AbortSignal- Optional cancellation signal (useAbortSignal.timeout(ms)for timeout)
otpDecrypt() parameters:
polyBytes:Uint8Array- The OTP-encrypted polynomial bytes fromgetPolynomialBytesForOtp()secretOtp:Uint8Array- The secret one-time pad bytes fromgenerateOtp()bitWidth:8 | 16 | 32 | 64- The bit width of the encrypted valuesigned:boolean- Whether the value is signed (default: false)
Returns: Promise<bigint> - The decrypted plaintext value
Complete reencryption workflow
Here is a complete example demonstrating the entire reencryption workflow:
export async function completeRecryptionWorkflow(
signer: spf.PrivateKeySigner,
ciphertextId: spf.CiphertextId,
bitWidth: spf.BitWidth = 16,
): Promise<bigint> {
// Step 1: Generate OTP keypair
const { publicOtp, secretOtp } = await spf.generateOtp();
// Step 2: Request reencryption
const reencryptHandle = await spf.requestReencryption(
signer,
ciphertextId,
publicOtp,
);
// Step 3: Wait for reencryption
const reencryptedCtId = await spf.waitForReencryption(reencryptHandle);
// Step 4: Request threshold decryption of the reencrypted ciphertext
const decryptHandle = await spf.requestDecryption(signer, reencryptedCtId);
// Step 5: Get OTP-encrypted polynomial bytes
const otpEncryptedPoly = await spf.getPolynomialBytesForOtp(decryptHandle);
// Step 6: Perform local OTP decryption
const plaintext = await spf.otpDecrypt(
otpEncryptedPoly,
secretOtp,
bitWidth,
false,
);
console.log("Final plaintext:", plaintext);
return plaintext;
}
This workflow:
- Generates an OTP keypair
- Requests reencryption with the public OTP
- Waits for reencryption to complete
- Requests threshold decryption of the reencrypted ciphertext
- Retrieves OTP-encrypted polynomial bytes
- Performs instant local decryption with the secret OTP