Skip to content

Commit be1192a

Browse files
j2kuncopybara-github
authored andcommitted
Add pass implementing trivial encryption as a ct-pt add
This change introduces a new pass, implement-trivial-encryption-as-addition that allows us to support server-side trivial encryption. This pass does it by adding a new "zero encryption" input and helper function, and then a lwe.trivial_encrypt is converted to a ct-pt addition. While this is efficient, it does incur the overhead of an additional client encryption of zero. Few library backends support `lwe.trivial_encrypt` natively. When a backend DOES support it, we should not use this pass, and instead lower `trivial_encrypt` appropriately. The next change will include integration of a lowering from secret-to-scheme to allow trivial.encrypt when `secret.conceal` is in a server-side function. PiperOrigin-RevId: 884111815
1 parent 1b55784 commit be1192a

32 files changed

Lines changed: 881 additions & 307 deletions

lib/Dialect/Arith/Conversions/ArithToCGGI/ArithToCGGI.cpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -170,18 +170,13 @@ static Value materializeTarget(OpBuilder& builder, Type type, ValueRange inputs,
170170
.getRing()
171171
.getCoefficientType()
172172
.getIntOrFloatBitWidth();
173-
auto ciphertextBits = ciphertextType.getCiphertextSpace()
174-
.getRing()
175-
.getCoefficientType()
176-
.getIntOrFloatBitWidth();
177173
auto ptxtTy = lwe::LWEPlaintextType::get(builder.getContext(),
178174
ciphertextType.getPlaintextSpace());
179175

180176
auto trivialEnc = lwe::TrivialEncryptOp::create(
181177
builder, loc, type,
182178
lwe::EncodeOp::create(builder, loc, ptxtTy, inputs[0],
183-
builder.getIndexAttr(plaintextBits)),
184-
builder.getIndexAttr(ciphertextBits));
179+
builder.getIndexAttr(plaintextBits)));
185180

186181
return trivialEnc;
187182
}

lib/Dialect/LWE/IR/LWEOps.cpp

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -82,20 +82,6 @@ LogicalResult TrivialEncryptOp::verify() {
8282
<< outPlaintextSpace;
8383
}
8484

85-
auto outCiphertextModulus = this->getOutput()
86-
.getType()
87-
.getCiphertextSpace()
88-
.getRing()
89-
.getCoefficientType()
90-
.getIntOrFloatBitWidth();
91-
if (outCiphertextModulus != this->getCiphertextBits().getZExtValue()) {
92-
return this->emitOpError()
93-
<< "ciphertext modulus of the output must match the ciphertext_bits "
94-
"parameter, expected "
95-
<< this->getCiphertextBits().getZExtValue() << " but found "
96-
<< outCiphertextModulus;
97-
}
98-
9985
return success();
10086
}
10187

