From ea9af49f4bb18b0123124ea11a9fd64242ce6595 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 4 Dec 2015 17:58:21 -0800 Subject: [PATCH] hd and p2sh --- etc/english.json | 2050 +++++++++++++++++++++++++++++++ lib/bcoin.js | 7 +- lib/bcoin/chain.js | 79 +- lib/bcoin/hd.js | 739 +++++++++++ lib/bcoin/peer.js | 84 +- lib/bcoin/pool.js | 86 +- lib/bcoin/protocol/constants.js | 9 + lib/bcoin/protocol/parser.js | 2 + lib/bcoin/script.js | 1 + lib/bcoin/tx.js | 53 +- lib/bcoin/utils.js | 14 + lib/bcoin/wallet.js | 134 +- 12 files changed, 3085 insertions(+), 173 deletions(-) create mode 100644 etc/english.json create mode 100644 lib/bcoin/hd.js diff --git a/etc/english.json b/etc/english.json new file mode 100644 index 00000000..5bc5c639 --- /dev/null +++ b/etc/english.json @@ -0,0 +1,2050 @@ +[ + "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" +] diff --git a/lib/bcoin.js b/lib/bcoin.js index 6ef8958e..7b64c3c2 100644 --- a/lib/bcoin.js +++ b/lib/bcoin.js @@ -1,7 +1,11 @@ var bcoin = exports; var elliptic = require('elliptic'); -bcoin.ecdsa = elliptic.ecdsa(elliptic.nist.secp256k1); +if (elliptic.ec) { + bcoin.ecdsa = elliptic.ec('secp256k1'); +} else { + bcoin.ecdsa = elliptic.ecdsa(elliptic.nist.secp256k1); +} bcoin.utils = require('./bcoin/utils'); bcoin.bloom = require('./bcoin/bloom'); bcoin.protocol = require('./bcoin/protocol'); @@ -13,3 +17,4 @@ bcoin.chain = require('./bcoin/chain'); bcoin.wallet = require('./bcoin/wallet'); bcoin.peer = require('./bcoin/peer'); bcoin.pool = require('./bcoin/pool'); +bcoin.hd = require('./bcoin/hd'); diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 130b234e..ad38b748 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -427,6 +427,76 @@ Chain.prototype.getStartHeight = function getLast(cb) { } }; +Chain.prototype.locatorHashes = function(index) { + var chain = this.index.hashes; + var hashes = []; + var top = chain.length - 1; + var step = 1; + var i; + + if (typeof index === 'string') { + for (i = top; i >= 0; i--) { + if (chain[i] === index) { + top = i; + break; + } + } + } else if (typeof index === 'number') { + top = index; + } + + i = top; + for (;;) { + hashes.push(chain[i]); + i = i - step; + if (i <= 0) { + hashes.push(chain[0]); + break; + } + if (hashes.length >= 10) { + step *= 2; + } + } + + return hashes; +}; + +Chain.prototype.locatorHashes = function(index) { + var chain = this.index.hashes; + var hashes = []; + var top = chain.length - 1; + var step = 1, start = 0; + + if (typeof index === 'string') { + for (i = top; i >= 0; i--) { + if (chain[i] === index) { + top = i; + break; + } + } + } else if (typeof index === 'number') { + top = index; + } + + for (var i = top; i > 0; i -= step, ++start) { + if (start >= 10) step *= 2; + hashes.push(chain[i]); + } + + hashes.push(chain[0]); + + return hashes; +}; + +Chain.prototype.__defineGetter__('orphan_bmap', function() { + var self = this; + return Object.keys(this.orphan.map).reduce(function(out, prev) { + var orphan = self.orphan.map[prev]; + out[orphan.hash('hex')] = orphan; + return out; + }, {}); +}); + Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) { var self = this; @@ -434,14 +504,7 @@ Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) { hash = hash.hash('hex'); } - /* - var orphans = Object.keys(this.orphan.map).reduce(function(out, prev) { - var orphan = self.orphan.map[prev]; - out[orphan.hash('hex')] = orphan; - return out; - }, {}); - */ - var orphans = this.orphan.bmap; + var orphans = this.orphan_bmap; /* var orphanRoot = hash; diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js new file mode 100644 index 00000000..8a77237b --- /dev/null +++ b/lib/bcoin/hd.js @@ -0,0 +1,739 @@ +/** + * hd.js - hierarchical determistic seeds and keys (BIP32, BIP39) + * https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki + * https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki + */ + +/** + * Code adapted from bitcore-lib: + * https://github.com/bitpay/bitcore-lib/blob/master/lib/hdprivatekey.js + * https://github.com/bitpay/bitcore-lib/blob/master/lib/hdpublickey.js + * https://github.com/ryanxcharles/fullnode/blob/master/lib/bip32.js + * + * Copyright (c) 2013-2015 BitPay, Inc. + * + * Parts of this software are based on Bitcoin Core + * Copyright (c) 2009-2015 The Bitcoin Core developers + * + * Parts of this software are based on fullnode + * Copyright (c) 2014 Ryan X. Charles + * Copyright (c) 2014 reddit, Inc. + * + * Parts of this software are based on BitcoinJS + * Copyright (c) 2011 Stefan Thomas + * + * Parts of this software are based on BitcoinJ + * Copyright (c) 2011 Google Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +var hd = exports; + +/** + * Modules + */ + +var bcoin = require('../bcoin'); +var hash = require('hash.js'); +var bn = require('bn.js'); +var elliptic = require('elliptic'); +var utils = bcoin.utils; +var assert = utils.assert; + +var EventEmitter = require('events').EventEmitter; +var crypto = require('crypto'); + +var english = require('../../etc/english.json'); + +var ec; +if (!elliptic.curves) { + ec = elliptic.nist.secp256k1; + ec.curve.g = ec.g; + ec.curve.n = ec.n; +} else { + ec = elliptic.curves.secp256k1; +} +var ecPoint = ec.curve.point.bind(ec.curve); +var ecPointFromX = ec.curve.pointFromX.bind(ec.curve); + +/** + * HD Seeds + */ + +function HDSeed(options) { + if (!(this instanceof HDSeed)) { + return new HDSeed(options); + } + this.bits = options.bits || 128; + this.entropy = options.entropy || HDSeed._entropy(this.bits / 8); + this.mnemonic = options.mnemonic || HDSeed._mnemonic(this.entropy); + if (options.passphrase !== undefined) { + this.seed = this.createSeed(options.passphrase); + } +} + +HDSeed.create = function(options) { + var obj = new HDSeed(options); + return obj.seed || obj; +}; + +HDSeed.prototype.createSeed = function(passphrase) { + this.passphrase = passphrase || ''; + return pbkdf2(this.mnemonic, 'mnemonic' + passphrase, 2048, 64); +}; + +HDSeed._entropy = function(size) { + // XXX Do not use crypto + return Array.prototype.slice.call(crypto.randomBytes(size)); +}; + +HDSeed._mnemonic = function(entropy) { + var bin = ''; + for (var i = 0; i < entropy.length; i++) { + bin = bin + ('00000000' + entropy[i].toString(2)).slice(-8); + } + + var mnemonic = []; + for (i = 0; i < bin.length / 11; i++) { + var wi = parseInt(bin.slice(i * 11, (i + 1) * 11), 2); + mnemonic.push(english[wi]); + } + + return mnemonic.join(' '); +}; + +/** + * HD Keys + */ + +var VERSION = { + xpubkey: 0x0488b21e, + xprivkey: 0x0488ade4 +}; + +var HARDENED = 0x80000000; +var MAX_INDEX = 2 * HARDENED; +var MIN_ENTROPY = 128 / 8; +var MAX_ENTROPY = 512 / 8; +var PARENT_FINGER_PRINT_SIZE = 4; +var PATH_ROOTS = ['m', 'M', 'm\'', 'M\'']; + +/** + * HD Private Key + */ + +function HDPriv(options) { + var data; + + if (!(this instanceof HDPriv)) { + return new HDPriv(options); + } + + if (!options) { + options = { seed: new HDSeed({ passphrase: '' }) }; + } + + if (typeof options === 'string' && options.indexOf('xprv') === 0) { + options = { xkey: options }; + } + + if (options.passphrase) { + options.seed = new HDSeed({ passphrase: options.passphrase }); + } + + if (options.seed) { + if (typeof options.seed === 'object' && !(options.seed instanceof HDSeed)) { + options.seed = new HDSeed(options.seed); + } + this.seed = options.seed; + data = this._seed(options.seed); + } else if (options.xkey) { + data = this._unbuild(options.xkey); + } else { + data = options; + } + + data = this._normalize(data, VERSION.xprivkey); + + this.data = data; + + this._build(data); +} + +HDPriv.prototype._normalize = function(data, version) { + var b; + data.version = version || VERSION.xprivkey; + data.version = +data.version; + data.depth = +data.depth; + if (typeof data.parentFingerPrint === 'number') { + b = []; + utils.writeU32BE(b, data.parentFingerPrint, 0); + data.parentFingerPrint = b; + } + data.childIndex = +data.childIndex; + if (typeof data.chainCode === 'string') { + data.chainCode = utils.toArray(data.chainCode, 'hex'); + } + data.privateKey = data.privateKey || data.priv; + if (data.privateKey) { + if (data.privateKey.getPrivate) { + data.privateKey = data.privateKey.getPrivate().toArray(); + } else if (typeof data.privateKey === 'string') { + data.privateKey = utils.toArray(data.privateKey, 'hex'); + } + } + data.publicKey = data.publicKey || data.pub; + if (data.publicKey) { + if (data.publicKey.getPublic) { + data.publicKey = data.privateKey.getPublic(true, 'array'); + } else if (typeof data.publicKey === 'string') { + data.publicKey = utils.toArray(data.publicKey, 'hex'); + } + } + if (typeof data.checksum === 'number') { + b = []; + utils.writeU32BE(b, data.checksum, 0); + data.checksum = b; + } + return data; +}; + +HDPriv.prototype._seed = function(seed) { + if (seed instanceof HDSeed) { + seed = seed.seed; + } + if (typeof seed === 'string' && /^[0-9a-f]+$/i.test(seed)) { + seed = utils.toArray(seed, 'hex'); + } + if (seed.length < MIN_ENTROPY || seed.length > MAX_ENTROPY) { + throw new Error; + } + var hash = sha512hmac(seed, 'Bitcoin seed'); + return { + // version: VERSION.xprivkey, + depth: 0, + parentFingerPrint: 0, + childIndex: 0, + chainCode: hash.slice(32, 64), + privateKey: hash.slice(0, 32), + checksum: null + }; +}; + +HDPriv.prototype._unbuild = function(xkey) { + var raw = utils.fromBase58(xkey); + var data = {}; + var off = 0; + data.version = utils.readU32BE(raw, off); + off += 4; + data.depth = raw[off]; + off += 1; + data.parentFingerPrint = utils.readU32BE(raw, off); + off += 4; + data.childIndex = utils.readU32BE(raw, off); + off += 4; + data.chainCode = raw.slice(off, off + 32); + off += data.chainCode.length; + off += 1; // nul byte + data.privateKey = raw.slice(off, off + 32); + off += data.privateKey.length; + data.checksum = utils.readU32BE(raw, off); + off += 4; + var hash = utils.dsha256(raw.slice(0, -4)); + if (data.checksum !== utils.readU32BE(hash, 0)) { + throw new Error('checksum mismatch'); + } + return data; +}; + +HDPriv.prototype._build = function(data) { + var sequence = []; + var off = 0; + utils.writeU32BE(sequence, data.version, off); + off += 4; + sequence[off] = data.depth; + off += 1; + utils.copy(data.parentFingerPrint, sequence, off, true); + off += data.parentFingerPrint.length; + utils.writeU32BE(sequence, data.childIndex, off); + off += 4; + utils.copy(data.chainCode, sequence, off, true); + off += data.chainCode.length; + sequence[off] = 0; + off += 1; + utils.copy(data.privateKey, sequence, off, true); + off += data.privateKey.length; + var checksum = utils.dsha256(sequence).slice(0, 4); + utils.copy(checksum, sequence, off, true); + off += 4; + + var xprivkey = utils.toBase58(sequence); + + var pair = bcoin.ecdsa.keyPair({ priv: data.privateKey }); + var privateKey = pair.getPrivate().toArray(); + var publicKey = pair.getPublic(true, 'array'); + + var size = PARENT_FINGER_PRINT_SIZE; + var fingerPrint = utils.ripesha(publicKey).slice(0, size); + + this.version = data.version; + this.depth = data.depth; + this.parentFingerPrint = data.parentFingerPrint; + this.childIndex = data.childIndex; + this.chainCode = data.chainCode; + this.privateKey = privateKey; + // this.checksum = checksum; + + this.xprivkey = xprivkey; + this.fingerPrint = fingerPrint; + this.publicKey = publicKey; + + this.hdpub = new HDPub(this); + this.xpubkey = this.hdpub.xpubkey; + this.pair = bcoin.ecdsa.keyPair({ priv: this.privateKey }); +}; + +HDPriv.prototype.derive = function(index, hard) { + if (typeof index === 'string') { + return this.deriveString(index); + } + + hard = index >= HARDENED ? true : hard; + if (index < HARDENED && hard === true) { + index += HARDENED; + } + + var index_ = []; + utils.writeU32BE(index_, index, 0); + + var data; + if (hard) { + data = [0].concat(this.privateKey).concat(index_); + } else { + data = [].concat(this.publicKey).concat(index_); + } + var hash = sha512hmac(data, this.chainCode); + var leftPart = new bn(hash.slice(0, 32)); + var chainCode = hash.slice(32, 64); + + var privateKey = leftPart.add(new bn(this.privateKey)).mod(ec.curve.n).toArray(); + + return new HDPriv({ + // version: this.version, + depth: this.depth + 1, + parentFingerPrint: this.fingerPrint, + childIndex: index, + chainCode: chainCode, + privateKey: privateKey + }); +}; + +// https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki +HDPriv._getIndexes = function(path) { + var steps = path.split('/'); + var root = steps.shift(); + var indexes = []; + + if (~PATH_ROOTS.indexOf(path)) { + return indexes; + } + + if (!~PATH_ROOTS.indexOf(root)) { + return null; + } + + for (var i = 0; i < steps.length; i++) { + var step = steps[i]; + var hard = step[step.length - 1] === '\''; + if (hard) { + step = step.slice(0, -1); + } + if (!step || step[0] === '-') { + return null; + } + var index = +step; // cast to number + if (hard) { + index += HARDENED; + } + indexes.push(index); + } + + return indexes; +}; + +HDPriv.isValidPath = function(path, hard) { + if (typeof path === 'string') { + var indexes = HDPriv._getIndexes(path); + return indexes !== null && indexes.every(HDPriv.isValidPath); + } + + if (typeof path === 'number') { + if (path < HARDENED && hard === true) { + path += HARDENED; + } + return path >= 0 && path < MAX_INDEX; + } + + return false; +}; + +HDPriv.prototype.deriveString = function(path) { + if (!HDPriv.isValidPath(path)) { + throw new Error('invalid path'); + } + + var indexes = HDPriv._getIndexes(path); + + return indexes.reduce(function(prev, index) { + return prev.derive(index); + }); +}; + +/** + * HD Public Key + */ + +function HDPub(options) { + if (!(this instanceof HDPub)) { + return new HDPriv(options); + } + + var data; + + if (typeof options === 'string' && options.indexOf('xpub') === 0) { + options = { xkey: options }; + } + + if (options.xkey) { + data = this._unbuild(options.xkey); + } else { + data = options; + } + + data = this._normalize(data, VERSION.xpubkey); + + this.data = data; + + this._build(data); +} + +HDPub.prototype._normalize = HDPriv.prototype._normalize; + +HDPub.prototype._unbuild = function(xkey) { + var raw = utils.fromBase58(xkey); + var data = {}; + var off = 0; + data.version = utils.readU32BE(raw, off); + off += 4; + data.depth = raw[off]; + off += 1; + data.parentFingerPrint = utils.readU32BE(raw, off); + off += 4; + data.childIndex = utils.readU32BE(raw, off); + off += 4; + data.chainCode = raw.slice(off, off + 32); + off += data.chainCode.length; + data.publicKey = raw.slice(off, off + 33); + off += data.publicKey.length; + data.checksum = utils.readU32BE(raw, off); + off += 4; + var hash = utils.dsha256(raw.slice(0, -4)); + if (data.checksum !== utils.readU32BE(hash, 0)) { + throw new Error('checksum mismatch'); + } + return data; +}; + +HDPub.prototype._build = function(data) { + var sequence = []; + var off = 0; + utils.writeU32BE(sequence, data.version, off); + off += 4; + sequence[off] = data.depth; + off += 1; + utils.copy(data.parentFingerPrint, sequence, off, true); + off += data.parentFingerPrint.length; + utils.writeU32BE(sequence, data.childIndex, off); + off += 4; + utils.copy(data.chainCode, sequence, off, true); + off += data.chainCode.length; + utils.copy(data.publicKey, sequence, off, true); + off += data.publicKey.length; + var checksum = utils.dsha256(sequence).slice(0, 4); + utils.copy(checksum, sequence, off, true); + off += 4; + + if (!data.checksum || !data.checksum.length) { + data.checksum = checksum; + } else if (utils.toHex(checksum) !== utils.toHex(data.checksum)) { + throw new Error; + } + + var xpubkey = utils.toBase58(sequence); + + var publicKey = data.publicKey; + var size = PARENT_FINGER_PRINT_SIZE; + var fingerPrint = utils.ripesha(publicKey).slice(0, size); + + this.version = data.version; + this.depth = data.depth; + this.parentFingerPrint = data.parentFingerPrint; + this.childIndex = data.childIndex; + this.chainCode = data.chainCode; + this.publicKey = publicKey; + // this.checksum = checksum; + + this.xpubkey = xpubkey; + this.fingerPrint = fingerPrint; + + this.xprivkey = data.xprivkey; + this.pair = bcoin.ecdsa.keyPair({ pub: this.publicKey }); +}; + +HDPub.prototype.derive = function(index, hard) { + if (typeof index === 'string') { + return this.deriveString(index); + } + + if (index >= HARDENED || hard) { + throw new Error; + } + if (index < 0) { + throw new Error; + } + + var index_ = []; + utils.writeU32BE(index_, index, 0); + + var data = [].concat(this.publicKey).concat(index_); + var hash = sha512hmac(data, this.chainCode); + var leftPart = new bn(hash.slice(0, 32)); + var chainCode = hash.slice(32, 64); + + var pair = bcoin.ecdsa.keyPair({ pub: this.publicKey }); + var pubkeyPoint = ec.curve.g.mul(leftPart).add(pair.pub); + var publicKey = bcoin.ecdsa.keyFromPublic(pubkeyPoint).getPublic(true, 'array'); + + return new HDPub({ + // version: VERSION.xpubkey, + depth: this.depth + 1, + parentFingerPrint: this.fingerPrint, + childIndex: index, + chainCode: chainCode, + publicKey: publicKey + }); +}; + +HDPub.isValidPath = function(arg) { + if (typeof arg === 'string') { + var indexes = HDPriv._getIndexes(arg); + return indexes !== null && indexes.every(HDPub.isValidPath); + } + if (typeof arg === 'number') { + return arg >= 0 && arg < HARDENED; + } + return false; +}; + +HDPub.prototype.deriveString = function(path) { + if (~path.indexOf('\'')) { + throw new Error('cannot derive hardened'); + } else if (!HDPub.isValidPath(path)) { + throw new Error('invalid path'); + } + + var indexes = HDPriv._getIndexes(path); + + return indexes.reduce(function(prev, index) { + return prev.derive(index); + }); +}; + +/** + * Helpers + */ + +function sha512hmac(data, salt) { + // XXX Do not use crypto + var hmac = crypto.createHmac('sha512', new Buffer(salt)); + var hash = hmac.update(new Buffer(data)).digest(); + return Array.prototype.slice.call(hash); +} + +/** + * PDKBF2 + * Credit to: https://github.com/stayradiated/pbkdf2-sha512 + * Copyright (c) 2014, JP Richardson Copyright (c) 2010-2011 Intalio Pte, All Rights Reserved + */ + +function pbkdf2(key, salt, iterations, dkLen) { + 'use strict'; + + var hLen = 64; + if (dkLen > (Math.pow(2, 32) - 1) * hLen) { + throw Error('Requested key length too long'); + } + + if (typeof key !== 'string' && typeof key.length !== 'number') { + throw new TypeError('key must a string or array'); + } + + if (typeof salt !== 'string' && typeof salt.length !== 'number') { + throw new TypeError('salt must a string or array'); + } + + if (typeof key === 'string') { + key = utils.toArray(key, null); + } + + if (typeof salt === 'string') { + salt = utils.toArray(salt, null); + } + + var DK = new Array(dkLen); + var U = new Array(hLen); + var T = new Array(hLen); + var block1 = new Array(salt.length + 4); + + var l = Math.ceil(dkLen / hLen); + var r = dkLen - (l - 1) * hLen; + + utils.copy(salt.slice(0, salt.length), block1, 0); + for (var i = 1; i <= l; i++) { + block1[salt.length + 0] = (i >> 24 & 0xff); + block1[salt.length + 1] = (i >> 16 & 0xff); + block1[salt.length + 2] = (i >> 8 & 0xff); + block1[salt.length + 3] = (i >> 0 & 0xff); + + U = sha512hmac(block1, key); + + utils.copy(U.slice(0, hLen), T, 0); + + for (var j = 1; j < iterations; j++) { + U = sha512hmac(U, key); + + for (var k = 0; k < hLen; k++) { + T[k] ^= U[k]; + } + } + + var destPos = (i - 1) * hLen; + var len = (i === l ? r : hLen); + utils.copy(T.slice(0, len), DK, 0); + } + + return DK; +} + +/** + * Expose + */ + +hd.seed = HDSeed; +hd.priv = HDPriv; +hd.pub = HDPub; + +/** + * Test + */ + +var phrase = 'volume doll flush federal inflict tomato result property total curtain shield aisle'; +var checkSeed = utils.toHex(pbkdf2(phrase, 'mnemonic' + 'foo', 2048, 64)); +var seed = '5559092716434b83f158bffb51337a944529ae30d7e62d46d3be0c66fa4b36e8d60ccfd2c976b831885dc9df9ac3716ee4bf90003f25621070a49cbea58f528b'; +assert.equal(checkSeed, seed); + +var master_priv = 'xprv9s21ZrQH143K35zTejeVRhkXgegDFUVpoh8Mxs2BQmXEB4w9SZ1CuoJPuQ2KGQrS1ZF3Pk7V7KWHn7FqR2JbAE9Bh8PURnrFnrmArj4kxos'; +var master_pub = 'xpub661MyMwAqRbcFa4vkmBVnqhGEgWhewDgAv3xmFRny74D3sGHz6KTTbcskg2vZEMbEwxc4oaR435oczhSu4GdNwhwiVewcewU8A1Rr6HehAU'; + +var child1_priv = 'xprv9v414VeuxMoGt3t7jzkPni79suCfkgFwjxG38X2wgfg2mrYtV4Bhj3prhDDCcBiJrz9n4xLYoDtBFRuQmreVLKzmiZAqvbGpx5q4yHfzfah'; +var child1_pub = 'xpub693MU1BonjMa6Xxar2HQ9r3tRw3AA8yo7BBdvuSZF1D1eet32bVxGr9LYViWMtaLfQaa2StXeUmDG5VELFkU9pc3yfTzCk61WQJdR6ezj7a'; + +var child2_pub = 'xpub693MU1BonjMa8MMoz9opJhrFejcXcGmhMP9gzySLsip4Dz1UrSLT4i2pAimHDyM2onW2H2L2HkbwrZqoizQLMoErXu8mPYxDf8tJUBAfBuT'; + +var child3_priv = 'xprv9v414VeuxMoGusHLt8GowZuX6hn3Cp3qzAE6Cb2jKPH5MBgLJu2CWuiLKTdWV8WoNFYvpCcBfbpWfeyEQ8zytZW5qy39roTcugBGUkeAvCc'; +var child3_pub = 'xpub693MU1BonjMa8MMoz9opJhrFejcXcGmhMP9gzySLsip4Dz1UrSLT4i2pAimHDyM2onW2H2L2HkbwrZqoizQLMoErXu8mPYxDf8tJUBAfBuT'; + +var child4_priv = 'xprv9v414VeuxMoGyViVYuzEN5vLDzff3nkrH5Bf4KzD1iTeY855Q4cCc6xPPNoc6MJcsqqRQiGqR977cEEGK2mhVp7ALKHqY1icEw3Q9UmfQ1v'; +var child4_pub = 'xpub693MU1BonjMaBynxewXEjDs4n2W9TFUheJ7FriPpa3zdQvQDwbvT9uGsEebvioAcYbtRUU7ge4yVgj8WDLrwtwuXKTWiieFoYX2B1JYUEfK'; + +var child5_priv = 'xprv9xaK29Nm86ytEwsV9YSsL3jWYR6KtZYY3cKdjAbxHrwKyxH9YWoxvqKwtgQmExGpxAEDrwB4WK9YG1iukth3XiSgsxXLK1W3NB31gLee8fi'; +var child5_pub = 'xpub6BZfReuexUYBTRwxFZyshBgF6SvpJ2GPQqFEXZ1ZrCUJrkcJ648DUdeRjx9QiNQxQvPcHYV3rGkvuExFQbVRS4kU5ynx4fAsWWhHgyPh1pP'; + +var child6_priv = 'xprv9xaK29Nm86ytGx9uDhNKUBjvbJ1sAEM11aYxGQS66Rmg6oHwy7HbB6kWwMHvukzdbPpGhfNXhZgePWFHm1DCh5PACPFywJJKr1AnUJTLjUc'; +var child6_pub = 'xpub6BZfReuexUYBVSENKiuKqKgf9KrMZh4rNoUZ4nqhemJeybd6Webqiu4zndBwa9UB4Jvr5jB5Bcgng6reXAKCuDiVm7zhzJ13BUDBiM8HidZ'; + +var master = hd.priv({ seed: seed }); +var child1 = master.derive(0); +var child2 = master.hdpub.derive(1); +var child3 = master.derive(1); +var child4 = master.derive(2); +var child5 = child4.derive(0); +var child6 = child4.derive(1); +master._unbuild(master.xprivkey); +master.hdpub._unbuild(master.hdpub.xpubkey); + +// console.log(new HDSeed({ +// // I have the same combination on my luggage: +// entropy: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], +// passphrase: 'foo' +// })); + +try { + assert.equal(master.xprivkey, master_priv); + assert.equal(master.xpubkey, master_pub); + + assert.equal(child1.xprivkey, child1_priv); + assert.equal(child1.xpubkey, child1_pub); + + assert.equal(child2.xpubkey, child2_pub); + + assert.equal(child3.xprivkey, child3_priv); + assert.equal(child3.xpubkey, child3_pub); + + assert.equal(child4.xprivkey, child4_priv); + assert.equal(child4.xpubkey, child4_pub); + + assert.equal(child5.xprivkey, child5_priv); + assert.equal(child5.xpubkey, child5_pub); + + assert.equal(child6.xprivkey, child6_priv); + assert.equal(child6.xpubkey, child6_pub); +} catch (e) { + console.log('master xprivkey: %s', master.xprivkey); + console.log('master xpubkey: %s', master.xpubkey); + console.log('-'); + console.log('child1 xprivkey: %s', child1.xprivkey); + console.log('child1 xpubkey: %s', child1.xpubkey); + console.log('-'); + console.log('child2 xpubkey: %s', child2.xpubkey); + console.log('-'); + console.log('child3 xprivkey: %s', child3.xprivkey); + console.log('child3 xpubkey: %s', child3.xpubkey); + console.log('-'); + console.log('child3 xprivkey: %s', child4.xprivkey); + console.log('child3 xpubkey: %s', child4.xpubkey); + console.log('-'); + console.log('child5 xprivkey: %s', child5.xprivkey); + console.log('child5 xpubkey: %s', child5.xpubkey); + console.log('-'); + console.log('child6 xprivkey: %s', child6.xprivkey); + console.log('child6 xpubkey: %s', child6.xpubkey); + throw e; +} diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index e2e7fb9b..a1c87aa0 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -94,8 +94,7 @@ Peer.prototype._init = function init() { var ip = self.socket && self.socket.remoteAddress || '0.0.0.0'; self.pool.emit('debug', 'version (%s): loading locator hashes for getblocks', ip); self.chain.on('load', function() { - // self.loadBlocks(self.pool.locatorHashes(), 0); - self.loadHeaders(self.pool.locatorHashes(), 0); + self.loadHeaders(self.chain.locatorHashes(), 0); }); }); } @@ -438,61 +437,6 @@ Peer.prototype._handleInv = function handleInv(items) { }); this.emit('blocks', blocks); - if (0) - if (this.pool.options.fullNode) { - var self = this; - - if (txs.length) { - this.emit('txs', txs.map(function(tx) { - return tx.hash; - })); - this.getData(txs); - } - - if (blocks.length === 1) { - this._rChainHead = utils.toHex(blocks[0]); - } - - var orph = blocks.filter(function(block) { - return self._requestingOrphan === utils.toHex(block); - }); - - if (orph.length) { - utils.debug('FOUND MISSING BLOCK'); - utils.debug('FOUND MISSING BLOCK'); - utils.debug('FOUND MISSING BLOCK'); - } - - /* - var orphans = Object.keys(self.chain.orphan.map).reduce(function(out, prev) { - var orphan = self.chain.orphan.map[prev]; - out[orphan.hash('hex')] = orphan; - return out; - }, {}); - */ - - var orphans = self.chain.orphan.bmap; - - for (var i = 0; i < blocks.length; i++) { - var hash = utils.toHex(blocks[i]); - if (orphans[hash]) { - this.loadBlocks(this.pool.locatorHashes(), this.chain.getOrphanRoot(hash)); - continue; - } - if (!this.chain.index.bloom.test(hash, 'hex')) { - this.getData([{ type: 'block', hash: hash }]); - } - } - -/* - this.getData(blocks.map(function(block) { - return { type: 'block', hash: utils.toHex(block) }; - })); -*/ - - return; - } - if (txs.length === 0) return; @@ -504,36 +448,30 @@ Peer.prototype._handleInv = function handleInv(items) { Peer.prototype._handleHeaders = function handleHeaders(headers) { var self = this; + headers = headers.map(function(header) { header.prevBlock = utils.toHex(header.prevBlock); header.merkleRoot = utils.toHex(header.merkleRoot); - var abbr = bcoin.block.prototype.abbr.call(header); - header._hash = utils.toHex(utils.dsha256(abbr)); + header.hash = utils.toHex(utils.dsha256(header._raw)); return header; }); if (this.pool.options.fullNode) { - var self = this; - - if (headers.length === 1) { - this._rChainHead = headers[0]._hash; - } - - var orphans = self.chain.orphan.bmap; - for (var i = 0; i < headers.length; i++) { - var hash = headers[i]._hash; - if (orphans[hash]) { - this.loadHeaders(this.pool.locatorHashes(), this.chain.getOrphanRoot(hash)); + var header = headers[i]; + var hash = header.hash; + // if (this.chain.orphan_bmap[hash]) { + if (this.chain.orphan.map[header.prevBlock]) { + this.loadHeaders(this.chain.locatorHashes(), this.chain.getOrphanRoot(hash)); continue; } - if (!this.chain.index.bloom.test(hash, 'hex')) { + if (!this.chain.index.bloom.test(hash, 'hex') || i === headers.length - 1) { this.getData([{ type: 'block', hash: hash }]); } } - - return; } + + this.emit('headers', headers); }; diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index d09d85bd..b56b89ba 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -327,25 +327,21 @@ Pool.prototype._addPeer = function _addPeer(backoff) { peer.on('block', function(block) { backoff = 0; - var hash = block.hash('hex'); - if (hash === peer._requestingOrphan) { - delete peer._requestingOrphan; - } var height = self.chain.index.hashes.length; + var hash = block.hash('hex'); + + if (!self.chain.index.bloom.test(block.prevBlock, 'hex')) { + peer.loadHeaders(self.chain.locatorHashes(), self.chain.getOrphanRoot(block)); + } - // Otherwise, accept block self._response(block); self.chain.add(block); - if (self.chain.orphan.map[block.prevBlock] || !self.chain.index.bloom.test(block.prevBlock)) { - // if (!self.chain.index.bloom.test(block.prevBlock)) { - if (peer._requestingOrphan) return; - var orphanRoot = self.chain.getOrphanRoot(block); - peer._requestingOrphan = orphanRoot; - peer.loadHeaders(self.locatorHashes(), orphanRoot); - } else if (self.chain.index.hashes.length === height) { - return; - } + // if (!self.chain.index.bloom.test(block.prevBlock, 'hex')) { + // peer.loadHeaders(self.chain.locatorHashes(), self.chain.getOrphanRoot(block)); + // } else if (self.chain.index.hashes.length === height) { + // return; + // } self.emit('chain-progress', self.chain.fillPercent(), peer); self.emit('block', block, peer); @@ -391,68 +387,6 @@ Pool.prototype._addPeer = function _addPeer(backoff) { }); }; -Pool.prototype.locatorHashes = function(index) { - var chain = this.chain.index.hashes; - var hashes = []; - var top = chain.length - 1; - var step = 1; - var i; - - if (typeof index === 'string') { - for (i = top; i >= 0; i--) { - if (chain[i] === index) { - top = i; - break; - } - } - } else if (typeof index === 'number') { - top = index; - } - - i = top; - for (;;) { - hashes.push(chain[i]); - i = i - step; - if (i <= 0) { - hashes.push(chain[0]); - break; - } - if (hashes.length >= 10) { - step *= 2; - } - } - - return hashes; -}; - -Pool.prototype.locatorHashes = function(index) { - var self = this; - var hashes = this.chain.index.hashes; - var indicies = []; - var top = hashes.length - 1; - var step = 1, start = 0; - - if (typeof index === 'string') { - for (i = top; i >= 0; i--) { - if (chain[i] === index) { - top = i; - break; - } - } - } else if (typeof index === 'number') { - top = index; - } - - for (var i = top; i > 0; i -= step, ++start) { - if (start >= 10) step *= 2; - indicies.push(hashes[i]); - } - - indicies.push(hashes[0]); - - return indicies; -}; - Pool.prototype._removePeer = function _removePeer(peer) { var i = this.peers.pending.indexOf(peer); if (i !== -1) diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index 0842b379..b3ce3946 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -19,6 +19,12 @@ exports.genesis = { nonce: 2083236893 }; +// address versions +exports.addr = { + normal: 0, + p2sh: 5 +}; + // version - services field exports.services = { network: 1 @@ -135,6 +141,9 @@ exports.opcodes = { for (var i = 1; i <= 16; i++) exports.opcodes[i] = 0x50 + i; +exports.opcodes['false'] = exports.opcodes['0']; +exports.opcodes['true'] = exports.opcodes['1']; + exports.opcodesByVal = new Array(256); Object.keys(exports.opcodes).forEach(function(name) { exports.opcodesByVal[exports.opcodes[name]] = name; diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index 39ecf24d..a5fd849d 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -237,6 +237,7 @@ Parser.prototype.parseHeaders = function parseHeaders(p) { if (p.length >= off + 81) { for (var i = 0; i < count && off + 81 < p.length; i++) { var header = {}; + var start = off; header.version = readU32(p, off); off += 4; header.prevBlock = p.slice(off, off + 32); @@ -252,6 +253,7 @@ Parser.prototype.parseHeaders = function parseHeaders(p) { var r = readIntv(p, off); header.totalTX = r.r; off = r.off; + header._raw = p.slice(start, start + 80); headers.push(header); } } diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 1578196f..cfd558f3 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -62,6 +62,7 @@ script.encode = function encode(s) { // Push value to stack if (Array.isArray(instr)) { if (instr.length === 0) { + // OP_FALSE res.push(0); } else if (1 <= instr.length && instr.length <= 0x4b) { res = res.concat(instr.length, instr); diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 970784d0..0db08f46 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -128,17 +128,45 @@ TX.prototype.out = function out(output, value) { var script = output.script ? output.script.slice() : []; - // Multisig script if given addresses if (Array.isArray(output.keys || output.address)) { + // Raw multisig transaction + // https://github.com/bitcoin/bips/blob/master/bip-0010.mediawiki + // https://github.com/bitcoin/bips/blob/master/bip-0011.mediawiki + // https://github.com/bitcoin/bips/blob/master/bip-0019.mediawiki + // [required-sigs] [pubkey-hash1] [pubkey-hash2] ... [number-of-keys] checkmultisig var keys = output.keys || output.address; + if (keys === output.address) { + keys = keys.map(function(address) { + return bcoin.wallet.addr2hash(address); + }); + } + keys = keys.map(function(key) { + if (typeof key === 'string') { + return utils.toKeyArray(key); + } + return key; + }); script = [ [ output.minSignatures || keys.length ] ].concat( keys, [ [ keys.length ], 'checkmultisig' ] ); - // Default script if given address + // outputs: [ [ 2 ], 'key1', 'key2', [ 2 ], 'checkmultisig' ] + // in reality: + // outputs: [ [ 2 ], [0,1,...], [2,3,...], [ 2 ], 'checkmultisig' ] + } else if (bcoin.wallet.validAddress(output.address, 'p2sh')) { + // p2sh transaction + // https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki + // hash160 [20-byte-redeemscript-hash] equal + script = [ + 'hash160', + bcoin.wallet.addr2hash(output.address, 'p2sh'), + 'eq' + ]; } else if (output.address) { + // p2pkh transaction + // dup hash160 [pubkey-hash] equalverify checksig script = [ 'dup', 'hash160', @@ -207,6 +235,23 @@ TX.prototype.verify = function verify(index, force) { }, this); }; +TX.prototype.signature = function signature(tx, key, index, sighash) { + if (typeof index !== 'number') { + index = tx.inputs.indexOf(index); + if (index === -1) return; + } + var input = tx.inputs[index]; + var subscript = input.out.tx.getSubscript(input.out.index); + var hash = tx.subscriptHash(input, subscript, sighash); + var signature = bcoin.ecdsa.sign(hash, key).toDER(); + signature = signature.concat(bcoin.protocol.constants.hashType[sighash]); + return signature; +}; + +TX.prototype.isCoinbase = function isCoinbase() { + return this.inputs.length === 1 && +this.inputs[0].out.hash === 0; +}; + TX.prototype.maxSize = function maxSize() { // Create copy with 0-script inputs var copy = this.clone(); @@ -300,7 +345,3 @@ TX.fromJSON = function fromJSON(json) { return tx; }; - -TX.prototype.clone = function clone() { - return new TX(new bcoin.protocol.parser().parseTX(this.render())); -}; diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index 748193c1..faa13036 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -467,6 +467,20 @@ utils.isIP = function(ip) { return 0; }; +utils.isHex = function(msg) { + return typeof msg === 'string' && /^[0-9a-f]+$/i.test(msg); +}; + +utils.toKeyArray = function(msg) { + if (typeof msg !== 'string') + return msg; + + if (utils.isHex(msg)) + return utils.toArray(msg, 'hex'); + + return utils.fromBase58(msg); +}; + utils.debug = function(msg) { console.log('\x1b[31m' + msg + '\x1b[m'); }; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 65b64919..bdce2416 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -5,6 +5,7 @@ var inherits = require('inherits'); var EventEmitter = require('events').EventEmitter; var utils = bcoin.utils; var assert = utils.assert; +var constants = bcoin.protocol.constants; function Wallet(options, passphrase) { if (!(this instanceof Wallet)) @@ -28,8 +29,18 @@ function Wallet(options, passphrase) { this.key = null; this.loaded = false; this.lastTs = 0; + this.publicKeys = options.publicKeys; - if (options.passphrase) { + if (options.priv instanceof bcoin.hd.priv) { + this.hd = options.priv; + this.key = this.hd.pair; + } else if (options.pub instanceof bcoin.hd.pub) { + this.hd = options.pub; + this.key = this.hd.pair; + } else if (options.hd) { + this.hd = bcoin.hd.priv(options); + this.key = this.hd.pair; + } else if (options.passphrase) { this.key = bcoin.ecdsa.genKeyPair({ pers: options.scope, entropy: hash.sha256().update(options.passphrase).digest() @@ -47,6 +58,26 @@ function Wallet(options, passphrase) { this.fee = 10000; this.dust = 5460; + if (options.m != null) { + this.m = options.m; + this.n = options.n; + this.publicKeys = options.publicKeys || []; + + if (this.n < this.publicKeys.length) { + this.publicKeys.push(this.getPublicKey('base58')); + } + + if (this.m < 1 || this.m > this.n) { + throw new Error('m ranges between 1 and n'); + } + if (this.n < 1 || this.n > 7) { + throw new Error('n ranges between 1 and 7'); + } + if (this.publicKeys.length !== this.n) { + throw new Error(this.n + ' public keys required'); + } + } + this._init(); } inherits(Wallet, EventEmitter); @@ -114,6 +145,8 @@ Wallet.prototype.getPublicKey = function getPublicKey(enc) { var pub = this.key.getPublic(this.compressed, 'array'); if (enc === 'base58') return utils.toBase58(pub); + else if (enc === 'hex') + return utils.toHex(pub); else return pub; }; @@ -126,23 +159,25 @@ Wallet.prototype.getAddress = function getAddress() { return Wallet.hash2addr(this.getHash()); }; -Wallet.hash2addr = function hash2addr(hash) { +Wallet.hash2addr = function hash2addr(hash, version) { hash = utils.toArray(hash, 'hex'); - // Add version - hash = [ 0 ].concat(hash); + version = constants.addr[version || 'normal']; + hash = [ version ].concat(hash); var addr = hash.concat(utils.checksum(hash)); return utils.toBase58(addr); }; -Wallet.addr2hash = function addr2hash(addr) { +Wallet.addr2hash = function addr2hash(addr, version) { if (!Array.isArray(addr)) addr = utils.fromBase58(addr); + version = constants.addr[version || 'normal']; + if (addr.length !== 25) return []; - if (addr[0] !== 0) + if (addr[0] !== version) return []; var chk = utils.checksum(addr.slice(0, -4)); @@ -152,6 +187,13 @@ Wallet.addr2hash = function addr2hash(addr) { return addr.slice(1, -4); }; +Wallet.validAddress = function validAddr(addr, version) { + if (!addr) + return false; + + return !!Wallet.addr2hash(addr, version).length; +}; + Wallet.prototype.validateAddress = function validateAddress(addr) { var p = Wallet.addr2hash(addr); return p.length !== 0; @@ -161,6 +203,7 @@ Wallet.validateAddress = Wallet.prototype.validateAddress; Wallet.prototype.ownOutput = function ownOutput(tx, index) { var hash = this.getHash(); var key = this.getPublicKey(); + var outputs = tx.outputs.filter(function(output, i) { if (index !== undefined && index !== i) return false; @@ -176,6 +219,11 @@ Wallet.prototype.ownOutput = function ownOutput(tx, index) { if (bcoin.script.isMultisig(s, key)) return true; + if (bcoin.script.isScripthash(s) + && utils.isEqual(s[1], this.getP2SHHash())) { + return true; + } + return false; }, this); if (outputs.length === 0) @@ -206,6 +254,11 @@ Wallet.prototype.ownInput = function ownInput(tx, index) { if (bcoin.script.isMultisig(s, key)) return true; + if (bcoin.script.isScripthash(s) + && utils.isEqual(s[1], this.getP2SHHash())) { + return true; + } + return false; }, this); if (inputs.length === 0) @@ -217,7 +270,8 @@ Wallet.prototype.ownInput = function ownInput(tx, index) { Wallet.prototype.sign = function sign(tx, type, inputs, off) { if (!type) type = 'all'; - assert.equal(type, 'all'); + + // assert.equal(type, 'all'); if (!off) off = 0; @@ -236,13 +290,31 @@ Wallet.prototype.sign = function sign(tx, type, inputs, off) { var signature = bcoin.ecdsa.sign(hash, this.key).toDER(); signature = signature.concat(bcoin.protocol.constants.hashType[type]); - if (bcoin.script.isPubkeyhash(s)) { + if (bcoin.script.isPubkeyhash(s) || bcoin.script.isSimplePubkeyhash(s)) { input.script = [ signature, pub ]; return true; } // Multisig - input.script = [ [], signature ]; + // empty array == OP_FALSE == OP_0 + // raw format: OP_FALSE [sig-1] [sig-2] ... + // p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script] + + if (bcoin.script.isMultisig(s) || bcoin.script.isScripthash(s)) { + // XXX Check own? + // || (bcoin.script.isScripthash(s) && utils.isEqual(s[1], this.getP2SHHash())) { + if (!input.script || !input.script.length) { + input.script = [ [], signature ]; + } else if (!~input.script.indexOf(signature)) { + input.script.push(signature); + } + } + + if (bcoin.script.isScripthash(s)) { + if (input.script.length - 1 === this.n) { + input.script.push(this.getP2SHRedemption()); + } + } return true; }, this); @@ -391,6 +463,50 @@ Wallet.prototype.getChange = function fill(tx) { return this.fill(tx, { _getChange: true }); }; +/** + * P2SH (and Multisig) + */ + +Wallet.prototype.getP2SHHash = function() { + return this.getP2SH().hash; +}; + +Wallet.prototype.getP2SHAddress = function() { + return this.getP2SH().address; +}; + +Wallet.prototype.getP2SHRedemption = function() { + return this.getP2SH().redemption; +}; + +Wallet.prototype.getP2SH = function(redeem) { + this.publicKeys = this.publicKeys.map(function(key) { + return utils.toKeyArray(key); + }); + var redemption = redeem || this._createMultisigRedemption(); + var hash = utils.ripasha(redemption); + return { + hash: hash, + address: bcoin.wallet.hash2addr(hash, 'p2sh'), + redemption: redemption + }; +}; + +Wallet.prototype._createMultisigRedemption = function() { + var publicKeys = this.publicKeys; + var mcode = constants.opcodes['1'] + (this.m - 1); + var ncode = script.push(constants.opcodes['1'] + (this.n - 1)); + var redemption = []; + redemption.push(mcode); + this.publicKeys.forEach(function(pubkey) { + redemption.push(pubkey.length); + redemption = redemption.concat(pubkey); + }, this); + redemption.push(ncode); + redemption.push(constants.opcodes.checkmultisig); + return redemption; +}; + Wallet.prototype.toJSON = function toJSON() { return { v: 1,