Вопрос относительно автоматического получения адресов с использованием xpub и пути для Zcash

В настоящее время я разрабатываю API для своей компании, который использует xpub и путь для создания адреса.

Дело в том, что мне удалось сделать это для всех необходимых нам криптовалют, кроме Zcash. Мы используем аппаратный кошелек Trezor, и Trezor Connect не работает для нас, поскольку нам нужно, чтобы поток был отключен от самого устройства.

Как правило, я использую библиотеку bitcoinjs для всех других монет, но мне не удалось получить правильный формат адреса для zcash. Потратил довольно много времени на поиски проблем с github, но безуспешно. Слово помощи было бы действительно отличным, спасибо, ребята! Мой код сейчас выглядит так:

xpub = process.env.ZEC_PUB_KEY;
network = {
messagePrefix: '\x18ZCash Signed Message:\n',
bech32: 't1',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4
},
pubKeyHash: 0x1cb8,
scriptHash: 0x1cbd,
wif: 0x80
};
p2wpkh = bjs.payments.p2wpkh({ pubkey: bjs.bip32.fromBase58(xpub, network).derive(0).derive(pathNumber).publicKey, network });
payment = bjs.payments.p2sh({ redeem: p2wpkh, network });
address = payment.address;

3 ответа

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

Во-первых, zcash не совместим с SegWit, поэтому функция p2wpkh для этого не будет работать, вам следует использовать p2pkh. Вызов метода p2sh здесь также избыточен, поэтому код будет выглядеть примерно так:

network = {
messagePrefix: '\x18ZCash Signed Message:\n',
bech32: 't1',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4
},
pubKeyHash: 0x1cb8,
scriptHash: 0x1cbd,
wif: 0x80
};
payment= bjs.payments.p2pkh({ pubkey: bjs.bip32.fromBase58(xpub, network).derive(0).derive(pathNumber).publicKey, network });
address = payment.address;

Кроме того, библиотека bitcoinjs-lib некоторым образом не поддерживает альткойны. В этом случае функция p2pkh использует toUint8 в своем ядре, поэтому вы можете в основном выполнить форк репо и обновить этот метод, чтобы использовать тип данных с более широкими границами, toUint16LE.

После этого функция вернет биткойн-адрес, но нам понадобится zcash. Мы можем легко преобразовать его, используя следующую функцию:

function baddrToTaddr(baddr_str) {
var baddr = bs58check.decode(baddr_str).slice(1);  // discard type byte
var taddr = new Uint8Array(22);
taddr.set(baddr, 2);
taddr.set([0x1c,0xb8], 0);  // set zcash type bytes
return bs58check.encode(Buffer.from(taddr));
}

Вот и все.

Я использовал решение, впервые опубликованное Лашей Ломидзе, и немного доработал его. У меня было требование сохранить пакет по умолчанию таким же, поэтому мне пришлось воссоздать используемую функцию hash160. Затем я смог немного упростить функцию адресного скрипта P2PKH, см. ниже. Обратите внимание, что я использую writeUInt16BE для правильной записи pubKeyHash по сравнению с writeUInt16LE из-за порядка, в котором записываются байты:

      import * as crypto from './utils/crypto'
import * as ecc from 'tiny-secp256k1'
import BIP32Factory from 'bip32'
import { Network } from 'bitcoinjs-lib'


const bs58check = require('bs58check')
const bip32 = BIP32Factory(ecc)

const ZCASH: Network = {
    messagePrefix: '\x18ZCash Signed Message:\n',
    bech32: 't1',
    bip32: {
        public: 0x0488b21e,
        private: 0x0488ade4
    },
    pubKeyHash: 0x1cb8,
    scriptHash: 0x1cbd,
    wif: 0x80
}

function deriveAddr (xpub: string, childNumber: number, changeIndex = 0): string {
    const node = bip32.fromBase58(xpub,ZCASH)
    const derivedKey = node.derive(changeIndex).derive(childNumber)

    return getP2PKHAddress2BytePubKeyHash(derivedKey.publicKey,ZCASH)
}

function getP2PKHAddress2BytePubKeyHash(pubkey: Buffer, network: Network): string{
    // allocate 22 bytes instead of bitcoinlib-js 21
    const payload = Buffer.allocUnsafe(22)
    // write UInt16BE to make use of the network
    // pubKeyHash
    payload.writeUInt16BE(network.pubKeyHash, 0)

    // hash the pubkey and copy starting at position 2
    // because the writeUInt16BE writes to two bytes
    const hashVal = crypto.hash160(pubkey)
    hashVal.copy(payload, 2)

    // return payload with bs58 encoding
    return bs58check.encode(payload)
}

// should derive t1dm4Nvu9QrQk3nRSxosmuyoVms8c2HSpMg
console.log(deriveAddr('xpub6DXNsrgyCs9dEAmjMBSXmZUFajUHHg6d6eT1TxT3egPMb4r4Aka5CLFtdW4eCd6Z3N4P8cHKiuUDeUauz25PfdzFZuvmnjtonUfQoCvoztj', 0, 0))

Приведенные ниже элементы я поместил в файл с именем crypto.ts в каталоге utils и экспортировал функцию hash160 для использования вышеуказанной функцией.

      export {}
const createHash = require('create-hash')

function ripemd160(buffer: Buffer): Buffer {
    try {
      return createHash('rmd160').update(buffer).digest()
    } catch (err) {
      return createHash('ripemd160').update(buffer).digest()
    }
}
function sha256(buffer: Buffer): Buffer {
    return createHash('sha256').update(buffer).digest()
}

export function hash160(buffer: Buffer): Buffer {
    return ripemd160(sha256(buffer))
}

Лаша / Зак, спасибо вам обоим за ваши ответы выше, вы положили конец моему разочарованию, которое длилось пару дней, поскольку я столкнулся с той же проблемой без очевидного ответа. Что касается меня, я пытался заставить некоторый «старый» код работать с новыми библиотеками, которые, как ни странно, не поддерживали это из коробки. В конце концов, простое обновление моего кода для использования функции getP2PKHAddress2BytePubKeyHash сработало отлично.

Это все. :)

Другие вопросы по тегам