Class: BSV::Primitives::Mnemonic

Inherits:
Object
  • Object
show all
Defined in:
lib/bsv/primitives/mnemonic.rb,
lib/bsv/primitives/mnemonic/wordlist.rb

Overview

BIP-39 mnemonic phrase generation and seed derivation.

Generates human-readable mnemonic phrases from random entropy, validates existing phrases (word count, vocabulary, checksum), and derives 512-bit seeds for HD key generation via PBKDF2.

Examples:

Generate a mnemonic and derive a master key

mnemonic = BSV::Primitives::Mnemonic.generate
mnemonic.phrase #=> "abandon ability able ..."
master = mnemonic.to_extended_key
master.derive_path("m/44'/0'/0'/0/0")

Import an existing mnemonic phrase

mnemonic = BSV::Primitives::Mnemonic.from_phrase('zoo zoo ... wrong')
mnemonic.valid? #=> true

Constant Summary collapse

VALID_STRENGTHS =

Valid entropy strengths in bits (128 = 12 words, 256 = 24 words).

[128, 160, 192, 224, 256].freeze
PBKDF2_ITERATIONS =

PBKDF2 iteration count per BIP-39 specification.

2048
PBKDF2_KEY_LENGTH =

Output key length in bytes for PBKDF2 (512 bits).

64
ENGLISH_WORDLIST =

rubocop:disable Metrics/CollectionLiteralLength

