Whisper
Whisper是一种简单的基于点对点身份的消息传递系统,旨在成为下一去中心化的应用程序的构建块。 它旨在以相当的代价提供弹性和隐私。 在接下来的部分中,我们将设置一个支持Whisper的以太坊节点,然后我们将学习如何在Whisper协议上发送和接收加密消息。
连接Whisper客户端
要使用连接Whisper客户端,我们必须首先连接到运行whisper的以太坊节点。 不幸的是,诸如infura之类的公共网关不支持whisper,因为没有金钱动力免费处理这些消息。 Infura可能会在不久的将来支持whisper,但现在我们必须运行我们自己的geth
节点。一旦你安装 geth, 运行geth的时候加 --shh
flag来支持whisper协议, 并且加 --ws
flag和 --rpc
,来支持websocket来接收实时信息,
geth --rpc --shh --ws
现在在我们的Go应用程序中,我们将导入在whisper/shhclient
中找到的go-ethereum whisper客户端软件包并初始化客户端,使用默认的websocket端口“8546”通过websockets连接我们的本地geth节点。
client, err := shhclient.Dial("ws://127.0.0.1:8546")
if err != nil {
log.Fatal(err)
}
_ = client // we'll be using this in the 下个章节
现在我们已经拨打了,让我们创建一个密钥对来加密消息,然后再发送消息 在下一章节.
完整代码
Commands
geth --rpc --shh --ws
package main
import (
"log"
"github.com/ethereum/go-ethereum/whisper/shhclient"
)
func main() {
client, err := shhclient.Dial("ws://127.0.0.1:8546")
if err != nil {
log.Fatal(err)
}
_ = client // we'll be using this in the 下个章节
fmt.Println("we have a whisper connection")
}
生成 Whisper 密匙对
在Whisper中,消息必须使用对称或非对称密钥加密,以防止除预期接收者以外的任何人读取消息。
在连接到Whisper客户端后,您需要调用客户端的NewKeyPair
方法来生成该节点将管理的新公共和私有对。 此函数的结果将是一个唯一的ID,它引用我们将在接下来的几节中用于加密和解密消息的密钥对。
keyID, err := client.NewKeyPair(context.Background())
if err != nil {
log.Fatal(err)
}
fmt.Println(keyID) // 0ec5cfe4e215239756054992dbc2e10f011db1cdfc88b9ba6301e2f9ea1b58d2
在下一章节让我们学习如何发送一个加密的消息。
完整代码
Commands
geth --rpc --shh --ws
package main
import (
"context"
"fmt"
"log"
"github.com/ethereum/go-ethereum/whisper/shhclient"
)
func main() {
client, err := shhclient.Dial("ws://127.0.0.1:8546")
if err != nil {
log.Fatal(err)
}
keyID, err := client.NewKeyPair(context.Background())
if err != nil {
log.Fatal(err)
}
fmt.Println(keyID) // 0ec5cfe4e215239756054992dbc2e10f011db1cdfc88b9ba6301e2f9ea1b58d2
}
在Whisper上发送消息
在我们能够创建消息之前,我们必须首先使用公钥来加密消息。在上个章节中,我们学习了如何使用NewKeyPair
函数生成公钥和私钥对,该函数返回了引用该密钥对的密钥ID。 我们现在必须调用PublicKey
函数以字节格式读取密钥对的公钥,我们将使用它来加密消息。
publicKey, err := client.PublicKey(context.Background(), keyID)
if err != nil {
log.Print(err)
}
fmt.Println(hexutil.Encode(publicKey)) // 0x04f17356fd52b0d13e5ede84f998d26276f1fc9d08d9e73dcac6ded5f3553405db38c2f257c956f32a0c1fca4c3ff6a38a2c277c1751e59a574aecae26d3bf5d1d
现在我们将通过从go-ethereumwhisper/whisperv6
包中初始化NewMessage
结构来构造我们的私语消息,这需要以下属性:
-
Payload
字节格式的消息内容 -
PublicKey
加密的公钥 -
TTL
消息的活跃时间 -
PowTime
做工证明的时间上限 -
PowTarget
做工证明的时间下限
message := whisperv6.NewMessage{
Payload: []byte("Hello"),
PublicKey: publicKey,
TTL: 60,
PowTime: 2,
PowTarget: 2.5,
}
我们现在可以通过调用客户端的Post
函数向网络广播,给它消息,它是否会返回消息的哈希值。
messageHash, err := client.Post(context.Background(), message)
if err != nil {
log.Fatal(err)
}
fmt.Println(messageHash) // 0xdbfc815d3d122a90d7fb44d1fc6a46f3d76ec752f3f3d04230fe5f1b97d2209a
在下个章节中我们将看到如何创建消息订阅以便能够实时接收消息。
完整代码
Commands
geth --shh --rpc --ws
package main
import (
"context"
"fmt"
"log"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/whisper/shhclient"
"github.com/ethereum/go-ethereum/whisper/whisperv6"
)
func main() {
client, err := shhclient.Dial("ws://127.0.0.1:8546")
if err != nil {
log.Fatal(err)
}
keyID, err := client.NewKeyPair(context.Background())
if err != nil {
log.Fatal(err)
}
fmt.Println(keyID) // 0ec5cfe4e215239756054992dbc2e10f011db1cdfc88b9ba6301e2f9ea1b58d2
publicKey, err := client.PublicKey(context.Background(), keyID)
if err != nil {
log.Print(err)
}
fmt.Println(hexutil.Encode(publicKey)) // 0x04f17356fd52b0d13e5ede84f998d26276f1fc9d08d9e73dcac6ded5f3553405db38c2f257c956f32a0c1fca4c3ff6a38a2c277c1751e59a574aecae26d3bf5d1d
message := whisperv6.NewMessage{
Payload: []byte("Hello"),
PublicKey: publicKey,
TTL: 60,
PowTime: 2,
PowTarget: 2.5,
}
messageHash, err := client.Post(context.Background(), message)
if err != nil {
log.Fatal(err)
}
fmt.Println(messageHash) // 0xdbfc815d3d122a90d7fb44d1fc6a46f3d76ec752f3f3d04230fe5f1b97d2209a
}
监听/订阅Whisper消息
在本节中,我们将订阅websockets上的Whisper消息。 我们首先需要的是一个通道,它将从whisper/whisperv6
包中的Message
类型接收Whispe消息。
messages := make(chan *whisperv6.Message)
在我们调用订阅之前,我们首先需要确定消息的过滤标准。 从whisperv6包中初始化一个新的Criteria
对象。 由于我们只对定位到我们的消息感兴趣,因此我们将条件对象上的PrivateKeyID
属性设置为我们用于加密消息的相同密钥ID。
criteria := whisperv6.Criteria{
PrivateKeyID: keyID,
}
接下来,我们调用客户端的SubscribeMessages
方法,该方法订阅符合给定条件的消息。 HTTP不支持此方法; 仅支持双向连接,例如websockets和IPC。 最后一个参数是我们之前创建的消息通道。
sub, err := client.SubscribeMessages(context.Background(), criteria, messages)
if err != nil {
log.Fatal(err)
}
现在我们已经订阅了,我们可以使用select
语句来读取消息,并处理订阅中的错误。 如果您从上一节回忆起来,消息内容在Payload
属性中作为字节切片,我们可以将其转换回人类可读的字符串。
for {
select {
case err := <-sub.Err():
log.Fatal(err)
case message := <-messages:
fmt.Printf(string(message.Payload)) // "Hello"
}
}
查看下面的完整代码,获取完整的栗子。 这就是消息订阅的所有内容。
完整代码
Commands
geth --shh --rpc --ws
package main
import (
"context"
"fmt"
"log"
"os"
"runtime"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/whisper/shhclient"
"github.com/ethereum/go-ethereum/whisper/whisperv6"
)
func main() {
client, err := shhclient.Dial("ws://127.0.0.1:8546")
if err != nil {
log.Fatal(err)
}
keyID, err := client.NewKeyPair(context.Background())
if err != nil {
log.Fatal(err)
}
fmt.Println(keyID) // 0ec5cfe4e215239756054992dbc2e10f011db1cdfc88b9ba6301e2f9ea1b58d2
messages := make(chan *whisperv6.Message)
criteria := whisperv6.Criteria{
PrivateKeyID: keyID,
}
sub, err := client.SubscribeMessages(context.Background(), criteria, messages)
if err != nil {
log.Fatal(err)
}
go func() {
for {
select {
case err := <-sub.Err():
log.Fatal(err)
case message := <-messages:
fmt.Printf(string(message.Payload)) // "Hello"
os.Exit(0)
}
}
}()
publicKey, err := client.PublicKey(context.Background(), keyID)
if err != nil {
log.Print(err)
}
fmt.Println(hexutil.Encode(publicKey)) // 0x04f17356fd52b0d13e5ede84f998d26276f1fc9d08d9e73dcac6ded5f3553405db38c2f257c956f32a0c1fca4c3ff6a38a2c277c1751e59a574aecae26d3bf5d1d
message := whisperv6.NewMessage{
Payload: []byte("Hello"),
PublicKey: publicKey,
TTL: 60,
PowTime: 2,
PowTarget: 2.5,
}
messageHash, err := client.Post(context.Background(), message)
if err != nil {
log.Fatal(err)
}
fmt.Println(messageHash) // 0xdbfc815d3d122a90d7fb44d1fc6a46f3d76ec752f3f3d04230fe5f1b97d2209a
runtime.Goexit() // wait for goroutines to finish
}