wallet-encrypt-decrypt

This skill should be used when the user asks to "encrypt message with BSV key", "decrypt with private key", "ECDH encryption", "AES-256-GCM BSV", "EncryptedMessage", "BRC-2 encryption", or needs to encrypt/decrypt data using BSV keys and @bsv/sdk.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "wallet-encrypt-decrypt" with this command: npx skills add b-open-io/bsv-skills/b-open-io-bsv-skills-wallet-encrypt-decrypt

BSV Message Encryption

Encrypt and decrypt messages between parties using @bsv/sdk.

Recommended: Use EncryptedMessage from @bsv/sdk

The @bsv/sdk provides EncryptedMessage for secure message encryption. This is the preferred approach - avoid rolling custom encryption implementations.

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)

Use when both parties have established keypairs (e.g., BAP identities, paymail addresses).

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

Pattern 2: ECIES-Style (Ephemeral Sender Key)

Use when sender doesn't have a persistent identity (e.g., browser-to-server).

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)

How ECDH Key Agreement Works

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.

API Reference

EncryptedMessage.encrypt()

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

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

Utility Functions

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

Common Mistakes

Don't roll your own crypto

Wrong: Implementing ECDH + AES manually

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

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:

// Encrypt the payload
const encrypted = EncryptedMessage.encrypt(payload, sender, recipient)

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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Web3

wallet-brc100

No summary provided by upstream source.

Repository SourceNeeds Review
Web3

wallet-brc100-go

No summary provided by upstream source.

Repository SourceNeeds Review
Web3

wallet-send-bsv

No summary provided by upstream source.

Repository SourceNeeds Review
General

stratum-v1

No summary provided by upstream source.

Repository SourceNeeds Review