Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,33 @@
GITOPIA_ENV ?= testing
LEDGER_ENABLED ?= true

build_tags = netgo
ifeq ($(LEDGER_ENABLED),true)
ifeq ($(OS),Windows_NT)
GCCEXE = $(shell where gcc.exe 2> NUL)
ifeq ($(GCCEXE),)
$(error gcc.exe not installed for ledger support, please install or set LEDGER_ENABLED=false)
else
build_tags += ledger
endif
else
UNAME_S = $(shell uname -s)
ifeq ($(UNAME_S),OpenBSD)
$(warning OpenBSD detected, disabling ledger support (https://github.com/cosmos/cosmos-sdk/issues/1988))
else
GCC = $(shell command -v gcc 2> /dev/null)
ifeq ($(GCC),)
$(error gcc not installed for ledger support, please install or set LEDGER_ENABLED=false)
else
build_tags += ledger
endif
endif
endif
endif

build_tags += $(BUILD_TAGS)
build_tags := $(strip $(build_tags))
BUILD_FLAGS := -tags "$(build_tags) $(GITOPIA_ENV)"

all: install

Expand All @@ -9,7 +38,7 @@ build:

install: go.sum
@echo "--> Installing git-remote-gitopia"
@go install -tags $(GITOPIA_ENV) -mod=readonly ./cmd/git-remote-gitopia
@go install -mod=readonly $(BUILD_FLAGS) ./cmd/git-remote-gitopia

go.sum: go.mod
@echo "--> Ensure dependencies have not been modified"
Expand Down
73 changes: 50 additions & 23 deletions cmd/git-remote-gitopia/gitopia.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (

"github.com/cosmos/cosmos-sdk/client/grpc/tmservice"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/ledger"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/gitopia/git-remote-gitopia/config"
core "github.com/gitopia/git-remote-gitopia/core"
Expand Down Expand Up @@ -164,42 +166,69 @@ func (h *GitopiaHandler) Fetch(remote *core.Remote, sha, ref string) error {

func (h *GitopiaHandler) Push(remote *core.Remote, refsToPush []core.RefToPush) (*[]string, error) {
var gitopiaWallet GitopiaWallet
var walletAddress sdk.AccAddress
var privKey cryptotypes.PrivKey
var ledgerPrivKey cryptotypes.LedgerPrivKey
var buffer []byte
var err error
h.didPush = true
var useLedger bool

// Read wallet
isGitHubAction := os.Getenv("GITHUB_ACTIONS")
if isGitHubAction == "true" {
// Read from GitHub secret
buffer = []byte(os.Getenv("GITOPIA_WALLET"))
} else {
gitopiaWalletPath := os.Getenv("GITOPIA_WALLET")
if gitopiaWalletPath == "" {
return nil, fmt.Errorf("fatal: GITOPIA_WALLET environment variable is not set")
err = json.Unmarshal(buffer, &gitopiaWallet)
if err != nil {
return nil, fmt.Errorf("fatal: error decoding wallet file")
}

// Generate private key
hdPath := gitopiaWallet.HDpath + strconv.Itoa(gitopiaWallet.PathIncrement)
derivedPriv, err := hd.Secp256k1.Derive()(gitopiaWallet.Mnemonic, "", hdPath)
if err != nil {
return nil, err
}

var err error
privKey = hd.Secp256k1.Generate()(derivedPriv)
walletAddress = sdk.AccAddress(privKey.PubKey().Address())
} else if len(os.Getenv("GITOPIA_WALLET")) != 0 {
gitopiaWalletPath := os.Getenv("GITOPIA_WALLET")
buffer, err = os.ReadFile(gitopiaWalletPath)
if err != nil {
return nil, fmt.Errorf("fatal: error reading gitopia wallet")
}

}
err = json.Unmarshal(buffer, &gitopiaWallet)
if err != nil {
return nil, fmt.Errorf("fatal: error decoding wallet file")
}

err := json.Unmarshal(buffer, &gitopiaWallet)
if err != nil {
return nil, fmt.Errorf("fatal: error decoding wallet file")
}
// Generate private key
hdPath := gitopiaWallet.HDpath + strconv.Itoa(gitopiaWallet.PathIncrement)
derivedPriv, err := hd.Secp256k1.Derive()(gitopiaWallet.Mnemonic, "", hdPath)
if err != nil {
return nil, err
}

// Generate private key
hdPath := gitopiaWallet.HDpath + strconv.Itoa(gitopiaWallet.PathIncrement)
derivedPriv, err := hd.Secp256k1.Derive()(gitopiaWallet.Mnemonic, "", hdPath)
if err != nil {
return nil, err
}
privKey = hd.Secp256k1.Generate()(derivedPriv)
walletAddress = sdk.AccAddress(privKey.PubKey().Address())
} else {
useLedger = true
ledgerPrivKey, err = ledger.NewPrivKeySecp256k1Unsafe(hd.BIP44Params{
Purpose: 44,
CoinType: 118,
Account: 0,
Change: false,
AddressIndex: 0,
})
if err != nil {
return nil, fmt.Errorf("fatal: Gitopia wallet is not configured! Set GITOPIA_WALLET environment variable or use Ledger")
}

privKey := hd.Secp256k1.Generate()(derivedPriv)
walletAddress := sdk.AccAddress(privKey.PubKey().Address())
walletAddress = sdk.AccAddress(ledgerPrivKey.PubKey().Address())
}

havePushPermission, err := h.havePushPermission(walletAddress.String())
if err != nil {
Expand All @@ -209,10 +238,6 @@ func (h *GitopiaHandler) Push(remote *core.Remote, refsToPush []core.RefToPush)
return nil, fmt.Errorf("fatal: you don't have write permissions to this repository")
}

var msg []sdk.Msg

// Delete branch/tag

remoteURL := fmt.Sprintf("%v/%v.git", config.GitServerHost, h.remoteRepository.Id)
remoteConfig := &goGitConfig.RemoteConfig{
Name: "gitopia-objects-store",
Expand Down Expand Up @@ -323,6 +348,8 @@ func (h *GitopiaHandler) Push(remote *core.Remote, refsToPush []core.RefToPush)
}
}

var msg []sdk.Msg

if len(setBranches) > 0 {
msg = append(msg, gitopiaTypes.NewMsgMultiSetRepositoryBranch(walletAddress.String(), h.remoteRepository.Id, setBranches))
}
Expand All @@ -336,7 +363,7 @@ func (h *GitopiaHandler) Push(remote *core.Remote, refsToPush []core.RefToPush)
msg = append(msg, gitopiaTypes.NewMsgMultiDeleteTag(walletAddress.String(), h.remoteRepository.Id, deleteTags))
}

err = signAndBroadcastTx(h.grpcConn, walletAddress.String(), h.chainId, privKey, msg)
err = signAndBroadcastTx(h.grpcConn, walletAddress.String(), h.chainId, privKey, ledgerPrivKey, msg, useLedger)
if err != nil {
return nil, err
}
Expand Down
159 changes: 143 additions & 16 deletions cmd/git-remote-gitopia/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,8 @@ const (
GAS_ADJUSTMENT = 1.2
)

func signAndBroadcastTx(cc *grpc.ClientConn, sender string, chainId string, privKey cryptotypes.PrivKey, msg []sdk.Msg) error {
func signWithWallet(cc *grpc.ClientConn, sender string, chainId string, privKey cryptotypes.PrivKey, msg []sdk.Msg, txClient tx.ServiceClient) ([]byte, error) {
accountQueryClient := authtype.NewQueryClient(cc)
txClient := tx.NewServiceClient(cc)

interfaceRegistry := types.NewInterfaceRegistry()
interfaceRegistry.RegisterInterface(
"cosmos.auth.v1beta1.AccountI",
Expand All @@ -44,34 +42,42 @@ func signAndBroadcastTx(cc *grpc.ClientConn, sender string, chainId string, priv
txCfg := authtx.NewTxConfig(marshaler, authtx.DefaultSignModes)

txBuilder := txCfg.NewTxBuilder()
txBuilder.SetMsgs(msg...)
err := txBuilder.SetMsgs(msg...)
if err != nil {
return nil, err
}
txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("utlore", sdk.NewInt(500))))

res, err := accountQueryClient.Account(context.Background(),
&authtype.QueryAccountRequest{
Address: sender,
},
)
if err != nil {
return nil, err
}
var acc authtype.AccountI
if err := interfaceRegistry.UnpackAny(res.Account, &acc); err != nil {
return err
return nil, err
}

signMode := txCfg.SignModeHandler().DefaultMode()
sigV2 := signing.SignatureV2{
PubKey: privKey.PubKey(),
Data: &signing.SingleSignatureData{
SignMode: txCfg.SignModeHandler().DefaultMode(),
SignMode: signMode,
Signature: nil,
},
Sequence: acc.GetSequence(),
}
err = txBuilder.SetSignatures(sigV2)
if err != nil {
return err
return nil, err
}

gas, err := calculateGas(cc, txClient, txCfg, txBuilder)
if err != nil {
return err
return nil, err
}
txBuilder.SetGasLimit(gas)

Expand All @@ -81,30 +87,151 @@ func signAndBroadcastTx(cc *grpc.ClientConn, sender string, chainId string, priv
Sequence: acc.GetSequence(),
}

sigV2, err = clientTx.SignWithPrivKey(txCfg.SignModeHandler().DefaultMode(), signerData,
sigV2, err = clientTx.SignWithPrivKey(signMode, signerData,
txBuilder, privKey, txCfg, acc.GetSequence())
if err != nil {
return err
return nil, err
}

err = txBuilder.SetSignatures(sigV2)
if err != nil {
return err
return nil, err
}

err = txBuilder.GetTx().ValidateBasic()
if err != nil {
fmt.Fprintf(os.Stderr, "fatal: tx validation failed: %v", err.Error())
}

var txBytes []byte
txBytes, err = txCfg.TxEncoder()(txBuilder.GetTx())
txBytes, err := txCfg.TxEncoder()(txBuilder.GetTx())
if err != nil {
return err
return nil, err
}

return txBytes, nil
}

func signWithLedger(cc *grpc.ClientConn, sender string, chainId string, ledgerPrivKey cryptotypes.LedgerPrivKey, msg []sdk.Msg, txClient tx.ServiceClient) ([]byte, error) {
accountQueryClient := authtype.NewQueryClient(cc)
interfaceRegistry := types.NewInterfaceRegistry()
interfaceRegistry.RegisterInterface(
"cosmos.auth.v1beta1.AccountI",
(*authtype.AccountI)(nil),
&authtype.BaseAccount{},
&authtype.ModuleAccount{},
)
interfaceRegistry.RegisterInterface("cosmos.crypto.PubKey", (*cryptotypes.PubKey)(nil))
interfaceRegistry.RegisterImplementations((*cryptotypes.PubKey)(nil), &cosmoscryptosecp.PubKey{})
interfaceRegistry.RegisterImplementations((*cryptotypes.PubKey)(nil), &cosmoscryptoed.PubKey{})
marshaler := codec.NewProtoCodec(interfaceRegistry)
txCfg := authtx.NewTxConfig(marshaler, authtx.DefaultSignModes)

txBuilder := txCfg.NewTxBuilder()
err := txBuilder.SetMsgs(msg...)
if err != nil {
return nil, err
}
txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("utlore", sdk.NewInt(500))))

res, err := accountQueryClient.Account(context.Background(),
&authtype.QueryAccountRequest{
Address: sender,
},
)
if err != nil {
return nil, err
}
var acc authtype.AccountI
if err := interfaceRegistry.UnpackAny(res.Account, &acc); err != nil {
return nil, err
}

signMode := signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON
sigV2 := signing.SignatureV2{
PubKey: ledgerPrivKey.PubKey(),
Data: &signing.SingleSignatureData{
SignMode: signMode,
Signature: nil,
},
Sequence: acc.GetSequence(),
}
err = txBuilder.SetSignatures(sigV2)
if err != nil {
return nil, err
}

gas, err := calculateGas(cc, txClient, txCfg, txBuilder)
if err != nil {
return nil, err
}
txBuilder.SetGasLimit(gas)

signerData := xauthsigning.SignerData{
ChainID: chainId,
AccountNumber: acc.GetAccountNumber(),
Sequence: acc.GetSequence(),
}

bytesToSign, err := txCfg.SignModeHandler().GetSignBytes(signMode, signerData, txBuilder.GetTx())
if err != nil {
return nil, err
}

// TODO: Gives error in case of large msg
// JSON. Too many tokens
sigBytes, err := ledgerPrivKey.Sign(bytesToSign)
if err != nil {
return nil, err
}

// Construct the SignatureV2 struct
sigData := signing.SingleSignatureData{
SignMode: signMode,
Signature: sigBytes,
}
sigV2 = signing.SignatureV2{
PubKey: ledgerPrivKey.PubKey(),
Data: &sigData,
Sequence: acc.GetSequence(),
}

err = txBuilder.SetSignatures(sigV2)
if err != nil {
return nil, err
}

err = txBuilder.GetTx().ValidateBasic()
if err != nil {
fmt.Fprintf(os.Stderr, "fatal: tx validation failed: %v", err.Error())
}

txBytes, err := txCfg.TxEncoder()(txBuilder.GetTx())
if err != nil {
return nil, err
}

return txBytes, nil
}

func signAndBroadcastTx(cc *grpc.ClientConn, sender string, chainId string, privKey cryptotypes.PrivKey,
ledgerPrivKey cryptotypes.LedgerPrivKey, msg []sdk.Msg, useLedger bool) error {
txClient := tx.NewServiceClient(cc)

var txBytes []byte
var err error
if useLedger {
txBytes, err = signWithLedger(cc, sender, chainId, ledgerPrivKey, msg, txClient)
if err != nil {
return err
}
} else {
txBytes, err = signWithWallet(cc, sender, chainId, privKey, msg, txClient)
if err != nil {
return err
}
}

var grpcRes *tx.BroadcastTxResponse
grpcRes, err = txClient.BroadcastTx(
grpcRes, err := txClient.BroadcastTx(
context.Background(),
&tx.BroadcastTxRequest{
Mode: tx.BroadcastMode_BROADCAST_MODE_SYNC,
Expand Down