Skip to content

Verified voting #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions applications/verified-voting/ReadMe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Sponsored KYC voting


Any user can init a voting with answers "pros" or "cons" with verified users.
Any voting process should be registered at Voting Registrar Account

#### Voting initiator account, voting assets (VA) and Voting Registrar Account (VR) should be scripted
Scripts can guarantee:
1. Voter selected only one choice "pros" or "cons" (send a token to one of these addresses)
2. Voter can take part in voting only once
3. Vote initiator payed fee for voting procedure
5. Voting finished at max voting height
6. Minimal voting interval is 100 blocks


## Script and voting details

Voting Registrar Account should contain script that checks new voting registration process and users' voting
Before setting script to VR account data with addresses for voting answers should be created

Data format:

| Type | Key | Value |
| --------- |:--------:| ---------:|
| String | pros | Address |
| String | cons | Address |
| Binary| IssuerAccountScriptHash | H6pkCiqWzuHwQ1ktmusj9bNy5wsh3kvwdZx1Ch4NfoLi |

#### After applying VotingRegistrar script users can initiate votes.

#### Voting Initiator (VI) account sets a data with verified accounts

Data format:

| Type | Key | Value |
| --------- |:---------:| ---------:|
| Boolean | Address | true |

#### To register a new voting VI account should
1. Script own account using VotingInitiatorSmartAccount script
* Store tx id (setScriptTxId)
* Script is standard for all VI accounts.
* Script should not be modified as it's hash would be checked at voting registration process
2. Issue new asset with properties
* reissuable = false
* token decimals = 0
* quantity is equal to number of voters
* description = voting question
3. Send Waves to pay fees for all voters. Save transaction Id (feeTransferId)
Transaction properties:
* amount = token quantity * 900_000 (where 900_000 is fee for one transfer tx - smartaccount fee + smartasset fee)
* attachment = assetId
4. Sign and send DataTransaction from VR account.
Transaction properties:
* proof at index 0 = feeTransferId
* proof at index 1 = signature. Tx should be signed by Voting Initiator account
Data format:

| Type | Key | Value |
| --------- |:----------:| -----------------:|
| Integer | assetID | max voting height |
| Binary | VI Address | setScriptTxId |

5. If data appllied to VR account, send all issued token (amount = quantity) to VR account

Now you can start voting.

### Voting process
User should register as a voter at Voting Initiator account and send his vote from VR account
1. To register as a voter user signs (not send) a transfer transaction and saves tx id and signature
Transaction properties:
* amount = 1
* fee = 900_000
* attachment = voter public key
2. Sends a data transaction from VR account:
Transaction properties:
* proof at index 0 = Voter public key
* proof at index 1 = signature. Tx should be signed by Voter account
Data format:

| Type | Key | Value |
| --------- |:-------------:| ----------------------------:|
| Binary | Voter Address | Signature + TransferTxId |

Signature + TransferTxId = signature bytes should be placed first and TransferTxId bytes are concated to signature

3. Sends signed a transfer transaction to one of the voting results address.
23 changes: 23 additions & 0 deletions applications/verified-voting/VoteInitiatorSmartAccount.ride
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

#deny account data modification if account already voted
#at the voting start only (address, boolean) data stored to account
#voter should send binary with address as key and signature+vote transaction id as value

let this = extract(tx.sender)

match (tx) {
case d:DataTransaction =>
if (size(d.data) == 1 && isDefined(getBinary(this,d.data[0].key))) then
throw("account already voted")
else
if (isDefined(getBoolean(this, d.data[0].key))) then
(size(extract(getBinary(d.data, 0))) > 64 || throw("data not includes signature value"))
&& (addressFromPublicKey(d.proofs[0]) == addressFromString(d.data[0].key) || throw("proof at idx 0 should contain voter public key"))
&& (sigVerify(d.bodyBytes, d.proofs[1], d.proofs[0]) || throw("invalid tx signature"))
else
throw("account not in voting list")
case s:SetScriptTransaction =>
false
case _ =>
true
}
47 changes: 47 additions & 0 deletions applications/verified-voting/VotingAssetScript.ride
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
let voteBankPublicKey = fromBase58String("CXpiWubcdkB79QekRVaEXMVY8N12qP2f9zJp5sziGfge")
let voteBank = addressFromPublicKey(voteBankPublicKey)

