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
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
all:
go build -o arbiter app/arbiter/main.go
go build -o keystore-generator app/keystore-generator/main.go

linux:
GOARCH=amd64 GOOS=linux go build -o arbiter app/arbiter/main.go
GOARCH=amd64 GOOS=linux go build -o arbiter app/arbiter/main.go
GOARCH=amd64 GOOS=linux go build -o keystore-generator app/keystore-generator/main.go
36 changes: 18 additions & 18 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -68,28 +68,28 @@ $ make
keyFilePath: "~/loan_arbiter/keys"
```



### 3. prepare keystore to keyFilePath

1. create **btcKey.json** **escKey.json** and put it into **keyFilePath**
1. create **btcKey** **escKey** and put it into **keyFilePath**
btcKey is used to sign request_arbitration_btc_tx
escKey is used to submit arbitration signature to esc arbiter contract, gas fee(esc ELA) is needed

2. keysotre file need to set hex encoded private key

btcKey.json
```json
{
"privKey": "[HEX_PRIV_KEY]"
}
```
escKey.json
```json
{
"privKey": "[HEX_PRIV_KEY]"
}
```
2. Use keystore-generator tool to create keystore files:

Install keystore-generator:
```shell
go install github.com/BeL2Labs/keystore-generator@latest
```

Generate BTC keystore:
```shell
keystore-generator -c btc -s YOUR_PRIVATE_KEY -p YOUR_PASSWORD -o btcKey
```

Generate ESC keystore:
```shell
keystore-generator -c eth -s YOUR_PRIVATE_KEY -p YOUR_PASSWORD -o escKey
```

### 4. run arbiter

Expand All @@ -113,4 +113,4 @@ The automated deployment process can be referenced in the documentation:
[deploy_loan_arbiter.md](https://github.com/BeL2Labs/Arbiter_Signer/blob/main/docs/deploy_loan_arbiter.md)

## License
arbiter signer is licensed under the [copyfree](http://copyfree.org) MIT License.
arbiter signer is licensed under the [copyfree](http://copyfree.org) MIT License.
31 changes: 8 additions & 23 deletions app/arbiter/arbiter/arbiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"crypto/sha256"
"encoding/gob"
"encoding/hex"
"encoding/json"
"log"
"os"
"path/filepath"
Expand All @@ -30,6 +29,7 @@ import (
"github.com/BeL2Labs/Arbiter_Signer/app/arbiter/config"
"github.com/BeL2Labs/Arbiter_Signer/app/arbiter/contract"
"github.com/BeL2Labs/Arbiter_Signer/app/arbiter/contract/events"
"github.com/BeL2Labs/Arbiter_Signer/app/arbiter/crypto"
)

const DELAY_BLOCK uint64 = 3
Expand All @@ -49,25 +49,21 @@ type Arbiter struct {
logger *log.Logger
}

func NewArbiter(ctx context.Context, config *config.Config) *Arbiter {
escData, err := os.ReadFile(config.EscKeyFilePath)
func NewArbiter(ctx context.Context, config *config.Config, password string) *Arbiter {
escPrivKey, err := crypto.GetEthKeyFromKeystore(config.EscKeyFilePath, password)
if err != nil {
g.Log().Fatal(ctx, "get esc keyfile error", err, " keystore path ", config.EscKeyFilePath)
}
var escAccount account
err = json.Unmarshal(escData, &escAccount)
if err != nil {
g.Log().Fatal(ctx, "Unmarshal keyfile error", err, " content ", string(escData))
escAccount := account{
PrivateKey: escPrivKey,
}

arbiterData, err := os.ReadFile(config.ArbiterKeyFilePath)
arbiterPrivKey, err := crypto.GetBtcKeyFromKeystore(config.ArbiterKeyFilePath, password)
if err != nil {
g.Log().Fatal(ctx, "get arbiter keyfile error", err, " keystore path ", config.ArbiterKeyFilePath)
}
var arbiterAccount account
err = json.Unmarshal(arbiterData, &arbiterAccount)
if err != nil {
g.Log().Fatal(ctx, "Unmarshal keyfile error", err, " content ", string(arbiterData))
arbiterAccount := account{
PrivateKey: arbiterPrivKey,
}

err = createDir(config)
Expand Down Expand Up @@ -114,17 +110,6 @@ func (v *Arbiter) listenESCContract() {
startHeight = v.config.ESCStartHeight
}

keyfile := v.config.EscKeyFilePath
data, err := os.ReadFile(keyfile)
if err != nil {
g.Log().Fatal(v.ctx, "get keyfile error", err, " private key path ", keyfile)
}
var a account
err = json.Unmarshal(data, &a)
if err != nil {
g.Log().Fatal(v.ctx, "Unmarshal keyfile error", err, " content ", string(data))
}

v.escNode.Start(startHeight)
}

Expand Down
135 changes: 135 additions & 0 deletions app/arbiter/crypto/keystore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package crypto

import (
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"os"

"github.com/ethereum/go-ethereum/accounts/keystore"
)

// ReadBTCKeystore reads a BTC keystore file
func ReadBTCKeystore(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}

// BTC keystore is binary format, return raw data
return data, nil
}

// ReadETHKeystore reads an ETH keystore file
func ReadETHKeystore(path string) (*keystore.Key, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}

// Parse ETH keystore JSON
var key keystore.Key
if err := json.Unmarshal(data, &key); err != nil {
return nil, err
}

return &key, nil
}

// ReadKeystore automatically detects and reads either BTC or ETH keystore
func ReadKeystore(path string) (interface{}, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}

// Try to parse as ETH keystore first
var ethKey keystore.Key
if err := json.Unmarshal(data, &ethKey); err == nil {
return &ethKey, nil
}

// If not ETH keystore, treat as BTC keystore
return data, nil
}

// GetKeyType determines if a keystore is BTC or ETH format
func GetKeyType(path string) (string, error) {
data, err := os.ReadFile(path)
if err != nil {
return "", err
}

// Check if data is valid JSON
var temp interface{}
if json.Unmarshal(data, &temp) == nil {
return "eth", nil
}

// Check if data is valid hex (BTC WIF)
if _, err := hex.DecodeString(string(data)); err == nil {
return "btc", nil
}

return "", errors.New("unknown keystore format")
}

// GetEthKeyFromKeystore reads an ETH keystore file and returns the private key as hex string
func GetEthKeyFromKeystore(path, password string) (string, error) {
// Read ETH keystore file
data, err := os.ReadFile(path)
if err != nil {
return "", err
}

// Decrypt ETH keystore
privateKey, err := keystore.DecryptKey(data, password)
if err != nil {
return "", err
}
return hex.EncodeToString(privateKey.PrivateKey.D.Bytes()), nil
}

// GetBtcKeyFromKeystore reads a BTC keystore file and returns the private key as hex string
func GetBtcKeyFromKeystore(path, password string) (string, error) {
data, err := ReadBTCKeystore(path)
if err != nil {
return "", err
}
// Decrypt BTC keystore using password
decryptedData, err := decryptBTCKeystore(data, password)
if err != nil {
return "", err
}
return hex.EncodeToString(decryptedData), nil
}

// decryptBTCKeystore decrypts BTC keystore data using password
func decryptBTCKeystore(data []byte, password string) ([]byte, error) {
key := sha256.Sum256([]byte(password))
block, err := aes.NewCipher(key[:])
if err != nil {
return nil, err
}

gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}

nonceSize := gcm.NonceSize()
if len(data) < nonceSize {
return nil, errors.New("ciphertext too short")
}

nonce, ciphertext := data[:nonceSize], data[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, err
}

return plaintext, nil
}
20 changes: 17 additions & 3 deletions app/arbiter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package main

import (
"context"
"flag"
"fmt"
"os"
"path/filepath"
Expand All @@ -20,6 +21,19 @@ import (
)

func main() {
passwordPtr := flag.String("p", "", "Specify the password")
flag.Parse()
var password string
if *passwordPtr == "" {
fmt.Print("please input key password: ")
_, err := fmt.Scanln(&password)
if err != nil {
fmt.Println("read password error:", err)
os.Exit(1)
}
} else {
password = *passwordPtr
}

if len(os.Args) > 1 {
operation := os.Args[1]
Expand All @@ -41,7 +55,7 @@ func main() {
wg.Add(1)
// start arbiter
g.Log().Info(ctx, "Starting arbiter...")
arb := arbiter.NewArbiter(ctx, initConfig(ctx))
arb := arbiter.NewArbiter(ctx, initConfig(ctx), password)
arb.Start()
wg.Wait()
}
Expand Down Expand Up @@ -116,8 +130,8 @@ func initConfig(ctx context.Context) *config.Config {
g.Log().Info(ctx, "keyFilePath:", keyFilePath)

// if want to submit to ESC contract successfully, need to use esc ela as gas.
escKeyFilePath := gfile.Join(keyFilePath, "escKey.json")
arbiterKeyFilePath := gfile.Join(keyFilePath, "btcKey.json")
escKeyFilePath := gfile.Join(keyFilePath, "escKey")
arbiterKeyFilePath := gfile.Join(keyFilePath, "btcKey")
logPath := gfile.Join(dataPath, "logs/")
loanPath := gfile.Join(dataPath, "loan/")
loanNeedSignReqPath := gfile.Join(loanPath, "request/")
Expand Down
75 changes: 75 additions & 0 deletions app/keystore-generator/btc/btc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) 2025 The bel2 developers
package btc

import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"errors"
"fmt"
"io"
"os"
)

func ParseKeystore(filePath string, password string) (string, error) {
data, err := os.ReadFile(filePath)
if err != nil {
return "", fmt.Errorf("failed to read keystore file: %v", err)
}

// Decrypt the data
decrypted, err := decrypt(data, password)
if err != nil {
return "", fmt.Errorf("failed to decrypt keystore: %v", err)
}

return string(decrypted), nil
}

func Encrypt(data []byte, password string) ([]byte, error) {
key := sha256.Sum256([]byte(password))
block, err := aes.NewCipher(key[:])
if err != nil {
return nil, err
}

gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}

nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}

ciphertext := gcm.Seal(nonce, nonce, data, nil)
return ciphertext, nil
}

func decrypt(data []byte, password string) ([]byte, error) {
key := sha256.Sum256([]byte(password))
block, err := aes.NewCipher(key[:])
if err != nil {
return nil, err
}

gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}

nonceSize := gcm.NonceSize()
if len(data) < nonceSize {
return nil, errors.New("ciphertext too short")
}

nonce, ciphertext := data[:nonceSize], data[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, err
}

return plaintext, nil
}
Loading
Loading