Skip to content

Commit

Permalink
Merge branch 'master' into bitcoin-nft-inscriptions
Browse files Browse the repository at this point in the history
  • Loading branch information
lamafab authored Jul 13, 2023
2 parents 3d7e93f + f266567 commit c2af9e4
Show file tree
Hide file tree
Showing 30 changed files with 497 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class CoinAddressDerivationTests {
ETHEREUM, SMARTCHAIN, POLYGON, OPTIMISM, ZKSYNC, ARBITRUM, ECOCHAIN, AVALANCHECCHAIN, XDAI,
FANTOM, CELO, CRONOSCHAIN, SMARTBITCOINCASH, KUCOINCOMMUNITYCHAIN, BOBA, METIS,
AURORA, EVMOS, MOONRIVER, MOONBEAM, KAVAEVM, KLAYTN, METER, OKXCHAIN, POLYGONZKEVM, SCROLL,
CONFLUXESPACE, ACALAEVM -> assertEquals("0x8f348F300873Fd5DA36950B2aC75a26584584feE", address)
CONFLUXESPACE, ACALAEVM, OPBNBTESTNET -> assertEquals("0x8f348F300873Fd5DA36950B2aC75a26584584feE", address)
RONIN -> assertEquals("ronin:8f348F300873Fd5DA36950B2aC75a26584584feE", address)
ETHEREUMCLASSIC -> assertEquals("0x078bA3228F3E6C08bEEac9A005de0b7e7089aD1c", address)
GOCHAIN -> assertEquals("0x5940ce4A14210d4Ccd0ac206CE92F21828016aC2", address)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import org.junit.Assert.assertEquals
import org.junit.Test
import wallet.core.java.AnySigner
import wallet.core.jni.*
import wallet.core.jni.Cardano.getByronAddress
import wallet.core.jni.Cardano.getStakingAddress
import wallet.core.jni.Cardano.outputMinAdaAmount
import wallet.core.jni.CoinType.CARDANO
Expand Down Expand Up @@ -75,6 +76,60 @@ class TestCardanoSigning {
assertEquals(Numeric.toHexString(txid.toByteArray()), "0x9b5b15e133cd73ccaa85307d2986aebc846505118a2eb4e6111e6b4b67d1f389");
}

/// Successfully broadcasted:
/// https://cardanoscan.io/transaction/0203ce2c91f59f169a26e9ef91254639d2b7911afac9c7c0ae64539f88ba46a5
@Test
fun testSignTransferFromLegacy() {
val privateKey = PrivateKey("98f266d1aac660179bc2f456033941238ee6b2beb8ed0f9f34c9902816781f5a9903d1d395d6ab887b65ea5e344ef09b449507c21a75f0ce8c59d0ed1c6764eba7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f4e890ca4eb6bec44bf751b5a843174534af64d6ad1f44e0613db78a7018781f5aa151d2997f52059466b715d8eefab30a78b874ae6ef4931fa58bb21ef8ce2423d46f19d0fbf75afb0b9a24e31d533f4fd74cee3b56e162568e8defe37123afc4".toHexByteArray())
var publicKey = privateKey.publicKeyEd25519Cardano
var byronAddress = wallet.core.jni.Cardano.getByronAddress(publicKey)

assertEquals(byronAddress, "Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8")

val message = Cardano.Transfer.newBuilder()
.setToAddress("addr1q90uh2eawrdc9vaemftgd50l28yrh9lqxtjjh4z6dnn0u7ggasexxdyyk9f05atygnjlccsjsggtc87hhqjna32fpv5qeq96ls")
.setChangeAddress("addr1qx55ymlqemndq8gluv40v58pu76a2tp4mzjnyx8n6zrp2vtzrs43a0057y0edkn8lh9su8vh5lnhs4npv6l9tuvncv8swc7t08")
.setAmount(3_000_000)
.build()
val input = Cardano.SigningInput.newBuilder()
.setTransferMessage(message)
.setTtl(190000000)

input.addPrivateKey(ByteString.copyFrom(privateKey.data()))

val outpoint1 = Cardano.OutPoint.newBuilder()
.setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("8316e5007d61fb90652cabb41141972a38b5bc60954d602cf843476aa3f67f63")))
.setOutputIndex(0)
.build()
val utxo1 = Cardano.TxInput.newBuilder()
.setOutPoint(outpoint1)
.setAddress("Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8")
.setAmount(2_500_000)
.build()
input.addUtxos(utxo1)

