npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@siwz/core

v0.2.2

Published

Sign in with Zcash — core protocol: message format, address parsing, signature verification

Readme

@siwz/core

npm

Core protocol primitives for Sign in with Zcash. Pure TypeScript, no React, no framework deps. Runs in Node, browsers, and edge runtimes.

Docs and live demos: https://siwz.vercel.app

Install

npm i @siwz/core
# or: pnpm add @siwz/core / yarn add @siwz/core

What this package gives you

Two protocol-level sign-in flows. The third (MetaMask Snap) is handled in @siwz/react.

| Flow | Primitives | When to use | |---|---|---| | Memo challenge (universal) | issueMemoChallenge, verifyMemoChallenge, buildZip321, buildZip321Multi, parseZip321 | Recommended primary flow. Works with every Zcash wallet via ZIP 321. No signmessage support required. | | Signed message (SIWE-style) | SiwzMessage, verifyMessage, verifyTransparentSignature, verifySaplingSignature | For wallets exposing signmessage (zcash-cli, YWallet transparent receivers, etc.). |

Memo-challenge sign-in

The user sends a tiny payment (shielded with a one-time memo, or a uniquely-numbered transparent payment) to the app's service address. Your server verifies the on-chain artifact and signs them in.

import { issueMemoChallenge, verifyMemoChallenge } from "@siwz/core";

// 1. server: issue a challenge
const challenge = await issueMemoChallenge({
  secret: process.env.NEXTAUTH_SECRET!,
  serviceAddress: process.env.SIWZ_SERVICE_ADDRESS!, // your zs.../u1.../t1...
  network: "mainnet",
});
// → { uri, amountZec, memo, token, expiresAt, mode, ... }

// 2. client renders `uri` as a QR. User pays from any Zcash wallet.

// 3. server: poll your block explorer or lightwallet RPC for incoming notes
//    to challenge.serviceAddress, then verify each candidate:
const result = await verifyMemoChallenge({
  secret: process.env.NEXTAUTH_SECRET!,
  token: challenge.token,
  observedRecipient: challenge.serviceAddress,
  observedMemo: incomingMemo,                      // shielded mode
  // observedAmountZatoshi: incomingAmountZatoshi, // transparent mode
});
if (result.ok) signTheUserIn(result.identity);

The mode (transparent-amount vs shielded-memo) is inferred from the service address type. inferMemoChallengeMode(address) exposes the same inference if you need it.

Transparent explorer helpers

@siwz/core/explorers ships three explorers and a fallback wrapper:

| Class | Use | |---|---| | ThreeXplExplorer | 3xpl-backed. Defaults to sandbox-api.3xpl.com (anonymous, rate-limited, no SLA). Pass apiKey for the prod tier. | | BlockchairExplorer | Blockchair-backed. Public tier without a key, paid tier with one. | | MultiExplorer | Wraps a list of explorers; falls back to the next on any thrown error. |

import { MultiExplorer, ThreeXplExplorer, BlockchairExplorer } from "@siwz/core/explorers";

const explorer = new MultiExplorer([
  new ThreeXplExplorer({ apiKey: process.env.THREEXPL_API_KEY }),
  new BlockchairExplorer({ apiKey: process.env.BLOCKCHAIR_API_KEY }),
]);

const outputs = await explorer.getRecentOutputsToAddress(serviceAddress, 50);
for (const output of outputs) {
  const r = await verifyMemoChallenge({
    secret,
    token,
    observedAmountZatoshi: output.amountZatoshi,
    observedRecipient: output.address,
  });
  if (r.ok) return r.identity;
}

For the full Next.js route-handler version (issue + poll, default-wired explorer, the 200/202/4xx wire convention <MemoSignIn /> expects), use @siwz/next-auth/memo. The pollMemoHandler there already chains this MultiExplorer as a default, so most consumers never touch these classes directly.

These explorers index the public chain only. For shielded-memo sign-in (zs…/u1… service address), run a backend that holds the IVK (see apps/lightwallet-rpc) and write a thin adapter implementing getRecentMemosToAddress on the MemoExplorer interface.

Signed-message sign-in (SIWE-style)

import { SiwzMessage, generateNonce, verifyMessage } from "@siwz/core";