match (tx) {
case t:TransferTransaction =>
let issueTx = transactionById(extract(t.assetId))
match (issueTx){
case issueTx:IssueTransaction =>
#asset can be transfered to voteReg account if vote registred in data: key - assetID, value - max voting height
#from voteReg account to specific vote-variants addresses - checked by vote bank account script
#tokens quantity should be equal to number of voters
#all issued tokens should be transfered to voteReg account
let regAssetForVoting = getInteger(voteBank, toBase58String(issueTx.id))
if (t.sender == issueTx.sender) then
(t.recipient == voteBank || throw("token recipient is not vote registrator acc"))
&&(isDefined(regAssetForVoting) || throw("asset should be registed before transfer"))
&& (!issueTx.reissuable || throw("token should not be reissuable"))
&& (issueTx.quantity == t.amount || throw("all issued quantity should be transfered to registrator"))
&& issueTx.decimals == 0
else
#voter should be registred using data tx to issuer address
#voter registration id should be equal to transfer (voting) transaction id
#if reg number exists in blockchain, then voter already voted
let voterPublicKey = t.attachment
let voterAddress = addressFromPublicKey(voterPublicKey)
let voterRegId = getBinary(issueTx.sender, toBase58String(voterAddress.bytes))
if (isDefined(voterRegId)) then
let voterSignature = take(extract(voterRegId), 64)
let voterTxId = drop(extract(voterRegId), 64)
if (t.id == voterTxId) then
t.senderPublicKey == voteBankPublicKey
&& (t.amount == 1 || throw("only one voice allowed"))
&& t.fee == 900000
&& (!isDefined(transactionById(voterTxId)) || throw("voter already voted"))
&& (sigVerify(t.bodyBytes, voterSignature, voterPublicKey) || throw("wrong signature"))
else
throw("wrong tx id registred")
else
throw("voter not registred")
case _ =>
false
}
case x:SetAssetScriptTransaction =>
throw("token script cannot be modified")
case _ =>
false
}
67 changes: 67 additions & 0 deletions applications/verified-voting/VotingRegistrator.ride
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
let voteBank = tx.sender
let minimalVotingHeight = 100

match (tx) {
case d:DataTransaction =>
if (size(d.data) == 2) then
#sender should place waves-transfer txId to pay fees for voting in proof[0] of data tx
#sender signature in proof[1]
let feeTransferId = transactionById(d.proofs[0])

match (feeTransferId) {
case fT:TransferTransaction =>
let votingHeight = extract(getInteger(d.data,0))
(!isDefined(getInteger(voteBank, d.data[0].key)) || throw("asset already registered"))
&&(!isDefined(getBinary(voteBank, d.data[1].key)) || throw("voting already registered"))
&& (fT.recipient == voteBank || throw("fee recipient not votebank"))
&& !isDefined(fT.assetId)
&& votingHeight > height
&& (votingHeight - height >= minimalVotingHeight || throw("voting interval should be more than 100 blocks"))

#script should guaranty that user payed fees for all transfers
#asset should not have decimals to prevent satoshi calculation
&& match(transactionById(fromBase58String(d.data[0].key))){
case i:IssueTransaction =>
let accountScriptedProof = extract(getBinary(d.data, 1))
let accountScriptHash = extract(getBinary(voteBank, "IssuerAccountScriptHash"))
let scriptTx = transactionById(accountScriptedProof)

if (fT.amount >= i.quantity * 900000) then
#voting initiator account should be scripted
match (scriptTx){
case s:SetScriptTransaction =>
((sha256(extract(s.script)) == accountScriptHash)
|| throw("script hash not fit requirements"))
case _ =>
throw("voting initiator not scripted, or script is wrong")
}
&& i.decimals == 0
&& fT.senderPublicKey == i.senderPublicKey
&& (d.data[1].key == toBase58String(i.sender.bytes) || throw("trying to reg asset of another issuer"))
&& (fromBase58String(d.data[0].key) == fT.attachment || throw("fee should be paid for voting asset, put assetId to attachment"))
&& (sigVerify(d.bodyBytes, d.proofs[1], fT.senderPublicKey) || throw("wrong signature"))
else
throw("minimum trasfer transaction amount is" + toString(i.quantity * 900000))
case _ =>
throw("data key at 0 index should contain voting asset id")
}
case _ =>
throw("proof at index 0 doesn't contain fee transfer transaction")
}
else
throw("data tx should contain two key-value pairs: 0 - assetId-maxVotingHeight, 1 - voting initiator setScriptTx id")
case t:TransferTransaction =>
if (isDefined(getInteger(voteBank, toBase58String(extract(t.assetId))))) then
let h = getInteger(voteBank, toBase58String(extract(t.assetId)))
(extract(h) > height)
&& (addressFromRecipient(t.recipient) == addressFromString(extract(getString(voteBank, "pros")))
|| addressFromRecipient(t.recipient) == addressFromString(extract(getString(voteBank, "cons"))))
&& isDefined(getString(voteBank, "pros"))
&& isDefined(getString(voteBank, "cons"))
else
throw("you cannot vote with this token. It's not registred")
case s:SetScriptTransaction =>
sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)
case _ =>
false
}