Skip to content

Instantly share code, notes, and snippets.

@aerique
Last active May 7, 2023 10:16
Show Gist options
  • Save aerique/0362a949ab4890d6b915300e14872a5a to your computer and use it in GitHub Desktop.
Save aerique/0362a949ab4890d6b915300e14872a5a to your computer and use it in GitHub Desktop.
;;;; schnorr-bip340.lisp
;;;;
;;;; https://github.com/bitcoin/bips/blob/master/bip-0340/reference.py
;;;; https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv
;;;;
;;;; sbcl --load schnorr-bip340.lisp --eval "(progn (run-tests) (quit))"
;;; Packages
(ql:quickload :ironclad)
;;; Globals
(defparameter +p+ #xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F)
(defparameter +n+ #xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141)
(defparameter +g+ (cons #x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
#x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8))
;;; Third Party Functions
;; https://en.wikipedia.org/wiki/Modular_exponentiation
;; https://rosettacode.org/wiki/Modular_exponentiation
(defun mod-expt (base power modulus)
(loop with c = 1
while (plusp power)
do (when (oddp power)
(setf c (mod (* base c) modulus)))
(setf power (ash power -1))
(setf base (mod (* base base) modulus))
finally (return c)))
;;; Functions
(defun tagged-hash (tag msg)
(let ((tag-hash (ironclad:digest-sequence :sha256 (string-to-octets tag))))
(ironclad:digest-sequence :sha256 (concatenate '(vector (unsigned-byte 8))
tag-hash tag-hash msg))))
(defun is-infinite (point)
(null point))
(defun x (point)
(when (is-infinite point) (error "~S is infinite" point))
(car point))
(defun y (point)
(when (is-infinite point) (error "~S is infinite" point))
(cdr point))
(defun point-add (p1 p2)
(let (lam x3)
(cond ((null p1)
(return-from point-add p2))
((null p2)
(return-from point-add p1))
((and (= (x p1) (x p2))
(/= (y p1) (y p2)))
(return-from point-add nil))
((equal p1 p2)
(setf lam (mod (* 3 (x p1) (x p1)
(mod-expt (* 2 (y p1)) (- +p+ 2) +p+))
+p+)))
(t
(setf lam (mod (* (- (y p2) (y p1))
(mod-expt (- (x p2) (x p1)) (- +p+ 2) +p+))
+p+))))
(setf x3 (mod (- (* lam lam) (x p1) (x p2)) +p+))
(cons x3 (mod (- (* lam (- (x p1) x3)) (y p1)) +p+))))
(defun point-mul (p n)
(loop with r = nil
for i from 0 below 256
do (when (= 1 (logand (ash n (- i)) 1))
(setf r (point-add r p)))
(setf p (point-add p p))
finally (return r)))
(defun bytes-from-int (x)
(ironclad:integer-to-octets x :n-bits 256)) ; 32 bytes
(defun bytes-from-point (p)
(bytes-from-int (x p)))
(defun xor-bytes (b0 b1)
(loop for x across b0
for y across b1
collect (logxor x y) into lst
finally (return (coerce lst '(vector (unsigned-byte 8))))))
(defun lift-x (x)
(when (> x +p+)
(return-from lift-x nil))
(let* ((y-sq (mod (+ (mod-expt x 3 +p+) 7) +p+))
(y (mod-expt y-sq (floor (+ +p+ 1) 4) +p+)))
(if (/= (mod-expt y 2 +p+) y-sq)
nil
(cons x (if (= (logand y 1) 0)
y
(- +p+ y))))))
(defun pubkey-gen (seckey)
(let ((d0 (ironclad:octets-to-integer seckey))
p)
(when (or (<= d0 1)
(>= d0 (- +n+ 1)))
(error "The secret key must be an integer between 1..n-1"))
(setf p (point-mul +g+ d0))
(when (null p)
(error "P is null"))
(bytes-from-point p)))
(defun schnorr-verify (msg pubkey sig)
(when (/= (length msg) 32)
(error "The message must be a 32-byte array."))
(when (/= (length pubkey) 32)
(error "The public key must be a 32-byte array."))
(when (/= (length sig) 64)
(error "The signature must be a 64-byte array."))
(let ((p (lift-x (ironclad:octets-to-integer pubkey)))
(r (ironclad:octets-to-integer (subseq sig 0 32)))
(s (ironclad:octets-to-integer (subseq sig 32 64)))
e rr)
(when (or (null p)
(>= r +p+)
(>= s +n+))
(return-from schnorr-verify nil))
(setf e (mod (ironclad:octets-to-integer
(tagged-hash "BIP0340/challenge"
(concatenate 'vector (subseq sig 0 32) pubkey msg)))
+n+))
(setf rr (point-add (point-mul +g+ s) (point-mul p (- +n+ e))))
(when (or (null rr)
(oddp (y rr))
(/= (x rr) r))
(return-from schnorr-verify nil))
t))
(defun schnorr-sign (msg seckey aux-rand)
(when (/= (length msg) 32)
(error "MSG must be 32 bytes"))
(when (/= (length aux-rand) 32)
(error "AUX-RAND must be 32 bytes"))
(let ((d0 (ironclad:octets-to-integer seckey))
p d tt k0 r k e sig)
(when (or (<= d0 1)
(>= d0 (- +n+ 1)))
(error "The secret key must be an integer between 1..n-1"))
(setf p (point-mul +g+ d0))
(when (null p)
(error "P is null"))
(setf d (if (evenp (y p))
d0
(- +n+ d0)))
(setf tt (xor-bytes (bytes-from-int d)
(tagged-hash "BIP0340/aux" aux-rand)))
(setf k0 (mod (ironclad:octets-to-integer
(tagged-hash "BIP0340/nonce"
(concatenate '(vector (unsigned-byte 8))
tt
(bytes-from-point p)
msg)))
+n+))
(when (= k0 0)
(error "Failure. This happens only with negligible probability."))
(setf r (point-mul +g+ k0))
(when (null r)
(error "R is null."))
(setf k (if (oddp (y r))
(- +n+ k0)
k0))
(setf e (mod (ironclad:octets-to-integer
(tagged-hash "BIP0340/challenge"
(concatenate '(vector (unsigned-byte 8))
(bytes-from-point r)
(bytes-from-point p)
msg)))
+n+))
(setf sig (concatenate '(vector (unsigned-byte 8))
(bytes-from-point r)
(bytes-from-int (mod (+ k (* e d)) +n+))))
(unless (schnorr-verify msg (bytes-from-point p) sig)
(error "The created signature does not pass verification."))
sig))
;;; Test Vectors
;; '(index seckey pubkey aux-rand msg sig verification-result comment)
(defvar +test-vectors+
'((0 "0000000000000000000000000000000000000000000000000000000000000003" "F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9" "0000000000000000000000000000000000000000000000000000000000000000" "0000000000000000000000000000000000000000000000000000000000000000" "E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0" t nil)
(1 "B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF" "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" "0000000000000000000000000000000000000000000000000000000000000001" "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89" "6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A" t nil)
(2 "C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9" "DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8" "C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906" "7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C" "5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7" t nil)
(3 "0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710" "25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517" "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" "7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3" t "test fails if msg is reduced modulo p or n")
(4 nil "D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9" nil "4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703" "00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4" t nil)
(5 nil "EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34" nil "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89" "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B" nil "public key not on the curve")
(6 nil "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" nil "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89" "FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2" nil "has_even_y(R) is false")
(7 nil "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" nil "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89" "1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD" nil "negated message")
(8 nil "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" nil "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89" "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6" nil "negated s value")
(9 nil "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" nil "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89" "0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051" nil "sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 0")
(10 nil "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" nil "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89" "00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197" nil "sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 1")
(11 nil "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" nil "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89" "4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B" nil "sig[0:32] is not an X coordinate on the curve")
(12 nil "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" nil "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89" "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B" nil "sig[0:32] is equal to field size")
(13 nil "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" nil "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89" "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" nil "sig[32:64] is equal to curve order")
(14 nil "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30" nil "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89" "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B" nil "public key is not a valid X coordinate because it exceeds the field size")))
(defun run-tests ()
(loop with all-passed = t
for test in +test-vectors+
for index = (elt test 0)
for seckey = (when (elt test 1)
(ironclad:hex-string-to-byte-array (elt test 1)))
for pubkey = (ironclad:hex-string-to-byte-array (elt test 2))
for aux-rand = (when (elt test 3)
(ironclad:hex-string-to-byte-array (elt test 3)))
for msg = (ironclad:hex-string-to-byte-array (elt test 4))
for sig = (ironclad:hex-string-to-byte-array (elt test 5))
for result = (elt test 6)
for comment = (elt test 7)
for pubkey-actual = (when seckey (pubkey-gen seckey))
for sig-actual = (when seckey (schnorr-sign msg seckey aux-rand))
for result-actual = (schnorr-verify msg pubkey sig)
do (format t "~%Test vector #~D:~%" index)
(when seckey
(if (equalp pubkey pubkey-actual)
(format t " * Passed key generation test.~%")
(progn (setf all-passed nil)
(format t " * Failed key generation.~%")
(format t " Expected key: ~S~%" pubkey)
(format t " Actual key: ~S~%" pubkey-actual)))
(if (equalp sig sig-actual)
(format t " * Passed signing test.~%")
(progn (setf all-passed nil)
(format t " * Failed signing test.~%")
(format t " Expected signature: ~S~%" sig)
(format t " Actual signature: ~S~%" sig-actual))))
(if (equalp result result-actual)
(format t " * Passed verification test.~%")
(progn (setf all-passed nil)
(format t " * Failed verification test.~%")
(format t " Expected result: ~S~%" result)
(format t " Actual result: ~S~%" result-actual)
(when comment
(format t " Comment: ~A~%" comment))))
finally (if all-passed
(format t "~%All test vectors passed.~%")
(format t "~%Some test vectors failed.~%"))
(return all-passed)))
@aerique
Copy link
Author

aerique commented May 5, 2023

Translated from https://github.com/bitcoin/bips/blob/master/bip-0340/reference.py

Released under same license as Ironclad: BSD 3-Clause "New" or "Revised" License

@aerique
Copy link
Author

aerique commented May 7, 2023

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment