Coder Social home page Coder Social logo

Comments (2)

KtorZ avatar KtorZ commented on August 12, 2024

Current State of Affairs

We currently manipulate and represent addresses as:

newtype Address = Address ByteString

where the address internal representation is the whole address payload on Byron. In other
words, the ByteString is a cbor encoding of various consistuent derived from the address
public key.

We represent addresses in a human-friendly way using a Base58 encoding at the API-level.
This goes though ToText and FromText instances:

instance FromText Address where
    fromText x = maybe
        (Left $ TextDecodingError err)
        (pure . Address)
        (decodeBase58 bitcoinAlphabet $ T.encodeUtf8 x)
      where
        err = "Unable to decode Address: expected Base58 encoding"

instance ToText Address where
    toText = T.decodeUtf8 . encodeBase58 bitcoinAlphabet . getAddress

We do also currently use this address instance to serialize to and deserialize form the
database.

NOTE:

There's a problem inherent to our current design: using an invalid address (which has
however a valid base58 encoding, throws hard because we only verify the address payload
when encoding it). This can be fixed by forcing the payload to be decoded when parsing the address
from a base58 text string.

Problem to solve

In Shelley, addresses can be one of these three formats:

  • Byron addresses, as described above
  • Single addresses, holding only a public spending key
  • Group addresses, holding both a spending key and delegation key

In addition to these format, we also have a new user-facing encoding for Single and
Group addresses: bech32.

New Address Data-Type

One area to explore would be to change Address definition with:

data Address
  = ByronAddress !ByteString
  | SingleAddress !???
  | GroupAddress !??? !???

This allows for clearly capturing within the Address type itself, what the encoding should be.
Now, what to put in ???, this requires some thoughts:

  • XPub seems like the most legitimate choice (since the address is a concatenation of a
    discriminant byte and a public key). However, how do we parse an address then? We would
    need to verify the discriminant byte and therefore, know what is the underlying network.
    This is something we don't know within the core package because this depends on the
    target backend we select. So the transformation from and to bech32 text has to be done within
    the corresponding backend package.

  • ByteString is a sensible choice here too despite the lost of expressiveness at the type-level (which isn't necessarily a bad thing, cf below). Storing the whole address payload as a
    raw ByteString helps to keep the whole core engine fairly agnostic to what the internals of an address are, leaving them up to the node backend.

Drawbacks

Although elegant from the design perspective, this approach present one major drawback:

We then have to consider addresses always as a sum type between these three constructors whereas, in most places (except at the edge of the application), addresses are really just treated as an opaque type; a wrapper around a payload so-to-speak.

The content of that payload is interesting only when we want to display the address in a human-friendly way. For the rest, we do maintain a dictionary of Address -> Derivation Index in the BIP-44 pools so address can be derived from their account key if necessary (and we do need this anyway to recover the private key from an address!).

Considering a sum-type means that we would have to consider testing all branches, and their possible interaction with the rest of the codebase, whereas they're all treated in an exact same way. Also, what happened for the code within the bridge, jormungandr or shelley haskell for they use different branches of this sum-type (bridge doesn't have group and single address, shelley haskell has extra "pointer" branches that aren't yet in jormungandr etc ...).

Keep it simple

On the contrary, keeping the address definition as simply a wrapper around a raw bytestring presents many advantages. For most of the code, the 'Address' remains quite opaque and is treated as such. We only consider the encoding at the edge of the application, for what is user-facing (i.e. the API) and we defer that decision to the backend package via proper type classes parameterized by the backend parameter.

This also gives us a very easy testing (as we do now) where the payload of an address can pretty much be anything. When it matters (comparing or submitting transactions), then we may shove in a relevant payload that is compatible with the target backend.

Parameterizing over the backend type

As we do for TxId, it is possible to parameterize our wallet layer over a target (or t)
type parameter which embeds the address format. This allows for keeping the address encoding
outside of the wallet core logic, decided by the caller / target backend. In particular,
it makes it possible to use different encoding on different backend, and/or, disallow specific
encodings or address constructors.

class EncodeAddress t where
    encodeAddress :: Proxy t -> Address -> Text

class DecodeAddress t where
    decodeAddress :: Proxy t -> Text -> Either TextDecodingError Address

So here, depending on which backend we're dealing with, we may end up rejecting addresses that are encoded in bech32, or those with a wrong network magic. The encoding is also a bit subtle as the instances will have to peek into the address raw bytes to determine what sort of address it is. Still, this is a fair price to pay for simplicity.

from cardano-wallet.

piotr-iohk avatar piotr-iohk commented on August 12, 2024

lgtm! 👍

from cardano-wallet.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.