From 33f1e443167b1b0410be4061f688f77da4355508 Mon Sep 17 00:00:00 2001 From: Jake Bordens Date: Mon, 1 Jan 2018 19:28:04 -0500 Subject: [PATCH] Initial commit --- .gitignore | 5 + Makefile | 92 ++++++ README.md | 133 ++++++++ glyphs/icon_back.gif | Bin 0 -> 74 bytes glyphs/icon_burst.gif | Bin 0 -> 83 bytes glyphs/icon_dashboard.gif | Bin 0 -> 1133 bytes nanos_app_burst.gif | Bin 0 -> 80 bytes python/RSAddress.py | 463 ++++++++++++++++++++++++++ python/curve25519.py | 154 +++++++++ python/ledger-burst.py | 266 +++++++++++++++ python/transaction.py | 78 +++++ src/crypto/curve25519_i64.c | 629 ++++++++++++++++++++++++++++++++++++ src/crypto/curve25519_i64.h | 79 +++++ src/crypto/rs_address.c | 77 +++++ src/crypto/rs_address.h | 27 ++ src/glyphs.c | 45 +++ src/glyphs.h | 45 +++ src/main.c | 485 +++++++++++++++++++++++++++ src/main.h | 99 ++++++ src/u2f/u2f_io.c | 79 +++++ src/u2f/u2f_io.h | 35 ++ src/u2f/u2f_processing.c | 279 ++++++++++++++++ src/u2f/u2f_processing.h | 29 ++ src/u2f/u2f_service.c | 147 +++++++++ src/u2f/u2f_service.h | 112 +++++++ src/u2f/u2f_timer.h | 33 ++ src/u2f/u2f_transport.c | 229 +++++++++++++ src/u2f/u2f_transport.h | 78 +++++ src/u2f/usbd_hid_impl.c | 539 ++++++++++++++++++++++++++++++ src/u2f/usbd_hid_impl.h | 10 + src/ui.c | 353 ++++++++++++++++++++ src/ui.h | 62 ++++ 32 files changed, 4662 insertions(+) create mode 100755 Makefile create mode 100644 glyphs/icon_back.gif create mode 100644 glyphs/icon_burst.gif create mode 100644 glyphs/icon_dashboard.gif create mode 100644 nanos_app_burst.gif create mode 100644 python/RSAddress.py create mode 100644 python/curve25519.py create mode 100644 python/ledger-burst.py create mode 100644 python/transaction.py create mode 100644 src/crypto/curve25519_i64.c create mode 100644 src/crypto/curve25519_i64.h create mode 100644 src/crypto/rs_address.c create mode 100644 src/crypto/rs_address.h create mode 100644 src/glyphs.c create mode 100644 src/glyphs.h create mode 100644 src/main.c create mode 100644 src/main.h create mode 100644 src/u2f/u2f_io.c create mode 100644 src/u2f/u2f_io.h create mode 100644 src/u2f/u2f_processing.c create mode 100644 src/u2f/u2f_processing.h create mode 100644 src/u2f/u2f_service.c create mode 100644 src/u2f/u2f_service.h create mode 100644 src/u2f/u2f_timer.h create mode 100644 src/u2f/u2f_transport.c create mode 100644 src/u2f/u2f_transport.h create mode 100644 src/u2f/usbd_hid_impl.c create mode 100644 src/u2f/usbd_hid_impl.h create mode 100644 src/ui.c create mode 100644 src/ui.h diff --git a/.gitignore b/.gitignore index c6127b3..199a053 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,8 @@ modules.order Module.symvers Mkfile.old dkms.conf + +# Project-level stuff +debug/ +.DS_Store +*.pyc \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100755 index 0000000..1d099a8 --- /dev/null +++ b/Makefile @@ -0,0 +1,92 @@ +#******************************************************************************* +# Ledger Blue +# (c) 2016 Ledger +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#******************************************************************************* + +ifeq ($(BOLOS_SDK),) +$(error BOLOS_SDK is not set) +endif +include $(BOLOS_SDK)/Makefile.defines + +# Main app configuration + +APPNAME = "Burstcoin" +APPVERSION = 1.0.0 +ICONNAME = nanos_app_burst.gif +APP_LOAD_PARAMS = --appFlags 0x40 --path "44'/30'" --curve secp256k1 --curve ed25519 $(COMMON_LOAD_PARAMS) + +# Build configuration + +APP_SOURCE_PATH += src +SDK_SOURCE_PATH += lib_stusb # lib_stusb_impl # - not included here because we're overriding + +DEFINES += APPVERSION=\"$(APPVERSION)\" + +DEFINES += OS_IO_SEPROXYHAL IO_SEPROXYHAL_BUFFER_SIZE_B=128 +DEFINES += HAVE_BAGL HAVE_SPRINTF +DEFINES += PRINTF\(...\)= + +DEFINES += HAVE_IO_USB HAVE_L4_USBLIB IO_USB_MAX_ENDPOINTS=7 IO_HID_EP_LENGTH=64 HAVE_USB_APDU + +# U2F Support +DEFINES += HAVE_U2F +DEFINES += USB_SEGMENT_SIZE=64 +DEFINES += BLE_SEGMENT_SIZE=32 #max MTU, min 20 +DEFINES += U2F_MAX_MESSAGE_SIZE=264 #257+5+2 +DEFINES += UNUSED\(x\)=\(void\)x +DEFINES += APPVERSION=\"$(APPVERSION)\" + +# Compiler, assembler, and linker + +ifneq ($(BOLOS_ENV),) +$(info BOLOS_ENV=$(BOLOS_ENV)) +CLANGPATH := $(BOLOS_ENV)/clang-arm-fropi/bin/ +GCCPATH := $(BOLOS_ENV)/gcc-arm-none-eabi-5_3-2016q1/bin/ +else +$(info BOLOS_ENV is not set: falling back to CLANGPATH and GCCPATH) +endif +ifeq ($(CLANGPATH),) +$(info CLANGPATH is not set: clang will be used from PATH) +endif +ifeq ($(GCCPATH),) +$(info GCCPATH is not set: arm-none-eabi-* will be used from PATH) +endif + +CC := $(CLANGPATH)clang +CFLAGS += -O3 -Os + +AS := $(GCCPATH)arm-none-eabi-gcc +AFLAGS += + +LD := $(GCCPATH)arm-none-eabi-gcc +LDFLAGS += -O3 -Os +LDLIBS += -lm -lgcc -lc + +# Main rules + +all: default + +load: all + python -m ledgerblue.loadApp $(APP_LOAD_PARAMS) + +delete: + python -m ledgerblue.deleteApp $(COMMON_DELETE_PARAMS) + +glyphs: + include $(BOLOS_SDK)/Makefile.glyphs + +# Import generic rules from the SDK + +include $(BOLOS_SDK)/Makefile.rules diff --git a/README.md b/README.md index 8d7cadf..df4a939 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,135 @@ # nanos-app-burst Burstcoin wallet application for Ledger Nano S + +# Introduction +This is a proof-of concept Ledger Nano S wallet app for Burstcoin. I say that +it is proof of concept for a few reasons: + +- I am not a cryptography or crypto-currency expert, and I could have introduced + errors or security flaws into the code unintentionally. + +- I am knowingly using an ED25519 seed function to generate a Curve25519 keypair + I think this is okay, and I'll discuss it more below, but I'm not sure. See + "technical details" section below. + +- Just in general, you shouldn't trust others with your crypto-currency. + +As such, you agree to use this code AT YOUR OWN RISK: + +The author provides the Work (and each Contributor provides its Contributions) on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied, including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or redistributing +the Work and assume any risks associated with Your exercise of permissions under +this License. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to damages +for loss of goodwill, work stoppage, computer failure or malfunction, or any and +all other commercial damages or losses), even if such Contributor has been +advised of the possibility of such damages. + +# License + +Copyright (c) 2017-2018 Jake B. + +This application is based on example sources from Ledger that were released under +the Apache License, Version 2.0 and I have maintained this license for this app. + +Other public domain materials, such as the Curve25519 sources, or implementations +of known algorithms are also included. + +# Building + +You'll need a Ledger Nano S development environment. More information can be +found here: https://github.com/LedgerHQ/blue-devenv/ + +A modification to the current version of the python loader app may be required +depending on whether Ledger has released a new version or not. More on this here: +https://github.com/lenondupe/ledger-app-stellar#troubleshooting + +# Technical Details + +## Why I used a curve25519 library rather than internal functions + +I spent some time trying to get the Ledger-provided keypair generation and curve +multiplication functions to do the necessary cryptographic singing necessary +for Burstcoin. + +After some time, I gave up and just went with a library known to work. I'm not +sure why the internal functions didn't work properly. I tried all sorts of +combinations and endianess but never got a keypair that worked. Possibilities +include: + +- I was just doing something wrong, misunderstanding the SDK or the cryptography +- Keys and signatures may have been in a different format than Burst expects... +Possibly endianness or key format, I'm not an expert. +- Possibly Curve25519 is not fully implemented in the Ledger SDK +- Possible Burst uses a specific variant or dialect of Curve25519, and I'm not +aware. + +## Why do I use the ED25519 key data generation function instead of Curve25519? + +The Ledger smartly uses a system of entitlements for apps. At load time, you +need to declare which BIP39 keypaths and algorithms your app exepects to +generate. + +This prevents a malicious or poorly secured app from generating keys or signing +transactions for a crypto-coin outside of the declared BIP39 keypath or for an +algorithm that is not explicitly needed by the Ledger app. + +The function involved is called os_perso_derive_node_bip32: + + os_perso_derive_node_bip32(CX_CURVE_Ed25519, path, 5, privateKeyData, NULL); + +I use CX_CURVE_Ed25519 because the entitlement system doesn't apparently +support the CX_CURVE_Curve25519 flag. + +I think both ED25519 and Curve25591 expect the key data 'secret' to be a +265-byte integer... so I think this is okay... but I'm not an expert as to +whether this introduces security or cryptography problems. Hence, I recommend +this for use on the testnet only at this time. + +## Web Integration + +Modifications to the PoCC Web Wallet for interfacing with the Ledger Nano S +can be found here: https://github.com/jake-b/burstcoin/tree/ledger + +Note you need to enable "Web Browser" support in the app on the Ledger device +in order to switch from the standard communication protocol to the U2F +protocol. + +# Room for improvement + +Lots of room to grow here. I make no promises to move this forward. I would +like to move my Ledger Nano from "Development" to "Production use" and that +would halt my ability to develop. I may get another unit for development +eventually. + +Some ideas for improvement: + +- Peer review to make sure this is cryptographically sound and secure. +- Progress indicators on the device -- "Please Wait" screens etc, so the +user knows something is going on. +- Support for other types of transactions on the "Confirm Transaction" screen +currently it is very "Send Money" focused. +- A web developer should really look at the desktop/web stuff to fix and +improve the experience. +- Support for multiple wallets (derived wallets/addresses) + +# Videos + +Some videos on this project + +[![Proof of Concept Hardware Wallet for Burstcoin Video](http://img.youtube.com/vi/8i87n5fAvWU/0.jpg)](http://www.youtube.com/watch?v=8i87n5fAvWU "Proof of Concept Hardware Wallet for Burstcoin") +[![Proof of Concept Burstcoin Ledger Web Wallet Integration Video](http://img.youtube.com/vi/7TjjhTY0eDU/0.jpg)](http://www.youtube.com/watch?v=7TjjhTY0eDU "Proof of Concept Burstcoin Ledger Web Wallet Integration") + +# Support + +Support this project by sending some BURST to: BURST-ZGEK-VQ86-M9FV-7SDWY +Again, no promises to future support or development, but thanks are appreciated. diff --git a/glyphs/icon_back.gif b/glyphs/icon_back.gif new file mode 100644 index 0000000000000000000000000000000000000000..a2a7e6d4fa290e4875992d4024e988d14b91df26 GIT binary patch literal 74 zcmZ?wbhEHb6_GT#!5ilS+%Mt eS=W1f_}E-$glEQUZF{Ibb5ZKP3olJs8LR literal 0 HcmV?d00001 diff --git a/glyphs/icon_dashboard.gif b/glyphs/icon_dashboard.gif new file mode 100644 index 0000000000000000000000000000000000000000..5c305517f83a1d2b0b90e7028b4d28b9eb12d82b GIT binary patch literal 1133 zcmZ?wbhEHbh+i z#(Mch>H3D2mX`VkM*2oZx|Z5PF?(>IEf;+ybD@E~!PCWvMA{Mftf3V2@j6;&zJ# zPV=C8Q*gV*5~p5$pkwqw(Tfz_Fd<+X0x{u<7s!Dp|I|ESnlAz-ZpQ!r{{H#>>*tT} z-@bnN{ORL|_wU}mdHw3;i|5atK6(7;;e-44?%uh5>*kH?*REcZ;0$^0LyB;-bQW{Jh+p?5xa; z^t9BJ|cQ;oTXD3GodplbjYb#3&b2C#D zVqwaWPR5VIe^Qem-6vZZ1v^b~aWPW+p~p z3GtH!SWYl7=zs`N*}=f1)WgepeBpkH7I)L>0efyf`cv6&*0!WWPeHu$lGW7Enz_D= G4Aua{vX6rR literal 0 HcmV?d00001 diff --git a/nanos_app_burst.gif b/nanos_app_burst.gif new file mode 100644 index 0000000000000000000000000000000000000000..cbd62cd9d84185b8fd0fd14126a815ecc0e32a8d GIT binary patch literal 80 zcmZ?wbh9u|6krfwn8*ME|Ns97(+r9~Sva{Em>6_GT#!5ilVVT*%F}ll7IGU}WqvsH b>%=X^grf{oW-gp9)%C#D?~{)-D}yxv2;~=$ literal 0 HcmV?d00001 diff --git a/python/RSAddress.py b/python/RSAddress.py new file mode 100644 index 0000000..4899d57 --- /dev/null +++ b/python/RSAddress.py @@ -0,0 +1,463 @@ +# Copyright (c) 2017 Jake B. + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# Converted to python from: +# https://github.com/BurstProject/burstcoin/blob/efaf44781b0f5101a86356571f90c8a56652a1e5/html/ui/js/util/nxtaddress.js +# Original code in public domain + +import math + +#from https://stackoverflow.com/questions/22747092/valueerror-substring-not-found-what-am-i-doing-wrong +def set_list(l, i, v): + try: + l[i] = v + except IndexError: + for _ in range(i-len(l)+1): + l.append(None) + l[i] = v + +class RSAddress: + codeword = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + syndrome = [0, 0, 0, 0, 0]; + + gexp = [1, 2, 4, 8, 16, 5, 10, 20, 13, 26, 17, 7, 14, 28, 29, 31, 27, 19, 3, 6, 12, 24, 21, 15, 30, 25, 23, 11, 22, 9, 18, 1]; + glog = [0, 0, 1, 18, 2, 5, 19, 11, 3, 29, 6, 27, 20, 8, 12, 23, 4, 10, 30, 17, 7, 22, 28, 26, 21, 25, 9, 16, 13, 14, 24, 15]; + + cwmap = [3, 2, 1, 0, 7, 6, 5, 4, 13, 14, 15, 16, 12, 8, 9, 10, 11]; + + alphabet = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ'; + + guess = [] + + prefix = None + + def __init__(self, addressPrefix): + self.prefix = addressPrefix + + def ginv(self, a): + return self.gexp[31 - self.glog[a]] + + def gmult(self, a, b): + if (a == 0 or b == 0): + return 0; + + idx = (self.glog[a] + self.glog[b]) % 31 + return self.gexp[idx] + + def calc_discrepancy(self, lam, r): + discr = 0; + + for i in range(0, r): + discr ^= self.gmult(lam[i], self.syndrome[r - i]); + + return discr + + def find_errors(self, lam): + errloc = [] + + for i in range(1, 31+1): + sum = 0 + + for j in range(0, 5): + sum ^= self.gmult(self.gexp[(j * i) % 31], lam[j]) + + if (sum == 0): + pos = 31 - i + if (pos > 12 and pos < 27): + return [] + + errloc.append(pos); + + return errloc; + + def guess_errors(self): + el = 0 + b = [0, 0, 0, 0, 0] + t = [] + + deg_lambda = 0, + lamb = [1, 0, 0, 0, 0] # error+erasure locator poly + + # Berlekamp-Massey algorithm to determine error+erasure locator polynomial + + for r in range(0, 4): + discr = self.calc_discrepancy(lamb, r + 1); # Compute discrepancy at the r-th step in poly-form + + if (discr != 0): + deg_lambda = 0 + + for i in range(0, 5): + t.append(lamb[i] ^ self.gmult(discr, b[i])) + + if (t[i]): + deg_lambda = i + + if (2 * el <= r): + el = r + 1 - el + + for i in range(0, 5): + b[i] = self.gmult(lamb[i], self.ginv(discr)) + + lamb = t[:] + + b.insert(0,0) + + # Find roots of the locator polynomial. + + errloc = self.find_errors(lamb) + + errors = len(errloc) + + if (errors < 1 or errors > 2): + return None + + if (deg_lambda != errors): + return None # deg(lambda) unequal to number of roots => uncorrectable error + + # Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo x**(4)). Also find deg(omega). + + omega = [0, 0, 0, 0, 0] + + for i in range(0, 4): + t = 0; + + for j in rnage(0, i): + t ^= gmult(syndrome[i + 1 - j], lamb[j]) + + omega[i] = t + + # Compute error values in poly-form. + + for r in (0, errors): + t = 0 + pos = errloc[r] + root = 31 - pos + + for i in range(0, 4): # evaluate Omega at alpha^(-i) + t ^= gmult(omega[i], gexp[(root * i) % 31]) + + if (t): # evaluate Lambda' (derivative) at alpha^(-i); all odd powers disappear + denom = gmult(lamb[1], 1) ^ gmult(lamb[3], gexp[(root * 2) % 31]) + + if (denom == 0): + return None + + if (pos > 12): + pos -= 14 + + self.codeword[pos] ^= gmult(t, ginv(denom)) + + return True + + def encode(self): + p = [0, 0, 0, 0]; + + for i in range(12, -1, -1): + fb = self.codeword[i] ^ p[3] + + p[3] = p[2] ^ self.gmult(30, fb) + p[2] = p[1] ^ self.gmult(6, fb) + p[1] = p[0] ^ self.gmult(9, fb) + p[0] = self.gmult(17, fb) + + self.codeword[13] = p[0] + self.codeword[14] = p[1] + self.codeword[15] = p[2] + self.codeword[16] = p[3] + + def reset(self): + for i in range(0,17): + self.codeword[i] = 1 + + def set_codeword(self, cw, length=17, skip=-1): + + j = 0; + for i in range(0, length): + if (i != skip): + self.codeword[self.cwmap[j]] = cw[i]; + j+=1 + + + def add_guess(self): + s = self.toString() + length = len(self.guess) + + if (length > 2): + return + + for i in range(0,length): + if (self.guess[i] == s): + return; + + self.guess.append(s); + + def ok(self): + sum = 0; + + for i in range(1, 5): + t=0 + for j in range(0, 31): + if (j > 12 and j < 27): + continue + + pos = j; + + if (j > 26): + pos -= 14; + + t ^= self.gmult(self.codeword[pos], self.gexp[(i * j) % 31]); + + sum |= t + self.syndrome[i] = t; + + return (sum == 0); + + def from_acc(self, acc): + inp = [] + out = [] + pos = 0 + length = len(acc) + + if (length == 20 and acc[0] != '1'): + return None + + for i in range(0, length): + inp.append(ord(acc[i]) - ord('0')) + + while True: + divide = 0 + newlen = 0 + + for i in range(0, length): + divide = divide * 10 + inp[i]; + + if (divide >= 32): + inp[newlen] = divide >> 5; + newlen+=1 + divide &= 31; + elif (newlen > 0): + inp[newlen] = 0; + newlen+=1 + + length = newlen; + out.append(divide); + pos+=1 + + if (newlen == 0): + break + + for i in range(0, 13): # copy to codeword in reverse, pad with 0's + pos-=1 + self.codeword[i] = out[i] if (pos >= 0) else 0 + + self.encode() + + return True + + def toString(self): + out = self.prefix + '-'; + + for i in range(0,17): + out += self.alphabet[self.codeword[self.cwmap[i]]]; + if ((i & 3) == 3 and i < 13): + out += '-' + + return out + + def account_id(self): + out = '' + inp = [] + length = 13 + + for i in range(0, 13): + inp.append(self.codeword[12 - i]) + + while True: + divide = 0 + newlen = 0 + + for i in range(0,length): + divide = divide * 32 + inp[i]; + + if (divide >= 10): + inp[newlen] = int(math.floor(divide / 10)) + newlen+=1 + divide %= 10 + elif (newlen > 0): + inp[newlen] = 0 + newlen+=1 + + length = newlen + out += chr(int(divide) + ord('0')); + + if (newlen==0): + break + + #return out.split("").reverse().join(""); + return out[::-1] + + def set(self, adr, allow_accounts = True): + + length = 0 + self.guess = [] + self.reset() + + adr = str(adr) + adr = adr.strip().upper() + + if (adr.startswith('BURST-')): + adr = adr[6:] + + if (unicode(adr, 'utf-8').isnumeric()): + if (allow_accounts): + return self.from_acc(adr); + else: + clean = [] + + for i in range(0, len(adr)): + try: + pos = self.alphabet.index(adr[i]); + except ValueError: + pos = -1 + + if (pos >= 0): + clean.append(pos) + length+=1 + if (length> 18): + return None + + if (length == 16): # // guess deletion + for i in range(16, -1, -1): + for j in range(0, 32): + set_list(clean, i, j) + + self.set_codeword(clean); + + if (self.ok()): + self.add_guess(); + + if (i > 0): + t = clean[i - 1] + clean[i - 1] = clean[i] + clean[i] = t + + if (length == 18): # // guess insertion + for i in range(0, 18): + set_codeword(clean, 18, i); + + if (this.ok()): + self.add_guess(); + + if (length == 17): + self.set_codeword(clean); + + if (self.ok()): + return True; + + if (self.guess_errors() and self.ok()): + self.add_guess(); + + self.reset(); + + return None + + def format_guess(self, s, org): + d = '' + list = [] + + s = s.upper(); + org = org.upper(); + + i = 0 + while True: + if not i < len(s): + break; + + m = 0; + + for j in range(1, len(s)): + pos = org.index(s.index(i, j)); + + if (pos != -1): + if (abs(pos - i) < 3): + m = j; + else: + break + + if (m): + list.append({ + 's': i, + 'e': i + m + }) + i += m; + else: + i+=1; + + if (len(list) == 0): + return s; + + j = 0 + for i in range(0, len(s)): + if (i >= list[j].e): + start; + + while (j < len(list) - 1): + start = list[j].s + j += 1 + + if (i < list[j].e or list[j].s >= start): + break; + if (i >= list[j].s and i < list[j].e): + d += s[i]; + else: + d += '' + s.charAt(i) + ''; + + return d; + +if __name__ == "__main__": + test_cases = [("BURST-UV6Y-2CMW-QM7Y-DBS9B", "13015131355133865118"), + ("13015131355133865118", "BURST-UV6Y-2CMW-QM7Y-DBS9B"), + ("BURST-5LM8-3VFQ-WTLJ-7FCQ8", "6005916304280767078"), + ("6005916304280767078", "BURST-5LM8-3VFQ-WTLJ-7FCQ8"), + ("BURST-CA9X-RC5H-5YMQ-77Z8V", "6745237456208339197"), + ("6745237456208339197", "BURST-CA9X-RC5H-5YMQ-77Z8V"), + ("BURST-CA9X-RC5H-5YM*-77Z8V", "6745237456208339197"), + ("BURST-YA3R-RLP6-H3NJ-ARGZY", "10339657524823662647"), + ("10339657524823662647", "BURST-YA3R-RLP6-H3NJ-ARGZY")] + + x = RSAddress("BURST") + + for test_pair in test_cases: + if not x.set(test_pair[0]): + print("SETFAIL: ", test_pair[0], " -> ", test_pair[1]) + + if test_pair[0].isnumeric(): + if x.toString() == test_pair[1]: + print("SUCCESS: ", test_pair[0], " -> ", test_pair[1]) + else: + print("FAIL: ", test_pair[0], " -> ", x.toString(), " expected: ", test_pair[1]) + else: + if x.account_id() == test_pair[1]: + print("SUCCESS: ", test_pair[0], " -> ", test_pair[1]) + else: + print("FAIL: ", test_pair[0], " -> ", x.account_id(), " expected: ", test_pair[1]) + + + diff --git a/python/curve25519.py b/python/curve25519.py new file mode 100644 index 0000000..f7f46bb --- /dev/null +++ b/python/curve25519.py @@ -0,0 +1,154 @@ +# a pedagogical implementation of curve25519 with ec-kcdsa +# coded by doctorevil to validate nxt's port of Matthijs van Duin's implementation +# warning: this implementation is not timing attack resistant +# ec arithmetic equations from http://hyperelliptic.org/EFD/g1p/auto-montgom.html + +# Modified slightly to support Python 3 these modifications are released under +# the MIT license. Specifically the disclaimer of warranty/liability: + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +from hashlib import sha256 +from ecdsa.numbertheory import square_root_mod_prime, SquareRootError, inverse_mod + +CURVE_P = 2**255 - 19 +CURVE_A = 486662 +CURVE_ORDER = 7237005577332262213973186563042994240857116359379907606001950938285454250989 +CURVE_G_X = 9 +CURVE_G_Y = 14781619447589544791020593568409986887264606134616475288964881837755586237401 + +def le32(n): # to little endian + #return bytes.fromhex('%064x' % n) #[::-1] + return bytes.fromhex('%064x' % n)[::-1] + +def from_le32(s): + #return int(s.hex(), 16) + return int(s[::-1].hex(), 16) + +def curve25519_x_to_y(x): + t = (x ** 3 + CURVE_A * x ** 2 + x) % CURVE_P + try: + return square_root_mod_prime(t, CURVE_P) + except SquareRootError: + return None + +def curve25519_affine_add(x1, y1, x2, y2): + if (x1, y1) == (1, 0): + return x2, y2 + if (x2, y2) == (1, 0): + return x1, y1 + if x1 == x2 and y1 != y2: + return (1, 0) + if x1 == x2 and y1 == y2: + return curve25519_affine_double(x1, y1) + + t1 = (y2 - y1) ** 2 % CURVE_P + t2 = (x2 - x1) ** 2 % CURVE_P + x3 = (t1 * inverse_mod(t2, CURVE_P) - 486662 - x1 - x2) % CURVE_P + t1 = (2 * x1 + x2 + 486662) % CURVE_P + t2 = (y2 - y1) % CURVE_P + t3 = (x2 - x1) % CURVE_P + + y3 = t1 * (y2 - y1) * inverse_mod((x2 - x1) % CURVE_P, CURVE_P) - \ + t2 ** 3 * inverse_mod(t3 ** 3 % CURVE_P, CURVE_P) - y1 + y3 = y3 % CURVE_P + return x3, y3 + +def curve25519_affine_double(x1, y1): + if (x1, y1) == (1, 0): + return (1, 0) + + x2 = (3 * x1 ** 2 + 2 * CURVE_A * x1 + 1) ** 2 * inverse_mod((2 * y1) ** 2, CURVE_P) - CURVE_A - x1 - x1 + y2 = (2 * x1 + x1 + CURVE_A) * (3 * x1 ** 2 + 2 * CURVE_A * x1 + 1) * inverse_mod(2 * y1, CURVE_P) - \ + (3 * x1 ** 2 + 2 * CURVE_A * x1 + 1) ** 3 * inverse_mod((2 * y1) ** 3, CURVE_P) - y1 + return x2 % CURVE_P, y2 % CURVE_P + +def curve25519_affine_mult(n, x1, y1): + tx, ty = 1, 0 + for bit in map(int, bin(n)[2:]): + tx, ty = curve25519_affine_double(tx, ty) + if bit: + tx, ty = curve25519_affine_add(tx, ty, x1, y1) + return tx, ty + +def clamp(secret): + a = secret[0] + a &= 248 + b = secret[31] + b &= 127 + b |= 64 + returnVal = (bytearray((a,)) + secret[1:-1] + bytearray((b,))) + return returnVal + +def is_negative(x): + return x & 1 + +def curve25519_eckcdsa_keygen(secret): + s = from_le32(clamp(secret)) + x, y = curve25519_affine_mult(s, CURVE_G_X, CURVE_G_Y) + signing_key = inverse_mod(s if is_negative(y) else -s, CURVE_ORDER) + return le32(x), le32(signing_key), le32(s) + +def kcdsa_sign(message, secret): + + verification_key, signing_key, ignored = curve25519_eckcdsa_keygen(secret) + + m = sha256(message).digest() + k = sha256(m + signing_key).digest() + k_Gx, ignored, k_clamped = curve25519_eckcdsa_keygen(k) + r = sha256(m + k_Gx).digest() + s = (from_le32(k_clamped) - from_le32(r)) * from_le32(signing_key) % CURVE_ORDER + + return le32(s) + r + +def kcdsa_verify(signature, message, public_key): + if len(signature) != 64: + return False + + s = from_le32(signature[:32]) + r = from_le32(signature[32:64]) + + px = from_le32(public_key) + py = curve25519_x_to_y(px) + if py is None: # pubkey is bogus; bail + return False + + tx1, ty1 = curve25519_affine_mult(s, px, py) + tx2, ty2 = curve25519_affine_mult(r, CURVE_G_X, CURVE_G_Y) + if not is_negative(py): + ty2 = -ty2 + k_Gx, k_Gy = curve25519_affine_add(tx1, ty1, tx2, ty2) + + m = sha256(message).digest() + return le32(r) == sha256(m + le32(k_Gx)).digest() + +if __name__ == "__main__": + import sys + + passphrase = sys.argv[1].encode('utf-8') + secret = sha256(passphrase).digest() + message = sys.argv[2].encode('utf-8') + + verification_key, signing_key, secret_clamped = curve25519_eckcdsa_keygen(secret) + print('pubkey', verification_key.hex()) + print('signing key', signing_key.hex()) + signature = kcdsa_sign(message, secret) + print('signature', signature.hex()) + assert kcdsa_verify(signature, message, verification_key) + assert not kcdsa_verify(signature[::-1], signature, verification_key) \ No newline at end of file diff --git a/python/ledger-burst.py b/python/ledger-burst.py new file mode 100644 index 0000000..7e10356 --- /dev/null +++ b/python/ledger-burst.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python2.7 +#******************************************************************************* +#* Ledger Blue +#* (c) 2016 Ledger +#* +#* Licensed under the Apache License, Version 2.0 (the "License"); +#* you may not use this file except in compliance with the License. +#* You may obtain a copy of the License at +#* +#* http://www.apache.org/licenses/LICENSE-2.0 +#* +#* Unless required by applicable law or agreed to in writing, software +#* distributed under the License is distributed on an "AS IS" BASIS, +#* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#* See the License for the specific language governing permissions and +#* limitations under the License. +#******************************************************************************** +from ledgerblue.comm import getDongle +from ledgerblue.commException import CommException +from RSAddress import RSAddress +import hashlib +import struct +import sys +import transaction +from datetime import datetime, timedelta +import pytz +import json +import urllib, urllib2 +from pprint import pprint + +global dongle +dongle = None + +class colors: + '''Colors class: + reset all colors with colors.reset + two subclasses fg for foreground and bg for background. + use as colors.subclass.colorname. + i.e. colors.fg.red or colors.bg.green + also, the generic bold, disable, underline, reverse, strikethrough, + and invisible work with the main class + i.e. colors.bold + ''' + reset='\033[0m' + bold='\033[01m' + disable='\033[02m' + underline='\033[04m' + reverse='\033[07m' + strikethrough='\033[09m' + invisible='\033[08m' + class fg: + black='\033[30m' + red='\033[31m' + green='\033[32m' + orange='\033[33m' + blue='\033[34m' + purple='\033[35m' + cyan='\033[36m' + white='\033[37m' + lightgrey='\033[90m' + lightred='\033[91m' + lightgreen='\033[92m' + yellow='\033[93m' + lightblue='\033[94m' + pink='\033[95m' + lightcyan='\033[96m' + class bg: + black='\033[40m' + red='\033[41m' + green='\033[42m' + orange='\033[43m' + blue='\033[44m' + purple='\033[45m' + cyan='\033[46m' + lightgrey='\033[47m' + +# Some constants +ONE_NXT = 100000000 +EPOCH_BEGINNING = datetime(2014, 8, 11, 2, 0, 0, 0) + +# This will take a timestamp in the BURST blockchain epoch format +# and convert it to a standard datetime +def convertFromEpochTime(theTime): + time = EPOCH_BEGINNING + timedelta(seconds=theTime) + utc = pytz.timezone('UTC') + return time.replace(tzinfo=utc).astimezone(tz=None) + +# This will take a standard date time and return a timetsamp int he +# BURST blockchain format +def convertToEpochTime(theTime): + diff = (theTime.replace(tzinfo=None) - EPOCH_BEGINNING) + return int(((diff.days * 86400000) + (diff.seconds * 1000) + (diff.microseconds / 1000)) / 1000) + +# Return the current time in the BURST Blockchain epoch format +def getEpochTime(): + utc = pytz.timezone('UTC') + return convertToEpochTime(datetime.now(utc)) + +# This is a class designed to handle the itneraction with an online wallet API +class OnlineWallet: + def __init__(self, baseWalletUrl): + self.base = baseWalletUrl + + # Get economic clustering information + def getECBlockInfo(self): + a = [('requestType','getECBlock')] + params = urllib.urlencode(a) + request_url = self.base + "?" + params + result = json.loads(urllib.urlopen(request_url).read()) + return (result['ecBlockHeight'], result['ecBlockId']) + + # Get information about a wallet + def getWallet(self, wallet): + a = (('requestType','getAccount'),('account', wallet)) + params = urllib.urlencode(a) + request_url = self.base + "?" + params + result = json.loads(urllib.urlopen(request_url).read()) + return result + + # get time + def getWalletTime(self): + request_url = self.base + "?requestType=getTime" + result = json.loads(urllib.urlopen(request_url).read()) + return result['time']; + + # Get wallet balance + def getWalletBalance(self, wallet): + return float(getWallet(wallet)['balanceNQT']) / ONE_NXT + + # Get wallet public key + def getWalletPublicKey(self, wallet): + return getWallet(wallet)['publicKey'] + + # Broadcast transaction to the network + def broadcastTransaction(self, hexData): + a = [('requestType','broadcastTransaction'), ('transactionBytes', hexData)] + params = urllib.urlencode(a) + request_url = self.base + result = json.loads(urllib.urlopen(request_url, params.encode('ascii')).read()) + return result + +publickKey = None +def getPublicKeyFromDongle(): + global dongle + if (publickKey): + return publicKey + else: + while (True): + try: + if (dongle == None): + dongle = getDongle(True) + publicKey = dongle.exchange(bytes("8004000000".decode('hex'))) + return publicKey + except Exception as e: + print e + answer = raw_input("Please conenct your Ledger Nano S, unlock, and launch the Burstcoin app. Press when ready. (Q quits)") + if (answer.upper() == 'Q'): + sys.exit(0) + sys.exc_clear() + + +wallet = OnlineWallet("http://176.9.47.157:6876/burst") +CHUNK_SIZE = 128 + +while (True): + print("") + print(colors.fg.lightcyan + colors.bold + "Ledger Nano S - Burstcoin proof of concept" + colors.reset) + print(colors.fg.white + "\t 1. Get PublicKey/Address from Ledger Nano S" + colors.reset) + print(colors.fg.white + "\t 2. Send money using Ledger Nano S"+ colors.reset) + print(colors.fg.white + "\t 3. Exit" + colors.reset) + select = raw_input(colors.fg.cyan + "Please select> "+colors.reset) + + if (select == "1"): + x = RSAddress("BURST") + + publicKey = getPublicKeyFromDongle() + print colors.fg.blue + "publicKey: " + colors.reset + str(publicKey).encode('hex') + + sha = hashlib.sha256() + sha.update(publicKey) + pk_hash = sha.digest() + numeric = struct.unpack(" CHUNK_SIZE: + chunk = binary_data[offset : offset + CHUNK_SIZE] + else: + chunk = binary_data[offset:] + if (offset + len(chunk)) == len(binary_data): + p1 = 0x80 + else: + p1 = 0x00 + + if (offset == 0): + print("Waiting for approval to sign on the Ledger Nano S"); + + apdu = bytes("8002".decode('hex')) + chr(p1) + chr(0x00) + chr(len(chunk)) + bytes(chunk) + if (apdu.encode('hex') == "6985"): + print("Error: user denied signing on device.") + sys.exit(1) + signature = dongle.exchange(apdu) + offset += len(chunk) + print "signature " + str(signature).encode('hex') + break; + except CommException as e: + print e.sw + if (e.sw == 0x6985): + print(colors.fg.red + "User denied signing request on Ledger Nano S device." + colors.reset) + + sys.exit(1) + except Exception as e: + print e, type(e) + answer = raw_input("Please conenct your Ledger Nano S, unlock, and launch the Burstcoin app. Press when ready. (Q quits)") + if (answer.upper() == 'Q'): + sys.exit(0) + sys.exc_clear() + + if (signature): + transaction_obj['signature']= signature + trans_bytes = transaction.transactionToBytes(transaction_obj); + hexdata=''.join(format(x, '02x') for x in trans_bytes) + print(hexdata) + result = wallet.broadcastTransaction(hexdata) + print(result) + + else: + break; + diff --git a/python/transaction.py b/python/transaction.py new file mode 100644 index 0000000..cb47e94 --- /dev/null +++ b/python/transaction.py @@ -0,0 +1,78 @@ +# Copyright (c) 2017 Jake B. + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +from io import BytesIO +import struct +import curve25519 +import hashlib + +def readTransactionData(fp): + transaction = {} + transaction['type'] = struct.unpack('> 4 + transaction['subtype'] = subtype & 0x0F; + transaction['timestamp'] = struct.unpack(' 0): + transaction['flags'] = struct.unpack(' 0): + if ('message' in t): + t['flags'] |= 0x01 + data.extend(struct.pack(" +#include "curve25519_i64.h" + +typedef int32_t i25519[10]; +typedef const int32_t *i25519ptr; +typedef const uint8_t *srcptr; +typedef uint8_t *dstptr; + +typedef struct expstep expstep; + +struct expstep { + unsigned nsqr; + unsigned muli; +}; + + +/********************* constants *********************/ + +const k25519 +zero25519 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, +prime25519 = { 237, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 127 }, +order25519 = { 237, 211, 245, 92, 26, 99, 18, 88, 214, 156, 247, 162, 222, 249, + 222, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16 }; + +/* smallest multiple of the order that's >= 2^255 */ +static const k25519 +order_times_8 = { 104, 159, 174, 231, 210, 24, 147, 192, 178, 230, 188, 23, + 245, 206, 247, 166, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128 }; + +/* constants 2Gy and 1/(2Gy) */ +static const i25519 +base_2y = { 39999547, 18689728, 59995525, 1648697, 57546132, + 24010086, 19059592, 5425144, 63499247, 16420658 }, +base_r2y = { 5744, 8160848, 4790893, 13779497, 35730846, + 12541209, 49101323, 30047407, 40071253, 6226132 }; + + +/********************* radix 2^8 math *********************/ + +static void cpy32(k25519 d, const k25519 s) { + int i; + for (i = 0; i < 32; i++) d[i] = s[i]; +} + +/* p[m..n+m-1] = q[m..n+m-1] + z * x */ +/* n is the size of x */ +/* n+m is the size of p and q */ +static inline +int mula_small(dstptr p, srcptr q, unsigned m, srcptr x, unsigned n, int z) { + int v = 0; + unsigned i; + for (i = 0; i < n; i++) { + p[i+m] = v += q[i+m] + z * x[i]; + v >>= 8; + } + return v; +} + +/* p += x * y * z where z is a small integer + * x is size 32, y is size t, p is size 32+t + * y is allowed to overlap with p+32 if you don't care about the upper half */ +static int mula32(dstptr p, srcptr x, srcptr y, unsigned t, int z) { + const unsigned n = 31; + int w = 0; + unsigned i; + for (i = 0; i < t; i++) { + int zy = z * y[i]; + p[i+n] = w += mula_small(p,p, i, x, n, zy) + p[i+n] + zy * x[n]; + w >>= 8; + } + p[i+n] += w; + return w >> 8; +} + +/* divide r (size n) by d (size t), returning quotient q and remainder r + * quotient is size n-t+1, remainder is size t + * requires t > 0 && d[t-1] != 0 + * requires that r[-1] and d[-1] are valid memory locations + * q may overlap with r+t */ +static void divmod(dstptr q, dstptr r, unsigned n, srcptr d, unsigned t) { + int rn = 0; + int dt = d[t-1] << 8 | (d[t-2] & -(t > 1)); + + while (n-- >= t) { + int z = (rn << 16 | r[n] << 8 | (r[n-1] & -(n > 0))) / dt; + rn += mula_small(r,r, n-t+1, d, t, -z); + q[n-t+1] = z + rn; /* rn is 0 or -1 (underflow) */ + mula_small(r,r, n-t+1, d, t, -rn); + rn = r[n]; + r[n] = 0; + } + + r[t-1] = rn; +} + +static inline unsigned numsize(srcptr x, unsigned n) { + while (n-- && !x[n]) + ; + return n+1; +} + +/* Returns x if a contains the gcd, y if b. Also, the returned buffer contains + * the inverse of a mod b, as 32-byte signed. + * x and y must have 64 bytes space for temporary use. + * requires that a[-1] and b[-1] are valid memory locations */ +static dstptr egcd32(dstptr x, dstptr y, dstptr a, dstptr b) { + unsigned an, bn = 32, qn, i; + for (i = 0; i < 32; i++) + x[i] = y[i] = 0; + x[0] = 1; + if (!(an = numsize(a, 32))) + return y; /* division by zero */ + while (42) { + qn = bn - an + 1; + divmod(y+32, b, bn, a, an); + if (!(bn = numsize(b, bn))) + return x; + mula32(y, x, y+32, qn, -1); + + qn = an - bn + 1; + divmod(x+32, a, an, b, bn); + if (!(an = numsize(a, an))) + return y; + mula32(x, y, x+32, qn, -1); + } +} + + +/********************* radix 2^25.5 GF(2^255-19) math *********************/ + +#define P25 33554431 /* (1 << 25) - 1 */ +#define P26 67108863 /* (1 << 26) - 1 */ + + +/* debugging code */ + +#ifdef WASP_DEBUG_CURVE +#include +#include +static void check_range(const char *where, int32_t x, int32_t lb, int32_t ub) { + if (x < lb || x > ub) { + fprintf(stderr, "%s check failed: %08x (%d)\n", where, x, x); + abort(); + } +} +static void check_nonred(const char *where, const i25519 x) { + int i; + for (i = 0; i < 10; i++) + check_range(where, x[i], -185861411, 185861411); +} +static void check_reduced(const char *where, const i25519 x) { + int i; + for (i = 0; i < 9; i++) + check_range(where, x[i], 0, P26 >> (i & 1)); + check_range(where, x[9], -675, P25+675); +} +#else +#define check_range(w, x, l, u) +#define check_nonred(w, x) +#define check_reduced(w, x) +#endif + +/* convenience macros */ + +#define M(i) ((uint32_t) m[i]) +#define X(i) ((int64_t) x[i]) +#define m64(arg1,arg2) ((int64_t) (arg1) * (arg2)) + +/* Convert to internal format from little-endian byte format */ +static void unpack25519(i25519 x, const k25519 m) { + x[0] = M( 0) | M( 1)<<8 | M( 2)<<16 | (M( 3)& 3)<<24; + x[1] = (M( 3)&~ 3)>>2 | M( 4)<<6 | M( 5)<<14 | (M( 6)& 7)<<22; + x[2] = (M( 6)&~ 7)>>3 | M( 7)<<5 | M( 8)<<13 | (M( 9)&31)<<21; + x[3] = (M( 9)&~31)>>5 | M(10)<<3 | M(11)<<11 | (M(12)&63)<<19; + x[4] = (M(12)&~63)>>6 | M(13)<<2 | M(14)<<10 | M(15) <<18; + x[5] = M(16) | M(17)<<8 | M(18)<<16 | (M(19)& 1)<<24; + x[6] = (M(19)&~ 1)>>1 | M(20)<<7 | M(21)<<15 | (M(22)& 7)<<23; + x[7] = (M(22)&~ 7)>>3 | M(23)<<5 | M(24)<<13 | (M(25)&15)<<21; + x[8] = (M(25)&~15)>>4 | M(26)<<4 | M(27)<<12 | (M(28)&63)<<20; + x[9] = (M(28)&~63)>>6 | M(29)<<2 | M(30)<<10 | M(31) <<18; + check_reduced("unpack output", x); +} + + +/* Check if reduced-form input >= 2^255-19 */ +static inline int is_overflow(const i25519 x) { + return ((x[0] > P26-19) & ((x[1] & x[3] & x[5] & x[7] & x[9]) == P25) & + ((x[2] & x[4] & x[6] & x[8]) == P26) + ) | (x[9] > P25); +} + + +/* Convert from internal format to little-endian byte format. The + * number must be in a reduced form which is output by the following ops: + * unpack, mul, sqr + * set -- if input in range 0 .. P25 + * If you're unsure if the number is reduced, first multiply it by 1. */ +static void pack25519(const i25519 x, k25519 m) { + int32_t ld = 0, ud = 0; + int64_t t; + check_reduced("pack input", x); + ld = is_overflow(x) - (x[9] < 0); + ud = ld * -(P25+1); + ld *= 19; + t = ld + X(0) + (X(1) << 26); + m[ 0] = t; m[ 1] = t >> 8; m[ 2] = t >> 16; m[ 3] = t >> 24; + t = (t >> 32) + (X(2) << 19); + m[ 4] = t; m[ 5] = t >> 8; m[ 6] = t >> 16; m[ 7] = t >> 24; + t = (t >> 32) + (X(3) << 13); + m[ 8] = t; m[ 9] = t >> 8; m[10] = t >> 16; m[11] = t >> 24; + t = (t >> 32) + (X(4) << 6); + m[12] = t; m[13] = t >> 8; m[14] = t >> 16; m[15] = t >> 24; + t = (t >> 32) + X(5) + (X(6) << 25); + m[16] = t; m[17] = t >> 8; m[18] = t >> 16; m[19] = t >> 24; + t = (t >> 32) + (X(7) << 19); + m[20] = t; m[21] = t >> 8; m[22] = t >> 16; m[23] = t >> 24; + t = (t >> 32) + (X(8) << 12); + m[24] = t; m[25] = t >> 8; m[26] = t >> 16; m[27] = t >> 24; + t = (t >> 32) + ((X(9) + ud) << 6); + m[28] = t; m[29] = t >> 8; m[30] = t >> 16; m[31] = t >> 24; +} + +/* Copy a number */ +static void cpy25519(i25519 out, const i25519 in) { + int i; + for (i = 0; i < 10; i++) + out[i] = in[i]; +} + +/* Set a number to value, which must be in range -185861411 .. 185861411 */ +static void set25519(i25519 out, const int32_t in) { + int i; + out[0] = in; + for (i = 1; i < 10; i++) + out[i] = 0; +} + +/* Add/subtract two numbers. The inputs must be in reduced form, and the + * output isn't, so to do another addition or subtraction on the output, + * first multiply it by one to reduce it. */ +static void add25519(i25519 xy, const i25519 x, const i25519 y) { + xy[0] = x[0] + y[0]; xy[1] = x[1] + y[1]; + xy[2] = x[2] + y[2]; xy[3] = x[3] + y[3]; + xy[4] = x[4] + y[4]; xy[5] = x[5] + y[5]; + xy[6] = x[6] + y[6]; xy[7] = x[7] + y[7]; + xy[8] = x[8] + y[8]; xy[9] = x[9] + y[9]; +} +static void sub25519(i25519 xy, const i25519 x, const i25519 y) { + xy[0] = x[0] - y[0]; xy[1] = x[1] - y[1]; + xy[2] = x[2] - y[2]; xy[3] = x[3] - y[3]; + xy[4] = x[4] - y[4]; xy[5] = x[5] - y[5]; + xy[6] = x[6] - y[6]; xy[7] = x[7] - y[7]; + xy[8] = x[8] - y[8]; xy[9] = x[9] - y[9]; +} + +/* Multiply a number by a small integer in range -185861411 .. 185861411. + * The output is in reduced form, the input x need not be. x and xy may point + * to the same buffer. */ +static i25519ptr mul25519small(i25519 xy, const i25519 x, const int32_t y) { + register int64_t t; + check_nonred("mul small x input", x); + check_range("mul small y input", y, -185861411, 185861411); + t = m64(x[8],y); + xy[8] = t & ((1 << 26) - 1); + t = (t >> 26) + m64(x[9],y); + xy[9] = t & ((1 << 25) - 1); + t = 19 * (t >> 25) + m64(x[0],y); + xy[0] = t & ((1 << 26) - 1); + t = (t >> 26) + m64(x[1],y); + xy[1] = t & ((1 << 25) - 1); + t = (t >> 25) + m64(x[2],y); + xy[2] = t & ((1 << 26) - 1); + t = (t >> 26) + m64(x[3],y); + xy[3] = t & ((1 << 25) - 1); + t = (t >> 25) + m64(x[4],y); + xy[4] = t & ((1 << 26) - 1); + t = (t >> 26) + m64(x[5],y); + xy[5] = t & ((1 << 25) - 1); + t = (t >> 25) + m64(x[6],y); + xy[6] = t & ((1 << 26) - 1); + t = (t >> 26) + m64(x[7],y); + xy[7] = t & ((1 << 25) - 1); + t = (t >> 25) + xy[8]; + xy[8] = t & ((1 << 26) - 1); + xy[9] += (int32_t)(t >> 26); + check_reduced("mul small output", xy); + return xy; +} + +/* Multiply two numbers. The output is in reduced form, the inputs need not + * be. */ +static i25519ptr mul25519(i25519 xy, const i25519 x, const i25519 y) { + register int64_t t; + check_nonred("mul input x", x); + check_nonred("mul input y", y); + t = m64(x[0],y[8]) + m64(x[2],y[6]) + m64(x[4],y[4]) + m64(x[6],y[2]) + + m64(x[8],y[0]) + 2 * (m64(x[1],y[7]) + m64(x[3],y[5]) + + m64(x[5],y[3]) + m64(x[7],y[1])) + 38 * + m64(x[9],y[9]); + xy[8] = t & ((1 << 26) - 1); + t = (t >> 26) + m64(x[0],y[9]) + m64(x[1],y[8]) + m64(x[2],y[7]) + + m64(x[3],y[6]) + m64(x[4],y[5]) + m64(x[5],y[4]) + + m64(x[6],y[3]) + m64(x[7],y[2]) + m64(x[8],y[1]) + + m64(x[9],y[0]); + xy[9] = t & ((1 << 25) - 1); + t = m64(x[0],y[0]) + 19 * ((t >> 25) + m64(x[2],y[8]) + m64(x[4],y[6]) + + m64(x[6],y[4]) + m64(x[8],y[2])) + 38 * + (m64(x[1],y[9]) + m64(x[3],y[7]) + m64(x[5],y[5]) + + m64(x[7],y[3]) + m64(x[9],y[1])); + xy[0] = t & ((1 << 26) - 1); + t = (t >> 26) + m64(x[0],y[1]) + m64(x[1],y[0]) + 19 * (m64(x[2],y[9]) + + m64(x[3],y[8]) + m64(x[4],y[7]) + m64(x[5],y[6]) + + m64(x[6],y[5]) + m64(x[7],y[4]) + m64(x[8],y[3]) + + m64(x[9],y[2])); + xy[1] = t & ((1 << 25) - 1); + t = (t >> 25) + m64(x[0],y[2]) + m64(x[2],y[0]) + 19 * (m64(x[4],y[8]) + + m64(x[6],y[6]) + m64(x[8],y[4])) + 2 * m64(x[1],y[1]) + + 38 * (m64(x[3],y[9]) + m64(x[5],y[7]) + + m64(x[7],y[5]) + m64(x[9],y[3])); + xy[2] = t & ((1 << 26) - 1); + t = (t >> 26) + m64(x[0],y[3]) + m64(x[1],y[2]) + m64(x[2],y[1]) + + m64(x[3],y[0]) + 19 * (m64(x[4],y[9]) + m64(x[5],y[8]) + + m64(x[6],y[7]) + m64(x[7],y[6]) + + m64(x[8],y[5]) + m64(x[9],y[4])); + xy[3] = t & ((1 << 25) - 1); + t = (t >> 25) + m64(x[0],y[4]) + m64(x[2],y[2]) + m64(x[4],y[0]) + 19 * + (m64(x[6],y[8]) + m64(x[8],y[6])) + 2 * (m64(x[1],y[3]) + + m64(x[3],y[1])) + 38 * + (m64(x[5],y[9]) + m64(x[7],y[7]) + m64(x[9],y[5])); + xy[4] = t & ((1 << 26) - 1); + t = (t >> 26) + m64(x[0],y[5]) + m64(x[1],y[4]) + m64(x[2],y[3]) + + m64(x[3],y[2]) + m64(x[4],y[1]) + m64(x[5],y[0]) + 19 * + (m64(x[6],y[9]) + m64(x[7],y[8]) + m64(x[8],y[7]) + + m64(x[9],y[6])); + xy[5] = t & ((1 << 25) - 1); + t = (t >> 25) + m64(x[0],y[6]) + m64(x[2],y[4]) + m64(x[4],y[2]) + + m64(x[6],y[0]) + 19 * m64(x[8],y[8]) + 2 * (m64(x[1],y[5]) + + m64(x[3],y[3]) + m64(x[5],y[1])) + 38 * + (m64(x[7],y[9]) + m64(x[9],y[7])); + xy[6] = t & ((1 << 26) - 1); + t = (t >> 26) + m64(x[0],y[7]) + m64(x[1],y[6]) + m64(x[2],y[5]) + + m64(x[3],y[4]) + m64(x[4],y[3]) + m64(x[5],y[2]) + + m64(x[6],y[1]) + m64(x[7],y[0]) + 19 * (m64(x[8],y[9]) + + m64(x[9],y[8])); + xy[7] = t & ((1 << 25) - 1); + t = (t >> 25) + xy[8]; + xy[8] = t & ((1 << 26) - 1); + xy[9] += (int32_t)(t >> 26); + check_reduced("mul output", xy); + return xy; +} + +/* Square a number. Optimization of mul25519(x2, x, x) */ +static i25519ptr sqr25519(i25519 x2, const i25519 x) { + register int64_t t; + check_nonred("sqr input", x); + t = m64(x[4],x[4]) + 2 * (m64(x[0],x[8]) + m64(x[2],x[6])) + 38 * + m64(x[9],x[9]) + 4 * (m64(x[1],x[7]) + m64(x[3],x[5])); + x2[8] = t & ((1 << 26) - 1); + t = (t >> 26) + 2 * (m64(x[0],x[9]) + m64(x[1],x[8]) + m64(x[2],x[7]) + + m64(x[3],x[6]) + m64(x[4],x[5])); + x2[9] = t & ((1 << 25) - 1); + t = 19 * (t >> 25) + m64(x[0],x[0]) + 38 * (m64(x[2],x[8]) + + m64(x[4],x[6]) + m64(x[5],x[5])) + 76 * (m64(x[1],x[9]) + + m64(x[3],x[7])); + x2[0] = t & ((1 << 26) - 1); + t = (t >> 26) + 2 * m64(x[0],x[1]) + 38 * (m64(x[2],x[9]) + + m64(x[3],x[8]) + m64(x[4],x[7]) + m64(x[5],x[6])); + x2[1] = t & ((1 << 25) - 1); + t = (t >> 25) + 19 * m64(x[6],x[6]) + 2 * (m64(x[0],x[2]) + + m64(x[1],x[1])) + 38 * m64(x[4],x[8]) + 76 * + (m64(x[3],x[9]) + m64(x[5],x[7])); + x2[2] = t & ((1 << 26) - 1); + t = (t >> 26) + 2 * (m64(x[0],x[3]) + m64(x[1],x[2])) + 38 * + (m64(x[4],x[9]) + m64(x[5],x[8]) + m64(x[6],x[7])); + x2[3] = t & ((1 << 25) - 1); + t = (t >> 25) + m64(x[2],x[2]) + 2 * m64(x[0],x[4]) + 38 * + (m64(x[6],x[8]) + m64(x[7],x[7])) + 4 * m64(x[1],x[3]) + 76 * + m64(x[5],x[9]); + x2[4] = t & ((1 << 26) - 1); + t = (t >> 26) + 2 * (m64(x[0],x[5]) + m64(x[1],x[4]) + m64(x[2],x[3])) + + 38 * (m64(x[6],x[9]) + m64(x[7],x[8])); + x2[5] = t & ((1 << 25) - 1); + t = (t >> 25) + 19 * m64(x[8],x[8]) + 2 * (m64(x[0],x[6]) + + m64(x[2],x[4]) + m64(x[3],x[3])) + 4 * m64(x[1],x[5]) + + 76 * m64(x[7],x[9]); + x2[6] = t & ((1 << 26) - 1); + t = (t >> 26) + 2 * (m64(x[0],x[7]) + m64(x[1],x[6]) + m64(x[2],x[5]) + + m64(x[3],x[4])) + 38 * m64(x[8],x[9]); + x2[7] = t & ((1 << 25) - 1); + t = (t >> 25) + x2[8]; + x2[8] = t & ((1 << 26) - 1); + x2[9] += (t >> 26); + check_reduced("sqr output", x2); + return x2; +} + +/* Calculates a reciprocal. The output is in reduced form, the inputs need not + * be. Simply calculates y = x^(p-2) so it's not too fast. */ +/* When sqrtassist is true, it instead calculates y = x^((p-5)/8) */ +static void recip25519(i25519 y, const i25519 x, int sqrtassist) { + i25519 t0, t1, t2, t3, t4; + int i; + /* the chain for x^(2^255-21) is straight from djb's implementation */ + sqr25519(t1, x); /* 2 == 2 * 1 */ + sqr25519(t2, t1); /* 4 == 2 * 2 */ + sqr25519(t0, t2); /* 8 == 2 * 4 */ + mul25519(t2, t0, x); /* 9 == 8 + 1 */ + mul25519(t0, t2, t1); /* 11 == 9 + 2 */ + sqr25519(t1, t0); /* 22 == 2 * 11 */ + mul25519(t3, t1, t2); /* 31 == 22 + 9 + == 2^5 - 2^0 */ + sqr25519(t1, t3); /* 2^6 - 2^1 */ + sqr25519(t2, t1); /* 2^7 - 2^2 */ + sqr25519(t1, t2); /* 2^8 - 2^3 */ + sqr25519(t2, t1); /* 2^9 - 2^4 */ + sqr25519(t1, t2); /* 2^10 - 2^5 */ + mul25519(t2, t1, t3); /* 2^10 - 2^0 */ + sqr25519(t1, t2); /* 2^11 - 2^1 */ + sqr25519(t3, t1); /* 2^12 - 2^2 */ + for (i = 1; i < 5; i++) { + sqr25519(t1, t3); + sqr25519(t3, t1); + } /* t3 */ /* 2^20 - 2^10 */ + mul25519(t1, t3, t2); /* 2^20 - 2^0 */ + sqr25519(t3, t1); /* 2^21 - 2^1 */ + sqr25519(t4, t3); /* 2^22 - 2^2 */ + for (i = 1; i < 10; i++) { + sqr25519(t3, t4); + sqr25519(t4, t3); + } /* t4 */ /* 2^40 - 2^20 */ + mul25519(t3, t4, t1); /* 2^40 - 2^0 */ + for (i = 0; i < 5; i++) { + sqr25519(t1, t3); + sqr25519(t3, t1); + } /* t3 */ /* 2^50 - 2^10 */ + mul25519(t1, t3, t2); /* 2^50 - 2^0 */ + sqr25519(t2, t1); /* 2^51 - 2^1 */ + sqr25519(t3, t2); /* 2^52 - 2^2 */ + for (i = 1; i < 25; i++) { + sqr25519(t2, t3); + sqr25519(t3, t2); + } /* t3 */ /* 2^100 - 2^50 */ + mul25519(t2, t3, t1); /* 2^100 - 2^0 */ + sqr25519(t3, t2); /* 2^101 - 2^1 */ + sqr25519(t4, t3); /* 2^102 - 2^2 */ + for (i = 1; i < 50; i++) { + sqr25519(t3, t4); + sqr25519(t4, t3); + } /* t4 */ /* 2^200 - 2^100 */ + mul25519(t3, t4, t2); /* 2^200 - 2^0 */ + for (i = 0; i < 25; i++) { + sqr25519(t4, t3); + sqr25519(t3, t4); + } /* t3 */ /* 2^250 - 2^50 */ + mul25519(t2, t3, t1); /* 2^250 - 2^0 */ + sqr25519(t1, t2); /* 2^251 - 2^1 */ + sqr25519(t2, t1); /* 2^252 - 2^2 */ + if (sqrtassist) { + mul25519(y, x, t2); /* 2^252 - 3 */ + } else { + sqr25519(t1, t2); /* 2^253 - 2^3 */ + sqr25519(t2, t1); /* 2^254 - 2^4 */ + sqr25519(t1, t2); /* 2^255 - 2^5 */ + mul25519(y, t1, t0); /* 2^255 - 21 */ + } +} + +/* checks if x is "negative", requires reduced input */ +static inline int is_negative(i25519 x) { + return (is_overflow(x) | (x[9] < 0)) ^ (x[0] & 1); +} + + +/********************* Elliptic curve *********************/ + +/* y^2 = x^3 + 486662 x^2 + x over GF(2^255-19) */ + + +/* t1 = ax + az + * t2 = ax - az */ +static inline void mont_prep(i25519 t1, i25519 t2, i25519 ax, i25519 az) { + add25519(t1, ax, az); + sub25519(t2, ax, az); +} + +/* A = P + Q where + * X(A) = ax/az + * X(P) = (t1+t2)/(t1-t2) + * X(Q) = (t3+t4)/(t3-t4) + * X(P-Q) = dx + * clobbers t1 and t2, preserves t3 and t4 */ +static inline void mont_add(i25519 t1, i25519 t2, i25519 t3, i25519 t4, + i25519 ax, i25519 az, const i25519 dx) { + mul25519(ax, t2, t3); + mul25519(az, t1, t4); + add25519(t1, ax, az); + sub25519(t2, ax, az); + sqr25519(ax, t1); + sqr25519(t1, t2); + mul25519(az, t1, dx); +} + +/* B = 2 * Q where + * X(B) = bx/bz + * X(Q) = (t3+t4)/(t3-t4) + * clobbers t1 and t2, preserves t3 and t4 */ +static inline void mont_dbl(i25519 t1, i25519 t2, i25519 t3, i25519 t4, + i25519 bx, i25519 bz) { + sqr25519(t1, t3); + sqr25519(t2, t4); + mul25519(bx, t1, t2); + sub25519(t2, t1, t2); + mul25519small(bz, t2, 121665); + add25519(t1, t1, bz); + mul25519(bz, t1, t2); +} + +/* Y^2 = X^3 + 486662 X^2 + X + * t is a temporary */ +static inline void x_to_y2(i25519 t, i25519 y2, const i25519 x) { + sqr25519(t, x); + mul25519small(y2, x, 486662); + add25519(t, t, y2); + t[0]++; + mul25519(y2, t, x); +} + +/* P = kG and s = sign(P)/k */ +void core25519(k25519 Px, k25519 s, const k25519 k, const k25519 Gx) { + i25519 dx, x[2], z[2], t1, t2, t3, t4; + unsigned i, j; + + /* unpack the base */ + if (Gx) + unpack25519(dx, Gx); + else + set25519(dx, 9); + + /* 0G = point-at-infinity */ + set25519(x[0], 1); + set25519(z[0], 0); + + /* 1G = G */ + cpy25519(x[1], dx); + set25519(z[1], 1); + + for (i = 32; i--; ) { + for (j = 8; j--; ) { + /* swap arguments depending on bit */ + const int bit1 = k[i] >> j & 1; + const int bit0 = ~k[i] >> j & 1; + int32_t *const ax = x[bit0]; + int32_t *const az = z[bit0]; + int32_t *const bx = x[bit1]; + int32_t *const bz = z[bit1]; + + /* a' = a + b */ + /* b' = 2 b */ + mont_prep(t1, t2, ax, az); + mont_prep(t3, t4, bx, bz); + mont_add(t1, t2, t3, t4, ax, az, dx); + mont_dbl(t1, t2, t3, t4, bx, bz); + } + } + + recip25519(t1, z[0], 0); + mul25519(dx, x[0], t1); + if (Px != NULL) pack25519(dx, Px); + + /* calculate s such that s abs(P) = G .. assumes G is std base point */ + if (s) { + x_to_y2(t2, t1, dx); /* t1 = Py^2 */ + recip25519(t3, z[1], 0); /* where Q=P+G ... */ + mul25519(t2, x[1], t3); /* t2 = Qx */ + add25519(t2, t2, dx); /* t2 = Qx + Px */ + t2[0] += 9 + 486662; /* t2 = Qx + Px + Gx + 486662 */ + dx[0] -= 9; /* dx = Px - Gx */ + sqr25519(t3, dx); /* t3 = (Px - Gx)^2 */ + mul25519(dx, t2, t3); /* dx = t2 (Px - Gx)^2 */ + sub25519(dx, dx, t1); /* dx = t2 (Px - Gx)^2 - Py^2 */ + dx[0] -= 39420360; /* dx = t2 (Px - Gx)^2 - Py^2 - Gy^2 */ + mul25519(t1, dx, base_r2y); /* t1 = -Py */ + if (is_negative(t1)) /* sign is 1, so just copy */ + cpy32(s, k); + else /* sign is -1, so negate */ + mula_small(s, order_times_8, 0, k, 32, -1); + + /* reduce s mod q + * (is this needed? do it just in case, it's fast anyway) */ + divmod((dstptr) t1, s, 32, order25519, 32); + + /* take reciprocal of s mod q */ + cpy32((dstptr) t1, order25519); + cpy32(s, egcd32((dstptr) x, (dstptr) z, s, (dstptr) t1)); + if ((int8_t) s[31] < 0) + mula_small(s, s, 0, order25519, 32, 1); + } +} + +/* v = (x - h) s mod q */ +int sign25519(k25519 v, const k25519 h, const priv25519 x, const spriv25519 s) { + uint8_t tmp[65]; + unsigned w; + int i; + for (i = 0; i < 32; i++) + v[i] = 0; + i = mula_small(v, x, 0, h, 32, -1); + mula_small(v, v, 0, order25519, 32, (15-(int8_t) v[31])/16); + for (i = 0; i < 64; i++) + tmp[i] = 0; + mula32(tmp, v, s, 32, 1); + divmod(tmp+32, tmp, 64, order25519, 32); + for (w = 0, i = 0; i < 32; i++) + w |= v[i] = tmp[i]; + return w != 0; +} diff --git a/src/crypto/curve25519_i64.h b/src/crypto/curve25519_i64.h new file mode 100644 index 0000000..c5bbd63 --- /dev/null +++ b/src/crypto/curve25519_i64.h @@ -0,0 +1,79 @@ +#ifndef CURVE25519_I64_H +#define CURVE25519_I64_H 1 + +/* Generic 64-bit integer implementation of Curve25519 ECDH + * Written by Matthijs van Duin, 200608242056 + * Public domain. + * + * Based on work by Daniel J Bernstein, http://cr.yp.to/ecdh.html + */ + +#include + +typedef unsigned char k25519[32]; /* any type of key */ + +extern const k25519 zero25519; /* 0 */ +extern const k25519 prime25519; /* the prime 2^255-19 */ +extern const k25519 order25519; /* group order (a prime near 2^252+2^124) */ + +typedef k25519 pub25519; /* public key */ +typedef k25519 priv25519; /* private key (for key agreement) */ +typedef k25519 spriv25519; /* private key for signing */ +typedef k25519 sec25519; /* shared secret */ + + +//Internal, do not use. +void core25519(k25519 Px, k25519 s, const k25519 k, const k25519 Gx); + +/********* KEY AGREEMENT *********/ + +/* Private key clamping + * k [out] your private key for key agreement + * k [in] 32 random bytes + */ +static inline +void clamp25519(priv25519 k) { + k[31] &= 0x7F; + k[31] |= 0x40; + k[ 0] &= 0xF8; +} + +/* Key-pair generation + * P [out] your public key + * s [out] your private key for signing + * k [out] your private key for key agreement + * k [in] 32 random bytes + * s may be NULL if you don't care + * + * WARNING: if s is not NULL, this function has data-dependent timing */ +static inline +void keygen25519(pub25519 P, spriv25519 s, priv25519 k) { + clamp25519(k); + core25519(P, s, k, NULL); +} + + +/* Key agreement + * Z [out] shared secret (needs hashing before use) + * k [in] your private key for key agreement + * P [in] peer's public key + * Buffers may overlap. */ +static inline +void curve25519(sec25519 Z, const priv25519 k, const pub25519 P) { + core25519(Z, NULL, k, P); +} + + +int sign25519(k25519 v, const k25519 h, const priv25519 x, const spriv25519 s); + + +/* Signature verification primitive, calculates Y = vP + hG + * Y [out] signature public key + * v [in] signature value + * h [in] signature hash + * P [in] public key + */ +void verify25519(pub25519 Y, const k25519 v, const k25519 h, const pub25519 P); + + +#endif diff --git a/src/crypto/rs_address.c b/src/crypto/rs_address.c new file mode 100644 index 0000000..7562e6d --- /dev/null +++ b/src/crypto/rs_address.c @@ -0,0 +1,77 @@ +/******************************************************************************* +* Burstcoin Wallet App for Nano Ledger S. +* Copyright (c) 2017-2018 Jake B. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#import "rs_address.h" +#include +#include +#include "os.h" +#include "cx.h" + +#define PREFIX "BURST-" + +static const uint8_t gexp[] = {1, 2, 4, 8, 16, 5, 10, 20, 13, 26, 17, 7, 14, 28, 29, 31, 27, 19, 3, 6, 12, 24, 21, 15, 30, 25, 23, 11, 22, 9, 18, 1}; +static const uint8_t glog[] = {0, 0, 1, 18, 2, 5, 19, 11, 3, 29, 6, 27, 20, 8, 12, 23, 4, 10, 30, 17, 7, 22, 28, 26, 21, 25, 9, 16, 13, 14, 24, 15}; +static const uint8_t cwmap[] = {3, 2, 1, 0, 7, 6, 5, 4, 13, 14, 15, 16, 12, 8, 9, 10, 11}; +static const char* alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"; + +uint8_t gmult(uint8_t a, uint8_t b) { + if (a == 0 || b == 0) + return 0; + + int idx = (glog[a] + glog[b]) % 31; + return gexp[idx]; +} + +bool addressFromAccountNumber(char* address, uint64_t account, bool prefix) { + + uint8_t codeword[17]; + os_memset(codeword, 0, 17); + + // The first 12 characters of the codeword are direct from the address + for(int i = 0; i < 13; i++) { + codeword[i] = (account >> (5 * i)) & 31; + } + + // The final 5 characters are calculated from the first 13. Start at position + uint8_t* p = codeword + 13; + + for (int i = 12; i>=0; i--) { + uint8_t fb = codeword[i] ^ p[3]; + + p[3] = p[2] ^ gmult(30, fb); + p[2] = p[1] ^ gmult( 6, fb); + p[1] = p[0] ^ gmult( 9, fb); + p[0] = gmult(17, fb); + } + + char* out = address; + if (prefix) { + int prefixLen = sizeof(PREFIX); + os_memmove(address, PREFIX, prefixLen); + out += prefixLen-1; + } + + for (int i=0; i<17; i++) { + *(out++) = alphabet[codeword[cwmap[i]]]; + if ((i & 3) == 3 && i < 13) { + *(out++) = '-'; ; + } + } + *(out) = '\0'; // null terminator + + return true; +} \ No newline at end of file diff --git a/src/crypto/rs_address.h b/src/crypto/rs_address.h new file mode 100644 index 0000000..c31982d --- /dev/null +++ b/src/crypto/rs_address.h @@ -0,0 +1,27 @@ +/******************************************************************************* +* Burstcoin Wallet App for Nano Ledger S. +* Copyright (c) 2017-2018 Jake B. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#ifndef __RS_ADDRESS_H__ +#define __RS_ADDRESS_H__ +#include +#include +#include "os.h" +#include "cx.h" + +bool addressFromAccountNumber(char* address, uint64_t account, bool prefix); + +#endif \ No newline at end of file diff --git a/src/glyphs.c b/src/glyphs.c new file mode 100644 index 0000000..2f05c20 --- /dev/null +++ b/src/glyphs.c @@ -0,0 +1,45 @@ +#include "glyphs.h" +unsigned int const C_icon_back_colors[] + = { + 0x00000000, + 0x00ffffff, +}; + +unsigned char const C_icon_back_bitmap[] = { +0xe0, 0x01, 0xfe, 0xc1, 0xfd, 0x38, 0x7f, 0x06, 0xdf, 0x81, 0xff, 0xc4, 0x7f, 0xf3, 0xff, 0xbc, + 0x1f, 0xe7, 0xe7, 0xf1, 0x3f, 0xf8, 0x07, 0x78, 0x00, }; + +#ifdef OS_IO_SEPROXYHAL +#include "os_io_seproxyhal.h" +const bagl_icon_details_t C_icon_back = { GLYPH_icon_back_WIDTH, GLYPH_icon_back_HEIGHT, 1, C_icon_back_colors, C_icon_back_bitmap }; +#endif // OS_IO_SEPROXYHAL +#include "glyphs.h" +unsigned int const C_icon_burst_colors[] + = { + 0x00000000, + 0x00ffffff, +}; + +unsigned char const C_icon_burst_bitmap[] = { +0xe0, 0x01, 0xfe, 0xc1, 0xc1, 0x78, 0x60, 0x9e, 0xd3, 0x67, 0x9e, 0xc0, 0x0f, 0xe0, 0x97, 0xb3, + 0xe7, 0xe4, 0x81, 0x71, 0x30, 0xf8, 0x07, 0x78, 0x00, }; + +#ifdef OS_IO_SEPROXYHAL +#include "os_io_seproxyhal.h" +const bagl_icon_details_t C_icon_burst = { GLYPH_icon_burst_WIDTH, GLYPH_icon_burst_HEIGHT, 1, C_icon_burst_colors, C_icon_burst_bitmap }; +#endif // OS_IO_SEPROXYHAL +#include "glyphs.h" +unsigned int const C_icon_dashboard_colors[] + = { + 0x00000000, + 0x00ffffff, +}; + +unsigned char const C_icon_dashboard_bitmap[] = { +0xe0, 0x01, 0xfe, 0xc1, 0xff, 0x38, 0x70, 0x06, 0xd8, 0x79, 0x7e, 0x9e, 0x9f, 0xe7, 0xe7, 0xb9, + 0x01, 0xe6, 0xc0, 0xf1, 0x3f, 0xf8, 0x07, 0x78, 0x00, }; + +#ifdef OS_IO_SEPROXYHAL +#include "os_io_seproxyhal.h" +const bagl_icon_details_t C_icon_dashboard = { GLYPH_icon_dashboard_WIDTH, GLYPH_icon_dashboard_HEIGHT, 1, C_icon_dashboard_colors, C_icon_dashboard_bitmap }; +#endif // OS_IO_SEPROXYHAL diff --git a/src/glyphs.h b/src/glyphs.h new file mode 100644 index 0000000..ff22419 --- /dev/null +++ b/src/glyphs.h @@ -0,0 +1,45 @@ +#ifndef GLYPH_icon_back_BPP +#define GLYPH_icon_back_WIDTH 14 +#define GLYPH_icon_back_HEIGHT 14 +#define GLYPH_icon_back_BPP 1 +extern +unsigned int const C_icon_back_colors[] +; +extern +unsigned char const C_icon_back_bitmap[]; +#ifdef OS_IO_SEPROXYHAL +#include "os_io_seproxyhal.h" +extern +const bagl_icon_details_t C_icon_back; +#endif // GLYPH_icon_back_BPP +#endif // OS_IO_SEPROXYHAL +#ifndef GLYPH_icon_burst_BPP +#define GLYPH_icon_burst_WIDTH 14 +#define GLYPH_icon_burst_HEIGHT 14 +#define GLYPH_icon_burst_BPP 1 +extern +unsigned int const C_icon_burst_colors[] +; +extern +unsigned char const C_icon_burst_bitmap[]; +#ifdef OS_IO_SEPROXYHAL +#include "os_io_seproxyhal.h" +extern +const bagl_icon_details_t C_icon_burst; +#endif // GLYPH_icon_burst_BPP +#endif // OS_IO_SEPROXYHAL +#ifndef GLYPH_icon_dashboard_BPP +#define GLYPH_icon_dashboard_WIDTH 14 +#define GLYPH_icon_dashboard_HEIGHT 14 +#define GLYPH_icon_dashboard_BPP 1 +extern +unsigned int const C_icon_dashboard_colors[] +; +extern +unsigned char const C_icon_dashboard_bitmap[]; +#ifdef OS_IO_SEPROXYHAL +#include "os_io_seproxyhal.h" +extern +const bagl_icon_details_t C_icon_dashboard; +#endif // GLYPH_icon_dashboard_BPP +#endif // OS_IO_SEPROXYHAL diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..bb1cff9 --- /dev/null +++ b/src/main.c @@ -0,0 +1,485 @@ +/******************************************************************************* +* Burstcoin Wallet App for Nano Ledger S. +* Copyright (c) 2017-2018 Jake B. +* +* Based on Sample code provided and (c) 2016 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#include + +#include "main.h" +#include "crypto/curve25519_i64.h" +#include "crypto/rs_address.h" + +// Ledger Stuff +#include "ui.h" +#include "os.h" +#include "cx.h" +#include "os_io_seproxyhal.h" + +// This symbol is defined by the link script to be at the start of the stack +// area. Intended for stack canary +extern unsigned long _stack; +#define STACK_CANARY (*((volatile uint32_t*) &_stack)) + +void init_canary() { + STACK_CANARY = 0xDEADBEEF; +} + +void check_canary() { + if (STACK_CANARY != 0xDEADBEEF) + THROW(EXCEPTION_OVERFLOW); +} + +// Temporary area to sore stuff and reuse the same memory +tmpContext_t tmpCtx; + +#ifdef HAVE_U2F + +// U2F Stuff +#include "u2f/u2f_service.h" +#include "u2f/u2f_transport.h" + +volatile unsigned char u2fMessageBuffer[U2F_MAX_MESSAGE_SIZE]; +volatile u2f_service_t u2fService; + +#endif + +// Non-volatile storage for the wallet app's stuff +WIDE internalStorage_t N_storage_real; + +// SPI Buffer for io_event +unsigned char G_io_seproxyhal_spi_buffer[IO_SEPROXYHAL_BUFFER_SIZE_B]; + +// Stuff for the SHA-256 hashing +volatile unsigned int hashCount; // notification to restart the hash +cx_sha256_t hash; + +// Supported device errors for preprocessor +#ifdef TARGET_BLUE +#error This application does not support the Ledger Blue +#endif + +#ifndef TARGET_NANOS +#error This application only supports the Ledger Nano S +#endif + +#ifdef HAVE_U2F + +// Function to respond to a u2f request. Similar to the io_exchange function +void u2f_proxy_response(u2f_service_t *service, unsigned int tx) { + os_memset(service->messageBuffer, 0, 5); + os_memmove(service->messageBuffer + 5, G_io_apdu_buffer, tx); + service->messageBuffer[tx + 5] = 0x90; + service->messageBuffer[tx + 6] = 0x00; + u2f_send_fragmented_response(service, U2F_CMD_MSG, service->messageBuffer, + tx + 7, true); +} + +#endif + +unsigned short io_exchange_al(unsigned char channel, unsigned short tx_len) { + switch (channel & ~(IO_FLAGS)) { + case CHANNEL_KEYBOARD: + break; + + // multiplexed io exchange over a SPI channel and TLV encapsulated protocol + case CHANNEL_SPI: + if (tx_len) { + io_seproxyhal_spi_send(G_io_apdu_buffer, tx_len); + + if (channel & IO_RESET_AFTER_REPLIED) { + reset(); + } + return 0; // nothing received from the master so far (it's a tx + // transaction) + } else { + return io_seproxyhal_spi_recv(G_io_apdu_buffer, + sizeof(G_io_apdu_buffer), 0); + } + + default: + THROW(INVALID_PARAMETER); + } + return 0; +} + +// Get a public key from the 44'/30' keypath. +static bool getPublicKeyForIndex(int index, cx_ecfp_public_key_t* publicKey) { + if (!os_global_pin_is_validated()) { + return false; + } + + if ((N_storage.keyIndex != NO_KEY_STORED) && (N_storage.keyIndex == index)) { + publicKey->W_len = 32; + publicKey->curve = CX_CURVE_Curve25519; + os_memmove(publicKey->W, N_storage.publicKey, 32); + return true; + } + + // BURST keypath of 44'/30'/n'/0'/0' + uint32_t path[] = {44 | 0x80000000, 30 | 0x80000000, index | 0x80000000, 0x80000000, 0x80000000}; + + unsigned char privateKeyData[32]; + os_perso_derive_node_bip32(CX_CURVE_Ed25519, path, 5, privateKeyData, NULL); + keygen25519(publicKey->W, NULL, privateKeyData); + publicKey->W_len = 32; + publicKey->curve = CX_CURVE_Curve25519; + + // Store the computed key in flash + internalStorage_t storage; + os_memmove(&storage, &N_storage, sizeof(internalStorage_t)); + storage.keyIndex = index; + os_memmove(storage.publicKey, publicKey->W, 32); + nvm_write(&N_storage, (void *)&storage, sizeof(internalStorage_t)); + + return true; +} + +// // Get a signing key from the 44'/30' keypath. +static bool getSigningKeyForIndex(int index, cx_ecfp_private_key_t* privateKey) { + if (!os_global_pin_is_validated()) { + return false; + } + + // BURST keypath of 44'/30'/n'/0'/0' + uint32_t path[] = {44 | 0x80000000, 30 | 0x80000000, index | 0x80000000, 0x80000000, 0x80000000}; + + unsigned char privateKeyData[32]; + os_perso_derive_node_bip32(CX_CURVE_Ed25519, path, 5, privateKeyData, NULL); + keygen25519(NULL, privateKey->d, privateKeyData); + privateKey->curve = CX_CURVE_Curve25519; + privateKey->d_len = 32; + + return true; +} + +// Hanlde a signing request -- called both from the main apdu loop as well as from +// the button handler after the user verifies the transaction. +bool handleSigning(volatile unsigned int *tx, volatile unsigned int *flags) { + // If if this is the first hash, then initialize the SHA-256 session + if (hashCount == 0) { + // This is the first segment to hash + cx_sha256_init(&hash); + } + + // Update the hash with the data from this segment. + cx_hash(&hash.header, 0, G_io_apdu_buffer+5, G_io_apdu_buffer[4], NULL); + hashCount++; + + // If this is the last segment, calculate the signature + if (G_io_apdu_buffer[2] == P1_LAST) { + cx_ecfp_private_key_t signingKey; + getSigningKeyForIndex(0, &signingKey); + + // m = hash(Z, message) + cx_hash(&hash.header, CX_LAST, G_io_apdu_buffer, 0, tmpCtx.signingContext.m); + + // x = hash(m, s) + cx_sha256_init(&hash); + cx_hash(&hash.header, 0, tmpCtx.signingContext.m, 32, NULL); + cx_hash(&hash.header, CX_LAST, signingKey.d, 32, tmpCtx.signingContext.x); + + // keygen25519(Y, NULL, x); + // reuse G_io_apdu_buffer = Y to save some memory. + keygen25519(G_io_apdu_buffer, NULL, tmpCtx.signingContext.x); + + // r = hash(m+Y); + cx_sha256_init(&hash); + cx_hash(&hash.header, 0, tmpCtx.signingContext.m, 32, NULL); + cx_hash(&hash.header, CX_LAST, G_io_apdu_buffer, 32, tmpCtx.signingContext.r); + + // output (v,r) as the signature + // put v into G_io_apdu_buffer first, followed by r + //int sign25519(k25519 v, const k25519 h, const priv25519 x, const spriv25519 s) + sign25519(G_io_apdu_buffer, tmpCtx.signingContext.r, tmpCtx.signingContext.x, signingKey.d); + memcpy(G_io_apdu_buffer+32, tmpCtx.signingContext.r, 32); + + // return 64 bytes to host + *tx=64; + + // Reset for next signing + os_memset(&signingKey, 0, sizeof(signingKey)); // just for good measure, remove signing key from memory. + hashCount = 0; // Reset for next hashing/signing session. + return false; + } + + return true; +} + +// Called by both the U2F and the standard communications channel +void handleApdu(volatile unsigned int *flags, volatile unsigned int *tx, volatile unsigned int rx) { + unsigned short sw = 0; + BEGIN_TRY { + TRY { + + if (G_io_apdu_buffer[0] != CLA) { + THROW(SW_CLA_NOT_SUPPORTED); + } + + switch (G_io_apdu_buffer[1]) { + case INS_SIGN: { + if (G_io_apdu_buffer[4] != rx - 5) { + // the length of the APDU should match what's int he 5-byte header. + // If not fail. Don't want to buffer overrun or anything. + THROW(SW_CONDITIONS_NOT_SATISFIED); + } + if ((G_io_apdu_buffer[2] != P1_MORE) && + (G_io_apdu_buffer[2] != P1_LAST)) { + THROW(SW_INCORRECT_P1_P2); + } + + if (hashCount == 0) { + if (G_io_apdu_buffer) + ui_verify(); + *flags |= IO_ASYNCH_REPLY; + } else { + bool more = handleSigning(tx, flags); + THROW(SW_OK); + } + + } break; + + case INS_GET_PUBLIC_KEY: { + // Get the public key and return it. + cx_ecfp_public_key_t publicKey; + if (getPublicKeyForIndex(0, &publicKey)) { + os_memmove(G_io_apdu_buffer, publicKey.W, publicKey.W_len); + *tx = publicKey.W_len; + THROW(SW_OK); + } else { + // Return an error + THROW(SW_INS_NOT_SUPPORTED); + } + } break; + + default: + // Instruction not supported + THROW(SW_INS_NOT_SUPPORTED); + break; + } + } + CATCH(EXCEPTION_IO_RESET) { + THROW(EXCEPTION_IO_RESET); + } + CATCH_OTHER(e) { + switch (e & 0xF000) { + case 0x6000: + // TODO: WIPE/Clean Up if necessary? + sw = e; + break; + case 0x9000: + // All is well + sw = e; + break; + default: + // Internal error + sw = 0x6800 | (e & 0x7FF); + break; + } + // Unexpected exception => report + G_io_apdu_buffer[*tx] = sw >> 8; + G_io_apdu_buffer[*tx + 1] = sw; + *tx += 2; + } + FINALLY { + } + END_TRY; + } +} + +static void burst_main(void) { + volatile unsigned int rx = 0; + volatile unsigned int tx = 0; + volatile unsigned int flags = 0; + + // next timer callback in 500 ms + UX_CALLBACK_SET_INTERVAL(500); + + // DESIGN NOTE: the bootloader ignores the way APDU are fetched. The only + // goal is to retrieve APDU. + // When APDU are to be fetched from multiple IOs, like NFC+USB+BLE, make + // sure the io_event is called with a + // switch event, before the apdu is replied to the bootloader. This avoid + // APDU injection faults. + for (;;) { + volatile unsigned short sw = 0; + + BEGIN_TRY { + TRY { + rx = tx; + tx = 0; // ensure no race in catch_other if io_exchange throws + // an error + rx = io_exchange(CHANNEL_APDU | flags, rx); + flags = 0; + + // no apdu received, well, reset the session, and reset the + // bootloader configuration + if (rx == 0) { + THROW(SW_SECURITY_STATUS_NOT_SATISFIED); + } + + // Call the Apdu handler, + handleApdu(&flags, &tx, rx); + } + CATCH(EXCEPTION_IO_RESET) { + THROW(EXCEPTION_IO_RESET); + } + CATCH_OTHER(e) { + switch (e & 0xF000) { + case 0x6000: + case 0x9000: + sw = e; + break; + default: + sw = 0x6800 | (e & 0x7FF); + break; + } + // Unexpected exception => report + G_io_apdu_buffer[tx] = sw >> 8; + G_io_apdu_buffer[tx + 1] = sw; + tx += 2; + } + FINALLY { + } + } + END_TRY; + } + + return; +} + +void io_seproxyhal_display(const bagl_element_t *element) { + io_seproxyhal_display_default((bagl_element_t *)element); +} + +unsigned char io_event(unsigned char channel) { + //check_canary(); + // nothing done with the event, throw an error on the transport layer if + // needed + // can't have more than one tag in the reply, not supported yet. + switch (G_io_seproxyhal_spi_buffer[0]) { + case SEPROXYHAL_TAG_FINGER_EVENT: + UX_FINGER_EVENT(G_io_seproxyhal_spi_buffer); + break; + + case SEPROXYHAL_TAG_BUTTON_PUSH_EVENT: // for Nano S + UX_BUTTON_PUSH_EVENT(G_io_seproxyhal_spi_buffer); + break; + + case SEPROXYHAL_TAG_DISPLAY_PROCESSED_EVENT: + UX_DISPLAYED_EVENT({ }); + break; + + case SEPROXYHAL_TAG_TICKER_EVENT: + UX_TICKER_EVENT(G_io_seproxyhal_spi_buffer, { + if (ux_step_count && UX_ALLOWED) { + // prepare next screen + ux_step = (ux_step + 1) % ux_step_count; + // redisplay screen + UX_REDISPLAY(); + } + }); + break; + + // unknown events are acknowledged + default: + UX_DEFAULT_EVENT(); + break; + } + + // close the event if not done previously (by a display or whatever) + if (!io_seproxyhal_spi_is_status_sent()) { + io_seproxyhal_general_status(); + } + + // command has been processed, DO NOT reset the current APDU transport + return 1; +} + +void app_exit(void) { + BEGIN_TRY_L(exit) { + TRY_L(exit) { + os_sched_exit(-1); + } + FINALLY_L(exit) { + } + } + END_TRY_L(exit); +} + +__attribute__((section(".boot"))) int main(void) { + // exit critical section + __asm volatile("cpsie i"); + + //init_canary(); + + // current_text_pos = 0; + // text_y = 60; + hashCount = 0; + uiState = UI_IDLE; + + for (;;) { + // ensure exception will work as planned + os_boot(); + + UX_INIT(); + + BEGIN_TRY { + TRY { + io_seproxyhal_init(); + + if (N_storage.initialized != 0x01) { + internalStorage_t storage; + storage.fidoTransport = 0x00; + storage.initialized = 0x01; + storage.keyIndex = NO_KEY_STORED; + os_memset(storage.publicKey, 0, 32); + nvm_write(&N_storage, (void *)&storage, + sizeof(internalStorage_t)); + } + + #ifdef HAVE_U2F + os_memset((unsigned char *)&u2fService, 0, sizeof(u2fService)); + u2fService.inputBuffer = G_io_apdu_buffer; + u2fService.outputBuffer = G_io_apdu_buffer; + u2fService.messageBuffer = (uint8_t *)u2fMessageBuffer; + u2fService.messageBufferSize = U2F_MAX_MESSAGE_SIZE; + u2f_initialize_service((u2f_service_t *)&u2fService); + + USB_power_U2F(1, N_storage.fidoTransport); + #else // HAVE_U2F + USB_power_U2F(1, 0); + #endif // HAVE_U2F + + ui_idle(); + + burst_main(); + } + CATCH(EXCEPTION_IO_RESET) { + // reset IO and UX before continuing + continue; + } + CATCH_ALL { + break; + } + FINALLY { + } + } + END_TRY; + } + app_exit(); + return 0; +} diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..39e7cf7 --- /dev/null +++ b/src/main.h @@ -0,0 +1,99 @@ + +/******************************************************************************* +* Burstcoin Wallet App for Nano Ledger S. +* Copyright (c) 2017-2018 Jake B. +* +* Based on Sample code provided and (c) 2016 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#ifndef __MAIN_H__ +#define __MAIN_H__ +#include "os.h" +#include "cx.h" + +#ifdef HAVE_U2F + +#include "u2f/u2f_service.h" +#include "u2f/u2f_transport.h" + +extern volatile unsigned char u2fMessageBuffer[U2F_MAX_MESSAGE_SIZE]; +extern volatile u2f_service_t u2fService; +extern bool fidoActivated; +extern void USB_power_U2F(unsigned char enabled, unsigned char fido); +void u2f_proxy_response(u2f_service_t *service, unsigned int tx); + +#endif + +// Host innteration communication protocol +#define CLA 0x80 // CLASS? +#define INS_SIGN 0x02 // Sign Instruction +#define INS_GET_PUBLIC_KEY 0x04 // Get Public Key Instruction +#define INS_PING 0x06 // Respond with PONG +#define P1_LAST 0x80 // Parameter 1 = End of Bytes to Sign (finalize) +#define P1_MORE 0x00 // Parameter 1 = More bytes coming + + +#define SW_OK 0x9000 +#define SW_CONDITIONS_NOT_SATISFIED 0x6985 +#define SW_INCORRECT_P1_P2 0x6A86 +#define SW_WRONG_P1_P2 0x6B00 +#define SW_INS_NOT_SUPPORTED 0x6D00 +#define SW_CLA_NOT_SUPPORTED 0x6E00 +#define SW_SECURITY_STATUS_NOT_SATISFIED 0x6982 + +#define KEEP_PRIVATE_KEY 1 + + +extern volatile unsigned int hashCount; // notification to restart the hash +extern cx_sha256_t hash; + +#define NO_KEY_STORED -1 + +typedef struct internalStorage_t { + uint8_t fidoTransport; + uint8_t initialized; + int keyIndex; + unsigned char publicKey[32]; +} internalStorage_t; + +extern WIDE internalStorage_t N_storage_real; +#define N_storage (*(WIDE internalStorage_t *)PIC(&N_storage_real)) + +bool handleSigning(volatile unsigned int *tx, volatile unsigned int *flags); + +// A place to store information about the transaction +// for displaying to the user when requesting approval +typedef struct transactionContext_t { + char feesAmount[32]; + char fullAddress[32]; + char fullAmount[32]; +} transactionContext_t; + +// A place to store data during hte signing (various) +// hashes and keys. +typedef struct signingContext_t { + unsigned char m[32]; + unsigned char x[32]; + unsigned char r[32]; +} messageSigningContext_t; + +typedef union { + transactionContext_t transactionContext; + messageSigningContext_t signingContext; +} tmpContext_t; + +extern tmpContext_t tmpCtx; // Temporary area to sore stuff + +#endif diff --git a/src/u2f/u2f_io.c b/src/u2f/u2f_io.c new file mode 100644 index 0000000..32b51ab --- /dev/null +++ b/src/u2f/u2f_io.c @@ -0,0 +1,79 @@ +#ifdef HAVE_U2F + +/* +******************************************************************************* +* Portable FIDO U2F implementation +* Ledger Blue specific initialization +* (c) 2016 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#include +#include +#include "os.h" + +#include "os_io_seproxyhal.h" + +#include "u2f_io.h" +#include "u2f_transport.h" + +extern void u2f_reset_display(void); + +volatile unsigned char u2fCommandSent = 0; +volatile unsigned char u2fFirstCommand = 0; +volatile unsigned char u2fClosed = 0; + +void u2f_io_open_session(void) { + // PRINTF("u2f_io_open_session\n"); + u2fCommandSent = 0; + u2fFirstCommand = 1; + u2fClosed = 0; +} + +unsigned char u2fSegment[MAX_SEGMENT_SIZE]; + +void u2f_io_send(uint8_t *buffer, uint16_t length, + u2f_transport_media_t media) { + if (media == U2F_MEDIA_USB) { + os_memset(u2fSegment, 0, sizeof(u2fSegment)); + } + os_memmove(u2fSegment, buffer, length); + // PRINTF("u2f_io_send\n"); + if (u2fFirstCommand) { + u2fFirstCommand = 0; + } + switch (media) { + case U2F_MEDIA_USB: + io_usb_send_apdu_data(u2fSegment, USB_SEGMENT_SIZE); + break; +#ifdef HAVE_BLE + case U2F_MEDIA_BLE: + BLE_protocol_send(buffer, length); + break; +#endif + default: + PRINTF("Request to send on unsupported media %d\n", media); + break; + } +} + +void u2f_io_close_session(void) { + // PRINTF("u2f_close_session\n"); + if (!u2fClosed) { + // u2f_reset_display(); + u2fClosed = 1; + } +} + +#endif diff --git a/src/u2f/u2f_io.h b/src/u2f/u2f_io.h new file mode 100644 index 0000000..495b222 --- /dev/null +++ b/src/u2f/u2f_io.h @@ -0,0 +1,35 @@ +/* +******************************************************************************* +* Portable FIDO U2F implementation +* (c) 2016 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#include +#include +#include + +#ifndef __U2F_IO_H__ + +#define __U2F_IO_H__ + +#include "u2f_service.h" + +#define EXCEPTION_DISCONNECT 0x80 + +void u2f_io_open_session(void); +void u2f_io_send(uint8_t *buffer, uint16_t length, u2f_transport_media_t media); +void u2f_io_close_session(void); + +#endif diff --git a/src/u2f/u2f_processing.c b/src/u2f/u2f_processing.c new file mode 100644 index 0000000..8131291 --- /dev/null +++ b/src/u2f/u2f_processing.c @@ -0,0 +1,279 @@ +#ifdef HAVE_U2F + +/* +******************************************************************************* +* Portable FIDO U2F implementation +* (c) 2016 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#include +#include +#include "os.h" +#include "cx.h" +#include "u2f_service.h" +#include "u2f_transport.h" +#include "u2f_processing.h" + +void handleApdu(volatile unsigned int *flags, volatile unsigned int *tx, volatile unsigned int rx); +void u2f_proxy_response(u2f_service_t *service, unsigned int tx); + +static const uint8_t SW_SUCCESS[] = {0x90, 0x00}; +static const uint8_t SW_PROOF_OF_PRESENCE_REQUIRED[] = {0x69, 0x85}; +static const uint8_t SW_BAD_KEY_HANDLE[] = {0x6A, 0x80}; + +static const uint8_t VERSION[] = {'U', '2', 'F', '_', 'V', '2', 0x90, 0x00}; +static const uint8_t DUMMY_ZERO[] = {0x00}; + +static const uint8_t SW_UNKNOWN_INSTRUCTION[] = {0x6d, 0x00}; +static const uint8_t SW_UNKNOWN_CLASS[] = {0x6e, 0x00}; +static const uint8_t SW_WRONG_LENGTH[] = {0x67, 0x00}; +static const uint8_t SW_INTERNAL[] = {0x6F, 0x00}; + +static const uint8_t NOTIFY_USER_PRESENCE_NEEDED[] = { + KEEPALIVE_REASON_TUP_NEEDED}; + +static const uint8_t PROXY_MAGIC[] = {'b', 'r', 's'}; + +#define INIT_U2F_VERSION 0x02 +#define INIT_DEVICE_VERSION_MAJOR 0 +#define INIT_DEVICE_VERSION_MINOR 1 +#define INIT_BUILD_VERSION 0 +#define INIT_CAPABILITIES 0x00 + +#define FIDO_CLA 0x00 +#define FIDO_INS_ENROLL 0x01 +#define FIDO_INS_SIGN 0x02 +#define FIDO_INS_GET_VERSION 0x03 + +#define FIDO_INS_PROP_GET_COUNTER 0xC0 // U2F_VENDOR_FIRST + +#define P1_SIGN_CHECK_ONLY 0x07 +#define P1_SIGN_SIGN 0x03 + +#define U2F_ENROLL_RESERVED 0x05 +#define SIGN_USER_PRESENCE_MASK 0x01 + +#define MAX_SEQ_TIMEOUT_MS 500 +#define MAX_KEEPALIVE_TIMEOUT_MS 500 + +static const uint8_t DUMMY_USER_PRESENCE[] = {SIGN_USER_PRESENCE_MASK}; + +void u2f_handle_enroll(u2f_service_t *service, uint8_t p1, uint8_t p2, + uint8_t *buffer, uint16_t length) { + (void)p1; + (void)p2; + (void)buffer; + if (length != 32 + 32) { + u2f_send_fragmented_response(service, U2F_CMD_MSG, + (uint8_t *)SW_WRONG_LENGTH, + sizeof(SW_WRONG_LENGTH), true); + return; + } + u2f_send_fragmented_response(service, U2F_CMD_MSG, (uint8_t *)SW_INTERNAL, + sizeof(SW_INTERNAL), true); +} + +void u2f_handle_sign(u2f_service_t *service, uint8_t p1, uint8_t p2, + uint8_t *buffer, uint16_t length) { + (void)p1; + (void)p2; + (void)length; + uint8_t keyHandleLength; + uint8_t i; + volatile unsigned int flags = 0; + volatile unsigned int tx = 0; + + if (length < 32 + 32 + 1) { + u2f_send_fragmented_response(service, U2F_CMD_MSG, + (uint8_t *)SW_WRONG_LENGTH, + sizeof(SW_WRONG_LENGTH), true); + return; + } + if ((p1 != P1_SIGN_CHECK_ONLY) && (p1 != P1_SIGN_SIGN)) { + u2f_response_error(service, ERROR_PROP_INVALID_PARAMETERS_APDU, true, + service->channel); + return; + } + + keyHandleLength = buffer[64]; + for (i = 0; i < keyHandleLength; i++) { + buffer[65 + i] ^= PROXY_MAGIC[i % sizeof(PROXY_MAGIC)]; + } + // Check magic + if (length != (32 + 32 + 1 + 5 + buffer[65 + 4])) { + u2f_send_fragmented_response(service, U2F_CMD_MSG, + (uint8_t *)SW_BAD_KEY_HANDLE, + sizeof(SW_BAD_KEY_HANDLE), true); + } + // Check that it looks like an APDU + os_memmove(G_io_apdu_buffer, buffer + 65, keyHandleLength); + handleApdu(&flags, &tx, keyHandleLength); + if ((flags & IO_ASYNCH_REPLY) == 0) { + u2f_proxy_response(service, tx); + } +} + +void u2f_handle_get_version(u2f_service_t *service, uint8_t p1, uint8_t p2, + uint8_t *buffer, uint16_t length) { + // screen_printf("U2F version\n"); + (void)p1; + (void)p2; + (void)buffer; + if (length != 0) { + u2f_send_fragmented_response(service, U2F_CMD_MSG, + (uint8_t *)SW_WRONG_LENGTH, + sizeof(SW_WRONG_LENGTH), true); + return; + } + u2f_send_fragmented_response(service, U2F_CMD_MSG, (uint8_t *)VERSION, + sizeof(VERSION), true); +} + +void u2f_handle_cmd_init(u2f_service_t *service, uint8_t *buffer, + uint16_t length, uint8_t *channelInit) { + // screen_printf("U2F init\n"); + uint8_t channel[4]; + (void)length; + uint16_t offset = 0; + if (u2f_is_channel_forbidden(channelInit)) { + u2f_response_error(service, ERROR_INVALID_CID, true, channelInit); + return; + } + if (u2f_is_channel_broadcast(channelInit)) { + cx_rng(channel, 4); + } else { + os_memmove(channel, channelInit, 4); + } + os_memmove(service->messageBuffer + offset, buffer, 8); + offset += 8; + os_memmove(service->messageBuffer + offset, channel, 4); + offset += 4; + service->messageBuffer[offset++] = INIT_U2F_VERSION; + service->messageBuffer[offset++] = INIT_DEVICE_VERSION_MAJOR; + service->messageBuffer[offset++] = INIT_DEVICE_VERSION_MINOR; + service->messageBuffer[offset++] = INIT_BUILD_VERSION; + service->messageBuffer[offset++] = INIT_CAPABILITIES; + if (u2f_is_channel_broadcast(channelInit)) { + os_memset(service->channel, 0xff, 4); + } else { + os_memmove(service->channel, channel, 4); + } + service->keepUserPresence = true; + u2f_send_fragmented_response(service, U2F_CMD_INIT, service->messageBuffer, + offset, true); + // os_memmove(service->channel, channel, 4); +} + +void u2f_handle_cmd_ping(u2f_service_t *service, uint8_t *buffer, + uint16_t length) { + // screen_printf("U2F ping\n"); + u2f_send_fragmented_response(service, U2F_CMD_PING, buffer, length, true); +} + +void u2f_handle_cmd_msg(u2f_service_t *service, uint8_t *buffer, + uint16_t length) { + // screen_printf("U2F msg\n"); + uint8_t cla = buffer[0]; + uint8_t ins = buffer[1]; + uint8_t p1 = buffer[2]; + uint8_t p2 = buffer[3]; + uint32_t dataLength = (buffer[4] << 16) | (buffer[5] << 8) | (buffer[6]); + if ((dataLength != (uint16_t)(length - 9)) && + (dataLength != (uint16_t)(length - 7))) { // Le is optional + u2f_send_fragmented_response(service, U2F_CMD_MSG, + (uint8_t *)SW_WRONG_LENGTH, + sizeof(SW_WRONG_LENGTH), true); + return; + } + if (cla != FIDO_CLA) { + u2f_send_fragmented_response(service, U2F_CMD_MSG, + (uint8_t *)SW_UNKNOWN_CLASS, + sizeof(SW_UNKNOWN_CLASS), true); + return; + } + switch (ins) { + case FIDO_INS_ENROLL: + // screen_printf("enroll\n"); + u2f_handle_enroll(service, p1, p2, buffer + 7, dataLength); + break; + case FIDO_INS_SIGN: + // screen_printf("sign\n"); + u2f_handle_sign(service, p1, p2, buffer + 7, dataLength); + break; + case FIDO_INS_GET_VERSION: + // screen_printf("version\n"); + u2f_handle_get_version(service, p1, p2, buffer + 7, dataLength); + break; + default: + // screen_printf("unsupported\n"); + u2f_send_fragmented_response(service, U2F_CMD_MSG, + (uint8_t *)SW_UNKNOWN_INSTRUCTION, + sizeof(SW_UNKNOWN_INSTRUCTION), true); + return; + } +} + +void u2f_process_message(u2f_service_t *service, uint8_t *buffer, + uint8_t *channel) { + uint8_t cmd = buffer[0]; + uint16_t length = (buffer[1] << 8) | (buffer[2]); + switch (cmd) { + case U2F_CMD_INIT: + u2f_handle_cmd_init(service, buffer + 3, length, channel); + break; + case U2F_CMD_PING: + service->pendingContinuation = false; + u2f_handle_cmd_ping(service, buffer + 3, length); + break; + case U2F_CMD_MSG: + service->pendingContinuation = false; + if (!service->noReentry && service->runningCommand) { + u2f_response_error(service, ERROR_CHANNEL_BUSY, false, + service->channel); + break; + } + service->runningCommand = true; + u2f_handle_cmd_msg(service, buffer + 3, length); + break; + } +} + +void u2f_timeout(u2f_service_t *service) { + service->timerNeedGeneralStatus = true; + if ((service->transportMedia == U2F_MEDIA_USB) && + (service->pendingContinuation)) { + service->seqTimeout += service->timerInterval; + if (service->seqTimeout > MAX_SEQ_TIMEOUT_MS) { + service->pendingContinuation = false; + u2f_response_error(service, ERROR_MSG_TIMEOUT, true, + service->lastContinuationChannel); + } + } +#ifdef HAVE_BLE + if ((service->transportMedia == U2F_MEDIA_BLE) && + (service->requireKeepalive)) { + service->keepaliveTimeout += service->timerInterval; + if (service->keepaliveTimeout > MAX_KEEPALIVE_TIMEOUT_MS) { + service->keepaliveTimeout = 0; + u2f_send_fragmented_response(service, U2F_CMD_KEEPALIVE, + (uint8_t *)NOTIFY_USER_PRESENCE_NEEDED, + sizeof(NOTIFY_USER_PRESENCE_NEEDED), + false); + } + } +#endif +} + +#endif diff --git a/src/u2f/u2f_processing.h b/src/u2f/u2f_processing.h new file mode 100644 index 0000000..429c9ba --- /dev/null +++ b/src/u2f/u2f_processing.h @@ -0,0 +1,29 @@ +/* +******************************************************************************* +* Portable FIDO U2F implementation +* (c) 2016 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#ifndef __U2F_PROCESSING_H__ + +#define __U2F_PROCESSING_H__ + +void u2f_process_message(u2f_service_t *service, uint8_t *buffer, + uint8_t *channel); +void u2f_timeout(u2f_service_t *service); + +void u2f_handle_ux_callback(u2f_service_t *service); + +#endif diff --git a/src/u2f/u2f_service.c b/src/u2f/u2f_service.c new file mode 100644 index 0000000..5720ad9 --- /dev/null +++ b/src/u2f/u2f_service.c @@ -0,0 +1,147 @@ +#ifdef HAVE_U2F + +/* +******************************************************************************* +* Portable FIDO U2F implementation +* (c) 2016 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#include +#include +#include "u2f_service.h" +#include "u2f_transport.h" +#include "u2f_processing.h" +#include "u2f_timer.h" +#include "u2f_io.h" +#include "os.h" + +// not too fast blinking +#define DEFAULT_TIMER_INTERVAL_MS 500 + +void u2f_reset(u2f_service_t *service, bool keepUserPresence) { + service->transportState = U2F_IDLE; + service->runningCommand = false; + // service->promptUserPresence = false; + if (service->keepUserPresence) { + keepUserPresence = true; + service->keepUserPresence = false; + } +#ifdef HAVE_NO_USER_PRESENCE_CHECK + service->keepUserPresence = true; + service->userPresence = true; +#endif // HAVE_NO_USER_PRESENCE_CHECK +} + +void u2f_initialize_service(u2f_service_t *service) { + service->handleFunction = (u2fHandle_t)u2f_process_message; + service->timeoutFunction = (u2fTimer_t)u2f_timeout; + service->timerInterval = DEFAULT_TIMER_INTERVAL_MS; + u2f_reset(service, false); + service->promptUserPresence = false; + service->userPresence = false; +#ifdef HAVE_NO_USER_PRESENCE_CHECK + service->keepUserPresence = true; + service->userPresence = true; +#endif // HAVE_NO_USER_PRESENCE_CHECK +} + +void u2f_send_direct_response_short(u2f_service_t *service, uint8_t *buffer, + uint16_t len) { + (void)service; + uint16_t maxSize = 0; + switch (service->packetMedia) { + case U2F_MEDIA_USB: + maxSize = USB_SEGMENT_SIZE; + break; +#ifdef HAVE_BLE + case U2F_MEDIA_BLE: + maxSize = service->bleMtu; + break; +#endif + default: + PRINTF("Request to send on unsupported media %d\n", + service->packetMedia); + break; + } + if (len > maxSize) { + return; + } + u2f_io_send(buffer, len, service->packetMedia); + u2f_io_close_session(); +} + +void u2f_send_fragmented_response(u2f_service_t *service, uint8_t cmd, + uint8_t *buffer, uint16_t len, + bool resetAfterSend) { + if (resetAfterSend) { + service->transportState = U2F_SENDING_RESPONSE; + } + service->sending = true; + service->sendPacketIndex = 0; + service->sendBuffer = buffer; + service->sendOffset = 0; + service->sendLength = len; + service->sendCmd = cmd; + service->resetAfterSend = resetAfterSend; + u2f_continue_sending_fragmented_response(service); +} + +void u2f_continue_sending_fragmented_response(u2f_service_t *service) { + do { + uint16_t channelHeader = + (service->transportMedia == U2F_MEDIA_USB ? 4 : 0); + uint8_t headerSize = + (service->sendPacketIndex == 0 ? (channelHeader + 3) + : (channelHeader + 1)); + uint16_t maxBlockSize = + (service->transportMedia == U2F_MEDIA_USB ? USB_SEGMENT_SIZE + : service->bleMtu); + uint16_t blockSize = ((service->sendLength - service->sendOffset) > + (maxBlockSize - headerSize) + ? (maxBlockSize - headerSize) + : service->sendLength - service->sendOffset); + uint16_t dataSize = blockSize + headerSize; + uint16_t offset = 0; + // Fragment + if (service->transportMedia == U2F_MEDIA_USB) { + os_memset(service->outputBuffer, 0, USB_SEGMENT_SIZE); + os_memmove(service->outputBuffer + offset, service->channel, 4); + offset += 4; + } + if (service->sendPacketIndex == 0) { + service->outputBuffer[offset++] = service->sendCmd; + service->outputBuffer[offset++] = (service->sendLength >> 8); + service->outputBuffer[offset++] = (service->sendLength & 0xff); + } else { + service->outputBuffer[offset++] = (service->sendPacketIndex - 1); + } + if (service->sendBuffer != NULL) { + os_memmove(service->outputBuffer + headerSize, + service->sendBuffer + service->sendOffset, blockSize); + } + u2f_io_send(service->outputBuffer, dataSize, service->packetMedia); + service->sendOffset += blockSize; + service->sendPacketIndex++; + } while (service->sendOffset != service->sendLength); + if (service->sendOffset == service->sendLength) { + u2f_io_close_session(); + service->sending = false; + if (service->resetAfterSend) { + u2f_reset(service, false); + } + } +} + +#endif diff --git a/src/u2f/u2f_service.h b/src/u2f/u2f_service.h new file mode 100644 index 0000000..a78319b --- /dev/null +++ b/src/u2f/u2f_service.h @@ -0,0 +1,112 @@ +/* +******************************************************************************* +* Portable FIDO U2F implementation +* (c) 2016 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#include +#include +#include + +#ifndef __U2F_SERVICE_H__ + +#define __U2F_SERVICE_H__ + +struct u2f_service_t; + +typedef void (*u2fHandle_t)(struct u2f_service_t *service, uint8_t *inputBuffer, + uint8_t *channel); +typedef void (*u2fPromptUserPresence_t)(struct u2f_service_t *service, + bool enroll, + uint8_t *applicationParameter); +typedef void (*u2fTimer_t)(struct u2f_service_t *service); + +typedef enum { + U2F_IDLE, + U2F_HANDLE_SEGMENTED, + U2F_PROCESSING_COMMAND, + U2F_SENDING_RESPONSE +} u2f_transport_state_t; + +typedef enum { + U2F_MEDIA_NONE, + U2F_MEDIA_USB, + U2F_MEDIA_NFC, + U2F_MEDIA_BLE +} u2f_transport_media_t; + +typedef struct u2f_service_t { + // Internal + + uint8_t channel[4]; + uint8_t lastContinuationChannel[4]; + uint16_t transportOffset; + u2f_transport_state_t transportState; + u2f_transport_media_t transportMedia; + u2f_transport_media_t packetMedia; + uint8_t expectedContinuationPacket; + uint16_t lastCommandLength; + bool runningCommand; + + u2fHandle_t handleFunction; + + bool userPresence; + bool promptUserPresence; + bool reportUserPresence; + bool keepUserPresence; + u2fPromptUserPresence_t promptUserPresenceFunction; + + u2fTimer_t timeoutFunction; + uint32_t timerInterval; + bool timerNeedGeneralStatus; + uint32_t seqTimeout; + bool pendingContinuation; + bool requireKeepalive; + uint32_t keepaliveTimeout; + + bool sending; + uint8_t sendPacketIndex; + uint8_t *sendBuffer; + uint16_t sendOffset; + uint16_t sendLength; + uint8_t sendCmd; + bool resetAfterSend; + + // External, to be filled + + uint8_t *inputBuffer; + uint8_t *outputBuffer; + uint8_t *messageBuffer; + uint16_t messageBufferSize; + uint8_t *confirmedApplicationParameter; + bool noReentry; + uint16_t bleMtu; +} u2f_service_t; + +void u2f_initialize_service(u2f_service_t *service); +void u2f_send_direct_response_short(u2f_service_t *service, uint8_t *buffer, + uint16_t len); +void u2f_send_fragmented_response(u2f_service_t *service, uint8_t cmd, + uint8_t *buffer, uint16_t len, + bool resetAfterSend); +void u2f_confirm_user_presence(u2f_service_t *service, bool userPresence, + bool resume); +void u2f_continue_sending_fragmented_response(u2f_service_t *service); +void u2f_reset(u2f_service_t *service, bool keepUserPresence); + +// export global +extern volatile u2f_service_t u2fService; + +#endif diff --git a/src/u2f/u2f_timer.h b/src/u2f/u2f_timer.h new file mode 100644 index 0000000..9a845ab --- /dev/null +++ b/src/u2f/u2f_timer.h @@ -0,0 +1,33 @@ +/* +******************************************************************************* +* Portable FIDO U2F implementation +* (c) 2016 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#include +#include +#include + +#ifndef __U2F_TIMER_H__ + +#define __U2F_TIMER_H__ + +typedef void (*u2fTimer_t)(struct u2f_service_t *service); + +void u2f_timer_init(void); +void u2f_timer_register(uint32_t timerMs, u2fTimer_t timerCallback); +void u2f_timer_cancel(void); + +#endif diff --git a/src/u2f/u2f_transport.c b/src/u2f/u2f_transport.c new file mode 100644 index 0000000..2f16227 --- /dev/null +++ b/src/u2f/u2f_transport.c @@ -0,0 +1,229 @@ +#ifdef HAVE_U2F + +/* +******************************************************************************* +* Portable FIDO U2F implementation +* (c) 2016 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#include +#include +#include "u2f_service.h" +#include "u2f_transport.h" +#include "u2f_io.h" +#include "os.h" + +#define U2F_MASK_COMMAND 0x80 +#define U2F_COMMAND_HEADER_SIZE 3 + +static const uint8_t const BROADCAST_CHANNEL[] = {0xff, 0xff, 0xff, 0xff}; +static const uint8_t const FORBIDDEN_CHANNEL[] = {0x00, 0x00, 0x00, 0x00}; + +void u2f_transport_handle(u2f_service_t *service, uint8_t *buffer, + uint16_t size, u2f_transport_media_t media) { + uint16_t channelHeader = (media == U2F_MEDIA_USB ? 4 : 0); + uint8_t channel[4] = {0}; + if (media == U2F_MEDIA_USB) { + os_memmove(channel, buffer, 4); + } + // screen_printf("U2F transport\n"); + service->packetMedia = media; + u2f_io_open_session(); + // If busy, answer immediately + if (service->noReentry) { + if ((service->transportState == U2F_PROCESSING_COMMAND) || + (service->transportState == U2F_SENDING_RESPONSE)) { + u2f_response_error(service, ERROR_CHANNEL_BUSY, false, channel); + goto error; + } + } + if (size < (1 + channelHeader)) { + // Message to short, abort + u2f_response_error(service, ERROR_PROP_MESSAGE_TOO_SHORT, true, + channel); + goto error; + } + if ((buffer[channelHeader] & U2F_MASK_COMMAND) != 0) { + if (size < (channelHeader + 3)) { + // Message to short, abort + u2f_response_error(service, ERROR_PROP_MESSAGE_TOO_SHORT, true, + channel); + goto error; + } + // If waiting for a continuation on a different channel, reply BUSY + // immediately + if (media == U2F_MEDIA_USB) { + if ((service->pendingContinuation) && + (os_memcmp(channel, service->lastContinuationChannel, 4) != + 0) && + (buffer[channelHeader] != U2F_CMD_INIT)) { + u2f_response_error(service, ERROR_CHANNEL_BUSY, false, channel); + goto error; + } + } + // If a command was already sent, and we are not processing a INIT + // command, abort + if ((service->transportState == U2F_HANDLE_SEGMENTED) && + !((media == U2F_MEDIA_USB) && + (buffer[channelHeader] == U2F_CMD_INIT))) { + // Unexpected continuation at this stage, abort + u2f_response_error(service, ERROR_INVALID_SEQ, true, channel); + goto error; + } + // Check the length + uint16_t commandLength = + (buffer[channelHeader + 1] << 8) | (buffer[channelHeader + 2]); + if (commandLength > (service->messageBufferSize - 3)) { + // Overflow in message size, abort + u2f_response_error(service, ERROR_INVALID_LEN, true, channel); + goto error; + } + // Check if the command is supported + switch (buffer[channelHeader]) { + case U2F_CMD_PING: + case U2F_CMD_MSG: + if (media == U2F_MEDIA_USB) { + if (u2f_is_channel_broadcast(channel) || + u2f_is_channel_forbidden(channel)) { + u2f_response_error(service, ERROR_INVALID_CID, true, + channel); + goto error; + } + } + break; + case U2F_CMD_INIT: + if (media != U2F_MEDIA_USB) { + // Unknown command, abort + u2f_response_error(service, ERROR_INVALID_CMD, true, channel); + goto error; + } + break; + default: + // Unknown command, abort + u2f_response_error(service, ERROR_INVALID_CMD, true, channel); + goto error; + } + // Ok, initialize the buffer + os_memmove(service->channel, channel, 4); + service->lastCommandLength = commandLength; + service->expectedContinuationPacket = 0; + os_memmove(service->messageBuffer, buffer + channelHeader, + size - channelHeader); + service->transportOffset = size - channelHeader; + service->transportMedia = media; + } else { + // Continuation + if (size < (channelHeader + 2)) { + // Message to short, abort + u2f_response_error(service, ERROR_PROP_MESSAGE_TOO_SHORT, true, + channel); + goto error; + } + if (media != service->transportMedia) { + // Mixed medias + u2f_response_error(service, ERROR_PROP_MEDIA_MIXED, true, channel); + goto error; + } + if (service->transportState != U2F_HANDLE_SEGMENTED) { + // Unexpected continuation at this stage, abort + // TODO : review the behavior is HID only + if (media == U2F_MEDIA_USB) { + u2f_reset(service, true); + goto error; + } else { + u2f_response_error(service, ERROR_INVALID_SEQ, true, channel); + goto error; + } + } + if (media == U2F_MEDIA_USB) { + // Check the channel + if (os_memcmp(buffer, service->channel, 4) != 0) { + u2f_response_error(service, ERROR_CHANNEL_BUSY, true, channel); + goto error; + } + } + + if (buffer[channelHeader] != service->expectedContinuationPacket) { + // Bad continuation packet, abort + u2f_response_error(service, ERROR_INVALID_SEQ, true, channel); + goto error; + } + if ((service->transportOffset + (size - (channelHeader + 1))) > + (service->messageBufferSize - 3)) { + // Overflow, abort + u2f_response_error(service, ERROR_INVALID_LEN, true, channel); + goto error; + } + os_memmove(service->messageBuffer + service->transportOffset, + buffer + channelHeader + 1, size - (channelHeader + 1)); + service->transportOffset += size - (channelHeader + 1); + service->expectedContinuationPacket++; + } + // See if we can process the command + if ((media != U2F_MEDIA_USB) && + (service->transportOffset > + (service->lastCommandLength + U2F_COMMAND_HEADER_SIZE))) { + // Overflow, abort + u2f_response_error(service, ERROR_INVALID_LEN, true, channel); + goto error; + } else if (service->transportOffset >= + (service->lastCommandLength + U2F_COMMAND_HEADER_SIZE)) { + // screen_printf("Process command\n"); + service->transportState = U2F_PROCESSING_COMMAND; + service->handleFunction(service, service->messageBuffer, channel); + } else { + // screen_printf("segmented\n"); + service->seqTimeout = 0; + service->transportState = U2F_HANDLE_SEGMENTED; + service->pendingContinuation = true; + os_memmove(service->lastContinuationChannel, channel, 4); + u2f_io_close_session(); + } + return; +error: + if ((media == U2F_MEDIA_USB) && (service->pendingContinuation) && + (os_memcmp(channel, service->lastContinuationChannel, 4) == 0)) { + service->pendingContinuation = false; + } + return; +} + +void u2f_response_error(u2f_service_t *service, char errorCode, bool reset, + uint8_t *channel) { + uint8_t offset = 0; + os_memset(service->outputBuffer, 0, MAX_SEGMENT_SIZE); + if (service->transportMedia == U2F_MEDIA_USB) { + os_memmove(service->outputBuffer + offset, channel, 4); + offset += 4; + } + service->outputBuffer[offset++] = U2F_STATUS_ERROR; + service->outputBuffer[offset++] = 0x00; + service->outputBuffer[offset++] = 0x01; + service->outputBuffer[offset++] = errorCode; + u2f_send_direct_response_short(service, service->outputBuffer, offset); + if (reset) { + u2f_reset(service, true); + } +} + +bool u2f_is_channel_broadcast(uint8_t *channel) { + return (os_memcmp(channel, BROADCAST_CHANNEL, 4) == 0); +} + +bool u2f_is_channel_forbidden(uint8_t *channel) { + return (os_memcmp(channel, FORBIDDEN_CHANNEL, 4) == 0); +} + +#endif diff --git a/src/u2f/u2f_transport.h b/src/u2f/u2f_transport.h new file mode 100644 index 0000000..2d9dc5e --- /dev/null +++ b/src/u2f/u2f_transport.h @@ -0,0 +1,78 @@ +/* +******************************************************************************* +* Portable FIDO U2F implementation +* (c) 2016 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#ifndef __U2F_TRANSPORT_H__ + +#define __U2F_TRANSPORT_H__ + +#include "u2f_service.h" + +#define MAX_SEGMENT_SIZE \ + (USB_SEGMENT_SIZE > BLE_SEGMENT_SIZE ? USB_SEGMENT_SIZE : BLE_SEGMENT_SIZE) + +// Shared commands +#define U2F_CMD_PING 0x81 +#define U2F_CMD_MSG 0x83 + +// USB only commands +#define U2F_CMD_INIT 0x86 +#define U2F_CMD_LOCK 0x84 +#define U2F_CMD_WINK 0x88 + +// BLE only commands +#define U2F_CMD_KEEPALIVE 0x82 +#define KEEPALIVE_REASON_PROCESSING 0x01 +#define KEEPALIVE_REASON_TUP_NEEDED 0x02 + +#define U2F_STATUS_ERROR 0xBF + +// Shared errors +#define ERROR_NONE 0x00 +#define ERROR_INVALID_CMD 0x01 +#define ERROR_INVALID_PAR 0x02 +#define ERROR_INVALID_LEN 0x03 +#define ERROR_INVALID_SEQ 0x04 +#define ERROR_MSG_TIMEOUT 0x05 +#define ERROR_OTHER 0x7f +// USB only errors +#define ERROR_CHANNEL_BUSY 0x06 +#define ERROR_LOCK_REQUIRED 0x0a +#define ERROR_INVALID_CID 0x0b +#define ERROR_PROP_UNKNOWN_COMMAND 0x80 +#define ERROR_PROP_COMMAND_TOO_LONG 0x81 +#define ERROR_PROP_INVALID_CONTINUATION 0x82 +#define ERROR_PROP_UNEXPECTED_CONTINUATION 0x83 +#define ERROR_PROP_CONTINUATION_OVERFLOW 0x84 +#define ERROR_PROP_MESSAGE_TOO_SHORT 0x85 +#define ERROR_PROP_UNCONSISTENT_MSG_LENGTH 0x86 +#define ERROR_PROP_UNSUPPORTED_MSG_APDU 0x87 +#define ERROR_PROP_INVALID_DATA_LENGTH_APDU 0x88 +#define ERROR_PROP_INTERNAL_ERROR_APDU 0x89 +#define ERROR_PROP_INVALID_PARAMETERS_APDU 0x8A +#define ERROR_PROP_INVALID_DATA_APDU 0x8B +#define ERROR_PROP_DEVICE_NOT_SETUP 0x8C +#define ERROR_PROP_MEDIA_MIXED 0x8D + +void u2f_transport_handle(u2f_service_t *service, uint8_t *buffer, + uint16_t size, u2f_transport_media_t media); +void u2f_response_error(u2f_service_t *service, char errorCode, bool reset, + uint8_t *channel); +bool u2f_is_channel_broadcast(uint8_t *channel); +bool u2f_is_channel_forbidden(uint8_t *channel); + +#endif diff --git a/src/u2f/usbd_hid_impl.c b/src/u2f/usbd_hid_impl.c new file mode 100644 index 0000000..0f5f5d5 --- /dev/null +++ b/src/u2f/usbd_hid_impl.c @@ -0,0 +1,539 @@ +/** + ****************************************************************************** + * @file usbd_hid.c + * @author MCD Application Team + * @version V2.2.0 + * @date 13-June-2014 + * @brief This file provides the HID core functions. + * + * @verbatim + * + * =================================================================== + * HID Class Description + * =================================================================== + * This module manages the HID class V1.11 following the "Device + *Class Definition + * for Human Interface Devices (HID) Version 1.11 Jun 27, 2001". + * This driver implements the following aspects of the specification: + * - The Boot Interface Subclass + * - Usage Page : Generic Desktop + * - Usage : Vendor + * - Collection : Application + * + * @note In HS mode and when the DMA is used, all variables and data + *structures + * dealing with the DMA during the transaction process should be + *32-bit aligned. + * + * + * @endverbatim + * + ****************************************************************************** + * @attention + * + *

