Skip to content

Commit

Permalink
Export bech32 and bech32m functions (trustwallet#4101)
Browse files Browse the repository at this point in the history
* Export bech32 and bech32m functions

* Fix test cases

* Merge bech32m functions into TWBech32 struct

* Fix failed test cases

* Fix failed swift test case
  • Loading branch information
10gic authored Nov 22, 2024
1 parent e34e9ba commit 6dc2c62
Show file tree
Hide file tree
Showing 9 changed files with 376 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.trustwallet.core.app.utils

import org.junit.Assert.*
import org.junit.Test
import wallet.core.jni.Bech32

class TestBech32 {
init {
System.loadLibrary("TrustWalletCore");
}

@Test
fun testEncode() {
val data = Numeric.hexStringToByteArray("00443214c74254b635cf84653a56d7c675be77df")
assertEquals(Bech32.encode("abcdef", data), "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw")
}

@Test
fun testDecode() {
val decoded = Bech32.decode("abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw")
assertEquals(Numeric.toHexString(decoded), "0x00443214c74254b635cf84653a56d7c675be77df")
}

@Test
fun testDecodeWrongChecksumVariant() {
// This is a Bech32m variant, not Bech32 variant. So it should fail using Bech32 decoder.
val decoded = Bech32.decode("abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx")
assertNull(decoded)
}

@Test
fun testEncodeM() {
val data = Numeric.hexStringToByteArray("ffbbcdeb38bdab49ca307b9ac5a928398a418820")
assertEquals(Bech32.encodeM("abcdef", data), "abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx")
}

@Test
fun testDecodeM() {
val decoded = Bech32.decodeM("abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx")
assertEquals(Numeric.toHexString(decoded), "0xffbbcdeb38bdab49ca307b9ac5a928398a418820")
}

@Test
fun testDecodeMWrongChecksumVariant() {
// This is a Bech32 variant, not Bech32m variant. So it should fail using Bech32M decoder.
val decoded = Bech32.decodeM("abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw")
assertNull(decoded)
}
}
78 changes: 78 additions & 0 deletions codegen-v2/manifest/TWBech32.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: TWBech32
structs:
- name: TWBech32
is_public: true
is_class: false
functions:
- name: TWBech32Encode
is_public: true
is_static: true
params:
- name: hrp
type:
variant: string
is_constant: true
is_nullable: false
is_pointer: true
- name: data
type:
variant: data
is_constant: true
is_nullable: false
is_pointer: true
return_type:
variant: string
is_constant: true
is_nullable: false
is_pointer: true
- name: TWBech32Decode
is_public: true
is_static: true
params:
- name: string
type:
variant: string
is_constant: true
is_nullable: false
is_pointer: true
return_type:
variant: data
is_constant: true
is_nullable: true
is_pointer: true
- name: TWBech32EncodeM
is_public: true
is_static: true
params:
- name: hrp
type:
variant: string
is_constant: true
is_nullable: false
is_pointer: true
- name: data
type:
variant: data
is_constant: true
is_nullable: false
is_pointer: true
return_type:
variant: string
is_constant: true
is_nullable: false
is_pointer: true
- name: TWBech32DecodeM
is_public: true
is_static: true
params:
- name: string
type:
variant: string
is_constant: true
is_nullable: false
is_pointer: true
return_type:
variant: data
is_constant: true
is_nullable: true
is_pointer: true
4 changes: 2 additions & 2 deletions include/TrustWalletCore/TWBase58.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ TWString *_Nonnull TWBase58EncodeNoCheck(TWData *_Nonnull data);
/// Decodes a Base58 string, checking the checksum. Returns null if the string is not a valid Base58 string.
///
/// \param string The Base58 string to decode.
/// \return the decoded data, empty if the string is not a valid Base58 string with checksum.
/// \return the decoded data, null if the string is not a valid Base58 string with checksum.
TW_EXPORT_STATIC_METHOD
TWData *_Nullable TWBase58Decode(TWString *_Nonnull string);

/// Decodes a Base58 string, w/o checking the checksum. Returns null if the string is not a valid Base58 string.
///
/// \param string The Base58 string to decode.
/// \return the decoded data, empty if the string is not a valid Base58 string without checksum.
/// \return the decoded data, null if the string is not a valid Base58 string without checksum.
TW_EXPORT_STATIC_METHOD
TWData *_Nullable TWBase58DecodeNoCheck(TWString *_Nonnull string);

Expand Down
47 changes: 47 additions & 0 deletions include/TrustWalletCore/TWBech32.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

#pragma once

#include "TWBase.h"
#include "TWData.h"
#include "TWString.h"

TW_EXTERN_C_BEGIN

/// Bech32 encode / decode functions
TW_EXPORT_STRUCT
struct TWBech32;

/// Encodes data as a Bech32 string.
///
/// \param hrp The human-readable part.
/// \param data The data part.
/// \return the encoded Bech32 string.
TW_EXPORT_STATIC_METHOD
TWString *_Nonnull TWBech32Encode(TWString* _Nonnull hrp, TWData *_Nonnull data);

/// Decodes a Bech32 string. Returns null if the string is not a valid Bech32 string.
///
/// \param string The Bech32 string to decode.
/// \return the decoded data, null if the string is not a valid Bech32 string. Note that the human-readable part is not returned.
TW_EXPORT_STATIC_METHOD
TWData *_Nullable TWBech32Decode(TWString *_Nonnull string);

/// Encodes data as a Bech32m string.
///
/// \param hrp The human-readable part.
/// \param data The data part.
/// \return the encoded Bech32m string.
TW_EXPORT_STATIC_METHOD
TWString *_Nonnull TWBech32EncodeM(TWString* _Nonnull hrp, TWData *_Nonnull data);

/// Decodes a Bech32m string. Returns null if the string is not a valid Bech32m string.
///
/// \param string The Bech32m string to decode.
/// \return the decoded data, null if the string is not a valid Bech32m string. Note that the human-readable part is not returned.
TW_EXPORT_STATIC_METHOD
TWData *_Nullable TWBech32DecodeM(TWString *_Nonnull string);

TW_EXTERN_C_END
4 changes: 2 additions & 2 deletions src/Bech32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Data create_checksum(const std::string& hrp, const Data& values, ChecksumVariant

} // namespace

/** Encode a Bech32 string. */
/** Encode a Bech32 string. Note that the values must each encode 5 bits, normally get from convertBits<8, 5, true> */
std::string encode(const std::string& hrp, const Data& values, ChecksumVariant variant) {
Data checksum = create_checksum(hrp, values, variant);
Data combined = values;
Expand All @@ -116,7 +116,7 @@ std::string encode(const std::string& hrp, const Data& values, ChecksumVariant v
return ret;
}

/** Decode a Bech32 string. */
/** Decode a Bech32 string. Note that the returned values are 5 bits each, you may want to use convertBits<5, 8, false> */
std::tuple<std::string, Data, ChecksumVariant> decode(const std::string& str) {
if (str.length() > 120 || str.length() < 2) {
// too long or too short
Expand Down
60 changes: 60 additions & 0 deletions src/interface/TWBech32.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

#include <TrustWalletCore/TWBech32.h>

#include "../Bech32.h"

#include <string>

using namespace TW;

static TWString *_Nonnull encodeGeneric(TWString* _Nonnull hrp, TWData *_Nonnull data, const Bech32::ChecksumVariant variant) {
const auto cppHrp = *static_cast<const std::string*>(hrp);
const auto cppData = *static_cast<const Data*>(data);
Data enc;
if (!Bech32::convertBits<8, 5, true>(enc, cppData)) {
return TWStringCreateWithUTF8Bytes("");
}
const auto result = Bech32::encode(cppHrp, enc, variant);
return TWStringCreateWithUTF8Bytes(result.c_str());
}

static TWData *_Nullable decodeGeneric(TWString *_Nonnull string, const Bech32::ChecksumVariant variant) {
const auto cppString = *static_cast<const std::string*>(string);
const auto decoded = Bech32::decode(cppString);

const auto data = std::get<1>(decoded);
if (data.empty()) { // Failed to decode
return nullptr;
}

if (std::get<2>(decoded) != variant) { // Wrong ChecksumVariant
return nullptr;
}

// Bech bits conversion
Data conv;
if (!Bech32::convertBits<5, 8, false>(conv, data)) {
return nullptr;
}

return TWDataCreateWithBytes(conv.data(), conv.size());
}

TWString *_Nonnull TWBech32Encode(TWString* _Nonnull hrp, TWData *_Nonnull data) {
return encodeGeneric(hrp, data, Bech32::ChecksumVariant::Bech32);
}

TWString *_Nonnull TWBech32EncodeM(TWString* _Nonnull hrp, TWData *_Nonnull data) {
return encodeGeneric(hrp, data, Bech32::ChecksumVariant::Bech32M);
}

TWData *_Nullable TWBech32Decode(TWString *_Nonnull string) {
return decodeGeneric(string, Bech32::ChecksumVariant::Bech32);
}

TWData *_Nullable TWBech32DecodeM(TWString *_Nonnull string) {
return decodeGeneric(string, Bech32::ChecksumVariant::Bech32M);
}
42 changes: 42 additions & 0 deletions swift/Tests/Bech32Tests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

import XCTest
import WalletCore

class Bech32Tests: XCTestCase {
func testEncode() {
let data = Data(hexString: "00443214c74254b635cf84653a56d7c675be77df")!
let encoded = Bech32.encode(hrp: "abcdef", data: data)
XCTAssertEqual(encoded, "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw")
}

func testDecode() {
let decoded = Bech32.decode(string: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw")!
XCTAssertEqual(decoded.hexString, "00443214c74254b635cf84653a56d7c675be77df")
}

func testDecodeWrongChecksumVariant() {
// This is a Bech32m variant, not Bech32 variant. So it should fail using Bech32 decoder.
let decoded = Bech32.decode(string: "abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx")
XCTAssertNil(decoded)
}

func testEncodeM() {
let data = Data(hexString: "ffbbcdeb38bdab49ca307b9ac5a928398a418820")!
let encoded = Bech32.encodeM(hrp: "abcdef", data: data)
XCTAssertEqual(encoded, "abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx")
}

func testDecodeM() {
let decoded = Bech32.decodeM(string: "abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx")!
XCTAssertEqual(decoded.hexString, "ffbbcdeb38bdab49ca307b9ac5a928398a418820")
}

func testDecodeMWrongChecksumVariant() {
// This is a Bech32 variant, not Bech32m variant. So it should fail using Bech32M decoder.
let decoded = Bech32.decodeM(string: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw")
XCTAssertNil(decoded)
}
}
49 changes: 49 additions & 0 deletions tests/interface/TWBech32Tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

#include "TestUtilities.h"

#include <TrustWalletCore/TWBech32.h>

#include <gtest/gtest.h>

TEST(TWBech32, Encode) {
const auto hrp = STRING("abcdef");
const auto data = DATA("00443214c74254b635cf84653a56d7c675be77df");
const auto result = WRAPS(TWBech32Encode(hrp.get(), data.get()));
assertStringsEqual(result, "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw");
}

TEST(TWBech32, Decode) {
const auto input = STRING("abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw");
const auto result = WRAPD(TWBech32Decode(input.get()));
assertHexEqual(result, "00443214c74254b635cf84653a56d7c675be77df");
}

TEST(TWBech32, Decode_WrongChecksumVariant) {
// This is a Bech32m variant, not Bech32 variant. So it should fail using Bech32 decoder.
const auto input = STRING("abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx");
const auto result = WRAPD(TWBech32Decode(input.get()));
ASSERT_EQ(result.get(), nullptr);
}

TEST(TWBech32, EncodeM) {
const auto hrp = STRING("abcdef");
const auto data = DATA("ffbbcdeb38bdab49ca307b9ac5a928398a418820");
const auto result = WRAPS(TWBech32EncodeM(hrp.get(), data.get()));
assertStringsEqual(result, "abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx");
}

TEST(TWBech32, DecodeM) {
const auto input = STRING("abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx");
auto result = WRAPD(TWBech32DecodeM(input.get()));
assertHexEqual(result, "ffbbcdeb38bdab49ca307b9ac5a928398a418820");
}

TEST(TWBech32, DecodeM_WrongChecksumVariant) {
// This is a Bech32 variant, not Bech32m variant. So it should fail using Bech32M decoder.
const auto input = STRING("abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw");
const auto result = WRAPD(TWBech32DecodeM(input.get()));
ASSERT_EQ(result.get(), nullptr);
}
Loading

0 comments on commit 6dc2c62

Please sign in to comment.