working on compilation errors

This commit is contained in:
Gabriel mermelstein 2024-11-27 12:08:50 +02:00
parent 45baa84658
commit 73d3f9a1fc
No known key found for this signature in database
GPG Key ID: 82B8134785FEAE0D
62 changed files with 6285 additions and 8 deletions

View File

@ -28,7 +28,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/status-im/status-go/logutils"
"github.com/waku-org/waku-go-bindings/logutils"
)
// Filter represents a Waku message filter

View File

@ -11,7 +11,7 @@ import (
"github.com/waku-org/go-waku/waku/v2/payload"
"github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/status-im/status-go/logutils"
"github.com/waku-org/waku-go-bindings/logutils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"

View File

@ -5,8 +5,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/rlp"
"github.com/status-im/status-go/common"
"github.com/status-im/status-go/eth-node/types"
"github.com/waku-org/waku-go-bindings/types"
)
type Measure struct {

18
eth-node/Makefile Normal file
View File

@ -0,0 +1,18 @@
GO111MODULE = on
ENABLE_METRICS ?= true
BUILD_FLAGS ?= $(shell echo "-ldflags '\
-X github.com/status-im/status-go/eth-node/vendor/github.com/ethereum/go-ethereum/metrics.EnabledStr=$(ENABLE_METRICS)'")
test:
go test ./...
.PHONY: test
lint:
golangci-lint run -v
.PHONY: lint
install-linter:
# install linter
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.21.0
.PHONY: install-linter

20
eth-node/README.md Normal file
View File

@ -0,0 +1,20 @@
# Abstraction for Ethereum node implementation
This package is a collection of interfaces, data types, and functions to make status-go independent from the go-ethereum implementation.
status-go is a wrapper around an Ethereum node. This package was created to have a possibility of selecting the underlying Ethereum node implementation, namely [go-ethereum](https://github.com/ethereum/go-ethereum) or [Nimbus](http://github.com/status-im/nimbus). The underlying implementation is selected using [Go build tags](https://golang.org/pkg/go/build/#hdr-Build_Constraints).
* `types` and `core/types` -- provide interfaces of node services, common data types, and functions,
* `bridge` -- provide implementation of interfaces declared in `types` using [go-ethereum](https://github.com/ethereum/go-ethereum) or [Nimbus](http://github.com/status-im/nimbus) in `geth` and `nimbus` directories respectively,
* `crypto` -- provide cryptographic utilities not depending on [go-ethereum](https://github.com/ethereum/go-ethereum),
* `keystore` -- provide a keystore implementation not depending on [go-ethereum](https://github.com/ethereum/go-ethereum).
Note: `crypto` and `keystore` are not finished by either depending on [go-ethereum](https://github.com/ethereum/go-ethereum) or not providing [Nimbus](http://github.com/status-im/nimbus) implementation.
## How to use it?
If you have a piece of code that depends on [go-ethereum](https://github.com/ethereum/go-ethereum), check out this package to see if there is a similar implementation that does not depend on [go-ethereum](https://github.com/ethereum/go-ethereum). For example, you want to decode a hex-string into a slice of bytes. You can do that using go-ethereum's `FromHex()` function or use equivalent from this package and avoid importing [go-ethereum](https://github.com/ethereum/go-ethereum). Thanks to this, your code fragment might be built with [Nimbus](http://github.com/status-im/nimbus).
## License
[Mozilla Public License 2.0](https://github.com/status-im/status-go/blob/develop/LICENSE.md)

View File

@ -0,0 +1,117 @@
package ens
import (
"bytes"
"context"
"crypto/elliptic"
"encoding/hex"
"math/big"
"time"
ens "github.com/wealdtech/go-ens/v3"
"go.uber.org/zap"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
gocommon "github.com/waku-org/waku-go-bindings/common"
"github.com/waku-org/waku-go-bindings/eth-node/crypto"
enstypes "github.com/waku-org/waku-go-bindings/eth-node/types/ens"
)
const (
contractQueryTimeout = 5000 * time.Millisecond
)
type Verifier struct {
logger *zap.Logger
}
// NewVerifier returns a Verifier attached to the specified logger
func NewVerifier(logger *zap.Logger) *Verifier {
return &Verifier{logger: logger}
}
func (m *Verifier) ReverseResolve(address common.Address, rpcEndpoint string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), contractQueryTimeout)
defer cancel()
ethClient, err := ethclient.DialContext(ctx, rpcEndpoint)
if err != nil {
return "", err
}
return ens.ReverseResolve(ethClient, address)
}
func (m *Verifier) verifyENSName(ensInfo enstypes.ENSDetails, ethclient *ethclient.Client) enstypes.ENSResponse {
publicKeyStr := ensInfo.PublicKeyString
ensName := ensInfo.Name
m.logger.Info("Resolving ENS name", zap.String("name", ensName), zap.String("publicKey", publicKeyStr))
response := enstypes.ENSResponse{
Name: ensName,
PublicKeyString: publicKeyStr,
VerifiedAt: time.Now().Unix(),
}
expectedPubKeyBytes, err := hex.DecodeString(publicKeyStr)
if err != nil {
response.Error = err
return response
}
publicKey, err := crypto.UnmarshalPubkey(expectedPubKeyBytes)
if err != nil {
response.Error = err
return response
}
// Resolve ensName
resolver, err := ens.NewResolver(ethclient, ensName)
if err != nil {
m.logger.Error("error while creating ENS name resolver", zap.String("ensName", ensName), zap.Error(err))
response.Error = err
return response
}
x, y, err := resolver.PubKey()
if err != nil {
m.logger.Error("error while resolving public key from ENS name", zap.String("ensName", ensName), zap.Error(err))
response.Error = err
return response
}
// Assemble the bytes returned for the pubkey
pubKeyBytes := elliptic.Marshal(crypto.S256(), new(big.Int).SetBytes(x[:]), new(big.Int).SetBytes(y[:]))
response.PublicKey = publicKey
response.Verified = bytes.Equal(pubKeyBytes, expectedPubKeyBytes)
return response
}
// CheckBatch verifies that a registered ENS name matches the expected public key
func (m *Verifier) CheckBatch(ensDetails []enstypes.ENSDetails, rpcEndpoint, contractAddress string) (map[string]enstypes.ENSResponse, error) {
ctx, cancel := context.WithTimeout(context.Background(), contractQueryTimeout)
defer cancel()
ch := make(chan enstypes.ENSResponse)
response := make(map[string]enstypes.ENSResponse)
ethclient, err := ethclient.DialContext(ctx, rpcEndpoint)
if err != nil {
return nil, err
}
for _, ensInfo := range ensDetails {
go func(info enstypes.ENSDetails) {
defer gocommon.LogOnPanic()
ch <- m.verifyENSName(info, ethclient)
}(ensInfo)
}
for range ensDetails {
r := <-ch
response[r.PublicKeyString] = r
}
close(ch)
return response, nil
}

View File

@ -0,0 +1,58 @@
package gethbridge
import (
"io"
"github.com/ethereum/go-ethereum/rlp"
waku "github.com/status-im/status-go/waku/common"
"github.com/waku-org/waku-go-bindings/eth-node/types"
)
type wakuEnvelope struct {
env *waku.Envelope
}
// NewWakuEnvelope returns an object that wraps Geth's Waku Envelope in a types interface.
func NewWakuEnvelope(e *waku.Envelope) types.Envelope {
return &wakuEnvelope{env: e}
}
func (w *wakuEnvelope) Unwrap() interface{} {
return w.env
}
func (w *wakuEnvelope) Hash() types.Hash {
return types.Hash(w.env.Hash())
}
func (w *wakuEnvelope) Bloom() []byte {
return w.env.Bloom()
}
func (w *wakuEnvelope) PoW() float64 {
return w.env.PoW()
}
func (w *wakuEnvelope) Expiry() uint32 {
return w.env.Expiry
}
func (w *wakuEnvelope) TTL() uint32 {
return w.env.TTL
}
func (w *wakuEnvelope) Topic() types.TopicType {
return types.TopicType(w.env.Topic)
}
func (w *wakuEnvelope) Size() int {
return len(w.env.Data)
}
func (w *wakuEnvelope) DecodeRLP(s *rlp.Stream) error {
return w.env.DecodeRLP(s)
}
func (w *wakuEnvelope) EncodeRLP(writer io.Writer) error {
return rlp.Encode(writer, w.env)
}

View File

@ -0,0 +1,43 @@
package gethbridge
import (
waku "github.com/status-im/status-go/waku/common"
wakuv2 "github.com/waku-org/waku-go-bindings/common"
"github.com/waku-org/waku-go-bindings/eth-node/types"
)
// NewWakuEnvelopeErrorWrapper returns a types.EnvelopeError object that mimics Geth's EnvelopeError
func NewWakuEnvelopeErrorWrapper(envelopeError *waku.EnvelopeError) *types.EnvelopeError {
if envelopeError == nil {
panic("envelopeError should not be nil")
}
return &types.EnvelopeError{
Hash: types.Hash(envelopeError.Hash),
Code: mapGethErrorCode(envelopeError.Code),
Description: envelopeError.Description,
}
}
// NewWakuEnvelopeErrorWrapper returns a types.EnvelopeError object that mimics Geth's EnvelopeError
func NewWakuV2EnvelopeErrorWrapper(envelopeError *wakuv2.EnvelopeError) *types.EnvelopeError {
if envelopeError == nil {
panic("envelopeError should not be nil")
}
return &types.EnvelopeError{
Hash: types.Hash(envelopeError.Hash),
Code: mapGethErrorCode(envelopeError.Code),
Description: envelopeError.Description,
}
}
func mapGethErrorCode(code uint) uint {
switch code {
case waku.EnvelopeTimeNotSynced:
return types.EnvelopeTimeNotSynced
case waku.EnvelopeOtherError:
return types.EnvelopeOtherError
}
return types.EnvelopeOtherError
}

View File

@ -0,0 +1,57 @@
package gethbridge
import (
"github.com/waku-org/waku-go-bindings/eth-node/types"
"github.com/waku-org/waku-go-bindings/waku"
wakucommon "github.com/status-im/status-go/waku/common"
wakuv2common "github.com/waku-org/waku-go-bindings/common"
)
// NewWakuEnvelopeEventWrapper returns a types.EnvelopeEvent object that mimics Geth's EnvelopeEvent
func NewWakuEnvelopeEventWrapper(envelopeEvent *wakucommon.EnvelopeEvent) *types.EnvelopeEvent {
if envelopeEvent == nil {
panic("envelopeEvent should not be nil")
}
wrappedData := envelopeEvent.Data
switch data := envelopeEvent.Data.(type) {
case []wakucommon.EnvelopeError:
wrappedData := make([]types.EnvelopeError, len(data))
for index := range data {
wrappedData[index] = *NewWakuEnvelopeErrorWrapper(&data[index])
}
case *waku.MailServerResponse:
wrappedData = NewWakuMailServerResponseWrapper(data)
}
return &types.EnvelopeEvent{
Event: types.EventType(envelopeEvent.Event),
Hash: types.Hash(envelopeEvent.Hash),
Batch: types.Hash(envelopeEvent.Batch),
Peer: types.EnodeID(envelopeEvent.Peer),
Data: wrappedData,
}
}
// NewWakuV2EnvelopeEventWrapper returns a types.EnvelopeEvent object that mimics Geth's EnvelopeEvent
func NewWakuV2EnvelopeEventWrapper(envelopeEvent *wakuv2common.EnvelopeEvent) *types.EnvelopeEvent {
if envelopeEvent == nil {
panic("envelopeEvent should not be nil")
}
wrappedData := envelopeEvent.Data
switch data := envelopeEvent.Data.(type) {
case []wakuv2common.EnvelopeError:
wrappedData := make([]types.EnvelopeError, len(data))
for index := range data {
wrappedData[index] = *NewWakuV2EnvelopeErrorWrapper(&data[index])
}
}
return &types.EnvelopeEvent{
Event: types.EventType(envelopeEvent.Event),
Hash: types.Hash(envelopeEvent.Hash),
Batch: types.Hash(envelopeEvent.Batch),
Peer: types.EnodeID(envelopeEvent.Peer),
Data: wrappedData,
}
}

View File

@ -0,0 +1,103 @@
package gethbridge
import (
"crypto/ecdsa"
"errors"
"strings"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/extkeys"
"github.com/waku-org/waku-go-bindings/eth-node/types"
)
type gethKeyStoreAdapter struct {
keystore *keystore.KeyStore
}
// WrapKeyStore creates a types.KeyStore wrapper over a keystore.KeyStore object
func WrapKeyStore(keystore *keystore.KeyStore) types.KeyStore {
return &gethKeyStoreAdapter{keystore: keystore}
}
func (k *gethKeyStoreAdapter) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (types.Account, error) {
gethAccount, err := k.keystore.ImportECDSA(priv, passphrase)
return accountFrom(gethAccount), err
}
func (k *gethKeyStoreAdapter) ImportSingleExtendedKey(extKey *extkeys.ExtendedKey, passphrase string) (types.Account, error) {
gethAccount, err := k.keystore.ImportSingleExtendedKey(extKey, passphrase)
return accountFrom(gethAccount), err
}
func (k *gethKeyStoreAdapter) ImportExtendedKeyForPurpose(keyPurpose extkeys.KeyPurpose, extKey *extkeys.ExtendedKey, passphrase string) (types.Account, error) {
gethAccount, err := k.keystore.ImportExtendedKeyForPurpose(keyPurpose, extKey, passphrase)
return accountFrom(gethAccount), err
}
func (k *gethKeyStoreAdapter) AccountDecryptedKey(a types.Account, auth string) (types.Account, *types.Key, error) {
gethAccount, err := gethAccountFrom(a)
if err != nil {
return types.Account{}, nil, err
}
var gethKey *keystore.Key
gethAccount, gethKey, err = k.keystore.AccountDecryptedKey(gethAccount, auth)
return accountFrom(gethAccount), keyFrom(gethKey), err
}
func (k *gethKeyStoreAdapter) Delete(a types.Account) error {
gethAccount, err := gethAccountFrom(a)
if err != nil {
return err
}
return k.keystore.Delete(gethAccount)
}
// parseGethURL converts a user supplied URL into the accounts specific structure.
func parseGethURL(url string) (accounts.URL, error) {
parts := strings.Split(url, "://")
if len(parts) != 2 || parts[0] == "" {
return accounts.URL{}, errors.New("protocol scheme missing")
}
return accounts.URL{
Scheme: parts[0],
Path: parts[1],
}, nil
}
func gethAccountFrom(account types.Account) (accounts.Account, error) {
var (
gethAccount accounts.Account
err error
)
gethAccount.Address = common.Address(account.Address)
if account.URL != "" {
gethAccount.URL, err = parseGethURL(account.URL)
}
return gethAccount, err
}
func accountFrom(gethAccount accounts.Account) types.Account {
return types.Account{
Address: types.Address(gethAccount.Address),
URL: gethAccount.URL.String(),
}
}
func keyFrom(k *keystore.Key) *types.Key {
if k == nil {
return nil
}
return &types.Key{
ID: k.Id,
Address: types.Address(k.Address),
PrivateKey: k.PrivateKey,
ExtendedKey: k.ExtendedKey,
SubAccountIndex: k.SubAccountIndex,
}
}

View File

@ -0,0 +1,19 @@
package gethbridge
import (
"github.com/waku-org/waku-go-bindings/eth-node/types"
"github.com/waku-org/waku-go-bindings/waku"
)
// NewWakuMailServerResponseWrapper returns a types.MailServerResponse object that mimics Geth's MailServerResponse
func NewWakuMailServerResponseWrapper(mailServerResponse *waku.MailServerResponse) *types.MailServerResponse {
if mailServerResponse == nil {
panic("mailServerResponse should not be nil")
}
return &types.MailServerResponse{
LastEnvelopeHash: types.Hash(mailServerResponse.LastEnvelopeHash),
Cursor: mailServerResponse.Cursor,
Error: mailServerResponse.Error,
}
}

View File

@ -0,0 +1,88 @@
package gethbridge
import (
"errors"
"go.uber.org/zap"
"github.com/status-im/status-go/waku"
"github.com/status-im/status-go/wakuv2"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p/enode"
gethens "github.com/waku-org/waku-go-bindings/eth-node/bridge/geth/ens"
"github.com/waku-org/waku-go-bindings/eth-node/types"
enstypes "github.com/waku-org/waku-go-bindings/eth-node/types/ens"
)
type gethNodeWrapper struct {
stack *node.Node
waku1 *waku.Waku
waku2 *wakuv2.Waku
}
func NewNodeBridge(stack *node.Node, waku1 *waku.Waku, waku2 *wakuv2.Waku) types.Node {
return &gethNodeWrapper{stack: stack, waku1: waku1, waku2: waku2}
}
func (w *gethNodeWrapper) Poll() {
// noop
}
func (w *gethNodeWrapper) NewENSVerifier(logger *zap.Logger) enstypes.ENSVerifier {
return gethens.NewVerifier(logger)
}
func (w *gethNodeWrapper) SetWaku1(waku *waku.Waku) {
w.waku1 = waku
}
func (w *gethNodeWrapper) SetWaku2(waku *wakuv2.Waku) {
w.waku2 = waku
}
func (w *gethNodeWrapper) GetWaku(ctx interface{}) (types.Waku, error) {
if w.waku1 == nil {
return nil, errors.New("waku service is not available")
}
return NewGethWakuWrapper(w.waku1), nil
}
func (w *gethNodeWrapper) GetWakuV2(ctx interface{}) (types.Waku, error) {
if w.waku2 == nil {
return nil, errors.New("waku service is not available")
}
return NewGethWakuV2Wrapper(w.waku2), nil
}
func (w *gethNodeWrapper) AddPeer(url string) error {
parsedNode, err := enode.ParseV4(url)
if err != nil {
return err
}
w.stack.Server().AddPeer(parsedNode)
return nil
}
func (w *gethNodeWrapper) RemovePeer(url string) error {
parsedNode, err := enode.ParseV4(url)
if err != nil {
return err
}
w.stack.Server().RemovePeer(parsedNode)
return nil
}
func (w *gethNodeWrapper) PeersCount() int {
if w.stack.Server() == nil {
return 0
}
return len(w.stack.Server().Peers())
}

View File

@ -0,0 +1,109 @@
package gethbridge
import (
"context"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/status-im/status-go/waku"
wakucommon "github.com/status-im/status-go/waku/common"
"github.com/waku-org/waku-go-bindings/eth-node/types"
)
type GethPublicWakuAPIWrapper struct {
api *waku.PublicWakuAPI
}
// NewGethPublicWakuAPIWrapper returns an object that wraps Geth's PublicWakuAPI in a types interface
func NewGethPublicWakuAPIWrapper(api *waku.PublicWakuAPI) types.PublicWakuAPI {
if api == nil {
panic("PublicWakuAPI cannot be nil")
}
return &GethPublicWakuAPIWrapper{
api: api,
}
}
// AddPrivateKey imports the given private key.
func (w *GethPublicWakuAPIWrapper) AddPrivateKey(ctx context.Context, privateKey types.HexBytes) (string, error) {
return w.api.AddPrivateKey(ctx, hexutil.Bytes(privateKey))
}
// GenerateSymKeyFromPassword derives a key from the given password, stores it, and returns its ID.
func (w *GethPublicWakuAPIWrapper) GenerateSymKeyFromPassword(ctx context.Context, passwd string) (string, error) {
return w.api.GenerateSymKeyFromPassword(ctx, passwd)
}
// DeleteKeyPair removes the key with the given key if it exists.
func (w *GethPublicWakuAPIWrapper) DeleteKeyPair(ctx context.Context, key string) (bool, error) {
return w.api.DeleteKeyPair(ctx, key)
}
// NewMessageFilter creates a new filter that can be used to poll for
// (new) messages that satisfy the given criteria.
func (w *GethPublicWakuAPIWrapper) NewMessageFilter(req types.Criteria) (string, error) {
topics := make([]wakucommon.TopicType, len(req.Topics))
for index, tt := range req.Topics {
topics[index] = wakucommon.TopicType(tt)
}
criteria := waku.Criteria{
SymKeyID: req.SymKeyID,
PrivateKeyID: req.PrivateKeyID,
Sig: req.Sig,
MinPow: req.MinPow,
Topics: topics,
AllowP2P: req.AllowP2P,
}
return w.api.NewMessageFilter(criteria)
}
func (w *GethPublicWakuAPIWrapper) BloomFilter() []byte {
return w.api.BloomFilter()
}
// GetFilterMessages returns the messages that match the filter criteria and
// are received between the last poll and now.
func (w *GethPublicWakuAPIWrapper) GetFilterMessages(id string) ([]*types.Message, error) {
msgs, err := w.api.GetFilterMessages(id)
if err != nil {
return nil, err
}
wrappedMsgs := make([]*types.Message, len(msgs))
for index, msg := range msgs {
wrappedMsgs[index] = &types.Message{
Sig: msg.Sig,
TTL: msg.TTL,
Timestamp: msg.Timestamp,
Topic: types.TopicType(msg.Topic),
Payload: msg.Payload,
Padding: msg.Padding,
PoW: msg.PoW,
Hash: msg.Hash,
Dst: msg.Dst,
P2P: msg.P2P,
}
}
return wrappedMsgs, nil
}
// Post posts a message on the network.
// returns the hash of the message in case of success.
func (w *GethPublicWakuAPIWrapper) Post(ctx context.Context, req types.NewMessage) ([]byte, error) {
msg := waku.NewMessage{
SymKeyID: req.SymKeyID,
PublicKey: req.PublicKey,
Sig: req.SigID, // Sig is really a SigID
TTL: req.TTL,
Topic: wakucommon.TopicType(req.Topic),
Payload: req.Payload,
Padding: req.Padding,
PowTime: req.PowTime,
PowTarget: req.PowTarget,
TargetPeer: req.TargetPeer,
Ephemeral: req.Ephemeral,
}
return w.api.Post(ctx, msg)
}

View File

@ -0,0 +1,105 @@
package gethbridge
import (
"context"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/status-im/status-go/wakuv2"
wakucommon "github.com/waku-org/waku-go-bindings/common"
"github.com/waku-org/waku-go-bindings/eth-node/types"
)
type gethPublicWakuV2APIWrapper struct {
api *wakuv2.PublicWakuAPI
}
// NewGethPublicWakuAPIWrapper returns an object that wraps Geth's PublicWakuAPI in a types interface
func NewGethPublicWakuV2APIWrapper(api *wakuv2.PublicWakuAPI) types.PublicWakuAPI {
if api == nil {
panic("PublicWakuV2API cannot be nil")
}
return &gethPublicWakuV2APIWrapper{
api: api,
}
}
// AddPrivateKey imports the given private key.
func (w *gethPublicWakuV2APIWrapper) AddPrivateKey(ctx context.Context, privateKey types.HexBytes) (string, error) {
return w.api.AddPrivateKey(ctx, hexutil.Bytes(privateKey))
}
// GenerateSymKeyFromPassword derives a key from the given password, stores it, and returns its ID.
func (w *gethPublicWakuV2APIWrapper) GenerateSymKeyFromPassword(ctx context.Context, passwd string) (string, error) {
return w.api.GenerateSymKeyFromPassword(ctx, passwd)
}
// DeleteKeyPair removes the key with the given key if it exists.
func (w *gethPublicWakuV2APIWrapper) DeleteKeyPair(ctx context.Context, key string) (bool, error) {
return w.api.DeleteKeyPair(ctx, key)
}
func (w *gethPublicWakuV2APIWrapper) BloomFilter() []byte {
return w.api.BloomFilter()
}
// NewMessageFilter creates a new filter that can be used to poll for
// (new) messages that satisfy the given criteria.
func (w *gethPublicWakuV2APIWrapper) NewMessageFilter(req types.Criteria) (string, error) {
topics := make([]wakucommon.TopicType, len(req.Topics))
for index, tt := range req.Topics {
topics[index] = wakucommon.TopicType(tt)
}
criteria := wakuv2.Criteria{
SymKeyID: req.SymKeyID,
PrivateKeyID: req.PrivateKeyID,
Sig: req.Sig,
PubsubTopic: req.PubsubTopic,
ContentTopics: topics,
}
return w.api.NewMessageFilter(criteria)
}
// GetFilterMessages returns the messages that match the filter criteria and
// are received between the last poll and now.
func (w *gethPublicWakuV2APIWrapper) GetFilterMessages(id string) ([]*types.Message, error) {
msgs, err := w.api.GetFilterMessages(id)
if err != nil {
return nil, err
}
wrappedMsgs := make([]*types.Message, len(msgs))
for index, msg := range msgs {
wrappedMsgs[index] = &types.Message{
Sig: msg.Sig,
Timestamp: msg.Timestamp,
PubsubTopic: msg.PubsubTopic,
Topic: types.TopicType(msg.ContentTopic),
Payload: msg.Payload,
Padding: msg.Padding,
Hash: msg.Hash,
Dst: msg.Dst,
}
}
return wrappedMsgs, nil
}
// Post posts a message on the network.
// returns the hash of the message in case of success.
func (w *gethPublicWakuV2APIWrapper) Post(ctx context.Context, req types.NewMessage) ([]byte, error) {
msg := wakuv2.NewMessage{
SymKeyID: req.SymKeyID,
PublicKey: req.PublicKey,
Sig: req.SigID, // Sig is really a SigID
PubsubTopic: req.PubsubTopic,
ContentTopic: wakucommon.TopicType(req.Topic),
Payload: req.Payload,
Padding: req.Padding,
TargetPeer: req.TargetPeer,
Ephemeral: req.Ephemeral,
Priority: req.Priority,
}
return w.api.Post(ctx, msg)
}

View File

@ -0,0 +1,30 @@
package gethbridge
import (
"github.com/ethereum/go-ethereum/event"
"github.com/waku-org/waku-go-bindings/eth-node/types"
)
type gethSubscriptionWrapper struct {
subscription event.Subscription
}
// NewGethSubscriptionWrapper returns an object that wraps Geth's Subscription in a types interface
func NewGethSubscriptionWrapper(subscription event.Subscription) types.Subscription {
if subscription == nil {
panic("subscription cannot be nil")
}
return &gethSubscriptionWrapper{
subscription: subscription,
}
}
func (w *gethSubscriptionWrapper) Err() <-chan error {
return w.subscription.Err()
}
func (w *gethSubscriptionWrapper) Unsubscribe() {
w.subscription.Unsubscribe()
}

View File

@ -0,0 +1,365 @@
package gethbridge
import (
"context"
"crypto/ecdsa"
"errors"
"time"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/multiformats/go-multiaddr"
"github.com/waku-org/go-waku/waku/v2/api/history"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/status-im/status-go/waku"
wakucommon "github.com/status-im/status-go/waku/common"
gocommon "github.com/waku-org/waku-go-bindings/common"
"github.com/waku-org/waku-go-bindings/connection"
"github.com/waku-org/waku-go-bindings/eth-node/types"
)
type GethWakuWrapper struct {
waku *waku.Waku
}
// NewGethWakuWrapper returns an object that wraps Geth's Waku in a types interface
func NewGethWakuWrapper(w *waku.Waku) types.Waku {
if w == nil {
panic("waku cannot be nil")
}
return &GethWakuWrapper{
waku: w,
}
}
// GetGethWhisperFrom retrieves the underlying whisper Whisper struct from a wrapped Whisper interface
func GetGethWakuFrom(m types.Waku) *waku.Waku {
return m.(*GethWakuWrapper).waku
}
func (w *GethWakuWrapper) PublicWakuAPI() types.PublicWakuAPI {
return NewGethPublicWakuAPIWrapper(waku.NewPublicWakuAPI(w.waku))
}
func (w *GethWakuWrapper) Version() uint {
return 1
}
// Added for compatibility with waku V2
func (w *GethWakuWrapper) PeerCount() int {
return -1
}
// Added for compatibility with waku V2
func (w *GethWakuWrapper) StartDiscV5() error {
return errors.New("not available in WakuV1")
}
// Added for compatibility with waku V2
func (w *GethWakuWrapper) StopDiscV5() error {
return errors.New("not available in WakuV1")
}
// SubscribeToPubsubTopic function only added for compatibility with waku V2
func (w *GethWakuWrapper) SubscribeToPubsubTopic(topic string, optPublicKey *ecdsa.PublicKey) error {
// not available in WakuV1
return errors.New("not available in WakuV1")
}
func (w *GethWakuWrapper) UnsubscribeFromPubsubTopic(topic string) error {
// not available in WakuV1
return errors.New("not available in WakuV1")
}
func (w *GethWakuWrapper) RetrievePubsubTopicKey(topic string) (*ecdsa.PrivateKey, error) {
// not available in WakuV1
return nil, errors.New("not available in WakuV1")
}
func (w *GethWakuWrapper) StorePubsubTopicKey(topic string, privKey *ecdsa.PrivateKey) error {
// not available in WakuV1
return errors.New("not available in WakuV1")
}
func (w *GethWakuWrapper) RemovePubsubTopicKey(topic string) error {
// not available in WakuV1
return errors.New("not available in WakuV1")
}
// AddRelayPeer function only added for compatibility with waku V2
func (w *GethWakuWrapper) AddRelayPeer(address multiaddr.Multiaddr) (peer.ID, error) {
return "", errors.New("not available in WakuV1")
}
// DialPeer function only added for compatibility with waku V2
func (w *GethWakuWrapper) DialPeer(address multiaddr.Multiaddr) error {
return errors.New("not available in WakuV1")
}
// DialPeerByID function only added for compatibility with waku V2
func (w *GethWakuWrapper) DialPeerByID(peerID peer.ID) error {
return errors.New("not available in WakuV1")
}
// ListenAddresses function only added for compatibility with waku V2
func (w *GethWakuWrapper) ListenAddresses() ([]multiaddr.Multiaddr, error) {
return nil, errors.New("not available in WakuV1")
}
func (w *GethWakuWrapper) RelayPeersByTopic(topic string) (*types.PeerList, error) {
return nil, errors.New("not available in WakuV1")
}
// ENR function only added for compatibility with waku V2
func (w *GethWakuWrapper) ENR() (*enode.Node, error) {
return nil, errors.New("not available in WakuV1")
}
// PeerCount function only added for compatibility with waku V2
func (w *GethWakuWrapper) DropPeer(peerID peer.ID) error {
return errors.New("not available in WakuV1")
}
func (w *GethWakuWrapper) SubscribeToConnStatusChanges() (*types.ConnStatusSubscription, error) {
return nil, errors.New("not available in WakuV1")
}
func (w *GethWakuWrapper) SetCriteriaForMissingMessageVerification(peerID peer.ID, pubsubTopic string, contentTopics []types.TopicType) error {
return errors.New("not available in WakuV1")
}
// Peers function only added for compatibility with waku V2
func (w *GethWakuWrapper) Peers() types.PeerStats {
p := make(types.PeerStats)
return p
}
// MinPow returns the PoW value required by this node.
func (w *GethWakuWrapper) MinPow() float64 {
return w.waku.MinPow()
}
// MaxMessageSize returns the MaxMessageSize set
func (w *GethWakuWrapper) MaxMessageSize() uint32 {
return w.waku.MaxMessageSize()
}
// BloomFilter returns the aggregated bloom filter for all the topics of interest.
// The nodes are required to send only messages that match the advertised bloom filter.
// If a message does not match the bloom, it will tantamount to spam, and the peer will
// be disconnected.
func (w *GethWakuWrapper) BloomFilter() []byte {
return w.waku.BloomFilter()
}
// GetCurrentTime returns current time.
func (w *GethWakuWrapper) GetCurrentTime() time.Time {
return w.waku.CurrentTime()
}
func (w *GethWakuWrapper) SubscribeEnvelopeEvents(eventsProxy chan<- types.EnvelopeEvent) types.Subscription {
events := make(chan wakucommon.EnvelopeEvent, 100) // must be buffered to prevent blocking whisper
go func() {
defer gocommon.LogOnPanic()
for e := range events {
eventsProxy <- *NewWakuEnvelopeEventWrapper(&e)
}
}()
return NewGethSubscriptionWrapper(w.waku.SubscribeEnvelopeEvents(events))
}
func (w *GethWakuWrapper) GetPrivateKey(id string) (*ecdsa.PrivateKey, error) {
return w.waku.GetPrivateKey(id)
}
// AddKeyPair imports a asymmetric private key and returns a deterministic identifier.
func (w *GethWakuWrapper) AddKeyPair(key *ecdsa.PrivateKey) (string, error) {
return w.waku.AddKeyPair(key)
}
// DeleteKeyPair deletes the key with the specified ID if it exists.
func (w *GethWakuWrapper) DeleteKeyPair(keyID string) bool {
return w.waku.DeleteKeyPair(keyID)
}
func (w *GethWakuWrapper) AddSymKeyDirect(key []byte) (string, error) {
return w.waku.AddSymKeyDirect(key)
}
func (w *GethWakuWrapper) AddSymKeyFromPassword(password string) (string, error) {
return w.waku.AddSymKeyFromPassword(password)
}
func (w *GethWakuWrapper) DeleteSymKey(id string) bool {
return w.waku.DeleteSymKey(id)
}
func (w *GethWakuWrapper) GetSymKey(id string) ([]byte, error) {
return w.waku.GetSymKey(id)
}
func (w *GethWakuWrapper) Subscribe(opts *types.SubscriptionOptions) (string, error) {
var (
err error
keyAsym *ecdsa.PrivateKey
keySym []byte
)
if opts.SymKeyID != "" {
keySym, err = w.GetSymKey(opts.SymKeyID)
if err != nil {
return "", err
}
}
if opts.PrivateKeyID != "" {
keyAsym, err = w.GetPrivateKey(opts.PrivateKeyID)
if err != nil {
return "", err
}
}
f, err := w.createFilterWrapper("", keyAsym, keySym, opts.PoW, opts.Topics)
if err != nil {
return "", err
}
id, err := w.waku.Subscribe(GetWakuFilterFrom(f))
if err != nil {
return "", err
}
f.(*wakuFilterWrapper).id = id
return id, nil
}
func (w *GethWakuWrapper) GetStats() types.StatsSummary {
return w.waku.GetStats()
}
func (w *GethWakuWrapper) GetFilter(id string) types.Filter {
return NewWakuFilterWrapper(w.waku.GetFilter(id), id)
}
func (w *GethWakuWrapper) Unsubscribe(ctx context.Context, id string) error {
return w.waku.Unsubscribe(id)
}
func (w *GethWakuWrapper) UnsubscribeMany(ids []string) error {
return w.waku.UnsubscribeMany(ids)
}
func (w *GethWakuWrapper) createFilterWrapper(id string, keyAsym *ecdsa.PrivateKey, keySym []byte, pow float64, topics [][]byte) (types.Filter, error) {
return NewWakuFilterWrapper(&wakucommon.Filter{
KeyAsym: keyAsym,
KeySym: keySym,
PoW: pow,
AllowP2P: true,
Topics: topics,
Messages: wakucommon.NewMemoryMessageStore(),
}, id), nil
}
func (w *GethWakuWrapper) ProcessingP2PMessages() bool {
return w.waku.ProcessingP2PMessages()
}
func (w *GethWakuWrapper) MarkP2PMessageAsProcessed(hash common.Hash) {
w.waku.MarkP2PMessageAsProcessed(hash)
}
func (w *GethWakuWrapper) ConnectionChanged(_ connection.State) {}
func (w *GethWakuWrapper) ClearEnvelopesCache() {
w.waku.ClearEnvelopesCache()
}
type wakuFilterWrapper struct {
filter *wakucommon.Filter
id string
}
// NewWakuFilterWrapper returns an object that wraps Geth's Filter in a types interface
func NewWakuFilterWrapper(f *wakucommon.Filter, id string) types.Filter {
if f.Messages == nil {
panic("Messages should not be nil")
}
return &wakuFilterWrapper{
filter: f,
id: id,
}
}
// GetWakuFilterFrom retrieves the underlying whisper Filter struct from a wrapped Filter interface
func GetWakuFilterFrom(f types.Filter) *wakucommon.Filter {
return f.(*wakuFilterWrapper).filter
}
// ID returns the filter ID
func (w *wakuFilterWrapper) ID() string {
return w.id
}
func (w *GethWakuWrapper) ConfirmMessageDelivered(hashes []common.Hash) {
}
func (w *GethWakuWrapper) PeerID() peer.ID {
panic("not available in WakuV1")
}
func (w *GethWakuWrapper) GetActiveStorenode() peer.ID {
panic("not available in WakuV1")
}
func (w *GethWakuWrapper) OnStorenodeAvailableOneShot() <-chan struct{} {
panic("not available in WakuV1")
}
func (w *GethWakuWrapper) OnStorenodeChanged() <-chan peer.ID {
panic("not available in WakuV1")
}
func (w *GethWakuWrapper) OnStorenodeNotWorking() <-chan struct{} {
panic("not available in WakuV1")
}
func (w *GethWakuWrapper) OnStorenodeAvailable() <-chan peer.ID {
panic("not available in WakuV1")
}
func (w *GethWakuWrapper) WaitForAvailableStoreNode(timeout time.Duration) bool {
return false
}
func (w *GethWakuWrapper) SetStorenodeConfigProvider(c history.StorenodeConfigProvider) {
panic("not available in WakuV1")
}
func (w *GethWakuWrapper) ProcessMailserverBatch(
ctx context.Context,
batch types.MailserverBatch,
storenodeID peer.ID,
pageLimit uint64,
shouldProcessNextPage func(int) (bool, uint64),
processEnvelopes bool,
) error {
return errors.New("not available in WakuV1")
}
func (w *GethWakuWrapper) IsStorenodeAvailable(peerID peer.ID) bool {
panic("not available in WakuV1")
}
func (w *GethWakuWrapper) PerformStorenodeTask(fn func() error, opts ...history.StorenodeTaskOption) error {
panic("not available in WakuV1")
}
func (w *GethWakuWrapper) DisconnectActiveStorenode(ctx context.Context, backoff time.Duration, shouldCycle bool) {
panic("not available in WakuV1")
}

View File

@ -0,0 +1,378 @@
package gethbridge
import (
"context"
"crypto/ecdsa"
"time"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/multiformats/go-multiaddr"
"google.golang.org/protobuf/proto"
"github.com/waku-org/go-waku/waku/v2/api/history"
"github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/store"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/status-im/status-go/wakuv2"
gocommon "github.com/waku-org/waku-go-bindings/common"
wakucommon "github.com/waku-org/waku-go-bindings/common"
"github.com/waku-org/waku-go-bindings/connection"
"github.com/waku-org/waku-go-bindings/eth-node/types"
)
type gethWakuV2Wrapper struct {
waku *wakuv2.Waku
}
// NewGethWakuWrapper returns an object that wraps Geth's Waku in a types interface
func NewGethWakuV2Wrapper(w *wakuv2.Waku) types.Waku {
if w == nil {
panic("waku cannot be nil")
}
return &gethWakuV2Wrapper{
waku: w,
}
}
// GetGethWhisperFrom retrieves the underlying whisper Whisper struct from a wrapped Whisper interface
func GetGethWakuV2From(m types.Waku) *wakuv2.Waku {
return m.(*gethWakuV2Wrapper).waku
}
func (w *gethWakuV2Wrapper) PublicWakuAPI() types.PublicWakuAPI {
return NewGethPublicWakuV2APIWrapper(wakuv2.NewPublicWakuAPI(w.waku))
}
func (w *gethWakuV2Wrapper) Version() uint {
return 2
}
func (w *gethWakuV2Wrapper) PeerCount() int {
return w.waku.PeerCount()
}
// DEPRECATED: Not used in WakuV2
func (w *gethWakuV2Wrapper) MinPow() float64 {
return 0
}
// MaxMessageSize returns the MaxMessageSize set
func (w *gethWakuV2Wrapper) MaxMessageSize() uint32 {
return w.waku.MaxMessageSize()
}
// DEPRECATED: not used in WakuV2
func (w *gethWakuV2Wrapper) BloomFilter() []byte {
return nil
}
// GetCurrentTime returns current time.
func (w *gethWakuV2Wrapper) GetCurrentTime() time.Time {
return w.waku.CurrentTime()
}
func (w *gethWakuV2Wrapper) SubscribeEnvelopeEvents(eventsProxy chan<- types.EnvelopeEvent) types.Subscription {
events := make(chan wakucommon.EnvelopeEvent, 100) // must be buffered to prevent blocking whisper
go func() {
defer gocommon.LogOnPanic()
for e := range events {
eventsProxy <- *NewWakuV2EnvelopeEventWrapper(&e)
}
}()
return NewGethSubscriptionWrapper(w.waku.SubscribeEnvelopeEvents(events))
}
func (w *gethWakuV2Wrapper) GetPrivateKey(id string) (*ecdsa.PrivateKey, error) {
return w.waku.GetPrivateKey(id)
}
// AddKeyPair imports a asymmetric private key and returns a deterministic identifier.
func (w *gethWakuV2Wrapper) AddKeyPair(key *ecdsa.PrivateKey) (string, error) {
return w.waku.AddKeyPair(key)
}
// DeleteKeyPair deletes the key with the specified ID if it exists.
func (w *gethWakuV2Wrapper) DeleteKeyPair(keyID string) bool {
return w.waku.DeleteKeyPair(keyID)
}
func (w *gethWakuV2Wrapper) AddSymKeyDirect(key []byte) (string, error) {
return w.waku.AddSymKeyDirect(key)
}
func (w *gethWakuV2Wrapper) AddSymKeyFromPassword(password string) (string, error) {
return w.waku.AddSymKeyFromPassword(password)
}
func (w *gethWakuV2Wrapper) DeleteSymKey(id string) bool {
return w.waku.DeleteSymKey(id)
}
func (w *gethWakuV2Wrapper) GetSymKey(id string) ([]byte, error) {
return w.waku.GetSymKey(id)
}
func (w *gethWakuV2Wrapper) Subscribe(opts *types.SubscriptionOptions) (string, error) {
var (
err error
keyAsym *ecdsa.PrivateKey
keySym []byte
)
if opts.SymKeyID != "" {
keySym, err = w.GetSymKey(opts.SymKeyID)
if err != nil {
return "", err
}
}
if opts.PrivateKeyID != "" {
keyAsym, err = w.GetPrivateKey(opts.PrivateKeyID)
if err != nil {
return "", err
}
}
f, err := w.createFilterWrapper("", keyAsym, keySym, opts.PubsubTopic, opts.Topics)
if err != nil {
return "", err
}
id, err := w.waku.Subscribe(GetWakuV2FilterFrom(f))
if err != nil {
return "", err
}
f.(*wakuV2FilterWrapper).id = id
return id, nil
}
func (w *gethWakuV2Wrapper) GetStats() types.StatsSummary {
return w.waku.GetStats()
}
func (w *gethWakuV2Wrapper) GetFilter(id string) types.Filter {
return NewWakuV2FilterWrapper(w.waku.GetFilter(id), id)
}
func (w *gethWakuV2Wrapper) Unsubscribe(ctx context.Context, id string) error {
return w.waku.Unsubscribe(ctx, id)
}
func (w *gethWakuV2Wrapper) UnsubscribeMany(ids []string) error {
return w.waku.UnsubscribeMany(ids)
}
func (w *gethWakuV2Wrapper) createFilterWrapper(id string, keyAsym *ecdsa.PrivateKey, keySym []byte, pubsubTopic string, topics [][]byte) (types.Filter, error) {
return NewWakuV2FilterWrapper(&wakucommon.Filter{
KeyAsym: keyAsym,
KeySym: keySym,
ContentTopics: wakucommon.NewTopicSetFromBytes(topics),
PubsubTopic: pubsubTopic,
Messages: wakucommon.NewMemoryMessageStore(),
}, id), nil
}
func (w *gethWakuV2Wrapper) StartDiscV5() error {
return w.waku.StartDiscV5()
}
func (w *gethWakuV2Wrapper) StopDiscV5() error {
return w.waku.StopDiscV5()
}
// Subscribe to a pubsub topic, passing an optional public key if the pubsub topic is protected
func (w *gethWakuV2Wrapper) SubscribeToPubsubTopic(topic string, optPublicKey *ecdsa.PublicKey) error {
return w.waku.SubscribeToPubsubTopic(topic, optPublicKey)
}
func (w *gethWakuV2Wrapper) UnsubscribeFromPubsubTopic(topic string) error {
return w.waku.UnsubscribeFromPubsubTopic(topic)
}
func (w *gethWakuV2Wrapper) RetrievePubsubTopicKey(topic string) (*ecdsa.PrivateKey, error) {
return w.waku.RetrievePubsubTopicKey(topic)
}
func (w *gethWakuV2Wrapper) StorePubsubTopicKey(topic string, privKey *ecdsa.PrivateKey) error {
return w.waku.StorePubsubTopicKey(topic, privKey)
}
func (w *gethWakuV2Wrapper) RemovePubsubTopicKey(topic string) error {
return w.waku.RemovePubsubTopicKey(topic)
}
func (w *gethWakuV2Wrapper) AddRelayPeer(address multiaddr.Multiaddr) (peer.ID, error) {
return w.waku.AddRelayPeer(address)
}
func (w *gethWakuV2Wrapper) Peers() types.PeerStats {
return w.waku.Peers()
}
func (w *gethWakuV2Wrapper) DialPeer(address multiaddr.Multiaddr) error {
return w.waku.DialPeer(address)
}
func (w *gethWakuV2Wrapper) DialPeerByID(peerID peer.ID) error {
return w.waku.DialPeerByID(peerID)
}
func (w *gethWakuV2Wrapper) ListenAddresses() ([]multiaddr.Multiaddr, error) {
return w.waku.ListenAddresses()
}
func (w *gethWakuV2Wrapper) RelayPeersByTopic(topic string) (*types.PeerList, error) {
return w.waku.RelayPeersByTopic(topic)
}
func (w *gethWakuV2Wrapper) ENR() (*enode.Node, error) {
return w.waku.ENR()
}
func (w *gethWakuV2Wrapper) DropPeer(peerID peer.ID) error {
return w.waku.DropPeer(peerID)
}
func (w *gethWakuV2Wrapper) ProcessingP2PMessages() bool {
return w.waku.ProcessingP2PMessages()
}
func (w *gethWakuV2Wrapper) MarkP2PMessageAsProcessed(hash common.Hash) {
w.waku.MarkP2PMessageAsProcessed(hash)
}
func (w *gethWakuV2Wrapper) SubscribeToConnStatusChanges() (*types.ConnStatusSubscription, error) {
return w.waku.SubscribeToConnStatusChanges(), nil
}
func (w *gethWakuV2Wrapper) SetCriteriaForMissingMessageVerification(peerID peer.ID, pubsubTopic string, contentTopics []types.TopicType) error {
var cTopics []string
for _, ct := range contentTopics {
cTopics = append(cTopics, wakucommon.BytesToTopic(ct.Bytes()).ContentTopic())
}
pubsubTopic = w.waku.GetPubsubTopic(pubsubTopic)
w.waku.SetTopicsToVerifyForMissingMessages(peerID, pubsubTopic, cTopics)
// No err can be be generated by this function. The function returns an error
// Just so there's compatibility with GethWakuWrapper from V1
return nil
}
func (w *gethWakuV2Wrapper) ConnectionChanged(state connection.State) {
w.waku.ConnectionChanged(state)
}
func (w *gethWakuV2Wrapper) ClearEnvelopesCache() {
w.waku.ClearEnvelopesCache()
}
type wakuV2FilterWrapper struct {
filter *wakucommon.Filter
id string
}
// NewWakuFilterWrapper returns an object that wraps Geth's Filter in a types interface
func NewWakuV2FilterWrapper(f *wakucommon.Filter, id string) types.Filter {
if f.Messages == nil {
panic("Messages should not be nil")
}
return &wakuV2FilterWrapper{
filter: f,
id: id,
}
}
// GetWakuFilterFrom retrieves the underlying whisper Filter struct from a wrapped Filter interface
func GetWakuV2FilterFrom(f types.Filter) *wakucommon.Filter {
return f.(*wakuV2FilterWrapper).filter
}
// ID returns the filter ID
func (w *wakuV2FilterWrapper) ID() string {
return w.id
}
func (w *gethWakuV2Wrapper) ConfirmMessageDelivered(hashes []common.Hash) {
w.waku.ConfirmMessageDelivered(hashes)
}
func (w *gethWakuV2Wrapper) PeerID() peer.ID {
return w.waku.PeerID()
}
func (w *gethWakuV2Wrapper) GetActiveStorenode() peer.ID {
return w.waku.StorenodeCycle.GetActiveStorenode()
}
func (w *gethWakuV2Wrapper) OnStorenodeAvailableOneShot() <-chan struct{} {
return w.waku.StorenodeCycle.StorenodeAvailableOneshotEmitter.Subscribe()
}
func (w *gethWakuV2Wrapper) OnStorenodeChanged() <-chan peer.ID {
return w.waku.StorenodeCycle.StorenodeChangedEmitter.Subscribe()
}
func (w *gethWakuV2Wrapper) OnStorenodeNotWorking() <-chan struct{} {
return w.waku.StorenodeCycle.StorenodeNotWorkingEmitter.Subscribe()
}
func (w *gethWakuV2Wrapper) OnStorenodeAvailable() <-chan peer.ID {
return w.waku.StorenodeCycle.StorenodeAvailableEmitter.Subscribe()
}
func (w *gethWakuV2Wrapper) WaitForAvailableStoreNode(timeout time.Duration) bool {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return w.waku.StorenodeCycle.WaitForAvailableStoreNode(ctx)
}
func (w *gethWakuV2Wrapper) SetStorenodeConfigProvider(c history.StorenodeConfigProvider) {
w.waku.StorenodeCycle.SetStorenodeConfigProvider(c)
}
func (w *gethWakuV2Wrapper) ProcessMailserverBatch(
ctx context.Context,
batch types.MailserverBatch,
storenodeID peer.ID,
pageLimit uint64,
shouldProcessNextPage func(int) (bool, uint64),
processEnvelopes bool,
) error {
pubsubTopic := w.waku.GetPubsubTopic(batch.PubsubTopic)
contentTopics := []string{}
for _, topic := range batch.Topics {
contentTopics = append(contentTopics, wakucommon.BytesToTopic(topic.Bytes()).ContentTopic())
}
criteria := store.FilterCriteria{
TimeStart: proto.Int64(int64(batch.From) * int64(time.Second)),
TimeEnd: proto.Int64(int64(batch.To) * int64(time.Second)),
ContentFilter: protocol.NewContentFilter(pubsubTopic, contentTopics...),
}
return w.waku.HistoryRetriever.Query(ctx, criteria, storenodeID, pageLimit, shouldProcessNextPage, processEnvelopes)
}
func (w *gethWakuV2Wrapper) IsStorenodeAvailable(peerID peer.ID) bool {
return w.waku.StorenodeCycle.IsStorenodeAvailable(peerID)
}
func (w *gethWakuV2Wrapper) PerformStorenodeTask(fn func() error, opts ...history.StorenodeTaskOption) error {
return w.waku.StorenodeCycle.PerformStorenodeTask(fn, opts...)
}
func (w *gethWakuV2Wrapper) DisconnectActiveStorenode(ctx context.Context, backoff time.Duration, shouldCycle bool) {
w.waku.StorenodeCycle.Lock()
defer w.waku.StorenodeCycle.Unlock()
w.waku.StorenodeCycle.DisconnectActiveStorenode(backoff)
if shouldCycle {
w.waku.StorenodeCycle.Cycle(ctx)
}
}

View File

@ -0,0 +1,48 @@
package types
import (
"math/big"
"github.com/waku-org/waku-go-bindings/eth-node/types"
)
type TransactionStatus uint64
const (
TransactionStatusFailed = 0
TransactionStatusSuccess = 1
TransactionStatusPending = 2
)
type Message struct {
to *types.Address
from types.Address
nonce uint64
amount *big.Int
gasLimit uint64
gasPrice *big.Int
data []byte
checkNonce bool
}
func NewMessage(from types.Address, to *types.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, checkNonce bool) Message {
return Message{
from: from,
to: to,
nonce: nonce,
amount: amount,
gasLimit: gasLimit,
gasPrice: gasPrice,
data: data,
checkNonce: checkNonce,
}
}
func (m Message) From() types.Address { return m.from }
func (m Message) To() *types.Address { return m.to }
func (m Message) GasPrice() *big.Int { return m.gasPrice }
func (m Message) Value() *big.Int { return m.amount }
func (m Message) Gas() uint64 { return m.gasLimit }
func (m Message) Nonce() uint64 { return m.nonce }
func (m Message) Data() []byte { return m.data }
func (m Message) CheckNonce() bool { return m.checkNonce }

254
eth-node/crypto/crypto.go Normal file
View File

@ -0,0 +1,254 @@
package crypto
import (
"context"
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"golang.org/x/crypto/sha3"
types "github.com/waku-org/waku-go-bindings/eth-node/types"
gethcrypto "github.com/ethereum/go-ethereum/crypto"
)
const (
aesNonceLength = 12
)
// Sign calculates an ECDSA signature.
//
// This function is susceptible to chosen plaintext attacks that can leak
// information about the private key that is used for signing. Callers must
// be aware that the given digest cannot be chosen by an adversery. Common
// solution is to hash any input before calculating the signature.
//
// The produced signature is in the [R || S || V] format where V is 0 or 1.
func Sign(digestHash []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) {
return gethcrypto.Sign(digestHash, prv)
}
// SignBytes signs the hash of arbitrary data.
func SignBytes(data []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) {
return Sign(Keccak256(data), prv)
}
// SignBytesAsHex signs the Keccak256 hash of arbitrary data and returns its hex representation.
func SignBytesAsHex(data []byte, identity *ecdsa.PrivateKey) (string, error) {
signature, err := SignBytes(data, identity)
if err != nil {
return "", err
}
return hex.EncodeToString(signature), nil
}
// SignStringAsHex signs the Keccak256 hash of arbitrary string and returns its hex representation.
func SignStringAsHex(data string, identity *ecdsa.PrivateKey) (string, error) {
return SignBytesAsHex([]byte(data), identity)
}
// VerifySignatures verifies tuples of signatures content/hash/public key
func VerifySignatures(signaturePairs [][3]string) error {
for _, signaturePair := range signaturePairs {
content := Keccak256([]byte(signaturePair[0]))
signature, err := hex.DecodeString(signaturePair[1])
if err != nil {
return err
}
publicKeyBytes, err := hex.DecodeString(signaturePair[2])
if err != nil {
return err
}
publicKey, err := UnmarshalPubkey(publicKeyBytes)
if err != nil {
return err
}
recoveredKey, err := SigToPub(
content,
signature,
)
if err != nil {
return err
}
if PubkeyToAddress(*recoveredKey) != PubkeyToAddress(*publicKey) {
return errors.New("identity key and signature mismatch")
}
}
return nil
}
// ExtractSignatures extract from tuples of signatures content a public key
// DEPRECATED: use ExtractSignature
func ExtractSignatures(signaturePairs [][2]string) ([]string, error) {
response := make([]string, len(signaturePairs))
for i, signaturePair := range signaturePairs {
content := Keccak256([]byte(signaturePair[0]))
signature, err := hex.DecodeString(signaturePair[1])
if err != nil {
return nil, err
}
recoveredKey, err := SigToPub(
content,
signature,
)
if err != nil {
return nil, err
}
response[i] = fmt.Sprintf("%x", FromECDSAPub(recoveredKey))
}
return response, nil
}
// ExtractSignature returns a public key for a given data and signature.
func ExtractSignature(data, signature []byte) (*ecdsa.PublicKey, error) {
dataHash := Keccak256(data)
return SigToPub(dataHash, signature)
}
func EncryptSymmetric(key, plaintext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// Never use more than 2^32 random nonces with a given key because of the risk of a repeat.
salt, err := generateSecureRandomData(aesNonceLength)
if err != nil {
return nil, err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
encrypted := aesgcm.Seal(nil, salt, plaintext, nil)
return append(encrypted, salt...), nil
}
func DecryptSymmetric(key []byte, cyphertext []byte) ([]byte, error) {
// symmetric messages are expected to contain the 12-byte nonce at the end of the payload
if len(cyphertext) < aesNonceLength {
return nil, errors.New("missing salt or invalid payload in symmetric message")
}
salt := cyphertext[len(cyphertext)-aesNonceLength:]
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
decrypted, err := aesgcm.Open(nil, salt, cyphertext[:len(cyphertext)-aesNonceLength], nil)
if err != nil {
return nil, err
}
return decrypted, nil
}
func containsOnlyZeros(data []byte) bool {
for _, b := range data {
if b != 0 {
return false
}
}
return true
}
func validateDataIntegrity(k []byte, expectedSize int) bool {
if len(k) != expectedSize {
return false
}
if containsOnlyZeros(k) {
return false
}
return true
}
func generateSecureRandomData(length int) ([]byte, error) {
res := make([]byte, length)
_, err := rand.Read(res)
if err != nil {
return nil, err
}
if !validateDataIntegrity(res, length) {
return nil, errors.New("crypto/rand failed to generate secure random data")
}
return res, nil
}
// TextHash is a helper function that calculates a hash for the given message that can be
// safely used to calculate a signature from.
//
// The hash is calulcated as
//
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
//
// This gives context to the signed message and prevents signing of transactions.
func TextHash(data []byte) []byte {
hash, _ := TextAndHash(data)
return hash
}
// TextAndHash is a helper function that calculates a hash for the given message that can be
// safely used to calculate a signature from.
//
// The hash is calulcated as
//
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
//
// This gives context to the signed message and prevents signing of transactions.
func TextAndHash(data []byte) ([]byte, string) {
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data))
hasher := sha3.NewLegacyKeccak256()
_, _ = hasher.Write([]byte(msg))
return hasher.Sum(nil), msg
}
func EcRecover(ctx context.Context, data types.HexBytes, sig types.HexBytes) (types.Address, error) {
// 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("\x19${byteVersion}Ethereum 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 be 27 or 28 for legacy reasons.
//
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover
if len(sig) != 65 {
return types.Address{}, fmt.Errorf("signature must be 65 bytes long")
}
if sig[64] != 27 && sig[64] != 28 {
return types.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)")
}
sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1
hash := TextHash(data)
rpk, err := SigToPub(hash, sig)
if err != nil {
return types.Address{}, err
}
return PubkeyToAddress(*rpk), nil
}

View File

@ -0,0 +1,134 @@
package crypto
import (
"encoding/hex"
"testing"
"github.com/stretchr/testify/require"
)
func TestExtractSignatures(t *testing.T) {
const content1 = "045a8cae84d8d139e887bb927d2b98cee481afae3770e0ee45f2dc19c6545e45921bc6a55ea92b705e45dfbbe47182c7b1d64a080a220d2781577163923d7cbb4b045a8cae84d8d139e887bb927d2b98cee481afae3770e0ee45f2dc19c6545e45921bc6a55ea92b705e45dfbbe47182c7b1d64a080a220d2781577163923d7cbb4b04ca82dd41fa592bf46ecf7e2eddae61013fc95a565b59c49f37f06b1b591ed3bd24e143495f2d1e241e151ab3572ac108d577be349d4b88d3d5a50c481ab35441"
const content2 = "045a8cae84d8d139e887bb927d2b98cee481afae3770e0ee45f2dc19c6545e45921bc6a55ea92b705e45dfbbe47182c7b1d64a080a220d2781577163923d7cbb4b045a8cae84d8d139e887bb927d2b98cee481afae3770e0ee45f2dc19c6545e45921bc6a55ea92b705e45dfbbe47182c7b1d64a080a220d2781577163923d7cbb4b04ca82dd41fa592bf46ecf7e2eddae61013fc95a565b59c49f37f06b1b591ed3bd24e143495f2d1e241e151ab3572ac108d577be349d4b88d3d5a50c481ab35440"
key1, err := GenerateKey()
require.NoError(t, err)
key2, err := GenerateKey()
require.NoError(t, err)
signature1, err := SignStringAsHex(content1, key1)
require.NoError(t, err)
signature2, err := SignStringAsHex(content2, key2)
require.NoError(t, err)
key1String := hex.EncodeToString(FromECDSAPub(&key1.PublicKey))
key2String := hex.EncodeToString(FromECDSAPub(&key2.PublicKey))
pair1 := [2]string{content1, signature1}
pair2 := [2]string{content2, signature2}
signaturePairs := [][2]string{pair1, pair2}
extractedSignatures, err := ExtractSignatures(signaturePairs)
require.NoError(t, err)
require.Equal(t, []string{key1String, key2String}, extractedSignatures)
// Test wrong content
pair3 := [2]string{content1, signature2}
signaturePairs = [][2]string{pair1, pair2, pair3}
extractedSignatures, err = ExtractSignatures(signaturePairs)
require.NoError(t, err)
// The public key is neither the one which generated the content, nor the one generated the signature
require.NotEqual(t, []string{key1String, key2String, key1String}, extractedSignatures)
require.NotEqual(t, []string{key1String, key2String, key2String}, extractedSignatures)
}
func TestVerifySignature(t *testing.T) {
const content1 = "045a8cae84d8d139e887bb927d2b98cee481afae3770e0ee45f2dc19c6545e45921bc6a55ea92b705e45dfbbe47182c7b1d64a080a220d2781577163923d7cbb4b045a8cae84d8d139e887bb927d2b98cee481afae3770e0ee45f2dc19c6545e45921bc6a55ea92b705e45dfbbe47182c7b1d64a080a220d2781577163923d7cbb4b04ca82dd41fa592bf46ecf7e2eddae61013fc95a565b59c49f37f06b1b591ed3bd24e143495f2d1e241e151ab3572ac108d577be349d4b88d3d5a50c481ab35441"
const content2 = "045a8cae84d8d139e887bb927d2b98cee481afae3770e0ee45f2dc19c6545e45921bc6a55ea92b705e45dfbbe47182c7b1d64a080a220d2781577163923d7cbb4b045a8cae84d8d139e887bb927d2b98cee481afae3770e0ee45f2dc19c6545e45921bc6a55ea92b705e45dfbbe47182c7b1d64a080a220d2781577163923d7cbb4b04ca82dd41fa592bf46ecf7e2eddae61013fc95a565b59c49f37f06b1b591ed3bd24e143495f2d1e241e151ab3572ac108d577be349d4b88d3d5a50c481ab35440"
key1, err := GenerateKey()
require.NoError(t, err)
key2, err := GenerateKey()
require.NoError(t, err)
signature1, err := SignStringAsHex(content1, key1)
require.NoError(t, err)
signature2, err := SignStringAsHex(content2, key2)
require.NoError(t, err)
key1String := hex.EncodeToString(FromECDSAPub(&key1.PublicKey))
key2String := hex.EncodeToString(FromECDSAPub(&key2.PublicKey))
pair1 := [3]string{content1, signature1, key1String}
pair2 := [3]string{content2, signature2, key2String}
signaturePairs := [][3]string{pair1, pair2}
err = VerifySignatures(signaturePairs)
require.NoError(t, err)
// Test wrong content
pair3 := [3]string{content1, signature2, key2String}
signaturePairs = [][3]string{pair1, pair2, pair3}
err = VerifySignatures(signaturePairs)
require.Error(t, err)
// Test wrong signature
pair3 = [3]string{content1, signature2, key1String}
signaturePairs = [][3]string{pair1, pair2, pair3}
err = VerifySignatures(signaturePairs)
require.Error(t, err)
// Test wrong pubkey
pair3 = [3]string{content1, signature1, key2String}
signaturePairs = [][3]string{pair1, pair2, pair3}
err = VerifySignatures(signaturePairs)
require.Error(t, err)
}
func TestSymmetricEncryption(t *testing.T) {
const rawKey = "0000000000000000000000000000000000000000000000000000000000000000"
expectedPlaintext := []byte("test")
key, err := hex.DecodeString(rawKey)
require.Nil(t, err, "Key should be generated without errors")
cyphertext1, err := EncryptSymmetric(key, expectedPlaintext)
require.Nil(t, err, "Cyphertext should be generated without errors")
cyphertext2, err := EncryptSymmetric(key, expectedPlaintext)
require.Nil(t, err, "Cyphertext should be generated without errors")
require.Equalf(
t,
32,
len(cyphertext1),
"Cyphertext with the correct length should be generated")
require.NotEqualf(
t,
cyphertext1,
cyphertext2,
"Same plaintext should not be encrypted in the same way")
plaintext, err := DecryptSymmetric(key, cyphertext1)
require.Nil(t, err, "Cyphertext should be decrypted without errors")
require.Equalf(
t,
expectedPlaintext,
plaintext,
"Cypther text should be decrypted successfully")
}

View File

@ -0,0 +1,366 @@
// Copyright (c) 2013 Kyle Isom <kyle@tyrfingr.is>
// Copyright (c) 2012 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package ecies
import (
"crypto/cipher"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/hmac"
"crypto/subtle"
"fmt"
"hash"
"io"
"math/big"
)
var (
ErrImport = fmt.Errorf("ecies: failed to import key")
ErrInvalidCurve = fmt.Errorf("ecies: invalid elliptic curve")
ErrInvalidParams = fmt.Errorf("ecies: invalid ECIES parameters")
ErrInvalidPublicKey = fmt.Errorf("ecies: invalid public key")
ErrSharedKeyIsPointAtInfinity = fmt.Errorf("ecies: shared key is point at infinity")
ErrSharedKeyTooBig = fmt.Errorf("ecies: shared key params are too big")
)
// PublicKey is a representation of an elliptic curve public key.
type PublicKey struct {
X *big.Int
Y *big.Int
elliptic.Curve
Params *ECIESParams
}
// Export an ECIES public key as an ECDSA public key.
func (pub *PublicKey) ExportECDSA() *ecdsa.PublicKey {
return &ecdsa.PublicKey{Curve: pub.Curve, X: pub.X, Y: pub.Y}
}
// Import an ECDSA public key as an ECIES public key.
func ImportECDSAPublic(pub *ecdsa.PublicKey) *PublicKey {
return &PublicKey{
X: pub.X,
Y: pub.Y,
Curve: pub.Curve,
Params: ParamsFromCurve(pub.Curve),
}
}
// PrivateKey is a representation of an elliptic curve private key.
type PrivateKey struct {
PublicKey
D *big.Int
}
// Export an ECIES private key as an ECDSA private key.
func (prv *PrivateKey) ExportECDSA() *ecdsa.PrivateKey {
pub := &prv.PublicKey
pubECDSA := pub.ExportECDSA()
return &ecdsa.PrivateKey{PublicKey: *pubECDSA, D: prv.D}
}
// Import an ECDSA private key as an ECIES private key.
func ImportECDSA(prv *ecdsa.PrivateKey) *PrivateKey {
pub := ImportECDSAPublic(&prv.PublicKey)
return &PrivateKey{*pub, prv.D}
}
// Generate an elliptic curve public / private keypair. If params is nil,
// the recommended default parameters for the key will be chosen.
func GenerateKey(rand io.Reader, curve elliptic.Curve, params *ECIESParams) (prv *PrivateKey, err error) {
pb, x, y, err := elliptic.GenerateKey(curve, rand)
if err != nil {
return
}
prv = new(PrivateKey)
prv.PublicKey.X = x
prv.PublicKey.Y = y
prv.PublicKey.Curve = curve
prv.D = new(big.Int).SetBytes(pb)
if params == nil {
params = ParamsFromCurve(curve)
}
prv.PublicKey.Params = params
return
}
// MaxSharedKeyLength returns the maximum length of the shared key the
// public key can produce.
func MaxSharedKeyLength(pub *PublicKey) int {
return (pub.Curve.Params().BitSize + 7) / 8
}
// ECDH key agreement method used to establish secret keys for encryption.
func (prv *PrivateKey) GenerateShared(pub *PublicKey, skLen, macLen int) (sk []byte, err error) {
if prv.PublicKey.Curve != pub.Curve {
return nil, ErrInvalidCurve
}
if skLen+macLen > MaxSharedKeyLength(pub) {
return nil, ErrSharedKeyTooBig
}
x, _ := pub.Curve.ScalarMult(pub.X, pub.Y, prv.D.Bytes())
if x == nil {
return nil, ErrSharedKeyIsPointAtInfinity
}
sk = make([]byte, skLen+macLen)
skBytes := x.Bytes()
copy(sk[len(sk)-len(skBytes):], skBytes)
return sk, nil
}
var (
ErrKeyDataTooLong = fmt.Errorf("ecies: can't supply requested key data")
ErrSharedTooLong = fmt.Errorf("ecies: shared secret is too long")
ErrInvalidMessage = fmt.Errorf("ecies: invalid message")
)
var (
big2To32 = new(big.Int).Exp(big.NewInt(2), big.NewInt(32), nil)
big2To32M1 = new(big.Int).Sub(big2To32, big.NewInt(1))
)
func incCounter(ctr []byte) {
if ctr[3]++; ctr[3] != 0 {
return
}
if ctr[2]++; ctr[2] != 0 {
return
}
if ctr[1]++; ctr[1] != 0 {
return
}
if ctr[0]++; ctr[0] != 0 {
return
}
}
// NIST SP 800-56 Concatenation Key Derivation Function (see section 5.8.1).
func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) (k []byte, err error) {
if s1 == nil {
s1 = make([]byte, 0)
}
reps := ((kdLen + 7) * 8) / (hash.BlockSize() * 8)
if big.NewInt(int64(reps)).Cmp(big2To32M1) > 0 {
fmt.Println(big2To32M1)
return nil, ErrKeyDataTooLong
}
counter := []byte{0, 0, 0, 1}
k = make([]byte, 0)
for i := 0; i <= reps; i++ {
hash.Write(counter)
hash.Write(z)
hash.Write(s1)
k = append(k, hash.Sum(nil)...)
hash.Reset()
incCounter(counter)
}
k = k[:kdLen]
return
}
// messageTag computes the MAC of a message (called the tag) as per
// SEC 1, 3.5.
func messageTag(hash func() hash.Hash, km, msg, shared []byte) []byte {
mac := hmac.New(hash, km)
mac.Write(msg)
mac.Write(shared)
tag := mac.Sum(nil)
return tag
}
// Generate an initialisation vector for CTR mode.
func generateIV(params *ECIESParams, rand io.Reader) (iv []byte, err error) {
iv = make([]byte, params.BlockSize)
_, err = io.ReadFull(rand, iv)
return
}
// symEncrypt carries out CTR encryption using the block cipher specified in the
// parameters.
func symEncrypt(rand io.Reader, params *ECIESParams, key, m []byte) (ct []byte, err error) {
c, err := params.Cipher(key)
if err != nil {
return
}
iv, err := generateIV(params, rand)
if err != nil {
return
}
ctr := cipher.NewCTR(c, iv)
ct = make([]byte, len(m)+params.BlockSize)
copy(ct, iv)
ctr.XORKeyStream(ct[params.BlockSize:], m)
return
}
// symDecrypt carries out CTR decryption using the block cipher specified in
// the parameters
func symDecrypt(params *ECIESParams, key, ct []byte) (m []byte, err error) {
c, err := params.Cipher(key)
if err != nil {
return
}
ctr := cipher.NewCTR(c, ct[:params.BlockSize])
m = make([]byte, len(ct)-params.BlockSize)
ctr.XORKeyStream(m, ct[params.BlockSize:])
return
}
// Encrypt encrypts a message using ECIES as specified in SEC 1, 5.1.
//
// s1 and s2 contain shared information that is not part of the resulting
// ciphertext. s1 is fed into key derivation, s2 is fed into the MAC. If the
// shared information parameters aren't being used, they should be nil.
func Encrypt(rand io.Reader, pub *PublicKey, m, s1, s2 []byte) (ct []byte, err error) {
params := pub.Params
if params == nil {
if params = ParamsFromCurve(pub.Curve); params == nil {
err = ErrUnsupportedECIESParameters
return
}
}
R, err := GenerateKey(rand, pub.Curve, params)
if err != nil {
return
}
hash := params.Hash()
z, err := R.GenerateShared(pub, params.KeyLen, params.KeyLen)
if err != nil {
return
}
K, err := concatKDF(hash, z, s1, params.KeyLen+params.KeyLen)
if err != nil {
return
}
Ke := K[:params.KeyLen]
Km := K[params.KeyLen:]
hash.Write(Km)
Km = hash.Sum(nil)
hash.Reset()
em, err := symEncrypt(rand, params, Ke, m)
if err != nil || len(em) <= params.BlockSize {
return
}
d := messageTag(params.Hash, Km, em, s2)
Rb := elliptic.Marshal(pub.Curve, R.PublicKey.X, R.PublicKey.Y)
ct = make([]byte, len(Rb)+len(em)+len(d))
copy(ct, Rb)
copy(ct[len(Rb):], em)
copy(ct[len(Rb)+len(em):], d)
return
}
// Decrypt decrypts an ECIES ciphertext.
func (prv *PrivateKey) Decrypt(c, s1, s2 []byte) (m []byte, err error) {
if len(c) == 0 {
return nil, ErrInvalidMessage
}
params := prv.PublicKey.Params
if params == nil {
if params = ParamsFromCurve(prv.PublicKey.Curve); params == nil {
err = ErrUnsupportedECIESParameters
return
}
}
hash := params.Hash()
var (
rLen int
hLen int = hash.Size()
mStart int
mEnd int
)
switch c[0] {
case 2, 3, 4:
rLen = (prv.PublicKey.Curve.Params().BitSize + 7) / 4
if len(c) < (rLen + hLen + 1) {
err = ErrInvalidMessage
return
}
default:
err = ErrInvalidPublicKey
return
}
mStart = rLen
mEnd = len(c) - hLen
R := new(PublicKey)
R.Curve = prv.PublicKey.Curve
R.X, R.Y = elliptic.Unmarshal(R.Curve, c[:rLen])
if R.X == nil {
err = ErrInvalidPublicKey
return
}
if !R.Curve.IsOnCurve(R.X, R.Y) {
err = ErrInvalidCurve
return
}
z, err := prv.GenerateShared(R, params.KeyLen, params.KeyLen)
if err != nil {
return
}
K, err := concatKDF(hash, z, s1, params.KeyLen+params.KeyLen)
if err != nil {
return
}
Ke := K[:params.KeyLen]
Km := K[params.KeyLen:]
hash.Write(Km)
Km = hash.Sum(nil)
hash.Reset()
d := messageTag(params.Hash, Km, c[mStart:mEnd], s2)
if subtle.ConstantTimeCompare(c[mEnd:], d) != 1 {
err = ErrInvalidMessage
return
}
m, err = symDecrypt(params, Ke, c[mStart:mEnd])
return
}

View File

@ -0,0 +1,117 @@
// Copyright (c) 2013 Kyle Isom <kyle@tyrfingr.is>
// Copyright (c) 2012 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package ecies
// This file contains parameters for ECIES encryption, specifying the
// symmetric encryption and HMAC parameters.
import (
"crypto"
"crypto/aes"
"crypto/cipher"
"crypto/elliptic"
"crypto/sha256"
"crypto/sha512"
"fmt"
"hash"
gethcrypto "github.com/ethereum/go-ethereum/crypto"
)
var (
DefaultCurve = gethcrypto.S256()
ErrUnsupportedECDHAlgorithm = fmt.Errorf("ecies: unsupported ECDH algorithm")
ErrUnsupportedECIESParameters = fmt.Errorf("ecies: unsupported ECIES parameters")
)
type ECIESParams struct {
Hash func() hash.Hash // hash function
hashAlgo crypto.Hash
Cipher func([]byte) (cipher.Block, error) // symmetric cipher
BlockSize int // block size of symmetric cipher
KeyLen int // length of symmetric key
}
// Standard ECIES parameters:
// * ECIES using AES128 and HMAC-SHA-256-16
// * ECIES using AES256 and HMAC-SHA-256-32
// * ECIES using AES256 and HMAC-SHA-384-48
// * ECIES using AES256 and HMAC-SHA-512-64
var (
ECIES_AES128_SHA256 = &ECIESParams{
Hash: sha256.New,
hashAlgo: crypto.SHA256,
Cipher: aes.NewCipher,
BlockSize: aes.BlockSize,
KeyLen: 16,
}
ECIES_AES256_SHA256 = &ECIESParams{
Hash: sha256.New,
hashAlgo: crypto.SHA256,
Cipher: aes.NewCipher,
BlockSize: aes.BlockSize,
KeyLen: 32,
}
ECIES_AES256_SHA384 = &ECIESParams{
Hash: sha512.New384,
hashAlgo: crypto.SHA384,
Cipher: aes.NewCipher,
BlockSize: aes.BlockSize,
KeyLen: 32,
}
ECIES_AES256_SHA512 = &ECIESParams{
Hash: sha512.New,
hashAlgo: crypto.SHA512,
Cipher: aes.NewCipher,
BlockSize: aes.BlockSize,
KeyLen: 32,
}
)
var paramsFromCurve = map[elliptic.Curve]*ECIESParams{
gethcrypto.S256(): ECIES_AES128_SHA256,
elliptic.P256(): ECIES_AES128_SHA256,
elliptic.P384(): ECIES_AES256_SHA384,
elliptic.P521(): ECIES_AES256_SHA512,
}
func AddParamsForCurve(curve elliptic.Curve, params *ECIESParams) {
paramsFromCurve[curve] = params
}
// ParamsFromCurve selects parameters optimal for the selected elliptic curve.
// Only the curves P256, P384, and P512 are supported.
func ParamsFromCurve(curve elliptic.Curve) (params *ECIESParams) {
return paramsFromCurve[curve]
}

View File

@ -0,0 +1,197 @@
package crypto
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/sha256"
"fmt"
"io"
dr "github.com/status-im/doubleratchet"
"golang.org/x/crypto/hkdf"
"github.com/waku-org/waku-go-bindings/eth-node/crypto/ecies"
)
// EthereumCrypto is an implementation of Crypto with cryptographic primitives recommended
// by the Double Ratchet Algorithm specification. However, some details are different,
// see function comments for details.
type EthereumCrypto struct{}
// See the Crypto interface.
func (c EthereumCrypto) GenerateDH() (dr.DHPair, error) {
keys, err := GenerateKey()
if err != nil {
return nil, err
}
return DHPair{
PubKey: CompressPubkey(&keys.PublicKey),
PrvKey: FromECDSA(keys),
}, nil
}
// See the Crypto interface.
func (c EthereumCrypto) DH(dhPair dr.DHPair, dhPub dr.Key) (dr.Key, error) {
tmpKey := dhPair.PrivateKey()
privateKey, err := ToECDSA(tmpKey)
if err != nil {
return nil, err
}
eciesPrivate := ecies.ImportECDSA(privateKey)
publicKey, err := DecompressPubkey(dhPub)
if err != nil {
return nil, err
}
eciesPublic := ecies.ImportECDSAPublic(publicKey)
key, err := eciesPrivate.GenerateShared(
eciesPublic,
16,
16,
)
if err != nil {
return nil, err
}
return key, nil
}
// See the Crypto interface.
func (c EthereumCrypto) KdfRK(rk, dhOut dr.Key) (dr.Key, dr.Key, dr.Key) {
var (
// We can use a non-secret constant as the last argument
r = hkdf.New(sha256.New, dhOut, rk, []byte("rsZUpEuXUqqwXBvSy3EcievAh4cMj6QL"))
buf = make([]byte, 96)
)
rootKey := make(dr.Key, 32)
chainKey := make(dr.Key, 32)
headerKey := make(dr.Key, 32)
// The only error here is an entropy limit which won't be reached for such a short buffer.
_, _ = io.ReadFull(r, buf)
copy(rootKey, buf[:32])
copy(chainKey, buf[32:64])
copy(headerKey, buf[64:96])
return rootKey, chainKey, headerKey
}
// See the Crypto interface.
func (c EthereumCrypto) KdfCK(ck dr.Key) (dr.Key, dr.Key) {
const (
ckInput = 15
mkInput = 16
)
chainKey := make(dr.Key, 32)
msgKey := make(dr.Key, 32)
h := hmac.New(sha256.New, ck)
_, _ = h.Write([]byte{ckInput})
copy(chainKey, h.Sum(nil))
h.Reset()
_, _ = h.Write([]byte{mkInput})
copy(msgKey, h.Sum(nil))
return chainKey, msgKey
}
// Encrypt uses a slightly different approach than in the algorithm specification:
// it uses AES-256-CTR instead of AES-256-CBC for security, ciphertext length and implementation
// complexity considerations.
func (c EthereumCrypto) Encrypt(mk dr.Key, plaintext, ad []byte) ([]byte, error) {
encKey, authKey, iv := c.deriveEncKeys(mk)
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
copy(ciphertext, iv[:])
block, err := aes.NewCipher(encKey)
if err != nil {
return nil, err
}
stream := cipher.NewCTR(block, iv[:])
stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)
return append(ciphertext, c.computeSignature(authKey, ciphertext, ad)...), nil
}
// See the Crypto interface.
func (c EthereumCrypto) Decrypt(mk dr.Key, authCiphertext, ad []byte) ([]byte, error) {
var (
l = len(authCiphertext)
ciphertext = authCiphertext[:l-sha256.Size]
signature = authCiphertext[l-sha256.Size:]
)
// Check the signature.
encKey, authKey, _ := c.deriveEncKeys(mk)
if s := c.computeSignature(authKey, ciphertext, ad); !bytes.Equal(s, signature) {
return nil, fmt.Errorf("invalid signature")
}
// Decrypt.
block, err := aes.NewCipher(encKey)
if err != nil {
return nil, err
}
stream := cipher.NewCTR(block, ciphertext[:aes.BlockSize])
plaintext := make([]byte, len(ciphertext[aes.BlockSize:]))
stream.XORKeyStream(plaintext, ciphertext[aes.BlockSize:])
return plaintext, nil
}
// deriveEncKeys derive keys for message encryption and decryption. Returns (encKey, authKey, iv, err).
func (c EthereumCrypto) deriveEncKeys(mk dr.Key) (dr.Key, dr.Key, [16]byte) {
// First, derive encryption and authentication key out of mk.
salt := make([]byte, 32)
var (
r = hkdf.New(sha256.New, mk, salt, []byte("pcwSByyx2CRdryCffXJwy7xgVZWtW5Sh"))
buf = make([]byte, 80)
)
encKey := make(dr.Key, 32)
authKey := make(dr.Key, 32)
var iv [16]byte
// The only error here is an entropy limit which won't be reached for such a short buffer.
_, _ = io.ReadFull(r, buf)
copy(encKey, buf[0:32])
copy(authKey, buf[32:64])
copy(iv[:], buf[64:80])
return encKey, authKey, iv
}
func (c EthereumCrypto) computeSignature(authKey, ciphertext, associatedData []byte) []byte {
h := hmac.New(sha256.New, authKey)
_, _ = h.Write(associatedData)
_, _ = h.Write(ciphertext)
return h.Sum(nil)
}
type DHPair struct {
PrvKey dr.Key
PubKey dr.Key
}
func (p DHPair) PrivateKey() dr.Key {
return p.PrvKey
}
func (p DHPair) PublicKey() dr.Key {
return p.PubKey
}

View File

@ -0,0 +1,237 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package crypto
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"io"
"io/ioutil"
"math/big"
"os"
"golang.org/x/crypto/sha3"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
"github.com/ethereum/go-ethereum/rlp"
"github.com/waku-org/waku-go-bindings/eth-node/types"
)
// SignatureLength indicates the byte length required to carry a signature with recovery id.
const SignatureLength = 64 + 1 // 64 bytes ECDSA signature + 1 byte recovery id
// RecoveryIDOffset points to the byte offset within the signature that contains the recovery id.
const RecoveryIDOffset = 64
// DigestLength sets the signature digest exact length
const DigestLength = 32
var (
secp256k1N, _ = new(big.Int).SetString("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16)
)
var errInvalidPubkey = errors.New("invalid secp256k1 public key")
// Keccak256 calculates and returns the Keccak256 hash of the input data.
func Keccak256(data ...[]byte) []byte {
d := sha3.NewLegacyKeccak256()
for _, b := range data {
_, _ = d.Write(b)
}
return d.Sum(nil)
}
// Keccak256Hash calculates and returns the Keccak256 hash of the input data,
// converting it to an internal Hash data structure.
func Keccak256Hash(data ...[]byte) (h types.Hash) {
d := sha3.NewLegacyKeccak256()
for _, b := range data {
_, _ = d.Write(b)
}
d.Sum(h[:0])
return h
}
// Keccak512 calculates and returns the Keccak512 hash of the input data.
func Keccak512(data ...[]byte) []byte {
d := sha3.NewLegacyKeccak512()
for _, b := range data {
_, _ = d.Write(b)
}
return d.Sum(nil)
}
// CreateAddress creates an ethereum address given the bytes and the nonce
func CreateAddress(b types.Address, nonce uint64) types.Address {
data, _ := rlp.EncodeToBytes([]interface{}{b, nonce})
return types.BytesToAddress(Keccak256(data)[12:])
}
// CreateAddress2 creates an ethereum address given the address bytes, initial
// contract code hash and a salt.
func CreateAddress2(b types.Address, salt [32]byte, inithash []byte) types.Address {
return types.BytesToAddress(Keccak256([]byte{0xff}, b.Bytes(), salt[:], inithash)[12:])
}
// ToECDSA creates a private key with the given D value.
func ToECDSA(d []byte) (*ecdsa.PrivateKey, error) {
return toECDSA(d, true)
}
// ToECDSAUnsafe blindly converts a binary blob to a private key. It should almost
// never be used unless you are sure the input is valid and want to avoid hitting
// errors due to bad origin encoding (0 prefixes cut off).
func ToECDSAUnsafe(d []byte) *ecdsa.PrivateKey {
priv, _ := toECDSA(d, false)
return priv
}
// toECDSA creates a private key with the given D value. The strict parameter
// controls whether the key's length should be enforced at the curve size or
// it can also accept legacy encodings (0 prefixes).
func toECDSA(d []byte, strict bool) (*ecdsa.PrivateKey, error) {
priv := new(ecdsa.PrivateKey)
priv.PublicKey.Curve = S256()
if strict && 8*len(d) != priv.Params().BitSize {
return nil, fmt.Errorf("invalid length, need %d bits", priv.Params().BitSize)
}
priv.D = new(big.Int).SetBytes(d)
// The priv.D must < N
if priv.D.Cmp(secp256k1N) >= 0 {
return nil, fmt.Errorf("invalid private key, >=N")
}
// The priv.D must not be zero or negative.
if priv.D.Sign() <= 0 {
return nil, fmt.Errorf("invalid private key, zero or negative")
}
priv.PublicKey.X, priv.PublicKey.Y = priv.PublicKey.Curve.ScalarBaseMult(d)
if priv.PublicKey.X == nil {
return nil, errors.New("invalid private key")
}
return priv, nil
}
// FromECDSA exports a private key into a binary dump.
func FromECDSA(priv *ecdsa.PrivateKey) []byte {
if priv == nil {
return nil
}
return math.PaddedBigBytes(priv.D, priv.Params().BitSize/8)
}
// UnmarshalPubkey converts bytes to a secp256k1 public key.
func UnmarshalPubkey(pub []byte) (*ecdsa.PublicKey, error) {
x, y := elliptic.Unmarshal(S256(), pub)
if x == nil {
return nil, errInvalidPubkey
}
return &ecdsa.PublicKey{Curve: S256(), X: x, Y: y}, nil
}
func FromECDSAPub(pub *ecdsa.PublicKey) []byte {
if pub == nil || pub.X == nil || pub.Y == nil {
return nil
}
return elliptic.Marshal(S256(), pub.X, pub.Y)
}
// HexToECDSA parses a secp256k1 private key.
func HexToECDSA(hexkey string) (*ecdsa.PrivateKey, error) {
b, err := hex.DecodeString(hexkey)
if err != nil {
return nil, errors.New("invalid hex string")
}
return ToECDSA(b)
}
// LoadECDSA loads a secp256k1 private key from the given file.
func LoadECDSA(file string) (*ecdsa.PrivateKey, error) {
buf := make([]byte, 64)
fd, err := os.Open(file)
if err != nil {
return nil, err
}
defer fd.Close()
if _, err := io.ReadFull(fd, buf); err != nil {
return nil, err
}
key, err := hex.DecodeString(string(buf))
if err != nil {
return nil, err
}
return ToECDSA(key)
}
// SaveECDSA saves a secp256k1 private key to the given file with
// restrictive permissions. The key data is saved hex-encoded.
func SaveECDSA(file string, key *ecdsa.PrivateKey) error {
k := hex.EncodeToString(FromECDSA(key))
return ioutil.WriteFile(file, []byte(k), 0600)
}
func GenerateKey() (*ecdsa.PrivateKey, error) {
return ecdsa.GenerateKey(S256(), rand.Reader)
}
func PubkeyToAddress(p ecdsa.PublicKey) types.Address {
pubBytes := FromECDSAPub(&p)
return types.BytesToAddress(Keccak256(pubBytes[1:])[12:])
}
// Ecrecover returns the uncompressed public key that created the given signature.
func Ecrecover(hash, sig []byte) ([]byte, error) {
return secp256k1.RecoverPubkey(hash, sig)
}
// SigToPub returns the public key that created the given signature.
func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) {
s, err := Ecrecover(hash, sig)
if err != nil {
return nil, err
}
x, y := elliptic.Unmarshal(S256(), s)
return &ecdsa.PublicKey{Curve: S256(), X: x, Y: y}, nil
}
// DecompressPubkey parses a public key in the 33-byte compressed format.
func DecompressPubkey(pubkey []byte) (*ecdsa.PublicKey, error) {
x, y := secp256k1.DecompressPubkey(pubkey)
if x == nil {
return nil, fmt.Errorf("invalid public key")
}
return &ecdsa.PublicKey{X: x, Y: y, Curve: S256()}, nil
}
// CompressPubkey encodes a public key to the 33-byte compressed format.
func CompressPubkey(pubkey *ecdsa.PublicKey) []byte {
return secp256k1.CompressPubkey(pubkey.X, pubkey.Y)
}
// S256 returns an instance of the secp256k1 curve.
func S256() elliptic.Curve {
return secp256k1.S256()
}

