From dcbd91b14647747aae2552eb73ab84b2fd8105a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Palmer?= Date: Thu, 10 Oct 2024 11:04:42 +0200 Subject: [PATCH] [crypto] add cryptography for bls --- Makefile | 5 +- src/crypto.c | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/crypto.h | 74 +++++++++++++++++++++++ 3 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 src/crypto.c create mode 100644 src/crypto.h diff --git a/Makefile b/Makefile index 8197c4db..4f1ab2eb 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ ENABLE_NBGL_QRCODE = 1 HAVE_APPLICATION_FLAG_GLOBAL_PIN = 1 -CURVE_APP_LOAD_PARAMS = ed25519 secp256k1 secp256r1 +CURVE_APP_LOAD_PARAMS = ed25519 secp256k1 secp256r1 bls12381g1 PATH_APP_LOAD_PARAMS = "44'/1729'" # VERSION @@ -35,7 +35,8 @@ GIT_DESCRIBE ?= $(shell git describe --tags --abbrev=8 --always --long --dirty 2 VERSION_TAG ?= $(shell echo "$(GIT_DESCRIBE)" | cut -f1 -d-) COMMIT ?= $(shell echo "$(GIT_DESCRIBE)" | awk -F'-g' '{print $2}' | sed 's/-dirty/*/') -DEFINES += COMMIT=\"$(COMMIT)\" +DEFINES += COMMIT=\"$(COMMIT)\" +DEFINES += HAVE_BLS # Only warn about version tags if specified/inferred ifeq ($(VERSION_TAG),) diff --git a/src/crypto.c b/src/crypto.c new file mode 100644 index 00000000..d181ef37 --- /dev/null +++ b/src/crypto.c @@ -0,0 +1,164 @@ +/* Tezos Ledger application - Cryptography operations + + Copyright 2024 Functori + Copyright 2023 Ledger SAS + + 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 // uint*_t +#include // memset, explicit_bzero +#include // bool + +#include "cx.h" +#include "os.h" +#include "os_io_seproxyhal.h" + +/** + * @brief Gets the bls private key from the device seed using the specified bip32 path + * key. + * + * @param[in] path Bip32 path to use for derivation. + * + * @param[in] path_len Bip32 path length. + * + * @param[out] privkey Generated private key. + * + * @return Error code: + * - CX_OK on success + * - CX_EC_INVALID_CURVE + * - CX_INTERNAL_ERROR + */ +static WARN_UNUSED_RESULT cx_err_t +bip32_derive_init_privkey_bls(const uint32_t *path, + size_t path_len, + cx_ecfp_384_private_key_t *privkey) { + cx_err_t error = CX_OK; + // Allocate 64 bytes to respect Syscall API but only 32 will be used + uint8_t raw_privkey[64] = {0}; + cx_curve_t curve = CX_CURVE_BLS12_381_G1; + size_t length = 48; + + // Derive private key according to the path + io_seproxyhal_io_heartbeat(); + CX_CHECK(os_derive_eip2333_no_throw(curve, path, path_len, raw_privkey + 16)); + io_seproxyhal_io_heartbeat(); + + // Init privkey from raw + CX_CHECK(cx_ecfp_init_private_key_no_throw(curve, + raw_privkey, + length, + (cx_ecfp_private_key_t *) privkey)); + +end: + explicit_bzero(raw_privkey, sizeof(raw_privkey)); + + if (error != CX_OK) { + // Make sure the caller doesn't use uninitialized data in case + // the return code is not checked. + explicit_bzero(privkey, sizeof(cx_ecfp_384_private_key_t)); + } + return error; +} + +WARN_UNUSED_RESULT cx_err_t bip32_derive_get_pubkey_bls(const uint32_t *path, + size_t path_len, + uint8_t raw_pubkey[static 97]) { + cx_err_t error = CX_OK; + cx_curve_t curve = CX_CURVE_BLS12_381_G1; + + uint8_t yFlag = 0; + uint8_t tmp[96] = {0}; + uint8_t bls_field[48] = {0}; + int diff = 0; + + cx_ecfp_384_private_key_t privkey = {0}; + cx_ecfp_384_public_key_t pubkey = {0}; + + // Derive private key according to BIP32 path + CX_CHECK(bip32_derive_init_privkey_bls(path, path_len, &privkey)); + + // Generate associated pubkey + CX_CHECK(cx_ecfp_generate_pair_no_throw(curve, + (cx_ecfp_public_key_t *) &pubkey, + (cx_ecfp_private_key_t *) &privkey, + true)); + + tmp[47] = 2; + CX_CHECK(cx_math_mult_no_throw(tmp, pubkey.W + 1 + 48, tmp, 48)); + CX_CHECK(cx_ecdomain_parameter(curve, CX_CURVE_PARAM_Field, bls_field, sizeof(bls_field))); + CX_CHECK(cx_math_cmp_no_throw(tmp + 48, bls_field, 48, &diff)); + if (diff > 0) { + yFlag = 0x20; + } + pubkey.W[1] &= 0x1f; + pubkey.W[1] |= 0x80 | yFlag; + + // Check pubkey length then copy it to raw_pubkey + if (pubkey.W_len != 97) { + error = CX_EC_INVALID_CURVE; + goto end; + } + memmove(raw_pubkey, pubkey.W, pubkey.W_len); + +end: + explicit_bzero(&privkey, sizeof(privkey)); + + if (error != CX_OK) { + // Make sure the caller doesn't use uninitialized data in case + // the return code is not checked. + explicit_bzero(raw_pubkey, 97); + } + return error; +} + +#ifndef TARGET_NANOS +WARN_UNUSED_RESULT cx_err_t bip32_derive_with_seed_bls_sign_hash(const uint32_t *path, + size_t path_len, + uint8_t const *msg, + uint8_t msg_len, + uint8_t *sig, + size_t *sig_len) { + cx_err_t error = CX_OK; + // cipher suite used in tezos: + // https://gitlab.com/tezos/tezos/-/blob/master/src/lib_bls12_381_signature/bls12_381_signature.ml?ref_type=heads#L351 + const uint8_t ciphersuite[] = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_AUG_"; + cx_ecfp_384_private_key_t privkey; + uint8_t hash[192]; + + if ((sig_len == NULL) || (*sig_len < 96)) { + error = CX_INVALID_PARAMETER_VALUE; + goto end; + } + + // Derive private key according to BIP32 path + CX_CHECK(bip32_derive_init_privkey_bls(path, path_len, &privkey)); + + CX_CHECK( + cx_hash_to_field(msg, msg_len, ciphersuite, sizeof(ciphersuite) - 1, hash, sizeof(hash))); + + CX_CHECK(ox_bls12381_sign(&privkey, hash, sizeof(hash), sig, 96)); + *sig_len = 96; + +end: + explicit_bzero(&privkey, sizeof(privkey)); + + if (error != CX_OK) { + // Make sure the caller doesn't use uninitialized data in case + // the return code is not checked. + explicit_bzero(sig, 96); + } + return error; +} +#endif diff --git a/src/crypto.h b/src/crypto.h new file mode 100644 index 00000000..d73ef82f --- /dev/null +++ b/src/crypto.h @@ -0,0 +1,74 @@ +/* Tezos Ledger application - Cryptography operations + + Copyright 2024 Functori + Copyright 2023 Ledger SAS + + 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. + +*/ + +#pragma once + +#include // uint*_t + +#include "cx.h" + +/** + * @brief Gets the bls public key from the device seed using the specified bip32 path + * key. + * + * @param[in] path Bip32 path to use for derivation. + * + * @param[in] path_len Bip32 path length. + * + * @param[out] raw_pubkey Buffer where to store the public key. + * + * @return Error code: + * - CX_OK on success + * - CX_EC_INVALID_CURVE + * - CX_INTERNAL_ERROR + */ +WARN_UNUSED_RESULT cx_err_t bip32_derive_get_pubkey_bls(const uint32_t *path, + size_t path_len, + uint8_t raw_pubkey[static 97]); + +#ifndef TARGET_NANOS +/** + * @brief Sign a hash with bls using the device seed derived from the specified bip32 path. + * + * @param[in] path Bip32 path to use for derivation. + * + * @param[in] path_len Bip32 path length. + * + * @param[in] msg Digest of the message to be signed. + * The length of *message* must be shorter than the group order size. + * Otherwise it is truncated. + * + * @param[in] msg_len Length of the digest in octets. + * + * @param[out] sig Buffer where to store the signature. + * + * @param[in] sig_len Length of the signature buffer, updated with signature length. + * + * @return Error code: + * - CX_OK on success + * - CX_EC_INVALID_CURVE + * - CX_INTERNAL_ERROR + */ +WARN_UNUSED_RESULT cx_err_t bip32_derive_with_seed_bls_sign_hash(const uint32_t *path, + size_t path_len, + uint8_t const *msg, + uint8_t msg_len, + uint8_t *sig, + size_t *sig_len); +#endif