wallet(HD): native BIP39/BIP32 HD wallet - daemon side
Adds deterministic HD key derivation (path m/44'/2222'/0'/0/i, matching the TRIdock web wallet) wired into CWallet: HD seed stored in wallet.dat (encrypted with the wallet master key when the wallet is encrypted), keypool derived from the seed, and new RPC commands hdnew/hdrestore/hdshow/hdinfo. Crypto core verified standalone against the official BIP39 vector and triWallet.js addresses.
This commit is contained in:
@@ -42,6 +42,7 @@ set(CORE_SOURCES
|
|||||||
bootstrap.cpp
|
bootstrap.cpp
|
||||||
checkpoints.cpp
|
checkpoints.cpp
|
||||||
crypter.cpp
|
crypter.cpp
|
||||||
|
hdwallet.cpp
|
||||||
crypto_ecdh.cpp
|
crypto_ecdh.cpp
|
||||||
crypto_ecdsa.cpp
|
crypto_ecdsa.cpp
|
||||||
db.cpp
|
db.cpp
|
||||||
|
|||||||
@@ -0,0 +1,263 @@
|
|||||||
|
// BIP39 English wordlist (2048 words, canonical). Auto-generated; do not edit.
|
||||||
|
#ifndef TRIANGLES_BIP39_ENGLISH_H
|
||||||
|
#define TRIANGLES_BIP39_ENGLISH_H
|
||||||
|
static const char* const BIP39_WORDLIST_EN[2048] = {
|
||||||
|
"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",
|
||||||
|
|
||||||
|
};
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
// Copyright (c) 2026 The Triangles developers
|
||||||
|
// Distributed under the MIT/X11 software license.
|
||||||
|
#include "hdwallet.h"
|
||||||
|
#include "bip39_english.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
#include <openssl/hmac.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/rand.h>
|
||||||
|
|
||||||
|
#include <secp256k1.h>
|
||||||
|
|
||||||
|
namespace hd {
|
||||||
|
|
||||||
|
// ---- secp256k1 context (self-contained; independent of crypto_ecdsa) ------
|
||||||
|
static secp256k1_context* HDContext()
|
||||||
|
{
|
||||||
|
static secp256k1_context* ctx = NULL;
|
||||||
|
if (!ctx)
|
||||||
|
ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void HmacSha512(const unsigned char* key, size_t keylen,
|
||||||
|
const unsigned char* data, size_t datalen,
|
||||||
|
unsigned char out[64])
|
||||||
|
{
|
||||||
|
unsigned int len = 64;
|
||||||
|
HMAC(EVP_sha512(), key, (int)keylen, data, datalen, out, &len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary search the (lexicographically sorted) BIP39 English wordlist.
|
||||||
|
static int WordIndex(const std::string& w)
|
||||||
|
{
|
||||||
|
int lo = 0, hi = 2047;
|
||||||
|
while (lo <= hi) {
|
||||||
|
int mid = (lo + hi) / 2;
|
||||||
|
int c = w.compare(BIP39_WORDLIST_EN[mid]);
|
||||||
|
if (c == 0) return mid;
|
||||||
|
if (c < 0) hi = mid - 1; else lo = mid + 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::string> SplitWords(const std::string& s)
|
||||||
|
{
|
||||||
|
std::vector<std::string> out;
|
||||||
|
size_t i = 0, n = s.size();
|
||||||
|
while (i < n) {
|
||||||
|
while (i < n && (s[i] == ' ' || s[i] == '\t' || s[i] == '\n' || s[i] == '\r')) i++;
|
||||||
|
size_t j = i;
|
||||||
|
while (j < n && !(s[j] == ' ' || s[j] == '\t' || s[j] == '\n' || s[j] == '\r')) j++;
|
||||||
|
if (j > i) out.push_back(s.substr(i, j - i));
|
||||||
|
i = j;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- BIP39 ----------------------------------------------------------------
|
||||||
|
std::string GenerateMnemonic(int strengthBits)
|
||||||
|
{
|
||||||
|
if (strengthBits != 128 && strengthBits != 256) strengthBits = 256;
|
||||||
|
int entBytes = strengthBits / 8;
|
||||||
|
std::vector<unsigned char> ent(entBytes);
|
||||||
|
if (RAND_bytes(&ent[0], entBytes) != 1) return std::string();
|
||||||
|
|
||||||
|
// checksum = first (ENT/32) bits of SHA256(entropy)
|
||||||
|
unsigned char hash[32];
|
||||||
|
SHA256(&ent[0], entBytes, hash);
|
||||||
|
int csBits = strengthBits / 32;
|
||||||
|
|
||||||
|
// bit buffer = entropy || checksum bits
|
||||||
|
std::vector<unsigned char> bits = ent;
|
||||||
|
bits.push_back(hash[0]); // up to 8 checksum bits live in hash[0]
|
||||||
|
|
||||||
|
int totalBits = strengthBits + csBits;
|
||||||
|
int words = totalBits / 11;
|
||||||
|
std::string out;
|
||||||
|
for (int i = 0; i < words; i++) {
|
||||||
|
int idx = 0;
|
||||||
|
for (int b = 0; b < 11; b++) {
|
||||||
|
int bitpos = i * 11 + b;
|
||||||
|
int byte = bitpos / 8, off = 7 - (bitpos % 8);
|
||||||
|
int bit = (bits[byte] >> off) & 1;
|
||||||
|
idx = (idx << 1) | bit;
|
||||||
|
}
|
||||||
|
if (i) out += ' ';
|
||||||
|
out += BIP39_WORDLIST_EN[idx];
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CheckMnemonic(const std::string& mnemonic)
|
||||||
|
{
|
||||||
|
std::vector<std::string> w = SplitWords(mnemonic);
|
||||||
|
size_t nw = w.size();
|
||||||
|
if (nw != 12 && nw != 15 && nw != 18 && nw != 21 && nw != 24) return false;
|
||||||
|
|
||||||
|
int totalBits = (int)nw * 11;
|
||||||
|
int csBits = totalBits / 33;
|
||||||
|
int entBits = totalBits - csBits;
|
||||||
|
if (entBits % 8 != 0) return false;
|
||||||
|
int entBytes = entBits / 8;
|
||||||
|
|
||||||
|
// unpack 11-bit indices into a bit buffer
|
||||||
|
std::vector<unsigned char> buf((totalBits + 7) / 8, 0);
|
||||||
|
for (size_t i = 0; i < nw; i++) {
|
||||||
|
int idx = WordIndex(w[i]);
|
||||||
|
if (idx < 0) return false;
|
||||||
|
for (int b = 0; b < 11; b++) {
|
||||||
|
int bit = (idx >> (10 - b)) & 1;
|
||||||
|
int bitpos = (int)i * 11 + b;
|
||||||
|
int byte = bitpos / 8, off = 7 - (bitpos % 8);
|
||||||
|
if (bit) buf[byte] |= (1 << off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::vector<unsigned char> ent(buf.begin(), buf.begin() + entBytes);
|
||||||
|
unsigned char hash[32];
|
||||||
|
SHA256(&ent[0], entBytes, hash);
|
||||||
|
// compare csBits checksum bits
|
||||||
|
for (int b = 0; b < csBits; b++) {
|
||||||
|
int bitpos = entBits + b;
|
||||||
|
int byte = bitpos / 8, off = 7 - (bitpos % 8);
|
||||||
|
int got = (buf[byte] >> off) & 1;
|
||||||
|
int want = (hash[b / 8] >> (7 - (b % 8))) & 1;
|
||||||
|
if (got != want) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MnemonicToSeed(const std::string& mnemonic, const std::string& passphrase,
|
||||||
|
unsigned char seed64[64])
|
||||||
|
{
|
||||||
|
std::string salt = "mnemonic" + passphrase;
|
||||||
|
int rc = PKCS5_PBKDF2_HMAC(mnemonic.c_str(), (int)mnemonic.size(),
|
||||||
|
(const unsigned char*)salt.c_str(), (int)salt.size(),
|
||||||
|
2048, EVP_sha512(), 64, seed64);
|
||||||
|
return rc == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- BIP32 ----------------------------------------------------------------
|
||||||
|
bool MasterFromSeed(const unsigned char* seed, size_t seedlen, ExtKey& out)
|
||||||
|
{
|
||||||
|
unsigned char I[64];
|
||||||
|
HmacSha512((const unsigned char*)"Bitcoin seed", 12, seed, seedlen, I);
|
||||||
|
memcpy(out.key, I, 32);
|
||||||
|
memcpy(out.chaincode, I + 32, 32);
|
||||||
|
if (!secp256k1_ec_seckey_verify(HDContext(), out.key)) return false;
|
||||||
|
out.valid = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CKDpriv(const ExtKey& parent, uint32_t index, ExtKey& child)
|
||||||
|
{
|
||||||
|
if (!parent.valid) return false;
|
||||||
|
secp256k1_context* ctx = HDContext();
|
||||||
|
unsigned char data[37];
|
||||||
|
size_t dlen = 0;
|
||||||
|
if (index & HARDENED) {
|
||||||
|
data[0] = 0x00;
|
||||||
|
memcpy(data + 1, parent.key, 32);
|
||||||
|
dlen = 33;
|
||||||
|
} else {
|
||||||
|
// serP(point(parent.key)) = 33-byte compressed pubkey
|
||||||
|
secp256k1_pubkey pk;
|
||||||
|
if (!secp256k1_ec_pubkey_create(ctx, &pk, parent.key)) return false;
|
||||||
|
size_t plen = 33;
|
||||||
|
secp256k1_ec_pubkey_serialize(ctx, data, &plen, &pk, SECP256K1_EC_COMPRESSED);
|
||||||
|
dlen = 33;
|
||||||
|
}
|
||||||
|
data[dlen + 0] = (index >> 24) & 0xff;
|
||||||
|
data[dlen + 1] = (index >> 16) & 0xff;
|
||||||
|
data[dlen + 2] = (index >> 8) & 0xff;
|
||||||
|
data[dlen + 3] = index & 0xff;
|
||||||
|
dlen += 4;
|
||||||
|
|
||||||
|
unsigned char I[64];
|
||||||
|
HmacSha512(parent.chaincode, 32, data, dlen, I);
|
||||||
|
|
||||||
|
memcpy(child.key, parent.key, 32);
|
||||||
|
// child = (IL + parent) mod n ; rejects invalid (IL>=n or result 0)
|
||||||
|
if (!secp256k1_ec_seckey_tweak_add(ctx, child.key, I)) return false;
|
||||||
|
memcpy(child.chaincode, I + 32, 32);
|
||||||
|
child.valid = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DerivePath(const ExtKey& master, const std::vector<uint32_t>& path, ExtKey& out)
|
||||||
|
{
|
||||||
|
ExtKey cur = master;
|
||||||
|
for (size_t i = 0; i < path.size(); i++) {
|
||||||
|
ExtKey nxt;
|
||||||
|
if (!CKDpriv(cur, path[i], nxt)) return false;
|
||||||
|
cur = nxt;
|
||||||
|
}
|
||||||
|
out = cur;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeriveTriangles(const std::string& mnemonic, const std::string& passphrase,
|
||||||
|
uint32_t account, uint32_t change, uint32_t index,
|
||||||
|
unsigned char privOut[32])
|
||||||
|
{
|
||||||
|
unsigned char seed[64];
|
||||||
|
if (!MnemonicToSeed(mnemonic, passphrase, seed)) return false;
|
||||||
|
ExtKey master;
|
||||||
|
if (!MasterFromSeed(seed, 64, master)) return false;
|
||||||
|
std::vector<uint32_t> path;
|
||||||
|
path.push_back(44u | HARDENED);
|
||||||
|
path.push_back(TRI_COIN_TYPE | HARDENED);
|
||||||
|
path.push_back(account | HARDENED);
|
||||||
|
path.push_back(change);
|
||||||
|
path.push_back(index);
|
||||||
|
ExtKey leaf;
|
||||||
|
if (!DerivePath(master, path, leaf)) return false;
|
||||||
|
memcpy(privOut, leaf.key, 32);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace hd
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright (c) 2026 The Triangles developers
|
||||||
|
// Distributed under the MIT/X11 software license.
|
||||||
|
//
|
||||||
|
// Native BIP39 (mnemonic) + BIP32 (HD) key derivation for Triangles.
|
||||||
|
// Produces keys identical to the TRIdock web wallet (derivation path
|
||||||
|
// m/44'/2222'/0'/0/i, coin type 2222), so a 24-word phrase round-trips
|
||||||
|
// between the Qt/daemon wallet and the web wallet.
|
||||||
|
#ifndef TRIANGLES_HDWALLET_H
|
||||||
|
#define TRIANGLES_HDWALLET_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
namespace hd {
|
||||||
|
|
||||||
|
static const uint32_t HARDENED = 0x80000000u;
|
||||||
|
static const uint32_t TRI_COIN_TYPE = 2222u; // matches triWallet.js
|
||||||
|
|
||||||
|
// A BIP32 extended private key (private scalar + chain code).
|
||||||
|
struct ExtKey {
|
||||||
|
unsigned char key[32];
|
||||||
|
unsigned char chaincode[32];
|
||||||
|
bool valid;
|
||||||
|
ExtKey() : valid(false) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- BIP39 ----------------------------------------------------------------
|
||||||
|
// Generate a new mnemonic. strengthBits must be 128 (12 words) or 256 (24).
|
||||||
|
std::string GenerateMnemonic(int strengthBits = 256);
|
||||||
|
// Validate word membership + checksum.
|
||||||
|
bool CheckMnemonic(const std::string& mnemonic);
|
||||||
|
// PBKDF2-HMAC-SHA512(mnemonic, "mnemonic"+passphrase, 2048) -> 64-byte seed.
|
||||||
|
bool MnemonicToSeed(const std::string& mnemonic, const std::string& passphrase,
|
||||||
|
unsigned char seed64[64]);
|
||||||
|
|
||||||
|
// ---- BIP32 ----------------------------------------------------------------
|
||||||
|
bool MasterFromSeed(const unsigned char* seed, size_t seedlen, ExtKey& out);
|
||||||
|
bool CKDpriv(const ExtKey& parent, uint32_t index, ExtKey& child);
|
||||||
|
bool DerivePath(const ExtKey& master, const std::vector<uint32_t>& path, ExtKey& out);
|
||||||
|
|
||||||
|
// ---- High level -----------------------------------------------------------
|
||||||
|
// Derive the 32-byte private scalar for m/44'/coinType'/account'/change/index.
|
||||||
|
bool DeriveTriangles(const std::string& mnemonic, const std::string& passphrase,
|
||||||
|
uint32_t account, uint32_t change, uint32_t index,
|
||||||
|
unsigned char privOut[32]);
|
||||||
|
|
||||||
|
} // namespace hd
|
||||||
|
#endif // TRIANGLES_HDWALLET_H
|
||||||
@@ -1858,3 +1858,78 @@ Value makekeypair(const Array& params, bool fHelp)
|
|||||||
result.push_back(Pair("PublicKey", HexStr(key.GetPubKey().Raw())));
|
result.push_back(Pair("PublicKey", HexStr(key.GetPubKey().Raw())));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Value hdinfo(const Array& params, bool fHelp)
|
||||||
|
{
|
||||||
|
if (fHelp || params.size() != 0)
|
||||||
|
throw runtime_error("hdinfo\nReturns HD (BIP39/BIP32) wallet status.");
|
||||||
|
Object obj;
|
||||||
|
obj.push_back(Pair("hdenabled", pwalletMain->IsHDEnabled()));
|
||||||
|
obj.push_back(Pair("coin_type", 2222));
|
||||||
|
obj.push_back(Pair("derivation_path", "m/44'/2222'/0'/0/i"));
|
||||||
|
obj.push_back(Pair("nextindex", (int64_t)pwalletMain->nHDChainIndex));
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value hdnew(const Array& params, bool fHelp)
|
||||||
|
{
|
||||||
|
if (fHelp || params.size() > 1)
|
||||||
|
throw runtime_error(
|
||||||
|
"hdnew [passphrase]\n"
|
||||||
|
"Generate a NEW 24-word HD seed phrase, activate it as this wallet's\n"
|
||||||
|
"deterministic seed, and return the phrase. WRITE IT DOWN: it is the\n"
|
||||||
|
"only backup of every address this wallet derives.");
|
||||||
|
EnsureWalletIsUnlocked();
|
||||||
|
if (pwalletMain->IsHDEnabled())
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet already has an HD seed; use 'hdshow' to back it up.");
|
||||||
|
string passphrase = params.size() > 0 ? params[0].get_str() : "";
|
||||||
|
string mnemonic, strError;
|
||||||
|
if (!pwalletMain->SetHDSeed("", passphrase, true, mnemonic, strError))
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR, strError);
|
||||||
|
pwalletMain->TopUpKeyPool();
|
||||||
|
Object obj;
|
||||||
|
obj.push_back(Pair("mnemonic", mnemonic));
|
||||||
|
obj.push_back(Pair("words", 24));
|
||||||
|
obj.push_back(Pair("warning", "Write these 24 words down and keep them secret and offline. Anyone with them can spend your coins."));
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value hdrestore(const Array& params, bool fHelp)
|
||||||
|
{
|
||||||
|
if (fHelp || params.size() < 1 || params.size() > 2)
|
||||||
|
throw runtime_error(
|
||||||
|
"hdrestore \"mnemonic\" [passphrase]\n"
|
||||||
|
"Activate an HD seed from an existing 24-word phrase, derive the keypool\n"
|
||||||
|
"and rescan the chain for funds on the derived addresses.");
|
||||||
|
EnsureWalletIsUnlocked();
|
||||||
|
string mnemonic = params[0].get_str();
|
||||||
|
string passphrase = params.size() > 1 ? params[1].get_str() : "";
|
||||||
|
string out, strError;
|
||||||
|
if (!pwalletMain->SetHDSeed(mnemonic, passphrase, false, out, strError))
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR, strError);
|
||||||
|
pwalletMain->TopUpKeyPool();
|
||||||
|
{
|
||||||
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||||
|
pwalletMain->ScanForWalletTransactions(pindexGenesisBlock, true);
|
||||||
|
pwalletMain->ReacceptWalletTransactions();
|
||||||
|
}
|
||||||
|
Object obj;
|
||||||
|
obj.push_back(Pair("restored", true));
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value hdshow(const Array& params, bool fHelp)
|
||||||
|
{
|
||||||
|
if (fHelp || params.size() != 0)
|
||||||
|
throw runtime_error(
|
||||||
|
"hdshow\nReveal the wallet's HD mnemonic for backup. The wallet must be unlocked.");
|
||||||
|
EnsureWalletIsUnlocked();
|
||||||
|
string mnemonic;
|
||||||
|
if (!pwalletMain->GetHDMnemonic(mnemonic))
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet has no HD seed (use 'hdnew' to create one).");
|
||||||
|
Object obj;
|
||||||
|
obj.push_back(Pair("mnemonic", mnemonic));
|
||||||
|
obj.push_back(Pair("warning", "Keep these words secret and offline."));
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|||||||
@@ -305,6 +305,10 @@ static const CRPCCommand vRPCCommands[] =
|
|||||||
{ "settxfee", &settxfee, false, false },
|
{ "settxfee", &settxfee, false, false },
|
||||||
{ "listsinceblock", &listsinceblock, false, false },
|
{ "listsinceblock", &listsinceblock, false, false },
|
||||||
{ "dumpprivkey", &dumpprivkey, false, false },
|
{ "dumpprivkey", &dumpprivkey, false, false },
|
||||||
|
{ "hdnew", &hdnew, false, false },
|
||||||
|
{ "hdrestore", &hdrestore, false, false },
|
||||||
|
{ "hdshow", &hdshow, false, false },
|
||||||
|
{ "hdinfo", &hdinfo, true, false },
|
||||||
{ "dumpwallet", &dumpwallet, true, false },
|
{ "dumpwallet", &dumpwallet, true, false },
|
||||||
{ "importwallet", &importwallet, false, false },
|
{ "importwallet", &importwallet, false, false },
|
||||||
{ "importprivkey", &importprivkey, false, false },
|
{ "importprivkey", &importprivkey, false, false },
|
||||||
|
|||||||
@@ -155,6 +155,10 @@ extern json_spirit::Value getwalletinfo(const json_spirit::Array& params, bool f
|
|||||||
extern json_spirit::Value dumpwallet(const json_spirit::Array& params, bool fHelp);
|
extern json_spirit::Value dumpwallet(const json_spirit::Array& params, bool fHelp);
|
||||||
extern json_spirit::Value importwallet(const json_spirit::Array& params, bool fHelp);
|
extern json_spirit::Value importwallet(const json_spirit::Array& params, bool fHelp);
|
||||||
extern json_spirit::Value dumpprivkey(const json_spirit::Array& params, bool fHelp); // in rpcdump.cpp
|
extern json_spirit::Value dumpprivkey(const json_spirit::Array& params, bool fHelp); // in rpcdump.cpp
|
||||||
|
extern json_spirit::Value hdnew(const json_spirit::Array& params, bool fHelp);
|
||||||
|
extern json_spirit::Value hdrestore(const json_spirit::Array& params, bool fHelp);
|
||||||
|
extern json_spirit::Value hdshow(const json_spirit::Array& params, bool fHelp);
|
||||||
|
extern json_spirit::Value hdinfo(const json_spirit::Array& params, bool fHelp);
|
||||||
extern json_spirit::Value importprivkey(const json_spirit::Array& params, bool fHelp);
|
extern json_spirit::Value importprivkey(const json_spirit::Array& params, bool fHelp);
|
||||||
|
|
||||||
extern json_spirit::Value getsubsidy(const json_spirit::Array& params, bool fHelp);
|
extern json_spirit::Value getsubsidy(const json_spirit::Array& params, bool fHelp);
|
||||||
|
|||||||
+92
-2
@@ -7,12 +7,14 @@
|
|||||||
#include "wallet.h"
|
#include "wallet.h"
|
||||||
#include "walletdb.h"
|
#include "walletdb.h"
|
||||||
#include "crypter.h"
|
#include "crypter.h"
|
||||||
|
#include "hdwallet.h"
|
||||||
#include "ui_interface.h"
|
#include "ui_interface.h"
|
||||||
#include "base58.h"
|
#include "base58.h"
|
||||||
#include "kernel.h"
|
#include "kernel.h"
|
||||||
#include "coincontrol.h"
|
#include "coincontrol.h"
|
||||||
#include "addressindex.h"
|
#include "addressindex.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
#include <cstring>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <random>
|
#include <random>
|
||||||
@@ -129,7 +131,12 @@ CPubKey CWallet::GenerateNewKey()
|
|||||||
|
|
||||||
RandAddSeedPerfmon();
|
RandAddSeedPerfmon();
|
||||||
CKey key;
|
CKey key;
|
||||||
key.MakeNewKey(fCompressed);
|
bool fUsedHD = false;
|
||||||
|
if (fHDEnabled && !hdMnemonic.empty()) {
|
||||||
|
if (DeriveHDKey(nHDChainIndex, key)) { fUsedHD = true; fCompressed = true; }
|
||||||
|
}
|
||||||
|
if (!fUsedHD)
|
||||||
|
key.MakeNewKey(fCompressed);
|
||||||
|
|
||||||
// Compressed public keys were introduced in version 0.6.0
|
// Compressed public keys were introduced in version 0.6.0
|
||||||
if (fCompressed)
|
if (fCompressed)
|
||||||
@@ -145,6 +152,11 @@ CPubKey CWallet::GenerateNewKey()
|
|||||||
|
|
||||||
if (!AddKey(key))
|
if (!AddKey(key))
|
||||||
throw std::runtime_error("CWallet::GenerateNewKey() : AddKey failed");
|
throw std::runtime_error("CWallet::GenerateNewKey() : AddKey failed");
|
||||||
|
if (fUsedHD) {
|
||||||
|
nHDChainIndex++;
|
||||||
|
if (fFileBacked)
|
||||||
|
CWalletDB(strWalletFile).WriteHDChain(nHDChainIndex);
|
||||||
|
}
|
||||||
return key.GetPubKey();
|
return key.GetPubKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,6 +221,9 @@ bool CWallet::Lock()
|
|||||||
if (fDebug)
|
if (fDebug)
|
||||||
printf("Locking wallet.\n");
|
printf("Locking wallet.\n");
|
||||||
|
|
||||||
|
if (IsCrypted())
|
||||||
|
hdMnemonic.clear(); // keep only the encrypted copy while locked
|
||||||
|
|
||||||
{
|
{
|
||||||
LOCK(cs_wallet);
|
LOCK(cs_wallet);
|
||||||
CWalletDB wdb(strWalletFile);
|
CWalletDB wdb(strWalletFile);
|
||||||
@@ -233,8 +248,14 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase)
|
|||||||
return false;
|
return false;
|
||||||
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
|
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
|
||||||
return false;
|
return false;
|
||||||
if (CCryptoKeyStore::Unlock(vMasterKey))
|
if (CCryptoKeyStore::Unlock(vMasterKey)) {
|
||||||
|
if (fHDEnabled && hdMnemonic.empty() && !vchCryptedHDMnemonic.empty()) {
|
||||||
|
CSecret sec;
|
||||||
|
if (DecryptSecret(vMasterKey, vchCryptedHDMnemonic, hdMnemonicIV, sec))
|
||||||
|
hdMnemonic.assign(sec.begin(), sec.end());
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SecureMsgWalletUnlocked();
|
SecureMsgWalletUnlocked();
|
||||||
@@ -407,6 +428,15 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fHDEnabled && !hdMnemonic.empty()) {
|
||||||
|
CSecret sec(hdMnemonic.begin(), hdMnemonic.end());
|
||||||
|
uint256 iv = GetRandHash();
|
||||||
|
std::vector<unsigned char> cipher;
|
||||||
|
if (!EncryptSecret(vMasterKey, sec, iv, cipher)) { dbEnc->TxnAbort(); return false; }
|
||||||
|
hdMnemonicIV = iv; vchCryptedHDMnemonic = cipher;
|
||||||
|
dbEnc->WriteHDCryptedMnemonic(iv, cipher);
|
||||||
|
}
|
||||||
|
|
||||||
SetMinVersion(WalletFeature::WalletCrypt, dbEnc.get(), true);
|
SetMinVersion(WalletFeature::WalletCrypt, dbEnc.get(), true);
|
||||||
|
|
||||||
if (!dbEnc->TxnCommit())
|
if (!dbEnc->TxnCommit())
|
||||||
@@ -2848,3 +2878,63 @@ void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---- HD wallet (BIP39/BIP32) implementation ----
|
||||||
|
bool CWallet::DeriveHDKey(int64_t index, CKey& keyOut) const
|
||||||
|
{
|
||||||
|
if (hdMnemonic.empty())
|
||||||
|
return false;
|
||||||
|
unsigned char priv[32];
|
||||||
|
if (!hd::DeriveTriangles(hdMnemonic, "", 0, 0, (uint32_t)index, priv))
|
||||||
|
return false;
|
||||||
|
CSecret secret(priv, priv + 32);
|
||||||
|
memset(priv, 0, sizeof(priv));
|
||||||
|
keyOut.SetSecret(secret, true); // HD keys are compressed
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CWallet::GetHDMnemonic(std::string& mnemonicOut) const
|
||||||
|
{
|
||||||
|
if (!fHDEnabled || hdMnemonic.empty())
|
||||||
|
return false;
|
||||||
|
mnemonicOut = hdMnemonic;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CWallet::SetHDSeed(const std::string& mnemonicIn, const std::string& passphrase, bool fGenerate, std::string& mnemonicOut, std::string& strError)
|
||||||
|
{
|
||||||
|
LOCK(cs_wallet);
|
||||||
|
if (IsLocked()) { strError = "Wallet is locked; unlock it before setting an HD seed."; return false; }
|
||||||
|
|
||||||
|
std::string m = mnemonicIn;
|
||||||
|
if (m.empty()) {
|
||||||
|
if (!fGenerate) { strError = "No mnemonic supplied."; return false; }
|
||||||
|
m = hd::GenerateMnemonic(256);
|
||||||
|
if (m.empty()) { strError = "Failed to generate mnemonic."; return false; }
|
||||||
|
}
|
||||||
|
if (!hd::CheckMnemonic(m)) { strError = "Invalid mnemonic (unknown word or bad checksum)."; return false; }
|
||||||
|
|
||||||
|
unsigned char priv[32];
|
||||||
|
if (!hd::DeriveTriangles(m, passphrase, 0, 0, 0, priv)) { strError = "Key derivation failed."; return false; }
|
||||||
|
memset(priv, 0, sizeof(priv));
|
||||||
|
|
||||||
|
hdMnemonic = m;
|
||||||
|
fHDEnabled = true;
|
||||||
|
nHDChainIndex = 0;
|
||||||
|
|
||||||
|
if (fFileBacked) {
|
||||||
|
CWalletDB wdb(strWalletFile);
|
||||||
|
if (IsCrypted()) {
|
||||||
|
CSecret sec(m.begin(), m.end());
|
||||||
|
uint256 iv = GetRandHash();
|
||||||
|
std::vector<unsigned char> cipher;
|
||||||
|
if (!EncryptSecret(vMasterKey, sec, iv, cipher)) { strError = "Failed to encrypt seed."; return false; }
|
||||||
|
hdMnemonicIV = iv; vchCryptedHDMnemonic = cipher;
|
||||||
|
wdb.WriteHDCryptedMnemonic(iv, cipher);
|
||||||
|
} else {
|
||||||
|
wdb.WriteHDMnemonic(m);
|
||||||
|
}
|
||||||
|
wdb.WriteHDChain(nHDChainIndex);
|
||||||
|
}
|
||||||
|
mnemonicOut = m;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -105,6 +105,8 @@ public:
|
|||||||
fFileBacked = false;
|
fFileBacked = false;
|
||||||
nMasterKeyMaxID = 0;
|
nMasterKeyMaxID = 0;
|
||||||
pwalletdbEncryption = nullptr;
|
pwalletdbEncryption = nullptr;
|
||||||
|
fHDEnabled = false;
|
||||||
|
nHDChainIndex = 0;
|
||||||
nOrderPosNext = 0;
|
nOrderPosNext = 0;
|
||||||
nCachedStakeWeight = 0;
|
nCachedStakeWeight = 0;
|
||||||
nCachedStakeWeightTime = 0;
|
nCachedStakeWeightTime = 0;
|
||||||
@@ -124,6 +126,13 @@ public:
|
|||||||
CPubKey vchDefaultKey;
|
CPubKey vchDefaultKey;
|
||||||
int64_t nTimeFirstKey;
|
int64_t nTimeFirstKey;
|
||||||
|
|
||||||
|
// ---- HD (BIP39/BIP32) wallet state ----
|
||||||
|
bool fHDEnabled; // an HD seed has been set
|
||||||
|
int64_t nHDChainIndex; // next external index (m/44'/2222'/0'/0/n)
|
||||||
|
std::string hdMnemonic; // in-memory phrase (present when unlocked/unencrypted)
|
||||||
|
std::vector<unsigned char> vchCryptedHDMnemonic; // encrypted phrase (loaded, decrypted on unlock)
|
||||||
|
uint256 hdMnemonicIV; // IV for the encrypted phrase
|
||||||
|
|
||||||
// check whether we are allowed to upgrade (or already support) to the named feature
|
// check whether we are allowed to upgrade (or already support) to the named feature
|
||||||
bool CanSupportFeature(WalletFeature wf) { return nWalletMaxVersion >= static_cast<int>(wf); }
|
bool CanSupportFeature(WalletFeature wf) { return nWalletMaxVersion >= static_cast<int>(wf); }
|
||||||
|
|
||||||
@@ -133,6 +142,14 @@ public:
|
|||||||
// keystore implementation
|
// keystore implementation
|
||||||
// Generate a new key
|
// Generate a new key
|
||||||
CPubKey GenerateNewKey();
|
CPubKey GenerateNewKey();
|
||||||
|
|
||||||
|
// ---- HD wallet (BIP39/BIP32) ----
|
||||||
|
bool IsHDEnabled() const { return fHDEnabled; }
|
||||||
|
bool SetHDSeed(const std::string& mnemonicIn, const std::string& passphrase, bool fGenerate, std::string& mnemonicOut, std::string& strError);
|
||||||
|
bool GetHDMnemonic(std::string& mnemonicOut) const;
|
||||||
|
bool DeriveHDKey(int64_t index, CKey& keyOut) const;
|
||||||
|
bool LoadHDMnemonic(const std::string& m) { hdMnemonic = m; fHDEnabled = true; return true; }
|
||||||
|
bool LoadCryptedHDMnemonic(const uint256& iv, const std::vector<unsigned char>& cipher) { hdMnemonicIV = iv; vchCryptedHDMnemonic = cipher; fHDEnabled = true; return true; }
|
||||||
// Adds a key to the store, and saves it to disk.
|
// Adds a key to the store, and saves it to disk.
|
||||||
bool AddKey(const CKey& key);
|
bool AddKey(const CKey& key);
|
||||||
// Adds a key to the store, without saving it to disk (used by LoadWallet)
|
// Adds a key to the store, without saving it to disk (used by LoadWallet)
|
||||||
|
|||||||
+20
-1
@@ -429,6 +429,24 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
|||||||
pwallet->mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime);
|
pwallet->mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
else if (strType == "hdmnemonic")
|
||||||
|
{
|
||||||
|
std::string m;
|
||||||
|
ssValue >> m;
|
||||||
|
pwallet->LoadHDMnemonic(m);
|
||||||
|
}
|
||||||
|
else if (strType == "hdcmnemonic")
|
||||||
|
{
|
||||||
|
std::pair<uint256, std::vector<unsigned char> > cm;
|
||||||
|
ssValue >> cm;
|
||||||
|
pwallet->LoadCryptedHDMnemonic(cm.first, cm.second);
|
||||||
|
}
|
||||||
|
else if (strType == "hdchain")
|
||||||
|
{
|
||||||
|
int64_t n;
|
||||||
|
ssValue >> n;
|
||||||
|
pwallet->nHDChainIndex = n;
|
||||||
|
}
|
||||||
else if (strType == "version")
|
else if (strType == "version")
|
||||||
{
|
{
|
||||||
ssValue >> wss.nFileVersion;
|
ssValue >> wss.nFileVersion;
|
||||||
@@ -461,7 +479,8 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
|||||||
static bool IsKeyType(string strType)
|
static bool IsKeyType(string strType)
|
||||||
{
|
{
|
||||||
return (strType== "key" || strType == "wkey" ||
|
return (strType== "key" || strType == "wkey" ||
|
||||||
strType == "mkey" || strType == "ckey");
|
strType == "mkey" || strType == "ckey" ||
|
||||||
|
strType == "hdmnemonic" || strType == "hdcmnemonic");
|
||||||
}
|
}
|
||||||
|
|
||||||
DBErrors CWalletDB::LoadWallet(CWallet* pwallet)
|
DBErrors CWalletDB::LoadWallet(CWallet* pwallet)
|
||||||
|
|||||||
@@ -169,6 +169,21 @@ public:
|
|||||||
return Write(std::string("defaultkey"), vchPubKey.Raw());
|
return Write(std::string("defaultkey"), vchPubKey.Raw());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WriteHDMnemonic(const std::string& mnemonic) {
|
||||||
|
nWalletDBUpdated++;
|
||||||
|
Erase(std::string("hdcmnemonic"));
|
||||||
|
return Write(std::string("hdmnemonic"), mnemonic);
|
||||||
|
}
|
||||||
|
bool WriteHDCryptedMnemonic(const uint256& iv, const std::vector<unsigned char>& cipher) {
|
||||||
|
nWalletDBUpdated++;
|
||||||
|
Erase(std::string("hdmnemonic"));
|
||||||
|
return Write(std::string("hdcmnemonic"), std::make_pair(iv, cipher));
|
||||||
|
}
|
||||||
|
bool WriteHDChain(int64_t nIndex) {
|
||||||
|
nWalletDBUpdated++;
|
||||||
|
return Write(std::string("hdchain"), nIndex);
|
||||||
|
}
|
||||||
|
|
||||||
bool ReadPool(int64_t nPool, CKeyPool& keypool)
|
bool ReadPool(int64_t nPool, CKeyPool& keypool)
|
||||||
{
|
{
|
||||||
return Read(std::make_pair(std::string("pool"), nPool), keypool);
|
return Read(std::make_pair(std::string("pool"), nPool), keypool);
|
||||||
|
|||||||
Reference in New Issue
Block a user