const msg = new SiwzMessage({
  domain: "myapp.com",
  address: "t1Hxw6JqWMnhDK5jRCieg5bFHM2qt7UtQvu",
  uri: "https://myapp.com/login",
  network: "mainnet",
  nonce: generateNonce(),
  issuedAt: new Date().toISOString(),
  expirationTime: new Date(Date.now() + 600_000).toISOString(),
  statement: "Sign in to MyApp.",
});

const wire = msg.toString();              // canonical SIWZ wire format
const parsed = SiwzMessage.parse(wire);   // round-trips losslessly

const result = await verifyMessage(wire, signatureBase64, {
  expectedDomain: "myapp.com",
  expectedNonce: theNonceYouIssued,
});
if (result.valid) signTheUserIn(result.address);

Wire format (vs EIP-4361)

Two intentional differences from SIWE:

  1. Network: replaces Chain ID:. Zcash has no chain id.
  2. Address: may be transparent (t1…), Sapling shielded (zs…), or Unified (u1…). The verifier dispatches the right algorithm.
example.com wants you to sign in with your Zcash account:
t1Hxw6JqWMnhDK5jRCieg5bFHM2qt7UtQvu

I accept the ToS at https://example.com/tos

URI: https://example.com/login
Version: 1
Network: mainnet
Nonce: abc12345xyz
Issued At: 2026-05-25T10:00:00Z
Expiration Time: 2026-05-25T11:00:00Z

Transparent (t-addr) verification

Identical to Bitcoin's signmessage except the magic prefix is "Zcash Signed Message:\n". That choice is what makes existing wallet signmessage UIs produce SIWZ-compatible signatures with no wallet-side change.

  • Hash: dsha256(varint(magic.len) || magic || varint(msg.len) || msg).
  • Signature: 65 bytes (recoveryByte || r (32) || s (32)).
  • recoveryByte: 27 + recovery_id + (compressed ? 4 : 0).
  • Recover the secp256k1 pubkey, serialize per the compressed flag, then HASH160(pubkey) == address.hash.

Shielded (Sapling z-addr) verification

ZIP 304 defines Sapling signed messages but requires the Sapling Spend authorization circuit, which is not yet practical to ship in pure JS at hackathon scope. verifySaplingSignature takes an optional saplingVerifier callback: pass a WASM wrapper around librustzcash and SIWZ dispatches z-addr verifies to it.

API surface

// SIWE-style message
SiwzMessage, generateNonce
verifyMessage, verifyTransparentSignature, verifySaplingSignature
type SiwzFields, type VerifyResult, type VerifyOptions

// Memo challenge + ZIP 321
issueMemoChallenge, verifyMemoChallenge, inferMemoChallengeMode
buildZip321, buildZip321Multi, parseZip321
type MemoChallenge, type MemoChallengeMode, type MemoVerifyErrorCode
type IssueMemoChallengeOpts, type VerifyMemoChallengeOpts, type VerifyMemoChallengeResult
type ZIP321Request

// Addresses and conversion
parseAddress, isZcashAddress, isShieldedAddress, encodeP2pkh
assertAddressNetwork, zecToZatoshi, zatoshiToZec
UA_RECEIVER_TYPES, type Network, type AddressType, type ParsedAddress

// Crypto primitives
ZCASH_SIGNED_MESSAGE_MAGIC, magicHash, hash160, dsha256
base58checkEncode, base58checkDecode

// Reference data
ZCASH_BLOCKS, type ZcashBlockName

// Errors
SiwzError, type SiwzErrorCode

// Type-level only at the root (runtime lives in the subpath)
type MemoExplorer, type RecentOutput, type RecentMemo
// Subpath: @siwz/core/explorers
BlockchairExplorer, ThreeXplExplorer, MultiExplorer, ExplorerError
type BlockchairExplorerOptions, ThreeXplExplorerOptions

Tests

pnpm --filter @siwz/core test

59 tests covering message build/parse, address decoding (t1/tm/t3/checksum), transparent signature verify (compressed/uncompressed, mismatched messages, addresses, expired messages, domain/nonce mismatches), ZIP 321 URI build/parse, and memo-challenge round-trips for both transparent and shielded modes.

Related packages

  • @siwz/react: <MemoSignIn />, <SignInWithZcash />, useSiwz(), MetaMask Snap helpers.
  • @siwz/next-auth: NextAuth credentials provider and stateless HMAC nonces.

License

MIT