val outpoint2 = Cardano.OutPoint.newBuilder()
.setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("e29392c59c903fefb905730587d22cae8bda30bd8d9aeec3eca082ae77675946")))
.setOutputIndex(0)
.build()
val utxo2 = Cardano.TxInput.newBuilder()
.setOutPoint(outpoint2)
.setAddress("Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8")
.setAmount(1_700_000)
.build()
input.addUtxos(utxo2)

val output = AnySigner.sign(input.build(), CARDANO, Cardano.SigningOutput.parser())
assertEquals(output.error, SigningError.OK)

val encoded = output.encoded
assertEquals(Numeric.toHexString(encoded.toByteArray()),
"0x83a400828258208316e5007d61fb90652cabb41141972a38b5bc60954d602cf843476aa3f67f6300825820e29392c59c903fefb905730587d22cae8bda30bd8d9aeec3eca082ae77675946000182825839015fcbab3d70db82b3b9da5686d1ff51c83b97e032e52bd45a6ce6fe7908ec32633484b152fa756444e5fc62128210bc1fd7b8253ec5490b281a002dc6c082583901a9426fe0cee6d01d1fe32af650e1e7b5d52c35d8a53218f3d0861531621c2b1ebdf4f11f96da67fdcb0e1d97a7e778566166be55f193c30f1a000f9ec1021a0002b0bf031a0b532b80a20081825820d163c8c4f0be7c22cd3a1152abb013c855ea614b92201497a568c5d93ceeb41e58406a23ab9267867fbf021c1cb2232bc83d2cdd663d651d22d59b6cddbca5cb106d4db99da50672f69a2309ca8a329a3f9576438afe4538b013de4591a6dfcd4d090281845820d163c8c4f0be7c22cd3a1152abb013c855ea614b92201497a568c5d93ceeb41e58406a23ab9267867fbf021c1cb2232bc83d2cdd663d651d22d59b6cddbca5cb106d4db99da50672f69a2309ca8a329a3f9576438afe4538b013de4591a6dfcd4d095820a7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f441a0f6");

val txid = output.txId
assertEquals(Numeric.toHexString(txid.toByteArray()), "0x0203ce2c91f59f169a26e9ef91254639d2b7911afac9c7c0ae64539f88ba46a5");
}

