| Crates.io | sn_transfers |
| lib.rs | sn_transfers |
| version | 0.20.3 |
| created_at | 2020-09-03 13:31:00.032757+00 |
| updated_at | 2024-11-12 19:00:31.111049+00 |
| description | Safe Network Transfer Logic |
| homepage | https://maidsafe.net |
| repository | https://github.com/maidsafe/safe_network |
| max_upload_size | |
| id | 284252 |
| size | 333,831 |
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 Spends which are all linked together all the way to the first Spend which we call Genesis. Those Spends contain transaction data and all the information necessary for verification and audit of the currency.
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 a MainPubkey is generated; it is used for spending ANT.Unlike one might expect, the MainPubkey itself never owns any money: UniquePubkeys derived from it do. Value is owned by those UniquePubkeys 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 UniquePubkeys is by using bls key derivation, an algorithm which creates a new key from another key by using a large number called a DerivationIndex. UniquePubkeys 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 UniquePubkeys 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 from MainPubkey)
MainSecretKey => blsttc::SecretKey
DerivedSecretKey => blsttc::SecretKey (derived from MainSecretKey)
DerivationIndex => u256 (big number impossible to guess, used to derive keys)
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
UniquePubkeyancestors (which refer to it as a one of the descendants)descendants (which could refer to it as one of the ancestors)Note that
ancestorsanddescendantsshould not be confused with inputs and outputs of a transaction. If we were to put that in traditional input output terms:
- The
ancestorsare the inputs of the transaction whereunique_pubkeyis an output.- The
unique_pubkeyis an input of the transaction wheredescendantsare an output.
GenesisSpend
/ \
SpendA SpendB
/ \ \
SpendC SpendD SpendE
/ \ \
... ... ...
All the
Spends 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
3and4. Z's value is the sum of the inherited value from its ancestors:3 + 4 = 7.In this example
SpendWof value9would look something like:Spend { unique_pubkey = W, ancestors = {S}, descendants = {Z : 3, Q : 6}, }
Spends 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:
SpendAddress derived from its UniquePubkeyIf multiple valid spend entries are found at a single address, that
UniquePubkeyis 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
Spendentries in the Network for theUniquePubkeyD. We say thatDis burnt. The result is thatEandFhave a burnt parent making them unspendable. When fetchingD, one would get a burnt spend entry as we have twoSpends on the Network at thatSpendAddress:Spend { unique_pubkey = D, ancestors = {A}, descendants = {E : 3}, } Spend { unique_pubkey = D, ancestors = {A}, descendants = {F : 3}, }
Spends 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.
All the spends on the Network form a DAG of Spends, 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 Spends 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 Spends 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:
To perform a Transfer, one must have money to spend: own at least a spendable UniquePubkey and the key to spend it:
UniquePubkey's secret DerivationIndex and the MainSecretKey in order to derive the DerivedSecretKeyDerivedSecretKeys that owns that UniquePubkeyThe 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 aSpendfrom itCashNoteRemption: the minimal information necessary for a recipient to identify a receivedUniquePubkeyand be able to spend itTransfer: an encrypted package ofCashNoteRemption, destined to the recipient
A Transfer consists of the following steps:
First we need to decide on the transfer's recipient and amount:
MainPubkey and an amount in NanoTokensThen we gather our local spendable UniquePubkeys:
UniquePubkeys we own that make up that amount or moreUniquePubkeys as we need them in the SpendAll 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.
UniquePubkey(s) for the recipient by deriving them from the recipient's MainPubkey with randomly generated DerivationIndex(es)With all the above data, we can finally create the Spends which represent the sender's commitment to do the transfer.
Spends for each spent UniquePubkey
unique_pubkey: UniquePubkey we own that we wish to spendancestors: reference to the ancestors of that UniquePubkey to prove its validitydescendants: reference to the UniquePubkey(s) of the recipient(s)Note that the
Spenddoes not contain anyDerivationIndexes nor does it contain anyMainPubkeys. This makesSpends 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)
Spend with the DerivedSecretKey that we derive from MainSecretKey with that Spend's UniquePubkey's DerivationIndexSignedSpends to the NetworkAfter 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
At this point, the recipient doesn't yet know of:
Spend(s) we uploaded to the Network for them at SpendAddressUniquePubkey(s) we created for them which can be obtained from the DerivationIndexNote that
SpendAddress: the network address of aSpendis 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
Transferis 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 correspondingUniquePubkeys that were received in thisTransfer.
An encrypted Transfer is a list of CashNoteRedemptions, each corresponding to one of the received UniquePubkeys:
pub struct CashNoteRedemption {
pub derivation_index: DerivationIndex,
pub parent_spends: BTreeSet<SpendAddress>,
}
It contains the DerivationIndex used to derive:
UniquePubkey that we're receiving from our MainPubkeyDerivedSecretKey from our MainSecretKey: needed to spend this new UniquePubkeyOnce received and decrypted by the recipient, the CashNoteRedemption can be used to verify the transfer using the Spends online and add the received UniquePubkeys to our spendable UniquePubkeys stash:
UniquePubkey from the CashNoteRedemption's DerivationIndex and our MainPubkeySpends at the SpendAddress on the Network provided in the CashNoteRedemption and making sure they all exist on the NetworkSpends
UniquePubkey as a descendantSpendsUniquePubkey is now ours and spendable!CashNote with all the above information to simplify spending the received UniquePubkeySince
CashNotes contain sensitive information, they should never be shared or leaked as it would reveal the link between theMainPubkeyand theUniquePubkeyof 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:
DerivationIndex for our spendable moneyUniquePubkeys are not burnable by anyone but ourselvesFailing to do so exposes the receiver to the risk of having their keys become unspendable if the sender decides to burn the parent
Spends
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
Any wallet software managing ANT must hold and secure:
MainSecretKey: password encrypted on disk or hardware wallet (leaking it could result in loss of funds)DerivationIndexes of UniquePubkeys it currently owns (leaking those could result in reduced anonymity)UniquePubkeys in order to build the Spends for each of themAfter spending a UniquePubkey, the wallet should never spend it again as it will result in burning the money.
After receiving a Transfer, it should:
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 moneyAll DerivationIndexes 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.