ledger-app-monero/src/monero_crypto.c

1404 lines
48 KiB
C

/*****************************************************************************
* Ledger Monero App.
* (c) 2017-2020 Cedric Mesnil <cslashm@gmail.com>, Ledger SAS.
* (c) 2020 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 "os.h"
#include "cx.h"
#include "monero_types.h"
#include "monero_api.h"
#include "monero_vars.h"
#define PXY_SIZE 65
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
static unsigned char const WIDE C_ED25519_G[] = {
// uncompressed
0x04,
// x
0x21, 0x69, 0x36, 0xd3, 0xcd, 0x6e, 0x53, 0xfe, 0xc0, 0xa4, 0xe2, 0x31, 0xfd, 0xd6, 0xdc, 0x5c,
0x69, 0x2c, 0xc7, 0x60, 0x95, 0x25, 0xa7, 0xb2, 0xc9, 0x56, 0x2d, 0x60, 0x8f, 0x25, 0xd5, 0x1a,
// y
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x58};
static unsigned char const WIDE C_ED25519_Hy[] = {
0x8b, 0x65, 0x59, 0x70, 0x15, 0x37, 0x99, 0xaf, 0x2a, 0xea, 0xdc, 0x9f, 0xf1, 0xad, 0xd0, 0xea,
0x6c, 0x72, 0x51, 0xd5, 0x41, 0x54, 0xcf, 0xa9, 0x2c, 0x17, 0x3a, 0x0d, 0xd3, 0x9c, 0x1f, 0x94};
unsigned char const C_ED25519_ORDER[32] = {
// l: 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x14, 0xDE, 0xF9, 0xDE, 0xA2, 0xF7, 0x9C, 0xD6, 0x58, 0x12, 0x63, 0x1A, 0x5C, 0xF5, 0xD3, 0xED};
unsigned char const C_ED25519_FIELD[32] = {
// q: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed
0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xed};
unsigned char const C_EIGHT[32] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08};
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_aes_derive(cx_aes_key_t *sk, unsigned char *seed32, unsigned char *a, unsigned char *b) {
unsigned char h1[KEY_SIZE];
int error;
error = monero_keccak_init_H();
if (error) {
return error;
}
error = monero_keccak_update_H(seed32, KEY_SIZE);
if (error) {
return error;
}
error = monero_keccak_update_H(a, KEY_SIZE);
if (error) {
return error;
}
error = monero_keccak_update_H(b, KEY_SIZE);
if (error) {
return error;
}
error = monero_keccak_final_H(h1);
if (error) {
return error;
}
error = monero_keccak_H(h1, KEY_SIZE, h1);
if (error) {
return error;
}
error = cx_aes_init_key_no_throw(h1, 16, sk);
if (error) {
return SW_SECURITY_INTERNAL;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- assert: max_len>0 --- */
/* ----------------------------------------------------------------------- */
unsigned int monero_encode_varint(unsigned char *varint, unsigned int max_len, uint64_t value,
unsigned int *out_len) {
if (!varint || !out_len) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA;
}
*out_len = 0;
while (value >= 0x80) {
if (*out_len == (max_len - 1)) {
return SW_WRONG_DATA_RANGE;
}
varint[*out_len] = (value & 0x7F) | 0x80;
value = value >> 7;
*out_len = *out_len + 1;
}
varint[*out_len] = value;
*out_len = *out_len + 1;
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- assert: max_len>0 --- */
/* ----------------------------------------------------------------------- */
unsigned int monero_decode_varint(const unsigned char *varint, size_t max_len, uint64_t *value,
unsigned int *out_len) {
uint64_t v;
size_t len;
v = 0;
len = 0;
if (!varint || !out_len) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA;
}
while ((varint[len]) & 0x80) {
if (len == (max_len - 1)) {
return SW_WRONG_DATA_RANGE;
}
v = v + ((uint64_t)((varint[len]) & 0x7f) << (len * 7));
len++;
}
v = v + ((uint64_t)((varint[len]) & 0x7f) << (len * 7));
*value = v;
*out_len = len + 1;
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_reverse32(unsigned char *rscal, unsigned char *scal, size_t rscal_len, size_t scal_len) {
unsigned char x;
unsigned int i;
if (!rscal || !scal) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA;
}
if (rscal_len < 32 || scal_len < 32) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA_RANGE;
}
for (i = 0; i < 16; i++) {
x = scal[i];
rscal[i] = scal[31 - i];
rscal[31 - i] = x;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
void monero_hash_init_sha256(cx_hash_t *hasher) {
cx_sha256_init((cx_sha256_t *)hasher);
}
int monero_hash_init_keccak(cx_hash_t *hasher) {
int error = cx_keccak_init_no_throw((cx_sha3_t *)hasher, 256);
if (error) {
return SW_SECURITY_INTERNAL;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_hash_update(cx_hash_t *hasher, const unsigned char *buf, unsigned int len) {
int error = cx_hash_no_throw(hasher, 0, buf, len, NULL, 0);
if (error) {
return SW_SECURITY_INTERNAL;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_hash_final(cx_hash_t *hasher, unsigned char *out) {
int error = cx_hash_no_throw(hasher, CX_LAST, NULL, 0, out, 32);
if (error) {
return SW_SECURITY_INTERNAL;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_hash(unsigned int algo, cx_hash_t *hasher, const unsigned char *buf, unsigned int len,
unsigned char *out) {
int err = 0;
if (algo == CX_SHA256) {
cx_sha256_init((cx_sha256_t *)hasher);
} else {
err = cx_keccak_init_no_throw((cx_sha3_t *)hasher, 256);
if (err) {
return SW_SECURITY_INTERNAL;
}
}
err = cx_hash_no_throw(hasher, CX_LAST, buf, len, out, 32);
if (err) {
return SW_SECURITY_INTERNAL;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
/* thanks to knaccc and moneromoo help on IRC #monero-research-lab */
/* From Monero code
*
* fe_sq2(v, u); // 2 * u^2
* fe_1(w);
* fe_add(w, v, w); // w = 2 * u^2 + 1
* fe_sq(x, w); // w^2
* fe_mul(y, fe_ma2, v); // -2 * A^2 * u^2
* fe_add(x, x, y); // x = w^2 - 2 * A^2 * u^2
* fe_divpowm1(r->X, w, x); // (w / x)^(m + 1)
* fe_sq(y, r->X);
* fe_mul(x, y, x);
* fe_sub(y, w, x);
* fe_copy(z, fe_ma);
* if (fe_isnonzero(y)) {
* fe_add(y, w, x);
* if (fe_isnonzero(y)) {
* goto negative;
* } else {
* fe_mul(r->X, r->X, fe_fffb1);
* }
* } else {
* fe_mul(r->X, r->X, fe_fffb2);
* }
* fe_mul(r->X, r->X, u); // u * sqrt(2 * A * (A + 2) * w / x)
* fe_mul(z, z, v); // -2 * A * u^2
* sign = 0;
* goto setsign;
*negative:
* fe_mul(x, x, fe_sqrtm1);
* fe_sub(y, w, x);
* if (fe_isnonzero(y)) {
* assert((fe_add(y, w, x), !fe_isnonzero(y)));
* fe_mul(r->X, r->X, fe_fffb3);
* } else {
* fe_mul(r->X, r->X, fe_fffb4);
* }
* // r->X = sqrt(A * (A + 2) * w / x)
* // z = -A
* sign = 1;
*setsign:
* if (fe_isnegative(r->X) != sign) {
* assert(fe_isnonzero(r->X));
* fe_neg(r->X, r->X);
* }
* fe_add(r->Z, z, w);
* fe_sub(r->Y, z, w);
* fe_mul(r->X, r->X, r->Z);
*/
// A = 486662
const unsigned char C_fe_ma2[] = {
/* -A^2
* 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffc8db3de3c9
*/
0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc8, 0xdb, 0x3d, 0xe3, 0xc9};
const unsigned char C_fe_ma[] = {
/* -A
* 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff892e7
*/
0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x92, 0xe7};
const unsigned char C_fe_fffb1[] = {
/* sqrt(-2 * A * (A + 2))
* 0x7e71fbefdad61b1720a9c53741fb19e3d19404a8b92a738d22a76975321c41ee
*/
0x7e, 0x71, 0xfb, 0xef, 0xda, 0xd6, 0x1b, 0x17, 0x20, 0xa9, 0xc5, 0x37, 0x41, 0xfb, 0x19, 0xe3,
0xd1, 0x94, 0x04, 0xa8, 0xb9, 0x2a, 0x73, 0x8d, 0x22, 0xa7, 0x69, 0x75, 0x32, 0x1c, 0x41, 0xee};
const unsigned char C_fe_fffb2[] = {
/* sqrt(2 * A * (A + 2))
* 0x4d061e0a045a2cf691d451b7c0165fbe51de03460456f7dfd2de6483607c9ae0
*/
0x4d, 0x06, 0x1e, 0x0a, 0x04, 0x5a, 0x2c, 0xf6, 0x91, 0xd4, 0x51, 0xb7, 0xc0, 0x16, 0x5f, 0xbe,
0x51, 0xde, 0x03, 0x46, 0x04, 0x56, 0xf7, 0xdf, 0xd2, 0xde, 0x64, 0x83, 0x60, 0x7c, 0x9a, 0xe0};
const unsigned char C_fe_fffb3[] = {
/* sqrt(-sqrt(-1) * A * (A + 2))
* 674a110d14c208efb89546403f0da2ed4024ff4ea5964229581b7d8717302c66
*/
0x67, 0x4a, 0x11, 0x0d, 0x14, 0xc2, 0x08, 0xef, 0xb8, 0x95, 0x46,
0x40, 0x3f, 0x0d, 0xa2, 0xed, 0x40, 0x24, 0xff, 0x4e, 0xa5, 0x96,
0x42, 0x29, 0x58, 0x1b, 0x7d, 0x87, 0x17, 0x30, 0x2c, 0x66
};
const unsigned char C_fe_fffb4[] = {
/* sqrt(sqrt(-1) * A * (A + 2))
* 1a43f3031067dbf926c0f4887ef7432eee46fc08a13f4a49853d1903b6b39186
*/
0x1a, 0x43, 0xf3, 0x03, 0x10, 0x67, 0xdb, 0xf9, 0x26, 0xc0, 0xf4,
0x88, 0x7e, 0xf7, 0x43, 0x2e, 0xee, 0x46, 0xfc, 0x08, 0xa1, 0x3f,
0x4a, 0x49, 0x85, 0x3d, 0x19, 0x03, 0xb6, 0xb3, 0x91, 0x86
};
const unsigned char C_fe_sqrtm1[] = {
/* sqrt(2 * A * (A + 2))
* 0x2b8324804fc1df0b2b4d00993dfbd7a72f431806ad2fe478c4ee1b274a0ea0b0
*/
0x2b, 0x83, 0x24, 0x80, 0x4f, 0xc1, 0xdf, 0x0b, 0x2b, 0x4d, 0x00, 0x99, 0x3d, 0xfb, 0xd7, 0xa7,
0x2f, 0x43, 0x18, 0x06, 0xad, 0x2f, 0xe4, 0x78, 0xc4, 0xee, 0x1b, 0x27, 0x4a, 0x0e, 0xa0, 0xb0};
const unsigned char C_fe_qm5div8[] = {
0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd};
int monero_ge_fromfe_frombytes(unsigned char *ge, unsigned char *bytes, size_t ge_len,
size_t bytes_len) {
int error = 0;
#define MOD (unsigned char *)C_ED25519_FIELD, 32
#define fe_isnegative(f) (f[31] & 1)
#define u (G_monero_vstate.io_buffer + 0 * 32)
#define v (G_monero_vstate.io_buffer + 1 * 32)
#define w (G_monero_vstate.io_buffer + 2 * 32)
#define x (G_monero_vstate.io_buffer + 3 * 32)
#define y (G_monero_vstate.io_buffer + 4 * 32)
#define z (G_monero_vstate.io_buffer + 5 * 32)
#define rX (G_monero_vstate.io_buffer + 6 * 32)
#define rY (G_monero_vstate.io_buffer + 7 * 32)
#define rZ (G_monero_vstate.io_buffer + 8 * 32)
union {
unsigned char _Pxy[PXY_SIZE];
struct {
unsigned char _uv7[32];
unsigned char _v3[32];
};
} uv;
#define uv7 uv._uv7
#define v3 uv._v3
#define Pxy uv._Pxy
#if MONERO_IO_BUFFER_LENGTH < (9 * 32)
#error MONERO_IO_BUFFER_LENGTH is too small
#endif
unsigned char sign;
if (!ge) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA;
}
if (ge_len < 32) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA_RANGE;
}
// cx works in BE
error |= monero_reverse32(u, bytes, 32, bytes_len);
error |= cx_math_modm_no_throw(u, 32, (unsigned char *)C_ED25519_FIELD, 32);
// go on
error |= cx_math_multm_no_throw(v, u, u, MOD); /* 2 * u^2 */
error |= cx_math_addm_no_throw(v, v, v, MOD);
explicit_bzero(w, 32);
w[31] = 1; /* w = 1 */
error |= cx_math_addm_no_throw(w, v, w, MOD); /* w = 2 * u^2 + 1 */
error |= cx_math_multm_no_throw(x, w, w, MOD); /* w^2 */
error |= cx_math_multm_no_throw(y, (unsigned char *)C_fe_ma2, v, MOD); /* -2 * A^2 * u^2 */
error |= cx_math_addm_no_throw(x, x, y, MOD); /* x = w^2 - 2 * A^2 * u^2 */
// inline fe_divpowm1(r->X, w, x); // (w / x)^(m + 1) => fe_divpowm1(r,u,v)
#define _u w
#define _v x
error |= cx_math_multm_no_throw(v3, _v, _v, MOD);
error |= cx_math_multm_no_throw(v3, v3, _v, MOD); /* v3 = v^3 */
error |= cx_math_multm_no_throw(uv7, v3, v3, MOD);
error |= cx_math_multm_no_throw(uv7, uv7, _v, MOD);
error |= cx_math_multm_no_throw(uv7, uv7, _u, MOD); /* uv7 = uv^7 */
error |= cx_math_powm_no_throw(uv7, uv7, (unsigned char *)C_fe_qm5div8, 32,
MOD); /* (uv^7)^((q-5)/8)*/
error |= cx_math_multm_no_throw(uv7, uv7, v3, MOD);
error |= cx_math_multm_no_throw(rX, uv7, w, MOD); /* u^(m+1)v^(-(m+1)) */
#undef _u
#undef _v
error |= cx_math_multm_no_throw(y, rX, rX, MOD);
error |= cx_math_multm_no_throw(x, y, x, MOD);
error |= cx_math_subm_no_throw(y, w, x, MOD);
memcpy(z, C_fe_ma, 32);
if (!cx_math_is_zero(y, 32)) {
error |= cx_math_addm_no_throw(y, w, x, MOD);
if (!cx_math_is_zero(y, 32)) {
goto negative;
} else {
error |= cx_math_multm_no_throw(rX, rX, (unsigned char *)C_fe_fffb1, MOD);
}
} else {
error |= cx_math_multm_no_throw(rX, rX, (unsigned char *)C_fe_fffb2, MOD);
}
error |= cx_math_multm_no_throw(rX, rX, u, MOD); // u * sqrt(2 * A * (A + 2) * w / x)
error |= cx_math_multm_no_throw(z, z, v, MOD); // -2 * A * u^2
sign = 0;
goto setsign;
negative:
error |= cx_math_multm_no_throw(x, x, (unsigned char *)C_fe_sqrtm1, MOD);
error |= cx_math_subm_no_throw(y, w, x, MOD);
if (!cx_math_is_zero(y, 32)) {
error |= cx_math_addm_no_throw(y, w, x, MOD);
error |= cx_math_multm_no_throw(rX, rX, (unsigned char *)C_fe_fffb3, MOD);
} else {
error |= cx_math_multm_no_throw(rX, rX, (unsigned char *)C_fe_fffb4, MOD);
}
// r->X = sqrt(A * (A + 2) * w / x)
// z = -A
sign = 1;
setsign:
if (fe_isnegative(rX) != sign) {
error |= cx_math_sub(rX, (unsigned char *)C_ED25519_FIELD, rX, 32);
}
error |= cx_math_addm_no_throw(rZ, z, w, MOD);
error |= cx_math_subm_no_throw(rY, z, w, MOD);
error |= cx_math_multm_no_throw(rX, rX, rZ, MOD);
// back to monero y-affine
error |= cx_math_invprimem_no_throw(u, rZ, MOD);
Pxy[0] = 0x04;
error |= cx_math_multm_no_throw(&Pxy[1], rX, u, MOD);
error |= cx_math_multm_no_throw(&Pxy[1 + 32], rY, u, MOD);
error |= cx_edwards_compress_point_no_throw(CX_CURVE_Ed25519, Pxy, sizeof(Pxy));
memcpy(ge, &Pxy[1], 32);
if (error) {
return SW_SECURITY_INTERNAL;
}
return 0;
#undef u
#undef v
#undef w
#undef x
#undef y
#undef z
#undef rX
#undef rY
#undef rZ
#undef uv7
#undef v3
#undef Pxy
}
/* ======================================================================= */
/* DERIVATION & KEY */
/* ======================================================================= */
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_hash_to_scalar(unsigned char *scalar, unsigned char *raw, size_t scalar_len,
unsigned int raw_len) {
int error;
error = monero_keccak_F(raw, raw_len, scalar);
if (error) {
return error;
}
error = monero_reduce(scalar, scalar, scalar_len, scalar_len);
if (error) {
return error;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_hash_to_ec(unsigned char *ec, unsigned char *ec_pub, size_t ec_len) {
int error;
error = monero_keccak_F(ec_pub, 32, ec);
if (error) {
return error;
}
error = monero_ge_fromfe_frombytes(ec, ec, ec_len, ec_len);
if (error) {
return error;
}
error = monero_ecmul_8(ec, ec, ec_len, ec_len);
if (error) {
return error;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_generate_keypair(unsigned char *ec_pub, unsigned char *ec_priv, size_t ec_pub_len,
size_t ec_priv_len) {
int error;
error = monero_rng_mod_order(ec_priv, ec_priv_len);
if (error) {
return error;
}
error = monero_ecmul_G(ec_pub, ec_priv, ec_pub_len, ec_priv_len);
if (error) {
return error;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- ok --- */
/* ----------------------------------------------------------------------- */
int monero_generate_key_derivation(unsigned char *drv_data, unsigned char *P, unsigned char *scalar,
size_t drv_data_len, size_t P_len, size_t scalar_len) {
return monero_ecmul_8k(drv_data, P, scalar, drv_data_len, P_len, scalar_len);
}
/* ----------------------------------------------------------------------- */
/* --- ok --- */
/* ----------------------------------------------------------------------- */
int monero_derivation_to_scalar(unsigned char *scalar, unsigned char *drv_data,
unsigned int out_idx, size_t scalar_len, size_t drv_data_len) {
unsigned char varint[32 + 8];
unsigned int len_varint;
int error = 0;
if (drv_data_len < 32) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA_RANGE;
}
memcpy(varint, drv_data, 32);
error = monero_encode_varint(varint + 32, 8, out_idx, &len_varint);
if (error) {
return error;
}
len_varint += 32;
error = monero_keccak_F(varint, len_varint, varint);
if (error) {
return error;
}
error = monero_reduce(scalar, varint, scalar_len, sizeof(varint));
if (error) {
return error;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_derive_secret_key(unsigned char *x, unsigned char *drv_data, unsigned int out_idx,
unsigned char *ec_priv, size_t x_len, size_t drv_data_len,
size_t ec_priv_len) {
unsigned char tmp[32];
int error;
// derivation to scalar
error = monero_derivation_to_scalar(tmp, drv_data, out_idx, sizeof(tmp), drv_data_len);
if (error) {
return error;
}
// generate
error = monero_addm(x, tmp, ec_priv, x_len, sizeof(tmp), ec_priv_len);
if (error) {
return error;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_derive_public_key(unsigned char *x, unsigned char *drv_data, unsigned int out_idx,
unsigned char *ec_pub, size_t x_len, size_t drv_data_len,
size_t ec_pub_len) {
unsigned char tmp[32];
// derivation to scalar
int error = monero_derivation_to_scalar(tmp, drv_data, out_idx, sizeof(tmp), drv_data_len);
if (error) {
return error;
}
// generate
error = monero_ecmul_G(tmp, tmp, sizeof(tmp), sizeof(tmp));
if (error) {
return error;
}
error = monero_ecadd(x, tmp, ec_pub, x_len, sizeof(tmp), ec_pub_len);
if (error) {
return error;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_secret_key_to_public_key(unsigned char *ec_pub, unsigned char *ec_priv,
size_t ec_pub_len, size_t ec_priv_len) {
return monero_ecmul_G(ec_pub, ec_priv, ec_pub_len, ec_priv_len);
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_generate_key_image(unsigned char *img, unsigned char *P, unsigned char *x,
size_t img_len, size_t x_len) {
unsigned char I[32];
int error;
error = monero_hash_to_ec(I, P, sizeof(I));
if (error) {
return error;
}
error = monero_ecmul_k(img, I, x, img_len, sizeof(I), x_len);
if (error) {
return error;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_derive_view_tag(unsigned char *view_tag, const unsigned char drv_data[static 32],
unsigned int out_idx) {
unsigned char varint[8 + 32 + 8];
unsigned int len_varint;
int error = 0;
memcpy(varint, "view_tag", 8);
memcpy(varint + 8, drv_data, 32);
error = monero_encode_varint(varint + 8 + 32, 8, out_idx, &len_varint);
if (error) {
return error;
}
len_varint += 8 + 32;
error = monero_keccak_F(varint, len_varint, varint);
if (error) {
return error;
}
*view_tag = varint[0];
return 0;
}
/* ======================================================================= */
/* SUB ADDRESS */
/* ======================================================================= */
/* ----------------------------------------------------------------------- */
/* --- ok --- */
/* ----------------------------------------------------------------------- */
int monero_derive_subaddress_public_key(unsigned char *x, unsigned char *pub,
unsigned char *drv_data, unsigned int index, size_t x_len,
size_t pub_len, size_t drv_data_len) {
unsigned char scalarG[32];
int error;
error = monero_derivation_to_scalar(scalarG, drv_data, index, sizeof(scalarG), drv_data_len);
if (error) {
return error;
}
error = monero_ecmul_G(scalarG, scalarG, sizeof(scalarG), sizeof(scalarG));
if (error) {
return error;
}
error = monero_ecsub(x, pub, scalarG, x_len, pub_len, sizeof(scalarG));
if (error) {
return error;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- ok --- */
/* ----------------------------------------------------------------------- */
int monero_get_subaddress_spend_public_key(unsigned char *x, unsigned char *index, size_t x_len,
size_t index_len) {
int error;
// m = Hs(a || index_major || index_minor)
error = monero_get_subaddress_secret_key(x, G_monero_vstate.a, index, x_len,
sizeof(G_monero_vstate.a), index_len);
if (error) {
return error;
}
// M = m*G
error = monero_secret_key_to_public_key(x, x, x_len, x_len);
if (error) {
return error;
}
// D = B + M
error = monero_ecadd(x, x, G_monero_vstate.B, x_len, x_len, sizeof(G_monero_vstate.B));
if (error) {
return error;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_get_subaddress(unsigned char *C, unsigned char *D, unsigned char *index, size_t C_len,
size_t D_len, size_t index_len) {
// retrieve D
int error = monero_get_subaddress_spend_public_key(D, index, D_len, index_len);
if (error) {
return error;
}
// C = a*D
error = monero_ecmul_k(C, D, G_monero_vstate.a, C_len, D_len, sizeof(G_monero_vstate.a));
if (error) {
return error;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- ok --- */
/* ----------------------------------------------------------------------- */
static const char C_sub_address_prefix[] = {'S', 'u', 'b', 'A', 'd', 'd', 'r', 0};
int monero_get_subaddress_secret_key(unsigned char *sub_s, unsigned char *s, unsigned char *index,
size_t sub_s_len, size_t s_len, size_t index_len) {
unsigned char in[sizeof(C_sub_address_prefix) + 32 + 8];
int error;
if (!s || s_len < 32) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA_RANGE;
}
if (!index || index_len < 8) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA_RANGE;
}
memcpy(in, C_sub_address_prefix, sizeof(C_sub_address_prefix));
memcpy(in + sizeof(C_sub_address_prefix), s, 32);
memcpy(in + sizeof(C_sub_address_prefix) + 32, index, 8);
// hash_to_scalar with more that 32bytes:
error = monero_keccak_F(in, sizeof(in), sub_s);
if (error) {
return error;
}
error = monero_reduce(sub_s, sub_s, sub_s_len, sub_s_len);
if (error) {
return error;
}
return 0;
}
/* ======================================================================= */
/* MATH */
/* ======================================================================= */
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
unsigned int monero_check_scalar_range_1N(unsigned char *s, size_t s_len) {
unsigned char x[32];
int diff;
int error = monero_reverse32(x, s, sizeof(x), s_len);
if (error) {
return error;
}
if (cx_math_cmp_no_throw(x, C_ED25519_ORDER, 32, &diff)) {
return SW_SECURITY_INTERNAL;
}
if (cx_math_is_zero(x, 32) || diff >= 0) {
return SW_WRONG_DATA_RANGE;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
unsigned int monero_check_scalar_not_null(unsigned char *s) {
if (cx_math_is_zero(s, 32)) {
return SW_WRONG_DATA_RANGE;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_ecmul_G(unsigned char *W, unsigned char *scalar32, size_t W_len, size_t scalar32_len) {
unsigned char Pxy[PXY_SIZE];
unsigned char s[32];
int error;
if (!W || W_len < 32) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA_RANGE;
}
error = monero_reverse32(s, scalar32, sizeof(s), scalar32_len);
if (error) {
return error;
}
memcpy(Pxy, C_ED25519_G, PXY_SIZE);
error = cx_ecfp_scalar_mult_no_throw(CX_CURVE_Ed25519, Pxy, s, 32);
if (error) {
return SW_SECURITY_INTERNAL;
}
error = cx_edwards_compress_point_no_throw(CX_CURVE_Ed25519, Pxy, sizeof(Pxy));
if (error) {
return SW_SECURITY_INTERNAL;
}
memcpy(W, &Pxy[1], 32);
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_ecmul_H(unsigned char *W, unsigned char *scalar32, size_t W_len, size_t scalar32_len) {
unsigned char Pxy[PXY_SIZE];
unsigned char s[32];
int error = 0;
if (!W || W_len < 32) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA_RANGE;
}
error = monero_reverse32(s, scalar32, sizeof(s), scalar32_len);
if (error) {
return error;
}
Pxy[0] = 0x02;
memcpy(&Pxy[1], C_ED25519_Hy, 32);
error |= cx_edwards_decompress_point_no_throw(CX_CURVE_Ed25519, Pxy, sizeof(Pxy));
error |= cx_ecfp_scalar_mult_no_throw(CX_CURVE_Ed25519, Pxy, s, 32);
error |= cx_edwards_compress_point_no_throw(CX_CURVE_Ed25519, Pxy, sizeof(Pxy));
if (error) {
return SW_SECURITY_INTERNAL;
}
memcpy(W, &Pxy[1], 32);
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_ecmul_k(unsigned char *W, unsigned char *P, unsigned char *scalar32, size_t W_len,
size_t P_len, size_t scalar32_len) {
unsigned char Pxy[PXY_SIZE];
unsigned char s[32];
int error = 0;
if (!W || W_len < 32) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA_RANGE;
}
if (!P || P_len < 32) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA_RANGE;
}
error = monero_reverse32(s, scalar32, sizeof(s), scalar32_len);
if (error) {
return error;
}
Pxy[0] = 0x02;
memcpy(&Pxy[1], P, 32);
error |= cx_edwards_decompress_point_no_throw(CX_CURVE_Ed25519, Pxy, sizeof(Pxy));
error |= cx_ecfp_scalar_mult_no_throw(CX_CURVE_Ed25519, Pxy, s, 32);
error |= cx_edwards_compress_point_no_throw(CX_CURVE_Ed25519, Pxy, sizeof(Pxy));
if (error) {
return SW_SECURITY_INTERNAL;
}
memcpy(W, &Pxy[1], 32);
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_ecmul_8k(unsigned char *W, unsigned char *P, unsigned char *scalar32, size_t W_len,
size_t P_len, size_t scalar32_len) {
unsigned char s[32];
int error = 0;
error = monero_multm_8(s, scalar32, sizeof(s), scalar32_len);
if (error) {
return error;
}
error = monero_ecmul_k(W, P, s, W_len, P_len, sizeof(s));
if (error) {
return error;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_ecmul_8(unsigned char *W, unsigned char *P, size_t W_len, size_t P_len) {
unsigned char Pxy[PXY_SIZE];
int error = 0;
if (!W || W_len < 32) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA_RANGE;
}
if (!P || P_len < 32) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA_RANGE;
}
Pxy[0] = 0x02;
memcpy(&Pxy[1], P, 32);
error |= cx_edwards_decompress_point_no_throw(CX_CURVE_Ed25519, Pxy, sizeof(Pxy));
error |= cx_ecfp_add_point_no_throw(CX_CURVE_Ed25519, Pxy, Pxy, Pxy);
error |= cx_ecfp_add_point_no_throw(CX_CURVE_Ed25519, Pxy, Pxy, Pxy);
error |= cx_ecfp_add_point_no_throw(CX_CURVE_Ed25519, Pxy, Pxy, Pxy);
error |= cx_edwards_compress_point_no_throw(CX_CURVE_Ed25519, Pxy, sizeof(Pxy));
if (error) {
return SW_SECURITY_INTERNAL;
}
memcpy(W, &Pxy[1], 32);
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_ecadd(unsigned char *W, unsigned char *P, unsigned char *Q, size_t W_len, size_t P_len,
size_t Q_len) {
unsigned char Pxy[PXY_SIZE];
unsigned char Qxy[PXY_SIZE];
int error = 0;
if (!W || W_len < 32) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA_RANGE;
}
if (!P || P_len < 32) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA_RANGE;
}
if (!Q || Q_len < 32) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA_RANGE;
}
Pxy[0] = 0x02;
memcpy(&Pxy[1], P, 32);
error |= cx_edwards_decompress_point_no_throw(CX_CURVE_Ed25519, Pxy, sizeof(Pxy));
Qxy[0] = 0x02;
memcpy(&Qxy[1], Q, 32);
error |= cx_edwards_decompress_point_no_throw(CX_CURVE_Ed25519, Qxy, sizeof(Qxy));
error |= cx_ecfp_add_point_no_throw(CX_CURVE_Ed25519, Pxy, Pxy, Qxy);
error |= cx_edwards_compress_point_no_throw(CX_CURVE_Ed25519, Pxy, sizeof(Pxy));
memcpy(W, &Pxy[1], 32);
if (error) {
return SW_SECURITY_INTERNAL;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_ecsub(unsigned char *W, unsigned char *P, unsigned char *Q, size_t W_len, size_t P_len,
size_t Q_len) {
unsigned char Pxy[PXY_SIZE];
unsigned char Qxy[PXY_SIZE];
int error = 0;
if (!W || W_len < 32) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA_RANGE;
}
if (!P || P_len < 32) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA_RANGE;
}
if (!Q || Q_len < 32) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA_RANGE;
}
Pxy[0] = 0x02;
memcpy(&Pxy[1], P, 32);
error |= cx_edwards_decompress_point_no_throw(CX_CURVE_Ed25519, Pxy, sizeof(Pxy));
Qxy[0] = 0x02;
memcpy(&Qxy[1], Q, 32);
error |= cx_edwards_decompress_point_no_throw(CX_CURVE_Ed25519, Qxy, sizeof(Qxy));
error |= cx_math_sub(Qxy + 1, (unsigned char *)C_ED25519_FIELD, Qxy + 1, 32);
error |= cx_ecfp_add_point_no_throw(CX_CURVE_Ed25519, Pxy, Pxy, Qxy);
error |= cx_edwards_compress_point_no_throw(CX_CURVE_Ed25519, Pxy, sizeof(Pxy));
memcpy(W, &Pxy[1], 32);
if (error) {
return SW_SECURITY_INTERNAL;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
/*
static key ecdhHash(const key &k)
{
char data[38];
rct::key hash;
memcpy(data, "amount", 6);
memcpy(data + 6, &k, sizeof(k));
cn_fast_hash(hash, data, sizeof(data));
return hash;
}
*/
int monero_ecdhHash(unsigned char *x, unsigned char *k, size_t k_len) {
unsigned char data[38];
int error;
if (!k || k_len < 32) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA;
}
memcpy(data, "amount", 6);
memcpy(data + 6, k, 32);
error = monero_keccak_F(data, 38, x);
return error;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
/*
key genCommitmentMask(const key &sk)
{
char data[15 + sizeof(key)];
memcpy(data, "commitment_mask", 15);
memcpy(data + 15, &sk, sizeof(sk));
key scalar;
hash_to_scalar(scalar, data, sizeof(data));
return scalar;
}
*/
int monero_genCommitmentMask(unsigned char *c, unsigned char *sk, size_t c_len, size_t sk_len) {
unsigned char data[15 + 32];
int error;
if (!sk || sk_len < 32) {
PRINTF("Buffer Error: %s:%d \n", __LINE__);
return SW_WRONG_DATA_RANGE;
}
memcpy(data, "commitment_mask", 15);
memcpy(data + 15, sk, 32);
error = monero_hash_to_scalar(c, data, c_len, 15 + 32);
if (error) {
return error;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_addm(unsigned char *r, unsigned char *a, unsigned char *b, size_t r_len, size_t a_len,
size_t b_len) {
unsigned char ra[32];
unsigned char rb[32];
int error;
error = monero_reverse32(ra, a, sizeof(ra), a_len);
if (error) {
return error;
}
error = monero_reverse32(rb, b, sizeof(rb), b_len);
if (error) {
return error;
}
error = cx_math_addm_no_throw(r, ra, rb, (unsigned char *)C_ED25519_ORDER, 32);
if (error) {
return SW_SECURITY_INTERNAL;
}
error = monero_reverse32(r, r, r_len, r_len);
if (error) {
return error;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_subm(unsigned char *r, unsigned char *a, unsigned char *b, size_t r_len, size_t a_len,
size_t b_len) {
unsigned char ra[32];
unsigned char rb[32];
int error;
error = monero_reverse32(ra, a, sizeof(ra), a_len);
if (error) {
return error;
}
error = monero_reverse32(rb, b, sizeof(rb), b_len);
if (error) {
return error;
}
error = cx_math_subm_no_throw(r, ra, rb, (unsigned char *)C_ED25519_ORDER, 32);
if (error) {
return SW_SECURITY_INTERNAL;
}
error = monero_reverse32(r, r, r_len, r_len);
if (error) {
return error;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_multm(unsigned char *r, unsigned char *a, unsigned char *b, size_t r_len, size_t a_len,
size_t b_len) {
unsigned char ra[32];
unsigned char rb[32];
int error;
error = monero_reverse32(ra, a, sizeof(ra), a_len);
if (error) {
return error;
}
error = monero_reverse32(rb, b, sizeof(rb), b_len);
if (error) {
return error;
}
error = cx_math_multm_no_throw(r, ra, rb, (unsigned char *)C_ED25519_ORDER, 32);
if (error) {
return SW_SECURITY_INTERNAL;
}
error = monero_reverse32(r, r, r_len, r_len);
if (error) {
return error;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_multm_8(unsigned char *r, unsigned char *a, size_t r_len, size_t a_len) {
unsigned char ra[32];
unsigned char rb[32];
int error;
error = monero_reverse32(ra, a, sizeof(ra), a_len);
if (error) {
return error;
}
explicit_bzero(rb, 32);
rb[31] = 8;
error = cx_math_multm_no_throw(r, ra, rb, (unsigned char *)C_ED25519_ORDER, 32);
if (error) {
return SW_SECURITY_INTERNAL;
}
error = monero_reverse32(r, r, r_len, r_len);
if (error) {
return error;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_reduce(unsigned char *r, unsigned char *a, size_t r_len, size_t a_len) {
unsigned char ra[32];
int error;
error = monero_reverse32(ra, a, sizeof(ra), a_len);
if (error) {
return error;
}
error = cx_math_modm_no_throw(ra, 32, (unsigned char *)C_ED25519_ORDER, 32);
if (error) {
return SW_SECURITY_INTERNAL;
}
error = monero_reverse32(r, ra, r_len, sizeof(ra));
if (error) {
return error;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_rng_mod_order(unsigned char *r, size_t r_len) {
unsigned char rnd[32 + 8];
int error;
cx_rng(rnd, 32 + 8);
error = cx_math_modm_no_throw(rnd, 32 + 8, (unsigned char *)C_ED25519_ORDER, 32);
if (error) {
return SW_SECURITY_INTERNAL;
}
error = monero_reverse32(r, rnd + 8, r_len, 32);
if (error) {
return error;
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
unsigned int monero_uint642str(uint64_t val, char *str, unsigned int str_len) {
char stramount[22];
unsigned int offset, len;
if (!str) {
PRINTF("%d \n\n", __LINE__);
return SW_WRONG_DATA;
}
explicit_bzero(str, str_len);
offset = 22;
while (val) {
offset--;
stramount[offset] = '0' + val % 10;
val = val / 10;
}
len = sizeof(stramount) - offset;
if (len > str_len) {
return SW_WRONG_DATA_RANGE;
}
memcpy(str, stramount + offset, len);
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
unsigned int monero_amount2str(uint64_t xmr, char *str, unsigned int str_len) {
// max uint64 is 18446744073709551616, aka 20 char, plus dot
char stramount[22];
unsigned int offset, len;
if (!str) {
PRINTF("%d \n\n", __LINE__);
return SW_WRONG_DATA;
}
explicit_bzero(str, str_len);
memset(stramount, '0', sizeof(stramount));
stramount[21] = 0;
// special case
if (xmr == 0) {
str[0] = '0';
return 0;
}
// uint64 units to str
// offset: 0 | 1-20 | 21
// ----------------------
// value: 0 | xmrunits | 0
offset = 20;
while (xmr) {
stramount[offset] = '0' + xmr % 10;
xmr = xmr / 10;
offset--;
}
// offset: 0-7 | 8 | 9-20 |21
// ----------------------
// value: xmr | . | units| 0
memmove(stramount, stramount + 1, 8);
stramount[8] = '.';
offset = 0;
while ((stramount[offset] == '0') && (stramount[offset] != '.')) {
offset++;
}
if (stramount[offset] == '.') {
offset--;
}
len = 20;
while ((stramount[len] == '0') && (stramount[len] != '.')) {
len--;
}
len = len - offset + 1;
if (len > (str_len - 1)) {
len = str_len - 1;
}
memcpy(str, stramount + offset, len);
return 0;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
uint64_t monero_bamount2uint64(unsigned char *binary, size_t binary_len) {
uint64_t xmr;
int i;
if (!binary || binary_len < 8) {
PRINTF("%d \n\n", __LINE__);
return 0;
}
xmr = 0;
for (i = 7; i >= 0; i--) {
xmr = xmr * 256 + binary[i];
}
return xmr;
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_bamount2str(unsigned char *binary, char *str, size_t binary_len, unsigned int str_len) {
return monero_amount2str(monero_bamount2uint64(binary, binary_len), str, str_len);
}
/* ----------------------------------------------------------------------- */
/* --- --- */
/* ----------------------------------------------------------------------- */
int monero_vamount2str(unsigned char *binary, char *str, unsigned int str_len) {
uint64_t amount;
unsigned int out_len;
unsigned int error = monero_decode_varint(binary, 8, &amount, &out_len);
if (error) {
return error;
}
return monero_amount2str(amount, str, str_len);
}