Skip to content

Commit f93169b

Browse files
authored
Merge pull request #1 from unpackdev/dstore
Decentralized Store: Preparation - Network && Topology V0
2 parents b45fe33 + 1fd466c commit f93169b

File tree

154 files changed

+18349
-452
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

154 files changed

+18349
-452
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,6 @@ testdb/
2929
build/
3030

3131
# binaries
32-
example
32+
example
33+
34+
~/

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ lint: ## Lint the Go code using golangci-lint
3939
.PHONY: build
4040
build: build-linux ## Build the binary for the current OS/Arch
4141

42+
.PHONY: build-osx
43+
build-osx: ## Build the binary for macOS/OSX
44+
go build -o ./$(BIN_NAME) -ldflags "-X main.Version=$(VERSION) -X main.CommitHash=$(COMMIT_HASH)" ./entrypoint/main.go
45+
4246
.PHONY: build-linux
4347
build-linux: ## Build the binary for Linux
4448
@GOOS=linux GOARCH=amd64 go build -o ./$(BIN_NAME) -ldflags "-X main.Version=$(VERSION) -X main.CommitHash=$(COMMIT_HASH)" ./entrypoint/main.go

accounts/README.md

Whitespace-only changes.

accounts/account.go

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// pkg/accounts/account.go
2+
3+
package accounts
4+
5+
import (
6+
"crypto/ecdsa"
7+
"github.com/ethereum/go-ethereum/crypto"
8+
libp2pCrypto "github.com/libp2p/go-libp2p/core/crypto"
9+
"github.com/libp2p/go-libp2p/core/peer"
10+
"github.com/unpackdev/fdb/logger"
11+
"github.com/unpackdev/fdb/rbac"
12+
"github.com/unpackdev/fdb/share"
13+
"github.com/unpackdev/fdb/types"
14+
15+
"github.com/pkg/errors"
16+
"github.com/sasha-s/go-deadlock"
17+
)
18+
19+
// Ensure Account implements the share.Account interface
20+
var _ share.Account = (*Account)(nil)
21+
22+
// Account represents a Decentralized Identifier with associated cryptographic keys and permissions.
23+
type Account struct {
24+
logger logger.Logger
25+
rbacMgr *rbac.Manager
26+
id string // String representation of the peer ID
27+
signerType types.SignerType // SignerType associated with the account
28+
address types.Address // Derived address from MasterPublicKey
29+
peerID peer.ID // Associated peer.ID object for easier manipulation
30+
name string // Name of the account for descriptive purposes
31+
comment string // Optional comment or description for the account
32+
masterPrivateKey libp2pCrypto.PrivKey // Private key associated with libp2p PeerID, used as the master private key for all signers
33+
masterPublicKey libp2pCrypto.PubKey // Public key associated with libp2p PeerID, used as the master public key for all signers
34+
roles []types.Role
35+
extraPermissions map[types.Role][]types.Permission
36+
mu deadlock.RWMutex // Mutex for thread-safe operations
37+
}
38+
39+
// NewConsensusAccount initializes Account used by the validators.
40+
// TODO: This needs to be extended with roles and many more things but for now it's this
41+
func NewConsensusAccount(logger logger.Logger, peerId peer.ID, address types.Address, pubKey libp2pCrypto.PubKey, roles []types.Role) (*Account, error) {
42+
account := &Account{
43+
logger: logger,
44+
peerID: peerId,
45+
id: peerId.String(),
46+
address: address,
47+
masterPublicKey: pubKey,
48+
roles: roles,
49+
}
50+
51+
return account, nil
52+
}
53+
54+
// NewAccount initializes a new Account with all the cryptographic keys and metadata.
55+
func NewAccount(
56+
logger logger.Logger,
57+
peerID peer.ID,
58+
peerSk libp2pCrypto.PrivKey,
59+
peerPk libp2pCrypto.PubKey,
60+
signerType types.SignerType,
61+
name, comment string,
62+
roles []types.Role,
63+
extraPermissions map[types.Role][]types.Permission,
64+
rbacMgr *rbac.Manager,
65+
) (*Account, error) {
66+
67+
// Null and validity checks
68+
if peerID == "" {
69+
return nil, errors.New("peerID cannot be empty")
70+
}
71+
if peerSk == nil {
72+
return nil, errors.New("peerSk (MasterPrivateKey) cannot be nil")
73+
}
74+
if peerPk == nil {
75+
return nil, errors.New("peerPk (MasterPublicKey) cannot be nil")
76+
}
77+
78+
// Marshal the public key bytes
79+
pubKeyBytes, err := libp2pCrypto.MarshalPublicKey(peerPk)
80+
if err != nil {
81+
return nil, errors.Wrap(err, "failed to marshal MasterPublicKey")
82+
}
83+
84+
// Compute account ID using SignerType
85+
accountID := ComputeAccountID(signerType, pubKeyBytes)
86+
87+
// Compute address from the master public key bytes
88+
address, err := computeAddressFromPublicKey(pubKeyBytes)
89+
if err != nil {
90+
return nil, errors.Wrap(err, "failed to derive address from MasterPublicKey")
91+
}
92+
93+
account := &Account{
94+
logger: logger,
95+
id: accountID,
96+
signerType: signerType,
97+
peerID: peerID,
98+
address: address,
99+
name: name,
100+
comment: comment,
101+
masterPrivateKey: peerSk,
102+
masterPublicKey: peerPk,
103+
roles: roles,
104+
extraPermissions: extraPermissions,
105+
rbacMgr: rbacMgr,
106+
}
107+
108+
return account, nil
109+
}
110+
111+
// ID returns the unique identifier of the account.
112+
func (a *Account) ID() string {
113+
return a.id
114+
}
115+
116+
// Address returns the derived address from the MasterPublicKey.
117+
func (a *Account) Address() types.Address {
118+
a.mu.RLock()
119+
defer a.mu.RUnlock()
120+
return a.address
121+
}
122+
123+
// PeerID returns the associated libp2p PeerID.
124+
func (a *Account) PeerID() peer.ID {
125+
a.mu.RLock()
126+
defer a.mu.RUnlock()
127+
return a.peerID
128+
}
129+
130+
// Name returns the name of the account.
131+
func (a *Account) Name() string {
132+
a.mu.RLock()
133+
defer a.mu.RUnlock()
134+
return a.name
135+
}
136+
137+
// Comment returns the optional comment or description of the account.
138+
func (a *Account) Comment() string {
139+
a.mu.RLock()
140+
defer a.mu.RUnlock()
141+
return a.comment
142+
}
143+
144+
// MasterPrivateKey returns the master private key associated with the account.
145+
func (a *Account) MasterPrivateKey() libp2pCrypto.PrivKey {
146+
a.mu.RLock()
147+
defer a.mu.RUnlock()
148+
return a.masterPrivateKey
149+
}
150+
151+
func (a *Account) MasterKeyToECDSA() (*ecdsa.PrivateKey, error) {
152+
a.mu.RLock()
153+
defer a.mu.RUnlock()
154+
155+
privKeyBytes, err := a.MasterPrivateKey().Raw()
156+
if err != nil {
157+
return nil, err
158+
}
159+
160+
return crypto.ToECDSA(privKeyBytes)
161+
}
162+
163+
// MasterPublicKey returns the master public key associated with the account.
164+
func (a *Account) MasterPublicKey() libp2pCrypto.PubKey {
165+
a.mu.RLock()
166+
defer a.mu.RUnlock()
167+
return a.masterPublicKey
168+
}
169+
170+
// ExtraPermissions returns the additional permissions associated with each role.
171+
func (a *Account) ExtraPermissions() map[types.Role][]types.Permission {
172+
a.mu.RLock()
173+
defer a.mu.RUnlock()
174+
return a.extraPermissions
175+
}
176+
177+
func (a *Account) MarshalPublicKey() ([]byte, error) {
178+
return libp2pCrypto.MarshalPublicKey(a.masterPublicKey)
179+
}