© COPYRIGHT 2014 STMicroelectronics

+ * + * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.st.com/software_license_agreement_liberty_v2 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ****************************************************************************** + */ +#include "os.h" + +/* Includes ------------------------------------------------------------------*/ +#include "usbd_hid.h" +#include "usbd_ctlreq.h" + +#include "usbd_core.h" +#include "usbd_conf.h" + +#include "usbd_def.h" +#include "os_io_seproxyhal.h" + +#include "u2f_service.h" +#include "u2f_transport.h" + +/** @togroup STM32_USB_DEVICE_LIBRARY + * @{ + */ + +/** @defgroup USBD_HID + * @brief usbd core module + * @{ + */ + +/** @defgroup USBD_HID_Private_TypesDefinitions + * @{ + */ +/** + * @} + */ + +/** @defgroup USBD_HID_Private_Defines + * @{ + */ + +/** + * @} + */ + +/** @defgroup USBD_HID_Private_Macros + * @{ + */ +/** + * @} + */ +/** @defgroup USBD_HID_Private_FunctionPrototypes + * @{ + */ + +/** + * @} + */ + +/** @defgroup USBD_HID_Private_Variables + * @{ + */ + +#define HID_EPIN_ADDR 0x82 +#define HID_EPIN_SIZE 0x40 + +#define HID_EPOUT_ADDR 0x02 +#define HID_EPOUT_SIZE 0x40 + +#define USBD_LANGID_STRING 0x409 + +#ifdef HAVE_VID_PID_PROBER +#define USBD_VID 0x2581 +#define USBD_PID 0xf1d1 +#else +#define USBD_VID 0x2C97 +#if defined(TARGET_BLUE) // blue +#define USBD_PID 0x0000 +const uint8_t const USBD_PRODUCT_FS_STRING[] = { + 4 * 2 + 2, USB_DESC_TYPE_STRING, 'B', 0, 'l', 0, 'u', 0, 'e', 0, +}; + +#elif defined(TARGET_NANOS) // nano s +#define USBD_PID 0x0001 +const uint8_t const USBD_PRODUCT_FS_STRING[] = { + 6 * 2 + 2, USB_DESC_TYPE_STRING, + 'N', 0, + 'a', 0, + 'n', 0, + 'o', 0, + ' ', 0, + 'S', 0, +}; +#elif defined(TARGET_ARAMIS) // aramis +#define USBD_PID 0x0002 +const uint8_t const USBD_PRODUCT_FS_STRING[] = { + 6 * 2 + 2, USB_DESC_TYPE_STRING, + 'A', 0, + 'r', 0, + 'a', 0, + 'm', 0, + 'i', 0, + 's', 0, +}; +#else +#error unknown TARGET_ID +#endif +#endif + +/* USB Standard Device Descriptor */ +const uint8_t const USBD_LangIDDesc[USB_LEN_LANGID_STR_DESC] = { + USB_LEN_LANGID_STR_DESC, USB_DESC_TYPE_STRING, LOBYTE(USBD_LANGID_STRING), + HIBYTE(USBD_LANGID_STRING), +}; + +const uint8_t const USB_SERIAL_STRING[] = { + 4 * 2 + 2, USB_DESC_TYPE_STRING, '0', 0, '0', 0, '0', 0, '1', 0, +}; + +const uint8_t const USBD_MANUFACTURER_STRING[] = { + 6 * 2 + 2, USB_DESC_TYPE_STRING, + 'L', 0, + 'e', 0, + 'd', 0, + 'g', 0, + 'e', 0, + 'r', 0, +}; + +#define USBD_INTERFACE_FS_STRING USBD_PRODUCT_FS_STRING +#define USBD_CONFIGURATION_FS_STRING USBD_PRODUCT_FS_STRING + +const uint8_t const HID_ReportDesc[] = { + 0x06, 0xD0, 0xF1, // Usage page (vendor defined) + 0x09, 0x01, // Usage ID (vendor defined) + 0xA1, 0x01, // Collection (application) + + // The Input report + 0x09, 0x03, // Usage ID - vendor defined + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8 bits) + 0x95, HID_EPIN_SIZE, // Report Count (64 fields) + 0x81, 0x08, // Input (Data, Variable, Absolute) + + // The Output report + 0x09, 0x04, // Usage ID - vendor defined + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8 bits) + 0x95, HID_EPOUT_SIZE, // Report Count (64 fields) + 0x91, 0x08, // Output (Data, Variable, Absolute) + 0xC0}; + +#define PAGE_FIDO 0xF1D0 +#define PAGE_GENERIC 0xFFA0 + +uint8_t HID_DynReportDesc[sizeof(HID_ReportDesc)]; +bool fidoActivated; + +/* USB HID device Configuration Descriptor */ +__ALIGN_BEGIN const uint8_t const USBD_HID_CfgDesc[] __ALIGN_END = { + 0x09, /* bLength: Configuration Descriptor size */ + USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */ + 0x29, + /* wTotalLength: Bytes returned */ + 0x00, 0x01, /*bNumInterfaces: 1 interface*/ + 0x01, /*bConfigurationValue: Configuration value*/ + USBD_IDX_PRODUCT_STR, /*iConfiguration: Index of string descriptor +describing +the configuration*/ + 0xC0, /*bmAttributes: bus powered */ + 0x32, /*MaxPower 100 mA: this current is used for detecting Vbus*/ + + /************** Descriptor of CUSTOM HID interface ****************/ + /* 09 */ + 0x09, /*bLength: Interface Descriptor size*/ + USB_DESC_TYPE_INTERFACE, /*bDescriptorType: Interface descriptor type*/ + 0x00, /*bInterfaceNumber: Number of Interface*/ + 0x00, /*bAlternateSetting: Alternate setting*/ + 0x02, /*bNumEndpoints*/ + 0x03, /*bInterfaceClass: HID*/ + 0x00, /*bInterfaceSubClass : 1=BOOT, 0=no boot*/ + 0x00, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/ + USBD_IDX_PRODUCT_STR, /*iInterface: Index of string descriptor*/ + /******************** Descriptor of HID *************************/ + /* 18 */ + 0x09, /*bLength: HID Descriptor size*/ + HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/ + 0x11, /*bHIDUSTOM_HID: HID Class Spec release number*/ + 0x01, 0x00, /*bCountryCode: Hardware target country*/ + 0x01, /*bNumDescriptors: Number of HID class descriptors to follow*/ + 0x22, /*bDescriptorType*/ + sizeof( + HID_DynReportDesc), /*wItemLength: Total length of Report descriptor*/ + 0x00, + /******************** Descriptor of Custom HID endpoints + ********************/ + /* 27 */ + 0x07, /*bLength: Endpoint Descriptor size*/ + USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/ + HID_EPIN_ADDR, /*bEndpointAddress: Endpoint Address (IN)*/ + 0x03, /*bmAttributes: Interrupt endpoint*/ + HID_EPIN_SIZE, /*wMaxPacketSize: 2 Byte max */ + 0x00, 0x01, /*bInterval: Polling Interval (20 ms)*/ + /* 34 */ + + 0x07, /* bLength: Endpoint Descriptor size */ + USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: */ + HID_EPOUT_ADDR, /*bEndpointAddress: Endpoint Address (OUT)*/ + 0x03, /* bmAttributes: Interrupt endpoint */ + HID_EPOUT_SIZE, /* wMaxPacketSize: 2 Bytes max */ + 0x00, 0x01, /* bInterval: Polling Interval (20 ms) */ + /* 41 */ +}; + +/* USB HID device Configuration Descriptor */ +__ALIGN_BEGIN const uint8_t const USBD_HID_Desc[] __ALIGN_END = { + /* 18 */ + 0x09, /*bLength: HID Descriptor size*/ + HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/ + 0x11, /*bHIDUSTOM_HID: HID Class Spec release number*/ + 0x01, 0x00, /*bCountryCode: Hardware target country*/ + 0x01, /*bNumDescriptors: Number of HID class descriptors to follow*/ + 0x22, /*bDescriptorType*/ + sizeof( + HID_DynReportDesc), /*wItemLength: Total length of Report descriptor*/ + 0x00, +}; + +/* USB Standard Device Descriptor */ +__ALIGN_BEGIN const uint8_t const USBD_HID_DeviceQualifierDesc[] __ALIGN_END = { + USB_LEN_DEV_QUALIFIER_DESC, + USB_DESC_TYPE_DEVICE_QUALIFIER, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x40, + 0x01, + 0x00, +}; + +/* USB Standard Device Descriptor */ +const uint8_t const USBD_DeviceDesc[USB_LEN_DEV_DESC] = { + 0x12, /* bLength */ + USB_DESC_TYPE_DEVICE, /* bDescriptorType */ + 0x00, /* bcdUSB */ + 0x02, + 0x00, /* bDeviceClass */ + 0x00, /* bDeviceSubClass */ + 0x00, /* bDeviceProtocol */ + USB_MAX_EP0_SIZE, /* bMaxPacketSize */ + LOBYTE(USBD_VID), /* idVendor */ + HIBYTE(USBD_VID), /* idVendor */ + LOBYTE(USBD_PID), /* idVendor */ + HIBYTE(USBD_PID), /* idVendor */ + 0x00, /* bcdDevice rel. 2.00 */ + 0x02, + USBD_IDX_MFC_STR, /* Index of manufacturer string */ + USBD_IDX_PRODUCT_STR, /* Index of product string */ + USBD_IDX_SERIAL_STR, /* Index of serial number string */ + USBD_MAX_NUM_CONFIGURATION /* bNumConfigurations */ +}; /* USB_DeviceDescriptor */ + +/** + * @brief Returns the device descriptor. + * @param speed: Current device speed + * @param length: Pointer to data length variable + * @retval Pointer to descriptor buffer + */ +uint8_t *USBD_HID_DeviceDescriptor(USBD_SpeedTypeDef speed, uint16_t *length) { + UNUSED(speed); + *length = sizeof(USBD_DeviceDesc); + return (uint8_t *)USBD_DeviceDesc; +} + +/** + * @brief Returns the LangID string descriptor. + * @param speed: Current device speed + * @param length: Pointer to data length variable + * @retval Pointer to descriptor buffer + */ +uint8_t *USBD_HID_LangIDStrDescriptor(USBD_SpeedTypeDef speed, + uint16_t *length) { + UNUSED(speed); + *length = sizeof(USBD_LangIDDesc); + return (uint8_t *)USBD_LangIDDesc; +} + +/** + * @brief Returns the product string descriptor. + * @param speed: Current device speed + * @param length: Pointer to data length variable + * @retval Pointer to descriptor buffer + */ +uint8_t *USBD_HID_ProductStrDescriptor(USBD_SpeedTypeDef speed, + uint16_t *length) { + UNUSED(speed); + *length = sizeof(USBD_PRODUCT_FS_STRING); + return (uint8_t *)USBD_PRODUCT_FS_STRING; +} + +/** + * @brief Returns the manufacturer string descriptor. + * @param speed: Current device speed + * @param length: Pointer to data length variable + * @retval Pointer to descriptor buffer + */ +uint8_t *USBD_HID_ManufacturerStrDescriptor(USBD_SpeedTypeDef speed, + uint16_t *length) { + UNUSED(speed); + *length = sizeof(USBD_MANUFACTURER_STRING); + return (uint8_t *)USBD_MANUFACTURER_STRING; +} + +/** + * @brief Returns the serial number string descriptor. + * @param speed: Current device speed + * @param length: Pointer to data length variable + * @retval Pointer to descriptor buffer + */ +uint8_t *USBD_HID_SerialStrDescriptor(USBD_SpeedTypeDef speed, + uint16_t *length) { + UNUSED(speed); + *length = sizeof(USB_SERIAL_STRING); + return (uint8_t *)USB_SERIAL_STRING; +} + +/** + * @brief Returns the configuration string descriptor. + * @param speed: Current device speed + * @param length: Pointer to data length variable + * @retval Pointer to descriptor buffer + */ +uint8_t *USBD_HID_ConfigStrDescriptor(USBD_SpeedTypeDef speed, + uint16_t *length) { + UNUSED(speed); + *length = sizeof(USBD_CONFIGURATION_FS_STRING); + return (uint8_t *)USBD_CONFIGURATION_FS_STRING; +} + +/** + * @brief Returns the interface string descriptor. + * @param speed: Current device speed + * @param length: Pointer to data length variable + * @retval Pointer to descriptor buffer + */ +uint8_t *USBD_HID_InterfaceStrDescriptor(USBD_SpeedTypeDef speed, + uint16_t *length) { + UNUSED(speed); + *length = sizeof(USBD_INTERFACE_FS_STRING); + return (uint8_t *)USBD_INTERFACE_FS_STRING; +} + +/** +* @brief DeviceQualifierDescriptor +* return Device Qualifier descriptor +* @param length : pointer data length +* @retval pointer to descriptor buffer +*/ +uint8_t *USBD_HID_GetDeviceQualifierDesc_impl(uint16_t *length) { + *length = sizeof(USBD_HID_DeviceQualifierDesc); + return (uint8_t *)USBD_HID_DeviceQualifierDesc; +} + +/** + * @brief USBD_CUSTOM_HID_GetCfgDesc + * return configuration descriptor + * @param speed : current device speed + * @param length : pointer data length + * @retval pointer to descriptor buffer + */ +uint8_t *USBD_HID_GetCfgDesc_impl(uint16_t *length) { + *length = sizeof(USBD_HID_CfgDesc); + return (uint8_t *)USBD_HID_CfgDesc; +} + +uint8_t *USBD_HID_GetHidDescriptor_impl(uint16_t *len) { + *len = sizeof(USBD_HID_Desc); + return (uint8_t *)USBD_HID_Desc; +} + +uint8_t *USBD_HID_GetReportDescriptor_impl(uint16_t *len) { + *len = sizeof(HID_DynReportDesc); + return (uint8_t *)HID_DynReportDesc; +} + +/** + * @} + */ + +/** + * @brief USBD_HID_DataOut + * handle data OUT Stage + * @param pdev: device instance + * @param epnum: endpoint index + * @retval status + * + * This function is the default behavior for our implementation when data are + * sent over the out hid endpoint + */ +extern volatile unsigned short G_io_apdu_length; + +uint8_t USBD_HID_DataOut_impl(USBD_HandleTypeDef *pdev, uint8_t epnum, + uint8_t *buffer) { + UNUSED(epnum); + + // prepare receiving the next chunk (masked time) + USBD_LL_PrepareReceive(pdev, HID_EPOUT_ADDR, HID_EPOUT_SIZE); + + if (fidoActivated) { +#ifdef HAVE_U2F + u2f_transport_handle(&u2fService, buffer, + io_seproxyhal_get_ep_rx_size(HID_EPOUT_ADDR), + U2F_MEDIA_USB); +#endif + } else { + // add to the hid transport + switch ( + io_usb_hid_receive(io_usb_send_apdu_data, buffer, + io_seproxyhal_get_ep_rx_size(HID_EPOUT_ADDR))) { + default: + break; + + case IO_USB_APDU_RECEIVED: + G_io_apdu_media = IO_APDU_MEDIA_USB_HID; // for application code + G_io_apdu_state = APDU_USB_HID; // for next call to io_exchange + G_io_apdu_length = G_io_usb_hid_total_length; + break; + } + } + + return USBD_OK; +} + +/** @defgroup USBD_HID_Private_Functions + * @{ + */ + +// note: how core lib usb calls the hid class +static const USBD_DescriptorsTypeDef const HID_Desc = { + USBD_HID_DeviceDescriptor, USBD_HID_LangIDStrDescriptor, + USBD_HID_ManufacturerStrDescriptor, USBD_HID_ProductStrDescriptor, + USBD_HID_SerialStrDescriptor, USBD_HID_ConfigStrDescriptor, + USBD_HID_InterfaceStrDescriptor, NULL, +}; + +static const USBD_ClassTypeDef const USBD_HID = { + USBD_HID_Init, USBD_HID_DeInit, USBD_HID_Setup, NULL, /*EP0_TxSent*/ + NULL, /*EP0_RxReady*/ /* STATUS STAGE IN */ + NULL, /*DataIn*/ + USBD_HID_DataOut_impl, /*DataOut*/ + NULL, /*SOF */ + NULL, NULL, USBD_HID_GetCfgDesc_impl, USBD_HID_GetCfgDesc_impl, + USBD_HID_GetCfgDesc_impl, USBD_HID_GetDeviceQualifierDesc_impl, +}; + +void USB_power_U2F(unsigned char enabled, unsigned char fido) { + uint16_t page = (fido ? PAGE_FIDO : PAGE_GENERIC); + os_memmove(HID_DynReportDesc, HID_ReportDesc, sizeof(HID_ReportDesc)); + HID_DynReportDesc[1] = (page & 0xff); + HID_DynReportDesc[2] = ((page >> 8) & 0xff); + fidoActivated = (fido ? true : false); + + os_memset(&USBD_Device, 0, sizeof(USBD_Device)); + + if (enabled) { + os_memset(&USBD_Device, 0, sizeof(USBD_Device)); + /* Init Device Library */ + USBD_Init(&USBD_Device, (USBD_DescriptorsTypeDef *)&HID_Desc, 0); + + /* Register the HID class */ + USBD_RegisterClass(&USBD_Device, (USBD_ClassTypeDef *)&USBD_HID); + + /* Start Device Process */ + USBD_Start(&USBD_Device); + } else { + USBD_DeInit(&USBD_Device); + } +} + +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/src/u2f/usbd_hid_impl.h b/src/u2f/usbd_hid_impl.h new file mode 100644 index 0000000..f586a47 --- /dev/null +++ b/src/u2f/usbd_hid_impl.h @@ -0,0 +1,10 @@ +#ifndef USBD_HID_IMPL_H +#define USBD_HID_IMPL_H + +#define HID_EPIN_ADDR 0x82 +#define HID_EPIN_SIZE 0x40 + +#define HID_EPOUT_ADDR 0x02 +#define HID_EPOUT_SIZE 0x40 + +#endif // USBD_HID_IMPL_H diff --git a/src/ui.c b/src/ui.c new file mode 100644 index 0000000..d559d2b --- /dev/null +++ b/src/ui.c @@ -0,0 +1,353 @@ + +/******************************************************************************* +* Burstcoin Wallet App for Nano Ledger S. +* Copyright (c) 2017-2018 Jake B. +* +* Based on Sample code provided and (c) 2016 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#import "ui.h" +#include +#include "glyphs.h" +#include "main.h" +#include "crypto/rs_address.h" + +ux_state_t ux; + +// UI currently displayed +enum UI_STATE uiState; + +//ux_state_t ux; +//unsigned int current_text_pos; // parsing cursor in the text to display +int ux_step, ux_step_count; +//unsigned int text_y; // current location of the displayed text + + +void print_amount(uint64_t amount, char *out, uint8_t len); + +#ifdef HAVE_U2F + +// change the setting +void menu_settings_browser_change(unsigned int enabled) { + uint8_t fidoTransport = enabled; + nvm_write(&N_storage.fidoTransport, (void *)&fidoTransport, sizeof(uint8_t)); + USB_power_U2F(0, 0); + USB_power_U2F(1, N_storage.fidoTransport); + // go back to the menu entry + UX_MENU_DISPLAY(1, menu_settings, NULL); +} + +// show the currently activated entry +void menu_settings_browser_init(unsigned int ignored) { + UNUSED(ignored); + UX_MENU_DISPLAY(N_storage.fidoTransport ? 1 : 0, menu_settings_browser, NULL); +} + +#endif + +#ifdef HAVE_U2F +const ux_menu_entry_t menu_settings_browser[] = { + {NULL, menu_settings_browser_change, 0, NULL, "No", NULL, 0, 0}, + {NULL, menu_settings_browser_change, 1, NULL, "Yes", NULL, 0, 0}, + UX_MENU_END}; + +const ux_menu_entry_t menu_settings[] = { + {NULL, menu_settings_browser_init, 0, NULL, "Browser support", NULL, 0, 0}, + {menu_main, NULL, 1, &C_icon_back, "Back", NULL, 61, 40}, + UX_MENU_END}; +#endif + +const ux_menu_entry_t menu_about[] = { + {NULL, NULL, 0, NULL, "Version", APPVERSION, 0, 0}, + {menu_main, NULL, 2, &C_icon_back, "Back", NULL, 61, 40}, + UX_MENU_END}; + +const ux_menu_entry_t menu_main[] = { + {NULL, NULL, 0, &C_icon_burst, "Use wallet to", "view accounts", 33, 12}, +#ifdef HAVE_U2F + {menu_settings, NULL, 0, NULL, "Settings", NULL, 0, 0}, +#endif + {menu_about, NULL, 0, NULL, "About", NULL, 0, 0}, + {NULL, os_sched_exit, 0, &C_icon_dashboard, "Quit app", NULL, 50, 29}, + UX_MENU_END}; + + +const bagl_element_t ui_verify_nanos[] = { + // type userid x y w h str rad + // fill fg bg fid iid txt touchparams... ] + {{BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, + 0, 0}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + + {{BAGL_ICON, 0x00, 3, 12, 7, 7, 0, 0, 0, 0xFFFFFF, 0x000000, 0, + BAGL_GLYPH_ICON_CROSS}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + {{BAGL_ICON, 0x00, 117, 13, 8, 6, 0, 0, 0, 0xFFFFFF, 0x000000, 0, + BAGL_GLYPH_ICON_CHECK}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + {{BAGL_LABELINE, 0x01, 0, 12, 128, 12, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + "Confirm", + 0, + 0, + 0, + NULL, + NULL, + NULL}, + {{BAGL_LABELINE, 0x01, 0, 26, 128, 12, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + "transaction", + 0, + 0, + 0, + NULL, + NULL, + NULL}, + + {{BAGL_LABELINE, 0x02, 0, 12, 128, 12, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_REGULAR_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + "Amount", + 0, + 0, + 0, + NULL, + NULL, + NULL}, + {{BAGL_LABELINE, 0x02, 23, 26, 82, 12, 0x80 | 10, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 26}, + tmpCtx.transactionContext.fullAmount, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + + {{BAGL_LABELINE, 0x03, 0, 12, 128, 12, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_REGULAR_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + "Address", + 0, + 0, + 0, + NULL, + NULL, + NULL}, + {{BAGL_LABELINE, 0x03, 23, 26, 82, 12, 0x80 | 10, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 26}, + tmpCtx.transactionContext.fullAddress, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + + {{BAGL_LABELINE, 0x04, 0, 12, 128, 12, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_REGULAR_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + "Fees", + 0, + 0, + 0, + NULL, + NULL, + NULL}, + {{BAGL_LABELINE, 0x04, 23, 26, 82, 12, 0x80 | 10, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 26}, + tmpCtx.transactionContext.feesAmount, + 0, + 0, + 0, + NULL, + NULL, + NULL}, +}; + +static const bagl_element_t* +io_seproxyhal_touch_approve(const bagl_element_t *e) { + + unsigned int tx = 0; + unsigned short sw = SW_OK; + + bool more = handleSigning(&tx, NULL); + + G_io_apdu_buffer[tx++] = sw >> 8; + G_io_apdu_buffer[tx++] = sw; + + +#if HAVE_U2F + if (fidoActivated) { + u2f_proxy_response((u2f_service_t *)&u2fService, tx); + } else { +#endif + // Send back the response, do not restart the event loop + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, tx); +#if HAVE_U2F + } // close fidoActivated if statement +#endif + + //if (more) { + // ui_signing(); + //} else { + ui_idle(); + //} + + return 0; // do not redraw the widget +} + +static const bagl_element_t *io_seproxyhal_touch_deny(const bagl_element_t *e) { + hashCount = 0; + + G_io_apdu_buffer[0] = 0x69; + G_io_apdu_buffer[1] = 0x85; + +#ifdef HAVE_U2F + if (fidoActivated) { + u2f_proxy_response((u2f_service_t *)&u2fService, 2); + } else { +#endif + // Send back the response, do not restart the event loop + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); +#ifdef HAVE_U2F + } +#endif + + // Display back the original UX + ui_idle(); + + return 0; // do not redraw the widget +} + +unsigned int ui_verify_nanos_button(unsigned int button_mask, + unsigned int button_mask_counter) { + switch (button_mask) { + case BUTTON_EVT_RELEASED | BUTTON_LEFT: + io_seproxyhal_touch_deny(NULL); + break; + + case BUTTON_EVT_RELEASED | BUTTON_RIGHT: + io_seproxyhal_touch_approve(NULL); + break; + } + return 0; +} + +// display or not according to step, and adjust delay +const bagl_element_t * ui_verify_prepro(const bagl_element_t *element) { + if (element->component.userid > 0) { + unsigned int display = (ux_step == element->component.userid - 1); + if (display) { + switch (element->component.userid) { + case 1: + UX_CALLBACK_SET_INTERVAL(2000); + break; + case 2: + case 3: + case 4: + UX_CALLBACK_SET_INTERVAL(MAX( + 3000, 1000 + bagl_label_roundtrip_duration_ms(element, 7))); + break; + } + } + if (!display) + return NULL; + } + return element; +} + +// Idle state, sow the menu +void ui_idle(void) { + ux_step = 0; ux_step_count = 0; + uiState = UI_IDLE; + UX_MENU_DISPLAY(0, menu_main, NULL); +} + +// Show the transaction details for the user to approve +void ui_verify(void) { + + // Recipient + uint64_t recipient; + os_memmove(&recipient, G_io_apdu_buffer + 5 + 40, 8); + addressFromAccountNumber(tmpCtx.transactionContext.fullAddress, recipient, true); + uint64_t amount, fee; + os_memmove(&amount, G_io_apdu_buffer + 5 + 48, 8); + os_memmove(&fee, G_io_apdu_buffer + 5 + 56, 8); + + print_amount(amount+fee, (char*)tmpCtx.transactionContext.fullAmount, + sizeof(tmpCtx.transactionContext.fullAmount)); + print_amount(fee, (char*)tmpCtx.transactionContext.feesAmount, + sizeof(tmpCtx.transactionContext.feesAmount)); + + // Set the step/step count, and uiState before requesting the UI + ux_step = 0; ux_step_count = 4; + uiState = UI_VERIFY; + UX_DISPLAY(ui_verify_nanos, ui_verify_prepro); +} + + +// borrowed from the Stellar wallet code, slgithly modified for BURST +void print_amount(uint64_t amount, char *out, uint8_t len) { + char buffer[len]; + uint64_t dVal = amount; + int i, j; + + memset(buffer, 0, len); + for (i = 0; dVal > 0 || i < 9; i++) { + if (dVal > 0) { + buffer[i] = (dVal % 10) + '0'; + dVal /= 10; + } else { + buffer[i] = '0'; + } + if (i == 7) { // 1 BURST = 100000000 quants + i += 1; + buffer[i] = '.'; + } + if (i >= len) { + THROW(0x6700); + } + } + // reverse order + for (i -= 1, j = 0; i >= 0 && j < len-1; i--, j++) { + out[j] = buffer[i]; + } + // strip trailing 0s + for (j -= 1; j > 0; j--) { + if (out[j] != '0') break; + } + j += 2; + + // strip trailing . + //if (out[j-1] == '.') j -= 1; + + out[j] = '\0'; +} \ No newline at end of file diff --git a/src/ui.h b/src/ui.h new file mode 100644 index 0000000..c17a18e --- /dev/null +++ b/src/ui.h @@ -0,0 +1,62 @@ + +/******************************************************************************* +* Burstcoin Wallet App for Nano Ledger S. +* Copyright (c) 2017-2018 Jake B. +* +* Based on Sample code provided and (c) 2016 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#ifndef __UI_H__ +#define __UI_H__ + +#include "os.h" +#include "cx.h" +#include +#include "os_io_seproxyhal.h" +#include "main.h" + +extern ux_state_t ux; + +enum UI_STATE { UI_IDLE, UI_VERIFY }; +#define UI_RECIPIENT 0 +#define UI_AMOUNT 1 +#define UI_FEE 2 +#define UI_TRANSACTION_PART_MAX 3; + +extern enum UI_STATE uiState; + +// extern unsigned int current_text_pos; // parsing cursor in the text to display +// extern unsigned int text_y; // current location of the displayed text +// extern char lineBuffer[50]; + +void ui_verify(void); +void ui_idle(void); + +extern int ux_step; +extern int ux_step_count; + +extern const ux_menu_entry_t menu_main[]; +extern const ux_menu_entry_t menu_settings[]; +extern const ux_menu_entry_t menu_settings_browser[]; + + +#define MAX_CHARS_PER_LINE 49 +#define DEFAULT_FONT BAGL_FONT_OPEN_SANS_LIGHT_16px | BAGL_FONT_ALIGNMENT_LEFT +#define TEXT_HEIGHT 15 +#define TEXT_SPACE 4 + + +#define QUANTS_PER_BURST 100000000 +#endif \ No newline at end of file