در پروژه های بلاکچینی، توسعه دهنده ها معمولاً قراردادهایی طراحی می کنند که به گروه خاصی از آدرس ها اجازه دسترسی ویژه می دهد. این قراردادها با عناوینی مثل ایردراپ، پیش فروش، لیست سفید یا لیست مجاز شناخته می شوند. اما هدف همه آن ها یکی است: فراهم کردن امکان خرید یک توکن پرتقاضا با قیمت مناسب (و گاهی حتی رایگان) برای مجموعه ای خاص از کاربران.
سه روش رایج برای کنترل دسترسی
سه روش متداول برای پیاده سازی این سازوکار وجود دارد: مپ ها (Mappings)، درخت های مرکل (Merkle Trees) و امضاهای دیجیتال ECDSA.
محققان و توسعه دهنده ها پیش از این مزایا و معایب این روش ها را به طور کامل بررسی کرده اند. در اینجا فقط خلاصه ای از نتایج آن تحلیل ها ارائه می شود:
-
تأیید امضای ECDSA حدود 29,293 گس مصرف می کند
-
استفاده از گواهی مرکل در یک درخت با 128 آدرس حدود 30,517 گس نیاز دارد
عملکرد روش Mapping
در روش mapping، توسعه دهنده باید آدرس های مجاز را به صورت دستی در یک mapping ثبت کند. این mapping برای هر آدرس یک مقدار بولی تعیین می کند که مشخص می کند آن آدرس مجاز به خرید هست یا نه. پس از انجام تراکنش توسط خریدار، سیستم مقدار مربوط به آن آدرس را به false تغییر می دهد تا از تکرار استفاده جلوگیری شود.
این روش از نظر مصرف گس برای خریدار بسیار به صرفه است، اما فروشنده برای اضافه کردن هزاران آدرس باید میلیون ها گس مصرف کند؛ چرا که فقط برای افزودن یک آدرس، حداقل 22,100 گس نیاز است.
مزایا و معایب روش ECDSA
بسیاری از توسعه دهنده ها ترجیح می دهند از روش ECDSA استفاده کنند، که از تابع از پیش کامپایل شده ecrecover
در اتریوم یا نسخه امن تر آن در کتابخانه OpenZeppelin بهره می گیرد.
اجرای این روش در مجموع حدود 29,293 گس هزینه دارد که 21,000 گس آن مربوط به اجرای تراکنش است؛ بنابراین هزینه خالص تأیید امضا برابر با 8,293 گس خواهد بود. این عدد همچنین شامل بارگذاری آدرس امضا کننده از حافظه است که برای امکان باطل کردن امضاها ضروری می باشد.
وضعیت درخت های مرکل از نظر مصرف گس
درخت های مرکل بسته به اندازه درخت، هزینه های متفاوتی دارند. هرچه تعداد آدرس ها بیشتر باشد، گواهی مرکل هم بزرگ تر و پرهزینه تر خواهد شد. اگر تعداد آدرس ها بیش از 1,000 باشد، تأیید یک آدرس حداقل 32,000 گس نیاز خواهد داشت. بنابراین از نظر بهینه بودن مصرف گس، این روش ضعیف تر از ECDSA عمل می کند.
معیارهای یک راهکار جایگزین ایده آل
هدف این مقاله این است که مصرف گس در روش جدید از 8,293 گس مورد نیاز ECDSA کمتر باشد. برای این که مقایسه منصفانه باشد، روش جایگزین باید شرایط زیر را داشته باشد:
-
باید بتواند آدرس های مجاز شده را لغو کند. توسعه دهنده می تواند ریشه درخت مرکل را تغییر دهد، آدرس امضا کننده را در ECDSA عوض کند یا مقدار mapping را false کند.
-
نباید فروشنده را با هزینه بالا مواجه کند (برخلاف mapping)
-
باید برای خریدار کمتر از 8,200 گس هزینه داشته باشد (شامل بارگذاری از حافظه)
-
باید از نظر امنیتی بدون نقص باشد
پیشنیازها
برای درک روش پیشنهادی، خواننده باید با موضوعات زیر آشنایی داشته باشد:
-
قراردادهای هوشمند از پیش کامپایل شده (Precompiled Smart Contracts) — (StackExchange, recommended reading)
-
قراردادهای متامورفیک (Metamorphic Contracts) — (introduction, recommended reading)
-
لیست های دسترسی (Access Lists) — recommended reading
-
نحوه استفاده از امضاهای دیجیتال و کاربرد صحیح آن ها در اپلیکیشن های مبتنی بر بلاکچین
-
نحوه محاسبه هزینه گس، بهویژه در ارتباط با
calldata
، کدهای اجرایی (opcodes)، استفاده از حافظه (memory) و فضای ذخیره سازی (storage)
الگوریتم RSA در سالیدیتی
برای این که بتوانیم بهطور چشمگیر عملکرد ECDSA را از نظر مصرف گس شکست دهیم، باید سراغ یک الگوریتم رمزنگاری متفاوت برویم که امکان اثبات عضویت در یک مجموعه را فراهم کند. در واقع، ECDSA نسخه جدیدتر و مدرنتر الگوریتم اصلی امضای دیجیتال یعنی RSA است.
الگوریتم ECDSA بر این فرض استوار است که حل لگاریتم گسسته روی منحنی های بیضوی (Elliptic Curves) دشوار است، به همین دلیل نام آن Elliptic Curve Digital Signature Algorithm انتخاب شده است. در مقابل، الگوریتم RSA (که نام آن برگرفته از نام نویسندگانش یعنی Rivest، Shamir و Adleman است) به این اصل تکیه دارد که تجزیه اعداد صحیح بزرگ به عوامل اول، بسیار سخت است.
از نظر تاریخی، RSA در دهه ۱۹۷۰ منتشر شد، اما الگوریتم ECDSA در اوایل دهه ۲۰۰۰ به عنوان یک استاندارد رسمی معرفی شد.
مروری بر RSA
در این بخش، وارد جزئیات کامل RSA نمیشویم، اما برای درک ادامه بحث، آشنایی با برخی مفاهیم پایه ضروری است.
در گام اول، امضا کننده دو عدد اول بسیار بزرگ به نام های p
و q
انتخاب می کند و آن ها را در هم ضرب می کند تا عدد n
به دست آید. این عدد n
اولین بخش از کلید عمومی (public key) محسوب می شود. سپس، امضا کننده یک عدد اول کوچکتر به نام e
انتخاب می کند (برای کاربرد ما می توان مقدار آن را ثابت و برابر ۳ قرار داد)، و جفت (n, e)
را به عنوان کلید عمومی منتشر می کند.
در پشت صحنه، امضا کننده مراحل زیر را محاسبه می کند:
1 2 |
t = (p - 1) * (q - 1) d = t^(-1) % n |
عدد d
به عنوان کلید خصوصی (private key) عمل می کند. اگر یک مهاجم بتواند عدد n
را به عوامل اول p
و q
تجزیه کند، محاسبه d
برای او بسیار ساده خواهد بود. اما همانطور که در مباحث رمزنگاری در برنامه نویسی پیشرفته مطرح میشود، تجزیه اعداد صحیح بزرگ به عوامل اول یکی از سختترین مسائل محاسباتی شناختهشده است.
برای امضا کردن یک پیام، امضا کننده ابتدا پیام m
را هش می کند تا مقدار h
به دست آید، و سپس h
را به توان d
رسانده و با n
پیمانه می گیرد. یعنی:
1 |
s = h(m) ^ d % n |
در نهایت، امضا کننده جفت (m, s)
را به عنوان پیام و امضا منتشر می کند.
برای تأیید امضا، گیرنده نیز پیام m
را هش می کند و مقدار هش را به توان e
می رساند و با n
پیمانه می گیرد. توجه داشته باشید که e
و n
همان کلید عمومی هستند. اگر و فقط اگر رابطه زیر برقرار باشد:
1 |
s == s ^ e % n |
n
بسیار بزرگ است، احتمال این که این تساوی به صورت تصادفی برقرار باشد، تقریباً صفر است. بنابراین اگر این بررسی موفق باشد، می توان با اطمینان گفت که امضا برای کلید عمومی (n, e)
معتبر است.
کاربرد این منطق در اتریوم
برای استفاده از این روش در اتریوم، کافی است آدرس خریدار را به صورت زیر امضا کنیم:
1 |
s = buyerAddress ^ d % n |
1 |
msg.sender == s ^ e % n |
استفاده از کلیدهای ۲۵۶ بیتی ناامن است
وقتی میگوییم “تجزیه اعداد صحیح به عوامل اول سخت است”، این گزاره تنها زمانی صحیح خواهد بود که عدد مورد نظر به اندازه کافی بزرگ باشد. برای مثال، عدد ۳۳ از حاصلضرب دو عدد اول تشکیل شده و تجزیه آن بسیار ساده است. خوشبختانه، برای ارزیابی توانایی ابزارهای موجود در شکستن RSA، میتوانیم به نتایج واقعی “چالش تجزیه RSA (RSA Factoring Challenge)” استناد کنیم.
برای شفافسازی، منظور از “تعداد بیت ها” در اینجا، اندازه کلید عمومی است. بنابراین وقتی میگوییم “RSA 2048″، یعنی عدد n
که به عنوان پیمانه (modulus) در الگوریتم استفاده میشود، دارای 2048 بیت است. در مقایسه اندازه کلیدها باید توجه داشت که هر بیت اضافی، اندازه عدد را دو برابر میکند. پس کلید 700 بیتی به صورت نمایی امنتر از کلید 350 بیتی است، نه فقط دو برابر امنتر.
تا این لحظه، بزرگترین کلید RSA که موفق به تجزیه آن شدهاند، دارای 829 بیت بوده است. برای انجام این عملیات، تیم تحقیقاتی از یک ابرکامپیوتر مدرن استفاده کرده است. آنها مجموعاً حدود ۲۷۰۰ سال-هسته پردازشی (CPU core-years) صرف کردند، آن هم با پردازندهای از نوع Intel Xeon Gold 6130 با فرکانس 2.1 گیگاهرتز.
برای درک بهتر هزینه این عملیات، کافی است بدانید ارزانترین پردازنده ۱۶ هستهای در سرویس AWS حدود ۰.۴۰ دلار در ساعت هزینه دارد. بنابراین، هزینه تقریبی برای شکستن این کلید چیزی در حدود ۹.۴ میلیون دلار برآورد میشود. حتی اگر تخفیفهای سخاوتمندانه از سمت ارائهدهنده سرویس ابری در نظر گرفته شود، باز هم هزینه نهایی در حد میلیونها دلار خواهد بود.
محاسبات ماژولار با بیش از ۲۵۶ بیت در سالیدیتی
اتریوم تنها از انواع دادهای ۳۲ بایتی پشتیبانی میکند. بنابراین به صورت پیشفرض امکان اجرای عملیات زیر را نداریم:
1 |
s ^ e % n |
خوشبختانه، بلاکچین اتریوم در قالب EIP-198 یک قرارداد از پیشکامپایلشده معرفی کرده است که به طور خاص برای پشتیبانی از محاسبات ماژولار (Modular Arithmetic) طراحی شده است. برای استفاده از آن، باید پایه (base)، توان (exponent) و ضریب (modulus) را به صورت کدگذاریشده با ABI در حافظه قرار دهیم، سپس قرارداد موجود در آدرس 0x05
را فراخوانی کنیم.
چالش ذخیره کلید عمومی
اگر بخواهیم از یک اندازه کلید امن استفاده کنیم، مثلاً 1024 بیت، ذخیره کلید عمومی به مشکلی جدی تبدیل میشود. چنین کلیدی به چهار اسلات ذخیره سازی (storage slots) نیاز دارد. برای خواندن این کلید از حافظه، باید چهار بار عملیات SLOAD
انجام دهیم که مجموعاً حدود 8,400 گس هزینه خواهد داشت. این مقدار بهتنهایی از روش ECDSA که پیشتر بررسی شد ناکارآمدتر است.
استفاده از متغیرهای غیرقابلتغییر (immutable
) تا حد زیادی این هزینه را حذف میکند. اما در این روش یک نقطه ضعف مهم وجود دارد: دیگر نمیتوان یک آدرس خاص را بعداً از لیست پیشفروش حذف کرد. در روشهای سنتی مانند ECDSA یا درخت مرکل، تنها کافی است آدرس امضاکننده یا ریشه مرکل را جایگزین کنیم. ولی وقتی از یک متغیر immutable
استفاده میکنیم، چنین امکانی وجود ندارد.
راهکار: ذخیره کلید عمومی در بایت کد
ایده کلیدی این است که بهجای ذخیره کلید عمومی در فضای ذخیره سازی (storage)، آن را مستقیماً در بایت کد قرارداد خارجی قرار دهیم. با این روش، برای خواندن کلید، از دستور EXTCODECOPY استفاده میکنیم که فقط 2,600 گس هزینه دارد. بسیار کمتر از 8,400 گس روش قبل.
برای باطل کردن یک کلید عمومی، کافی است یک قرارداد جدید با کلید متفاوت ایجاد کنیم و سپس آدرس قرارداد جدید را در یک متغیر ذخیره ای (storage variable) بهروزرسانی کنیم. البته این عملیات خودش 2,100 گس به هزینه اضافه میکند.
اما نکته جالب اینجاست: میتوان آدرس قرارداد خارجی (که کلید عمومی را در بایت کد خود ذخیره کرده) را در یک متغیر immutable
نگه داشت، و در عین حال کلید عمومی را با تغییر بایت کد آن قرارداد خارجی باطل کرد. این یعنی بدون تغییر آدرس، همچنان میتوانیم کلید را بیاعتبار کنیم.
بیاعتبارسازی کلید عمومی با الگوی قرارداد متامورفیک (Metamorphic Contract)
برای ایجاد یک قرارداد هوشمند، ابتدا بایت کد مربوط به استقرار (deployment bytecode) را در حافظه بارگذاری میکنیم، سپس بازهای از آن را که شامل کد اجرایی (runtime code) است، بازمیگردانیم.
زمانی که از دستور create2
برای ساخت قرارداد استفاده میکنیم، آدرس قرارداد قبل از استقرار قابل پیشبینی خواهد بود. این آدرس از ترکیب سه مؤلفه بهدست میآید: salt، آدرس ایجادکننده قرارداد (deployer) و بایت کد اولیه (initialization bytecode). اگر این قرارداد خود را نابود کند (selfdestruct
)، میتوانیم یک قرارداد جدید با همان آدرس قبلی ایجاد کنیم.
نکته مهم اینجاست که آدرس قرارداد تابعی از کد اولیه است، نه کدی که در نهایت مستقر میشود. بنابراین میتوان بایت کد متفاوتی را مستقر کرد؛ کافی است کد اولیه طوری نوشته شود که نسخهای متفاوت از کد اجرایی را بارگذاری کند. در نتیجه، با selfdestruct کردن و سپس استقرار مجدد با همان کد اولیه اما با کد اجرایی متفاوت، میتوان بایتکد قرارداد را بهصورت پویا تغییر داد.
ایده اصلی: قرارداد قابل تغییر با کلید عمومی داخلی
در این الگو، میتوانیم یک قرارداد هوشمند طراحی کنیم که بخش اول بایت کد آن شامل منطق selfdestruct تحت یک شرط خاص باشد، و بخش باقیمانده از بایتکد صرفاً شامل کلید عمومی RSA باشد.
از آنجایی که آدرس این قرارداد قبل از استقرار قابل پیشبینی است، میتوان آدرس آن را در یک متغیر immutable
ذخیره کرد. هر زمان به کلید عمومی نیاز داشتیم، از دستور EXTCODECOPY
برای خواندن بایتکد این قرارداد استفاده میکنیم.
برای جایگزین کردن کلید عمومی، کافی است به قرارداد دستور selfdestruct بدهیم، و سپس یک قرارداد جدید با بایتکد جدید اما همان آدرس مستقر کنیم.
صرفهجویی ۱۰۰ گس اضافی با استفاده از Access List
پیشنهاد بهبود EIP-2930 نوع جدیدی از تراکنش ها را به اتریوم اضافه کرد که به کاربر اجازه میدهد از پیش مشخص کند که کدام آدرس ها و اسلات های ذخیره سازی (storage slots) قرار است مورد استفاده قرار گیرند. این قابلیت به نودهای شبکه اجازه میدهد که مقادیر مربوط به آن آدرس ها و داده ها را از قبل واکشی (prefetch) کنند و در نتیجه، زمان اجرای تراکنش را کاهش دهند.
هنگامی که از این نوع تراکنش برای فراخوانی یک قرارداد خارجی استفاده میکنیم، میتوانیم حدود ۱۰۰ گس در هر اجرا صرفهجویی کنیم.
نکته مهم این است که این صرفهجویی زمانی اعمال نمیشود که یک قرارداد هوشمند به متغیرهای ذخیره سازی داخلی خودش دسترسی داشته باشد. اما در طراحی فعلی ایردراپ پیشفروش مبتنی بر RSA، کلید عمومی در یک قرارداد خارجی ذخیره میشود. بنابراین استفاده از Access List در این سناریو کاملاً منطقی و مؤثر خواهد بود.
مقایسه عملکرد: هزینه گس در برابر اندازه کلید
بخش عمدهای از هزینه گس در این طراحی، ناشی از بزرگ بودن calldata است؛ چرا که امضاهای RSA در سالیدیتی به دلیل اندازه کلید، فضای زیادی اشغال میکنند. به عنوان مثال، اگر اندازه کلید روی 1024 بیت تنظیم شود، آنگاه طول کال دیتا برابر با 128 بایت خواهد بود. در اتریوم، هر بایت از کال دیتا ۱۶ گس هزینه دارد، بنابراین فقط برای ارسال چنین کالدیتایی باید ۲,۰۴۸ گس پرداخت شود.
در مقایسه با اکثر کاربردهای رایج، این سیستم از حافظه بیشتری استفاده میکند، و اتریوم برای استفاده از حافظه نیز هزینه جداگانهای دریافت میکند.
چرا این روش نسبت به ECDSA گس کمتری مصرف میکند؟
بررسیهای عددی نشان میدهند که هرچه اندازه کلید، و بهتبع آن اندازه امضا بزرگتر باشد، هزینه گس نیز افزایش پیدا میکند. مزیت اصلی این طراحی در بهرهگیری از ساختار محاسباتی قرارداد از پیشکامپایلشده RSA در اتریوم است. این قرارداد، زمانی که پایه عدد به توان کوچکی برسد، اجرای آن تنها چند صد گس هزینه دارد؛ در حالی که اجرای معادل آن برای ECDSA نیاز به چند هزار گس دارد. این تفاوت چشمگیر، بهویژه در مقیاس وسیع، صرفهجویی قابل توجهی ایجاد میکند.
انتخاب اندازه کلید: تعادل میان امنیت و کارایی
درست است که تاکنون یک کلید ۸۲۹ بیتی شکسته شده، اما این کار تنها با استفاده از یک ابرکامپیوتر قدرتمند و منابع محاسباتی بسیار بالا ممکن شده است. برای کاربردهایی مانند ایردراپ توکن های با ارزش پایین یا پیشفروش NFT، هیچ توجیه اقتصادی برای مهاجم وجود ندارد که میلیونها دلار صرف کند تا یک کلید عمومی را فاکتور بگیرد و در نهایت یک NFT دریافت کند.
در حال حاضر، گرانترین مجموعههای NFT اتریوم مانند Fidenza یا Bored Ape Yacht Club هر کدام حدود ۱۰۰ هزار دلار قیمت دارند. بنابراین در اکثر کاربردهای معمول، انجام چنین حملهای منطقی نیست.
با توجه به اینکه افزایش هر بیت، سختی تجزیه عدد را دو برابر میکند، میتوان گفت تا سال ۲۰۲۲ استفاده از کلید ۸۹۶ بیتی برای توکنهای با ارزش پایین به اندازه کافی ایمن محسوب میشود. در این شرایط، صرفهجویی بیش از ۲۵۰۰ گس برای هر کاربر در مقایسه با ECDSA، یک مزیت فنی بسیار مهم به شمار میرود.
بنچمارک اندازه کلید
-
RSA-896 (گس مصرفی: 26,850)
-
RSA-960 (گس مصرفی: 26,925)
-
RSA-1024 (گس مصرفی: 27,033)
-
RSA-2048 (گس مصرفی: 29,271)
نتیجهگیری
در این مقاله، یک روش جدید برای اختصاص آدرس ها در فرآیند پیشفروش یا ایردراپ معرفی شد که از نظر مصرف گس کارآمدتر از تمامی راهحلهای رایج فعلی است. با ترکیب قابلیت محاسبه توان ماژولار (modular exponentiation precompile)، الگوی قرارداد متامورفیک (metamorphic contract pattern)، و تراکنشهای مبتنی بر access list، میتوان امضاهای امن RSA در سالیدیتی را بهصورت on-chain و با مصرف گس بسیار بهینه تأیید کرد.
البته این پروژه در حال حاضر در مرحله اثبات مفهوم (Proof of Concept) قرار دارد. به همین دلیل، توصیه میشود که توسعهدهندگان هنگام استفاده از کد در محیطهای واقعی، احتیاط لازم را داشته باشند.
راستی! برای دریافت مطالب جدید در کانال تلگرام یا پیج اینستاگرام سورس باران عضو شوید.
- انتشار: ۸ مرداد ۱۴۰۴
دسته بندی موضوعات
- آموزش ارز دیجیتال
- آموزش برنامه نویسی
- آموزش متنی برنامه نویسی
- اطلاعیه و سایر مطالب
- پروژه برنامه نویسی
- دوره های تخصصی برنامه نویسی
- رپورتاژ
- فیلم های آموزشی
- ++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
- اچ تی ام ال
- بانک اطلاعاتی
- برنامه نویسی سوکت
- برنامه نویسی موبایل
- پاسکال
- پایان نامه
- پایتون
- جاوا
- جاوا اسکریپت
- جی کوئری
- داده کاوی
- دلفی
- رباتیک
- سئو
- سایر کتاب ها
- سخت افزار
- سی اس اس
- سی پلاس پلاس
- سی شارپ
- طراحی الگوریتم
- فتوشاپ
- مقاله
- مهندسی نرم افزار
- هک و امنیت
- هوش مصنوعی
- ویژوال بیسیک
- نرم افزار و ابزار برنامه نویسی
- وردپرس