[{"data":1,"prerenderedAt":1612},["ShallowReactive",2],{"navigation":3,"-examples-signed-receipts":167,"-examples-signed-receipts-surround":1609},[4,22,78,106,141,148],{"title":5,"path":6,"stem":7,"children":8},"Introduction","\u002Fgetting-started","0.Getting-Started\u002F0.index",[9,10,14,18],{"title":5,"path":6,"stem":7},{"title":11,"path":12,"stem":13},"Installation","\u002Fgetting-started\u002Finstallation","0.Getting-Started\u002F1.installation",{"title":15,"path":16,"stem":17},"Quickstart","\u002Fgetting-started\u002Fquickstart","0.Getting-Started\u002F2.quickstart",{"title":19,"path":20,"stem":21},"Core concepts","\u002Fgetting-started\u002Fcore-concepts","0.Getting-Started\u002F3.core-concepts",{"title":23,"path":24,"stem":25,"children":26,"icon":28},"JWT","\u002Fjwt","1.JWT\u002F0.index",[27,29,52],{"title":23,"path":24,"stem":25,"icon":28},"i-carbon-certificate",{"title":30,"path":31,"stem":32,"children":33,"icon":35},"JWS","\u002Fjwt\u002Fjws","1.JWT\u002F1.JWS\u002F0.index",[34,36,40,44,48],{"title":30,"path":31,"stem":32,"icon":35},"i-carbon-document-signed",{"title":37,"path":38,"stem":39},"Signing","\u002Fjwt\u002Fjws\u002Fsigning","1.JWT\u002F1.JWS\u002F1.signing",{"title":41,"path":42,"stem":43},"Verifying","\u002Fjwt\u002Fjws\u002Fverifying","1.JWT\u002F1.JWS\u002F2.verifying",{"title":45,"path":46,"stem":47},"Multi-signature","\u002Fjwt\u002Fjws\u002Fmulti-signature","1.JWT\u002F1.JWS\u002F3.multi-signature",{"title":49,"path":50,"stem":51},"Algorithms","\u002Fjwt\u002Fjws\u002Falgorithms","1.JWT\u002F1.JWS\u002F4.algorithms",{"title":53,"path":54,"stem":55,"children":56,"icon":58},"JWE","\u002Fjwt\u002Fjwe","1.JWT\u002F2.JWE\u002F0.index",[57,59,63,67,71,75],{"title":53,"path":54,"stem":55,"icon":58},"i-carbon-locked",{"title":60,"path":61,"stem":62},"Encrypting","\u002Fjwt\u002Fjwe\u002Fencrypting","1.JWT\u002F2.JWE\u002F1.encrypting",{"title":64,"path":65,"stem":66},"Decrypting","\u002Fjwt\u002Fjwe\u002Fdecrypting","1.JWT\u002F2.JWE\u002F2.decrypting",{"title":68,"path":69,"stem":70},"Multi-recipient","\u002Fjwt\u002Fjwe\u002Fmulti-recipient","1.JWT\u002F2.JWE\u002F3.multi-recipient",{"title":72,"path":73,"stem":74},"ECDH-ES and end-to-end encryption","\u002Fjwt\u002Fjwe\u002Fecdh-es","1.JWT\u002F2.JWE\u002F4.ecdh-es",{"title":49,"path":76,"stem":77},"\u002Fjwt\u002Fjwe\u002Falgorithms","1.JWT\u002F2.JWE\u002F5.algorithms",{"title":79,"path":80,"stem":81,"children":82,"icon":84},"Examples","\u002Fexamples","10.Examples\u002F0.index",[83,85,90,94,98,102],{"title":79,"path":80,"stem":81,"icon":84},"i-carbon-code-reference",{"title":86,"path":87,"stem":88,"icon":89},"Authentication basics","\u002Fexamples\u002Fauthentication-basics","10.Examples\u002F1.authentication-basics","i-lucide-code",{"title":91,"path":92,"stem":93,"icon":89},"Consuming a JWKS endpoint","\u002Fexamples\u002Fjwks-endpoint","10.Examples\u002F2.jwks-endpoint",{"title":95,"path":96,"stem":97,"icon":89},"Refresh token pattern","\u002Fexamples\u002Frefresh-token-pattern","10.Examples\u002F3.refresh-token-pattern",{"title":99,"path":100,"stem":101,"icon":89},"End-to-end encryption","\u002Fexamples\u002Fend-to-end-encryption","10.Examples\u002F4.end-to-end-encryption",{"title":103,"path":104,"stem":105,"icon":89},"Signed receipts","\u002Fexamples\u002Fsigned-receipts","10.Examples\u002F5.signed-receipts",{"title":107,"path":108,"stem":109,"children":110,"icon":112},"JWK","\u002Fjwk","2.JWK\u002F0.index",[111,113,117,121,125,129,133,137],{"title":107,"path":108,"stem":109,"icon":112},"i-carbon-two-factor-authentication",{"title":114,"path":115,"stem":116},"Generating keys","\u002Fjwk\u002Fgenerating","2.JWK\u002F1.generating",{"title":118,"path":119,"stem":120},"Importing and exporting","\u002Fjwk\u002Fimport-export","2.JWK\u002F2.import-export",{"title":122,"path":123,"stem":124},"PEM conversion","\u002Fjwk\u002Fpem","2.JWK\u002F3.pem",{"title":126,"path":127,"stem":128},"Key wrapping","\u002Fjwk\u002Fwrapping","2.JWK\u002F4.wrapping",{"title":130,"path":131,"stem":132},"Password derivation","\u002Fjwk\u002Fpassword-derivation","2.JWK\u002F5.password-derivation",{"title":134,"path":135,"stem":136},"JWK Sets","\u002Fjwk\u002Fjwk-sets","2.JWK\u002F6.jwk-sets",{"title":138,"path":139,"stem":140},"JWK cache","\u002Fjwk\u002Fcache","2.JWK\u002F7.cache",{"title":142,"path":143,"stem":144,"children":145,"icon":147},"Utilities","\u002Futilities","3.Utilities\u002F0.index",[146],{"title":142,"path":143,"stem":144,"icon":147},"i-carbon-tool-box",{"title":149,"path":150,"stem":151,"children":152,"icon":154},"Adapters","\u002Fadapters","99.Adapters\u002F0.index",[153,155,159,163],{"title":149,"path":150,"stem":151,"icon":154},"i-carbon-plug",{"title":156,"path":157,"stem":158},"H3 sessions","\u002Fadapters\u002Fh3-sessions","99.Adapters\u002F1.h3-sessions",{"title":160,"path":161,"stem":162},"Lifecycle hooks","\u002Fadapters\u002Fhooks","99.Adapters\u002F2.hooks",{"title":164,"path":165,"stem":166},"Lower-level functions","\u002Fadapters\u002Flower-level","99.Adapters\u002F3.lower-level",{"id":168,"title":103,"body":169,"description":244,"extension":1604,"meta":1605,"navigation":1606,"path":104,"seo":1607,"stem":105,"__hash__":1608},"content\u002F10.Examples\u002F5.signed-receipts.md",{"type":170,"value":171,"toc":1590,"icon":89},"minimark",[172,189,196,201,204,230,233,237,394,401,404,665,672,686,697,701,717,722,966,970,1119,1123,1126,1310,1314,1317,1429,1433,1447,1528,1532,1535,1563,1567,1586],[173,174,175,176,180,181,188],"p",{},"Some operations need ",[177,178,179],"strong",{},"more than one signature"," — notarized approvals, multi-party contracts, M-of-N quorum operations, signed audit trails. The compact JWS has one signature slot; multi-signature JWS (",[182,183,187],"a",{"href":184,"rel":185},"https:\u002F\u002Fwww.rfc-editor.org\u002Frfc\u002Frfc7515#section-7.2",[186],"nofollow","RFC 7515 §7.2",") has as many as you need.",[173,190,191,192,195],{},"This example walks through a ",[177,193,194],{},"notarized document"," use case — Alice issues a document, and a neutral Notary witnesses it — then generalizes to quorum approval.",[197,198,200],"h2",{"id":199},"the-scenario","The scenario",[173,202,203],{},"A user authorizes a bank transfer. Your system requires:",[205,206,208,216,223],"steps",{"level":207},"4",[209,210,211,212,215],"h4",{},"The ",[177,213,214],{},"user's"," signature (proves they authorized it).",[209,217,218,219,222],{},"Your ",[177,220,221],{},"service's"," signature (proves it was processed by you, not a replay).",[209,224,225,226,229],{},"A ",[177,227,228],{},"notary's"," signature (third-party attestation for audit).",[173,231,232],{},"All three signatures cover the same payload. Any verifier can check any subset; a court can verify all three independently.",[197,234,236],{"id":235},"setup-three-signers","Setup — three signers",[238,239,245],"pre",{"className":240,"code":241,"filename":242,"language":243,"meta":244,"style":244},"language-ts shiki shiki-themes github-light github-dark github-dark","import { generateJWK } from \"unjwt\u002Fjwk\";\n\n\u002F\u002F User's key (e.g. on their device, RSA from their YubiKey)\nconst userKey = await generateJWK(\"ES256\", { kid: \"alice@example.com\" });\n\n\u002F\u002F Your service's key (persisted, long-lived)\nconst serviceKey = await generateJWK(\"Ed25519\", { kid: \"banktransfer-svc-2025\" });\n\n\u002F\u002F Notary's key (third party)\nconst notaryKey = await generateJWK(\"RS256\", { kid: \"notary.gov\u002F2025-q1\" });\n","keys.ts","ts","",[246,247,248,271,278,285,320,325,331,357,362,368],"code",{"__ignoreMap":244},[249,250,253,257,261,264,268],"span",{"class":251,"line":252},"line",1,[249,254,256],{"class":255},"so5gQ","import",[249,258,260],{"class":259},"slsVL"," { generateJWK } ",[249,262,263],{"class":255},"from",[249,265,267],{"class":266},"sfrk1"," \"unjwt\u002Fjwk\"",[249,269,270],{"class":259},";\n",[249,272,274],{"class":251,"line":273},2,[249,275,277],{"emptyLinePlaceholder":276},true,"\n",[249,279,281],{"class":251,"line":280},3,[249,282,284],{"class":283},"sCsY4","\u002F\u002F User's key (e.g. on their device, RSA from their YubiKey)\n",[249,286,288,291,295,298,301,305,308,311,314,317],{"class":251,"line":287},4,[249,289,290],{"class":255},"const",[249,292,294],{"class":293},"suiK_"," userKey",[249,296,297],{"class":255}," =",[249,299,300],{"class":255}," await",[249,302,304],{"class":303},"shcOC"," generateJWK",[249,306,307],{"class":259},"(",[249,309,310],{"class":266},"\"ES256\"",[249,312,313],{"class":259},", { kid: ",[249,315,316],{"class":266},"\"alice@example.com\"",[249,318,319],{"class":259}," });\n",[249,321,323],{"class":251,"line":322},5,[249,324,277],{"emptyLinePlaceholder":276},[249,326,328],{"class":251,"line":327},6,[249,329,330],{"class":283},"\u002F\u002F Your service's key (persisted, long-lived)\n",[249,332,334,336,339,341,343,345,347,350,352,355],{"class":251,"line":333},7,[249,335,290],{"class":255},[249,337,338],{"class":293}," serviceKey",[249,340,297],{"class":255},[249,342,300],{"class":255},[249,344,304],{"class":303},[249,346,307],{"class":259},[249,348,349],{"class":266},"\"Ed25519\"",[249,351,313],{"class":259},[249,353,354],{"class":266},"\"banktransfer-svc-2025\"",[249,356,319],{"class":259},[249,358,360],{"class":251,"line":359},8,[249,361,277],{"emptyLinePlaceholder":276},[249,363,365],{"class":251,"line":364},9,[249,366,367],{"class":283},"\u002F\u002F Notary's key (third party)\n",[249,369,371,373,376,378,380,382,384,387,389,392],{"class":251,"line":370},10,[249,372,290],{"class":255},[249,374,375],{"class":293}," notaryKey",[249,377,297],{"class":255},[249,379,300],{"class":255},[249,381,304],{"class":303},[249,383,307],{"class":259},[249,385,386],{"class":266},"\"RS256\"",[249,388,313],{"class":259},[249,390,391],{"class":266},"\"notary.gov\u002F2025-q1\"",[249,393,319],{"class":259},[173,395,396,397,400],{},"Each signer brings their own algorithm — multi-signature doesn't require everyone to agree on ",[246,398,399],{},"alg",".",[197,402,37],{"id":403},"signing",[238,405,408],{"className":240,"code":406,"filename":407,"language":243,"meta":244,"style":244},"import { signMulti } from \"unjwt\u002Fjws\";\n\nconst payload = {\n  txId: \"tx_4aX9\",\n  from: \"alice@example.com\",\n  to: \"bob@example.com\",\n  amount: 500,\n  currency: \"EUR\",\n  iss: \"https:\u002F\u002Fbanktransfer.example.com\",\n};\n\nconst jws = await signMulti(\n  payload,\n  [\n    { key: userKey.privateKey, protectedHeader: { typ: \"authorization+jwt\" } },\n    { key: serviceKey.privateKey, protectedHeader: { typ: \"transfer+jwt\" } },\n    { key: notaryKey.privateKey, unprotectedHeader: { \"x-role\": \"notary\" } },\n  ],\n  { expiresIn: \"30D\" }, \u002F\u002F iat and exp are shared across all signatures\n);\n\n\u002F\u002F jws: {\n\u002F\u002F   payload: \"base64url(JSON)\",\n\u002F\u002F   signatures: [\n\u002F\u002F     { protected: \"...\", signature: \"...\" },    \u002F\u002F user\n\u002F\u002F     { protected: \"...\", signature: \"...\" },    \u002F\u002F service\n\u002F\u002F     { protected: \"...\", header: { \"x-role\": \"notary\" }, signature: \"...\" },\n\u002F\u002F   ]\n\u002F\u002F }\n","sign.ts",[246,409,410,424,428,440,451,460,470,480,490,500,505,510,528,534,540,552,563,580,586,601,607,612,618,624,630,639,647,653,659],{"__ignoreMap":244},[249,411,412,414,417,419,422],{"class":251,"line":252},[249,413,256],{"class":255},[249,415,416],{"class":259}," { signMulti } ",[249,418,263],{"class":255},[249,420,421],{"class":266}," \"unjwt\u002Fjws\"",[249,423,270],{"class":259},[249,425,426],{"class":251,"line":273},[249,427,277],{"emptyLinePlaceholder":276},[249,429,430,432,435,437],{"class":251,"line":280},[249,431,290],{"class":255},[249,433,434],{"class":293}," payload",[249,436,297],{"class":255},[249,438,439],{"class":259}," {\n",[249,441,442,445,448],{"class":251,"line":287},[249,443,444],{"class":259},"  txId: ",[249,446,447],{"class":266},"\"tx_4aX9\"",[249,449,450],{"class":259},",\n",[249,452,453,456,458],{"class":251,"line":322},[249,454,455],{"class":259},"  from: ",[249,457,316],{"class":266},[249,459,450],{"class":259},[249,461,462,465,468],{"class":251,"line":327},[249,463,464],{"class":259},"  to: ",[249,466,467],{"class":266},"\"bob@example.com\"",[249,469,450],{"class":259},[249,471,472,475,478],{"class":251,"line":333},[249,473,474],{"class":259},"  amount: ",[249,476,477],{"class":293},"500",[249,479,450],{"class":259},[249,481,482,485,488],{"class":251,"line":359},[249,483,484],{"class":259},"  currency: ",[249,486,487],{"class":266},"\"EUR\"",[249,489,450],{"class":259},[249,491,492,495,498],{"class":251,"line":364},[249,493,494],{"class":259},"  iss: ",[249,496,497],{"class":266},"\"https:\u002F\u002Fbanktransfer.example.com\"",[249,499,450],{"class":259},[249,501,502],{"class":251,"line":370},[249,503,504],{"class":259},"};\n",[249,506,508],{"class":251,"line":507},11,[249,509,277],{"emptyLinePlaceholder":276},[249,511,513,515,518,520,522,525],{"class":251,"line":512},12,[249,514,290],{"class":255},[249,516,517],{"class":293}," jws",[249,519,297],{"class":255},[249,521,300],{"class":255},[249,523,524],{"class":303}," signMulti",[249,526,527],{"class":259},"(\n",[249,529,531],{"class":251,"line":530},13,[249,532,533],{"class":259},"  payload,\n",[249,535,537],{"class":251,"line":536},14,[249,538,539],{"class":259},"  [\n",[249,541,543,546,549],{"class":251,"line":542},15,[249,544,545],{"class":259},"    { key: userKey.privateKey, protectedHeader: { typ: ",[249,547,548],{"class":266},"\"authorization+jwt\"",[249,550,551],{"class":259}," } },\n",[249,553,555,558,561],{"class":251,"line":554},16,[249,556,557],{"class":259},"    { key: serviceKey.privateKey, protectedHeader: { typ: ",[249,559,560],{"class":266},"\"transfer+jwt\"",[249,562,551],{"class":259},[249,564,566,569,572,575,578],{"class":251,"line":565},17,[249,567,568],{"class":259},"    { key: notaryKey.privateKey, unprotectedHeader: { ",[249,570,571],{"class":266},"\"x-role\"",[249,573,574],{"class":259},": ",[249,576,577],{"class":266},"\"notary\"",[249,579,551],{"class":259},[249,581,583],{"class":251,"line":582},18,[249,584,585],{"class":259},"  ],\n",[249,587,589,592,595,598],{"class":251,"line":588},19,[249,590,591],{"class":259},"  { expiresIn: ",[249,593,594],{"class":266},"\"30D\"",[249,596,597],{"class":259}," }, ",[249,599,600],{"class":283},"\u002F\u002F iat and exp are shared across all signatures\n",[249,602,604],{"class":251,"line":603},20,[249,605,606],{"class":259},");\n",[249,608,610],{"class":251,"line":609},21,[249,611,277],{"emptyLinePlaceholder":276},[249,613,615],{"class":251,"line":614},22,[249,616,617],{"class":283},"\u002F\u002F jws: {\n",[249,619,621],{"class":251,"line":620},23,[249,622,623],{"class":283},"\u002F\u002F   payload: \"base64url(JSON)\",\n",[249,625,627],{"class":251,"line":626},24,[249,628,629],{"class":283},"\u002F\u002F   signatures: [\n",[249,631,633,636],{"class":251,"line":632},25,[249,634,635],{"class":283},"\u002F\u002F     { protected: \"...\", signature: \"...\" },",[249,637,638],{"class":283},"    \u002F\u002F user\n",[249,640,642,644],{"class":251,"line":641},26,[249,643,635],{"class":283},[249,645,646],{"class":283},"    \u002F\u002F service\n",[249,648,650],{"class":251,"line":649},27,[249,651,652],{"class":283},"\u002F\u002F     { protected: \"...\", header: { \"x-role\": \"notary\" }, signature: \"...\" },\n",[249,654,656],{"class":251,"line":655},28,[249,657,658],{"class":283},"\u002F\u002F   ]\n",[249,660,662],{"class":251,"line":661},29,[249,663,664],{"class":283},"\u002F\u002F }\n",[173,666,667,668,671],{},"Each signature is computed independently by each signer — in a real deployment, this would typically be ",[177,669,670],{},"three separate ceremonies",":",[205,673,674,677,680,683],{"level":207},[209,675,676],{},"User signs their part on their device.",[209,678,679],{},"Sends the signed piece to your service.",[209,681,682],{},"Service signs their part, forwards to notary.",[209,684,685],{},"Notary signs and returns the complete envelope.",[173,687,688,689,692,693,696],{},"Multi-signature JWS supports this: you can build the structure incrementally by ",[246,690,691],{},"push","ing new ",[246,694,695],{},"signatures[]"," entries. For this example we sign all three at once for clarity.",[197,698,700],{"id":699},"policy-driven-verification","Policy-driven verification",[173,702,703,704,708,709,712,713,716],{},"Now consumers of the signed envelope need to decide what \"verified\" means for ",[705,706,707],"em",{},"their"," use case. ",[246,710,711],{},"verifyMultiAll"," returns the status of every signature so the caller applies their own policy — ",[182,714,187],{"href":184,"rel":715},[186]," deliberately leaves this to the application.",[718,719,721],"h3",{"id":720},"policy-1-all-signatures-must-verify-strict","Policy 1 — all signatures must verify (strict)",[238,723,726],{"className":240,"code":724,"filename":725,"language":243,"meta":244,"style":244},"import { verifyMultiAll } from \"unjwt\u002Fjws\";\n\nconst outcomes = await verifyMultiAll(jws, async (header) => {\n  \u002F\u002F Lookup each signer's public key by kid\n  return keyStoreFor(header.kid!);\n});\n\nif (!outcomes.every((o) => o.verified)) {\n  const failed = outcomes.filter((o) => !o.verified);\n  throw new Error(\n    `Not all signatures valid: ${failed.map((o) => `${o.signerIndex}:${o.error.code}`).join(\", \")}`,\n  );\n}\n\n\u002F\u002F All three signatures verified — safe to process\n","policy-all.ts",[246,727,728,741,745,780,785,801,806,810,838,868,881,947,952,957,961],{"__ignoreMap":244},[249,729,730,732,735,737,739],{"class":251,"line":252},[249,731,256],{"class":255},[249,733,734],{"class":259}," { verifyMultiAll } ",[249,736,263],{"class":255},[249,738,421],{"class":266},[249,740,270],{"class":259},[249,742,743],{"class":251,"line":273},[249,744,277],{"emptyLinePlaceholder":276},[249,746,747,749,752,754,756,759,762,765,768,772,775,778],{"class":251,"line":280},[249,748,290],{"class":255},[249,750,751],{"class":293}," outcomes",[249,753,297],{"class":255},[249,755,300],{"class":255},[249,757,758],{"class":303}," verifyMultiAll",[249,760,761],{"class":259},"(jws, ",[249,763,764],{"class":255},"async",[249,766,767],{"class":259}," (",[249,769,771],{"class":770},"sQHwn","header",[249,773,774],{"class":259},") ",[249,776,777],{"class":255},"=>",[249,779,439],{"class":259},[249,781,782],{"class":251,"line":287},[249,783,784],{"class":283},"  \u002F\u002F Lookup each signer's public key by kid\n",[249,786,787,790,793,796,799],{"class":251,"line":322},[249,788,789],{"class":255},"  return",[249,791,792],{"class":303}," keyStoreFor",[249,794,795],{"class":259},"(header.kid",[249,797,798],{"class":255},"!",[249,800,606],{"class":259},[249,802,803],{"class":251,"line":327},[249,804,805],{"class":259},"});\n",[249,807,808],{"class":251,"line":333},[249,809,277],{"emptyLinePlaceholder":276},[249,811,812,815,817,819,822,825,828,831,833,835],{"class":251,"line":359},[249,813,814],{"class":255},"if",[249,816,767],{"class":259},[249,818,798],{"class":255},[249,820,821],{"class":259},"outcomes.",[249,823,824],{"class":303},"every",[249,826,827],{"class":259},"((",[249,829,830],{"class":770},"o",[249,832,774],{"class":259},[249,834,777],{"class":255},[249,836,837],{"class":259}," o.verified)) {\n",[249,839,840,843,846,848,851,854,856,858,860,862,865],{"class":251,"line":364},[249,841,842],{"class":255},"  const",[249,844,845],{"class":293}," failed",[249,847,297],{"class":255},[249,849,850],{"class":259}," outcomes.",[249,852,853],{"class":303},"filter",[249,855,827],{"class":259},[249,857,830],{"class":770},[249,859,774],{"class":259},[249,861,777],{"class":255},[249,863,864],{"class":255}," !",[249,866,867],{"class":259},"o.verified);\n",[249,869,870,873,876,879],{"class":251,"line":370},[249,871,872],{"class":255},"  throw",[249,874,875],{"class":255}," new",[249,877,878],{"class":303}," Error",[249,880,527],{"class":259},[249,882,883,886,889,891,894,896,898,900,902,905,907,909,912,915,917,919,922,924,926,929,932,935,937,940,943,945],{"class":251,"line":507},[249,884,885],{"class":266},"    `Not all signatures valid: ${",[249,887,888],{"class":259},"failed",[249,890,400],{"class":266},[249,892,893],{"class":303},"map",[249,895,827],{"class":266},[249,897,830],{"class":293},[249,899,774],{"class":266},[249,901,777],{"class":255},[249,903,904],{"class":266}," `${",[249,906,830],{"class":259},[249,908,400],{"class":266},[249,910,911],{"class":259},"signerIndex",[249,913,914],{"class":266},"}:${",[249,916,830],{"class":259},[249,918,400],{"class":266},[249,920,921],{"class":259},"error",[249,923,400],{"class":266},[249,925,246],{"class":259},[249,927,928],{"class":266},"}`",[249,930,931],{"class":266},").",[249,933,934],{"class":303},"join",[249,936,307],{"class":266},[249,938,939],{"class":266},"\", \"",[249,941,942],{"class":266},")",[249,944,928],{"class":266},[249,946,450],{"class":259},[249,948,949],{"class":251,"line":512},[249,950,951],{"class":259},"  );\n",[249,953,954],{"class":251,"line":530},[249,955,956],{"class":259},"}\n",[249,958,959],{"class":251,"line":536},[249,960,277],{"emptyLinePlaceholder":276},[249,962,963],{"class":251,"line":542},[249,964,965],{"class":283},"\u002F\u002F All three signatures verified — safe to process\n",[718,967,969],{"id":968},"policy-2-required-signers-notary-must-sign","Policy 2 — required signers (notary MUST sign)",[238,971,974],{"className":240,"code":972,"filename":973,"language":243,"meta":244,"style":244},"const outcomes = await verifyMultiAll(jws, keyLookup);\n\nconst verifiedKids = new Set(outcomes.filter((o) => o.verified).map((o) => o.protectedHeader.kid));\n\nif (!verifiedKids.has(\"notary.gov\u002F2025-q1\")) {\n  throw new Error(\"Missing notary signature — cannot audit\");\n}\nif (!verifiedKids.has(\"alice@example.com\")) {\n  throw new Error(\"Missing user authorization\");\n}\n","policy-notary.ts",[246,975,976,991,995,1038,1042,1063,1078,1082,1100,1115],{"__ignoreMap":244},[249,977,978,980,982,984,986,988],{"class":251,"line":252},[249,979,290],{"class":255},[249,981,751],{"class":293},[249,983,297],{"class":255},[249,985,300],{"class":255},[249,987,758],{"class":303},[249,989,990],{"class":259},"(jws, keyLookup);\n",[249,992,993],{"class":251,"line":273},[249,994,277],{"emptyLinePlaceholder":276},[249,996,997,999,1002,1004,1006,1009,1012,1014,1016,1018,1020,1022,1025,1027,1029,1031,1033,1035],{"class":251,"line":280},[249,998,290],{"class":255},[249,1000,1001],{"class":293}," verifiedKids",[249,1003,297],{"class":255},[249,1005,875],{"class":255},[249,1007,1008],{"class":303}," Set",[249,1010,1011],{"class":259},"(outcomes.",[249,1013,853],{"class":303},[249,1015,827],{"class":259},[249,1017,830],{"class":770},[249,1019,774],{"class":259},[249,1021,777],{"class":255},[249,1023,1024],{"class":259}," o.verified).",[249,1026,893],{"class":303},[249,1028,827],{"class":259},[249,1030,830],{"class":770},[249,1032,774],{"class":259},[249,1034,777],{"class":255},[249,1036,1037],{"class":259}," o.protectedHeader.kid));\n",[249,1039,1040],{"class":251,"line":287},[249,1041,277],{"emptyLinePlaceholder":276},[249,1043,1044,1046,1048,1050,1053,1056,1058,1060],{"class":251,"line":322},[249,1045,814],{"class":255},[249,1047,767],{"class":259},[249,1049,798],{"class":255},[249,1051,1052],{"class":259},"verifiedKids.",[249,1054,1055],{"class":303},"has",[249,1057,307],{"class":259},[249,1059,391],{"class":266},[249,1061,1062],{"class":259},")) {\n",[249,1064,1065,1067,1069,1071,1073,1076],{"class":251,"line":327},[249,1066,872],{"class":255},[249,1068,875],{"class":255},[249,1070,878],{"class":303},[249,1072,307],{"class":259},[249,1074,1075],{"class":266},"\"Missing notary signature — cannot audit\"",[249,1077,606],{"class":259},[249,1079,1080],{"class":251,"line":333},[249,1081,956],{"class":259},[249,1083,1084,1086,1088,1090,1092,1094,1096,1098],{"class":251,"line":359},[249,1085,814],{"class":255},[249,1087,767],{"class":259},[249,1089,798],{"class":255},[249,1091,1052],{"class":259},[249,1093,1055],{"class":303},[249,1095,307],{"class":259},[249,1097,316],{"class":266},[249,1099,1062],{"class":259},[249,1101,1102,1104,1106,1108,1110,1113],{"class":251,"line":364},[249,1103,872],{"class":255},[249,1105,875],{"class":255},[249,1107,878],{"class":303},[249,1109,307],{"class":259},[249,1111,1112],{"class":266},"\"Missing user authorization\"",[249,1114,606],{"class":259},[249,1116,1117],{"class":251,"line":370},[249,1118,956],{"class":259},[718,1120,1122],{"id":1121},"policy-3-m-of-n-quorum","Policy 3 — M-of-N quorum",[173,1124,1125],{},"For an \"any 2 of our 3 backup signers can authorize\" setup:",[238,1127,1130],{"className":240,"code":1128,"filename":1129,"language":243,"meta":244,"style":244},"const AUTHORIZED_KIDS = new Set([\n  \"backup-1@example.com\",\n  \"backup-2@example.com\",\n  \"backup-3@example.com\",\n]);\n\nconst outcomes = await verifyMultiAll(jws, keyLookup);\n\nconst verifiedAuthorized = outcomes\n  .filter((o) => o.verified && AUTHORIZED_KIDS.has(o.protectedHeader.kid ?? \"\"))\n  .map((o) => o.protectedHeader.kid);\n\nif (new Set(verifiedAuthorized).size \u003C 2) {\n  throw new Error(\"Quorum not met (need 2 authorized signatures)\");\n}\n","policy-quorum.ts",[246,1131,1132,1148,1155,1162,1169,1174,1178,1192,1196,1208,1247,1264,1268,1291,1306],{"__ignoreMap":244},[249,1133,1134,1136,1139,1141,1143,1145],{"class":251,"line":252},[249,1135,290],{"class":255},[249,1137,1138],{"class":293}," AUTHORIZED_KIDS",[249,1140,297],{"class":255},[249,1142,875],{"class":255},[249,1144,1008],{"class":303},[249,1146,1147],{"class":259},"([\n",[249,1149,1150,1153],{"class":251,"line":273},[249,1151,1152],{"class":266},"  \"backup-1@example.com\"",[249,1154,450],{"class":259},[249,1156,1157,1160],{"class":251,"line":280},[249,1158,1159],{"class":266},"  \"backup-2@example.com\"",[249,1161,450],{"class":259},[249,1163,1164,1167],{"class":251,"line":287},[249,1165,1166],{"class":266},"  \"backup-3@example.com\"",[249,1168,450],{"class":259},[249,1170,1171],{"class":251,"line":322},[249,1172,1173],{"class":259},"]);\n",[249,1175,1176],{"class":251,"line":327},[249,1177,277],{"emptyLinePlaceholder":276},[249,1179,1180,1182,1184,1186,1188,1190],{"class":251,"line":333},[249,1181,290],{"class":255},[249,1183,751],{"class":293},[249,1185,297],{"class":255},[249,1187,300],{"class":255},[249,1189,758],{"class":303},[249,1191,990],{"class":259},[249,1193,1194],{"class":251,"line":359},[249,1195,277],{"emptyLinePlaceholder":276},[249,1197,1198,1200,1203,1205],{"class":251,"line":364},[249,1199,290],{"class":255},[249,1201,1202],{"class":293}," verifiedAuthorized",[249,1204,297],{"class":255},[249,1206,1207],{"class":259}," outcomes\n",[249,1209,1210,1213,1215,1217,1219,1221,1223,1226,1229,1231,1233,1235,1238,1241,1244],{"class":251,"line":370},[249,1211,1212],{"class":259},"  .",[249,1214,853],{"class":303},[249,1216,827],{"class":259},[249,1218,830],{"class":770},[249,1220,774],{"class":259},[249,1222,777],{"class":255},[249,1224,1225],{"class":259}," o.verified ",[249,1227,1228],{"class":255},"&&",[249,1230,1138],{"class":293},[249,1232,400],{"class":259},[249,1234,1055],{"class":303},[249,1236,1237],{"class":259},"(o.protectedHeader.kid ",[249,1239,1240],{"class":255},"??",[249,1242,1243],{"class":266}," \"\"",[249,1245,1246],{"class":259},"))\n",[249,1248,1249,1251,1253,1255,1257,1259,1261],{"class":251,"line":507},[249,1250,1212],{"class":259},[249,1252,893],{"class":303},[249,1254,827],{"class":259},[249,1256,830],{"class":770},[249,1258,774],{"class":259},[249,1260,777],{"class":255},[249,1262,1263],{"class":259}," o.protectedHeader.kid);\n",[249,1265,1266],{"class":251,"line":512},[249,1267,277],{"emptyLinePlaceholder":276},[249,1269,1270,1272,1274,1277,1279,1282,1285,1288],{"class":251,"line":530},[249,1271,814],{"class":255},[249,1273,767],{"class":259},[249,1275,1276],{"class":255},"new",[249,1278,1008],{"class":303},[249,1280,1281],{"class":259},"(verifiedAuthorized).size ",[249,1283,1284],{"class":255},"\u003C",[249,1286,1287],{"class":293}," 2",[249,1289,1290],{"class":259},") {\n",[249,1292,1293,1295,1297,1299,1301,1304],{"class":251,"line":536},[249,1294,872],{"class":255},[249,1296,875],{"class":255},[249,1298,878],{"class":303},[249,1300,307],{"class":259},[249,1302,1303],{"class":266},"\"Quorum not met (need 2 authorized signatures)\"",[249,1305,606],{"class":259},[249,1307,1308],{"class":251,"line":542},[249,1309,956],{"class":259},[718,1311,1313],{"id":1312},"policy-4-audit-log","Policy 4 — audit log",[173,1315,1316],{},"Even when verification fails, record every outcome for forensics:",[238,1318,1321],{"className":240,"code":1319,"filename":1320,"language":243,"meta":244,"style":244},"const outcomes = await verifyMultiAll(jws, keyLookup);\n\nfor (const o of outcomes) {\n  await audit.log({\n    txId: (o.verified ? o.payload : {}).txId,\n    signerIndex: o.signerIndex,\n    signerKid: o.protectedHeader?.kid,\n    verified: o.verified,\n    error: o.verified ? null : o.error.code,\n  });\n}\n","policy-audit.ts",[246,1322,1323,1337,1341,1359,1373,1389,1394,1399,1404,1420,1425],{"__ignoreMap":244},[249,1324,1325,1327,1329,1331,1333,1335],{"class":251,"line":252},[249,1326,290],{"class":255},[249,1328,751],{"class":293},[249,1330,297],{"class":255},[249,1332,300],{"class":255},[249,1334,758],{"class":303},[249,1336,990],{"class":259},[249,1338,1339],{"class":251,"line":273},[249,1340,277],{"emptyLinePlaceholder":276},[249,1342,1343,1346,1348,1350,1353,1356],{"class":251,"line":280},[249,1344,1345],{"class":255},"for",[249,1347,767],{"class":259},[249,1349,290],{"class":255},[249,1351,1352],{"class":293}," o",[249,1354,1355],{"class":255}," of",[249,1357,1358],{"class":259}," outcomes) {\n",[249,1360,1361,1364,1367,1370],{"class":251,"line":287},[249,1362,1363],{"class":255},"  await",[249,1365,1366],{"class":259}," audit.",[249,1368,1369],{"class":303},"log",[249,1371,1372],{"class":259},"({\n",[249,1374,1375,1378,1381,1384,1386],{"class":251,"line":322},[249,1376,1377],{"class":259},"    txId: (o.verified ",[249,1379,1380],{"class":255},"?",[249,1382,1383],{"class":259}," o.payload ",[249,1385,671],{"class":255},[249,1387,1388],{"class":259}," {}).txId,\n",[249,1390,1391],{"class":251,"line":327},[249,1392,1393],{"class":259},"    signerIndex: o.signerIndex,\n",[249,1395,1396],{"class":251,"line":333},[249,1397,1398],{"class":259},"    signerKid: o.protectedHeader?.kid,\n",[249,1400,1401],{"class":251,"line":359},[249,1402,1403],{"class":259},"    verified: o.verified,\n",[249,1405,1406,1409,1411,1414,1417],{"class":251,"line":364},[249,1407,1408],{"class":259},"    error: o.verified ",[249,1410,1380],{"class":255},[249,1412,1413],{"class":293}," null",[249,1415,1416],{"class":255}," :",[249,1418,1419],{"class":259}," o.error.code,\n",[249,1421,1422],{"class":251,"line":370},[249,1423,1424],{"class":259},"  });\n",[249,1426,1427],{"class":251,"line":507},[249,1428,956],{"class":259},[197,1430,1432],{"id":1431},"lightweight-first-valid-signature","Lightweight — first valid signature",[173,1434,1435,1436,1439,1440,1446],{},"If you don't care which of the three signed, only that ",[705,1437,1438],{},"some"," authorized key signed, ",[182,1441,1443],{"href":1442},"\u002Fjwt\u002Fjws\u002Fmulti-signature#verifymulti",[246,1444,1445],{},"verifyMulti"," is enough:",[238,1448,1451],{"className":240,"code":1449,"filename":1450,"language":243,"meta":244,"style":244},"import { verifyMulti } from \"unjwt\u002Fjws\";\n\nconst allPublicKeys = {\n  keys: [userKey.publicKey, serviceKey.publicKey, notaryKey.publicKey],\n};\n\nconst { payload, signerIndex } = await verifyMulti(jws, allPublicKeys);\n\u002F\u002F Returns on first valid signature — others are not attempted\n","policy-any.ts",[246,1452,1453,1466,1470,1481,1486,1490,1494,1523],{"__ignoreMap":244},[249,1454,1455,1457,1460,1462,1464],{"class":251,"line":252},[249,1456,256],{"class":255},[249,1458,1459],{"class":259}," { verifyMulti } ",[249,1461,263],{"class":255},[249,1463,421],{"class":266},[249,1465,270],{"class":259},[249,1467,1468],{"class":251,"line":273},[249,1469,277],{"emptyLinePlaceholder":276},[249,1471,1472,1474,1477,1479],{"class":251,"line":280},[249,1473,290],{"class":255},[249,1475,1476],{"class":293}," allPublicKeys",[249,1478,297],{"class":255},[249,1480,439],{"class":259},[249,1482,1483],{"class":251,"line":287},[249,1484,1485],{"class":259},"  keys: [userKey.publicKey, serviceKey.publicKey, notaryKey.publicKey],\n",[249,1487,1488],{"class":251,"line":322},[249,1489,504],{"class":259},[249,1491,1492],{"class":251,"line":327},[249,1493,277],{"emptyLinePlaceholder":276},[249,1495,1496,1498,1501,1504,1507,1509,1512,1515,1517,1520],{"class":251,"line":333},[249,1497,290],{"class":255},[249,1499,1500],{"class":259}," { ",[249,1502,1503],{"class":293},"payload",[249,1505,1506],{"class":259},", ",[249,1508,911],{"class":293},[249,1510,1511],{"class":259}," } ",[249,1513,1514],{"class":255},"=",[249,1516,300],{"class":255},[249,1518,1519],{"class":303}," verifyMulti",[249,1521,1522],{"class":259},"(jws, allPublicKeys);\n",[249,1524,1525],{"class":251,"line":359},[249,1526,1527],{"class":283},"\u002F\u002F Returns on first valid signature — others are not attempted\n",[197,1529,1531],{"id":1530},"other-use-cases","Other use cases",[173,1533,1534],{},"The same pattern scales to:",[1536,1537,1538,1545,1551,1557],"ul",{},[1539,1540,1541,1544],"li",{},[177,1542,1543],{},"Contract co-signing."," A lease signed by tenant, landlord, and guarantor. All three must verify for the contract to be valid.",[1539,1546,1547,1550],{},[177,1548,1549],{},"Key rotation overlap."," During a rotation window, sign with both the old and new key. Either verifies; callers choose.",[1539,1552,1553,1556],{},[177,1554,1555],{},"Hybrid classical + post-quantum."," Sign with a classical scheme (e.g. Ed25519) and a PQC scheme. Systems that support PQC check that signature; older systems use the classical one.",[1539,1558,1559,1562],{},[177,1560,1561],{},"Signed audit trails."," Append-only structures where each new entry is signed by a monotonically larger set of signers.",[197,1564,1566],{"id":1565},"see-also","See also",[1536,1568,1569,1574,1580],{},[1539,1570,1571],{},[182,1572,1573],{"href":46},"JWS multi-signature →",[1539,1575,1576,1579],{},[182,1577,1578],{"href":135},"JWK Sets →"," — for managing many signer keys.",[1539,1581,1582,1585],{},[182,1583,1584],{"href":87},"Authentication basics →"," — the single-signer case.",[1587,1588,1589],"style",{},"html pre.shiki code .so5gQ, html code.shiki .so5gQ{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .slsVL, html code.shiki .slsVL{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .sfrk1, html code.shiki .sfrk1{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .suiK_, html code.shiki .suiK_{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .shcOC, html code.shiki .shcOC{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#B392F0}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sQHwn, html code.shiki .sQHwn{--shiki-light:#E36209;--shiki-default:#FFAB70;--shiki-dark:#FFAB70}",{"title":244,"searchDepth":273,"depth":273,"links":1591},[1592,1593,1594,1595,1601,1602,1603],{"id":199,"depth":273,"text":200},{"id":235,"depth":273,"text":236},{"id":403,"depth":273,"text":37},{"id":699,"depth":273,"text":700,"children":1596},[1597,1598,1599,1600],{"id":720,"depth":280,"text":721},{"id":968,"depth":280,"text":969},{"id":1121,"depth":280,"text":1122},{"id":1312,"depth":280,"text":1313},{"id":1431,"depth":273,"text":1432},{"id":1530,"depth":273,"text":1531},{"id":1565,"depth":273,"text":1566},"md",{"icon":89},{"icon":89},{"title":103,"description":244},"mQE2yQWRyENxfUh3WhXs5f59VzCmIHGh1tgQRcpk3pU",[1610,1611],{"title":99,"path":100,"stem":101,"description":244,"icon":89,"children":-1},{"title":107,"path":108,"stem":109,"description":244,"icon":112,"children":-1},1776888559112]