]> jxnshi.xyz Git - mesange.git/commitdiff
Working on loading the profile.
authorjxnshi <jxnshi@cock.li>
Thu, 2 Jan 2025 12:55:42 +0000 (13:55 +0100)
committerjxnshi <jxnshi@cock.li>
Thu, 2 Jan 2025 12:55:42 +0000 (13:55 +0100)
client-cli/bip_0039.odin [new file with mode: 0644]
client-cli/client-cli
client-cli/command.odin
client-cli/config.odin [new file with mode: 0644]
client-cli/main.odin
client-cli/profile.odin [new file with mode: 0644]
client-cli/state.odin

diff --git a/client-cli/bip_0039.odin b/client-cli/bip_0039.odin
new file mode 100644 (file)
index 0000000..c80dca7
--- /dev/null
@@ -0,0 +1,2064 @@
+package main
+
+bip_0039_words :: [?]string{
+    "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",
+}
+
+bip_0039_max_word_len :: proc() -> int {
+    max_len: int
+
+    for word in bip_0039_words {
+        if len(word) > max_len {
+            max_len = len(word)
+        }
+    }
+
+    return max_len
+}
index 47655fcbc5c844bf7325104921c626a50279e05d..00379315ca18186bcb67cc6180e65127181be0c9 100755 (executable)
Binary files a/client-cli/client-cli and b/client-cli/client-cli differ
index bd17fb080939af1ded8717e03af0fd100c7bbd45..749181a46dbed5f3e0377cbabef7f0f465237199 100644 (file)
@@ -18,6 +18,13 @@ handle_command :: proc(app: ^App, command: string) -> bool {
             config_path := fpath.join({ app.storage_path, "config.json" }, context.temp_allocator)
             os.remove_directory(config_path)
         }
+    case .Invalid_Profile:
+        switch command {
+        case ":r",   ":retry":
+        case ":del", ":delete":
+            profile_path := app_get_profile_path(app, context.temp_allocator)
+            os.remove_directory(profile_path)
+        }
     }
 
     // General commands.
diff --git a/client-cli/config.odin b/client-cli/config.odin
new file mode 100644 (file)
index 0000000..4079819
--- /dev/null
@@ -0,0 +1,83 @@
+package main
+
+import "core:encoding/json"
+import "core:os"
+import "core:strings"
+
+import fpath "core:path/filepath"
+
+Config :: struct {
+    selected_profile: Maybe(string),
+}
+
+Config_Parsed :: struct {
+    selected_profile: Maybe(string),
+}
+
+Config_Load_Error :: enum {
+    No_File_Present,
+    Invalid_File,
+}
+
+config_from_parsed :: proc(parsed_config: Config_Parsed) -> Config {
+    selected_profile: Maybe(string)
+
+    if parsed_selected_profile, ok := parsed_config.selected_profile.?; ok {
+        selected_profile, _ = strings.clone(parsed_selected_profile)
+    }
+
+    return {
+        selected_profile = selected_profile,
+    }
+}
+
+config_to_parsed :: proc(config: Config) -> Config_Parsed {
+    return {
+        selected_profile = config.selected_profile,
+    }
+}
+
+config_load :: proc(app: ^App) -> (Config, Config_Load_Error) {
+    config_path := fpath.join({ app.storage_path, "config.json" }, context.temp_allocator)
+
+    raw_config, config_read := os.read_entire_file_from_filename(config_path)
+    parsed_config: Config_Parsed
+
+    if !config_read {
+        return {}, .No_File_Present
+    }
+
+    err := json.unmarshal(raw_config, &parsed_config, allocator = context.temp_allocator)
+
+    if err != nil {
+        return {}, .Invalid_File
+    }
+
+    return config_from_parsed(parsed_config), nil
+}
+
+config_deinit :: proc(config: Config) {
+    if selected_profiles, ok := config.selected_profile.?; ok {
+        delete(selected_profiles)
+    }
+}
+
+config_update :: proc(config: Config, app: ^App) {
+    config_path := fpath.join({ app.storage_path, "config.json" }, context.temp_allocator)
+    config_file, err1 := os.open(config_path, os.O_WRONLY | os.O_CREATE | os.O_TRUNC, 0777)
+
+    parsed_config := config_to_parsed(config)
+
+    if err1 != nil {
+        app_set_info_bar(app, "Failed to save config.")
+        return
+    }
+
+    config_file_stream := os.stream_from_handle(config_file)
+    err2 := json.marshal_to_writer(config_file_stream, parsed_config, &json_marshal_options)
+
+    if err2 != nil {
+        app_set_info_bar(app, "Failed to save config.")
+        return
+    }
+}
index bd9fd59c2e247c4c91404778bc6734589d8a0b3d..c3c163004e14aeb07bf84349b474bfc9e247a19d 100644 (file)
@@ -1,8 +1,5 @@
 package main
 