View File

@ -0,0 +1,15 @@
// Imported from github.com/ethereum/go-ethereum/accounts/keystore/keystore.go
package keystore
import (
"errors"
)
const (
version = 3
)
var (
ErrDecrypt = errors.New("could not decrypt key with given password")
)

View File

@ -0,0 +1,353 @@
// Imported from github.com/ethereum/go-ethereum/accounts/keystore/passphrase.go
// and github.com/ethereum/go-ethereum/accounts/keystore/presale.go
package keystore
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/google/uuid"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/scrypt"
"github.com/status-im/status-go/extkeys"
"github.com/waku-org/waku-go-bindings/eth-node/crypto"
"github.com/waku-org/waku-go-bindings/eth-node/types"
)
const (
keyHeaderKDF = "scrypt"
)
type EncryptedKeyJSONV3 struct {
Address string `json:"address"`
Crypto CryptoJSON `json:"crypto"`
Id string `json:"id"`
Version int `json:"version"`
ExtendedKey CryptoJSON `json:"extendedkey"`
SubAccountIndex uint32 `json:"subaccountindex"`
}
type encryptedKeyJSONV1 struct {
Address string `json:"address"`
Crypto CryptoJSON `json:"crypto"`
Id string `json:"id"`
Version string `json:"version"`
}
type CryptoJSON struct {
Cipher string `json:"cipher"`
CipherText string `json:"ciphertext"`
CipherParams cipherparamsJSON `json:"cipherparams"`
KDF string `json:"kdf"`
KDFParams map[string]interface{} `json:"kdfparams"`
MAC string `json:"mac"`
}
type cipherparamsJSON struct {
IV string `json:"iv"`
}
// DecryptKey decrypts a key from a json blob, returning the private key itself.
func DecryptKey(keyjson []byte, auth string) (*types.Key, error) {
// Parse the json into a simple map to fetch the key version
m := make(map[string]interface{})
if err := json.Unmarshal(keyjson, &m); err != nil {
return nil, err
}
// Depending on the version try to parse one way or another
var (
keyBytes, keyId []byte
err error
extKeyBytes []byte
extKey *extkeys.ExtendedKey
)
subAccountIndex, ok := m["subaccountindex"].(float64)
if !ok {
subAccountIndex = 0
}
if version, ok := m["version"].(string); ok && version == "1" {
k := new(encryptedKeyJSONV1)
if err := json.Unmarshal(keyjson, k); err != nil {
return nil, err
}
keyBytes, keyId, err = decryptKeyV1(k, auth)
if err != nil {
return nil, err
}
extKey, err = extkeys.NewKeyFromString(extkeys.EmptyExtendedKeyString)
} else {
k := new(EncryptedKeyJSONV3)
if err := json.Unmarshal(keyjson, k); err != nil {
return nil, err
}
keyBytes, keyId, err = decryptKeyV3(k, auth)
if err != nil {
return nil, err
}
extKeyBytes, err = decryptExtendedKey(k, auth)
if err != nil {
return nil, err
}
extKey, err = extkeys.NewKeyFromString(string(extKeyBytes))
}
// Handle any decryption errors and return the key
if err != nil {
return nil, err
}
key := crypto.ToECDSAUnsafe(keyBytes)
id, err := uuid.FromBytes(keyId)
if err != nil {
return nil, err
}
return &types.Key{
ID: id,
Address: crypto.PubkeyToAddress(key.PublicKey),
PrivateKey: key,
ExtendedKey: extKey,
SubAccountIndex: uint32(subAccountIndex),
}, nil
}
func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) {
if cryptoJson.Cipher != "aes-128-ctr" {
return nil, fmt.Errorf("Cipher not supported: %v", cryptoJson.Cipher)
}
mac, err := hex.DecodeString(cryptoJson.MAC)
if err != nil {
return nil, err
}
iv, err := hex.DecodeString(cryptoJson.CipherParams.IV)
if err != nil {
return nil, err
}
cipherText, err := hex.DecodeString(cryptoJson.CipherText)
if err != nil {
return nil, err
}
derivedKey, err := getKDFKey(cryptoJson, auth)
if err != nil {
return nil, err
}
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
if !bytes.Equal(calculatedMAC, mac) {
return nil, ErrDecrypt
}
plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
if err != nil {
return nil, err
}
return plainText, err
}
func decryptKeyV3(keyProtected *EncryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) {
if keyProtected.Version != version {
return nil, nil, fmt.Errorf("Version not supported: %v", keyProtected.Version)
}
id, err := uuid.Parse(keyProtected.Id)
if err != nil {
return nil, nil, err
}
keyId = id[:]
plainText, err := DecryptDataV3(keyProtected.Crypto, auth)
if err != nil {
return nil, nil, err
}
return plainText, keyId, err
}
func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byte, keyId []byte, err error) {
id, err := uuid.Parse(keyProtected.Id)
if err != nil {
return nil, nil, err
}
keyId = id[:]
mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
if err != nil {
return nil, nil, err
}
iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
if err != nil {
return nil, nil, err
}
cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
if err != nil {
return nil, nil, err
}
derivedKey, err := getKDFKey(keyProtected.Crypto, auth)
if err != nil {
return nil, nil, err
}
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
if !bytes.Equal(calculatedMAC, mac) {
return nil, nil, ErrDecrypt
}
plainText, err := aesCBCDecrypt(crypto.Keccak256(derivedKey[:16])[:16], cipherText, iv)
if err != nil {
return nil, nil, err
}
return plainText, keyId, err
}
func decryptExtendedKey(keyProtected *EncryptedKeyJSONV3, auth string) (plainText []byte, err error) {
if len(keyProtected.ExtendedKey.CipherText) == 0 {
return []byte(extkeys.EmptyExtendedKeyString), nil
}
if keyProtected.Version != version {
return nil, fmt.Errorf("Version not supported: %v", keyProtected.Version)
}
if keyProtected.ExtendedKey.Cipher != "aes-128-ctr" {
return nil, fmt.Errorf("Cipher not supported: %v", keyProtected.ExtendedKey.Cipher)
}
mac, err := hex.DecodeString(keyProtected.ExtendedKey.MAC)
if err != nil {
return nil, err
}
iv, err := hex.DecodeString(keyProtected.ExtendedKey.CipherParams.IV)
if err != nil {
return nil, err
}
cipherText, err := hex.DecodeString(keyProtected.ExtendedKey.CipherText)
if err != nil {
return nil, err
}
derivedKey, err := getKDFKey(keyProtected.ExtendedKey, auth)
if err != nil {
return nil, err
}
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
if !bytes.Equal(calculatedMAC, mac) {
return nil, ErrDecrypt
}
plainText, err = aesCTRXOR(derivedKey[:16], cipherText, iv)
if err != nil {
return nil, err
}
return plainText, err
}
func getKDFKey(cryptoJSON CryptoJSON, auth string) ([]byte, error) {
authArray := []byte(auth)
salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
if err != nil {
return nil, err
}
dkLen := ensureInt(cryptoJSON.KDFParams["dklen"])
if cryptoJSON.KDF == keyHeaderKDF {
n := ensureInt(cryptoJSON.KDFParams["n"])
r := ensureInt(cryptoJSON.KDFParams["r"])
p := ensureInt(cryptoJSON.KDFParams["p"])
return scrypt.Key(authArray, salt, n, r, p, dkLen)
} else if cryptoJSON.KDF == "pbkdf2" {
c := ensureInt(cryptoJSON.KDFParams["c"])
prf := cryptoJSON.KDFParams["prf"].(string)
if prf != "hmac-sha256" {
return nil, fmt.Errorf("Unsupported PBKDF2 PRF: %s", prf)
}
key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New)
return key, nil
}
return nil, fmt.Errorf("Unsupported KDF: %s", cryptoJSON.KDF)
}
// TODO: can we do without this when unmarshalling dynamic JSON?
// why do integers in KDF params end up as float64 and not int after
// unmarshal?
func ensureInt(x interface{}) int {
res, ok := x.(int)
if !ok {
res = int(x.(float64))
}
return res
}
func aesCTRXOR(key, inText, iv []byte) ([]byte, error) {
// AES-128 is selected due to size of encryptKey.
aesBlock, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
stream := cipher.NewCTR(aesBlock, iv)
outText := make([]byte, len(inText))
stream.XORKeyStream(outText, inText)
return outText, err
}
func aesCBCDecrypt(key, cipherText, iv []byte) ([]byte, error) {
aesBlock, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
decrypter := cipher.NewCBCDecrypter(aesBlock, iv)
paddedPlaintext := make([]byte, len(cipherText))
decrypter.CryptBlocks(paddedPlaintext, cipherText)
plaintext := pkcs7Unpad(paddedPlaintext)
if plaintext == nil {
return nil, ErrDecrypt
}
return plaintext, err
}
// From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes
func pkcs7Unpad(in []byte) []byte {
if len(in) == 0 {
return nil
}
padding := in[len(in)-1]
if int(padding) > len(in) || padding > aes.BlockSize {
return nil
} else if padding == 0 {
return nil
}
for i := len(in) - 1; i > len(in)-int(padding)-1; i-- {
if in[i] != padding {
return nil
}
}
return in[:len(in)-int(padding)]
}
func RawKeyToCryptoJSON(rawKeyFile []byte) (cj CryptoJSON, e error) {
var keyJSON EncryptedKeyJSONV3
if e := json.Unmarshal(rawKeyFile, &keyJSON); e != nil {
return cj, fmt.Errorf("failed to read key file: %s", e)
}
return keyJSON.Crypto, e
}

