Solidity 中的数学(第 2 部分:溢出)

news2024/9/27 15:24:18

本文是关于在 Solidity 中进行数学运算的系列文章中的第二篇。这次的主题是:溢出

介绍

每次我看到+***审计另一个 Solidity 智能合约时,我都会开始写以下评论:“这里可能会溢出”。我需要几秒钟来写这四个字,在这几秒钟内,我观察附近的行,试图找出原因,为什么不可能溢出,或者为什么在这种特殊情况下应该允许溢出。如果找到原因,我会删除评论,但大多数情况下评论会保留在最终审计报告中。

事情不应该是这样的。算术运算符应该允许编写紧凑且易于阅读的公式,例如a**2 + 2*a*b + b**2. 然而,这个表达式几乎肯定会引起一堆安全问题,真正的代码更可能是这样的:

add (add (pow (a, 2), mul (mul (2, a), b)), pow (b, 2))

这里addmulpow分别是实现 、 和 的“安全”版本+*函数**

不鼓励简洁方便的语法,很少使用简单的算术运算符(一次不超过一个),到处都是繁琐且不可读的函数语法。在这篇文章中,我们分析了这个问题,它让事情变得如此奇怪,它臭名昭著的名字是:溢出

我们在某处走错了路

有人会说,溢出总是存在的,所有的编程语言都深受其害。但这真的是真的吗?您是否见过类似为 C++、Python 或 JavaScript 实现的SafeMath库?在证明相反的情况之前,您真的认为每个+*都是安全漏洞吗?很可能,您对这两个问题的回答都是“否”。所以,

为什么 Solidity 溢出会如此痛苦?

剧透:无处可逃,无处可藏。

数字在纯数学中不会溢出。可以将两个任意大数相加并得到精确的结果。在 JavaScript 和 Python 等高级编程语言中,数字不会溢出。在某些情况下,结果可能会落入无穷大,但至少将两个正数相加可能永远不会产生负结果。在 C++ 和 Java 中,整数会溢出,但浮点数不会。

在那些整数类型确实会溢出的语言中,纯整数主要用于索引、计数器和缓冲区大小,即用于受正在处理的数据大小限制的值。对于可能超出普通整数范围的值,有浮点数、大整数和大十进制数据类型,它们是内置的或通过库实现的。

基本上,当算术运算的结果不适合参数的类型时,编译器可能会做一些选择:i)使用更宽的结果类型;ii) 返回截断的结果并使用侧通道通知程序溢出;iii) 抛出异常;和 iv) 只是默默地返回截断的结果。

int在处理类型溢出时,第一个选项在 Python 2 中实现。第二个选项是 CPU 中的进位/溢出标志的用途。第三个选项由 SafeMath 库为 Solidity 实现。第四个选项是 Solidity 自己实现的。

第四个选项可能是最糟糕的一个,因为它使算术运算容易出错,同时使溢出检测非常昂贵,特别是对于乘法情况。为了安全起见,需要在每次乘法后执行额外的除法。

因此,Solidity 既没有安全类型,可以跑到,也没有安全操作,可以躲在后面。无处可逃,无处可躲,开发人员不得不面对溢出并在整个代码中与它们作斗争。

那么,下一个问题是:

为什么 Solidity 没有安全类型和操作?

剧透:因为 EVM 没有它们。

智能合约必须是安全的。它们中的错误和漏洞会造成数百万美元的损失,因为我们已经从惨痛的教训中吸取了教训。作为智能合约开发的主要语言,Solidity 非常重视安全性。如果有许多功能应该可以防止开发人员搬起石头砸自己的脚。我们指的是payable关键字、类型转换限制等功能。每个主要版本都会添加此类功能,通常会破坏向后兼容性,但社区为了更好的安全性而容忍这种情况。

然而,基本的算术运算非常不安全,现在几乎没有人直接使用它们,而且情况也没有改善。唯一变得更安全的操作是除法:除以零以前返回零,但现在它抛出异常,但即使是除法也没有变得完全安全,因为它仍然可能溢出。是的,在intSolidity 类型中,当 -2¹²⁷ 除以 -1 时会溢出,因为正确答案 (2¹²⁷) 不适合int. 所有其他操作,即+-***仍然容易发生溢出或下溢,因此本质上是不安全的。

Solidity 中的算术运算复制相应的 EVM 操作码的行为,并且使这些运算在编译器级别安全会增加数倍的气体消耗。普通ADD操作码需要 3 个 gas。作者设法找到的用于实现安全添加的最便宜的操作码序列是:

