From 23a4e59df1392d1d38fb056284da1e2c12f8de3b Mon Sep 17 00:00:00 2001 From: Zaki Manian Date: Thu, 21 Jan 2021 22:35:41 -0800 Subject: [PATCH 1/5] Partial resolution on issues with Ethereum signature verification --- app/prop29.go | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/app/prop29.go b/app/prop29.go index 699d5df8b95..4c3eddcf295 100644 --- a/app/prop29.go +++ b/app/prop29.go @@ -9,6 +9,7 @@ import ( "encoding/base64" "encoding/json" "log" + "strings" "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg" @@ -80,7 +81,8 @@ type FundRecoveryMessage struct { } func (f *FundRecoveryMessage) unMarshalSignedJSON() (signedJSON, error) { - return unmarshalSignedJSON([]byte(f.signedMessage)) + newLineStripped := strings.Replace(f.signedMessage, `\n`, "", -1) + return unmarshalSignedJSON([]byte(strings.Replace(newLineStripped, "\\", "", -1))) } // nolint: unparam @@ -186,8 +188,8 @@ func ethDonor1() FundRecoveryMessage { destAddress, _ := sdk.AccAddressFromBech32("cosmos17cg9xxpjnammafyqwfryr3hn32h6vjmh9x0y6j") // nolint: errcheck return FundRecoveryMessage{ - `{ \"Message\":\"I remember After I successfully sent my 16 Eth to the Cosmos fundraising address 3 years ago,I wrote down those seed words and had a picture.But when we could claim,I found my seed phase does not work,I've tried every means I could,but all failed.Honestly,I still don’t know the problem,maybe I wrote wrong letters,maybe something wrong with that webpage,or maybe I mixed something,only God knows.\", \"Txid\":\"https://etherscan.io/tx/0x42d0f860e1cd484f51647f34479843008b69d8f1158c94ad44ae30df33fdc080\", \"Contribution\":\"16 ETH\", \"Contributing Addresses\":[ { \"address\":\"0x53ad4398f76a453a2d4dac4470f0b81cd1d72715\" } ], \"Genesis Cosmos Address\":\"cosmos1m06fehere8q09sqcj08dcc0xyj3fjlzc2x24y4\", \"Recovery Cosmos Address\":\"cosmos17cg9xxpjnammafyqwfryr3hn32h6vjmh9x0y6j\" }`, - "ceed630f7e8d102b125a22d9ec06ced12a016f376b408b9832a63a9b4b5f352b4b67c94e9cfa35e4f2676f0f92643f8b56caf82f6c6eabd765b787d3f6af77fb1b", + `{ "Message":"I remember After I successfully sent my 16 Eth to the Cosmos fundraising address 3 years ago,I wrote down those seed words and had a picture.But when we could claim,I found my seed phase does not work,I've tried every means I could,but all failed.Honestly,I still don’t know the problem,maybe I wrote wrong letters,maybe something wrong with that webpage,or maybe I mixed something,only God knows.", "Txid":"https://etherscan.io/tx/0x42d0f860e1cd484f51647f34479843008b69d8f1158c94ad44ae30df33fdc080", "Contribution":"16 ETH", "Contributing Addresses":[ { "address":"0x53ad4398f76a453a2d4dac4470f0b81cd1d72715" } ], "Genesis Cosmos Address":"cosmos1m06fehere8q09sqcj08dcc0xyj3fjlzc2x24y4", "Recovery Cosmos Address":"cosmos17cg9xxpjnammafyqwfryr3hn32h6vjmh9x0y6j" }`, + "0xceed630f7e8d102b125a22d9ec06ced12a016f376b408b9832a63a9b4b5f352b4b67c94e9cfa35e4f2676f0f92643f8b56caf82f6c6eabd765b787d3f6af77fb1b", sourceAddress, destAddress, sdk.NewInt64Coin("uatom", 6512400000)} @@ -199,7 +201,7 @@ func ethDonor2() FundRecoveryMessage { return FundRecoveryMessage{ `{\n \"Message\":\"In April 2017, I participated in 24 ETH at Cosmos ico. In about two years when Cosmos was listed on exchange, the cell phone that had taken the seed phrase picture was destroyed. I tried to go to a data recovery company, but I couldn't recover the picture, so there was no way to get ATOM. However, I still have the private key and keystore file of the Ethereum wallet that participated in Cosmos ico.\",\n \"Txid\":\"https://etherscan.io/tx/0xe2bb8c832c237b9ed898d4616649347e84931d56b8942cb409cafd6b01e1913d\",\n \"Contribution\":\"24 ETH\",\n \"Contributing Addresses\":[\n {\n \"address\":\"0xff3fa81a59f31bd563d2554401438a1678d43593\"\n }\n ],\n \"Genesis Cosmos Address\":\"cosmos1gzuqry88awndjjsa5exzx4gwnmktcpdrxgdcf6\",\n \"Recovery Cosmos Address\":\"cosmos1teux7wdnnq03r7r277yu762mq3cket5mg4xd3e\"\n}\n`, - "38de4018152de5f42d24b1150c04f5010dc3edce9a3436a57d318beae5e6955228a5e2c1255591c0324e4e9f1bbd13806e51bbdb11259c9e2aeddbdbc91bc11a1b", + "0x38de4018152de5f42d24b1150c04f5010dc3edce9a3436a57d318beae5e6955228a5e2c1255591c0324e4e9f1bbd13806e51bbdb11259c9e2aeddbdbc91bc11a1b", sourceAddress, destAddress, sdk.NewInt64Coin("uatom", 9769500000)} @@ -211,7 +213,7 @@ func ethDonor3() FundRecoveryMessage { return FundRecoveryMessage{ `{ \"Message\":\"One of the early Ethereum alternatives is Cosmos. This is one of the reasons ICONOMI that is a collective of 1500 small donors , donated to Cosmos fundraiser about 2,222 ETH similarly to numerous ICO where we participated. Due to the possible bug in the code, we did not get the seed phrases. During the subscription process using the Brave browser, following all the steps the seed phrases simply did not show-up. Iconomi will be grateful to the community for the recovery of the atoms.\", \"Txid\":\"https://etherscan.io/tx/0x0c85c7cc2b66840357c3f293ae2010f0c79f2cc9f4b1220028afe780fdfdb426\", \"Contribution\":\"2222 ETH\", \"Contributing Addresses\":[ { \"address\":\"0xb4dc54df11d2dcecd046e5c7318fb241a73ee370\" } ], \"Genesis Cosmos Address\":\"cosmos1r3xvguuhwvlk34esxclvrh3g7ycmcqqc2kcn9v\", \"Recovery Cosmos Address\":\"cosmos18qjynuyrfja9qugzs4zjcs6dh0qyprqa2vwktp\" }`, - "4c29f9d74a070a8c475553597a1bd461137af0ba9120c183a1cfe3dc8c729f367dcf76ed9a384eea18c920b7ba7613ddb5632da9642fdb42cc183ff5ea74614e1b", + "0x4c29f9d74a070a8c475553597a1bd461137af0ba9120c183a1cfe3dc8c729f367dcf76ed9a384eea18c920b7ba7613ddb5632da9642fdb42cc183ff5ea74614e1b", sourceAddress, destAddress, sdk.NewInt64Coin("uatom", 904509000000)} @@ -228,19 +230,19 @@ func validateFundRecovery() recoveryMessages { //Bitcoin Verification _, err := bDonor1.verifyBitcoinSignature(0) if err != nil { - log.Fatal(err) + log.Fatalf("BTC1 %e", err) } _, err = bDonor2.verifyBitcoinSignature(0) if err != nil { - log.Fatal(err) + log.Fatalf("BTC2 %e", err) } _, err = bDonor3.verifyBitcoinSignature(1) if err != nil { - log.Fatal(err) + log.Fatalf("BTC3 %e", err) } _, err = bDonor4.verifyBitcoinSignature(0) if err != nil { - log.Fatal(err) + log.Fatalf("BTC4 %e", err) } //Ethereum Donors @@ -251,15 +253,17 @@ func validateFundRecovery() recoveryMessages { //Ethereum Verification _, err = eDonor1.verifyEthereumSignature(0) if err != nil { - log.Fatal(err) + log.Fatalf("Eth1 %e", err) } + _, err = eDonor2.verifyEthereumSignature(0) if err != nil { - log.Fatal(err) + log.Fatalf("Eth2 %e", err) } + _, err = eDonor3.verifyEthereumSignature(0) if err != nil { - log.Fatal(err) + log.Fatalf("Eth3 %e", err) } return []FundRecoveryMessage{bDonor1, bDonor2, bDonor3, bDonor4, eDonor1, eDonor2, eDonor3} @@ -271,31 +275,28 @@ func validateFundRecovery() recoveryMessages { func verifyEthereumSignature(sig, msg, addr string) { sigBytes, err := hexutil.Decode(sig) if err != nil { - log.Fatal(err) + log.Fatalf("Eth Sig Decode %e", err) } + sigBytes[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 addrBytes, err := hexutil.Decode(addr) if err != nil { - log.Fatal(err) + log.Fatalf("Eth Address Decode %e", err) } + // fmt.Println(msg) hash := accounts.TextHash([]byte(msg)) - sigPublicKey, err := crypto.Ecrecover(hash, sigBytes) - - if err != nil { - log.Fatal(err) - } + sigPublicKey, err := crypto.SigToPub(hash, sigBytes) - pubkey, err := crypto.DecompressPubkey(sigPublicKey) if err != nil { - log.Fatal(err) + log.Fatalf("Eth Pub Key Recover %e", err) } - addrRecovered := crypto.PubkeyToAddress(*pubkey) + addrRecovered := crypto.PubkeyToAddress(*sigPublicKey) if !bytes.Equal(addrRecovered.Bytes(), addrBytes) { - log.Fatal("invalid signature") + log.Fatalf("invalid signature given address %s, recovered address %s", addr, addrRecovered.String()) } } From 99b606c8fa5bd853929347cd93d65fc2218233e4 Mon Sep 17 00:00:00 2001 From: Zaki Manian Date: Fri, 22 Jan 2021 00:06:18 -0800 Subject: [PATCH 2/5] More signature verification fixes --- app/prop29.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/prop29.go b/app/prop29.go index 4c3eddcf295..3495e02a1f3 100644 --- a/app/prop29.go +++ b/app/prop29.go @@ -277,7 +277,12 @@ func verifyEthereumSignature(sig, msg, addr string) { if err != nil { log.Fatalf("Eth Sig Decode %e", err) } - sigBytes[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 + + if sigBytes[64] != 27 && sigBytes[64] != 28 { + log.Fatalf("Invalid Eth Signatures for %s. The last byte is neither 27 not 28. %d", addr, sigBytes[64]) + } + + sigBytes[64] -= 27 addrBytes, err := hexutil.Decode(addr) if err != nil { @@ -287,6 +292,8 @@ func verifyEthereumSignature(sig, msg, addr string) { // fmt.Println(msg) hash := accounts.TextHash([]byte(msg)) + // fmt.Println(hexutil.Encode(hash)) + sigPublicKey, err := crypto.SigToPub(hash, sigBytes) if err != nil { From 5957a5a6f3e4ef5e90696d76d246824a48369d4e Mon Sep 17 00:00:00 2001 From: Zaki Manian Date: Fri, 22 Jan 2021 07:36:55 -0800 Subject: [PATCH 3/5] Switch to strings.ReplaceAll --- app/prop29.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/prop29.go b/app/prop29.go index 3495e02a1f3..03feb778e2c 100644 --- a/app/prop29.go +++ b/app/prop29.go @@ -81,8 +81,8 @@ type FundRecoveryMessage struct { } func (f *FundRecoveryMessage) unMarshalSignedJSON() (signedJSON, error) { - newLineStripped := strings.Replace(f.signedMessage, `\n`, "", -1) - return unmarshalSignedJSON([]byte(strings.Replace(newLineStripped, "\\", "", -1))) + newLineStripped := strings.ReplaceAll(f.signedMessage, `\n`, "") + return unmarshalSignedJSON([]byte(strings.ReplaceAll(newLineStripped, "\\", ""))) } // nolint: unparam From 1e1b8dd51548ed97b4dd70db90a9d443ef449d2a Mon Sep 17 00:00:00 2001 From: Zaki Manian Date: Fri, 22 Jan 2021 09:01:04 -0800 Subject: [PATCH 4/5] All signatures verify now --- app/prop29.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/app/prop29.go b/app/prop29.go index 03feb778e2c..b8c246d8374 100644 --- a/app/prop29.go +++ b/app/prop29.go @@ -200,7 +200,7 @@ func ethDonor2() FundRecoveryMessage { destAddress, _ := sdk.AccAddressFromBech32("cosmos1teux7wdnnq03r7r277yu762mq3cket5mg4xd3e") // nolint: errcheck return FundRecoveryMessage{ - `{\n \"Message\":\"In April 2017, I participated in 24 ETH at Cosmos ico. In about two years when Cosmos was listed on exchange, the cell phone that had taken the seed phrase picture was destroyed. I tried to go to a data recovery company, but I couldn't recover the picture, so there was no way to get ATOM. However, I still have the private key and keystore file of the Ethereum wallet that participated in Cosmos ico.\",\n \"Txid\":\"https://etherscan.io/tx/0xe2bb8c832c237b9ed898d4616649347e84931d56b8942cb409cafd6b01e1913d\",\n \"Contribution\":\"24 ETH\",\n \"Contributing Addresses\":[\n {\n \"address\":\"0xff3fa81a59f31bd563d2554401438a1678d43593\"\n }\n ],\n \"Genesis Cosmos Address\":\"cosmos1gzuqry88awndjjsa5exzx4gwnmktcpdrxgdcf6\",\n \"Recovery Cosmos Address\":\"cosmos1teux7wdnnq03r7r277yu762mq3cket5mg4xd3e\"\n}\n`, + "{\n \"Message\":\"In April 2017, I participated in 24 ETH at Cosmos ico. In about two years when Cosmos was listed on exchange, the cell phone that had taken the seed phrase picture was destroyed. I tried to go to a data recovery company, but I couldn't recover the picture, so there was no way to get ATOM. However, I still have the private key and keystore file of the Ethereum wallet that participated in Cosmos ico.\",\n \"Txid\":\"https://etherscan.io/tx/0xe2bb8c832c237b9ed898d4616649347e84931d56b8942cb409cafd6b01e1913d\",\n \"Contribution\":\"24 ETH\",\n \"Contributing Addresses\":[\n {\n \"address\":\"0xff3fa81a59f31bd563d2554401438a1678d43593\"\n }\n ],\n \"Genesis Cosmos Address\":\"cosmos1gzuqry88awndjjsa5exzx4gwnmktcpdrxgdcf6\",\n \"Recovery Cosmos Address\":\"cosmos1teux7wdnnq03r7r277yu762mq3cket5mg4xd3e\"\n}\n", "0x38de4018152de5f42d24b1150c04f5010dc3edce9a3436a57d318beae5e6955228a5e2c1255591c0324e4e9f1bbd13806e51bbdb11259c9e2aeddbdbc91bc11a1b", sourceAddress, destAddress, @@ -212,7 +212,7 @@ func ethDonor3() FundRecoveryMessage { destAddress, _ := sdk.AccAddressFromBech32("cosmos18qjynuyrfja9qugzs4zjcs6dh0qyprqa2vwktp") // nolint: errcheck return FundRecoveryMessage{ - `{ \"Message\":\"One of the early Ethereum alternatives is Cosmos. This is one of the reasons ICONOMI that is a collective of 1500 small donors , donated to Cosmos fundraiser about 2,222 ETH similarly to numerous ICO where we participated. Due to the possible bug in the code, we did not get the seed phrases. During the subscription process using the Brave browser, following all the steps the seed phrases simply did not show-up. Iconomi will be grateful to the community for the recovery of the atoms.\", \"Txid\":\"https://etherscan.io/tx/0x0c85c7cc2b66840357c3f293ae2010f0c79f2cc9f4b1220028afe780fdfdb426\", \"Contribution\":\"2222 ETH\", \"Contributing Addresses\":[ { \"address\":\"0xb4dc54df11d2dcecd046e5c7318fb241a73ee370\" } ], \"Genesis Cosmos Address\":\"cosmos1r3xvguuhwvlk34esxclvrh3g7ycmcqqc2kcn9v\", \"Recovery Cosmos Address\":\"cosmos18qjynuyrfja9qugzs4zjcs6dh0qyprqa2vwktp\" }`, + "{ \"Message\":\"One of the early Ethereum alternatives is Cosmos. This is one of the reasons ICONOMI that is a collective of 1500 small donors , donated to Cosmos fundraiser about 2,222 ETH similarly to numerous ICO where we participated. Due to the possible bug in the code, we did not get the seed phrases. During the subscription process using the Brave browser, following all the steps the seed phrases simply did not show-up. Iconomi will be grateful to the community for the recovery of the atoms.\", \"Txid\":\"https://etherscan.io/tx/0x0c85c7cc2b66840357c3f293ae2010f0c79f2cc9f4b1220028afe780fdfdb426\", \"Contribution\":\"2222 ETH\", \"Contributing Addresses\":[ { \"address\":\"0xb4dc54df11d2dcecd046e5c7318fb241a73ee370\" } ], \"Genesis Cosmos Address\":\"cosmos1r3xvguuhwvlk34esxclvrh3g7ycmcqqc2kcn9v\", \"Recovery Cosmos Address\":\"cosmos18qjynuyrfja9qugzs4zjcs6dh0qyprqa2vwktp\" }", "0x4c29f9d74a070a8c475553597a1bd461137af0ba9120c183a1cfe3dc8c729f367dcf76ed9a384eea18c920b7ba7613ddb5632da9642fdb42cc183ff5ea74614e1b", sourceAddress, destAddress, @@ -275,7 +275,7 @@ func validateFundRecovery() recoveryMessages { func verifyEthereumSignature(sig, msg, addr string) { sigBytes, err := hexutil.Decode(sig) if err != nil { - log.Fatalf("Eth Sig Decode %e", err) + log.Fatalf("Eth Sig Decode error %e", err) } if sigBytes[64] != 27 && sigBytes[64] != 28 { @@ -286,18 +286,15 @@ func verifyEthereumSignature(sig, msg, addr string) { addrBytes, err := hexutil.Decode(addr) if err != nil { - log.Fatalf("Eth Address Decode %e", err) + log.Fatalf("Eth Address Decode error %e", err) } - // fmt.Println(msg) hash := accounts.TextHash([]byte(msg)) - // fmt.Println(hexutil.Encode(hash)) - sigPublicKey, err := crypto.SigToPub(hash, sigBytes) if err != nil { - log.Fatalf("Eth Pub Key Recover %e", err) + log.Fatalf("Eth Pub Key Recover error %e", err) } addrRecovered := crypto.PubkeyToAddress(*sigPublicKey) From cb0e824cc731e886a70435cf61d9ac3e6ec9498d Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Fri, 22 Jan 2021 15:23:10 -0300 Subject: [PATCH 5/5] cleanup --- app/prop29.go | 154 +++++++++++++++++++++++++++------------------ app/prop29_test.go | 32 +++++++--- 2 files changed, 116 insertions(+), 70 deletions(-) diff --git a/app/prop29.go b/app/prop29.go index b8c246d8374..dd6c9cfa373 100644 --- a/app/prop29.go +++ b/app/prop29.go @@ -8,6 +8,7 @@ import ( "bytes" "encoding/base64" "encoding/json" + "fmt" "log" "strings" @@ -21,7 +22,9 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" distr "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" ) @@ -60,6 +63,7 @@ func (r *recoveryMessages) GetRemainingBalances() (balances []banktypes.Balance) return } +// GetRemainingAccounts returns the recovery destination addresses with positive balance func (r *recoveryMessages) GetRemainingAccounts() (addresses []sdk.Address) { zeroBalance := sdk.NewInt64Coin("uatom", 0) for _, m := range *r { @@ -68,7 +72,7 @@ func (r *recoveryMessages) GetRemainingAccounts() (addresses []sdk.Address) { } } - return + return addresses } // FundRecoveryMessage were signed messages provided by fundraiser particpants who could not access their ATOM to facilate this process for recovering access to their funds. @@ -91,7 +95,12 @@ func (f *FundRecoveryMessage) verifyBitcoinSignature(addrIdx int) (signedJSON, e if err != nil { return signedJSON{}, err } - verifyBitcoinSignature(f.signature, f.signedMessage, msgJSON.ContributingAddresses[addrIdx].Address) + + err = verifyBitcoinSignature(f.signature, f.signedMessage, msgJSON.ContributingAddresses[addrIdx].Address) + if err != nil { + return signedJSON{}, err + } + return msgJSON, nil } @@ -102,7 +111,11 @@ func (f *FundRecoveryMessage) verifyEthereumSignature(addrIdx int) (signedJSON, return signedJSON{}, err } - verifyEthereumSignature(f.signature, f.signedMessage, msgJSON.ContributingAddresses[addrIdx].Address) + err = verifyEthereumSignature(f.signature, f.signedMessage, msgJSON.ContributingAddresses[addrIdx].Address) + if err != nil { + return signedJSON{}, err + } + return msgJSON, nil } @@ -267,41 +280,54 @@ func validateFundRecovery() recoveryMessages { } return []FundRecoveryMessage{bDonor1, bDonor2, bDonor3, bDonor4, eDonor1, eDonor2, eDonor3} - } -// Eth_sign verifier for MEW signatures. - -func verifyEthereumSignature(sig, msg, addr string) { - sigBytes, err := hexutil.Decode(sig) +// EcRecover returns the address for the account that was used to create the signature. +// Note, this function is compatible with eth_sign and personal_sign. As such it recovers +// the address of: +// hash = keccak256("\x19Ethereum Signed Message:\n"${message length}${message}) +// addr = ecrecover(hash, signature) +// +// Note, the signature must conform to the secp256k1 curve R, S and V values, where +// the V value must be 27 or 28 for legacy reasons. +// +// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover +func ecRecover(sig, msg hexutil.Bytes) (common.Address, error) { + if len(sig) != crypto.SignatureLength { + return common.Address{}, fmt.Errorf("signature must be %d bytes long", crypto.SignatureLength) + } + if sig[crypto.RecoveryIDOffset] != 27 && sig[crypto.RecoveryIDOffset] != 28 { + return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") + } + sig[crypto.RecoveryIDOffset] -= 27 // Transform yellow paper V from 27/28 to 0/1 + + rpk, err := crypto.SigToPub(accounts.TextHash(msg), sig) if err != nil { - log.Fatalf("Eth Sig Decode error %e", err) - } - - if sigBytes[64] != 27 && sigBytes[64] != 28 { - log.Fatalf("Invalid Eth Signatures for %s. The last byte is neither 27 not 28. %d", addr, sigBytes[64]) + return common.Address{}, err } + return crypto.PubkeyToAddress(*rpk), nil +} - sigBytes[64] -= 27 +// Eth_sign verifier for MEW signatures. +func verifyEthereumSignature(sig, msg, addr string) error { + var sigBz hexutil.Bytes - addrBytes, err := hexutil.Decode(addr) - if err != nil { - log.Fatalf("Eth Address Decode error %e", err) + if err := sigBz.UnmarshalText([]byte(sig)); err != nil { + return fmt.Errorf("eth sig unmarshal error: %w", err) } - hash := accounts.TextHash([]byte(msg)) - - sigPublicKey, err := crypto.SigToPub(hash, sigBytes) - + addrRecovered, err := ecRecover(sigBz, []byte(msg)) if err != nil { - log.Fatalf("Eth Pub Key Recover error %e", err) + return fmt.Errorf("ecRecover error: %w", err) } - addrRecovered := crypto.PubkeyToAddress(*sigPublicKey) + address := common.HexToAddress(addr) - if !bytes.Equal(addrRecovered.Bytes(), addrBytes) { - log.Fatalf("invalid signature given address %s, recovered address %s", addr, addrRecovered.String()) + if !bytes.Equal(addrRecovered.Bytes(), address.Bytes()) { + log.Fatalf("invalid signature, given address %s, recovered address %s", address.String(), addrRecovered.String()) } + + return nil } const messageSignatureHeader = "Bitcoin Signed Message:\n" @@ -309,29 +335,27 @@ const messageSignatureHeader = "Bitcoin Signed Message:\n" // Modified Bitcoin signature key recovery and address verification script that verifies // signed messages against simple pay to pub key hash addresses. // Should panic on failure. -func verifyBitcoinSignature(sig, msg, addr string) { - +func verifyBitcoinSignature(sig, msg, addr string) error { var buf bytes.Buffer err := wire.WriteVarString(&buf, 0, messageSignatureHeader) if err != nil { - log.Fatal(err) + return fmt.Errorf("msg signature header serialization failed: %w", err) } - err = wire.WriteVarString(&buf, 0, msg) - if err != nil { - log.Fatal(err) + if err := wire.WriteVarString(&buf, 0, msg); err != nil { + return fmt.Errorf("msg serialization failed: %w", err) } expectedMessageHash := chainhash.DoubleHashB(buf.Bytes()) sigBytes, err := base64.StdEncoding.DecodeString(sig) if err != nil { - log.Fatal(err) + return fmt.Errorf("base64 signature decoding failed: %w", err) } pk, wasCompressed, err := btcec.RecoverCompact(btcec.S256(), sigBytes, expectedMessageHash) if err != nil { - log.Fatal(err) + return fmt.Errorf("signature verification failed: %w", err) } var serializedPK []byte @@ -343,82 +367,88 @@ func verifyBitcoinSignature(sig, msg, addr string) { address, err := btcutil.NewAddressPubKey(serializedPK, &chaincfg.MainNetParams) if err != nil { - log.Fatal("Address recovery failed") - + return fmt.Errorf("address recovery from pubkey failed: %w", err) } if address.EncodeAddress() != addr { - log.Fatal("invalid signature") + return fmt.Errorf("address mismatch, expected %s, got %s", addr, address.EncodeAddress()) } + + return nil } func Prop29Migration(authGenesis *authtypes.GenesisState, bankGenesis *banktypes.GenesisState, distrGenesis *distr.GenesisState) (authtypes.GenesisState, banktypes.GenesisState, distr.GenesisState) { - fundRecovery := validateFundRecovery() recoveryAccounting := sdk.NewInt64Coin("uatom", 0) - emptyCoins := []sdk.Coin{} - distModuleAccount := authtypes.NewModuleAddress(distr.ModuleName) - //Set All Source Addresses to Zero and accumulate the total funds being moved + // zero out all source addresses balances and accumulate the total funds being moved for i, balance := range bankGenesis.Balances { _, isSourceAddress := fundRecovery.IsSourceAddress(balance.Address) - if isSourceAddress { - if len(balance.Coins) > 1 { - log.Fatal("Expected all balances to contain only 1 denom during the migration") - } - // Accumulate all the coins removed from the balances - recoveryAccounting = recoveryAccounting.Add(balance.Coins[0]) - // Zero out the Balance - bankGenesis.Balances[i].Coins = emptyCoins + if !isSourceAddress { + continue } + if len(balance.Coins) > 1 { + log.Fatal("expected all balances to contain only 1 denom during the migration") + } + // accumulate all the coins removed from the balances into the pool + recoveryAccounting = recoveryAccounting.Add(balance.Coins[0]) + // Empty the source address balance + bankGenesis.Balances[i].Coins = emptyCoins } + // migrate the balances to the the destination addresses for i, balance := range bankGenesis.Balances { index, isDestAddress := fundRecovery.IsDestAddress(balance.Address) - if isDestAddress { - recoveryAccounting = recoveryAccounting.Sub(fundRecovery[index].destBalance) - bankGenesis.Balances[i].Coins = bankGenesis.Balances[i].Coins.Add(fundRecovery[index].destBalance) - fundRecovery[index].destBalance = sdk.NewInt64Coin("uatom", 0) + if !isDestAddress { + continue } + + // transfer coins from the atom recovery pool to the dest address + recoveryAccounting = recoveryAccounting.Sub(fundRecovery[index].destBalance) + bankGenesis.Balances[i].Coins = bankGenesis.Balances[i].Coins.Add(fundRecovery[index].destBalance) + fundRecovery[index].destBalance = sdk.NewInt64Coin("uatom", 0) } + + // add the balances to the bank genesis bankGenesis.Balances = append(bankGenesis.Balances, fundRecovery.GetRemainingBalances()...) accs, err := authtypes.UnpackAccounts(authGenesis.Accounts) if err != nil { - log.Fatal("Could not unpack genesis account") + log.Fatalf("could not unpack genesis accounts: %s", err.Error()) } + // add the accounts with positive balance to the genesis accounts for _, addr := range fundRecovery.GetRemainingAccounts() { recoveryAccount := authtypes.NewBaseAccount(sdk.AccAddress(addr.Bytes()), nil, 0, 0) - accs = append(accs, recoveryAccount) - } genAccs, err := authtypes.PackAccounts(accs) if err != nil { - log.Fatal(err) + log.Fatalf("could not pack genesis accounts: %s", err.Error()) } authGenesis.Accounts = genAccs + // subtract the coins from the addresses with positive balances from the recovery accounting for _, balance := range fundRecovery.GetRemainingBalances() { recoveryAccounting = recoveryAccounting.Sub(balance.Coins[0]) } // Add the remaining ATOMs to the fee pool by adding them to distribution modules for i, balance := range bankGenesis.Balances { - if distModuleAccount.String() == balance.Address { - bankGenesis.Balances[i].Coins = bankGenesis.Balances[i].Coins.Add(recoveryAccounting) - - distrGenesis.FeePool.CommunityPool = distrGenesis.FeePool.CommunityPool.Add(sdk.NewDecCoinFromCoin(recoveryAccounting)) - - recoveryAccounting = sdk.NewInt64Coin("uatom", 0) + if distModuleAccount.String() != balance.Address { + continue } + + // add coins to the community pool bank and distr balances + bankGenesis.Balances[i].Coins = bankGenesis.Balances[i].Coins.Add(recoveryAccounting) + distrGenesis.FeePool.CommunityPool = distrGenesis.FeePool.CommunityPool.Add(sdk.NewDecCoinFromCoin(recoveryAccounting)) + break } return *authGenesis, *bankGenesis, *distrGenesis diff --git a/app/prop29_test.go b/app/prop29_test.go index 60ab57a9608..df2f6e00a2f 100644 --- a/app/prop29_test.go +++ b/app/prop29_test.go @@ -1,6 +1,10 @@ package gaia -import "testing" +import ( + "testing" + + "github.com/stretchr/testify/require" +) func TestVerifySignatures(t *testing.T) { //Bitcoin Donors @@ -10,10 +14,17 @@ func TestVerifySignatures(t *testing.T) { bDonor4 := btcDonor4() //Bitcoin Verification - bDonor1.verifyBitcoinSignature(0) - bDonor2.verifyBitcoinSignature(0) - bDonor3.verifyBitcoinSignature(1) - bDonor4.verifyBitcoinSignature(0) + _, err := bDonor1.verifyBitcoinSignature(0) + require.NoError(t, err, "bitcoin donor 1") + + _, err = bDonor2.verifyBitcoinSignature(0) + require.NoError(t, err, "bitcoin donor 2") + + _, err = bDonor3.verifyBitcoinSignature(1) + require.NoError(t, err, "bitcoin donor 3") + + _, err = bDonor4.verifyBitcoinSignature(0) + require.NoError(t, err, "bitcoin donor 4") //Ethereum Donors eDonor1 := ethDonor1() @@ -21,7 +32,12 @@ func TestVerifySignatures(t *testing.T) { eDonor3 := ethDonor3() //Ethereum Verification - eDonor1.verifyEthereumSignature(0) - eDonor2.verifyEthereumSignature(0) - eDonor3.verifyEthereumSignature(0) + _, err = eDonor1.verifyEthereumSignature(0) + require.NoError(t, err, "ethereum donor 1") + + _, err = eDonor2.verifyEthereumSignature(0) + require.NoError(t, err, "ethereum donor 2") + + _, err = eDonor3.verifyEthereumSignature(0) + require.NoError(t, err, "ethereum donor 3") }