sign and verify

This commit is contained in:
Kyle Zsembery 2020-11-23 15:27:34 +11:00
parent 61bccfd590
commit 898f9165c2
11 changed files with 504 additions and 21 deletions

View File

@ -390,6 +390,14 @@ export class WalletRPC {
);
break;
case "sign":
this.sign(params.data);
break;
case "verify":
this.verify(params.data, params.address, params.signature);
break;
case "add_address_book":
this.addAddressBook(
params.address,
@ -1281,6 +1289,104 @@ export class WalletRPC {
}
}
async sign(data) {
// set to loading
this.sendGateway("set_sign_status", {
code: 0,
sending: true
});
try {
const rpcData = await this.sendRPC("sign", { data });
if (
!rpcData ||
rpcData.hasOwnProperty("error") ||
!rpcData.hasOwnProperty("result")
) {
const error = rpcData?.error?.message || "Unknown error";
this.sendGateway("set_sign_status", {
code: -1,
message: error,
sending: false
});
return;
}
const signature = rpcData.result.signature;
this.sendGateway("set_sign_status", {
code: 1,
sending: false,
signature: signature
});
return;
} catch (err) {
console.debug(`Failed to sign data: ${data} with error: ${err}`);
this.sendGateway("set_sign_status", {
code: -1,
message: err,
sending: false
});
}
}
async verify(data, address, signature) {
this.sendGateway("set_verify_status", {
code: 0,
sending: true
});
try {
const rpcData = await this.sendRPC("verify", {
data,
address,
signature
});
if (
!rpcData ||
rpcData.hasOwnProperty("error") ||
!rpcData.hasOwnProperty("result")
) {
let errorI18n = "";
const error = rpcData.error.message || "Unknown error";
if (error && error.includes("Invalid address")) {
errorI18n = "notification.errors.invalidAddress";
}
this.sendGateway("set_verify_status", {
code: -1,
message: "",
i18n: errorI18n || "Unknown error",
sending: false
});
return;
}
const good = rpcData.result.good;
if (good) {
// success
this.sendGateway("set_verify_status", {
code: 1,
sending: false,
i18n: "notification.positive.signatureVerified",
message: ""
});
} else {
// error
this.sendGateway("set_verify_status", {
code: -1,
sending: false,
i18n: "notification.errors.invalidSignature",
message: ""
});
}
return;
} catch (err) {
this.sendGateway("set_verify_status", {
code: -1,
message: err.toString(),
i18n: "",
sending: false
});
}
}
stake(password, amount, service_node_key, destination) {
crypto.pbkdf2(
password,

View File

@ -1,9 +1,14 @@
<template>
<div class="check-transaction">
<div class="q-pa-md">
<div class="q-mb-lg description">{{ $t("strings.checkTransaction.description") }}</div>
<div class="q-mb-lg description">
{{ $t("strings.checkTransaction.description") }}
</div>
<div>
<LokiField :label="$t('fieldLabels.transactionId')" :error="$v.txid.$error">
<LokiField
:label="$t('fieldLabels.transactionId')"
:error="$v.txid.$error"
>
<q-input
v-model.trim="txid"
:dark="theme == 'dark'"
@ -13,7 +18,12 @@
@blur="$v.txid.$touch"
/>
</LokiField>
<LokiField class="q-mt-md" :label="$t('fieldLabels.address')" :error="$v.address.$error" optional>
<LokiField
class="q-mt-md"
:label="$t('fieldLabels.address')"
:error="$v.address.$error"
optional
>
<q-input
v-model.trim="address"
:dark="theme == 'dark'"
@ -32,7 +42,11 @@
dense
/>
</LokiField>
<LokiField class="q-mt-md" :label="$t('fieldLabels.signature')" :error="$v.signature.$error">
<LokiField
class="q-mt-md"
:label="$t('fieldLabels.signature')"
:error="$v.signature.$error"
>
<q-input
v-model.trim="signature"
:dark="theme == 'dark'"
@ -43,7 +57,12 @@
</LokiField>
<div class="submit-button">
<q-btn color="primary" :label="$t('buttons.check')" @click="check" />
<q-btn v-if="canClear" color="secondary" :label="$t('buttons.clear')" @click="clear" />
<q-btn
v-if="canClear"
color="secondary"
:label="$t('buttons.clear')"
@click="clear"
/>
</div>
</div>
<div v-if="status.state.txid">
@ -52,21 +71,31 @@
<div>{{ status.state.txid }}</div>
</div>
<div class="q-mb-sm">
<div class="title">{{ $t("strings.checkTransaction.infoTitles.validTransaction") }}</div>
<div :class="status.state.good ? 'good' : 'bad'">{{ validTransaction }}</div>
<div class="title">
{{ $t("strings.checkTransaction.infoTitles.validTransaction") }}
</div>
<div :class="status.state.good ? 'good' : 'bad'">
{{ validTransaction }}
</div>
</div>
<div v-if="status.state.received != null" class="q-mb-sm">
<div class="title">{{ $t("strings.checkTransaction.infoTitles.received") }}</div>
<div class="title">
{{ $t("strings.checkTransaction.infoTitles.received") }}
</div>
<div>
<FormatLoki :amount="status.state.received" raw-value />
</div>
</div>
<div v-if="status.state.in_pool != null" class="q-mb-sm">
<div class="title">{{ $t("strings.checkTransaction.infoTitles.inPool") }}</div>
<div class="title">
{{ $t("strings.checkTransaction.infoTitles.inPool") }}
</div>
<div>{{ status.state.in_pool }}</div>
</div>
<div v-if="status.state.confirmations != null" class="q-mb-sm">
<div class="title">{{ $t("strings.checkTransaction.infoTitles.confirmations") }}</div>
<div class="title">
{{ $t("strings.checkTransaction.infoTitles.confirmations") }}
</div>
<div>{{ status.state.confirmations }}</div>
</div>
</div>
@ -100,7 +129,12 @@ export default {
theme: state => state.gateway.app.config.appearance.theme,
status: state => state.gateway.check_transaction_status,
canClear() {
return this.txid !== "" || this.address !== "" || this.message !== "" || this.signature != "";
return (
this.txid !== "" ||
this.address !== "" ||
this.message !== "" ||
this.signature != ""
);
},
validTransaction() {
let key = this.status.state.good ? "yes" : "no";

View File

@ -5,7 +5,10 @@
{{ $t("strings.proveTransactionDescription") }}
</div>
<div>
<LokiField :label="$t('fieldLabels.transactionId')" :error="$v.txid.$error">
<LokiField
:label="$t('fieldLabels.transactionId')"
:error="$v.txid.$error"
>
<q-input
v-model.trim="txid"
:dark="theme == 'dark'"
@ -15,7 +18,12 @@
@blur="$v.txid.$touch"
/>
</LokiField>
<LokiField class="q-mt-md" :label="$t('fieldLabels.address')" :error="$v.address.$error" optional>
<LokiField
class="q-mt-md"
:label="$t('fieldLabels.address')"
:error="$v.address.$error"
optional
>
<q-input
v-model.trim="address"
:dark="theme == 'dark'"
@ -35,9 +43,23 @@
/>
</LokiField>
<div class="buttons submit-button">
<q-btn color="primary" :label="$t('buttons.generate')" @click="generate" />
<q-btn v-if="canClear" color="secondary" :label="$t('buttons.clear')" @click="clear" />
<q-btn v-if="status.state.signature" color="secondary" :label="$t('buttons.copySignature')" @click="copy" />
<q-btn
color="primary"
:label="$t('buttons.generate')"
@click="generate"
/>
<q-btn
v-if="canClear"
color="secondary"
:label="$t('buttons.clear')"
@click="clear"
/>
<q-btn
v-if="status.state.signature"
color="secondary"
:label="$t('buttons.copySignature')"
@click="copy"
/>
</div>
</div>
<div v-if="status.state.signature" class="signature-wrapper">

View File

@ -0,0 +1,202 @@
<template>
<div class="sign-and-verify">
<div class="q-pa-md">
<div class="q-mb-lg description">
{{ $t("strings.signAndVerifyDescription") }}
</div>
<div class="text-h6">Sign</div>
<div class="row justify-between items-end">
<LokiField :label="$t('fieldLabels.data')">
<q-input
v-model.trim="toSign"
:dark="theme == 'dark'"
borderless
dense
:placeholder="$t('placeholders.dataToSign')"
/>
</LokiField>
<div class="btn-wrapper q-ml-md q-py-sm">
<q-btn
color="primary"
:label="$t('buttons.sign')"
:loading="sign_status.sending"
:disable="!toSign"
@click="sign()"
/>
</div>
</div>
<div class="verify-heading text-h6">Verify</div>
<div class="justify-between items-end">
<LokiField class="q-mt-md" :label="$t('fieldLabels.signature')">
<q-input
v-model.trim="signatureToVerify"
:dark="theme == 'dark'"
borderless
dense
:placeholder="$t('placeholders.signature')"
/>
</LokiField>
<LokiField class="q-mt-md" :label="$t('fieldLabels.data')">
<q-input
v-model.trim="unsignedData"
:dark="theme == 'dark'"
borderless
dense
:placeholder="$t('placeholders.unsignedData')"
/>
</LokiField>
<LokiField class="q-mt-md" :label="$t('fieldLabels.address')">
<q-input
v-model.trim="address"
:dark="theme == 'dark'"
borderless
dense
:placeholder="$t('placeholders.address')"
/>
</LokiField>
<div class="submit-button">
<q-btn
color="primary"
:label="$t('buttons.verify')"
:disable="!signatureToVerify || !unsignedData || !address"
@click="verify()"
/>
</div>
<SignatureDialog
:on-copy="copySignature"
:on-close="closeDialog"
:signature="signature"
:show="!!signature"
/>
</div>
</div>
</div>
</template>
<script>
const { clipboard } = require("electron");
import LokiField from "components/loki_field";
import SignatureDialog from "./signature_dialog";
import { mapState } from "vuex";
export default {
name: "SignAndVerify",
components: {
LokiField,
SignatureDialog
},
data() {
return {
toSign: "",
// returned from the RPC
signature: "",
// entered by the user to verify
signatureToVerify: "",
unsignedData: "",
address: ""
};
},
computed: mapState({
theme: state => state.gateway.app.config.appearance.theme,
sign_status: state => state.gateway.sign_status,
verify_status: state => state.gateway.verify_status
}),
watch: {
sign_status: {
handler(val, old) {
if (val.code == old.code) return;
const { code, message, signature } = val;
switch (code) {
case -1:
this.$q.notify({
type: "negative",
timeout: 3000,
message
});
break;
case 1:
this.showSignature(signature);
}
},
deep: true
},
verify_status: {
handler(val, old) {
if (val.code == old.code) return;
const { code, message, i18n } = val;
switch (code) {
case -1:
this.$q.notify({
type: "negative",
timeout: 3000,
message: i18n ? this.$t(i18n) : message
});
break;
case 1:
this.$q.notify({
type: "positive",
timeout: 3000,
message: i18n ? this.$t(i18n) : message
});
break;
}
},
deep: true
}
},
methods: {
sign() {
this.$gateway.send("wallet", "sign", { data: this.toSign });
},
verify() {
this.$gateway.send("wallet", "verify", {
address: this.address,
data: this.unsignedData,
signature: this.signatureToVerify
});
},
showSignature(signature) {
this.signature = signature;
},
copySignature() {
clipboard.writeText(this.signature);
this.$q.notify({
type: "positive",
timeout: 2000,
message: this.$t("notification.positive.signatureCopied")
});
},
closeDialog() {
this.signature = "";
}
}
};
</script>
<style lang="scss">
.description {
white-space: pre-line;
}
.sign-and-verify {
.height {
font-size: 0.9em;
}
.q-item {
cursor: default;
}
.loki-field {
flex: 1;
}
}
.verify-heading {
margin-top: 24px;
}
.submit-button {
.q-btn:not(:first-child) {
margin-left: 8px;
}
margin-bottom: 12px;
}
</style>

View File

@ -0,0 +1,58 @@
<template>
<q-dialog v-model="show" persistent>
<q-card dark>
<q-card-section>
<div class="text-h6">{{ $t("dialog.signature.title") }}</div>
<div>
{{ $t("dialog.signature.message") }}
</div>
</q-card-section>
<q-card-section>
<p class="signature">{{ signature }}</p>
</q-card-section>
<q-card-actions align="right">
<q-btn flat :label="$t('buttons.close')" @click="onClose" />
<q-btn
:label="$t('buttons.copySignature')"
color="primary"
@click="onCopy"
/>
</q-card-actions>
</q-card>
</q-dialog>
</template>
<script>
export default {
name: "SignatureDialog",
props: {
onCopy: {
type: Function,
required: true
},
onClose: {
type: Function,
required: true
},
signature: {
type: String,
required: true
},
show: {
type: Boolean,
required: false,
default: true
}
}
};
</script>
<style lang="scss">
.signature {
flex: 1;
word-break: break-all;
word-wrap: break-word;
-webkit-user-select: all;
user-select: all;
}
</style>

View File

@ -651,7 +651,7 @@ footer,
}
}
.service-node-registration, .prove-transaction, .check-transaction {
.service-node-registration, .prove-transaction, .check-transaction, .sign-and-verify {
.description{
color: #b7b7b7;
font-style: normal;

View File

@ -237,6 +237,14 @@ export class Gateway extends EventEmitter {
this.app.store.commit("gateway/set_check_transaction_status", data);
break;
}
case "set_sign_status": {
this.app.store.commit("gateway/set_sign_status", decrypted_data.data);
break;
}
case "set_verify_status": {
this.app.store.commit("gateway/set_verify_status", decrypted_data.data);
break;
}
case "set_old_gui_import_status":
this.app.store.commit(
"gateway/set_old_gui_import_status",

View File

@ -42,10 +42,12 @@ export default {
settings: "SETTINGS",
showQRCode: "SHOW QR CODE",
showTxDetails: "SHOW TX DETAILS",
sign: "SIGN",
stake: "STAKE",
sweepAll: "SWEEP ALL",
unlock: "UNLOCK",
update: "UPDATE",
verify: "VERIFY",
viewOnExplorer: "VIEW ON EXPLORER"
},
dialog: {
@ -139,6 +141,11 @@ export default {
message: "Do you want to view your private keys?",
ok: "SHOW"
},
signature: {
title: "Signature",
message:
"Copy the data signed by your primary address's private key below"
},
stake: {
title: "Stake",
message: "Do you want to stake?",
@ -202,6 +209,7 @@ export default {
confirmPassword: "CONFIRM PASSWORD",
daemonLogLevel: "DAEMON LOG LEVEL",
daemonP2pPort: "DAEMON P2P PORT",
data: "DATA",
dataStoragePath: "DATA STORAGE PATH",
decryptRecord: "DECRYPT RECORD",
filter: "FILTER",
@ -324,6 +332,7 @@ export default {
serviceNodeInfoFilled: "Service node key and min amount filled",
sessionIdCopied: "Session ID copied to clipboard",
signatureCopied: "Signature copied to clipboard",
signatureVerified: "Signature verified",
stakeSuccess: "Successfully staked",
transactionNotesSaved: "Transaction notes saved"
},
@ -370,6 +379,7 @@ export default {
"Please enter the service node registration command",
invalidServiceNodeKey: "Service node key not valid",
invalidSessionId: "Session ID not valid",
invalidSignature: "Invalid signature",
invalidWalletPath: "Invalid wallet path",
keyImages: {
exporting: "Error exporting key images",
@ -397,6 +407,8 @@ export default {
placeholders: {
additionalNotes: "Additional notes",
addressBookName: "Name that belongs to this address",
address: "Public address",
dataToSign: "Data you want to sign with your primary address's private key",
filterTx: "Enter an ID, name, address or amount",
hexCharacters: "{count} hexadecimal characters",
lnsName: "The name to purchase via Loki Name Service",
@ -412,7 +424,9 @@ export default {
recipientWalletAddress: "Recipient's wallet address",
selectAFile: "Please select a file",
sessionId: "The Session ID to link to Loki Name Service",
signature: "Signature to verify",
transactionNotes: "Additional notes to locally attach to the transaction",
unsignedData: "The data as it should look before it was signed",
walletName: "A name for your wallet",
walletPassword: "An optional password for the wallet"
},
@ -540,6 +554,8 @@ export default {
stakingRequirement: "Staking requirement",
totalContributed: "Total contributed"
},
signAndVerifyDescription:
"Sign data with your primary address's private key or verify a signature against a public address.",
spendKey: "Spend key",
startingDaemon: "Starting daemon",
startingWallet: "Starting wallet",
@ -585,7 +601,8 @@ export default {
addressDetails: "Address details",
advanced: {
checkTransaction: "CHECK TRANSACTION",
prove: "PROVE"
prove: "PROVE",
signAndVerify: "SIGN/VERIFY"
},
availableForContribution: "Service nodes available for contribution",
changePassword: "Change password",

View File

@ -10,22 +10,30 @@
{
label: $t('titles.advanced.checkTransaction'),
value: 'check'
},
{
label: $t('titles.advanced.signAndVerify'),
value: 'signAndVerify'
}
]"
/>
</div>
<ProveTransaction v-if="screen === 'prove'" />
<CheckTransaction v-if="screen === 'check'" />
<SignAndVerify v-if="screen === 'signAndVerify'" />
</q-page>
</template>
<script>
import ProveTransaction from "components/prove_transaction";
import CheckTransaction from "components/check_transaction";
import ProveTransaction from "components/advanced/prove_transaction";
import CheckTransaction from "components/advanced/check_transaction";
import SignAndVerify from "components/advanced/sign_and_verify";
export default {
components: {
ProveTransaction,
CheckTransaction
CheckTransaction,
SignAndVerify
},
data() {
return {

View File

@ -39,6 +39,21 @@ export const set_check_transaction_status = (state, data) => {
...data
};
};
export const set_sign_status = (state, data) => {
state.sign_status = {
...state.sign_status,
...data
};
};
export const set_verify_status = (state, data) => {
state.verify_status = {
...state.verify_status,
...data
};
};
export const set_lns_status = (state, data) => {
state.lns_status = data;
};

View File

@ -96,6 +96,19 @@ export default {
i18n: "",
state: {}
},
sign_status: {
code: 0,
message: "",
i18n: "",
signature: "",
sending: false
},
verify_status: {
code: 0,
message: "",
i18n: "",
sending: false
},
lns_status: {
code: 0,
message: "",