Skip to content

Instantly share code, notes, and snippets.

@coranos
Last active May 11, 2021 14:51
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save coranos/9cdd2074ebe5c4a94cee1787accaee38 to your computer and use it in GitHub Desktop.
Camo Banano - Private Coins on Banano using ECDH.

Camo Banano

Private Coins on Banano using ECDH key exchange.

by Coranos

camo banano consists of three layers of technology:

  1. blockchain steganography
  2. storing ecdh public key in the blockchain 3.a. private reversible transactions, using shared seeds. 3.b. private irreversible transactions, using shared public keys.

starting state

it is assumed that each party in the transaction has an existing seed with an existing wallet.

the whole purpose of this system is to send funds to the other wallets on a given seed knowing only the first wallet.

so the parties are in control of the funds but do not show up on a rich list.

blockchain steganography

Since the banano balance has a precision of 29 places after the decimal, you can store 11 bytes of data in the balance field.

since the representative field is only needed for voting, you can use it to store 32 bytes of data.

storing ecdh public key in the blockchain

the ecdh and EdDSA public keys derived from the same private key are not byte-identical.

so the ecdh public key must be stored in the blockchain.

to do this, we split the 32 byte public key into 3 chunks of 11, 11, and 10 bytes and send three transactions to ourselves encoding these bytes in the balance field.

to help is find the data later, we also change the rep to the blake hash of our public key. it is unlikely to be a real rep, and the low vote weight wont matter much anyways.

private reversible transactions

the ecdh public keys are used to create a shared secret.

the shared secret is then hashed once, so its more uniform.

a new seed is created, never used before. that seed is stored in the blockchain in a simmilar way as the ecdh publick key. first you xor the hashed shared secret with the new seed, then you split the seed into three chunks, then you send transactions to yourself to store the seed in the blockchain. in this case the representative is set to the hash of the shared secret, to help find the data.

once both parties have the shared seed, transactions are 'reversible' because both parties can spend the coins as they both have the seed.

private irreversible transactions

using the same method as reversible transactions, a new wallet is created instead of a new seed.

the new wallet is controlled by only one party, so transactions are not reversible.

that new wallet is stored on the blockchain in almost the same way as the reversible seed. the only difference is that the rep is set to the hash of the hashed shared secret and the public key, to distinguish it from the reversble transaction.

mixing

It is possible to use camo to create a mixer, though this is not trustless. Every day the mixer creates a new seed, never used before, and uses it as a hot wallet. You send reversible transactions to the mixer address. The mixer puts one or two transactions into the hot wallet. The mixer sends reversible transactions back to you. The mixer destroys the new seed.

Now assume the mixer is compromised, it's seed is exposed.

  • it will be possible to trace who sent inbound transactions to the mixer.
  • it will be computationaly intensive to trace outbound transactions, as the hot wallet consolidates all inbound transactions, so as long as there is sufficient volume going through the mixer.

false transactions

When making a transfer, it is useful to have several fake transfers made as well, so a third party cannot do timing analysis. This would require the sender to make 1-N transfers to himself, as well as the 1 transfer to the reciever with the same amount.

@meany
Copy link

meany commented Oct 17, 2018

image

@inkeliz
Copy link

inkeliz commented Oct 25, 2018

The irreversible is reversible. It's describe as "new wallet is created instead of a new seed.", who create the wallet? If the sender creates the wallet it's reversible.

The X25519 (ECDH over Curve25519) has a public-key of 32 bytes, also the Ed25519 public-key can be used to a X25519. The LibSodium have a fuction for it, crypto_sign_ed25519_pk_to_curve25519. It's remove the "storage" of the ECDH key, the Nano uses Ed25519.

The shared secret from ECDH is mathematical, not uniform random. In case of X25519 it simply multiplies a point by a scalar on an elliptic curve.

Saying "first you xor the shared secret with the new seed" means that xor will be biased. The xor is acting as a "OTP", however the key is not random and you can't proof that is uniformally random. This is why you MUST derivate the shared key. It can be done by SharedKey =Blake2(message = SharedKey, key = (Sender_PK || Receiver_PK)) or just plain hash like SharedKey =Blake2(message = SharedKey, key = null). In this case the SharedKey will be a uniform random, if the Blake2 is a secure PRF. This kind of construction is often use to create a encryption key from ECDH (X25519), it's done by LibSodium and done by Nanollet 2FA.