View File

@ -0,0 +1,8 @@
package types
// Account represents an Ethereum account located at a specific location defined
// by the optional URL field.
type Account struct {
Address Address `json:"address"` // Ethereum account address derived from the key
URL string `json:"url"` // Optional resource locator within a backend
}

219
eth-node/types/address.go Normal file
View File

@ -0,0 +1,219 @@
package types
import (
"database/sql/driver"
"encoding/hex"
"encoding/json"
"fmt"
"math/big"
"reflect"
"strings"
"golang.org/x/crypto/sha3"
)
/////////// Address
// AddressLength is the expected length of the address
const AddressLength = 20
var (
addressT = reflect.TypeOf(Address{})
)
// Address represents the 20 byte address of an Ethereum account.
type Address [AddressLength]byte
// BytesToAddress returns Address with value b.
// If b is larger than len(h), b will be cropped from the left.
func BytesToAddress(b []byte) Address {
var a Address
a.SetBytes(b)
return a
}
// BigToAddress returns Address with byte values of b.
// If b is larger than len(h), b will be cropped from the left.
func BigToAddress(b *big.Int) Address { return BytesToAddress(b.Bytes()) }
// HexToAddress returns Address with byte values of s.
// If s is larger than len(h), s will be cropped from the left.
func HexToAddress(s string) Address { return BytesToAddress(FromHex(s)) }
// IsHexAddress verifies whether a string can represent a valid hex-encoded
// Ethereum address or not.
func IsHexAddress(s string) bool {
if has0xPrefix(s) {
s = s[2:]
}
return len(s) == 2*AddressLength && isHex(s)
}
// Bytes gets the string representation of the underlying address.
func (a Address) Bytes() []byte { return a[:] }
// Hash converts an address to a hash by left-padding it with zeros.
func (a Address) Hash() Hash { return BytesToHash(a[:]) }
// Hex returns an EIP55-compliant hex string representation of the address.
func (a Address) Hex() string {
unchecksummed := hex.EncodeToString(a[:])
sha := sha3.NewLegacyKeccak256()
_, _ = sha.Write([]byte(unchecksummed))
hash := sha.Sum(nil)
result := []byte(unchecksummed)
for i := 0; i < len(result); i++ {
hashByte := hash[i/2]
if i%2 == 0 {
hashByte = hashByte >> 4
} else {
hashByte &= 0xf
}
if result[i] > '9' && hashByte > 7 {
result[i] -= 32
}
}
return "0x" + string(result)
}
// String implements fmt.Stringer.
func (a Address) String() string {
return a.Hex()
}
// Format implements fmt.Formatter, forcing the byte slice to be formatted as is,
// without going through the stringer interface used for logging.
func (a Address) Format(s fmt.State, c rune) {
fmt.Fprintf(s, "%"+string(c), a[:])
}
// SetBytes sets the address to the value of b.
// If b is larger than len(a) it will panic.
func (a *Address) SetBytes(b []byte) {
if len(b) > len(a) {
b = b[len(b)-AddressLength:]
}
copy(a[AddressLength-len(b):], b)
}
// MarshalText returns the hex representation of a.
func (a Address) MarshalText() ([]byte, error) {
return HexBytes(a[:]).MarshalText()
}
// UnmarshalText parses a hash in hex syntax.
func (a *Address) UnmarshalText(input []byte) error {
return UnmarshalFixedText("Address", input, a[:])
}
// UnmarshalJSON parses a hash in hex syntax.
func (a *Address) UnmarshalJSON(input []byte) error {
return UnmarshalFixedJSON(addressT, input, a[:])
}
// Scan implements Scanner for database/sql.
func (a *Address) Scan(src interface{}) error {
srcB, ok := src.([]byte)
if !ok {
return fmt.Errorf("can't scan %T into Address", src)
}
if len(srcB) != AddressLength {
return fmt.Errorf("can't scan []byte of len %d into Address, want %d", len(srcB), AddressLength)
}
copy(a[:], srcB)
return nil
}
// Value implements valuer for database/sql.
func (a Address) Value() (driver.Value, error) {
return a[:], nil
}
// ImplementsGraphQLType returns true if Hash implements the specified GraphQL type.
func (a Address) ImplementsGraphQLType(name string) bool { return name == "Address" }
// UnmarshalGraphQL unmarshals the provided GraphQL query data.
func (a *Address) UnmarshalGraphQL(input interface{}) error {
var err error
switch input := input.(type) {
case string:
err = a.UnmarshalText([]byte(input))
default:
err = fmt.Errorf("Unexpected type for Address: %v", input)
}
return err
}
// UnprefixedAddress allows marshaling an Address without 0x prefix.
type UnprefixedAddress Address
// UnmarshalText decodes the address from hex. The 0x prefix is optional.
func (a *UnprefixedAddress) UnmarshalText(input []byte) error {
return UnmarshalFixedUnprefixedText("UnprefixedAddress", input, a[:])
}
// MarshalText encodes the address as hex.
func (a UnprefixedAddress) MarshalText() ([]byte, error) {
return []byte(hex.EncodeToString(a[:])), nil
}
// MixedcaseAddress retains the original string, which may or may not be
// correctly checksummed
type MixedcaseAddress struct {
addr Address
original string
}
// NewMixedcaseAddress constructor (mainly for testing)
func NewMixedcaseAddress(addr Address) MixedcaseAddress {
return MixedcaseAddress{addr: addr, original: addr.Hex()}
}
// NewMixedcaseAddressFromString is mainly meant for unit-testing
func NewMixedcaseAddressFromString(hexaddr string) (*MixedcaseAddress, error) {
if !IsHexAddress(hexaddr) {
return nil, fmt.Errorf("Invalid address")
}
a := FromHex(hexaddr)
return &MixedcaseAddress{addr: BytesToAddress(a), original: hexaddr}, nil
}
// UnmarshalJSON parses MixedcaseAddress
func (ma *MixedcaseAddress) UnmarshalJSON(input []byte) error {
if err := UnmarshalFixedJSON(addressT, input, ma.addr[:]); err != nil {
return err
}
return json.Unmarshal(input, &ma.original)
}
// MarshalJSON marshals the original value
func (ma *MixedcaseAddress) MarshalJSON() ([]byte, error) {
if strings.HasPrefix(ma.original, "0x") || strings.HasPrefix(ma.original, "0X") {
return json.Marshal(fmt.Sprintf("0x%s", ma.original[2:]))
}
return json.Marshal(fmt.Sprintf("0x%s", ma.original))
}
// Address returns the address
func (ma *MixedcaseAddress) Address() Address {
return ma.addr
}
// String implements fmt.Stringer
func (ma *MixedcaseAddress) String() string {
if ma.ValidChecksum() {
return fmt.Sprintf("%s [chksum ok]", ma.original)
}
return fmt.Sprintf("%s [chksum INVALID]", ma.original)
}
// ValidChecksum returns true if the address has valid checksum
func (ma *MixedcaseAddress) ValidChecksum() bool {
return ma.original == ma.addr.Hex()
}
// Original returns the mixed-case input string
func (ma *MixedcaseAddress) Original() string {
return ma.original
}

