AtlatestRepositorysigil-crypto
sigil-crypto / tree / testtest-ed25519.sgl
1
;;; Ed25519 (RFC 8032) and BLAKE2b-512 in (sigil crypto).2
;;;3
;;; The bulk vector sets (RFC 8032, Wycheproof, ed25519-speccheck) are in4
;;; test-ed25519-vectors.sgl. This file holds the RFC 8032 key derivation and5
;;; signing checks, the negative cases built from a valid signature with one6
;;; controlled change, and BLAKE2b-512.8
(import (sigil test)9
(sigil crypto)10
(sigil math))12
(define (hex->bv s)13
(let* ((n (quotient (string-length s) 2))14
(bv (make-bytevector n 0)))15
(let loop ((i 0))16
(if (< i n)17
(begin18
(bytevector-u8-set! bv i (string->number (substring s (* 2 i) (+ 2 (* 2 i))) 16))19
(loop (+ i 1)))20
bv))))22
(define (bv->hex bv)23
(let loop ((i 0) (acc '()))24
(if (< i (bytevector-length bv))25
(let* ((b (bytevector-u8-ref bv i))26
(h (number->string b 16)))27
(loop (+ i 1) (cons (if (< b 16) (string-append "0" h) h) acc)))28
(apply string-append (reverse acc)))))30
;; A copy of bv with bit `bit` of byte `index` flipped.31
(define (flip-bit bv index bit)32
(let ((out (bytevector-copy bv)))33
(bytevector-u8-set! out index34
(bitwise-xor (bytevector-u8-ref out index)35
(arithmetic-shift 1 bit)))36
out))38
;; RFC 8032 section 7.1, TEST 1 to TEST 3.39
(define t1-seed (hex->bv "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"))40
(define t1-pk (hex->bv "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"))41
(define t1-msg (make-bytevector 0 0))42
(define t1-sig (hex->bv "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"))44
(define t2-seed (hex->bv "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb"))45
(define t2-pk (hex->bv "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c"))46
(define t2-msg (hex->bv "72"))47
(define t2-sig (hex->bv "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00"))49
(define t3-seed (hex->bv "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7"))50
(define t3-pk (hex->bv "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025"))51
(define t3-msg (hex->bv "af82"))52
(define t3-sig (hex->bv "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a"))54
;; TEST 1's signature with S replaced by S + L (L = 2^252 +55
;; 27742317777372353535851937790883648493, the group order), computed with56
;; Python integers. [S + L]B = [S]B, so this satisfies the verification57
;; equation; only the S < L range check can reject it.58
(define t1-sig-s-plus-l59
(hex->bv (string-append60
"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155"61
"4c8c7872aa064e049dbb3013fbf29380d25bf5f0595bbe24655141438e7a101b")))63
(test-group "ed25519 RFC 8032 derivation and signing"64
(test "TEST 1 public key from seed"65
(assert-equal (bv->hex t1-pk) (bv->hex (ed25519-public-key t1-seed))))66
(test "TEST 2 public key from seed"67
(assert-equal (bv->hex t2-pk) (bv->hex (ed25519-public-key t2-seed))))68
(test "TEST 3 public key from seed"69
(assert-equal (bv->hex t3-pk) (bv->hex (ed25519-public-key t3-seed))))70
(test "TEST 1 signature is the RFC's"71
(assert-equal (bv->hex t1-sig) (bv->hex (ed25519-sign t1-seed t1-msg))))72
(test "TEST 2 signature is the RFC's"73
(assert-equal (bv->hex t2-sig) (bv->hex (ed25519-sign t2-seed t2-msg))))74
(test "TEST 3 signature is the RFC's"75
(assert-equal (bv->hex t3-sig) (bv->hex (ed25519-sign t3-seed t3-msg))))76
(test "the seed bytevector is not wiped by derivation or signing"77
(let ((seed (bytevector-copy t2-seed)))78
(ed25519-public-key seed)79
(ed25519-sign seed "x")80
(assert-equal (bv->hex t2-seed) (bv->hex seed))))81
(test "a string message signs its UTF-8 bytes"82
(assert-equal (bv->hex (ed25519-sign t2-seed (bytevector 104 105)))83
(bv->hex (ed25519-sign t2-seed "hi")))))85
(test-group "ed25519 verify: controls"86
(test "TEST 1 verifies"87
(assert-eq #t (ed25519-verify t1-pk t1-msg t1-sig)))88
(test "TEST 2 verifies"89
(assert-eq #t (ed25519-verify t2-pk t2-msg t2-sig)))90
(test "TEST 3 verifies"91
(assert-eq #t (ed25519-verify t3-pk t3-msg t3-sig)))92
(test "a sign/verify round trip with a string message"93
(let ((sig (ed25519-sign t3-seed "round trip")))94
(assert-eq #t (ed25519-verify t3-pk "round trip" sig)))))96
;; Each negative below changes ONE input of a signature the control group97
;; shows verifying, so the change is the only possible cause of the #f.98
(test-group "ed25519 verify: negatives"99
(test "every single-bit flip of TEST 2's message fails"100
(let loop ((bit 0))101
(when (< bit 8)102
(assert-eq #f (ed25519-verify t2-pk (flip-bit t2-msg 0 bit) t2-sig))103
(loop (+ bit 1)))))104
(test "a flipped bit in each byte of TEST 3's signature fails"105
(let loop ((i 0))106
(when (< i 64)107
(assert-eq #f (ed25519-verify t3-pk t3-msg (flip-bit t3-sig i (modulo i 8))))108
(loop (+ i 1)))))109
(test "a flipped bit in each byte of TEST 3's public key fails"110
(let loop ((i 0))111
(when (< i 32)112
(assert-eq #f (ed25519-verify (flip-bit t3-pk i (modulo i 8)) t3-msg t3-sig))113
(loop (+ i 1)))))114
(test "a signature under another key fails"115
(assert-eq #f (ed25519-verify t2-pk t3-msg t3-sig)))116
(test "an extra trailing message byte fails"117
(assert-eq #f (ed25519-verify t3-pk (hex->bv "af8200") t3-sig)))118
(test "S + L (non-canonical S) fails although the equation holds"119
(assert-eq #f (ed25519-verify t1-pk t1-msg t1-sig-s-plus-l)))120
(test "the all-zero signature fails"121
(assert-eq #f (ed25519-verify t1-pk t1-msg (make-bytevector 64 0))))122
(test "the identity public key fails for any signature"123
;; Identity (order 1) as A: [8](sB - hA - R) = [8](sB - R), so a124
;; cofactored check alone would accept R = [s]B for any message.125
;; With R = B (the base point) and S = 1: [8]([1]B - h*O - B) = O for126
;; every message, so only the small-order check on A can reject it.127
(let* ((identity (let ((b (make-bytevector 32 0))) (bytevector-u8-set! b 0 1) b))128
(base-point (hex->bv "5866666666666666666666666666666666666666666666666666666666666666"))129
(one (let ((b (make-bytevector 32 0))) (bytevector-u8-set! b 0 1) b))130
(forged (bytevector-append base-point one)))131
(assert-eq #f (ed25519-verify identity "anything" forged))132
(assert-eq #f (ed25519-verify identity "anything else" forged)))))134
;; The second shape of the documented divergence from libsodium, crafted by135
;; the adversarial review (independent Python curve arithmetic): an honest136
;; key from seed 00..1f, and R = rB + T8 with T8 of order 8. Cofactored137
;; verification accepts it; libsodium's cofactorless check rejects it138
;; (test/differential/run.sh pins that). Only the key holder can make one.139
(test-group "ed25519 verify: documented cofactored acceptance"140
(test "the crafted key is the honest key for its seed"141
(let ((seed (let ((b (make-bytevector 32 0)))142
(let loop ((i 0))143
(if (< i 32) (begin (bytevector-u8-set! b i i) (loop (+ i 1))) b)))))144
(assert-equal "03a107bff3ce10be1d70dd18e74bc09967e4d6309ba50d5f1ddc8664125531b8"145
(bv->hex (ed25519-public-key seed)))))146
(test "a mixed-order R under that key is accepted"147
(assert-eq #t (ed25519-verify148
(hex->bv "03a107bff3ce10be1d70dd18e74bc09967e4d6309ba50d5f1ddc8664125531b8")149
(hex->bv "6d697865642d6f7264657220522c20686f6e657374206b6579")150
(hex->bv "12b8d902558f234e020abb7be4e3bd2c2f1e847c3e49560413512eecab8f131ec05dc8d713151494f13f8bace8263ae68b6e564f07764b39bd1f9d25b1bf6f08")))))152
(test-group "ed25519 verify: argument checking"153
(test "a 31-byte public key raises"154
(assert-error (ed25519-verify (make-bytevector 31 0) "m" t1-sig)))155
(test "a 65-byte signature raises"156
(assert-error (ed25519-verify t1-pk "m" (make-bytevector 65 0))))157
(test "a string public key raises"158
(assert-error (ed25519-verify "not a key" "m" t1-sig)))159
(test "a 16-byte seed raises"160
(assert-error (ed25519-sign (make-bytevector 16 0) "m"))))162
(test-group "blake2b-512"163
(test "RFC 7693 appendix A: abc"164
(assert-equal "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923"165
(bv->hex (blake2b-512 "abc"))))166
(test "empty input (coreutils b2sum)"167
(assert-equal "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce"168
(bv->hex (blake2b-512 (make-bytevector 0 0)))))169
(test "1000 x 'a', more than one 128-byte block (coreutils b2sum)"170
(assert-equal "d6a69459fe93fc6b9537ed4336e5099e0dcca3e97290a412500ed7a0daffb03d80cf3650a20e0591f748e10c3c534945ee83d5f2c9722f1a68d98b8c01af23fd"171
(bv->hex (blake2b-512 (make-bytevector 1000 97)))))172
(test "string and bytevector input agree"173
(assert-equal (bv->hex (blake2b-512 "abc"))174
(bv->hex (blake2b-512 (bytevector 97 98 99))))))