Вопрос относительно автоматического получения адресов с использованием 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 сработало отлично.
Это все. :)