Skip to content

Commit 1a0f70a

Browse files
committed
v2.0.0
Resolved issue with Multisignature address
1 parent 1710790 commit 1a0f70a

File tree

6 files changed

+346
-3
lines changed

6 files changed

+346
-3
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.0.0
2+
- Updated golang.org/x/crypto to the latest version v0.18.0
3+
- Resolved issue with Multisignature address
4+
15
## 1.0.0
26

37
* Release.

example/d_test.go

Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
// Examples, of how to use the package
2+
package example
3+
4+
import (
5+
"testing"
6+
7+
"github.com/mrtnetwork/bitcoin/address"
8+
"github.com/mrtnetwork/bitcoin/provider"
9+
10+
"errors"
11+
"fmt"
12+
"math/big"
13+
14+
"github.com/mrtnetwork/bitcoin/constant"
15+
hdwallet "github.com/mrtnetwork/bitcoin/hd_wallet"
16+
"github.com/mrtnetwork/bitcoin/keypair"
17+
)
18+
19+
func TestD(t *testing.T) {
20+
network := address.TestnetNetwork
21+
api := provider.SelectApi(provider.BlockCyperApi, &network)
22+
mnemonic := "spy often critic spawn produce volcano depart fire theory fog turn retire"
23+
// accsess to private and public keys
24+
masterWallet, _ := hdwallet.FromMnemonic(mnemonic, "")
25+
26+
// wallet with path
27+
// i generate 4 HD wallet for this test and now i have access to private and pulic key of each wallet
28+
sp1, _ := hdwallet.DrivePath(masterWallet, "m/44'/0'/0'/0/0/1")
29+
sp2, _ := hdwallet.DrivePath(masterWallet, "m/44'/0'/0'/0/0/2")
30+
sp3, _ := hdwallet.DrivePath(masterWallet, "m/44'/0'/0'/0/0/3")
31+
sp4, _ := hdwallet.DrivePath(masterWallet, "m/44'/0'/0'/0/0/4")
32+
33+
// access to private key `ECPrivate`
34+
private1, _ := sp1.GetPrivate()
35+
private2, _ := sp2.GetPrivate()
36+
private3, _ := sp3.GetPrivate()
37+
private4, _ := sp4.GetPrivate()
38+
// access to public key `ECPublic`
39+
public1 := sp1.GetPublic()
40+
public2 := sp2.GetPublic()
41+
public3 := sp3.GetPublic()
42+
public4 := sp4.GetPublic()
43+
44+
signer1, _ := provider.CreateMultiSignaturSigner(
45+
// public key of signer
46+
public1.ToHex(),
47+
// siger weight
48+
2,
49+
)
50+
signer2, _ := provider.CreateMultiSignaturSigner(
51+
public2.ToHex(),
52+
2,
53+
)
54+
signer3, _ := provider.CreateMultiSignaturSigner(
55+
public3.ToHex(),
56+
1,
57+
)
58+
signer4, _ := provider.CreateMultiSignaturSigner(
59+
public4.ToHex(),
60+
1,
61+
)
62+
63+
/*
64+
In general, this address requires 5 signatures to spend:
65+
2 signatures from signer1
66+
2 signatures from signer2
67+
and 1 signature from either signer 3 or signer 4.
68+
And the address script is as follows
69+
70+
["OP_5", public1 ,public1 ,public2 ,public2 ,public3 ,public4, "OP_6", "OP_CHECKMULTISIG"]
71+
72+
And the unlock script will be like this
73+
74+
["", signer1Signataure, signer1Signataure, signer2Signatur, signer2Signatur, (signer3Signatur or signer4Signatur), ScriptInHex ]
75+
76+
*/
77+
multiSigBuilder, err := provider.CreateMultiSignatureAddress(
78+
5, provider.MultiSignaturAddressSigners{
79+
signer1,
80+
signer2, signer3, signer4,
81+
}, address.P2WSHInP2SH, // P2SH(P2WSH)
82+
)
83+
if err != nil {
84+
fmt.Println(err)
85+
return
86+
}
87+
88+
/*
89+
In general, this address requires 5 signatures to spend:
90+
2 signatures from signer1
91+
2 signatures from signer2
92+
and 1 signature from either signer 3 or signer 4.
93+
And the address script is as follows
94+
95+
["OP_5", public1 ,public1 ,public2 ,public2 ,public3 ,public4, "OP_6", "OP_CHECKMULTISIG"]
96+
97+
And the unlock script will be like this
98+
99+
["", signer1Signataure, signer1Signataure, signer2Signatur, signer2Signatur, (signer3Signatur or signer4Signatur), ScriptInHex ]
100+
*/
101+
multiSigBuilder2, err2 := provider.CreateMultiSignatureAddress(
102+
5, provider.MultiSignaturAddressSigners{
103+
signer1,
104+
signer2, signer3, signer4,
105+
}, address.P2WSH, // P2WSH
106+
)
107+
if err2 != nil {
108+
fmt.Println(err2)
109+
return
110+
}
111+
// P2SH(P2WSH) 5-6 multi-sig ADDRESS
112+
// 2MxVXBKFwvkWFeN4nij3n8s2GMeBeqF6cL4
113+
multiSigAddress := multiSigBuilder.Address
114+
115+
// P2SH(P2WPKH) 5-6 multi-sig ADDRESS
116+
// tb1q4aw8qjc4eys27y8hnslzqexkgs920ewx8ssuxhwq0sc28vly0w0sv3mvu9
117+
multiSigAddress2 := multiSigBuilder2.Address
118+
119+
// P2TR ADDRESS
120+
// tb1pyhmqwlcrws4dxcgalt4mrffgnys879vs59xf6sve4hazyvmhecxq3e6sc0
121+
// equals to exampleAddr1 := address.P2TRAddressFromAddress("tb1pyhmqwlcrws4dxcgalt4mrffgnys879vs59xf6sve4hazyvmhecxq3e6sc0")
122+
exampleAddr1 := public2.ToTaprootAddress()
123+
124+
// P2SH(P2PK) ADDRESS
125+
// 2MugsNcgzLJ1HosnZyC2CfZVmgbMPK1XubR
126+
// equals to exampleAddr2 := address.P2SHAddressFromAddress("2MugsNcgzLJ1HosnZyC2CfZVmgbMPK1XubR", address.P2PKInP2SH)
127+
exampleAddr2 := public4.ToP2PKInP2SH()
128+
129+
// P2WSH ADDRESS
130+
// tb1qf4qwtr5kp5q87dtp3ul3402vkzssxfv7f4aettjq2hcfhnt92dmq5xzs6n
131+
// equals to exampleAddr3 := address.P2WSHAddresssFromAddress("tb1qf4qwtr5kp5q87dtp3ul3402vkzssxfv7f4aettjq2hcfhnt92dmq5xzs6n")
132+
// created with 1-1 MultiSig script: ["OP_1", publicHex(Hex of compressed public key) , "OP_1", "OP_CHECKMULTISIG"]
133+
exampleAddr3 := public3.ToP2WSHAddress()
134+
135+
// now we chose some address for spending from multiple address
136+
// i use some different address type for this
137+
spenders := []provider.UtxoOwnerDetails{
138+
{Address: multiSigAddress, MultiSigAddress: multiSigBuilder},
139+
{Address: multiSigAddress2, MultiSigAddress: multiSigBuilder2},
140+
{PublicKey: public2.ToHex(), Address: exampleAddr1},
141+
}
142+
143+
// now we need to read spenders account UTXOS
144+
utxos := provider.UtxoWithOwnerList{}
145+
146+
// i add some method for provider to read utxos from mempol or blockCypher
147+
// looping address to read Utxos
148+
for _, spender := range spenders {
149+
// read ech address utxo from mempol
150+
spenderUtxos, err := api.GetAccountUtxo(spender)
151+
// oh something bad happen when reading Utxos
152+
if err != nil {
153+
fmt.Println("something bad happen when reading Utxos: ", err)
154+
return
155+
}
156+
// oh this address does not have any satoshi for spending
157+
if !spenderUtxos.CanSpending() {
158+
fmt.Println("address does not have any satoshi for spending: ", spender.Address.Show(network))
159+
continue
160+
}
161+
fmt.Println("spending: ", spenderUtxos.SumOfUtxosValue(), spender.Address.Show(network), spender.Address.GetType())
162+
163+
// we append address utxos to utxos list
164+
utxos = append(utxos, spenderUtxos...)
165+
166+
}
167+
// Well, now we calculate how much we can spend
168+
sumOfUtxo := utxos.SumOfUtxosValue()
169+
fmt.Println("sum of utxos: ", sumOfUtxo)
170+
hasSatoshi := sumOfUtxo.Cmp(big.NewInt(0)) != 0
171+
172+
if !hasSatoshi {
173+
// Are you kidding? We don't have btc to spend
174+
fmt.Println("Are you kidding? We don't have btc to spend")
175+
return
176+
}
177+
178+
fmt.Println("sum of Utxos: ", *sumOfUtxo)
179+
// 656,928 sum of all utxos
180+
181+
// We consider 50,000 satoshi for the cost
182+
// in next example i show you how to calculate fee
183+
FEE := big.NewInt(3000)
184+
185+
// now we have 606,920 for spending let do it
186+
// we create 5 different output with different address type
187+
// We consider the spendable amount for 5 outputs and divide by 5, each output 121,384
188+
189+
output3 := provider.BitcoinOutputDetails{
190+
Address: exampleAddr3,
191+
Value: big.NewInt(236768),
192+
}
193+
output4 := provider.BitcoinOutputDetails{
194+
Address: exampleAddr2,
195+
Value: big.NewInt(1000),
196+
}
197+
output5 := provider.BitcoinOutputDetails{
198+
Address: exampleAddr1,
199+
Value: big.NewInt(1000),
200+
}
201+
output6 := provider.BitcoinOutputDetails{
202+
Address: multiSigAddress,
203+
Value: big.NewInt(1000),
204+
}
205+
output7 := provider.BitcoinOutputDetails{
206+
Address: multiSigAddress2,
207+
Value: big.NewInt(1000),
208+
}
209+
210+
// Well, now it is clear to whom we are going to pay the amount
211+
// Now let's create the transaction
212+
transactionBuilder := provider.NewBitcoinTransactionBuilder(
213+
// Now, we provide the UTXOs we want to spend.
214+
utxos,
215+
// We select transaction outputs
216+
[]provider.BitcoinOutputDetails{output3, output4, output5, output6, output7},
217+
/*
218+
Transaction fee
219+
Ensure that you have accurately calculated the amounts.
220+
If the sum of the outputs, including the transaction fee,
221+
does not match the total amount of UTXOs,
222+
it will result in an error. Please double-check your calculations.
223+
*/
224+
FEE,
225+
// network (address.BitcoinNetwork ,ddress.TestnetNetwork)
226+
&network,
227+
228+
// If you like the note write something else and leave it blank
229+
// I will put my GitHub address here
230+
"https://github.com/MohsenHaydari",
231+
/*
232+
RBF, or Replace-By-Fee, is a feature in Bitcoin that allows you to increase the fee of an unconfirmed
233+
transaction that you've broadcasted to the network.
234+
This feature is useful when you want to speed up a
235+
transaction that is taking longer than expected to get confirmed due to low transaction fees.
236+
*/
237+
true,
238+
)
239+
240+
// now we use BuildTransaction to complete them
241+
// I considered a method parameter for this, to sign the transaction
242+
243+
// utxo Utxo infos with owner details
244+
// trDigest transaction digest of current UTXO (must be sign with correct privateKey)
245+
246+
// tweak: cheack is script path spending or tweaking the script.
247+
// If tweak is set to false, it implies that you are not using the script path spending feature of Taproot,
248+
// and you intend to sign the transaction using the actual script conditions.
249+
250+
// sighash
251+
// Each input in a Bitcoin transaction can include a "sighash type."
252+
// This type is a flag that determines which parts of the transaction are covered by the digital signature.
253+
// Common sighash types include SIGHASH_ALL, SIGHASH_SINGLE, SIGHASH_ANYONECANPAY, etc.
254+
// This TransactionBuilder only works with SIGHASH_ALL and TAPROOT_SIGHASH_ALL for taproot input
255+
// If you want to use another sighash, you should create another TransactionBuilder
256+
transaction, err := transactionBuilder.BuildTransaction(func(trDigest []byte, utxo provider.UtxoWithOwner, multiSigPublicKey string) (string, error) {
257+
var key keypair.ECPrivate
258+
currentPublicKey := utxo.OwnerDetails.PublicKey
259+
if utxo.IsMultiSig() {
260+
currentPublicKey = multiSigPublicKey
261+
}
262+
// ok we have the public key of the current UTXO and we use some conditions to find private key and sign transaction
263+
switch currentPublicKey {
264+
case public3.ToHex():
265+
{
266+
key = *private3
267+
}
268+
case public2.ToHex():
269+
{
270+
key = *private2
271+
}
272+
273+
case public1.ToHex():
274+
{
275+
key = *private1
276+
}
277+
case public4.ToHex():
278+
{
279+
key = *private4
280+
}
281+
default:
282+
{
283+
return "", errors.New("cannot find private key")
284+
}
285+
}
286+
// Ok, now we have the private key, we need to check which method to use for signing
287+
// We check whether the UTX corresponds to the P2TR address or not.
288+
if utxo.Utxo.IsP2tr() {
289+
// yes is p2tr utxo and now we use SignTaprootTransaction(Schnorr sign)
290+
return key.SignTaprootTransaction(
291+
trDigest, constant.TAPROOT_SIGHASH_ALL, []interface{}{}, true,
292+
), nil
293+
}
294+
// is seqwit(v0) or lagacy address we use SingInput (ECDSA)
295+
return key.SingInput(trDigest, constant.SIGHASH_ALL), nil
296+
297+
})
298+
299+
if err != nil {
300+
fmt.Println("oh we have some error when build and sign transaction ", err)
301+
return
302+
}
303+
304+
// ok everything is fine and we need a transaction output for broadcasting
305+
// We use the Serialize method to receive the transaction output
306+
digest := transaction.Serialize()
307+
308+
// we check if transaction is segwit or not
309+
// When one of the input UTXO addresses is SegWit, the transaction is considered SegWit.
310+
isSegwitTr := transactionBuilder.HasSegwit()
311+
312+
// transaction id
313+
transactionId := transaction.TxId()
314+
fmt.Println("transaction ID: ", transactionId)
315+
316+
// transaction size
317+
var transactionSize int
318+
319+
if isSegwitTr {
320+
transactionSize = transaction.GetVSize()
321+
} else {
322+
transactionSize = transaction.GetSize()
323+
}
324+
fmt.Println("transaction size: ", transactionSize)
325+
326+
// now we send transaction to network
327+
trId, err := provider.TestMempoolAccept([]interface{}{[]string{digest}})
328+
329+
if err != nil {
330+
fmt.Println("something bad happen when sending transaction: ", err)
331+
return
332+
}
333+
// Yes, we did :) 72b7244693960879bb07f9f96e87790a8b57bb2e91c8dfd79e6f9b8ee520adff
334+
// Now we check Mempol for what happened https://mempool.space/testnet/tx/72b7244693960879bb07f9f96e87790a8b57bb2e91c8dfd79e6f9b8ee520adff
335+
fmt.Println("Transaction ID: ", trId)
336+
}