%w[
  abandon ability able about above absent absorb abstract
  absurd abuse access accident account accuse achieve acid
  acoustic acquire across act action actor actress actual
  adapt add addict address adjust admit adult advance
  advice aerobic affair afford afraid again age agent
  agree ahead aim air airport aisle alarm album
  alcohol alert alien all alley allow almost alone
  alpha already also alter always amateur amazing among
  amount amused analyst anchor ancient anger angle angry
  animal ankle announce annual another answer antenna antique
  anxiety any apart apology appear apple approve april
  arch arctic area arena argue arm armed armor
  army around arrange arrest arrive arrow art artefact
  artist artwork ask aspect assault asset assist assume
  asthma athlete atom attack attend attitude attract auction
  audit august aunt author auto autumn average avocado
  avoid awake aware away awesome awful awkward axis
  baby bachelor bacon badge bag balance balcony ball
  bamboo banana banner bar barely bargain barrel base
  basic basket battle beach bean beauty because become
  beef before begin behave behind believe below belt
  bench benefit best betray better between beyond bicycle
  bid bike bind biology bird birth bitter black
  blade blame blanket blast bleak bless blind blood
  blossom blouse blue blur blush board boat body
  boil bomb bone bonus book boost border boring
  borrow boss bottom bounce box boy bracket brain
  brand brass brave bread breeze brick bridge brief
  bright bring brisk broccoli broken bronze broom brother
  brown brush bubble buddy budget buffalo build bulb
  bulk bullet bundle bunker burden burger burst bus
  business busy butter buyer buzz cabbage cabin cable
  cactus cage cake call calm camera camp can
  canal cancel candy cannon canoe canvas canyon capable
  capital captain car carbon card cargo carpet carry
  cart case cash casino castle casual cat catalog
  catch category cattle caught cause caution cave ceiling
  celery cement census century cereal certain chair chalk
  champion change chaos chapter charge chase chat cheap
  check cheese chef cherry chest chicken chief child
  chimney choice choose chronic chuckle chunk churn cigar
  cinnamon circle citizen city civil claim clap clarify
  claw clay clean clerk clever click client cliff
  climb clinic clip clock clog close cloth cloud
  clown club clump cluster clutch coach coast coconut
  code coffee coil coin collect color column combine
  come comfort comic common company concert conduct confirm
  congress connect consider control convince cook cool copper
  copy coral core corn correct cost cotton couch
  country couple course cousin cover coyote crack cradle
  craft cram crane crash crater crawl crazy cream
  credit creek crew cricket crime crisp critic crop
  cross crouch crowd crucial cruel cruise crumble crunch
  crush cry crystal cube culture cup cupboard curious
  current curtain curve cushion custom cute cycle dad
  damage damp dance danger daring dash daughter dawn
  day deal debate debris decade december decide decline
  decorate decrease deer defense define defy degree delay
  deliver demand demise denial dentist deny depart depend
  deposit depth deputy derive describe desert design desk
  despair destroy detail detect develop device devote diagram
  dial diamond diary dice diesel diet differ digital
  dignity dilemma dinner dinosaur direct dirt disagree discover
  disease dish dismiss disorder display distance divert divide
  divorce dizzy doctor document dog doll dolphin domain
  donate donkey donor door dose double dove draft
  dragon drama drastic draw dream dress drift drill
  drink drip drive drop drum dry duck dumb
  dune during dust dutch duty dwarf dynamic eager
  eagle early earn earth easily east easy echo
  ecology economy edge edit educate effort egg eight
  either elbow elder electric elegant element elephant elevator
  elite else embark embody embrace emerge emotion employ
  empower empty enable enact end endless endorse enemy
  energy enforce engage engine enhance enjoy enlist enough
  enrich enroll ensure enter entire entry envelope episode
  equal equip era erase erode erosion error erupt
  escape essay essence estate eternal ethics evidence evil
  evoke evolve exact example excess exchange excite exclude
  excuse execute exercise exhaust exhibit exile exist exit
  exotic expand expect expire explain expose express extend
  extra eye eyebrow fabric face faculty fade faint
  faith fall false fame family famous fan fancy
  fantasy farm fashion fat fatal father fatigue fault
  favorite feature february federal fee feed feel female
  fence festival fetch fever few fiber fiction field
  figure file film filter final find fine finger
  finish fire firm first fiscal fish fit fitness
  fix flag flame flash flat flavor flee flight
  flip float flock floor flower fluid flush fly
  foam focus fog foil fold follow food foot
  force forest forget fork fortune forum forward fossil
  foster found fox fragile frame frequent fresh friend
  fringe frog front frost frown frozen fruit fuel
  fun funny furnace fury future gadget gain galaxy
  gallery game gap garage garbage garden garlic garment
  gas gasp gate gather gauge gaze general genius
  genre gentle genuine gesture ghost giant gift giggle
  ginger giraffe girl give glad glance glare glass
  glide glimpse globe gloom glory glove glow glue
  goat goddess gold good goose gorilla gospel gossip
  govern gown grab grace grain grant grape grass
  gravity great green grid grief grit grocery group
  grow grunt guard guess guide guilt guitar gun
  gym habit hair half hammer hamster hand happy
  harbor hard harsh harvest hat have hawk hazard
  head health heart heavy hedgehog height hello helmet
  help hen hero hidden high hill hint hip
  hire history hobby hockey hold hole holiday hollow
  home honey hood hope horn horror horse hospital
  host hotel hour hover hub huge human humble
  humor hundred hungry hunt hurdle hurry hurt husband
  hybrid ice icon idea identify idle ignore ill
  illegal illness image imitate immense immune impact impose
  improve impulse inch include income increase index indicate
  indoor industry infant inflict inform inhale inherit initial
  inject injury inmate inner innocent input inquiry insane
  insect inside inspire install intact interest into invest
  invite involve iron island isolate issue item ivory
  jacket jaguar jar jazz jealous jeans jelly jewel
  job join joke journey joy judge juice jump
  jungle junior junk just kangaroo keen keep ketchup
  key kick kid kidney kind kingdom kiss kit
  kitchen kite kitten kiwi knee knife knock know
  lab label labor ladder lady lake lamp language
  laptop large later latin laugh laundry lava law
  lawn lawsuit layer lazy leader leaf learn leave
  lecture left leg legal legend leisure lemon lend
  length lens leopard lesson letter level liar liberty
  library license life lift light like limb limit
  link lion liquid list little live lizard load
  loan lobster local lock logic lonely long loop
  lottery loud lounge love loyal lucky luggage lumber
  lunar lunch luxury lyrics machine mad magic magnet
  maid mail main major make mammal man manage
  mandate mango mansion manual maple marble march margin
  marine market marriage mask mass master match material
  math matrix matter maximum maze meadow mean measure
  meat mechanic medal media melody melt member memory
  mention menu mercy merge merit merry mesh message
  metal method middle midnight milk million mimic mind
  minimum minor minute miracle mirror misery miss mistake
  mix mixed mixture mobile model modify mom moment
  monitor monkey monster month moon moral more morning
  mosquito mother motion motor mountain mouse move movie
  much muffin mule multiply muscle museum mushroom music
  must mutual myself mystery myth naive name napkin
  narrow nasty nation nature near neck need negative
  neglect neither nephew nerve nest net network neutral
  never news next nice night noble noise nominee
  noodle normal north nose notable note nothing notice
  novel now nuclear number nurse nut oak obey
  object oblige obscure observe obtain obvious occur ocean
  october odor off offer office often oil okay
  old olive olympic omit once one onion online
  only open opera opinion oppose option orange orbit
  orchard order ordinary organ orient original orphan ostrich
  other outdoor outer output outside oval oven over
  own owner oxygen oyster ozone pact paddle page
  pair palace palm panda panel panic panther paper
  parade parent park parrot party pass patch path
  patient patrol pattern pause pave payment peace peanut
  pear peasant pelican pen penalty pencil people pepper
  perfect permit person pet phone photo phrase physical
  piano picnic picture piece pig pigeon pill pilot
  pink pioneer pipe pistol pitch pizza place planet
  plastic plate play please pledge pluck plug plunge
  poem poet point polar pole police pond pony
  pool popular portion position possible post potato pottery
  poverty powder power practice praise predict prefer prepare
  present pretty prevent price pride primary print priority
  prison private prize problem process produce profit program
  project promote proof property prosper protect proud provide
  public pudding pull pulp pulse pumpkin punch pupil
  puppy purchase purity purpose purse push put puzzle
  pyramid quality quantum quarter question quick quit quiz
  quote rabbit raccoon race rack radar radio rail
  rain raise rally ramp ranch random range rapid
  rare rate rather raven raw razor ready real
  reason rebel rebuild recall receive recipe record recycle
  reduce reflect reform refuse region regret regular reject
  relax release relief rely remain remember remind remove
  render renew rent reopen repair repeat replace report
  require rescue resemble resist resource response result retire
  retreat return reunion reveal review reward rhythm rib
  ribbon rice rich ride ridge rifle right rigid
  ring riot ripple risk ritual rival river road
  roast robot robust rocket romance roof rookie room
  rose rotate rough round route royal rubber rude
  rug rule run runway rural sad saddle sadness
  safe sail salad salmon salon salt salute same
  sample sand satisfy satoshi sauce sausage save say
  scale scan scare scatter scene scheme school science
  scissors scorpion scout scrap screen script scrub sea
  search season seat second secret section security seed
  seek segment select sell seminar senior sense sentence
  series service session settle setup seven shadow shaft
  shallow share shed shell sheriff shield shift shine
  ship shiver shock shoe shoot shop short shoulder
  shove shrimp shrug shuffle shy sibling sick side
  siege sight sign silent silk silly silver similar
  simple since sing siren sister situate six size
  skate sketch ski skill skin skirt skull slab
  slam sleep slender slice slide slight slim slogan
  slot slow slush small smart smile smoke smooth
  snack snake snap sniff snow soap soccer social
  sock soda soft solar soldier solid solution solve
  someone song soon sorry sort soul sound soup
  source south space spare spatial spawn speak special
  speed spell spend sphere spice spider spike spin
  spirit split spoil sponsor spoon sport spot spray
  spread spring spy square squeeze squirrel stable stadium
  staff stage stairs stamp stand start state stay
  steak steel stem step stereo stick still sting
  stock stomach stone stool story stove strategy street
  strike strong struggle student stuff stumble style subject
  submit subway success such sudden suffer sugar suggest
  suit summer sun sunny sunset super supply supreme
  sure surface surge surprise surround survey suspect sustain
  swallow swamp swap swarm swear sweet swift swim
  swing switch sword symbol symptom syrup system table
  tackle tag tail talent talk tank tape target
  task taste tattoo taxi teach team tell ten
  tenant tennis tent term test text thank that
  theme then theory there they thing this thought
  three thrive throw thumb thunder ticket tide tiger
  tilt timber time tiny tip tired tissue title
  toast tobacco today toddler toe together toilet token
  tomato tomorrow tone tongue tonight tool tooth top
  topic topple torch tornado tortoise toss total tourist
  toward tower town toy track trade traffic tragic
  train transfer trap trash travel tray treat tree
  trend trial tribe trick trigger trim trip trophy
  trouble truck true truly trumpet trust truth try
  tube tuition tumble tuna tunnel turkey turn turtle
  twelve twenty twice twin twist two type typical
  ugly umbrella unable unaware uncle uncover under undo
  unfair unfold unhappy uniform unique unit universe unknown
  unlock until unusual unveil update upgrade uphold upon
  upper upset urban urge usage use used useful
  useless usual utility vacant vacuum vague valid valley
  valve van vanish vapor various vast vault vehicle
  velvet vendor venture venue verb verify version very
  vessel veteran viable vibrant vicious victory video view
  village vintage violin virtual virus visa visit visual
  vital vivid vocal voice void volcano volume vote
  voyage wage wagon wait walk wall walnut want
  warfare warm warrior wash wasp waste water wave
  way wealth weapon wear weasel weather web wedding
  weekend weird welcome west wet whale what wheat
  wheel when where whip whisper wide width wife
  wild will win window wine wing wink winner
  winter wire wisdom wise wish witness wolf woman
  wonder wood wool word work world worry worth
  wrap wreck wrestle wrist write wrong yard year
  yellow you young youth zebra zero zone zoo
].freeze
ENGLISH_WORD_MAP =

