Использование zCash через BitcoinJ
Использование zCash с BitcoinJ кажется мне разумным шагом вперед, так как мне нужно отслеживать различные валюты для академического проекта. В настоящее время я запускаю полный узел с zCash вместе с BitcoinJ для Bitcoin и Litecoin, используя параметры сети от разработчиков Dogecoin.
Из-за обстоятельств, что zCash использует много биткойн-кода, я думаю, что это может быть совместимо, но, к сожалению, я не могу заставить его работать в одиночку.
Вот мой подход к ZcashMainNetParams (много чего он использовал из класса MaincoParams BitcoinJ вместе с zCashs chainparams.cpp):
public class ZcashMainNetParams extends AbstractZcashMainNetParams {
public static final int MAINNET_MAJORITY_WINDOW = 1000;
public static final int MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED = 950;
public static final int MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE = 750;
public ZcashMainNetParams() {
super();
interval = INTERVAL;
targetTimespan = TARGET_TIMESPAN;
maxTarget = Utils.decodeCompactBits(0x1d00ffffL);
dumpedPrivateKeyHeader = 128;
addressHeader = 0;
p2shHeader = 5;
acceptableAddressCodes = new int[] { addressHeader, p2shHeader };
port = 8233;
packetMagic = 0xf9beb4d9 /* netmagic same as BTC ??? */;
bip32HeaderPub = 0x0488B21E; //The 4 byte header that serializes in base58 to "xpub".
bip32HeaderPriv = 0x0488ADE4; //The 4 byte header that serializes in base58 to "xprv"
majorityEnforceBlockUpgrade = MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE;
majorityRejectBlockOutdated = MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED;
majorityWindow = MAINNET_MAJORITY_WINDOW;
genesisBlock = createGenesis(this);
id = ID_MAINNET;
subsidyDecreaseBlockCount = 210000;
spendableCoinbaseDepth = 100;
String genesisHash = genesisBlock.getHashAsString();
checkState(genesisHash.equals("00040fe8ec8471911baa1db1266ea15dd06b4a8a5c453883c000b031973dce08"),
genesisHash); /* updated */
// This contains (at a minimum) the blocks which are not BIP30 compliant. BIP30 changed how duplicate
// transactions are handled. Duplicated transactions could occur in the case where a coinbase had the same
// extraNonce and the same outputs but appeared at different heights, and greatly complicated re-org handling.
// Having these here simplifies block connection logic considerably.
checkpoints.put(2500, Sha256Hash.wrap("00000006dc968f600be11a86cbfbf7feb61c7577f45caced2e82b6d261d19744")) /* updated */;
checkpoints.put(15000, Sha256Hash.wrap("00000000b6bc56656812a5b8dcad69d6ad4446dec23b5ec456c18641fb5381ba")) /* updated */;
checkpoints.put(67500, Sha256Hash.wrap("000000006b366d2c1649a6ebb4787ac2b39c422f451880bc922e3a6fbd723616")) /* updated */;
dnsSeeds = new String[] {
"dnsseed.z.cash", // Zcash
"dnsseed.str4d.xyz", // @str4d
"dnsseed.znodes.org" // @bitcartel
};
}
private static AltcoinBlock createGenesis(NetworkParameters params) {
AltcoinBlock genesisBlock = new AltcoinBlock(params, 4L);
Transaction t = new Transaction(params);
try {
byte[] bytes = Hex.decode
("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f");
t.addInput(new TransactionInput(params, t, bytes));
ByteArrayOutputStream scriptPubKeyBytes = new ByteArrayOutputStream();
Script.writeBytes(scriptPubKeyBytes, Hex.decode
("000a889f00854b8665cd555f4656f68179d31ccadc1b1f7fb0952726313b16941da348284d67add4686121d4e3d930160c1348d8191c25f12b267a6a9c131b5031cbf8af1f79c9d513076a216ec87ed045fa966e01214ed83ca02dc1797270a454720d3206ac7d931a0a680c5c5e099057592570ca9bdf6058343958b31901fce1a15a4f38fd347750912e14004c73dfe588b903b6c03166582eeaf30529b14072a7b3079e3a684601b9b3024054201f7440b0ee9eb1a7120ff43f713735494aa27b1f8bab60d7f398bca14f6abb2adbf29b04099121438a7974b078a11635b594e9170f1086140b4173822dd697894483e1c6b4e8b8dcd5cb12ca4903bc61e108871d4d915a9093c18ac9b02b6716ce1013ca2c1174e319c1a570215bc9ab5f7564765f7be20524dc3fdf8aa356fd94d445e05ab165ad8bb4a0db096c097618c81098f91443c719416d39837af6de85015dca0de89462b1d8386758b2cf8a99e00953b308032ae44c35e05eb71842922eb69797f68813b59caf266cb6c213569ae3280505421a7e3a0a37fdf8e2ea354fc5422816655394a9454bac542a9298f176e211020d63dee6852c40de02267e2fc9d5e1ff2ad9309506f02a1a71a0501b16d0d36f70cdfd8de78116c0c506ee0b8ddfdeb561acadf31746b5a9dd32c21930884397fb1682164cb565cc14e089d66635a32618f7eb05fe05082b8a3fae620571660a6b89886eac53dec109d7cbb6930ca698a168f301a950be152da1be2b9e07516995e20baceebecb5579d7cdbc16d09f3a50cb3c7dffe33f26686d4ff3f8946ee6475e98cf7b3cf9062b6966e838f865ff3de5fb064a37a21da7bb8dfd2501a29e184f207caaba364f36f2329a77515dcb710e29ffbf73e2bbd773fab1f9a6b005567affff605c132e4e4dd69f36bd201005458cfbd2c658701eb2a700251cefd886b1e674ae816d3f719bac64be649c172ba27a4fd55947d95d53ba4cbc73de97b8af5ed4840b659370c556e7376457f51e5ebb66018849923db82c1c9a819f173cccdb8f3324b239609a300018d0fb094adf5bd7cbb3834c69e6d0b3798065c525b20f040e965e1a161af78ff7561cd874f5f1b75aa0bc77f720589e1b810f831eac5073e6dd46d00a2793f70f7427f0f798f2f53a67e615e65d356e66fe40609a958a05edb4c175bcc383ea0530e67ddbe479a898943c6e3074c6fcc252d6014de3a3d292b03f0d88d312fe221be7be7e3c59d07fa0f2f4029e364f1f355c5d01fa53770d0cd76d82bf7e60f6903bc1beb772e6fde4a70be51d9c7e03c8d6d8dfb361a234ba47c470fe630820bbd920715621b9fbedb49fcee165ead0875e6c2b1af16f50b5d6140cc981122fcbcf7c5a4e3772b3661b628e08380abc545957e59f634705b1bbde2f0b4e055a5ec5676d859be77e20962b645e051a880fddb0180b4555789e1f9344a436a84dc5579e2553f1e5fb0a599c137be36cabbed0319831fea3fddf94ddc7971e4bcf02cdc93294a9aab3e3b13e3b058235b4f4ec06ba4ceaa49d675b4ba80716f3bc6976b1fbf9c8bf1f3e3a4dc1cd83ef9cf816667fb94f1e923ff63fef072e6a19321e4812f96cb0ffa864da50ad74deb76917a336f31dce03ed5f0303aad5e6a83634f9fcc371096f8288b8f02ddded5ff1bb9d49331e4a84dbe1543164438fde9ad71dab024779dcdde0b6602b5ae0a6265c14b94edd83b37403f4b78fcd2ed555b596402c28ee81d87a909c4e8722b30c71ecdd861b05f61f8b1231795c76adba2fdefa451b283a5d527955b9f3de1b9828e7b2e74123dd47062ddcc09b05e7fa13cb2212a6fdbc65d7e852cec463ec6fd929f5b8483cf3052113b13dac91b69f49d1b7d1aec01c4a68e41ce157"));
scriptPubKeyBytes.write(ScriptOpCodes.OP_CHECKSIG);
t.addOutput(new TransactionOutput(params, t, COIN.multiply(50), scriptPubKeyBytes.toByteArray()));
} catch (Exception e) {
// Cannot happen.
throw new RuntimeException(e);
}
genesisBlock.addTransaction(t);
genesisBlock.setTime(1477641360L);
genesisBlock.setDifficultyTarget(504365040L);
genesisBlock.setNonce(0x0000000000000000000000000000000000000000000000000000000000001257L);
return genesisBlock;
}
private static ZcashMainNetParams instance;
public static synchronized ZcashMainNetParams get() {
if (instance == null) {
instance = new ZcashMainNetParams();
}
return instance;
}
@Override
public String getPaymentProtocolId() {
return PAYMENT_PROTOCOL_ID_MAINNET;
}
}
Абстрактный класс также повторно используется разработчиками dogecoin:
public abstract class AbstractZcashMainNetParams extends NetworkParameters {
/**
* Scheme part for Bitcoin URIs.
*/
public static final String BITCOIN_SCHEME = "zcash";
public static final int REWARD_HALVING_INTERVAL = 210000;
private static final Logger log = LoggerFactory.getLogger(AbstractZcashMainNetParams.class);
public AbstractZcashMainNetParams() {
super();
}
/**
* Checks if we are at a reward halving point.
* @param height The height of the previous stored block
* @return If this is a reward halving point
*/
public final boolean isRewardHalvingPoint(final int height) {
return ((height + 1) % REWARD_HALVING_INTERVAL) == 0;
}
/**
* Checks if we are at a difficulty transition point.
* @param height The height of the previous stored block
* @return If this is a difficulty transition point
*/
public final boolean isDifficultyTransitionPoint(final int height) {
return ((height + 1) % this.getInterval()) == 0;
}
@Override
public void checkDifficultyTransitions(final StoredBlock storedPrev, final Block nextBlock,
final BlockStore blockStore) throws VerificationException, BlockStoreException {
final Block prev = storedPrev.getHeader();
// Is this supposed to be a difficulty transition point?
if (!isDifficultyTransitionPoint(storedPrev.getHeight())) {
// No ... so check the difficulty didn't actually change.
if (nextBlock.getDifficultyTarget() != prev.getDifficultyTarget())
throw new VerificationException("Unexpected change in difficulty at height " + storedPrev.getHeight() +
": " + Long.toHexString(nextBlock.getDifficultyTarget()) + " vs " +
Long.toHexString(prev.getDifficultyTarget()));
return;
}
// We need to find a block far back in the chain. It's OK that this is expensive because it only occurs every
// two weeks after the initial block chain download.
final Stopwatch watch = Stopwatch.createStarted();
Sha256Hash hash = prev.getHash();
StoredBlock cursor = null;
final int interval = this.getInterval();
for (int i = 0; i < interval; i++) {
cursor = blockStore.get(hash);
if (cursor == null) {
// This should never happen. If it does, it means we are following an incorrect or busted chain.
throw new VerificationException(
"Difficulty transition point but we did not find a way back to the last transition point. Not found: " + hash);
}
hash = cursor.getHeader().getPrevBlockHash();
}
checkState(cursor != null && isDifficultyTransitionPoint(cursor.getHeight() - 1),
"Didn't arrive at a transition point.");
watch.stop();
if (watch.elapsed(TimeUnit.MILLISECONDS) > 50)
log.info("Difficulty transition traversal took {}", watch);
Block blockIntervalAgo = cursor.getHeader();
int timespan = (int) (prev.getTimeSeconds() - blockIntervalAgo.getTimeSeconds());
// Limit the adjustment step.
final int targetTimespan = this.getTargetTimespan();
if (timespan < targetTimespan / 4)
timespan = targetTimespan / 4;
if (timespan > targetTimespan * 4)
timespan = targetTimespan * 4;
BigInteger newTarget = Utils.decodeCompactBits(prev.getDifficultyTarget());
newTarget = newTarget.multiply(BigInteger.valueOf(timespan));
newTarget = newTarget.divide(BigInteger.valueOf(targetTimespan));
if (newTarget.compareTo(this.getMaxTarget()) > 0) {
log.info("Difficulty hit proof of work limit: {}", newTarget.toString(16));
newTarget = this.getMaxTarget();
}
int accuracyBytes = (int) (nextBlock.getDifficultyTarget() >>> 24) - 3;
long receivedTargetCompact = nextBlock.getDifficultyTarget();
// The calculated difficulty is to a higher precision than received, so reduce here.
BigInteger mask = BigInteger.valueOf(0xFFFFFFL).shiftLeft(accuracyBytes * 8);
newTarget = newTarget.and(mask);
long newTargetCompact = Utils.encodeCompactBits(newTarget);
if (newTargetCompact != receivedTargetCompact)
throw new VerificationException("Network provided difficulty bits do not match what was calculated: " +
Long.toHexString(newTargetCompact) + " vs " + Long.toHexString(receivedTargetCompact));
}
@Override
public Coin getMaxMoney() {
return MAX_MONEY;
}
@Override
public Coin getMinNonDustOutput() {
return Transaction.MIN_NONDUST_OUTPUT;
}
@Override
public MonetaryFormat getMonetaryFormat() {
return new MonetaryFormat();
}
@Override
public int getProtocolVersionNum(final ProtocolVersion version) {
return version.getBitcoinProtocolVersion();
}
@Override
public BitcoinSerializer getSerializer(boolean parseRetain) {
return new BitcoinSerializer(this, parseRetain);
}
@Override
public String getUriScheme() {
return BITCOIN_SCHEME;
}
@Override
public boolean hasMaxMoney() {
return true;
}
}
Моя проблема здесь заключается в генерации блока генезиса. Я не могу подделать это с правильным хешем. Я полагаю, что это связано с транзакцией, которую я пытаюсь понять в chainparams.cpp zCash. Однако мой блок генезиса имеет хэш e88b11fd3581e170f86db9c574f65c0ada3216e126011ac968869f1b64ea4c4a
вместо обязательного00040fe8ec8471911baa1db1266ea15dd06b4a8a5c453883c000b031973dce08
Спасибо за любую помощь, включая разумный вывод, что это невозможно.
1 ответ
tl;dr Ваш код не учитывает формат заголовка блока Zcash, который отличается как от биткойнов, так и от большинства альткойнов. Вам нужно добавить hashReserved
а также nSolution
поля, и сделать nNonce
256 бит вместо 32.
Давайте пока предположим, что все, что вы хотите сделать, это проанализировать блокчейн Zcash. Для этого вам нужно будет сделать два изменения:
- Измените код анализа транзакции для обработки наших транзакций v2 (содержащих JoinSplits).
- Измените заголовок блока, чтобы он соответствовал нашему формату (о чем вы спрашиваете).
Заголовок блока Zcash - это обычная биткойн-сериализация следующего:
int32_t nVersion;
uint256 hashPrevBlock;
uint256 hashMerkleRoot;
uint256 hashReserved;
uint32_t nTime;
uint32_t nBits;
uint256 nNonce;
std::vector<unsigned char> nSolution;
Большинство из них работают так же, как и в биткойнах. Те, которые отличаются от Биткойна и большинства других альткойнов, которые вам нужно будет реализовать в Биткойне:
hashReserved
- простое поле, в настоящее время всегда нулевое (но не предполагайте, что).nNonce
- 256 бит вместо 32, но в остальном имеет ту же функцию.nSolution
- это решение Equihash в заголовке каждого блока. Он сериализуется как вектор (в стиле Биткойн), поэтому вам не нужно технически разбирать его внутренние компоненты; Вы можете просто прочитать поле длины и затем столько байтов (в настоящее время 1344, но не принимайте это).
Реализация вышеуказанного позволит вам проанализировать заголовки блоков Zcash. Если вы хотите иметь возможность проверить их, вам также нужно будет полностью разобрать nSolution
, а затем реализовать валидатор Equihash и алгоритм корректировки сложности.
Для полноты nSolution
это битовая конкатенация индексов решения Equihash, представленная в виде байтового массива. Учитывая параметры Equihash N
а также K
, имеются 2^K
индексы, каждый из длины (N/(K+1))+1
биты. Для текущих параметров (N, K) = (200, 9)
, каждый индекс решения (200/(9+1))+1 = 21
биты и nSolution
содержит 2^9 = 512
индексы. В общей сложности это 10752 бита, или 1344 байта.
Источник: инженер Zcash (я изначально опубликовал это со старого аккаунта SE).