mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-04 04:27:34 +00:00
the backup flag can be left "hanging around" if a restore of PDDB is aborted. This command allows a user to override that setting in case this is necessary.
1301 lines
61 KiB
Python
1301 lines
61 KiB
Python
#! /usr/bin/env python3
|
|
|
|
import argparse
|
|
|
|
import usb.core
|
|
import usb.util
|
|
import array
|
|
import sys
|
|
import hashlib
|
|
import csv
|
|
import urllib.request
|
|
import re
|
|
|
|
from progressbar.bar import ProgressBar
|
|
from Crypto.Cipher import AES
|
|
from Crypto.Hash import SHA256
|
|
from Crypto.Random import get_random_bytes
|
|
|
|
def bip39_to_bits(phrase):
|
|
BIP39_TABLE_EN = [
|
|
"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",
|
|
]
|
|
wordlist = phrase.rstrip().split()
|
|
if len(wordlist) not in [12, 15, 18, 21, 24]:
|
|
raise ValueError("BIP-39 phrase has incorrect length")
|
|
indices = []
|
|
for word in wordlist:
|
|
try:
|
|
index = BIP39_TABLE_EN.index(word)
|
|
except ValueError:
|
|
raise ValueError("{} is not a BIP-39 word".format(word))
|
|
indices.append(index)
|
|
|
|
data = bytearray()
|
|
bucket = 0
|
|
bits_in_bucket = 0
|
|
for index in indices:
|
|
bucket = (bucket << 11) | index
|
|
bits_in_bucket += 11
|
|
while bits_in_bucket >= 8:
|
|
data.append((bucket >> (bits_in_bucket - 8)) & 0xFF)
|
|
mask = 0xFFFF_FFFF ^ (0xFF << (bits_in_bucket - 8))
|
|
bucket &= mask
|
|
bits_in_bucket -= 8
|
|
|
|
if bits_in_bucket == 0:
|
|
entered_checksum = data[-1]
|
|
data = data[:-1]
|
|
else:
|
|
entered_checksum = bucket
|
|
|
|
hasher = SHA256.new()
|
|
hasher.update(data)
|
|
digest = hasher.digest()
|
|
checksum_bits = len(data) // 4
|
|
checksum = digest[0] >> (8 - checksum_bits)
|
|
|
|
if checksum == entered_checksum:
|
|
return data
|
|
else:
|
|
raise ValueError("checksum did not match on BIP-39 phrase")
|
|
|
|
class PrecursorUsb:
|
|
def __init__(self, dev):
|
|
self.dev = dev
|
|
self.RDSR = 0x05
|
|
self.RDSCUR = 0x2B
|
|
self.RDID = 0x9F
|
|
self.WREN = 0x06
|
|
self.WRDI = 0x04
|
|
self.SE4B = 0x21
|
|
self.BE4B = 0xDC
|
|
self.PP4B = 0x12
|
|
self.registers = {}
|
|
self.regions = {}
|
|
self.gitrev = ''
|
|
|
|
def register(self, name):
|
|
return int(self.registers[name], 0)
|
|
|
|
def halt(self):
|
|
if 'vexriscv_debug' in self.regions:
|
|
self.poke(int(self.regions['vexriscv_debug'][0], 0), 0x00020000)
|
|
elif 'reboot_cpu_hold_reset' in self.registers:
|
|
self.poke(self.register('reboot_cpu_hold_reset'), 1)
|
|
else:
|
|
print("Can't find reset CSR. Try updating to the latest version of this program")
|
|
|
|
def unhalt(self):
|
|
if 'vexriscv_debug' in self.regions:
|
|
self.poke(int(self.regions['vexriscv_debug'][0], 0), 0x02000000)
|
|
elif 'reboot_cpu_hold_reset' in self.registers:
|
|
self.poke(self.register('reboot_cpu_hold_reset'), 0)
|
|
else:
|
|
print("Can't find reset CSR. Try updating to the latest version of this program")
|
|
|
|
def peek(self, addr, display=False):
|
|
_dummy_s = '\x00'.encode('utf-8')
|
|
data = array.array('B', _dummy_s * 4)
|
|
|
|
numread = self.dev.ctrl_transfer(bmRequestType=(0x80 | 0x43), bRequest=0,
|
|
wValue=(addr & 0xffff), wIndex=((addr >> 16) & 0xffff),
|
|
data_or_wLength=data, timeout=500)
|
|
|
|
read_data = int.from_bytes(data.tobytes(), byteorder='little', signed=False)
|
|
if display == True:
|
|
print("0x{:08x}".format(read_data))
|
|
return read_data
|
|
|
|
def poke(self, addr, wdata, check=False, display=False):
|
|
if check == True:
|
|
_dummy_s = '\x00'.encode('utf-8')
|
|
data = array.array('B', _dummy_s * 4)
|
|
|
|
numread = self.dev.ctrl_transfer(bmRequestType=(0x80 | 0x43), bRequest=0,
|
|
wValue=(addr & 0xffff), wIndex=((addr >> 16) & 0xffff),
|
|
data_or_wLength=data, timeout=500)
|
|
|
|
read_data = int.from_bytes(data.tobytes(), byteorder='little', signed=False)
|
|
print("before poke: 0x{:08x}".format(read_data))
|
|
|
|
data = array.array('B', wdata.to_bytes(4, 'little'))
|
|
numwritten = self.dev.ctrl_transfer(bmRequestType=(0x00 | 0x43), bRequest=0,
|
|
wValue=(addr & 0xffff), wIndex=((addr >> 16) & 0xffff),
|
|
data_or_wLength=data, timeout=500)
|
|
|
|
if check == True:
|
|
_dummy_s = '\x00'.encode('utf-8')
|
|
data = array.array('B', _dummy_s * 4)
|
|
|
|
numread = self.dev.ctrl_transfer(bmRequestType=(0x80 | 0x43), bRequest=0,
|
|
wValue=(addr & 0xffff), wIndex=((addr >> 16) & 0xffff),
|
|
data_or_wLength=data, timeout=500)
|
|
|
|
read_data = int.from_bytes(data.tobytes(), byteorder='little', signed=False)
|
|
print("after poke: 0x{:08x}".format(read_data))
|
|
if display == True:
|
|
print("wrote 0x{:08x} to 0x{:08x}".format(wdata, addr))
|
|
|
|
def burst_read(self, addr, len):
|
|
_dummy_s = '\x00'.encode('utf-8')
|
|
maxlen = 4096
|
|
|
|
ret = bytearray()
|
|
packet_count = len // maxlen
|
|
if (len % maxlen) != 0:
|
|
packet_count += 1
|
|
|
|
for pkt_num in range(packet_count):
|
|
cur_addr = addr + pkt_num * maxlen
|
|
if pkt_num == packet_count - 1:
|
|
if len % maxlen != 0:
|
|
bufsize = len % maxlen
|
|
else:
|
|
bufsize = maxlen
|
|
else:
|
|
bufsize = maxlen
|
|
|
|
data = array.array('B', _dummy_s * bufsize)
|
|
numread = self.dev.ctrl_transfer(bmRequestType=(0x80 | 0x43), bRequest=0,
|
|
wValue=(cur_addr & 0xffff), wIndex=((cur_addr >> 16) & 0xffff),
|
|
data_or_wLength=data, timeout=500)
|
|
|
|
if numread != bufsize:
|
|
print("Burst read error: {} bytes requested, {} bytes read at 0x{:08x}".format(bufsize, numread, cur_addr))
|
|
exit(1)
|
|
|
|
ret = ret + data
|
|
|
|
return ret
|
|
|
|
def burst_write(self, addr, data):
|
|
if len(data) == 0:
|
|
return
|
|
|
|
# the actual "addr" doesn't matter for a burst_write, because it's specified
|
|
# as an argument to the flash_pp4b command. We lock out access to the base of
|
|
# SPINOR because it's part of the gateware, so, we pick a "safe" address to
|
|
# write to instead. The page write responder will aggregate any write data
|
|
# to anywhere in the SPINOR address range.
|
|
writebuf_addr = 0x2098_0000 # the current start address of the kernel, for example
|
|
|
|
maxlen = 4096
|
|
packet_count = len(data) // maxlen
|
|
if (len(data) % maxlen) != 0:
|
|
packet_count += 1
|
|
|
|
for pkt_num in range(packet_count):
|
|
cur_addr = addr + pkt_num * maxlen
|
|
if pkt_num == packet_count - 1:
|
|
if len(data) % maxlen != 0:
|
|
bufsize = len(data) % maxlen
|
|
else:
|
|
bufsize = maxlen
|
|
else:
|
|
bufsize = maxlen
|
|
|
|
wdata = array.array('B', data[(pkt_num * maxlen):(pkt_num * maxlen) + bufsize])
|
|
numwritten = self.dev.ctrl_transfer(bmRequestType=(0x00 | 0x43), bRequest=0,
|
|
# note use of writebuf_addr instead of cur_addr -> see comment above about the quirk of write addressing
|
|
wValue=(writebuf_addr & 0xffff), wIndex=((writebuf_addr >> 16) & 0xffff),
|
|
data_or_wLength=wdata, timeout=500)
|
|
|
|
if numwritten != bufsize:
|
|
print("Burst write error: {} bytes requested, {} bytes written at 0x{:08x}".format(bufsize, numwritten, cur_addr))
|
|
exit(1)
|
|
|
|
def ping_wdt(self):
|
|
self.poke(self.register('wdt_watchdog'), 1, display=False)
|
|
self.poke(self.register('wdt_watchdog'), 1, display=False)
|
|
|
|
def spinor_command_value(self, exec=0, lock_reads=0, cmd_code=0, dummy_cycles=0, data_words=0, has_arg=0):
|
|
return ((exec & 1) << 1 |
|
|
(lock_reads & 1) << 24 |
|
|
(cmd_code & 0xff) << 2 |
|
|
(dummy_cycles & 0x1f) << 11 |
|
|
(data_words & 0xff) << 16 |
|
|
(has_arg & 1) << 10
|
|
)
|
|
|
|
def flash_rdsr(self, lock_reads):
|
|
self.poke(self.register('spinor_cmd_arg'), 0)
|
|
self.poke(self.register('spinor_command'),
|
|
self.spinor_command_value(exec=1, lock_reads=lock_reads, cmd_code=self.RDSR, dummy_cycles=4, data_words=1, has_arg=1)
|
|
)
|
|
return self.peek(self.register('spinor_cmd_rbk_data'), display=False)
|
|
|
|
def flash_rdscur(self):
|
|
self.poke(self.register('spinor_cmd_arg'), 0)
|
|
self.poke(self.register('spinor_command'),
|
|
self.spinor_command_value(exec=1, lock_reads=1, cmd_code=self.RDSCUR, dummy_cycles=4, data_words=1, has_arg=1)
|
|
)
|
|
return self.peek(self.register('spinor_cmd_rbk_data'), display=False)
|
|
|
|
def flash_rdid(self, offset):
|
|
self.poke(self.register('spinor_cmd_arg'), 0)
|
|
self.poke(self.register('spinor_command'),
|
|
self.spinor_command_value(exec=1, cmd_code=self.RDID, dummy_cycles=4, data_words=offset, has_arg=1)
|
|
)
|
|
return self.peek(self.register('spinor_cmd_rbk_data'), display=False)
|
|
|
|
def flash_wren(self):
|
|
self.poke(self.register('spinor_cmd_arg'), 0)
|
|
self.poke(self.register('spinor_command'),
|
|
self.spinor_command_value(exec=1, lock_reads=1, cmd_code=self.WREN)
|
|
)
|
|
|
|
def flash_wrdi(self):
|
|
self.poke(self.register('spinor_cmd_arg'), 0)
|
|
self.poke(self.register('spinor_command'),
|
|
self.spinor_command_value(exec=1, lock_reads=1, cmd_code=self.WRDI)
|
|
)
|
|
|
|
def flash_se4b(self, sector_address):
|
|
self.poke(self.register('spinor_cmd_arg'), sector_address)
|
|
self.poke(self.register('spinor_command'),
|
|
self.spinor_command_value(exec=1, lock_reads=1, cmd_code=self.SE4B, has_arg=1)
|
|
)
|
|
|
|
def flash_be4b(self, block_address):
|
|
self.poke(self.register('spinor_cmd_arg'), block_address)
|
|
self.poke(self.register('spinor_command'),
|
|
self.spinor_command_value(exec=1, lock_reads=1, cmd_code=self.BE4B, has_arg=1)
|
|
)
|
|
|
|
def flash_pp4b(self, address, data_bytes):
|
|
self.poke(self.register('spinor_cmd_arg'), address)
|
|
self.poke(self.register('spinor_command'),
|
|
self.spinor_command_value(exec=1, lock_reads=1, cmd_code=self.PP4B, has_arg=1, data_words=(data_bytes//2))
|
|
)
|
|
|
|
def load_csrs(self, fname=None):
|
|
LOC_CSRCSV = 0x20277000 # this address shouldn't change because it's how we figure out our version number
|
|
# CSR extraction:
|
|
# dd if=soc_csr.bin of=csr_data_0.9.6.bin skip=2524 count=32 bs=1024
|
|
if fname == None:
|
|
csr_data = self.burst_read(LOC_CSRCSV, 0x8000)
|
|
else:
|
|
with open(fname, "rb") as f:
|
|
csr_data = f.read(0x8000)
|
|
|
|
hasher = hashlib.sha512()
|
|
hasher.update(csr_data[:0x7FC0])
|
|
digest = hasher.digest()
|
|
if digest != csr_data[0x7fc0:]:
|
|
print("Could not find a valid csr.csv descriptor on the device, aborting!")
|
|
exit(1)
|
|
|
|
csr_len = int.from_bytes(csr_data[:4], 'little')
|
|
csr_extracted = csr_data[4:4+csr_len]
|
|
decoded = csr_extracted.decode('utf-8')
|
|
# strip comments
|
|
stripped = []
|
|
for line in decoded.split('\n'):
|
|
if line.startswith('#') == False:
|
|
stripped.append(line)
|
|
# create database
|
|
csr_db = csv.reader(stripped)
|
|
for row in csr_db:
|
|
if len(row) > 1:
|
|
if 'csr_register' in row[0]:
|
|
self.registers[row[1]] = row[2]
|
|
if 'memory_region' in row[0]:
|
|
self.regions[row[1]] = [row[2], row[3]]
|
|
if 'git_rev' in row[0]:
|
|
self.gitrev = row[1]
|
|
print("Using SoC {} registers".format(self.gitrev))
|
|
|
|
def erase_region(self, addr, length):
|
|
# ID code check
|
|
code = self.flash_rdid(1)
|
|
print("ID code bytes 1-2: 0x{:08x}".format(code))
|
|
if code != 0x8080c2c2:
|
|
print("ID code mismatch")
|
|
exit(1)
|
|
code = self.flash_rdid(2)
|
|
print("ID code bytes 2-3: 0x{:08x}".format(code))
|
|
if code != 0x3b3b8080:
|
|
print("ID code mismatch")
|
|
exit(1)
|
|
|
|
# block erase
|
|
progress = ProgressBar(min_value=0, max_value=length, prefix='Erasing ').start()
|
|
erased = 0
|
|
while erased < length:
|
|
self.ping_wdt()
|
|
if (length - erased >= 65536) and ((addr & 0xFFFF) == 0):
|
|
blocksize = 65536
|
|
else:
|
|
blocksize = 4096
|
|
|
|
while True:
|
|
self.flash_wren()
|
|
status = self.flash_rdsr(1)
|
|
if status & 0x02 != 0:
|
|
break
|
|
|
|
if blocksize == 4096:
|
|
self.flash_se4b(addr + erased)
|
|
else:
|
|
self.flash_be4b(addr + erased)
|
|
erased += blocksize
|
|
|
|
while (self.flash_rdsr(1) & 0x01) != 0:
|
|
pass
|
|
|
|
result = self.flash_rdscur()
|
|
if result & 0x60 != 0:
|
|
print("E_FAIL/P_FAIL set on erase, programming may fail, but trying anyways...")
|
|
|
|
if self.flash_rdsr(1) & 0x02 != 0:
|
|
self.flash_wrdi()
|
|
while (self.flash_rdsr(1) & 0x02) != 0:
|
|
pass
|
|
if erased < length:
|
|
progress.update(erased)
|
|
progress.finish()
|
|
print("Erase finished")
|
|
|
|
# addr is relative to the base of FLASH (not absolute)
|
|
def flash_program(self, addr, data, verify=True):
|
|
flash_region = int(self.regions['spiflash'][0], 0)
|
|
flash_len = int(self.regions['spiflash'][1], 0)
|
|
|
|
if (addr + len(data) > flash_len):
|
|
print("Write data out of bounds! Aborting.")
|
|
exit(1)
|
|
|
|
# ID code check
|
|
code = self.flash_rdid(1)
|
|
print("ID code bytes 1-2: 0x{:08x}".format(code))
|
|
if code != 0x8080c2c2:
|
|
print("ID code mismatch")
|
|
exit(1)
|
|
code = self.flash_rdid(2)
|
|
print("ID code bytes 2-3: 0x{:08x}".format(code))
|
|
if code != 0x3b3b8080:
|
|
print("ID code mismatch")
|
|
exit(1)
|
|
|
|
# block erase
|
|
progress = ProgressBar(min_value=0, max_value=len(data), prefix='Erasing ').start()
|
|
erased = 0
|
|
while erased < len(data):
|
|
self.ping_wdt()
|
|
if (len(data) - erased >= 65536) and ((addr & 0xFFFF) == 0):
|
|
blocksize = 65536
|
|
else:
|
|
blocksize = 4096
|
|
|
|
while True:
|
|
self.flash_wren()
|
|
status = self.flash_rdsr(1)
|
|
if status & 0x02 != 0:
|
|
break
|
|
|
|
if blocksize == 4096:
|
|
self.flash_se4b(addr + erased)
|
|
else:
|
|
self.flash_be4b(addr + erased)
|
|
erased += blocksize
|
|
|
|
while (self.flash_rdsr(1) & 0x01) != 0:
|
|
pass
|
|
|
|
result = self.flash_rdscur()
|
|
if result & 0x60 != 0:
|
|
print("E_FAIL/P_FAIL set on erase, programming may fail, but trying anyways...")
|
|
|
|
if self.flash_rdsr(1) & 0x02 != 0:
|
|
self.flash_wrdi()
|
|
while (self.flash_rdsr(1) & 0x02) != 0:
|
|
pass
|
|
if erased < len(data):
|
|
progress.update(erased)
|
|
progress.finish()
|
|
print("Erase finished")
|
|
|
|
# program
|
|
# pad out to the nearest word length
|
|
if len(data) % 4 != 0:
|
|
data += bytearray([0xff] * (4 - (len(data) % 4)))
|
|
written = 0
|
|
progress = ProgressBar(min_value=0, max_value=len(data), prefix='Writing ').start()
|
|
while written < len(data):
|
|
self.ping_wdt()
|
|
if len(data) - written > 256:
|
|
chunklen = 256
|
|
else:
|
|
chunklen = len(data) - written
|
|
|
|
while True:
|
|
self.flash_wren()
|
|
status = self.flash_rdsr(1)
|
|
if status & 0x02 != 0:
|
|
break
|
|
|
|
self.burst_write(self.register('spinor_wdata'), data[written:(written+chunklen)])
|
|
self.flash_pp4b(addr + written, chunklen)
|
|
|
|
written += chunklen
|
|
if written < len(data):
|
|
progress.update(written)
|
|
progress.finish()
|
|
print("Write finished")
|
|
|
|
if self.flash_rdsr(1) & 0x02 != 0:
|
|
self.flash_wrdi()
|
|
while (self.flash_rdsr(1) & 0x02) != 0:
|
|
pass
|
|
|
|
# dummy reads to clear the "read lock" bit
|
|
self.flash_rdsr(0)
|
|
|
|
# verify
|
|
self.ping_wdt()
|
|
if verify:
|
|
print("Performing readback for verification...")
|
|
self.ping_wdt()
|
|
rbk_data = self.burst_read(addr + flash_region, len(data))
|
|
if rbk_data != data:
|
|
errs = 0
|
|
err_thresh = 64
|
|
for i in range(0, len(rbk_data)):
|
|
if rbk_data[i] != data[i]:
|
|
if errs < err_thresh:
|
|
print("Error at 0x{:x}: {:x}->{:x}".format(i, data[i], rbk_data[i]))
|
|
errs += 1
|
|
if errs == err_thresh:
|
|
print("Too many errors, stopping print...")
|
|
print("Errors were found in verification, programming failed")
|
|
print("Total byte errors: {}".format(errs))
|
|
exit(1)
|
|
else:
|
|
print("Verification passed.")
|
|
else:
|
|
print("Skipped verification at user request")
|
|
|
|
self.ping_wdt()
|
|
|
|
def bitflip(data_block, bitwidth=32):
|
|
if bitwidth == 0:
|
|
return data_block
|
|
bytewidth = bitwidth // 8
|
|
bitswapped = bytearray()
|
|
i = 0
|
|
while i < len(data_block):
|
|
data = int.from_bytes(data_block[i:i+bytewidth], byteorder='big', signed=False)
|
|
b = '{:0{width}b}'.format(data, width=bitwidth)
|
|
bitswapped.extend(int(b[::-1], 2).to_bytes(bytewidth, byteorder='big'))
|
|
i = i + bytewidth
|
|
return bytes(bitswapped)
|
|
|
|
# assumes a, b are the same length eh?
|
|
def xor_bytes(a, b):
|
|
i = 0
|
|
y = bytearray()
|
|
while i < len(a):
|
|
y.extend((a[i] ^ b[i]).to_bytes(1, byteorder='little'))
|
|
i = i + 1
|
|
|
|
return bytes(y)
|
|
|
|
def try_key_to_bytes(input):
|
|
if len(input.split(' ')) == 24: # 24 words is BIP-39
|
|
key_bytes = bip39_to_bits(input)
|
|
else:
|
|
key_bytes = int(input, 16).to_bytes(32, byteorder='big')
|
|
return key_bytes
|
|
|
|
# binfile should be the input SoC file, already read in as bytes()
|
|
# returns the encrypted version of binfile
|
|
def encrypt_to_efuse(binfile, key):
|
|
print("Encrypting gateware to target-specific key...")
|
|
# extract the keys
|
|
key_bytes = bytes([0] * 32)
|
|
new_key = try_key_to_bytes(key)
|
|
new_hmac = get_random_bytes(32)
|
|
new_iv = get_random_bytes(16)
|
|
|
|
# search for structure
|
|
# 0x3001_6004 -> specifies the CBC key
|
|
# 4 words of CBC IV
|
|
# 0x3003_4001 -> ciphertext len
|
|
# 1 word of ciphertext len
|
|
# then ciphertext
|
|
|
|
position = 0
|
|
iv_pos = 0
|
|
while position < len(binfile):
|
|
cwd = int.from_bytes(binfile[position:position+4], 'big')
|
|
if cwd == 0x3001_6004:
|
|
iv_pos = position+4
|
|
if cwd == 0x3003_4001:
|
|
break
|
|
position = position + 1
|
|
|
|
position = position + 4
|
|
|
|
ciphertext_len = 4* int.from_bytes(binfile[position:position+4], 'big')
|
|
position = position + 4
|
|
|
|
active_area = binfile[position : position+ciphertext_len]
|
|
postamble = binfile[position+ciphertext_len:]
|
|
|
|
iv_bytes = bitflip(binfile[iv_pos : iv_pos+0x10]) # note that the IV is embedded in the file
|
|
|
|
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
|
|
plain_bitstream = cipher.decrypt(bitflip(active_area))
|
|
|
|
# now construct the output file and its hashes
|
|
plaintext = bytearray()
|
|
f = bytearray()
|
|
|
|
# fixed header that sets 66MHz config speed, x1, 1.8V, eFuse target
|
|
device_header = [
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xaa, 0x99, 0x55, 0x66, 0x20, 0x00, 0x00, 0x00, 0x30, 0x03, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x0b,
|
|
0x30, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x12, 0x20, 0x00, 0x00, 0x00, 0x30, 0x00, 0xc0, 0x01,
|
|
0x80, 0x00, 0x00, 0x40, 0x30, 0x00, 0xa0, 0x01, 0x80, 0x00, 0x00, 0x40, 0x30, 0x01, 0xc0, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
|
|
0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
|
|
0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
|
|
0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x30, 0x01, 0x60, 0x04,
|
|
]
|
|
|
|
for item in device_header: # add the cleartext header
|
|
f.extend(bytes([item]))
|
|
|
|
f.extend(bitflip(new_iv)) # insert the IV
|
|
|
|
ciphertext_header = [
|
|
0x30, 0x03, 0x40, 0x01, 0x00, 0x08, 0x5b, 0x98,
|
|
]
|
|
for item in ciphertext_header: # add the cleartext length-of-ciphertext field before the ciphertext
|
|
f.extend(bytes([item]))
|
|
|
|
# generate the header and footer hash keys.
|
|
header = int(0x6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C).to_bytes(32, byteorder='big')
|
|
keyed_header = xor_bytes(header, new_hmac)
|
|
footer = int(0x3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A).to_bytes(32, byteorder='big')
|
|
keyed_footer = xor_bytes(footer, new_hmac)
|
|
|
|
# add the header
|
|
plaintext.extend(keyed_header)
|
|
plaintext.extend(header)
|
|
|
|
# insert the bitstream plaintext, skipping the header and the trailing HMAC.
|
|
plaintext.extend(plain_bitstream[64:-160])
|
|
|
|
# compute first HMAC of stream with new HMAC key
|
|
h1 = SHA256.new()
|
|
k = 0
|
|
while k < len(plaintext) - 320: # HMAC does /not/ cover the whole file, it stops 320 bytes short of the end
|
|
h1.update(bitflip(plaintext[k:k+16], 32))
|
|
k = k + 16
|
|
h1_digest = h1.digest()
|
|
|
|
# add the footer
|
|
plaintext.extend(keyed_footer)
|
|
plaintext.extend(footer)
|
|
plaintext.extend(bytes(32)) # empty spot where hash #1 would be stored
|
|
hash_pad = [ # sha-256 padding for the zero'd hash #1, which is in the bitstream and seems necessary for verification
|
|
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00,
|
|
]
|
|
plaintext.extend(hash_pad)
|
|
|
|
# compute the hash of the hash, presumably to prevent length extension attacks?
|
|
h2 = SHA256.new()
|
|
h2.update(bitflip(keyed_footer))
|
|
h2.update(bitflip(footer))
|
|
h2.update(h1_digest)
|
|
h2_digest = h2.digest()
|
|
|
|
# commit the final HMAC to the bitstream plaintext
|
|
plaintext.extend(bitflip(h2_digest))
|
|
|
|
# encrypt the bitstream
|
|
newcipher = AES.new(new_key, AES.MODE_CBC, new_iv)
|
|
|
|
# finally generate the ciphertext block, which encapsulates the HMACs
|
|
ciphertext = newcipher.encrypt(bytes(plaintext))
|
|
|
|
# add ciphertext to the bitstream
|
|
f.extend(bitflip(ciphertext))
|
|
|
|
# add the cleartext postamble to the bitstream. These are a series of NOP commands + all of the csr.csv data & signatures
|
|
f.extend(postamble)
|
|
print("Encryption success! {} bytes generated.".format(len(f)))
|
|
assert len(f) == 2621440, "Encryption length is incorrect; aborting!"
|
|
|
|
return f
|
|
|
|
def auto_int(x):
|
|
return int(x, 0)
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Developer tool for loading local images to a Precursor. Use `precursorupdater` to fetch from a pre-built image.")
|
|
parser.add_argument(
|
|
"--soc", required=False, help="'Factory Reset' the SoC gateware. Note: this will overwrite any secret keys stored in your device!", type=str, nargs='?', metavar=('SoC gateware file'), const='../precursors/soc_csr.bin'
|
|
)
|
|
parser.add_argument(
|
|
"-s", "--staging", required=False, help="Stage an update to apply", type=str, nargs='?', metavar=('SoC gateware file'), const='../precursors/soc_csr.bin'
|
|
)
|
|
parser.add_argument(
|
|
"-l", "--loader", required=False, help="Loader", type=str, nargs='?', metavar=('loader file'), const='../target/riscv32imac-unknown-xous-elf/release/loader.bin'
|
|
)
|
|
parser.add_argument(
|
|
"--disable-boot", required=False, action='store_true', help="Disable system boot (for use in multi-stage updates)"
|
|
)
|
|
parser.add_argument(
|
|
"--enable-boot-wipe", required=False, action='store_true', help="Re-enable system boot for factory reset. Requires both a loader (-l) and a soc (--soc) spec. Overwrites root keys."
|
|
)
|
|
parser.add_argument(
|
|
"--enable-boot-update", required=False, action='store_true', help="Re-enable system boot for updates. Requires both a loader (-l) and a staging (-s) spec. Stages SOC without overwriting root keys."
|
|
)
|
|
parser.add_argument(
|
|
"-k", "--kernel", required=False, help="Kernel", type=str, nargs='?', metavar=('kernel file'), const='../target/riscv32imac-unknown-xous-elf/release/xous.img'
|
|
)
|
|
parser.add_argument(
|
|
"-e", "--ec", required=False, help="EC gateware", type=str, nargs='?', metavar=('EC gateware package'), const='ec_fw.bin'
|
|
)
|
|
parser.add_argument(
|
|
"-w", "--wf200", required=False, help="WF200 firmware", type=str, nargs='?', metavar=('WF200 firmware package'), const='wf200_fw.bin'
|
|
)
|
|
parser.add_argument(
|
|
"--erase-pddb", help="Erase the PDDB area", action="store_true"
|
|
)
|
|
parser.add_argument(
|
|
"--audiotest", required=False, help="Test audio clip (must be 8kHz WAV)", type=str, nargs='?', metavar=('Test audio clip'), const="testaudio.wav"
|
|
)
|
|
parser.add_argument(
|
|
"--peek", required=False, help="Inspect an address", type=auto_int, metavar=('ADDR')
|
|
)
|
|
parser.add_argument(
|
|
"--poke", required=False, help="Write to an address", type=auto_int, nargs=2, metavar=('ADDR', 'DATA')
|
|
)
|
|
parser.add_argument(
|
|
"--check-poke", required=False, action='store_true', help="Read data before and after the poke"
|
|
)
|
|
parser.add_argument(
|
|
"--config", required=False, help="Print the descriptor", action='store_true'
|
|
)
|
|
parser.add_argument(
|
|
"-i", "--image", required=False, help="Manually specify an image and address. Offset is relative to bottom of flash.", type=str, nargs=2, metavar=('IMAGEFILE', 'ADDR')
|
|
)
|
|
parser.add_argument(
|
|
"--verify", help="Readback verification. May fail for large files due to WDT timeout.", default=False, action='store_true'
|
|
)
|
|
parser.add_argument(
|
|
"--force", help="Ignore gitrev version on SoC and try to burn an image anyways", action="store_true"
|
|
)
|
|
parser.add_argument(
|
|
"--bounce", help="cycle the device through a reset", action="store_true"
|
|
)
|
|
parser.add_argument(
|
|
"--factory-new", help="reset the entire image to mimic exactly what comes out of the factory, including temp files for testing. Warning: this will take a long time.", action="store_true"
|
|
)
|
|
parser.add_argument(
|
|
"--override-csr", required=False, help="CSR file to use instead of CSR values stored with the image. Used to recover in case of partial update of soc_csr.bin", type=str,
|
|
)
|
|
parser.add_argument(
|
|
"--dump", required=False, help="Dump a region of memory. Takes a virtual memory map file as an argument. Looks for lines of format `V|P vaddr paddr`", type=str,
|
|
)
|
|
parser.add_argument(
|
|
"--dump-file", required=False, help="Name out output file for dump. Required if --dump is specified.", type=str,
|
|
)
|
|
parser.add_argument(
|
|
"--key", help="Backup key in hex or BIP-39 format. Used to factory-reset efused devices. Specify BIP-39 phrase within double-quotes.", type=str
|
|
)
|
|
parser.add_argument(
|
|
"--clear-backup", help="Clear backup flag", required=False, action='store_true'
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
if not len(sys.argv) > 1:
|
|
print("No arguments specified, doing nothing. Use --help for more information.")
|
|
print("If you are looking to fetch pre-built images and burn them, this is the wrong tool:")
|
|
print("Use `precursorupdater` instead (via `pip3 install precursorupdater`)")
|
|
exit(1)
|
|
|
|
dev = usb.core.find(idProduct=0x5bf0, idVendor=0x1209)
|
|
|
|
if dev is None:
|
|
raise ValueError('Precursor device not found')
|
|
|
|
dev.set_configuration()
|
|
if args.config:
|
|
cfg = dev.get_active_configuration()
|
|
print(cfg)
|
|
|
|
pc_usb = PrecursorUsb(dev)
|
|
|
|
if args.verify:
|
|
verify = True
|
|
else:
|
|
verify = False
|
|
|
|
if args.peek:
|
|
pc_usb.peek(args.peek, display=True)
|
|
# print(burst_read(dev, args.peek, 256).hex())
|
|
exit(0)
|
|
|
|
if args.poke:
|
|
addr, data = args.poke
|
|
pc_usb.poke(addr, data, check=args.check_poke, display=True)
|
|
# import os
|
|
# d = bytearray(os.urandom(8000))
|
|
# burst_write(dev, addr, d)
|
|
# r = burst_read(dev, addr, 8000)
|
|
# print(r.hex())
|
|
# if d != r:
|
|
# print("mismatch")
|
|
# else:
|
|
# print("match")
|
|
exit(0)
|
|
|
|
pc_usb.load_csrs(args.override_csr) # prime the CSR values
|
|
if "v0.8" in pc_usb.gitrev:
|
|
locs = {
|
|
"LOC_SOC" : [0x0000_0000, "soc_csr.bin"],
|
|
"LOC_STAGING": [0x0028_0000, "pass"],
|
|
"LOC_LOADER" : [0x0050_0000, "loader.bin"],
|
|
"LOC_KERNEL" : [0x0098_0000, "xous.img"],
|
|
"LOC_WF200" : [0x07F8_0000, "pass"],
|
|
"LOC_EC" : [0x07FC_E000, "pass"],
|
|
"LOC_AUDIO" : [0x0634_0000, "short_8khz.wav"],
|
|
"LEN_AUDIO" : [0x01C4_0000, "pass"],
|
|
"LOC_PDDB" : [0x0100_0000, "pass"],
|
|
}
|
|
elif "v0.9" in pc_usb.gitrev:
|
|
locs = {
|
|
"LOC_SOC" : [0x0000_0000, "soc_csr.bin"],
|
|
"LOC_STAGING": [0x0028_0000, "pass"],
|
|
"LOC_LOADER" : [0x0050_0000, "loader.bin"],
|
|
"LOC_KERNEL" : [0x0098_0000, "xous.img"],
|
|
"LOC_WF200" : [0x07F8_0000, "pass"],
|
|
"LOC_EC" : [0x07FC_E000, "pass"],
|
|
"LOC_AUDIO" : [0x0634_0000, "short_8khz.wav"],
|
|
"LEN_AUDIO" : [0x01C4_0000, "pass"],
|
|
"LOC_PDDB" : [0x01D8_0000, "pass"],
|
|
}
|
|
elif args.force == True:
|
|
# try the v0.9 offsets
|
|
locs = {
|
|
"LOC_SOC" : [0x00000000, "soc_csr.bin"],
|
|
"LOC_STAGING": [0x00280000, "pass"],
|
|
"LOC_LOADER" : [0x00500000, "loader.bin"],
|
|
"LOC_KERNEL" : [0x00980000, "xous.img"],
|
|
"LOC_WF200" : [0x07F80000, "pass"],
|
|
"LOC_EC" : [0x07FCE000, "pass"],
|
|
"LOC_AUDIO" : [0x06340000, "short_8khz.wav"],
|
|
"LEN_AUDIO" : [0x01C40000, "pass"],
|
|
"LOC_PDDB" : [0x01D80000, "pass"],
|
|
}
|
|
else:
|
|
print("SoC is from an unknown rev '{}', use --force to continue anyways with v0.9 firmware offsets".format(pc_usb.load_csrs()))
|
|
exit(1)
|
|
|
|
pc_usb.ping_wdt()
|
|
|
|
if args.dump:
|
|
table = []
|
|
with open(args.dump, 'r') as map:
|
|
for line in map.readlines():
|
|
rgx = re.search('.*V\|P\s([0-9a-fA-F]*)\s([0-9a-fA-F]*).*', line)
|
|
if rgx is not None:
|
|
map = rgx.groups()
|
|
table += [int(map[1], 16)]
|
|
|
|
if args.dump_file is None:
|
|
print("Must specify --dump-file when using --dump")
|
|
exit(0)
|
|
with open(args.dump_file, 'wb') as f:
|
|
for page in table:
|
|
pc_usb.halt()
|
|
data = pc_usb.burst_read(page, 4096)
|
|
pc_usb.unhalt()
|
|
f.write(data)
|
|
exit(0)
|
|
|
|
print("Halting CPU.")
|
|
pc_usb.halt()
|
|
|
|
if args.erase_pddb:
|
|
print("Erasing PDDB region")
|
|
pc_usb.erase_region(locs['LOC_PDDB'][0], locs['LOC_EC'][0] - locs['LOC_PDDB'][0])
|
|
|
|
if args.disable_boot:
|
|
print("Disabling boot")
|
|
pc_usb.erase_region(locs['LOC_LOADER'][0], 1024 * 256)
|
|
|
|
if args.clear_backup:
|
|
print("Clearing backup flag")
|
|
pc_usb.erase_region(locs["LOC_KERNEL"][0] + 0x0140_0000 - 0x1000, 4096)
|
|
print("Backup flag cleared")
|
|
exit(0)
|
|
|
|
if args.enable_boot_wipe:
|
|
if args.loader == None:
|
|
print("Must provide both a loader and soc image")
|
|
if args.soc == None:
|
|
print("Must provide both a soc and loader image")
|
|
print("Enabling boot with {} and {}".format(args.loader, args.soc))
|
|
print("WARNING: if a backup key is set, the correct key MUST be specified with `--key`, or else the device will be bricked.")
|
|
print("Continue? (y/n)")
|
|
confirm = input()
|
|
if len(confirm) > 0 and confirm.lower()[:1] == 'y':
|
|
print("Programming loader image {}".format(args.loader))
|
|
with open(args.loader, "rb") as f:
|
|
image = f.read()
|
|
pc_usb.flash_program(locs['LOC_LOADER'][0], image, verify=verify)
|
|
print("Programming SoC gateware".format(args.soc))
|
|
with open(args.soc, "rb") as f:
|
|
image = f.read()
|
|
if args.key is not None:
|
|
image = encrypt_to_efuse(image, args.key)
|
|
if verify == True:
|
|
print("Note: SoC verification is not possible as readback is locked for security purposes")
|
|
pc_usb.flash_program(locs['LOC_SOC'][0], image, verify=False)
|
|
|
|
print("Erasing PDDB root structures")
|
|
pc_usb.erase_region(locs['LOC_PDDB'][0], 1024 * 1024)
|
|
|
|
print("Resuming CPU.")
|
|
pc_usb.unhalt()
|
|
|
|
print("Resetting SOC...")
|
|
try:
|
|
pc_usb.poke(pc_usb.register('reboot_soc_reset'), 0xac, display=False)
|
|
except usb.core.USBError:
|
|
pass # we expect an error because we reset the SOC and that includes the USB core
|
|
exit(0)
|
|
|
|
if args.enable_boot_update:
|
|
if args.loader == None:
|
|
print("Must provide both a loader and soc image")
|
|
if args.staging == None:
|
|
print("Must provide both a soc and loader image")
|
|
print("Enabling boot with {} and {}".format(args.loader, args.staging))
|
|
print("Programming loader image {}".format(args.loader))
|
|
with open(args.loader, "rb") as f:
|
|
image = f.read()
|
|
pc_usb.flash_program(locs['LOC_LOADER'][0], image, verify=verify)
|
|
print("Staging SoC gateware".format(args.staging))
|
|
with open(args.staging, "rb") as f:
|
|
image = f.read()
|
|
if verify == True:
|
|
print("Note: staging area verification is not possible as readback is locked for security purposes")
|
|
pc_usb.flash_program(locs['LOC_STAGING'][0], image, verify=verify)
|
|
|
|
print("Resuming CPU.")
|
|
pc_usb.unhalt()
|
|
|
|
print("Resetting SOC...")
|
|
try:
|
|
pc_usb.poke(pc_usb.register('reboot_soc_reset'), 0xac, display=False)
|
|
except usb.core.USBError:
|
|
pass # we expect an error because we reset the SOC and that includes the USB core
|
|
exit(0)
|
|
|
|
if args.image:
|
|
image_file, addr_str = args.image
|
|
addr = int(addr_str, 0)
|
|
print("Burning manually specified image '{}' to address 0x{:08x} relative to bottom of FLASH".format(image_file, addr))
|
|
with open(image_file, "rb") as f:
|
|
image_data = f.read()
|
|
pc_usb.flash_program(addr, image_data, verify=verify)
|
|
|
|
if args.ec != None:
|
|
print("Staging EC firmware package '{}' in SOC memory space...".format(args.ec))
|
|
with open(args.ec, "rb") as f:
|
|
image = f.read()
|
|
pc_usb.flash_program(locs['LOC_EC'][0], image, verify=verify)
|
|
|
|
if args.wf200 != None:
|
|
print("Staging WF200 firmware package '{}' in SOC memory space...".format(args.wf200))
|
|
with open(args.wf200, "rb") as f:
|
|
image = f.read()
|
|
pc_usb.flash_program(locs['LOC_WF200'][0], image, verify=verify)
|
|
|
|
if args.staging != None:
|
|
print("Staging SoC gateware {}".format(args.soc))
|
|
with open(args.staging, "rb") as f:
|
|
image = f.read()
|
|
if verify == True:
|
|
print("Note: staging area verification is not possible as readback is locked for security purposes")
|
|
pc_usb.flash_program(locs['LOC_STAGING'][0], image, verify=verify)
|
|
|
|
if args.kernel != None:
|
|
print("Programming kernel image {}".format(args.kernel))
|
|
with open(args.kernel, "rb") as f:
|
|
image = f.read()
|
|
pc_usb.flash_program(locs['LOC_KERNEL'][0], image, verify=verify)
|
|
|
|
if args.loader != None:
|
|
print("Programming loader image {}".format(args.loader))
|
|
with open(args.loader, "rb") as f:
|
|
image = f.read()
|
|
pc_usb.flash_program(locs['LOC_LOADER'][0], image, verify=verify)
|
|
|
|
if args.soc != None:
|
|
if args.force == True:
|
|
print("`--force` specified; all checks disabled, you may brick your device!")
|
|
print("Programming SoC gateware {}".format(args.soc))
|
|
with open(args.soc, "rb") as f:
|
|
image = f.read()
|
|
if verify == True:
|
|
print("Note: SoC verification is not possible as readback is locked for security purposes")
|
|
pc_usb.flash_program(locs['LOC_SOC'][0], image, verify=False)
|
|
print("Erasing PDDB root structures")
|
|
pc_usb.erase_region(locs['LOC_PDDB'][0], 1024 * 1024)
|
|
else:
|
|
print("This will overwrite any secret keys in your device and erase PDDB keys.")
|
|
print("WARNING: if a backup key is set, the correct key MUST be specified with `--key`, or else the device will be bricked.")
|
|
print("Continue? (y/n)")
|
|
confirm = input()
|
|
if len(confirm) > 0 and confirm.lower()[:1] == 'y':
|
|
print("Programming SoC gateware {}".format(args.soc))
|
|
with open(args.soc, "rb") as f:
|
|
image = f.read()
|
|
if args.key is not None:
|
|
image = encrypt_to_efuse(image, args.key)
|
|
if verify == True:
|
|
print("Note: SoC verification is not possible as readback is locked for security purposes")
|
|
pc_usb.flash_program(locs['LOC_SOC'][0], image, verify=False)
|
|
print("Erasing PDDB root structures")
|
|
pc_usb.erase_region(locs['LOC_PDDB'][0], 1024 * 1024)
|
|
|
|
|
|
if args.audiotest != None:
|
|
print("Loading audio test clip {}".format(args.audiotest))
|
|
with open(args.audiotest, "rb") as f:
|
|
image = f.read()
|
|
if len(image) >= locs['LEN_AUDIO'][0]:
|
|
print("audio file is too long, aborting audio burn!")
|
|
else:
|
|
pc_usb.flash_program(locs['LOC_AUDIO'][0], image, verify=verify)
|
|
|
|
if args.factory_new:
|
|
print("WARNING: if a backup key is set, the correct key MUST be specified with `--key`, or else the device will be bricked.")
|
|
print("Continue? (y/n)")
|
|
confirm = input()
|
|
if len(confirm) > 0 and confirm.lower()[:1] == 'y':
|
|
base_url = "https://ci.betrusted.io/releases/v0.9.5/"
|
|
# erase the entire flash
|
|
pc_usb.erase_region(0, 0x800_0000)
|
|
# burn the gateware
|
|
for sections in locs.values():
|
|
if sections[1] != 'pass':
|
|
print('retrieving {}'.format(base_url + sections[1]))
|
|
with urllib.request.urlopen(base_url + sections[1]) as f:
|
|
print('burning at {:x}'.format(sections[0]))
|
|
image = f.read()
|
|
if sections[1] == "soc_csr.bin" and args.key is not None:
|
|
image = encrypt_to_efuse(image, args.key)
|
|
pc_usb.flash_program(sections[0], image, verify=False)
|
|
|
|
|
|
print("Resuming CPU.")
|
|
pc_usb.unhalt()
|
|
|
|
print("Resetting SOC...")
|
|
try:
|
|
pc_usb.poke(pc_usb.register('reboot_soc_reset'), 0xac, display=False)
|
|
except usb.core.USBError:
|
|
pass # we expect an error because we reset the SOC and that includes the USB core
|
|
|
|
# print("If you need to run more commands, please unplug and re-plug your device in, as the Precursor USB core was just reset")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
exit(0)
|