یکی از موانع پیش روی پذیرش برنامههای غیرمتمرکز اتریوم این است که کاربران مجبورند برای ثبت تراکنشهای خود در بلاک چین، گس (کارمزد تراکنش) پرداخت کنند. برای مثال، برنامه غیرمتمرکز رایگیری داریم که به افراد امکان میدهد تا به کاندیدای موردنظر خود رای بدهند و رایها نیز در بلاک چین ذخیره شوند. کاربری که میخواهد رای خود را در بلاک چین ثبت کند باید کارمزد تراکنش پرداخت کند. این شرایط ایدهآلی نیست زیرا کاربران باید برای پرداخت گس، اتریوم داشته باشند، در حالی که تنها کاری که میخواهند انجام دهند یک اقدام ساده است که ارتباطی با انتقال پول هم ندارد. اما برای انجام تراکنش در بلاک چین، گزینه دیگری به غیر از پرداخت کارمزد وجود ندارد. اگر روش دیگری برای انجام ایمن تراکنش ها وجود داشته باشد و به فرد دیگری امکان داده شود که تراکنش را در بلاک چین را ثبت کند و برای این کار هزینه پرداخت کنیم چه شرایطی رخ میدهد؟
تو صرافی ارز پلاس میتونی فقط با ۱۰ هزار تومان و با کارمزد صفر، همه ارزهای دیجیتال رو معامله کنی!
در ادامه جزییاتی در خصوص نحوه اجرای چنین راهکاری برای برنامه غیرمتمرکز موردنظر ارائه میدهیم تا افراد بیشتری بتوانند از این روش در برنامههای غیرمتمرکز اتریوم خود استفاده کنند و آن را ارتقا دهند. در ادامه مباحث زیر مطرح میشود:
- بررسی سطح بسیار بالای رمزنگاری کلید عمومی و امضاهای دیجیتال که نکات بسیار مهم و کلیدی در خصوص درک و شناخت راهکار موردنظر هستند.
- جزییات راهکار موردنظر و مسیر جدید برنامه
- جزییات پیادهسازی راهکار موردنظر شامل فرانتاند js و کد قرارداد سالیدیتی
- مسائل و بهبودهای بالقوه
امضاهای دیجیتال
برای آنکه این راهکار عملکرد درستی داشته باشد، باید شناخت مقدماتی از نحوه عملکرد امضاهای دیجیتال داشته باشید. در ادامه به توضیح مفاهیم کلید عمومی و خصوصی و امضاهای دیجیتال در سطح بسیار پیشرفته خواهیم پرداخت اما به شدت توصیه میکنیم که جزییات بیشتری در این خصوص را بیاموزید.
رمزنگاری کلید عمومی یک سیستم رمزنگاری است که در آن ۲ کلید در اختیار دارید: کلید عمومی (Pu) و کلید خصوصی (Pr). میتوانید کلید عمومی خود را به افراد کل جهان ارائه دهید، اما کلید خصوصی را باید نزد خود حفظ کنید. برای مثال آدرس اتریوم شما یک کلید عمومی است (در واقع این آدرس از کلید عمومی به دست میآید، اما فعلا فرض میکنیم که همان کلید عمومی است) و کلید خصوصی در مرورگر یا در رایانه و تلفن همراه ذخیره میشود. همانطور که میدانید، برای آنکه فرد دیگری بتواند به شما اتر ارسال کند، باید آدرس کلید عمومی شما را داشته باشند. هرچند، فقط شما میتوانید به سرمایههای خود دسترسی داشته باشید، زیرا شما تنها فردی هستید که کلید خصوصی را در اختیار دارید.
رمزنگاری کلید عمومی، الگوریتمهایی دارد که به شما امکان میدهد تا با استفاده از جفت کلید خود به رمزگزاری، رمزگشایی، امضا و تایید پیامها بپردازید.
با ذکر یک مثال به توضیح امضا و تایید پیامها میپردازیم. فرض کنید کاربر کیم (Kiرمزنگاری کلید عمومی الگوریتم هایی دارد که به شما امکان می دهد پیام ها را با استفاده از جفت کلید خود رمزگذاری ، رمزگشایی ، امضا و تأیید کنید.m) دارای جفت کلید عمومی و خصوصی است.
Pu = “0x44ac12c1e3dfd8edaf83b6f65918229d5279a6f5”
Pr = “dbc226043e390cf39280e5edfd418d7ad61931c76509270867d300f110c46506”
به منظور امضای پیام، کیم تابع امضا (رای به آلیس) را اجرا میکند که خروجی آن یک رشته متشکل از حروف و اعداد است.
signature = 0x9127112de0033555c7f6508d963d484965a953844dfcff092712102c236467a25af57edc53b63880ea39af8ce7334f6d77a8206e805305e7c6ad919d12bfae5c1b
این امضای دیجیتال پیامی است که کیم با استفاده از کلید خصوصی خود، امضا کرده است.
اکنون هرکسی میتواند با اجرای تابع بررسی، مشخص کند که پیام “رای به آلیس” توسط کیم امضا شده است و خروجی این تابع نیز “0x44ac12c1e3dfd8edaf83b6f65918229d5279a6f5” است. اگر دقت کنید متوجه میشوید که این خروجی همان کلید عمومی کیم است. این موضوع به معنای آن است که پیام موردنظر قطعا توسط کیم امضا شده است. اگر در امضا یا پیام دستکاری کنید (حتی تغییر یک حرف یا عدد)، الگوریتم بررسی و تایید، خروجی کاملا متفاوتی را نسبت به کلید عمومی ارائه میدهد و بدین صورت متوجه خواهید شد که پیام دستکاری شده است، زیرا نتیجه با کلید عمومی کیم فرق میکند.
جزییات راهکار
اگر امضای دیجیتال را متوجه شدهاید، راهکار موردنظر ما بسیار آسان خواهد شد. در ادامه به بررسی نحوه عملکرد راهکار در برنامه رایگیری موردنظر ما میپردازیم تا کاربران کارمزد گس کمتری را بپردازند بدون آنکه رای آنها نیز لو برود. در نمودار زیر میتوانید تمام کاربران برنامه غیرمتمرکز و اقدامات آنها را مشاهده کنید.
۱- رایدهنده با امضای پیام با استفاده از کلید خصوصی، قصد خود برای رای دادن به کاندیدای موردنظر را نشان میدهد. رأیدهندگان تراکنش خود را در بلاک چین ثبت نمیکنند، در نتیجه کارمزد تراکنشی پرداخت نمیشود. قسمت صف پیامها (message queue) در نمودار فوق صرفا مکان برون زنجیرهای است که تمام جزییات رای در آن ذخیره میشود.
۲- هرکسی که به پرداخت کارمزد تراکنش تمایل داشته باشد (معمولا دارنده قرارداد)، امضا، اسم کاندیدا و آدرس حساب رایدهنده را دریافت کرده و آنها را در بلاک چین ثبت میکند.
۳- قرارداد هوشمند از تابع بررسی به منظور دریافت کلید عمومی بر اساس اسم کاندیدا و امضا استفاده میکند. اگر کلید عمومی به دستآمده مطابق با آدرس کاربری باشد که پیام را امضا کرده است، رای ثبت میشود در غیر اینصورت تراکنش انجام نمیشود.
جزییات پیادهسازی راهکار
اکنون به نحوه پیادهسازی راهکار میپردازیم.
گام اول: امضای پیام
گام اول امضای پیام توسط رایدهنده است. ما از تابع eth_signTypedData برای امضای پیام خود استفاده خواهیم کرد. این تابع در متامسک پیادهسازی شده است، بنابراین امضای پیام بسیار آسان میشود. کد امضای پیام به شرح زیر است:
window.voteForCandidate = function(candidate) {
let candidateName = $(“#candidate”).val();
let msgParams = [
{
type: ‘string’, // Any valid solidity type
name: ‘Message’, // Any string label you want
value: ‘Vote for ‘ + candidateName // The value to sign
}
]
var from = web3.eth.accounts[0]
var params = [msgParams, from]
var method = ‘eth_signTypedData’
console.log(“Hash is “);
console.log(sigUtil.typedSignatureHash(msgParams));
// Invoke the eth_signTypedData function and pass in the message and account address.
web3.currentProvider.sendAsync({
method,
params,
from,
}, function (err, result) {
if (err) return console.dir(err)
if (result.error) {
alert(result.error.message)
}
if (result.error) return console.error(result)
$(“#msg”).html(“User intends to vote for ” + candidateName + “. Any one can now submit the vote to the blockchain on behalf of this user. Copy the values”);
$(“#vote-for”).html(“Candidate: ” + candidateName);
$(“#addr”).html(“Address: ” + from);
$(“#signature”).html(“Signature: ” + result.result);
console.log(‘PERSONAL SIGNED:’ + JSON.stringify(result.result))
})
}
نکنه قابل ذکر این است که تابع eth_signTypedData به هش کردن پیام میپردازد و پیام هششده، امضا میشود.
گام دوم: ثبت رای امضاشده در بلاک چین
از آنجایی که برنامه موردنظر صرفا یک برنامه آزمایشی است، امضا و سایر جزییات را ذخیره نمیکنیم. پس از امضای پیام، اعلان آن نمایش داده میشود. هرکسی میتواند با استفاده از جزییات به ثبت پیام در بلاک چین بپردازد. کد ثبت رای در بلاک چین به شرح زیر است:
window.submitVote = function(candidate) {
let candidateName = $(“#candidate-name”).val();
let signature = $(“#vote-signature”).val();
let voterAddress = $(“#voter-address”).val();
$(“#msg”).html(“Vote has been submitted. The vote count will increment as soon as the vote is recorded on the blockchain. Please wait.”)
Voting.deployed().then(function(contractInstance) {
contractInstance.voteForCandidate(candidateName, voterAddress, signature, {gas: 140000, from: web3.eth.accounts[0]}).then(function() {
let div_id = candidates[candidateName];
console.log(div_id);
return contractInstance.totalVotesFor.call(candidateName).then(function(v) {
console.log(v.toString());
$(“#” + div_id).html(v.toString());
$(“#msg”).html(“”);
});
});
});
}
گام سوم: بررسی جزییات رای در قرارداد هوشمند
اکنون در قرارداد هوشمند بررسی میکنیم که آیا اطلاعات رای ثبتشده صحیح و معتبر است یا خیر.
pragma solidity ^0.4.18;
import “./ECRecovery.sol”;
contract Voting {
using ECRecovery for bytes32;
mapping (bytes32 => uint8) public votesReceived;
mapping(bytes32 => bytes32) public candidateHash;
mapping(address => bool) public voterStatus;
mapping(bytes32 => bool) public validCandidates;
function Voting(bytes32[] _candidateNames, bytes32[] _candidateHashes) public {
for(uint i = 0; i < _candidateNames.length; i++) {
validCandidates[_candidateNames[i]] = true;
candidateHash[_candidateNames[i]] = _candidateHashes[i];
}
}
function totalVotesFor(bytes32 _candidate) view public returns (uint8) {
require(validCandidates[_candidate]);
return votesReceived[_candidate];
}
function voteForCandidate(bytes32 _candidate, address _voter, bytes _signedMessage) public {
require(!voterStatus[_voter]);
bytes32 voteHash = candidateHash[_candidate];
address recoveredAddress = voteHash.recover(_signedMessage);
require(recoveredAddress == _voter);
require(validCandidates[_candidate]);
votesReceived[_candidate] += 1;
voterStatus[_voter] = true;
}
}
زپلین (Zeppelin) کتابخانه مفیدی به اسم ECRecovery دارد که از آن برای بررسی پیام امضاشده استفاده میکنیم. تابع voteForCandidate پیام امضاشده را بررسی کرده و در صورت تایید بررسی، تعداد رایها را بهروزرسانی میکند.
همانطور که به خاطر دارید به این موضوع اشاره کردیم که تابع eth_signTypedData قبل از امضای پیام به هش کردن آن میپردازد. تابع بازیابی سالیدیتی هیچگونه اطلاع و دانشی از تابع هشینگ استفاده شده در eth_signTypedData ندارد و در نتیجه نمیتواند به بررسی پیام رای به آلیس (Vote for Alice) بپردازد و باید هش پیام موردنظر را ایجاد کند و سپس آن را بررسی کند. به جای تولید هش در قرارداد، ما از قبل تمام پیامها را هش کرده و به کانستراکتور ارسال میکنیم تا انجام بررسی آسان شود. کد تولید هش به شرح زیر است:
مسائل و مشکلات بالقوه
هنگام توسعه یک برنامه غیرمتمرکز واقعی با استفاده از روش بیان شده در این مقاله، باید چند مسأله را مدنظر قرار داد. این مسائل عبارتند از:
۱- پیامهای امضاشده کجا ذخیره میشوند؟ میتوانید از سیستم صفبندی (queuing system) برای ذخیرهسازی این پیامها استفاده کنید.
۲- چه تضمینی وجود دارد که پیام امضاشده سرانجام در بلاک چین ثبت شود؟
۳- ذخیرهسازی هش تمام پیامها در بلاک چین، شرایط ایدهآلی را رقم نمیزند، بنابراین بهترین راهکار برای این موضوع چیست؟
نکته: ظاهراً API مرتبط با eth_signTypedData هنوز باثبات نیست و فقط توسط متامسک پیادهسازی شده است. بنابراین اگر در صدد استفاده از این روش هستید، لطفاً به این موضوع دقت کنید.