diff --git a/cryptography/crypto/userdata.go b/cryptography/crypto/userdata.go index daa77448..706fc079 100644 --- a/cryptography/crypto/userdata.go +++ b/cryptography/crypto/userdata.go @@ -16,11 +16,13 @@ package crypto -import "math/big" -import "golang.org/x/crypto/chacha20" -import "github.com/deroproject/derohe/cryptography/bn256" +import ( + "math/big" -import "github.com/go-logr/logr" + "github.com/deroproject/derohe/cryptography/bn256" + "github.com/go-logr/logr" + "golang.org/x/crypto/chacha20" +) var Logger logr.Logger = logr.Discard() // default discard all logs, someone needs to set this up @@ -54,3 +56,32 @@ func GenerateSharedSecret(secret *big.Int, peer_publickey *bn256.G1) (shared_key return } + +// Derive a key from a random scalar +// We multiply the generator point by the scalar and hash the compressed point +func DeriveKeyFromR(r *BNRed) (key [32]byte) { + compressed := GPoint.ScalarMult(r).EncodeCompressed() + if len(compressed) != 33 { + panic("point compression needs to be fixed") + } + + key = Keccak256(compressed[:]) + + return +} + +// Derive a key from a point and a secret +// We multiply the point by the inverse of the secret and hash the compressed point +func DeriveKeyFromPoint(point *bn256.G1, secret *big.Int) (key [32]byte) { + sk_inversed := new(big.Int).ModInverse(secret, bn256.Order) + + decrypted_point := new(bn256.G1).ScalarMult(point, sk_inversed) + compressed := decrypted_point.EncodeCompressed() + if len(compressed) != 33 { + panic("point compression needs to be fixed") + } + + key = Keccak256(compressed[:]) + + return +} diff --git a/cryptography/crypto/userdata_test.go b/cryptography/crypto/userdata_test.go new file mode 100644 index 00000000..ba3a1567 --- /dev/null +++ b/cryptography/crypto/userdata_test.go @@ -0,0 +1,42 @@ +package crypto + +import ( + "testing" + + "github.com/deroproject/derohe/cryptography/bn256" +) + +func TestEncryptDecrypt(t *testing.T) { + var key [32]byte + expected := []byte("Hello World") + var payload []byte + payload = append(payload, expected...) + + EncryptDecryptUserData(key, payload) + EncryptDecryptUserData(key, payload) + + for i := range expected { + if expected[i] != payload[i] { + t.Fatal("error on encrypt/decrypt") + } + } +} + +func TestSharedKey(t *testing.T) { + r := RandomScalarBNRed() + private_key := RandomScalarBNRed() + public_key := GPoint.ScalarMult(private_key) + + // Create a curve point using PK + encrypted_key := new(bn256.G1).ScalarMult(public_key.G1(), r.BigInt()) + + // Decrypt it + decrypted_r := DeriveKeyFromPoint(encrypted_key, private_key.BigInt()) + + r_bytes := DeriveKeyFromR(r) + for i := range r_bytes { + if r_bytes[i] != decrypted_r[i] { + t.Fatalf("error on shared key, index: %d", i) + } + } +} diff --git a/proof/proof.go b/proof/proof.go index efa5c83b..f5103124 100644 --- a/proof/proof.go +++ b/proof/proof.go @@ -16,15 +16,17 @@ package proof -import "fmt" -import "math/big" -import "strings" -import "encoding/hex" - -import "github.com/deroproject/derohe/cryptography/crypto" -import "github.com/deroproject/derohe/rpc" -import "github.com/deroproject/derohe/cryptography/bn256" -import "github.com/deroproject/derohe/transaction" +import ( + "encoding/hex" + "fmt" + "math/big" + "strings" + + "github.com/deroproject/derohe/cryptography/bn256" + "github.com/deroproject/derohe/cryptography/crypto" + "github.com/deroproject/derohe/rpc" + "github.com/deroproject/derohe/transaction" +) //import "github.com/deroproject/derosuite/walletapi" // to decode encrypted payment ID @@ -98,13 +100,20 @@ func Prove(proof string, input_tx string, ring_string [][]string, mainnet bool) receivers = append(receivers, astring.String()) amounts = append(amounts, amount) - //crypto.EncryptDecryptUserData(addr.PublicKey.G1(), tx.Payloads[t].RPCPayload) - crypto.EncryptDecryptUserData(crypto.Keccak256(shared_key[:], tx.Payloads[t].Statement.Publickeylist[k].EncodeCompressed()), tx.Payloads[t].RPCPayload) - // skip first byte as it is not guaranteed, even rest of the bytes are not + payload := tx.Payloads[t].RPCPayload + switch tx.Payloads[t].RPCType { + case transaction.ENCRYPTED_DEFAULT_PAYLOAD_CBOR: + crypto.EncryptDecryptUserData(crypto.Keccak256(shared_key[:], tx.Payloads[t].Statement.Publickeylist[k].EncodeCompressed()), payload) + case transaction.ENCRYPTED_DEFAULT_PAYLOAD_CBOR_V2: + // Skip first 66 bytes as they are encrypted keys for both parties + payload = payload[66:] + crypto.EncryptDecryptUserData(crypto.Keccak256(shared_key[:], tx.Payloads[t].Statement.Publickeylist[k].EncodeCompressed()), payload) + } - payload_raw = append(payload_raw, tx.Payloads[t].RPCPayload[1:]) + // skip first byte as it is not guaranteed, even rest of the bytes are not + payload_raw = append(payload_raw, payload[1:]) var args rpc.Arguments - if err := args.UnmarshalBinary(tx.Payloads[t].RPCPayload[1:]); err == nil { + if err := args.UnmarshalBinary(payload[1:]); err == nil { payload_decoded = append(payload_decoded, fmt.Sprintf("%s", args)) } else { payload_decoded = append(payload_decoded, err.Error()) diff --git a/transaction/transaction.go b/transaction/transaction.go index f56d653a..4d64076e 100644 --- a/transaction/transaction.go +++ b/transaction/transaction.go @@ -16,14 +16,16 @@ package transaction -import "fmt" -import "bytes" -import "math/big" -import "encoding/binary" - -import "github.com/deroproject/derohe/cryptography/crypto" -import "github.com/deroproject/derohe/cryptography/bn256" -import "github.com/deroproject/derohe/rpc" +import ( + "bytes" + "encoding/binary" + "fmt" + "math/big" + + "github.com/deroproject/derohe/cryptography/bn256" + "github.com/deroproject/derohe/cryptography/crypto" + "github.com/deroproject/derohe/rpc" +) type TransactionType uint64 @@ -62,8 +64,15 @@ const PAYLOAD_LIMIT = 1 + 144 // entire payload header is mandatorily encrypted // 144 byte payload ( to implement specific functionality such as delivery of keys etc), user dependent encryption const PAYLOAD0_LIMIT = 144 // 1 byte has been reserved for sender position in ring representation in a byte, uptp 256 ring +// Payload V2 limit size +// 33 bytes are for the two keys included in the payload for decrypt/encrypt process +const PAYLOAD_V2_LIMIT = PAYLOAD0_LIMIT - (33 * 2) + const ENCRYPTED_DEFAULT_PAYLOAD_CBOR = byte(0) +// New payload format used +const ENCRYPTED_DEFAULT_PAYLOAD_CBOR_V2 = byte(1) + // the core transaction // in our design, tx cam be sent by 1 wallet, but SC part/gas can be signed by any other user, but this is not implemented type Transaction_Prefix struct { diff --git a/walletapi/daemon_communication.go b/walletapi/daemon_communication.go index cf0e9c4d..bdc38e44 100644 --- a/walletapi/daemon_communication.go +++ b/walletapi/daemon_communication.go @@ -922,18 +922,17 @@ func (w *Wallet_Memory) synchistory_block(scid crypto.Hash, topo int64) (err err x.Add(new(bn256.G1).Set(&x), tx.Payloads[t].Statement.C[k]) // get the blinder blinder := &x - shared_key := crypto.GenerateSharedSecret(r, tx.Payloads[t].Statement.Publickeylist[k]) - // proof is blinder + amount transferred, it will recover the encrypted rpc payload also // enable sender side proofs proof := rpc.NewAddressFromKeys((*crypto.Point)(blinder)) proof.Proof = true - proof.Arguments = rpc.Arguments{{Name: "H", DataType: rpc.DataHash, Value: crypto.Hash(shared_key)}, {Name: rpc.RPC_VALUE_TRANSFER, DataType: rpc.DataUint64, Value: uint64(entry.Amount - entry.Burn)}} - entry.Proof = proof.String() + entry.PayloadType = tx.Payloads[t].RPCType switch tx.Payloads[t].RPCType { case transaction.ENCRYPTED_DEFAULT_PAYLOAD_CBOR: + shared_key := crypto.GenerateSharedSecret(r, tx.Payloads[t].Statement.Publickeylist[k]) + proof.Arguments = rpc.Arguments{{Name: "H", DataType: rpc.DataHash, Value: crypto.Hash(shared_key)}, {Name: rpc.RPC_VALUE_TRANSFER, DataType: rpc.DataUint64, Value: uint64(entry.Amount - entry.Burn)}} crypto.EncryptDecryptUserData(crypto.Keccak256(shared_key[:], tx.Payloads[t].Statement.Publickeylist[k].EncodeCompressed()), tx.Payloads[t].RPCPayload) //fmt.Printf("decoded plaintext payload t %d %x\n",t,tx.Payloads[t].RPCPayload) @@ -949,13 +948,39 @@ func (w *Wallet_Memory) synchistory_block(scid crypto.Hash, topo int64) (err err args, _ := entry.ProcessPayload() _ = args - // fmt.Printf("data received %s idx %d arguments %s\n", string(entry.Payload), sender_idx, args) + case transaction.ENCRYPTED_DEFAULT_PAYLOAD_CBOR_V2: + rpc_payload := tx.Payloads[t].RPCPayload + // We are the sender + var handle bn256.G1 + err = handle.DecodeCompressed(rpc_payload[0:33]) + if err != nil { + continue + } + + r := crypto.DeriveKeyFromPoint(&handle, w.Get_Keys().Secret.BigInt()) + key := crypto.Keccak256(r[:], tx.Payloads[t].Statement.Publickeylist[k].EncodeCompressed()) + + proof.Arguments = rpc.Arguments{{Name: "H", DataType: rpc.DataHash, Value: crypto.Hash(key)}, {Name: rpc.RPC_VALUE_TRANSFER, DataType: rpc.DataUint64, Value: uint64(entry.Amount - entry.Burn)}} + + payload := rpc_payload[66:] + crypto.EncryptDecryptUserData(key, payload) + + addr := rpc.NewAddressFromKeys((*crypto.Point)(w.account.Keys.Public.G1())) + addr.Mainnet = w.GetNetwork() + entry.Sender = addr.String() + + entry.Payload = append(entry.Payload, rpc_payload[1:]...) + entry.Data = append(entry.Data, rpc_payload[:]...) + + args, _ := entry.ProcessPayload() + _ = args default: entry.PayloadError = fmt.Sprintf("unknown payload type %d", tx.Payloads[t].RPCType) entry.Payload = tx.Payloads[t].RPCPayload } + entry.Proof = proof.String() //paymentID := binary.BigEndian.Uint64(payment_id_encrypted_bytes[:]) // get decrypted payment id addr := rpc.NewAddressFromKeys((*crypto.Point)(tx.Payloads[t].Statement.Publickeylist[k])) @@ -981,18 +1006,16 @@ func (w *Wallet_Memory) synchistory_block(scid crypto.Hash, topo int64) (err err blinder := &x - shared_key := crypto.GenerateSharedSecret(w.account.Keys.Secret.BigInt(), tx.Payloads[t].Statement.D) - // enable receiver side proofs proof := rpc.NewAddressFromKeys((*crypto.Point)(blinder)) proof.Proof = true - proof.Arguments = rpc.Arguments{{Name: "H", DataType: rpc.DataHash, Value: crypto.Hash(shared_key)}, {Name: rpc.RPC_VALUE_TRANSFER, DataType: rpc.DataUint64, Value: uint64(entry.Amount)}} - entry.Proof = proof.String() entry.PayloadType = tx.Payloads[t].RPCType switch tx.Payloads[t].RPCType { - case 0: + case transaction.ENCRYPTED_DEFAULT_PAYLOAD_CBOR: + shared_key := crypto.GenerateSharedSecret(w.account.Keys.Secret.BigInt(), tx.Payloads[t].Statement.D) + proof.Arguments = rpc.Arguments{{Name: "H", DataType: rpc.DataHash, Value: crypto.Hash(shared_key)}, {Name: rpc.RPC_VALUE_TRANSFER, DataType: rpc.DataUint64, Value: uint64(entry.Amount)}} //fmt.Printf("decoding encrypted payload %x\n",tx.Payloads[t].RPCPayload) crypto.EncryptDecryptUserData(crypto.Keccak256(shared_key[:], w.GetAddress().PublicKey.EncodeCompressed()), tx.Payloads[t].RPCPayload) @@ -1020,11 +1043,47 @@ func (w *Wallet_Memory) synchistory_block(scid crypto.Hash, topo int64) (err err // fmt.Printf("data received %s idx %d arguments %s\n", string(entry.Payload), sender_idx, args) + case transaction.ENCRYPTED_DEFAULT_PAYLOAD_CBOR_V2: + rpc_payload := tx.Payloads[t].RPCPayload + // We are the receiver + var handle bn256.G1 + err = handle.DecodeCompressed(rpc_payload[33:66]) + if err != nil { + continue + } + + r := crypto.DeriveKeyFromPoint(&handle, w.Get_Keys().Secret.BigInt()) + key := crypto.Keccak256(r[:], w.GetAddress().PublicKey.EncodeCompressed()) + proof.Arguments = rpc.Arguments{{Name: "H", DataType: rpc.DataHash, Value: crypto.Hash(key)}, {Name: rpc.RPC_VALUE_TRANSFER, DataType: rpc.DataUint64, Value: uint64(entry.Amount)}} + + payload := rpc_payload[66:] + crypto.EncryptDecryptUserData(key, payload) + sender_idx := uint(payload[0]) + // if ring size is 2, the other party is the sender so mark it so + if uint(tx.Payloads[t].Statement.RingSize) == 2 { + sender_idx = 0 + if j == 0 { + sender_idx = 1 + } + } + + if sender_idx <= uint(tx.Payloads[t].Statement.RingSize) { + addr := rpc.NewAddressFromKeys((*crypto.Point)(tx.Payloads[t].Statement.Publickeylist[sender_idx])) + addr.Mainnet = w.GetNetwork() + entry.Sender = addr.String() + } + + entry.Payload = append(entry.Payload, payload[1:]...) + entry.Data = append(entry.Data, payload[:]...) + + args, _ := entry.ProcessPayload() + _ = args + default: entry.PayloadError = fmt.Sprintf("unknown payload type %d", tx.Payloads[t].RPCType) entry.Payload = tx.Payloads[t].RPCPayload } - + entry.Proof = proof.String() //fmt.Printf("Received %s amount in TX(%d) %s payment id %x Src_ID %s data %s\n", globals.FormatMoney(changed_balance-previous_balance), tx.Height, bl.Tx_hashes[i].String(), entry.PaymentID, tx.Src_ID, tx.Data) //fmt.Printf("Received amount in TX(%d) %s payment id %x Src_ID %s data %s\n", tx.Height, bl.Tx_hashes[i].String(), entry.PaymentID, tx.SrcID, tx.Data) total_received += (changed_balance - previous_balance) diff --git a/walletapi/transaction_build.go b/walletapi/transaction_build.go index 572a8221..56b64a0b 100644 --- a/walletapi/transaction_build.go +++ b/walletapi/transaction_build.go @@ -59,7 +59,7 @@ rebuild_tx: panic("currently we cannot use more than 240 bits") } - for t, _ := range transfers { + for t := range transfers { var publickeylist, C, CLn, CRn []*bn256.G1 var D bn256.G1 @@ -148,6 +148,9 @@ rebuild_tx: fees_done = true } + // Generate a new randomness for the payload + r_payload := crypto.RandomScalarBNRed() + for i := range publickeylist { // setup commitments var x bn256.G1 switch { @@ -172,24 +175,36 @@ rebuild_tx: panic("currently we donot support ring size >= 512") } - asset.RPCType = transaction.ENCRYPTED_DEFAULT_PAYLOAD_CBOR + asset.RPCType = transaction.ENCRYPTED_DEFAULT_PAYLOAD_CBOR_V2 - data, _ := transfers[t].Payload_RPC.CheckPack(transaction.PAYLOAD0_LIMIT) + data, err := transfers[t].Payload_RPC.CheckPack(transaction.PAYLOAD_V2_LIMIT) + if err != nil { + // TODO, we should returns all the error + return nil + } - shared_key := crypto.GenerateSharedSecret(r, publickeylist[i]) + // Generate the keys for each party + // those are stored in the payload too + sender_key := new(bn256.G1).ScalarMult(publickeylist[witness_index[0]], r_payload.BigInt()) + receiver_key := new(bn256.G1).ScalarMult(publickeylist[i], r_payload.BigInt()) - // witness_index[0] is sender, witness_index[1] is receiver - asset.RPCPayload = append([]byte{byte(uint(witness_index[0]))}, data...) + // Each point is 33 bytes compressed + // This means we have a 66 bytes overhead + asset.RPCPayload = append(asset.RPCPayload, sender_key.EncodeCompressed()...) + asset.RPCPayload = append(asset.RPCPayload, receiver_key.EncodeCompressed()...) - //fmt.Printf("buulding shared_key %x index of receiver %d\n",shared_key,i) - //fmt.Printf("building plaintext payload %x\n",asset.RPCPayload) + var payload []byte + // witness_index[0] is sender, witness_index[1] is receiver + payload = append(payload, byte(uint(witness_index[0]))) + payload = append(payload, data...) - //fmt.Printf("%d packed rpc payload %d %x\n ", t, len(data), data) // make sure used data encryption is optional, just in case we would like to play together with ring members // we intoduce an element to create dependency of input key, so receiver cannot prove otherwise - crypto.EncryptDecryptUserData(crypto.Keccak256(shared_key[:], publickeylist[i].EncodeCompressed()), asset.RPCPayload) + bytes := crypto.DeriveKeyFromR(r_payload) + crypto.EncryptDecryptUserData(crypto.Keccak256(bytes[:], publickeylist[i].EncodeCompressed()), payload) - //fmt.Printf("building encrypted payload %x\n",asset.RPCPayload) + // Inject the encrypted payload now + asset.RPCPayload = append(asset.RPCPayload, payload...) default: x.ScalarMult(crypto.G, new(big.Int).SetInt64(0))