From 1a0f70a54924cb0b1d863201f85c29264a144067 Mon Sep 17 00:00:00 2001 From: Mohsen <56779182+mrtnetwork@users.noreply.github.com> Date: Thu, 25 Jan 2024 16:24:38 +0330 Subject: [PATCH] v2.0.0 Resolved issue with Multisignature address --- CHANGELOG.md | 4 + example/d_test.go | 336 +++++++++++++++++++++++ example/example_multi_sig_transaction.go | 2 +- go.mod | 2 +- go.sum | 2 + provider/multi_sig_script.go | 3 +- 6 files changed, 346 insertions(+), 3 deletions(-) create mode 100644 example/d_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 3824749..a8f6d97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.0 + - Updated golang.org/x/crypto to the latest version v0.18.0 + - Resolved issue with Multisignature address + ## 1.0.0 * Release. \ No newline at end of file diff --git a/example/d_test.go b/example/d_test.go new file mode 100644 index 0000000..8a7959b --- /dev/null +++ b/example/d_test.go @@ -0,0 +1,336 @@ +// Examples, of how to use the package +package example + +import ( + "testing" + + "github.com/mrtnetwork/bitcoin/address" + "github.com/mrtnetwork/bitcoin/provider" + + "errors" + "fmt" + "math/big" + + "github.com/mrtnetwork/bitcoin/constant" + hdwallet "github.com/mrtnetwork/bitcoin/hd_wallet" + "github.com/mrtnetwork/bitcoin/keypair" +) + +func TestD(t *testing.T) { + network := address.TestnetNetwork + api := provider.SelectApi(provider.BlockCyperApi, &network) + mnemonic := "spy often critic spawn produce volcano depart fire theory fog turn retire" + // accsess to private and public keys + masterWallet, _ := hdwallet.FromMnemonic(mnemonic, "") + + // wallet with path + // i generate 4 HD wallet for this test and now i have access to private and pulic key of each wallet + sp1, _ := hdwallet.DrivePath(masterWallet, "m/44'/0'/0'/0/0/1") + sp2, _ := hdwallet.DrivePath(masterWallet, "m/44'/0'/0'/0/0/2") + sp3, _ := hdwallet.DrivePath(masterWallet, "m/44'/0'/0'/0/0/3") + sp4, _ := hdwallet.DrivePath(masterWallet, "m/44'/0'/0'/0/0/4") + + // access to private key `ECPrivate` + private1, _ := sp1.GetPrivate() + private2, _ := sp2.GetPrivate() + private3, _ := sp3.GetPrivate() + private4, _ := sp4.GetPrivate() + // access to public key `ECPublic` + public1 := sp1.GetPublic() + public2 := sp2.GetPublic() + public3 := sp3.GetPublic() + public4 := sp4.GetPublic() + + signer1, _ := provider.CreateMultiSignaturSigner( + // public key of signer + public1.ToHex(), + // siger weight + 2, + ) + signer2, _ := provider.CreateMultiSignaturSigner( + public2.ToHex(), + 2, + ) + signer3, _ := provider.CreateMultiSignaturSigner( + public3.ToHex(), + 1, + ) + signer4, _ := provider.CreateMultiSignaturSigner( + public4.ToHex(), + 1, + ) + + /* + In general, this address requires 5 signatures to spend: + 2 signatures from signer1 + 2 signatures from signer2 + and 1 signature from either signer 3 or signer 4. + And the address script is as follows + + ["OP_5", public1 ,public1 ,public2 ,public2 ,public3 ,public4, "OP_6", "OP_CHECKMULTISIG"] + + And the unlock script will be like this + + ["", signer1Signataure, signer1Signataure, signer2Signatur, signer2Signatur, (signer3Signatur or signer4Signatur), ScriptInHex ] + + */ + multiSigBuilder, err := provider.CreateMultiSignatureAddress( + 5, provider.MultiSignaturAddressSigners{ + signer1, + signer2, signer3, signer4, + }, address.P2WSHInP2SH, // P2SH(P2WSH) + ) + if err != nil { + fmt.Println(err) + return + } + + /* + In general, this address requires 5 signatures to spend: + 2 signatures from signer1 + 2 signatures from signer2 + and 1 signature from either signer 3 or signer 4. + And the address script is as follows + + ["OP_5", public1 ,public1 ,public2 ,public2 ,public3 ,public4, "OP_6", "OP_CHECKMULTISIG"] + + And the unlock script will be like this + + ["", signer1Signataure, signer1Signataure, signer2Signatur, signer2Signatur, (signer3Signatur or signer4Signatur), ScriptInHex ] + */ + multiSigBuilder2, err2 := provider.CreateMultiSignatureAddress( + 5, provider.MultiSignaturAddressSigners{ + signer1, + signer2, signer3, signer4, + }, address.P2WSH, // P2WSH + ) + if err2 != nil { + fmt.Println(err2) + return + } + // P2SH(P2WSH) 5-6 multi-sig ADDRESS + // 2MxVXBKFwvkWFeN4nij3n8s2GMeBeqF6cL4 + multiSigAddress := multiSigBuilder.Address + + // P2SH(P2WPKH) 5-6 multi-sig ADDRESS + // tb1q4aw8qjc4eys27y8hnslzqexkgs920ewx8ssuxhwq0sc28vly0w0sv3mvu9 + multiSigAddress2 := multiSigBuilder2.Address + + // P2TR ADDRESS + // tb1pyhmqwlcrws4dxcgalt4mrffgnys879vs59xf6sve4hazyvmhecxq3e6sc0 + // equals to exampleAddr1 := address.P2TRAddressFromAddress("tb1pyhmqwlcrws4dxcgalt4mrffgnys879vs59xf6sve4hazyvmhecxq3e6sc0") + exampleAddr1 := public2.ToTaprootAddress() + + // P2SH(P2PK) ADDRESS + // 2MugsNcgzLJ1HosnZyC2CfZVmgbMPK1XubR + // equals to exampleAddr2 := address.P2SHAddressFromAddress("2MugsNcgzLJ1HosnZyC2CfZVmgbMPK1XubR", address.P2PKInP2SH) + exampleAddr2 := public4.ToP2PKInP2SH() + + // P2WSH ADDRESS + // tb1qf4qwtr5kp5q87dtp3ul3402vkzssxfv7f4aettjq2hcfhnt92dmq5xzs6n + // equals to exampleAddr3 := address.P2WSHAddresssFromAddress("tb1qf4qwtr5kp5q87dtp3ul3402vkzssxfv7f4aettjq2hcfhnt92dmq5xzs6n") + // created with 1-1 MultiSig script: ["OP_1", publicHex(Hex of compressed public key) , "OP_1", "OP_CHECKMULTISIG"] + exampleAddr3 := public3.ToP2WSHAddress() + + // now we chose some address for spending from multiple address + // i use some different address type for this + spenders := []provider.UtxoOwnerDetails{ + {Address: multiSigAddress, MultiSigAddress: multiSigBuilder}, + {Address: multiSigAddress2, MultiSigAddress: multiSigBuilder2}, + {PublicKey: public2.ToHex(), Address: exampleAddr1}, + } + + // now we need to read spenders account UTXOS + utxos := provider.UtxoWithOwnerList{} + + // i add some method for provider to read utxos from mempol or blockCypher + // looping address to read Utxos + for _, spender := range spenders { + // read ech address utxo from mempol + spenderUtxos, err := api.GetAccountUtxo(spender) + // oh something bad happen when reading Utxos + if err != nil { + fmt.Println("something bad happen when reading Utxos: ", err) + return + } + // oh this address does not have any satoshi for spending + if !spenderUtxos.CanSpending() { + fmt.Println("address does not have any satoshi for spending: ", spender.Address.Show(network)) + continue + } + fmt.Println("spending: ", spenderUtxos.SumOfUtxosValue(), spender.Address.Show(network), spender.Address.GetType()) + + // we append address utxos to utxos list + utxos = append(utxos, spenderUtxos...) + + } + // Well, now we calculate how much we can spend + sumOfUtxo := utxos.SumOfUtxosValue() + fmt.Println("sum of utxos: ", sumOfUtxo) + hasSatoshi := sumOfUtxo.Cmp(big.NewInt(0)) != 0 + + if !hasSatoshi { + // Are you kidding? We don't have btc to spend + fmt.Println("Are you kidding? We don't have btc to spend") + return + } + + fmt.Println("sum of Utxos: ", *sumOfUtxo) + // 656,928 sum of all utxos + + // We consider 50,000 satoshi for the cost + // in next example i show you how to calculate fee + FEE := big.NewInt(3000) + + // now we have 606,920 for spending let do it + // we create 5 different output with different address type + // We consider the spendable amount for 5 outputs and divide by 5, each output 121,384 + + output3 := provider.BitcoinOutputDetails{ + Address: exampleAddr3, + Value: big.NewInt(236768), + } + output4 := provider.BitcoinOutputDetails{ + Address: exampleAddr2, + Value: big.NewInt(1000), + } + output5 := provider.BitcoinOutputDetails{ + Address: exampleAddr1, + Value: big.NewInt(1000), + } + output6 := provider.BitcoinOutputDetails{ + Address: multiSigAddress, + Value: big.NewInt(1000), + } + output7 := provider.BitcoinOutputDetails{ + Address: multiSigAddress2, + Value: big.NewInt(1000), + } + + // Well, now it is clear to whom we are going to pay the amount + // Now let's create the transaction + transactionBuilder := provider.NewBitcoinTransactionBuilder( + // Now, we provide the UTXOs we want to spend. + utxos, + // We select transaction outputs + []provider.BitcoinOutputDetails{output3, output4, output5, output6, output7}, + /* + Transaction fee + Ensure that you have accurately calculated the amounts. + If the sum of the outputs, including the transaction fee, + does not match the total amount of UTXOs, + it will result in an error. Please double-check your calculations. + */ + FEE, + // network (address.BitcoinNetwork ,ddress.TestnetNetwork) + &network, + + // If you like the note write something else and leave it blank + // I will put my GitHub address here + "https://github.com/MohsenHaydari", + /* + RBF, or Replace-By-Fee, is a feature in Bitcoin that allows you to increase the fee of an unconfirmed + transaction that you've broadcasted to the network. + This feature is useful when you want to speed up a + transaction that is taking longer than expected to get confirmed due to low transaction fees. + */ + true, + ) + + // now we use BuildTransaction to complete them + // I considered a method parameter for this, to sign the transaction + + // utxo Utxo infos with owner details + // trDigest transaction digest of current UTXO (must be sign with correct privateKey) + + // tweak: cheack is script path spending or tweaking the script. + // If tweak is set to false, it implies that you are not using the script path spending feature of Taproot, + // and you intend to sign the transaction using the actual script conditions. + + // sighash + // Each input in a Bitcoin transaction can include a "sighash type." + // This type is a flag that determines which parts of the transaction are covered by the digital signature. + // Common sighash types include SIGHASH_ALL, SIGHASH_SINGLE, SIGHASH_ANYONECANPAY, etc. + // This TransactionBuilder only works with SIGHASH_ALL and TAPROOT_SIGHASH_ALL for taproot input + // If you want to use another sighash, you should create another TransactionBuilder + transaction, err := transactionBuilder.BuildTransaction(func(trDigest []byte, utxo provider.UtxoWithOwner, multiSigPublicKey string) (string, error) { + var key keypair.ECPrivate + currentPublicKey := utxo.OwnerDetails.PublicKey + if utxo.IsMultiSig() { + currentPublicKey = multiSigPublicKey + } + // ok we have the public key of the current UTXO and we use some conditions to find private key and sign transaction + switch currentPublicKey { + case public3.ToHex(): + { + key = *private3 + } + case public2.ToHex(): + { + key = *private2 + } + + case public1.ToHex(): + { + key = *private1 + } + case public4.ToHex(): + { + key = *private4 + } + default: + { + return "", errors.New("cannot find private key") + } + } + // Ok, now we have the private key, we need to check which method to use for signing + // We check whether the UTX corresponds to the P2TR address or not. + if utxo.Utxo.IsP2tr() { + // yes is p2tr utxo and now we use SignTaprootTransaction(Schnorr sign) + return key.SignTaprootTransaction( + trDigest, constant.TAPROOT_SIGHASH_ALL, []interface{}{}, true, + ), nil + } + // is seqwit(v0) or lagacy address we use SingInput (ECDSA) + return key.SingInput(trDigest, constant.SIGHASH_ALL), nil + + }) + + if err != nil { + fmt.Println("oh we have some error when build and sign transaction ", err) + return + } + + // ok everything is fine and we need a transaction output for broadcasting + // We use the Serialize method to receive the transaction output + digest := transaction.Serialize() + + // we check if transaction is segwit or not + // When one of the input UTXO addresses is SegWit, the transaction is considered SegWit. + isSegwitTr := transactionBuilder.HasSegwit() + + // transaction id + transactionId := transaction.TxId() + fmt.Println("transaction ID: ", transactionId) + + // transaction size + var transactionSize int + + if isSegwitTr { + transactionSize = transaction.GetVSize() + } else { + transactionSize = transaction.GetSize() + } + fmt.Println("transaction size: ", transactionSize) + + // now we send transaction to network + trId, err := provider.TestMempoolAccept([]interface{}{[]string{digest}}) + + if err != nil { + fmt.Println("something bad happen when sending transaction: ", err) + return + } + // Yes, we did :) 72b7244693960879bb07f9f96e87790a8b57bb2e91c8dfd79e6f9b8ee520adff + // Now we check Mempol for what happened https://mempool.space/testnet/tx/72b7244693960879bb07f9f96e87790a8b57bb2e91c8dfd79e6f9b8ee520adff + fmt.Println("Transaction ID: ", trId) +} diff --git a/example/example_multi_sig_transaction.go b/example/example_multi_sig_transaction.go index c46167b..6ba634e 100644 --- a/example/example_multi_sig_transaction.go +++ b/example/example_multi_sig_transaction.go @@ -136,7 +136,7 @@ func ExampleMultiSigTransactionSpending() { // now we chose some address for spending from multiple address // i use some different address type for this spenders := []provider.UtxoOwnerDetails{ - {Address: multiSigAddress, MultiSigAddress: multiSigBuilder2}, + {Address: multiSigAddress, MultiSigAddress: multiSigBuilder}, {Address: multiSigAddress2, MultiSigAddress: multiSigBuilder2}, {PublicKey: public2.ToHex(), Address: exampleAddr1}, } diff --git a/go.mod b/go.mod index 35313d9..73456c2 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/mrtnetwork/bitcoin go 1.21 -require golang.org/x/crypto v0.13.0 +require golang.org/x/crypto v0.18.0 diff --git a/go.sum b/go.sum index 8886ba7..d1494eb 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= diff --git a/provider/multi_sig_script.go b/provider/multi_sig_script.go index 8c25e85..e2b2c06 100644 --- a/provider/multi_sig_script.go +++ b/provider/multi_sig_script.go @@ -2,6 +2,7 @@ package provider import ( "fmt" + "github.com/mrtnetwork/bitcoin/address" "github.com/mrtnetwork/bitcoin/keypair" "github.com/mrtnetwork/bitcoin/scripts" @@ -114,7 +115,7 @@ func CreateMultiSignatureAddress(threshold int, signers MultiSignaturAddressSign multiSigScript := []interface{}{fmt.Sprint("OP_", threshold)} for i := 0; i < len(signers); i++ { for w := 0; w < signers[i].Weight(); w++ { - multiSigScript = append(multiSigScript, signers[i].PublicKey) + multiSigScript = append(multiSigScript, signers[i].PublicKey()) } } multiSigScript = append(multiSigScript, []interface{}{fmt.Sprint("OP_", sumWeight), "OP_CHECKMULTISIG"}...)