feat: Waku v2 bridge

Issue #12610
This commit is contained in:
Michal Iskierko
2023-11-12 13:29:38 +01:00
parent 56e7bd01ca
commit 6d31343205
6716 changed files with 1982502 additions and 5891 deletions

View File

@@ -0,0 +1,113 @@
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"
"github.com/status-im/status-go/eth-node/crypto"
enstypes "github.com/status-im/status-go/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) { 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"
"github.com/status-im/status-go/eth-node/types"
waku "github.com/status-im/status-go/waku/common"
)
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 (
"github.com/status-im/status-go/eth-node/types"
waku "github.com/status-im/status-go/waku/common"
wakuv2 "github.com/status-im/status-go/wakuv2/common"
)
// 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,60 @@
package gethbridge
import (
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/waku"
"github.com/status-im/status-go/wakuv2"
wakucommon "github.com/status-im/status-go/waku/common"
wakuv2common "github.com/status-im/status-go/wakuv2/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])
}
case *wakuv2.MailServerResponse:
wrappedData = NewWakuV2MailServerResponseWrapper(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,
}
}

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/eth-node/types"
"github.com/status-im/status-go/extkeys"
)
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,33 @@
package gethbridge
import (
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/waku"
"github.com/status-im/status-go/wakuv2"
)
// 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,
}
}
// NewWakuV2MailServerResponseWrapper returns a types.MailServerResponse object that mimics Geth's MailServerResponse
func NewWakuV2MailServerResponseWrapper(mailServerResponse *wakuv2.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/status-im/status-go/eth-node/bridge/geth/ens"
"github.com/status-im/status-go/eth-node/types"
enstypes "github.com/status-im/status-go/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/eth-node/types"
"github.com/status-im/status-go/waku"
wakucommon "github.com/status-im/status-go/waku/common"
)
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,104 @@
package gethbridge
import (
"context"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/wakuv2"
wakucommon "github.com/status-im/status-go/wakuv2/common"
)
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,
}
return w.api.Post(ctx, msg)
}

View File

@@ -0,0 +1,30 @@
package gethbridge
import (
"github.com/ethereum/go-ethereum/event"
"github.com/status-im/status-go/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,316 @@
package gethbridge
import (
"context"
"crypto/ecdsa"
"errors"
"time"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/connection"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/waku"
wakucommon "github.com/status-im/status-go/waku/common"
)
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")
}
// PeerCount function only added for compatibility with waku V2
func (w *GethWakuWrapper) AddStorePeer(address string) (peer.ID, 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 string) (peer.ID, error) {
return "", errors.New("not available in WakuV1")
}
// DialPeer function only added for compatibility with waku V2
func (w *GethWakuWrapper) DialPeer(address string) error {
return errors.New("not available in WakuV1")
}
// DialPeerByID function only added for compatibility with waku V2
func (w *GethWakuWrapper) DialPeerByID(peerID string) error {
return errors.New("not available in WakuV1")
}
// ListenAddresses function only added for compatibility with waku V2
func (w *GethWakuWrapper) ListenAddresses() ([]string, error) {
return nil, errors.New("not available in WakuV1")
}
// PeerCount function only added for compatibility with waku V2
func (w *GethWakuWrapper) DropPeer(peerID string) error {
return errors.New("not available in WakuV1")
}
func (w *GethWakuWrapper) SubscribeToConnStatusChanges() (*types.ConnStatusSubscription, error) {
return nil, errors.New("not available in WakuV1")
}
// Peers function only added for compatibility with waku V2
func (w *GethWakuWrapper) Peers() map[string]types.WakuV2Peer {
p := make(map[string]types.WakuV2Peer)
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() {
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) SendMessagesRequest(peerID []byte, r types.MessagesRequest) error {
return w.waku.SendMessagesRequest(peerID, wakucommon.MessagesRequest{
ID: r.ID,
From: r.From,
To: r.To,
Limit: r.Limit,
Cursor: r.Cursor,
Bloom: r.Bloom,
Topics: r.ContentTopics,
})
}
// RequestHistoricMessages sends a message with p2pRequestCode to a specific peer,
// which is known to implement MailServer interface, and is supposed to process this
// request and respond with a number of peer-to-peer messages (possibly expired),
// which are not supposed to be forwarded any further.
// The whisper protocol is agnostic of the format and contents of envelope.
func (w *GethWakuWrapper) RequestHistoricMessagesWithTimeout(peerID []byte, envelope types.Envelope, timeout time.Duration) error {
return w.waku.RequestHistoricMessagesWithTimeout(peerID, envelope.Unwrap().(*wakucommon.Envelope), timeout)
}
func (w *GethWakuWrapper) ProcessingP2PMessages() bool {
return w.waku.ProcessingP2PMessages()
}
func (w *GethWakuWrapper) MarkP2PMessageAsProcessed(hash common.Hash) {
w.waku.MarkP2PMessageAsProcessed(hash)
}
func (w *GethWakuWrapper) RequestStoreMessages(ctx context.Context, peerID []byte, r types.MessagesRequest, processEnvelopes bool) (*types.StoreRequestCursor, int, error) {
return nil, 0, errors.New("not implemented")
}
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
}

