Piri Signing Service
A secure HTTP signing service for Storacha's Proof of Data Possession (PDP) operations on Filecoin.
Why This Service Exists
The Piri Signing Service acts as a secure bridge between Storacha's cold wallet and the PDP verification system on Filecoin. Instead of exposing private keys directly to multiple piri nodes, this service provides a centralized, auditable signing endpoint that:
- Protects Storacha's private keys - Keys never leave the signing service
- Enables distributed operations - Multiple piri nodes can request signatures without key access
- Provides audit trails - All signing operations can be logged and monitored
- Supports future authentication - Designed to evolve from blind signing to authenticated operations
Quick Start
1. Install the Service
go install github.com/storacha/piri-signing-service@latest
Or build from source:
git clone https://github.com/storacha/piri-signing-service.git
cd piri-signing-service
go build -o signing-service
Create a signer.yaml file in your working directory:
# Network configuration
host: localhost
port: 7446 # Spells SIGN on T9 keyboard
# Ethereum RPC endpoint (required)
rpc_url: https://api.calibration.node.glif.io/rpc/v1
# Contract address (required)
service_contract_address: "0x8b7aa0a68f5717e400F1C4D37F7a28f84f76dF91"
# Private key configuration (choose one)
signing_key: "0x0000000000000000000000000000000000000000"
# OR
# signing_key_path: /secure/path/to/key.hex
# OR
# signing_keystore_path: /secure/path/to/keystore.json
# signing_keystore_password: your-password
See signer.yaml.example for full configuration options.
3. Run
./signing-service
The service will start and display:
- Signer address (the address that will sign operations)
- Chain ID (detected from RPC endpoint)
- Contract address (for verification)
- Listening endpoint
Configuration
The service supports multiple configuration methods with the following priority:
- Command-line flags (highest priority)
- Environment variables
- Configuration file (
signer.yaml)
- Default values (only for host and port)
Configuration Methods
Via Configuration File
Place a signer.yaml file in the current directory. See signer.yaml.example for a complete template.
Via Environment Variables
All configuration can be set via environment variables with the SIGNING_SERVICE_ prefix:
export SIGNING_SERVICE_RPC_URL=https://api.calibration.node.glif.io/rpc/v1
export SIGNING_SERVICE_SERVICE_CONTRACT_ADDRESS=0x8b7aa0a68f5717e400F1C4D37F7a28f84f76dF91
export SIGNING_SERVICE_SIGNING_KEY_PATH=/secure/path/to/key.hex
Via Command-line Flags
./signing-service \
--rpc-url=https://api.calibration.node.glif.io/rpc/v1 \
--contract-address=0x8b7aa0a68f5717e400F1C4D37F7a28f84f76dF91 \
--signing-key-path=/secure/path/to/key.hex
Required Configuration
The following must be provided (no defaults):
rpc_url - Ethereum RPC endpoint
service_contract_address - FilecoinWarmStorageService contract address
- Either
signing_key OR signing_key_path OR signing_keystore_path + signing_keystore_password
Service deployment
This project contains Terraform/OpenTofu deployment scripts for the service, generated using the storoku tool. The scripts are located in the deploy directory.
A GitHub Actions workflow will deploy the service when a new release is created. These deployments can also be triggered manually from a development environment.
Required environment variables that are not secrets are set in deploy/.env.production.local.tpl. More specifically, rpc_url and service_contract_address are defined there (as SIGNING_SERVICE_RPC_URL and SIGNING_SERVICE_SERVICE_CONTRACT_ADDRESS, respectively).
API Endpoints
Health Check
curl http://localhost:7446/healthcheck
Sign Create Dataset
curl -X POST http://localhost:7446/sign/create-dataset \
-H "Content-Type: application/json" \
-d '{
"clientDatasetId": "1",
"payeeAddress": "0xYourPayeeAddress",
"metadata": {
"key1": "value1",
"key2": "value2"
}
}'
Sign Add Pieces
curl -X POST http://localhost:7446/sign/add-pieces \
-H "Content-Type: application/json" \
-d '{
"clientDatasetId": "1",
"pieces": ["piece1", "piece2"]
}'
Sign Schedule Piece Removals
curl -X POST http://localhost:7446/sign/schedule-piece-removals \
-H "Content-Type: application/json" \
-d '{
"clientDatasetId": "1",
"pieces": ["piece1", "piece2"]
}'
Sign Delete Dataset
curl -X POST http://localhost:7446/sign/delete-dataset \
-H "Content-Type: application/json" \
-d '{
"clientDatasetId": "1"
}'
Security Roadmap
The service is designed to evolve through multiple security phases:
Phase 1: Blind Signing (Current)
- Signs any properly formatted request
- No authentication required
- Suitable for trusted, private networks only
- ⚠️ WARNING: Do not expose to public internet
Phase 2: UCAN Authentication (Planned)
- Registered operators authenticate via UCAN tokens
- Each operator has specific permissions
- Audit trail of who requested each signature
Phase 3: Session Keys (Future)
- Temporary session keys for time-limited operations
- Reduced exposure of primary signing key
- Automatic key rotation
Phase 4: Hardware Security Module (Future)
- Cold wallet replaced with HSM or cloud KMS
- Hardware-level key protection
- Compliance with security standards
Production Deployment
Recommended Setup
- Run on a secure, isolated network - Not exposed to public internet
- Use environment variables or secure key management for sensitive configuration
- Enable comprehensive logging for audit trails
- Monitor all signing requests for anomalies
- Implement rate limiting at the network level
- Use TLS/HTTPS for all communications
Example Systemd Service
[Unit]
Description=Piri Signing Service
After=network.target
[Service]
Type=simple
User=signing-service
WorkingDirectory=/opt/signing-service
ExecStart=/opt/signing-service/signing-service
Restart=always
RestartSec=10
# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/signing-service/logs
# Environment variables
Environment="SIGNING_SERVICE_RPC_URL=https://api.node.glif.io/rpc/v1"
EnvironmentFile=/etc/signing-service/env
[Install]
WantedBy=multi-user.target
Docker Deployment
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o signing-service
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/signing-service .
EXPOSE 7446
CMD ["./signing-service"]
docker run -d \
-p 7446:7446 \
-v /secure/path/to/config:/root/signer.yaml:ro \
-v /secure/path/to/keys:/keys:ro \
--name signing-service \
storacha/piri-signing-service
Monitoring
Key Metrics to Monitor
- Request rate per endpoint
- Response times
- Error rates
- Unique requesters (when authentication is added)
- Gas price at signature time
- Contract verification failures
Example Prometheus Metrics
The service exposes metrics at /metrics (when enabled):
signing_service_requests_total{endpoint="create_dataset"}
signing_service_request_duration_seconds{endpoint="create_dataset"}
signing_service_errors_total{endpoint="create_dataset",error="validation_failed"}
signing_service_signer_address{address="0x..."}
Troubleshooting
Common Issues
Service won't start
- Check all required configuration is provided
- Verify RPC endpoint is reachable
- Ensure private key file has correct permissions (600)
"Invalid contract address"
- Verify the contract address is a valid Ethereum address
- Ensure it matches the deployed FilecoinWarmStorageService contract
"Failed to get chain ID"
- Check RPC endpoint is correct and accessible
- Verify network connectivity
"Invalid signature" from contract
- Ensure chain ID matches the network
- Verify contract address is correct
- Check that the signer address has appropriate permissions
Development
Running Tests
go test ./...
# Linux
GOOS=linux GOARCH=amd64 go build -o signing-service-linux
# macOS
GOOS=darwin GOARCH=amd64 go build -o signing-service-darwin
# Windows
GOOS=windows GOARCH=amd64 go build -o signing-service.exe
Support
For issues, feature requests, or questions:
License
[License details here]