DUP2(3) DUP2(3) NOT(3) LT(3) <overflow>(3) JUMPI(10) ADD(3)

<overflow>是溢出时跳转的地址。括号中的数字是操作的 gas 成本,这些数字总共给我们 28 gas。几乎是 plain 的 10 倍ADD。太多了,对吧?这取决于你比较的是什么。比如说,从 SafeMath 库调用add函数将花费大约 88 gas。

因此,库或编译器级别的安全算术成本很高,但是

为什么 EVM 没有安全的算术操作码?

剧透:没有充分的理由。

出于性能原因,有人会说 EVM 中的算术语义复制了 CPU 的算术语义。是的,一些现代 CPU 有256 位运算的操作码,但主流 EVM 实现似乎不使用这些操作码。Geth 使用big.IntGo 编程语言标准库中的类型。这种类型实现了由原生单词数组支持的任意宽大整数。Parity 使用自己的库在本机 64 位字之上实现固定宽度的大整数。

对于这两种实现,算术溢出检测的额外成本几乎为零。因此,一旦 EVM 拥有算术操作码版本,在溢出时恢复,它们的 gas 成本可以与现有的不安全版本相同,或者略高。

更有用的是完全不溢出的操作码,而是返回整个结果。这样的操作码将允许在编译器或库级别有效地实现任意宽的大整数。

我们不知道为什么 EVM 没有上述操作码。也许只是因为其他主流虚拟机没有它们?

到目前为止,我们讲述的是真正的溢出:计算结果太大而不适合结果数据类型的情况。现在是时候发现问题的另一面了:

幻影溢出

如何计算 Solidity 中x的 3%?在主流语言中,人们只是写0.03*x,但 Solidity 不支持分数。怎么样x*3/100?好吧,这在大多数情况下都有效,但是如果x太大以至于x*3会溢出怎么办?从上一节我们知道该怎么做,对吧?只需使用mulSafeMath 并确保安全:mul (x, 3) / 100......没那么快。

后一个版本更安全一些,因为它会还原前一个版本返回错误结果的位置。这很好,但是……到底为什么计算 3% 的东西可能会溢出?某物的 3% 保证低于原始价值:无论是名义价值还是绝对价值。所以,只要x适合 256 位字,那么x的 3%也应该适合,不是吗?

好吧,我称之为“幻影溢出”:最终计算结果适合结果数据类型,但某些中间操作溢出的情况。

虚拟溢出比真实溢出更难检测和解决。一种解决方案是对中间值使用更宽的整数类型甚至浮点类型。另一个是重构表达式以使幻像溢出成为不可能。让我们尝试用我们的表达式来做后者。

算术定律告诉我们,以下公式应该产生相同的结果:

(x * 3) / 100
(3 * x) / 100
(x / 100) * 3
(3 / 100) * x

但是,Solidity 中的整数除法与纯数学中的除法不同,因为在 Solidity 中它将结果四舍五入为零。前两个变体基本上是等价的,并且都存在幻象溢出。第三种变体没有幻象溢出问题,但不太精确,尤其是对于小x。第四个变体更有趣,因为它会令人惊讶地导致编译错误:

browser/Junk.sol:5:18: TypeError: Operator * not compatible with types rational_const 3 / 10 and uint256
browser/Junk.sol:5:18: TypeError: Operator * not compatible with types rational_const 3 / 10 and uint256

我们已经在上一篇文章中中描述了这种行为。为了使第四个表达式编译,我们需要像这样更改它:

(uint (3) / 100) * x

然而,这并没有多大帮助,因为更正后的表达式的结果始终为零,因为3 / 100向零四舍五入就是零。

通过第三个变体,我们设法以精度为代价解决了幻影溢出问题。实际上,精度损失仅对小x显着,而对于大x则可以忽略不计。请记住,对于原始表达式,只有大x才会出现幻像溢出问题,因此我们似乎可以像这样组合这两种变体:

x > SOME_LARGE_NUMBER ? x / 100 * 3 : x * 3 / 100

这里SOME_LARGE_NUMBER可以计算为 (2²⁵⁶-1)/3 并将该值向下舍入。现在对于小x我们使用原始公式,而对于大x我们使用不允许幻像溢出的修改公式。看起来我们现在解决了幻像溢出问题,而没有显着降低精度。干得好,对吧?

在这种特殊情况下,可能是的。但是,如果我们需要计算的不是 3%,而是 3.1415926535% 怎么办?公式为:

x > SOME_LARGE_NUMBER ?
x / 1000000000000 * 31415926535 :
x * 31415926535 / 1000000000000

我们的SOME_LARGE_NUMBER意愿变为 (2²⁵⁶-1)/31415926535。那时没有那么大。那么 3.141592653589793238462643383279% 呢?这种方法适用于简单的情况,但似乎不能很好地扩展。

结论

EVM 不提供溢出保护操作码。Solidity 在编译器级别不提供任何溢出保护。因此,智能合约开发人员必须在代码级别解决溢出问题,这使得代码变得繁琐且 gas 效率较低。

幻象溢出更难检测和解决。直截了当会导致权衡取舍,而且通常无法扩展。

在我们的下一篇文章中,我们将提出解决幻影溢出问题的更好方法

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/170312.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Pandas】18 小练习

#【Pandas】18 小练习 2023.1.16 两个pandas小练习 18.1 疫情数据分析 18.1.1 观察数据 import pandas as pd import osdf pd.read_csv("data/covid19_day_wise.csv") dfDateConfirmedDeathsRecoveredActiveNew casesNew deathsNew recoveredDeaths / 100 CasesR…

日常渗透刷洞的一些小工具

SecurityServiceBox&#xff1a;一个Windows平台下既可以满足安服仔日常渗透工作也可以批量刷洞的工具盒子 0x00 更新题外话—终端选取 在盒子的tools当中&#xff0c;很多工具运行都是带有颜色标识的&#xff0c;例如nuclei&#xff0c; vulmap&#xff0c;原生的cmd终端虽然…

MD5有哪些特性,常用的MD5加密真的安全吗

在密码学中&#xff0c;MD5是比较常用的算法之一。大家都知道MD5曾一度被认为十分安全&#xff0c;并且在国内外得到广泛适用。然而&#xff0c;王小云教授的研究证明利用MD5算法的磕碰能够严重威胁信息体系安全&#xff0c;因此引发了密码学界的轩然大波。那么&#xff0c;关于…

为什么JDK中String类的indexof不使用KMP或者Boyer-Moore等时间复杂度低的算法编辑器

indexOf底层使用的方法是典型的BF算法。 1、KMP算法 由来 外国人&#xff1a; Knuth&#xff0c;Morris和Pratt发明了这个算法&#xff0c;然后取它三个的首字母进行了命名。所以叫做KMP。 KMP真的很难理解&#xff0c;建议多看几遍 B站代码随想录&#xff0c;文章也的再好 …

【蓝桥杯备赛系列 | 真题 | 简单题】2014年第五届真题-分糖果

&#x1f935;‍♂️ 个人主页: 计算机魔术师 &#x1f468;‍&#x1f4bb; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 蓝桥杯竞赛专栏 | 简单题系列 &#xff08;一&#xff09; 作者&#xff1a; 计算机魔术师 版本&#xff1a; 1.0 &#xff08…

【博客597】iptables如何借助连续内存块通过xt_table结构管理流量规则

iptables如何借助连续内存块通过xt_table结构管理流量规则 1、iptables 分为两部分&#xff1a; 用户空间的 iptables 命令向用户提供访问内核 iptables 模块的管理界面。内核空间的 iptables 模块在内存中维护规则表&#xff0c;实现表的创建及注册。 2、iptables如何管理众…

第十二章 数据库设计

前言 本文章为看视频所写。 视频链接&#xff1a;168. 14.1 数据库设计前言_哔哩哔哩_bilibili 目录 前言 章节提要 一、数据库设计过程 二、E-R模型 三、答题技巧 四、案例分析 1、案例1 二、案例2 章节提要 一、数据库设计过程 ER模型&#xff1a;是实体联系模型&#x…

第一章 数据结构绪论

数据结构&#xff1a;是相互之间存在一种或多种特定关系的数据元素的集合。数据结构是一门研究非数值计算的程序设计问题中的操作对象&#xff0c;以及它们之间关系和操作等相关问题的学科。程序设计数据结构算法数据&#xff1a;是描述客观事物的符号&#xff0c;是计算机中可…

2.2、进程的状态与转换

整体框架 1、三种基本状态 进程是程序的一次执行。在这个执行过程中&#xff0c;有时进程正在被 CPU 处理&#xff0c;有时又需要等待 CPU 服务&#xff0c; 可见进程的状态是会有各种变化。 为了方便对各个进程的管理&#xff0c;操作系统需要将进程合理地划分为几种状态 ①…

随机梯度下降法的数学基础