@Test
fun testSignTransferToken1() {
val toToken = Cardano.TokenAmount.newBuilder()
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,19 @@ class TestWebAuthn {
val result = WebAuthn.getPublicKey(attestationObject).data()
assertEquals(Numeric.toHexString(result), "0x04a620a8cfc88fd062b11eab31663e56cad95278bef612959be214d98779f645b84e7b905b42917570148b0432f99ba21f2e7eebe018cbf837247e38150a89f771")
}

@Test
fun testGetRSValues() {
val signature = Numeric.hexStringToByteArray("0x30440220766589b461a838748708cdf88444b21b1fa52b57d70671b4f9bf60ad14b372ec022020cc439c9c20661bfa39bbea24a900ec1484b2395eb065ead8ef4e273144a57d")
val result = WebAuthn.getRSValues(signature)
assertEquals(Numeric.toHexString(result), "0x766589b461a838748708cdf88444b21b1fa52b57d70671b4f9bf60ad14b372ec20cc439c9c20661bfa39bbea24a900ec1484b2395eb065ead8ef4e273144a57d")
}

@Test
fun testReconstructOriginalMessage() {
val authenticatorData = Numeric.hexStringToByteArray("0x1a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e071d00000000")
val clientDataJSON = Numeric.hexStringToByteArray("0x7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a224e5549794f5545774d6b45744e554535517930304d6b5a424c546847516a4174517a52474f4441794d3045304f546b30222c226f726967696e223a2268747470733a2f2f747275737477616c6c65742e636f6d227d")
val result = WebAuthn.reconstructOriginalMessage(authenticatorData, clientDataJSON)
assertEquals(Numeric.toHexString(result), "0x3254cdbd677e6e31e75d2135bad0cf56440d7c6b108c141a3509d76ce45c6731")
}
}
1 change: 1 addition & 0 deletions docs/registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ This list is generated from [./registry.json](../registry.json)
| 2301 | Qtum | QTUM | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/qtum/info/logo.png" width="32" /> | <https://qtum.org> |
| 2718 | Nebulas | NAS | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/nebulas/info/logo.png" width="32" /> | <https://nebulas.io> |
| 3030 | Hedera | HBAR | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/hedera/info/logo.png" width="32" /> | <https://hedera.com/> |
| 5611 | OpBNB testnet | tBNB | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/opbnb/info/logo.png" width="32" /> | <https://opbnb.bnbchain.org/en> |
| 6060 | GoChain | GO | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/gochain/info/logo.png" width="32" /> | <https://gochain.io> |
| 8964 | NULS | NULS | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/nuls/info/logo.png" width="32" /> | <https://nuls.io> |
| 14001 | WAX | WAXP | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/wax/info/logo.png" width="32" /> | <http://wax.io> |
Expand Down
6 changes: 6 additions & 0 deletions include/TrustWalletCore/TWCardano.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,10 @@ TWString *_Nullable TWCardanoOutputMinAdaAmount(TWString *_Nonnull toAddress, TW
TW_EXPORT_STATIC_METHOD
TWString *_Nonnull TWCardanoGetStakingAddress(TWString *_Nonnull baseAddress) TW_VISIBILITY_DEFAULT;

/// Return the legacy(byron) address.
/// \param publicKey A valid public key with TWPublicKeyTypeED25519Cardano type.
/// \return the legacy(byron) address, as string, or empty string on error.
TW_EXPORT_STATIC_METHOD
TWString *_Nonnull TWCardanoGetByronAddress(struct TWPublicKey *_Nonnull publicKey);

TW_EXTERN_C_END
1 change: 1 addition & 0 deletions include/TrustWalletCore/TWCoinType.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ enum TWCoinType {
TWCoinTypeConfluxeSpace = 1030,
TWCoinTypeAcala = 787,
TWCoinTypeAcalaEVM = 10000787,
TWCoinTypeOpBNBtestnet = 5611,
};

/// Returns the blockchain for a coin type.
Expand Down
15 changes: 15 additions & 0 deletions include/TrustWalletCore/TWWebAuthn.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,19 @@ struct TWWebAuthn;
/// \return Public key.
TW_EXPORT_STATIC_METHOD
struct TWPublicKey *_Nullable TWWebAuthnGetPublicKey(TWData *_Nonnull attestationObject);

/// Uses ASN parser to extract r and s values from a webauthn signature
///
/// \param signature ASN encoded webauthn signature: https://www.w3.org/TR/webauthn-2/#sctn-signature-attestation-types
/// \return Concatenated r and s values.
TW_EXPORT_STATIC_METHOD
TWData *_Nonnull TWWebAuthnGetRSValues(TWData *_Nonnull signature);

/// Reconstructs the original message that was signed via P256 curve. Can be used for signature validation.
///
/// \param authenticatorData Authenticator Data: https://www.w3.org/TR/webauthn-2/#authenticator-data
/// \param clientDataJSON clientDataJSON: https://www.w3.org/TR/webauthn-2/#dom-authenticatorresponse-clientdatajson
/// \return original messages.
TW_EXPORT_STATIC_METHOD
TWData *_Nonnull TWWebAuthnReconstructOriginalMessage(TWData* _Nonnull authenticatorData, TWData* _Nonnull clientDataJSON);
TW_EXTERN_C_END
30 changes: 30 additions & 0 deletions registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -3960,6 +3960,36 @@
"documentation": "https://doc.confluxnetwork.org/docs/espace"
}
},
{
"id": "opbnb",
"name": "OpBNB testnet",
"coinId": 5611,
"chainId": "5611",
"symbol": "tBNB",
"decimals": 18,
"blockchain": "Ethereum",
"derivation": [
{
"path": "m/44'/60'/0'/0/0"
}
],
"curve": "secp256k1",
"publicKeyType": "secp256k1Extended",
"addressHasher": "keccak256",
"explorer": {
"url": "https://opbnbscan.com",
"txPath": "/tx/",
"accountPath": "/address/",
"sampleTx": "0x788ea8fb4a82dae957f1d3b18af3cd0bbde55a276e66bd17af8c869f24c03a0f",
"sampleAccount": "0x4eaf936c172b5e5511959167e8ab4f7031113ca3"
},
"info": {
"url": "https://opbnb.bnbchain.org/en",
"source": "https://github.com/bnb-chain/opbnb",
"rpc": "https://opbnb-testnet-rpc.bnbchain.org",
"documentation": "https://docs.bnbchain.org/opbnb-docs"
}
},
{
"id": "stratis",
"name": "Stratis",
Expand Down
12 changes: 6 additions & 6 deletions src/Cardano/AddressV2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,26 @@ bool AddressV2::parseAndCheck(const std::string& addr, Data& root_out, Data& att
// Decode Bas58, decode payload + crc, decode root, attr
Data base58decoded = Base58::decode(addr);
if (base58decoded.empty()) {
throw std::invalid_argument("Invalid address: could not Base58 decode");
return false;
}
auto elems = Cbor::Decode(base58decoded).getArrayElements();
if (elems.size() < 2) {
throw std::invalid_argument("Could not parse address payload from CBOR data");
return false;
}
auto tag = elems[0].getTagValue();
if (tag != PayloadTag) {
throw std::invalid_argument("wrong tag value");
return false;
}
Data payload = elems[0].getTagElement().getBytes();
uint64_t crcPresent = (uint32_t)elems[1].getValue();
uint32_t crcComputed = TW::Crc::crc32(payload);
if (crcPresent != crcComputed) {
throw std::invalid_argument("CRC mismatch");
return false;
}
// parse payload, 3 elements
auto payloadElems = Cbor::Decode(payload).getArrayElements();
if (payloadElems.size() < 3) {
throw std::invalid_argument("Could not parse address root and attrs from CBOR data");
return false;
}
root_out = payloadElems[0].getBytes();
attrs_out = payloadElems[1].encoded(); // map, but encoded as bytes
Expand Down Expand Up @@ -105,7 +105,7 @@ std::string AddressV2::string() const {

Data AddressV2::keyHash(const TW::Data& xpub) {
if (xpub.size() != 64) {
throw std::invalid_argument("invalid xpub length");
return {};
}
// hash of following Cbor-array: [0, [0, xpub], {} ]
// 3rd entry map is empty map for V2, contains derivation path for V1
Expand Down
75 changes: 59 additions & 16 deletions src/Cardano/Signer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ Common::Proto::SigningError Signer::assembleSignatures(std::vector<std::pair<Dat
const auto address = AddressV3(publicKey);
privateKeys[address.string()] = privateKeyData;

const auto legacyAddress = AddressV2(publicKey);
privateKeys[legacyAddress.string()] = privateKeyData;

// Also add the derived staking private key (the 2nd half) and associated address; because staking keys also need signature
const auto stakingPrivKeyData = deriveStakingPrivateKey(privateKeyData);
if (!stakingPrivKeyData.empty()) {
Expand All @@ -131,7 +134,7 @@ Common::Proto::SigningError Signer::assembleSignatures(std::vector<std::pair<Dat
// collect every unique input UTXO address, preserving order
std::vector<std::string> addresses;
for (auto& u : plan.utxos) {
if (!AddressV3::isValid(u.address)) {
if (!AddressV3::isValidLegacy(u.address)) {
return Common::Proto::Error_invalid_address;
}
addresses.emplace_back(u.address);
Expand Down Expand Up @@ -174,31 +177,53 @@ Common::Proto::SigningError Signer::assembleSignatures(std::vector<std::pair<Dat
const auto privateKey = PrivateKey(privateKeyData);
const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano);
const auto signature = privateKey.sign(txId, TWCurveED25519ExtendedCardano);
// public key (first 32 bytes) and signature (64 bytes)
signatures.emplace_back(subData(publicKey.bytes, 0, 32), signature);
signatures.emplace_back(publicKey.bytes, signature);
}

return Common::Proto::OK;
}

Cbor::Encode cborizeSignatures(const std::vector<std::pair<Data, Data>>& signatures) {
Cbor::Encode cborizeSignatures(const std::vector<std::pair<Data, Data>>& signatures, const bool addByronSignatures) {
std::map<Cbor::Encode, Cbor::Encode> cborizeSigs;
// signatures as Cbor
// clang-format off
std::vector<Cbor::Encode> sigsCbor;
std::vector<Cbor::Encode> sigsShelly;
std::vector<Cbor::Encode> sigsByron;

for (auto& s : signatures) {
sigsCbor.emplace_back(Cbor::Encode::array({
Cbor::Encode::bytes(s.first),
sigsShelly.emplace_back(Cbor::Encode::array({
// public key (first 32 bytes)
Cbor::Encode::bytes(subData(s.first, 0, 32)),
Cbor::Encode::bytes(s.second)
}));

if (addByronSignatures) {
sigsByron.emplace_back(Cbor::Encode::array({
// skey - public key (first 32 bytes)
Cbor::Encode::bytes(subData(s.first, 0, 32)),
Cbor::Encode::bytes(s.second),
// vkey - public key (second 32 bytes started from 32)
Cbor::Encode::bytes(subData(s.first, 32, 32)),
// payload
Cbor::Encode::bytes(parse_hex("A0"))
}));
}
}

cborizeSigs.emplace(
Cbor::Encode::uint(0),
Cbor::Encode::array(sigsShelly)
);

if (!sigsByron.empty()) {
cborizeSigs.emplace(
Cbor::Encode::uint(2),
Cbor::Encode::array(sigsByron)
);
}

// Cbor-encode txAux & signatures
return Cbor::Encode::map({
std::make_pair(
Cbor::Encode::uint(0),
Cbor::Encode::array(sigsCbor)
)
});
return Cbor::Encode::map(cborizeSigs);
// clang-format on
}

Expand Down Expand Up @@ -242,7 +267,16 @@ Common::Proto::SigningError Signer::encodeTransaction(Data& encoded, Data& txId,
if (sigError != Common::Proto::OK) {
return sigError;
}
const auto sigsCbor = cborizeSignatures(signatures);

bool hasLegacyUtxos = false;
for (const auto& utxo : input.utxos()) {
if (AddressV2::isValid(utxo.address())) {
hasLegacyUtxos = true;
break;
}
}

const auto sigsCbor = cborizeSignatures(signatures, hasLegacyUtxos);

// Cbor-encode txAux & signatures
const auto cbor = Cbor::Encode::array({
Expand Down Expand Up @@ -557,8 +591,17 @@ Data Signer::encodeTransactionWithSig(const Proto::SigningInput &input, const Pu
}

std::vector<std::pair<Data, Data>> signatures;
signatures.emplace_back(subData(publicKey.bytes, 0, 32), signature);
const auto sigsCbor = cborizeSignatures(signatures);
signatures.emplace_back(publicKey.bytes, signature);

bool hasLegacyUtxos = false;
for (const auto& utxo : input.utxos()) {
if (AddressV2::isValid(utxo.address())) {
hasLegacyUtxos = true;
break;
}
}

const auto sigsCbor = cborizeSignatures(signatures, hasLegacyUtxos);

// Cbor-encode txAux & signatures
const auto cbor = Cbor::Encode::array({
Expand Down
5 changes: 1 addition & 4 deletions src/Ethereum/Barz.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,11 @@ using ParamCollection = std::vector<ParamBasePtr>;

std::string getCounterfactualAddress(const Proto::ContractAddressInput input) {
auto params = Ethereum::ABI::ParamTuple();
params.addParam(std::make_shared<Ethereum::ABI::ParamAddress>(parse_hex(input.diamond_cut_facet())));
params.addParam(std::make_shared<Ethereum::ABI::ParamAddress>(parse_hex(input.account_facet())));
params.addParam(std::make_shared<Ethereum::ABI::ParamAddress>(parse_hex(input.verification_facet())));
params.addParam(std::make_shared<Ethereum::ABI::ParamAddress>(parse_hex(input.token_receiver_facet())));
params.addParam(std::make_shared<Ethereum::ABI::ParamAddress>(parse_hex(input.diamond_loupe_facet())));
params.addParam(std::make_shared<Ethereum::ABI::ParamAddress>(parse_hex(input.entry_point())));
params.addParam(std::make_shared<Ethereum::ABI::ParamAddress>(parse_hex(input.diamond_init())));
params.addParam(std::make_shared<Ethereum::ABI::ParamAddress>(parse_hex(input.facet_registry())));
params.addParam(std::make_shared<Ethereum::ABI::ParamAddress>(parse_hex(input.default_fallback())));
params.addParam(std::make_shared<Ethereum::ABI::ParamByteArray>(parse_hex(input.public_key())));

Data encoded;
Expand Down
6 changes: 4 additions & 2 deletions src/Polkadot/Extrinsic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,15 @@ Data Extrinsic::encodeStakingCall(const Proto::Staking& staking) const {
Data data;
switch (staking.message_oneof_case()) {
case Proto::Staking::kBond: {
auto address = SS58Address(staking.bond().controller(), network);
auto value = load(staking.bond().value());
auto reward = byte(staking.bond().reward_destination());
// call index
append(data, getCallIndex(staking.bond().call_indices(), network, stakingBond));
// controller
append(data, encodeAccountId(address.keyBytes(), encodeRawAccount()));
if (!staking.bond().controller().empty()) {
auto controller = SS58Address(staking.bond().controller(), network);
append(data, encodeAccountId(controller.keyBytes(), encodeRawAccount()));
}
// value
append(data, encodeCompact(value));
// reward destination
Expand Down
Loading

0 comments on commit c2af9e4

Please sign in to comment.