Also, instead of XOR you can use a stream-cipher (like Salsa20, ChaCha20), the stream-cipher has the same length of the input.

It's possible to reduce three-blocks to a single block. One block can hold 64 bytes, abusing of the "Representative" (32 byte) and "Link" (32 byte), you can simply store the data using a single block and sent just a 1 RAW.

The main point: The receiver needs a way to get the public-key of the sender, to do the ECDH, seems that it's not cover here.

@renesq
Copy link

renesq commented Oct 25, 2018

It's 29 decimal places after the dot for banano, i.e. 10^29 net decimal capacity (including the 0) which certainly doesn't equal 22 bytes which is 256^22 or roughly 10^52. As far as I know, 29 decimals is about 12 bytes.

It may be irrelevant for this project, but keep in mind that when using byte-encoding, you can't send any specific amount of funds on top, because the two number systems don't harmonize well. Data payload on top of real funds is only feasible if the data is encoded in decimals, and only if the trailing digits are not used by the actual money transfer.

Utilizing the representative and link fields is an option, but it's quite hacky too. They have a capacity of 32 bytes each (64 is the length of the string when represented in hex)

@coranos
Copy link
Author

coranos commented Oct 25, 2018

@inkeliz

Both [RFC7748] and [RFC8032] define the public key value as being a byte string. It should be noted that the public key is computed differently for each of these documents, thus the same private key will not produce the same public key.

So before doing ECDH, I need to store the ECDH public key in the blockchain.
As the ECDH public key is not the same as the EdDSA public key.

@coranos
Copy link
Author

coranos commented Oct 25, 2018

@renesq thanks that was a typo. It should say a 32 byte key stored as 11 bytes.

@inkeliz
Copy link

inkeliz commented Oct 25, 2018

It's possilble to convert a Ed25519 to a X25519. You can use the LibSodium function, which allow to use a public key of Ed25519 to a X25519 equivalent.

Ed25519 keys can be converted to Curve25519 keys, so that the same key pair can be used both for authenticated encryption (crypto_box) and for signatures (crypto_sign).
https://libsodium.gitbook.io/doc/advanced/ed25519-curve25519

Also, we have a Golang code, which may be more friendly at https://github.com/agl/ed25519/blob/master/extra25519/extra25519.go, which have two functions:

// PrivateKeyToCurve25519 converts an ed25519 private key into a corresponding
// curve25519 private key such that the resulting curve25519 public key will
// equal the result from PublicKeyToCurve25519.

// PublicKeyToCurve25519 converts an Ed25519 public key into the curve25519
// public key that would be generated from the same private key.

@coranos
Copy link
Author

coranos commented Oct 26, 2018

@inkeliz on the .. same page you linked:

If you can afford it, using distinct keys for signing and for encryption is still highly recommended.

@coranos
Copy link
Author

coranos commented Oct 26, 2018

im storing the key in the blockchain.

@coranos
Copy link
Author

coranos commented Dec 15, 2018

@inkeliz so it turns out it's slow to store the key in the blockchain (takes 3 blocks, so 3 round trips)

So I'm revisiting your suggestion. I'm using Java, and the best X25519 implementation I found was https://github.com/google/tink

I don't see a public key to public key conversion, do you know of any pure java implementations of this feature? Or just in golang?

@brunoerg
Copy link

@coranos
Copy link
Author

coranos commented Jun 28, 2019

Turns out I found another solution, using javascript, and have implemented it:

https://coranos.github.io/bananos/camo/

@brunoerg
Copy link

brunoerg commented Jun 28, 2019

Isn't https://coranos.github.io/bananos/camo/ open source? I didn't find the source code..

@coranos
Copy link
Author

coranos commented Jun 28, 2019

You serious, you can't find the source code to a webpage?
Control + U.

If you want it as a git repo, the source is here:
https://github.com/coranos/coranos.github.io/tree/master/bananos/camo

@brunoerg
Copy link

Just asking for a repo to contribute! don't worry!

@coranos
Copy link
Author

coranos commented Jun 28, 2019

Ok cool. If you think it's worth something I can pull the page into it's own repo.

The meat of the code is also in bananojs.

https://github.com/coranos/bananojs

Instructions on the commands are in the readme.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment