icostan/cryptos-ruby

View on GitHub
lib/cryptos/monero/wallet.rb

Summary

Maintainability
A
2 hrs
Test Coverage
require 'zlib'

module Cryptos
  module Monero
    # Deterministic wallet
    class Wallet
      # the English dictionary
      WORDS = [
        "abbey", "abducts", "ability", "ablaze", "abnormal", "abort", "abrasive", "absorb",
        "abyss", "academy", "aces", "aching", "acidic", "acoustic", "acquire", "across",
        "actress", "acumen", "adapt", "addicted", "adept", "adhesive", "adjust", "adopt",
        "adrenalin", "adult", "adventure", "aerial", "afar", "affair", "afield", "afloat",
        "afoot", "afraid", "after", "against", "agenda", "aggravate", "agile", "aglow",
        "agnostic", "agony", "agreed", "ahead", "aided", "ailments", "aimless", "airport",
        "aisle", "ajar", "akin", "alarms", "album", "alchemy", "alerts", "algebra",
        "alkaline", "alley", "almost", "aloof", "alpine", "already", "also", "altitude",
        "alumni", "always", "amaze", "ambush", "amended", "amidst", "ammo", "amnesty",
        "among", "amply", "amused", "anchor", "android", "anecdote", "angled", "ankle",
        "annoyed", "answers", "antics", "anvil", "anxiety", "anybody", "apart", "apex",
        "aphid", "aplomb", "apology", "apply", "apricot", "aptitude", "aquarium", "arbitrary",
        "archer", "ardent", "arena", "argue", "arises", "army", "around", "arrow",
        "arsenic", "artistic", "ascend", "ashtray", "aside", "asked", "asleep", "aspire",
        "assorted", "asylum", "athlete", "atlas", "atom", "atrium", "attire", "auburn",
        "auctions", "audio", "august", "aunt", "austere", "autumn", "avatar", "avidly",
        "avoid", "awakened", "awesome", "awful", "awkward", "awning", "awoken", "axes",
        "axis", "axle", "aztec", "azure", "baby", "bacon", "badge", "baffles",
        "bagpipe", "bailed", "bakery", "balding", "bamboo", "banjo", "baptism", "basin",
        "batch", "bawled", "bays", "because", "beer", "befit", "begun", "behind",
        "being", "below", "bemused", "benches", "berries", "bested", "betting", "bevel",
        "beware", "beyond", "bias", "bicycle", "bids", "bifocals", "biggest", "bikini",
        "bimonthly", "binocular", "biology", "biplane", "birth", "biscuit", "bite", "biweekly",
        "blender", "blip", "bluntly", "boat", "bobsled", "bodies", "bogeys", "boil",
        "boldly", "bomb", "border", "boss", "both", "bounced", "bovine", "bowling",
        "boxes", "boyfriend", "broken", "brunt", "bubble", "buckets", "budget", "buffet",
        "bugs", "building", "bulb", "bumper", "bunch", "business", "butter", "buying",
        "buzzer", "bygones", "byline", "bypass", "cabin", "cactus", "cadets", "cafe",
        "cage", "cajun", "cake", "calamity", "camp", "candy", "casket", "catch",
        "cause", "cavernous", "cease", "cedar", "ceiling", "cell", "cement", "cent",
        "certain", "chlorine", "chrome", "cider", "cigar", "cinema", "circle", "cistern",
        "citadel", "civilian", "claim", "click", "clue", "coal", "cobra", "cocoa",
        "code", "coexist", "coffee", "cogs", "cohesive", "coils", "colony", "comb",
        "cool", "copy", "corrode", "costume", "cottage", "cousin", "cowl", "criminal",
        "cube", "cucumber", "cuddled", "cuffs", "cuisine", "cunning", "cupcake", "custom",
        "cycling", "cylinder", "cynical", "dabbing", "dads", "daft", "dagger", "daily",
        "damp", "dangerous", "dapper", "darted", "dash", "dating", "dauntless", "dawn",
        "daytime", "dazed", "debut", "decay", "dedicated", "deepest", "deftly", "degrees",
        "dehydrate", "deity", "dejected", "delayed", "demonstrate", "dented", "deodorant", "depth",
        "desk", "devoid", "dewdrop", "dexterity", "dialect", "dice", "diet", "different",
        "digit", "dilute", "dime", "dinner", "diode", "diplomat", "directed", "distance",
        "ditch", "divers", "dizzy", "doctor", "dodge", "does", "dogs", "doing",
        "dolphin", "domestic", "donuts", "doorway", "dormant", "dosage", "dotted", "double",
        "dove", "down", "dozen", "dreams", "drinks", "drowning", "drunk", "drying",
        "dual", "dubbed", "duckling", "dude", "duets", "duke", "dullness", "dummy",
        "dunes", "duplex", "duration", "dusted", "duties", "dwarf", "dwelt", "dwindling",
        "dying", "dynamite", "dyslexic", "each", "eagle", "earth", "easy", "eating",
        "eavesdrop", "eccentric", "echo", "eclipse", "economics", "ecstatic", "eden", "edgy",
        "edited", "educated", "eels", "efficient", "eggs", "egotistic", "eight", "either",
        "eject", "elapse", "elbow", "eldest", "eleven", "elite", "elope", "else",
        "eluded", "emails", "ember", "emerge", "emit", "emotion", "empty", "emulate",
        "energy", "enforce", "enhanced", "enigma", "enjoy", "enlist", "enmity", "enough",
        "enraged", "ensign", "entrance", "envy", "epoxy", "equip", "erase", "erected",
        "erosion", "error", "eskimos", "espionage", "essential", "estate", "etched", "eternal",
        "ethics", "etiquette", "evaluate", "evenings", "evicted", "evolved", "examine", "excess",
        "exhale", "exit", "exotic", "exquisite", "extra", "exult", "fabrics", "factual",
        "fading", "fainted", "faked", "fall", "family", "fancy", "farming", "fatal",
        "faulty", "fawns", "faxed", "fazed", "feast", "february", "federal", "feel",
        "feline", "females", "fences", "ferry", "festival", "fetches", "fever", "fewest",
        "fiat", "fibula", "fictional", "fidget", "fierce", "fifteen", "fight", "films",
        "firm", "fishing", "fitting", "five", "fixate", "fizzle", "fleet", "flippant",
        "flying", "foamy", "focus", "foes", "foggy", "foiled", "folding", "fonts",
        "foolish", "fossil", "fountain", "fowls", "foxes", "foyer", "framed", "friendly",
        "frown", "fruit", "frying", "fudge", "fuel", "fugitive", "fully", "fuming",
        "fungal", "furnished", "fuselage", "future", "fuzzy", "gables", "gadget", "gags",
        "gained", "galaxy", "gambit", "gang", "gasp", "gather", "gauze", "gave",
        "gawk", "gaze", "gearbox", "gecko", "geek", "gels", "gemstone", "general",
        "geometry", "germs", "gesture", "getting", "geyser", "ghetto", "ghost", "giant",
        "giddy", "gifts", "gigantic", "gills", "gimmick", "ginger", "girth", "giving",
        "glass", "gleeful", "glide", "gnaw", "gnome", "goat", "goblet", "godfather",
        "goes", "goggles", "going", "goldfish", "gone", "goodbye", "gopher", "gorilla",
        "gossip", "gotten", "gourmet", "governing", "gown", "greater", "grunt", "guarded",
        "guest", "guide", "gulp", "gumball", "guru", "gusts", "gutter", "guys",
        "gymnast", "gypsy", "gyrate", "habitat", "hacksaw", "haggled", "hairy", "hamburger",
        "happens", "hashing", "hatchet", "haunted", "having", "hawk", "haystack", "hazard",
        "hectare", "hedgehog", "heels", "hefty", "height", "hemlock", "hence", "heron",
        "hesitate", "hexagon", "hickory", "hiding", "highway", "hijack", "hiker", "hills",
        "himself", "hinder", "hippo", "hire", "history", "hitched", "hive", "hoax",
        "hobby", "hockey", "hoisting", "hold", "honked", "hookup", "hope", "hornet",
        "hospital", "hotel", "hounded", "hover", "howls", "hubcaps", "huddle", "huge",
        "hull", "humid", "hunter", "hurried", "husband", "huts", "hybrid", "hydrogen",
        "hyper", "iceberg", "icing", "icon", "identity", "idiom", "idled", "idols",
        "igloo", "ignore", "iguana", "illness", "imagine", "imbalance", "imitate", "impel",
        "inactive", "inbound", "incur", "industrial", "inexact", "inflamed", "ingested", "initiate",
        "injury", "inkling", "inline", "inmate", "innocent", "inorganic", "input", "inquest",
        "inroads", "insult", "intended", "inundate", "invoke", "inwardly", "ionic", "irate",
        "iris", "irony", "irritate", "island", "isolated", "issued", "italics", "itches",
        "items", "itinerary", "itself", "ivory", "jabbed", "jackets", "jaded", "jagged",
        "jailed", "jamming", "january", "jargon", "jaunt", "javelin", "jaws", "jazz",
        "jeans", "jeers", "jellyfish", "jeopardy", "jerseys", "jester", "jetting", "jewels",
        "jigsaw", "jingle", "jittery", "jive", "jobs", "jockey", "jogger", "joining",
        "joking", "jolted", "jostle", "journal", "joyous", "jubilee", "judge", "juggled",
        "juicy", "jukebox", "july", "jump", "junk", "jury", "justice", "juvenile",
        "kangaroo", "karate", "keep", "kennel", "kept", "kernels", "kettle", "keyboard",
        "kickoff", "kidneys", "king", "kiosk", "kisses", "kitchens", "kiwi", "knapsack",
        "knee", "knife", "knowledge", "knuckle", "koala", "laboratory", "ladder", "lagoon",
        "lair", "lakes", "lamb", "language", "laptop", "large", "last", "later",
        "launching", "lava", "lawsuit", "layout", "lazy", "lectures", "ledge", "leech",
        "left", "legion", "leisure", "lemon", "lending", "leopard", "lesson", "lettuce",
        "lexicon", "liar", "library", "licks", "lids", "lied", "lifestyle", "light",
        "likewise", "lilac", "limits", "linen", "lion", "lipstick", "liquid", "listen",
        "lively", "loaded", "lobster", "locker", "lodge", "lofty", "logic", "loincloth",
        "long", "looking", "lopped", "lordship", "losing", "lottery", "loudly", "love",
        "lower", "loyal", "lucky", "luggage", "lukewarm", "lullaby", "lumber", "lunar",
        "lurk", "lush", "luxury", "lymph", "lynx", "lyrics", "macro", "madness",
        "magically", "mailed", "major", "makeup", "malady", "mammal", "maps", "masterful",
        "match", "maul", "maverick", "maximum", "mayor", "maze", "meant", "mechanic",
        "medicate", "meeting", "megabyte", "melting", "memoir", "menu", "merger", "mesh",
        "metro", "mews", "mice", "midst", "mighty", "mime", "mirror", "misery",
        "mittens", "mixture", "moat", "mobile", "mocked", "mohawk", "moisture", "molten",
        "moment", "money", "moon", "mops", "morsel", "mostly", "motherly", "mouth",
        "movement", "mowing", "much", "muddy", "muffin", "mugged", "mullet", "mumble",
        "mundane", "muppet", "mural", "musical", "muzzle", "myriad", "mystery", "myth",
        "nabbing", "nagged", "nail", "names", "nanny", "napkin", "narrate", "nasty",
        "natural", "nautical", "navy", "nearby", "necklace", "needed", "negative", "neither",
        "neon", "nephew", "nerves", "nestle", "network", "neutral", "never", "newt",
        "nexus", "nibs", "niche", "niece", "nifty", "nightly", "nimbly", "nineteen",
        "nirvana", "nitrogen", "nobody", "nocturnal", "nodes", "noises", "nomad", "noodles",
        "northern", "nostril", "noted", "nouns", "novelty", "nowhere", "nozzle", "nuance",
        "nucleus", "nudged", "nugget", "nuisance", "null", "number", "nuns", "nurse",
        "nutshell", "nylon", "oaks", "oars", "oasis", "oatmeal", "obedient", "object",
        "obliged", "obnoxious", "observant", "obtains", "obvious", "occur", "ocean", "october",
        "odds", "odometer", "offend", "often", "oilfield", "ointment", "okay", "older",
        "olive", "olympics", "omega", "omission", "omnibus", "onboard", "oncoming", "oneself",
        "ongoing", "onion", "online", "onslaught", "onto", "onward", "oozed", "opacity",
        "opened", "opposite", "optical", "opus", "orange", "orbit", "orchid", "orders",
        "organs", "origin", "ornament", "orphans", "oscar", "ostrich", "otherwise", "otter",
        "ouch", "ought", "ounce", "ourselves", "oust", "outbreak", "oval", "oven",
        "owed", "owls", "owner", "oxidant", "oxygen", "oyster", "ozone", "pact",
        "paddles", "pager", "pairing", "palace", "pamphlet", "pancakes", "paper", "paradise",
        "pastry", "patio", "pause", "pavements", "pawnshop", "payment", "peaches", "pebbles",
        "peculiar", "pedantic", "peeled", "pegs", "pelican", "pencil", "people", "pepper",
        "perfect", "pests", "petals", "phase", "pheasants", "phone", "phrases", "physics",
        "piano", "picked", "pierce", "pigment", "piloted", "pimple", "pinched", "pioneer",
        "pipeline", "pirate", "pistons", "pitched", "pivot", "pixels", "pizza", "playful",
        "pledge", "pliers", "plotting", "plus", "plywood", "poaching", "pockets", "podcast",
        "poetry", "point", "poker", "polar", "ponies", "pool", "popular", "portents",
        "possible", "potato", "pouch", "poverty", "powder", "pram", "present", "pride",
        "problems", "pruned", "prying", "psychic", "public", "puck", "puddle", "puffin",
        "pulp", "pumpkins", "punch", "puppy", "purged", "push", "putty", "puzzled",
        "pylons", "pyramid", "python", "queen", "quick", "quote", "rabbits", "racetrack",
        "radar", "rafts", "rage", "railway", "raking", "rally", "ramped", "randomly",
        "rapid", "rarest", "rash", "rated", "ravine", "rays", "razor", "react",
        "rebel", "recipe", "reduce", "reef", "refer", "regular", "reheat", "reinvest",
        "rejoices", "rekindle", "relic", "remedy", "renting", "reorder", "repent", "request",
        "reruns", "rest", "return", "reunion", "revamp", "rewind", "rhino", "rhythm",
        "ribbon", "richly", "ridges", "rift", "rigid", "rims", "ringing", "riots",
        "ripped", "rising", "ritual", "river", "roared", "robot", "rockets", "rodent",
        "rogue", "roles", "romance", "roomy", "roped", "roster", "rotate", "rounded",
        "rover", "rowboat", "royal", "ruby", "rudely", "ruffled", "rugged", "ruined",
        "ruling", "rumble", "runway", "rural", "rustled", "ruthless", "sabotage", "sack",
        "sadness", "safety", "saga", "sailor", "sake", "salads", "sample", "sanity",
        "sapling", "sarcasm", "sash", "satin", "saucepan", "saved", "sawmill", "saxophone",
        "sayings", "scamper", "scenic", "school", "science", "scoop", "scrub", "scuba",
        "seasons", "second", "sedan", "seeded", "segments", "seismic", "selfish", "semifinal",
        "sensible", "september", "sequence", "serving", "session", "setup", "seventh", "sewage",
        "shackles", "shelter", "shipped", "shocking", "shrugged", "shuffled", "shyness", "siblings",
        "sickness", "sidekick", "sieve", "sifting", "sighting", "silk", "simplest", "sincerely",
        "sipped", "siren", "situated", "sixteen", "sizes", "skater", "skew", "skirting",
        "skulls", "skydive", "slackens", "sleepless", "slid", "slower", "slug", "smash",
        "smelting", "smidgen", "smog", "smuggled", "snake", "sneeze", "sniff", "snout",
        "snug", "soapy", "sober", "soccer", "soda", "software", "soggy", "soil",
        "solved", "somewhere", "sonic", "soothe", "soprano", "sorry", "southern", "sovereign",
        "sowed", "soya", "space", "speedy", "sphere", "spiders", "splendid", "spout",
        "sprig", "spud", "spying", "square", "stacking", "stellar", "stick", "stockpile",
        "strained", "stunning", "stylishly", "subtly", "succeed", "suddenly", "suede", "suffice",
        "sugar", "suitcase", "sulking", "summon", "sunken", "superior", "surfer", "sushi",
        "suture", "swagger", "swept", "swiftly", "sword", "swung", "syllabus", "symptoms",
        "syndrome", "syringe", "system", "taboo", "tacit", "tadpoles", "tagged", "tail",
        "taken", "talent", "tamper", "tanks", "tapestry", "tarnished", "tasked", "tattoo",
        "taunts", "tavern", "tawny", "taxi", "teardrop", "technical", "tedious", "teeming",
        "tell", "template", "tender", "tepid", "tequila", "terminal", "testing", "tether",
        "textbook", "thaw", "theatrics", "thirsty", "thorn", "threaten", "thumbs", "thwart",
        "ticket", "tidy", "tiers", "tiger", "tilt", "timber", "tinted", "tipsy",
        "tirade", "tissue", "titans", "toaster", "tobacco", "today", "toenail", "toffee",
        "together", "toilet", "token", "tolerant", "tomorrow", "tonic", "toolbox", "topic",
        "torch", "tossed", "total", "touchy", "towel", "toxic", "toyed", "trash",
        "trendy", "tribal", "trolling", "truth", "trying", "tsunami", "tubes", "tucks",
        "tudor", "tuesday", "tufts", "tugs", "tuition", "tulips", "tumbling", "tunnel",
        "turnip", "tusks", "tutor", "tuxedo", "twang", "tweezers", "twice", "twofold",
        "tycoon", "typist", "tyrant", "ugly", "ulcers", "ultimate", "umbrella", "umpire",
        "unafraid", "unbending", "uncle", "under", "uneven", "unfit", "ungainly", "unhappy",
        "union", "unjustly", "unknown", "unlikely", "unmask", "unnoticed", "unopened", "unplugs",
        "unquoted", "unrest", "unsafe", "until", "unusual", "unveil", "unwind", "unzip",
        "upbeat", "upcoming", "update", "upgrade", "uphill", "upkeep", "upload", "upon",
        "upper", "upright", "upstairs", "uptight", "upwards", "urban", "urchins", "urgent",
        "usage", "useful", "usher", "using", "usual", "utensils", "utility", "utmost",
        "utopia", "uttered", "vacation", "vague", "vain", "value", "vampire", "vane",
        "vapidly", "vary", "vastness", "vats", "vaults", "vector", "veered", "vegan",
        "vehicle", "vein", "velvet", "venomous", "verification", "vessel", "veteran", "vexed",
        "vials", "vibrate", "victim", "video", "viewpoint", "vigilant", "viking", "village",
        "vinegar", "violin", "vipers", "virtual", "visited", "vitals", "vivid", "vixen",
        "vocal", "vogue", "voice", "volcano", "vortex", "voted", "voucher", "vowels",
        "voyage", "vulture", "wade", "waffle", "wagtail", "waist", "waking", "wallets",
        "wanted", "warped", "washing", "water", "waveform", "waxing", "wayside", "weavers",
        "website", "wedge", "weekday", "weird", "welders", "went", "wept", "were",
        "western", "wetsuit", "whale", "when", "whipped", "whole", "wickets", "width",
        "wield", "wife", "wiggle", "wildly", "winter", "wipeout", "wiring", "wise",
        "withdrawn", "wives", "wizard", "wobbly", "woes", "woken", "wolf", "womanly",
        "wonders", "woozy", "worry", "wounded", "woven", "wrap", "wrist", "wrong",
        "yacht", "yahoo", "yanks", "yard", "yawning", "yearbook", "yellow", "yesterday",
        "yeti", "yields", "yodel", "yoga", "younger", "yoyo", "zapped", "zeal",
        "zebra", "zero", "zesty", "zigzags", "zinger", "zippers", "zodiac", "zombie",
        "zones", "zoom"
      ].freeze

      attr_reader :seed

      def initialize(seed)
        @seed = seed
      end

      # Create a wallet instance from mnemomnic words
      # @param mnemonic the mnemonic words
      # @return [Cryptos::Monero::Wallet] the wallet instance
      def self.from_mnemonic(mnemonic)
        words = mnemonic.split
        raise ArgumentError, "invalid mnemonic size: expected=25, got=#{words.length}" if words.length != 25

        last_word = words.pop
        checksum_word = checksum_word words
        raise ArgumentError, "invalid checksum: expected=#{checksum_word} got=#{last_word}" if last_word != checksum_word

        seeds = words.each_slice(3).map do |w1, w2, w3|
          i1 = WORDS.index w1
          i2 = WORDS.index w2
          i3 = WORDS.index w3
          raise "invalid word: #{w1}: #{i1} #{w2}: #{i2} #{w3}: #{i3}" if [i1, i2, i3].any? &:nil?

          x = i1 + base * (((base - i1) + i2) % base) + base**2 * (((base - i2) + i3) % base);
          raise 'invalid decoding' if x % base != i1

          [x].pack('V').unpack 'H*'
        end
        new seeds.flatten.join
      end

      # Mnemonic words
      # 4 bytes -> 3 words.  8 digits base 16 -> 3 digits base 1626
      # @return [Array] mnemonic words
      def to_mnemonic
        words = [@seed].pack('H*').unpack('V*').map do |n|
          i1 = (n / base**0 + 0) % base
          i2 = (n / base**1 + i1) % base
          i3 = (n / base**2 + i2) % base
          [WORDS[i1], WORDS[i2], WORDS[i3]]
        end.flatten
        words << self.class.checksum_word(words)
        words.join ' '
      end

      # Create private spend key
      # @return [Cryptos::Monero::PrivateSpendKey] the private spend key instance
      def private_spend_key
        key = bytes_to_bignum [seed].pack('H*').reverse
        Cryptos::Monero::PrivateSpendKey.new key
      end

      def base
        self.class.base
      end

      def self.base
        WORDS.length
      end

      def self.checksum_word(words, len = 3)
        checksum = Zlib::crc32(words.map{ |w| w[0, len]}.join)
        index = checksum % words.length
        words[index]
      end
    end
  end
end