// TestVerifyKeysSiag_1_0 loads some keys generated by siag1.0. // Verification must still work. func TestVerifyKeysSiag_1_0(t *testing.T) { if testing.Short() { t.SkipNow() } var kp KeyPairSiag_1_0 // 1 of 1 err := encoding.ReadFile("siag_1.0_1of1_Key0.siakey", &kp) if err != nil { t.Fatal(err) } err = verifyKeysSiag_1_0(kp.UnlockConditions, "", "siag_1.0_1of1") if err != nil { t.Fatal(err) } // 1 of 2 err = encoding.ReadFile("siag_1.0_1of2_Key0.siakey", &kp) if err != nil { t.Fatal(err) } err = verifyKeysSiag_1_0(kp.UnlockConditions, "", "siag_1.0_1of2") if err != nil { t.Fatal(err) } // 2 of 3 err = encoding.ReadFile("siag_1.0_2of3_Key0.siakey", &kp) if err != nil { t.Fatal(err) } err = verifyKeysSiag_1_0(kp.UnlockConditions, "", "siag_1.0_2of3") if err != nil { t.Fatal(err) } // 3 of 3 err = encoding.ReadFile("siag_1.0_3of3_Key0.siakey", &kp) if err != nil { t.Fatal(err) } err = verifyKeysSiag_1_0(kp.UnlockConditions, "", "siag_1.0_3of3") if err != nil { t.Fatal(err) } // 4 of 9 err = encoding.ReadFile("siag_1.0_4of9_Key0.siakey", &kp) if err != nil { t.Fatal(err) } err = verifyKeysSiag_1_0(kp.UnlockConditions, "", "siag_1.0_4of9") if err != nil { t.Fatal(err) } }
// loadWallet pulls a wallet from disk into memory, merging it with whatever // wallet is already in memory. The result is a combined wallet that has all of // the addresses. func (w *Wallet) loadWallet(filepath string) error { var savedKeys []savedKey err := encoding.ReadFile(filepath, &savedKeys) if err != nil { return err } err = w.loadKeys(savedKeys) if err != nil { return err } return nil }
// loadSiagKeys loads a set of siag keyfiles into the wallet, so that the // wallet may spend the siafunds. func (w *Wallet) loadSiagKeys(masterKey crypto.TwofishKey, keyfiles []string) error { // Load the keyfiles from disk. if len(keyfiles) < 1 { return ErrNoKeyfile } skps := make([]SiagKeyPair, len(keyfiles)) for i, keyfile := range keyfiles { err := encoding.ReadFile(keyfile, &skps[i]) if err != nil { return err } if skps[i].Header != SiagFileHeader { return ErrUnknownHeader } if skps[i].Version != SiagFileVersion { return ErrUnknownVersion } } // Check that all of the loaded files have the same address, and that there // are enough to create the transaction. baseUnlockHash := skps[0].UnlockConditions.UnlockHash() for _, skp := range skps { if skp.UnlockConditions.UnlockHash() != baseUnlockHash { return ErrInconsistentKeys } } if uint64(len(skps)) < skps[0].UnlockConditions.SignaturesRequired { return ErrInsufficientKeys } // Drop all unneeded keys. skps = skps[0:skps[0].UnlockConditions.SignaturesRequired] // Merge the keys into a single spendableKey and save it to the wallet. var sk spendableKey sk.UnlockConditions = skps[0].UnlockConditions for _, skp := range skps { sk.SecretKeys = append(sk.SecretKeys, skp.SecretKey) } err := w.loadSpendableKey(masterKey, sk) if err != nil { return err } err = w.saveSettingsSync() if err != nil { return err } return w.createBackup(filepath.Join(w.persistDir, "Sia Wallet Encrypted Backup - "+persist.RandomSuffix()+settingsFileSuffix)) }
// verifyKeysSiag_1_0 is a copy-pasted version of the verifyKeys method // from siag 1.0. func verifyKeysSiag_1_0(uc types.UnlockConditions, folder string, keyname string) error { keysRequired := uc.SignaturesRequired totalKeys := uint64(len(uc.PublicKeys)) loadedKeys := make([]KeyPairSiag_1_0, totalKeys) for i := 0; i < len(loadedKeys); i++ { err := encoding.ReadFile(filepath.Join(folder, keyname+"_Key"+strconv.Itoa(i)+".siakey"), &loadedKeys[i]) if err != nil { return err } } for _, loadedKey := range loadedKeys { if loadedKey.UnlockConditions.UnlockHash() != uc.UnlockHash() { return errors.New("ErrCorruptedKey") } } txn := types.Transaction{ SiafundInputs: []types.SiafundInput{ types.SiafundInput{ UnlockConditions: loadedKeys[0].UnlockConditions, }, }, } var i uint64 for i != totalKeys { if i+keysRequired > totalKeys { i = totalKeys - keysRequired } var j uint64 for j < keysRequired { txn.TransactionSignatures = append(txn.TransactionSignatures, types.TransactionSignature{ PublicKeyIndex: i, CoveredFields: types.CoveredFields{WholeTransaction: true}, }) sigHash := txn.SigHash(int(j)) sig, err := crypto.SignHash(sigHash, loadedKeys[i].SecretKey) if err != nil { return err } txn.TransactionSignatures[j].Signature = sig[:] i++ j++ } err := txn.StandaloneValid(0) if err != nil { return err } txn.TransactionSignatures = nil } return nil }
// loadSiafundTracking loads siafund addresses for tracking. func (w *Wallet) loadSiafundTracking(filepath string) error { // Load the siafunds file, which is intentionally called 'outputs.dat'. var siafundAddresses []types.UnlockHash err := encoding.ReadFile(filepath, &siafundAddresses) if err != nil { return err } // Load the addresses into the wallet. for _, sa := range siafundAddresses { w.siafundAddresses[sa] = struct{}{} } return nil }
// printKeyInfo opens a keyfile and prints the contents, returning an error if // there's a problem. func printKeyInfo(filename string) error { var kp KeyPair err := encoding.ReadFile(filename, &kp) if err != nil { return err } if kp.Header != FileHeader { return ErrUnknownHeader } if kp.Version != FileVersion { return ErrUnknownVersion } fmt.Printf("Found a key for a %v of %v address.\n", kp.UnlockConditions.SignaturesRequired, len(kp.UnlockConditions.PublicKeys)) fmt.Printf("The address is: %v\n", kp.UnlockConditions.UnlockHash()) return nil }
// WatchSiagSiafundAddress loads a siafund address from a siag key. The private // key is NOT loaded. func (w *Wallet) WatchSiagSiafundAddress(keyfile string) error { lockID := w.mu.Lock() defer w.mu.Unlock(lockID) var skp SiagKeyPair err := encoding.ReadFile(keyfile, &skp) if err != nil { return err } if skp.Header != SiagFileHeader { return ErrUnknownHeader } if skp.Version != SiagFileVersion { return ErrUnknownVersion } w.siafundAddresses[skp.UnlockConditions.UnlockHash()] = struct{}{} // Janky println... but it's important to get this message to the user. println("Loaded a siafund address. Please note that the private key was not loaded, you must KEEP the original keyfile. You must restart siad before your balance can be displayed.") w.save() return nil }
// Load033xWallet loads a v0.3.3.x wallet as an unseeded key, such that the // funds become spendable to the current wallet. func (w *Wallet) Load033xWallet(masterKey crypto.TwofishKey, filepath033x string) error { if err := w.tg.Add(); err != nil { return err } defer w.tg.Done() w.mu.Lock() defer w.mu.Unlock() err := w.checkMasterKey(masterKey) if err != nil { return err } var savedKeys []SavedKey033x err = encoding.ReadFile(filepath033x, &savedKeys) if err != nil { return err } var seedsLoaded int for _, savedKey := range savedKeys { spendKey := spendableKey{ UnlockConditions: savedKey.UnlockConditions, SecretKeys: []crypto.SecretKey{savedKey.SecretKey}, } err = w.loadSpendableKey(masterKey, spendKey) if err != nil && err != errDuplicateSpendableKey { return err } if err == nil { seedsLoaded++ } } err = w.saveSettingsSync() if err != nil { return err } if seedsLoaded == 0 { return errAllDuplicates } return w.createBackup(filepath.Join(w.persistDir, "Sia Wallet Encrypted Backup - "+persist.RandomSuffix()+settingsFileSuffix)) }
// TestPrintKeyInfo probes the printKeyInfo function. func TestPrintKeyInfo(t *testing.T) { if testing.Short() { t.SkipNow() } testDir := build.TempDir("siakg", "TestPrintKeyInfo") // Check that a corrupted header or version will trigger an error. keyname := "headerCheck" _, err := generateKeys(1, 1, testDir, keyname) if err != nil { t.Fatal(err) } var kp, badKP KeyPair keyfile := filepath.Join(testDir, keyname+"_Key0"+FileExtension) err = encoding.ReadFile(keyfile, &kp) if err != nil { t.Fatal(err) } badKP = kp badKP.Header = "bad" err = encoding.WriteFile(keyfile, badKP) if err != nil { t.Fatal(err) } err = printKeyInfo(keyfile) if err != ErrUnknownHeader { t.Error("Expected ErrUnknownHeader:", err) } badKP = kp badKP.Version = "bad" err = encoding.WriteFile(keyfile, badKP) if err != nil { t.Fatal(err) } err = printKeyInfo(keyfile) if err != ErrUnknownVersion { t.Error("Expected ErrUnknownVersion:", err) } }
// TestVerifyKeys proves the verifyKeys function. func TestVerifyKeys(t *testing.T) { if testing.Short() { t.SkipNow() } testDir := build.TempDir("siag", "TestVerifyKeys") // Check that a corrupted header or version will trigger an error. keyname := "headerCheck" uc, err := generateKeys(1, 1, testDir, keyname) if err != nil { t.Fatal(err) } var kp, badKP KeyPair keyfile := filepath.Join(testDir, keyname+"_Key0"+FileExtension) err = encoding.ReadFile(keyfile, &kp) if err != nil { t.Fatal(err) } badKP = kp badKP.Header = "bad" err = encoding.WriteFile(keyfile, badKP) if err != nil { t.Fatal(err) } err = verifyKeys(uc, testDir, keyname) if err != ErrUnknownHeader { t.Error("Expected ErrUnknownHeader:", err) } badKP = kp badKP.Version = "bad" err = encoding.WriteFile(keyfile, badKP) if err != nil { t.Fatal(err) } err = verifyKeys(uc, testDir, keyname) if err != ErrUnknownVersion { t.Error("Expected ErrUnknownVersion:", err) } // Create sets of keys that cover all boundaries from 0 of 1 to 5 of 9. // This is to check for errors in the keycheck calculations. for i := 1; i < 5; i++ { for j := i; j < 9; j++ { keyname := "genuine" + strconv.Itoa(i) + strconv.Itoa(j) uc, err := generateKeys(i, j, testDir, keyname) if err != nil { t.Fatal(err) } // Check that the validate under standard conditions. err = verifyKeys(uc, testDir, keyname) if err != nil { t.Error(err) } // Provide the wrong keyname to simulate a file does not exist error. err = verifyKeys(uc, testDir, "wrongName") if err == nil { t.Error("Expecting an error") } // Corrupt the unlock conditions of the files 1 by 1, and see that each // file is checked for validity. for k := 0; k < j; k++ { // Load, corrupt, and then save the keypair. This corruption // alters the UnlockConditions. var originalKP, badKP KeyPair keyfile := filepath.Join(testDir, keyname+"_Key"+strconv.Itoa(k)+FileExtension) err := encoding.ReadFile(keyfile, &originalKP) if err != nil { t.Fatal(err) } badKP = originalKP badKP.UnlockConditions.PublicKeys = nil err = encoding.WriteFile(keyfile, badKP) if err != nil { t.Fatal(err) } // Run verifyKeys with the corrupted file. err = verifyKeys(uc, testDir, keyname) if err == nil { t.Error("Expecting error after corrupting unlock conditions") } // Restore the original keyfile. err = encoding.WriteFile(keyfile, originalKP) if err != nil { t.Fatal(err) } // Verify that things work again. err = verifyKeys(uc, testDir, keyname) if err != nil { t.Fatal(err) } } // Corrupt the secret keys of the files 1 by 1, and see that each secret // key is checked for validity. for k := 0; k < j; k++ { // Load, corrupt, and then save the keypair. This corruption // alters the secret key. var originalKP, badKP KeyPair keyfile := filepath.Join(testDir, keyname+"_Key"+strconv.Itoa(k)+FileExtension) err := encoding.ReadFile(keyfile, &originalKP) if err != nil { t.Fatal(err) } badKP = originalKP badKP.SecretKey[0]++ err = encoding.WriteFile(keyfile, badKP) if err != nil { t.Fatal(err) } // Run verifyKeys with the corrupted file. err = verifyKeys(uc, testDir, keyname) if err == nil { t.Error("Expecting error after corrupting unlock conditions") } // Restore the original keyfile. err = encoding.WriteFile(keyfile, originalKP) if err != nil { t.Fatal(err) } // Verify that things work again. err = verifyKeys(uc, testDir, keyname) if err != nil { t.Fatal(err) } } } } }
// verifyKeys checks a set of keys on disk to see that they can spend funds // sent to their address. func verifyKeys(uc types.UnlockConditions, folder string, keyname string) error { keysRequired := uc.SignaturesRequired totalKeys := uint64(len(uc.PublicKeys)) // Load the keys from disk back into memory, then verify that the keys on // disk are able to sign outputs in transactions. loadedKeys := make([]KeyPair, totalKeys) for i := 0; i < len(loadedKeys); i++ { err := encoding.ReadFile(filepath.Join(folder, keyname+"_Key"+strconv.Itoa(i)+FileExtension), &loadedKeys[i]) if err != nil { return err } if loadedKeys[i].Header != FileHeader { return ErrUnknownHeader } if loadedKeys[i].Version != FileVersion { return ErrUnknownVersion } } // Check that the keys can be used to spend transactions. for _, loadedKey := range loadedKeys { if loadedKey.UnlockConditions.UnlockHash() != uc.UnlockHash() { return ErrCorruptedKey } } // Create a transaction for the keys to sign. txn := types.Transaction{ SiafundInputs: []types.SiafundInput{ types.SiafundInput{ UnlockConditions: loadedKeys[0].UnlockConditions, }, }, } // Loop through and sign the transaction multiple times. All keys will be // used at least once by the time the loop terminates. var i uint64 for i != totalKeys { // i tracks which key is next to be used. If i + RequiredKeys results // in going out-of-bounds, reduce i so that the last key will be used // for the final signature. if i+keysRequired > totalKeys { i = totalKeys - keysRequired } var j uint64 for j < keysRequired { txn.TransactionSignatures = append(txn.TransactionSignatures, types.TransactionSignature{ PublicKeyIndex: i, CoveredFields: types.CoveredFields{WholeTransaction: true}, }) sigHash := txn.SigHash(int(j)) sig, err := crypto.SignHash(sigHash, loadedKeys[i].SecretKey) if err != nil { return err } txn.TransactionSignatures[j].Signature = sig[:] i++ j++ } // Check that the signature is valid. err := txn.StandaloneValid(0) if err != nil { return err } // Delete all of the signatures for the next iteration. txn.TransactionSignatures = nil } return nil }
// SendSiagSiafunds sends siafunds to another address. The siacoins stored in // the siafunds are sent to an address in the wallet. func (w *Wallet) SendSiagSiafunds(amount types.Currency, dest types.UnlockHash, keyfiles []string) (types.Transaction, error) { if len(keyfiles) < 1 { return types.Transaction{}, ErrNoKeyfile } // Load the siafund keys and verify they are sufficient to sign the // transaction. skps := make([]SiagKeyPair, len(keyfiles)) for i, keyfile := range keyfiles { err := encoding.ReadFile(keyfile, &skps[i]) if err != nil { return types.Transaction{}, err } if skps[i].Header != SiagFileHeader { return types.Transaction{}, ErrUnknownHeader } if skps[i].Version != SiagFileVersion { return types.Transaction{}, ErrUnknownVersion } } // Check that all of the loaded files have the same address, and that there // are enough to create the transaction. baseUnlockHash := skps[0].UnlockConditions.UnlockHash() for _, skp := range skps { if skp.UnlockConditions.UnlockHash() != baseUnlockHash { return types.Transaction{}, ErrInconsistentKeys } } if uint64(len(skps)) < skps[0].UnlockConditions.SignaturesRequired { return types.Transaction{}, ErrInsufficientKeys } // Check that there are enough siafunds in the key to complete the spend. lockID := w.mu.RLock() var availableSiafunds types.Currency var sfoids []types.SiafundOutputID for sfoid, sfo := range w.siafundOutputs { if sfo.UnlockHash == baseUnlockHash { availableSiafunds = availableSiafunds.Add(sfo.Value) sfoids = append(sfoids, sfoid) } if availableSiafunds.Cmp(amount) >= 0 { break } } w.mu.RUnlock(lockID) if availableSiafunds.Cmp(amount) < 0 { return types.Transaction{}, ErrInsufficientSiafunds } // Truncate the keys to exactly the number needed. skps = skps[:skps[0].UnlockConditions.SignaturesRequired] // Assemble the base transction, including a 10 siacoin fee if possible. id, err := w.RegisterTransaction(types.Transaction{}) if err != nil { return types.Transaction{}, err } // Add a miner fee - if funding the transaction fails, we'll just send a // transaction with no fee. txn, err := w.FundTransaction(id, types.NewCurrency64(TransactionFee)) if err == nil { txn, _, err = w.AddMinerFee(id, types.NewCurrency64(TransactionFee)) if err != nil { return types.Transaction{}, err } } // Add the siafund inputs to the transcation. for _, sfoid := range sfoids { // Get an address for the siafund claims. lockID := w.mu.Lock() claimDest, _, err := w.coinAddress(false) w.mu.Unlock(lockID) if err != nil { return types.Transaction{}, err } // Assemble the SiafundInput to spend this output. sfi := types.SiafundInput{ ParentID: sfoid, UnlockConditions: skps[0].UnlockConditions, ClaimUnlockHash: claimDest, } txn, _, err = w.AddSiafundInput(id, sfi) if err != nil { return types.Transaction{}, err } } // Add the siafund output to the transaction. sfo := types.SiafundOutput{ Value: amount, UnlockHash: dest, } txn, _, err = w.AddSiafundOutput(id, sfo) if err != nil { return types.Transaction{}, err } // Add a refund siafund output if needed. if amount.Cmp(availableSiafunds) != 0 { refund := availableSiafunds.Sub(amount) sfo := types.SiafundOutput{ Value: refund, UnlockHash: baseUnlockHash, } txn, _, err = w.AddSiafundOutput(id, sfo) if err != nil { return types.Transaction{}, err } } // Add signatures for the siafund inputs. sigIndex := 0 for _, sfoid := range sfoids { for _, key := range skps { txnSig := types.TransactionSignature{ ParentID: crypto.Hash(sfoid), CoveredFields: types.CoveredFields{WholeTransaction: true}, PublicKeyIndex: uint64(key.Index), } txn.TransactionSignatures = append(txn.TransactionSignatures, txnSig) sigHash := txn.SigHash(sigIndex) encodedSig, err := crypto.SignHash(sigHash, key.SecretKey) if err != nil { return types.Transaction{}, err } txn.TransactionSignatures[sigIndex].Signature = encodedSig[:] txn, _, err = w.AddTransactionSignature(id, txn.TransactionSignatures[sigIndex]) if err != nil { return types.Transaction{}, err } sigIndex++ } } // Sign the transaction. txn, err = w.SignTransaction(id, true) if err != nil { return types.Transaction{}, err } err = w.tpool.AcceptTransaction(txn) if err != nil { return types.Transaction{}, err } return txn, nil }