wallet-encrypt-decrypt

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

BSV Message Encryption

BSV消息加密

Encrypt and decrypt messages between parties using
@bsv/sdk
.
使用
@bsv/sdk
在各方之间加密和解密消息。

Recommended: Use EncryptedMessage from @bsv/sdk

推荐方案:使用@bsv/sdk中的EncryptedMessage

The
@bsv/sdk
provides
EncryptedMessage
for secure message encryption. This is the preferred approach - avoid rolling custom encryption implementations.
typescript
import { PrivateKey, EncryptedMessage, Utils } from '@bsv/sdk'

const sender = PrivateKey.fromRandom()
const recipient = PrivateKey.fromRandom()

// Encrypt: sender uses their private key + recipient's public key
const message = Utils.toArray('Secret message', 'utf8')
const encrypted = EncryptedMessage.encrypt(message, sender, recipient.toPublicKey())

// Decrypt: recipient uses their private key
const decrypted = EncryptedMessage.decrypt(encrypted, recipient)
const plaintext = Utils.toUTF8(decrypted)
@bsv/sdk
提供了
EncryptedMessage
用于安全的消息加密。这是首选方案——请避免自行实现自定义加密逻辑。
typescript
import { PrivateKey, EncryptedMessage, Utils } from '@bsv/sdk'

const sender = PrivateKey.fromRandom()
const recipient = PrivateKey.fromRandom()

// Encrypt: sender uses their private key + recipient's public key
const message = Utils.toArray('Secret message', 'utf8')
const encrypted = EncryptedMessage.encrypt(message, sender, recipient.toPublicKey())

// Decrypt: recipient uses their private key
const decrypted = EncryptedMessage.decrypt(encrypted, recipient)
const plaintext = Utils.toUTF8(decrypted)

Two Encryption Patterns

两种加密模式

Pattern 1: Static-Static ECDH (Both Parties Have Keys)

模式1:静态-静态ECDH(双方均持有密钥)

Use when both parties have established keypairs (e.g., BAP identities, paymail addresses).
typescript
import { PrivateKey, EncryptedMessage, Utils } from '@bsv/sdk'

// Alice and Bob both have persistent keys
const alice = PrivateKey.fromWif('L1...')
const bob = PrivateKey.fromWif('K1...')

// Alice encrypts to Bob
const ciphertext = EncryptedMessage.encrypt(
  Utils.toArray('Hello Bob', 'utf8'),
  alice,
  bob.toPublicKey()
)

// Bob decrypts from Alice
const plaintext = EncryptedMessage.decrypt(ciphertext, bob)
Use cases:
  • Peer-to-peer encrypted messaging
  • Encrypted backups to your own key
  • Communication between known identities
适用于双方均已建立密钥对的场景(例如BAP身份、paymail地址)。
typescript
import { PrivateKey, EncryptedMessage, Utils } from '@bsv/sdk'

// Alice and Bob both have persistent keys
const alice = PrivateKey.fromWif('L1...')
const bob = PrivateKey.fromWif('K1...')

// Alice encrypts to Bob
const ciphertext = EncryptedMessage.encrypt(
  Utils.toArray('Hello Bob', 'utf8'),
  alice,
  bob.toPublicKey()
)

// Bob decrypts from Alice
const plaintext = EncryptedMessage.decrypt(ciphertext, bob)
适用场景:
  • 点对点加密消息传输
  • 使用自有密钥进行加密备份
  • 已知身份之间的通信

Pattern 2: ECIES-Style (Ephemeral Sender Key)

模式2:ECIES风格(临时发送方密钥)

Use when sender doesn't have a persistent identity (e.g., browser-to-server).
typescript
import { PrivateKey, EncryptedMessage, Utils } from '@bsv/sdk'

// Server has persistent key, client generates ephemeral
const serverKey = PrivateKey.fromWif('L1...')
const ephemeralClient = PrivateKey.fromRandom()

// Client encrypts (ephemeral → server)
const encrypted = EncryptedMessage.encrypt(
  Utils.toArray('sensitive data', 'utf8'),
  ephemeralClient,
  serverKey.toPublicKey()
)

// Include ephemeral public key so server can decrypt
const payload = {
  ephemeralPub: ephemeralClient.toPublicKey().toString(),
  ciphertext: Utils.toHex(encrypted)
}

// Server decrypts using ephemeral pubkey
const clientPub = PublicKey.fromString(payload.ephemeralPub)
// Note: EncryptedMessage.decrypt needs the sender info embedded in ciphertext
Use cases:
  • Browser-to-server encryption
  • One-way secure channels
  • Forward secrecy (fresh key per message)
适用于发送方无持久身份的场景(例如浏览器到服务器的通信)。
typescript
import { PrivateKey, EncryptedMessage, Utils } from '@bsv/sdk'

// Server has persistent key, client generates ephemeral
const serverKey = PrivateKey.fromWif('L1...')
const ephemeralClient = PrivateKey.fromRandom()

// Client encrypts (ephemeral → server)
const encrypted = EncryptedMessage.encrypt(
  Utils.toArray('sensitive data', 'utf8'),
  ephemeralClient,
  serverKey.toPublicKey()
)

