در شبکه اتریوم، مجموعهای از قراردادهای خاص با عنوان “قراردادهای precompiled” وجود دارند که بهصورت داخلی در پروتکل تعریف شدهاند. این قراردادها مانند قراردادهای هوشمند عمل میکنند، اما داخل خود پروتکل قرار دارند و در بازه آدرس های 0x01
تا 0x09
مستقر شدهاند.
عملکرد این قراردادها در چهار دسته اصلی قرار میگیرد:
-
بازیابی امضای دیجیتال بر پایه منحنی بیضوی
-
توابع هش برای ارتباط با بیت کوین و زیکش (Zcash)
-
عملیات کپی حافظه با کارایی بالا
-
محاسبات ریاضی منحنی بیضوی برای استفاده در اثبات های بدون افشا (Zero-Knowledge Proofs)
اتریوم این عملیات را بهدلیل اهمیت و استفاده گستردهشان، به شکل بهینه تری از نظر مصرف گس ارائه کرده است. اجرای این الگوریتمها با استفاده از سالیدیتی، مقدار قابل توجهی گس مصرف میکند و کارایی کمتری دارد.
این قراردادها داخل یک قرارداد هوشمند اجرا نمیشوند. بلکه در سطح کلاینت اتریوم پیاده سازی شدهاند و بهعنوان بخشی از مشخصات رسمی پروتکل شناخته میشوند. لیست کامل این قراردادها را میتوان در کلاینت Geth مشاهده کرد. همچنین، این توابع در سند رسمی Ethereum Yellow Paper نیز در ضمیمه E معرفی شدهاند. درک نحوه عملکرد این قراردادها برای هر توسعهدهندهای که بهصورت حرفهای در زمینه برنامه نویسی بلاکچین فعالیت میکند، ضروری است.
فراخوانی قراردادهای precompiled با استفاده از سالیدیتی
اغلب قراردادهای از پیش کامپایل شده (precompiled) در اتریوم فاقد رابط (Wrapper) مخصوص در زبان سالیدیتی هستند؛ تنها استثنا در این مورد تابع ecRecover
است که یک رابط آماده دارد. بنابراین برای فراخوانی این نوع قراردادها، باید مستقیماً از آدرس مربوط به آنها استفاده کنید، مثلاً از طریق addressOfPrecompile.staticcall(...)
یا با نوشتن کد در بخش اسمبلی (Assembly).
هرچند هیچیک از این قراردادها وضعیت (state) بلاکچین را تغییر نمیدهند، اما تابع سالیدیتی که آنها را فراخوانی میکند نمیتواند بهصورت pure
تعریف شود. دلیل این محدودیت این است که کامپایلر سالیدیتی نمیتواند اطمینان حاصل کند که فراخوانی با staticcall
بهطور قطع تغییری در وضعیت ایجاد نمیکند، زیرا این تضمین در سطح زبان مشخص نیست.
در نتیجه، حتی اگر فراخوانی شما فقط خواندنی باشد، باید تابع مورد نظر را حداقل بهصورت view
تعریف کنید.
آدرس 0x01: تابع ecRecover
تابع ecRecover
یک قرارداد از پیش کامپایل شده (precompiled) در اتریوم است که برای بازیابی آدرس امضاکننده از روی یک هش و امضای دیجیتال آن استفاده میشود. به بیان ساده، این تابع مشخص میکند چه کسی پیامی را امضا کرده، مشروط بر اینکه امضا معتبر باشد.
مثال:
1 2 3 4 |
function recoverSignature(bytes32 hash, uint8 v, bytes32 r, bytes32 s) public view returns (address) { address r = ecrecover(hash, v, r, s); require(r != address(0), "signature is invalid"); } |
نکته مهم: تابع ecrecover
در صورتی که امضا نامعتبر باشد، عملیات را متوقف نمیکند (revert نمیشود). در عوض، مقدار address(0)
را بازمیگرداند. به همین دلیل، بررسی صراحتی اعتبار امضا پس از فراخوانی این تابع کاملاً ضروری است.
توصیه میشود برای جلوگیری از خطاهای رایج در پردازش امضاها، از کتابخانه OpenZeppelin استفاده کنید. این کتابخانه بسیاری از پیچیدگیهای مرتبط با امضاهای دیجیتال را بهطور ایمن مدیریت میکند.
چون اگر با نحوه عملکرد امضاها آشنا نباشید، ممکن است با خطاهایی مواجه شوید که امنیت یا صحت قرارداد شما را به خطر بیندازد.
آدرس های 0x02 و 0x03: توابع هش SHA-256 و RIPEMD-160
این دو قرارداد precompiled در سالیدیتی برای محاسبه هش روی داده های ارسالی بهصورت مستقیم از طریق calldata
طراحی شدهاند. در اینجا مثالی از استفاده تابع SHA-256 را میبینید. برای ساده تر شدن، در این نمونه یک عدد uint256
را هش میکنیم:
1 2 3 4 5 |
function hashSha256(uint256 numberToHash) public view returns (bytes32 h) { (bool ok, bytes memory out) = address(2).staticcall(abi.encode(numberToHash)); require(ok); h = abi.decode(out, (bytes32)); } |
1 2 3 4 5 |
function hashRIPEMD160(bytes calldata data) public view returns (bytes20 h) { (bool ok, bytes memory out) = address(3).staticcall(data); require(ok); h = bytes20(abi.decode(out, (bytes32)) << 96); } |
bytes32
دریافت میشود و سپس با استفاده از شیفت بیت و تبدیل نوع، به bytes20
تبدیل میگردد.
چرا اتریوم از SHA-256 و RIPEMD-160 پشتیبانی میکند؟ در حالی که اتریوم بهطور گسترده از تابع keccak256
برای هش کردن استفاده میکند، بیت کوین مبتنی بر SHA-256
طراحی شده است. همچنین، آدرسهای بیت کوین برای فشردهتر شدن، ابتدا کلید عمومی را با SHA-256
هش میکنند و سپس خروجی را با RIPEMD-160
دوباره هش میزنند تا یک مقدار ۲۰ بایتی بهدست آید.
در اتریوم نیز، الگویی مشابه وجود دارد. این شبکه برای ساختن آدرس، از آخرین ۲۰ بایت هش keccak256
کلید عمومی استفاده میکند (یعنی همان ۱۶۰ بیت، که با خروجی RIPEMD-160
برابری میکند). بنابراین، وجود این توابع هش در پروتکل اتریوم، امکان تعامل آسانتر با ساختار بیت کوین و پروتکلهای مشابه را فراهم میسازد.
استفاده از اسمبلی Yul در فراخوانی قرارداد precompiled در سالیدیتی
وقتی اندازه خروجی از قبل مشخص است، دیگر نیازی به استفاده از دستور returndatasize
وجود ندارد. در اسمبلی Yul (و همینطور در سطح opcode ماشین مجازی)، تابع staticcall
شش آرگومان دریافت میکند:
-
میزان گس برای اختصاص دادن
-
آدرس تابع مقصد
-
محل داده ورودی در حافظه
-
اندازه داده ورودی (در اینجا: 32 بایت)
-
محل ذخیره خروجی در حافظه
-
اندازه خروجی مورد انتظار
در مثال زیر، ابتدا عدد uint256
مورد نظر را در حافظه ذخیره میکنیم و سپس همان داده را به آدرس 2
(قرارداد پیش کامپایل شده SHA-256) ارسال میکنیم تا هش شود.
1 2 3 4 5 6 7 8 9 10 11 |
function hashSha256Yul(uint256 numberToHash) public view returns (bytes32) { assembly { mstore(0, numberToHash) // store number in the zeroth memory word let ok := staticcall(gas(), 2, 0, 32, 0, 32) if iszero(ok) { revert(0,0) } return(0, 32) } } |
آدرس 0x04: تابع Identity (کپی حافظه)
قرارداد از پیش کامپایل شده (precompiled) identity
برای کپیکردن بخشی از حافظه به بخشی دیگر به کار میرود. در اتریوم، هیچ opcode مستقلی با نام memcopy
(برای کپی مستقیم حافظه) وجود ندارد. در حالت عادی، برای کپی کردن داده از یک بخش حافظه به بخش دیگر، باید ابتدا با دستور MLOAD
داده را روی استک بارگذاری کنید، سپس با MSTORE
آن را به موقعیت جدید منتقل کنید. این کار باید برای هر ۳۲ بایت (یک “کلمه” حافظه) بهصورت جداگانه انجام شود.
اما با استفاده از قرارداد پیش کامپایل شده identity
، میتوان یک بخش پیوسته از حافظه را که شامل چندین کلمه ۳۲ بایتی است، بهصورت یکجا کپی کرد. بدون نیاز به پردازش بایت به بایت یا دستورهای جداگانه برای هر کلمه.
این ویژگی بهویژه در مواقعی که نیاز به انتقال سریع یا بهینه سازی حافظه وجود دارد، بسیار مفید است و باعث صرفهجویی در گس مصرفی و سادهتر شدن منطق کدنویسی میشود.
آدرس 0x05: تابع Modexp
در الگوریتم ECDSA که در اتریوم استفاده میشود، امکان رمزنگاری عمومی (Public Encryption) وجود ندارد. اگر یک کاربرد نیاز به چنین قابلیتی داشته باشد، باید از رمزنگاری سنتی RSA استفاده کند.
در سطح مفهومی، RSA به این صورت عمل میکند: ابتدا پیام را به توان کلید عمومی گیرنده میرسانند، سپس حاصل را در یک عدد بسیار بزرگ (Modulus) پیمانه (modulo) میگیرند. خروجی این عملیات همان پیام رمزنگاری شده است.
اما چون این فرآیند فقط برای پیامهای بسیار کوتاه مناسب است، معمولاً در عمل از RSA فقط برای رمزنگاری یک کلید متقارن (مثلاً کلید AES-256) استفاده میشود و بعد از آن، پیام اصلی با همان کلید متقارن رمزگذاری میشود.
فرآیند امضا در RSA بهصورت معکوس انجام میشود: فرستنده، هش پیام را به توان کلید خصوصی خود میرساند و نتیجه را پیمانهگیری میکند. این مقدار همان امضای دیجیتال پیام است. دریافتکننده میتواند با استفاده از کلید عمومی فرستنده، امضا را بررسی کند و مطمئن شود که مقدار آن با هش پیام مطابقت دارد.
اتریوم بهصورت داخلی زیرساختی برای مدیریت کلید عمومی RSA ندارد. اما یک آدرس اتریومی میتواند با استفاده از کلید خصوصی RSA خود، آدرس اتریوم را امضا کند و به این شکل مالکیت کلید عمومی RSA را اثبات نماید.
توجه کنید که این کار برعکس قابل انجام نیست. چون در الگوریتم ECDSA هر کسی میتواند هر رشتهای (از جمله کلید عمومی RSA) را امضا کند، امضای یک کلید RSA با ECDSA نمیتواند اعتبار یا مالکیت آن را اثبات کند و از نظر امنیتی قابل اتکا نیست.
مثال از استفاده modexp
در سالیدیتی:
1 2 3 4 5 6 |
function modExp(uint256 base, uint256 exp, uint256 mod) public view returns (uint256) { bytes memory precompileData = abi.encode(32, 32, 32, base, exp, mod); (bool ok, bytes memory data) = address(5).staticcall(precompileData); require(ok, "expMod failed"); return abi.decode(data, (uint256)); } |
آدرسهای 0x06، 0x07 و 0x08: ecAdd، ecMul و ecPairing (مطابق EIP-196 و EIP-197)
توسعه دهندگان این قراردادهای precompiled در سالیدیتی را برای افزایش کارایی الگوریتمهای رمزنگاری مبتنی بر اثباتهای بدون افشا (Zero-Knowledge Proofs) طراحی کرده اند. در واقع، می توانید هر سه مورد را در ماژول تأییدگر اثباتهای بدون افشای پروژه Tornado Cash ببینید:
-
Elliptic Curve Addition: فراخوانی با staticcall به آدرس address(6)
-
Elliptic Curve Multiplication: فراخوانی با staticcall به آدرس address(7)
-
Elliptic Curve Pairing: فراخوانی با staticcall به آدرس address(8)
این عملیات فقط روی منحنیهای خاص BN-128 Barreto-Naehrig قابل استفاده هستند. این منحنیها با منحنیهایی که در امضاهای دیجیتال بهکار میروند، یکسان نیستند.
اتریوم با پیاده سازی EIP-196 توابع ecAdd
و ecMul
را معرفی کرد و تابع ecPairing
را نیز از طریق EIP-197 به پروتکل اضافه کرد.
هزینه گس برای ecAdd، ecMul و ecPairing
EIP-1108 با هدف بهینه سازی، هزینه گس این قراردادهای precompiled در سالیدیتی را نسبت به نسخه اولیه کاهش داده است. برای دسترسی به اطلاعات بهروز در مورد هزینههای گس این توابع، بهتر است به جای مراجعه به مستندات EIP-های اصلی، به متن EIP-1108 مراجعه کنید.
آدرس 0x09: تابع هش Blake2 (مطابق EIP-152)
Blake2 تابع هش اصلی مورد استفاده در شبکه Zcash است. مشابه توابع SHA-256 و RIPEMD-160، تابع Blake2 نیز بهمنظور ایجاد امکان اعتبارسنجی ادعاها درباره تراکنشهای بلاکچین Zcash به اتریوم افزوده شد. توسعه دهندگان این قرارداد را در قالب EIP-152 معرفی کردند و نمونه کد آن را نیز در متن همان پیشنهاد منتشر کردهاند.
آدرس 0x0a: از پیش کامپایل ارزیابی نقطهای (مطابق EIP-4844)
هاردفورک Decun یک قرارداد از پیش کامپایل جدید را در آدرس 10 (0x0a) اضافه کرد. این تابع برای بررسی تعهدات KZG بهکار میرود. به این صورت که در صورت دریافت یک “blob commitment” همراه با یک اثبات zero-knowledge، اگر اثبات نامعتبر باشد، تابع به حالت revert میرود و اجرای آن متوقف میشود.
قراردادهای precompiled در سایر بلاکچینها
توسعه دهندگان قراردادهای هوشمند باید هنگام انتقال کد سالیدیتی به سایر شبکه های سازگار با EVM، بسیار دقت کنند. دلیل آن این است که ممکن است قراردادهای precompiled در آن شبکه ها دقیقاً با اتریوم یکسان نباشند.
برای مثال، در شبکه zkSync توابعی مانند ecrecover
و سایر قراردادهای رمزنگاری از پیش کامپایل شده (precompiled) پشتیبانی نمیشوند. علت فنی این موضوع این است که بیشتر الگوریتمهای رمزنگاری برای محیطهای مبتنی بر اثباتهای zero-knowledge بهینه نیستند و تأیید آنها در این چارچوب بسیار پرهزینه است.
راستی! برای دریافت مطالب جدید در کانال تلگرام یا پیج اینستاگرام سورس باران عضو شوید.
- انتشار: ۸ مرداد ۱۴۰۴
دسته بندی موضوعات
- آموزش ارز دیجیتال
- آموزش برنامه نویسی
- آموزش متنی برنامه نویسی
- اطلاعیه و سایر مطالب
- پروژه برنامه نویسی
- دوره های تخصصی برنامه نویسی
- رپورتاژ
- فیلم های آموزشی
- ++C
- ADO.NET
- Adobe Flash
- Ajax
- AngularJS
- apache
- ARM
- Asp.Net
- ASP.NET MVC
- AVR
- Bootstrap
- CCNA
- CCNP
- CMD
- CSS
- Dreameaver
- EntityFramework
- HTML
- IOS
- jquery
- Linq
- Mysql
- Oracle
- PHP
- PHPMyAdmin
- Rational Rose
- silver light
- SQL Server
- Stimulsoft Reports
- Telerik
- UML
- VB.NET&VB6
- WPF
- Xml
- آموزش های پروژه محور
- اتوکد
- الگوریتم تقریبی
- امنیت
- اندروید
- اندروید استودیو
- بک ترک
- بیسیک فور اندروید
- پایتون
- جاوا
- جاوا اسکریپت
- جوملا
- دلفی
- دوره آموزش Go
- دوره های رایگان پیشنهادی
- زامارین
- سئو
- ساخت CMS
- سی شارپ
- شبکه و مجازی سازی
- طراحی الگوریتم
- طراحی بازی
- طراحی وب
- فتوشاپ
- فریم ورک codeigniter
- فلاتر
- کانستراکت
- کریستال ریپورت
- لاراول
- معماری کامپیوتر
- مهندسی اینترنت
- هوش مصنوعی
- یونیتی
- کتاب های آموزشی
- Android
- ASP.NET
- AVR
- LINQ
- php
- Workflow
- اچ تی ام ال
- بانک اطلاعاتی
- برنامه نویسی سوکت
- برنامه نویسی موبایل
- پاسکال
- پایان نامه
- پایتون
- جاوا
- جاوا اسکریپت
- جی کوئری
- داده کاوی
- دلفی
- رباتیک
- سئو
- سایر کتاب ها
- سخت افزار
- سی اس اس
- سی پلاس پلاس
- سی شارپ
- طراحی الگوریتم
- فتوشاپ
- مقاله
- مهندسی نرم افزار
- هک و امنیت
- هوش مصنوعی
- ویژوال بیسیک
- نرم افزار و ابزار برنامه نویسی
- وردپرس