feat: Introduce Addresses crate (#137)

This commit is contained in:
Santiago Carmuega 2022-06-23 22:31:12 -03:00 committed by GitHub
parent b68a278529
commit 2a70c6a090
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 747 additions and 12 deletions

View file

@ -2,6 +2,7 @@
members = [
"pallas-codec",
"pallas-addresses",
"pallas-multiplexer",
"pallas-miniprotocols",
"pallas-crypto",

View file

@ -43,6 +43,7 @@ As already explained, _Pallas_ aims at being an expanding set of components. The
| --------------------------------------- | ----------------------------------------------------------------------- |
| [pallas-primitives](/pallas-primitives) | Ledger primitives and cbor codec for the different Cardano eras |
| [pallas-traverse](/pallas-traverse) | Utilities to traverse over multi-era block data |
| [pallas-addresses](/pallas-addresses) | Encode / decode Cardano addresses of any type |
| pallas-ticking | Time passage implementation for consensus algorithm |
| pallas-applying | Logic for validating and applying new blocks and txs to the chain state |
| pallas-forecasting | Ledger forecasting algorithm to be used by the consensus layer |

View file

@ -1,7 +1,7 @@
[package]
name = "pallas-primitives"
description = "Ledger primitives and cbor codec for the different Cardano eras"
version = "0.9.1"
name = "pallas-addresses"
description = "Ergonomic library to work with different Cardano addresses"
version = "0.11.0-alpha.0"
edition = "2021"
repository = "https://github.com/txpipe/pallas"
homepage = "https://github.com/txpipe/pallas"
@ -14,14 +14,8 @@ authors = [
[dependencies]
hex = "0.4.3"
log = "0.4.14"
pallas-crypto = { version = "0.9.0", path = "../pallas-crypto" }
pallas-codec = { version = "0.9.0", path = "../pallas-codec" }
pallas-crypto = { version = "0.11.0-alpha.0", path = "../pallas-crypto" }
pallas-codec = { version = "0.11.0-alpha.0", path = "../pallas-codec" }
base58 = "0.2.0"
bech32 = "0.8.1"
serde = { version ="1.0.136", optional = true }
serde_json = { version ="1.0.79", optional = true }
[features]
json = ["serde", "serde_json"]
default = ["json"]
thiserror = "1.0.31"

686
pallas-addresses/src/lib.rs Normal file
View file

@ -0,0 +1,686 @@
//! Interact with Cardano addresses of any type
//!
//! This module contains utilities to decode / encode Cardano addresses from /
//! to different formats. The entry point to most of the methods is the
//! [Address] enum, which holds the decoded values of either a Byron, Shelley or
//! Stake address.
//!
//! For more information regarding Cardano addresses and their formats, please refer to [CIP-19](https://cips.cardano.org/cips/cip19/).
pub mod varuint;
use std::io::Cursor;
use pallas_crypto::hash::Hash;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("error converting from/to bech32 {0}")]
BadBech32(bech32::Error),
#[error("address header not found")]
MissingHeader,
#[error("address header is invalid {0:08b}")]
InvalidHeader(u8),
#[error("invalid operation for Byron address")]
InvalidForByron,
#[error("unkown hrp for network {0:08b}")]
UnknownNetworkHrp(u8),
#[error("invalid hash size {0}")]
InvalidHashSize(usize),
#[error("invalid pointer data")]
InvalidPointerData,
#[error("variable-length uint error: {0}")]
VarUintError(varuint::Error),
}
pub type PaymentKeyHash = Hash<28>;
pub type StakeKeyHash = Hash<28>;
pub type ScriptHash = Hash<28>;
pub type Slot = u64;
pub type TxIdx = u64;
pub type CertIdx = u64;
/// An on-chain pointer to a stake key
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct Pointer(Slot, TxIdx, CertIdx);
fn slice_to_hash(slice: &[u8]) -> Result<Hash<28>, Error> {
if slice.len() == 28 {
let mut sized = [0u8; 28];
sized.copy_from_slice(slice);
Ok(sized.into())
} else {
Err(Error::InvalidHashSize(slice.len()))
}
}
impl Pointer {
pub fn new(slot: Slot, tx_idx: TxIdx, cert_idx: CertIdx) -> Self {
Pointer(slot, tx_idx, cert_idx)
}
pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
let mut cursor = Cursor::new(bytes);
let a = varuint::read(&mut cursor).map_err(Error::VarUintError)?;
let b = varuint::read(&mut cursor).map_err(Error::VarUintError)?;
let c = varuint::read(&mut cursor).map_err(Error::VarUintError)?;
Ok(Pointer(a, b, c))
}
pub fn to_vec(&self) -> Vec<u8> {
let mut cursor = Cursor::new(vec![]);
varuint::write(&mut cursor, self.0);
varuint::write(&mut cursor, self.1);
varuint::write(&mut cursor, self.2);
cursor.into_inner()
}
pub fn slot(&self) -> u64 {
self.0
}
pub fn tx_idx(&self) -> u64 {
self.1
}
pub fn cert_idx(&self) -> u64 {
self.2
}
}
/// The payment part of a Shelley address
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum ShelleyPaymentPart {
PaymentKey(PaymentKeyHash),
Script(ScriptHash),
}
impl ShelleyPaymentPart {
fn payment_key(bytes: &[u8]) -> Result<Self, Error> {
slice_to_hash(bytes).map(ShelleyPaymentPart::PaymentKey)
}
fn script(bytes: &[u8]) -> Result<Self, Error> {
slice_to_hash(bytes).map(ShelleyPaymentPart::Script)
}
/// Get a reference to the inner hash of this address part
pub fn as_hash(&self) -> &Hash<28> {
match self {
Self::PaymentKey(x) => x,
Self::Script(x) => x,
}
}
/// Encodes this address as a sequence of bytes
pub fn to_vec(&self) -> Vec<u8> {
match self {
Self::PaymentKey(x) => x.to_vec(),
Self::Script(x) => x.to_vec(),
}
}
/// Indicates if this is the hash of a script
pub fn is_script(&self) -> bool {
matches!(self, Self::Script(_))
}
}
/// The delegation part of a Shelley address
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum ShelleyDelegationPart {
StakeKey(StakeKeyHash),
Script(ScriptHash),
Pointer(Pointer),
Null,
}
impl ShelleyDelegationPart {
fn stake_key(bytes: &[u8]) -> Result<Self, Error> {
slice_to_hash(bytes).map(Self::StakeKey)
}
fn script(bytes: &[u8]) -> Result<Self, Error> {
slice_to_hash(bytes).map(Self::Script)
}
fn pointer(bytes: &[u8]) -> Result<Self, Error> {
let pointer = Pointer::parse(bytes)?;
Ok(Self::Pointer(pointer))
}
/// Get a reference to the inner hash of this address part
pub fn as_hash(&self) -> Option<&Hash<28>> {
match self {
Self::StakeKey(x) => Some(x),
Self::Script(x) => Some(x),
Self::Pointer(_) => todo!(),
Self::Null => todo!(),
}
}
pub fn to_vec(&self) -> Vec<u8> {
match self {
Self::StakeKey(x) => x.to_vec(),
Self::Script(x) => x.to_vec(),
Self::Pointer(x) => x.to_vec(),
Self::Null => vec![],
}
}
pub fn is_script(&self) -> bool {
matches!(self, ShelleyDelegationPart::Script(_))
}
}
impl StakePayload {
fn stake_key(bytes: &[u8]) -> Result<Self, Error> {
slice_to_hash(bytes).map(StakePayload::Stake)
}
fn script(bytes: &[u8]) -> Result<Self, Error> {
slice_to_hash(bytes).map(StakePayload::Script)
}
pub fn is_script(&self) -> bool {
matches!(self, StakePayload::Script(_))
}
}
/// The network tag of an address
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub enum Network {
Testnet,
Mainnet,
Other(u8),
}
/// A decoded Shelley address
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct ShelleyAddress(Network, ShelleyPaymentPart, ShelleyDelegationPart);
/// The payload of a Stake address
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum StakePayload {
Stake(StakeKeyHash),
Script(ScriptHash),
}
/// A decoded Stake address
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct StakeAddress(Network, StakePayload);
/// Newtype representing a Byron address
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct ByronAddress(Vec<u8>);
/// A decoded Cardano address of any type
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum Address {
Byron(ByronAddress),
Shelley(ShelleyAddress),
Stake(StakeAddress),
}
fn encode_bech32(addr: &[u8], hrp: &str) -> Result<String, Error> {
let base32 = bech32::ToBase32::to_base32(&addr);
bech32::encode(hrp, base32, bech32::Variant::Bech32).map_err(Error::BadBech32)
}
fn decode_bech32(bech32: &str) -> Result<(String, Vec<u8>), Error> {
let (hrp, addr, _) = bech32::decode(bech32).map_err(Error::BadBech32)?;
let base10 = bech32::FromBase32::from_base32(&addr).map_err(Error::BadBech32)?;
Ok((hrp, base10))
}
fn parse_network(header: u8) -> Network {
let masked = header & 0b0000_1111;
match masked {
0b_0000_0000 => Network::Testnet,
0b_0000_0001 => Network::Mainnet,
_ => Network::Other(masked),
}
}
macro_rules! parse_shelley_fn {
($name:tt, $payment:tt, pointer) => {
fn $name(header: u8, payload: &[u8]) -> Result<Address, Error> {
let net = parse_network(header);
let p1 = ShelleyPaymentPart::$payment(&payload[0..=27])?;
let p2 = ShelleyDelegationPart::pointer(&payload[28..])?;
let addr = ShelleyAddress(net, p1, p2);
Ok(addr.into())
}
};
($name:tt, $payment:tt, $delegation:tt) => {
fn $name(header: u8, payload: &[u8]) -> Result<Address, Error> {
let net = parse_network(header);
let p1 = ShelleyPaymentPart::$payment(&payload[0..=27])?;
let p2 = ShelleyDelegationPart::$delegation(&payload[28..=55])?;
let addr = ShelleyAddress(net, p1, p2);
Ok(addr.into())
}
};
($name:tt, $payment:tt) => {
fn $name(header: u8, payload: &[u8]) -> Result<Address, Error> {
let net = parse_network(header);
let p1 = ShelleyPaymentPart::$payment(&payload[0..=27])?;
let addr = ShelleyAddress(net, p1, ShelleyDelegationPart::Null);
Ok(addr.into())
}
};
}
macro_rules! parse_stake_fn {
($name:tt, $type:tt) => {
fn $name(header: u8, payload: &[u8]) -> Result<Address, Error> {
let net = parse_network(header);
let p1 = StakePayload::$type(&payload[0..=27])?;
let addr = StakeAddress(net, p1);
Ok(addr.into())
}
};
}
// types 0-7 are Shelley addresses
parse_shelley_fn!(parse_type_0, payment_key, stake_key);
parse_shelley_fn!(parse_type_1, script, stake_key);
parse_shelley_fn!(parse_type_2, payment_key, script);
parse_shelley_fn!(parse_type_3, script, script);
parse_shelley_fn!(parse_type_4, payment_key, pointer);
parse_shelley_fn!(parse_type_5, script, pointer);
parse_shelley_fn!(parse_type_6, payment_key);
parse_shelley_fn!(parse_type_7, script);
// type 8 (1000) are Byron addresses
fn parse_type_8(header: u8, payload: &[u8]) -> Result<Address, Error> {
let vec = [&[header], payload].concat();
Ok(Address::Byron(ByronAddress(vec)))
}
// types 14-15 are Stake addresses
parse_stake_fn!(parse_type_14, stake_key);
parse_stake_fn!(parse_type_15, script);
fn bytes_to_address(bytes: &[u8]) -> Result<Address, Error> {
let header = *bytes.get(0).ok_or(Error::MissingHeader)?;
let payload = &bytes[1..];
match header & 0b1111_0000 {
0b0000_0000 => parse_type_0(header, payload),
0b0001_0000 => parse_type_1(header, payload),
0b0010_0000 => parse_type_2(header, payload),
0b0011_0000 => parse_type_3(header, payload),
0b0100_0000 => parse_type_4(header, payload),
0b0101_0000 => parse_type_5(header, payload),
0b0110_0000 => parse_type_6(header, payload),
0b0111_0000 => parse_type_7(header, payload),
0b1000_0000 => parse_type_8(header, payload),
0b1110_0000 => parse_type_14(header, payload),
0b1111_0000 => parse_type_15(header, payload),
_ => Err(Error::InvalidHeader(header)),
}
}
fn bech32_to_address(bech32: &str) -> Result<Address, Error> {
let (_, bytes) = decode_bech32(bech32)?;
bytes_to_address(&bytes)
}
fn address_to_bech32(addr: &Address) -> Result<String, Error> {
match addr {
Address::Byron(_) => Err(Error::InvalidForByron),
Address::Shelley(ref x) => {
let hrp = x.hrp()?;
let bytes = x.to_vec();
encode_bech32(&bytes, hrp)
}
Address::Stake(ref x) => {
let hrp = x.hrp()?;
let bytes = x.to_vec();
encode_bech32(&bytes, hrp)
}
}
}
impl Network {
pub fn is_mainnet(&self) -> bool {
matches!(self, Network::Mainnet)
}
pub fn value(&self) -> u8 {
match self {
Network::Testnet => 0,
Network::Mainnet => 1,
Network::Other(x) => *x,
}
}
}
impl ByronAddress {
/// Gets a numeric id describing the type of the address
pub fn typeid(&self) -> u8 {
0b1000
}
}
impl ShelleyAddress {
/// Gets the network assoaciated with this address
pub fn network(&self) -> Network {
self.0
}
/// Gets a numeric id describing the type of the address
pub fn typeid(&self) -> u8 {
match (&self.1, &self.2) {
(ShelleyPaymentPart::PaymentKey(_), ShelleyDelegationPart::StakeKey(_)) => 0b0000,
(ShelleyPaymentPart::Script(_), ShelleyDelegationPart::StakeKey(_)) => 0b0001,
(ShelleyPaymentPart::PaymentKey(_), ShelleyDelegationPart::Script(_)) => 0b0010,
(ShelleyPaymentPart::Script(_), ShelleyDelegationPart::Script(_)) => 0b0011,
(ShelleyPaymentPart::PaymentKey(_), ShelleyDelegationPart::Pointer(_)) => 0b0100,
(ShelleyPaymentPart::Script(_), ShelleyDelegationPart::Pointer(_)) => 0b0101,
(ShelleyPaymentPart::PaymentKey(_), ShelleyDelegationPart::Null) => 0b0110,
(ShelleyPaymentPart::Script(_), ShelleyDelegationPart::Null) => 0b0111,
}
}
pub fn to_header(&self) -> u8 {
let type_id = self.typeid();
let type_id = type_id << 4;
let network = self.0.value();
type_id | network
}
pub fn payment(&self) -> &ShelleyPaymentPart {
&self.1
}
pub fn delegation(&self) -> &ShelleyDelegationPart {
&self.2
}
/// Gets the bech32 human-readable-part for this address
pub fn hrp(&self) -> Result<&'static str, Error> {
match &self.0 {
Network::Testnet => Ok("addr_test"),
Network::Mainnet => Ok("addr"),
Network::Other(x) => Err(Error::UnknownNetworkHrp(*x)),
}
}
pub fn to_vec(&self) -> Vec<u8> {
let header = self.to_header();
let payment = self.1.to_vec();
let delegation = self.2.to_vec();
[&[header], payment.as_slice(), delegation.as_slice()].concat()
}
/// Indicates if either the payment or delegation part is a script
pub fn has_script(&self) -> bool {
self.payment().is_script() || self.delegation().is_script()
}
}
impl AsRef<[u8]> for StakePayload {
fn as_ref(&self) -> &[u8] {
match self {
Self::Stake(x) => x.as_ref(),
Self::Script(x) => x.as_ref(),
}
}
}
impl StakeAddress {
/// Gets the network assoaciated with this address
pub fn network(&self) -> Network {
self.0
}
/// Gets a numeric id describing the type of the address
pub fn typeid(&self) -> u8 {
match &self.1 {
StakePayload::Stake(_) => 0b1110,
StakePayload::Script(_) => 0b1111,
}
}
/// Builds the header for this address
pub fn to_header(&self) -> u8 {
let type_id = self.typeid();
let type_id = type_id << 4;
let network = self.0.value();
type_id | network
}
/// Gets the payload of this address
pub fn payload(&self) -> &StakePayload {
&self.1
}
/// Gets the bech32 human-readable-part for this address
pub fn hrp(&self) -> Result<&'static str, Error> {
match &self.0 {
Network::Testnet => Ok("stake_test"),
Network::Mainnet => Ok("stake"),
Network::Other(x) => Err(Error::UnknownNetworkHrp(*x)),
}
}
pub fn to_vec(&self) -> Vec<u8> {
let header = self.to_header();
[&[header], self.1.as_ref()].concat()
}
pub fn is_script(&self) -> bool {
self.payload().is_script()
}
}
impl Address {
/// Tries to encode an Address into a bech32 string
pub fn to_bech32(&self) -> Result<String, Error> {
address_to_bech32(self)
}
/// Tries to parse a bech32 address into an Address
pub fn from_bech32(bech32: &str) -> Result<Self, Error> {
bech32_to_address(bech32)
}
/// Gets the network assoaciated with this address
pub fn network(&self) -> Option<Network> {
match self {
Address::Byron(_) => None,
Address::Shelley(x) => Some(x.network()),
Address::Stake(x) => Some(x.network()),
}
}
/// Gets a numeric id describing the type of the address
pub fn typeid(&self) -> u8 {
match self {
Address::Byron(x) => x.typeid(),
Address::Shelley(x) => x.typeid(),
Address::Stake(x) => x.typeid(),
}
}
/// Gets the bech32 human-readable-part for this address
pub fn hrp(&self) -> Result<&'static str, Error> {
match self {
Address::Byron(_) => Err(Error::InvalidForByron),
Address::Shelley(x) => x.hrp(),
Address::Stake(x) => x.hrp(),
}
}
/// Indicates if this is address includes a script hash
pub fn has_script(&self) -> bool {
match self {
Address::Byron(_) => false,
Address::Shelley(x) => x.has_script(),
Address::Stake(x) => x.is_script(),
}
}
/// Indicates if this is an enterpise address
pub fn is_enterprise(&self) -> bool {
match self {
Address::Shelley(x) => matches!(x.delegation(), ShelleyDelegationPart::Null),
_ => false,
}
}
}
impl From<ByronAddress> for Address {
fn from(addr: ByronAddress) -> Self {
Address::Byron(addr)
}
}
impl From<ShelleyAddress> for Address {
fn from(addr: ShelleyAddress) -> Self {
Address::Shelley(addr)
}
}
impl From<StakeAddress> for Address {
fn from(addr: StakeAddress) -> Self {
Address::Stake(addr)
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
const MAINNET_TEST_VECTORS: &[(&str, u8)] = &[
("addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x", 00u8),
("addr1z8phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gten0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgs9yc0hh", 01u8),
("addr1yx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzerkr0vd4msrxnuwnccdxlhdjar77j6lg0wypcc9uar5d2shs2z78ve", 02u8),
("addr1x8phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gt7r0vd4msrxnuwnccdxlhdjar77j6lg0wypcc9uar5d2shskhj42g", 03u8),
("addr1gx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer5pnz75xxcrzqf96k", 04u8),
("addr128phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gtupnz75xxcrtw79hu", 05u8),
("addr1vx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzers66hrl8", 06u8),
("addr1w8phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gtcyjy7wx", 07u8),
("stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw", 14u8),
("stake178phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gtcccycj5", 15u8),
];
const PAYMENT_PUBLIC_KEY: &str =
"addr_vk1w0l2sr2zgfm26ztc6nl9xy8ghsk5sh6ldwemlpmp9xylzy4dtf7st80zhd";
const STAKE_PUBLIC_KEY: &str =
"stake_vk1px4j0r2fk7ux5p23shz8f3y5y2qam7s954rgf3lg5merqcj6aetsft99wu";
const SCRIPT_HASH: &str = "script1cda3khwqv60360rp5m7akt50m6ttapacs8rqhn5w342z7r35m37";
fn hash_vector_key(key: &str) -> Hash<28> {
let (_, x) = decode_bech32(key).unwrap();
pallas_crypto::hash::Hasher::<224>::hash(&x)
}
#[test]
fn roundtrip_bech32() {
for vector in MAINNET_TEST_VECTORS {
let original = vector.0;
let addr = Address::from_bech32(original).unwrap();
let ours = addr.to_bech32().unwrap();
assert_eq!(original, ours);
}
}
#[test]
fn typeid_matches() {
for vector in MAINNET_TEST_VECTORS {
let original = vector.0;
let addr = Address::from_bech32(original).unwrap();
assert_eq!(addr.typeid(), vector.1);
}
}
#[test]
fn network_matches() {
for vector in MAINNET_TEST_VECTORS {
let original = vector.0;
let addr = Address::from_bech32(original).unwrap();
assert!(matches!(addr.network(), Some(Network::Mainnet)));
}
}
#[test]
fn payload_matches() {
for vector in MAINNET_TEST_VECTORS {
let original = vector.0;
let addr = Address::from_bech32(original).unwrap();
match addr {
Address::Shelley(x) => {
match x.payment() {
ShelleyPaymentPart::PaymentKey(hash) => {
let expected = &hash_vector_key(PAYMENT_PUBLIC_KEY);
assert_eq!(hash, expected);
}
ShelleyPaymentPart::Script(hash) => {
let (_, expected) = &decode_bech32(SCRIPT_HASH).unwrap();
let expected = &Hash::<28>::from_str(&hex::encode(&expected)).unwrap();
assert_eq!(hash, expected);
}
};
match x.delegation() {
ShelleyDelegationPart::StakeKey(hash) => {
let expected = &hash_vector_key(STAKE_PUBLIC_KEY);
assert_eq!(hash, expected);
}
ShelleyDelegationPart::Script(hash) => {
let (_, expected) = &decode_bech32(SCRIPT_HASH).unwrap();
let expected = &Hash::<28>::from_str(&hex::encode(&expected)).unwrap();
assert_eq!(hash, expected);
}
ShelleyDelegationPart::Pointer(ptr) => {
assert_eq!(ptr.slot(), 2498243);
assert_eq!(ptr.tx_idx(), 27);
assert_eq!(ptr.cert_idx(), 3);
}
_ => (),
};
}
Address::Stake(x) => match x.payload() {
StakePayload::Stake(hash) => {
let expected = &hash_vector_key(STAKE_PUBLIC_KEY);
assert_eq!(hash, expected);
}
StakePayload::Script(hash) => {
let (_, expected) = &decode_bech32(SCRIPT_HASH).unwrap();
let expected = &Hash::<28>::from_str(&hex::encode(&expected)).unwrap();
assert_eq!(hash, expected);
}
},
Address::Byron(_) => (),
};
}
}
}

View file

@ -0,0 +1,49 @@
//! Decode / encode variable-length uints
use std::io::{Cursor, Read, Write};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("variable-length uint overflow")]
VarUintOverflow,
#[error("unexpected end-of-buffer")]
UnexpectedEof,
}
pub fn read(cursor: &mut Cursor<&[u8]>) -> Result<u64, Error> {
let mut output = 0u128;
let mut buf = [0u8; 1];
loop {
cursor
.read_exact(&mut buf)
.map_err(|_| Error::UnexpectedEof)?;
let byte = buf[0];
output = (output << 7) | (byte & 0x7F) as u128;
if output > u64::MAX.into() {
return Err(Error::VarUintOverflow);
}
if (byte & 0x80) == 0 {
return Ok(output as u64);
}
}
}
pub fn write(cursor: &mut Cursor<Vec<u8>>, mut num: u64) {
let mut output = vec![num as u8 & 0x7F];
num /= 128;
while num > 0 {
output.push((num & 0x7F) as u8 | 0x80);
num /= 128;
}
output.reverse();
cursor.write_all(&output).unwrap();
}

View file

@ -17,5 +17,6 @@ pallas-multiplexer = { version = "0.11.0-alpha.0", path = "../pallas-multiplexer
pallas-miniprotocols = { version = "0.11.0-alpha.0", path = "../pallas-miniprotocols/" }
pallas-primitives = { version = "0.11.0-alpha.0", path = "../pallas-primitives/" }
pallas-traverse = { version = "0.11.0-alpha.0", path = "../pallas-traverse/" }
pallas-addresses = { version = "0.11.0-alpha.0", path = "../pallas-addresses/" }
pallas-crypto = { version = "0.11.0-alpha.0", path = "../pallas-crypto/" }
pallas-codec = { version = "0.11.0-alpha.0", path = "../pallas-codec/" }

View file

@ -5,3 +5,6 @@ pub use pallas_primitives as primitives;
#[doc(inline)]
pub use pallas_traverse as traverse;
#[doc(inline)]
pub use pallas_addresses as addresses;