View File

@@ -0,0 +1,329 @@
package gethbridge
import (
"context"
"crypto/ecdsa"
"errors"
"time"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/waku-org/go-waku/waku/v2/protocol/store"
storepb "github.com/waku-org/go-waku/waku/v2/protocol/store/pb"
"github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/connection"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/wakuv2"
wakucommon "github.com/status-im/status-go/wakuv2/common"
)
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() {
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.PoW, 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, pow float64, 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
}
// DEPRECATED: Not used in waku V2
func (w *gethWakuV2Wrapper) SendMessagesRequest(peerID []byte, r types.MessagesRequest) error {
return errors.New("DEPRECATED")
}
func (w *gethWakuV2Wrapper) RequestStoreMessages(ctx context.Context, peerID []byte, r types.MessagesRequest, processEnvelopes bool) (*types.StoreRequestCursor, int, error) {
var options []store.HistoryRequestOption
peer, err := peer.Decode(string(peerID))
if err != nil {
return nil, 0, err
}
options = []store.HistoryRequestOption{
store.WithPaging(false, uint64(r.Limit)),
}
if r.StoreCursor != nil {
options = append(options, store.WithCursor(&storepb.Index{
Digest: r.StoreCursor.Digest,
ReceiverTime: r.StoreCursor.ReceiverTime,
SenderTime: r.StoreCursor.SenderTime,
PubsubTopic: r.StoreCursor.PubsubTopic,
}))
}
var contentTopics []wakucommon.TopicType
for _, topic := range r.ContentTopics {
contentTopics = append(contentTopics, wakucommon.BytesToTopic(topic))
}
pbCursor, envelopesCount, err := w.waku.Query(ctx, peer, r.PubsubTopic, contentTopics, uint64(r.From), uint64(r.To), options, processEnvelopes)
if err != nil {
return nil, 0, err
}
if pbCursor != nil {
return &types.StoreRequestCursor{
Digest: pbCursor.Digest,
ReceiverTime: pbCursor.ReceiverTime,
SenderTime: pbCursor.SenderTime,
PubsubTopic: pbCursor.PubsubTopic,
}, envelopesCount, nil
}
return nil, envelopesCount, nil
}
// DEPRECATED: Not used in waku V2
func (w *gethWakuV2Wrapper) RequestHistoricMessagesWithTimeout(peerID []byte, envelope types.Envelope, timeout time.Duration) error {
return errors.New("DEPRECATED")
}
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) AddStorePeer(address string) (peer.ID, error) {
return w.waku.AddStorePeer(address)
}
func (w *gethWakuV2Wrapper) AddRelayPeer(address string) (peer.ID, error) {
return w.waku.AddRelayPeer(address)
}
func (w *gethWakuV2Wrapper) Peers() map[string]types.WakuV2Peer {
return w.waku.Peers()
}
func (w *gethWakuV2Wrapper) DialPeer(address string) error {
return w.waku.DialPeer(address)
}
func (w *gethWakuV2Wrapper) DialPeerByID(peerID string) error {
return w.waku.DialPeerByID(peerID)
}
func (w *gethWakuV2Wrapper) ListenAddresses() ([]string, error) {
return w.waku.ListenAddresses(), nil
}
func (w *gethWakuV2Wrapper) DropPeer(peerID string) 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) 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
}

View File

@@ -0,0 +1,48 @@
package types
import (
"math/big"
"github.com/status-im/status-go/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 }

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/status-im/status-go/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,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/status-im/status-go/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/status-im/status-go/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/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/extkeys"
)
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
}

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
}

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)
}

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
)

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
}

