PTN Certificates
Certificate-based membership for Private TealeNets.
Overview
A Private TealeNet (PTN) is an invite-only subnet where access is controlled by a certificate authority (CA). The PTN creator generates an Ed25519 CA keypair. The CA public key becomes the PTN ID. Membership is granted by issuing signed certificates to nodes.
PTN Creation
When a user creates a new PTN:
- Generate a new Ed25519 keypair (the CA keypair)
- The CA public key (hex-encoded) becomes the PTN ID
- The creator's node receives an
admincertificate signed by the CA - The CA private key is stored on the creator's device
PTN ID = hex_encode(ca_public_key) // 64 hex characters
Certificate Structure
PTNCertificatePayload
The payload that gets signed by the CA.
{
"ptnID": "a1b2c3...ca-public-key-hex",
"nodeID": "d4e5f6...member-public-key-hex",
"role": "provider",
"issuedAt": 1713100000.0,
"expiresAt": 1744636000.0,
"issuerNodeID": "a1b2c3...issuer-node-hex"
}
| Field | Type | Description |
|---|---|---|
ptnID | string | CA public key hex (identifies the PTN) |
nodeID | string | Member's Ed25519 public key hex |
role | string | One of: admin, provider, consumer |
issuedAt | number | Unix timestamp of issuance |
expiresAt | number or null | Unix timestamp of expiry (null = no expiry) |
issuerNodeID | string | Node ID of the admin who issued this certificate |
PTNCertificate
The signed certificate that a member holds.
{
"payload": { ...PTNCertificatePayload... },
"signature": "hex-encoded-ed25519-signature"
}
Signature
The signature is computed over the canonical JSON encoding of the payload:
- Encode the
PTNCertificatePayloadas JSON with sorted keys (deterministic key ordering) - Sign the resulting bytes with the CA's Ed25519 private key
- Hex-encode the signature
canonical_json = json_encode(payload, sorted_keys=true)
signature = ed25519_sign(ca_private_key, canonical_json)
hex_signature = hex_encode(signature)
Sorted keys are critical for reproducible signatures. Different JSON encoders may produce different key orderings, so canonicalization ensures that any implementation can verify signatures produced by any other.
Verification
To verify a certificate:
- Obtain the PTN's CA public key (from the PTN ID or from the join response)
- Compute the canonical JSON of the certificate payload (sorted keys)
- Verify the Ed25519 signature against the CA public key
- Check that
expiresAtis null or in the future
canonical_json = json_encode(certificate.payload, sorted_keys=true)
valid = ed25519_verify(ca_public_key, canonical_json, certificate.signature)
expired = certificate.payload.expiresAt != null && now > certificate.payload.expiresAt
Roles
| Role | Capabilities |
|---|---|
admin | Can issue certificates, invite new members, revoke memberships, manage PTN settings |
provider | Can serve inference requests to PTN members |
consumer | Can request inference from PTN providers |
Admins have all the capabilities of providers and consumers in addition to management privileges.
Invite Tokens
Invitations are shared as base64url-encoded JSON tokens.
Token Structure
{
"ptnID": "a1b2c3...",
"ptnName": "My Team",
"inviterNodeID": "d4e5f6...",
"nonce": "hex-encoded-16-random-bytes",
"expiresAt": 1713103600.0
}
| Field | Type | Description |
|---|---|---|
ptnID | string | PTN to join |
ptnName | string | Human-readable PTN name |
inviterNodeID | string | Node ID of the admin who created the invite |
nonce | string | 16 random bytes hex-encoded (prevents replay) |
expiresAt | number | Unix timestamp of token expiry |
Default validity: 1 hour.
Encoding
Tokens are encoded as base64url (RFC 4648 Section 5) of the JSON, producing a string of approximately 100-150 characters that can be shared via any text channel.
token_string = base64url_encode(json_encode(invite_token))
Join Flow
- Admin creates an invite token and shares the encoded string
- Joiner decodes the token and verifies it has not expired
- Joiner sends a
ptnJoinRequestto the inviter's node via relay:{"inviteToken": { ...PTNInviteToken... },"joinerNodeID": "...","joinerDisplayName": "My Laptop"} - Inviter validates the token, issues a certificate, and sends a
ptnJoinResponse:{"certificate": { ...PTNCertificate... },"ptnName": "My Team","caPublicKeyHex": "a1b2c3...","accepted": true} - Joiner stores the certificate and CA public key locally
- Joiner advertises the PTN ID in
capabilities.ptnIDsduring subsequent registrations
CA Key Transfer
For multi-admin setups, the CA private key can be exported from one admin node and imported on another. This allows multiple devices to issue certificates. The transfer should be done over an encrypted channel (e.g., a Noise-encrypted peer connection).
Security Considerations
- CA key is the root of trust. Compromise of the CA private key means an attacker can issue certificates for any node. Protect it accordingly.
- Nonce in invites prevents replay. Each invite token has a unique 16-byte nonce. Even if intercepted, a token can only be used once and expires quickly.
- Certificate expiry. Set
expiresAtfor non-admin certificates to limit the blast radius of compromised node keys. - Canonical JSON is required. Signature verification will fail if the JSON encoder does not produce sorted keys.