قرارداد precompiled در سالیدیتی چیست

در شبکه اتریوم، مجموعه‌ای از قراردادهای خاص با عنوان “قراردادهای 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) در اتریوم است که برای بازیابی آدرس امضاکننده از روی یک هش و امضای دیجیتال آن استفاده می‌شود. به بیان ساده، این تابع مشخص می‌کند چه کسی پیامی را امضا کرده، مشروط بر اینکه امضا معتبر باشد.

مثال:

نکته مهم: تابع ecrecover در صورتی که امضا نامعتبر باشد، عملیات را متوقف نمی‌کند (revert نمی‌شود). در عوض، مقدار address(0) را بازمی‌گرداند. به همین دلیل، بررسی صراحتی اعتبار امضا پس از فراخوانی این تابع کاملاً ضروری است.

توصیه می‌شود برای جلوگیری از خطاهای رایج در پردازش امضاها، از کتابخانه‌ OpenZeppelin استفاده کنید. این کتابخانه بسیاری از پیچیدگی‌های مرتبط با امضاهای دیجیتال را به‌طور ایمن مدیریت می‌کند.

چون اگر با نحوه عملکرد امضاها آشنا نباشید، ممکن است با خطاهایی مواجه شوید که امنیت یا صحت قرارداد شما را به خطر بیندازد.

آدرس های 0x02 و 0x03: توابع هش SHA-256 و RIPEMD-160

این دو قرارداد precompiled در سالیدیتی برای محاسبه هش روی داده های ارسالی به‌صورت مستقیم از طریق calldata طراحی شده‌اند. در اینجا مثالی از استفاده تابع SHA-256 را می‌بینید. برای ساده تر شدن، در این نمونه یک عدد uint256 را هش می‌کنیم:

و در این مثال، از تابع RIPEMD-160 استفاده شده است:
تابع RIPEMD-160 تنها ۲۰ بایت خروجی می‌دهد، اما ماشین مجازی اتریوم (EVM) فقط با مقادیر ۳۲ بایتی (256 بیت) کار می‌کند. به همین دلیل، خروجی ابتدا به صورت bytes32 دریافت می‌شود و سپس با استفاده از شیفت بیت و تبدیل نوع، به bytes20 تبدیل می‌گردد.

چرا اتریوم از SHA-256 و RIPEMD-160 پشتیبانی می‌کند؟ در حالی که اتریوم به‌طور گسترده از تابع keccak256 برای هش کردن استفاده می‌کند، بیت کوین مبتنی بر SHA-256 طراحی شده است. همچنین، آدرس‌های بیت کوین برای فشرده‌تر شدن، ابتدا کلید عمومی را با SHA-256 هش می‌کنند و سپس خروجی را با RIPEMD-160 دوباره هش می‌زنند تا یک مقدار ۲۰ بایتی به‌دست آید.

در اتریوم نیز، الگویی مشابه وجود دارد. این شبکه برای ساختن آدرس، از آخرین ۲۰ بایت هش keccak256 کلید عمومی استفاده می‌کند (یعنی همان ۱۶۰ بیت، که با خروجی RIPEMD-160 برابری می‌کند). بنابراین، وجود این توابع هش در پروتکل اتریوم، امکان تعامل آسان‌تر با ساختار بیت کوین و پروتکل‌های مشابه را فراهم می‌سازد.

استفاده از اسمبلی Yul در فراخوانی قرارداد precompiled در سالیدیتی

وقتی اندازه خروجی از قبل مشخص است، دیگر نیازی به استفاده از دستور returndatasize وجود ندارد. در اسمبلی Yul (و همین‌طور در سطح opcode ماشین مجازی)، تابع staticcall شش آرگومان دریافت می‌کند:

  1. میزان گس برای اختصاص دادن

  2. آدرس تابع مقصد

  3. محل داده ورودی در حافظه

  4. اندازه داده ورودی (در اینجا: 32 بایت)

  5. محل ذخیره خروجی در حافظه

  6. اندازه خروجی مورد انتظار

در مثال زیر، ابتدا عدد uint256 مورد نظر را در حافظه ذخیره می‌کنیم و سپس همان داده را به آدرس 2 (قرارداد پیش کامپایل شده SHA-256) ارسال می‌کنیم تا هش شود.

آدرس 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 در سالیدیتی:

آدرس‌های 0x06، 0x07 و 0x08: ecAdd، ecMul و ecPairing (مطابق EIP-196 و EIP-197)

توسعه دهندگان این قراردادهای precompiled در سالیدیتی را برای افزایش کارایی الگوریتم‌های رمزنگاری مبتنی بر اثبات‌های بدون افشا (Zero-Knowledge Proofs) طراحی کرده اند. در واقع، می توانید هر سه مورد را در ماژول تأییدگر اثبات‌های بدون افشای پروژه Tornado Cash ببینید:

این عملیات فقط روی منحنی‌های خاص 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 بهینه نیستند و تأیید آن‌ها در این چارچوب بسیار پرهزینه است.

5/5 - (1 امتیاز)

راستی! برای دریافت مطالب جدید در کانال تلگرام یا پیج اینستاگرام سورس باران عضو شوید.

پکیج صفر تا صد آموزش سئو و بهینه سازی بصورت عملی
  • انتشار: ۸ مرداد ۱۴۰۴

دسته بندی موضوعات

آخرین محصولات فروشگاه

مشاهده همه

نظرات

بازخوردهای خود را برای ما ارسال کنید