Skip to content

Commit

Permalink
BIP-39 japanese support
Browse files Browse the repository at this point in the history
  • Loading branch information
AlanVerbner committed Sep 16, 2017
1 parent 4a2d93f commit fa90136
Show file tree
Hide file tree
Showing 5 changed files with 22 additions and 15 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ bip39.check(sentence, EnglishWordList) shouldEqual true

### TODO

- [ ] Japanese support
- [x] Japanese support
- [ ] Add more tests
- [ ] Allow custom wordlists

### API

Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ organization := "com.github.alanverbner"

name := "bip39"

version := "0.1"
version := "0.2"

scalaVersion := "2.12.3"

Expand Down
6 changes: 3 additions & 3 deletions src/main/scala/com/github/alanverbner/bip39/WordList.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.github.alanverbner.bip39

import java.text.Normalizer

import scala.io.{BufferedSource, Source}
import scala.io.{BufferedSource, Codec, Source}
import scala.util.Try

object WordList {
Expand All @@ -21,15 +21,15 @@ object WordList {
private def loadFile(loadFile: () => BufferedSource): Try[Seq[String]] = {
Try(loadFile()).map { r =>
try {
r.getLines().toList.map(word => Normalizer.normalize(word, Normalizer.Form.NFKD))
r.getLines().toList
} finally {
r.close()
}
}
}

private def fileLoader(fileName: String): () => BufferedSource =
() => Source.fromResource(s"wordlists/$fileName")
() => Source.fromResource(s"wordlists/$fileName")(Codec.UTF8)
}

case class WordList(words: Seq[String], delimiter: String)
Expand Down
15 changes: 9 additions & 6 deletions src/main/scala/com/github/alanverbner/bip39/package.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.github.alanverbner

import java.security.SecureRandom
import java.text.Normalizer
import java.text.Normalizer.Form.NFKD
import java.text.Normalizer.normalize
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.PBEKeySpec

Expand Down Expand Up @@ -66,9 +67,11 @@ package object bip39 {
val words = mnemonic.split(wordList.delimiter)
if (words.length % 3 != 0) false
else {
val entWithChecksum = words.map(wordList.words.indexOf(_)).foldLeft(BitVector.empty) { (ent: BitVector, wordIndex: Int) =>
ent ++ BitVector.fromInt(wordIndex, size = BitsGroupSize)
}
val entWithChecksum = words
.map(normalize(_, NFKD))
.map(wordList.words.indexOf(_)).foldLeft(BitVector.empty) { (ent: BitVector, wordIndex: Int) =>
ent ++ BitVector.fromInt(wordIndex, size = BitsGroupSize)
}
val checkSumSize = entWithChecksum.length / 33
val ent = entWithChecksum.dropRight(checkSumSize)
val checksum = entWithChecksum.takeRight(checkSumSize)
Expand All @@ -84,8 +87,8 @@ package object bip39 {
* @return Seed bytes
*/
def toSeed(mnemonic: String, passphrase: Option[String] = None): Array[Byte] = {
val normalizedMnemonic = Normalizer.normalize(mnemonic.toCharArray, Normalizer.Form.NFKD).toCharArray
val normalizedSeed = Normalizer.normalize(s"mnemonic${passphrase.getOrElse("")}", Normalizer.Form.NFKD)
val normalizedMnemonic = normalize(mnemonic.toCharArray, NFKD).toCharArray
val normalizedSeed = normalize(s"mnemonic${passphrase.getOrElse("")}", NFKD)

val spec = new PBEKeySpec(
normalizedMnemonic,
Expand Down
11 changes: 7 additions & 4 deletions src/test/scala/com/github/alanverbner/bip39/Bip39Suite.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.github.alanverbner.bip39

import java.text.Normalizer.Form.NFKD
import java.text.Normalizer.normalize

import com.github.alanverbner._
import org.scalatest.prop.TableDrivenPropertyChecks
import org.scalatest.{FlatSpec, Matchers}
import scodec.bits.ByteVector
import com.github.alanverbner._

class Bip39Suite extends FlatSpec with Matchers with TableDrivenPropertyChecks {

Expand Down Expand Up @@ -165,15 +168,15 @@ class Bip39Suite extends FlatSpec with Matchers with TableDrivenPropertyChecks {

}

ignore should "pass https://github.com/bip32JP/bip32JP.github.io/blob/master/test_JP_BIP39.json" in {
it should "pass https://github.com/bip32JP/bip32JP.github.io/blob/master/test_JP_BIP39.json" in {
val wordList = WordList.load(JapaneseWordList).get
val passphrase = "TREZOR"

val cases = Table(
("Entropy", "Sentence", "Passphase", "Seed", "bip32_xprv"),
(
"00000000000000000000000000000000",
"あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あおぞら",
"あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あおぞら",
"㍍ガバヴァぱばぐゞちぢ十人十色",
"a262d6fb6122ecf45be09c50492b31f92e9beb7d9a845987a02cefda57a15f9c467a17872029a9e92299b5cbdf306e3a0ee620245cbd508959b6cb7ca637bd55",
"xprv9s21ZrQH143K258jAiWPAM6JYT9hLA91MV3AZUKfxmLZJCjCHeSjBvMbDy8C1mJ2FL5ytExyS97FAe6pQ6SD5Jt9SwHaLorA8i5Eojokfo1"
Expand Down Expand Up @@ -371,7 +374,7 @@ class Bip39Suite extends FlatSpec with Matchers with TableDrivenPropertyChecks {

def runChecks(entropy: String, sentence: String, passphrase: String, seed: String, wordList: WordList): Unit = {
// Generation
bip39.generate(ByteVector.fromHex(entropy).get.toArray, wordList) shouldEqual sentence
normalize(bip39.generate(ByteVector.fromHex(entropy).get.toArray, wordList), NFKD) shouldEqual normalize(sentence, NFKD)
// Check
bip39.check(sentence, wordList) shouldEqual true
// Seed calculation
Expand Down

0 comments on commit fa90136

Please sign in to comment.