46
eth-node/types/bytes.go Normal file
View File

@ -0,0 +1,46 @@
package types
import "encoding/hex"
// has0xPrefix validates str begins with '0x' or '0X'.
func has0xPrefix(str string) bool {
return len(str) >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')
}
// isHexCharacter returns bool of c being a valid hexadecimal.
func isHexCharacter(c byte) bool {
return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')
}
// isHex validates whether each byte is valid hexadecimal string.
func isHex(str string) bool {
if len(str)%2 != 0 {
return false
}
for _, c := range []byte(str) {
if !isHexCharacter(c) {
return false
}
}
return true
}
// Bytes2Hex returns the hexadecimal encoding of d.
func Bytes2Hex(d []byte) string {
return hex.EncodeToString(d)
}
// Hex2Bytes returns the bytes represented by the hexadecimal string str.
func Hex2Bytes(str string) []byte {
if has0xPrefix(str) {
str = str[2:]
}
h, _ := hex.DecodeString(str)
return h
}
// ToHex returns the hex string representation of bytes with 0x prefix.
func ToHex(bytes []byte) string {
return "0x" + Bytes2Hex(bytes)
}

8
eth-node/types/const.go Normal file
View File

@ -0,0 +1,8 @@
package types
const (
// PubKeyLength represents the length (in bytes) of an uncompressed public key
PubKeyLength = 512 / 8
// AesKeyLength represents the length (in bytes) of an private key
AesKeyLength = 256 / 8
)

