2020-02-01 18:17:32 +01:00
|
|
|
|
<template>
|
|
|
|
|
<div class="ceremony">
|
2020-02-08 16:18:00 +01:00
|
|
|
|
<h1 class="title is-size-1 is-size-2-mobile is-spaced">
|
2020-02-06 16:41:43 +01:00
|
|
|
|
Hello, <span>@{{ userHandle }}</span>
|
2020-02-01 18:17:32 +01:00
|
|
|
|
</h1>
|
2020-02-05 11:53:45 +01:00
|
|
|
|
<h2 class="subtitle">
|
2020-04-29 21:26:36 +02:00
|
|
|
|
How would you like to contribute to the Tornado.cash Trusted Setup Ceremony?
|
2020-02-05 11:53:45 +01:00
|
|
|
|
</h2>
|
2020-02-06 16:41:43 +01:00
|
|
|
|
<fieldset :disabled="status.type === 'is-success'">
|
|
|
|
|
<div class="columns is-centered">
|
|
|
|
|
<div class="column is-one-third">
|
|
|
|
|
<button
|
|
|
|
|
:class="{ 'is-hovered': contributionType === 'anonymous' }"
|
|
|
|
|
@click="onAnonymousHandler"
|
|
|
|
|
class="box box-anonymous"
|
|
|
|
|
>
|
|
|
|
|
<div class="title is-5">Anonymously</div>
|
|
|
|
|
<Cloak />
|
|
|
|
|
</button>
|
2020-02-01 18:17:32 +01:00
|
|
|
|
</div>
|
2020-02-06 16:41:43 +01:00
|
|
|
|
<div class="column is-one-third">
|
|
|
|
|
<div :class="{ 'is-hovered': isLoggedIn }" class="box">
|
2020-04-29 21:26:36 +02:00
|
|
|
|
<div class="title is-5">By using social account</div>
|
2020-02-06 16:41:43 +01:00
|
|
|
|
<Form />
|
2020-02-01 18:17:32 +01:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2020-02-06 16:41:43 +01:00
|
|
|
|
</fieldset>
|
2020-02-01 18:17:32 +01:00
|
|
|
|
|
2020-02-29 13:47:04 +01:00
|
|
|
|
<div v-show="contributionHash" class="status">
|
|
|
|
|
<div class="label">Your contribution hash (Blake2b)</div>
|
2020-03-02 15:57:39 +01:00
|
|
|
|
<b-field position="is-centered" class="has-addons contribution-hash">
|
|
|
|
|
<b-input
|
|
|
|
|
@click.native="copyContributionHash"
|
|
|
|
|
:value="contributionHash"
|
|
|
|
|
icon="copy"
|
|
|
|
|
readonly
|
|
|
|
|
></b-input>
|
2020-02-29 13:47:04 +01:00
|
|
|
|
</b-field>
|
2020-02-29 12:22:45 +01:00
|
|
|
|
</div>
|
2020-03-02 15:57:39 +01:00
|
|
|
|
<div v-show="status.type !== ''" class="status">
|
|
|
|
|
<div :class="status.type" class="status-message">{{ status.msg }}</div>
|
|
|
|
|
<div
|
|
|
|
|
v-show="status.type === 'is-success' && contributionType !== 'anonymous'"
|
|
|
|
|
class="status-message is-success"
|
|
|
|
|
>
|
2020-04-29 21:26:36 +02:00
|
|
|
|
And now you can post your attestation to Twitter.
|
2020-03-02 15:57:39 +01:00
|
|
|
|
<div class="buttons is-centered">
|
|
|
|
|
<b-button @click="makeTweet" type="is-primary" tag="a" target="_blank" outlined>
|
|
|
|
|
Post attestation
|
|
|
|
|
</b-button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2020-02-29 13:47:04 +01:00
|
|
|
|
<div v-show="authorizeLink" class="status">
|
2020-04-29 21:26:36 +02:00
|
|
|
|
You can still provide identity for your contribution by following
|
|
|
|
|
<a :href="authorizeLink" class="has-text-primary">this link</a>.
|
2020-02-29 12:22:45 +01:00
|
|
|
|
</div>
|
2020-02-01 18:17:32 +01:00
|
|
|
|
|
|
|
|
|
<div class="buttons is-centered">
|
|
|
|
|
<b-button
|
2020-02-05 11:53:45 +01:00
|
|
|
|
v-if="!isContributeBtnSnown"
|
2020-03-11 11:08:01 +01:00
|
|
|
|
@click="getUserRandom"
|
2020-02-05 11:53:45 +01:00
|
|
|
|
:disabled="isContributeBtnDisabled"
|
2020-02-01 18:17:32 +01:00
|
|
|
|
type="is-primary"
|
|
|
|
|
outlined
|
|
|
|
|
>
|
2020-04-29 21:26:36 +02:00
|
|
|
|
Contribute
|
2020-02-01 18:17:32 +01:00
|
|
|
|
</b-button>
|
|
|
|
|
</div>
|
2020-02-05 11:53:45 +01:00
|
|
|
|
<p class="p">
|
2020-04-29 21:26:36 +02:00
|
|
|
|
If you don’t trust binaries, we encorage you to follow these
|
|
|
|
|
<router-link to="/instructions">instructions</router-link> to contribute by compiling from the
|
|
|
|
|
source code. It is fairly easy!
|
2020-02-05 11:53:45 +01:00
|
|
|
|
</p>
|
2020-02-01 18:17:32 +01:00
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
/* eslint-disable no-console */
|
2020-02-06 16:41:43 +01:00
|
|
|
|
import { mapGetters, mapActions } from 'vuex'
|
2020-02-01 18:17:32 +01:00
|
|
|
|
import Cloak from '@/components/Cloak'
|
2020-02-06 16:41:43 +01:00
|
|
|
|
import Form from '@/components/Form'
|
2020-02-01 18:17:32 +01:00
|
|
|
|
const timeout = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
2020-04-27 13:11:35 +02:00
|
|
|
|
function buf2hex(buffer) {
|
|
|
|
|
// buffer is an ArrayBuffer
|
|
|
|
|
return Array.prototype.map
|
|
|
|
|
.call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2))
|
|
|
|
|
.join('')
|
|
|
|
|
}
|
2020-02-01 18:17:32 +01:00
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
components: {
|
2020-02-06 16:41:43 +01:00
|
|
|
|
Cloak,
|
|
|
|
|
Form
|
2020-02-01 18:17:32 +01:00
|
|
|
|
},
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
2020-02-05 11:53:45 +01:00
|
|
|
|
isContributeBtnSnown: false,
|
2020-02-01 18:17:32 +01:00
|
|
|
|
status: {
|
|
|
|
|
type: '',
|
|
|
|
|
msg: ''
|
2020-02-29 12:22:45 +01:00
|
|
|
|
},
|
|
|
|
|
contributionHash: null,
|
|
|
|
|
authorizeLink: null
|
2020-02-01 18:17:32 +01:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
computed: {
|
2020-02-06 16:41:43 +01:00
|
|
|
|
...mapGetters('user', ['isLoggedIn', 'hasErrorName']),
|
|
|
|
|
userName: {
|
|
|
|
|
get() {
|
|
|
|
|
return this.$store.state.user.name
|
|
|
|
|
},
|
|
|
|
|
set(value) {
|
|
|
|
|
this.$store.commit('user/SET_NAME', value)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
userHandle: {
|
|
|
|
|
get() {
|
|
|
|
|
return this.$store.state.user.handle
|
|
|
|
|
},
|
|
|
|
|
set(value) {
|
|
|
|
|
this.$store.commit('user/SET_HANDLE', value)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
userCompany: {
|
|
|
|
|
get() {
|
|
|
|
|
return this.$store.state.user.company
|
|
|
|
|
},
|
|
|
|
|
set(value) {
|
|
|
|
|
this.$store.commit('user/SET_COMPANY', value)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
contributionType: {
|
|
|
|
|
get() {
|
|
|
|
|
return this.$store.state.user.contributionType
|
|
|
|
|
},
|
|
|
|
|
set(value) {
|
|
|
|
|
this.$store.commit('user/SET_CONTRIBUTION_TYPE', value)
|
|
|
|
|
}
|
2020-02-05 11:53:45 +01:00
|
|
|
|
},
|
|
|
|
|
isContributeBtnDisabled() {
|
2020-02-06 16:41:43 +01:00
|
|
|
|
return (
|
|
|
|
|
!this.contributionType ||
|
|
|
|
|
(!this.isLoggedIn && this.contributionType !== 'anonymous') ||
|
|
|
|
|
this.hasErrorName.invalid
|
|
|
|
|
)
|
2020-02-01 18:17:32 +01:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
async mounted() {
|
2020-02-09 15:11:37 +01:00
|
|
|
|
this.$root.$emit('enableLoading')
|
2020-02-06 17:16:38 +01:00
|
|
|
|
await this.getUserData()
|
2020-02-08 16:36:03 +01:00
|
|
|
|
setTimeout(() => {
|
2020-02-09 15:11:37 +01:00
|
|
|
|
this.$root.$emit('disableLoading')
|
2020-02-08 16:36:03 +01:00
|
|
|
|
}, 800)
|
2020-02-01 18:17:32 +01:00
|
|
|
|
},
|
|
|
|
|
methods: {
|
2020-02-06 17:16:38 +01:00
|
|
|
|
...mapActions('user', ['makeTweet', 'logOut', 'getUserData']),
|
2020-03-11 11:08:01 +01:00
|
|
|
|
getUserRandom() {
|
|
|
|
|
this.$buefy.dialog.prompt({
|
|
|
|
|
title: 'Contribution',
|
2020-04-29 21:26:36 +02:00
|
|
|
|
message: `Please provide your random input that will be used as a source of entropy for your contribution along with browser's RNG.`,
|
2020-03-11 11:08:01 +01:00
|
|
|
|
inputAttrs: {
|
|
|
|
|
maxlength: 300
|
|
|
|
|
},
|
2020-04-29 21:26:36 +02:00
|
|
|
|
confirmText: 'Contribute',
|
2020-03-11 11:08:01 +01:00
|
|
|
|
trapFocus: true,
|
|
|
|
|
onConfirm: (userInput) => {
|
|
|
|
|
this.makeContribution({ userInput })
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
async makeContribution({ userInput, retry = 0 } = {}) {
|
2020-02-01 18:17:32 +01:00
|
|
|
|
try {
|
2020-03-11 11:08:01 +01:00
|
|
|
|
const contribute = await this.$contribute()
|
2020-02-05 11:53:45 +01:00
|
|
|
|
this.isContributeBtnSnown = true
|
2020-02-09 15:11:37 +01:00
|
|
|
|
this.status.msg = ''
|
2020-02-01 18:17:32 +01:00
|
|
|
|
this.status.type = ''
|
2020-02-09 15:11:37 +01:00
|
|
|
|
this.$root.$emit('enableLoading', 'Downloading last contribution')
|
2020-02-01 18:17:32 +01:00
|
|
|
|
let data = await fetch('api/challenge')
|
|
|
|
|
data = new Uint8Array(await data.arrayBuffer())
|
|
|
|
|
|
2020-04-13 14:21:34 +02:00
|
|
|
|
this.$root.$emit(
|
|
|
|
|
'enableLoading',
|
2020-04-29 21:26:36 +02:00
|
|
|
|
'Generating random contribution. Your browser may appear unresponsive. It can take a minute or so to complete'
|
2020-04-13 14:21:34 +02:00
|
|
|
|
)
|
2020-02-01 18:17:32 +01:00
|
|
|
|
await timeout(100) // allow UI to update before freezing in wasm
|
|
|
|
|
console.log('Source params', data)
|
2020-03-11 11:08:01 +01:00
|
|
|
|
|
2020-04-29 19:55:45 +02:00
|
|
|
|
const msgBuffer = new TextEncoder('utf-8').encode(userInput)
|
|
|
|
|
const hashBuffer = await window.crypto.subtle.digest('SHA-256', msgBuffer)
|
2020-04-13 17:37:05 +02:00
|
|
|
|
const entropyFromUser = new Uint8Array(hashBuffer)
|
2020-04-27 13:11:35 +02:00
|
|
|
|
// console.log('entropyFromUser', entropyFromUser.toString())
|
2020-04-13 17:37:05 +02:00
|
|
|
|
|
2020-04-29 19:55:45 +02:00
|
|
|
|
const entropyFromBrowser = window.crypto.getRandomValues(new Uint8Array(32))
|
2020-04-27 13:11:35 +02:00
|
|
|
|
// console.log('entropyFromBrowser', entropyFromBrowser.toString())
|
2020-03-11 11:08:01 +01:00
|
|
|
|
|
|
|
|
|
// suffle the browser and user random
|
2020-04-13 17:37:05 +02:00
|
|
|
|
const entropy = new Uint8Array(entropyFromBrowser.length)
|
2020-03-11 11:08:01 +01:00
|
|
|
|
for (let i = 0; i < entropyFromBrowser.length; i++) {
|
|
|
|
|
entropy[i] = entropyFromBrowser[i] + entropyFromUser[i]
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-27 13:11:35 +02:00
|
|
|
|
// console.log('entropy', entropy)
|
2020-04-13 22:13:01 +02:00
|
|
|
|
await this.sleep(100) // so browser can render the messages
|
2020-03-11 11:08:01 +01:00
|
|
|
|
const result = contribute(data, entropy)
|
2020-02-01 18:17:32 +01:00
|
|
|
|
console.log('Updated params', result)
|
2020-04-27 13:11:35 +02:00
|
|
|
|
const hash = '0x' + buf2hex(result.slice(0, 64))
|
|
|
|
|
const contribution = result.slice(64)
|
|
|
|
|
|
|
|
|
|
console.log('hash', hash)
|
|
|
|
|
console.log('contribution', contribution)
|
2020-02-01 18:17:32 +01:00
|
|
|
|
|
2020-02-09 15:11:37 +01:00
|
|
|
|
this.$root.$emit('enableLoading', 'Uploading and verifying your contribution')
|
2020-02-01 18:17:32 +01:00
|
|
|
|
const formData = new FormData()
|
2020-04-27 13:11:35 +02:00
|
|
|
|
formData.append('response', new Blob([contribution], { type: 'application/octet-stream' }))
|
2020-02-05 11:53:45 +01:00
|
|
|
|
if (this.contributionType !== 'anonymous') {
|
2020-02-06 16:41:43 +01:00
|
|
|
|
formData.append('name', this.userName)
|
|
|
|
|
formData.append('company', this.userCompany)
|
2020-02-05 11:53:45 +01:00
|
|
|
|
}
|
2020-02-01 18:17:32 +01:00
|
|
|
|
const resp = await fetch('api/response', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
body: formData
|
|
|
|
|
})
|
|
|
|
|
if (resp.ok) {
|
2020-02-05 16:02:34 +01:00
|
|
|
|
const responseData = await resp.json()
|
2020-02-06 16:41:43 +01:00
|
|
|
|
this.$store.commit('user/SET_CONTRIBUTION_INDEX', responseData.contributionIndex)
|
2020-04-29 21:26:36 +02:00
|
|
|
|
this.status.msg = 'Your contribution has been verified and recorded.'
|
2020-02-29 05:28:00 +01:00
|
|
|
|
this.status.type = 'is-success'
|
2020-04-27 13:11:35 +02:00
|
|
|
|
this.contributionHash = hash
|
2020-02-29 05:28:00 +01:00
|
|
|
|
if (this.contributionType === 'anonymous') {
|
2020-02-29 12:22:45 +01:00
|
|
|
|
this.authorizeLink = `${window.location.origin}/authorize-contribution?token=${responseData.token}`
|
2020-02-29 05:28:00 +01:00
|
|
|
|
}
|
2020-02-01 18:17:32 +01:00
|
|
|
|
} else if (resp.status === 422) {
|
|
|
|
|
if (retry < 3) {
|
|
|
|
|
console.log(`Looks like someone else uploaded contribution ahead of us, retrying`)
|
2020-04-13 17:37:05 +02:00
|
|
|
|
await this.makeContribution({ userInput, retry: retry++ })
|
2020-02-01 18:17:32 +01:00
|
|
|
|
} else {
|
|
|
|
|
this.status.msg = `Failed to upload your contribution after ${retry} attempts`
|
|
|
|
|
this.status.type = 'is-danger'
|
2020-02-05 11:53:45 +01:00
|
|
|
|
this.isContributeBtnSnown = false
|
2020-02-01 18:17:32 +01:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
this.status.msg = 'Error uploading your contribution'
|
|
|
|
|
this.status.type = 'is-danger'
|
2020-02-05 11:53:45 +01:00
|
|
|
|
this.isContributeBtnSnown = false
|
2020-02-01 18:17:32 +01:00
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
2020-03-11 11:08:01 +01:00
|
|
|
|
console.error(e)
|
2020-02-01 18:17:32 +01:00
|
|
|
|
this.status.msg = e.message
|
|
|
|
|
this.status.type = 'is-danger'
|
2020-02-05 11:53:45 +01:00
|
|
|
|
this.isContributeBtnSnown = false
|
2020-02-06 12:58:35 +01:00
|
|
|
|
} finally {
|
2020-02-09 15:11:37 +01:00
|
|
|
|
this.$root.$emit('disableLoading')
|
2020-02-01 18:17:32 +01:00
|
|
|
|
}
|
|
|
|
|
},
|
2020-02-05 11:53:45 +01:00
|
|
|
|
onAnonymousHandler() {
|
2020-02-06 16:41:43 +01:00
|
|
|
|
this.logOut()
|
2020-02-05 11:53:45 +01:00
|
|
|
|
this.contributionType = 'anonymous'
|
2020-02-29 13:47:04 +01:00
|
|
|
|
},
|
2020-02-29 13:54:36 +01:00
|
|
|
|
copyContributionHash() {
|
2020-02-29 13:47:04 +01:00
|
|
|
|
navigator.clipboard.writeText(this.contributionHash).then(() => {
|
|
|
|
|
this.$buefy.toast.open({
|
|
|
|
|
message: 'Copied!',
|
|
|
|
|
type: 'is-primary'
|
|
|
|
|
})
|
|
|
|
|
})
|
2020-04-13 22:13:01 +02:00
|
|
|
|
},
|
|
|
|
|
sleep(ms) {
|
|
|
|
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
2020-02-01 18:17:32 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|