feat(tool): bip39-generator
This commit is contained in:
		
							parent
							
								
									3ae872847b
								
							
						
					
					
						commit
						d55329f3ab
					
				
							
								
								
									
										32
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										32
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -8,6 +8,7 @@ | ||||
|       "name": "it-tools", | ||||
|       "version": "0.0.0", | ||||
|       "dependencies": { | ||||
|         "@it-tools/bip39": "^0.0.4", | ||||
|         "@vicons/material": "^0.12.0", | ||||
|         "@vicons/tabler": "^0.12.0", | ||||
|         "@vueuse/core": "^8.2.1", | ||||
| @ -686,6 +687,18 @@ | ||||
|         "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": { | ||||
|       "version": "3.0.5", | ||||
|       "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", | ||||
| @ -4961,6 +4974,11 @@ | ||||
|         "@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": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", | ||||
| @ -8416,6 +8434,15 @@ | ||||
|       "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", | ||||
|       "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": { | ||||
|       "version": "3.0.5", | ||||
|       "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", | ||||
| @ -11516,6 +11543,11 @@ | ||||
|         "@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": { | ||||
|       "version": "1.0.2", | ||||
|       "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" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@it-tools/bip39": "^0.0.4", | ||||
|     "@vicons/material": "^0.12.0", | ||||
|     "@vicons/tabler": "^0.12.0", | ||||
|     "@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, | ||||
|   NEllipsis, | ||||
|   NTag, | ||||
|   NInputGroup, | ||||
|   NInputGroupLabel, | ||||
| } from 'naive-ui'; | ||||
| 
 | ||||
| const components = [ | ||||
|   NInputGroup, | ||||
|   NInputGroupLabel, | ||||
|   NTag, | ||||
|   NResult, | ||||
|   NEllipsis, | ||||
|  | ||||
| @ -1,27 +1,45 @@ | ||||
| <template> | ||||
|     <div> | ||||
|         <n-card> | ||||
|             <n-space item-style="flex: 1 1 0"> | ||||
|                 <n-form-item label="Language:"> | ||||
|                     <n-select v-model:value="language" :options="languages" /> | ||||
|                 </n-form-item> | ||||
|                 <n-form-item label="Entropy (seed):"> | ||||
|                     <n-input v-model:value="entropy" placeholder="Your string..." /> | ||||
|                 </n-form-item> | ||||
|             </n-space> | ||||
|             <n-form-item label="Passphrase (mnemonic):"> | ||||
|                 <n-input | ||||
|                     style="text-align: center;" | ||||
|                     :value="passphrase" | ||||
|                     type="textarea" | ||||
|                     placeholder="Your string hash" | ||||
|                     :autosize="{ minRows: 1 }" | ||||
|                     readonly | ||||
|                     autocomplete="off" | ||||
|                     autocorrect="off" | ||||
|                     autocapitalize="off" | ||||
|                     spellcheck="false" | ||||
|                 /> | ||||
|             <n-grid cols="3" x-gap="12"> | ||||
|                 <n-gi span="1"> | ||||
|                     <n-form-item label="Language:"> | ||||
|                         <n-select v-model:value="language" | ||||
|                             :options="Object.keys(languages).map(label => ({ label, value: label }))" /> | ||||
|                     </n-form-item> | ||||
|                 </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-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-gi> | ||||
|             </n-grid> | ||||
|             <br> | ||||
|             <n-form-item label="Passphrase (mnemonic):" :feedback="mnemonicValidation.message" | ||||
|                 :validation-status="mnemonicValidation.status"> | ||||
|                 <n-input-group> | ||||
|                     <n-input style="text-align: center; flex: 1;" v-model:value="passphrase" | ||||
|                         placeholder="Your mnemonic..." autocomplete="off" autocorrect="off" autocapitalize="off" | ||||
|                         spellcheck="false" /> | ||||
| 
 | ||||
|                     <n-button @click="copyPassphrase"> | ||||
|                         <n-icon size="22" :component="Copy" /> | ||||
|                     </n-button> | ||||
|                 </n-input-group> | ||||
|             </n-form-item> | ||||
|         </n-card> | ||||
|     </div> | ||||
| @ -30,16 +48,99 @@ | ||||
| <script setup lang="ts"> | ||||
| import { useCopy } from '@/composable/copy'; | ||||
| 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 = ref(['English']) | ||||
| const passphrase = computed(() => { | ||||
|     // setDefaultWordlist(language.value) | ||||
|     return entropyToMnemonic(Buffer.from(entropy.value, "utf-8")) | ||||
| 
 | ||||
| const languages = { | ||||
|     'English': englishWordList, | ||||
|     'Chinese simplified': chineseSimplifiedWordList, | ||||
|     'Chinese traditional': chineseTraditionalWordList, | ||||
|     '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> | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user