rubocop:enable Metrics/CollectionLiteralLength

ENGLISH_WORDLIST.each_with_index.to_h.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#phraseString (readonly)

Returns the space-separated mnemonic phrase.

Returns:

  • (String)

    the space-separated mnemonic phrase



34
35
36
# File 'lib/bsv/primitives/mnemonic.rb', line 34

def phrase
  @phrase
end

#wordsArray<String> (readonly)

Returns the individual mnemonic words.

Returns:

  • (Array<String>)

    the individual mnemonic words



37
38
39
# File 'lib/bsv/primitives/mnemonic.rb', line 37

def words
  @words
end

Class Method Details

.from_entropy(entropy) ⇒ Mnemonic

Create a mnemonic from raw entropy bytes.

Parameters:

  • entropy (String)

    16, 20, 24, 28, or 32 bytes of entropy

Returns:

Raises:

  • (ArgumentError)

    if entropy length is invalid



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/bsv/primitives/mnemonic.rb', line 56

def self.from_entropy(entropy)
  entropy = entropy.b
  unless [16, 20, 24, 28, 32].include?(entropy.length)
    raise ArgumentError, "invalid entropy length: #{entropy.length} bytes. Must be 16, 20, 24, 28, or 32"
  end

  bits = bytes_to_bitstring(entropy)
  checksum = checksum_for(entropy)
  all_bits = bits + checksum

  word_indices = all_bits.scan(/.{11}/).map { |group| group.to_i(2) }
  words = word_indices.map { |i| ENGLISH_WORDLIST[i] }

  new(words.join(' '))