-import "core:crypto/aes"
-import "core:crypto/ed25519"
-import "core:encoding/csv"
 import "core:encoding/json"
 import "core:fmt"
 import "core:log"
@@ -30,144 +27,6 @@ json_marshal_options := json.Marshal_Options{
     use_enum_names = true,
 }
 
-Config :: struct {
-    selected_profile: Maybe(string),
-}
-
-config_deinit :: proc(config: Config) {
-    if selected_profiles, ok := config.selected_profile.?; ok {
-        delete(selected_profiles)
-    }
-}
-
-config_update :: proc(config: Config, app: App) {
-    config_path := fpath.join({ app.storage_path, "config.json" }, context.temp_allocator)
-    config_file, err1 := os.open(config_path, os.O_WRONLY | os.O_CREATE | os.O_TRUNC, 0777)
-
-    if err1 != nil {
-        app_set_info_bar(app, "Failed to save config.")
-        return
-    }
-
-    config_file_stream := os.stream_from_handle(config_file)
-    err2 := json.marshal_to_writer(config_file_stream, config, &json_marshal_options)
-
-    if err2 != nil {
-        app_set_info_bar(app, "Failed to save config.")
-        return
-    }
-}
-
-Profile :: struct {
-    private_key: ed25519.Private_Key,
-}
-
-profile_load_from_name :: proc(name: string, app: App) -> (Profile, bool) {
-    buffer: [4096]u8
-    profile_content: string
-
-    // Loads encrypted.
-    if profile_password, ok := app.profile_password.?; ok {
-        encrypted_profile_path := fpath.join({ app.storage_path, selected_profile }, context.temp_allocator)
-        encrypted_profile_content, profile_read := os.read_entire_file_from_filename(encrypted_profile_path, context.temp_allocator)
-
-        if !profile_read {
-            return {}, false
-        }
-
-        ecb_context: aes.Context_ECB
-        aes.init_ecb(&ecb_context, app.profile_password.?)
-
-        iv := encrypted_profile_content[:aes.GCM_IV_SIZE]
-        tag := encrypted_profile_content[len(iv):][:aes.GCM_TAG_SIZE]
-        ciphertext := encrypted_profile_content[len(iv) + len(tag):]
-
-        if !aes.decrypt(&ecb_context, buffer[:], iv, {}, ciphertext, tag) {
-            app_set_info_bar(app, "Invalid password.")
-            app_set_state(app, .Ask_Profile_Password)
-            return
-        }
-
-        profile_content = string(buffer[:len(ciphertext)])
-    }
-    
-    // Loads clear text.
-    else {
-        profile_filename := strings.concatenate(app.config.selected_profile.?, ".json", context.temp_allocator)
-        profile_path = fpath.join({ app.storage_path, profile_filename }, context.temp_allocator)
-
-        profile_content, profile_read := os.read_entire_file_from_filename(profile_path, context.temp_allocator)
-
-        if !profile_read {
-            return {}, false
-        }
-    }
-}
-
-profile_update :: proc(profile: Profile, app: App) {
-    profile_path: string
-
-    if _, ok := app.profile_password.?; ok {
-        profile_path = fpath.join({ app.storage_path, app.config.selected_profile.? }, context.temp_allocator)
-    } else {
-        profile_filename := strings.concatenate(app.config.selected_profile.?, ".json", context.temp_allocator)
-        profile_path = fpath.join({ app.storage_path, profile_filename }, context.temp_allocator)
-    }
-
-    profile_file, err1 := os.open(profile_path, os.O_WRONLY, | os.O_CREATE | os.O_TRUNC, 0777)
-
-    if err1 != nil {
-        app_set_info_bar(app, "Failed to save profile.")
-        return
-    }
-
-    // Saves encrypted.
-    if profile_password, ok := app.profile_password.?; ok {
-        json_string_builder: strings.Builder
-        strings.builder_init_none(&json_string_builder, context.temp_allocator)
-
-        err2 := json.marshal_to_builder(&json_string_builder, profile, &json_marshal_options)
-
-        if err2 != nil {
-            app_set_info_bar(app, "Failed to save profile.")
-            return
-        }
-
-        json_string := strings.to_string(json_string_builder)
-
-        aes_context: aes.Context_GCM
-        aes.init_gcm(&aes_context, profile_password)
-
-        buffer: [4096]u8
-        iv := buffer[:aes.GCM_IV_SIZE]
-        tag := buffer[len(iv):][:aes.GCM_TAG_SIZE]
-
-        rand.read(iv[:])
-
-        aes.seal_gcm(&ecb_context, buffer[len(iv) + len(tag):], tag, iv, {}, transmute([]u8)json_string)
-
-        content := string(buffer[:len(iv) + len(tag) + len(json_string)])
-
-        _, err3 := os.write_string(profile_file, content)
-
-        if err3 != nil {
-            app_set_info_bar(app, "Failed to save profile.")
-            return
-        }
-    }
-    
-    // Saves clear text.
-    else {
-        profile_file_stream := os.stream_from_handle(profile_file)
-        err2 := json.marshal_to_writer(profile_file_stream, config, &json_marshal_options)
-
-        if err2 != nil {
-            app_set_info_bar(app, "Failed to save profile.")
-            return
-        }
-    }
-}
-
 App :: struct {
     mutex: sync.Mutex,
     running: bool,
@@ -185,7 +44,7 @@ App :: struct {
     profile: Profile,
 }
 
-app_init :: proc() -> App {
+app_init :: proc(storage_path: string) -> App {
     screen := nc.initscr()
 
     nc.cbreak()
@@ -210,9 +69,6 @@ app_init :: proc() -> App {
 
        nc.refresh()
 
-    home_path := os.get_env("HOME")
-    storage_path := fpath.join({ home_path, "mesange" })
-
     if !os.exists(storage_path) {
         err := os.make_directory(storage_path)
 
@@ -232,13 +88,13 @@ app_init :: proc() -> App {
         input_window = input_window,
     }
 
-    app_clear_info_bar(app)
+    app_clear_info_bar(&app)
     app_set_state(&app, .Load_Config)
 
     return app
 }
 
-app_deinit :: proc(app: App) {
+app_deinit :: proc(app: ^App) {
     config_deinit(app.config)
 
     nc.delwin(app.input_window)
@@ -256,13 +112,13 @@ app_set_state :: proc(app: ^App, state: State) {
     nc.refresh()
 
     app_clear_box_message(app)
-    app_clear_info_bar(app^)
+    app_clear_info_bar(app)
 
     app.state = state
 }
 
 app_set_info_bar :: proc(app: ^App, content: string) {
-    app_clear_info_bar(app^)
+    app_clear_info_bar(app)
 
     c_content := strings.clone_to_cstring(content, context.temp_allocator)
 
@@ -314,7 +170,7 @@ app_set_box_message :: proc(app: ^App, lines: []string) {
     sync.mutex_unlock(&app.mutex)
 }
 
-app_clear_info_bar :: proc(app: App) {
+app_clear_info_bar :: proc(app: ^App) {
     height, width := nc.getmaxyx(app.info_bar)
     color := nc.COLOR_PAIR(1)
 
@@ -339,7 +195,7 @@ app_clear_box_message :: proc(app: ^App) {
     }
 }
 
-app_get_input :: proc(app: App, allocator := context.allocator) -> string {
+app_get_input :: proc(app: ^App, allocator := context.allocator) -> string {
     context.allocator = allocator
 
     nc.wclear(app.input_window)
@@ -358,6 +214,15 @@ app_get_input :: proc(app: App, allocator := context.allocator) -> string {
     return strings.clone_from_cstring(cstring(raw_data(&buffer)))
 }
 
+app_get_profile_path :: proc(app: ^App, allocator := context.allocator) -> string {
+    if _, ok := app.profile_password.?; ok {
+        return fpath.join({ app.storage_path, app.config.selected_profile.? }, allocator)
+    } else {
+        profile_filename := strings.concatenate({ app.config.selected_profile.?, ".json" }, allocator)
+        return fpath.join({ app.storage_path, profile_filename }, allocator)
+    }
+}
+
 handle_state :: proc(app: ^App) {
     for {
         switch app.state {
@@ -365,6 +230,7 @@ handle_state :: proc(app: ^App) {
         case .Invalid_Config: state_invalid_config(app)
 
         case .Load_Profile: state_load_profile(app)
+        case .Invalid_Profile: state_invalid_profile(app)
         case .Ask_Profile: state_ask_profile(app)
         case .Ask_Profile_Password: state_ask_profile_password(app)
         case .Ask_Profile_Seed_Phrase: state_ask_profile_seed_phrase(app)
@@ -373,12 +239,50 @@ handle_state :: proc(app: ^App) {
 }
 
 main :: proc() {
+    home_path := os.get_env("HOME")
+    storage_path := fpath.join({ home_path, "mesange" }, context.temp_allocator)
+    log_path := fpath.join({ storage_path, "log.txt" }, context.temp_allocator)
+
+    log_file, err := os.open(log_path, os.O_WRONLY | os.O_CREATE | os.O_TRUNC, 0777)
+    defer os.close(log_file)
+
+    context.logger = log.create_file_logger(log_file)
+    defer log.destroy_file_logger(context.logger)
+
     now := transmute(u64)time.now()
+    rng_state := rand.create(now)
+
+    context.random_generator = rand.default_random_generator(&rng_state)
+
+    when ODIN_DEBUG {
+               track: mem.Tracking_Allocator
+               mem.tracking_allocator_init(&track, context.allocator)
+
+               context.allocator = mem.tracking_allocator(&track)
+
+               defer {
+                       if len(track.allocation_map) > 0 {
+                               log.errorf("%v allocations not freed:", len(track.allocation_map))
+
+                               for _, entry in track.allocation_map {
+                                       log.errorf("- %v bytes at %v\n", entry.size, entry.location)
+                               }
+                       }
+
+                       if len(track.bad_free_array) > 0 {
+                               log.errof("%v incorrect frees:\n", len(track.bad_free_array))
 
-    context.random_generator := rand.create(now)
+                               for entry in track.bad_free_array {
+                                       log.errorf("- %p at %v\n", entry.memory, entry.location)
+                               }
+                       }
 
-    app := app_init()
-    defer app_deinit(app)
+                       mem.tracking_allocator_destroy(&track)
+               }
+       }
+       
+    app := app_init(storage_path)
+    defer app_deinit(&app)
 
     handle_state_thread := thread.create_and_start_with_poly_data(&app, handle_state, context)
     defer thread.terminate(handle_state_thread, 0)
diff --git a/client-cli/profile.odin b/client-cli/profile.odin
new file mode 100644 (file)
index 0000000..238fae5
--- /dev/null
@@ -0,0 +1,157 @@
+package main
+import "core:crypto/aes"
+import "core:crypto/ed25519"
+import "core:encoding/json"
+import "core:math/rand"
+import "core:os"
+import "core:strings"
+
+import fpath "core:path/filepath"
+
+Profile :: struct {
+    private_key: ed25519.Private_Key,
+    host: string,
+}
+
+Profile_Parsed :: struct {
+    private_key: [ed25519.PRIVATE_KEY_SIZE]u8,
+    host: string,
+}
+
+Profile_Load_Error :: enum {
+    No_File_Present,
+    Invalid_Password,
+    Invalid_File,
+}
+
+profile_from_parsed :: proc(parsed_profile: Profile_Parsed, allocator := context.allocator) -> Profile {
+    parsed_profile := parsed_profile
+
+    private_key: ed25519.Private_Key
+    _ = ed25519.private_key_set_bytes(&private_key, parsed_profile.private_key[:])
+
+    host, err := strings.clone(parsed_profile.host, allocator)
+
+    return {
+        private_key = private_key,
+        host = host,
+    }
+}
+
+profile_to_parsed :: proc(profile: Profile) -> Profile_Parsed {
+    profile := profile
+
+    private_key: [ed25519.PRIVATE_KEY_SIZE]u8
+    ed25519.private_key_bytes(&profile.private_key, private_key[:])
+
+    return {
+        private_key = private_key,
+        host = profile.host,
+    }
+}
+
+profile_load_from_name :: proc(name: string, app: ^App) -> (Profile, Profile_Load_Error) {
+    buffer: [4096]u8
+    profile_content: string
+
+    if profile_password, ok := app.profile_password.?; ok {
+        encrypted_profile_path := fpath.join({ app.storage_path, name }, context.temp_allocator)
+        encrypted_profile_content, profile_read := os.read_entire_file_from_filename(encrypted_profile_path, context.temp_allocator)
+
+        if !profile_read {
+            return {}, .No_File_Present
+        }
+
+        aes_context: aes.Context_GCM
+        aes.init_gcm(&aes_context, transmute([]u8)app.profile_password.?)
+
+        iv := encrypted_profile_content[:aes.GCM_IV_SIZE]
+        tag := encrypted_profile_content[len(iv):][:aes.GCM_TAG_SIZE]
+        ciphertext := encrypted_profile_content[len(iv) + len(tag):]
+
+        if !aes.open_gcm(&aes_context, buffer[:], iv, {}, ciphertext, tag) {
+            return {}, .Invalid_Password
+        }
+
+        profile_content = string(buffer[:len(ciphertext)])
+    } else {
+        profile_filename := strings.concatenate({ name, ".json" }, context.temp_allocator)
+        profile_path := fpath.join({ app.storage_path, profile_filename }, context.temp_allocator)
+
+        profile_content, profile_read := os.read_entire_file_from_filename(profile_path, context.temp_allocator)
+
+        if !profile_read {
+            return {}, .No_File_Present
+        }
+    }
+
+    parsed_profile: Profile_Parsed
+    err := json.unmarshal(transmute([]u8)profile_content, &parsed_profile, allocator = context.temp_allocator)
+
+    if err != nil {
+        return {}, .Invalid_File
+    }
+
+    return profile_from_parsed(parsed_profile), nil
+}
+
+profile_deinit :: proc(profile: ^Profile) {
+    delete(profile.host)
+}
+
+profile_update :: proc(profile: ^Profile, app: ^App) {
+    profile_path := app_get_profile_path(app, context.temp_allocator)
+    profile_file, err1 := os.open(profile_path, os.O_WRONLY | os.O_CREATE | os.O_TRUNC, 0777)
+
+    parsed_profile := profile_to_parsed(profile^)
+
+    if err1 != nil {
+        app_set_info_bar(app, "Failed to save profile.")
+        return
+    }
+
+    if profile_password, ok := app.profile_password.?; ok {
+        json_string_builder: strings.Builder
+        strings.builder_init_none(&json_string_builder, context.temp_allocator)
+
+        err2 := json.marshal_to_builder(&json_string_builder, parsed_profile, &json_marshal_options)
+
+        if err2 != nil {
+            app_set_info_bar(app, "Failed to save profile.")
+            return
+        }
+
+        json_string := strings.to_string(json_string_builder)
+
+        aes_context: aes.Context_GCM
+        aes.init_gcm(&aes_context, transmute([]u8)profile_password)
+
+        buffer: [4096]u8
+        iv := buffer[:aes.GCM_IV_SIZE]
+        tag := buffer[len(iv):][:aes.GCM_TAG_SIZE]
+
+        if rand.read(iv[:]) < len(iv) {
+            app_set_info_bar(app, "Failed to save profile.")
+            return
+        }
+
+        aes.seal_gcm(&aes_context, buffer[len(iv) + len(tag):], tag, iv, {}, transmute([]u8)json_string)
+
+        content := string(buffer[:len(iv) + len(tag) + len(json_string)])
+
+        _, err3 := os.write_string(profile_file, content)
+
+        if err3 != nil {
+            app_set_info_bar(app, "Failed to save profile.")
+            return
+        }
+    } else {
+        profile_file_stream := os.stream_from_handle(profile_file)
+        err2 := json.marshal_to_writer(profile_file_stream, parsed_profile, &json_marshal_options)
+
+        if err2 != nil {
+            app_set_info_bar(app, "Failed to save profile.")
+            return
+        }
+    }
+}
index eefad0e42f16248ea6964f5d212aaf4dbfe82879..783cdb8d2004cff9fa1161bef6ff31c41071e3b2 100644 (file)
@@ -13,9 +13,12 @@ State :: enum {
     Invalid_Config,
 
     Load_Profile,
+    Invalid_Profile,
     Ask_Profile,
     Ask_Profile_Password,
     Ask_Profile_Seed_Phrase,
+
+    Main,
 }
 
 state_load_config :: proc(app: ^App) {
@@ -49,7 +52,7 @@ state_invalid_config :: proc(app: ^App) {
         },
     )
 
-    input := app_get_input(app^, context.temp_allocator)
+    input := app_get_input(app, context.temp_allocator)
 
     if input[0] == ':' {
         if !handle_command(app, input) {
@@ -79,59 +82,50 @@ state_load_profile :: proc(app: ^App) {
         return
     }
 
-    profile, ok := profile_load_from_name(selected_profile, app)
-
-    if !ok {
-        app_set_info_bar("Could not load profile, creating one.")
-    }
-
-    load_profile: {
-        profile_content: string
-
-        load_profile_content: {
-            load_encrypted: {
-                encrypted_profile_content, profile_read := os.read_entire_file_from_filename(encrypted_profile_path, context.temp_allocator)
-
-                if !profile_read {
-                    break load_encrypted
-                }
-
-                ecb_context: aes.Context_ECB
-                aes.init_ecb(&ecb_context, app.profile_password.?)
-
-                tag := encrypted_profile_content[:aes.GCM_TAG_SIZE]
-
-                buffer: [4096]u8
-                iv := encrypted_profile_content[:aes.GCM_IV_SIZE]
-                tag := encrypted_profile_content[len(iv):][:aes.GCM_TAG_SIZE]
-                ciphertext := encrypted_profile_content[len(iv) + len(tag):]
+    profile, err := profile_load_from_name(selected_profile, app)
 
-                if !aes.decrypt(&ecb_context, buffer[:], iv, {}, ciphertext, tag) {
-                    app_set_info_bar(app, "Invalid password.")
-                    app_set_state(app, .Ask_Profile_Password)
-                    return
-                }
-
-                profile_content = string(buffer[:len(ciphertext)])
+    if err != nil {
+        switch err {
+        case .No_File_Present:
+        case .Invalid_Password:
+            app_set_info_bar(app, "Invalid password.")
+            app_set_state(app, .Ask_Profile_Password)
+            return
 
-                break load_profile_content
-            }
+        case .Invalid_File:
+            app_set_state(app, .Invalid_Profile)
+            return
+        }
 
-            load_unencrypted: {
-                profile_filename := strings.concatenate(app.config.selected_profile.?, ".json", context.temp_allocator)
-                profile_path = fpath.join({ app.storage_path, profile_filename }, context.temp_allocator)
+        app_set_state(app, .Ask_Profile_Seed_Phrase)
+        return
+    }
 
-                profile_content, profile_read := os.read_entire_file_from_filename(profile_path, context.temp_allocator)
+    app.profile = profile    
+}
 
-                if !profile_read {
-                    break load_unencrypted
-                }
-            }
+state_invalid_profile :: proc(app: ^App) {
+    app_set_box_message(
+        app,
+        {
+            "Invalid profile.",
+            "Type :delete or :retry.",
+        },
+    )
 
+    input := app_get_input(app, context.temp_allocator)
 
-            break load_profile
+    if input[0] == ':' {
+        if !handle_command(app, input) {
+            app_set_info_bar(app, "Invalid command.")
+            return
         }
+    } else {
+        app_set_info_bar(app, "Invalid input.")
+        return
     }
+
+    app_set_state(app, .Load_Config)
 }
 
 state_ask_profile :: proc(app: ^App) {
@@ -143,7 +137,7 @@ state_ask_profile :: proc(app: ^App) {
         },
     )
 
-    input := app_get_input(app^)
+    input := app_get_input(app)
 
     if input[0] == ':' {
         if !handle_command(app, input) {
@@ -161,9 +155,9 @@ state_ask_profile :: proc(app: ^App) {
         return
     }
 
-    app.config.selected_profile = app_get_input(app^)
+    app.config.selected_profile = app_get_input(app)
 
-    config_update(app.config)
+    config_update(app.config, app)
     app_set_state(app, .Load_Profile)
 }
 
@@ -176,11 +170,25 @@ state_ask_profile_password :: proc(app: ^App) {
         },
     )
 
-    app.profile_password = app_get_input(app^)
+    app.profile_password = app_get_input(app)
 
     app_set_state(app, .Load_Profile)
 }
 
 state_ask_profile_seed_phrase :: proc(app: ^App) {
+    max_seed_word_len := bip_0039_max_word_len()
+
+    app_set_box_message(
+        app,
+        {
+            "No seed phrase found.",
+            "Please enter either your seed phrase",
+            "or the one bellow.",
+            "",
+        }
+    )
+}
+
+state_main :: proc(app: ^App) {
     
 }