From 5dd73fc912cd0eccebfe74179b6fc8109426b7bd Mon Sep 17 00:00:00 2001 From: christopherngutierrez Date: Fri, 7 Mar 2025 11:31:57 -0800 Subject: [PATCH 1/7] modified Parser class to produce Polys with rns=current_rns rather than max_rns, tested for add, mul, ntt/intt --- kerngen/high_parser/parser.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kerngen/high_parser/parser.py b/kerngen/high_parser/parser.py index 32ad2583..0df064df 100644 --- a/kerngen/high_parser/parser.py +++ b/kerngen/high_parser/parser.py @@ -124,7 +124,9 @@ def _delegate(self, command_str: str, context_seen: list[Context], symbols_map): # Populate the polys map data = Data.from_string(rest) symbols_map[data.name] = Polys( - name=data.name, parts=data.parts, rns=context.max_rns + name=data.name, + parts=data.parts, + rns=context.current_rns, ) return data case _: From 84ada8a1f6d2d270d915170d4485e2308d1f0e01 Mon Sep 17 00:00:00 2001 From: christopherngutierrez Date: Wed, 12 Mar 2025 11:28:40 -0700 Subject: [PATCH 2/7] added rns filter to support dirty ciphertext relin/rotate. Tested and working --- kerngen/pisa_generators/basic.py | 13 ++++++++++++- kerngen/pisa_generators/mod.py | 1 - kerngen/pisa_generators/relin.py | 30 +++++++++++++++++------------- kerngen/pisa_generators/rotate.py | 30 +++++++++++++++++------------- 4 files changed, 46 insertions(+), 28 deletions(-) diff --git a/kerngen/pisa_generators/basic.py b/kerngen/pisa_generators/basic.py index 34399413..214d2ba6 100644 --- a/kerngen/pisa_generators/basic.py +++ b/kerngen/pisa_generators/basic.py @@ -9,7 +9,7 @@ from string import ascii_letters import high_parser.pisa_operations as pisa_op -from high_parser.pisa_operations import PIsaOp +from high_parser.pisa_operations import PIsaOp, Comment from high_parser import ( Immediate, HighOp, @@ -45,6 +45,17 @@ def helper(op) -> list[PIsaOp]: return [pisa_op for pisa_ops in ops_pisa_clusters for pisa_op in pisa_ops] +def filter_rns(current_rns: int, max_rns: int, pisa_list: list[PIsaOp]): + """Filter out spent RNS from PIsaOps list""" + remove_pisa_q = range(current_rns, max_rns) + return list( + filter( + lambda pisa: (isinstance(pisa, Comment) or pisa.q not in remove_pisa_q), + pisa_list, + ) + ) + + @dataclass class CartesianOp(HighOp): """Class representing the high-level cartesian operation""" diff --git a/kerngen/pisa_generators/mod.py b/kerngen/pisa_generators/mod.py index 6d10e255..1c5d1990 100644 --- a/kerngen/pisa_generators/mod.py +++ b/kerngen/pisa_generators/mod.py @@ -39,7 +39,6 @@ def to_pisa(self) -> list[PIsaOp]: """Return the p-isa code to perform an mod switch down""" # Immediates last_q = self.input0.rns - 1 - self.input0.start_rns = (self.context.key_rns - 1) - self.context.current_rns it = Immediate(name="it" + self.var_suffix) t = Immediate(name="t", rns=last_q) diff --git a/kerngen/pisa_generators/relin.py b/kerngen/pisa_generators/relin.py index 0cae1398..665f30a3 100644 --- a/kerngen/pisa_generators/relin.py +++ b/kerngen/pisa_generators/relin.py @@ -5,7 +5,8 @@ from dataclasses import dataclass from high_parser.pisa_operations import PIsaOp, Comment from high_parser import KernelContext, HighOp, KeyPolys, Polys -from .basic import Add, KeyMul, mixed_to_pisa_ops, extract_last_part_polys +from .basic import Add, KeyMul, mixed_to_pisa_ops, extract_last_part_polys, filter_rns + from .mod import Mod from .decomp import DigitDecompExtend @@ -24,7 +25,6 @@ def to_pisa(self) -> list[PIsaOp]: supports number of digits equal to the RNS size""" self.output.parts = 2 self.input0.parts = 3 - relin_key = KeyPolys( "rlk", parts=2, rns=self.context.key_rns, digits=self.input0.rns ) @@ -41,15 +41,19 @@ def to_pisa(self) -> list[PIsaOp]: add_original = Polys.from_polys(mul_by_rlk_modded_down) add_original.name = self.input0.name - return mixed_to_pisa_ops( - Comment("Start of relin kernel"), - Comment("Digit decomposition and extend base from Q to PQ"), - DigitDecompExtend(self.context, last_coeff, input_last_part), - Comment("Multiply by relin key"), - KeyMul(self.context, mul_by_rlk, upto_last_coeffs, relin_key, 2), - Comment("Mod switch down to Q"), - Mod(self.context, mul_by_rlk_modded_down, mul_by_rlk, Mod.MOD_P), - Comment("Add to original poly"), - Add(self.context, self.output, mul_by_rlk_modded_down, add_original), - Comment("End of relin kernel"), + return filter_rns( + self.context.current_rns, + self.context.max_rns, + mixed_to_pisa_ops( + Comment("Start of relin kernel"), + Comment("Digit decomposition and extend base from Q to PQ"), + DigitDecompExtend(self.context, last_coeff, input_last_part), + Comment("Multiply by relin key"), + KeyMul(self.context, mul_by_rlk, upto_last_coeffs, relin_key, 2), + Comment("Mod switch down to Q"), + Mod(self.context, mul_by_rlk_modded_down, mul_by_rlk, Mod.MOD_P), + Comment("Add to original poly"), + Add(self.context, self.output, mul_by_rlk_modded_down, add_original), + Comment("End of relin kernel"), + ), ) diff --git a/kerngen/pisa_generators/rotate.py b/kerngen/pisa_generators/rotate.py index 078f7fd0..b7648466 100644 --- a/kerngen/pisa_generators/rotate.py +++ b/kerngen/pisa_generators/rotate.py @@ -8,7 +8,7 @@ from high_parser.pisa_operations import PIsaOp, Comment from high_parser import KernelContext, HighOp, Polys, KeyPolys -from .basic import Add, KeyMul, mixed_to_pisa_ops, extract_last_part_polys +from .basic import Add, KeyMul, mixed_to_pisa_ops, extract_last_part_polys, filter_rns from .decomp import DigitDecompExtend from .mod import Mod from .ntt import INTT, NTT @@ -54,16 +54,20 @@ def to_pisa(self) -> list[PIsaOp]: first_part_rlk.parts = 1 first_part_rlk.start_parts = 0 - return mixed_to_pisa_ops( - Comment("Start of rotate kernel"), - Comment("Digit Decomp"), - DigitDecompExtend(self.context, last_coeff, input_last_part), - Comment("Multiply by rotate key"), - KeyMul(self.context, mul_by_rlk, upto_last_coeffs, relin_key, 1), - Comment("Mod switch down to Q"), - Mod(self.context, mul_by_rlk_modded_down, mul_by_rlk, Mod.MOD_P), - INTT(self.context, cd, start_input), - NTT(self.context, cd, cd), - Add(self.context, self.output, cd, first_part_rlk), - Comment("End of rotate kernel"), + return filter_rns( + self.context.current_rns, + self.context.max_rns, + mixed_to_pisa_ops( + Comment("Start of rotate kernel"), + Comment("Digit Decomp"), + DigitDecompExtend(self.context, last_coeff, input_last_part), + Comment("Multiply by rotate key"), + KeyMul(self.context, mul_by_rlk, upto_last_coeffs, relin_key, 1), + Comment("Mod switch down to Q"), + Mod(self.context, mul_by_rlk_modded_down, mul_by_rlk, Mod.MOD_P), + INTT(self.context, cd, start_input), + NTT(self.context, cd, cd), + Add(self.context, self.output, cd, first_part_rlk), + Comment("End of rotate kernel"), + ), ) From 971973e84482977c65cb60bf75167c6646b5668c Mon Sep 17 00:00:00 2001 From: christopherngutierrez Date: Thu, 13 Mar 2025 15:18:54 -0700 Subject: [PATCH 3/7] Fixed issue with dirty ciphertext. RNS indexes were not considering current_rns and krns throughout multiple places in the code. --- kerngen/pisa_generators/basic.py | 24 +++++++++++++++++++----- kerngen/pisa_generators/decomp.py | 27 +++++++++++++++++++++++++-- kerngen/pisa_generators/mod.py | 4 +++- kerngen/pisa_generators/relin.py | 29 ++++++++++++----------------- kerngen/pisa_generators/rotate.py | 30 +++++++++++++----------------- 5 files changed, 72 insertions(+), 42 deletions(-) diff --git a/kerngen/pisa_generators/basic.py b/kerngen/pisa_generators/basic.py index 214d2ba6..92f282f7 100644 --- a/kerngen/pisa_generators/basic.py +++ b/kerngen/pisa_generators/basic.py @@ -284,7 +284,21 @@ def get_pisa_op(num): ) for part, q, unit in it.product( range(self.input1.start_parts, self.input1.parts), - range(self.input0.start_rns, self.input0.rns), + range(self.context.current_rns), + range(self.context.units), + ) + ) + ls.extend( + op( + self.context.label, + self.output(part, q, unit), + input0_tmp(self.input0_fixed_part, q, unit), + self.input1(digit, part, q, unit), + q, + ) + for part, q, unit in it.product( + range(self.input1.start_parts, self.input1.parts), + range(self.context.max_rns, self.context.key_rns), range(self.context.units), ) ) @@ -307,11 +321,11 @@ def extract_last_part_polys(input0: Polys, rns: int) -> Tuple[Polys, Polys, Poly return input_last_part, last_coeff, upto_last_coeffs -def split_last_rns_polys(input0: Polys) -> Tuple[Polys, Polys]: +def split_last_rns_polys(input0: Polys, current_rns) -> Tuple[Polys, Polys]: """Split and extract last RNS of input0""" - return Polys.from_polys(input0, mode="last_rns"), Polys.from_polys( - input0, mode="drop_last_rns" - ) + out = Polys.from_polys(input0) + out.rns = current_rns + return Polys.from_polys(input0, mode="last_rns"), out def duplicate_polys(input0: Polys, name: str) -> Polys: diff --git a/kerngen/pisa_generators/decomp.py b/kerngen/pisa_generators/decomp.py index f8262489..0ebf0dd1 100644 --- a/kerngen/pisa_generators/decomp.py +++ b/kerngen/pisa_generators/decomp.py @@ -44,13 +44,36 @@ def to_pisa(self) -> list[PIsaOp]: ) for part, pq, unit in it.product( range(self.input0.start_parts, self.input0.parts), - range(self.context.key_rns), + range(self.context.current_rns), range(self.context.units), ) ) + + ls.extend( + pisa_op.Muli( + self.context.label, + self.output(part, pq, unit), + rns_poly(part, input_rns_index, unit), + r2(part, pq, unit), + pq, + ) + for part, pq, unit in it.product( + range(self.input0.start_parts, self.input0.parts), + range(self.context.max_rns, self.context.key_rns), + range(self.context.units), + ) + ) + output_tmp = Polys.from_polys(self.output) output_tmp.name += "_" + ascii_letters[input_rns_index] - ls.extend(NTT(self.context, output_tmp, self.output).to_pisa()) + output_split = Polys.from_polys(self.output) + output_split.rns = self.context.current_rns + ls.extend(NTT(self.context, output_tmp, output_split).to_pisa()) + + output_split = Polys.from_polys(self.output) + output_split.rns = self.context.key_rns + output_split.start_rns = self.context.max_rns + ls.extend(NTT(self.context, output_tmp, output_split).to_pisa()) return mixed_to_pisa_ops( INTT(self.context, rns_poly, self.input0), diff --git a/kerngen/pisa_generators/mod.py b/kerngen/pisa_generators/mod.py index 1c5d1990..77b97d35 100644 --- a/kerngen/pisa_generators/mod.py +++ b/kerngen/pisa_generators/mod.py @@ -47,7 +47,9 @@ def to_pisa(self) -> list[PIsaOp]: ) # Drop down input rns - input_last_rns, input_remaining_rns = split_last_rns_polys(self.input0) + input_last_rns, input_remaining_rns = split_last_rns_polys( + self.input0, self.context.current_rns + ) # Temp. temp_input_last_rns = duplicate_polys(input_last_rns, "y") diff --git a/kerngen/pisa_generators/relin.py b/kerngen/pisa_generators/relin.py index 665f30a3..9f390172 100644 --- a/kerngen/pisa_generators/relin.py +++ b/kerngen/pisa_generators/relin.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from high_parser.pisa_operations import PIsaOp, Comment from high_parser import KernelContext, HighOp, KeyPolys, Polys -from .basic import Add, KeyMul, mixed_to_pisa_ops, extract_last_part_polys, filter_rns +from .basic import Add, KeyMul, mixed_to_pisa_ops, extract_last_part_polys from .mod import Mod from .decomp import DigitDecompExtend @@ -40,20 +40,15 @@ def to_pisa(self) -> list[PIsaOp]: add_original = Polys.from_polys(mul_by_rlk_modded_down) add_original.name = self.input0.name - - return filter_rns( - self.context.current_rns, - self.context.max_rns, - mixed_to_pisa_ops( - Comment("Start of relin kernel"), - Comment("Digit decomposition and extend base from Q to PQ"), - DigitDecompExtend(self.context, last_coeff, input_last_part), - Comment("Multiply by relin key"), - KeyMul(self.context, mul_by_rlk, upto_last_coeffs, relin_key, 2), - Comment("Mod switch down to Q"), - Mod(self.context, mul_by_rlk_modded_down, mul_by_rlk, Mod.MOD_P), - Comment("Add to original poly"), - Add(self.context, self.output, mul_by_rlk_modded_down, add_original), - Comment("End of relin kernel"), - ), + return mixed_to_pisa_ops( + Comment("Start of relin kernel"), + Comment("Digit decomposition and extend base from Q to PQ"), + DigitDecompExtend(self.context, last_coeff, input_last_part), + Comment("Multiply by relin key"), + KeyMul(self.context, mul_by_rlk, upto_last_coeffs, relin_key, 2), + Comment("Mod switch down to Q"), + Mod(self.context, mul_by_rlk_modded_down, mul_by_rlk, Mod.MOD_P), + Comment("Add to original poly"), + Add(self.context, self.output, mul_by_rlk_modded_down, add_original), + Comment("End of relin kernel"), ) diff --git a/kerngen/pisa_generators/rotate.py b/kerngen/pisa_generators/rotate.py index b7648466..078f7fd0 100644 --- a/kerngen/pisa_generators/rotate.py +++ b/kerngen/pisa_generators/rotate.py @@ -8,7 +8,7 @@ from high_parser.pisa_operations import PIsaOp, Comment from high_parser import KernelContext, HighOp, Polys, KeyPolys -from .basic import Add, KeyMul, mixed_to_pisa_ops, extract_last_part_polys, filter_rns +from .basic import Add, KeyMul, mixed_to_pisa_ops, extract_last_part_polys from .decomp import DigitDecompExtend from .mod import Mod from .ntt import INTT, NTT @@ -54,20 +54,16 @@ def to_pisa(self) -> list[PIsaOp]: first_part_rlk.parts = 1 first_part_rlk.start_parts = 0 - return filter_rns( - self.context.current_rns, - self.context.max_rns, - mixed_to_pisa_ops( - Comment("Start of rotate kernel"), - Comment("Digit Decomp"), - DigitDecompExtend(self.context, last_coeff, input_last_part), - Comment("Multiply by rotate key"), - KeyMul(self.context, mul_by_rlk, upto_last_coeffs, relin_key, 1), - Comment("Mod switch down to Q"), - Mod(self.context, mul_by_rlk_modded_down, mul_by_rlk, Mod.MOD_P), - INTT(self.context, cd, start_input), - NTT(self.context, cd, cd), - Add(self.context, self.output, cd, first_part_rlk), - Comment("End of rotate kernel"), - ), + return mixed_to_pisa_ops( + Comment("Start of rotate kernel"), + Comment("Digit Decomp"), + DigitDecompExtend(self.context, last_coeff, input_last_part), + Comment("Multiply by rotate key"), + KeyMul(self.context, mul_by_rlk, upto_last_coeffs, relin_key, 1), + Comment("Mod switch down to Q"), + Mod(self.context, mul_by_rlk_modded_down, mul_by_rlk, Mod.MOD_P), + INTT(self.context, cd, start_input), + NTT(self.context, cd, cd), + Add(self.context, self.output, cd, first_part_rlk), + Comment("End of rotate kernel"), ) From 065473fce5eb6ff35570dac6e836c7c9570e47c8 Mon Sep 17 00:00:00 2001 From: christopherngutierrez Date: Fri, 14 Mar 2025 11:00:34 -0700 Subject: [PATCH 4/7] Fixed bug for standalone mod. --- kerngen/pisa_generators/basic.py | 5 +++++ kerngen/pisa_generators/mod.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/kerngen/pisa_generators/basic.py b/kerngen/pisa_generators/basic.py index 92f282f7..1e998765 100644 --- a/kerngen/pisa_generators/basic.py +++ b/kerngen/pisa_generators/basic.py @@ -323,6 +323,11 @@ def extract_last_part_polys(input0: Polys, rns: int) -> Tuple[Polys, Polys, Poly def split_last_rns_polys(input0: Polys, current_rns) -> Tuple[Polys, Polys]: """Split and extract last RNS of input0""" + + if input0.rns <= current_rns: + return Polys.from_polys(input0, mode="last_rns"), Polys.from_polys( + input0, mode="drop_last_rns" + ) out = Polys.from_polys(input0) out.rns = current_rns return Polys.from_polys(input0, mode="last_rns"), out diff --git a/kerngen/pisa_generators/mod.py b/kerngen/pisa_generators/mod.py index 77b97d35..dabeecc6 100644 --- a/kerngen/pisa_generators/mod.py +++ b/kerngen/pisa_generators/mod.py @@ -33,7 +33,7 @@ class Mod(HighOp): context: KernelContext output: Polys input0: Polys - var_suffix: str = MOD_QLAST # default to qlast, use mod_q otherwise + var_suffix: str = MOD_QLAST # default to qlast, use mod_p otherwise def to_pisa(self) -> list[PIsaOp]: """Return the p-isa code to perform an mod switch down""" From d61b4df79d4d860171e5757115ef9baf76344c52 Mon Sep 17 00:00:00 2001 From: christopherngutierrez Date: Fri, 14 Mar 2025 11:38:20 -0700 Subject: [PATCH 5/7] Fixed issue with rescale, incorrect iq label --- kerngen/pisa_generators/rescale.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/kerngen/pisa_generators/rescale.py b/kerngen/pisa_generators/rescale.py index 3bfe56b3..5b7441f3 100644 --- a/kerngen/pisa_generators/rescale.py +++ b/kerngen/pisa_generators/rescale.py @@ -28,21 +28,30 @@ class Rescale(HighOp): """Class representing mod down operation""" + MOD_QLAST = "_mod_qLast" context: KernelContext output: Polys input0: Polys + var_suffix: str = MOD_QLAST # default to qlast def to_pisa(self) -> list[PIsaOp]: """Return the p-isa code to perform an mod switch down""" + # Immediates last_q = self.input0.rns - 1 one, r2, iq = common_immediates(r2_rns=last_q, iq_rns=last_q) + one, r2, iq = common_immediates( + r2_rns=last_q, iq_rns=last_q, iq_suffix=self.var_suffix + ) + q_last_half = Polys("qLastHalf", 1, self.input0.rns) q_i_last_half = Polys("qiLastHalf", 1, rns=last_q) # split input - input_last_rns, input_remaining_rns = split_last_rns_polys(self.input0) + input_last_rns, input_remaining_rns = split_last_rns_polys( + self.input0, self.context.current_rns + ) # Create temp vars for input_last/remaining temp_input_last_rns = duplicate_polys(input_last_rns, "y") From 54f40bac2a3628caeed1bd91ab1adf1bcb9a6ec9 Mon Sep 17 00:00:00 2001 From: christopherngutierrez Date: Tue, 18 Mar 2025 11:30:27 -0700 Subject: [PATCH 6/7] removed rns filter, to be considered in the future --- kerngen/pisa_generators/basic.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/kerngen/pisa_generators/basic.py b/kerngen/pisa_generators/basic.py index 1e998765..9151944f 100644 --- a/kerngen/pisa_generators/basic.py +++ b/kerngen/pisa_generators/basic.py @@ -9,7 +9,7 @@ from string import ascii_letters import high_parser.pisa_operations as pisa_op -from high_parser.pisa_operations import PIsaOp, Comment +from high_parser.pisa_operations import PIsaOp from high_parser import ( Immediate, HighOp, @@ -45,17 +45,6 @@ def helper(op) -> list[PIsaOp]: return [pisa_op for pisa_ops in ops_pisa_clusters for pisa_op in pisa_ops] -def filter_rns(current_rns: int, max_rns: int, pisa_list: list[PIsaOp]): - """Filter out spent RNS from PIsaOps list""" - remove_pisa_q = range(current_rns, max_rns) - return list( - filter( - lambda pisa: (isinstance(pisa, Comment) or pisa.q not in remove_pisa_q), - pisa_list, - ) - ) - - @dataclass class CartesianOp(HighOp): """Class representing the high-level cartesian operation""" From 27dbeb0e6e0e8d6d7a4956feed07c35e6f42c2ca Mon Sep 17 00:00:00 2001 From: christopherngutierrez Date: Tue, 18 Mar 2025 11:54:33 -0700 Subject: [PATCH 7/7] Code clean up: added comments, formatting --- kerngen/pisa_generators/basic.py | 12 ++++++++---- kerngen/pisa_generators/decomp.py | 5 ++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/kerngen/pisa_generators/basic.py b/kerngen/pisa_generators/basic.py index 9151944f..08d21341 100644 --- a/kerngen/pisa_generators/basic.py +++ b/kerngen/pisa_generators/basic.py @@ -263,6 +263,8 @@ def get_pisa_op(num): for digit, op in get_pisa_op(self.input1.digits): input0_tmp = Polys.from_polys(self.input0) input0_tmp.name += "_" + ascii_letters[digit] + + # mul/mac for 0-current_rns ls.extend( op( self.context.label, @@ -277,6 +279,7 @@ def get_pisa_op(num): range(self.context.units), ) ) + # mul/mac for max_rns-krns terms ls.extend( op( self.context.label, @@ -312,14 +315,15 @@ def extract_last_part_polys(input0: Polys, rns: int) -> Tuple[Polys, Polys, Poly def split_last_rns_polys(input0: Polys, current_rns) -> Tuple[Polys, Polys]: """Split and extract last RNS of input0""" - if input0.rns <= current_rns: return Polys.from_polys(input0, mode="last_rns"), Polys.from_polys( input0, mode="drop_last_rns" ) - out = Polys.from_polys(input0) - out.rns = current_rns - return Polys.from_polys(input0, mode="last_rns"), out + + # do not include consumed rns + remaining = Polys.from_polys(input0) + remaining.rns = current_rns + return Polys.from_polys(input0, mode="last_rns"), remaining def duplicate_polys(input0: Polys, name: str) -> Polys: diff --git a/kerngen/pisa_generators/decomp.py b/kerngen/pisa_generators/decomp.py index 0ebf0dd1..1149fda5 100644 --- a/kerngen/pisa_generators/decomp.py +++ b/kerngen/pisa_generators/decomp.py @@ -34,6 +34,7 @@ def to_pisa(self) -> list[PIsaOp]: ls: list[pisa_op] = [] for input_rns_index in range(self.input0.start_rns, self.input0.rns): + # muli for 0-current_rns ls.extend( pisa_op.Muli( self.context.label, @@ -48,7 +49,7 @@ def to_pisa(self) -> list[PIsaOp]: range(self.context.units), ) ) - + # muli for krns ls.extend( pisa_op.Muli( self.context.label, @@ -68,11 +69,13 @@ def to_pisa(self) -> list[PIsaOp]: output_tmp.name += "_" + ascii_letters[input_rns_index] output_split = Polys.from_polys(self.output) output_split.rns = self.context.current_rns + # ntt for 0-current_rns ls.extend(NTT(self.context, output_tmp, output_split).to_pisa()) output_split = Polys.from_polys(self.output) output_split.rns = self.context.key_rns output_split.start_rns = self.context.max_rns + # ntt for krns ls.extend(NTT(self.context, output_tmp, output_split).to_pisa()) return mixed_to_pisa_ops(