View File

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

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
}

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
}

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}
}

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
// SubAccountIndex is DEPRECATED
// 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,89 @@
package types
import (
"time"
)
const (
// MaxLimitInMessagesRequest represents the maximum number of messages
// that can be requested from the mailserver
MaxLimitInMessagesRequest = 1000
)
// MessagesRequest contains details of a request of historic messages.
type MessagesRequest struct {
// ID of the request. The current implementation requires ID to be 32-byte array,
// however, it's not enforced for future implementation.
ID []byte `json:"id"`
// From is a lower bound of time range.
From uint32 `json:"from"`
// To is a upper bound of time range.
To uint32 `json:"to"`
// Limit determines the number of messages sent by the mail server
// for the current paginated request.
Limit uint32 `json:"limit"`
// Cursor is used as starting point for paginated requests.
Cursor []byte `json:"cursor"`
// StoreCursor is used as starting point for WAKUV2 paginatedRequests
StoreCursor *StoreRequestCursor `json:"storeCursor"`
// Bloom is a filter to match requested messages.
Bloom []byte `json:"bloom"`
// PubsubTopic is the gossipsub topic on which the message was broadcasted
PubsubTopic string `json:"pubsubTopic"`
// ContentTopics is a list of topics. A returned message should
// belong to one of the topics from the list.
ContentTopics [][]byte `json:"contentTopics"`
}
type StoreRequestCursor struct {
Digest []byte `json:"digest"`
ReceiverTime int64 `json:"receiverTime"`
SenderTime int64 `json:"senderTime"`
PubsubTopic string `json:"pubsubTopic"`
}
// SetDefaults sets the From and To defaults
func (r *MessagesRequest) SetDefaults(now time.Time) {
// set From and To defaults
if r.To == 0 {
r.To = uint32(now.UTC().Unix())
}
if r.From == 0 {
oneDay := uint32(86400) // -24 hours
if r.To < oneDay {
r.From = 0
} else {
r.From = r.To - oneDay
}
}
}
// 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
}

View File

@@ -0,0 +1,26 @@
package types
import (
"fmt"
"go.uber.org/zap"
enstypes "github.com/status-im/status-go/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
}

View File

@@ -0,0 +1,96 @@
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"`
}
// 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
}

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
}

View File

@@ -0,0 +1,99 @@
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[:])
}
// 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]
}

View File

@@ -0,0 +1,171 @@
package types
import (
"context"
"crypto/ecdsa"
"sync"
"time"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
"github.com/pborman/uuid"
"github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/connection"
)
type ConnStatus struct {
IsOnline bool `json:"isOnline"`
HasHistory bool `json:"hasHistory"`
Peers map[string]WakuV2Peer `json:"peers"`
}
type WakuV2Peer struct {
Protocols []protocol.ID `json:"protocols"`
Addresses []string `json:"addresses"`
}
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
}
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() ([]string, error)
Peers() map[string]WakuV2Peer
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
AddStorePeer(address string) (peer.ID, error)
AddRelayPeer(address string) (peer.ID, error)
DialPeer(address string) error
DialPeerByID(peerID string) error
DropPeer(peerID string) error
SubscribeToConnStatusChanges() (*ConnStatusSubscription, 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
// RequestHistoricMessages sends a message with p2pRequestCode to a specific peer,
// which is known to implement MailServer interface, and is supposed to process this
// request and respond with a number of peer-to-peer messages (possibly expired),
// which are not supposed to be forwarded any further.
// The whisper protocol is agnostic of the format and contents of envelope.
// A timeout of 0 never expires.
RequestHistoricMessagesWithTimeout(peerID []byte, envelope Envelope, timeout time.Duration) error
// SendMessagesRequest sends a MessagesRequest. This is an equivalent to RequestHistoricMessages
// in terms of the functionality.
SendMessagesRequest(peerID []byte, request MessagesRequest) error
// RequestStoreMessages uses the WAKU2-STORE protocol to request historic messages
RequestStoreMessages(ctx context.Context, peerID []byte, request MessagesRequest, processEnvelopes bool) (*StoreRequestCursor, int, 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()
}

View File

@@ -0,0 +1,59 @@
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
// RequestHistoricMessages sends a message with p2pRequestCode to a specific peer,
// which is known to implement MailServer interface, and is supposed to process this
// request and respond with a number of peer-to-peer messages (possibly expired),
// which are not supposed to be forwarded any further.
// The whisper protocol is agnostic of the format and contents of envelope.
// A timeout of 0 never expires.
RequestHistoricMessagesWithTimeout(peerID []byte, envelope Envelope, timeout time.Duration) error
// SendMessagesRequest sends a MessagesRequest. This is an equivalent to RequestHistoricMessages
// in terms of the functionality.
SendMessagesRequest(peerID []byte, request MessagesRequest) 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{}
}