import { Component , EventEmitter , Input , OnInit , Output } from '@angular/core' ; import * as bip39 from 'bip39' ; import { sha3_256 } from 'js-sha3' ; type SingleWord = string ; const ADDITIONAL_WORDS = 2 ; @ Component ( { selector : 'airgap-verify-key' , templateUrl : './verify-key.component.html' , styleUrls : [ './verify-key.component.scss' ] } ) export class VerifyKeyComponent implements OnInit { @ Input ( ) public secret : string ; @ Output ( ) public onContinue = new EventEmitter < boolean > ( ) ; @ Output ( ) public onComplete = new EventEmitter < boolean > ( ) ; public isCompleted : boolean = false ; public splittedSecret : SingleWord [ ] = [ ] ; public currentWords : SingleWord [ ] = [ ] ; public promptedWords : SingleWord [ ] = [ ] ; public selectedWordIndex : number | null = null ; public ngOnInit ( ) { this . splittedSecret = this . secret . toLowerCase ( ) . split ( ' ' ) ; this . reset ( ) ; } public continue ( ) { this . onContinue . emit ( ) ; } public promptNextWord ( ) { this . promptedWords = [ ] ; const correctWord = this . splittedSecret [ this . emptySpot ( this . currentWords ) ] ; this . promptedWords . push ( correctWord ) ; const wordList = bip39 . wordlists . EN ; for ( let i = 0 ; i < ADDITIONAL_WORDS ; i ++ ) { const filteredList = wordList . filter ( ( originalWord ) => ! this . splittedSecret . find ( ( word ) => word === originalWord ) ) ; let hashedWord = sha3_256 ( correctWord ) ; for ( let hashRuns = 0 ; hashRuns <= i ; hashRuns ++ ) { hashedWord = sha3_256 ( hashedWord ) ; } const additionalWord = filteredList [ this . stringToIntHash ( hashedWord , 0 , filteredList . length ) ] ; this . promptedWords . push ( additionalWord ) ; } this . promptedWords = this . shuffle ( this . promptedWords ) ; } public shuffle ( a ) { let counter = a . length ; while ( counter > 0 ) { const index = Math . floor ( Math . random ( ) * counter ) ; counter -- ; const temp = a [ counter ] ; a [ counter ] = a [ index ] ; a [ index ] = temp ; } return a ; } public stringToIntHash ( str , lowerbound , upperbound ) { let result = 0 ; for ( let i = 0 ; i < str . length ; i ++ ) { result = result + str . charCodeAt ( i ) ; } return ( result % ( upperbound - lowerbound ) ) + lowerbound ; } public isSelectedWord ( word ) { if ( this . selectedWordIndex !== null ) { return this . currentWords [ this . selectedWordIndex ] === word ; } return false ; } public selectEmptySpot ( ) { this . selectedWordIndex = null ; this . promptNextWord ( ) ; } public useWord ( word ) { const index = this . emptySpot ( this . currentWords ) ; this . selectedWordIndex = null ; this . currentWords [ index ] = word ; if ( ! this . isFull ( ) && index < this . splittedSecret . length - 1 ) { this . promptNextWord ( ) ; return ; } if ( this . isFull ( ) ) { this . promptedWords = [ ] ; this . setCompletedState ( this . isCorrect ( ) ) ; } } public setCompletedState ( state ) { this . isCompleted = state ; this . onComplete . emit ( state ) ; } public emptySpot ( array ) { if ( this . selectedWordIndex !== null ) { return this . selectedWordIndex ; } return array . findIndex ( ( word ) => word === null ) ; } public selectWord ( index ) { this . selectedWordIndex = index ; this . promptNextWord ( ) ; } public reset ( ) { this . selectedWordIndex = null ; this . currentWords = Array ( this . splittedSecret . length ) . fill ( null ) ; this . promptNextWord ( ) ; } public isFull ( ) { return this . currentWords . filter ( ( word ) => word !== null ) . length === this . splittedSecret . length ; } public isCorrect ( ) { return ( this . currentWords . map ( ( word ) => ( word ? word : '-' ) ) . join ( ' ' ) . trim ( ) === this . secret . trim ( ) ) ; } }