This repository contains a Rust implementation of the Transaction Authorization Protocol (TAP), a decentralized protocol for securely authorizing blockchain transactions before they are submitted on-chain. TAP-RS targets payment-related use cases, Travel Rule compliance, and secure transaction coordination.
TAP-RS is organized as a Rust workspace with multiple crates:
- tap-msg: Core message processing for TAP with integrated DIDComm support
- tap-msg-derive: Procedural derive macro for automatic TAP message trait implementation
- tap-agent: TAP agent functionality and identity management
- tap-caip: Implementation of Chain Agnostic Identifier Standards
- tap-node: TAP node orchestration, message routing, and transaction storage
- tap-http: HTTP DIDComm server implementation
- tap-wasm: WebAssembly bindings with DIDComm SecretsResolver integration
- tap-ts: TypeScript/WASM wrapper for browser and Node.js environments
The Transaction Authorization Protocol (TAP) adds a secure authorization layer to blockchain transactions, enabling participants to:
- Verify transaction details before settlement
- Exchange required compliance information privately
- Prevent sending to wrong addresses or incorrect amounts
- Implement multi-party authorization workflows
- Conduct Travel Rule compliance checks off-chain
TAP-RS implements this protocol with a focus on:
- Security: End-to-end encrypted messaging via DIDComm v2
- Interoperability: Support for multiple blockchains through CAIP standards
- Extensibility: Modular design allowing custom integrations
- Cross-Platform: Native support and WebAssembly for browser environments
This project has successfully implemented all core TAP message types and flows as specified in the TAIPs (Transaction Authorization Protocol Improvement Proposals). The codebase is feature-complete for standard TAP use cases.
This project has specific dependency version requirements:
- UUID v0.8.2: Required for compatibility with the didcomm crate. Do not upgrade! See DEPENDENCIES.md for details.
- WASM Support: Several dependencies require special features for WebAssembly compatibility.
Please review the DEPENDENCIES.md file before updating any dependencies or adding new crates to the workspace.
- Rust 1.71.0 or later
- Cargo
# Clone the repository
git clone https://github.com/notabene/tap-rs.git
cd tap-rs
# Build all crates
cargo build
# Run tests
cargo test
TAP-RS includes several useful command-line tools that can be installed from crates.io or from source:
# Install tools from crates.io
cargo install tap-agent tap-http
# Or install from the repository
cargo install --path tap-rs/tap-agent
cargo install --path tap-rs/tap-http
Available command-line tools:
-
tap-agent-cli: Manage DIDs and keys for TAP protocol
# Generate a new did:key with Ed25519 tap-agent-cli generate # List stored keys tap-agent-cli keys list # Pack a plaintext DIDComm message (supports signed, authcrypt, and anoncrypt modes) tap-agent-cli pack --input message.json --output packed.json --mode signed tap-agent-cli pack --input message.json --output packed.json --mode authcrypt --recipient did:key:z6Mk... tap-agent-cli pack --input message.json --output packed.json --mode anoncrypt --recipient did:key:z6Mk... # Unpack a signed or encrypted DIDComm message tap-agent-cli unpack --input packed.json --output unpacked.json # Pack/unpack with specific key selection tap-agent-cli pack --input message.json --output packed.json --mode signed --key did:key:z6Mk... tap-agent-cli unpack --input packed.json --output unpacked.json --key did:key:z6Mk...
-
tap-http: Run a TAP HTTP server for DIDComm messaging
# Start a server with default settings tap-http
-
tap-payment-simulator: Test TAP payment flows against a server
# Send a test payment flow to a server tap-payment-simulator --url http://localhost:8000/didcomm --did <server-agent-did>
See individual tool READMEs for detailed usage instructions.
- Complete TAP Implementation: Support for all TAP message types (Transfer, Authorize, Reject, Settle, etc.)
- DIDComm v2 Integration: Secure, encrypted messaging with authenticated signatures
- Chain Agnostic Identifiers: Implementation of CAIP-2 (ChainID), CAIP-10 (AccountID), and CAIP-19 (AssetID)
- Multiple DID Methods: Support for did:key, did:web, did:pkh, and more
- Command-line Tools: Utilities for DID generation, resolution, and key management
- Modular Agent Architecture: Flexible identity and cryptography primitives
- High-Performance Message Routing: Efficient node implementation for high-throughput environments
- HTTP and WebSocket Transport: Multiple communication options with robust error handling
- WASM Compatibility: Run in browsers and Node.js via WebAssembly
- TypeScript API: Developer-friendly TypeScript wrapper for web integrations
- Comprehensive Validation: All messages validated against TAP specifications
- Generic Typed Messages: Compile-time type safety with
PlainMessage<Transfer>
while maintaining backward compatibility - Derive Macro: Automatic implementation of
TapMessage
andMessageContext
traits with#[derive(TapMessage)]
- Persistent Storage: SQLite-based storage with automatic migrations providing:
- Transaction tracking for Transfer and Payment messages
- Complete audit trail of all messages for compliance and debugging
use tap_msg::message::types::{Transfer, Participant};
use tap_msg::message::tap_message_trait::TapMessageBody;
use tap_caip::AssetId;
use std::collections::HashMap;
use std::str::FromStr;
// Create a Transfer message body
let asset = AssetId::from_str("eip155:1/erc20:0xdac17f958d2ee523a2206206994597c13d831ec7")?;
let originator = Participant {
id: "did:example:sender".to_string(),
role: Some("originator".to_string()),
};
let beneficiary = Participant {
id: "did:example:receiver".to_string(),
role: Some("beneficiary".to_string()),
};
let transfer = Transfer {
asset,
originator,
beneficiary: Some(beneficiary),
amount: "100.0".to_string(),
agents: vec![],
settlement_id: None,
memo: Some("Test transfer".to_string()),
metadata: HashMap::new(),
};
// Create a DIDComm message from the transfer
let message = transfer.to_didcomm_with_route(
Some("did:example:sender"),
["did:example:receiver"].iter().copied()
)?;
See the tap-msg README for more detailed examples.
TAP-RS now supports generic typed messages for compile-time type safety while maintaining 100% backward compatibility:
use tap_msg::{PlainMessage, Transfer, Participant};
use tap_agent::{Agent, TapAgent};
// Create a strongly-typed message
let transfer = Transfer {
asset: "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48".parse()?,
originator: Participant::new("did:example:alice"),
beneficiary: Some(Participant::new("did:example:bob")),
amount: "100".to_string(),
// ... other fields
};
// Type-safe message construction
let typed_msg = PlainMessage::new_typed(transfer, "did:example:alice")
.with_recipient("did:example:bob")
.with_thread_id(Some("payment-123".to_string()));
// Send with compile-time type checking
let (packed, results) = agent.send_typed(typed_msg, true).await?;
// Receive with type safety
let received: PlainMessage<Transfer> = agent.receive_typed(&packed).await?;
println!("Amount: {}", received.body.amount);
// Backward compatibility - existing code unchanged
let plain_msg: PlainMessage = serde_json::from_str(json_data)?;
// This is now PlainMessage<Value> due to default type parameter
See GENERIC_PLAINMESSAGE.md for complete documentation.
TAP-RS provides a procedural derive macro that automatically implements TapMessage
and MessageContext
traits:
use tap_msg::TapMessage;
use tap_msg::message::{Participant, TapMessageBody};
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Serialize, Deserialize, TapMessage)]
pub struct CustomMessage {
#[tap(participant)]
pub sender: Participant,
#[tap(participant)]
pub receiver: Option<Participant>,
#[tap(participant_list)]
pub validators: Vec<Participant>,
#[tap(transaction_id)]
pub transaction_id: String,
pub data: String,
}
// The macro automatically provides:
// - thread_id() -> transaction_id
// - get_all_participants() -> extracts all participant DIDs
// - participants() -> returns &Participant references
// - transaction_context() -> creates context with ID and type
See the tap-msg README for detailed documentation.
use tap_agent::agent::{Agent, DefaultAgent};
use tap_agent::config::AgentConfig;
use tap_agent::crypto::{DefaultMessagePacker, BasicSecretResolver};
use tap_agent::did::DefaultDIDResolver;
use std::sync::Arc;
// Configure the agent
let config = AgentConfig::new("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string());
// Set up components
let did_resolver = Arc::new(DefaultDIDResolver::new());
let secret_resolver = Arc::new(BasicSecretResolver::new());
let message_packer = Arc::new(DefaultMessagePacker::new(did_resolver, secret_resolver));
// Create the agent
let agent = DefaultAgent::new(config, message_packer);
See the tap-agent README for more detailed examples.
TAP-RS provides comprehensive tools for DID generation and key management:
# Install the tap-agent CLI
cargo install tap-agent
# Generate a did:key with Ed25519 key type
tap-agent-cli generate --method key --key-type ed25519 --output did-document.json --key-output private-key.json
# Generate a did:web for a specific domain
tap-agent-cli generate --method web --domain example.com --output web-did.json
# Look up and resolve a DID to its DID Document
tap-agent-cli lookup did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK
# Look up a DID and save the result to a file
tap-agent-cli lookup did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK --output did-document.json
# Key management operations
tap-agent-cli keys list
tap-agent-cli keys view did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK
tap-agent-cli keys set-default did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK
For TypeScript and WebAssembly bindings, see the tap-ts README.
- VASP-to-VASP Transfers: Exchanges and custodians can coordinate transfers with Travel Rule compliance
- Self-Custody Verification: Wallets can verify transaction details before settlement
- Multi-Party Authorization: Complex transfers requiring approval from multiple entities
- Cross-Chain Coordination: Consistent messaging across different blockchain networks
- Compliance Automation: Streamline compliance workflows with secure messaging
Comprehensive documentation for TAP-RS is available in the docs directory:
- Getting Started - Learn how to set up and start using TAP-RS
- Implementing TAP Flows - Guide to implementing various TAP message flows
- Security Best Practices - Guidelines for securing your implementation
- WASM Integration - Using TAP-RS in browser and Node.js environments
- Complete Transfer Flow - End-to-end example integrating multiple TAP-RS components
The following commands are available for working with the codebase:
# Build all crates
cargo build
# Run tests for all crates
cargo test
# Run tests for a specific crate
cargo test --package tap-msg
# Run benchmarks
cargo bench
# Format code
cargo fmt
# Lint code
cargo clippy
# Install command-line tools
cargo install --path tap-agent
cargo install --path tap-http
The tap-agent-cli
tool provides commands for packing and unpacking DIDComm messages:
# Install the tap-agent CLI
cargo install tap-agent
# Pack a plaintext message to a signed DIDComm message
tap-agent-cli pack --input message.json --output packed.json --mode signed
# Pack using authenticated encryption (requires recipient DID)
tap-agent-cli pack --input message.json --output packed.json --mode authcrypt --recipient did:key:z6Mk...
# Pack using anonymous encryption (requires recipient DID)
tap-agent-cli pack --input message.json --output packed.json --mode anoncrypt --recipient did:key:z6Mk...
# Use a specific key for packing (otherwise the default key is used)
tap-agent-cli pack --input message.json --output packed.json --mode signed --key did:key:z6Mk...
# Unpack a DIDComm message (works with signed, authcrypt, or anoncrypt messages)
tap-agent-cli unpack --input packed.json --output unpacked.json
# Unpack using a specific key (otherwise all available keys are tried)
tap-agent-cli unpack --input packed.json --output unpacked.json --key did:key:z6Mk...
The input message.json should be a plain JSON object following the DIDComm message format:
{
"id": "1234567890",
"type": "https://tap.rsvp/schema/1.0#Transfer",
"from": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
"to": ["did:key:z6MkrJVSYwmQgxBBCnZWuYpKSJ4qWRhWGsc9hhsVf43yirpL"],
"body": {
"asset": "eip155:1/erc20:0xdac17f958d2ee523a2206206994597c13d831ec7",
"originator": {
"@id": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
"role": "originator"
},
"beneficiary": {
"@id": "did:key:z6MkrJVSYwmQgxBBCnZWuYpKSJ4qWRhWGsc9hhsVf43yirpL",
"role": "beneficiary"
},
"amount": "100.0",
"agents": []
}
}
The TAP HTTP server provides a DIDComm messaging endpoint for the TAP protocol:
# Install the server
cargo install tap-http
# Run with default settings (creates ephemeral agent)
tap-http
# Run with custom settings
tap-http --host 0.0.0.0 --port 8080 --endpoint /didcomm --logs-dir /var/log/tap
# Use a stored key from tap-agent-cli
tap-http --use-stored-key
You can test the server using the payment simulator:
# Install the simulator
cargo install tap-http
# Run a test payment flow (using the DID printed when starting the server)
tap-payment-simulator --url http://localhost:8000/didcomm --did did:key:z6Mk...
This project is licensed under the MIT License - see the LICENSE-MIT file for details.