Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
*.aar
*.jar
Tss.xcframework/
bin/
scripts/bin/
scripts/*.json
scripts/*.ks
scripts/*.nostr
nostr-*/
test-*/
Tss.xcframework/
tss.aar
tss-sources.jar
peer*.json
148 changes: 148 additions & 0 deletions NOSTR_MESSAGE_ENCRYPTION_FLOW.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# NIP-44 Encryption and Rumor/Seal/Wrap Pattern

This document explains the security scheme of performing transactions over NOSTR work in the BoldWallet TSS (Threshold Signature Scheme) implementation.
NIP-44 encryption and the rumor/seal/wrap pattern works

## Table of Contents

1. [Overview](#overview)
2. [NIP-44 Encryption Basics](#nip-44-encryption-basics)
3. [Rumor/Seal/Wrap Pattern](#rumorsealwrap-pattern)
4. [Complete Message Flow](#complete-message-flow)
5. [Implementation Details](#implementation-details)

---

## Overview

The BoldWallet TSS implementation uses a Multi-layer encryption pattern based on Nostr Improvement Proposals (NIPs):

- **NIP-44**: Encrypted Direct Messages using shared secret derivation
- **NIP-59**: Gift Wraps (Rumor → Seal → Wrap for TSS message transport) for additional privacy

This provides:
- **End-to-end encryption** between parties
- **Metadata privacy** (relays can't see sender/recipient relationships)
- **Forward secrecy** (one-time keys for wraps)
- **Authentication** (signed seals verify sender identity)

---

## Rumor/Seal/Wrap: Simplified Flowchart

Below is a step-by-step flowchart of the message pipeline, showing key details for each stage.
The NIP-44 encryption step is shown as its own box with details.
Breaking the message into chunks is required due to the size limit of NIP-44 being 16KB per message.
Keysign is under the 16KB limit, but keygen is usually larger and needs chunking.

```plaintext

┌──────────────────────────────────────────────┐
│ Content (pre-agreed) │
├──────────────────────────────────────────────┤
│ - session_id │
│ - chunk │
│ - data (TSS payload) │
│ - tx_intent_hash: hash(address, amount, fee) │
│ (All parties must agree on this hash) │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ Rumor │
├──────────────────────────────────────────────┤
│ Type: Unsigned Nostr Event │
│ Kind: 14 (Chat Message) │
│ Content: { │
│ "session_id": "...", │
│ "chunk": "...", │
│ "data": "..." │
│ } │
│ ID: Calculated from JSON content │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ NIP-44 Encrypt │
├──────────────────────────────────────────────┤
│ Purpose: Shared secret encryption between │
│ sender and recipient │
│ Algorithm: XChaCha20-Poly1305 (NIP-44) │
│ Keys: Sender's nsec + Recipient's npub │
│ Output: Encrypted rumor JSON │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ Seal │
├──────────────────────────────────────────────┤
│ Type: Signed Nostr Event │
│ Kind: 13 (Sealed Direct Message) │
│ Content: NIP-44 encrypted Rumor JSON │
│ ID: From signed/encrypted content │
│ Signature: Sender's nsec │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ Wrap │
├──────────────────────────────────────────────┤
│ Type: One-time Nostr Event │
│ Kind: 1059 (Gift Wrap) │
│ Content: NIP-44 encrypted Seal │
│ ID: From one-time key │
│ Sender pubkey: Ephemeral/one-time key │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ Publish to Nostr Relay │
└──────────────────────────────────────────────┘
|
|
┌──────────────────────────────────────────────┐
│ Recipient/s subscribed to Nostr relay │
│ receive and process message │
└──────────────────────────────────────────────┘


```

**Summary**:
- **Rumor**: Unsigned, raw message chunk. Basic JSON chunk data. `Kind: 14`
- **NIP-44 Encrypt**: Uses sender's nsec and recipient's npub to encrypt Rumor using XChaCha20-Poly1305.
- **Seal**: Rumor encrypted (NIP-44) and signed. `Kind: 13`
- **Wrap**: Seal is encrypted again and wrapped in a one-time-use event (kind 1059), using a new ephemeral key for sender.

Each stage adds a layer of privacy, authentication, and unlinkability.

---

## Code References

- **Crypto functions**: `BBMTLib/tss/nostrtransport/crypto.go`
- **Messenger (sending)**: `BBMTLib/tss/nostrtransport/messenger.go`
- **Message pump (receiving)**: `BBMTLib/tss/nostrtransport/pump.go`
- **Client (publishing)**: `BBMTLib/tss/nostrtransport/client.go`

---

### Summary

The implementation is **fully compliant** with NIP-59 core requirements:
- ✅ Seals use kind:13 with empty tags
- ✅ Wraps use kind:1059 with recipient "p" tag
- ✅ Uses NIP-44 encryption
- ✅ Uses one-time keys for wraps

The differences are **extensions** that add TSS-specific functionality (chunking, session management) without violating the specification. The code correctly implements the rumor/seal/wrap pattern as specified in [NIP-59](https://www.e2encrypted.com/nostr/nips/59/).

---

## References

- [NIP-44: Encrypted Direct Messages](https://github.com/nostr-protocol/nips/blob/master/44.md)
- [NIP-59: Gift Wraps](https://www.e2encrypted.com/nostr/nips/59/)
- [NIP-19: bech32-encoded entities](https://github.com/nostr-protocol/nips/blob/master/19.md)

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ A secure Multi-Party Computation (MPC) Threshold Signature Scheme (TSS) library
# Get dependencies
go mod tidy

# Initialize Go Mobile
go get golang.org/x/mobile/bind
# Initialize Go Mobile (install as tool, doesn't modify go.mod)
go install golang.org/x/mobile/bind@latest

# Set build flags
export GOFLAGS="-mod=mod"
Expand Down
36 changes: 14 additions & 22 deletions build.sh
Original file line number Diff line number Diff line change
@@ -1,28 +1,20 @@
#!/bin/bash
echo "building gomobile tss lib"
go mod tidy

set -e

# Default to Android
TARGET="android"

# Check CLI args
if [[ "$1" == "--iphone" ]]; then
TARGET="ios"
elif [[ "$1" == "--android" ]]; then
TARGET="android"
# Install gomobile if not already installed
if ! command -v gomobile &> /dev/null; then
echo "gomobile not found, installing..."
go install golang.org/x/mobile/cmd/gomobile@latest
# Add Go bin directory to PATH if not already there
export PATH="$PATH:$(go env GOPATH)/bin"
fi

# Setup
export GOFLAGS="-mod=mod"
go mod tidy
go get golang.org/x/mobile/bind
gomobile init
export GOFLAGS="-mod=mod"
gomobile bind -v -target=android -androidapi 21 github.com/BoldBitcoinWallet/BBMTLib/tss

# Build
if [[ "$TARGET" == "android" ]]; then
echo "Building gomobile TSS Android lib..."
gomobile bind -v -target=android -androidapi 21 github.com/BoldBitcoinWallet/BBMTLib/tss
else
echo "Building gomobile TSS iOS + macOS lib..."
gomobile bind -v -target=ios,macos,iossimulator -tags=ios,macos,iossimulator github.com/BoldBitcoinWallet/BBMTLib/tss
fi
# Run go mod tidy again at the end to ensure go.mod/go.sum are up to date
# This ensures any dependencies added during the build are included
echo "Updating go.mod/go.sum..."
go mod tidy
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ require (
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.3
github.com/gorilla/mux v1.8.1
github.com/ipfs/go-log/v2 v2.1.3
github.com/nbd-wtf/go-nostr v0.52.0
github.com/nbd-wtf/go-nostr v0.52.3
github.com/patrickmn/go-cache v2.1.0+incompatible
)

Expand All @@ -38,7 +38,7 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
golang.org/x/arch v0.15.0 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/net v0.47.0 // indirect
)

require (
Expand All @@ -59,8 +59,8 @@ require (
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/crypto v0.44.0 // indirect
golang.org/x/sys v0.38.0 // indirect
google.golang.org/protobuf v1.36.2 // indirect
)

Expand Down
16 changes: 8 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nbd-wtf/go-nostr v0.52.0 h1:9gtz0VOUPOb0PC2kugr2WJAxThlCSSM62t5VC3tvk1g=
github.com/nbd-wtf/go-nostr v0.52.0/go.mod h1:4avYoc9mDGZ9wHsvCOhHH9vPzKucCfuYBtJUSpHTfNk=
github.com/nbd-wtf/go-nostr v0.52.3 h1:Xd87pXfJEJRXHpM+fLjQQln8dBNNaoPA10V7BbyP4KI=
github.com/nbd-wtf/go-nostr v0.52.3/go.mod h1:4avYoc9mDGZ9wHsvCOhHH9vPzKucCfuYBtJUSpHTfNk=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
Expand Down Expand Up @@ -227,8 +227,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
Expand Down Expand Up @@ -256,8 +256,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand All @@ -282,8 +282,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
Expand Down
Loading