Skip to content

SmartPoint Pairs

A SmartPoint pair is the foundational cryptographic primitive of the SmartPoints architecture (ADR-088). It binds two identities in an asymmetric relationship where each party holds directional encryption keys and agreed-upon terms that are cryptographically bound to the pair.

Core Concepts

A SmartPoint pair is not a symmetric tunnel. Each party has:

  • A send key (encrypts messages to the peer)
  • A recv key (decrypts messages from the peer)
  • A signing key (signs outbound envelopes)
  • A verify key (verifies inbound envelopes from the peer)
  • Terms (capabilities, TTL, resource limits) bound via BLAKE3 hash

The two halves are mirrored: the initiator's send key equals the responder's receive key, and vice versa.

Formation Protocol

Pair formation is a three-step protocol:

  1. Initiator calls SmartPointPair::create with their DID, the peer's DID, the peer's KEM public key, desired terms, and crypto suite. This encapsulates a shared secret against the peer's ML-KEM public key and produces a PairOffer.

  2. Responder calls SmartPointPair::accept with the offer and their KEM secret key. This decapsulates the shared secret, derives mirrored directional keys, and produces an AcceptResponse containing the responder's verification key.

  3. Initiator calls SmartPointPair::finalize with the response to install the peer's verification key. Both sides can now exchange envelopes.

rust,ignore
use spt_smartpoint::{SmartPointPair, SmartPointTerms, CryptoSuite};

// Step 1: Initiator creates a pair offer
let terms = SmartPointTerms::empty().with_ttl(3600);
let (mut init_pair, offer) = SmartPointPair::create(
    "did:said:1:sw:blake3_alice".into(),
    "did:said:1:sw:blake3_bob".into(),
    &bob_kem_pk,
    terms,
    CryptoSuite::PqV1,
    alice_signing_sk,
    alice_signing_pk,
)?;

// Step 2: Responder accepts the offer
let (mut resp_pair, response) = SmartPointPair::accept(
    &offer, &bob_kem_sk, bob_signing_sk, bob_signing_pk,
)?;

// Step 3: Initiator finalizes
init_pair.finalize(&response);

Three-Layer Vector Envelope

Every message exchanged through a SmartPoint pair is wrapped in a VectorEnvelope with three distinct layers:

LayerNameVisibilityContents
1RoutingPlaintextSender/recipient DIDs, crypto suite ID, routing hints, sequence number
2PayloadEncryptedChaCha20-Poly1305 ciphertext of the actual content
3SignedVerifiableML-DSA-65 signature over (ciphertext + terms hash + sequence + timestamp)

The routing layer is readable by intermediaries for message relay. The encrypted payload is only decryptable by the intended recipient. The signed layer provides tamper detection and terms binding.

rust,ignore
use spt_smartpoint::VectorEnvelope;

// Seal a message (initiator -> responder)
let sealed = VectorEnvelope::seal(&mut init_pair, b"hello", vec![])?;

// Open the message (responder side)
let content = VectorEnvelope::open(&mut resp_pair, &sealed)?;
assert_eq!(b"hello", &content[..]);

Replay Protection

Each pair maintains a monotonic sequence counter. VectorEnvelope::seal advances the outbound counter automatically. VectorEnvelope::open validates that each inbound sequence is strictly greater than the last seen value.

Terms Binding

The terms hash (BLAKE3 of the canonical JSON) is included in the signed layer. If either party's terms are modified after pair formation, envelope verification fails with TermsHashMismatch.

Crypto Suite Agility

The CryptoSuite enum selects the full algorithm set for a pair. The suite ID is embedded in every envelope so recipients know which algorithms to use.

SuiteKey ExchangeEncryptionSignature
pq-v1 (default)ML-KEM-768ChaCha20-Poly1305ML-DSA-65
hybrid-v1X25519 + ML-KEM-768ChaCha20-Poly1305Ed25519 + ML-DSA-65
classical-v1X25519ChaCha20-Poly1305Ed25519

The default suite (pq-v1) provides post-quantum security. The hybrid suite combines classical and post-quantum algorithms for defense-in-depth. The classical suite is available for interoperability with systems that do not support post-quantum cryptography.

rust,ignore
use spt_smartpoint::CryptoSuite;

let suite = CryptoSuite::PqV1;
assert_eq!(suite.as_str(), "pq-v1");
assert_eq!(
    suite.description(),
    "ML-KEM-768 + ChaCha20-Poly1305 + ML-DSA-65"
);

SmartPoint Terms

Terms define what each party in the pair is allowed to do. They are serialized as canonical JSON and hashed with BLAKE3 to produce a tamper-evident binding.

rust,ignore
use spt_smartpoint::SmartPointTerms;
use std::collections::BTreeMap;

let mut caps = BTreeMap::new();
caps.insert("documents".into(), vec!["read".into(), "write".into()]);

let terms = SmartPointTerms::with_capabilities(caps)
    .with_ttl(3600)
    .with_resource_limit("max_message_size", "1048576")
    .with_resource_limit("rate_limit_per_min", "100");

assert!(terms.has_capability("documents", "read"));
assert!(!terms.has_capability("admin", "read"));

Terms fields:

FieldTypeDescription
capabilitiesBTreeMap<String, Vec<String>>Resource name to permitted actions
ttl_secondsOption<u64>Time-to-live; None means no expiration
resource_limitsBTreeMap<String, String>Constraint key-value pairs
customBTreeMap<String, String>Arbitrary extension terms

Key Types

TypeCrateDescription
SmartPointPairspt-smartpointOne party's view of the pair
PairOfferspt-smartpointSerializable offer from initiator
AcceptResponsespt-smartpointResponse from responder
VectorEnvelopespt-smartpointThree-layer sealed message
RoutingLayerspt-smartpointLayer 1: plaintext routing
SignedLayerspt-smartpointLayer 3: signature + terms hash
SmartPointTermsspt-smartpointCapabilities, TTL, limits
CryptoSuitespt-smartpointAlgorithm suite selector
SymmetricKeyspt-smartpoint32-byte key with zeroize-on-drop

Released under the MIT License.