188 releases (19 breaking)
0.20.3 | Nov 12, 2024 |
---|---|
0.20.1 | Oct 29, 2024 |
0.18.9 | Jul 29, 2024 |
0.17.1 | Mar 28, 2024 |
0.2.4 | Nov 24, 2020 |
#85 in Cryptography
1,217 downloads per month
Used in 18 crates
(8 directly)
290KB
5K
SLoC
Autonomi Network Token
The Autonomi Network Token (ANT) is a currency built on top of the storage layer of the Autonomi Network. It is used to reward Network nodes for storing data.
. ANT does not use a blockchain but a distributed Directed Acyclic Graph (DAG) of Spend
s which are all linked together all the way to the first Spend
which we call Genesis
. Those Spend
s contain transaction data and all the information necessary for verification and audit of the currency.
Keys
Just like many digital currencies, we use public/private key cryptography (in our case we use bls keys, implemented in the blsttc rust crate). A wallet consists of two keys:
MainPubkey
: equivalent to a Bitcoin address, this is used to receive ANT. It can be shared publicly.MainSecretKey
: the secret from which aMainPubkey
is generated; it is used for spending ANT.
Unlike one might expect, the MainPubkey
itself never owns any money: UniquePubkey
s derived from it do. Value is owned by those UniquePubkey
s which are spendable only once in the form of a Spend
uploaded at that UniquePubkey
's address (known as a SpendAddress
) on the Network.
The way we obtain those UniquePubkey
s is by using bls key derivation, an algorithm which creates a new key from another key by using a large number called a DerivationIndex
. UniquePubkey
s are derived from the MainPubkey
. To spend the value owned by a UniquePubkey
, one uses the associated DerivedSecretKey
which was derived from the MainSecretKey
using the same DerivationIndex
as was used to create the UniquePubkey
.
This DerivedSecretKey
is used to sign the Spend
which is then sent to the Network for validation and storage. Once the Network has stored and properly replicated that Spend
, that UniquePubkey
is considered to be spent and cannot ever be spent again. If more than one Spend
entry exist at a given SpendAddress
on the Network, that key is considered to be burnt which makes any Spend
refering to it unspendable.
Without the DerivationIndex
, there is no way to link a MainPubkey
to a UniquePubkey
. Since UniquePubkey
s are spendable only once, this means every transaction involves new and unique keys which are all unrelated and unlinkable to their original owner's MainPubkey
.
Under the hood, those types are simply:
MainPubkey
=>blsttc::PublicKey
UniquePubkey
=>blsttc::PublicKey
(derived fromMainPubkey
)MainSecretKey
=>blsttc::SecretKey
DerivedSecretKey
=>blsttc::SecretKey
(derived fromMainSecretKey
)DerivationIndex
=>u256
(big number impossible to guess, used to derive keys)
Spends
When a UniquePubkey
is spent, the owner creates a Spend
and signs it with the associated DerivedSecretKey
before uploading it to the Network. A Spend
contains the following information:
pub struct Spend {
pub unique_pubkey: UniquePubkey,
pub ancestors: BTreeSet<UniquePubkey>,
pub descendants: BTreeMap<UniquePubkey, NanoTokens>,
}
A Spend
refers to
- its own
UniquePubkey
- its
ancestors
(which refer to it as a one of thedescendants
) - its
descendants
(which could refer to it as one of theancestors
)
Note that
ancestors
anddescendants
should not be confused with inputs and outputs of a transaction. If we were to put that in traditional input output terms:
- The
ancestors
are the inputs of the transaction whereunique_pubkey
is an output.- The
unique_pubkey
is an input of the transaction wheredescendants
are an output.
GenesisSpend
/ \
SpendA SpendB
/ \ \
SpendC SpendD SpendE
/ \ \
... ... ...
All the
Spend
s on a Network come from Genesis.
Each descendant is given some of the value of the spent UniquePubkey
. The value of a Spend
is the sum of the values inherited from its ancestors.
SpendS(19) value
/ | \ |
9 4 6 value inherited
/ | \ |
SpendW(9) SpendX(4) SpendY(6) value
/ \ | |
6 3 4 value inherited
/ \ | |
SpendQ(6) SpendZ(7) V
In the above example, Spend Z has 2 ancestors W and X which gave it respectively
3
and4
. Z's value is the sum of the inherited value from its ancestors:3 + 4 = 7
.In this example
SpendW
of value9
would look something like:Spend { unique_pubkey = W, ancestors = {S}, descendants = {Z : 3, Q : 6}, }
Spend
s on the Network are always signed by their owner (DerivedSecretKey
) and come with that signature:
pub struct SignedSpend {
pub spend: Spend,
pub derived_key_sig: Signature,
}
In order to be valid and accepted by the Network a Spend must:
- be addressed at the
SpendAddress
derived from itsUniquePubkey
- refer to existing and valid ancestors that refer to it as a descendant
- refer to descendants and donate a non zero amount to them
- the sum of the donated value to descendants must be equal to the sum of the Spend's inherited value from its ancestors
- the ancestors must not be burnt
If multiple valid spend entries are found at a single address, that
UniquePubkey
is said to be burnt and its descendants will therefore fail the above verificationSpendA / \ SpendB (SpendD, SpendD) / \ \ ... [E] [F]
In the figure above, there are two
Spend
entries in the Network for theUniquePubkey
D
. We say thatD
is burnt. The result is thatE
andF
have a burnt parent making them unspendable. When fetchingD
, one would get a burnt spend entry as we have twoSpend
s on the Network at thatSpendAddress
:Spend { unique_pubkey = D, ancestors = {A}, descendants = {E : 3}, } Spend { unique_pubkey = D, ancestors = {A}, descendants = {F : 3}, }
Spend
s are the only currency related data on the Network, they are stored in a sharded manner by nodes whose address is close to the UniquePubkey
. This ensures that any other Spend
with the same UniquePubkey
is the responsibility of the same nodes, countering knowledge forks.
Spend DAG
All the spends on the Network form a DAG of Spend
s, with each Spend
stored in different locations on the Network. No single node has the entire knowledge of the DAG, but the Network as a whole contains that DAG.
The Spend DAG starts from Genesis, and by following its descendants recursively, one can find all the Spend
s on the Network.
An application collecting all those spends from Genesis could rebuild the DAG locally and use it for auditing or external verification. There is no need to run a node to download the entire DAG as the Spend
s can be fetched for free by a Network client. Similarly to how blockchains have block explorers, a DAG explorer could be built using this.
The figure below is an example output of such a DAG collecting application:
Transfers
To perform a Transfer
, one must have money to spend: own at least a spendable UniquePubkey
and the key to spend it:
- either the
UniquePubkey
's secretDerivationIndex
and theMainSecretKey
in order to derive theDerivedSecretKey
- or just the
DerivedSecretKey
s that owns thatUniquePubkey
The Transfer
needs an amount and a recipient: a MainPubkey
. All the amounts on the Network are in NanoTokens
, the smallest unit of ANT (10^-9 ANT). Think of it as the ANT equivalent to Satoshi for Bitcoin or Wei for Ethereum.
The following concepts are used in the performing of a transfer:
UniquePubkey
: a unique key that can own money but only be spent onceSpend
: the spend commitment of aUniquePubkey
, once uploaded to the Network, that key is considered to be spent, if a key is spent more than once, it is considered to be burnt and its descendants unspendableCashNote
: a package of information associated with aUniquePubkey
: simplifies the process of creating aSpend
from itCashNoteRemption
: the minimal information necessary for a recipient to identify a receivedUniquePubkey
and be able to spend itTransfer
: an encrypted package ofCashNoteRemption
, destined to the recipient
A Transfer consists of the following steps:
Preparation
First we need to decide on the transfer's recipient and amount:
- decide on a recipient:
MainPubkey
and an amount inNanoTokens
Then we gather our local spendable UniquePubkey
s:
- gather spendable
UniquePubkey
s we own that make up that amount or more - gather the ancestors of our
UniquePubkey
s as we need them in theSpend
All the information regarding a spendable
UniquePubkey
(except for the secret keys) can conveniently be packed together into what we call aCashNote
:pub struct CashNote { pub main_pubkey: MainPubkey, pub derivation_index: DerivationIndex, // note that MainPubkey + DerivationIndex => UniquePubkey pub parent_spends: BTreeSet<SignedSpend>, }
Then, to protect the identity of the recipient on the Network, we derive a completely new UniquePubkey
from the recipient's MainPubkey
using a randomly generated DerivationIndex
. From an third party's eye, that UniquePubkey
is unlinkable to the MainPubkey
we're sending money to. The result is that only the sender and the recipient know that they are involved in this transfer.
- creation of
UniquePubkey
(s) for the recipient by deriving them from the recipient'sMainPubkey
with randomly generatedDerivationIndex
(es)
With all the above data, we can finally create the Spend
s which represent the sender's commitment to do the transfer.
- creation of the
Spend
s for each spentUniquePubkey
unique_pubkey
:UniquePubkey
we own that we wish to spendancestors
: reference to the ancestors of thatUniquePubkey
to prove its validitydescendants
: reference to theUniquePubkey
(s) of the recipient(s)
Note that the
Spend
does not contain anyDerivationIndex
es nor does it contain anyMainPubkey
s. This makesSpend
s unlinkable to any of the involved parties.
// we own:
-> UniquePubkey_A of value (4)
-> UniquePubkey_B of value (5)
// we send to:
-> NewUniquePubkey = RecipientMainPubkey.derive(RandomDerivationIndex)
Commitment
- sign each
Spend
with theDerivedSecretKey
that we derive fromMainSecretKey
with thatSpend
'sUniquePubkey
'sDerivationIndex
- upload of the
SignedSpend
s to the Network
After this step, it is not possible to cancel the transfer.
ParentSpendA(4) ParentSpendB(5) <- spends on the Network
\ /
4 5
\ /
NewUniquePubkey(9) <- refering to this yet unspent key
Out of Band Transfer
At this point, the recipient doesn't yet know of:
- the
Spend
(s) we uploaded to the Network for them atSpendAddress
- the
UniquePubkey
(s) we created for them which can be obtained from theDerivationIndex
Note that
SpendAddress
: the network address of aSpend
is derived from the hash of aUniquePubkey
We send this information out of band in the form of an encrypted Transfer
encrypted to the recipient's MainPubkey
so only they can decypher it.
Since the
Transfer
is encrypted, it can be sent safely by any chosen media to the recipient: by email, chat app or even shared publicly on a forum.If the encryption is ever broken, this information is unusable without the recipient's
MainSecretKey
. However, coupled with the recipient'sMainPubkey
, this information can identify the correspondingUniquePubkey
s that were received in thisTransfer
.
An encrypted Transfer
is a list of CashNoteRedemption
s, each corresponding to one of the received UniquePubkey
s:
pub struct CashNoteRedemption {
pub derivation_index: DerivationIndex,
pub parent_spends: BTreeSet<SpendAddress>,
}
It contains the DerivationIndex
used to derive:
- the
UniquePubkey
that we're receiving from ourMainPubkey
- the
DerivedSecretKey
from ourMainSecretKey
: needed to spend this newUniquePubkey
Redemption and Verification
Once received and decrypted by the recipient, the CashNoteRedemption
can be used to verify the transfer using the Spend
s online and add the received UniquePubkey
s to our spendable UniquePubkey
s stash:
- getting the
UniquePubkey
from theCashNoteRedemption
'sDerivationIndex
and ourMainPubkey
- getting the
Spend
s at theSpendAddress
on the Network provided in theCashNoteRedemption
and making sure they all exist on the Network - verifying the content of those parent
Spend
s- make sure they all refer to our
UniquePubkey
as a descendant - make sure they are valid
Spend
s
- make sure they all refer to our
- the
UniquePubkey
is now ours and spendable! - for convenience, one can create a
CashNote
with all the above information to simplify spending the receivedUniquePubkey
Since
CashNote
s contain sensitive information, they should never be shared or leaked as it would reveal the link between theMainPubkey
and theUniquePubkey
of thisCashNote
Once successfully received, for safety, it is advised to re-send the received tokens to ourselves on a new UniquePubkey
that only we can link back to our MainPubkey
. This ensures:
- that the original sender doesn't have the
DerivationIndex
for our spendable money - that we know the parent of our spendable
UniquePubkey
s are not burnable by anyone but ourselves
Failing to do so exposes the receiver to the risk of having their keys become unspendable if the sender decides to burn the parent
Spend
s
ParentSpendA(4) ParentSpendB(5) <- spends on the Network
\ /
4 5
\ /
NewSpend(9) <- spend on the Network
|
9
|
AnotherUniquePubkey(9) <- refering to this new unspent key
After this final step, the transaction can be considered settled, and we have reached finality.
recipient sender Network
| | |
| ----- share MainPubkey ----> | |
| | |
| | --- send Spends ----> |
| | |
| <---- send Transfer -------- | |
| |
| |
| ------------ verify Transfer ----------------------> |
| | <- at this point
| | the tx is settled
| ------------ send Spend to reissue to self --------> |
| ------------ verify spends ------------------------> |
| |
===================== finality ===================== <- at this point
the funds are safe
Wallet
Any wallet software managing ANT must hold and secure:
- the
MainSecretKey
: password encrypted on disk or hardware wallet (leaking it could result in loss of funds) - the
DerivationIndex
es ofUniquePubkey
s it currently owns (leaking those could result in reduced anonymity) - the ancestry data (parent spends) for each
UniquePubkey
s in order to build theSpend
s for each of them
After spending a UniquePubkey
, the wallet should never spend it again as it will result in burning the money.
After receiving a Transfer
, it should:
- verify that the ancestor spends exist on the Network and are valid
- reissue the received amount to a new
UniquePubkey
by spending the received money immediately. This is necessary to prevent the original sender from burning the ancestors spends which would result in the recipient not being able to spend the money - verify that it didn't do the reissue above already to avoid burning its own money
All DerivationIndex
es should be discarded without a trace (no cache/log) as soon as they are not useful anymore as this could result in a loss of privacy.
Dependencies
~23–36MB
~731K SLoC