SignatureKit
Get Started

First Use

Load an A1 certificate, inspect the identity, and sign your first bytes — then an XML and a PDF — in a single Effect program.

What you'll do: assemble an Effect program that loads the A1 certificate, reads the identity, signs and verifies bytes — then signs an XML and a PDF reusing the same boundary.

Add the core boundary and the A1 signer. Pull in effect for the Effect runtime and Redacted.

bun add @signature-kit/core @signature-kit/a1 effect

a1SignaturesLayer takes the .pfx bytes and the Redacted password and provides the Signatures service. Inside one Effect.gen, signatures.inspect() returns a normalized SignerIdentitydocument carries the e-CPF or e-CNPJ when present — then signatures.sign and signatures.verify run over the same boundary. verify returns a value: result.valid is a boolean, and a real failure surfaces as a SignatureKitError in the error channel, never a thrown exception.

The .pfx bytes, not the path

pfx is a Uint8Array holding the binary contents of the .pfx/.p12 — not the file path string. Keep these bytes and the password out of logs and version control.

quickstart.ts
import { a1SignaturesLayer } from "@signature-kit/a1/signer"
import { signatures } from "@signature-kit/core/signatures"
import { Effect, Redacted } from "effect"

const layer = a1SignaturesLayer({
  pfx,                                          // Uint8Array — bytes of the .pfx/.p12
  password: Redacted.make(process.env.A1_PASSWORD ?? ""),
})

export const program = Effect.gen(function* () {
  // normalized identity — identity.document carries e-CPF / e-CNPJ
  const identity = yield* signatures.inspect()

  const content = new TextEncoder().encode("content to sign")
  const artifact = yield* signatures.sign({
    content,
    algorithm: "rsa-sha256",
  })

  const result = yield* signatures.verify({
    content,
    signature: artifact.signature,
    algorithm: artifact.algorithm,
  })

  return { identity, artifact, result }
}).pipe(Effect.provide(layer))

Run the Effect with Effect.runPromise, or call it from another Effect program.

run.ts
import { Effect } from "effect"

const { result } = await Effect.runPromise(program)
console.log(result.valid) // boolean — true if the signature checks out

Both formats consume the same boundary via Effect.provide; the layer line is identical to the previous step. XML also needs xmlRuntimeLayer; verifyXml / verifyPdf read the public key from the document and do not require the boundary.

documents.ts
import { signXml } from "@signature-kit/xml/sign"
import { xmlRuntimeLayer } from "@signature-kit/xml/runtime"
import { signPdf } from "@signature-kit/pdf/sign"
import { a1SignaturesLayer } from "@signature-kit/a1/signer"
import { Effect, Redacted } from "effect"

const layer = a1SignaturesLayer({
  pfx,                                          // Uint8Array — bytes of the .pfx/.p12
  password: Redacted.make(process.env.A1_PASSWORD ?? ""),
})

const signedXml = signXml({ xml, referenceId: "nfe-1" })
  .pipe(Effect.provide(layer), Effect.provide(xmlRuntimeLayer))

const signedPdf = signPdf({ pdf, policy: "pades-icp-brasil" })
  .pipe(Effect.provide(layer))

Errors you might see

Incorrect certificate password

If the .pfx password is wrong, the boundary fails with signature-kit.WRONG_PASSWORD in the error channel — not an exception. Check the secret before proceeding.

Further reading

On this page