27
eth-node/types/ens/ens.go Normal file
View File

@ -0,0 +1,27 @@
package enstypes
import (
"crypto/ecdsa"
"github.com/ethereum/go-ethereum/common"
)
type ENSVerifier interface {
// CheckBatch verifies that a registered ENS name matches the expected public key
CheckBatch(ensDetails []ENSDetails, rpcEndpoint, contractAddress string) (map[string]ENSResponse, error)
ReverseResolve(address common.Address, rpcEndpoint string) (string, error)
}
type ENSDetails struct {
Name string `json:"name"`
PublicKeyString string `json:"publicKey"`
}
type ENSResponse struct {
Name string `json:"name"`
Verified bool `json:"verified"`
VerifiedAt int64 `json:"verifiedAt"`
Error error `json:"error"`
PublicKey *ecdsa.PublicKey `json:"-"`
PublicKeyString string `json:"publicKey"`
}

View File

@ -0,0 +1,88 @@
package types
// Envelope represents a clear-text data packet to transmit through the Whisper
// network. Its contents may or may not be encrypted and signed.
type Envelope interface {
Wrapped
Hash() Hash // cached hash of the envelope to avoid rehashing every time
Bloom() []byte
PoW() float64
Expiry() uint32
TTL() uint32
Topic() TopicType
Size() int
}
// EventType used to define known envelope events.
type EventType string
// NOTE: This list of event names is extracted from Geth. It must be kept in sync, or otherwise a mapping layer needs to be created
const (
// EventEnvelopeSent fires when envelope was sent to a peer.
EventEnvelopeSent EventType = "envelope.sent"
// EventEnvelopeExpired fires when envelop expired
EventEnvelopeExpired EventType = "envelope.expired"
// EventEnvelopeReceived is sent once envelope was received from a peer.
// EventEnvelopeReceived must be sent to the feed even if envelope was previously in the cache.
// And event, ideally, should contain information about peer that sent envelope to us.
EventEnvelopeReceived EventType = "envelope.received"
// EventBatchAcknowledged is sent when batch of envelopes was acknowledged by a peer.
EventBatchAcknowledged EventType = "batch.acknowledged"
// EventEnvelopeAvailable fires when envelop is available for filters
EventEnvelopeAvailable EventType = "envelope.available"
// EventMailServerRequestSent fires when such request is sent.
EventMailServerRequestSent EventType = "mailserver.request.sent"
// EventMailServerRequestCompleted fires after mailserver sends all the requested messages
EventMailServerRequestCompleted EventType = "mailserver.request.completed"
// EventMailServerRequestExpired fires after mailserver the request TTL ends.
// This event is independent and concurrent to EventMailServerRequestCompleted.
// Request should be considered as expired only if expiry event was received first.
EventMailServerRequestExpired EventType = "mailserver.request.expired"
// EventMailServerEnvelopeArchived fires after an envelope has been archived
EventMailServerEnvelopeArchived EventType = "mailserver.envelope.archived"
// EventMailServerSyncFinished fires when the sync of messages is finished.
EventMailServerSyncFinished EventType = "mailserver.sync.finished"
)
const (
// EnvelopeTimeNotSynced represents the code passed to notify of a clock skew situation
EnvelopeTimeNotSynced uint = 1000
// EnvelopeOtherError represents the code passed to notify of a generic error situation
EnvelopeOtherError
)
// EnvelopeEvent used for envelopes events.
type EnvelopeEvent struct {
Event EventType
Hash Hash
Batch Hash
Peer EnodeID
Data interface{}
}
// EnvelopeError code and optional description of the error.
type EnvelopeError struct {
Hash Hash
Code uint
Description string
}
// Subscription represents a stream of events. The carrier of the events is typically a
// channel, but isn't part of the interface.
//
// Subscriptions can fail while established. Failures are reported through an error
// channel. It receives a value if there is an issue with the subscription (e.g. the
// network connection delivering the events has been closed). Only one value will ever be
// sent.
//
// The error channel is closed when the subscription ends successfully (i.e. when the
// source of events is closed). It is also closed when Unsubscribe is called.
//
// The Unsubscribe method cancels the sending of events. You must call Unsubscribe in all
// cases to ensure that resources related to the subscription are released. It can be
// called any number of times.
type Subscription interface {
Err() <-chan error // returns the error channel
Unsubscribe() // cancels sending of events, closing the error channel
}

