Em termos de computação, a parte mais cara do SPDZ é qualquer coisa que esteja relacionada com o esquema de criptografia e operações homomórficas. O algoritmo de cifragem não é usado apenas para inputs, mas também pelo provador e pelo verificador na prova de conhecimento zero. Como um protocolo de conhecimento zero não interativo permite que as partes gerem apenas uma prova por entrada, independentemente do número de partes, todas as partes precisam verificar as provas de todas as outras partes, pois assume-se que todas as outras partes sejam corruptas. Com um número crescente de participantes, esse é claramente o bottleneck computacional do protocolo. Uma maneira de evitar isto é somar todas as provas e verificar apenas a soma. No entanto, isso não reduz a comunicação nem as computações, uma vez que cada parte ainda tem que enviar todas as provas para todas as partes e depois somar todas as provas recebidas. Contudo, somar as provas é muito mais barato do que verificá-las individualmente. Para conseguir isso, foi projetada uma nova prova de conhecimento nulo, que escala melhor ao aumentar o número de participantes.
Desenho e Desenvolvimento
Nesta secção apresentamos o problema em concreto, e os passos necessários para resolver o mesmo, desde a implementação e o modo de funcionamento do protocolo, até às soluções encontradas para os problemas que surgiram.
5.1
Superfície de volatilidade
Esta tese foi desenvolvida numa tentativa de dar resposta a um problema, que as instituições bancárias enfrentam, de gestão de risco financeiro.
Para estipular os preços a um valor justo de mercado, os bancos precisam de compartilhar dados de volatilidade para que uma superfície de volatilidade de consenso seja avaliada, calculando- se um valor médio para cada ponto da superfície. Cada banco, no entanto, não quer divulgar os seus dados internos com outros bancos. Para este propósito, algumas empresas de serviços atuam como intermediárias confiáveis e lidam com todos os cálculos, com a imparcialidade e confidencialidade exigidas. O processo funciona da seguinte forma: no final de cada mês, cada banco entrega à empresa de serviços os seus dados de volatilidade interna; a empresa de serviços elimina as contribuições que são outliers, provenientes de bancos que não estão no mercado, para o resto dos dados; uma superfície de volatilidade, que representa a média de todas as contribuições, é computada e é devolvida aos restantes bancos contribuintes juntamente com a sua classificação. Quanto mais próxima estiver a volatilidade interna do banco da superfície de consenso, melhor.
Atualmente, os bancos usam um modelo de confiança com uma Trusted Entity (TE) que recebe os inputs dos bancos e faz os cálculos e devolve os resultados. No entanto, a TE apresenta como principal limitação a necessidade de acesso a todos os dados de input dos bancos, o que acarreta grandes riscos na medida em que a TE pode ser corrompida e haver fuga de informação confidencial importante. Assim, surgiu a necessidade de criar um modelo que não precisasse de uma TE. Os avanços na registados na área do MPC, associados ao aumento dos recursos de computação e comunicação disponíveis, tornaram-na numa ferramenta prática e cada vez mais
passiva de ser aplicada a situações reais, nomeadamente neste contexto.
Com este projeto pretendeu-se resolver o problema de observabilidade do mercado de opções usando um sistema distribuído baseado em MPC, e a criação de um protótipo totalmente operacional que pudesse ser aplicado pelas instituições bancárias interessadas. O projeto começou com uma análise dos requisitos funcionais e de segurança, o que permitiu determinar qual a solução de MPCmais adequada ao cenário existente. Após essa primeira fase, foi projetada uma solução de prova de conceito, que posteriormente foi implementada e testada.
Face a este problema, foi necessária a utilização de um protocolo que fosse seguro contra atacantes ativos e passivos que não pudessem introduzir inputs errados nem que se desviassem do protocolo e que pudessem ser detetados mesmo que controlassem uma maioria dos participantes. Foi, por isso, escolhido o SPDZ, que como foi referido anteriormente na secção 4.1, fornece tanto segurança contra adversários ativos e passivos, mesmo que controlem n − 1 dos participantes.
Idealmente, para garantir que não houve fuga de informação e que todos os participantes seguiram o protocolo corretamente, cada banco deveria ter um servidor SPDZ que fizesse as computações enquanto fornecia os inputs aos restantes servidores das outras instituições. No entanto, mesmo que um banco não seja responsável por fazer computações ele saberá quais os restantes bancos que as estão a realizar e, que se pelo menos um banco for honesto, mesmo que todos os outros sejam corruptos, o protocolo irá ser abortado. Então por cada banco que faça computações, terá que se ligar previamente aos outros para realizar a fase de pré-processamento. Apesar do pré-processamento demorar horas, pode ser executado durante a noite, uma vez que corre sozinho e não precisa de supervisão. Na fase online, cada banco enviará os seus inputs para os responsáveis pela computação, que vão fazer os cálculos e devolver os resultados aos bancos relevantes. Na figura 5.1 pode-se ver um exemplo do sistema em que 3 dos 4 bancos têm um servidor SPDZ.
Figura 5.1: Sistema com 4 bancos e 3 servidores SPDZ.
5.2
Implementação
Nesta secção descreveremos qual o problema em concreto enfrentado pelas instituições bancárias e de que forma o SPDZ pode ser aplicado a dar resposta ao mesmo.
5.2.1 O problema
Os bancos irão fornecer, como input, as suas volatilidades, presentes numa matriz, através dos quais avaliam a sua pontuação em relação aos outros bancos. A matriz representa a volatilidade para diferentes strikes (linhas) e diferentes maturidades (colunas), pode-se ver em baixo um exemplo na figura 5.2.
Figura 5.2: Exemplo de uma matriz de volatilidades.
Cada banco envia a sua própria matriz, cujos valores devem ser semelhantes, mas não idênticos. Na primeira coordenada, começa-se com x = 0, todos os valores provenientes de bancos são
comparados estatisticamente, valores dentro de (1 + x) desvio padrão são mantidos, valores externos levam a exclusão de bancos desta pontuação. A média dos valores dos bancos incluídos é calculada. Os bancos não excluídos recebem o valor médio e o número de bancos mais próximos da média, sendo 0 o mais próximo da média. Nas coordenadas seguintes, x é aumentado e o processo é repetido para os restantes bancos. Foi então desenvolvido um programa em Python que para cada strike:
1. Lê a volatilidade de cada banco de um ficheiro. 2. Calcula a média das volatilidades, x = Pn
i=1 voli.
3. Calcula o desvio padrão, s =
r PN
i=1(voli−x)2
N .
4. Calcula Difi = | voli - x|.
5. Se Difi> (1 + x) · sd, o banco i é descartado.
6. Para os bancos não descartados, calcula a nova média de volatilidades. 7. Agrupa os bancos pelos mais próximos à média.
8. Para cada banco incluído, devolve a média e quantos bancos estão mais próximos da média do que ele.
9. Incrementa o x e repete o processo até não ter mais maturidades para calcular.
while(calculados<nBancos and numMaturities>x): volatilities = []
for i in range(nBancos): volatilities.append(
float(values[i][strikesCalculated+1][x+1].replace(’,’, ’’))) media = calcMedia(volatilities, nBancos-calculados)
sd = calcSD(volatilities, media) tempVols = []
tempDif = 0 contVol = 0
for i in range(nBancos):
if(bankIncluded[i] == 0):
tempDif = abs(volatilities[i]-media)
if(tempDif <= (1+x)*sd):
dif.append(tuple([i,tempDif])) bankIncluded[i] = 0.5
tempVols.append(volatilities[i]) contVol += 1
}
Bloco de Código 5.1: Excerto de um script Python da funcionalidade alvo do programa.
5.2.1.1 Linguagem MAMBA
Multiparty AlgorithMs Basic Argot ou MAMBA é uma linguagem de programação baseada em Python, que o SPDZ utiliza no seu sistema [1]. Os programas para o compilador são então escritos nessa linguagem, que consiste uma forma restrita do python, com uma biblioteca adicional de funções e classes especificas para funcionalidades MPC[1]. O compilador executa então o código Python e as funções da biblioteca MPCtransformam o bytecode do output para ser executado na máquina virtual. Enquanto que o compilador em teoria irá aceitar qualquer código Python como
input, algumas funcionalidades da linguagem podem não funcionar como esperado. Da definição
da funcionalidade e das limitações da linguagem MAMBA com o SPDZ, surgem então algumas questões:
1. As computações no SPDZ são muito mais eficientes com inteiros do que floats. 2. Não se podem usar módulos.
3. O SPDZ não suporta divisões, que são utilizadas para fazer a média.
Como sugerido por Costache et al. [15] o problema dos floats pode ser resolvido transformando- os em inteiros sem perder precisão dada pelos floats. No entanto, podem surgir problemas de overflow, tal que:
voli < log(p) − kappa
Por exemplo, na nossa configuração, sendo p = 128 bits e kappa = 40 bits, para não haver
overflow os inputs têm de ser menores que 88 bits. Se fosse necessário de trabalhar com números
maiores, no entanto, poder-se-ia utilizar um número primo p maior, o que dependia de uma fase de pré-processamento mais demorada.
Para resolver o problema do módulo podemos elevar o resultado ao quadrado o que dará sempre um resultado positivo. Como só usamos a primeira média para calcular o desvio padrão, utilizamos em vez disso, a soma para calcular um pseudo desvio padrão (sdNum) em que não se usa a raiz quadrada. O truque depois consiste em multiplicar por n e elevar ao quadrado todos os outros valores que seriam usados nas comparações. Ou seja, a nova forma de fazer os cálculos consiste em:
1. Calcular a soma das volatilidades. 2. Calcular sdN um = Pn
i=1 vol2
i · n2− (2 · vol2i · soma · n) + soma2.
3. Para cada banco calcular dif = vol · n2− (2 · soma · vol · n) + soma2.
4. Se Dif · n > (1 + x)2 . sdNum, o banco é descartado.
5. Para os bancos não descartados calcular a nova soma de volatilidades.
6. Agrupar os bancos pelos mais próximos à soma e devolver a cada banco quantos estão mais próximos da soma do que ele. Como o SPDZ não suporta valores absolutos elevamos ao quadrado a comparação de (vol · n − soma), ficando:
n2· vol2
b1− (2 · n · volb1· soma) + soma2 <= n2· vol2b2− (2 · n · volb2· soma) + soma2
@for_range(nBanks)
def loop_body(i):
soma.write((bankIncluded[i] * vol[i]) +soma) nIncluidos.write(nIncluidos + bankIncluded[i]) @for_range(nBanks)
def loop_body(i):
sdNum.write(sdNum + ((vol[i]**2 * nIncluidos**2 - (2*vol[i] *soma * nIncluidos) + soma**2)) * bankIncluded[i])
@for_range(nBanks)
def loop_body(i): maisProx[i] = 0
dif[i] = (vol[i]**2 * nIncluidos**2 - (2*soma*vol[i]*nIncluidos) + soma**2)
bankIncludedNesta[i] = myLE(dif[i]*nIncluidos, (1+x)**2 *sdNum) * bankIncluded[i]
nIncluidosNesta[0] += bankIncludedNesta[i]
somaIncluidos.write(somaIncluidos+ (bankIncludedNesta[i]* vol[i])) }
Bloco de Código 5.2: Excerto de código para evitar divisões e raízes quadradas.
5.2.2 Comunicação e criptografia
Os servidores SPDZ utilizam sockets para comunicar entre si e entre os bancos. É utilizada criptografia de chave pública, entre os bancos e o SPDZ, para enviar uma chave simétrica, e posteriormente os valores, de um lado para o outro, sem que ocorra divulgação de informação na presença de um adversário. No bloco de código 5.3pode-se ver um excerto do código do cliente e no excerto de código 5.4do servidor.
// s e t u p s p d z c o n n e c t i o n s
for (int j = 0; j < nparties; j++) {
set_up_client_socket(sockets[j], host_name.c_str(), port_base + j);
send_public_key(sts_key.client_publickey_ints, sockets[j]); send_public_key(client_public_key_ints, sockets[j]);
commseckey[j] = sts_initiator_role(sts_key, sockets, j); }
cout << "Finish setup socket connections to SPDZ engines." << endl; }
def accept_client_input(): " " "
Wait f o r s o c k e t c o n n e c t i o n and r e a d f o r c l i e n t p u b l i c k e y . send s h a r e o f random v a l u e , r e c e i v e i n p u t and deduce s h a r e .
E xp ec t 3 i n p u t s : u n i q u e i d , bonus v a l u e and f l a g t o i n d i c a t e end o f t h i s round . " " " client_socket_id = regint() acceptclientconnection(client_socket_id, PORTNUM) public_signing_key = regint.read_from_socket(client_socket_id, 8) public_key = regint.read_client_public_key(client_socket_id) regint.resp_secure_socket(client_socket_id,*public_signing_key) client_inputs = sint.receive_from_client(6, client_socket_id)
return client_socket_id, client_inputs[0], client_inputs[1],
client_inputs[2], client_inputs[3], client_inputs[4], client_inputs[5] }
Bloco de Código 5.4: Excerto de código para comunicações com criptografia.
5.3
Análise de complexidade
Nesta secção irá ser analisada a complexidade do código da primeira versão do projeto, em Python, e da versão final no SPDZ, emMAMBA (no código do servidor) e C++ (no código do cliente).
5.3.1 Versão Python
A análise da versão Python do código apresenta complexidade O(n3log
2n), atingida pela
sequência de funções abaixo representada no bloco de código 5.5, onde foram extraídas do código apenas as funções relevantes.
while(strikesCalculated<nStrikes):
while(calculados<nBancos and numMaturities>x):
for i in range(nBancos):
for i in range(nBancos-1):
if(bankIncluded[i] == 0.5):
for j in range(i+1, nBancos):
if(bankIncluded[j] == 0.5): if(abs(volatilities[i]-newMedia) <= abs(volatilities[j]-newMedia)): contBank[i] += 1 else: contBank[j] += 1 }
Bloco de Código 5.5: Excerto de código Python.