{"record":{"id":"94b8e9a4c879e896","repo":"siyuan-note/siyuan","slug":"conf-language-310","errorCode":null,"errorMessage":"Conf.Language(310)","messagePattern":"Conf\\.Language\\(310\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":1194,"sourceCode":"\t*Conf.NotebookCrypto = *backup\n\tConf.m.Unlock()\n\tConf.Save()\n\t// 恢复成功后同步重写备份，确保配置和备份内容一致。\n\t// 调用方已持有 notebookCryptoMu，且 writeNotebookCryptoBackupData 不再申请该锁，故无死锁；\n\t// 同步写避免与 ChangeMasterPassword 的并发备份写竞争同一文件（lost update 导致 verifier 被回退）。\n\tnc := *backup\n\tif err := writeNotebookCryptoBackupData(&nc, kek); err != nil {\n\t\tlogging.LogWarnf(\"rewrite notebook crypto backup after restore failed: %s\", err)\n\t}\n\tlogging.LogInfof(\"notebook crypto restored from backup (e.g. after sync to a new device)\")\n\treturn kek, nil\n}\n\n// deriveNotebookCryptoBackupCandidate 对同步备份做无副作用验证，并确认它覆盖全部现有加密笔记本。\nfunc deriveNotebookCryptoBackupCandidate(password string) (backup *conf.NotebookCrypto, kek []byte, err error) {\n\tbackup, err = loadNotebookCryptoBackup()\n\tif err != nil || backup == nil || len(backup.MasterSalt) == 0 || len(backup.KEKVerifier) == 0 {\n\t\treturn nil, nil, errors.New(Conf.Language(310))\n\t}\n\tparams, validErr := util.ValidateArgon2Params(backup.KDFParams)\n\tif validErr != nil {\n\t\treturn nil, nil, errors.New(Conf.Language(317))\n\t}\n\tkek = util.DeriveKey(password, backup.MasterSalt, params)\n\tdecrypted, decryptErr := util.DecryptWithAAD(kek, backup.KEKVerifier, []byte(\"siyuan:kek-verifier\"))\n\tif decryptErr != nil || string(decrypted) != string(kekVerifierMagic) {\n\t\tzeroAndClear(kek)\n\t\treturn nil, nil, errors.New(Conf.Language(311))\n\t}\n\tif backup.Spec != conf.CurrentNotebookCryptoSpec || backup.Checksum == \"\" ||\n\t\tlen(backup.KEKMAC) == 0 || !verifyKEKMAC(backup, kek) {\n\t\tzeroAndClear(kek)\n\t\treturn nil, nil, errors.New(Conf.Language(316))\n\t}\n\tif !verifyKEKAgainstExistingBoxes(kek, backup) || !verifyKEKAgainstEncryptedHistory(kek, backup) {\n\t\tzeroAndClear(kek)","sourceCodeStart":1176,"sourceCodeEnd":1212,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/crypto.go#L1176-L1212","documentation":"deriveNotebookCryptoBackupCandidate throws Conf.Language(310) when the synchronized notebook-crypto backup file cannot be loaded or is incomplete (missing MasterSalt or KEKVerifier). Without a valid backup there is no salt to derive the KEK from and no verifier to check the password against, so unlock/restore from backup is impossible. It means 'no usable encrypted-notebook crypto backup found'.","triggerScenarios":"Calling tryRestoreNotebookCryptoFromBackupLocked or deriveKEK on a device where Conf.NotebookCrypto.Enabled is false and loadNotebookCryptoBackup() returns an error, nil, or a backup with empty MasterSalt/KEKVerifier — e.g. unlocking an encrypted notebook right after data was synced/imported to a new device without the backup file.","commonSituations":"Syncing or importing Data to a new machine where the crypto backup file was excluded or not yet synced; a corrupted/empty backup file; user attempting to unlock an encrypted notebook on a device that never had the crypto config.","solutions":["Ensure the notebook-crypto backup file exists in the workspace Data directory (re-sync from a device where the feature is enabled) and retry the unlock","Restore the conf.json NotebookCrypto section from the original device (or full workspace backup) instead of relying on the backup file","If the data genuinely has no crypto backup and history snapshots were also deleted, the encrypted content cannot be unlocked — restore from a full backup that includes both Data and the crypto backup"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Pre-check that the crypto backup is loadable and complete before attempting unlock/restore\nbackup, err := model.LoadNotebookCryptoBackup()\nif err != nil || backup == nil || len(backup.MasterSalt) == 0 || len(backup.KEKVerifier) == 0 {\n    return errors.New(\"no usable notebook-crypto backup in Data dir; re-sync from the source device first\")\n}","typeGuard":null,"tryCatchPattern":"// Caller pattern\nkek, err := deriveKEK(password)\nif errors.Is(err, errNoUsableBackup) { // Language(310)\n    // prompt user to re-sync Data (including the crypto backup file) and retry\n}","preventionTips":["Always sync/import the complete Data directory, including the notebook-crypto backup file","After moving to a new device, confirm the backup file exists before unlocking encrypted notebooks","Keep an offline copy of the workspace that includes the crypto backup"],"tags":["go","kernel","encryption","backup-missing","sync"],"backgroundTag":"file-not-found","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}