6
eth-node/types/filter.go Normal file
View File

@ -0,0 +1,6 @@
package types
// Filter represents a Whisper message filter
type Filter interface {
ID() string
}

119
eth-node/types/hash.go Normal file
View File

@ -0,0 +1,119 @@
// Code extracted from vendor/github.com/ethereum/go-ethereum/common/types.go
package types
import (
"encoding/hex"
"fmt"
"reflect"
)
const (
// HashLength is the expected length of the hash
HashLength = 32
)
// Hash represents the 32 byte Keccak256 hash of arbitrary data.
type Hash [HashLength]byte
var hashT = reflect.TypeOf(Hash{})
// Encode encodes b as a hex string with 0x prefix.
func encode(b []byte) string {
enc := make([]byte, len(b)*2+2)
copy(enc, "0x")
hex.Encode(enc[2:], b)
return string(enc)
}
// FromHex returns the bytes represented by the hexadecimal string s.
// s may be prefixed with "0x".
func FromHex(s string) []byte {
if has0xPrefix(s) {
s = s[2:]
}
if len(s)%2 == 1 {
s = "0" + s
}
return Hex2Bytes(s)
}
// HexToHash sets byte representation of s to hash.
// If b is larger than len(h), b will be cropped from the left.
func HexToHash(s string) Hash { return BytesToHash(FromHex(s)) }
// Hex converts a hash to a hex string.
func (h *Hash) Hex() string { return encode(h[:]) }
// Bytes gets the byte representation of the underlying hash.
func (h Hash) Bytes() []byte { return h[:] }
// String implements the stringer interface and is used also by the logger when
// doing full logging into a file.
func (h Hash) String() string {
return h.Hex()
}
// SetBytes sets the hash to the value of b.
// If b is larger than len(h), b will be cropped from the left.
func (h *Hash) SetBytes(b []byte) {
if len(b) > len(h) {
b = b[len(b)-HashLength:]
}
copy(h[HashLength-len(b):], b)
}
// UnmarshalText parses a hash in hex syntax.
func (h *Hash) UnmarshalText(input []byte) error {
return UnmarshalFixedText("Hash", input, h[:])
}
// UnmarshalJSON parses a hash in hex syntax.
func (h *Hash) UnmarshalJSON(input []byte) error {
return UnmarshalFixedJSON(hashT, input, h[:])
}
// MarshalText returns the hex representation of h.
func (h Hash) MarshalText() ([]byte, error) {
return HexBytes(h[:]).MarshalText()
}
// BytesToHash sets b to hash.
// If b is larger than len(h), b will be cropped from the left.
func BytesToHash(b []byte) Hash {
var h Hash
h.SetBytes(b)
return h
}
// UnmarshalFixedJSON decodes the input as a string with 0x prefix. The length of out
// determines the required input length. This function is commonly used to implement the
// UnmarshalJSON method for fixed-size types.
func UnmarshalFixedJSON(typ reflect.Type, input, out []byte) error {
if !isString(input) {
return errNonString(typ)
}
return wrapTypeError(UnmarshalFixedText(typ.String(), input[1:len(input)-1], out), typ)
}
// UnmarshalFixedText decodes the input as a string with 0x prefix. The length of out
// determines the required input length. This function is commonly used to implement the
// UnmarshalText method for fixed-size types.
func UnmarshalFixedText(typname string, input, out []byte) error {
raw, err := checkText(input, true)
if err != nil {
return err
}
if len(raw)/2 != len(out) {
return fmt.Errorf("hex string has length %d, want %d for %s", len(raw), len(out)*2, typname)
}
// Pre-verify syntax before modifying out.
for _, b := range raw {
if decodeNibble(b) == badNibble {
return ErrSyntax
}
}
_, err = hex.Decode(out, raw)
return err
}

58
eth-node/types/hex.go Normal file
View File

@ -0,0 +1,58 @@
// Code extracted from vendor/github.com/ethereum/go-ethereum/common/hexutil/hexutil.go
package types
import (
"encoding/hex"
"fmt"
"reflect"
)
var (
bytesT = reflect.TypeOf(HexBytes(nil))
)
// HexBytes marshals/unmarshals as a JSON string with 0x prefix.
// The empty slice marshals as "0x".
type HexBytes []byte
func (b HexBytes) Bytes() []byte {
result := make([]byte, len(b)*2+2)
copy(result, `0x`)
hex.Encode(result[2:], b)
return result
}
// MarshalText implements encoding.TextMarshaler
func (b HexBytes) MarshalText() ([]byte, error) {
return b.Bytes(), nil
}
// UnmarshalJSON implements json.Unmarshaler.
func (b *HexBytes) UnmarshalJSON(input []byte) error {
if !isString(input) {
return errNonString(bytesT)
}
return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), bytesT)
}
// UnmarshalFixedUnprefixedText decodes the input as a string with optional 0x prefix. The
// length of out determines the required input length. This function is commonly used to
// implement the UnmarshalText method for fixed-size types.
func UnmarshalFixedUnprefixedText(typname string, input, out []byte) error {
raw, err := checkText(input, false)
if err != nil {
return err
}
if len(raw)/2 != len(out) {
return fmt.Errorf("hex string has length %d, want %d for %s", len(raw), len(out)*2, typname)
}
// Pre-verify syntax before modifying out.
for _, b := range raw {
if decodeNibble(b) == badNibble {
return ErrSyntax
}
}
_, err = hex.Decode(out, raw)
return err
}

180
eth-node/types/json.go Normal file
View File

