// Rust Elements Library
// Written by
// The Elements developers
//
// To the extent possible under law, the author(s) have dedicated all
// copyright and related and neighboring rights to this software to
// the public domain worldwide. This software is distributed without
// any warranty.
//
// You should have received a copy of the CC0 Public Domain Dedication
// along with this software.
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
//
//! Helpers to calculate the genesis block for a given network.
use bitcoin::secp256k1::impl_array_newtype;
use crate::hashes::{sha256, HashEngine};
use crate::opcodes::all::OP_RETURN;
use crate::opcodes::OP_TRUE;
use crate::{confidential, script, AssetId, Block, BlockExtData, BlockHash, BlockHeader, LockTime, Script, Sequence, Transaction, TxIn, TxInWitness, TxOut, TxOutWitness};
use crate::{AssetBlindingNonce, AssetEntropy, AssetIssuance, ContractHash, OutPoint, Txid};
use crate::confidential::Nonce;
/// Parameters that influence chain consensus. The contents of the genesis block for a given network
/// are defined by these values.
#[derive(Clone, Debug)]
pub struct NetworkParams {
/// The network identifier string that elementsd accepts as the `chain` config argument
pub network_id: String,
/// This network's Fedpeg script
pub fedpeg_script: Script,
/// This network's `sign_block_script`
pub sign_block_script: Script,
/// How many free coins are present in this network
pub initial_free_coins: u64,
}
impl NetworkParams {
/// New custom network params
pub fn new(
network_id: String,
fedpeg_script: Script,
sign_block_script: Script,
initial_free_coins: u64,
) -> NetworkParams {
NetworkParams {
network_id,
fedpeg_script,
sign_block_script,
initial_free_coins,
}
}
/// Network params for Liquid mainnet
pub fn liquidv1() -> Self {
NetworkParams {
network_id: "liquidv1".to_string(),
// Can be verified at https://github.com/ElementsProject/elements/blob/27c2fb6b7de404908f9ef2eb5c98c9989d1ab8e4/src/chainparams.cpp#L1243
fedpeg_script: Script::from_hex_no_prefix("745c87635b21020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b678172612102675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af992102896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d4821029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c2102a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc40102102f8a00b269f8c5e59c67d36db3cdc11b11b21f64b4bffb2815e9100d9aa8daf072103079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b2103111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2210318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa08401742103230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de121035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a62103bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c2103cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d175462103d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d4248282103ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a5f6702c00fb275522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb5368ae").expect("constant fedpeg script parse"),
// Can be verified at https://github.com/ElementsProject/elements/blob/27c2fb6b7de404908f9ef2eb5c98c9989d1ab8e4/src/chainparams.cpp#L1193
sign_block_script: Script::from_hex_no_prefix("5b21026a2a106ec32c8a1e8052e5d02a7b0a150423dbd9b116fc48d46630ff6e6a05b92102791646a8b49c2740352b4495c118d876347bf47d0551c01c4332fdc2df526f1a2102888bda53a424466b0451627df22090143bbf7c060e9eacb1e38426f6b07f2ae12102aee8967150dee220f613de3b239320355a498808084a93eaf39a34dcd62024852102d46e9259d0a0bb2bcbc461a3e68f34adca27b8d08fbe985853992b4b104e27412102e9944e35e5750ab621e098145b8e6cf373c273b7c04747d1aa020be0af40ccd62102f9a9d4b10a6d6c56d8c955c547330c589bb45e774551d46d415e51cd9ad5116321033b421566c124dfde4db9defe4084b7aa4e7f36744758d92806b8f72c2e943309210353dcc6b4cf6ad28aceb7f7b2db92a4bf07ac42d357adf756f3eca790664314b621037f55980af0455e4fb55aad9b85a55068bb6dc4740ea87276dc693f4598db45fa210384001daa88dabd23db878dbb1ce5b4c2a5fa72c3113e3514bf602325d0c37b8e21039056d089f2fe72dbc0a14780b4635b0dc8a1b40b7a59106325dd1bc45cc70493210397ab8ea7b0bf85bc7fc56bb27bf85e75502e94e76a6781c409f3f2ec3d1122192103b00e3b5b77884bf3cae204c4b4eac003601da75f96982ffcb3dcb29c5ee419b92103c1f3c0874cfe34b8131af34699589aacec4093399739ae352e8a46f80a6f68375fae").expect("constant sign_block_script parse"),
initial_free_coins: 0,
}
}
/// Network params for Liquid testnet
pub fn liquidtestnet() -> Self {
NetworkParams {
network_id: "liquidtestnet".to_string(),
fedpeg_script: script::Builder::new().push_opcode(OP_TRUE).into_script(),
// Can be verified at https://github.com/ElementsProject/elements/blob/27c2fb6b7de404908f9ef2eb5c98c9989d1ab8e4/src/chainparams.cpp#L1108
sign_block_script: Script::from_hex_no_prefix("51210217e403ddb181872c32a0cd468c710040b2f53d8cac69f18dad07985ee37e9a7151ae").expect("constant sign_block_script parse"),
initial_free_coins: 2_100_000_000_000_000,
}
}
/// Network params for a custom Elements network with defaults
pub fn custom_network(network_id: String, fedpeg_script: Option<Script>, sign_block_script: Option<Script>, initial_free_coins: Option<u64>) -> Self {
NetworkParams {
network_id,
fedpeg_script: fedpeg_script.unwrap_or_else(|| script::Builder::new().push_opcode(OP_TRUE).into_script()),
sign_block_script: sign_block_script.unwrap_or_else(|| script::Builder::new().push_opcode(OP_TRUE).into_script()),
initial_free_coins: initial_free_coins.unwrap_or(0),
}
}
}
/// Hash commitment of network parameters for a given Network
pub fn commit_to_custom_network_parameters(params: &NetworkParams) -> sha256::Hash {
use hex::{BytesToHexIter, Case};
let mut eng = sha256::Hash::engine();
eng.input(params.network_id.clone().as_bytes());
for ch in BytesToHexIter::new(params.fedpeg_script[..].iter(), Case::Lower).flatten() {
eng.input(&[ch.into()]);
}
for ch in BytesToHexIter::new(params.sign_block_script[..].iter(), Case::Lower).flatten() {
eng.input(&[ch.into()]);
}
sha256::Hash::from_engine(eng)
}
/// Produce the genesis transaction for a given elements Network
fn liquid_genesis_tx(network_params: &NetworkParams) -> Transaction {
let commit = commit_to_custom_network_parameters(network_params);
let input = TxIn {
previous_output: OutPoint::default(),
is_pegin: false,
script_sig: script::Builder::new()
.push_slice(commit.as_byte_array())
.into_script(),
sequence: Sequence::default(),
asset_issuance: AssetIssuance::default(),
witness: TxInWitness::default(),
};
let output = TxOut {
asset: confidential::Asset::Explicit(AssetId::default()),
value: confidential::Value::Explicit(0),
nonce: Nonce::default(),
script_pubkey: script::Builder::new().push_opcode(OP_RETURN).into_script(),
witness: TxOutWitness::default(),
};
Transaction {
version: 1,
lock_time: LockTime::ZERO,
input: vec![input],
output: vec![output],
}
}
/// Create the confidential asset transaction for the genesis block if required by the specified Network
fn liquid_genesis_asset_tx(network_params: &NetworkParams) -> Option<Transaction> {
let commit = commit_to_custom_network_parameters(network_params);
let asset_amount = network_params.initial_free_coins;
if asset_amount == 0 {
return None;
}
let asset_outpoint = OutPoint::new(Txid::from_byte_array(commit.to_byte_array()), 0);
let contract_hash = ContractHash::from_byte_array([0u8; 32]);
let asset_entropy = AssetId::generate_asset_entropy(asset_outpoint, contract_hash);
let asset_id = AssetId::from_entropy(asset_entropy);
let asset_issuance = AssetIssuance {
asset_blinding_nonce: AssetBlindingNonce::NEW_ISSUANCE,
asset_entropy: AssetEntropy::NEW_ISSUANCE,
amount: confidential::Value::Explicit(asset_amount),
inflation_keys: confidential::Value::Explicit(0),
};
let input = TxIn {
previous_output: asset_outpoint,
is_pegin: false,
script_sig: Script::new(),
sequence: Sequence::default(),
asset_issuance,
witness: TxInWitness::default(),
};
let output = TxOut {
asset: confidential::Asset::Explicit(asset_id),
value: confidential::Value::Explicit(asset_amount),
nonce: Nonce::default(),
script_pubkey: script::Builder::new().push_opcode(OP_TRUE).into_script(),
witness: TxOutWitness::default(),
};
let ret = Transaction {
version: 1,
lock_time: LockTime::ZERO,
input: vec![input],
output: vec![output],
};
Some(ret)
}
/// Constructs and returns the Liquid genesis blocks assuming default consensus parameters
/// Does not return upstream bitcoin network blocks as they are a different format and can be
/// acquired from the `rust-bitcoin` library
pub fn genesis_block(params: &NetworkParams) -> Block {
let tx = liquid_genesis_tx(params);
let mut txdata = vec![tx.clone()];
let merkle_root: crate::TxMerkleNode =
if let Some(asset_tx) = liquid_genesis_asset_tx(params) {
// To use `bitcoin::merkle_tree::calculate_root` from bitcoin 0.32, we need a hash
// with fairly specific trait bounds, even though it's just used as a byte array.
// sha256d::Hash from the rust-bitcoin version of bitcoin_hashes works.
use bitcoin::hashes::sha256d::Hash as MerkleHash;
use bitcoin::hashes::Hash as _;
txdata.push(asset_tx.clone());
let tx_hashes = [
MerkleHash::from_byte_array(tx.txid().to_byte_array()),
MerkleHash::from_byte_array(asset_tx.txid().to_byte_array()),
];
let root = bitcoin::merkle_tree::calculate_root(tx_hashes.iter().copied())
.expect("merkle root");
crate::TxMerkleNode::from_byte_array(root.to_byte_array())
} else {
crate::TxMerkleNode::from_byte_array(tx.txid().to_byte_array())
};
Block {
header: BlockHeader {
version: 1,
prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH,
merkle_root,
time: 1_296_688_602,
height: 0,
ext: BlockExtData::Proof {
challenge: params.sign_block_script.clone(),
solution: Script::default(),
},
},
txdata,
}
}
/// The uniquely identifying hash of the target blockchain.
/// Liquid networks assume default consensus constants, Elements allows for modification of fedpeg and signblock scripts
/// via configuration argument which will cause these values to differ so these are only for default parameters
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ChainHash([u8; 32]);
impl_array_newtype!(ChainHash, u8, 32);
impl ChainHash {
/// `ChainHash` for Liquid v1 mainnet.
pub const LIQUIDV1: Self = Self([
3, 96, 32, 138, 136, 150, 146, 55, 44, 141, 104, 176, 132, 166, 46, 253, 246, 14, 161, 163,
89, 160, 76, 148, 178, 13, 34, 54, 88, 39, 102, 20,
]);
/// `ChainHash` for Liquid testnet.
pub const LIQUIDTESTNET: Self = Self([
193, 177, 106, 226, 79, 36, 35, 174, 162, 234, 52, 85, 34, 146, 121, 59, 91, 94, 130, 153,
154, 30, 237, 129, 213, 106, 238, 82, 142, 218, 113, 167,
]);
/// Calculates the chainhash for a set of network parameters
pub fn for_params(params: &NetworkParams) -> Self {
let genesis_block = genesis_block(params);
ChainHash(genesis_block.block_hash().to_byte_array())
}
}
#[cfg(test)]
mod test {
use crate::genesis::{genesis_block, ChainHash, NetworkParams};
#[test]
fn genesis_block_hash() {
let genesis_block = genesis_block(&NetworkParams::liquidv1());
assert_eq!(
genesis_block.block_hash().to_byte_array(),
ChainHash::LIQUIDV1.0
);
let genesis_block =
crate::genesis::genesis_block(&NetworkParams::liquidtestnet());
assert_eq!(
genesis_block.block_hash().to_byte_array(),
ChainHash::LIQUIDTESTNET.0
);
}
}