diff --git a/.changeset/little-eels-decide.md b/.changeset/little-eels-decide.md new file mode 100644 index 0000000000..cc09ac8862 --- /dev/null +++ b/.changeset/little-eels-decide.md @@ -0,0 +1,5 @@ +--- +'@penumbra-zone/keys': major +--- + +publish package containing keys diff --git a/packages/keys/README.md b/packages/keys/README.md new file mode 100644 index 0000000000..dfdc4e2a46 --- /dev/null +++ b/packages/keys/README.md @@ -0,0 +1,80 @@ +# `@penumbra-zone/keys` + +This package contains cryptographic keys relevant to the Penumbra blockchain, +and checksums to validate the integrity of those keys. + +The default export is a JSON mapping of Penumbra `Action` names to their +relevant key, necessary for building cryptographic proofs for those actions. +Keys are exported as `[key_name]_pk.bin`. + +A basic shell script `penumbra-download-keys` is provided, if you have specific +versioning, bundling, or copying needs. Most users can use the keys exported +from the package. + +## Using Keys + +If your bundler supports `import.meta.resolve`, you can handle the raw key material like this: + +```ts +const res: Result = await fetch(import.meta.resolve('@penumbra-zone/keys/convert_pk.bin')); +const keyBuf: ArrayBuffer = await res.arrayBuffer(); +const convertPk = new Uint8Array(keyBuf); +``` + +## A More Complex Example + +If your bundler doesn't support `import.meta.resolve`, or you rely on customized +bundling, you might want to just handle URLs. Our `@penumbra-zone/wasm` package +expects you to input key URL strings when building `Action`s. + +Here's a simplified example of how we use the packages together in our reference +wallet extension: + +```ts +import actionKeys from '@penumbra-zone/keys'; + +import type { FullViewingKey } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb'; +import type { + Action, + TransactionPlan, + WitnessData, +} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/transaction/v1/transaction_pb'; + +// map filenames to bundled key asset URLs +const keyUrls = actionKeys.map(keyFileName => new URL(`keys/${keyFileName}`, PRAX_ORIGIN)); + +async function buildAction( + txPlan: TransactionPlan, + witness: WitnessData, + fvk: FullViewingKey, + actionIndex: number, +): Promise { + // Dynamically load wasm module + const builder = await import('@penumbra-zone/wasm/build'); + + // Identify action type + const actionType: Action['value']['case'] = transactionPlan.actions[actionPlanIndex]!.action.case; + + // Identify key url, if present + const keyUrl: string | undefined = keyUrls[actionType]?.href; + + // Build action + return builder.buildActionParallel(txPlan, witness, fvk, actionIndex, keyUrl); +} +``` + +### Using the management script + +An executable `penumbra-download-keys` is included. It can checksum the keys +included in this package, and download other versions referenced by git tag if +you are working with a testnet or other custom chain with its own proving keys. + +In any workspace where this package is installed, you can use + +```sh +[npm|pnpm|yarn] exec penumbra-download-keys [output-path] [git tag] [sha256 manifest] +``` + +Which will acquire the default keys and display checksum validation, if a key +manifest is already present. You can further specify a version and custom +manifest file. diff --git a/packages/keys/download-keys b/packages/keys/download-keys index 82c5eda2e5..e346b39ec2 100755 --- a/packages/keys/download-keys +++ b/packages/keys/download-keys @@ -1,52 +1,90 @@ #!/bin/sh set -e -defaultKeysVersion="v0.71.0" +defaultKeysVersion="v0.70.0" + +scriptDir=$(dirname "$(readlink -f -- "$0")") +cacheDir="$scriptDir/keys" +scriptName=$(basename "$0") +shaCmd=$(which sha256sum || which shasum || true) + +[ -z $shaCmd ] && ( + echo "No checksum tool found. $scriptName requires sha256sum or shasum in \$PATH." + exit 69 # EX_UNAVAILABLE +) + +# usage usage() { - echo "${1}" - echo "Usage: $(basename ${0}) [ ]" + echo "$1" + echo "Usage: $scriptName [output-path] [git tag] [sha256 manifest]" exit 64 # EX_USAGE } -if [ -z "${PENUMBRA_KEYS_DIR}" ]; then - [ -z "${1}" ] && usage "No output path specified." - PENUMBRA_KEYS_DIR=$1 -fi +# nearest_checksums +nearest_checksums() { + ( + ( + # generate list + echo "$1.zzzEND" # version.zzzEND will sort after version.shasum + ls -1 "$scriptDir/shasums" # list known checksum manifests + ) | + # select nearest + sort --version-sort | # sort by version + grep --before-context=1 --max-count=1 "zzzEND" | # locate sentinel and previous item + grep -v "zzzEND" # remove sentinel, leaving previous/nothing + ) || ( + # if all of that fails, use latest + ls -1 "$scriptDir/shasums" | sort --version-sort | tail -n 1 + ) +} -if [ -z "${PENUMBRA_KEYS_VERSION}"]; then - if [ -z "${2}" ]; then - PENUMBRA_KEYS_VERSION=$defaultKeysVersion - else - [ -z "${3}" ] && usage "No sha256 provided." - PENUMBRA_KEYS_VERSION=$2 - PENUMBRA_KEYS_SHA256=$(readlink -f "${3}") - fi -fi +# env var or arg or default +outDir="$PENUMBRA_KEYS_OUT" # env var output path +[ -z "$outDir" ] && outDir="$1" # arg output path +[ -z "$outDir" ] && outDir="$scriptDir/keys" # default +[ -d "$outDir" ] || mkdir -p "$outDir" # create output dir +# resolve absolute +outDir=$(readlink -f "$outDir" || usage "No output directory available") + +# env var or arg or default +keysVersion=$PENUMBRA_KEYS_VERSION +[ -z $keysVersion ] && keysVersion=$2 +[ -z $keysVersion ] && keysVersion=$defaultKeysVersion -if [ -z "${SKIP_DOWNLOAD_KEYS}" ]; then - keysUrl="https://github.com/penumbra-zone/penumbra/raw/${PENUMBRA_KEYS_VERSION}/crates/crypto/proof-params/src/gen/" +# env var or arg or default to nearest version +shaFile="$PENUMBRA_KEYS_SHA256" +[ -z "$shaFile"] && shaFile="$3" +[ -z "$shaFile" ] && shaFile="$scriptDir/shasums/"$(nearest_checksums $keysVersion) +# resolve absolute +shaFile=$(readlink -f "$shaFile" || usage "No checksum manifest available") + +# check_keys +check_keys() { + pwd=$(pwd) # save current dir + ( + # cd, check, cd back + cd "$1" && $shaCmd -c "$shaFile" 2>/dev/null && cd "$pwd" + ) || ( + # if that fails, cd back and fail + cd "$pwd" && false + ) + # return failure + return $? +} + +## main ## + +# if good keys exist, we're already done +check_keys "$outDir" && exit 0 + +if [ -z $PENUMBRA_KEYS_SKIP ]; then + keysUrl="https://github.com/penumbra-zone/penumbra/raw/$keysVersion/crates/crypto/proof-params/src/gen/" keysGlob="{convert,delegator_vote,nullifier_derivation,output,spend,swap,swapclaim}_pk.bin" - curl --output-dir "${PENUMBRA_KEYS_DIR}/${PENUMBRA_KEYS_VERSION}" --continue-at - \ + curl --output-dir "$outDir/$keysVersion" --continue-at - \ --parallel --create-dirs --location --remote-name \ - "${keysUrl}${keysGlob}" + "$keysUrl$keysGlob" fi -ln -vf "${PENUMBRA_KEYS_DIR}/${PENUMBRA_KEYS_VERSION}/"*_pk.bin "${PENUMBRA_KEYS_DIR}" - -shacmd=$(which sha256sum || which shasum) -[ -z $shacmd ] && usage "No sha256sum or shasum tool available." - -cd "${PENUMBRA_KEYS_DIR}" -if [ -f "${PENUMBRA_KEYS_SHA256}" ]; then - ${shacmd} -c "${PENUMBRA_KEYS_SHA256}" -elif [ "${PENUMBRA_KEYS_VERSION}" = "${defaultKeysVersion}" ]; then - ${shacmd} -c <