@ -0,0 +1,180 @@
// Code extracted from vendor/github.com/ethereum/go-ethereum/common/hexutil/json.go
package types
import (
"encoding/hex"
"encoding/json"
"fmt"
"reflect"
"strconv"
)
const (
badNibble = ^uint64(0)
uintBits = 32 << (uint64(^uint(0)) >> 63)
)
// Errors
var (
ErrEmptyString = &decError{"empty hex string"}
ErrSyntax = &decError{"invalid hex string"}
ErrMissingPrefix = &decError{"hex string without 0x prefix"}
ErrOddLength = &decError{"hex string of odd length"}
ErrEmptyNumber = &decError{"hex string \"0x\""}
ErrLeadingZero = &decError{"hex number with leading zero digits"}
ErrUint64Range = &decError{"hex number > 64 bits"}
ErrUintRange = &decError{fmt.Sprintf("hex number > %d bits", uintBits)}
ErrBig256Range = &decError{"hex number > 256 bits"}
)
type decError struct{ msg string }
func (err decError) Error() string { return err.msg }
func decodeNibble(in byte) uint64 {
switch {
case in >= '0' && in <= '9':
return uint64(in - '0')
case in >= 'A' && in <= 'F':
return uint64(in - 'A' + 10)
case in >= 'a' && in <= 'f':
return uint64(in - 'a' + 10)
default:
return badNibble
}
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (b *HexBytes) UnmarshalText(input []byte) error {
raw, err := checkText(input, true)
if err != nil {
return err
}
dec := make([]byte, len(raw)/2)
if _, err = hex.Decode(dec, raw); err != nil {
err = mapError(err)
} else {
*b = dec
}
return err
}
// UnmarshalFixedHexText decodes the input as a string with 0x prefix. The length of out
// determines the required input length. This function is commonly used to implement the
// UnmarshalText method for fixed-size types.
func UnmarshalFixedHexText(typname string, input, out []byte) error {
raw, err := checkText(input, true)
if err != nil {
return err
}
if len(raw)/2 != len(out) {
return fmt.Errorf("hex string has length %d, want %d for %s", len(raw), len(out)*2, typname)
}
// Pre-verify syntax before modifying out.
for _, b := range raw {
if decodeNibble(b) == badNibble {
return ErrSyntax
}
}
_, err = hex.Decode(out, raw)
return err
}
// String returns the hex encoding of b.
func (b HexBytes) String() string {
return EncodeHex(b)
}
// EncodeHex encodes b as a hex string with 0x prefix.
func EncodeHex(b []byte) string {
enc := make([]byte, len(b)*2+2)
copy(enc, "0x")
hex.Encode(enc[2:], b)
return string(enc)
}
// EncodeHex encodes bs as a hex strings with 0x prefix.
func EncodeHexes(bs [][]byte) []string {
result := make([]string, len(bs))
for i, b := range bs {
result[i] = EncodeHex(b)
}
return result
}
// DecodeHex decodes a hex string with 0x prefix.
func DecodeHex(input string) ([]byte, error) {
if len(input) == 0 {
return nil, ErrEmptyString
}
if !has0xPrefix(input) {
return nil, ErrMissingPrefix
}
b, err := hex.DecodeString(input[2:])
if err != nil {
err = mapError(err)
}
return b, err
}
// MustDecodeHex decodes a hex string with 0x prefix. It panics for invalid input.
func MustDecodeHex(input string) []byte {
dec, err := DecodeHex(input)
if err != nil {
panic(err)
}
return dec
}
func isString(input []byte) bool {
return len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"'
}
func bytesHave0xPrefix(input []byte) bool {
return len(input) >= 2 && input[0] == '0' && (input[1] == 'x' || input[1] == 'X')
}
func checkText(input []byte, wantPrefix bool) ([]byte, error) {
if len(input) == 0 {
return nil, nil // empty strings are allowed
}
if bytesHave0xPrefix(input) {
input = input[2:]
} else if wantPrefix {
return nil, ErrMissingPrefix
}
if len(input)%2 != 0 {
return nil, ErrOddLength
}
return input, nil
}
func mapError(err error) error {
if err, ok := err.(*strconv.NumError); ok {
switch err.Err {
case strconv.ErrRange:
return ErrUint64Range
case strconv.ErrSyntax:
return ErrSyntax
}
}
if _, ok := err.(hex.InvalidByteError); ok {
return ErrSyntax
}
if err == hex.ErrLength {
return ErrOddLength
}
return err
}
func wrapTypeError(err error, typ reflect.Type) error {
if _, ok := err.(*decError); ok {
return &json.UnmarshalTypeError{Value: err.Error(), Type: typ}
}
return err
}
func errNonString(typ reflect.Type) error {
return &json.UnmarshalTypeError{Value: "non-string", Type: typ}
}

25
eth-node/types/key.go Normal file
View File

@ -0,0 +1,25 @@
package types
import (
"crypto/ecdsa"
"github.com/google/uuid"
"github.com/status-im/status-go/extkeys"
)
type Key struct {
ID uuid.UUID // Version 4 "random" for unique id not derived from key data
// to simplify lookups we also store the address
Address Address
// we only store privkey as pubkey/address can be derived from it
// privkey in this struct is always in plaintext
PrivateKey *ecdsa.PrivateKey
// ExtendedKey is the extended key of the PrivateKey itself, and it's used
// to derive child keys.
ExtendedKey *extkeys.ExtendedKey
// Deprecated: SubAccountIndex
// It was use in Status to keep track of the number of sub-account created
// before having multi-account support.
SubAccountIndex uint32
}

View File

@ -0,0 +1,26 @@
package types
import (
"crypto/ecdsa"
"github.com/status-im/status-go/extkeys"
)
type KeyStore interface {
// ImportAccount imports the account specified with privateKey.
ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (Account, error)
// ImportSingleExtendedKey imports an extended key setting it in both the PrivateKey and ExtendedKey fields
// of the Key struct.
// ImportExtendedKey is used in older version of Status where PrivateKey is set to be the BIP44 key at index 0,
// and ExtendedKey is the extended key of the BIP44 key at index 1.
ImportSingleExtendedKey(extKey *extkeys.ExtendedKey, passphrase string) (Account, error)
// ImportExtendedKeyForPurpose stores ECDSA key (obtained from extended key) along with CKD#2 (root for sub-accounts)
// If key file is not found, it is created. Key is encrypted with the given passphrase.
// Deprecated: status-go is now using ImportSingleExtendedKey
ImportExtendedKeyForPurpose(keyPurpose extkeys.KeyPurpose, extKey *extkeys.ExtendedKey, passphrase string) (Account, error)
// AccountDecryptedKey returns decrypted key for account (provided that password is correct).
AccountDecryptedKey(a Account, auth string) (Account, *Key, error)
// Delete deletes the key matched by account if the passphrase is correct.
// If the account contains no filename, the address must match a unique key.
Delete(a Account) error
}

View File

@ -0,0 +1,30 @@
package types
// MailServerResponse is the response payload sent by the mailserver.
type MailServerResponse struct {
LastEnvelopeHash Hash
Cursor []byte
Error error
}
// SyncMailRequest contains details which envelopes should be synced
// between Mail Servers.
type SyncMailRequest struct {
// Lower is a lower bound of time range for which messages are requested.
Lower uint32
// Upper is a lower bound of time range for which messages are requested.
Upper uint32
// Bloom is a bloom filter to filter envelopes.
Bloom []byte
// Limit is the max number of envelopes to return.
Limit uint32
// Cursor is used for pagination of the results.
Cursor []byte
}
// SyncEventResponse is a response from the Mail Server
// form which the peer received envelopes.
type SyncEventResponse struct {
Cursor []byte
Error string
}

View File

@ -0,0 +1,11 @@
package types
import (
"crypto/ecdsa"
)
// NegotiatedSecret represents a negotiated secret (both public and private keys)
type NegotiatedSecret struct {
PublicKey *ecdsa.PublicKey
Key []byte
}

26
eth-node/types/node.go Normal file
View File

@ -0,0 +1,26 @@
package types
import (
"fmt"
"go.uber.org/zap"
enstypes "github.com/waku-org/waku-go-bindings/eth-node/types/ens"
)
// EnodeID is a unique identifier for each node.
type EnodeID [32]byte
// ID prints as a long hexadecimal number.
func (n EnodeID) String() string {
return fmt.Sprintf("%x", n[:])
}
type Node interface {
NewENSVerifier(logger *zap.Logger) enstypes.ENSVerifier
GetWaku(ctx interface{}) (Waku, error)
GetWakuV2(ctx interface{}) (Waku, error)
AddPeer(url string) error
RemovePeer(url string) error
PeersCount() int
}

97
eth-node/types/rpc.go Normal file
View File

@ -0,0 +1,97 @@
package types
import (
"context"
)
// NewMessage represents a new whisper message that is posted through the RPC.
type NewMessage struct {
SymKeyID string `json:"symKeyID"`
PublicKey []byte `json:"pubKey"`
SigID string `json:"sig"`
TTL uint32 `json:"ttl"`
PubsubTopic string `json:"pubsubTopic"`
Topic TopicType `json:"topic"`
Payload []byte `json:"payload"`
Padding []byte `json:"padding"`
PowTime uint32 `json:"powTime"`
PowTarget float64 `json:"powTarget"`
TargetPeer string `json:"targetPeer"`
Ephemeral bool `json:"ephemeral"`
Priority *int `json:"priority"`
}
// Message is the RPC representation of a whisper message.
type Message struct {
Sig []byte `json:"sig,omitempty"`
TTL uint32 `json:"ttl"`
Timestamp uint32 `json:"timestamp"`
PubsubTopic string `json:"pubsubTopic"`
Topic TopicType `json:"topic"`
Payload []byte `json:"payload"`
Padding []byte `json:"padding"`
PoW float64 `json:"pow"`
Hash []byte `json:"hash"`
Dst []byte `json:"recipientPublicKey,omitempty"`
P2P bool `json:"bool,omitempty"`
ThirdPartyID string `json:"thirdPartyId,omitempty"`
}
// Criteria holds various filter options for inbound messages.
type Criteria struct {
SymKeyID string `json:"symKeyID"`
PrivateKeyID string `json:"privateKeyID"`
Sig []byte `json:"sig"`
MinPow float64 `json:"minPow"`
PubsubTopic string `json:"pubsubTopic"`
Topics []TopicType `json:"topics"`
AllowP2P bool `json:"allowP2P"`
}
// PublicWhisperAPI provides the whisper RPC service that can be
// use publicly without security implications.
type PublicWhisperAPI interface {
// AddPrivateKey imports the given private key.
AddPrivateKey(ctx context.Context, privateKey HexBytes) (string, error)
// GenerateSymKeyFromPassword derives a key from the given password, stores it, and returns its ID.
GenerateSymKeyFromPassword(ctx context.Context, passwd string) (string, error)
// DeleteKeyPair removes the key with the given key if it exists.
DeleteKeyPair(ctx context.Context, key string) (bool, error)
// Post posts a message on the Whisper network.
// returns the hash of the message in case of success.
Post(ctx context.Context, req NewMessage) ([]byte, error)
// NewMessageFilter creates a new filter that can be used to poll for
// (new) messages that satisfy the given criteria.
NewMessageFilter(req Criteria) (string, error)
// GetFilterMessages returns the messages that match the filter criteria and
// are received between the last poll and now.
GetFilterMessages(id string) ([]*Message, error)
// BloomFilter returns the current bloomfilter of the node
BloomFilter() []byte
}
// PublicWakuAPI provides the waku RPC service that can be
// use publicly without security implications.
type PublicWakuAPI interface {
// AddPrivateKey imports the given private key.
AddPrivateKey(ctx context.Context, privateKey HexBytes) (string, error)
// GenerateSymKeyFromPassword derives a key from the given password, stores it, and returns its ID.
GenerateSymKeyFromPassword(ctx context.Context, passwd string) (string, error)
// DeleteKeyPair removes the key with the given key if it exists.
DeleteKeyPair(ctx context.Context, key string) (bool, error)
// Post posts a message on the Whisper network.
// returns the hash of the message in case of success.
Post(ctx context.Context, req NewMessage) ([]byte, error)
// NewMessageFilter creates a new filter that can be used to poll for
// (new) messages that satisfy the given criteria.
NewMessageFilter(req Criteria) (string, error)
// GetFilterMessages returns the messages that match the filter criteria and
// are received between the last poll and now.
GetFilterMessages(id string) ([]*Message, error)
BloomFilter() []byte
}

6
eth-node/types/stats.go Normal file
View File

@ -0,0 +1,6 @@
package types
type StatsSummary struct {
UploadRate uint64 `json:"uploadRate"`
DownloadRate uint64 `json:"downloadRate"`
}

View File

@ -0,0 +1,11 @@
package types
// SubscriptionOptions represents the parameters passed to Subscribe()
// to customize the subscription behavior.
type SubscriptionOptions struct {
PrivateKeyID string
SymKeyID string
PoW float64
PubsubTopic string
Topics [][]byte
}

103
eth-node/types/topic.go Normal file
View File

@ -0,0 +1,103 @@
package types
import (
"github.com/ethereum/go-ethereum/common/hexutil"
)
const (
// TopicLength is the expected length of the topic, in bytes
TopicLength = 4
// BloomFilterSize is the expected length of a bloom filter byte array, in bytes
BloomFilterSize = 64
)
// TopicType represents a cryptographically secure, probabilistic partial
// classifications of a message, determined as the first (left) 4 bytes of the
// SHA3 hash of some arbitrary data given by the original author of the message.
type TopicType [TopicLength]byte
// BytesToTopic converts from the byte array representation of a topic
// into the TopicType type.
func BytesToTopic(b []byte) (t TopicType) {
sz := TopicLength
if x := len(b); x < TopicLength {
sz = x
}
for i := 0; i < sz; i++ {
t[i] = b[i]
}
return t
}
// String converts a topic byte array to a string representation.
func (t TopicType) String() string {
return EncodeHex(t[:])
}
func (t TopicType) Bytes() []byte {
return TopicTypeToByteArray(t)
}
// MarshalText returns the hex representation of t.
func (t TopicType) MarshalText() ([]byte, error) {
return HexBytes(t[:]).MarshalText()
}
// UnmarshalText parses a hex representation to a topic.
func (t *TopicType) UnmarshalText(input []byte) error {
return UnmarshalFixedText("Topic", input, t[:])
}
// TopicToBloom converts the topic (4 bytes) to the bloom filter (64 bytes)
func TopicToBloom(topic TopicType) []byte {
b := make([]byte, BloomFilterSize)
var index [3]int
for j := 0; j < 3; j++ {
index[j] = int(topic[j])
if (topic[3] & (1 << uint(j))) != 0 {
index[j] += 256
}
}
for j := 0; j < 3; j++ {
byteIndex := index[j] / 8
bitIndex := index[j] % 8
b[byteIndex] = (1 << uint(bitIndex))
}
return b
}
// BloomFilterMatch returns true if a sample matches a bloom filter
func BloomFilterMatch(filter, sample []byte) bool {
if filter == nil {
return true
}
for i := 0; i < BloomFilterSize; i++ {
f := filter[i]
s := sample[i]
if (f | s) != f {
return false
}
}
return true
}
// MakeFullNodeBloom returns a bloom filter which matches all topics
func MakeFullNodeBloom() []byte {
bloom := make([]byte, BloomFilterSize)
for i := 0; i < BloomFilterSize; i++ {
bloom[i] = 0xFF
}
return bloom
}
func StringToTopic(s string) (t TopicType) {
str, _ := hexutil.Decode(s)
return BytesToTopic(str)
}
func TopicTypeToByteArray(t TopicType) []byte {
return t[:4]
}

253
eth-node/types/waku.go Normal file
View File

@ -0,0 +1,253 @@
package types
import (
"context"
"crypto/ecdsa"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"sync"
"time"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
"github.com/multiformats/go-multiaddr"
"github.com/pborman/uuid"
"github.com/waku-org/go-waku/waku/v2/api/history"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/status-im/status-go/connection"
)
type ConnStatus struct {
IsOnline bool `json:"isOnline"`
Peers PeerStats `json:"peers"`
}
type PeerStats map[peer.ID]WakuV2Peer
func (m PeerStats) MarshalJSON() ([]byte, error) {
tmpMap := make(map[string]WakuV2Peer)
for k, v := range m {
tmpMap[k.String()] = v
}
return json.Marshal(tmpMap)
}
type WakuV2Peer struct {
Protocols []protocol.ID `json:"protocols"`
Addresses []multiaddr.Multiaddr `json:"addresses"`
}
type PeerList struct {
FullMeshPeers peer.IDSlice `json:"fullMesh"`
AllPeers peer.IDSlice `json:"all"`
}
type ConnStatusSubscription struct {
sync.RWMutex
ID string
C chan ConnStatus
active bool
}
func NewConnStatusSubscription() *ConnStatusSubscription {
return &ConnStatusSubscription{
ID: uuid.NewRandom().String(),
C: make(chan ConnStatus, 100),
active: true,
}
}
func (u *ConnStatusSubscription) Active() bool {
u.RLock()
defer u.RUnlock()
return u.active
}
func (u *ConnStatusSubscription) Unsubscribe() {
u.Lock()
defer u.Unlock()
close(u.C)
u.active = false
}
func (u *ConnStatusSubscription) Send(s ConnStatus) bool {
u.RLock()
defer u.RUnlock()
if !u.active {
return false
}
u.C <- s
return true
}
type WakuKeyManager interface {
// GetPrivateKey retrieves the private key of the specified identity.
GetPrivateKey(id string) (*ecdsa.PrivateKey, error)
// AddKeyPair imports a asymmetric private key and returns a deterministic identifier.
AddKeyPair(key *ecdsa.PrivateKey) (string, error)
// DeleteKeyPair deletes the key with the specified ID if it exists.
DeleteKeyPair(keyID string) bool
// DeleteKeyPairs deletes all the keys
DeleteKeyPairs() error
AddSymKeyDirect(key []byte) (string, error)
AddSymKeyFromPassword(password string) (string, error)
DeleteSymKey(id string) bool
GetSymKey(id string) ([]byte, error)
}
// Whisper represents a dark communication interface through the Ethereum
// network, using its very own P2P communication layer.
type Waku interface {
PublicWakuAPI() PublicWakuAPI
// Waku protocol version
Version() uint
// PeerCount
PeerCount() int
ListenAddresses() ([]multiaddr.Multiaddr, error)
RelayPeersByTopic(topic string) (*PeerList, error)
ENR() (*enode.Node, error)
Peers() PeerStats
StartDiscV5() error
StopDiscV5() error
SubscribeToPubsubTopic(topic string, optPublicKey *ecdsa.PublicKey) error
UnsubscribeFromPubsubTopic(topic string) error
StorePubsubTopicKey(topic string, privKey *ecdsa.PrivateKey) error
RetrievePubsubTopicKey(topic string) (*ecdsa.PrivateKey, error)
RemovePubsubTopicKey(topic string) error
AddRelayPeer(address multiaddr.Multiaddr) (peer.ID, error)
DialPeer(address multiaddr.Multiaddr) error
DialPeerByID(peerID peer.ID) error
DropPeer(peerID peer.ID) error
SubscribeToConnStatusChanges() (*ConnStatusSubscription, error)
SetCriteriaForMissingMessageVerification(peerID peer.ID, pubsubTopic string, contentTopics []TopicType) error
// MinPow returns the PoW value required by this node.
MinPow() float64
// BloomFilter returns the aggregated bloom filter for all the topics of interest.
// The nodes are required to send only messages that match the advertised bloom filter.
// If a message does not match the bloom, it will tantamount to spam, and the peer will
// be disconnected.
BloomFilter() []byte
// GetCurrentTime returns current time.
GetCurrentTime() time.Time
// GetPrivateKey retrieves the private key of the specified identity.
GetPrivateKey(id string) (*ecdsa.PrivateKey, error)
SubscribeEnvelopeEvents(events chan<- EnvelopeEvent) Subscription
// AddKeyPair imports a asymmetric private key and returns a deterministic identifier.
AddKeyPair(key *ecdsa.PrivateKey) (string, error)
// DeleteKeyPair deletes the key with the specified ID if it exists.
DeleteKeyPair(keyID string) bool
AddSymKeyDirect(key []byte) (string, error)
AddSymKeyFromPassword(password string) (string, error)
DeleteSymKey(id string) bool
GetSymKey(id string) ([]byte, error)
MaxMessageSize() uint32
GetStats() StatsSummary
Subscribe(opts *SubscriptionOptions) (string, error)
GetFilter(id string) Filter
Unsubscribe(ctx context.Context, id string) error
UnsubscribeMany(ids []string) error
// ProcessingP2PMessages indicates whether there are in-flight p2p messages
ProcessingP2PMessages() bool
// MarkP2PMessageAsProcessed tells the waku layer that a P2P message has been processed
MarkP2PMessageAsProcessed(common.Hash)
// ConnectionChanged is called whenever the client knows its connection status has changed
ConnectionChanged(connection.State)
// ClearEnvelopesCache clears waku envelopes cache
ClearEnvelopesCache()
// ConfirmMessageDelivered updates a message has been delivered in waku
ConfirmMessageDelivered(hash []common.Hash)
// PeerID returns node's PeerID
PeerID() peer.ID
// GetActiveStorenode returns the peer ID of the currently active storenode. It will be empty if no storenode is active
GetActiveStorenode() peer.ID
// OnStorenodeAvailableOneShot returns a channel that will be triggered only once when a storenode becomes available
OnStorenodeAvailableOneShot() <-chan struct{}
// OnStorenodeChanged is triggered when a new storenode is promoted to become the active storenode or when the active storenode is removed
OnStorenodeChanged() <-chan peer.ID
// OnStorenodeNotWorking is triggered when the last active storenode fails to return results consistently
OnStorenodeNotWorking() <-chan struct{}
// OnStorenodeAvailable is triggered when there is a new active storenode selected
OnStorenodeAvailable() <-chan peer.ID
// WaitForAvailableStoreNode will wait for a storenode to be available until `timeout` happens
WaitForAvailableStoreNode(timeout time.Duration) bool
// SetStorenodeConfigProvider will set the configuration provider for the storenode cycle
SetStorenodeConfigProvider(c history.StorenodeConfigProvider)
// ProcessMailserverBatch will receive a criteria and storenode and execute a query
ProcessMailserverBatch(
ctx context.Context,
batch MailserverBatch,
storenodeID peer.ID,
pageLimit uint64,
shouldProcessNextPage func(int) (bool, uint64),
processEnvelopes bool,
) error
// IsStorenodeAvailable is used to determine whether a storenode is available or not
IsStorenodeAvailable(peerID peer.ID) bool
PerformStorenodeTask(fn func() error, opts ...history.StorenodeTaskOption) error
// DisconnectActiveStorenode will trigger a disconnection of the active storenode, and potentially execute a cycling so a new storenode is promoted
DisconnectActiveStorenode(ctx context.Context, backoff time.Duration, shouldCycle bool)
}
type MailserverBatch struct {
From uint32
To uint32
Cursor string
PubsubTopic string
Topics []TopicType
ChatIDs []string
}
func (mb *MailserverBatch) Hash() string {
data := fmt.Sprintf("%d%d%s%s%v%v", mb.From, mb.To, mb.Cursor, mb.PubsubTopic, mb.Topics, mb.ChatIDs)
hash := sha256.Sum256([]byte(data))
return hex.EncodeToString(hash[:4])
}

49
eth-node/types/whisper.go Normal file
View File

@ -0,0 +1,49 @@
package types
import (
"crypto/ecdsa"
"time"
)
// Whisper represents a dark communication interface through the Ethereum
// network, using its very own P2P communication layer.
type Whisper interface {
PublicWhisperAPI() PublicWhisperAPI
// MinPow returns the PoW value required by this node.
MinPow() float64
// BloomFilter returns the aggregated bloom filter for all the topics of interest.
// The nodes are required to send only messages that match the advertised bloom filter.
// If a message does not match the bloom, it will tantamount to spam, and the peer will
// be disconnected.
BloomFilter() []byte
// SetTimeSource assigns a particular source of time to a whisper object.
SetTimeSource(timesource func() time.Time)
// GetCurrentTime returns current time.
GetCurrentTime() time.Time
MaxMessageSize() uint32
// GetPrivateKey retrieves the private key of the specified identity.
GetPrivateKey(id string) (*ecdsa.PrivateKey, error)
SubscribeEnvelopeEvents(events chan<- EnvelopeEvent) Subscription
// AddKeyPair imports a asymmetric private key and returns a deterministic identifier.
AddKeyPair(key *ecdsa.PrivateKey) (string, error)
// DeleteKeyPair deletes the key with the specified ID if it exists.
DeleteKeyPair(keyID string) bool
// DeleteKeyPairs removes all cryptographic identities known to the node
DeleteKeyPairs() error
AddSymKeyDirect(key []byte) (string, error)
AddSymKeyFromPassword(password string) (string, error)
DeleteSymKey(id string) bool
GetSymKey(id string) ([]byte, error)
Subscribe(opts *SubscriptionOptions) (string, error)
GetFilter(id string) Filter
Unsubscribe(id string) error
UnsubscribeMany(ids []string) error
// SyncMessages can be sent between two Mail Servers and syncs envelopes between them.
SyncMessages(peerID []byte, req SyncMailRequest) error
}

View File

@ -0,0 +1,7 @@
package types
// Wrapped tells that a given object has an underlying representation
// and this representation can be accessed using `Unwrap` method.
type Wrapped interface {
Unwrap() interface{}
}

2
go.mod
View File

@ -1,4 +1,4 @@
module github.com/status-im/status-go
module github.com/waku-org/waku-go-bindings
go 1.21

24
logutils/custom.go Normal file
View File

@ -0,0 +1,24 @@
package logutils
import (
"fmt"
"time"
"go.uber.org/zap"
)
func WakuMessageTimestamp(key string, value *int64) zap.Field {
valueStr := "-"
if value != nil {
valueStr = fmt.Sprintf("%d", *value)
}
return zap.String(key, valueStr)
}
func UnixTimeMs(key string, t time.Time) zap.Field {
return zap.String(key, fmt.Sprintf("%d", t.UnixMilli()))
}
func UnixTimeNano(key string, t time.Time) zap.Field {
return zap.String(key, fmt.Sprintf("%d", t.UnixNano()))
}

27
logutils/logger.go Normal file
View File

@ -0,0 +1,27 @@
package logutils
import (
"sync"
"go.uber.org/zap"
"github.com/ethereum/go-ethereum/log"
)
var (
_zapLogger *zap.Logger
_initZapLogger sync.Once
)
// ZapLogger creates a custom zap.Logger which will forward logs
// to status-go logger.
func ZapLogger() *zap.Logger {
_initZapLogger.Do(func() {
var err error
_zapLogger, err = NewZapLoggerWithAdapter(log.Root())
if err != nil {
panic(err)
}
})
return _zapLogger
}

18
logutils/logger_test.go Normal file
View File

@ -0,0 +1,18 @@
package logutils
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/log"
)
func TestPrintOrigins(t *testing.T) {
buf := bytes.NewBuffer(nil)
handler := log.StreamHandler(buf, log.TerminalFormat(false))
require.NoError(t, enableRootLog("debug", handler))
log.Debug("hello")
require.Contains(t, buf.String(), "logutils/logger_test.go:16")
}