lib/Dialect/LWE/IR/LWEOps.td

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -110,21 +110,11 @@ def LWE_EncodeOp : LWE_Op<"encode"> {
110110
def LWE_TrivialEncryptOp: LWE_Op<"trivial_encrypt", [
111111
EncodingsMatch<"input", "LWEPlaintextType", "output", "LWECiphertextType">]> {
112112
let summary = "Create a trivial encryption of a plaintext.";
113-
114-
let arguments = (ins
115-
LWEPlaintext:$input,
116-
IndexAttr:$ciphertext_bits
117-
);
118-
113+
let arguments = (ins LWEPlaintext:$input);
119114
let results = (outs LWECiphertext:$output);
120-
121115
let assemblyFormat = [{
122-
$input attr-dict `:` qualified(type(operands)) `to` qualified(type(results))
116+
$input attr-dict `:` type(operands) `->` type(results)
123117
}];
124-
125-
// Verify that the ciphertext modulus matches the output ciphertext type's
126-
// ciphertext modulus and the application data of the input and output are
127-
// equal..
128118
let hasVerifier = 1;
129119
}
130120

lib/Dialect/LWE/Transforms/AddDebugPort.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#ifndef LIB_DIALECT_LWE_TRANSFORMS_ADDDEBUGPORT_H_
22
#define LIB_DIALECT_LWE_TRANSFORMS_ADDDEBUGPORT_H_
33

4+
// IWYU pragma: begin_keep
45
#include "mlir/include/mlir/Pass/Pass.h" // from @llvm-project
6+
// IWYU pragma: end_keep
57

68
namespace mlir {
79
namespace heir {

lib/Dialect/LWE/Transforms/BUILD

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ cc_library(
1313
],
1414
deps = [
1515
":AddDebugPort",
16+
":ImplementTrivialEncryptionAsAddition",
1617
":pass_inc_gen",
1718
"@heir//lib/Dialect/LWE/IR:Dialect",
1819
],
@@ -38,6 +39,27 @@ cc_library(
3839
],
3940
)
4041

42+
cc_library(
43+
name = "ImplementTrivialEncryptionAsAddition",
44+
srcs = ["ImplementTrivialEncryptionAsAddition.cpp"],
45+
hdrs = ["ImplementTrivialEncryptionAsAddition.h"],
46+
deps = [
47+
":pass_inc_gen",
48+
"@heir//lib/Dialect:FuncUtils",
49+
"@heir//lib/Dialect:ModuleAttributes",
50+
"@heir//lib/Dialect/LWE/IR:Dialect",
51+
"@llvm-project//llvm:Support",
52+
"@llvm-project//mlir:ArithDialect",
53+
"@llvm-project//mlir:FuncDialect",
54+
"@llvm-project//mlir:IR",
55+
"@llvm-project//mlir:Pass",
56+
"@llvm-project//mlir:Support",
57+
"@llvm-project//mlir:TensorDialect",
58+
"@llvm-project//mlir:TransformUtils",
59+
"@llvm-project//mlir:Transforms",
60+
],
61+
)
62+
4163
add_heir_transforms(
4264
header_filename = "Passes.h.inc",
4365
pass_name = "LWE",
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
#include "lib/Dialect/LWE/Transforms/ImplementTrivialEncryptionAsAddition.h"
2+
3+
#include <cassert>
4+
#include <cstdint>
5+
#include <string>
6+
7+
#include "lib/Dialect/FuncUtils.h"
8+
#include "lib/Dialect/LWE/IR/LWEAttributes.h"
9+
#include "lib/Dialect/LWE/IR/LWEOps.h"
10+
#include "lib/Dialect/LWE/IR/LWETypes.h"
11+
#include "lib/Dialect/ModuleAttributes.h"
12+
#include "llvm/include/llvm/Support/Debug.h" // from @llvm-project
13+
#include "llvm/include/llvm/Support/Format.h" // from @llvm-project
14+
#include "llvm/include/llvm/Support/raw_ostream.h" // from @llvm-project
15+
#include "mlir/include/mlir/Dialect/Arith/IR/Arith.h" // from @llvm-project
16+
#include "mlir/include/mlir/Dialect/Func/IR/FuncOps.h" // from @llvm-project
17+
#include "mlir/include/mlir/Dialect/Tensor/IR/Tensor.h" // from @llvm-project
18+
#include "mlir/include/mlir/IR/Block.h" // from @llvm-project
19+
#include "mlir/include/mlir/IR/Builders.h" // from @llvm-project
20+
#include "mlir/include/mlir/IR/BuiltinOps.h" // from @llvm-project
21+
#include "mlir/include/mlir/IR/BuiltinTypes.h" // from @llvm-project
22+
#include "mlir/include/mlir/IR/ImplicitLocOpBuilder.h" // from @llvm-project
23+
#include "mlir/include/mlir/IR/Operation.h" // from @llvm-project
24+
#include "mlir/include/mlir/IR/PatternMatch.h" // from @llvm-project
25+
#include "mlir/include/mlir/IR/TypeRange.h" // from @llvm-project
26+
#include "mlir/include/mlir/IR/TypeUtilities.h" // from @llvm-project
27+
#include "mlir/include/mlir/IR/Types.h" // from @llvm-project
28+
#include "mlir/include/mlir/IR/Value.h" // from @llvm-project
29+
#include "mlir/include/mlir/IR/Visitors.h" // from @llvm-project
30+
#include "mlir/include/mlir/Support/LLVM.h" // from @llvm-project
31+
#include "mlir/include/mlir/Support/LogicalResult.h" // from @llvm-project
32+
#include "mlir/include/mlir/Support/WalkResult.h" // from @llvm-project
33+
#include "mlir/include/mlir/Transforms/WalkPatternRewriteDriver.h" // from @llvm-project
34+
35+
// IWYU pragma: begin_keep
36+
#include "mlir/include/mlir/Transforms/Passes.h" // from @llvm-project
37+
// IWYU pragma: end_keep
38+
39+
#define DEBUG_TYPE "implement-trivial-encryption-as-addition"
40+
41+
namespace mlir {
42+
namespace heir {
43+
namespace lwe {
44+
45+
#define GEN_PASS_DEF_IMPLEMENTTRIVIALENCRYPTIONASADDITION
46+
#include "lib/Dialect/LWE/Transforms/Passes.h.inc"
47+
48+
using func::FuncOp;
49+
50+
Type findEncryptionKeyTypeFromHelper(func::FuncOp funcOp, ModuleOp module) {
51+
Type foundType;
52+
53+
// First try looking for public key types, since the result decryption func
54+
// always has a secret key type and we don't want to accidentally visit it
55+
// first.
56+
auto result = module.walk([&](func::FuncOp func) {
57+
if (isClientHelper(func.getOperation())) {
58+
for (Type ty : func.getArgumentTypes())
59+
if (isa<LWEPublicKeyType>(ty)) {
60+
foundType = ty;
61+
return WalkResult::interrupt();
62+
}
63+
}
64+
return WalkResult::advance();
65+
});
66+
67+
if (result.wasInterrupted()) {
68+
return foundType;
69+
}
70+
71+
module.walk([&](func::FuncOp func) {
72+
if (isClientHelper(func.getOperation())) {
73+
for (Type ty : func.getArgumentTypes())
74+
if (isa<LWESecretKeyType>(ty)) {
75+
foundType = ty;
76+
return WalkResult::interrupt();
77+
}
78+
}
79+
return WalkResult::advance();
80+
});
81+
82+
return foundType;
83+
}
84+
85+
uint64_t hashTypeStringFormat(Type type) {
86+
SmallString<16> typeString;
87+
llvm::raw_svector_ostream typeOS(typeString);
88+
typeOS << type;
89+
return llvm::hash_value(typeString.str());
90+
}
91+
92+
// Creates a function that returns a single ciphertext encrypting zero. A new
93+
// function is created for each ciphertext type returned by originalOp, and
94+
// otherwise duplicate functions are looked up by symbol name. Created functions
95+
// are tagged with client.enc_zero_func.
96+
func::FuncOp getOrCreateEncryptionOfZerosFunc(func::FuncOp parentFunc,
97+
TrivialEncryptOp originalOp,
98+
ModuleOp module) {
99+
LWEPlaintextType plaintextType = cast<LWEPlaintextType>(
100+
getElementTypeOrSelf(originalOp.getInput().getType()));
101+
LWECiphertextType ciphertextType = cast<LWECiphertextType>(
102+
getElementTypeOrSelf(originalOp.getResult().getType()));
103+
SmallString<16> buffer;
104+
llvm::raw_svector_ostream os(buffer);
105+
os << parentFunc.getSymName() << "__encrypt__zero__"
106+
<< llvm::format_hex_no_prefix(hashTypeStringFormat(ciphertextType), 16);
107+
SmallString<16> buffer2;
108+
std::string encFuncName = std::string(sanitizeIdentifier(buffer, buffer2));
109+
110+
if (auto existingFunc = module.lookupSymbol<func::FuncOp>(encFuncName)) {
111+
return existingFunc;
112+
}
113+
114+
ImplicitLocOpBuilder builder =
115+
ImplicitLocOpBuilder::atBlockEnd(module.getLoc(), module.getBody());
116+
builder.setInsertionPointAfter(parentFunc);
117+
118+
Type keyTy = findEncryptionKeyTypeFromHelper(parentFunc, module);
119+
if (!keyTy) {
120+
keyTy = lwe::LWESecretKeyType::get(
121+
module.getContext(), lwe::KeyAttr::get(module.getContext(), 0),
122+
ciphertextType.getCiphertextSpace().getRing());
123+
}
124+
125+
FunctionType encFuncType =
126+
FunctionType::get(builder.getContext(), {keyTy}, {ciphertextType});
127+
auto encFuncOp = func::FuncOp::create(builder, encFuncName, encFuncType);
128+
129+
encFuncOp->setAttr(kClientEncZeroFuncAttrName, builder.getUnitAttr());
130+
Block* entryBlock = encFuncOp.addEntryBlock();
131+
builder.setInsertionPointToEnd(entryBlock);
132+
133+
int numSlots;
134+
auto numSlotsAttr = dyn_cast_or_null<IntegerAttr>(
135+
module->getAttr(kRequestedSlotCountAttrName));
136+
if (numSlotsAttr) {
137+
numSlots = numSlotsAttr.getInt();
138+
} else {
139+
module->emitWarning()
140+
<< "Encountered module op with no requested_slots "
141+
"attribute; defaulting to polynomial modulus ring "
142+
"degree / 2, which will be off by a factor of 2 for BGV/BFV.\n";
143+
numSlots = plaintextType.getPlaintextSpace()
144+
.getRing()
145+
.getPolynomialModulus()
146+
.getPolynomial()
147+
.getDegree() /
148+
2;
149+
}
150+
151+
Type coeffType =
152+
plaintextType.getPlaintextSpace().getRing().getCoefficientType();
153+
Type cleartextElementType = builder.getIntegerType(
154+
llvm::TypeSwitch<Type, int>(coeffType)
155+
.Case<IntegerType, FloatType>(
156+
[&](auto ty) { return ty.getIntOrFloatBitWidth(); })
157+
.Case<mod_arith::ModArithType>([&](auto ty) {
158+
return ty.getModulus().getType().getIntOrFloatBitWidth();
159+
})
160+
.Default([&](auto ty) {
161+
originalOp->emitOpError()
162+
<< "has unsupported plaintext coefficient type; can't "
163+
"determine what constant type to use for encryption of "
164+
"zero.";
165+
return 0;
166+
}));
167+
168+
RankedTensorType zeroType =
169+
RankedTensorType::get({numSlots}, cleartextElementType);
170+
auto zeroAttr = builder.getZeroAttr(zeroType);
171+
arith::ConstantOp constantOp = arith::ConstantOp::create(builder, zeroAttr);
172+
173+
auto plaintextSpace = plaintextType.getPlaintextSpace();
174+
auto encodeOp = RLWEEncodeOp::create(
175+
builder, plaintextType, constantOp.getResult(),
176+
plaintextSpace.getEncoding(), plaintextSpace.getRing());
177+
auto encrypted = RLWEEncryptOp::create(
178+
builder, ciphertextType, encodeOp.getResult(), encFuncOp.getArgument(0));
179+
180+
for (auto attr : originalOp->getAttrs()) {
181+
if (attr.getName().strref().contains('.')) {
182+
encrypted->setAttr(attr.getName(), attr.getValue());
183+
}
184+
}
185+
186+
func::ReturnOp::create(builder, encrypted.getResult());
187+
return encFuncOp;
188+
}
189+
190+
// Creates a new function arg containing a ciphertext encrypting zero
191+
// with the attribute client.enc_zero_arg
192+
Value getOrCreateNewFuncArg(func::FuncOp func, LWECiphertextType type,
193+
PatternRewriter& rewriter) {
194+
for (unsigned i = 0; i < func.getNumArguments(); ++i) {
195+
if (func.getArgument(i).getType() == type &&
196+
func.getArgAttr(i, kClientEncZeroArgAttrName)) {
197+
return func.getArgument(i);
198+
}
199+
}
200+
auto context = func.getContext();
201+
auto oldType = func.getFunctionType();
202+
SmallVector<Type> newInputs(oldType.getInputs().begin(),
203+
oldType.getInputs().end());
204+
newInputs.push_back(type);
205+
auto newType = FunctionType::get(context, newInputs, oldType.getResults());
206+
func.setType(newType);
207+
208+
auto newArg = func.getBody().addArgument(type, func.getLoc());
209+
func.setArgAttr(newArg.getArgNumber(), kClientEncZeroArgAttrName,
210+
rewriter.getUnitAttr());
211+
return newArg;
212+
}
213+
214+
struct TrivialEncryptionRewritePattern
215+
: public OpRewritePattern<TrivialEncryptOp> {
216+
using OpRewritePattern<TrivialEncryptOp>::OpRewritePattern;
217+
218+
LogicalResult matchAndRewrite(TrivialEncryptOp op,
219+
PatternRewriter& rewriter) const override {
220+
auto func = op->getParentOfType<FuncOp>();
221+
auto module = func->getParentOfType<ModuleOp>();
222+
getOrCreateEncryptionOfZerosFunc(func, op, module);
223+
Type resultType = op.getResult().getType();
224+
LWECiphertextType ctTy =
225+
cast<LWECiphertextType>(getElementTypeOrSelf(resultType));
226+
Value newFuncArg = getOrCreateNewFuncArg(func, ctTy, rewriter);
227+
228+
// The newFuncArg is a single ciphertext, so we may need to splat it
229+
// into a tensor of the appropriate shape
230+
Value operand = newFuncArg;
231+
if (isa<ShapedType>(op.getResult().getType())) {
232+
operand =
233+
tensor::SplatOp::create(rewriter, op.getLoc(), resultType, operand);
234+
}
235+
auto newAddOp =
236+
RAddPlainOp::create(rewriter, op.getLoc(), operand, op.getInput());
237+
rewriter.replaceOp(op, newAddOp);
238+
return success();
239+
}
240+
};
241+
242+
struct ImplementTrivialEncryptionAsAddition
243+
: impl::ImplementTrivialEncryptionAsAdditionBase<
244+
ImplementTrivialEncryptionAsAddition> {
245+
using ImplementTrivialEncryptionAsAdditionBase::
246+
ImplementTrivialEncryptionAsAdditionBase;
247+
248+
void runOnOperation() override {
249+
MLIRContext* context = &getContext();
250+
RewritePatternSet patterns(context);
251+
patterns.add<TrivialEncryptionRewritePattern>(context);
252+
walkAndApplyPatterns(getOperation(), std::move(patterns));
253+
}
254+
};
255+
256+
} // namespace lwe
257+
} // namespace heir
258+
} // namespace mlir
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#ifndef LIB_DIALECT_LWE_TRANSFORMS_IMPLEMENTTRIVIALENCRYPTIONASADDITION_H_
2+
#define LIB_DIALECT_LWE_TRANSFORMS_IMPLEMENTTRIVIALENCRYPTIONASADDITION_H_
3+
4+
// IWYU pragma: begin_keep
5+
#include "mlir/include/mlir/Pass/Pass.h" // from @llvm-project
6+
// IWYU pragma: end_keep
7+
8+
namespace mlir {
9+
namespace heir {
10+
namespace lwe {
11+
12+
#define GEN_PASS_DECL_IMPLEMENTTRIVIALENCRYPTIONASADDITION
13+
#include "lib/Dialect/LWE/Transforms/Passes.h.inc"
14+
15+
} // namespace lwe
16+
} // namespace heir
17+
} // namespace mlir
18+
19+
#endif // LIB_DIALECT_LWE_TRANSFORMS_IMPLEMENTTRIVIALENCRYPTIONASADDITION_H_

0 commit comments

Comments
 (0)