// TimelockedCoinAddress returns an address that can only be spent after block // `unlockHeight`. func (w *Wallet) timelockedCoinAddress(unlockHeight types.BlockHeight, visible bool) (coinAddress types.UnlockHash, unlockConditions types.UnlockConditions, err error) { // Create the address + spend conditions. sk, pk, err := crypto.GenerateSignatureKeys() if err != nil { return } unlockConditions = types.UnlockConditions{ Timelock: unlockHeight, SignaturesRequired: 1, PublicKeys: []types.SiaPublicKey{ types.SiaPublicKey{ Algorithm: types.SignatureEd25519, Key: encoding.Marshal(pk), }, }, } coinAddress = unlockConditions.UnlockHash() // Create a spendableAddress for the keys and add it to the // timelockedUnlockableAddresses map. If the address has already been // unlocked, also add it to the list of currently spendable addresses. It // needs to go in both though in case there is a reorganization of the // blockchain. w.keys[coinAddress] = &key{ spendable: w.consensusHeight >= unlockHeight, unlockConditions: unlockConditions, secretKey: sk, outputs: make(map[types.SiacoinOutputID]*knownOutput), } // Add this key to the list of addresses that get unlocked at // `unlockHeight` w.timelockedKeys[unlockHeight] = append(w.timelockedKeys[unlockHeight], coinAddress) // Put the address in the list of visible addresses if 'visible' is set. if visible { w.visibleAddresses[coinAddress] = struct{}{} } // Save the wallet state, which now includes the new address. err = w.save() if err != nil { return } return }
// BenchmarkStandaloneValid times how long it takes to verify a single // large transaction, with a certain number of signatures func BenchmarkStandaloneValid(b *testing.B) { numSigs := 7 // make a transaction numSigs with valid inputs with valid signatures b.ReportAllocs() txn := Transaction{} sk := make([]crypto.SecretKey, numSigs) pk := make([]crypto.PublicKey, numSigs) for i := 0; i < numSigs; i++ { s, p, err := crypto.GenerateSignatureKeys() if err != nil { b.Fatal(err) } sk[i] = s pk[i] = p uc := UnlockConditions{ PublicKeys: []SiaPublicKey{ {Algorithm: SignatureEd25519, Key: pk[i][:]}, }, SignaturesRequired: 1, } txn.SiacoinInputs = append(txn.SiacoinInputs, SiacoinInput{ UnlockConditions: uc, }) copy(txn.SiacoinInputs[i].ParentID[:], encoding.Marshal(i)) txn.TransactionSignatures = append(txn.TransactionSignatures, TransactionSignature{ CoveredFields: CoveredFields{WholeTransaction: true}, }) copy(txn.TransactionSignatures[i].ParentID[:], encoding.Marshal(i)) } // Transaction must be constructed before signing for i := 0; i < numSigs; i++ { sigHash := txn.SigHash(i) sig0, err := crypto.SignHash(sigHash, sk[i]) if err != nil { b.Fatal(err) } txn.TransactionSignatures[i].Signature = sig0[:] } b.ResetTimer() for i := 0; i < b.N; i++ { err := txn.StandaloneValid(10) if err != nil { b.Fatal(err) } } }
// coinAddress returns a new address for receiving coins. func (w *Wallet) coinAddress(visible bool) (coinAddress types.UnlockHash, unlockConditions types.UnlockConditions, err error) { // Create the keys and address. sk, pk, err := crypto.GenerateSignatureKeys() if err != nil { return } unlockConditions = types.UnlockConditions{ SignaturesRequired: 1, PublicKeys: []types.SiaPublicKey{ types.SiaPublicKey{ Algorithm: types.SignatureEd25519, Key: encoding.Marshal(pk), }, }, } coinAddress = unlockConditions.UnlockHash() // Add the address to the set of spendable addresses. newKey := &key{ spendable: true, unlockConditions: unlockConditions, secretKey: sk, outputs: make(map[types.SiacoinOutputID]*knownOutput), } w.keys[coinAddress] = newKey if visible { w.visibleAddresses[coinAddress] = struct{}{} } // Save the wallet state, which now includes the new address. err = w.save() if err != nil { return } return }
// TestTransactionValidSignatures probes the validSignatures method of the // Transaction type. func TestTransactionValidSignatures(t *testing.T) { // Create keys for use in signing and verifying. sk, pk, err := crypto.GenerateSignatureKeys() if err != nil { t.Fatal(err) } // Create UnlockConditions with 3 keys, 2 of which are required. The first // possible key is a standard signature. The second key is an unknown // signature type, which should always be accepted. The final type is an // entropy type, which should never be accepted. uc := UnlockConditions{ PublicKeys: []SiaPublicKey{ {Algorithm: SignatureEd25519, Key: pk[:]}, {}, {Algorithm: SignatureEntropy}, }, SignaturesRequired: 2, } // Create a transaction with each type of unlock condition. txn := Transaction{ SiacoinInputs: []SiacoinInput{ {UnlockConditions: uc}, }, FileContractRevisions: []FileContractRevision{ {UnlockConditions: uc}, }, SiafundInputs: []SiafundInput{ {UnlockConditions: uc}, }, } txn.FileContractRevisions[0].ParentID[0] = 1 // can't overlap with other objects txn.SiafundInputs[0].ParentID[0] = 2 // can't overlap with other objects // Create the signatures that spend the output. txn.TransactionSignatures = []TransactionSignature{ // First signatures use cryptography. { Timelock: 5, CoveredFields: CoveredFields{WholeTransaction: true}, }, { CoveredFields: CoveredFields{WholeTransaction: true}, }, { CoveredFields: CoveredFields{WholeTransaction: true}, }, // The second signatures should always work for being unrecognized // types. {PublicKeyIndex: 1}, {PublicKeyIndex: 1}, {PublicKeyIndex: 1}, } txn.TransactionSignatures[1].ParentID[0] = 1 txn.TransactionSignatures[2].ParentID[0] = 2 txn.TransactionSignatures[4].ParentID[0] = 1 txn.TransactionSignatures[5].ParentID[0] = 2 sigHash0 := txn.SigHash(0) sigHash1 := txn.SigHash(1) sigHash2 := txn.SigHash(2) sig0, err := crypto.SignHash(sigHash0, sk) if err != nil { t.Fatal(err) } sig1, err := crypto.SignHash(sigHash1, sk) if err != nil { t.Fatal(err) } sig2, err := crypto.SignHash(sigHash2, sk) if err != nil { t.Fatal(err) } txn.TransactionSignatures[0].Signature = sig0[:] txn.TransactionSignatures[1].Signature = sig1[:] txn.TransactionSignatures[2].Signature = sig2[:] // Check that the signing was successful. err = txn.validSignatures(10) if err != nil { t.Error(err) } // Corrupt one of the sigantures. sig0[0]++ txn.TransactionSignatures[0].Signature = sig0[:] err = txn.validSignatures(10) if err == nil { t.Error("Corrupted a signature but the txn was still accepted as valid!") } sig0[0]-- txn.TransactionSignatures[0].Signature = sig0[:] // Fail the validCoveredFields check. txn.TransactionSignatures[0].CoveredFields.SiacoinInputs = []uint64{33} err = txn.validSignatures(10) if err == nil { t.Error("failed to flunk the validCoveredFields check") } txn.TransactionSignatures[0].CoveredFields.SiacoinInputs = nil // Double spend a SiacoinInput, FileContractTermination, and SiafundInput. txn.SiacoinInputs = append(txn.SiacoinInputs, SiacoinInput{UnlockConditions: UnlockConditions{}}) err = txn.validSignatures(10) if err == nil { t.Error("failed to double spend a siacoin input") } txn.SiacoinInputs = txn.SiacoinInputs[:len(txn.SiacoinInputs)-1] txn.FileContractRevisions = append(txn.FileContractRevisions, FileContractRevision{UnlockConditions: UnlockConditions{}}) err = txn.validSignatures(10) if err == nil { t.Error("failed to double spend a file contract termination") } txn.FileContractRevisions = txn.FileContractRevisions[:len(txn.FileContractRevisions)-1] txn.SiafundInputs = append(txn.SiafundInputs, SiafundInput{UnlockConditions: UnlockConditions{}}) err = txn.validSignatures(10) if err == nil { t.Error("failed to double spend a siafund input") } txn.SiafundInputs = txn.SiafundInputs[:len(txn.SiafundInputs)-1] // Add a frivilous signature txn.TransactionSignatures = append(txn.TransactionSignatures, TransactionSignature{}) err = txn.validSignatures(10) if err != ErrFrivilousSignature { t.Error(err) } txn.TransactionSignatures = txn.TransactionSignatures[:len(txn.TransactionSignatures)-1] // Replace one of the cryptography signatures with an always-accepted // signature. This should get rejected because the always-accepted // signature has already been used. tmpTxn0 := txn.TransactionSignatures[0] txn.TransactionSignatures[0] = TransactionSignature{PublicKeyIndex: 1} err = txn.validSignatures(10) if err != ErrPublicKeyOveruse { t.Error(err) } txn.TransactionSignatures[0] = tmpTxn0 // Fail the timelock check for signatures. err = txn.validSignatures(4) if err != ErrPrematureSignature { t.Error(err) } // Try to spend an entropy signature. txn.TransactionSignatures[0] = TransactionSignature{PublicKeyIndex: 2} err = txn.validSignatures(10) if err != ErrEntropyKey { t.Error(err) } txn.TransactionSignatures[0] = tmpTxn0 // Try to point to a nonexistant public key. txn.TransactionSignatures[0] = TransactionSignature{PublicKeyIndex: 5} err = txn.validSignatures(10) if err != ErrInvalidPubKeyIndex { t.Error(err) } txn.TransactionSignatures[0] = tmpTxn0 // Insert a malformed public key into the transaction. txn.SiacoinInputs[0].UnlockConditions.PublicKeys[0].Key = []byte{'b', 'a', 'd'} err = txn.validSignatures(10) if err == nil { t.Error(err) } txn.SiacoinInputs[0].UnlockConditions.PublicKeys[0].Key = pk[:] // Insert a malformed signature into the transaction. txn.TransactionSignatures[0].Signature = []byte{'m', 'a', 'l'} err = txn.validSignatures(10) if err == nil { t.Error(err) } txn.TransactionSignatures[0] = tmpTxn0 // Try to spend a transaction when not every required signature is // available. txn.TransactionSignatures = txn.TransactionSignatures[1:] err = txn.validSignatures(10) if err != ErrMissingSignatures { t.Error(err) } }
func TestReviseContract(t *testing.T) { if testing.Short() { t.SkipNow() } rt, err := newRenterTester("TestNegotiateContract") if err != nil { t.Fatal(err) } // get an address ourAddr, err := rt.wallet.NextAddress() if err != nil { t.Fatal(err) } // generate keys sk, pk, err := crypto.GenerateSignatureKeys() if err != nil { t.Fatal(err) } renterPubKey := types.SiaPublicKey{ Algorithm: types.SignatureEd25519, Key: pk[:], } uc := types.UnlockConditions{ PublicKeys: []types.SiaPublicKey{renterPubKey, renterPubKey}, SignaturesRequired: 1, } // create file contract payout := types.NewCurrency64(1e16) fc := types.FileContract{ FileSize: 0, FileMerkleRoot: crypto.Hash{}, // no proof possible without data WindowStart: 100, WindowEnd: 1000, Payout: payout, UnlockHash: uc.UnlockHash(), RevisionNumber: 0, } // outputs need account for tax fc.ValidProofOutputs = []types.SiacoinOutput{ {Value: payout.Sub(fc.Tax()), UnlockHash: ourAddr.UnlockHash()}, {Value: types.ZeroCurrency, UnlockHash: types.UnlockHash{}}, // no collateral } fc.MissedProofOutputs = []types.SiacoinOutput{ // same as above fc.ValidProofOutputs[0], // goes to the void, not the renter {Value: types.ZeroCurrency, UnlockHash: types.UnlockHash{}}, } txnBuilder := rt.wallet.StartTransaction() err = txnBuilder.FundSiacoins(fc.Payout) if err != nil { t.Fatal(err) } txnBuilder.AddFileContract(fc) signedTxnSet, err := txnBuilder.Sign(true) if err != nil { t.Fatal(err) } // submit contract err = rt.tpool.AcceptTransactionSet(signedTxnSet) if err != nil { t.Fatal(err) } // create revision fcid := signedTxnSet[len(signedTxnSet)-1].FileContractID(0) rev := types.FileContractRevision{ ParentID: fcid, UnlockConditions: uc, NewFileSize: 10, NewWindowStart: 100, NewWindowEnd: 1000, NewRevisionNumber: 1, NewValidProofOutputs: fc.ValidProofOutputs, NewMissedProofOutputs: fc.MissedProofOutputs, } // create transaction containing the revision signedTxn := types.Transaction{ FileContractRevisions: []types.FileContractRevision{rev}, TransactionSignatures: []types.TransactionSignature{{ ParentID: crypto.Hash(fcid), CoveredFields: types.CoveredFields{FileContractRevisions: []uint64{0}}, PublicKeyIndex: 0, // renter key is always first -- see negotiateContract }}, } // sign the transaction encodedSig, err := crypto.SignHash(signedTxn.SigHash(0), sk) if err != nil { t.Fatal(err) } signedTxn.TransactionSignatures[0].Signature = encodedSig[:] err = signedTxn.StandaloneValid(rt.renter.blockHeight) if err != nil { t.Fatal(err) } // submit revision err = rt.tpool.AcceptTransactionSet([]types.Transaction{signedTxn}) if err != nil { t.Fatal(err) } }
// generateKeys generates a set of keys and saves them to disk. func generateKeys(requiredKeys int, totalKeys int, folder string, keyname string) (types.UnlockConditions, error) { // Check that the inputs have sane values. if requiredKeys < 1 { return types.UnlockConditions{}, ErrInsecureAddress } if totalKeys < requiredKeys { return types.UnlockConditions{}, ErrUnspendableAddress } // Generate 'TotalKeys', filling out everything except the unlock // conditions. keys := make([]KeyPair, totalKeys) pubKeys := make([]crypto.PublicKey, totalKeys) for i := range keys { var err error keys[i].Header = FileHeader keys[i].Version = FileVersion keys[i].Index = i keys[i].SecretKey, pubKeys[i], err = crypto.GenerateSignatureKeys() if err != nil { return types.UnlockConditions{}, err } } // Generate the unlock conditions and add them to each KeyPair object. This // must be done second because the keypairs can't be given unlock // conditions until the PublicKeys have all been added. unlockConditions := types.UnlockConditions{ Timelock: 0, SignaturesRequired: uint64(requiredKeys), } for i := range keys { unlockConditions.PublicKeys = append(unlockConditions.PublicKeys, types.SiaPublicKey{ Algorithm: types.SignatureEd25519, Key: pubKeys[i][:], }) } for i := range keys { keys[i].UnlockConditions = unlockConditions } // Save the KeyPairs to disk. if folder != "" { err := os.MkdirAll(folder, 0700) if err != nil { return types.UnlockConditions{}, err } } for i, key := range keys { keyFilename := filepath.Join(folder, keyname+"_Key"+strconv.Itoa(i)+FileExtension) _, err := os.Stat(keyFilename) if !os.IsNotExist(err) { if err != nil { return types.UnlockConditions{}, err } return types.UnlockConditions{}, ErrOverwrite } err = encoding.WriteFile(keyFilename, key) if err != nil { return types.UnlockConditions{}, err } } return unlockConditions, nil }
// New returns an initialized Host. func New(cs *consensus.ConsensusSet, hdb modules.HostDB, tpool modules.TransactionPool, wallet modules.Wallet, addr string, saveDir string) (*Host, error) { if cs == nil { return nil, errors.New("host cannot use a nil state") } if hdb == nil { return nil, errors.New("host cannot use a nil hostdb") } if tpool == nil { return nil, errors.New("host cannot use a nil tpool") } if wallet == nil { return nil, errors.New("host cannot use a nil wallet") } // Create host directory if it does not exist. err := os.MkdirAll(saveDir, 0700) if err != nil { return nil, err } h := &Host{ cs: cs, hostdb: hdb, tpool: tpool, wallet: wallet, // default host settings HostSettings: modules.HostSettings{ TotalStorage: 10e9, // 10 GB MaxFilesize: 5 * 1024 * 1024 * 1024, // 5 GiB MaxDuration: 144 * 60, // 60 days WindowSize: 288, // 48 hours Price: types.NewCurrency64(100e12), // 0.1 siacoin / mb / week Collateral: types.NewCurrency64(0), }, saveDir: saveDir, obligationsByID: make(map[types.FileContractID]contractObligation), obligationsByHeight: make(map[types.BlockHeight][]contractObligation), mu: safesync.New(modules.SafeMutexDelay, 1), } h.spaceRemaining = h.TotalStorage // Generate signing key, for revising contracts. sk, pk, err := crypto.GenerateSignatureKeys() if err != nil { return nil, err } h.secretKey = sk h.publicKey = types.SiaPublicKey{ Algorithm: types.SignatureEd25519, Key: pk[:], } // Load the old host data. err = h.load() if err != nil && !os.IsNotExist(err) { return nil, err } // Create listener and set address. h.listener, err = net.Listen("tcp", addr) if err != nil { return nil, err } _, port, _ := net.SplitHostPort(h.listener.Addr().String()) h.myAddr = modules.NetAddress(net.JoinHostPort("::1", port)) // Learn our external IP. go h.learnHostname() // Forward the hosting port, if possible. go h.forwardPort(port) // spawn listener go h.listen() h.cs.ConsensusSetSubscribe(h) return h, nil }