diff --git a/applications/verified-voting/ReadMe.md b/applications/verified-voting/ReadMe.md new file mode 100644 index 0000000..335f0b5 --- /dev/null +++ b/applications/verified-voting/ReadMe.md @@ -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. \ No newline at end of file diff --git a/applications/verified-voting/VoteInitiatorSmartAccount.ride b/applications/verified-voting/VoteInitiatorSmartAccount.ride new file mode 100644 index 0000000..56ef089 --- /dev/null +++ b/applications/verified-voting/VoteInitiatorSmartAccount.ride @@ -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 +} \ No newline at end of file diff --git a/applications/verified-voting/VotingAssetScript.ride b/applications/verified-voting/VotingAssetScript.ride new file mode 100644 index 0000000..24f260f --- /dev/null +++ b/applications/verified-voting/VotingAssetScript.ride @@ -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 +} \ No newline at end of file diff --git a/applications/verified-voting/VotingRegistrator.ride b/applications/verified-voting/VotingRegistrator.ride new file mode 100644 index 0000000..b0b7141 --- /dev/null +++ b/applications/verified-voting/VotingRegistrator.ride @@ -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 + } \ No newline at end of file