diff --git a/.cli.env b/.cli.env index a8c1365..5595c4e 100644 --- a/.cli.env +++ b/.cli.env @@ -1,7 +1,12 @@ -# compiler args -# can add --inspect and -c for example +# compiler args, can add --inspect and -c for example CIRCOMKIT_COMPILER_ARGS="-l ./node_modules --r1cs --wasm --sym" +# proof system to be used, can be: groth16 | plonk | fflonk +CIRCOMKIT_PROOF_SYSTEM="plonk" + +# solidity contract export path +CIRCOMKIT_SOLIDITY_PATH="./contracts" + # colors for swag terminal outputs CIRCOMKIT_COLOR_TITLE='\033[0;34m' # blue CIRCOMKIT_COLOR_LOG='\033[2;37m' # gray diff --git a/README.md b/README.md index 5fa9c1a..078897e 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,10 @@ - [x] **Witness Testing**: You can test computations & assertions for every template in a circuit, with minimal code-repetition. - [x] **Proof Testing**: With prover & verification keys and the WASM circuit, you can test proof generation & verification. - [x] **Simple CLI**: A very easy to use CLI is provided as a wrapper around SnarkJS commands, and they are all provided as `package.json` scripts! -- [ ] **Multiple Proof-Systems**: Soon, only Groth16 for now! -- [ ] **Type Generation**: Generate input & output types for a given circuit, perhaps in future. -- [ ] **Multiple Back-ends**: We only use WASM right now, but we would like to compile prover libraries for other backends. +- [x] **Multiple Proof-Systems**: Just change the configured proof-system at [`.cli.env`](./.cli.env) and you are good to go. +- [x] **Solidity Exports**: Export a verifier contract in Solidity, or export a calldata for your proofs & public signals. +- [ ] **Multiple Backends**: We only use WASM & SnarkJS right now, but we would like to export prover libraries for other backends such as mobile. +- [ ] **Type Generation**: Generate input & output signal type declarations for a given circuit, [work in progress](./scripts/functions/type.sh). ## Usage @@ -65,25 +66,19 @@ Use the [CLI](./scripts/cli.sh), or its wrapper scripts in [package.json](./pack # Compile the circuit (generates the main component too) yarn compile circuit-name [-d directory-name (default: main)] -# Phase-2 Circuit-specific setup -yarn ptau circuit-name -p phase1-ptau-path [-n num-contribs (default: 1)] +# Circuit setup +yarn setup circuit-name -p phase1-ptau-path [-n num-contribs (default: 1)] -# Shorthand for `compile` and then `ptau` +# Shorthand for `compile` and then `setup` yarn keygen circuit-name -p phase1-ptau-path [-n num-contribs (default: 1)] +# Create a Solidity verifier contract +yarn contract circuit-name + # Clean circuit artifacts yarn clean circuit-name -# Run the test for a circuit -yarn test circuit-name - -# Run all tests -yarn test:all -``` - -If you just want to generate the `main` component but not compile it, you can also call: - -```bash +# Generate the `main` component without compiling it afterwards yarn instantiate circuit-name [-d directory-name (default: main)] ``` @@ -100,8 +95,25 @@ yarn prove circuit-name -i input-name # Verify a proof for some input (public signals only) yarn verify circuit-name -i input-name + +# Export calldata to call your Solidity verifier contract +yarn contract circuit-name -i input-name ``` +## Testing + +To run tests do the following: + +```bash +# test a specific circuit +yarn test "circuit name" + +# test all circuits +yarn test:all +``` + +You can test both witness calculations and proof generation & verification. We describe both in their respective sections, going over an example of "Multiplication" circuit. + ### Example Circuits We have several example circuits to help guide you: @@ -111,19 +123,6 @@ We have several example circuits to help guide you: - **Sudoku**: A circuit to prove that you know the solution to a Sudoku puzzle. - **Floating-Point Addition**: A circuit to compute the sum of two floating-point numbers, adapted from [Berkeley ZKP MOOC 2023 - Lab 1](https://github.com/rdi-berkeley/zkp-mooc-lab). -## Testing - -To run tests do the following: - -```bash -# test all circuits -yarn test:all -# test a specific circuit -yarn test "circuit name" -``` - -You can test both witness calculations and proof generation & verification. We describe both in their respective sections, going over an example of "Multiplication" circuit. - ### Witness Calculation Witness calculation tests check whether your circuit computes the correct result based on your inputs, and makes sure that assertions are correct. We provide very useful utility functions to help write these tests. diff --git a/circuits/main/multiplier_3.circom b/circuits/main/multiplier_3.circom deleted file mode 100644 index a8559aa..0000000 --- a/circuits/main/multiplier_3.circom +++ /dev/null @@ -1,6 +0,0 @@ -// auto-generated by instantiate.js -pragma circom 2.0.0; - -include "../multiplier.circom"; - -component main = Multiplier(3); diff --git a/package.json b/package.json index 843fdcc..4526c6a 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "instantiate": "./scripts/cli.sh -f instantiate -c", "compile": "./scripts/cli.sh -f compile -c", "clean": "./scripts/cli.sh -f clean -c", + "contract": "./scripts/cli.sh -f contract -c", + "calldata": "./scripts/cli.sh -f calldata -c", "ptau": "./scripts/cli.sh -f ptau -c", "keygen": "./scripts/cli.sh -f keygen -c", "witness": "./scripts/cli.sh -f witness -c", @@ -35,7 +37,7 @@ "ejs": "^3.1.9", "gts": "^3.1.1", "mocha": "^10.2.0", - "snarkjs": "^0.5.0", + "snarkjs": "^0.6.0", "ts-node": "^10.9.1", "typescript": "^4.9.5" } diff --git a/scripts/cli.sh b/scripts/cli.sh index 8febd8f..aae294f 100755 --- a/scripts/cli.sh +++ b/scripts/cli.sh @@ -8,8 +8,10 @@ source ./.cli.env # import functions source ./scripts/functions/type.sh -source ./scripts/functions/ptau.sh +source ./scripts/functions/setup.sh source ./scripts/functions/clean.sh +source ./scripts/functions/contract.sh +source ./scripts/functions/calldata.sh source ./scripts/functions/prove.sh source ./scripts/functions/verify.sh source ./scripts/functions/witness.sh @@ -66,6 +68,12 @@ case $FUNC in clean) clean $CIRCUIT ;; + contract) + contract $CIRCUIT + ;; + calldata) + calldata $CIRCUIT $INPUT + ;; compile) instantiate $CIRCUIT $COMPILE_DIR && compile $CIRCUIT $COMPILE_DIR ;; @@ -75,11 +83,11 @@ case $FUNC in type) type $CIRCUIT ;; - ptau) - ptau $CIRCUIT $NUM_CONTRIBS $P1_PTAU + setup) + setup $CIRCUIT $P1_PTAU $NUM_CONTRIBS ;; keygen) - compile $CIRCUIT $COMPILE_DIR && ptau $CIRCUIT $NUM_CONTRIBS $P1_PTAU + compile $CIRCUIT $COMPILE_DIR && setup $CIRCUIT $P1_PTAU $NUM_CONTRIBS ;; prove) witness $CIRCUIT $INPUT && prove $CIRCUIT $INPUT @@ -94,14 +102,16 @@ case $FUNC in echo "Usage:" echo " -f " echo " clean Cleans the build artifacts" + echo " contract Export Solidity verifier" + echo " calldata Export Solidity calldata for verification" echo " compile Compile the circuit" echo " instantiate Instantiate the main component" echo " type Generate types for TypeScript" - echo " ptau Phase-2 setup for the circuit" + echo " setup Phase-2 setup for the circuit" echo " witness Generate witness from an input" echo " prove Prove an input" echo " verify Verify a proof & public signals" - echo " keygen Shorthand for compile & ptau" + echo " keygen Shorthand for compile & setup" echo " -c " echo " -d (default: $COMPILE_DIR)" echo " -n (default: $NUM_CONTRIBS)" diff --git a/scripts/functions/calldata.sh b/scripts/functions/calldata.sh new file mode 100644 index 0000000..530cc4f --- /dev/null +++ b/scripts/functions/calldata.sh @@ -0,0 +1,13 @@ +## Exports a solidity contract for the verifier +calldata() { + echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Exporting calldata ===${CIRCOMKIT_COLOR_RESET}" + local CIRCUIT=$1 + local INPUT=$2 + local CIRCUIT_DIR=./build/$CIRCUIT + + snarkjs zkey export soliditycalldata \ + $CIRCUIT_DIR/$INPUT/public.json \ + $CIRCUIT_DIR/$INPUT/proof.json + + echo -e "${CIRCOMKIT_COLOR_LOG}Done!${CIRCOMKIT_COLOR_RESET}" +} diff --git a/scripts/functions/contract.sh b/scripts/functions/contract.sh new file mode 100755 index 0000000..8d94161 --- /dev/null +++ b/scripts/functions/contract.sh @@ -0,0 +1,12 @@ +## Exports a solidity contract for the verifier +contract() { + echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Generating Solidity verifier ===${CIRCOMKIT_COLOR_RESET}" + local CIRCUIT=$1 + local CIRCUIT_DIR=./build/$CIRCUIT + + snarkjs zkey export solidityverifier \ + $CIRCUIT_DIR/prover_key.zkey \ + $CIRCUIT_DIR/verifier.sol + + echo -e "${CIRCOMKIT_COLOR_LOG}Contract created at $CIRCUIT_DIR/verifier.sol!${CIRCOMKIT_COLOR_RESET}" +} diff --git a/scripts/functions/prove.sh b/scripts/functions/prove.sh index ff35871..386651b 100755 --- a/scripts/functions/prove.sh +++ b/scripts/functions/prove.sh @@ -6,7 +6,7 @@ prove() { local CIRCUIT_DIR=./build/$CIRCUIT local OUTPUT_DIR=$CIRCUIT_DIR/$INPUT - snarkjs groth16 prove \ + snarkjs $CIRCOMKIT_PROOF_SYSTEM prove \ $CIRCUIT_DIR/prover_key.zkey \ $OUTPUT_DIR/witness.wtns \ $OUTPUT_DIR/proof.json \ diff --git a/scripts/functions/ptau.sh b/scripts/functions/ptau.sh deleted file mode 100755 index 8300347..0000000 --- a/scripts/functions/ptau.sh +++ /dev/null @@ -1,53 +0,0 @@ -## Commence a circuit-specific phase-2 powers-of-tau ceremony -ptau() { - echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Phase-2 Powers of Tau ===${CIRCOMKIT_COLOR_RESET}" - local CIRCUIT=$1 # circuit name - local NUM_CONTRIBS=$2 # number of contributions - local P1_PTAU=$3 # path to phase-1 ptau - local CIRCUIT_DIR=./build/$CIRCUIT # circuit directory - local P2_PTAU=$CIRCUIT_DIR/phase2_final.ptau # phase-2 ptau - local CUR=000 # zkey id, initially 0 - local PROVER_KEY=$CIRCUIT_DIR/prover_key.zkey - local VERIFICATION_KEY=$CIRCUIT_DIR/verification_key.json - - # check if P1_PTAU exists - if [ ! -f "$P1_PTAU" ]; then - echo -e "${CIRCOMKIT_COLOR_ERR}${P1_PTAU} does not exist.${CIRCOMKIT_COLOR_RESET}" - exit 1 - fi - - # start phase-2 ceremony (circuit specific) - echo -e "${CIRCOMKIT_COLOR_LOG}this may take a while...${CIRCOMKIT_COLOR_RESET}" - snarkjs powersoftau prepare phase2 $P1_PTAU $P2_PTAU -v - - # generate a zkey that contains proving and verification keys, along with phase-2 contributions - snarkjs groth16 setup \ - $CIRCUIT_DIR/$CIRCUIT.r1cs \ - $P2_PTAU \ - $CIRCUIT_DIR/${CIRCUIT}_${CUR}.zkey - - # get rid of phase-2 ptau - rm $P2_PTAU - - # make contributions (001, 002, ...) - for NEXT in $(seq -f "%03g" 1 ${NUM_CONTRIBS}) - do - echo "Making Phase-2 Contribution: ${NEXT}" - snarkjs zkey contribute \ - $CIRCUIT_DIR/${CIRCUIT}_${CUR}.zkey \ - $CIRCUIT_DIR/${CIRCUIT}_${NEXT}.zkey -v - - rm $CIRCUIT_DIR/${CIRCUIT}_${CUR}.zkey - - CUR=$NEXT - done - echo "Phase 2 Complete." - - # rename key - mv $CIRCUIT_DIR/${CIRCUIT}_${CUR}.zkey $CIRCUIT_DIR/prover_key.zkey - - # export - snarkjs zkey export verificationkey $PROVER_KEY $VERIFICATION_KEY - - echo -e "${CIRCOMKIT_COLOR_LOG}Generated keys\n\tProver key: $PROVER_KEY\n\tVerification key: $VERIFICATION_KEY${CIRCOMKIT_COLOR_RESET}" -} diff --git a/scripts/functions/setup.sh b/scripts/functions/setup.sh new file mode 100755 index 0000000..ceb2a1d --- /dev/null +++ b/scripts/functions/setup.sh @@ -0,0 +1,62 @@ +## Commence a circuit-specific phase-2 powers-of-tau ceremony +setup() { + echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Circuit Setup ($CIRCOMKIT_PROOF_SYSTEM) ===${CIRCOMKIT_COLOR_RESET}" + local CIRCUIT=$1 # circuit name + local P1_PTAU=$2 # path to phase-1 ptau + local NUM_CONTRIBS=$3 # number of contributions (for groth16) + local CIRCUIT_DIR=./build/$CIRCUIT # circuit directory + local PROVER_KEY=$CIRCUIT_DIR/prover_key.zkey + local VERIFICATION_KEY=$CIRCUIT_DIR/verification_key.json + + # check if P1_PTAU exists + if [ ! -f "$P1_PTAU" ]; then + echo -e "${CIRCOMKIT_COLOR_ERR}${P1_PTAU} does not exist.${CIRCOMKIT_COLOR_RESET}" + exit 1 + fi + + if [[ "$CIRCOMKIT_PROOF_SYSTEM" == "groth16" ]]; then + local P2_PTAU=$CIRCUIT_DIR/phase2_final.ptau # phase-2 ptau + local CUR=000 # zkey id, initially 0 + + # if Groth16, circuit specific ceremony is needed + # start phase-2 ceremony (circuit specific) + echo -e "${CIRCOMKIT_COLOR_LOG}this may take a while...${CIRCOMKIT_COLOR_RESET}" + snarkjs powersoftau prepare phase2 $P1_PTAU $P2_PTAU -v + + # generate a zkey that contains proving and verification keys, along with phase-2 contributions + snarkjs groth16 setup \ + $CIRCUIT_DIR/$CIRCUIT.r1cs \ + $P2_PTAU \ + $CIRCUIT_DIR/${CIRCUIT}_${CUR}.zkey + + # get rid of phase-2 ptau + rm $P2_PTAU + + # make contributions (001, 002, ...) + for NEXT in $(seq -f "%03g" 1 ${NUM_CONTRIBS}) + do + echo "Making Phase-2 Contribution: ${NEXT}" + snarkjs zkey contribute \ + $CIRCUIT_DIR/${CIRCUIT}_${CUR}.zkey \ + $CIRCUIT_DIR/${CIRCUIT}_${NEXT}.zkey -v + + rm $CIRCUIT_DIR/${CIRCUIT}_${CUR}.zkey + + CUR=$NEXT + done + echo "Phase 2 Complete." + + # rename key to the prover key + mv $CIRCUIT_DIR/${CIRCUIT}_${CUR}.zkey $PROVER_KEY + else + # otherwise, we can use that phase-1 ptau alone + snarkjs $CIRCOMKIT_PROOF_SYSTEM setup $CIRCUIT_DIR/$CIRCUIT.r1cs \ + $P1_PTAU \ + $PROVER_KEY + fi + + # export + snarkjs zkey export verificationkey $PROVER_KEY $VERIFICATION_KEY + + echo -e "${CIRCOMKIT_COLOR_LOG}Generated keys\n\tProver key: $PROVER_KEY\n\tVerification key: $VERIFICATION_KEY${CIRCOMKIT_COLOR_RESET}" +} diff --git a/scripts/functions/verify.sh b/scripts/functions/verify.sh index 3df6871..78001a6 100755 --- a/scripts/functions/verify.sh +++ b/scripts/functions/verify.sh @@ -3,9 +3,9 @@ verify() { echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Verifying proof ===${CIRCOMKIT_COLOR_RESET}" local CIRCUIT=$1 local INPUT=$2 - local CIRCUIT_DIR=./build/${CIRCUIT} + local CIRCUIT_DIR=./build/$CIRCUIT - snarkjs groth16 verify \ + snarkjs $CIRCOMKIT_PROOF_SYSTEM verify \ $CIRCUIT_DIR/verification_key.json \ $CIRCUIT_DIR/$INPUT/public.json \ $CIRCUIT_DIR/$INPUT/proof.json diff --git a/tests/sudoku.test.ts b/tests/sudoku.test.ts index ecd866b..2ab1333 100644 --- a/tests/sudoku.test.ts +++ b/tests/sudoku.test.ts @@ -44,9 +44,9 @@ const INPUTS: {[N in BoardSizes]: any} = { }, }; -[9, 4].map(N => +([9, 4] as BoardSizes[]).map(N => describe(`sudoku (${N} by ${N})`, () => { - const INPUT = INPUTS[N as BoardSizes]; + const INPUT = INPUTS[N]; let circuit: Awaited>; before(async () => { diff --git a/types/circuit.ts b/types/circuit.ts index 612e614..906f6b4 100644 --- a/types/circuit.ts +++ b/types/circuit.ts @@ -50,3 +50,8 @@ export type CircuitConfig = { export type Config = { [circuitName: string]: CircuitConfig; }; + +/** + * Proof system to be used + */ +export type ProofSystem = 'groth16' | 'plonk' | 'fflonk'; diff --git a/utils/proofTester.ts b/utils/proofTester.ts index bff9d32..e958f03 100644 --- a/utils/proofTester.ts +++ b/utils/proofTester.ts @@ -1,13 +1,14 @@ import fs from 'fs'; const snarkjs = require('snarkjs'); import {expect} from 'chai'; -import type {CircuitSignals, FullProof} from '../types/circuit'; +import type {CircuitSignals, FullProof, ProofSystem} from '../types/circuit'; /** * A more extensive Circuit class, able to generate proofs & verify them. * Assumes that prover key and verifier key have been computed. */ export class ProofTester { + public readonly proofSystem: ProofSystem; private readonly wasmPath: string; private readonly proverKeyPath: string; private readonly verificationKey: object; @@ -15,8 +16,9 @@ export class ProofTester { /** * Sets the paths & loads the verification key * @param circuit a proof tester + * @param proofSystem proof system to use, defaults to `groth16` */ - constructor(circuit: string) { + constructor(circuit: string, proofSystem: ProofSystem = 'groth16') { // find paths (computed w.r.t circuit name) this.wasmPath = `./build/${circuit}/${circuit}_js/${circuit}.wasm`; this.proverKeyPath = `./build/${circuit}/prover_key.zkey`; @@ -30,15 +32,19 @@ export class ProofTester { // load verification key this.verificationKey = JSON.parse(fs.readFileSync(verificationKeyPath).toString()); + + // set proof system + this.proofSystem = proofSystem; } /** * Generate a proof for the witness computed from the given input signals. + * Calls `fullProve` behind the scenes. * @param input input signals for the circuit * @returns a proof and public signals */ async prove(input: CircuitSignals): Promise { - return await snarkjs.groth16.fullProve(input, this.wasmPath, this.proverKeyPath); + return await snarkjs[this.proofSystem].fullProve(input, this.wasmPath, this.proverKeyPath); } /** @@ -48,7 +54,7 @@ export class ProofTester { * @returns `true` if proof verifies, `false` otherwise */ async verify(proof: object, publicSignals: string[]): Promise { - return await snarkjs.groth16.verify(this.verificationKey, publicSignals, proof); + return await snarkjs[this.proofSystem].verify(this.verificationKey, publicSignals, proof); } /** diff --git a/yarn.lock b/yarn.lock index 198bce3..6d05ffe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -927,6 +927,13 @@ circom_runtime@0.1.21: dependencies: ffjavascript "0.2.56" +circom_runtime@0.1.22: + version "0.1.22" + resolved "https://registry.yarnpkg.com/circom_runtime/-/circom_runtime-0.1.22.tgz#f957c47662cdd03cd3fb76979c434c719a366373" + integrity sha512-V/XYZWBhbZY8SotkaGH4FbiDYAZ8a1Md++MBiKPDOuWS/NIJB+Q+XIiTC8zKMgoDaa9cd2OiTvsC9J6te7twNg== + dependencies: + ffjavascript "0.2.57" + circom_tester@^0.0.19: version "0.0.19" resolved "https://registry.yarnpkg.com/circom_tester/-/circom_tester-0.0.19.tgz#e8bed494d080f8186bd0ac6571755d00ccec83bd" @@ -1400,7 +1407,7 @@ ffjavascript@0.2.56: wasmcurves "0.2.0" web-worker "^1.2.0" -ffjavascript@^0.2.45, ffjavascript@^0.2.48, ffjavascript@^0.2.56: +ffjavascript@0.2.57, ffjavascript@^0.2.45, ffjavascript@^0.2.48, ffjavascript@^0.2.56: version "0.2.57" resolved "https://registry.yarnpkg.com/ffjavascript/-/ffjavascript-0.2.57.tgz#ba1be96015b2688192e49f2f4de2cc5150fd8594" integrity sha512-V+vxZ/zPNcthrWmqfe/1YGgqdkTamJeXiED0tsk7B84g40DKlrTdx47IqZuiygqAVG6zMw4qYuvXftIJWsmfKQ== @@ -2354,6 +2361,16 @@ r1csfile@0.0.41, r1csfile@^0.0.41: fastfile "0.0.20" ffjavascript "0.2.56" +r1csfile@0.0.45: + version "0.0.45" + resolved "https://registry.yarnpkg.com/r1csfile/-/r1csfile-0.0.45.tgz#59d59a33f8b5280017fc00ee691d003a3d705fe0" + integrity sha512-YKIp4D441aZ6OoI9y+bfAyb2j4Cl+OFq/iiX6pPWDrL4ZO968h0dq0w07i65edvrTt7/G43mTnl0qEuLXyp/Yw== + dependencies: + "@iden3/bigarray" "0.0.2" + "@iden3/binfileutils" "0.0.11" + fastfile "0.0.20" + ffjavascript "0.2.57" + randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -2542,7 +2559,7 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -snarkjs@0.5.0, snarkjs@^0.5.0: +snarkjs@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/snarkjs/-/snarkjs-0.5.0.tgz#cf26bf1d3835eb16b4b330a438bad9824837d6b0" integrity sha512-KWz8mZ2Y+6wvn6GGkQo6/ZlKwETdAGohd40Lzpwp5TUZCn6N6O4Az1SuX1rw/qREGL6Im+ycb19suCFE8/xaKA== @@ -2558,6 +2575,22 @@ snarkjs@0.5.0, snarkjs@^0.5.0: logplease "^1.2.15" r1csfile "0.0.41" +snarkjs@^0.6.0: + version "0.6.10" + resolved "https://registry.yarnpkg.com/snarkjs/-/snarkjs-0.6.10.tgz#d751645a45d2390abcb007c409b31d813bedd108" + integrity sha512-Ls9XPTIuW2Ivk2sACM0Pn/FnKZP8UYYqRbzDl3SnglY5Gw28BIp1sfArOxdJJ2QrlbK7e6FFjMHQPvTp0Ttn0w== + dependencies: + "@iden3/binfileutils" "0.0.11" + bfj "^7.0.2" + blake2b-wasm "^2.4.0" + circom_runtime "0.1.22" + ejs "^3.1.6" + fastfile "0.0.20" + ffjavascript "0.2.57" + js-sha3 "^0.8.0" + logplease "^1.2.15" + r1csfile "0.0.45" + spdx-correct@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"