梯度是微积分中的基本概念&#xff0c;也是机器学习解优化问题经常使用的数学工具&#xff08;梯度下降算法&#xff09;。因此&#xff0c;有必要从头理解梯度的来源和意义。本文从导数开始讲起&#xff0c;讲述了导数、偏导数、方向导数和梯度的定义、意义和数学公式&#xf…

SpringBoot-自动配置-切换内置web服务器

SpringBoot-自动配置-切换内置web服务器 介绍 SpringBoot的web环境中默认使用tomcat作为内置服务器其实SpringBoot提供了4种内置服务器供我们选择分别为&#xff1a;Jetty&#xff0c;Netty&#xff0c;Tomcat&#xff0c;Undertow我们可以很方便的进行切换 实例演示 在pom文件…

简单了解操作系统、进程内存管理

目录 前言&#xff1a; 一、操作系统&#xff1a; 操作系统的定位&#xff1a; 应用程序&#xff1a; 系统调用&#xff1a; 操作系统内核&#xff1a; 驱动程序&#xff1a; 硬件设备&#xff1a; 二、进程&#xff1a; 什么是进程&#xff1f; 进程的描述与组…

自定义类型,结构体、枚举、联合(C语言)

目录 结构体 结构体的基础知识&#xff1a; 结构体的声明&#xff1a; 特殊声明&#xff1a; 结构体的自引用 结构体变量的定义和初始化 结构体内存对齐&#xff1a; 修改默认对齐数&#xff1a; 结构体传参 结构体的柔型数组 柔型数组的书写 柔性数组的特点 柔性数组的使用 柔…

【Java寒假打卡】JavaWeb-Tomcat

【Java寒假打卡】JavaWeb-Tomcat服务器Tomcat下载和安装Tomcat的目录结构基本使用控制台乱码的问题IDEA集成TomcatJavaWeb项目的目录结构Tomcat-idea发布项目Tomcat-WAR包发布项目Tomcat配置文件的介绍Tomcat配置虚拟目录Tomcat配置虚拟主机服务器 Tomcat下载和安装 将下载好的…

干货 | 数据安全和个人信息保护审计的方法研究

以下内容整理自清华大学《数智安全与标准化》课程大作业期末报告同学的汇报内容。第一部分&#xff1a;概述我们的研究核心是个人信息保护合规审计&#xff0c;具体指个人信息处理活动是否遵守我国相关法律法规的监督性审计。在个保法出台后&#xff0c;我国形成了以内部审计为…

我用ChatGPT写神经网络:一字不改,结果竟很好用

自从去年底推出以来&#xff0c;对话式 AI 模型 ChatGPT 火遍了整个社区。 ChatGPT 的确是一个了不起的工具&#xff0c;就像一个「潘多拉魔盒」。一旦找到正确的打开方式&#xff0c;你或许会发现&#xff0c;自己再也离不开它了。 作为一个全能选手&#xff0c;人们给 Chat…

Fedora 38发布Budgie与Sway定制版

导读两款新的 Fedora 定制版将在 Fedora 38 发布时首次亮相。我们期待着它们在 Fedora 37 时出现&#xff0c;但在 Fedora 38 中终于来了&#xff01; 早在 2022 年 5 月&#xff0c;Budgie 项目的主要开发者 Joshua Strobl ​​宣布​​&#xff0c;Budgie 已被提交到 Fedora…

第五届字节跳动青训营 前端进阶学习笔记(四)TypeScript入门

文章目录前言TypeScript概要1.什么是TypeScript2.TypeScript基本语法基础数据类型对象类型函数类型函数重载数组类型补充类型泛型约束和泛型默认参数类型别名和类型断言高级类型1.联合类型2.交叉类型3.类型守卫类型谓词总结前言 课程重点&#xff1a; TypeScript概要TypeScri…

Kubernets核心介绍及实战

1、资源创建方式 命令行YAML 2、Namespace 名称空间用来隔离资源 “namespace"通常被翻译为「命名空间」&#xff0c;听起来好像比较抽象&#xff0c;其实重点是在这个"space”。它和描述进程的虚拟地址空间的address space一样&#xff0c;都是提供一种独占的视角…

linux引导和启动程序

1.BIOS/Bootloader: 一上电&#xff0c;硬件强制让cpu的cs:ip寄存器指向bios程序的位置&#xff0c;从bios程序开始执行&#xff0c;由pc机的BIOS &#xff08;0xFFFFO是BIOs存储的总线地址&#xff09;把bootsect从某个固定的地址拿到了内存中的某个固定地址&#xff08;0x90…