简介:在上一节《安全多方计算(MPC)从入门到精通:简易教程》中,我们已经简单介绍过Frutta语言,Frutta是JUGO为计算逻辑而开发的编程语言,计算逻辑在MPC中是为解决具体业务而编写的算法。它是一门类C高级语言,支持大部分运算符、数据类型,表达方式的实现——300个门电路,仅需一行代码!
1.什么是Frutta
  Frutta是矩阵元为安全多方计算的算法电路文件生成而专门定制的编程语言。它是一门类高级语言,也是一门面向过程的编程语言。Frutta支持大部分运算符、数据类型,表达方式灵活实用。目前只有矩阵元提供的在线ide可以进行编译运行。ide可以把Frutta文件编译成可在JUGO平台上执行的电路文件。
2.程序结构
  在我们学习Frutta语言的基本构建块之前,让我们先来看看一个最小的Frutta程序结构,在接下来的章节中可以以此作为参考。
  Frutta语言主要包括以下部分:
预处理器指令
输入输出定义
函数
变量
语句&表达式
注释

  1. #parties 2 / 两个参与方 /
  2. #input 1 int32 / 参与方1,可以是基础类型,也可以是数组,结构体等复杂类型 /
  3. #input 2 int32 / 参与方2 /
  4. #output 1 int32 / 输出方 /
  5. / 主函数,完成加法操作 /
  6. function void main()
  7. {
  8. output1 = input1 + input2;
  9. }

一、基本语法
  Frutta程序由各种Token组成,Token可以是关键字、标识符、常量、字符串值,或者是一个符号。
分号
  在Frutta程序中,分号是语句结束符。也就是说,每个语句必须以分号结束。它表明一个逻辑实体的结束。
注释
  注释就像是程序中的帮助文本,它们会被编译器忽略。它们以 / 开始,以字符 / 终止。
  您不能在注释内嵌套注释,注释也不能出现在字符串或字符值中。
标识符
  标识符是用来标识变量、函数,或任何其他用户自定义项目的名称。一个标识符以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母、下划线和数字(0-9)。
  标识符内不允许出现标点字符,比如 @、$ 和 %。标识符是区分大小写。因此,Manpower 和 manpower 是两个不同的标识符。下面列出几个有效的标识符:
mohd zara abc move_name a_123 myname50 _temp j a23b9 retVal
关键字
  下表列出了Frutta中的保留字。这些保留字不能作为常量名、变量名或其他标识符名称。对于输入输出,请按照顺序,依次写。输入个数跟参与计算方个数保持一致。比如有3个计算方个数,那么请依次定义input 1 xxx, input 2 xxx, input 3 xxx。输出也同样。
else typedef return for void if struct abs max min
int8 uint8 int16 uint16 int32 uint32 int64 uint64 bool
input1 input2 ... inputN output1 output2 ... outputN
空格
  只包含空格的行,被称为空白行,可能带有注释,编译器会完全忽略它。
  在Frutta中,空格用于描述空白符、制表符、换行符和注释。空格分隔语句的各个部分,让编译器能识别语句中的某个元素(比如 int)在哪里结束,下一个元素在哪里开始。因此,在下面的语句中:type age;。在这里,type 和 age 之间必须至少有一个空格字符(通常是一个空白符),这样编译器才能够区分它们。另一方面,在下面的语句中:
fruit = apples + oranges;
Copy
  fruit 和 =,或者 = 和 apples 之间的空格字符不是必需的,但是为了增强可读性,您可以根据需要适当增加一些空格。

