[{"data":1,"prerenderedAt":2615},["ShallowReactive",2],{"docs:\u002Fdocs\u002Fauthentication":3,"docs:nav":2569},{"id":4,"title":5,"badge":6,"body":7,"description":119,"extension":2562,"meta":2563,"navigation":276,"order":273,"path":2564,"section":2565,"seo":2566,"stem":2567,"__hash__":2568},"docs\u002Fdocs\u002Fauthentication.md","Authentication",null,{"type":8,"value":9,"toc":2551},"minimark",[10,19,32,37,45,110,120,151,155,162,168,175,178,227,231,1224,1227,1418,1438,1442,1988,1998,2002,2288,2292,2297,2336,2340,2387,2397,2401,2408,2503,2518,2522,2532,2547],[11,12,13,14,18],"p",{},"This page is the storage API at ",[15,16,17],"code",{},"index.tessera.storage",". No sessions, no bearer tokens, no\ncookies. Each request is signed and expires on its own.",[11,20,21,22,25,26,31],{},"The account API at ",[15,23,24],{},"https:\u002F\u002Ftessera.storage\u002Fapi\u002Fv1"," is different: an agent API key. See\n",[27,28,30],"a",{"href":29},"\u002Fdocs\u002Faccounts","Accounts & billing",". Nothing on this page applies to it.",[33,34,36],"h2",{"id":35},"the-three-query-parameters","The three query parameters",[11,38,39,40,44],{},"Every authenticated request carries these as ",[41,42,43],"strong",{},"query parameters"," — not headers:",[46,47,48,61],"table",{},[49,50,51],"thead",{},[52,53,54,58],"tr",{},[55,56,57],"th",{},"Parameter",[55,59,60],{},"Value",[62,63,64,83,97],"tbody",{},[52,65,66,72],{},[67,68,69],"td",{},[15,70,71],{},"credential",[67,73,74,75,78,79,82],{},"Your ed25519 ",[41,76,77],{},"public"," key, base64url encoded ",[41,80,81],{},"with padding",". This is also your account identifier.",[52,84,85,90],{},[67,86,87],{},[15,88,89],{},"validUntil",[67,91,92,93,96],{},"Unix timestamp in ",[41,94,95],{},"seconds"," at which the signature expires. You choose it.",[52,98,99,104],{},[67,100,101],{},[15,102,103],{},"signature",[67,105,106,107,109],{},"The ed25519 signature, base64url encoded ",[41,108,81],{},".",[111,112,117],"pre",{"className":113,"code":115,"language":116},[114],"language-text","POST \u002Fprepare-write?credential=Aq3f…%3D&validUntil=1775212800&signature=9Kd…%3D%3D\n","text",[15,118,115],{"__ignoreMap":119},"",[121,122,125],"app-callout",{"title":123,"tone":124},"Both encodings are padded, and it is checked","warn",[11,126,127,129,130,132,133,136,137,140,141,144,145,129,147,150],{},[15,128,71],{}," and ",[15,131,103],{}," are decoded with padded base64url. A 32-byte key produces a trailing\n",[15,134,135],{},"=","; a 64-byte signature produces a trailing ",[15,138,139],{},"==",". Strip the padding and you get\n",[15,142,143],{},"401 invalid credential",". Note also that the parameters must be URL-encoded in the query string,\nsince ",[15,146,135],{},[15,148,149],{},"+","-adjacent characters appear in base64url output.",[33,152,154],{"id":153},"what-gets-hashed","What gets hashed",[11,156,157,158,161],{},"The signature is over a ",[41,159,160],{},"blake2b-256"," digest — not SHA-256 — of five values concatenated with no\nseparators and no length prefixes:",[111,163,166],{"className":164,"code":165,"language":116},[114],"digest = blake2b256(\n    method            \u002F\u002F ASCII, e.g. \"POST\"\n  ‖ host              \u002F\u002F the HTTP Host header value, e.g. \"index.tessera.storage\"\n  ‖ path              \u002F\u002F path only, e.g. \"\u002Fprepare-write\"\n  ‖ uint64le(validUntil)   \u002F\u002F 8 bytes, LITTLE-endian\n  ‖ body              \u002F\u002F raw request body bytes; omitted entirely if empty\n)\n\nsignature = ed25519_sign(privateKey, digest)\n",[15,167,165],{"__ignoreMap":119},[11,169,170,171,174],{},"Then verification is ",[15,172,173],{},"ed25519.Verify(publicKey, digest, signature)"," — the signature is over the\n32-byte digest, not over the message.",[11,176,177],{},"Three details that account for most failed integrations:",[179,180,181,200,211],"ol",{},[182,183,184,187,188,190,191,190,193,195,196,199],"li",{},[41,185,186],{},"The query string is not hashed."," Only the path. So ",[15,189,71],{},", ",[15,192,89],{},[15,194,103],{},",\nand any other query parameter such as ",[15,197,198],{},"?limit=50"," are outside the signature's protection.",[182,201,202,210],{},[41,203,204,206,207],{},[15,205,89],{}," is hashed as a little-endian ",[15,208,209],{},"uint64",", while being transmitted in the query\nstring as an ASCII decimal. These are different representations of the same number and both are\nrequired.",[182,212,213,223,224,226],{},[41,214,215,218,219,222],{},[15,216,217],{},"host"," must be byte-identical to the ",[15,220,221],{},"Host"," header the server receives"," — including the port\nif it is non-default. If you terminate TLS at a proxy that rewrites ",[15,225,221],{},", sign the value the\norigin sees, not the one you dialled.",[33,228,230],{"id":229},"typescript","TypeScript",[111,232,236],{"className":233,"code":234,"language":235,"meta":119,"style":119},"language-ts shiki shiki-themes github-dark-default github-dark-default","import { blake2b } from '@noble\u002Fhashes\u002Fblake2b'\nimport { ed25519 } from '@noble\u002Fcurves\u002Fed25519'\n\n\u002F** base64url WITH padding, as the API requires. *\u002F\nfunction b64urlPad(bytes: Uint8Array): string {\n  let s = ''\n  for (const b of bytes) s += String.fromCharCode(b)\n  return btoa(s).replace(\u002F\\+\u002Fg, '-').replace(\u002F\\\u002F\u002Fg, '_')\n}\n\nfunction b64urlDecode(s: string): Uint8Array {\n  const b64 = s.replace(\u002F-\u002Fg, '+').replace(\u002F_\u002Fg, '\u002F')\n  const pad = b64 + '='.repeat((4 - (b64.length % 4)) % 4)\n  return Uint8Array.from(atob(pad), (c) => c.charCodeAt(0))\n}\n\nexport interface Credential {\n  \u002F** app_key from your storage credential — base64url, unpadded, 64 bytes decoded. *\u002F\n  appKey: string\n  \u002F** app_id (public half) from your storage credential — base64url, padded, 32 bytes decoded. *\u002F\n  credential: string\n}\n\nexport function signRequest(\n  cred: Credential,\n  method: string,\n  host: string,\n  path: string,\n  body: Uint8Array | null,\n  ttlSeconds = 60,\n): URLSearchParams {\n  const validUntil = Math.floor(Date.now() \u002F 1000) + ttlSeconds\n\n  const enc = new TextEncoder()\n  const le = new Uint8Array(8)\n  new DataView(le.buffer).setBigUint64(0, BigInt(validUntil), true) \u002F\u002F little-endian\n\n  const parts = [enc.encode(method), enc.encode(host), enc.encode(path), le]\n  if (body && body.length) parts.push(body)\n\n  const total = parts.reduce((n, p) => n + p.length, 0)\n  const message = new Uint8Array(total)\n  let off = 0\n  for (const p of parts) {\n    message.set(p, off)\n    off += p.length\n  }\n\n  const digest = blake2b(message, { dkLen: 32 })\n\n  \u002F\u002F appKey decodes to 64 bytes (seed ‖ public); ed25519 signing takes the 32-byte seed.\n  const seed = b64urlDecode(cred.appKey).slice(0, 32)\n  const signature = ed25519.sign(digest, seed)\n\n  return new URLSearchParams({\n    credential: cred.credential,\n    validUntil: String(validUntil),\n    signature: b64urlPad(signature),\n  })\n}\n","ts",[15,237,238,258,271,278,285,320,334,367,425,431,436,461,508,562,603,608,613,627,633,644,650,660,665,670,684,697,709,721,733,751,764,776,813,818,837,858,893,898,927,953,958,1003,1020,1033,1050,1062,1075,1081,1086,1108,1113,1119,1147,1166,1171,1183,1189,1201,1213,1219],{"__ignoreMap":119},[239,240,243,247,251,254],"span",{"class":241,"line":242},"line",1,[239,244,246],{"class":245},"srH4v","import",[239,248,250],{"class":249},"sb7RM"," { blake2b } ",[239,252,253],{"class":245},"from",[239,255,257],{"class":256},"skZ57"," '@noble\u002Fhashes\u002Fblake2b'\n",[239,259,261,263,266,268],{"class":241,"line":260},2,[239,262,246],{"class":245},[239,264,265],{"class":249}," { ed25519 } ",[239,267,253],{"class":245},[239,269,270],{"class":256}," '@noble\u002Fcurves\u002Fed25519'\n",[239,272,274],{"class":241,"line":273},3,[239,275,277],{"emptyLinePlaceholder":276},true,"\n",[239,279,281],{"class":241,"line":280},4,[239,282,284],{"class":283},"sI3U9","\u002F** base64url WITH padding, as the API requires. *\u002F\n",[239,286,288,291,295,298,302,305,308,311,313,317],{"class":241,"line":287},5,[239,289,290],{"class":245},"function",[239,292,294],{"class":293},"sYGIp"," b64urlPad",[239,296,297],{"class":249},"(",[239,299,301],{"class":300},"sAxfE","bytes",[239,303,304],{"class":245},":",[239,306,307],{"class":300}," Uint8Array",[239,309,310],{"class":249},")",[239,312,304],{"class":245},[239,314,316],{"class":315},"sEQcL"," string",[239,318,319],{"class":249}," {\n",[239,321,323,326,329,331],{"class":241,"line":322},6,[239,324,325],{"class":245},"  let",[239,327,328],{"class":249}," s ",[239,330,135],{"class":245},[239,332,333],{"class":256}," ''\n",[239,335,337,340,343,346,349,352,355,358,361,364],{"class":241,"line":336},7,[239,338,339],{"class":245},"  for",[239,341,342],{"class":249}," (",[239,344,345],{"class":245},"const",[239,347,348],{"class":315}," b",[239,350,351],{"class":245}," of",[239,353,354],{"class":249}," bytes) s ",[239,356,357],{"class":245},"+=",[239,359,360],{"class":249}," String.",[239,362,363],{"class":293},"fromCharCode",[239,365,366],{"class":249},"(b)\n",[239,368,370,373,376,379,382,384,387,391,393,396,398,401,404,406,408,410,413,415,417,419,422],{"class":241,"line":369},8,[239,371,372],{"class":245},"  return",[239,374,375],{"class":293}," btoa",[239,377,378],{"class":249},"(s).",[239,380,381],{"class":293},"replace",[239,383,297],{"class":249},[239,385,386],{"class":256},"\u002F",[239,388,390],{"class":389},"sq3Sb","\\+",[239,392,386],{"class":256},[239,394,395],{"class":245},"g",[239,397,190],{"class":249},[239,399,400],{"class":256},"'-'",[239,402,403],{"class":249},").",[239,405,381],{"class":293},[239,407,297],{"class":249},[239,409,386],{"class":256},[239,411,412],{"class":389},"\\\u002F",[239,414,386],{"class":256},[239,416,395],{"class":245},[239,418,190],{"class":249},[239,420,421],{"class":256},"'_'",[239,423,424],{"class":249},")\n",[239,426,428],{"class":241,"line":427},9,[239,429,430],{"class":249},"}\n",[239,432,434],{"class":241,"line":433},10,[239,435,277],{"emptyLinePlaceholder":276},[239,437,439,441,444,446,449,451,453,455,457,459],{"class":241,"line":438},11,[239,440,290],{"class":245},[239,442,443],{"class":293}," b64urlDecode",[239,445,297],{"class":249},[239,447,448],{"class":300},"s",[239,450,304],{"class":245},[239,452,316],{"class":315},[239,454,310],{"class":249},[239,456,304],{"class":245},[239,458,307],{"class":300},[239,460,319],{"class":249},[239,462,464,467,470,473,476,478,480,483,485,487,490,492,494,496,499,501,503,506],{"class":241,"line":463},12,[239,465,466],{"class":245},"  const",[239,468,469],{"class":315}," b64",[239,471,472],{"class":245}," =",[239,474,475],{"class":249}," s.",[239,477,381],{"class":293},[239,479,297],{"class":249},[239,481,482],{"class":256},"\u002F-\u002F",[239,484,395],{"class":245},[239,486,190],{"class":249},[239,488,489],{"class":256},"'+'",[239,491,403],{"class":249},[239,493,381],{"class":293},[239,495,297],{"class":249},[239,497,498],{"class":256},"\u002F_\u002F",[239,500,395],{"class":245},[239,502,190],{"class":249},[239,504,505],{"class":256},"'\u002F'",[239,507,424],{"class":249},[239,509,511,513,516,518,521,523,526,528,531,534,537,540,543,546,549,552,555,558,560],{"class":241,"line":510},13,[239,512,466],{"class":245},[239,514,515],{"class":315}," pad",[239,517,472],{"class":245},[239,519,520],{"class":249}," b64 ",[239,522,149],{"class":245},[239,524,525],{"class":256}," '='",[239,527,109],{"class":249},[239,529,530],{"class":293},"repeat",[239,532,533],{"class":249},"((",[239,535,536],{"class":315},"4",[239,538,539],{"class":245}," -",[239,541,542],{"class":249}," (b64.",[239,544,545],{"class":315},"length",[239,547,548],{"class":245}," %",[239,550,551],{"class":315}," 4",[239,553,554],{"class":249},")) ",[239,556,557],{"class":245},"%",[239,559,551],{"class":315},[239,561,424],{"class":249},[239,563,565,567,570,572,574,577,580,583,586,589,592,595,597,600],{"class":241,"line":564},14,[239,566,372],{"class":245},[239,568,569],{"class":249}," Uint8Array.",[239,571,253],{"class":293},[239,573,297],{"class":249},[239,575,576],{"class":293},"atob",[239,578,579],{"class":249},"(pad), (",[239,581,582],{"class":300},"c",[239,584,585],{"class":249},") ",[239,587,588],{"class":245},"=>",[239,590,591],{"class":249}," c.",[239,593,594],{"class":293},"charCodeAt",[239,596,297],{"class":249},[239,598,599],{"class":315},"0",[239,601,602],{"class":249},"))\n",[239,604,606],{"class":241,"line":605},15,[239,607,430],{"class":249},[239,609,611],{"class":241,"line":610},16,[239,612,277],{"emptyLinePlaceholder":276},[239,614,616,619,622,625],{"class":241,"line":615},17,[239,617,618],{"class":245},"export",[239,620,621],{"class":245}," interface",[239,623,624],{"class":300}," Credential",[239,626,319],{"class":249},[239,628,630],{"class":241,"line":629},18,[239,631,632],{"class":283},"  \u002F** app_key from your storage credential — base64url, unpadded, 64 bytes decoded. *\u002F\n",[239,634,636,639,641],{"class":241,"line":635},19,[239,637,638],{"class":300},"  appKey",[239,640,304],{"class":245},[239,642,643],{"class":315}," string\n",[239,645,647],{"class":241,"line":646},20,[239,648,649],{"class":283},"  \u002F** app_id (public half) from your storage credential — base64url, padded, 32 bytes decoded. *\u002F\n",[239,651,653,656,658],{"class":241,"line":652},21,[239,654,655],{"class":300},"  credential",[239,657,304],{"class":245},[239,659,643],{"class":315},[239,661,663],{"class":241,"line":662},22,[239,664,430],{"class":249},[239,666,668],{"class":241,"line":667},23,[239,669,277],{"emptyLinePlaceholder":276},[239,671,673,675,678,681],{"class":241,"line":672},24,[239,674,618],{"class":245},[239,676,677],{"class":245}," function",[239,679,680],{"class":293}," signRequest",[239,682,683],{"class":249},"(\n",[239,685,687,690,692,694],{"class":241,"line":686},25,[239,688,689],{"class":300},"  cred",[239,691,304],{"class":245},[239,693,624],{"class":300},[239,695,696],{"class":249},",\n",[239,698,700,703,705,707],{"class":241,"line":699},26,[239,701,702],{"class":300},"  method",[239,704,304],{"class":245},[239,706,316],{"class":315},[239,708,696],{"class":249},[239,710,712,715,717,719],{"class":241,"line":711},27,[239,713,714],{"class":300},"  host",[239,716,304],{"class":245},[239,718,316],{"class":315},[239,720,696],{"class":249},[239,722,724,727,729,731],{"class":241,"line":723},28,[239,725,726],{"class":300},"  path",[239,728,304],{"class":245},[239,730,316],{"class":315},[239,732,696],{"class":249},[239,734,736,739,741,743,746,749],{"class":241,"line":735},29,[239,737,738],{"class":300},"  body",[239,740,304],{"class":245},[239,742,307],{"class":300},[239,744,745],{"class":245}," |",[239,747,748],{"class":315}," null",[239,750,696],{"class":249},[239,752,754,757,759,762],{"class":241,"line":753},30,[239,755,756],{"class":300},"  ttlSeconds",[239,758,472],{"class":245},[239,760,761],{"class":315}," 60",[239,763,696],{"class":249},[239,765,767,769,771,774],{"class":241,"line":766},31,[239,768,310],{"class":249},[239,770,304],{"class":245},[239,772,773],{"class":300}," URLSearchParams",[239,775,319],{"class":249},[239,777,779,781,784,786,789,792,795,798,801,803,806,808,810],{"class":241,"line":778},32,[239,780,466],{"class":245},[239,782,783],{"class":315}," validUntil",[239,785,472],{"class":245},[239,787,788],{"class":249}," Math.",[239,790,791],{"class":293},"floor",[239,793,794],{"class":249},"(Date.",[239,796,797],{"class":293},"now",[239,799,800],{"class":249},"() ",[239,802,386],{"class":245},[239,804,805],{"class":315}," 1000",[239,807,585],{"class":249},[239,809,149],{"class":245},[239,811,812],{"class":249}," ttlSeconds\n",[239,814,816],{"class":241,"line":815},33,[239,817,277],{"emptyLinePlaceholder":276},[239,819,821,823,826,828,831,834],{"class":241,"line":820},34,[239,822,466],{"class":245},[239,824,825],{"class":315}," enc",[239,827,472],{"class":245},[239,829,830],{"class":245}," new",[239,832,833],{"class":293}," TextEncoder",[239,835,836],{"class":249},"()\n",[239,838,840,842,845,847,849,851,853,856],{"class":241,"line":839},35,[239,841,466],{"class":245},[239,843,844],{"class":315}," le",[239,846,472],{"class":245},[239,848,830],{"class":245},[239,850,307],{"class":293},[239,852,297],{"class":249},[239,854,855],{"class":315},"8",[239,857,424],{"class":249},[239,859,861,864,867,870,873,875,877,879,882,885,888,890],{"class":241,"line":860},36,[239,862,863],{"class":245},"  new",[239,865,866],{"class":293}," DataView",[239,868,869],{"class":249},"(le.buffer).",[239,871,872],{"class":293},"setBigUint64",[239,874,297],{"class":249},[239,876,599],{"class":315},[239,878,190],{"class":249},[239,880,881],{"class":293},"BigInt",[239,883,884],{"class":249},"(validUntil), ",[239,886,887],{"class":315},"true",[239,889,585],{"class":249},[239,891,892],{"class":283},"\u002F\u002F little-endian\n",[239,894,896],{"class":241,"line":895},37,[239,897,277],{"emptyLinePlaceholder":276},[239,899,901,903,906,908,911,914,917,919,922,924],{"class":241,"line":900},38,[239,902,466],{"class":245},[239,904,905],{"class":315}," parts",[239,907,472],{"class":245},[239,909,910],{"class":249}," [enc.",[239,912,913],{"class":293},"encode",[239,915,916],{"class":249},"(method), enc.",[239,918,913],{"class":293},[239,920,921],{"class":249},"(host), enc.",[239,923,913],{"class":293},[239,925,926],{"class":249},"(path), le]\n",[239,928,930,933,936,939,942,944,947,950],{"class":241,"line":929},39,[239,931,932],{"class":245},"  if",[239,934,935],{"class":249}," (body ",[239,937,938],{"class":245},"&&",[239,940,941],{"class":249}," body.",[239,943,545],{"class":315},[239,945,946],{"class":249},") parts.",[239,948,949],{"class":293},"push",[239,951,952],{"class":249},"(body)\n",[239,954,956],{"class":241,"line":955},40,[239,957,277],{"emptyLinePlaceholder":276},[239,959,961,963,966,968,971,974,976,979,981,983,985,987,990,992,995,997,999,1001],{"class":241,"line":960},41,[239,962,466],{"class":245},[239,964,965],{"class":315}," total",[239,967,472],{"class":245},[239,969,970],{"class":249}," parts.",[239,972,973],{"class":293},"reduce",[239,975,533],{"class":249},[239,977,978],{"class":300},"n",[239,980,190],{"class":249},[239,982,11],{"class":300},[239,984,585],{"class":249},[239,986,588],{"class":245},[239,988,989],{"class":249}," n ",[239,991,149],{"class":245},[239,993,994],{"class":249}," p.",[239,996,545],{"class":315},[239,998,190],{"class":249},[239,1000,599],{"class":315},[239,1002,424],{"class":249},[239,1004,1006,1008,1011,1013,1015,1017],{"class":241,"line":1005},42,[239,1007,466],{"class":245},[239,1009,1010],{"class":315}," message",[239,1012,472],{"class":245},[239,1014,830],{"class":245},[239,1016,307],{"class":293},[239,1018,1019],{"class":249},"(total)\n",[239,1021,1023,1025,1028,1030],{"class":241,"line":1022},43,[239,1024,325],{"class":245},[239,1026,1027],{"class":249}," off ",[239,1029,135],{"class":245},[239,1031,1032],{"class":315}," 0\n",[239,1034,1036,1038,1040,1042,1045,1047],{"class":241,"line":1035},44,[239,1037,339],{"class":245},[239,1039,342],{"class":249},[239,1041,345],{"class":245},[239,1043,1044],{"class":315}," p",[239,1046,351],{"class":245},[239,1048,1049],{"class":249}," parts) {\n",[239,1051,1053,1056,1059],{"class":241,"line":1052},45,[239,1054,1055],{"class":249},"    message.",[239,1057,1058],{"class":293},"set",[239,1060,1061],{"class":249},"(p, off)\n",[239,1063,1065,1068,1070,1072],{"class":241,"line":1064},46,[239,1066,1067],{"class":249},"    off ",[239,1069,357],{"class":245},[239,1071,994],{"class":249},[239,1073,1074],{"class":315},"length\n",[239,1076,1078],{"class":241,"line":1077},47,[239,1079,1080],{"class":249},"  }\n",[239,1082,1084],{"class":241,"line":1083},48,[239,1085,277],{"emptyLinePlaceholder":276},[239,1087,1089,1091,1094,1096,1099,1102,1105],{"class":241,"line":1088},49,[239,1090,466],{"class":245},[239,1092,1093],{"class":315}," digest",[239,1095,472],{"class":245},[239,1097,1098],{"class":293}," blake2b",[239,1100,1101],{"class":249},"(message, { dkLen: ",[239,1103,1104],{"class":315},"32",[239,1106,1107],{"class":249}," })\n",[239,1109,1111],{"class":241,"line":1110},50,[239,1112,277],{"emptyLinePlaceholder":276},[239,1114,1116],{"class":241,"line":1115},51,[239,1117,1118],{"class":283},"  \u002F\u002F appKey decodes to 64 bytes (seed ‖ public); ed25519 signing takes the 32-byte seed.\n",[239,1120,1122,1124,1127,1129,1131,1134,1137,1139,1141,1143,1145],{"class":241,"line":1121},52,[239,1123,466],{"class":245},[239,1125,1126],{"class":315}," seed",[239,1128,472],{"class":245},[239,1130,443],{"class":293},[239,1132,1133],{"class":249},"(cred.appKey).",[239,1135,1136],{"class":293},"slice",[239,1138,297],{"class":249},[239,1140,599],{"class":315},[239,1142,190],{"class":249},[239,1144,1104],{"class":315},[239,1146,424],{"class":249},[239,1148,1150,1152,1155,1157,1160,1163],{"class":241,"line":1149},53,[239,1151,466],{"class":245},[239,1153,1154],{"class":315}," signature",[239,1156,472],{"class":245},[239,1158,1159],{"class":249}," ed25519.",[239,1161,1162],{"class":293},"sign",[239,1164,1165],{"class":249},"(digest, seed)\n",[239,1167,1169],{"class":241,"line":1168},54,[239,1170,277],{"emptyLinePlaceholder":276},[239,1172,1174,1176,1178,1180],{"class":241,"line":1173},55,[239,1175,372],{"class":245},[239,1177,830],{"class":245},[239,1179,773],{"class":293},[239,1181,1182],{"class":249},"({\n",[239,1184,1186],{"class":241,"line":1185},56,[239,1187,1188],{"class":249},"    credential: cred.credential,\n",[239,1190,1192,1195,1198],{"class":241,"line":1191},57,[239,1193,1194],{"class":249},"    validUntil: ",[239,1196,1197],{"class":293},"String",[239,1199,1200],{"class":249},"(validUntil),\n",[239,1202,1204,1207,1210],{"class":241,"line":1203},58,[239,1205,1206],{"class":249},"    signature: ",[239,1208,1209],{"class":293},"b64urlPad",[239,1211,1212],{"class":249},"(signature),\n",[239,1214,1216],{"class":241,"line":1215},59,[239,1217,1218],{"class":249},"  })\n",[239,1220,1222],{"class":241,"line":1221},60,[239,1223,430],{"class":249},[11,1225,1226],{},"Using it:",[111,1228,1230],{"className":233,"code":1229,"language":235,"meta":119,"style":119},"const base = new URL('https:\u002F\u002Findex.tessera.storage')\nconst path = '\u002Fprepare-write'\nconst body = new TextEncoder().encode(JSON.stringify({ totalShards: 30, minShards: 10 }))\n\nconst params = signRequest(cred, 'POST', base.host, path, body)\n\nconst res = await fetch(`${base.origin}${path}?${params}`, {\n  method: 'POST',\n  headers: { 'content-type': 'application\u002Fjson' },\n  body,\n})\n",[15,1231,1232,1253,1265,1308,1312,1332,1336,1382,1391,1408,1413],{"__ignoreMap":119},[239,1233,1234,1236,1239,1241,1243,1246,1248,1251],{"class":241,"line":242},[239,1235,345],{"class":245},[239,1237,1238],{"class":315}," base",[239,1240,472],{"class":245},[239,1242,830],{"class":245},[239,1244,1245],{"class":293}," URL",[239,1247,297],{"class":249},[239,1249,1250],{"class":256},"'https:\u002F\u002Findex.tessera.storage'",[239,1252,424],{"class":249},[239,1254,1255,1257,1260,1262],{"class":241,"line":260},[239,1256,345],{"class":245},[239,1258,1259],{"class":315}," path",[239,1261,472],{"class":245},[239,1263,1264],{"class":256}," '\u002Fprepare-write'\n",[239,1266,1267,1269,1272,1274,1276,1278,1281,1283,1285,1288,1290,1293,1296,1299,1302,1305],{"class":241,"line":273},[239,1268,345],{"class":245},[239,1270,1271],{"class":315}," body",[239,1273,472],{"class":245},[239,1275,830],{"class":245},[239,1277,833],{"class":293},[239,1279,1280],{"class":249},"().",[239,1282,913],{"class":293},[239,1284,297],{"class":249},[239,1286,1287],{"class":315},"JSON",[239,1289,109],{"class":249},[239,1291,1292],{"class":293},"stringify",[239,1294,1295],{"class":249},"({ totalShards: ",[239,1297,1298],{"class":315},"30",[239,1300,1301],{"class":249},", minShards: ",[239,1303,1304],{"class":315},"10",[239,1306,1307],{"class":249}," }))\n",[239,1309,1310],{"class":241,"line":280},[239,1311,277],{"emptyLinePlaceholder":276},[239,1313,1314,1316,1319,1321,1323,1326,1329],{"class":241,"line":287},[239,1315,345],{"class":245},[239,1317,1318],{"class":315}," params",[239,1320,472],{"class":245},[239,1322,680],{"class":293},[239,1324,1325],{"class":249},"(cred, ",[239,1327,1328],{"class":256},"'POST'",[239,1330,1331],{"class":249},", base.host, path, body)\n",[239,1333,1334],{"class":241,"line":322},[239,1335,277],{"emptyLinePlaceholder":276},[239,1337,1338,1340,1343,1345,1348,1351,1353,1356,1359,1361,1364,1367,1370,1373,1376,1379],{"class":241,"line":336},[239,1339,345],{"class":245},[239,1341,1342],{"class":315}," res",[239,1344,472],{"class":245},[239,1346,1347],{"class":245}," await",[239,1349,1350],{"class":293}," fetch",[239,1352,297],{"class":249},[239,1354,1355],{"class":256},"`${",[239,1357,1358],{"class":249},"base",[239,1360,109],{"class":256},[239,1362,1363],{"class":249},"origin",[239,1365,1366],{"class":256},"}${",[239,1368,1369],{"class":249},"path",[239,1371,1372],{"class":256},"}?${",[239,1374,1375],{"class":249},"params",[239,1377,1378],{"class":256},"}`",[239,1380,1381],{"class":249},", {\n",[239,1383,1384,1387,1389],{"class":241,"line":369},[239,1385,1386],{"class":249},"  method: ",[239,1388,1328],{"class":256},[239,1390,696],{"class":249},[239,1392,1393,1396,1399,1402,1405],{"class":241,"line":427},[239,1394,1395],{"class":249},"  headers: { ",[239,1397,1398],{"class":256},"'content-type'",[239,1400,1401],{"class":249},": ",[239,1403,1404],{"class":256},"'application\u002Fjson'",[239,1406,1407],{"class":249}," },\n",[239,1409,1410],{"class":241,"line":433},[239,1411,1412],{"class":249},"  body,\n",[239,1414,1415],{"class":241,"line":438},[239,1416,1417],{"class":249},"})\n",[121,1419,1422],{"title":1420,"tone":1421},"Note the `host` value","note",[11,1423,1424,1427,1428,1430,1431,1433,1434,1437],{},[15,1425,1426],{},"base.host"," gives ",[15,1429,17],{}," — hostname plus port when present, without the scheme.\nThat is exactly what the ",[15,1432,221],{}," header carries and exactly what must be hashed. ",[15,1435,1436],{},"base.hostname","\nwould drop the port and break signing against a non-default port.",[33,1439,1441],{"id":1440},"go","Go",[111,1443,1446],{"className":1444,"code":1445,"language":1440,"meta":119,"style":119},"language-go shiki shiki-themes github-dark-default github-dark-default","package tessera\n\nimport (\n    \"crypto\u002Fed25519\"\n    \"encoding\u002Fbase64\"\n    \"encoding\u002Fbinary\"\n    \"net\u002Furl\"\n    \"strconv\"\n    \"time\"\n\n    \"golang.org\u002Fx\u002Fcrypto\u002Fblake2b\"\n)\n\n\u002F\u002F SignParams returns the credential, validUntil, and signature query parameters.\nfunc SignParams(appKey, credential, method, host, path string, body []byte, ttl time.Duration) (url.Values, error) {\n    priv, err := base64.RawURLEncoding.DecodeString(appKey)\n    if err != nil {\n        return nil, err\n    }\n\n    validUntil := time.Now().Add(ttl).Unix()\n\n    h, err := blake2b.New256(nil)\n    if err != nil {\n        return nil, err\n    }\n    h.Write([]byte(method))\n    h.Write([]byte(host))\n    h.Write([]byte(path))\n\n    var le [8]byte\n    binary.LittleEndian.PutUint64(le[:], uint64(validUntil))\n    h.Write(le[:])\n\n    if len(body) > 0 {\n        h.Write(body)\n    }\n    digest := h.Sum(nil)\n\n    sig := ed25519.Sign(ed25519.PrivateKey(priv), digest)\n\n    return url.Values{\n        \"credential\": {credential},\n        \"validUntil\": {strconv.FormatInt(validUntil, 10)},\n        \"signature\":  {base64.URLEncoding.EncodeToString(sig)},\n    }, nil\n}\n",[15,1447,1448,1456,1460,1467,1478,1487,1496,1505,1514,1523,1527,1536,1540,1544,1549,1624,1641,1657,1667,1672,1676,1702,1706,1726,1738,1746,1750,1766,1779,1792,1796,1812,1828,1837,1841,1859,1868,1872,1891,1895,1916,1920,1935,1943,1962,1976,1984],{"__ignoreMap":119},[239,1449,1450,1453],{"class":241,"line":242},[239,1451,1452],{"class":245},"package",[239,1454,1455],{"class":300}," tessera\n",[239,1457,1458],{"class":241,"line":260},[239,1459,277],{"emptyLinePlaceholder":276},[239,1461,1462,1464],{"class":241,"line":273},[239,1463,246],{"class":245},[239,1465,1466],{"class":249}," (\n",[239,1468,1469,1472,1475],{"class":241,"line":280},[239,1470,1471],{"class":256},"    \"",[239,1473,1474],{"class":300},"crypto\u002Fed25519",[239,1476,1477],{"class":256},"\"\n",[239,1479,1480,1482,1485],{"class":241,"line":287},[239,1481,1471],{"class":256},[239,1483,1484],{"class":300},"encoding\u002Fbase64",[239,1486,1477],{"class":256},[239,1488,1489,1491,1494],{"class":241,"line":322},[239,1490,1471],{"class":256},[239,1492,1493],{"class":300},"encoding\u002Fbinary",[239,1495,1477],{"class":256},[239,1497,1498,1500,1503],{"class":241,"line":336},[239,1499,1471],{"class":256},[239,1501,1502],{"class":300},"net\u002Furl",[239,1504,1477],{"class":256},[239,1506,1507,1509,1512],{"class":241,"line":369},[239,1508,1471],{"class":256},[239,1510,1511],{"class":300},"strconv",[239,1513,1477],{"class":256},[239,1515,1516,1518,1521],{"class":241,"line":427},[239,1517,1471],{"class":256},[239,1519,1520],{"class":300},"time",[239,1522,1477],{"class":256},[239,1524,1525],{"class":241,"line":433},[239,1526,277],{"emptyLinePlaceholder":276},[239,1528,1529,1531,1534],{"class":241,"line":438},[239,1530,1471],{"class":256},[239,1532,1533],{"class":300},"golang.org\u002Fx\u002Fcrypto\u002Fblake2b",[239,1535,1477],{"class":256},[239,1537,1538],{"class":241,"line":463},[239,1539,424],{"class":249},[239,1541,1542],{"class":241,"line":510},[239,1543,277],{"emptyLinePlaceholder":276},[239,1545,1546],{"class":241,"line":564},[239,1547,1548],{"class":283},"\u002F\u002F SignParams returns the credential, validUntil, and signature query parameters.\n",[239,1550,1551,1554,1557,1559,1562,1564,1566,1568,1571,1573,1575,1577,1579,1581,1583,1586,1589,1592,1594,1597,1600,1602,1605,1608,1611,1613,1616,1618,1621],{"class":241,"line":605},[239,1552,1553],{"class":245},"func",[239,1555,1556],{"class":293}," SignParams",[239,1558,297],{"class":249},[239,1560,1561],{"class":300},"appKey",[239,1563,190],{"class":249},[239,1565,71],{"class":300},[239,1567,190],{"class":249},[239,1569,1570],{"class":300},"method",[239,1572,190],{"class":249},[239,1574,217],{"class":300},[239,1576,190],{"class":249},[239,1578,1369],{"class":300},[239,1580,316],{"class":245},[239,1582,190],{"class":249},[239,1584,1585],{"class":300},"body",[239,1587,1588],{"class":249}," []",[239,1590,1591],{"class":245},"byte",[239,1593,190],{"class":249},[239,1595,1596],{"class":300},"ttl",[239,1598,1599],{"class":300}," time",[239,1601,109],{"class":249},[239,1603,1604],{"class":300},"Duration",[239,1606,1607],{"class":249},") (",[239,1609,1610],{"class":300},"url",[239,1612,109],{"class":249},[239,1614,1615],{"class":300},"Values",[239,1617,190],{"class":249},[239,1619,1620],{"class":245},"error",[239,1622,1623],{"class":249},") {\n",[239,1625,1626,1629,1632,1635,1638],{"class":241,"line":610},[239,1627,1628],{"class":249},"    priv, err ",[239,1630,1631],{"class":245},":=",[239,1633,1634],{"class":249}," base64.RawURLEncoding.",[239,1636,1637],{"class":293},"DecodeString",[239,1639,1640],{"class":249},"(appKey)\n",[239,1642,1643,1646,1649,1652,1655],{"class":241,"line":615},[239,1644,1645],{"class":245},"    if",[239,1647,1648],{"class":249}," err ",[239,1650,1651],{"class":245},"!=",[239,1653,1654],{"class":315}," nil",[239,1656,319],{"class":249},[239,1658,1659,1662,1664],{"class":241,"line":629},[239,1660,1661],{"class":245},"        return",[239,1663,1654],{"class":315},[239,1665,1666],{"class":249},", err\n",[239,1668,1669],{"class":241,"line":635},[239,1670,1671],{"class":249},"    }\n",[239,1673,1674],{"class":241,"line":646},[239,1675,277],{"emptyLinePlaceholder":276},[239,1677,1678,1681,1683,1686,1689,1691,1694,1697,1700],{"class":241,"line":652},[239,1679,1680],{"class":249},"    validUntil ",[239,1682,1631],{"class":245},[239,1684,1685],{"class":249}," time.",[239,1687,1688],{"class":293},"Now",[239,1690,1280],{"class":249},[239,1692,1693],{"class":293},"Add",[239,1695,1696],{"class":249},"(ttl).",[239,1698,1699],{"class":293},"Unix",[239,1701,836],{"class":249},[239,1703,1704],{"class":241,"line":662},[239,1705,277],{"emptyLinePlaceholder":276},[239,1707,1708,1711,1713,1716,1719,1721,1724],{"class":241,"line":667},[239,1709,1710],{"class":249},"    h, err ",[239,1712,1631],{"class":245},[239,1714,1715],{"class":249}," blake2b.",[239,1717,1718],{"class":293},"New256",[239,1720,297],{"class":249},[239,1722,1723],{"class":315},"nil",[239,1725,424],{"class":249},[239,1727,1728,1730,1732,1734,1736],{"class":241,"line":672},[239,1729,1645],{"class":245},[239,1731,1648],{"class":249},[239,1733,1651],{"class":245},[239,1735,1654],{"class":315},[239,1737,319],{"class":249},[239,1739,1740,1742,1744],{"class":241,"line":686},[239,1741,1661],{"class":245},[239,1743,1654],{"class":315},[239,1745,1666],{"class":249},[239,1747,1748],{"class":241,"line":699},[239,1749,1671],{"class":249},[239,1751,1752,1755,1758,1761,1763],{"class":241,"line":711},[239,1753,1754],{"class":249},"    h.",[239,1756,1757],{"class":293},"Write",[239,1759,1760],{"class":249},"([]",[239,1762,1591],{"class":245},[239,1764,1765],{"class":249},"(method))\n",[239,1767,1768,1770,1772,1774,1776],{"class":241,"line":723},[239,1769,1754],{"class":249},[239,1771,1757],{"class":293},[239,1773,1760],{"class":249},[239,1775,1591],{"class":245},[239,1777,1778],{"class":249},"(host))\n",[239,1780,1781,1783,1785,1787,1789],{"class":241,"line":735},[239,1782,1754],{"class":249},[239,1784,1757],{"class":293},[239,1786,1760],{"class":249},[239,1788,1591],{"class":245},[239,1790,1791],{"class":249},"(path))\n",[239,1793,1794],{"class":241,"line":753},[239,1795,277],{"emptyLinePlaceholder":276},[239,1797,1798,1801,1804,1806,1809],{"class":241,"line":766},[239,1799,1800],{"class":245},"    var",[239,1802,1803],{"class":249}," le [",[239,1805,855],{"class":315},[239,1807,1808],{"class":249},"]",[239,1810,1811],{"class":245},"byte\n",[239,1813,1814,1817,1820,1823,1825],{"class":241,"line":778},[239,1815,1816],{"class":249},"    binary.LittleEndian.",[239,1818,1819],{"class":293},"PutUint64",[239,1821,1822],{"class":249},"(le[:], ",[239,1824,209],{"class":245},[239,1826,1827],{"class":249},"(validUntil))\n",[239,1829,1830,1832,1834],{"class":241,"line":815},[239,1831,1754],{"class":249},[239,1833,1757],{"class":293},[239,1835,1836],{"class":249},"(le[:])\n",[239,1838,1839],{"class":241,"line":820},[239,1840,277],{"emptyLinePlaceholder":276},[239,1842,1843,1845,1848,1851,1854,1857],{"class":241,"line":839},[239,1844,1645],{"class":245},[239,1846,1847],{"class":293}," len",[239,1849,1850],{"class":249},"(body) ",[239,1852,1853],{"class":245},">",[239,1855,1856],{"class":315}," 0",[239,1858,319],{"class":249},[239,1860,1861,1864,1866],{"class":241,"line":860},[239,1862,1863],{"class":249},"        h.",[239,1865,1757],{"class":293},[239,1867,952],{"class":249},[239,1869,1870],{"class":241,"line":895},[239,1871,1671],{"class":249},[239,1873,1874,1877,1879,1882,1885,1887,1889],{"class":241,"line":900},[239,1875,1876],{"class":249},"    digest ",[239,1878,1631],{"class":245},[239,1880,1881],{"class":249}," h.",[239,1883,1884],{"class":293},"Sum",[239,1886,297],{"class":249},[239,1888,1723],{"class":315},[239,1890,424],{"class":249},[239,1892,1893],{"class":241,"line":929},[239,1894,277],{"emptyLinePlaceholder":276},[239,1896,1897,1900,1902,1904,1907,1910,1913],{"class":241,"line":955},[239,1898,1899],{"class":249},"    sig ",[239,1901,1631],{"class":245},[239,1903,1159],{"class":249},[239,1905,1906],{"class":293},"Sign",[239,1908,1909],{"class":249},"(ed25519.",[239,1911,1912],{"class":293},"PrivateKey",[239,1914,1915],{"class":249},"(priv), digest)\n",[239,1917,1918],{"class":241,"line":960},[239,1919,277],{"emptyLinePlaceholder":276},[239,1921,1922,1925,1928,1930,1932],{"class":241,"line":1005},[239,1923,1924],{"class":245},"    return",[239,1926,1927],{"class":300}," url",[239,1929,109],{"class":249},[239,1931,1615],{"class":300},[239,1933,1934],{"class":249},"{\n",[239,1936,1937,1940],{"class":241,"line":1022},[239,1938,1939],{"class":256},"        \"credential\"",[239,1941,1942],{"class":249},": {credential},\n",[239,1944,1945,1948,1951,1954,1957,1959],{"class":241,"line":1035},[239,1946,1947],{"class":256},"        \"validUntil\"",[239,1949,1950],{"class":249},": {strconv.",[239,1952,1953],{"class":293},"FormatInt",[239,1955,1956],{"class":249},"(validUntil, ",[239,1958,1304],{"class":315},[239,1960,1961],{"class":249},")},\n",[239,1963,1964,1967,1970,1973],{"class":241,"line":1052},[239,1965,1966],{"class":256},"        \"signature\"",[239,1968,1969],{"class":249},":  {base64.URLEncoding.",[239,1971,1972],{"class":293},"EncodeToString",[239,1974,1975],{"class":249},"(sig)},\n",[239,1977,1978,1981],{"class":241,"line":1064},[239,1979,1980],{"class":249},"    }, ",[239,1982,1983],{"class":315},"nil\n",[239,1985,1986],{"class":241,"line":1077},[239,1987,430],{"class":249},[11,1989,1990,1991,190,1994,1997],{},"If you already depend on ",[15,1992,1993],{},"go.sia.tech\u002Fcore",[15,1995,1996],{},"types.NewHasher()"," produces the identical digest —\nit is blake2b-256 with a raw-byte encoder, which is what the construction above reproduces by hand.",[33,1999,2001],{"id":2000},"python","Python",[111,2003,2006],{"className":2004,"code":2005,"language":2000,"meta":119,"style":119},"language-python shiki shiki-themes github-dark-default github-dark-default","import base64, hashlib, struct, time\nfrom nacl.signing import SigningKey\n\ndef sign_params(app_key: str, credential: str, method: str, host: str,\n                path: str, body: bytes = b\"\", ttl: int = 60) -> dict:\n    valid_until = int(time.time()) + ttl\n\n    priv = base64.urlsafe_b64decode(app_key + \"=\" * (-len(app_key) % 4))\n    seed = priv[:32]\n\n    h = hashlib.blake2b(digest_size=32)\n    h.update(method.encode())\n    h.update(host.encode())\n    h.update(path.encode())\n    h.update(struct.pack(\"\u003CQ\", valid_until))   # little-endian uint64\n    if body:\n        h.update(body)\n\n    sig = SigningKey(seed).sign(h.digest()).signature\n\n    return {\n        \"credential\": credential,\n        \"validUntil\": str(valid_until),\n        \"signature\": base64.urlsafe_b64encode(sig).decode(),\n    }\n",[15,2007,2008,2015,2027,2031,2062,2100,2118,2122,2157,2172,2176,2195,2200,2205,2210,2224,2231,2236,2240,2249,2253,2259,2266,2277,2284],{"__ignoreMap":119},[239,2009,2010,2012],{"class":241,"line":242},[239,2011,246],{"class":245},[239,2013,2014],{"class":249}," base64, hashlib, struct, time\n",[239,2016,2017,2019,2022,2024],{"class":241,"line":260},[239,2018,253],{"class":245},[239,2020,2021],{"class":249}," nacl.signing ",[239,2023,246],{"class":245},[239,2025,2026],{"class":249}," SigningKey\n",[239,2028,2029],{"class":241,"line":273},[239,2030,277],{"emptyLinePlaceholder":276},[239,2032,2033,2036,2039,2042,2045,2048,2050,2053,2055,2058,2060],{"class":241,"line":280},[239,2034,2035],{"class":245},"def",[239,2037,2038],{"class":293}," sign_params",[239,2040,2041],{"class":249},"(app_key: ",[239,2043,2044],{"class":315},"str",[239,2046,2047],{"class":249},", credential: ",[239,2049,2044],{"class":315},[239,2051,2052],{"class":249},", method: ",[239,2054,2044],{"class":315},[239,2056,2057],{"class":249},", host: ",[239,2059,2044],{"class":315},[239,2061,696],{"class":249},[239,2063,2064,2067,2069,2072,2074,2076,2078,2081,2084,2087,2089,2091,2094,2097],{"class":241,"line":287},[239,2065,2066],{"class":249},"                path: ",[239,2068,2044],{"class":315},[239,2070,2071],{"class":249},", body: ",[239,2073,301],{"class":315},[239,2075,472],{"class":245},[239,2077,348],{"class":245},[239,2079,2080],{"class":256},"\"\"",[239,2082,2083],{"class":249},", ttl: ",[239,2085,2086],{"class":315},"int",[239,2088,472],{"class":245},[239,2090,761],{"class":315},[239,2092,2093],{"class":249},") -> ",[239,2095,2096],{"class":315},"dict",[239,2098,2099],{"class":249},":\n",[239,2101,2102,2105,2107,2110,2113,2115],{"class":241,"line":322},[239,2103,2104],{"class":249},"    valid_until ",[239,2106,135],{"class":245},[239,2108,2109],{"class":315}," int",[239,2111,2112],{"class":249},"(time.time()) ",[239,2114,149],{"class":245},[239,2116,2117],{"class":249}," ttl\n",[239,2119,2120],{"class":241,"line":336},[239,2121,277],{"emptyLinePlaceholder":276},[239,2123,2124,2127,2129,2132,2134,2137,2140,2142,2145,2148,2151,2153,2155],{"class":241,"line":369},[239,2125,2126],{"class":249},"    priv ",[239,2128,135],{"class":245},[239,2130,2131],{"class":249}," base64.urlsafe_b64decode(app_key ",[239,2133,149],{"class":245},[239,2135,2136],{"class":256}," \"=\"",[239,2138,2139],{"class":245}," *",[239,2141,342],{"class":249},[239,2143,2144],{"class":245},"-",[239,2146,2147],{"class":315},"len",[239,2149,2150],{"class":249},"(app_key) ",[239,2152,557],{"class":245},[239,2154,551],{"class":315},[239,2156,602],{"class":249},[239,2158,2159,2162,2164,2167,2169],{"class":241,"line":427},[239,2160,2161],{"class":249},"    seed ",[239,2163,135],{"class":245},[239,2165,2166],{"class":249}," priv[:",[239,2168,1104],{"class":315},[239,2170,2171],{"class":249},"]\n",[239,2173,2174],{"class":241,"line":433},[239,2175,277],{"emptyLinePlaceholder":276},[239,2177,2178,2181,2183,2186,2189,2191,2193],{"class":241,"line":438},[239,2179,2180],{"class":249},"    h ",[239,2182,135],{"class":245},[239,2184,2185],{"class":249}," hashlib.blake2b(",[239,2187,2188],{"class":300},"digest_size",[239,2190,135],{"class":245},[239,2192,1104],{"class":315},[239,2194,424],{"class":249},[239,2196,2197],{"class":241,"line":463},[239,2198,2199],{"class":249},"    h.update(method.encode())\n",[239,2201,2202],{"class":241,"line":510},[239,2203,2204],{"class":249},"    h.update(host.encode())\n",[239,2206,2207],{"class":241,"line":564},[239,2208,2209],{"class":249},"    h.update(path.encode())\n",[239,2211,2212,2215,2218,2221],{"class":241,"line":605},[239,2213,2214],{"class":249},"    h.update(struct.pack(",[239,2216,2217],{"class":256},"\"\u003CQ\"",[239,2219,2220],{"class":249},", valid_until))   ",[239,2222,2223],{"class":283},"# little-endian uint64\n",[239,2225,2226,2228],{"class":241,"line":610},[239,2227,1645],{"class":245},[239,2229,2230],{"class":249}," body:\n",[239,2232,2233],{"class":241,"line":615},[239,2234,2235],{"class":249},"        h.update(body)\n",[239,2237,2238],{"class":241,"line":629},[239,2239,277],{"emptyLinePlaceholder":276},[239,2241,2242,2244,2246],{"class":241,"line":635},[239,2243,1899],{"class":249},[239,2245,135],{"class":245},[239,2247,2248],{"class":249}," SigningKey(seed).sign(h.digest()).signature\n",[239,2250,2251],{"class":241,"line":646},[239,2252,277],{"emptyLinePlaceholder":276},[239,2254,2255,2257],{"class":241,"line":652},[239,2256,1924],{"class":245},[239,2258,319],{"class":249},[239,2260,2261,2263],{"class":241,"line":662},[239,2262,1939],{"class":256},[239,2264,2265],{"class":249},": credential,\n",[239,2267,2268,2270,2272,2274],{"class":241,"line":667},[239,2269,1947],{"class":256},[239,2271,1401],{"class":249},[239,2273,2044],{"class":315},[239,2275,2276],{"class":249},"(valid_until),\n",[239,2278,2279,2281],{"class":241,"line":672},[239,2280,1966],{"class":256},[239,2282,2283],{"class":249},": base64.urlsafe_b64encode(sig).decode(),\n",[239,2285,2286],{"class":241,"line":686},[239,2287,1671],{"class":249},[33,2289,2291],{"id":2290},"choosing-an-expiry","Choosing an expiry",[11,2293,2294,2296],{},[15,2295,89],{}," is entirely yours to set, and it is the only replay protection in the scheme — there is\nno nonce and no server-side record of used signatures. A signed request can be replayed by anyone\nwho observes it until it expires.",[2298,2299,2300,2306,2316,2322],"ul",{},[182,2301,2302,2305],{},[41,2303,2304],{},"60 seconds"," is a sensible default for interactive calls.",[182,2307,2308,2311,2312,2315],{},[41,2309,2310],{},"Do not sign far into the future for convenience."," A one-year signature on a ",[15,2313,2314],{},"DELETE"," is a\none-year deletion capability sitting in whatever logged the URL.",[182,2317,2318,2321],{},[41,2319,2320],{},"Sign per request."," Because the path and body are covered, a signature is not reusable for a\ndifferent call anyway.",[182,2323,2324,2327,2328,2331,2332,2335],{},[41,2325,2326],{},"Query parameters are not covered",", so a signature for ",[15,2329,2330],{},"GET \u002Fobjects?limit=50"," is equally valid\nfor ",[15,2333,2334],{},"GET \u002Fobjects?limit=1000",". Treat parameters as unauthenticated.",[33,2337,2339],{"id":2338},"verifying-a-credential","Verifying a credential",[111,2341,2345],{"className":2342,"code":2343,"language":2344,"meta":119,"style":119},"language-bash shiki shiki-themes github-dark-default github-dark-default","curl -sD - -o \u002Fdev\u002Fnull \"$TESSERA_API\u002Fauth\u002Fcheck?$AUTH\"\n# HTTP\u002F1.1 204 No Content   → valid and active\n# HTTP\u002F1.1 401 Unauthorized → expired, unknown, revoked, or mis-signed\n","bash",[15,2346,2347,2377,2382],{"__ignoreMap":119},[239,2348,2349,2352,2355,2357,2360,2363,2366,2369,2372,2375],{"class":241,"line":242},[239,2350,2351],{"class":300},"curl",[239,2353,2354],{"class":315}," -sD",[239,2356,539],{"class":256},[239,2358,2359],{"class":315}," -o",[239,2361,2362],{"class":256}," \u002Fdev\u002Fnull",[239,2364,2365],{"class":256}," \"",[239,2367,2368],{"class":249},"$TESSERA_API",[239,2370,2371],{"class":256},"\u002Fauth\u002Fcheck?",[239,2373,2374],{"class":249},"$AUTH",[239,2376,1477],{"class":256},[239,2378,2379],{"class":241,"line":260},[239,2380,2381],{"class":283},"# HTTP\u002F1.1 204 No Content   → valid and active\n",[239,2383,2384],{"class":241,"line":273},[239,2385,2386],{"class":283},"# HTTP\u002F1.1 401 Unauthorized → expired, unknown, revoked, or mis-signed\n",[11,2388,2389,2392,2393,2396],{},[15,2390,2391],{},"204"," means the credential exists, is in the ",[15,2394,2395],{},"active"," state, and your signature verified.",[33,2398,2400],{"id":2399},"failure-modes","Failure modes",[11,2402,2403,2404,2407],{},"Every one of these returns ",[15,2405,2406],{},"401"," with a plain-text body. The body tells you which:",[46,2409,2410,2420],{},[49,2411,2412],{},[52,2413,2414,2417],{},[55,2415,2416],{},"Body",[55,2418,2419],{},"Cause",[62,2421,2422,2432,2442,2454,2466,2476,2487],{},[52,2423,2424,2429],{},[67,2425,2426],{},[15,2427,2428],{},"missing auth params",[67,2430,2431],{},"One of the three query parameters is absent.",[52,2433,2434,2439],{},[67,2435,2436],{},[15,2437,2438],{},"invalid validUntil",[67,2440,2441],{},"Not parseable as a base-10 integer.",[52,2443,2444,2449],{},[67,2445,2446],{},[15,2447,2448],{},"signature expired",[67,2450,2451,2453],{},[15,2452,89],{}," is in the past. Check clock skew before blaming us.",[52,2455,2456,2461],{},[67,2457,2458],{},[15,2459,2460],{},"invalid credential",[67,2462,2463,2465],{},[15,2464,71],{}," is not valid padded base64url.",[52,2467,2468,2473],{},[67,2469,2470],{},[15,2471,2472],{},"unknown credential",[67,2474,2475],{},"Decoded fine, but is not a provisioned, active key — including revoked keys.",[52,2477,2478,2483],{},[67,2479,2480],{},[15,2481,2482],{},"invalid signature",[67,2484,2485,2465],{},[15,2486,103],{},[52,2488,2489,2494],{},[67,2490,2491],{},[15,2492,2493],{},"signature mismatch",[67,2495,2496,2497,2499,2500,2502],{},"Verification failed. Usually a wrong ",[15,2498,217],{},", a hashed query string, big-endian ",[15,2501,89],{},", or SHA-256 instead of blake2b.",[121,2504,2506],{"title":2505,"tone":1421},"Debugging `signature mismatch`",[11,2507,2508,2509,2511,2512,2514,2515,2517],{},"Work through it in this order: is ",[15,2510,217],{}," exactly the ",[15,2513,221],{}," header, including port? Is the path free\nof the query string? Is ",[15,2516,89],{}," little-endian in the hash and decimal in the URL? Is the digest\nblake2b-256? Is the body byte-identical to what you transmitted — same serialisation, no\nre-encoding by an HTTP client in between? That last one catches more people than the rest combined.",[33,2519,2521],{"id":2520},"storing-credentials","Storing credentials",[11,2523,2524,2525,2527,2528,2531],{},"The ",[15,2526,1561],{}," is the private key from storage registration (",[15,2529,2530],{},"sdk.AppKey()","), 64 bytes, seed then\npublic key. Pass it to the examples above as unpadded base64url. Tessera does not have a copy.",[2298,2533,2534,2537,2540],{},[182,2535,2536],{},"Store it in a secret manager, not in configuration or version control.",[182,2538,2539],{},"It is the account. Rotating it means a new credential and a new namespace. Existing objects do\nnot move.",[182,2541,2542,2543,2546],{},"Revoke a compromised credential with ",[15,2544,2545],{},"DELETE \u002Fauth\u002Frevoke",". Revocation takes effect on the next\nrequest.",[2548,2549,2550],"style",{},"html pre.shiki code .srH4v, html code.shiki .srH4v{--shiki-default:#FF7B72;--shiki-dark:#FF7B72}html pre.shiki code .sAxfE, html code.shiki .sAxfE{--shiki-default:#FFA657;--shiki-dark:#FFA657}html pre.shiki code .sb7RM, html code.shiki .sb7RM{--shiki-default:#E6EDF3;--shiki-dark:#E6EDF3}html pre.shiki code .skZ57, html code.shiki .skZ57{--shiki-default:#A5D6FF;--shiki-dark:#A5D6FF}html pre.shiki code .sI3U9, html code.shiki .sI3U9{--shiki-default:#8B949E;--shiki-dark:#8B949E}html pre.shiki code .sYGIp, html code.shiki .sYGIp{--shiki-default:#D2A8FF;--shiki-dark:#D2A8FF}html pre.shiki code .sEQcL, html code.shiki .sEQcL{--shiki-default:#79C0FF;--shiki-dark:#79C0FF}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 .sq3Sb, html code.shiki .sq3Sb{--shiki-default:#7EE787;--shiki-default-font-weight:bold;--shiki-dark:#7EE787;--shiki-dark-font-weight:bold}",{"title":119,"searchDepth":273,"depth":273,"links":2552},[2553,2554,2555,2556,2557,2558,2559,2560,2561],{"id":35,"depth":260,"text":36},{"id":153,"depth":260,"text":154},{"id":229,"depth":260,"text":230},{"id":1440,"depth":260,"text":1441},{"id":2000,"depth":260,"text":2001},{"id":2290,"depth":260,"text":2291},{"id":2338,"depth":260,"text":2339},{"id":2399,"depth":260,"text":2400},{"id":2520,"depth":260,"text":2521},"md",{},"\u002Fdocs\u002Fauthentication","Guides",{"title":5,"description":119},"docs\u002Fauthentication","6ZH6MSPyI8MEUrkEx1Fw9qK0Isb2S1M1eLXK-P2iP20",[2570,2571,2576,2580,2583,2586,2589,2592,2593,2597,2601,2604,2607,2612],{"path":29,"title":30,"description":119,"section":2565,"order":260,"badge":6},{"path":2572,"title":2573,"description":2574,"section":2575,"order":242,"badge":6},"\u002Fdocs\u002Fapi-quickstart","API quickstart","Create a billing account over HTTP, then register a storage key with the SDK.","API",{"path":2577,"title":2578,"description":119,"section":2579,"order":287,"badge":6},"\u002Fdocs\u002Fapi\u002Faccount","Account","API reference",{"path":2581,"title":2582,"description":119,"section":2579,"order":280,"badge":6},"\u002Fdocs\u002Fapi\u002Fobjects","Objects",{"path":2584,"title":2585,"description":119,"section":2579,"order":260,"badge":6},"\u002Fdocs\u002Fapi\u002Fplacement","Placement",{"path":2587,"title":2588,"description":119,"section":2579,"order":242,"badge":6},"\u002Fdocs\u002Fapi\u002Fprovisioning","Credentials",{"path":2590,"title":2591,"description":119,"section":2579,"order":273,"badge":6},"\u002Fdocs\u002Fapi\u002Fslabs","Slabs",{"path":2564,"title":5,"description":119,"section":2565,"order":273,"badge":6},{"path":2594,"title":2595,"description":119,"section":2596,"order":260,"badge":6},"\u002Fdocs\u002Fconcepts","Concepts & data model","Introduction",{"path":2598,"title":2599,"description":119,"section":2600,"order":242,"badge":6},"\u002Fdocs\u002Ferrors","Errors","Reference",{"path":2602,"title":2603,"description":119,"section":2575,"order":242,"badge":6},"\u002Fdocs","Overview",{"path":2605,"title":2606,"description":119,"section":2600,"order":260,"badge":6},"\u002Fdocs\u002Flimits","Limits & quotas",{"path":2608,"title":2609,"description":2610,"section":2611,"order":242,"badge":6},"\u002Fdocs\u002Fquickstart","CLI quickstart","Install the CLI, create an account, store one file, and read it back.","CLI",{"path":2613,"title":2614,"description":119,"section":2565,"order":280,"badge":6},"\u002Fdocs\u002Frecovery","Recovery without Tessera",1794060204991]