[{"data":1,"prerenderedAt":1028},["ShallowReactive",2],{"navigation_docs":3,"-math-ed25519":80,"-math-ed25519-surround":1023},[4,24],{"title":5,"path":6,"stem":7,"children":8,"icon":23},"Guide","\u002Fguide","1.guide\u002F01.index",[9,11,15,19],{"title":10,"path":6,"stem":7},"Getting started",{"title":12,"path":13,"stem":14},"CLI","\u002Fguide\u002Fcli","1.guide\u002F02.cli",{"title":16,"path":17,"stem":18},"Agents","\u002Fguide\u002Fagents","1.guide\u002F03.agents",{"title":20,"path":21,"stem":22},"Playground","\u002Fguide\u002Fplayground","1.guide\u002F04.playground","i-lucide-book-open",{"title":25,"path":26,"stem":27,"children":28,"icon":79},"Math","\u002Fmath","2.math\u002F00.index",[29,31,35,39,43,47,51,55,59,63,67,71,75],{"title":30,"path":26,"stem":27},"Overview",{"title":32,"path":33,"stem":34},"Curves and points","\u002Fmath\u002Fcurves","2.math\u002F01.curves",{"title":36,"path":37,"stem":38},"Orders and logs","\u002Fmath\u002Fgroups","2.math\u002F02.groups",{"title":40,"path":41,"stem":42},"secp256k1","\u002Fmath\u002Fsecp256k1","2.math\u002F03.secp256k1",{"title":44,"path":45,"stem":46},"sr25519","\u002Fmath\u002Fsr25519","2.math\u002F04.sr25519",{"title":48,"path":49,"stem":50},"ed25519","\u002Fmath\u002Fed25519","2.math\u002F05.ed25519",{"title":52,"path":53,"stem":54},"P-256","\u002Fmath\u002Fp256","2.math\u002F06.p256",{"title":56,"path":57,"stem":58},"P-384","\u002Fmath\u002Fp384","2.math\u002F07.p384",{"title":60,"path":61,"stem":62},"P-521","\u002Fmath\u002Fp521","2.math\u002F08.p521",{"title":64,"path":65,"stem":66},"brainpoolP256r1","\u002Fmath\u002Fbrainpoolp256r1","2.math\u002F09.brainpoolp256r1",{"title":68,"path":69,"stem":70},"brainpoolP384r1","\u002Fmath\u002Fbrainpoolp384r1","2.math\u002F10.brainpoolp384r1",{"title":72,"path":73,"stem":74},"brainpoolP512r1","\u002Fmath\u002Fbrainpoolp512r1","2.math\u002F11.brainpoolp512r1",{"title":76,"path":77,"stem":78},"Ed448","\u002Fmath\u002Fed448","2.math\u002F12.ed448","i-lucide-sigma",{"id":81,"title":48,"body":82,"description":1015,"extension":1016,"links":1017,"meta":1018,"navigation":136,"path":49,"seo":1020,"stem":50,"__hash__":1022},"docs\u002F2.math\u002F05.ed25519.md",{"type":83,"value":84,"toc":999},"minimark",[85,90,99,173,180,184,191,265,279,283,290,407,426,430,444,447,453,457,460,578,585,588,595,600,603,611,618,622,625,654,660,677,681,684,761,767,771,774,781,849,855,858,868,872,875,881,891,895,898,929,935,961,965,971,974,977,980,986,992,995],[86,87,89],"h2",{"id":88},"the-cofactor-is-back","The cofactor is back",[91,92,93,94,98],"p",{},"ristretto255 hides the cofactor of edwards25519. Monero doesn't bother. Its keys are plain ed25519 points, 32 bytes each, and the 8 sits right there in the group. So ",[95,96,97],"code",{},"@agntn\u002Fcurves\u002Fed25519"," keeps it too.",[100,101,106],"pre",{"className":102,"code":103,"language":104,"meta":105,"style":105},"language-ts shiki shiki-themes compressions compressions compressions","import { multiplyGenerator } from \"@agntn\u002Fcurves\u002Fed25519\";\n\nmultiplyGenerator(1n); \u002F\u002F \"5866666666666666…6666\"\nmultiplyGenerator(0n); \u002F\u002F \"0100000000000000…0000\"\n","ts","",[95,107,108,131,138,158],{"__ignoreMap":105},[109,110,113,117,121,124,128],"span",{"class":111,"line":112},"line",1,[109,114,116],{"class":115},"skH_V","import",[109,118,120],{"class":119},"s38Sx"," { multiplyGenerator } ",[109,122,123],{"class":115},"from",[109,125,127],{"class":126},"shU9J"," \"@agntn\u002Fcurves\u002Fed25519\"",[109,129,130],{"class":119},";\n",[109,132,134],{"class":111,"line":133},2,[109,135,137],{"emptyLinePlaceholder":136},true,"\n",[109,139,141,145,148,151,154],{"class":111,"line":140},3,[109,142,144],{"class":143},"sK71F","multiplyGenerator",[109,146,147],{"class":119},"(1",[109,149,150],{"class":115},"n",[109,152,153],{"class":119},"); ",[109,155,157],{"class":156},"scIB-","\u002F\u002F \"5866666666666666…6666\"\n",[109,159,161,163,166,168,170],{"class":111,"line":160},4,[109,162,144],{"class":143},[109,164,165],{"class":119},"(0",[109,167,150],{"class":115},[109,169,153],{"class":119},[109,171,172],{"class":156},"\u002F\u002F \"0100000000000000…0000\"\n",[91,174,175,176,179],{},"All those sixes? That's y = 4\u002F5, the base point of RFC 8032. The identity is ",[95,177,178],{},"01"," and zeros. And 32 zero bytes? In ristretto255 that's the identity. Here it's a point of order 4. Same bytes, other curve, other answer.",[86,181,183],{"id":182},"valid-isnt-the-same-as-safe","Valid isn't the same as safe",[91,185,186,187,190],{},"Every canonical encoding decodes, whatever its order. A point with a piece of order 2, 4 or 8 in it is a fine point. It's also a lousy key. ",[95,188,189],{},"isTorsionFree"," catches it: l times the point has to land on the identity.",[100,192,194],{"className":102,"code":193,"language":104,"meta":105,"style":105},"import { isTorsionFree, isValidPoint, multiplyPoint } from \"@agntn\u002Fcurves\u002Fed25519\";\n\nconst sneaky = \"c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a\";\nisValidPoint(sneaky); \u002F\u002F true\nisTorsionFree(sneaky); \u002F\u002F false\nmultiplyPoint(sneaky, 8n); \u002F\u002F \"0100000000000000…0000\"\n",[95,195,196,209,213,229,240,250],{"__ignoreMap":105},[109,197,198,200,203,205,207],{"class":111,"line":112},[109,199,116],{"class":115},[109,201,202],{"class":119}," { isTorsionFree, isValidPoint, multiplyPoint } ",[109,204,123],{"class":115},[109,206,127],{"class":126},[109,208,130],{"class":119},[109,210,211],{"class":111,"line":133},[109,212,137],{"emptyLinePlaceholder":136},[109,214,215,218,221,224,227],{"class":111,"line":140},[109,216,217],{"class":115},"const",[109,219,220],{"class":119}," sneaky ",[109,222,223],{"class":115},"=",[109,225,226],{"class":126}," \"c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a\"",[109,228,130],{"class":119},[109,230,231,234,237],{"class":111,"line":160},[109,232,233],{"class":143},"isValidPoint",[109,235,236],{"class":119},"(sneaky); ",[109,238,239],{"class":156},"\u002F\u002F true\n",[109,241,243,245,247],{"class":111,"line":242},5,[109,244,189],{"class":143},[109,246,236],{"class":119},[109,248,249],{"class":156},"\u002F\u002F false\n",[109,251,253,256,259,261,263],{"class":111,"line":252},6,[109,254,255],{"class":143},"multiplyPoint",[109,257,258],{"class":119},"(sneaky, 8",[109,260,150],{"class":115},[109,262,153],{"class":119},[109,264,172],{"class":156},[91,266,267,268,271,272,274,275,278],{},"Monero's ",[95,269,270],{},"check_address"," wants two things. Not the identity, and nothing of small order. The identity is in the subgroup, so ",[95,273,189],{}," waves it through. ",[95,276,277],{},"isIdentity"," does the other half.",[86,280,282],{"id":281},"a-monero-subaddress","A Monero subaddress",[91,284,285,286,289],{},"Two points, one sum, one product. D = B + mG and C = aD. B is the public spend key, a the private view key. And m? Keccak of ",[95,287,288],{},"SubAddr\\0",", a and the two indices, reduced mod l.",[100,291,293],{"className":102,"code":292,"language":104,"meta":105,"style":105},"import { addPoints, multiplyGenerator, multiplyPoint, reduceScalar } from \"@agntn\u002Fcurves\u002Fed25519\";\n\nconst spend = \"1b3bd040020d3712ab84992b773d0a965134eb2df0392fb84af95de8a17be2ab\";\nconst a = reduceScalar(\"49774391fa5e8d249fc2c5b45dadef13534bf2483dede880dac88f061e809100\");\nconst m = reduceScalar(\"ce0c5c5c5ebc57951183b271e8257cd136d1efff778c4ecc6f89e95e575324cc\"); \u002F\u002F that keccak\n\nconst d = addPoints(spend, multiplyGenerator(m)); \u002F\u002F \"8e4841533f2f07f9…9abd\"\nmultiplyPoint(d, a); \u002F\u002F \"b833d78e77da6d8d…5269\"\n",[95,294,295,308,312,326,347,368,372,396],{"__ignoreMap":105},[109,296,297,299,302,304,306],{"class":111,"line":112},[109,298,116],{"class":115},[109,300,301],{"class":119}," { addPoints, multiplyGenerator, multiplyPoint, reduceScalar } ",[109,303,123],{"class":115},[109,305,127],{"class":126},[109,307,130],{"class":119},[109,309,310],{"class":111,"line":133},[109,311,137],{"emptyLinePlaceholder":136},[109,313,314,316,319,321,324],{"class":111,"line":140},[109,315,217],{"class":115},[109,317,318],{"class":119}," spend ",[109,320,223],{"class":115},[109,322,323],{"class":126}," \"1b3bd040020d3712ab84992b773d0a965134eb2df0392fb84af95de8a17be2ab\"",[109,325,130],{"class":119},[109,327,328,330,333,335,338,341,344],{"class":111,"line":160},[109,329,217],{"class":115},[109,331,332],{"class":119}," a ",[109,334,223],{"class":115},[109,336,337],{"class":143}," reduceScalar",[109,339,340],{"class":119},"(",[109,342,343],{"class":126},"\"49774391fa5e8d249fc2c5b45dadef13534bf2483dede880dac88f061e809100\"",[109,345,346],{"class":119},");\n",[109,348,349,351,354,356,358,360,363,365],{"class":111,"line":242},[109,350,217],{"class":115},[109,352,353],{"class":119}," m ",[109,355,223],{"class":115},[109,357,337],{"class":143},[109,359,340],{"class":119},[109,361,362],{"class":126},"\"ce0c5c5c5ebc57951183b271e8257cd136d1efff778c4ecc6f89e95e575324cc\"",[109,364,153],{"class":119},[109,366,367],{"class":156},"\u002F\u002F that keccak\n",[109,369,370],{"class":111,"line":252},[109,371,137],{"emptyLinePlaceholder":136},[109,373,375,377,380,382,385,388,390,393],{"class":111,"line":374},7,[109,376,217],{"class":115},[109,378,379],{"class":119}," d ",[109,381,223],{"class":115},[109,383,384],{"class":143}," addPoints",[109,386,387],{"class":119},"(spend, ",[109,389,144],{"class":143},[109,391,392],{"class":119},"(m)); ",[109,394,395],{"class":156},"\u002F\u002F \"8e4841533f2f07f9…9abd\"\n",[109,397,399,401,404],{"class":111,"line":398},8,[109,400,255],{"class":143},[109,402,403],{"class":119},"(d, a); ",[109,405,406],{"class":156},"\u002F\u002F \"b833d78e77da6d8d…5269\"\n",[91,408,409,410,419,420,425],{},"That's account 1, index 1 of a test wallet, and monero-wallet-cli shows its address there too. Keccak comes from ",[411,412,416],"a",{"href":413,"rel":414},"https:\u002F\u002Fhashes.agntn.dev",[415],"nofollow",[95,417,418],{},"@agntn\u002Fhashes",". The address around the keys lives in ",[411,421,424],{"href":422,"rel":423},"https:\u002F\u002Fgithub.com\u002Fagntn\u002Fkeys",[415],"@agntn\u002Fkeys",".",[86,427,429],{"id":428},"scalars","Scalars",[91,431,432,435,436,439,440,443],{},[95,433,434],{},"reduceScalar"," reads hex little-endian and reduces it mod l. 32 bytes is Monero's ",[95,437,438],{},"sc_reduce32",", 64 bytes is ",[95,441,442],{},"sc_reduce",", the one for a SHA-512. Monero runs every key through the first one, so it's not optional.",[91,445,446],{},"Everything else takes bigints from 0 to l - 1, like ristretto255. An RFC 8032 secret scalar sits above l after clamping. Reduce it first. On the base point the answer doesn't change.",[91,448,449,450,425],{},"Decoding follows RFC 8032 to the letter. y at or above p, a y with no x on the curve, a sign bit on x = 0. All three throw ",[95,451,452],{},"Invalid ed25519 point",[86,454,456],{"id":455},"sign-twice-get-one-signature","Sign twice, get one signature",[91,458,459],{},"RFC 8032 test 1 signs nothing at all. Zero bytes in, 64 bytes out.",[100,461,463],{"className":102,"code":462,"language":104,"meta":105,"style":105},"import { getPublicKey, sign, verify } from \"@agntn\u002Fcurves\u002Fed25519\";\n\nconst secret = \"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60\";\nconst publicKey = getPublicKey(secret); \u002F\u002F \"d75a980182b10ab7…f707511a\"\n\nconst signature = sign(secret, { message: \"\" }); \u002F\u002F \"e5564300c360ac72…8e7a100b\"\nverify(signature, { message: \"\", publicKey }); \u002F\u002F true\nsign(secret, { message: \"\" }) === signature; \u002F\u002F true, every time\n",[95,464,465,478,482,496,514,518,542,557],{"__ignoreMap":105},[109,466,467,469,472,474,476],{"class":111,"line":112},[109,468,116],{"class":115},[109,470,471],{"class":119}," { getPublicKey, sign, verify } ",[109,473,123],{"class":115},[109,475,127],{"class":126},[109,477,130],{"class":119},[109,479,480],{"class":111,"line":133},[109,481,137],{"emptyLinePlaceholder":136},[109,483,484,486,489,491,494],{"class":111,"line":140},[109,485,217],{"class":115},[109,487,488],{"class":119}," secret ",[109,490,223],{"class":115},[109,492,493],{"class":126}," \"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60\"",[109,495,130],{"class":119},[109,497,498,500,503,505,508,511],{"class":111,"line":160},[109,499,217],{"class":115},[109,501,502],{"class":119}," publicKey ",[109,504,223],{"class":115},[109,506,507],{"class":143}," getPublicKey",[109,509,510],{"class":119},"(secret); ",[109,512,513],{"class":156},"\u002F\u002F \"d75a980182b10ab7…f707511a\"\n",[109,515,516],{"class":111,"line":242},[109,517,137],{"emptyLinePlaceholder":136},[109,519,520,522,525,527,530,533,536,539],{"class":111,"line":252},[109,521,217],{"class":115},[109,523,524],{"class":119}," signature ",[109,526,223],{"class":115},[109,528,529],{"class":143}," sign",[109,531,532],{"class":119},"(secret, { message: ",[109,534,535],{"class":126},"\"\"",[109,537,538],{"class":119}," }); ",[109,540,541],{"class":156},"\u002F\u002F \"e5564300c360ac72…8e7a100b\"\n",[109,543,544,547,550,552,555],{"class":111,"line":374},[109,545,546],{"class":143},"verify",[109,548,549],{"class":119},"(signature, { message: ",[109,551,535],{"class":126},[109,553,554],{"class":119},", publicKey }); ",[109,556,239],{"class":156},[109,558,559,562,564,566,569,572,575],{"class":111,"line":398},[109,560,561],{"class":143},"sign",[109,563,532],{"class":119},[109,565,535],{"class":126},[109,567,568],{"class":119}," }) ",[109,570,571],{"class":115},"===",[109,573,574],{"class":119}," signature; ",[109,576,577],{"class":156},"\u002F\u002F true, every time\n",[91,579,580,581,584],{},"The secret is 32 random bytes, 64 hex digits without ",[95,582,583],{},"0x",". SHA-512 cuts it in two. The low half gets clamped into the scalar s, and A = sB is the public key. The high half seeds every nonce.",[91,586,587],{},"That's why the bytes never move. The nonce is a hash of that half and the message. One secret and one message, one signature. sr25519 hands you a new one on every call. Ed25519 doesn't bother.",[91,589,590,591,594],{},"Writing a test? There's no ",[95,592,593],{},"{ random }"," to pass and nothing to seed. No random number generator, so nothing to get wrong there.",[596,597,599],"h3",{"id":598},"what-does-sign-refuse","What does sign refuse?",[91,601,602],{},"Wrong shapes throw, same as in verify.",[100,604,609],{"className":605,"code":607,"language":608,"meta":105},[606],"language-text","sign(\"00\", { message: \"\" })        \u002F\u002F Secret must be 64 hex digits without 0x, the 32 bytes RFC 8032 hashes\nsign(secret, { message: \"0x00\" })  \u002F\u002F Message must be hex without 0x\nsign(secret, \"\")                   \u002F\u002F Call it as sign(secret, { message })\n","text",[95,610,607],{"__ignoreMap":105},[91,612,613,614,617],{},"A 64-byte secret gets the first answer too. The last one is on purpose. A call in another library's shape, ",[95,615,616],{},"sign(message, secret)",", fails loudly. Signing the wrong bytes in silence would be worse. And none of the messages quote your secret.",[596,619,621],{"id":620},"from-a-terminal-or-a-model","From a terminal or a model",[91,623,624],{},"The CLI signs the same empty message.",[100,626,630],{"className":627,"code":628,"language":629,"meta":105,"style":105},"language-bash shiki shiki-themes compressions compressions compressions","curves ed25519 sign --secret 9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60 --message \"\"\n","bash",[95,631,632],{"__ignoreMap":105},[109,633,634,637,640,642,645,648,651],{"class":111,"line":112},[109,635,636],{"class":143},"curves",[109,638,639],{"class":126}," ed25519",[109,641,529],{"class":126},[109,643,644],{"class":126}," --secret",[109,646,647],{"class":126}," 9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",[109,649,650],{"class":126}," --message",[109,652,653],{"class":126}," \"\"\n",[100,655,658],{"className":656,"code":657,"language":608,"meta":105},[606],"{\"operation\":\"sign\",\"signature\":\"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b\",\"publicKey\":\"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a\"}\n",[95,659,657],{"__ignoreMap":105},[91,661,662,665,666,669,670,673,674,676],{},[95,663,664],{},"curves ed25519 keypair --secret …"," prints just the key. The agent tool ",[95,667,668],{},"curves_ed25519_compute"," has the same ",[95,671,672],{},"keypair"," and ",[95,675,561],{},". Mind the transcript, though. The secret goes in as is, so test vectors and throwaway keys only.",[86,678,680],{"id":679},"verify-an-ed25519-signature","Verify an Ed25519 signature",[91,682,683],{},"RFC 8032 test 1 signs an empty message. Change the message and the signature doesn't match.",[100,685,687],{"className":102,"code":686,"language":104,"meta":105,"style":105},"import { verify } from \"@agntn\u002Fcurves\u002Fed25519\";\n\nconst publicKey = \"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a\";\nconst signature = \"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b\";\n\nverify(signature, { message: \"\", publicKey }); \u002F\u002F true\nverify(signature, { message: \"00\", publicKey }); \u002F\u002F false\n",[95,688,689,702,706,719,732,736,748],{"__ignoreMap":105},[109,690,691,693,696,698,700],{"class":111,"line":112},[109,692,116],{"class":115},[109,694,695],{"class":119}," { verify } ",[109,697,123],{"class":115},[109,699,127],{"class":126},[109,701,130],{"class":119},[109,703,704],{"class":111,"line":133},[109,705,137],{"emptyLinePlaceholder":136},[109,707,708,710,712,714,717],{"class":111,"line":140},[109,709,217],{"class":115},[109,711,502],{"class":119},[109,713,223],{"class":115},[109,715,716],{"class":126}," \"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a\"",[109,718,130],{"class":119},[109,720,721,723,725,727,730],{"class":111,"line":160},[109,722,217],{"class":115},[109,724,524],{"class":119},[109,726,223],{"class":115},[109,728,729],{"class":126}," \"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b\"",[109,731,130],{"class":119},[109,733,734],{"class":111,"line":242},[109,735,137],{"emptyLinePlaceholder":136},[109,737,738,740,742,744,746],{"class":111,"line":252},[109,739,546],{"class":143},[109,741,549],{"class":119},[109,743,535],{"class":126},[109,745,554],{"class":119},[109,747,239],{"class":156},[109,749,750,752,754,757,759],{"class":111,"line":374},[109,751,546],{"class":143},[109,753,549],{"class":119},[109,755,756],{"class":126},"\"00\"",[109,758,554],{"class":119},[109,760,249],{"class":156},[91,762,763,764,766],{},"The signature is R then S, 128 hex digits total. The key is 64 hex digits in RFC 8032 encoding. The message is hex of any length, including empty. Leave ",[95,765,583],{}," off all three.",[596,768,770],{"id":769},"zip-215-or-strict-rules","ZIP 215 or strict rules?",[91,772,773],{},"ZIP 215 is the default. It accepts non-canonical encodings of R and the key. That includes y at or above p, read mod p. It also accepts a sign bit on x = 0. A key of small order gets through too.",[91,775,776,777,780],{},"Set ",[95,778,779],{},"zip215: false"," for strict rules. R and the key must have canonical encodings. Keys of order 1, 2, 4 or 8 are refused.",[100,782,784],{"className":102,"code":783,"language":104,"meta":105,"style":105},"const zeroSignature = `${sneaky}${\"00\".repeat(32)}`;\n\nverify(zeroSignature, { message: \"68656c6c6f\", publicKey: sneaky }); \u002F\u002F true\nverify(zeroSignature, { message: \"68656c6c6f\", publicKey: sneaky, zip215: false }); \u002F\u002F false\n",[95,785,786,817,821,836],{"__ignoreMap":105},[109,787,788,790,793,795,798,801,804,806,809,812,815],{"class":111,"line":112},[109,789,217],{"class":115},[109,791,792],{"class":119}," zeroSignature ",[109,794,223],{"class":115},[109,796,797],{"class":126}," `${",[109,799,800],{"class":126},"sneaky",[109,802,803],{"class":126},"}${\"00\"",[109,805,425],{"class":126},[109,807,808],{"class":143},"repeat",[109,810,811],{"class":126},"(32)",[109,813,814],{"class":126},"}`",[109,816,130],{"class":119},[109,818,819],{"class":111,"line":133},[109,820,137],{"emptyLinePlaceholder":136},[109,822,823,825,828,831,834],{"class":111,"line":140},[109,824,546],{"class":143},[109,826,827],{"class":119},"(zeroSignature, { message: ",[109,829,830],{"class":126},"\"68656c6c6f\"",[109,832,833],{"class":119},", publicKey: sneaky }); ",[109,835,239],{"class":156},[109,837,838,840,842,844,847],{"class":111,"line":160},[109,839,546],{"class":143},[109,841,827],{"class":119},[109,843,830],{"class":126},[109,845,846],{"class":119},", publicKey: sneaky, zip215: false }); ",[109,848,249],{"class":156},[91,850,851,852,854],{},"That key can \"sign\" anything under ZIP 215. Lousy key, convenient trick. Eight times ",[95,853,800],{}," is the identity. With S = 0, both sides of the equation are zero.",[91,856,857],{},"Both rules use RFC 8032's cofactored equation: 8SB = 8R + 8kA. The challenge k is SHA-512 over R, the key and the message. R and the key are hashed exactly as written.",[91,859,860,861,864,865,867],{},"Zcash wrote ZIP 215 so verifiers agree on valid signatures. ",[95,862,863],{},"@noble\u002Fcurves"," uses it by default too. ",[95,866,424],{}," uses strict rules for OpenPGP. For XRPL and signed messages, it uses the default.",[596,869,871],{"id":870},"what-does-it-refuse","What does it refuse?",[91,873,874],{},"Wrong shapes throw. The messages tell you what needs fixing.",[100,876,879],{"className":877,"code":878,"language":608,"meta":105},[606],"verify(\"00\", { message: \"\", publicKey })                         \u002F\u002F Signature must be 128 hex digits without 0x, R then S\nverify(signature, { message: \"0x00\", publicKey })                \u002F\u002F Message must be hex without 0x\nverify(signature, { message: \"\", publicKey: \"02\" + publicKey })  \u002F\u002F Public key must be 64 hex digits without 0x\nverify(signature, { message: \"\", publicKey, zip215: \"no\" })      \u002F\u002F zip215 must be a boolean\n",[95,880,878],{"__ignoreMap":105},[91,882,883,884,887,888,890],{},"A wrong signature returns ",[95,885,886],{},"false"," instead. So do an R or key that doesn't decode, and S at or above l. A small order key returns ",[95,889,886],{}," under strict rules.",[596,892,894],{"id":893},"cli-and-agent-tool","CLI and agent tool",[91,896,897],{},"The CLI checks the same empty message.",[100,899,901],{"className":627,"code":900,"language":629,"meta":105,"style":105},"curves ed25519 verify --public-key d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a --message \"\" --signature e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b\n",[95,902,903],{"__ignoreMap":105},[109,904,905,907,909,912,915,918,920,923,926],{"class":111,"line":112},[109,906,636],{"class":143},[109,908,639],{"class":126},[109,910,911],{"class":126}," verify",[109,913,914],{"class":126}," --public-key",[109,916,917],{"class":126}," d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",[109,919,650],{"class":126},[109,921,922],{"class":126}," \"\"",[109,924,925],{"class":126}," --signature",[109,927,928],{"class":126}," e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b\n",[100,930,933],{"className":931,"code":932,"language":608,"meta":105},[606],"{\"operation\":\"verify\",\"valid\":true}\n",[95,934,932],{"__ignoreMap":105},[91,936,937,938,941,942,944,945,947,948,951,952,951,955,673,958,425],{},"Add ",[95,939,940],{},"--no-zip215"," for strict rules. The agent tool is ",[95,943,668],{},", with operation ",[95,946,546],{},". It takes ",[95,949,950],{},"publicKey",", ",[95,953,954],{},"message",[95,956,957],{},"signature",[95,959,960],{},"zip215",[86,962,964],{"id":963},"written-from-the-specs-again","Written from the specs, again",[91,966,967,968,970],{},"The encoding is RFC 8032. The additions and doublings are the ones ristretto255 runs on, the same file underneath. The tests hold it to the keys of RFC 8032 and to that Monero wallet. A local run against ",[95,969,863],{}," agreed on 300 random cases, small order points mixed in.",[91,972,973],{},"The base point takes about 0.45 ms to multiply, any other point about 1 ms. Every scalar takes the same steps. Even scalar 1 has to do the work of l - 1.",[91,975,976],{},"In a local dudect run, fixed and random scalars stayed within about one percent. JavaScript promises nothing about timing, though. Puzzles yes, a wallet with money in it no.",[91,978,979],{},"Verification tests run all five RFC 8032 vectors under both rules. They also run all 210 Wycheproof Ed25519 vectors under both rules. Wycheproof calls small order keys \"acceptable\". That's exactly where the rules split.",[91,981,982,983,985],{},"All twelve ed25519-speccheck cases match ",[95,984,863],{}," 2.4.0 under both rules. In a local run, verification took about 1.4 ms. Noble took about 1.1 ms. Verification takes public input only, so timing doesn't matter here.",[91,987,988,989,991],{},"Signing comes out of the tests byte for byte. That's all five RFC 8032 vectors, keys included, and six signatures frozen from ",[95,990,863],{}," 2.4.0. In a local run, signing took about 1.8 ms. Noble took about 1.0 ms. Slower, and not hiding it.",[91,993,994],{},"Is signing constant time, then? No. Both multiplications, sB and rB, take the same steps for every scalar. SHA-512 and the bigint math mod l promise nothing about time.",[996,997,998],"style",{},"html pre.shiki code .skH_V, html code.shiki .skH_V{--shiki-light:var(--shiki-token-keyword);--shiki-default:var(--shiki-token-keyword);--shiki-dark:var(--shiki-token-keyword)}html pre.shiki code .s38Sx, html code.shiki .s38Sx{--shiki-light:var(--ui-text-highlighted);--shiki-default:var(--ui-text-highlighted);--shiki-dark:var(--ui-text-highlighted)}html pre.shiki code .shU9J, html code.shiki .shU9J{--shiki-light:var(--shiki-token-string);--shiki-default:var(--shiki-token-string);--shiki-dark:var(--shiki-token-string)}html pre.shiki code .sK71F, html code.shiki .sK71F{--shiki-light:var(--shiki-token-function);--shiki-default:var(--shiki-token-function);--shiki-dark:var(--shiki-token-function)}html pre.shiki code .scIB-, html code.shiki .scIB-{--shiki-light:var(--shiki-token-comment);--shiki-default:var(--shiki-token-comment);--shiki-dark:var(--shiki-token-comment)}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);}",{"title":105,"searchDepth":133,"depth":133,"links":1000},[1001,1002,1003,1004,1005,1009,1014],{"id":88,"depth":133,"text":89},{"id":182,"depth":133,"text":183},{"id":281,"depth":133,"text":282},{"id":428,"depth":133,"text":429},{"id":455,"depth":133,"text":456,"children":1006},[1007,1008],{"id":598,"depth":140,"text":599},{"id":620,"depth":140,"text":621},{"id":679,"depth":133,"text":680,"children":1010},[1011,1012,1013],{"id":769,"depth":140,"text":770},{"id":870,"depth":140,"text":871},{"id":893,"depth":140,"text":894},{"id":963,"depth":133,"text":964},"RFC 8032 points and scalars for Monero keys. Sign Ed25519 and verify it under ZIP 215 or strict rules","md",null,{"icon":1019},"i-lucide-shield-half",{"title":1021,"description":1015},"Ed25519 signatures and ed25519 points for Monero keys","z05Z46BXxYl40b88_TiFs0pqlVM4W20k8JZ8xpvoyH8",[1024,1026],{"title":44,"path":45,"stem":46,"description":1025,"children":-1},"Polkadot keys and signatures. Schnorr over ristretto255 with Merlin transcripts and Substrate HDKD. Written from the specs",{"title":52,"path":53,"stem":54,"description":1027,"children":-1},"P-256 point math and ECDSA verification for passkeys and ES256",1793795813590]