mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-04-20 23:06:14 -04:00
feat(tool): bip39-generator
This commit is contained in:
parent
3ae872847b
commit
d55329f3ab
5 changed files with 193 additions and 28 deletions
32
package-lock.json
generated
32
package-lock.json
generated
|
@ -8,6 +8,7 @@
|
||||||
"name": "it-tools",
|
"name": "it-tools",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@it-tools/bip39": "^0.0.4",
|
||||||
"@vicons/material": "^0.12.0",
|
"@vicons/material": "^0.12.0",
|
||||||
"@vicons/tabler": "^0.12.0",
|
"@vicons/tabler": "^0.12.0",
|
||||||
"@vueuse/core": "^8.2.1",
|
"@vueuse/core": "^8.2.1",
|
||||||
|
@ -686,6 +687,18 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@it-tools/bip39": {
|
||||||
|
"version": "0.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@it-tools/bip39/-/bip39-0.0.4.tgz",
|
||||||
|
"integrity": "sha512-0PWO7VKi6VALiFcm8z2WgxzSZ5wAko0OctBZ0I5+jjtSIXm3t1d54yrrHfgFOZDTyMpCXi638oLpzqexcfRtbA==",
|
||||||
|
"dependencies": {
|
||||||
|
"js-sha256": "^0.9.0",
|
||||||
|
"nanoid": "^3.3.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/CorentinTh"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/resolve-uri": {
|
"node_modules/@jridgewell/resolve-uri": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz",
|
||||||
|
@ -4961,6 +4974,11 @@
|
||||||
"@sideway/pinpoint": "^2.0.0"
|
"@sideway/pinpoint": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/js-sha256": {
|
||||||
|
"version": "0.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz",
|
||||||
|
"integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA=="
|
||||||
|
},
|
||||||
"node_modules/js-stringify": {
|
"node_modules/js-stringify": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz",
|
||||||
|
@ -8416,6 +8434,15 @@
|
||||||
"integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
|
"integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@it-tools/bip39": {
|
||||||
|
"version": "0.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@it-tools/bip39/-/bip39-0.0.4.tgz",
|
||||||
|
"integrity": "sha512-0PWO7VKi6VALiFcm8z2WgxzSZ5wAko0OctBZ0I5+jjtSIXm3t1d54yrrHfgFOZDTyMpCXi638oLpzqexcfRtbA==",
|
||||||
|
"requires": {
|
||||||
|
"js-sha256": "^0.9.0",
|
||||||
|
"nanoid": "^3.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@jridgewell/resolve-uri": {
|
"@jridgewell/resolve-uri": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz",
|
||||||
|
@ -11516,6 +11543,11 @@
|
||||||
"@sideway/pinpoint": "^2.0.0"
|
"@sideway/pinpoint": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"js-sha256": {
|
||||||
|
"version": "0.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz",
|
||||||
|
"integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA=="
|
||||||
|
},
|
||||||
"js-stringify": {
|
"js-stringify": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz",
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@it-tools/bip39": "^0.0.4",
|
||||||
"@vicons/material": "^0.12.0",
|
"@vicons/material": "^0.12.0",
|
||||||
"@vicons/tabler": "^0.12.0",
|
"@vicons/tabler": "^0.12.0",
|
||||||
"@vueuse/core": "^8.2.1",
|
"@vueuse/core": "^8.2.1",
|
||||||
|
|
27
src/composable/validation.ts
Normal file
27
src/composable/validation.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { reactive, watch, type Ref } from 'vue';
|
||||||
|
|
||||||
|
type UseValidationRule<T> = {
|
||||||
|
validator: (value: T) => boolean
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useValidation<T>({ source, rules }: { source: Ref<T>; rules: UseValidationRule<T>[] }) {
|
||||||
|
const state = reactive<{
|
||||||
|
message: string,
|
||||||
|
status: undefined | 'error'
|
||||||
|
}>({
|
||||||
|
message: '',
|
||||||
|
status: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
watch([source], () => {
|
||||||
|
for(const rule of rules) {
|
||||||
|
if(!rule.validator(source.value)){
|
||||||
|
state.message = rule.message
|
||||||
|
state.status = 'error'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
|
@ -42,9 +42,13 @@ import {
|
||||||
NH3,
|
NH3,
|
||||||
NEllipsis,
|
NEllipsis,
|
||||||
NTag,
|
NTag,
|
||||||
|
NInputGroup,
|
||||||
|
NInputGroupLabel,
|
||||||
} from 'naive-ui';
|
} from 'naive-ui';
|
||||||
|
|
||||||
const components = [
|
const components = [
|
||||||
|
NInputGroup,
|
||||||
|
NInputGroupLabel,
|
||||||
NTag,
|
NTag,
|
||||||
NResult,
|
NResult,
|
||||||
NEllipsis,
|
NEllipsis,
|
||||||
|
|
|
@ -1,27 +1,45 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<n-card>
|
<n-card>
|
||||||
<n-space item-style="flex: 1 1 0">
|
<n-grid cols="3" x-gap="12">
|
||||||
|
<n-gi span="1">
|
||||||
<n-form-item label="Language:">
|
<n-form-item label="Language:">
|
||||||
<n-select v-model:value="language" :options="languages" />
|
<n-select v-model:value="language"
|
||||||
|
:options="Object.keys(languages).map(label => ({ label, value: label }))" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="Entropy (seed):">
|
</n-gi>
|
||||||
|
<n-gi span="2">
|
||||||
|
<n-form-item label="Entropy (seed):" :feedback="entropyValidation.message"
|
||||||
|
:validation-status="entropyValidation.status">
|
||||||
|
<n-input-group>
|
||||||
<n-input v-model:value="entropy" placeholder="Your string..." />
|
<n-input v-model:value="entropy" placeholder="Your string..." />
|
||||||
|
<n-button @click="refreshEntropy">
|
||||||
|
<n-icon size="22">
|
||||||
|
<Refresh />
|
||||||
|
</n-icon>
|
||||||
|
</n-button>
|
||||||
|
<n-button @click="copyEntropy">
|
||||||
|
<n-icon size="22">
|
||||||
|
<Copy />
|
||||||
|
</n-icon>
|
||||||
|
</n-button>
|
||||||
|
</n-input-group>
|
||||||
|
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-space>
|
</n-gi>
|
||||||
<n-form-item label="Passphrase (mnemonic):">
|
</n-grid>
|
||||||
<n-input
|
<br>
|
||||||
style="text-align: center;"
|
<n-form-item label="Passphrase (mnemonic):" :feedback="mnemonicValidation.message"
|
||||||
:value="passphrase"
|
:validation-status="mnemonicValidation.status">
|
||||||
type="textarea"
|
<n-input-group>
|
||||||
placeholder="Your string hash"
|
<n-input style="text-align: center; flex: 1;" v-model:value="passphrase"
|
||||||
:autosize="{ minRows: 1 }"
|
placeholder="Your mnemonic..." autocomplete="off" autocorrect="off" autocapitalize="off"
|
||||||
readonly
|
spellcheck="false" />
|
||||||
autocomplete="off"
|
|
||||||
autocorrect="off"
|
<n-button @click="copyPassphrase">
|
||||||
autocapitalize="off"
|
<n-icon size="22" :component="Copy" />
|
||||||
spellcheck="false"
|
</n-button>
|
||||||
/>
|
</n-input-group>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-card>
|
</n-card>
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,16 +48,99 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCopy } from '@/composable/copy';
|
import { useCopy } from '@/composable/copy';
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { entropyToMnemonic } from 'bip39'
|
import {
|
||||||
|
entropyToMnemonic,
|
||||||
|
englishWordList,
|
||||||
|
chineseSimplifiedWordList,
|
||||||
|
chineseTraditionalWordList,
|
||||||
|
czechWordList,
|
||||||
|
frenchWordList,
|
||||||
|
italianWordList,
|
||||||
|
japaneseWordList,
|
||||||
|
koreanWordList,
|
||||||
|
portugueseWordList,
|
||||||
|
spanishWordList,
|
||||||
|
generateEntropy,
|
||||||
|
mnemonicToEntropy
|
||||||
|
} from '@it-tools/bip39'
|
||||||
|
import { Copy, Refresh } from '@vicons/tabler'
|
||||||
|
import { useValidation } from '@/composable/validation';
|
||||||
|
|
||||||
const entropy = ref('1d60683972011cb97322ed6ae96225f3')
|
|
||||||
const language = ref('English')
|
const languages = {
|
||||||
const languages = ref(['English'])
|
'English': englishWordList,
|
||||||
const passphrase = computed(() => {
|
'Chinese simplified': chineseSimplifiedWordList,
|
||||||
// setDefaultWordlist(language.value)
|
'Chinese traditional': chineseTraditionalWordList,
|
||||||
return entropyToMnemonic(Buffer.from(entropy.value, "utf-8"))
|
'Czech': czechWordList,
|
||||||
|
'French': frenchWordList,
|
||||||
|
'Italian': italianWordList,
|
||||||
|
'Japanese': japaneseWordList,
|
||||||
|
'Korean': koreanWordList,
|
||||||
|
'Portuguese': portugueseWordList,
|
||||||
|
'Spanish': spanishWordList
|
||||||
|
}
|
||||||
|
|
||||||
|
const entropy = ref(generateEntropy())
|
||||||
|
const passphraseInput = ref('')
|
||||||
|
|
||||||
|
const language = ref<keyof typeof languages>('English')
|
||||||
|
const passphrase = computed({
|
||||||
|
get() {
|
||||||
|
try {
|
||||||
|
return entropyToMnemonic(entropy.value, languages[language.value])
|
||||||
|
} catch (_) {
|
||||||
|
return passphraseInput.value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set(value: string) {
|
||||||
|
passphraseInput.value = value
|
||||||
|
|
||||||
|
try {
|
||||||
|
entropy.value = mnemonicToEntropy(value, languages[language.value])
|
||||||
|
} catch (_) {
|
||||||
|
entropy.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const entropyValidation = useValidation({
|
||||||
|
source: entropy,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
validator: (value) => value === '' || (value.length <= 32 && value.length >= 16 && value.length % 4 === 0),
|
||||||
|
message: 'Entropy length should be >= 16, <= 32 and be a multiple of 4'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
validator: (value) => /^[a-fA-f0-9]?$/.test(value),
|
||||||
|
message: 'Entropy should an hexadecimal number'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const mnemonicValidation = useValidation({
|
||||||
|
source: passphrase,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
validator: (value) => {
|
||||||
|
try {
|
||||||
|
mnemonicToEntropy(value)
|
||||||
|
return true
|
||||||
|
} catch (_) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
message: 'Invalid mnemonic'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
function refreshEntropy() {
|
||||||
|
entropy.value = generateEntropy()
|
||||||
|
}
|
||||||
|
|
||||||
|
const { copy: copyEntropy } = useCopy({ source: entropy, text: 'Entropy copied to the clipboard' })
|
||||||
|
const { copy: copyPassphrase } = useCopy({ source: passphrase, text: 'Passphrase copied to the clipboard' })
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue