Zipline Documentation

repository·trunk·Indexed 25 days ago

https://github.com/cashapp/zipline

A library for integrating Kotlin/JS libraries into Kotlin/JVM and Kotlin/Native applications using the QuickJS engine. Zipline enables continuous deployment of business logic, plugins, and content without app store updates through interface bridging, secure code signing with EdDSA/ECDSA, and a dedicated Gradle plugin for compiling JavaScript into .zipline binary files.

Tokens
7.2K
Snippets
17
Records
45
Agent score
81%

What's inside Zipline

  1. Access Wycheproof test data

    trunk

    This directory provides a subset of Google's Apache-licensed Wycheproof test data for cryptographic testing. It includes test vectors for ECDSA (secp256r1 with SHA-256) and EdDSA. These vectors can be used to verify the correctness and security of cryptographic implementations.

    https://github.com/google/wycheproof/blob/master/testvectors/ecdsa_secp256r1_sha256_test.json
    https://github.com/google/wycheproof/blob/master/testvectors/eddsa_test.json
  2. Important limitations of Zipline Bytecode

    trunk

    When using the zipline-bytecode module, be aware of the following constraints:

    1. Undocumented Format: The QuickJS serialized object format is undocumented and may change.
    2. Build Flag Dependency: The structure of serialized data depends on specific QuickJS build flags, such as CONFIG_ATOMIC and CONFIG_BIGNUM.
    3. Version Coupling: This module only supports the exact version of QuickJS that is built alongside this project.
  3. How interface bridging works in Zipline

    trunk

    Zipline allows you to share interfaces between a host platform (Kotlin/JVM or Kotlin/Native) and a guest runtime (Kotlin/JS).

    Key Rules for Bridging:

    • Interface Requirement: All bridged interfaces must extend ZiplineService. This provides a close() method used to release resources and allow garbage collection.
    • Data Passing: By default, arguments and return values are passed by value using kotlinx.serialization for encoding/decoding.
    • Reference Passing: Interfaces extending ZiplineService are passed by reference, meaning the receiver can call methods on a live instance of the implementation.
    • Asynchrony: Interface functions can be suspend functions. Zipline uses an internal setTimeout() implementation to support Kotlin/JS coroutines.
    • Reactive Streams: Zipline supports Flow<T> as both parameters and return types.

    Important: You must call close() on a bridged interface once it is no longer needed to prevent memory leaks.

    interface TriviaService : ZiplineService {
      fun games(): List<TriviaGame>
      fun answer(questionId: String, answer: String): AnswerResult
    }
  4. Understand the Zipline Loader data sources

    trunk

    The Zipline Loader is responsible for structured, cached loading of JavaScript modules into a Zipline QuickJS instance. It retrieves code from three distinct sources in order of priority:

    1. Embedded: Built-in resources packaged directly within the application (e.g., inside a .jar file).
    2. Cache: Files stored on the local disk.
    3. Network: Remote resources fetched via URLs.

    When an application successfully loads, its manifest and modules are pinned. Pinned files are protected from deletion during cache pruning and are only unpinned when a newer version of the application is successfully loaded.

  5. Understand the Zipline multiplatform structure

    trunk

    Zipline is organized into a multiplatform hierarchy that allows code to be shared across different environments. The structure is centered around a common module, which branches into a js module and a host module. The host module further branches into jni and native implementations, with jni supporting both jvm and android targets. This architecture enables running JavaScript logic within various host environments (like Android or JVM) via a bridge.

    ┌────────┐
                            │ common │
                            └───┬────┘
                      ┌─────────┴───────┐
                   ┌──┴───┐           ┌─┴──┐
                   │ host │           │ js │
                   └──┬───┘           └────┘
              ┌───────┴─────────┐
           ┌──┴───┐          ┌───┴────┐
           │ jni │          │ native │
           └──┬──┘          └────────┘
       ┌──────┴────────┐
    ┌──┴──┐       ┌────┴────┐
    │ jvm │       │ android │
    └─────┘       └─────────┘
  6. Install the zipline-cli

    trunk

    The zipline-cli is a command-line tool that allows non-Gradle projects (such as iOS-Swift apps) to access Zipline functionality, specifically for downloading Zipline code.

    To install it, download the latest zip archive from the official releases page. Once extracted, you can execute the tool using bin/zipline-cli (macOS/Linux) or bin/zipline-cli.bat (Windows).

  7. Set up zipline-cli for local development

    trunk
    If you are contributing to the zipline-cli repository, you can create a local distribution to test changes without manually unzipping archives. Run the installDist Gradle task to generate an exploded version of the tool in the build/install/ directory.
  8. Build JNI Libraries Locally using Zig

    trunk

    You can cross-compile Zipline's JVM native libraries to various platforms and architectures using Zig.

    Requirements:

    • Zig version: 0.14.0
    • Supported Platforms: macOS (tested) and Linux (may work). Windows is not supported.

    Steps:

    1. Install Zig 0.14.0 to your system.
    2. Navigate to the zipline directory.
    3. Run the build command targeting the JNI resources directory.
    $ cd zipline
    $ zig build -p src/jvmMain/resources/jni/
  9. Configure Zipline code signing and verification

    trunk

    Zipline uses EdDSA Ed25519 or ECDSA P-256 signatures to authenticate downloaded libraries.

    1. Generate Keys

    Use the Zipline Gradle plugin to generate an EdDSA key pair:

    $ ./gradlew :generateZiplineManifestKeyPairEd25519

    2. Sign Builds (Build Server)

    Configure your build to use the private key in your build.gradle.kts:

    zipline {
      signingKeys {
        create("key1") {
          privateKeyHex.set(...)
          algorithmId.set(app.cash.zipline.loader.SignatureAlgorithmId.Ed25519)
        }
      }
    }

    3. Verify Signatures (Host Application)

    In your host application, use the public key to create a ManifestVerifier and pass it to the ZiplineLoader:

    val manifestVerifier = ManifestVerifier.Builder()
      .addEd25519("key1", ...)
      .build()
    
    val loader = ZiplineLoader(
      manifestVerifier = manifestVerifier,
      ...
    )