A Go client for Turnkey's Visualsign API that provides end-to-end verification of transactions executed in AWS Nitro enclaves, including QoS manifest decoding and validation.
- Parse blockchain transactions via Turnkey's Visualsign API - the CLI doesn't need to be updated when a new chain support is added
- Verify AWS Nitro enclave attestations
- Decode and display QoS (QuorumOS) manifests
- Validate signatures from ephemeral keys
- Export QoS manifests for offline verification
- Compare against reference
qos_clientimplementation
This project requires Go 1.25.0 or later.
# Option 1: Install to $GOBIN (recommended for users)
go install github.com/anchorageoss/visualsign-turnkeyclient@latest
# Option 2: Build locally to bin/ directory (recommended for developers)
make build
# Option 3: Build directly with go
go build -o bin/visualsign-turnkeyclient .
# Option 4: Run directly without building
go run . <command> [args...]After installation, the binary will be available as visualsign-turnkeyclient.
# Run all tests with coverage (excludes cmd package)
make test
# Run tests with strict coverage threshold check (80% minimum)
make test-strict
# Generate and view HTML coverage report
make test-coverage
# Serve coverage report interactively (http://localhost:3000)
make test-coverage-serve
# Run specific test suite
make test-manifest # Tests for manifest parsing
make test-crypto # Tests for cryptography
make test-apikey # Tests for API key handling
make test-client # Tests for client functionality
# Run benchmarks
make bench
# Code quality checks
make check-deps # Check for prohibited dependencies
make lint # Run linter with dependency checks
make fmt # Format Go code
# Clean build artifacts
make cleanThe CI workflow runs on every push and pull request:
- ✅ Dependency checks (no prohibited
anchorlabsincimports) - ✅ Tests with race detection and 80% coverage minimum
- ✅ Linting with
golangci-lint - ✅ Build verification
- 📊 Coverage reports uploaded to artifacts
Parse a transaction and extract attestations:
./bin/visualsign-turnkeyclient parse \
--host https://api.turnkey.com \
--organization-id <your-org-id> \
--key-name testkey \
--unsigned-payload <base64-encoded-payload>Perform end-to-end verification of a transaction:
./bin/visualsign-turnkeyclient verify \
--host https://api.turnkey.com \
--organization-id <your-org-id> \
--key-name testkey \
--unsigned-payload <base64-encoded-payload>--qos-manifest-hex <hex>: Expected QoS manifest hash to verify against UserData--pivot-binary-hash-hex <hex>: Hash of the binary that QoS runs after initrd is booted--save-qos-manifest <path>: Save the QoS manifest envelope to a binary file
Decode and display a QoS manifest from a file or base64 string:
# Decode from file (human-readable)
./bin/visualsign-turnkeyclient decode-manifest raw --file /tmp/manifest.bin
# Decode from file (JSON output)
./bin/visualsign-turnkeyclient decode-manifest raw --file /tmp/manifest.bin --json
# Decode from base64 string
./bin/visualsign-turnkeyclient decode-manifest raw --base64 "AQAAAAAAA..." --json
# Decode manifest envelope (with approvals)
./bin/visualsign-turnkeyclient decode-manifest envelope --file /tmp/manifest.bin --json--file <path>: Path to manifest envelope binary file--base64 <string>: Base64-encoded manifest envelope string--json: Output in JSON format (compatible with qos_client format)
Note: Either --file or --base64 must be provided, but not both.
The verify command performs comprehensive validation:
- API Call: Calls Turnkey's Visualsign API to parse the transaction
- Attestation Verification: Validates the AWS Nitro attestation document
- Public Key Extraction: Extracts the ephemeral public key from the signature
- Signature Verification: Verifies the ECDSA signature matches the message
- Manifest Decoding: Decodes the QoS manifest from borsh-encoded data
- Hash Validation: Compares manifest hash against UserData in attestation
=== STEP 1: API Response Received ===
✓ Received boot attestation document
✓ Public key: 04083c74f3776534d731...
✓ Signature: a81944cb12c2a2c0f4d4...
=== STEP 2: Verify Attestation Document ===
✓ Attestation document verified successfully
✓ Module ID: i-0f9376f6d51f64b81-enc0199e598f3733503
✓ PCRs verified: 16 PCRs found
📋 UserData (QoS Manifest Hash / Pivot Binary Hash):
Hex: 60d9c5754d6979afca7a5e75edfa43b629110301d8c57f9ff1718b74f70b5a9c
=== QoS Manifest Decoding ===
✓ Manifest decoded successfully
✓ Raw Manifest Bytes SHA256: 1748b319a6353f8191c79f2e4841ef7c...
✓ Envelope SHA256: de3900c56a32686ab5c0d752f63ecf61...
📋 Manifest Details:
Namespace:
Name: testkey/anchorageoss/visualsign-parser
Nonce: 20251001
Quorum Key: 04451028fc9d42cef6d8f2a3ebe17d65...
Pivot Config:
Binary Hash: ef9f552a75bf22c7556b9900bae09f3557eb46f9123b00f94fe71baa8656e678
Restart Policy: 1 (Always)
Manifest Set:
Threshold: 2
Members: 2
Enclave (Nitro Config):
PCR0: f67076a8f9796b90d7f0eb148ec6926f66fe04c80861151916961f7dec715b3c...
The QoS (QuorumOS) manifest defines the security policy for the Nitro enclave. QuorumOS is Turnkey's secure operating system for AWS Nitro Enclaves that enforces threshold cryptography and secure key management.
The manifest specifies:
- Namespace: Organization and enclave identifier (e.g.,
testkey/anchorageoss/visualsign-parser) - Pivot Config: Binary hash of the enclave application and restart policy
- Manifest Set: Quorum members who can update the manifest (threshold-based)
- Share Set: Members who hold key shares for cryptographic operations
- Enclave Config: Expected PCR values that attest to the enclave state
- Patch Set: Members authorized to apply security patches
The manifest hash in the attestation's UserData field proves that:
- The enclave is running QuorumOS
- The enclave is running the expected binary (via pivot hash)
- The enclave has the correct security configuration in the correct environment (via PCR3)
- Only authorized parties can modify the manifest (via quorum members)
- The enclave configuration hasn't been tampered with
- Extract
qosManifestEnvelopeB64from API response'sbootProoffield - Decode from base64 to get borsh-encoded bytes
- Deserialize using borsh format to extract manifest structure
- Compute SHA256 hash and compare against attestation UserData
// Manifest structure (simplified)
type Manifest struct {
Namespace Namespace // org/app identifier
Pivot PivotConfig // binary hash + restart policy
ManifestSet ManifestSet // quorum for manifest updates
ShareSet ShareSet // key share holders
Enclave NitroConfig // expected PCRs
PatchSet PatchSet // patch approvers
}We validate our Go implementation against Turnkey's reference Rust qos_client:
./bin/visualsign-turnkeyclient verify \
--host https://api.testkey.turnkey.com \
--organization-id <your-org-id> \
--key-name testkey \
--unsigned-payload 'AQAAAAA...' \
--save-qos-manifest /tmp/manifest.binThe easiest way to verify against the reference implementation is to use a containerized version:
# Build a container image with qos_client
docker run -v /tmp:/tmp \
ghcr.io/tkhq/qos:latest \
qos_client display \
--display-type manifest-envelope \
--file-path /tmp/manifest.binOr manually verify with qos_client (Rust Reference) if you have it installed:
cd ~/projects/tkhq/qos/src/qos_client/
cargo run --bin qos_client -- display \
--display-type manifest-envelope \
--file-path /tmp/manifest.bin# Get JSON from reference implementation
cd ~/projects/tkhq/qos/src/qos_client/
cargo run --bin qos_client -- display \
--display-type manifest-envelope \
--file-path /tmp/manifest.bin \
--json > /tmp/reference.json
# View formatted output
cat /tmp/reference.json | jq .Use the provided script to automate the comparison between our Go client and the reference implementation. By default, it uses a Docker container for the reference implementation:
chmod +x verify-manifest.sh
./verify-manifest.sh /tmp/manifest.binOr use a local qos_client installation:
./verify-manifest.sh /tmp/manifest.bin falseThe script:
- Runs
qos_client --jsonin Docker container to get reference output (or local if specified) - Runs
./visualsign-turnkey-client decode-manifest --jsonto get our output - Compares key fields (namespace, nonce, pivot hash, PCRs, etc.)
- Reports matches/mismatches with clear visual indicators
Script Output:
=== QoS Manifest Verification ===
Manifest file: /tmp/manifest.bin
Reference tool: ~/projects/tkhq/qos/src/qos_client
Running qos_client (reference implementation)...
Running Go client (our implementation)...
=== Extracting Key Fields ===
✓ Fields extracted from both implementations
=== Comparison Results ===
✅ Namespace: MATCH
✅ Nonce: MATCH
✅ Quorum Key: MATCH
✅ Pivot Hash: MATCH
✅ Restart Policy: MATCH
✅ Manifest Threshold: MATCH
✅ Manifest Members: MATCH
✅ PCR0: MATCH
✅ PCR1: MATCH
✅ PCR2: MATCH
✅ PCR3: MATCH
✅ Verification Complete: All fields match!
Reference JSON saved to: /tmp/qos_manifest_reference.json
Go client JSON saved to: /tmp/go_manifest_output.json
Name: testkey/anchorageoss/visualsign-parser
- Environment:
testkeyorprod - Organization:
anchorageoss - Application:
visualsign-parser
The namespace identifies the specific enclave instance and must match expectations.
Binary Hash: ef9f552a75bf22c7556b9900bae09f3557eb46f9123b00f94fe71baa8656e678
Restart Policy: Always
- Binary Hash: SHA256 of the enclave application binary
- Restart Policy: Controls enclave restart behavior
Never(0): Enclave stops on exitAlways(1): Enclave automatically restarts
Platform Configuration Registers (PCRs) attest to the enclave state:
| PCR | Measures | Purpose |
|---|---|---|
| PCR0 | Enclave image file | Verifies the exact enclave image |
| PCR1 | Linux kernel and bootstrap | Validates the OS environment |
| PCR2 | Application | Confirms the application code |
| PCR3 | IAM role and instance ID | Ties to AWS identity |
Critical: These PCR values MUST match the expected PCRs. You should be able to reproduce them locally.
Manifest Set:
Threshold: 2
Members: 2
- Threshold: Minimum signatures needed to update the manifest
- Members: List of authorized public keys
Each member has:
- Alias: Human-readable identifier (e.g., "1", "2")
- PubKey: P-256 public key (130 hex characters)
The client computes three types of hashes:
Raw Manifest Hash: 1748b319a6353f8191c79f2e4841ef7c948a722107ab3d99fec82bf6f306d464
Re-serialized Hash: 1748b319a6353f8191c79f2e4841ef7c948a722107ab3d99fec82bf6f306d464
Envelope Hash: de3900c56a32686ab5c0d752f63ecf61a27a82f9c1b0da3c30d95c30de141d3e
UserData (from attestation): 60d9c5754d6979afca7a5e75edfa43b629110301d8c57f9ff1718b74f70b5a9c
Hash Mismatch Reasons:
⚠️ Different manifest versions: The API may return a newer manifest than what was present at enclave boot time⚠️ Manifest update: The manifest was updated after the enclave started⚠️ Environment difference: Comparing prod manifest against testkey attestation
Note: When the Enclave reboots/is redeployed, it generates a new manifest. It's possible that deployments happen without callers knowing about it, so always get latest value and confirm end to end.
If manifest hash doesn't match UserData:
- Check Environment: Ensure you're comparing the same environment (testkey vs prod)
- Check Timing: Verify the manifest wasn't updated after enclave boot
- Use Reference: Compare with
qos_clientoutput to verify decoding is correct - Check Envelope: Try comparing envelope hash vs raw manifest hash
# Compare hashes
./bin/visualsign-turnkeyclient verify ... 2>&1 | grep "SHA256"If manifest decoding fails:
- Verify Base64: Check that the base64 encoding is valid
- Check Field Name: Use
qosManifestEnvelopeB64(notqosManifestB64) - Borsh Format: Ensure the borsh deserialization format matches the manifest structure
- Compare with Reference: Run
qos_clientto see if it can decode the same file
# Test with Docker container (easiest method)
docker run --rm -v /tmp:/tmp \
ghcr.io/tkhq/qos:latest \
qos_client display \
--display-type manifest-envelope \
--file-path /tmp/manifest.bin
# Or test with local qos_client if installed
cd ~/projects/tkhq/qos/src/qos_client/
cargo run --bin qos_client -- display \
--display-type manifest-envelope \
--file-path /tmp/manifest.binOur Go implementation should produce identical output to qos_client. If you find differences:
- Field Order: Check if fields are in the correct order
- Type Mismatches: Verify uint32 vs uint64, etc.
- Missing Fields: Ensure all struct fields are present
- Hex Encoding: Check lowercase vs uppercase hex
Report any discrepancies as they indicate a bug in the Go implementation.
The manifest uses Borsh (Binary Object Representation Serializer for Hashing):
type Manifest struct {
Namespace Namespace `borsh:"namespace"`
Pivot PivotConfig `borsh:"pivot"`
ManifestSet ManifestSet `borsh:"manifest_set"`
ShareSet ShareSet `borsh:"share_set"`
Enclave NitroConfig `borsh:"enclave"`
PatchSet PatchSet `borsh:"patch_set"`
}Key Features of Borsh:
- Deterministic serialization (same object → same bytes)
- Efficient binary format
- Strong typing with explicit field order
- Used by Turnkey QuorumOS for manifest integrity
github.com/anchorageoss/awsnitroverifier: AWS Nitro attestation verificationgithub.com/near/borsh-go: Borsh serialization/deserializationgithub.com/urfave/cli/v3: Command-line interface framework
The client verifies:
- ✅ AWS Nitro attestation document signature chain
- ✅ PCR values match expected enclave measurements
- ✅ Certificate chain roots to AWS Nitro service
- ✅ Timestamp is recent (can be disabled for historical data)
- ✅ Module ID matches expected format
The client verifies:
- ✅ ECDSA signature over the message hash
- ✅ Public key matches the one in the attestation
- ✅ Message hash matches the signed transaction payload
- ✅ Signature scheme is P-256 (ephemeral key)
- ✅ Public key is on the P-256 curve
The client verifies:
- ✅ Manifest hash matches UserData in attestation (or explains mismatch)
- ✅ Borsh deserialization succeeds without errors
- ✅ All required fields are present and valid
- ✅ PCR values in manifest match attestation PCRs
- ✅ Quorum thresholds are sensible (≥ 1, ≤ member count)
# 0. Build the binary
make build
# 1. Run verification and save manifest
./bin/visualsign-turnkeyclient verify \
--host https://api.testkey.turnkey.com \
--organization-id <your-org-id> \
--key-name testkey \
--unsigned-payload '<base64-encoded-payload>' \
--save-qos-manifest /tmp/manifest.bin
# 2. Decode manifest with our Go client
./bin/visualsign-turnkeyclient decode-manifest raw --file /tmp/manifest.bin
# 3. Get JSON output from our Go client
./bin/visualsign-turnkeyclient decode-manifest raw --file /tmp/manifest.bin --json | jq .
# 4. Verify with Docker container (easiest method)
docker run -v /tmp:/tmp \
ghcr.io/tkhq/qos:latest \
qos_client display \
--display-type manifest-envelope \
--file-path /tmp/manifest.bin \
--json | jq .
# 5. Or manually verify with reference implementation if installed locally
cd ~/projects/tkhq/qos/src/qos_client/
cargo run --bin qos_client -- display \
--display-type manifest-envelope \
--file-path /tmp/manifest.bin
# 6. Run automated verification (compares both implementations)
./verify-manifest.sh /tmp/manifest.binThe client expects API keys in the Turnkey CLI format at ~/.config/turnkey/keys/:
~/.config/turnkey/keys/<key-name>.public # Hex-encoded compressed public key
~/.config/turnkey/keys/<key-name>.private # Format: "hexkey:p256"
This project can be used both as a CLI tool and as a Go library for programmatic access to Turnkey's Visualsign API and attestation verification.
Import the packages you need:
import (
"context"
"net/http"
"github.com/anchorageoss/visualsign-turnkey-client/api"
"github.com/anchorageoss/visualsign-turnkey-client/keys"
"github.com/anchorageoss/visualsign-turnkey-client/verify"
)
// Create an API key provider
keyProvider := &keys.FileKeyProvider{KeyName: "my-key"}
// Create an API client
client, err := api.NewClient(
"https://api.turnkey.com",
&http.Client{},
"your-org-id",
keyProvider,
)
// Call Turnkey's Visualsign API
response, err := client.CreateSignablePayload(
context.Background(),
&api.CreateSignablePayloadRequest{
UnsignedPayload: "your-payload",
Chain: "CHAIN_SOLANA",
},
)
// Verify attestations
verifier := verify.NewService(verifyClient)
result, err := verifier.Verify(context.Background(), &verify.VerifyRequest{
// ... verification request details
})All packages use interfaces for dependency injection, making them easy to test and mock.
# Run all tests with coverage
make test
# Run tests and view coverage report
make test-coverage
# Run tests and serve coverage interactively
make test-cover# Build binary to bin/ directory
make build
# Or build directly with go
go build -o bin/visualsign-turnkeyclient .
# Or run directly without building
go run . <command> [args...]- API Client: Update
api/client.goandapi/types.gofor API changes - Manifest Parsing: Update
manifest/types.gofor new manifest fields - Verification Logic: Update
verify/service.gofor verification changes - CLI Commands: Add command handlers to
cmd/ - Tests: Add tests in corresponding
*_test.gofiles - Validation: Test against
qos_clientreference implementation using./verify-manifest.sh
For private method testing, add tests to the same-package *_test.go file (e.g., api/client_test.go can test generateStamp() which is private to the api package).
- visualsign-parser - The enclave application binary
- AWS Nitro Enclaves - Secure compute environment
- QuorumOS (QoS) - Turnkey's secure enclave operating system
- awsnitroverifier - AWS Nitro attestation verification library
- Borsh Specification - Binary serialization format
- AWS Nitro Attestation - Nitro Security Module API
- Turnkey Documentation - Official Turnkey docs
Licensed under the Apache License, Version 2.0. See the LICENSE file for details.