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", |       "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…
	
		Reference in New Issue
	
	Block a user