accounts/common_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// pkg/accounts/common_test.go
2+
3+
package accounts
4+
5+
import (
6+
"github.com/unpackdev/fdb/config"
7+
"github.com/unpackdev/fdb/logger"
8+
"testing"
9+
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
// setupTestEnvironment initializes the test environment by creating a temporary directory
14+
// and initializing the logger. It returns the configuration and logger instances.
15+
func setupTestEnvironment(t *testing.T) (config.Identity, logger.Logger) {
16+
// Set up a temporary directory for YAML file-based storage
17+
tempDir := t.TempDir()
18+
cfg := config.Identity{
19+
Enabled: true,
20+
BasePath: tempDir,
21+
}
22+
23+
// Initialize the logger
24+
_, err := logger.InitializeGlobalLogger(config.Logger{
25+
Enabled: true,
26+
Environment: "development",
27+
Level: "debug",
28+
})
29+
require.NoError(t, err, "Failed to initialize logger")
30+
31+
return cfg, logger.G()
32+
}

accounts/ethereum.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// pkg/accounts/ethereum.go
2+
package accounts
3+
4+
import (
5+
"crypto/ecdsa"
6+
"github.com/ethereum/go-ethereum/common"
7+
ethCrypto "github.com/ethereum/go-ethereum/crypto"
8+
libp2pCrypto "github.com/libp2p/go-libp2p/core/crypto"
9+
"github.com/pkg/errors"
10+
)
11+
12+
// computeEthereumAddress computes the Ethereum address from the libp2p public key.
13+
// It uses Keccak-256 hashing and returns the address in hexadecimal format with '0x' prefix.
14+
// computeEthereumAddress computes the Ethereum address from the libp2p public key.
15+
func computeEthereumAddress(pubKey libp2pCrypto.PubKey) (common.Address, error) {
16+
var pubKeyBytes []byte
17+
var err error
18+
19+
switch pubKey.Type() {
20+
case libp2pCrypto.Secp256k1:
21+
// Get the raw public key bytes
22+
rawPubKeyBytes, err := pubKey.Raw()
23+
if err != nil {
24+
return common.Address{}, errors.Wrap(err, "failed to get raw public key bytes")
25+
}
26+
27+
if len(rawPubKeyBytes) == 33 {
28+
// Compressed key, decompress it
29+
pubKeyDecompressed, err := ethCrypto.DecompressPubkey(rawPubKeyBytes)
30+
if err != nil {
31+
return common.Address{}, errors.Wrap(err, "failed to decompress Secp256k1 public key")
32+
}
33+
pubKeyBytes = ethCrypto.FromECDSAPub(pubKeyDecompressed)[1:] // Remove 0x04 prefix
34+
} else if len(rawPubKeyBytes) == 65 {
35+
// Uncompressed key, remove the prefix
36+
pubKeyBytes = rawPubKeyBytes[1:] // Remove 0x04 prefix
37+
} else {
38+
return common.Address{}, errors.Errorf("unexpected Secp256k1 public key length: got %d bytes", len(rawPubKeyBytes))
39+
}
40+
case libp2pCrypto.Ed25519:
41+
// Use the raw public key bytes for Ed25519 keys
42+
pubKeyBytes, err = pubKey.Raw()
43+
if err != nil {
44+
return common.Address{}, errors.Wrap(err, "failed to get raw Ed25519 public key bytes")
45+
}
46+
default:
47+
return common.Address{}, errors.Errorf("unsupported key type: %d", pubKey.Type())
48+
}
49+
50+
// Compute Keccak-256 hash of the public key bytes
51+
hash := ethCrypto.Keccak256(pubKeyBytes)
52+
53+
// Take the last 20 bytes for the Ethereum address
54+
ethAddress := common.BytesToAddress(hash[12:])
55+
56+
return ethAddress, nil
57+
}
58+
59+
// computeEthereumAddressFromECDSAPubKey computes the Ethereum address from an ECDSA public key.
60+
// It uses Keccak-256 hashing and returns the address in hexadecimal format with '0x' prefix.
61+
func computeEthereumAddressFromECDSAPubKey(pubKey *ecdsa.PublicKey) (common.Address, error) {
62+
if pubKey == nil {
63+
return common.Address{}, errors.New("public key is nil")
64+
}
65+
66+
pubBytes := ethCrypto.FromECDSAPub(pubKey) // 65 bytes with 0x04 prefix
67+
hash := ethCrypto.Keccak256(pubBytes[1:]) // Exclude the 0x04 prefix
68+
69+
return common.BytesToAddress(hash[12:]), nil // Last 20 bytes
70+
}

accounts/protocols.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package accounts
2+
3+
import "github.com/unpackdev/fdb/types"
4+
5+
// TODO: Not even sure how to do this... And is this even how it should be done...
6+
// Perhaps based on node type and configuration instead of anything else...
7+
func (a *Account) SupportedProtocols() []types.ProtocolType {
8+
return []types.ProtocolType{}
9+
}

accounts/rbac.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// pkg/accounts/rbac.go
2+
package accounts
3+
4+
import (
5+
"fmt"
6+
"github.com/unpackdev/fdb/types"
7+
)
8+
9+
// Roles returns the list of roles assigned to the account.
10+
func (a *Account) Roles() []types.Role {
11+
a.mu.RLock()
12+
defer a.mu.RUnlock()
13+
return a.roles
14+
}
15+
16+
// AssignRole assigns a new role to the account.
17+
func (a *Account) AssignRole(role types.Role, permissions ...types.Permission) error {
18+
a.mu.Lock()
19+
defer a.mu.Unlock()
20+
21+
// Assign the role using RBAC Manager
22+
if err := a.rbacMgr.AssignRole(role, permissions...); err != nil {
23+
return err
24+
}
25+
26+
// Add the role to the account's Roles slice if not already present
27+
for _, r := range a.roles {
28+
if r == role {
29+
// Role already assigned
30+
return nil
31+
}
32+
}
33+
a.roles = append(a.roles, role)
34+
return nil
35+
}
36+
37+
// RemoveRole removes a role from the account.
38+
func (a *Account) RemoveRole(role types.Role) error {
39+
a.mu.Lock()
40+
defer a.mu.Unlock()
41+
42+
// Remove the role from RBAC Manager
43+
if err := a.rbacMgr.RemoveRole(role); err != nil {
44+
return err
45+
}
46+
47+
// Remove the role from the account's Roles slice
48+
for i, r := range a.roles {
49+
if r == role {
50+
a.roles = append(a.roles[:i], a.roles[i+1:]...)
51+
break
52+
}
53+
}
54+
return nil
55+
}
56+
57+
// HasPermission checks if the account has the specified permission.
58+
func (a *Account) HasPermission(permission types.Permission) bool {
59+
a.mu.RLock()
60+
defer a.mu.RUnlock()
61+
62+
return a.rbacMgr.HasPermission(a.roles, permission)
63+
}
64+
65+
// Authorize ensures the account has the required permission.
66+
func (a *Account) Authorize(permission types.Permission) error {
67+
if a.HasPermission(permission) {
68+
return nil
69+
}
70+
return fmt.Errorf("account %s does not have permission %s", a.name, permission)
71+
}

0 commit comments

Comments
 (0)