// Include ephemeral public key so server can decrypt
const payload = {
  ephemeralPub: ephemeralClient.toPublicKey().toString(),
  ciphertext: Utils.toHex(encrypted)
}

// Server decrypts using ephemeral pubkey
const clientPub = PublicKey.fromString(payload.ephemeralPub)
// Note: EncryptedMessage.decrypt needs the sender info embedded in ciphertext
适用场景:
  • 浏览器到服务器的加密通信
  • 单向安全通道
  • 前向保密(每条消息使用新密钥)

How ECDH Key Agreement Works

ECDH密钥协商的工作原理

Both parties derive the same shared secret:
Alice: alicePrivKey × bobPubKey = sharedPoint
Bob:   bobPrivKey × alicePubKey = sharedPoint
The shared point is then used to derive a symmetric key for AES-256-GCM encryption.
双方会生成相同的共享密钥:
Alice: alicePrivKey × bobPubKey = sharedPoint
Bob:   bobPrivKey × alicePubKey = sharedPoint
随后,该共享点会被用于生成AES-256-GCM加密所需的对称密钥。

API Reference

API参考

EncryptedMessage.encrypt()

EncryptedMessage.encrypt()

typescript
static encrypt(
  message: number[],      // Plaintext as byte array
  sender: PrivateKey,     // Sender's private key
  recipient: PublicKey    // Recipient's public key
): number[]               // Encrypted bytes
typescript
static encrypt(
  message: number[],      // Plaintext as byte array
  sender: PrivateKey,     // Sender's private key
  recipient: PublicKey    // Recipient's public key
): number[]               // Encrypted bytes

EncryptedMessage.decrypt()

EncryptedMessage.decrypt()

typescript
static decrypt(
  ciphertext: number[],   // Encrypted bytes from encrypt()
  recipient: PrivateKey   // Recipient's private key
): number[]               // Decrypted plaintext bytes
typescript
static decrypt(
  ciphertext: number[],   // Encrypted bytes from encrypt()
  recipient: PrivateKey   // Recipient's private key
): number[]               // Decrypted plaintext bytes

Utility Functions

工具函数

typescript
// String to bytes
Utils.toArray('hello', 'utf8')  // number[]

// Bytes to string
Utils.toUTF8([104, 101, 108, 108, 111])  // 'hello'

// Bytes to hex
Utils.toHex([1, 2, 3])  // '010203'

// Hex to bytes
Utils.toArray('010203', 'hex')  // [1, 2, 3]
typescript
// String to bytes
Utils.toArray('hello', 'utf8')  // number[]

// Bytes to string
Utils.toUTF8([104, 101, 108, 108, 111])  // 'hello'

// Bytes to hex
Utils.toHex([1, 2, 3])  // '010203'

// Hex to bytes
Utils.toArray('010203', 'hex')  // [1, 2, 3]

Security Properties

安全特性

  • Authenticated encryption: AES-256-GCM provides confidentiality + integrity
  • Key agreement: ECDH ensures only intended recipient can decrypt
  • No key transmission: Private keys never leave their owner
  • 认证加密:AES-256-GCM同时提供机密性与完整性保障
  • 密钥协商:ECDH确保只有目标接收方能够解密消息
  • 无密钥传输:私钥永远不会离开其所有者

Common Mistakes

常见误区

Don't roll your own crypto

不要自行实现加密逻辑

Wrong: Implementing ECDH + AES manually
typescript
// Don't do this - use EncryptedMessage instead
const sharedSecret = myPrivKey.deriveSharedSecret(theirPubKey)
const aesKey = sha256(sharedSecret)
// ... manual AES encryption
Correct: Use the SDK's built-in class
typescript
const encrypted = EncryptedMessage.encrypt(message, sender, recipient)
错误做法:手动实现ECDH + AES
typescript
// Don't do this - use EncryptedMessage instead
const sharedSecret = myPrivKey.deriveSharedSecret(theirPubKey)
const aesKey = sha256(sharedSecret)
// ... manual AES encryption
正确做法:使用SDK内置的类
typescript
const encrypted = EncryptedMessage.encrypt(message, sender, recipient)

Don't confuse encryption with authentication

不要混淆加密与认证

  • Encryption (this skill): Hides message content
  • Authentication (
    bitcoin-auth
    ): Proves sender identity
For authenticated + encrypted communication, use both:
typescript
// Encrypt the payload
const encrypted = EncryptedMessage.encrypt(payload, sender, recipient)

// Sign the request (proves sender identity)
const authToken = getAuthToken({ privateKeyWif, requestPath, body })
  • 加密(本技能):隐藏消息内容
  • 认证
    bitcoin-auth
    ):证明发送方身份
如需同时实现认证与加密的通信,请结合使用两者:
typescript
// Encrypt the payload
const encrypted = EncryptedMessage.encrypt(payload, sender, recipient)

// Sign the request (proves sender identity)
const authToken = getAuthToken({ privateKeyWif, requestPath, body })