end

.from_phrase(phrase) ⇒ Mnemonic

Import and validate an existing mnemonic phrase.

Normalises the phrase (NFKD, whitespace), validates word count, vocabulary, and checksum.

Parameters:

  • phrase (String)

    space-separated mnemonic words

Returns:

Raises:

  • (ArgumentError)

    if word count, vocabulary, or checksum is invalid



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/bsv/primitives/mnemonic.rb', line 80

def self.from_phrase(phrase)
  normalised = phrase.unicode_normalize(:nfkd).strip.gsub(/\s+/, ' ')
  mnemonic = new(normalised)

  unless [12, 15, 18, 21, 24].include?(mnemonic.words.length)
    raise ArgumentError, "invalid word count: #{mnemonic.words.length}. Must be 12, 15, 18, 21, or 24"
  end

  mnemonic.words.each do |word|
    raise ArgumentError, "unknown word: #{word}" unless ENGLISH_WORD_MAP.key?(word)
  end

  raise ArgumentError, 'invalid checksum' unless mnemonic.valid?

  mnemonic
end

.generate(strength: 128) ⇒ Mnemonic

Generate a new random mnemonic phrase.

Parameters:

  • strength (Integer) (defaults to: 128)

    entropy bits (128, 160, 192, 224, or 256)

Returns:

  • (Mnemonic)

    a new mnemonic with valid checksum

Raises:

  • (ArgumentError)

    if strength is not a valid value



