前置知识:Go、Geth、EVM、Solidity、操作系统
首先我们来简单介绍一下标题中的名词:
EVM:由GO语言实现的虚拟机,封装在Ethereum的客户端Geth中。
Opcode:EVM中字节操作码,是编写Solidity的低级语言。
MUL:操作码中的乘法操作。
MULMOD:操作码中的乘法取模操作。
首先我们来看一下MUL的大致用法,MUL接收两个整数参数,然后再将它们做乘法操作:
mul(2,5) = 10;
输出的是一个取模2^256的整数。注意,取模后的位数被限制在了256位以内,也就是如果乘法出现了溢出的情况,高于256位会被截断丢弃,只保留低位的256位。
我们可以使用该示例代码测试一下:

在cal函数中,我们使用三个变量来接收MUL计算的结果:
result0 = 0x ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff
result1 = 0x 0001 ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff fffe
result2 = 0x ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff fffe 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001
但由于MUL的结果只保留低位256,所以:
result0 保持不变
result1 = 0x ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff fffe
result2 = 0x 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001
最后的结果:
result0 = 2^256 - 1
result1 = 2^256 - 2
result2 = 2^256 - (2^256 - 1) = 1

介绍完了MUL,我们来看下MULMOD,
该方法接收三个整数参数,前两个参数做乘法操作,得到结果模上第三个参数:
mulmod(5,2,3) = 1;
到目前为止,我们似乎就能发现,MUL与MULMOD的区别就在于MULMOD仅仅是将MUL操作与MOD操作封装在了一个操作码里面。假如使用MUL与MOD似乎也能达到MULMOD的效果,让我们来测试一下。
首先提供两个函数,分别使用MUL与MOD,MULMOD来进行计算。

我们分别测试了两组数据,两种函数得到计算结果都相同,并无差异。但是细心的你也许会发现,所有的计算结果均未溢出256位。

接下来,让我们测试一下256位溢出的情况。
最后得到的结果大相径庭,可能与很多人预想的不一样。大多数人可能以为EVM支持最高的计算结果位数是256位,但其实不是,最高是512位。

在Solidity的汇编操作码中,有两个操作码支持512位以内的运算,分别是ADDMOD和MULMOD,他们都接收两个最大256位的整数,得到一个512位的数,然后再模上第三个参数,最后得到的始终会是一个256位的数。
上面的例子中,造成结果不一样的原因是因为mulThenMod会先得到一个256位的数,如果溢出,会先截断高位,再用低位去模上第三个参数。
而mulMod却不会截断高位。
想要进一步探究其原理,我们需要查看EVM的源码,也就是Geth。
你可以将代码克隆下来查看:
git clone https://github.com/ethereum/go-ethereum.git
cd go-ethereum
在core/vm/instructions.go这个源码文件中,有所有opcode的源码。
找到opMulmod,这便是我们在汇编中写的mulmod操作码的源码,

如果你不想深究EVM的源码,只想了解个大概的底层原理,我们可以直接跳转到z.MulMod()这个函数的源码:

同样,你无需了解全部代码,只需跳转第五行代码umul()函数。
在下面图片中,我省略了大量计算源码,用cal…代替,如果你对计算过程感兴趣,可以自行查看。
该函数最后返回一个[8]uint64的数组,该数组的8个uint64整数,组合起来刚好是一个512位的整数。该整数便是操作码mulmod的中间计算结果。

最后在z.MulMod()中会用调用udivrem()方法返回一个[4]uint64的数组,该数组在Go的uint256.go中被声明位Int类型,也就是我们在Solidity中使用的uint256。
到这里,你或许已经知道操作码MUL与MULMOD的区别了。
如何本文有任何错误,或者你有什么疑问需要咨询,欢迎联系我。

