Руководство по созданию собственного кошелька

Для обеспечения совместимости кошельков Aptos необходимо следующее:

  1. Мнемоника - набор слов, из которых можно вывести закрытые ключи учетных записей

  2. Dapp API - точки входа в кошелек для поддержки доступа к идентичности, управляемой кошельком

  3. Ротация ключей - обработка отношений вокруг мнемоник и восстановление учетных записей в разных кошельках

Мнемоника

Хотя кошелек Petra рекомендует использовать 1 мнемонику <-> 1 учетную запись, мы понимаем, что некоторые кошельки могут захотеть поддерживать 1 мнемонику <-> n учетных записей, поступающих из других сетей. Чтобы поддержать оба этих случая, мы используем производный путь BIP44 для мнемоник к учетным записям.

Создание учетной записи Aptos

  1. Сгенерируйте мнемонику, используя что-то вроде BIP39

  2. Получите master seed из этой мнемоники с помощью BIP39

  3. Используйте производный путь BIP44 для получения адреса учетной записи (например, m/44'/637'/0'/0'/0').

/**
   * Creates new account with bip44 path and mnemonics,
   * @param path. (e.g. m/44'/637'/0'/0'/0')
   * Detailed description: {@link https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki}
   * @param mnemonics.
   * @returns AptosAccount
   */
  static fromDerivePath(path: string, mnemonics: string): AptosAccount {
    if (!AptosAccount.isValidPath(path)) {
      throw new Error("Invalid derivation path");
    }

    const normalizeMnemonics = mnemonics
      .trim()
      .split(/\s+/)
      .map((part) => part.toLowerCase())
      .join(" ");

    const { key } = derivePath(path, bytesToHex(bip39.mnemonicToSeedSync(normalizeMnemonics)));

    return new AptosAccount(new Uint8Array(key));
  }

Поддержка 1 мнемоники <-> N кошельков учетных записей

Опять же, потому что парадигма 1 мнемоника <-> n учетных записей не очень хорошо подходит для ротации ключей. В настоящее время мы не рекомендуем этот подход. Но для поддержки импорта такого типа учетных записей мы будем следовать этому стандарту.

  1. Аналогично вышеуказанным шагам 1-3

  2. Используйте производный путь BIP44 для получения закрытых ключей (например, m/44'/637'/i'/0'/0'), где i - индекс учетной записи.

  3. Теперь выполним итерацию i, пока не получим все учетные записи, которые пользователь хочет импортировать

    • Мы не хотим итерировать до бесконечности, чтобы проверить, существуют ли учетные записи в сети. Если во время итерации учетная запись не существует, мы будем продолжать итерацию в течение постоянного лимита address_gap_limit (пока 10), чтобы узнать, есть ли другие учетные записи. Если учетная запись найдена, мы продолжим итерацию в обычном режиме.

т.е.

const gapLimit = 10;
let currentGap = 0;

for (let i = 0; currentGap < gapLimit; i += 1) {
    const derivationPath = `m/44'/637'/${i}'/0'/0'`;
    const account = fromDerivePath(derivationPath, mnemonic);
    const response = account.getResources();
    if (response.status !== 404) {
        wallet.addAccount(account);
        currentGap = 0;
    } else {
        currentGap += 1;
    }
}

Dapp API

Сообщение форума с обсуждением Будут некоторые apis, которые могут добавить определенные кошельки, но должно быть несколько apis, которые являются стандартными для всех кошельков. Это облегчит массовое внедрение и облегчит жизнь разработчикам dapp.

  • connect(), disconnect(), и isConnected()

  • account()

  • signAndSubmitTransaction(transaction: EntryFunctionPayload)

  • signMessage(payload: SignMessagePayload)

  • Проверка событий (onAccountChanged(listener), onNetworkChanged(listener))

// Common Args and Responses

interface PublicAccount {
    string address;
    string publicKey;
}

// The important thing to return here is the transaction hash the dapp can wait for it
type [PendingTransaction](https://github.com/aptos-labs/aptos-core/blob/1bc5fd1f5eeaebd2ef291ac741c0f5d6f75ddaef/ecosystem/typescript/sdk/src/generated/models/PendingTransaction.ts)

type [EntryFunctionPayload](https://github.com/aptos-labs/aptos-core/blob/1bc5fd1f5eeaebd2ef291ac741c0f5d6f75ddaef/ecosystem/typescript/sdk/src/generated/models/EntryFunctionPayload.ts)

connect(), disconnect(), isConnected()

Важно, чтобы dapps не могли отправлять запросы в кошелек, пока пользователь не подтвердит, что хочет видеть эти запросы.

  • connect() предложит пользователю

    • возвратить Promise<PublicAccount>

  • disconnect() позволяет пользователю прекратить предоставление доступа к dapp, а также помогает dapp в управлении состоянием

    • возвратить Promise<void>

  • isConnected() возможность делать запросы к кошельку для получения текущего состояния соединения

    • возвратить Promise<boolean>

account()

Должен быть подключен dapp может запросить текущий подключенная учетная запись, чтобы получить адрес или открытый ключ.

  • account() без предложения пользователю

    • возвратить Promise<PublicAccount>

signAndSubmitTransaction(transaction: EntryFunctionPayload)

Мы будем генерировать транзакцию из полезной нагрузки (простой JSON) с помощью sdk, а затем подписывать и отправлять ее на ноду кошелька.

  • signAndSubmitTransaction(transaction: EntryFunctionPayload) предложит пользователю ввести транзакцию, которую он подписывает

    • возвратить Promise<PendingTransaction>

signMessage(payload: SignMessagePayload)

Чаще всего эта функция используется для подтверждения личности, но есть и несколько других возможных вариантов использования. Вы можете заметить, что некоторые кошельки других сетей просто предоставляют интерфейс для подписи произвольных строк. Это может быть чревато атаками типа "man-in-the-middle", подписанием строковых транзакций и т.д.

Типы:

export interface SignMessagePayload {
  address?: boolean; // Should we include the address of the account in the message
  application?: boolean; // Should we include the domain of the dapp
  chainId?: boolean; // Should we include the current chain id the wallet is connected to
  message: string; // The message to be signed and displayed to the user
  nonce: string; // A nonce the dapp should generate
}

export interface SignMessageResponse {
  address: string;
  application: string;
  chainId: number;
  fullMessage: string; // The message that was generated to sign
  message: string; // The message passed in by the user
  nonce: string,
  prefix: string, // Should always be APTOS
  signature: string; // The signed full message
}

  • signMessage(payload: SignMessagePayload) запрашивает у пользователя payload.message должен быть подписан

    • возвратить Promise<SignMessageResponse>

Пример: signMessage({nonce: 1234034, message: "Welcome to dapp!", address: true, application: true, chainId: true })

В результате будет создано полное сообщение (FullMessage), которое будет подписано и возвращено в качестве подписи:

APTOS
address: 0x000001
chain_id: 7
application: badsite.firebase.google.com
nonce: 1234034
message: Welcome to dapp!

Проверка событий (в процессе выполнения)

Ротация ключей (в процессе выполнения)

Отображение было реализовано, но интеграция sdk находится в процессе. Это будет обновлено в ближайшее время.

Last updated