44
45
46
47
48
49
# File 'lib/bsv/primitives/mnemonic.rb', line 44

def self.generate(strength: 128)
  raise ArgumentError, "invalid strength: #{strength}. Must be one of #{VALID_STRENGTHS.join(', ')}" unless VALID_STRENGTHS.include?(strength)

  entropy = SecureRandom.random_bytes(strength / 8)
  from_entropy(entropy)
end

Instance Method Details

#==(other) ⇒ Boolean

Returns true if both mnemonics have the same phrase.

Parameters:

  • other (Object)

    the object to compare

Returns:

  • (Boolean)

    true if both mnemonics have the same phrase



165
166
167
168
169
# File 'lib/bsv/primitives/mnemonic.rb', line 165

def ==(other)
  return false unless other.is_a?(Mnemonic)

  @phrase == other.phrase
end

#to_entropyString

Extract the original entropy bytes from this mnemonic.

Returns:

  • (String)

    the entropy bytes



126
127
128
129
130
131
132
133
134
# File 'lib/bsv/primitives/mnemonic.rb', line 126

def to_entropy
  indices = @words.map { |w| ENGLISH_WORD_MAP[w] }
  all_bits = indices.map { |i| i.to_s(2).rjust(11, '0') }.join

  entropy_bit_count = (@words.length * 11 * 32) / 33
  entropy_bits = all_bits[0, entropy_bit_count]

  entropy_bits.chars.each_slice(8).map { |byte| byte.join.to_i(2) }.pack('C*')
end

#to_extended_key(passphrase: '', network: :mainnet) ⇒ ExtendedKey

Derive a BIP-32 master extended key from this mnemonic.

Convenience method equivalent to ExtendedKey.from_seed(to_seed).

Parameters:

  • passphrase (String) (defaults to: '')

    optional BIP-39 passphrase

  • network (Symbol) (defaults to: :mainnet)

    :mainnet or :testnet

Returns:



119
120
121
# File 'lib/bsv/primitives/mnemonic.rb', line 119

def to_extended_key(passphrase: '', network: :mainnet)
  ExtendedKey.from_seed(to_seed(passphrase: passphrase), network: network)
end

#to_sString

Returns the mnemonic phrase.

Returns:

  • (String)

    the mnemonic phrase



159
160
161
# File 'lib/bsv/primitives/mnemonic.rb', line 159

def to_s
  @phrase
end

#to_seed(passphrase: '') ⇒ String

Derive a 512-bit seed from this mnemonic using PBKDF2-HMAC-SHA-512.

Parameters:

  • passphrase (String) (defaults to: '')

    optional passphrase (default: empty string)

Returns:

  • (String)

    64-byte seed



101
102
103
104
105
106
107
108
109
110
# File 'lib/bsv/primitives/mnemonic.rb', line 101

def to_seed(passphrase: '')
  normalised_phrase = @phrase.unicode_normalize(:nfkd)
  normalised_passphrase = passphrase.unicode_normalize(:nfkd)
  salt = "mnemonic#{normalised_passphrase}"

  Digest.pbkdf2_hmac_sha512(
    normalised_phrase, salt,
    iterations: PBKDF2_ITERATIONS, key_length: PBKDF2_KEY_LENGTH
  )
end

#valid?Boolean

Validate the mnemonic’s word count, vocabulary, and checksum.

Returns:

  • (Boolean)

    true if the mnemonic is valid



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/bsv/primitives/mnemonic.rb', line 139

def valid?
  return false unless [12, 15, 18, 21, 24].include?(@words.length)
  return false if @words.any? { |w| !ENGLISH_WORD_MAP.key?(w) }

  indices = @words.map { |w| ENGLISH_WORD_MAP[w] }
  all_bits = indices.map { |i| i.to_s(2).rjust(11, '0') }.join

  entropy_bit_count = (@words.length * 11 * 32) / 33
  checksum_bit_count = entropy_bit_count / 32

  entropy_bits = all_bits[0, entropy_bit_count]
  checksum_bits = all_bits[entropy_bit_count, checksum_bit_count]

  entropy_bytes = entropy_bits.chars.each_slice(8).map { |byte| byte.join.to_i(2) }.pack('C*')
  expected_checksum = self.class.send(:checksum_for, entropy_bytes)

  checksum_bits == expected_checksum
end