diff options
| author | mtg0001 <106211915+mtg0001@users.noreply.github.com> | 2026-08-03 15:11:55 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-03 15:11:55 -0300 |
| commit | aba252206bf39ac81e6627354062c28fae05b0aa (patch) | |
| tree | 751691f0baaf925c54a2c03f9f02ca87fb29956f /src/main.cpp | |
| parent | 1aee805b232a33484e3db1dc8ea4bfb3f585212e (diff) | |
Activate BIP44 wallet derivation (#124)
* Add BIP32 path derivation foundation
* Add HD wallet schema metadata
* Add provisional BIP44 coin type metadata
* Activate BIP44 wallet derivation
* wallet: scan BIP44 restore depth per branch
* wallet: widen default BIP44 restore lookahead
---------
Co-authored-by: Gilcimar Duarte <ti@gpm.infra>
Diffstat (limited to 'src/main.cpp')
| -rw-r--r-- | src/main.cpp | 232 |
1 files changed, 215 insertions, 17 deletions
diff --git a/src/main.cpp b/src/main.cpp index 75647ba..efece6c 100644 --- a/src/main.cpp +++ b/src/main.cpp | |||
| @@ -181,7 +181,7 @@ static bool AddKeyIfMissing(const CKey& key) | |||
| 181 | return AddKey(key); | 181 | return AddKey(key); |
| 182 | } | 182 | } |
| 183 | 183 | ||
| 184 | static bool DeriveNextHDWalletKey(vector<unsigned char>& vchPubKeyRet, string& strErrorRet) | 184 | static bool DeriveNextHDReceiveKey(vector<unsigned char>& vchPubKeyRet, string& strErrorRet) |
| 185 | { | 185 | { |
| 186 | vchPubKeyRet.clear(); | 186 | vchPubKeyRet.clear(); |
| 187 | CRITICAL_BLOCK(cs_keyPool) | 187 | CRITICAL_BLOCK(cs_keyPool) |
| @@ -193,7 +193,12 @@ static bool DeriveNextHDWalletKey(vector<unsigned char>& vchPubKeyRet, string& s | |||
| 193 | } | 193 | } |
| 194 | 194 | ||
| 195 | CKey key; | 195 | CKey key; |
| 196 | if (!DeriveHDKey(nHDNext, key, strErrorRet)) | 196 | unsigned int nIndex = 0; |
| 197 | if (nHDKeySchema == HD_SCHEMA_BIP44) | ||
| 198 | nIndex = nHDReceiveNext; | ||
| 199 | else | ||
| 200 | nIndex = nHDNext; | ||
| 201 | if (!DeriveHDKey(nIndex, key, strErrorRet)) | ||
| 197 | return false; | 202 | return false; |
| 198 | if (!AddKeyIfMissing(key)) | 203 | if (!AddKeyIfMissing(key)) |
| 199 | { | 204 | { |
| @@ -201,14 +206,77 @@ static bool DeriveNextHDWalletKey(vector<unsigned char>& vchPubKeyRet, string& s | |||
| 201 | return false; | 206 | return false; |
| 202 | } | 207 | } |
| 203 | 208 | ||
| 204 | unsigned int nNext = nHDNext + 1; | 209 | unsigned int nNext = nIndex + 1; |
| 205 | if (!CWalletDB().WriteHDNext(nNext)) | 210 | if (nHDKeySchema == HD_SCHEMA_BIP44) |
| 211 | { | ||
| 212 | if (!CWalletDB().WriteHDReceiveNext(nNext)) | ||
| 213 | { | ||
| 214 | strErrorRet = "could not advance the receive derivation counter"; | ||
| 215 | return false; | ||
| 216 | } | ||
| 217 | nHDReceiveNext = nNext; | ||
| 218 | } | ||
| 219 | else | ||
| 220 | { | ||
| 221 | if (!CWalletDB().WriteHDNext(nNext)) | ||
| 222 | { | ||
| 223 | strErrorRet = "could not advance the derivation counter"; | ||
| 224 | return false; | ||
| 225 | } | ||
| 226 | nHDNext = nNext; | ||
| 227 | } | ||
| 228 | |||
| 229 | vchPubKeyRet = key.GetPubKey(); | ||
| 230 | } | ||
| 231 | return true; | ||
| 232 | } | ||
| 233 | |||
| 234 | static bool DeriveNextHDChangeKey(vector<unsigned char>& vchPubKeyRet, string& strErrorRet) | ||
| 235 | { | ||
| 236 | vchPubKeyRet.clear(); | ||
| 237 | CRITICAL_BLOCK(cs_keyPool) | ||
| 238 | { | ||
| 239 | if (!HaveHDSeed()) | ||
| 206 | { | 240 | { |
| 207 | strErrorRet = "could not advance the derivation counter"; | 241 | strErrorRet = "no seed"; |
| 208 | return false; | 242 | return false; |
| 209 | } | 243 | } |
| 210 | 244 | ||
| 211 | nHDNext = nNext; | 245 | CKey key; |
| 246 | if (nHDKeySchema == HD_SCHEMA_BIP44) | ||
| 247 | { | ||
| 248 | if (!DeriveHDChangeKey(nHDChangeNext, key, strErrorRet)) | ||
| 249 | return false; | ||
| 250 | if (!AddKeyIfMissing(key)) | ||
| 251 | { | ||
| 252 | strErrorRet = "could not write the derived change key to wallet.dat"; | ||
| 253 | return false; | ||
| 254 | } | ||
| 255 | unsigned int nNext = nHDChangeNext + 1; | ||
| 256 | if (!CWalletDB().WriteHDChangeNext(nNext)) | ||
| 257 | { | ||
| 258 | strErrorRet = "could not advance the change derivation counter"; | ||
| 259 | return false; | ||
| 260 | } | ||
| 261 | nHDChangeNext = nNext; | ||
| 262 | } | ||
| 263 | else | ||
| 264 | { | ||
| 265 | if (!DeriveHDKey(nHDNext, key, strErrorRet)) | ||
| 266 | return false; | ||
| 267 | if (!AddKeyIfMissing(key)) | ||
| 268 | { | ||
| 269 | strErrorRet = "could not write the derived change key to wallet.dat"; | ||
| 270 | return false; | ||
| 271 | } | ||
| 272 | unsigned int nNext = nHDNext + 1; | ||
| 273 | if (!CWalletDB().WriteHDNext(nNext)) | ||
| 274 | { | ||
| 275 | strErrorRet = "could not advance the derivation counter"; | ||
| 276 | return false; | ||
| 277 | } | ||
| 278 | nHDNext = nNext; | ||
| 279 | } | ||
| 212 | vchPubKeyRet = key.GetPubKey(); | 280 | vchPubKeyRet = key.GetPubKey(); |
| 213 | } | 281 | } |
| 214 | return true; | 282 | return true; |
| @@ -229,6 +297,40 @@ map<int64, vector<unsigned char> > mapKeyPool; | |||
| 229 | vector<unsigned char> vchHDMaster; | 297 | vector<unsigned char> vchHDMaster; |
| 230 | vector<unsigned char> vchHDChainCode; | 298 | vector<unsigned char> vchHDChainCode; |
| 231 | unsigned int nHDNext = 0; | 299 | unsigned int nHDNext = 0; |
| 300 | int nHDKeySchema = HD_SCHEMA_NONE; | ||
| 301 | unsigned int nHDReceiveNext = 0; | ||
| 302 | unsigned int nHDChangeNext = 0; | ||
| 303 | unsigned int nHDCoinType = HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL; | ||
| 304 | |||
| 305 | string HDKeySchemaName(int nSchema) | ||
| 306 | { | ||
| 307 | if (nSchema == HD_SCHEMA_LEGACY) | ||
| 308 | return "legacy-hd"; | ||
| 309 | if (nSchema == HD_SCHEMA_BIP44) | ||
| 310 | return "bip44"; | ||
| 311 | return "none"; | ||
| 312 | } | ||
| 313 | |||
| 314 | std::vector<unsigned int> HDLegacyPath(unsigned int nIndex) | ||
| 315 | { | ||
| 316 | std::vector<unsigned int> path; | ||
| 317 | path.push_back(nIndex | bitflash::BIP32_HARDENED); | ||
| 318 | return path; | ||
| 319 | } | ||
| 320 | |||
| 321 | std::vector<unsigned int> HDBIP44Path(unsigned int nCoinType, | ||
| 322 | unsigned int nAccount, | ||
| 323 | unsigned int nChain, | ||
| 324 | unsigned int nIndex) | ||
| 325 | { | ||
| 326 | std::vector<unsigned int> path; | ||
| 327 | path.push_back(HD_BIP44_PURPOSE | bitflash::BIP32_HARDENED); | ||
| 328 | path.push_back(nCoinType | bitflash::BIP32_HARDENED); | ||
| 329 | path.push_back(nAccount | bitflash::BIP32_HARDENED); | ||
| 330 | path.push_back(nChain); | ||
| 331 | path.push_back(nIndex); | ||
| 332 | return path; | ||
| 333 | } | ||
| 232 | 334 | ||
| 233 | static bool ClearKeyPoolRecords(string& strErrorRet) | 335 | static bool ClearKeyPoolRecords(string& strErrorRet) |
| 234 | { | 336 | { |
| @@ -254,13 +356,59 @@ bool DeriveHDKey(unsigned int nIndex, CKey& keyRet, string& strErrorRet) | |||
| 254 | strErrorRet = "no seed"; | 356 | strErrorRet = "no seed"; |
| 255 | return false; | 357 | return false; |
| 256 | } | 358 | } |
| 359 | if (nIndex >= bitflash::BIP32_HARDENED) | ||
| 360 | { | ||
| 361 | strErrorRet = "child index must be non-hardened; hardening is applied here"; | ||
| 362 | return false; | ||
| 363 | } | ||
| 364 | |||
| 365 | bitflash::BIP32PrivateNode parent; | ||
| 366 | parent.privateKey = vchHDMaster; | ||
| 367 | parent.chainCode = vchHDChainCode; | ||
| 368 | |||
| 369 | bitflash::BIP32PrivateNode child; | ||
| 370 | std::vector<unsigned int> path = nHDKeySchema == HD_SCHEMA_BIP44 | ||
| 371 | ? HDBIP44Path(nHDCoinType, HD_BIP44_ACCOUNT, HD_BIP44_CHAIN_RECEIVE, nIndex) | ||
| 372 | : HDLegacyPath(nIndex); | ||
| 373 | if (!bitflash::BIP32DerivePath(parent, path, child, strErrorRet)) | ||
| 374 | return false; | ||
| 375 | |||
| 376 | if (!keyRet.SetSecret(child.privateKey)) | ||
| 377 | { | ||
| 378 | strErrorRet = "derived scalar is not a usable key"; | ||
| 379 | return false; | ||
| 380 | } | ||
| 381 | return true; | ||
| 382 | } | ||
| 383 | |||
| 384 | bool DeriveHDChangeKey(unsigned int nIndex, CKey& keyRet, string& strErrorRet) | ||
| 385 | { | ||
| 386 | if (!HaveHDSeed()) | ||
| 387 | { | ||
| 388 | strErrorRet = "no seed"; | ||
| 389 | return false; | ||
| 390 | } | ||
| 391 | if (nIndex >= bitflash::BIP32_HARDENED) | ||
| 392 | { | ||
| 393 | strErrorRet = "child index must be non-hardened; hardening is applied here"; | ||
| 394 | return false; | ||
| 395 | } | ||
| 396 | |||
| 397 | if (nHDKeySchema != HD_SCHEMA_BIP44) | ||
| 398 | return DeriveHDKey(nIndex, keyRet, strErrorRet); | ||
| 257 | 399 | ||
| 258 | bitflash::BIP32PrivateNode parent; | 400 | bitflash::BIP32PrivateNode parent; |
| 259 | parent.privateKey = vchHDMaster; | 401 | parent.privateKey = vchHDMaster; |
| 260 | parent.chainCode = vchHDChainCode; | 402 | parent.chainCode = vchHDChainCode; |
| 261 | 403 | ||
| 262 | bitflash::BIP32PrivateNode child; | 404 | bitflash::BIP32PrivateNode child; |
| 263 | if (!bitflash::BIP32DeriveHardenedChild(parent, nIndex, child, strErrorRet)) | 405 | if (!bitflash::BIP32DerivePath(parent, |
| 406 | HDBIP44Path(nHDCoinType, | ||
| 407 | HD_BIP44_ACCOUNT, | ||
| 408 | HD_BIP44_CHAIN_CHANGE, | ||
| 409 | nIndex), | ||
| 410 | child, | ||
| 411 | strErrorRet)) | ||
| 264 | return false; | 412 | return false; |
| 265 | 413 | ||
| 266 | if (!keyRet.SetSecret(child.privateKey)) | 414 | if (!keyRet.SetSecret(child.privateKey)) |
| @@ -307,20 +455,28 @@ bool SetHDSeedFromMnemonic(const string& strMnemonic, string& strErrorRet) | |||
| 307 | return false; | 455 | return false; |
| 308 | 456 | ||
| 309 | if (!CWalletDB().WriteHDMaster(master.privateKey, master.chainCode) || | 457 | if (!CWalletDB().WriteHDMaster(master.privateKey, master.chainCode) || |
| 310 | !CWalletDB().WriteHDNext(1)) | 458 | !CWalletDB().WriteHDSchema(HD_SCHEMA_BIP44) || |
| 459 | !CWalletDB().WriteHDCoinType(HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL) || | ||
| 460 | !CWalletDB().WriteHDNext(0) || | ||
| 461 | !CWalletDB().WriteHDReceiveNext(1) || | ||
| 462 | !CWalletDB().WriteHDChangeNext(0)) | ||
| 311 | { | 463 | { |
| 312 | strErrorRet = "could not write the seed to wallet.dat"; | 464 | strErrorRet = "could not write the seed metadata to wallet.dat"; |
| 313 | return false; | 465 | return false; |
| 314 | } | 466 | } |
| 315 | vchHDMaster = master.privateKey; | 467 | vchHDMaster = master.privateKey; |
| 316 | vchHDChainCode = master.chainCode; | 468 | vchHDChainCode = master.chainCode; |
| 469 | nHDKeySchema = HD_SCHEMA_BIP44; | ||
| 470 | nHDReceiveNext = 1; | ||
| 471 | nHDChangeNext = 0; | ||
| 472 | nHDCoinType = HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL; | ||
| 317 | 473 | ||
| 318 | // Index 0 is spoken for before it is derived, so a failure below cannot | 474 | // Index 0 is spoken for before it is derived, so a failure below cannot |
| 319 | // leave the pool free to hand it out as an ordinary key. The cost of | 475 | // leave the pool free to hand it out as an ordinary key. The cost of |
| 320 | // the counter being ahead of the derivation is one unused index; the | 476 | // the counter being ahead of the derivation is one unused index; the |
| 321 | // cost of the reverse is the default address and a pool key sharing a | 477 | // cost of the reverse is the default address and a pool key sharing a |
| 322 | // derivation path. | 478 | // derivation path. |
| 323 | nHDNext = 1; | 479 | nHDNext = 0; |
| 324 | 480 | ||
| 325 | // Remember which address stops being the default, so it can be named | 481 | // Remember which address stops being the default, so it can be named |
| 326 | // for what it is. Two entries reading "Your Address" -- one covered by | 482 | // for what it is. Two entries reading "Your Address" -- one covered by |
| @@ -372,15 +528,18 @@ void TopUpKeyPool() | |||
| 372 | // without a seed uses anyway. It will not come back from the | 528 | // without a seed uses anyway. It will not come back from the |
| 373 | // phrase, and that is better than a node that cannot mine. | 529 | // phrase, and that is better than a node that cannot mine. |
| 374 | string strError; | 530 | string strError; |
| 375 | if (DeriveHDKey(nHDNext, key, strError)) | 531 | vector<unsigned char> vchPubKey; |
| 532 | if (DeriveNextHDReceiveKey(vchPubKey, strError)) | ||
| 376 | { | 533 | { |
| 377 | nHDNext++; | 534 | if (!CWalletDB().WritePool(nIndex, vchPubKey)) |
| 378 | CWalletDB().WriteHDNext(nHDNext); | 535 | return; |
| 536 | mapKeyPool[nIndex] = vchPubKey; | ||
| 537 | continue; | ||
| 379 | } | 538 | } |
| 380 | else | 539 | else |
| 381 | { | 540 | { |
| 382 | printf("TopUpKeyPool() : derivation at index %u failed (%s), " | 541 | printf("TopUpKeyPool() : receive derivation failed (%s), " |
| 383 | "falling back to a random key\n", nHDNext, strError.c_str()); | 542 | "falling back to a random key\n", strError.c_str()); |
| 384 | key.MakeNewKey(); | 543 | key.MakeNewKey(); |
| 385 | } | 544 | } |
| 386 | } | 545 | } |
| @@ -3668,11 +3827,20 @@ WalletRecoveryAudit GetWalletRecoveryAudit() | |||
| 3668 | CRITICAL_BLOCK(cs_keyPool) | 3827 | CRITICAL_BLOCK(cs_keyPool) |
| 3669 | { | 3828 | { |
| 3670 | audit.fHaveSeed = HaveHDSeed(); | 3829 | audit.fHaveSeed = HaveHDSeed(); |
| 3830 | audit.nSchema = nHDKeySchema; | ||
| 3671 | audit.nDerivedKnown = nHDNext; | 3831 | audit.nDerivedKnown = nHDNext; |
| 3832 | audit.nReceiveNext = nHDReceiveNext; | ||
| 3833 | audit.nChangeNext = nHDChangeNext; | ||
| 3834 | audit.nCoinType = nHDCoinType; | ||
| 3835 | if (nHDKeySchema == HD_SCHEMA_BIP44) | ||
| 3836 | audit.nDerivedKnown = nHDReceiveNext + nHDChangeNext + nHDNext; | ||
| 3672 | if (audit.fHaveSeed) | 3837 | if (audit.fHaveSeed) |
| 3673 | { | 3838 | { |
| 3674 | string strError; | 3839 | string strError; |
| 3675 | for (unsigned int i = 0; i < nHDNext; i++) | 3840 | unsigned int nReceiveDepth = nHDKeySchema == HD_SCHEMA_BIP44 |
| 3841 | ? nHDReceiveNext | ||
| 3842 | : nHDNext; | ||
| 3843 | for (unsigned int i = 0; i < nReceiveDepth; i++) | ||
| 3676 | { | 3844 | { |
| 3677 | CKey key; | 3845 | CKey key; |
| 3678 | if (!DeriveHDKey(i, key, strError)) | 3846 | if (!DeriveHDKey(i, key, strError)) |
| @@ -3684,6 +3852,36 @@ WalletRecoveryAudit GetWalletRecoveryAudit() | |||
| 3684 | } | 3852 | } |
| 3685 | setDerivedPubKeys.insert(key.GetPubKey()); | 3853 | setDerivedPubKeys.insert(key.GetPubKey()); |
| 3686 | } | 3854 | } |
| 3855 | if (nHDKeySchema == HD_SCHEMA_BIP44) | ||
| 3856 | { | ||
| 3857 | int nSavedSchema = nHDKeySchema; | ||
| 3858 | for (unsigned int i = 0; i < nHDChangeNext; i++) | ||
| 3859 | { | ||
| 3860 | CKey key; | ||
| 3861 | if (!DeriveHDChangeKey(i, key, strError)) | ||
| 3862 | { | ||
| 3863 | audit.fDeriveComplete = false; | ||
| 3864 | audit.strDeriveError = strprintf("change derivation failed at index %u: %s", | ||
| 3865 | i, strError.c_str()); | ||
| 3866 | break; | ||
| 3867 | } | ||
| 3868 | setDerivedPubKeys.insert(key.GetPubKey()); | ||
| 3869 | } | ||
| 3870 | nHDKeySchema = HD_SCHEMA_LEGACY; | ||
| 3871 | for (unsigned int i = 0; i < nHDNext; i++) | ||
| 3872 | { | ||
| 3873 | CKey key; | ||
| 3874 | if (!DeriveHDKey(i, key, strError)) | ||
| 3875 | { | ||
| 3876 | audit.fDeriveComplete = false; | ||
| 3877 | audit.strDeriveError = strprintf("legacy compatibility derivation failed at index %u: %s", | ||
| 3878 | i, strError.c_str()); | ||
| 3879 | break; | ||
| 3880 | } | ||
| 3881 | setDerivedPubKeys.insert(key.GetPubKey()); | ||
| 3882 | } | ||
| 3883 | nHDKeySchema = nSavedSchema; | ||
| 3884 | } | ||
| 3687 | } | 3885 | } |
| 3688 | } | 3886 | } |
| 3689 | 3887 | ||
| @@ -3926,7 +4124,7 @@ bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, in | |||
| 3926 | if (HaveHDSeed()) | 4124 | if (HaveHDSeed()) |
| 3927 | { | 4125 | { |
| 3928 | string strError; | 4126 | string strError; |
| 3929 | if (!DeriveNextHDWalletKey(vchPubKey, strError)) | 4127 | if (!DeriveNextHDChangeKey(vchPubKey, strError)) |
| 3930 | { | 4128 | { |
| 3931 | printf("CreateTransaction() : could not derive a phrase-backed change key: %s\n", | 4129 | printf("CreateTransaction() : could not derive a phrase-backed change key: %s\n", |
| 3932 | strError.c_str()); | 4130 | strError.c_str()); |