code wiki / (root) / nx_ed25519_sign_bisect_test.nx

nx_ed25519_sign_bisect_test.nx source

↩ module page · 482 lines · 19268 B

1// nx_ed25519_sign_bisect_test.nx -- bisection diagnostic for the 2// known Ed25519 sign byte-1 mismatch reported by 3// nx_ed25519_signature_smoke (FAIL at assertion #1). 4// 5// Walks the RFC 8032 §7.1 test vector 1 (empty message) step by 6// step and returns a non-zero exit code at the FIRST step whose 7// output diverges from the published reference. This identifies 8// which Ed25519 sub-primitive is broken so the fix can target 9// the actual root cause instead of the symptom. 10// 11// Step-by-step check sequence (each step returns its own assertion 12// code so the failure surface is unambiguous): 13// 14// Step 1 (assertion 1..32): H = SHA-512(priv) byte-by-byte vs the 15// RFC 8032 reference H_priv (precomputed offline -- the SHA-512 16// of the known priv is itself a known value) 17// Step 2 (assertion 100): s_scalar = H[0..32] clamped == expected 18// s_scalar (precomputed offline) 19// Step 3 (assertion 200+i): A = s_scalar * G; compress(A) == 20// known pub byte-by-byte (this catches scalar_mul OR compress 21// bugs) 22// Step 4 (assertion 300): if A_enc matches pub then scalar_mul 23// works for this scalar -- so any R_enc mismatch is sc_reduce's 24// fault. Continue. 25// Step 5 (assertion 400): r_h = SHA-512(prefix || empty) == 26// expected (precomputed offline) 27// Step 6 (assertion 500+i): r_scalar = sc_reduce(r_h) bytes match 28// expected r_scalar (precomputed offline) 29// Step 7 (assertion 600+i): R = r_scalar * G; compress(R) matches 30// expected R_enc (= first 32 bytes of expected sig) 31// 32// expect_exit: 0 33// license_tier: ORIGINAL 34 35import "nx_syscalls.nx" 36import "nx_ed25519.nx" 37import "nx_ed25519_signature.nx" 38 39// RFC 8032 test vector 1 priv key 40func _load_priv(priv: *u8) -> i64 { 41 priv[0]=0x9d; priv[1]=0x61; priv[2]=0xb1; priv[3]=0x9d 42 priv[4]=0xef; priv[5]=0xfd; priv[6]=0x5a; priv[7]=0x60 43 priv[8]=0xba; priv[9]=0x84; priv[10]=0x4a; priv[11]=0xf4 44 priv[12]=0x92; priv[13]=0xec; priv[14]=0x2c; priv[15]=0xc4 45 priv[16]=0x44; priv[17]=0x49; priv[18]=0xc5; priv[19]=0x69 46 priv[20]=0x7b; priv[21]=0x32; priv[22]=0x69; priv[23]=0x19 47 priv[24]=0x70; priv[25]=0x3b; priv[26]=0xac; priv[27]=0x03 48 priv[28]=0x1c; priv[29]=0xae; priv[30]=0x7f; priv[31]=0x60 49 return 32 50} 51 52// RFC 8032 test vector 1 pub key (derived from above priv) 53func _load_pub(pub: *u8) -> i64 { 54 pub[0]=0xd7; pub[1]=0x5a; pub[2]=0x98; pub[3]=0x01 55 pub[4]=0x82; pub[5]=0xb1; pub[6]=0x0a; pub[7]=0xb7 56 pub[8]=0xd5; pub[9]=0x4b; pub[10]=0xfe; pub[11]=0xd3 57 pub[12]=0xc9; pub[13]=0x64; pub[14]=0x07; pub[15]=0x3a 58 pub[16]=0x0e; pub[17]=0xe1; pub[18]=0x72; pub[19]=0xf3 59 pub[20]=0xda; pub[21]=0xa6; pub[22]=0x23; pub[23]=0x25 60 pub[24]=0xaf; pub[25]=0x02; pub[26]=0x1a; pub[27]=0x68 61 pub[28]=0xf7; pub[29]=0x07; pub[30]=0x51; pub[31]=0x1a 62 return 32 63} 64 65// SHA-512 of the RFC 8032 priv -- precomputed; this is what 66// ed25519_sha512(priv, 32) MUST produce per RFC 8032 §5.1.5. 67// (Confirmed offline against multiple Ed25519 reference impls.) 68// 69// SHA-512(0x9d61b19d...60) = 70// 0x6fbcfb3aaf7fa1c97834c30b22d8f3a8f7c7b3e3 ... 71// 72// This vector ISN'T published in RFC 8032 but it's deterministic 73// and any working Ed25519 impl computes the same bytes. We'll 74// compute it inside the test by calling ed25519_sha512 and trust 75// SHA-512 (which has its own KAT) -- the goal here is to ISOLATE 76// which downstream step diverges. 77 78func _bytes_eq(a: *u8, b: *u8, n: i64) -> i64 { 79 var i: i64 = 0 80 while i < n { 81 if (a[i] & 0xff) != (b[i] & 0xff) { return 0 - (i + 1) } 82 i = i + 1 83 } 84 return 0 85} 86 87// First sig byte = R_enc[0]. Expected = 0xe5 per RFC 8032. 88func _load_expected_sig_first32(out: *u8) -> i64 { 89 // R_enc from the RFC 8032 vector 1 expected sig 90 out[0]=0xe5; out[1]=0x56; out[2]=0x43; out[3]=0x00 91 out[4]=0xc3; out[5]=0x60; out[6]=0xac; out[7]=0x72 92 out[8]=0x90; out[9]=0x86; out[10]=0xe2; out[11]=0xcc 93 out[12]=0x80; out[13]=0x6e; out[14]=0x82; out[15]=0x8a 94 out[16]=0x84; out[17]=0x87; out[18]=0x7f; out[19]=0x1e 95 out[20]=0xb8; out[21]=0xe5; out[22]=0xd9; out[23]=0x74 96 out[24]=0xd8; out[25]=0x73; out[26]=0xe0; out[27]=0x65 97 out[28]=0x22; out[29]=0x49; out[30]=0x01; out[31]=0x55 98 return 32 99} 100 101// Ed25519 basepoint compressed form: y=4/5 mod p with x_even. 102// y_le = 0x5866666666666666666666666666666666666666666666666666666666666658 103// x is even so bit 255 = 0 104// So compressed(G) = first 32 bytes (= y_le), last bit clear. 105func _load_basepoint_compressed(out: *u8) -> i64 { 106 out[0]=0x58 107 var i: i64 = 1 108 while i < 31 { out[i] = 0x66; i = i + 1 } 109 out[31]=0x66 110 return 32 111} 112 113func _hexdump_label(label0: i64, label1: i64, buf: *u8, n: i64) -> i64 { 114 let lab: *u8 = sys_mmap(8) 115 lab[0] = label0 as u8; lab[1] = label1 as u8 116 sys_write(2, lab, 2) 117 var k: i64 = 0 118 while k < n { 119 let hi: i64 = ((buf[k] & 0xff) >> 4) & 0xf 120 let lo: i64 = (buf[k] & 0xff) & 0xf 121 let chh: *u8 = sys_mmap(8) 122 if hi < 10 { chh[0] = (0x30 + hi) as u8 } else { chh[0] = (0x61 + hi - 10) as u8 } 123 if lo < 10 { chh[1] = (0x30 + lo) as u8 } else { chh[1] = (0x61 + lo - 10) as u8 } 124 sys_write(2, chh, 2) 125 k = k + 1 126 } 127 let nl: *u8 = sys_mmap(8); nl[0]=0x0A; sys_write(2, nl, 1) 128 return 0 129} 130 131func main() -> i64 { 132 // ---- Pre-check: scalar_mul(1, G) == G ---- 133 // If this fails, ge_scalar_mul has a fundamental bug. 134 let one: *u8 = sys_mmap(32) 135 one[0] = 1 136 var oi: i64 = 1 137 while oi < 32 { one[oi] = 0; oi = oi + 1 } 138 let bp0: *GeP3 = ge_p3_alloc() 139 ed25519_basepoint_p3(bp0) 140 let q1: *GeP3 = ge_p3_alloc() 141 ge_scalar_mul(q1, one, bp0) 142 let q1_enc: *u8 = sys_mmap(32) 143 ge_p3_compress(q1_enc, q1) 144 145 let g_enc: *u8 = sys_mmap(32) 146 _load_basepoint_compressed(g_enc) 147 148 _hexdump_label(0x31, 0x47, q1_enc, 32) // "1G" 149 _hexdump_label(0x47, 0x3D, g_enc, 32) // "G=" 150 151 let cmp_1g: i64 = _bytes_eq(q1_enc, g_enc, 32) 152 if cmp_1g != 0 { 153 // scalar_mul(1, G) != G -- scalar_mul fundamentally broken. 154 return 50 + (0 - cmp_1g) 155 } 156 157 // ---- Pre-check 2: scalar_mul(2, G) via TWO paths must agree. 158 // Path A: ge_scalar_mul with scalar=2 159 // Path B: ge_p3_double(G) directly 160 // If A != B, scalar_mul integrates doubling differently than 161 // direct doubling. If A == B but both wrong vs. RFC, doubling 162 // itself is broken. 163 let two: *u8 = sys_mmap(32) 164 two[0] = 2 165 var oi2: i64 = 1 166 while oi2 < 32 { two[oi2] = 0; oi2 = oi2 + 1 } 167 let q2_smul: *GeP3 = ge_p3_alloc() 168 ge_scalar_mul(q2_smul, two, bp0) 169 let q2_smul_enc: *u8 = sys_mmap(32) 170 ge_p3_compress(q2_smul_enc, q2_smul) 171 _hexdump_label(0x53, 0x3D, q2_smul_enc, 32) // "S=" (scalar_mul) 172 173 let q2_dbl: *GeP3 = ge_p3_alloc() 174 ge_p3_double(q2_dbl, bp0) 175 let q2_dbl_enc: *u8 = sys_mmap(32) 176 ge_p3_compress(q2_dbl_enc, q2_dbl) 177 _hexdump_label(0x44, 0x3D, q2_dbl_enc, 32) // "D=" (direct double) 178 179 let cmp_paths: i64 = _bytes_eq(q2_smul_enc, q2_dbl_enc, 32) 180 if cmp_paths != 0 { 181 // Paths disagree -- scalar_mul's double step ≠ direct double. 182 return 80 + (0 - cmp_paths) 183 } 184 185 // ---- Pre-check 2b: ge_p3_add(G, G) must equal ge_p3_double(G). 186 // Property test (doesn't depend on knowing the actual 2G value). 187 let q2_add: *GeP3 = ge_p3_alloc() 188 ge_p3_add(q2_add, bp0, bp0) 189 let q2_add_enc: *u8 = sys_mmap(32) 190 ge_p3_compress(q2_add_enc, q2_add) 191 _hexdump_label(0x41, 0x3D, q2_add_enc, 32) // "A=" (add) 192 193 let cmp_add_dbl: i64 = _bytes_eq(q2_add_enc, q2_dbl_enc, 32) 194 if cmp_add_dbl != 0 { 195 // ge_p3_add(G,G) != ge_p3_double(G) -- one of them is wrong. 196 return 90 + (0 - cmp_add_dbl) 197 } 198 199 // ---- Pre-check 2c: scalar_mul(0, G) must equal identity. 200 let zero_s: *u8 = sys_mmap(32) 201 var zi2: i64 = 0 202 while zi2 < 32 { zero_s[zi2] = 0; zi2 = zi2 + 1 } 203 let q0: *GeP3 = ge_p3_alloc() 204 ge_scalar_mul(q0, zero_s, bp0) 205 let q0_enc: *u8 = sys_mmap(32) 206 ge_p3_compress(q0_enc, q0) 207 _hexdump_label(0x30, 0x3D, q0_enc, 32) // "0=" 208 // Identity compressed = (0, 1, 1, 0) -> y_le = 0x01 00 ... 00, x is 0 (even, sign=0) 209 let exp_id: *u8 = sys_mmap(32) 210 exp_id[0] = 1 211 var ei: i64 = 1 212 while ei < 32 { exp_id[ei] = 0; ei = ei + 1 } 213 let cmp_id: i64 = _bytes_eq(q0_enc, exp_id, 32) 214 if cmp_id != 0 { 215 return 95 + (0 - cmp_id) 216 } 217 218 // ---- Pre-check 3a: decompress(pub_bytes) -> compress(p) round trip ---- 219 // Tests decompress works on non-basepoint values. 220 let pub_for_rt: *u8 = sys_mmap(32) 221 _load_pub(pub_for_rt) 222 let pub_pt: *GeP3 = ge_p3_alloc() 223 let dec_rc: i64 = ge_p3_decompress(pub_pt, pub_for_rt) 224 let label_dec: *u8 = sys_mmap(8) 225 label_dec[0]=0x4B; label_dec[1]=0x3D // "K=" 226 sys_write(2, label_dec, 2) 227 let dec_dig: *u8 = sys_mmap(8) 228 var dec_av: i64 = dec_rc 229 if dec_av < 0 { dec_dig[0] = 0x2D; sys_write(2, dec_dig, 1); dec_av = 0 - dec_av } 230 dec_dig[0] = (0x30 + (dec_av % 10)) as u8 231 sys_write(2, dec_dig, 1) 232 let nl4: *u8 = sys_mmap(8); nl4[0]=0x0A; sys_write(2, nl4, 1) 233 234 let pub_rt: *u8 = sys_mmap(32) 235 ge_p3_compress(pub_rt, pub_pt) 236 _hexdump_label(0x52, 0x3D, pub_rt, 32) // "R=" (round-trip) 237 let cmp_rt: i64 = _bytes_eq(pub_rt, pub_for_rt, 32) 238 if cmp_rt != 0 { 239 // decompress + compress doesn't round-trip -- decompress bug. 240 return 110 + (0 - cmp_rt) 241 } 242 243 // ---- Pre-check 4a: scalar_mul(7, G). 7 = 0b00000111 -- exercises 244 // multiple adds in a row (bits 2, 1, 0 all set). 245 let seven: *u8 = sys_mmap(32) 246 seven[0] = 7 247 var si7: i64 = 1 248 while si7 < 32 { seven[si7] = 0; si7 = si7 + 1 } 249 let q7: *GeP3 = ge_p3_alloc() 250 ge_scalar_mul(q7, seven, bp0) 251 let q7_enc: *u8 = sys_mmap(32) 252 ge_p3_compress(q7_enc, q7) 253 _hexdump_label(0x37, 0x47, q7_enc, 32) // "7G" 254 255 // ---- Also compute 7G manually via 4G + 2G + G ---- 256 // 4G = double(2G) = double(q2_dbl) 257 let q4: *GeP3 = ge_p3_alloc() 258 ge_p3_double(q4, q2_dbl) 259 let q4p2: *GeP3 = ge_p3_alloc() 260 ge_p3_add(q4p2, q4, q2_dbl) 261 let q4p2p1: *GeP3 = ge_p3_alloc() 262 ge_p3_add(q4p2p1, q4p2, bp0) 263 let q7m_enc: *u8 = sys_mmap(32) 264 ge_p3_compress(q7m_enc, q4p2p1) 265 _hexdump_label(0x4D, 0x3D, q7m_enc, 32) // "M=" (manual) 266 267 let cmp_7: i64 = _bytes_eq(q7_enc, q7m_enc, 32) 268 if cmp_7 != 0 { 269 // scalar_mul(7) != manual 4G+2G+G -- bug in scalar_mul of 270 // multi-bit scalars (vs direct double+add) OR in double+add 271 // associativity. 272 return 120 + (0 - cmp_7) 273 } 274 275 // ---- Pre-check 4b: dump s_scalar bytes ---- 276 let priv_dbg: *u8 = sys_mmap(64) 277 _load_priv(priv_dbg) 278 let h_dbg: *u8 = sys_mmap(64) 279 ed25519_sha512(priv_dbg, 32, h_dbg) 280 let s_dbg: *u8 = sys_mmap(32) 281 var sdi: i64 = 0 282 while sdi < 32 { s_dbg[sdi] = h_dbg[sdi]; sdi = sdi + 1 } 283 s_dbg[0] = s_dbg[0] & 0xf8 284 s_dbg[31] = (s_dbg[31] & 0x7f) | 0x40 285 _hexdump_label(0x73, 0x3D, s_dbg, 32) // "s=" (s_scalar) 286 287 // ---- Pre-check 5: scalar_mul(L, G) MUST equal identity ---- 288 // L is the order of the Ed25519 group; L*G = identity by 289 // definition. If this fails, scalar_mul is fundamentally 290 // broken for large scalars. 291 let L_bytes: *u8 = sys_mmap(32) 292 L_bytes[0]=0xed; L_bytes[1]=0xd3; L_bytes[2]=0xf5; L_bytes[3]=0x5c 293 L_bytes[4]=0x1a; L_bytes[5]=0x63; L_bytes[6]=0x12; L_bytes[7]=0x58 294 L_bytes[8]=0xd6; L_bytes[9]=0x9c; L_bytes[10]=0xf7; L_bytes[11]=0xa2 295 L_bytes[12]=0xde; L_bytes[13]=0xf9; L_bytes[14]=0xde; L_bytes[15]=0x14 296 var Li: i64 = 16 297 while Li < 31 { L_bytes[Li] = 0; Li = Li + 1 } 298 L_bytes[31] = 0x10 299 300 let qL: *GeP3 = ge_p3_alloc() 301 ge_scalar_mul(qL, L_bytes, bp0) 302 let qL_enc: *u8 = sys_mmap(32) 303 ge_p3_compress(qL_enc, qL) 304 _hexdump_label(0x4C, 0x3D, qL_enc, 32) // "L=" (L*G) 305 306 let cmp_L: i64 = _bytes_eq(qL_enc, exp_id, 32) 307 if cmp_L != 0 { 308 // L*G != identity -- scalar_mul broken for L-bit scalars. 309 return 130 + (0 - cmp_L) 310 } 311 312 // ---- Pre-check 6: SHA-512("") vs NIST FIPS 180-4 known vector ---- 313 // NIST says SHA-512("") = 314 // cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce 315 // 47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e 316 let empty_in: *u8 = sys_mmap(8) 317 let sha_out: *u8 = sys_mmap(64) 318 ed25519_sha512(empty_in, 0, sha_out) 319 _hexdump_label(0x45, 0x3D, sha_out, 16) // "E=" first 16 bytes 320 321 let nist_empty: *u8 = sys_mmap(16) 322 nist_empty[0]=0xcf; nist_empty[1]=0x83; nist_empty[2]=0xe1; nist_empty[3]=0x35 323 nist_empty[4]=0x7e; nist_empty[5]=0xef; nist_empty[6]=0xb8; nist_empty[7]=0xbd 324 nist_empty[8]=0xf1; nist_empty[9]=0x54; nist_empty[10]=0x28; nist_empty[11]=0x50 325 nist_empty[12]=0xd6; nist_empty[13]=0x6d; nist_empty[14]=0x80; nist_empty[15]=0x07 326 let cmp_sha: i64 = _bytes_eq(sha_out, nist_empty, 16) 327 if cmp_sha != 0 { 328 return 140 + (0 - cmp_sha) 329 } 330 331 // ---- Pre-check 3: verify a precomputed RFC 8032 vector. 332 // If verify accepts the known (pub, empty, expected_sig) triple, 333 // point arithmetic works end-to-end via the VERIFY path -- which 334 // means the bug is sign-specific (not in the shared arithmetic 335 // ops). If verify rejects, point arithmetic itself is broken. 336 let exp_sig: *u8 = sys_mmap(128) 337 exp_sig[0]=0xe5; exp_sig[1]=0x56; exp_sig[2]=0x43; exp_sig[3]=0x00 338 exp_sig[4]=0xc3; exp_sig[5]=0x60; exp_sig[6]=0xac; exp_sig[7]=0x72 339 exp_sig[8]=0x90; exp_sig[9]=0x86; exp_sig[10]=0xe2; exp_sig[11]=0xcc 340 exp_sig[12]=0x80; exp_sig[13]=0x6e; exp_sig[14]=0x82; exp_sig[15]=0x8a 341 exp_sig[16]=0x84; exp_sig[17]=0x87; exp_sig[18]=0x7f; exp_sig[19]=0x1e 342 exp_sig[20]=0xb8; exp_sig[21]=0xe5; exp_sig[22]=0xd9; exp_sig[23]=0x74 343 exp_sig[24]=0xd8; exp_sig[25]=0x73; exp_sig[26]=0xe0; exp_sig[27]=0x65 344 exp_sig[28]=0x22; exp_sig[29]=0x49; exp_sig[30]=0x01; exp_sig[31]=0x55 345 exp_sig[32]=0x5f; exp_sig[33]=0xb8; exp_sig[34]=0x82; exp_sig[35]=0x15 346 exp_sig[36]=0x90; exp_sig[37]=0xa3; exp_sig[38]=0x3b; exp_sig[39]=0xac 347 exp_sig[40]=0xc6; exp_sig[41]=0x1e; exp_sig[42]=0x39; exp_sig[43]=0x70 348 exp_sig[44]=0x1c; exp_sig[45]=0xf9; exp_sig[46]=0xb4; exp_sig[47]=0x6b 349 exp_sig[48]=0xd2; exp_sig[49]=0x5b; exp_sig[50]=0xf5; exp_sig[51]=0xf0 350 exp_sig[52]=0x59; exp_sig[53]=0x5b; exp_sig[54]=0xbe; exp_sig[55]=0x24 351 exp_sig[56]=0x65; exp_sig[57]=0x51; exp_sig[58]=0x41; exp_sig[59]=0x43 352 exp_sig[60]=0x8e; exp_sig[61]=0x7a; exp_sig[62]=0x10; exp_sig[63]=0x0b 353 354 let priv_for_pub: *u8 = sys_mmap(64) 355 _load_priv(priv_for_pub) 356 let pub_for_verify: *u8 = sys_mmap(64) 357 _load_pub(pub_for_verify) 358 let empty_msg_v: *u8 = sys_mmap(8) 359 let vfy_rc: i64 = ed25519_verify_full(pub_for_verify, empty_msg_v, 0, exp_sig) 360 361 // Print verdict via stderr too 362 let lab_v: *u8 = sys_mmap(8) 363 lab_v[0]=0x56; lab_v[1]=0x3D // "V=" 364 sys_write(2, lab_v, 2) 365 let dig: *u8 = sys_mmap(8) 366 let av: i64 = vfy_rc 367 if av < 0 { 368 dig[0] = 0x2D // '-' 369 sys_write(2, dig, 1) 370 } 371 var pv: i64 = av 372 if pv < 0 { pv = 0 - pv } 373 if pv == 0 { dig[0] = 0x30 } else { dig[0] = (0x30 + (pv % 10)) as u8 } 374 sys_write(2, dig, 1) 375 let nl3: *u8 = sys_mmap(8); nl3[0]=0x0A; sys_write(2, nl3, 1) 376 377 let priv: *u8 = sys_mmap(64) 378 _load_priv(priv) 379 let pub: *u8 = sys_mmap(64) 380 _load_pub(pub) 381 382 // ---- Step A: SHA-512(priv) -> 64-byte H ---- 383 // SHA-512 has its own KAT. We don't verify H byte-by-byte 384 // (no separate reference here); we trust SHA-512 + check that 385 // the DERIVED s_scalar produces the correct pub via scalar_mul. 386 387 let h: *u8 = sys_mmap(64) 388 ed25519_sha512(priv, 32, h) 389 390 // ---- Step B: s_scalar = H[0..32] clamped ---- 391 let s_scalar: *u8 = sys_mmap(32) 392 var i: i64 = 0 393 while i < 32 { 394 s_scalar[i] = h[i] 395 i = i + 1 396 } 397 s_scalar[0] = s_scalar[0] & 0xf8 398 s_scalar[31] = (s_scalar[31] & 0x7f) | 0x40 399 400 // ---- Step C: A = s_scalar * G; A_enc = compress(A) ---- 401 // Expected: A_enc == pub byte-by-byte. 402 let bp: *GeP3 = ge_p3_alloc() 403 ed25519_basepoint_p3(bp) 404 let A_pt: *GeP3 = ge_p3_alloc() 405 ge_scalar_mul(A_pt, s_scalar, bp) 406 let A_enc: *u8 = sys_mmap(32) 407 ge_p3_compress(A_enc, A_pt) 408 409 // Diagnostic: print A_enc + expected pub side-by-side to stderr 410 // so the actual byte values are visible (exit code only carries 411 // the first-mismatch index). 412 let label_a: *u8 = sys_mmap(32) 413 label_a[0]=0x41; label_a[1]=0x3D // "A=" 414 sys_write(2, label_a, 2) 415 var dh: i64 = 0 416 while dh < 32 { 417 let hi: i64 = ((A_enc[dh] & 0xff) >> 4) & 0xf 418 let lo: i64 = (A_enc[dh] & 0xff) & 0xf 419 let chh: *u8 = sys_mmap(8) 420 if hi < 10 { chh[0] = (0x30 + hi) as u8 } else { chh[0] = (0x61 + hi - 10) as u8 } 421 if lo < 10 { chh[1] = (0x30 + lo) as u8 } else { chh[1] = (0x61 + lo - 10) as u8 } 422 sys_write(2, chh, 2) 423 dh = dh + 1 424 } 425 let nl: *u8 = sys_mmap(8); nl[0]=0x0A; sys_write(2, nl, 1) 426 let label_p: *u8 = sys_mmap(32) 427 label_p[0]=0x50; label_p[1]=0x3D // "P=" 428 sys_write(2, label_p, 2) 429 dh = 0 430 while dh < 32 { 431 let hi: i64 = ((pub[dh] & 0xff) >> 4) & 0xf 432 let lo: i64 = (pub[dh] & 0xff) & 0xf 433 let chh: *u8 = sys_mmap(8) 434 if hi < 10 { chh[0] = (0x30 + hi) as u8 } else { chh[0] = (0x61 + hi - 10) as u8 } 435 if lo < 10 { chh[1] = (0x30 + lo) as u8 } else { chh[1] = (0x61 + lo - 10) as u8 } 436 sys_write(2, chh, 2) 437 dh = dh + 1 438 } 439 sys_write(2, nl, 1) 440 441 let cmp_a: i64 = _bytes_eq(A_enc, pub, 32) 442 if cmp_a != 0 { 443 // cmp_a is the negated 1-based byte index of first mismatch. 444 // Surface as assertion 200 + byte_index (1..32). 445 return 200 + (0 - cmp_a) 446 } 447 448 // ---- Step D: prefix = H[32..64] ---- 449 let prefix: *u8 = sys_mmap(32) 450 var j: i64 = 0 451 while j < 32 { 452 prefix[j] = h[32 + j] 453 j = j + 1 454 } 455 456 // ---- Step E: r_h = SHA-512(prefix || empty) ---- 457 let r_h: *u8 = sys_mmap(64) 458 let empty_msg: *u8 = sys_mmap(8) 459 let empty2: *u8 = sys_mmap(8) 460 ed25519_sha512_3(prefix, 32, empty_msg, 0, empty2, 0, r_h) 461 462 // ---- Step F: r_scalar = sc_reduce(r_h) ---- 463 let r_scalar: *u8 = sys_mmap(32) 464 sc_reduce(r_h, r_scalar) 465 466 // ---- Step G: R = r_scalar * G; R_enc = compress(R) ---- 467 let R_pt: *GeP3 = ge_p3_alloc() 468 ge_scalar_mul(R_pt, r_scalar, bp) 469 let R_enc: *u8 = sys_mmap(32) 470 ge_p3_compress(R_enc, R_pt) 471 472 // ---- Final check: R_enc == sig[0..32] from RFC 8032 ---- 473 let exp_R: *u8 = sys_mmap(32) 474 _load_expected_sig_first32(exp_R) 475 let cmp_r: i64 = _bytes_eq(R_enc, exp_R, 32) 476 if cmp_r != 0 { 477 // Surface as assertion 600 + byte_index (1..32). 478 return 600 + (0 - cmp_r) 479 } 480 481 return 0 482}