二、预处理器
  预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。简言之,预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理。
  所有的预处理器命令都是以井号(#)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。
  在Frutta中,支持两类预编译器指令:
include
  支持包含外部文件,外部文件支持以下几类外部文件:
Frutta文件
  使用Frutta实现的文件,里面实现的函数可以在当前文件里面调用。注意,只支持当前文件,不支持非当前目录,比如不支持#include "./lib/math.wir",只支持#include "math.wir"。一个完整的示例如下:

  1. // math.wir
  2. function int32 add(int32 x, int32 y)
  3. {
  4. return x + y;
  5. }
  6. function int32 sub(int32 x, int32 y)
  7. {
  8. return x - y;
  9. }
  10. function int32 mul(int32 x, int32 y)
  11. {
  12. return x * y;
  13. }
  14. function int32 div(int32 x, int32 y)
  15. {
  16. return x / y;
  17. }
  18. typedef struct Point
  19. {
  20. int32 x;
  21. int32 y;
  22. }
  23. / 求两个点之间的距离 /
  24. function int32 dis(Point p1, Point p2)
  25. {
  26. int32 x = sub(p1.x, p2.x);
  27. int32 y = sub(p1.y, p2.y);
  28. return add(mul(x, x), mul(y, y));
  29. }
  30. // include.wir
  31. #include "math.wir"
  32. #parties 2
  33. #input 1 Point
  34. #input 2 Point
  35. #output 1 int32
  36. function void main()
  37. {
  38. output1 = dis(input1, input2);
  39. }
    define
      简单替换程序中的标识符。不支持宏参数,以及##连接符与#符。
  40. #define NAME expression

三、数据类型
1.基本类型
  Frutta语言中,任何运算都表示为整数运算,因此Frutta语言以下几类基本类型:

类型 位数 数值范围
bool 8 取值为0和1
int8 8 -128 ~ 127
uint8 8 0 ~ 255
int16 16 -32768 ~ 32767
uint16 16 0 ~ 65535
int32 32 -2147483648 ~ 2147483647
uint32 32 0 ~ 4294967295
int64 64 -9223372036854775808 ~ 9223372036854775807
uint64 64 0 ~ 18446744073709551615

 也可以定义不同字节数的自定义类型。
1.typedef signed bytelength type;
2.typedef unsigned bytelength utype;

2.数据类型的隐式转换
  Frutta在以下四种情况下会进行隐式转换:
a. 算术运算式中,低类型能够转换为高类型。
b. 赋值表达式中,右边表达式的值自动隐式转换为左边变量的类型,并赋值给他。
c. 函数调用中参数传递时,系统隐式地将实参转换为形参的类型后,赋给形参。
d. 函数有返回值时,系统将隐式地将返回表达式类型转换为返回值类型,赋值给调用函数。
  当不同类型的数据进行操作时,应当首先将其转换成相同的数据类型,然后进行操作,转换规则是由低级向高级转换。
转换规则如下示:
  int8->uint8->int16->uint16->int32->uint32->int64->uint64。
  举一个int8 -> uint8的例子:

  1. int8 a = -41;
  2. int8 b = -90;
  3. uint8 c = a + b;
  4. // 最终结果为 125

3.结构体(struct)
  Frutta支持类C语言的Struct,来定义结构体。结构体允许定义可存储不同类型数据项的变量。
1) 定义结构

  1. typedef struct Point
  2. {
  3. int32 x;
  4. int32 y;
  5. }
  6. typedef struct Circle
  7. {
  8. Point p;
  9. uint32 radius;
  10. }

  以上定义可以使用 Point,Circle 定义结构体变量。
2) 访问结构成员
   Frutta语言支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量。
1) 声明数组
  在Frutta中要声明一个数组,需要指定元素的类型和元素的数量,如下所示:type arrayName [ arraySize ]
  Frutta支持多维数组,如下所示:type arrayName [row][col];
2) 初始化数组
  在Frutta中,您可以逐个初始化数组,也可以使用一个初始化语句,如下所示:type balance[5] = {0, 0, 0, 0, 0};
  大括号{ }之间的值的数目不能大于我们在数组声明时在方括号[ ]中指定的元素数目。
3) 访问数组元素
  数组元素可以通过数组名称加索引进行访问。元素的索引是放在方括号内,跟在数组名称的后边。例如: type salary = balance[9];
  上面的语句将把数组中第 10 个元素的值赋给 salary 变量。

四、常量
  Frutta中,有整型常量和bool常量。
bool常量:在Frutta中,内置两个bool常量true和false,二进制分别表示00000001和00000000。如:
bool flag = true。
整型常量:整型常量可以使用10进制和16进制表示。16进制需要以0x开头。如:
uint32 YEAR = 365。

五、变量
  Frutta中,变量其实只不过是程序可操作的多个Wire的组合。Frutta中每个变量都有不同数量的Wire,运算符可应用于变量上。
  变量的名称可以由字母、数字和下划线字符组成。它必须以字母或下划线开头。大写字母和小写字母是不同的。
  变量定义指定一个数据类型(数据类型必须是类型定义里面声明的),并包含了该类型的一个或多个变量的列表,如:
type variable_list;
  变量可以在声明的时候被初始化(指定一个初始值)。初始化器由一个等号,后跟一个常量表达式组成,如下所示:
type variable_name = value;

六、运算符
  运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。Frutta语言内置了丰富的运算符,并提供了以下类型的运算符:
1.算术运算符

运算符 描述
+ 把两个操作数相加
- 从第一个操作数中减去第二个操作数
* 把两个操作数相乘
/ 分子除以分母
% 取模运算符,整除后的余数

2.关系运算符

运算符 描述
== 检查两个操作数的值是否相等,如果相等则条件为真。
!= 检查两个操作数的值是否相等,如果不相等则条件为真。
> 检查左操作数的值是否大于右操作数的值,如果是则条件为真。
< 检查左操作数的值是否小于右操作数的值,如果是则条件为真。
>= 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。
<= 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。

3.位运算符

运算符 描述
& 如果同时存在于两个操作数中,二进制 AND 运算符复制一位到结果中。
\ 如果存在于任一操作数中,二进制 OR 运算符复制一位到结果中。
^ 如果存在于其中一个操作数中但不同时存在于两个操作数中,二进制异或运算符复制一位到结果中。
<< 二进制左移运算符。左操作数的值向左移动右操作数指定的位数。
>> 二进制右移运算符。左操作数的值向右移动右操作数指定的位数。

4.赋值运算符

运算符 描述
= 简单的赋值运算符,把右边操作数的值赋给左边操作数

七、操作符
1.按位取线
  支持按位取变量的线
  支持以VAR{start:length}方式获取某个变量从start开始的length根线,返回值可以赋值
  给某个变量,也可以直接参与计算。

  1. #parties 2
  2. #input 1 int32
  3. #input 2 int32
  4. #output 1 int32
  5. function void main()
  6. {
  7. output1 = input1{0:8} + input2{0:8};//上面的数从0开始,取8个bit进行运算
  8. }

八、控制语句
1.条件判断语句
  判断结构要求程序员指定一个或多个要评估或测试的条件,以及条件为真时要执行的语句(必需的)和条件为假时要执行的语句(可选的)。

语句 描述
if语句 一个 if 语句 由一个布尔表达式后跟一个或多个语句组成。
if...else语句 一个 if 语句 后可跟一个可选的 else 语句,else 语句在布尔表达式为假时执行。
嵌套 if 语句 可以在一个 if 或 else if 语句内使用另一个 if 或 else if 语句。

2.循环判断语句
  循环语句允许我们多次执行一个语句或语句组,Frutta只支持for循环语句,如下所示:

  1. for(type i=0; i<max; i++)
  2. {
  3. }

注意:
不支持break,continue语句
结束条件i<max必须不依赖input取值,必须为明确取值
不支持外部类型声明如for(; i<max; i++){ }
九、函数
1.函数定义
  函数是一组一起执行一个任务的语句。每个Frutta程序都至少有一个函数,即主函数 main() ,所有简单的程序都可以定义其他额外的函数。
  Frutta的函数定义如下:

  1. function return_type foo(paramtype1 name1, paramtype2 name2, paramtype3[5] name3)
    1. {
    2. return blah;
    3. }
    4. // 一个例子如下
    5. function int32 add(int32 x, int32 y)
    6. {
    7. return x + y;
    8. }
    9. // 调用
    10. output1 = add(input1, input2);
        一个函数的所有组成部分:
      返回类型:一个函数可以返回一个值。return_type 是函数返回的值的数据类型。有些函数执行所需的操作而不返回值,在这种情况下,return_type 是关键字 void。
      函数名称:这是函数的实际名称。函数名和参数列表一起构成了函数签名。
      参数:参数就像是占位符。当函数被调用时,您向参数传递一个值,这个值被称为实际参数。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数。
      函数主体:函数主体包含一组定义函数执行任务的语句。

2.内置函数
  在Frutta中,内置以下函数:

函数 函数定义 函数说明
abs intxxx abs(intxxx) 取绝对值,输入输出为有符整数。如下:int32 y = abs(x)
min (u)intxxx min((u)intxxx, (u)intxxx) 取两个数中的最小值,输入可以为有符和无符整数,如果同时有有符和无符,有符自动转换为无符,返回值为无符。如下:int16 min(x, y)
max (u)intxxx max((u)intxxx, (u)intxxx) 取两个数中的最大值,输入可以为有符和无符整数,如果同时有有符和无符,有符自动转换为无符,返回值为无符。如下:int16 max (x, y)

更多内容可以参考视频:安全多方计算MPC视频课程