Seedelf is a stealth wallet that hides the receiver and spender using a non-interactive variant of Schnorr's ÎŁ-protocol for the Discrete Logarithm Relation. It should be computationally infeasible to deduce the intended receiver or spender of UTxOs inside this wallet.
The seedelf-cli is available on Linux, Windows, and macOS.
The wallet name, Seedelf, comes from the prefix of the identifier token used to locate the datum of a UTxO inside the wallet contract. The root datum becomes readily available by searching for a specific seedelf token, providing a personalized touch while maintaining privacy.
Its primary purpose is to locate the root datum for re-randomization. Alice can ask Bob to send funds to their seedelf. Bob can find the UTxO that holds the seedelf token inside the contract and will use that datum to re-randomize a new datum for Alice. Bob will then send funds to the contract with this new randomized datum.
The token name scheme:
5eed0e1f | personal | Idx | Tx
The token name scheme has these lengths: 8 for the prefix, 30 for personal, 2 for Tx#Idx, and 24 for Tx#Id.
Note: Not all personal tags can be converted into ASCII.
For example, the personal tag below can't be converted to ASCII, but it can still be displayed in hex.
seedelf: 5eed0e1f00000acab00000018732122c62aea887cd16d743c3045e524f019aea
username: 00000acab00000018732122c62aea8
display name: 00000acab00000018732122c62aea8
But some do convert and can be displayed correctly.
seedelf: 5eed0e1f5b416e6369656e744b72616b656e5d016ad73d1216555b07ad5a449ff2
username: 5b416e6369656e744b72616b656e5d
display name: [AncientKraken]
The personal tag creates a custom touch for a seedelf that can be used for search purposes.
A stealth wallet hides the receiver and spender of funds inside the contract. Below is a quick overview of how the wallet contract works.
G1
: The base
Generator
: An element of the curve that produces more curve elements through scalar multiplication.
Public Value
: The user's public value element; this information is known publicly.
Register
: The datum consisting of the generator and the public value.
Re-randomizing
: Allows constructing a new register from an existing one.
The Register contains the generator and the public key for some UTxO.
pub type Register {
// the generator, #<Bls12_381, G1>
generator: ByteArray,
// the public value, #<Bls12_381, G1>
public_value: ByteArray,
}
A UTxO is spendable if the transaction can prove the knowledge of the secret key using a non-interactive zero-knowledge Schnorr ÎŁ-protocol. A valid proof has the form:
where
A register defines a type of public address used to produce private addresses. A user wishing to create a stealth address for another user will find their public address and re-randomize the Register as the new datum of a future UTxO.
A user selects a random integer,
The new Register appears random from the outside viewer and can not be inverted back into the public Register because we assume the Elliptic Curve Decisional-Diffie-Hellman (ECDDH) problem is hard. The scalar multiplication of the Register maintains spendability while providing privacy about who owns the UTxO.
The proof of re-randomization reduces to proving the original Schnorr ÎŁ-protocol.
In the contract, there will be many UTxOs with unique registers. A user can always find their UTxOs by searching the UTxO set at the contract address and finding all the registers that satisfy
The wallet is just a smart contract. It is bound by the cpu and memory units of the underlying blockchain, meaning that a UTxO can only hold so many tokens before it becomes unspendable due to space and time limitations. In this implementation, the value is not hidden nor mixed in any way, shape, or fashion. This contract is equivalent to generating addresses using a hierarchical deterministic wallet. However, instead of keeping the root key private and generating the address when requested, it is now public via a datum. Consequently, address generation is the sender's responsibility, not the receiver's.
Sending funds requires a correct and equal
This Register would become unspendable, resulting in lost funds.
This wallet does not verify that the points supplied when you build a Register lie in the prime-order subgroup of BLS12-381. If you insert a point that carries any torsion component (i.e., it fails a subgroup membership check), the resulting on-chain Register becomes unspendable, as the validator will never consider it valid, resulting in lost funds. Always generate Registers with the supplied CLI commands (the points will always be torsion-free). If you decide to bring your points, you must run is_torsion_free() (or multiply by the cofactor) before submitting them to the wallet.
There exist multiple attacks that are known to break the privacy of this wallet. The first attack is picking a bad
Privacy is preserved if
Seedelf communicates with third-party APIs such as koios.rest
and giveme.my
. These services track IP addresses as part of their abuse prevention and DoS protection mechanisms. koios.rest
does not support Tor access. giveme.my
does support Tor access but is not implemented at the CLI level. Your public IP directly connects to your Koios API and GiveMeMy requests. Consider routing traffic through a trusted VPN that doesn't log activity, not using a personal device, or using an identifiable IP for maximum privacy when engaging in sensitive activity.
We're actively and continuously exploring options for Tor access to all services Seedelf depends on to function.
Please note that crate.io
and github.com
track IP addresses when using cargo install
and git clone
, respectively.
The design of the wallet contract opens users to a troll attack by overloading a UTxO with a large but useless reference script. Creating this UTxO results in the user, Alice, paying significantly more fees for that transaction. In contrast, Bob will pay more but less than Alice to spend that resulting UTxO. It's a useless troll attack that exists. The attack does not favor Alice and will cost her more to execute than it will be for Bob to spend the UTxO.
As noted above, direct tracking methods are not feasible as long as the privacy-preserving techniques are followed. However, implicit tracking methods (ITMs) exist even under the assumption of privacy preservation.
The first ITM is entering the wallet for the first time, as a CIP30 wallet will have to be used to mint the original seedelf. This requirement means that an outside wallet, util mint
command to do a stealth seedelf mint producing
The second ITM is the linkability of sequential UTxOs via the transaction fee. The owner is hidden and unknown during a transaction. Still, it is safe to assume that the UTxO that pays the transaction fee remains under the original owner's control. Thus, the ownership could be implicitly linked if the fee-paying UTxO chain returns to
The third ITM is a flood attack on the protocol itself. The stealthiness of the Seedelf protocol relies on a healthy population of seedelfs as the probability of ownership,
During the create-a-wallet process, a file will be saved to the default $HOME/.seedelf
folder. This file is an encrypted file, via AES256GCM, requiring a password to decrypt. Using the CLI or GUI, seedelf will require the user to select a password with these requirements below.
# Minimum Length: At Least 14 Characters.
# Uppercase Letter: Requires At Least One Uppercase Character.
# Lowercase Letter: Requires At Least One Lowercase Character.
# Number: Requires At Least One Digit.
# Special Character: Requires At Least One Special Symbol.
Seedelf attempts to help the user with the password requirements above. Still, ultimately, it is up to the user to select a sufficiently complex password. The seedelf wallet file has this structure:
{
"salt": "salt_here",
"nonce": "nonce here",
"data": "encrypted_data_here"
}
The wallet is generated randomly using the user's default randomness provided by their os. The user must store the file or file contents safely.
Failure to do so may result in lost or corrupted files.
Seedelf does not contain a full peer-to-peer node. It relies heavily on koios.rest
for the data layer, providing UTxO information and transaction evaluation. Though due to the wallet architecture, faking UTxO information should be impossible from the Koios side, as this would require knowledge of a user's secret key or the locally known seedelf token name. The worst case for the data layer is Koios restricting access to the data necessary for Seedelf to function correctly. So as long as Koios exists and is fair, Seedelf should be safe to use.
The happy path for testing follows Alice and Bob as they interact with their seedelf wallets. The scripts will allow users to create and delete seedelfs, send tokens to another seedelf, and remove their tokens. The happy path has basic functionality, but it does serve as an example of how a seedelf wallet would work.
A seedelf will be saved locally inside a file. This file is a simple way to store the secret value
Removing funds is a simple process. Given a secret value
/// The zero-knowledge elements required for the proof. The c value will be
/// computed using the Fiat-Shamir heuristic. The vkh used here is a one-time
/// pad for the proof to prevent rollback attacks.
///
pub type Proof {
// this is z = r + c * x as a byte array
z_b: ByteArray,
// this is the g^r compressed G1Element
g_r_b: ByteArray,
// one-time use signature
vkh: VerificationKeyHash,
}
These ZK elements, combined with a register, are the only required knowledge to spend a UTxO. The spent UTxOs can be sent to any non-seedelf wallet or recombined into a new UTxO inside the seedelf wallet with a new re-randomized register. A random key signature is required to create a one-time pad for the proof, as funds could be re-spent without it because a transaction can drop mempool during a rollback event. A malicious user can pick off the proof and resubmit. The re-spending of the proof is entirely circumvented by having a random key sign the transaction and using that verification key hash inside the Fiat-Shamir transform.
Sending funds works similarly to removing funds, but instead of sending funds out of the contract, they spend them back in the contract with a new re-randomized register by finding the Register on some other seedelf token. This act preserves privacy. An outside user should only see random UTxOs collected and sent to a new random register. The link between Alice and Bob should remain hidden.
Spendability is always in the hands of the original owner. It is safe to assume a singular owner if two UTxOs from the contract are inside the same transaction. If two different users spent UTxOs together inside a single transaction, then there would be no way to ensure that one of the parties does not lose or steal funds. If Alice and Bob work together, then either Alice or Bob will have the chance of losing funds. Inside real mixers, the possibility of losing funds does not exist as the spendability is arbitrary, thus ensuring the mixing probably exists. The seedelf wallet is purely for stealth, not for mixing.
The seedelf-cli
uses the Cardano Collateral Provider. Every user will share the same collateral UTxO, thus defeating the collateral problem.
Users can interact with the wallet protocol via the seedelf-platform.
For questions, suggestions, or concerns, please get in touch with [email protected] or join the Seedelf Discord.