41
logutils/logrotation.go Normal file
View File

@ -0,0 +1,41 @@
package logutils
import (
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
"github.com/ethereum/go-ethereum/log"
)
// FileOptions are all options supported by internal rotation module.
type FileOptions struct {
// Base name for log file.
Filename string
// Size in megabytes.
MaxSize int
// Number of rotated log files.
MaxBackups int
// If true rotated log files will be gzipped.
Compress bool
}
// FileHandlerWithRotation instantiates log.Handler with a configured rotation
func FileHandlerWithRotation(opts FileOptions, format log.Format) log.Handler {
logger := &lumberjack.Logger{
Filename: opts.Filename,
MaxSize: opts.MaxSize,
MaxBackups: opts.MaxBackups,
Compress: opts.Compress,
}
return log.StreamHandler(logger, format)
}
// ZapSyncerWithRotation creates a zapcore.WriteSyncer with a configured rotation
func ZapSyncerWithRotation(opts FileOptions) zapcore.WriteSyncer {
return zapcore.AddSync(&lumberjack.Logger{
Filename: opts.Filename,
MaxSize: opts.MaxSize,
MaxBackups: opts.MaxBackups,
Compress: opts.Compress,
})
}

100
logutils/override.go Normal file
View File

@ -0,0 +1,100 @@
package logutils
import (
"os"
"strings"
logging "github.com/ipfs/go-log/v2"
"github.com/ethereum/go-ethereum/log"
)
type LogSettings struct {
Enabled bool `json:"Enabled"`
MobileSystem bool `json:"MobileSystem"`
Level string `json:"Level"`
File string `json:"File"`
MaxSize int `json:"MaxSize"`
MaxBackups int `json:"MaxBackups"`
CompressRotated bool `json:"CompressRotated"`
}
// OverrideWithStdLogger overwrites ethereum's root logger with a logger from golang std lib.
func OverrideWithStdLogger(logLevel string) error {
return enableRootLog(logLevel, NewStdHandler(log.TerminalFormat(false)))
}
// OverrideRootLogWithConfig derives all configuration from params.NodeConfig and configures logger using it.
func OverrideRootLogWithConfig(settings LogSettings, colors bool) error {
if !settings.Enabled {
return nil
}
if settings.MobileSystem {
return OverrideWithStdLogger(settings.Level)
}
return OverrideRootLog(settings.Enabled, settings.Level, FileOptions{
Filename: settings.File,
MaxSize: settings.MaxSize,
MaxBackups: settings.MaxBackups,
Compress: settings.CompressRotated,
}, colors)
}
// OverrideRootLog overrides root logger with file handler, if defined,
// and log level (defaults to INFO).
func OverrideRootLog(enabled bool, levelStr string, fileOpts FileOptions, terminal bool) error {
if !enabled {
disableRootLog()
return nil
}
if os.Getenv("CI") == "true" {
terminal = false
}
var (
handler log.Handler
)
if fileOpts.Filename != "" {
if fileOpts.MaxBackups == 0 {
// Setting MaxBackups to 0 causes all log files to be kept. Even setting MaxAge to > 0 doesn't fix it
// Docs: https://pkg.go.dev/gopkg.in/natefinch/lumberjack.v2@v2.0.0#readme-cleaning-up-old-log-files
fileOpts.MaxBackups = 1
}
handler = FileHandlerWithRotation(fileOpts, log.TerminalFormat(terminal))
} else {
handler = log.StreamHandler(os.Stderr, log.TerminalFormat(terminal))
}
return enableRootLog(levelStr, handler)
}
func disableRootLog() {
log.Root().SetHandler(log.DiscardHandler())
}
func enableRootLog(levelStr string, handler log.Handler) error {
if levelStr == "" {
levelStr = "INFO"
}
levelStr = strings.ToLower(levelStr)
level, err := log.LvlFromString(levelStr)
if err != nil {
return err
}
filteredHandler := log.LvlFilterHandler(level, handler)
log.Root().SetHandler(filteredHandler)
log.PrintOrigins(true)
// go-libp2p logger
lvl, err := logging.LevelFromString(levelStr)
if err != nil {
return err
}
logging.SetAllLoggers(lvl)
return nil
}

View File

@ -0,0 +1,50 @@
package requestlog
import (
"errors"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"github.com/status-im/status-go/protocol/zaputil"
"github.com/waku-org/waku-go-bindings/logutils"
)
var (
requestLogger *zap.Logger
)
// IsRequestLoggingEnabled returns whether RPC logging is enabled
func IsRequestLoggingEnabled() bool {
return requestLogger != nil
}
// GetRequestLogger returns the RPC logger object
func GetRequestLogger() *zap.Logger {
return requestLogger
}
func ConfigureAndEnableRequestLogging(file string) error {
if len(file) == 0 {
return errors.New("file is required")
}
if IsRequestLoggingEnabled() {
return errors.New("request logging is already enabled")
}
fileOpts := logutils.FileOptions{
Filename: file,
MaxBackups: 1,
}
core := zapcore.NewCore(
zaputil.NewConsoleHexEncoder(zap.NewDevelopmentEncoderConfig()),
zapcore.AddSync(logutils.ZapSyncerWithRotation(fileOpts)),
zap.DebugLevel,
)
requestLogger = zap.New(core).Named("RequestLogger")
return nil
}

17
logutils/stdhandler.go Normal file
View File

@ -0,0 +1,17 @@
package logutils
import (
stdlog "log"
"github.com/ethereum/go-ethereum/log"
)
// NewStdHandler returns handler that uses logger from golang std lib.
func NewStdHandler(fmtr log.Format) log.Handler {
return log.FuncHandler(func(r *log.Record) error {
line := fmtr.Format(r)
// 8 is a number of frames that will be skipped when log is printed.
// this is needed to show the file (with line number) where call to a logger was made
return stdlog.Output(8, string(line))
})
}

147
logutils/zap_adapter.go Normal file
View File

@ -0,0 +1,147 @@
package logutils
import (
"fmt"
"math"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/protocol/zaputil"
)
type gethLoggerCore struct {
zapcore.LevelEnabler
fields []zapcore.Field
logger log.Logger
}
func (c gethLoggerCore) With(fields []zapcore.Field) zapcore.Core {
clone := c.clone()
clone.fields = append(clone.fields, fields...)
return clone
}
func (c gethLoggerCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
if c.Enabled(ent.Level) {
return ce.AddCore(ent, c)
}
return ce
}
func (c gethLoggerCore) Write(ent zapcore.Entry, fields []zapcore.Field) error {
fields = append(c.fields[:], fields...)
var args []interface{}
for _, f := range fields {
switch f.Type {
case zapcore.ArrayMarshalerType,
zapcore.ObjectMarshalerType,
zapcore.BinaryType,
zapcore.ByteStringType,
zapcore.Complex128Type,
zapcore.ReflectType,
zapcore.StringerType,
zapcore.ErrorType:
args = append(args, f.Key, f.Interface)
case zapcore.BoolType:
args = append(args, f.Key, f.Integer == 1)
case zapcore.DurationType:
args = append(args, f.Key, time.Duration(f.Integer))
case zapcore.Float64Type:
args = append(args, f.Key, math.Float64frombits(uint64(f.Integer)))
case zapcore.Float32Type:
args = append(args, f.Key, math.Float32frombits(uint32(f.Integer)))
case zapcore.Int64Type,
zapcore.Int32Type,
zapcore.Int16Type,
zapcore.Int8Type,
zapcore.Uint64Type,
zapcore.Uint32Type,
zapcore.Uint16Type,
zapcore.Uint8Type:
args = append(args, f.Key, f.Integer)
case zapcore.UintptrType:
args = append(args, f.Key, uintptr(f.Integer))
case zapcore.StringType:
args = append(args, f.Key, f.String)
case zapcore.TimeType:
if f.Interface != nil {
args = append(args, f.Key, time.Unix(0, f.Integer).In(f.Interface.(*time.Location)))
} else {
// Fall back to UTC if location is nil.
args = append(args, f.Key, time.Unix(0, f.Integer))
}
case zapcore.NamespaceType:
args = append(args, "namespace", f.Key)
case zapcore.SkipType:
break
default:
panic(fmt.Sprintf("unknown field type: %v", f))
}
}
// set callDepth to 3 for `Output` to skip the calls to zap.Logger
// and get the correct caller in the log
callDepth := 3
switch ent.Level {
case zapcore.DebugLevel:
c.logger.Output(ent.Message, log.LvlDebug, callDepth, args...)
case zapcore.InfoLevel:
c.logger.Output(ent.Message, log.LvlInfo, callDepth, args...)
case zapcore.WarnLevel:
c.logger.Output(ent.Message, log.LvlWarn, callDepth, args...)
case zapcore.ErrorLevel:
c.logger.Output(ent.Message, log.LvlError, callDepth, args...)
case zapcore.DPanicLevel, zapcore.PanicLevel, zapcore.FatalLevel:
c.logger.Output(ent.Message, log.LvlCrit, callDepth, args...)
}
return nil
}
func (gethLoggerCore) Sync() error {
return nil
}
func (c *gethLoggerCore) clone() *gethLoggerCore {
return &gethLoggerCore{
LevelEnabler: c.LevelEnabler,
fields: c.fields,
logger: c.logger,
}
}
// NewZapAdapter returns a new zapcore.Core interface which forwards logs to log.Logger.
func NewZapAdapter(logger log.Logger, enab zapcore.LevelEnabler) zapcore.Core {
return &gethLoggerCore{
LevelEnabler: enab,
logger: logger,
}
}
// NewZapLoggerWithAdapter returns a logger forwarding all logs with level info and above.
func NewZapLoggerWithAdapter(logger log.Logger) (*zap.Logger, error) {
if err := zaputil.RegisterJSONHexEncoder(); err != nil {
panic(err)
}
cfg := zap.Config{
Level: zap.NewAtomicLevelAt(zapcore.DebugLevel),
Development: false,
Sampling: nil,
Encoding: "json-hex",
EncoderConfig: zap.NewProductionEncoderConfig(),
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
}
adapter := zap.WrapCore(
func(zapcore.Core) zapcore.Core {
return NewZapAdapter(logger, cfg.Level)
},
)
log.PrintOrigins(true)
return cfg.Build(adapter)
}

View File

@ -0,0 +1,72 @@
package logutils
import (
"bytes"
"errors"
"testing"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"github.com/ethereum/go-ethereum/log"
)
func TestNewZapAdapter(t *testing.T) {
buf := bytes.NewBuffer(nil)
logger := log.New()
handler := log.StreamHandler(buf, log.LogfmtFormat())
logger.SetHandler(handler)
cfg := zap.NewDevelopmentConfig()
adapter := NewZapAdapter(logger, cfg.Level)
zapLogger := zap.New(adapter)
buf.Reset()
zapLogger.
With(zap.Error(errors.New("some error"))).
Error("some message with error level")
require.Contains(t, buf.String(), `lvl=eror msg="some message with error level" error="some error`)
buf.Reset()
zapLogger.
With(zap.Int("counter", 100)).
Info("some message with param", zap.String("another-field", "another-value"))
require.Contains(t, buf.String(), `lvl=info msg="some message with param" counter=100 another-field=another-value`)
buf.Reset()
zapLogger.
With(zap.Namespace("some-namespace")).
With(zap.String("site", "SomeSite")).
Info("some message with param")
require.Contains(t, buf.String(), `lvl=info msg="some message with param" namespace=some-namespace site=SomeSite`)
}
func TestNewZapLoggerWithAdapter(t *testing.T) {
buf := bytes.NewBuffer(nil)
logger := log.New()
handler := log.StreamHandler(buf, log.LogfmtFormat())
logger.SetHandler(handler)
zapLogger, err := NewZapLoggerWithAdapter(logger)
require.NoError(t, err)
buf.Reset()
zapLogger.
With(zap.Error(errors.New("some error"))).
Error("some message with error level")
require.Contains(t, buf.String(), `lvl=eror msg="some message with error level" error="some error`)
}
func TestZapLoggerTerminalFormat(t *testing.T) {
buf := bytes.NewBuffer(nil)
logger := log.New()
handler := log.StreamHandler(buf, log.TerminalFormat(false))
logger.SetHandler(handler)
zapLogger, err := NewZapLoggerWithAdapter(logger)
require.NoError(t, err)
zapLogger.Info("some message with error level")
require.Contains(t, buf.String(), `logutils/zap_adapter_test.go:70`)
}

View File

@ -363,8 +363,8 @@ import (
storepb "github.com/waku-org/go-waku/waku/v2/protocol/store/pb"
"github.com/waku-org/go-waku/waku/v2/utils"
"github.com/status-im/status-go/wakuv2/common"
"github.com/status-im/status-go/wakuv2/persistence"
"github.com/waku-org/waku-go-bindings/common"
"github.com/waku-org/waku-go-bindings/persistence"
node "github.com/waku-org/go-waku/waku/v2/node"
"github.com/waku-org/go-waku/waku/v2/protocol/pb"

1090
nwaku_test.go Normal file

File diff suppressed because it is too large Load Diff

58
nwaku_test_utils.go Normal file
View File

@ -0,0 +1,58 @@
package wakuv2
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
)
type NwakuInfo struct {
ListenAddresses []string `json:"listenAddresses"`
EnrUri string `json:"enrUri"`
}
func GetNwakuInfo(host *string, port *int) (NwakuInfo, error) {
nwakuRestPort := 8645
if port != nil {
nwakuRestPort = *port
}
envNwakuRestPort := os.Getenv("NWAKU_REST_PORT")
if envNwakuRestPort != "" {
v, err := strconv.Atoi(envNwakuRestPort)
if err != nil {
return NwakuInfo{}, err
}
nwakuRestPort = v
}
nwakuRestHost := "localhost"
if host != nil {
nwakuRestHost = *host
}
envNwakuRestHost := os.Getenv("NWAKU_REST_HOST")
if envNwakuRestHost != "" {
nwakuRestHost = envNwakuRestHost
}
resp, err := http.Get(fmt.Sprintf("http://%s:%d/debug/v1/info", nwakuRestHost, nwakuRestPort))
if err != nil {
return NwakuInfo{}, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return NwakuInfo{}, err
}
var data NwakuInfo
err = json.Unmarshal(body, &data)
if err != nil {
return NwakuInfo{}, err
}
return data, nil
}

View File

@ -16,7 +16,7 @@ import (
"github.com/waku-org/go-waku/waku/v2/timesource"
"github.com/waku-org/go-waku/waku/v2/utils"
"github.com/status-im/status-go/common"
"github.com/waku-org/waku-go-bindings/common"
"go.uber.org/zap"
)