Docs/API Reference/Predicates

Predicates

The three predicate methods on OctetSdk.loc. All three are async (Swift) / suspend (Kotlin) and do not throw. Runtime problems surface as INDETERMINATE verdicts, not exceptions.

isWithin

The primary predicate. Reads as: "my currently provable location is inside region."

public func isWithin(
    region: OctetRegion,
    atTime: Date = Date(),
    sessionNonce: Data? = nil
) async -> OctetVerdict
suspend fun isWithin(
    region: OctetRegion,
    atTime: Instant = Instant.now(),
    sessionNonce: ByteArray? = null,
): OctetVerdict

Example

let v = await sdk.loc.isWithin(
    region: .country(isoCode: "US"),
    atTime: Date()
)
if v.result == .yes { ship(v.proof!) }
val v = sdk.loc.isWithin(
    region = OctetRegion.country("US"),
    atTime = Instant.now()
)
if (v.result == OctetVerdict.Result.YES) ship(v.proof!!)

isOutside

Reads as: "my currently provable location is outside region."

public func isOutside(
    region: OctetRegion,
    atTime: Date = Date(),
    sessionNonce: Data? = nil
) async -> OctetVerdict
suspend fun isOutside(
    region: OctetRegion,
    atTime: Instant = Instant.now(),
    sessionNonce: ByteArray? = null,
): OctetVerdict
Caution

isOutside is not !isWithin. An INDETERMINATE is not outside. The separate call lets the caller assert the negative claim and get a proof of the negative. A verifier can check that proof just as easily as a YES. Negating an INDETERMINATE would erase the difference between "I'm not inside" and "I can't tell".


contains

Reads from the device's perspective: "my location contains this point within tol meters."

public func contains(
    center: LatLon,
    tol: Meters,
    atTime: Date = Date(),
    sessionNonce: Data? = nil
) async -> OctetVerdict
suspend fun contains(
    center: LatLon,
    tol: Meters,
    atTime: Instant = Instant.now(),
    sessionNonce: ByteArray? = null,
): OctetVerdict

Mathematically equivalent to isWithin(region: .disc(center: center, radiusMeters: tol), atTime:). Kept as a distinct verb because the device-centric reading is the natural one for proximity use cases ("am I near the delivery dropoff", "did I arrive at the geofence").

Rolling your own

isWithin is the load-bearing primitive. contains is a thin one-liner over it. The same trick rolls custom predicates:

extension OctetLoc {
    func isNear(_ center: LatLon, _ tol: Meters) async -> OctetVerdict {
        await isWithin(region: .disc(center: center, radiusMeters: tol))
    }
    func isInsideAny(_ regions: [OctetRegion]) async -> OctetVerdict? {
        for r in regions {
            let v = await isWithin(region: r)
            if v.result == .yes { return v }
        }
        return nil
    }
}
suspend fun OctetLoc.isNear(center: LatLon, tol: Meters): OctetVerdict =
    isWithin(OctetRegion.disc(center, tol))

suspend fun OctetLoc.isInsideAny(regions: List<OctetRegion>): OctetVerdict? {
    for (r in regions) {
        val v = isWithin(r)
        if (v.result == OctetVerdict.Result.YES) return v
    }
    return null
}

Time semantics

All three predicates take an optional atTime. See Time Semantics for the live / historical / future regimes and the per-resolution validity windows. Default is "now". Future times beyond ±2 s come back INDETERMINATE / FUTURE_TIME.

Session-binding

All three predicates take an optional sessionNonce, added in 1.2. Pass the one-time nonce your login backend issued for this login. The SDK commits a hash of the nonce inside the signed proof, so your verifier can confirm the proof was made for that login. Only the hash is serialized, never the raw bytes. Omitting sessionNonce produces the same proof as 1.1.

let nonce = try await loginBackend.issueNonce()   // one-time login nonce
let v = await sdk.loc.isWithin(
    region: .country(isoCode: "US"),
    sessionNonce: nonce
)
if v.result == .yes { send(v.proof!, to: loginBackend) }
val nonce: ByteArray = loginBackend.issueNonce()   // one-time login nonce
val v = sdk.loc.isWithin(
    region = OctetRegion.country("US"),
    sessionNonce = nonce,
)
if (v.result == OctetVerdict.Result.YES) send(v.proof!!, loginBackend)

The nonce is 1 to 512 bytes. An empty or larger nonce returns INDETERMINATE / INVALID_SESSION_NONCE with no proof. A session-bound call always generates a fresh proof rather than serving a cached one.

Forward the returned verdict.proof to your backend, then verify it with octet-verify 1.2.0 or later. A verifier below 1.2.0 reports the session binding as NOT-CHECKED and validates the rest of the proof. Session-binding explains the mechanism and what it defends against.

See also