Add support for ERC-721 and ERC-1155 (v3) (#218)
* First draft for erc721 token allowance * Split ui and provide parameters into their own files * Print txtype when not supported * fix compilation for erc721 * Use pluginType * Add debug statement in compound plugin * add debug error msg in plugin error * Add parameter parsing for all methods * Remove debug logs * Add SET_APPROVAL_FOR_ALL; Add correct parsing method on contract init * Add dst_size parameter to copy functions * Add query contract id code * format * Add UIs * update ethapp.asc * Change setExternalPlugin to setPlugin; Add support for ERC721 * clang-format * Fix typo Unconsistent -> Inconsistent * Add support for 721; use extraInfo * Add extraInfo to ethpluginQueryConractUI * Rename extraInfo to item * Add txFromEtherscan to tests * Add nft key and temp padding * Remove comments around HAVE_BYPASS_SIGNATURES * Rename TESTING_KEY to NFT_TESTING_KEY * Add comments regarding value of queryContractUI->item * Fix comment regarding method selector * Rename provideToken to provideInfo; Update plugin doc * fix caps of eth_plugin_prepare_provide_info * fix caps of handle_provide_info * Use verificationFn insead of hardcoded cx_ecdsa_verify * Add comments about nftInfo_t and tokenDefinition_t * Add erc721 test * Remove comment from plugin interface version * Fix network_ticker duplicate * Add setPlugin and provideNFTInfo to doc.asc * Add back setExternalPlugin; implement new setPlugin * Update plugin sdk * Call setPlugin instead of setExternalPlugin * setPlugin work without checking sig * Remove printf of displayed fees * Add working 721 test * Finalize ERC721 and add simple test * Display NFT address on set approval and operator * Support set approval for all for erc721 * Finish UI for set approval for all erc721 * Move copy_parameter and copy_address to eth_plugin_internal; Add tests for erc721 * update plugin sdk * Add erc1155 plugin and 1155 tests placeholder * Add restriction for AWS key and setPlugin * Add NOT_OLD_INTERNAL variant; Add erc_1155_plugin_call * Fixed compilation warnings (function pointer casting) Co-authored-by: pscott <scott.piriou@ledger.fr>
This commit is contained in:
294
src_features/setPlugin/cmd_setPlugin.c
Normal file
294
src_features/setPlugin/cmd_setPlugin.c
Normal file
@@ -0,0 +1,294 @@
|
||||
#include "shared_context.h"
|
||||
#include "apdu_constants.h"
|
||||
#include "ui_flow.h"
|
||||
#include "tokens.h"
|
||||
#include "eth_plugin_interface.h"
|
||||
#include "eth_plugin_internal.h"
|
||||
|
||||
// Supported internal plugins
|
||||
#define ERC721_STR "ERC721"
|
||||
#define ERC1155_STR "ERC1155"
|
||||
|
||||
#define TYPE_SIZE 1
|
||||
#define VERSION_SIZE 1
|
||||
#define PLUGIN_NAME_LENGTH_SIZE 1
|
||||
#define CHAIN_ID_SIZE 8
|
||||
#define KEY_ID_SIZE 1
|
||||
#define ALGORITHM_ID_SIZE 1
|
||||
#define SIGNATURE_LENGTH_SIZE 1
|
||||
|
||||
#define HEADER_SIZE TYPE_SIZE + VERSION_SIZE + PLUGIN_NAME_LENGTH_SIZE
|
||||
|
||||
#define MIN_DER_SIG_SIZE 67
|
||||
#define MAX_DER_SIG_SIZE 72
|
||||
|
||||
typedef enum Type {
|
||||
ETH_PLUGIN = 0x01,
|
||||
} Type;
|
||||
|
||||
typedef enum Version {
|
||||
VERSION_1 = 0x01,
|
||||
} Version;
|
||||
|
||||
typedef enum KeyId {
|
||||
TEST_KEY = 0x00,
|
||||
PERSO_V2_KEY_1 = 0x01,
|
||||
// Must ONLY be used with ERC721 and ERC1155 plugin
|
||||
AWS_PLUGIN_KEY_1 = 0x02,
|
||||
} KeyId;
|
||||
|
||||
// Algorithm Id consists of a Key spec and an algorithm spec.
|
||||
// Format is: KEYSPEC__ALGOSPEC
|
||||
typedef enum AlgorithmID {
|
||||
ECC_SECG_P256K1__ECDSA_SHA_256 = 0x01,
|
||||
} AlgorithmID;
|
||||
|
||||
#ifdef HAVE_NFT_TESTING_KEY
|
||||
static const uint8_t LEDGER_TESTING_KEY[] = {
|
||||
0x04, 0xf5, 0x70, 0x0c, 0xa1, 0xe8, 0x74, 0x24, 0xc7, 0xc7, 0xd1, 0x19, 0xe7,
|
||||
0xe3, 0xc1, 0x89, 0xb1, 0x62, 0x50, 0x94, 0xdb, 0x6e, 0xa0, 0x40, 0x87, 0xc8,
|
||||
0x30, 0x00, 0x7d, 0x0b, 0x46, 0x9a, 0x53, 0x11, 0xee, 0x6a, 0x1a, 0xcd, 0x1d,
|
||||
0xa5, 0xaa, 0xb0, 0xf5, 0xc6, 0xdf, 0x13, 0x15, 0x8d, 0x28, 0xcc, 0x12, 0xd1,
|
||||
0xdd, 0xa6, 0xec, 0xe9, 0x46, 0xb8, 0x9d, 0x5c, 0x05, 0x49, 0x92, 0x59, 0xc4};
|
||||
#endif
|
||||
static const uint8_t LEDGER_PERSO_V2_PUBLIC_KEY[] = {};
|
||||
|
||||
// Only used for signing NFT plugins (ERC721 and ERC1155)
|
||||
static const uint8_t LEDGER_NFT_SELECTOR_PUBLIC_KEY[] = {};
|
||||
|
||||
// Verification function used to verify the signature
|
||||
typedef bool verificationAlgo(const cx_ecfp_public_key_t *,
|
||||
int,
|
||||
cx_md_t,
|
||||
const unsigned char *,
|
||||
unsigned int,
|
||||
unsigned char *,
|
||||
unsigned int);
|
||||
|
||||
// Returns the plugin type of a given plugin name.
|
||||
// If the plugin name is not a specific known internal plugin, this function default return value is
|
||||
// `EXERNAL`.
|
||||
static pluginType_t getPluginType(char *pluginName, uint8_t pluginNameLength) {
|
||||
if (pluginNameLength == sizeof(ERC721_STR) - 1 &&
|
||||
strncmp(pluginName, ERC721_STR, pluginNameLength) == 0) {
|
||||
return ERC721;
|
||||
} else if (pluginNameLength == sizeof(ERC1155_STR) - 1 &&
|
||||
strncmp(pluginName, ERC1155_STR, pluginNameLength) == 0) {
|
||||
return ERC1155;
|
||||
} else {
|
||||
return EXTERNAL;
|
||||
}
|
||||
}
|
||||
|
||||
void handleSetPlugin(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *workBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx) {
|
||||
UNUSED(p1);
|
||||
UNUSED(p2);
|
||||
UNUSED(flags);
|
||||
PRINTF("Handling set Plugin\n");
|
||||
uint8_t hash[INT256_LENGTH] = {0};
|
||||
cx_ecfp_public_key_t pluginKey = {0};
|
||||
tokenContext_t *tokenContext = &dataContext.tokenContext;
|
||||
|
||||
uint8_t offset = 0;
|
||||
|
||||
if (dataLength <= HEADER_SIZE) {
|
||||
PRINTF("Data too small for headers: expected at least %d, got %d\n",
|
||||
HEADER_SIZE,
|
||||
dataLength);
|
||||
THROW(0x6A80);
|
||||
}
|
||||
|
||||
enum Type type = workBuffer[offset];
|
||||
PRINTF("Type: %d\n", type);
|
||||
switch (type) {
|
||||
case ETH_PLUGIN:
|
||||
break;
|
||||
default:
|
||||
PRINTF("Unsupported type %d\n", type);
|
||||
THROW(0x6a80);
|
||||
break;
|
||||
}
|
||||
offset += TYPE_SIZE;
|
||||
|
||||
uint8_t version = workBuffer[offset];
|
||||
PRINTF("version: %d\n", version);
|
||||
switch (version) {
|
||||
case VERSION_1:
|
||||
break;
|
||||
default:
|
||||
PRINTF("Unsupported version %d\n", version);
|
||||
THROW(0x6a80);
|
||||
break;
|
||||
}
|
||||
offset += VERSION_SIZE;
|
||||
|
||||
uint8_t pluginNameLength = workBuffer[offset];
|
||||
offset += PLUGIN_NAME_LENGTH_SIZE;
|
||||
|
||||
// Size of the payload (everything except the signature)
|
||||
uint8_t payloadSize = HEADER_SIZE + pluginNameLength + ADDRESS_LENGTH + SELECTOR_SIZE +
|
||||
CHAIN_ID_SIZE + KEY_ID_SIZE + ALGORITHM_ID_SIZE;
|
||||
if (dataLength < payloadSize) {
|
||||
PRINTF("Data too small for payload: expected at least %d, got %d\n",
|
||||
payloadSize,
|
||||
dataLength);
|
||||
THROW(0x6A80);
|
||||
}
|
||||
|
||||
// `+ 1` because we want to add a null terminating character.
|
||||
if (pluginNameLength + 1 > sizeof(tokenContext->pluginName)) {
|
||||
PRINTF("plugin name too big: expected max %d, got %d\n",
|
||||
sizeof(dataContext.tokenContext.pluginName),
|
||||
pluginNameLength + 1);
|
||||
THROW(0x6A80);
|
||||
}
|
||||
|
||||
// Safe because we've checked the size before.
|
||||
memcpy(tokenContext->pluginName, workBuffer + offset, pluginNameLength);
|
||||
tokenContext->pluginName[pluginNameLength] = '\0';
|
||||
|
||||
PRINTF("Length: %d\n", pluginNameLength);
|
||||
PRINTF("plugin name: %s\n", tokenContext->pluginName);
|
||||
offset += pluginNameLength;
|
||||
|
||||
memcpy(tokenContext->contractAddress, workBuffer + offset, ADDRESS_LENGTH);
|
||||
PRINTF("Address: %.*H\n", ADDRESS_LENGTH, workBuffer + offset);
|
||||
offset += ADDRESS_LENGTH;
|
||||
|
||||
memcpy(tokenContext->methodSelector, workBuffer + offset, SELECTOR_SIZE);
|
||||
PRINTF("Selector: %.*H\n", SELECTOR_SIZE, tokenContext->methodSelector);
|
||||
offset += SELECTOR_SIZE;
|
||||
|
||||
// TODO: store chainID and assert that tx is using the same chainid.
|
||||
// uint64_t chainid = u64_from_BE(workBuffer + offset, CHAIN_ID_SIZE);
|
||||
// PRINTF("ChainID: %.*H\n", sizeof(chainid), &chainid);
|
||||
offset += CHAIN_ID_SIZE;
|
||||
|
||||
enum KeyId keyId = workBuffer[offset];
|
||||
uint8_t const *rawKey;
|
||||
uint8_t rawKeyLen;
|
||||
|
||||
PRINTF("KeyID: %d\n", keyId);
|
||||
switch (keyId) {
|
||||
#ifdef HAVE_NFT_TESTING_KEY
|
||||
case TEST_KEY:
|
||||
rawKey = LEDGER_TESTING_KEY;
|
||||
rawKeyLen = sizeof(LEDGER_TESTING_KEY);
|
||||
break;
|
||||
#endif
|
||||
case PERSO_V2_KEY_1:
|
||||
rawKey = LEDGER_PERSO_V2_PUBLIC_KEY;
|
||||
rawKeyLen = sizeof(LEDGER_PERSO_V2_PUBLIC_KEY);
|
||||
break;
|
||||
case AWS_PLUGIN_KEY_1:
|
||||
rawKey = LEDGER_NFT_SELECTOR_PUBLIC_KEY;
|
||||
rawKeyLen = sizeof(LEDGER_NFT_SELECTOR_PUBLIC_KEY);
|
||||
break;
|
||||
default:
|
||||
PRINTF("KeyID %d not supported\n", keyId);
|
||||
THROW(0x6A80);
|
||||
break;
|
||||
}
|
||||
|
||||
PRINTF("RawKey: %.*H\n", rawKeyLen, rawKey);
|
||||
offset += KEY_ID_SIZE;
|
||||
|
||||
uint8_t algorithmId = workBuffer[offset];
|
||||
PRINTF("Algorithm: %d\n", algorithmId);
|
||||
cx_curve_t curve;
|
||||
verificationAlgo *verificationFn;
|
||||
cx_md_t hashId;
|
||||
|
||||
switch (algorithmId) {
|
||||
case ECC_SECG_P256K1__ECDSA_SHA_256:
|
||||
curve = CX_CURVE_256K1;
|
||||
verificationFn = (verificationAlgo*)cx_ecdsa_verify;
|
||||
hashId = CX_SHA256;
|
||||
break;
|
||||
default:
|
||||
PRINTF("Incorrect algorithmId %d\n", algorithmId);
|
||||
THROW(0x6a80);
|
||||
break;
|
||||
}
|
||||
offset += ALGORITHM_ID_SIZE;
|
||||
PRINTF("hashing: %.*H\n", payloadSize, workBuffer);
|
||||
cx_hash_sha256(workBuffer, payloadSize, hash, sizeof(hash));
|
||||
|
||||
if (dataLength < payloadSize + SIGNATURE_LENGTH_SIZE) {
|
||||
PRINTF("Data too short to hold signature length\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
|
||||
uint8_t signatureLen = workBuffer[offset];
|
||||
PRINTF("Sigature len: %d\n", signatureLen);
|
||||
if (signatureLen < MIN_DER_SIG_SIZE || signatureLen > MAX_DER_SIG_SIZE) {
|
||||
PRINTF("SignatureLen too big or too small. Must be between %d and %d, got %d\n",
|
||||
MIN_DER_SIG_SIZE,
|
||||
MAX_DER_SIG_SIZE,
|
||||
signatureLen);
|
||||
THROW(0x6a80);
|
||||
}
|
||||
offset += SIGNATURE_LENGTH_SIZE;
|
||||
|
||||
if (dataLength < payloadSize + SIGNATURE_LENGTH_SIZE + signatureLen) {
|
||||
PRINTF("Signature could not fit in data\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
|
||||
cx_ecfp_init_public_key(curve, rawKey, rawKeyLen, &pluginKey);
|
||||
if (!verificationFn(&pluginKey,
|
||||
CX_LAST,
|
||||
hashId,
|
||||
hash,
|
||||
sizeof(hash),
|
||||
workBuffer + offset,
|
||||
signatureLen)) {
|
||||
#ifndef HAVE_BYPASS_SIGNATURES
|
||||
PRINTF("Invalid NFT signature\n");
|
||||
THROW(0x6A80);
|
||||
#endif
|
||||
}
|
||||
|
||||
pluginType = getPluginType(tokenContext->pluginName, pluginNameLength);
|
||||
if (keyId == AWS_PLUGIN_KEY_1) {
|
||||
if (pluginType != ERC721 && pluginType != ERC1155) {
|
||||
PRINTF("AWS key must only be used to set NFT internal plugins\n");
|
||||
THROW(0x6A80);
|
||||
}
|
||||
}
|
||||
|
||||
switch (pluginType) {
|
||||
case EXTERNAL: {
|
||||
PRINTF("Check external plugin %s\n", tokenContext->pluginName);
|
||||
|
||||
// Check if the plugin is present on the device
|
||||
uint32_t params[2];
|
||||
params[0] = (uint32_t) tokenContext->pluginName;
|
||||
params[1] = ETH_PLUGIN_CHECK_PRESENCE;
|
||||
BEGIN_TRY {
|
||||
TRY {
|
||||
os_lib_call(params);
|
||||
}
|
||||
CATCH_OTHER(e) {
|
||||
PRINTF("%s external plugin is not present\n", tokenContext->pluginName);
|
||||
memset(tokenContext->pluginName, 0, sizeof(tokenContext->pluginName));
|
||||
THROW(0x6984);
|
||||
}
|
||||
FINALLY {
|
||||
}
|
||||
}
|
||||
END_TRY;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
G_io_apdu_buffer[(*tx)++] = 0x90;
|
||||
G_io_apdu_buffer[(*tx)++] = 0x00;
|
||||
}
|
||||
Reference in New Issue
Block a user