This commit is contained in:
Marcel Keller
2022-08-16 17:29:17 +10:00
11 changed files with 253 additions and 30 deletions

View File

@@ -176,6 +176,12 @@ class Compiler:
"using direct conversion if supported "
"(number of parties as argument)",
)
parser.add_option(
"--invperm",
action="store_true",
dest="invperm",
help="speedup inverse permutation (only use in two-party, "
"semi-honest environment)")
parser.add_option(
"-C",
"--CISC",

View File

@@ -2501,6 +2501,26 @@ class delshuffle(base.Instruction):
code = base.opcodes['DELSHUFFLE']
arg_format = ['ci']
class inverse_permutation(base.VectorInstruction, shuffle_base):
""" Calculate the inverse permutation of a secret permutation.
:param: destination (sint)
:param: source (sint)
"""
__slots__ = []
code = base.opcodes['INVPERM']
arg_format = ['sw', 's']
def __init__(self, *args, **kwargs):
super(inverse_permutation, self).__init__(*args, **kwargs)
assert len(args[0]) == len(args[1])
def add_usage(self, req_node):
self.add_gen_usage(req_node, len(self.args[0]))
self.add_apply_usage(req_node, len(self.args[0]), 1)
class check(base.Instruction):
"""
Force MAC check in current thread and all idle thread if current

View File

@@ -111,6 +111,7 @@ opcodes = dict(
GENSECSHUFFLE = 0xFB,
APPLYSHUFFLE = 0xFC,
DELSHUFFLE = 0xFD,
INVPERM = 0xFE,
# Data access
TRIPLE = 0x50,
BIT = 0x51,

View File

@@ -50,6 +50,7 @@ class defaults:
budget = 100000
mixed = False
edabit = False
invperm = False
split = None
cisc = False
comparison = None
@@ -159,6 +160,8 @@ class Program(object):
self.use_dabit = options.mixed
""" Setting whether to use daBits for non-linear functionality. """
self._edabit = options.edabit
""" Whether to use the low-level INVPERM instruction (only implemented with the assumption of a semi-honest two-party environment)"""
self._invperm = options.invperm
self._split = False
if options.split:
self.use_split(int(options.split))
@@ -534,6 +537,20 @@ class Program(object):
else:
self._edabit = change
def use_invperm(self, change=None):
""" Set whether to use the low-level INVPERM instruction to inverse a permutation (see sint.inverse_permutation). The INVPERM instruction assumes a semi-honest two-party environment. If false, a general protocol implemented in the high-level language is used.
:param change: change setting if not :py:obj:`None`
:returns: setting if :py:obj:`change` is :py:obj:`None`
"""
if change is None:
if not self._invperm:
self.relevant_opts.add('invperm')
return self._invperm
else:
self._invperm = change
def use_edabit_for(self, *args):
return True
@@ -594,6 +611,8 @@ class Program(object):
self.always_raw(True)
if "edabit" in self.args:
self.use_edabit(True)
if "invperm" in self.args:
self.use_invperm(True)
if "linear_rounds" in self.args:
self.linear_rounds(True)

View File

@@ -2776,6 +2776,23 @@ class sint(_secret, _int):
applyshuffle(res, self, unit_size, shuffle, reverse)
return res
def inverse_permutation(self):
if program.use_invperm():
# If enabled, we use the low-level INVPERM instruction.
# This instruction has only been implemented for a semi-honest two-party environement.
res = sint(size=self.size)
inverse_permutation(res, self)
else:
shuffle = sint.get_secure_shuffle(len(self))
shuffled = self.secure_permute(shuffle).reveal()
idx = Array.create_from(shuffled)
res = Array.create_from(sint(regint.inc(len(self))))
res.secure_permute(shuffle, reverse=False)
res.assign_slice_vector(idx, res.get_vector())
library.break_point()
res = res.get_vector()
return res
class sintbit(sint):
""" :py:class:`sint` holding a bit, supporting binary operations
(``&, |, ^``). """

View File

@@ -113,6 +113,7 @@ enum
GENSECSHUFFLE = 0xFB,
APPLYSHUFFLE = 0xFC,
DELSHUFFLE = 0xFD,
INVPERM = 0xFE,
// Data access
TRIPLE = 0x50,
BIT = 0x51,

View File

@@ -283,6 +283,10 @@ void BaseInstruction::parse_operands(istream& s, int pos, int file_pos)
n = get_int(s);
get_vector(2, start, s);
break;
// instructions with 2 register operands
case INVPERM:
get_vector(2, start, s);
break;
// open instructions + read/write instructions with variable length args
case OPEN:
case GOPEN:
@@ -1076,6 +1080,9 @@ inline void Instruction::execute(Processor<sint, sgf2n>& Proc) const
case DELSHUFFLE:
Proc.Procp.delete_shuffle(Proc.read_Ci(r[0]));
return;
case INVPERM:
Proc.Procp.inverse_permutation(*this);
return;
case CHECK:
{
CheckJob job;

View File

@@ -77,6 +77,7 @@ public:
size_t generate_secure_shuffle(const Instruction& instruction);
void apply_shuffle(const Instruction& instruction, int handle);
void delete_shuffle(int handle);
void inverse_permutation(const Instruction& instruction);
void input_personal(const vector<int>& args);
void send_personal(const vector<int>& args);
@@ -101,6 +102,8 @@ public:
{
return C[i];
}
void inverse_permutation(const Instruction &instruction, int handle);
};
class ArithmeticProcessor : public ProcessorBase

View File

@@ -668,6 +668,12 @@ void SubProcessor<T>::delete_shuffle(int handle)
shuffler.del(handle);
}
template<class T>
void SubProcessor<T>::inverse_permutation(const Instruction& instruction) {
shuffler.inverse_permutation(S, instruction.get_size(), instruction.get_start()[0],
instruction.get_start()[1]);
}
template<class T>
void SubProcessor<T>::input_personal(const vector<int>& args)
{
@@ -686,6 +692,16 @@ void SubProcessor<T>::input_personal(const vector<int>& args)
S[args[i + 2] + j] = input.finalize(args[i + 1]);
}
/**
*
* @tparam T
* @param args Args contains four arguments
* a[0] = the size of the input (and output) vector
* a[1] = the player to which to reveal the output
* a[2] = the memory address of the input vector (sint) (i.e. the value to reveal)
* a[3] = the memory address of the output vector (cint) (i.e. the register to store the revealed value)
* // TODO: When would there be multiple sets of arguments? (for ... i < args.size(); i += 4 ... )
*/
template<class T>
void SubProcessor<T>::private_output(const vector<int>& args)
{

View File

@@ -24,8 +24,28 @@ class SecureShuffle
size_t n_shuffle;
bool exact;
/**
* Generates and returns a newly generated random permutation. This permutation is generated locally.
*
* @param n The size of the permutation to generate.
* @return A vector representing a permutation, a shuffled array of integers 0 through n-1.
*/
vector<int> generate_random_permutation(int n);
/**
* Configure a shared waksman network from a permutation known only to config_player.
* Note that although the configuration bits of the waksman network are secret shared,
* the player that generated the permutation (config_player) knows the value of these bits.
*
* A permutation is a mapping represented as a vector.
* Each item in the vector represents the output of mapping(i) where i is the index of that item.
* e.g. [2, 4, 0, 3, 1] -> perm(1) = 4
*
* @param config_player The player tasked with generating the random permutation from which to configure the waksman network.
* @param n_shuffle The size of the permutation to generate.
*/
void configure(int config_player, vector<int>* perm, int n);
void player_round(int config_player);
void generate(int config_player, int n_shuffle);
void waksman(vector<T>& a, int depth, int start);
void cond_swap(T& x, T& y, const T& b);
@@ -44,9 +64,37 @@ public:
int generate(int n_shuffle);
/**
*
* @param a The vector of registers representing the stack // TODO: Is this correct?
* @param n The size of the input vector to shuffle
* @param unit_size Determines how many vector items constitute a single block with regards to permutation:
* i.e. input vector [1,2,3,4] with <code>unit_size=2</code> under permutation map [1,0]
* would result in [3,4,1,2]
* @param output_base The starting address of the output vector (i.e. the location to write the inverted permutation to)
* @param input_base The starting address of the input vector (i.e. the location from which to read the permutation)
* @param handle The integer identifying the preconfigured waksman network (shuffle) to use. Such a handle can be obtained from calling
* @param reverse Boolean indicating whether to apply the inverse of the permutation
* @see SecureShuffle::generate for obtaining a shuffle handle
*/
void apply(vector<T>& a, size_t n, int unit_size, size_t output_base,
size_t input_base, int handle, bool reverse);
/**
* Calculate the secret inverse permutation of stack given secret permutation.
*
* This method is given in [1], based on stack technique in [2]. It is used in the Compiler (high-level) implementation of Square-Root ORAM.
*
* [1] Samee Zahur, Xiao Wang, Mariana Raykova, Adrià Gascón, Jack Doerner, David Evans, and Jonathan Katz. 2016. Revisiting Square Root ORAM: Efficient Random Access in Multi-Party Computation. In IEEE S&P.
* [2] Ivan Damgård, Matthias Fitzi, Eike Kiltz, Jesper Buus Nielsen, and Tomas Toft. Unconditionally Secure Constant-rounds Multi-Party Computation for Equality, Comparison, Bits and Exponentiation. In Theory of Cryptography, 2006.
*
* @param stack The vector or registers representing the stack (?)
* @param n The size of the input vector for which to calculate the inverse permutation
* @param output_base The starting address of the output vector (i.e. the location to write the inverted permutation to)
* @param input_base The starting address of the input vector (i.e. the location from which to read the permutation)
*/
void inverse_permutation(vector<T>& stack, size_t n, size_t output_base, size_t input_base);
void del(int handle);
};

View File

@@ -58,6 +58,82 @@ void SecureShuffle<T>::apply(vector<T>& a, size_t n, int unit_size, size_t outpu
post(a, n, output_base);
}
template<class T>
void SecureShuffle<T>::inverse_permutation(vector<T> &stack, size_t n, size_t output_base,
size_t input_base) {
int alice = 0;
int bob = 1;
auto &P = proc.P;
auto &input = proc.input;
// This method only supports two players
assert(proc.protocol.get_relevant_players().size() == 2);
// The current implementation assumes a semi-honest environment
assert(!T::malicious);
// We are dealing directly with permutations, so the unit_size will always be 1.
this->unit_size = 1;
// We need to account for sizes which are not a power of 2
size_t n_pow2 = (1u << int(ceil(log2(n))));
// Copy over the input registers
pre(stack, n, input_base);
// Alice generates stack local permutation and shares the waksman configuration bits secretly to Bob.
vector<int> perm_alice(n_pow2);
if (P.my_num() == alice)
perm_alice = generate_random_permutation(n);
configure(alice, &perm_alice, n);
// Apply perm_alice to perm_alice to get perm_bob,
// stack permutation that we can reveal to Bob without Bob learning anything about perm_alice (since it is masked by perm_a)
iter_waksman(true);
// Store perm_bob at stack[output_base]
post(stack, n, output_base);
// Reveal permutation perm_bob = perm_a * perm_alice
// Since this permutation is masked by perm_a, Bob learns nothing about perm
vector<int> perm_bob(n_pow2);
typename T::PrivateOutput output(proc);
for (size_t i = 0; i < n; i++)
output.prepare_sending(stack[output_base + i], bob);
output.exchange();
for (size_t i = 0; i < n; i++) {
// TODO: Is there a better way to convert a T::clear to int?
bigint val;
output.finalize(bob).to(val);
perm_bob[i] = (int) val.get_si();
}
vector<int> perm_bob_inv(n_pow2);
if (P.my_num() == bob) {
for (int i = 0; i < (int) n; i++)
perm_bob_inv[perm_bob[i]] = i;
// Pad the permutation to n_pow2
// Required when using waksman networks
for (int i = (int) n; i < (int) n_pow2; i++)
perm_bob_inv[i] = i;
}
// Alice secret shares perm_a with bob
// perm_a is stored in the stack at output_base
input.reset_all(P);
if (P.my_num() == alice) {
for (int i = 0; i < (int) n; i++)
input.add_mine(perm_alice[i]);
}
input.exchange();
for (int i = 0; i < (int) n; i++)
stack[output_base + i] = input.finalize(alice);
// The two parties now jointly compute perm_a * perm_bob_inv to obtain perm_inv
pre(stack, n, output_base);
configure(bob, &perm_bob_inv, n);
iter_waksman(true);
// perm_inv is written back to stack[output_base]
post(stack, n, output_base);
}
template<class T>
void SecureShuffle<T>::del(int handle)
{
@@ -129,9 +205,27 @@ void SecureShuffle<T>::post(vector<T>& a, size_t n, size_t output_base)
}
template<class T>
void SecureShuffle<T>::player_round(int config_player)
{
generate(config_player, n_shuffle);
vector<int> SecureShuffle<T>::generate_random_permutation(int n) {
vector<int> perm;
int n_pow2 = 1 << int(ceil(log2(n)));
int shuffle_size = n;
for (int j = 0; j < n_pow2; j++)
perm.push_back(j);
SeededPRNG G;
for (int i = 0; i < shuffle_size; i++) {
int j = G.get_uint(shuffle_size - i);
swap(perm[i], perm[i + j]);
}
return perm;
}
template<class T>
void SecureShuffle<T>::player_round(int config_player) {
vector<int> random_perm(n_shuffle);
if (proc.P.my_num() == config_player)
random_perm = generate_random_permutation(n_shuffle);
configure(config_player, &random_perm, n_shuffle);
iter_waksman();
}
@@ -142,9 +236,12 @@ int SecureShuffle<T>::generate(int n_shuffle)
shuffles.push_back({});
auto& shuffle = shuffles.back();
for (auto i : proc.protocol.get_relevant_players())
{
generate(i, n_shuffle);
for (auto i: proc.protocol.get_relevant_players()) {
vector<int> perm;
if (proc.P.my_num() == i)
perm = generate_random_permutation(n_shuffle);
configure(i, &perm, n_shuffle);
shuffle.push_back(config);
}
@@ -152,39 +249,27 @@ int SecureShuffle<T>::generate(int n_shuffle)
}
template<class T>
void SecureShuffle<T>::generate(int config_player, int n)
{
auto& P = proc.P;
auto& input = proc.input;
void SecureShuffle<T>::configure(int config_player, vector<int> *perm, int n) {
auto &P = proc.P;
auto &input = proc.input;
input.reset_all(P);
int n_pow2 = 1 << int(ceil(log2(n)));
Waksman waksman(n_pow2);
if (P.my_num() == config_player)
{
vector<int> perm;
int shuffle_size = n;
for (int j = 0; j < n_pow2; j++)
perm.push_back(j);
SeededPRNG G;
for (int i = 0; i < shuffle_size; i++)
{
int j = G.get_uint(shuffle_size - i);
swap(perm[i], perm[i + j]);
}
auto config_bits = waksman.configure(perm);
for (size_t i = 0; i < config_bits.size(); i++)
{
auto& x = config_bits[i];
// The player specified by config_player configures the shared waksman network
// using its personal permutation
if (P.my_num() == config_player) {
auto config_bits = waksman.configure(*perm);
for (size_t i = 0; i < config_bits.size(); i++) {
auto &x = config_bits[i];
for (size_t j = 0; j < x.size(); j++)
if (waksman.matters(i, j))
input.add_mine(int(x[j]));
else
assert(x[j] == 0);
}
}
else
// The other player waits for its share of the configured waksman network
} else
for (size_t i = 0; i < waksman.n_bits(); i++)
input.add_other(config_player);