README ¶
Postal Services over Swarm
pss
enables message relay over swarm. This means nodes can send messages to each other without being directly connected with each other, while taking advantage of the efficient routing algorithms that swarm uses for transporting and storing data.
CONTENTS
- Status of this document
- Core concepts
- Caveat
- Examples
- API
- Retrieve node information
- Receive messages
- Send messages using public key encryption
- Send messages using symmetric encryption
- Querying peer keys
- Handshakes
STATUS OF THIS DOCUMENT
pss
is under active development, and the first implementation is yet to be merged to the Severeum main branch. Expect things to change.
Details on swarm routing and encryption schemes out of scope of this document.
Please refer to ARCHITECTURE.md for in-depth topics concerning pss
.
CORE CONCEPTS
Three things are required to send a pss
message:
- Encryption key
- Topic
- Message payload
Encryption key can be a public key or a 32 byte symmetric key. It must be coupled with a peer address in the node prior to sending.
Topic is the initial 4 bytes of a hash value.
Message payload is an arbitrary byte slice of data.
Upon sending the message it is encrypted and passed on from peer to peer. Any node along the route that can successfully decrypt the message is regarded as a recipient. Recipients continue to pass on the message to their peers, to make traffic analysis attacks more difficult.
The Address that is coupled with the encryption keys are used for routing the message. This does not need to be a full addresses; the network will route the message to the best of its ability with the information that is available. If no address is given (zero-length byte slice), routing is effectively deactivated, and the message is passed to all peers by all peers.
CAVEAT
pss
connectivity resembles UDP. This means there is no delivery guarantee for a message. Furthermore there is no strict definition of what a connection between two nodes communicating via pss
is. Reception acknowledgements and keepalive-schemes is the responsibility of the application.
Due to the inherent properties of the swarm
routing algorithm, a node may receive the same message more than once. Message deduplication cannot be guaranteed by pss
, and must be handled in the application layer to ensure predictable results.
EXAMPLES
The code tutorial p2p programming in go-severeum by @nolash provides step-by-step code examples for usage of pss
API with go-severeum
nodes.
A quite unpolished example using javascript
is available here: https://github.com/nolash/pss-js/tree/withcrypt
API
The pss
API is available through IPC and Websockets. There is currently no web3.js
implementation, as this does not support message subscription.
For golang
clients, please use the rpc.Client
provided by the go-severeum
repository. The return values may have special types in golang
. Please refer to godoc
for details.
RETRIEVE NODE INFORMATION
pss_getPublicKey
Retrieves the public key of the node, in hex format
parameters:
none
returns:
1. publickey (hex)
pss_baseAddr
Retrieves the swarm overlay address of the node, in hex format
parameters:
none
returns:
1. swarm overlay address (hex)
pss_stringToTopic
Creates a deterministic 4 byte topic value from input, returned in hex format
parameters:
1. topic string (string)
returns:
1. pss topic (hex)
RECEIVE MESSAGES
pss_subscribe
Creates a subscription. Received messages with matching topic will be passed to subscription client.
parameters:
1. string("receive")
2. topic (4 bytes in hex)
returns:
1. subscription handle `base64(byte)` `rpc.ClientSubscription`
In golang
as special method is used:
rpc.Client.Subscribe(context.Context, "pss", chan pss.APIMsg, "receive", pss.Topic)
Incoming messages are encapsulated in an object (pss.APIMsg
in golang
) with the following members:
1. Msg (hex) - the message payload
2. Asymmetric (bool) - true if message used public key encryption
3. Key (string) - the encryption key used
SEND MESSAGE USING PUBLIC KEY ENCRYPTION
pss_setPeerPublicKey
Register a peer's public key. This is done once for every topic that will be used with the peer. Address can be anything from 0 to 32 bytes inclusive of the peer's swarm overlay address.
parameters:
1. public key of peer (hex)
2. topic (4 bytes in hex)
3. address of peer (hex)
returns:
none
pss_sendAsym
Encrypts the message using the provided public key, and signs it using the node's private key. It then wraps it in an envelope containing the topic, and sends it to the network.
parameters:
1. public key of peer (hex)
2. topic (4 bytes in hex)
3. message (hex)
returns:
none
SEND MESSAGE USING SYMMETRIC ENCRYPTION
pss_setSymmetricKey
Register a symmetric key shared with a peer. This is done once for every topic that will be used with the peer. Address can be anything from 0 to 32 bytes inclusive of the peer's swarm overlay address.
If the fourth parameter is false, the key will not be added to the list of symmetric keys used for decryption attempts.
parameters:
1. symmetric key (hex)
2. topic (4 bytes in hex)
3. address of peer (hex)
4. use for decryption (bool)
returns:
1. symmetric key id (string)
pss_sendSym
Encrypts the message using the provided symmetric key, wraps it in an envelope containing the topic, and sends it to the network.
parameters:
1. symmetric key id (string)
2. topic (4 bytes in hex)
3. message (hex)
returns:
none
QUERY PEER KEYS
pss_GetSymmetricAddressHint
Return the swarm overlay address associated with the peer registered with the given symmetric key and topic combination.
parameters:
1. topic (4 bytes in hex)
2. symmetric key id (string)
returns:
1. peer address (hex)
pss_GetAsymmetricAddressHint
Return the swarm overlay address associated with the peer registered with the given symmetric key and topic combination.
parameters:
1. topic (4 bytes in hex)
2. public key in hex form (string)
returns:
1. peer address (hex)
HANDSHAKES
Convenience implementation of Diffie-Hellman handshakes using ephemeral symmetric keys. Peers keep separate sets of keys for incoming and outgoing communications.
This functionality is an optional feature in pss
. It is compiled in by default, but can be omitted by providing the nopsshandshake
build tag.
pss_addHandshake
Activate handshake functionality on the specified topic.
parameters:
1. topic (4 bytes in hex)
returns:
none
pss_removeHandshake
Remove handshake functionality on the specified topic.
parameters:
1. topic (4 bytes in hex)
returns:
none
pss_handshake
Instantiate handshake with peer, refreshing symmetric encryption keys.
If parameter 3 is false, the returned array will be empty.
parameters:
1. public key of peer in hex format (string)
2. topic (4 bytes in hex)
3. block calls until keys are received (bool)
4. flush existing incoming keys (bool)
returns:
1. list of symmetric keys (string[])
pss_getHandshakeKeys
Get valid symmetric encryption keys for a specified peer and topic.
parameters:
- public key of peer in hex format (string)
- topic (4 bytes in hex)
- include keys for incoming messages (bool)
- include keys for outgoing messages (bool)
returns:
- list of symmetric keys (string[])
pss_getHandshakeKeyCapacity
Get amount of remaining messages the specified key is valid for.
parameters:
1. symmetric key id (string)
returns:
1. number of messages (uint16)
pss_getHandshakePublicKey
Get the peer's public key associated with the specified symmetric key.
parameters:
1. symmetric key id (string)
returns:
1. Associated public key in hex format (string)
pss_releaseHandshakeKey
Invalidate the specified key.
Normally, the key will be kept for a grace period to allow for decryption of delayed messages. If instant removal is set, this grace period is omitted, and the key removed instantaneously.
parameters:
1. public key of peer in hex format (string)
2. topic (4 bytes in hex)
3. symmetric key id to release (string)
4. remove keys instantly (bool)
returns:
1. whether key was successfully removed (bool)
Documentation ¶
Overview ¶
Pss provides devp2p functionality for swarm nodes without the need for a direct tcp connection between them.
Messages are encapsulated in a devp2p message structure `PssMsg`. These capsules are forwarded from node to node using ordinary tcp devp2p until it reaches its destination: The node or nodes who can successfully decrypt the message.
Routing of messages is done using swarm's own kademlia routing. Optionally routing can be turned off, forcing the message to be sent to all peers, similar to the behavior of the whisper protocol.
Pss is intended for messages of limited size, typically a couple of Kbytes at most. The messages themselves can be anything at all; complex data structures or non-descript byte sequences.
Documentation can be found in the README file.
For the current state and roadmap of pss development please see https://github.com/ethersphere/swarm/wiki/swarm-dev-progress.
Please report issues on https://github.com/ethersphere/go-severeum
Feel free to ask questions in https://gitter.im/ethersphere/pss
TOPICS ¶
An encrypted envelope of a pss messages always contains a Topic. This is pss' way of determining what action to take on the message. The topic is only visible for the node(s) who can decrypt the message.
This "topic" is not like the subject of an email message, but a hash-like arbitrary 4 byte value. A valid topic can be generated using the `pss_*ToTopic` API methods.
IDENTITY IN PSS ¶
Pss aims to achieve perfect darkness. That means that the minimum requirement for two nodes to communicate using pss is a shared secret. This secret can be an arbitrary byte slice, or a ECDSA keypair.
Peer keys can manually be added to the pss node through its API calls `pss_setPeerPublicKey` and `pss_setSymmetricKey`. Keys are always coupled with a topic, and the keys will only be valid for these topics.
CONNECTIONS ¶
A "connection" in pss is a purely virtual construct. There is no mechanisms in place to ensure that the remote peer actually is there. In fact, "adding" a peer involves merely the node's opinion that the peer is there. It may issue messages to that remote peer to a directly connected peer, which in turn passes it on. But if it is not present on the network - or if there is no route to it - the message will never reach its destination through mere forwarding.
When implementing the devp2p protocol stack, the "adding" of a remote peer is a prerequisite for the side actually initiating the protocol communication. Adding a peer in effect "runs" the protocol on that peer, and adds an internal mapping between a topic and that peer. It also enables sending and receiving messages using the main io-construct in devp2p - the p2p.MsgReadWriter.
Under the hood, pss implements its own MsgReadWriter, which bridges MsgReadWriter.WriteMsg with Pss.SendRaw, and deftly adds an InjectMsg method which pipes incoming messages to appear on the MsgReadWriter.ReadMsg channel.
An incoming connection is nothing more than an actual PssMsg appearing with a certain Topic. If a Handler har been registered to that Topic, the message will be passed to it. This constitutes a "new" connection if:
- The pss node never called AddPeer with this combination of remote peer address and topic, and
- The pss node never received a PssMsg from this remote peer with this specific Topic before.
If it is a "new" connection, the protocol will be "run" on the remote peer, in the same manner as if it was pre-emptively added.
Index ¶
- Constants
- Variables
- func NewHandler(f HandlerFunc) *handler
- func NewPingProtocol(ping *Ping) *p2p.Protocol
- func NewProtocolMsg(code uint64, msg interface{}) ([]byte, error)
- func SetHandshakeController(pss *Pss, params *HandshakeParams) error
- func ToP2pMsg(msg []byte) (p2p.Msg, error)
- type API
- func (pssapi *API) BaseAddr() (PssAddress, error)
- func (pssapi *API) GetAddress(topic Topic, asymmetric bool, key string) (PssAddress, error)
- func (pssapi *API) GetAsymmetricAddressHint(topic Topic, pubkeyid string) (PssAddress, error)
- func (pssapi *API) GetPeerAddress(pubkeyhex string, topic Topic) (PssAddress, error)
- func (pssapi *API) GetPeerTopics(pubkeyhex string) ([]Topic, error)
- func (pssapi *API) GetPublicKey() (keybytes hexutil.Bytes)
- func (pssapi *API) GetSymmetricAddressHint(topic Topic, symkeyid string) (PssAddress, error)
- func (pssapi *API) GetSymmetricKey(symkeyid string) (hexutil.Bytes, error)
- func (pssapi *API) Receive(ctx context.Context, topic Topic, raw bool, prox bool) (*rpc.Subscription, error)
- func (pssapi *API) SendAsym(pubkeyhex string, topic Topic, msg hexutil.Bytes) error
- func (pssapi *API) SendRaw(addr hexutil.Bytes, topic Topic, msg hexutil.Bytes) error
- func (pssapi *API) SendSym(symkeyhex string, topic Topic, msg hexutil.Bytes) error
- func (pssapi *API) SetPeerPublicKey(pubkey hexutil.Bytes, topic Topic, addr PssAddress) error
- func (pssapi *API) StringToTopic(topicstring string) (Topic, error)
- type APIMsg
- type HandlerFunc
- type HandshakeAPI
- func (api *HandshakeAPI) AddHandshake(topic Topic) error
- func (api *HandshakeAPI) GetHandshakeKeyCapacity(symkeyid string) (uint16, error)
- func (api *HandshakeAPI) GetHandshakeKeys(pubkeyid string, topic Topic, in bool, out bool) (keys []string, err error)
- func (api *HandshakeAPI) GetHandshakePublicKey(symkeyid string) (string, error)
- func (api *HandshakeAPI) Handshake(pubkeyid string, topic Topic, sync bool, flush bool) (keys []string, err error)
- func (api *HandshakeAPI) ReleaseHandshakeKey(pubkeyid string, topic Topic, symkeyid string, flush bool) (removed bool, err error)
- func (api *HandshakeAPI) RemoveHandshake(topic *Topic) error
- func (api *HandshakeAPI) SendSym(symkeyid string, topic Topic, msg hexutil.Bytes) (err error)
- type HandshakeController
- type HandshakeParams
- type Ping
- type PingMsg
- type Protocol
- type ProtocolMsg
- type ProtocolParams
- type Pss
- func (p *Pss) APIs() []rpc.API
- func (p *Pss) BaseAddr() []byte
- func (p *Pss) GenerateSymmetricKey(topic Topic, address PssAddress, addToCache bool) (string, error)
- func (p *Pss) GetPublickeyPeers(keyid string) (topic []Topic, address []PssAddress, err error)
- func (p *Pss) GetSymmetricKey(symkeyid string) ([]byte, error)
- func (p *Pss) Protocols() []p2p.Protocol
- func (p *Pss) PublicKey() *ecdsa.PublicKey
- func (p *Pss) Register(topic *Topic, hndlr *handler) func()
- func (p *Pss) Run(peer *p2p.Peer, rw p2p.MsgReadWriter) error
- func (p *Pss) SendAsym(pubkeyid string, topic Topic, msg []byte) error
- func (p *Pss) SendRaw(address PssAddress, topic Topic, msg []byte) error
- func (p *Pss) SendSym(symkeyid string, topic Topic, msg []byte) error
- func (p *Pss) SetPeerPublicKey(pubkey *ecdsa.PublicKey, topic Topic, address PssAddress) error
- func (p *Pss) SetSymmetricKey(key []byte, topic Topic, address PssAddress, addtocache bool) (string, error)
- func (p *Pss) Start(srv *p2p.Server) error
- func (p *Pss) Stop() error
- func (p *Pss) String() string
- type PssAddress
- type PssMsg
- type PssParams
- type PssReadWriter
- type Topic
Constants ¶
const (
DefaultMsgTTL = time.Second * 120
)
const (
IsActiveHandshake = true
)
const (
IsActiveProtocol = true
)
Variables ¶
var PingProtocol = &protocols.Spec{ Name: "psstest", Version: 1, MaxMsgSize: 1024, Messages: []interface{}{ PingMsg{}, }, }
var PingTopic = ProtocolTopic(PingProtocol)
Functions ¶
func NewPingProtocol ¶
func NewProtocolMsg ¶
Creates a ProtocolMsg
func SetHandshakeController ¶
func SetHandshakeController(pss *Pss, params *HandshakeParams) error
Attach HandshakeController to pss node
Must be called before starting the pss node service
Types ¶
type API ¶
type API struct {
*Pss
}
Additional public methods accessible through API for pss
func (*API) BaseAddr ¶
func (pssapi *API) BaseAddr() (PssAddress, error)
Retrieves the node's base address in hex form
func (*API) GetAddress ¶
func (*API) GetAsymmetricAddressHint ¶
func (pssapi *API) GetAsymmetricAddressHint(topic Topic, pubkeyid string) (PssAddress, error)
func (*API) GetPeerAddress ¶
func (pssapi *API) GetPeerAddress(pubkeyhex string, topic Topic) (PssAddress, error)
func (*API) GetPublicKey ¶
Retrieves the node's public key in hex form
func (*API) GetSymmetricAddressHint ¶
func (pssapi *API) GetSymmetricAddressHint(topic Topic, symkeyid string) (PssAddress, error)
func (*API) GetSymmetricKey ¶
func (*API) Receive ¶
func (pssapi *API) Receive(ctx context.Context, topic Topic, raw bool, prox bool) (*rpc.Subscription, error)
Creates a new subscription for the caller. Enables external handling of incoming messages.
A new handler is registered in pss for the supplied topic ¶
All incoming messages to the node matching this topic will be encapsulated in the APIMsg struct and sent to the subscriber
func (*API) SetPeerPublicKey ¶
Set Public key to associate with a particular Pss peer
type APIMsg ¶
Wrapper for receiving pss messages when using the pss API providing access to sender of message
type HandlerFunc ¶
Signature for a message handler function for a PssMsg Implementations of this type are passed to Pss.Register tosether with a topic,
type HandshakeAPI ¶
type HandshakeAPI struct {
// contains filtered or unexported fields
}
func (*HandshakeAPI) AddHandshake ¶
func (api *HandshakeAPI) AddHandshake(topic Topic) error
Activate handshake functionality on a topic
func (*HandshakeAPI) GetHandshakeKeyCapacity ¶
func (api *HandshakeAPI) GetHandshakeKeyCapacity(symkeyid string) (uint16, error)
Returns the amount of messages the specified symmetric key is still valid for under the handshake scheme
func (*HandshakeAPI) GetHandshakeKeys ¶
func (api *HandshakeAPI) GetHandshakeKeys(pubkeyid string, topic Topic, in bool, out bool) (keys []string, err error)
Returns all valid symmetric keys in store per peer (public key) and topic.
The `in` and `out` parameters indicate for which direction(s) symmetric keys will be returned. If both are false, no keys (and no error) will be returned.
func (*HandshakeAPI) GetHandshakePublicKey ¶
func (api *HandshakeAPI) GetHandshakePublicKey(symkeyid string) (string, error)
Returns the byte representation of the public key in ascii hex associated with the given symmetric key
func (*HandshakeAPI) Handshake ¶
func (api *HandshakeAPI) Handshake(pubkeyid string, topic Topic, sync bool, flush bool) (keys []string, err error)
Initiate a handshake session for a peer (public key) and topic combination.
If `sync` is set, the call will block until keys are received from peer, or if the handshake request times out
If `flush` is set, the max amount of keys will be sent to the peer regardless of how many valid keys that currently exist in the store.
Returns list of symmetric key ids that can be passed to pss.GetSymmetricKey() for retrieval of the symmetric key bytes themselves.
Fails if the incoming symmetric key store is already full (and `flush` is false), or if the underlying key dispatcher fails
func (*HandshakeAPI) ReleaseHandshakeKey ¶
func (api *HandshakeAPI) ReleaseHandshakeKey(pubkeyid string, topic Topic, symkeyid string, flush bool) (removed bool, err error)
Manually expire the given symkey
If `flush` is set, garbage collection will be performed before returning.
Returns true on successful removal, false otherwise
func (*HandshakeAPI) RemoveHandshake ¶
func (api *HandshakeAPI) RemoveHandshake(topic *Topic) error
Deactivate handshake functionality on a topic
type HandshakeController ¶
type HandshakeController struct {
// contains filtered or unexported fields
}
Singleton object enabling semi-automatic Diffie-Hellman exchange of ephemeral symmetric keys
type HandshakeParams ¶
type HandshakeParams struct { SymKeyRequestTimeout time.Duration SymKeyExpiryTimeout time.Duration SymKeySendLimit uint16 SymKeyCapacity uint8 }
Initialization parameters for the HandshakeController
SymKeyRequestExpiry: Timeout for waiting for a handshake reply (default 8000 ms)
SymKeySendLimit: Amount of messages symmetric keys issues by this node is valid for (default 256)
SymKeyCapacity: Ideal (and maximum) amount of symmetric keys held per direction per peer (default 4)
func NewHandshakeParams ¶
func NewHandshakeParams() *HandshakeParams
Sane defaults for HandshakeController initialization
type Protocol ¶
type Protocol struct { *Pss Asymmetric bool Symmetric bool RWPoolMu sync.Mutex // contains filtered or unexported fields }
Convenience object for emulation devp2p over pss
func RegisterProtocol ¶
func RegisterProtocol(ps *Pss, topic *Topic, spec *protocols.Spec, targetprotocol *p2p.Protocol, options *ProtocolParams) (*Protocol, error)
Activates devp2p emulation over a specific pss topic
One or both encryption schemes must be specified. If only one is specified, the protocol will not be valid for the other, and will make the message handler return errors
func (*Protocol) AddPeer ¶
func (p *Protocol) AddPeer(peer *p2p.Peer, topic Topic, asymmetric bool, key string) (p2p.MsgReadWriter, error)
Runs an emulated pss Protocol on the specified peer, linked to a specific topic `key` and `asymmetric` specifies what encryption key to link the peer to. The key must exist in the pss store prior to adding the peer.
func (*Protocol) Handle ¶
Generic handler for incoming messages over devp2p emulation
To be passed to pss.Register()
Will run the protocol on a new incoming peer, provided that the encryption key of the message has a match in the internal pss keypool
Fails if protocol is not valid for the message encryption scheme, if adding a new peer fails, or if the message is not a serialized p2p.Msg (which it always will be if it is sent from this object).
func (*Protocol) RemovePeer ¶
type ProtocolMsg ¶
Convenience wrapper for devp2p protocol messages for transport over pss
type ProtocolParams ¶
Protocol options to be passed to a new Protocol instance
The parameters specify which encryption schemes to allow
type Pss ¶
type Pss struct { *network.Kademlia // we can get the Kademlia address from this // contains filtered or unexported fields }
Toplevel pss object, takes care of message sending, receiving, decryption and encryption, message handler dispatchers and message forwarding.
Implements node.Service
func NewPss ¶
Creates a new Pss instance.
In addition to params, it takes a swarm network Kademlia and a FileStore storage for message cache storage.
func (*Pss) GenerateSymmetricKey ¶
func (p *Pss) GenerateSymmetricKey(topic Topic, address PssAddress, addToCache bool) (string, error)
Automatically generate a new symkey for a topic and address hint
func (*Pss) GetPublickeyPeers ¶
func (p *Pss) GetPublickeyPeers(keyid string) (topic []Topic, address []PssAddress, err error)
Returns all recorded topic and address combination for a specific public key
func (*Pss) GetSymmetricKey ¶
Returns a symmetric key byte seqyence stored in the whisper backend by its unique id
Passes on the error value from the whisper backend
func (*Pss) Register ¶
Links a handler function to a Topic
All incoming messages with an envelope Topic matching the topic specified will be passed to the given Handler function.
There may be an arbitrary number of handler functions per topic.
Returns a deregister function which needs to be called to deregister the handler,
func (*Pss) SendAsym ¶
Send a message using asymmetric encryption
Fails if the key id does not match any in of the stored public keys
func (*Pss) SendRaw ¶
func (p *Pss) SendRaw(address PssAddress, topic Topic, msg []byte) error
Send a raw message (any encryption is responsibility of calling client)
Will fail if raw messages are disallowed
func (*Pss) SendSym ¶
Send a message using symmetric encryption
Fails if the key id does not match any of the stored symmetric keys
func (*Pss) SetPeerPublicKey ¶
Links a peer ECDSA public key to a topic
This is required for asymmetric message exchange on the given topic
The value in `address` will be used as a routing hint for the public key / topic association
func (*Pss) SetSymmetricKey ¶
func (p *Pss) SetSymmetricKey(key []byte, topic Topic, address PssAddress, addtocache bool) (string, error)
Links a peer symmetric key (arbitrary byte sequence) to a topic
This is required for symmetrically encrypted message exchange on the given topic
The key is stored in the whisper backend.
If addtocache is set to true, the key will be added to the cache of keys used to attempt symmetric decryption of incoming messages.
Returns a string id that can be used to retrieve the key bytes from the whisper backend (see pss.GetSymmetricKey())
type PssAddress ¶
type PssAddress []byte
PssAddress is an alias for []byte. It represents a variable length address
func (PssAddress) MarshalJSON ¶
func (a PssAddress) MarshalJSON() ([]byte, error)
MarshalJSON implements the json.Marshaler interface
func (*PssAddress) UnmarshalJSON ¶
func (a *PssAddress) UnmarshalJSON(input []byte) error
UnmarshalJSON implements the json.Marshaler interface
type PssParams ¶
type PssParams struct { MsgTTL time.Duration CacheTTL time.Duration SymKeyCacheCapacity int AllowRaw bool // If true, enables sending and receiving messages without builtin pss encryption // contains filtered or unexported fields }
Pss configuration parameters
func (*PssParams) WithPrivateKey ¶
func (params *PssParams) WithPrivateKey(privatekey *ecdsa.PrivateKey) *PssParams
type PssReadWriter ¶
PssReadWriter bridges pss send/receive with devp2p protocol send/receive
Implements p2p.MsgReadWriter
type Topic ¶
Topic is the PSS encapsulation of the Whisper topic type
func BytesToTopic ¶
BytesToTopic hashes an arbitrary length byte slice and truncates it to the length of a topic, using only the first bytes of the digest
func ProtocolTopic ¶
Uniform translation of protocol specifiers to topic
func (Topic) MarshalJSON ¶
MarshalJSON implements the json.Marshaler interface
func (*Topic) UnmarshalJSON ¶
MarshalJSON implements the json.Marshaler interface