Skip to content

Commit

Permalink
Updated multisig to separate out logic from view (ie. formatted outpu…
Browse files Browse the repository at this point in the history
…ts), with updated tests testing logic only
  • Loading branch information
soroushjp committed Dec 14, 2014
1 parent 26b79b0 commit 332e109
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 112 deletions.
8 changes: 1 addition & 7 deletions README.md
Expand Up @@ -31,10 +31,6 @@ Next, if you have your Go environment set up in the [usual way](https://golang.o

```bash
go get github.com/soroushjp/go-bitcoin-multisig

cd $GOPATH/src/github.com/soroushjp/go-bitcoin-multisig/

go install
```

And that's it! Now you can run the binary:
Expand Down Expand Up @@ -126,11 +122,9 @@ go-bitcoin-multisig spend --input-tx 02b082113e35d5386285094c2829e7e2963fa0b5369
go-bitcoin-multisig includes a full suite of tests to test low and high level functionality, including expected multisig funding and spending transactions. To run tests:

```bash
go test ./...
go test ./... -v
```

Beware that using the -v flag when testing the multisig package will output messages usually encountered by the user using the CLI interface, since this is high level testing of functionality. No -v flag is usually necessary since failing tests will automatically output relevant errors.

##License

go-bitcoin-multisig project is released under the terms of the MIT license. Thank you to [prettymuchbryce for his hellobitcoin project](https://github.com/prettymuchbryce/hellobitcoin) which provided both early code and inspiration for this project.
Expand Down
12 changes: 7 additions & 5 deletions main.go
Expand Up @@ -41,16 +41,18 @@ func main() {

//keys -- Generate public/private key pairs
case cmdKeys.FullCommand():
multisig.GenerateKeys(*cmdKeysCount, *cmdKeysConcise)
multisig.OutputKeys(*cmdKeysCount, *cmdKeysConcise)

//address -- Create a P2SH address
//address -- Create a multisig P2SH address
case cmdAddress.FullCommand():
multisig.GenerateAddress(*cmdAddressM, *cmdAddressN, *cmdAddressPublicKeys)
multisig.OutputAddress(*cmdAddressM, *cmdAddressN, *cmdAddressPublicKeys)

//address -- Fund a P2SH address
case cmdFund.FullCommand():
multisig.GenerateFund(*cmdFundPrivateKey, *cmdFundInputTx, *cmdFundAmount, *cmdFundDestination)
multisig.OutputFund(*cmdFundPrivateKey, *cmdFundInputTx, *cmdFundAmount, *cmdFundDestination)

//address -- Spend a multisig P2SH address
case cmdSpend.FullCommand():
multisig.GenerateSpend(*cmdSpendPrivateKeys, *cmdSpendDestination, *cmdSpendRedeemScript, *cmdSpendInputTx, *cmdSpendAmount)
multisig.OutputSpend(*cmdSpendPrivateKeys, *cmdSpendDestination, *cmdSpendRedeemScript, *cmdSpendInputTx, *cmdSpendAmount)
}
}
56 changes: 31 additions & 25 deletions multisig/address.go
Expand Up @@ -14,13 +14,36 @@ import (
"strings"
)

var P2SHAddress string
var RedeemScriptHex string
//OutputAddress formats and prints relevant outputs to the user.
func OutputAddress(flagM int, flagN int, flagPublicKeys string) {
P2SHAddress, redeemScriptHex := generateAddress(flagM, flagN, flagPublicKeys)

// GenerateAddress is the main thread for creating P2SH multisig addresses with the 'go-bitcoin-multisig address' subcommand.
if flagM*73+flagN*66 > 496 {
fmt.Println("------------------------------------------------------------------------------------------------------------------------")
fmt.Printf("WARNING: %d-of-%d multisig transaction is valid but *non-standard* for Bitcoin v0.9.x and earlier.\n", flagM, flagN)
fmt.Println("\tIt may take a very long time (possibly never) for transaction spending multisig funds to be included in a block.")
fmt.Println("\tTo remain valid, choose smaller m and n values such that m*73+n*66 <= 496, as per standardness rules.")
fmt.Println("\tSee http://bitcoin.stackexchange.com/questions/23893/what-are-the-limits-of-m-and-n-in-m-of-n-multisig-addresses for more details.")
fmt.Println("------------------------------------------------------------------------------------------------------------------------")
}

//Output P2SH and redeemScript
fmt.Println("-------------------------------------------------------------")
fmt.Println("Your *P2SH ADDRESS* is:")
fmt.Println(P2SHAddress)
fmt.Println("Give this to sender funding multisig address with Bitcoin.")
fmt.Println("-------------------------------------------------------------")
fmt.Println("-------------------------------------------------------------")
fmt.Println("Your *REDEEM SCRIPT* is:")
fmt.Println(redeemScriptHex)
fmt.Println("Keep private and provide this to redeem multisig balance later.")
fmt.Println("-------------------------------------------------------------")
}

// generateAddress is the high-level logic for creating P2SH multisig addresses with the 'go-bitcoin-multisig address' subcommand.
// Takes flagM (number of keys required to spend), flagN (total number of keys)
// and flagPublicKeys (comma separated list of N public keys) as arguments.
func GenerateAddress(flagM int, flagN int, flagPublicKeys string) {
func generateAddress(flagM int, flagN int, flagPublicKeys string) (string, string) {
//Convert public keys argument into slice of public key bytes with necessary tidying
flagPublicKeys = strings.Replace(flagPublicKeys, "'", "\"", -1) //Replace single quotes with double since csv package only recognizes double quotes
publicKeyStrings, err := csv.NewReader(strings.NewReader(flagPublicKeys)).Read()
Expand All @@ -36,14 +59,6 @@ func GenerateAddress(flagM int, flagN int, flagPublicKeys string) {
}
}
//Create redeemScript from public keys
if flagM*73+flagN*66 > 496 {
fmt.Println("------------------------------------------------------------------------------------------------------------------------")
fmt.Printf("WARNING: %d-of-%d multisig transaction is valid but *non-standard* for Bitcoin v0.9.x and earlier.\n", flagM, flagN)
fmt.Println("\tIt may take a very long time (possibly never) for transaction spending multisig funds to be included in a block.")
fmt.Println("\tTo remain valid, choose smaller m and n values such that m*73+n*66 <= 496, as per standardness rules.")
fmt.Println("\tSee http://bitcoin.stackexchange.com/questions/23893/what-are-the-limits-of-m-and-n-in-m-of-n-multisig-addresses for more details.")
fmt.Println("------------------------------------------------------------------------------------------------------------------------")
}
redeemScript, err := btcutils.NewMOfNRedeemScript(flagM, flagN, publicKeys)
if err != nil {
log.Fatal(err)
Expand All @@ -53,18 +68,9 @@ func GenerateAddress(flagM int, flagN int, flagPublicKeys string) {
log.Fatal(err)
}
//Get P2SH address by base58 encoding with P2SH prefix 0x05
P2SHAddress = base58check.Encode("05", redeemScriptHash)
P2SHAddress := base58check.Encode("05", redeemScriptHash)
//Get redeemScript in Hex
RedeemScriptHex = hex.EncodeToString(redeemScript)
//Output P2SH and redeemScript
fmt.Println("-------------------------------------------------------------")
fmt.Println("Your *P2SH ADDRESS* is:")
fmt.Println(P2SHAddress)
fmt.Println("Give this to sender funding multisig address with Bitcoin.")
fmt.Println("-------------------------------------------------------------")
fmt.Println("-------------------------------------------------------------")
fmt.Println("Your *REDEEM SCRIPT* is:")
fmt.Println(RedeemScriptHex)
fmt.Println("Keep private and provide this to redeem multisig balance later.")
fmt.Println("-------------------------------------------------------------")
redeemScriptHex := hex.EncodeToString(redeemScript)

return P2SHAddress, redeemScriptHex
}
18 changes: 9 additions & 9 deletions multisig/address_test.go
Expand Up @@ -14,12 +14,12 @@ func TestGenerateAddress(t *testing.T) {
testAddress := "347N1Thc213QqfYCz3PZkjoJpNv5b14kBd"
testRedeemScriptHex := "524104a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd41046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187410411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e8353ae"

GenerateAddress(testM, testN, testPublicKeys)
P2SHAddress, redeemScriptHex := generateAddress(testM, testN, testPublicKeys)
if testAddress != P2SHAddress {
testutils.CompareError(t, "Generated P2SH address different from expected address.", testAddress, P2SHAddress)
}
if testRedeemScriptHex != RedeemScriptHex {
testutils.CompareError(t, "Generated P2SH address different from expected address.", testRedeemScriptHex, RedeemScriptHex)
if testRedeemScriptHex != redeemScriptHex {
testutils.CompareError(t, "Generated P2SH address different from expected address.", testRedeemScriptHex, redeemScriptHex)
}
}
{
Expand All @@ -30,12 +30,12 @@ func TestGenerateAddress(t *testing.T) {
testAddress := "3ErDPiDD7AsJDqKkayMA39iLJevTjDCjUa"
testRedeemScriptHex := "57410446f1c8de232a065da428bf76e44b41f59a46620dec0aedfc9b5ab651e91f2051d610fddc78b8eba38a634bfe9a74bb015a88c52b9b844c74997035e08a695ce94104704e19d4fc234a42d707d41053c87011f990b564949532d72cab009e136bd60d7d0602f925fce79da77c0dfef4a49c6f44bd0540faef548e37557d74b36da1244104b75a8cb10fd3f1785addbafdb41b409ecd6ffd50d5ad71d8a3cdc5503bcb35d3d13cdf23f6d0eb6ab88446276e2ba5b92d8786da7e5c0fb63aafb62f87443d284104033a82ccb1291bbc27cf541c6c487c213f25db85c620ecb9cbb76ca461ef13db5a80b90c3ae7d2a5e47623cdf520a2586cac7e41f779103a71a1fe177189781e41045e3b4030be5fd9c4c40e7076bd49f022118d90ae9182de61f3a1adb2ff511c97e8a6a82a9292b01878a18c08b7cd658ebdf80e6ed3f26783b25ba1a52fa9e52d4104c93ceb8f4482e131addc58d3efa0b4967bb7c574de15786d55379cc4a43a61571518abe0f05ebf188bcce9580aa70b3f5b1024ca579819c8810ff79967de3f234104a66f63d2941f0befcfba4b73495a7b99fc7ed28cb41e7934e1de82d852628766dc96ee1e196387a68e7fd8898862c2260f1f2557ac2147af07900695f15abd3f57ae"

GenerateAddress(testM, testN, testPublicKeys)
P2SHAddress, redeemScriptHex := generateAddress(testM, testN, testPublicKeys)
if testAddress != P2SHAddress {
testutils.CompareError(t, "Generated P2SH address different from expected address.", testAddress, P2SHAddress)
}
if testRedeemScriptHex != RedeemScriptHex {
testutils.CompareError(t, "Generated P2SH address different from expected address.", testRedeemScriptHex, RedeemScriptHex)
if testRedeemScriptHex != redeemScriptHex {
testutils.CompareError(t, "Generated P2SH address different from expected address.", testRedeemScriptHex, redeemScriptHex)
}
}
{
Expand All @@ -46,12 +46,12 @@ func TestGenerateAddress(t *testing.T) {
testAddress := "34wgSuG9qtaNEV4MGye9UJcffcFTxnmXSC"
testRedeemScriptHex := "554104c22e4293d1d462eef905e592ad4aff332aa52c3415b824cd85cf594258d92c836fe797187bc2459261e0597c4ef351c5d0c26f7a60165221e221a38e448ad08c4104bb28684dfe23852a7c276827dd448c955007e7ccbfacbf536e13f1097b30430ebec5af0bc001e50d3f0e796d52ba43e3c07337bfed2a842659d51632f2b21d2841048f8551173f8e7414ff0e144899b3f70accd957e6913f5cf877bd576f6c16f0aa67fb9b96e0df10562b4f7ba4060acd22f142329ff83f1d96e27f4e4394adeda24104aa81def7dda6a4f40be2f3287ee3423f255b07965104a7888df075217c9ee5b3e9e2e70115d43bfecbff8062f8289f5cab3d0ebd96c9f55c85f6147ff3a5e9494104493aa5f89ec34184a235b2c9f608eade1634636f94f64b59419875e15cb86a6d8c708a9d5eda3304cb983b2325a57af881ed75f28179f5f263d7758039b68d894104dc284f749208d7fec57937bc5e72187b064df7d29b7aa82cae273e9a1c91beae9c510e0fd632a3db272c67db04061ea761d1ed91fdb8ab07e354047c64ce405d41042fc7796f54dd482db20f1bcce584f930ae74d5f27fc8336e2701bd0243d681281810c57e079947ebdfdfc8860ed34b0ba32db82a85249adc7c64ab547d48af6457ae"

GenerateAddress(testM, testN, testPublicKeys)
P2SHAddress, redeemScriptHex := generateAddress(testM, testN, testPublicKeys)
if testAddress != P2SHAddress {
testutils.CompareError(t, "Generated P2SH address different from expected address.", testAddress, P2SHAddress)
}
if testRedeemScriptHex != RedeemScriptHex {
testutils.CompareError(t, "Generated P2SH address different from expected address.", testRedeemScriptHex, RedeemScriptHex)
if testRedeemScriptHex != redeemScriptHex {
testutils.CompareError(t, "Generated P2SH address different from expected address.", testRedeemScriptHex, redeemScriptHex)
}
}
}
27 changes: 16 additions & 11 deletions multisig/fund.go
Expand Up @@ -11,15 +11,23 @@ import (
"log"
)

//Anything outputed to the user is declared globally so that we can easily run tests on these.
var finalTransactionHex string
var SetFixedNonce bool
//OutputFund formats and prints relevant outputs to the user.
func OutputFund(flagPrivateKey string, flagInputTx string, flagAmount int, flagP2SHDestination string) {

// GenerateFund is the main thread for funding any P2SH address with the 'go-bitcoin-multisig fund' subcommand.
finalTransactionHex := generateFund(flagPrivateKey, flagInputTx, flagAmount, flagP2SHDestination)

//Output our final transaction
fmt.Println("-------------------------------------------------------------")
fmt.Println("Your raw funding transaction is")
fmt.Println(finalTransactionHex)
fmt.Println("-------------------------------------------------------------")
}

// generateFund is the high-level logic for funding any P2SH address with the 'go-bitcoin-multisig fund' subcommand.
// Takes flagPrivateKey (private key of input Bitcoins to fund with), flagInputTx (input transaction hash of
// Bitcoins to fund with), flagAmount (amount in Satoshis to send, with balance left over from input being used
// as transaction fee) and flagP2SHDestination (destination P2SH multisig address which is being funded) as arguments.
func GenerateFund(flagPrivateKey string, flagInputTx string, flagAmount int, flagP2SHDestination string) {
func generateFund(flagPrivateKey string, flagInputTx string, flagAmount int, flagP2SHDestination string) string {
//Get private key as decoded raw bytes
privateKey := base58check.Decode(flagPrivateKey)
//In order to construct the raw transaction we need the input transaction hash,
Expand Down Expand Up @@ -63,12 +71,9 @@ func GenerateFund(flagPrivateKey string, flagInputTx string, flagAmount int, fla
if err != nil {
log.Fatal(err)
}
finalTransactionHex = hex.EncodeToString(finalTransaction)
//Output our final transaction
fmt.Println("-------------------------------------------------------------")
fmt.Println("Your raw funding transaction is")
fmt.Println(finalTransactionHex)
fmt.Println("-------------------------------------------------------------")
finalTransactionHex := hex.EncodeToString(finalTransaction)

return finalTransactionHex
}

// signP2PKHTransaction signs a raw P2PKH transaction, given a private key and the scriptPubKey, inputTx and amount
Expand Down
6 changes: 3 additions & 3 deletions multisig/fund_test.go
Expand Up @@ -17,7 +17,7 @@ func TestGenerateFund(t *testing.T) {
testP2SHDestination := "347N1Thc213QqfYCz3PZkjoJpNv5b14kBd"
testFinalTransanctionHex := "0100000001acc6fb9ec2c3884d3a12a89e7078c83853d9b7912281cefb14bac00a2737d33a000000008a47304402206d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e202207d1c7fb129adec15700c378e142c506b5bbadafdedbb62f614dd0bb128faeecd01410431393af9984375830971ab5d3094c6a7d02db3568b2b06212a7090094549701bbb9e84d9477451acc42638963635899ce91bacb451a1bb6da73ddfbcf596bddfffffffff01400001000000000017a9141a8b0026343166625c7475f01e48b5ede8c0252e8700000000"

GenerateFund(testPrivateKeyWIF, testInputTx, testAmount, testP2SHDestination)
finalTransactionHex := generateFund(testPrivateKeyWIF, testInputTx, testAmount, testP2SHDestination)
if finalTransactionHex != testFinalTransanctionHex {
testutils.CompareError(t, "Generated funding transaction different from expected transaction.", testFinalTransanctionHex, finalTransactionHex)
}
Expand All @@ -29,7 +29,7 @@ func TestGenerateFund(t *testing.T) {
testP2SHDestination := "3ErDPiDD7AsJDqKkayMA39iLJevTjDCjUa"
testFinalTransanctionHex := "01000000019f47d9bab82f8e92a61d74908456e2507257105cd7f0813c6fa68f647c864826000000008a47304402206d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2022004ad7b55e6c3a595bb770f2a172c99c2b03c903401c4fd05718b2e470ac70a4b014104ff4c2ce7513a6c896ebfaaa4ae52cea35374e0eac90ccb8f4e5fa14b8322e2bae4c65116c7af2ba6a82831e48c451fc29a66d49c24757130ebf07c142bbcbe75ffffffff01b01102000000000017a9149056f3c2a8cbd11340fa2ee4736dea1d298c9d118700000000"

GenerateFund(testPrivateKeyWIF, testInputTx, testAmount, testP2SHDestination)
finalTransactionHex := generateFund(testPrivateKeyWIF, testInputTx, testAmount, testP2SHDestination)
if finalTransactionHex != testFinalTransanctionHex {
testutils.CompareError(t, "Generated funding transaction different from expected transaction.", testFinalTransanctionHex, finalTransactionHex)
}
Expand All @@ -41,7 +41,7 @@ func TestGenerateFund(t *testing.T) {
testP2SHDestination := "34wgSuG9qtaNEV4MGye9UJcffcFTxnmXSC"
testFinalTransanctionHex := "0100000001507b8cda2448a92b51333b5d7e4a5cc9c45c8b85a58f7c91d4403e66d3ce73d0000000008a47304402206d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2022017a181a29869fb641bab86b1fe60fefdf918ed44ec0ab32409effff94af606dc014104d95cf578183f346117b9743722bb6df93e1c62990824a1fc6645fd3dee45fa7ea5f164da7b518c3fd08a623664410df5a3b5f6ef1c5a285e834fd57c5a24a41effffffff0110fc02000000000017a91423ae5bc99220a608aefb8455cdf7f43bfdbae67d8700000000"

GenerateFund(testPrivateKeyWIF, testInputTx, testAmount, testP2SHDestination)
finalTransactionHex := generateFund(testPrivateKeyWIF, testInputTx, testAmount, testP2SHDestination)
if finalTransactionHex != testFinalTransanctionHex {
testutils.CompareError(t, "Generated funding transaction different from expected transaction.", testFinalTransanctionHex, finalTransactionHex)
}
Expand Down

0 comments on commit 332e109

Please sign in to comment.