example/example_multi_sig_transaction.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ func ExampleMultiSigTransactionSpending() {
136136
// now we chose some address for spending from multiple address
137137
// i use some different address type for this
138138
spenders := []provider.UtxoOwnerDetails{
139-
{Address: multiSigAddress, MultiSigAddress: multiSigBuilder2},
139+
{Address: multiSigAddress, MultiSigAddress: multiSigBuilder},
140140
{Address: multiSigAddress2, MultiSigAddress: multiSigBuilder2},
141141
{PublicKey: public2.ToHex(), Address: exampleAddr1},
142142
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ module github.com/mrtnetwork/bitcoin
22

33
go 1.21
44

5-
require golang.org/x/crypto v0.13.0
5+
require golang.org/x/crypto v0.18.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
22
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
3+
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
4+
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=

provider/multi_sig_script.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package provider
22

33
import (
44
"fmt"
5+
56
"github.com/mrtnetwork/bitcoin/address"
67
"github.com/mrtnetwork/bitcoin/keypair"
78
"github.com/mrtnetwork/bitcoin/scripts"
@@ -114,7 +115,7 @@ func CreateMultiSignatureAddress(threshold int, signers MultiSignaturAddressSign
114115
multiSigScript := []interface{}{fmt.Sprint("OP_", threshold)}
115116
for i := 0; i < len(signers); i++ {
116117
for w := 0; w < signers[i].Weight(); w++ {
117-
multiSigScript = append(multiSigScript, signers[i].PublicKey)
118+
multiSigScript = append(multiSigScript, signers[i].PublicKey())
118119
}
119120
}
120121
multiSigScript = append(multiSigScript, []interface{}{fmt.Sprint("OP_", sumWeight), "OP_CHECKMULTISIG"}...)

0 commit comments

Comments
 (0)