Added unsafe []byte to []uint64 conversion and XOR

This commit is contained in:
David Vennik
2022-12-09 18:22:47 +01:00
parent 69d43ca6e3
commit f4b5dfffc1
7 changed files with 367 additions and 18 deletions

View File

@@ -5,6 +5,8 @@ import (
"crypto/aes"
"crypto/cipher"
"fmt"
"reflect"
"unsafe"
"github.com/Indra-Labs/indra"
"github.com/Indra-Labs/indra/pkg/key/ecdh"
@@ -38,7 +40,7 @@ func Encipher(blk cipher.Block, n nonce.IV, b []byte) {
const SecretLength = 32
func CombineCiphers(secrets [][]byte) (combined []byte, e error) {
func CombineCiphers(secrets ...[]byte) (combined []byte, e error) {
// All ciphers must be the same length, 32 bytes for AES-CTR 256
for i := range secrets {
if len(secrets[i]) != SecretLength {
@@ -55,3 +57,50 @@ func CombineCiphers(secrets [][]byte) (combined []byte, e error) {
}
return
}
type CipherKeys struct {
From *prv.Key
To *pub.Key
}
func GenerateCompositeCipher(ck ...*CipherKeys) (secret []byte, e error) {
// var secret64 []uint64
var secrets [][]byte
for i := range ck {
var sec []byte
sec = ecdh.Compute(ck[i].From, ck[i].To)
secrets = append(secrets, sec)
// sec64 := touint64(sec)
// if i == 0 {
// secret64 = sec64
// } else {
// xor64(secret64, sec64)
// }
}
return CombineCiphers(secrets...)
}
// touint64 assumes len(x)%8 == 0
func touint64(x []byte) []uint64 {
xx := make([]uint64, 0, 0)
hdrp := (*reflect.SliceHeader)(unsafe.Pointer(&xx))
hdrp.Data = (*reflect.SliceHeader)(unsafe.Pointer(&x)).Data
hdrp.Len = len(x) / 8
hdrp.Cap = len(x) / 8
return xx
}
func tobytes(x []uint64) []byte {
xx := make([]byte, 0, 0)
hdrp := (*reflect.SliceHeader)(unsafe.Pointer(&xx))
hdrp.Data = (*reflect.SliceHeader)(unsafe.Pointer(&x)).Data
hdrp.Len = len(x) * 8
hdrp.Cap = len(x) * 8
return xx
}
func xor64(a, b []uint64) {
for i := range a {
a[i] ^= b[i]
}
}

156
pkg/ciph/cipher_test.go Normal file
View File

@@ -0,0 +1,156 @@
package ciph
import (
"crypto/aes"
"crypto/cipher"
"testing"
"github.com/Indra-Labs/indra/pkg/key/ecdh"
"github.com/Indra-Labs/indra/pkg/key/prv"
"github.com/Indra-Labs/indra/pkg/key/pub"
nonce2 "github.com/Indra-Labs/indra/pkg/nonce"
"github.com/Indra-Labs/indra/pkg/sha256"
"github.com/Indra-Labs/indra/pkg/testutils"
)
func TestGenerateCompositeCipher(t *testing.T) {
var cifkeys []*CipherKeys
var e error
for i := 0; i < 3; i++ {
var p1, p2 *prv.Key
if p1, e = prv.GenerateKey(); check(e) {
t.Error(e)
t.FailNow()
}
pub1 := pub.Derive(p1)
if p2, e = prv.GenerateKey(); check(e) {
t.Error(e)
t.FailNow()
}
cifkeys = append(cifkeys, &CipherKeys{p2, pub1})
}
var m, original []byte
m, _, e = testutils.GenerateTestMessage(1024)
log.I.S(sha256.Single(m))
original = make([]byte, len(m))
copy(original, m)
var compositeSecret []byte
if compositeSecret, e =
GenerateCompositeCipher(cifkeys...); check(e) {
t.Error(e)
t.FailNow()
}
var block cipher.Block
if block, e = aes.NewCipher(compositeSecret); check(e) {
t.Error(e)
t.FailNow()
}
nonce := nonce2.New()
Encipher(block, nonce, m)
log.I.S(sha256.Single(m))
// for i := range cifkeys {
// var blk cipher.Block
// if blk, e = GetBlock(cifkeys[i].From,
// cifkeys[i].To); check(e) {
// t.Error(e)
// t.FailNow()
// }
// Encipher(blk, nonce, m)
// }
// log.I.S(sha256.Single(m), sha256.Single(original))
for i := range cifkeys {
var blk cipher.Block
if blk, e = GetBlock(cifkeys[i].From,
cifkeys[i].To); check(e) {
t.Error(e)
t.FailNow()
}
Encipher(blk, nonce, original)
}
log.I.S(sha256.Single(original))
}
func TestCompositing(t *testing.T) {
var cifkeys []*CipherKeys
var e error
for i := 0; i < 3; i++ {
var p1, p2 *prv.Key
if p1, e = prv.GenerateKey(); check(e) {
t.Error(e)
t.FailNow()
}
pub1 := pub.Derive(p1)
if p2, e = prv.GenerateKey(); check(e) {
t.Error(e)
t.FailNow()
}
cifkeys = append(cifkeys, &CipherKeys{p2, pub1})
}
var secrets [][]byte
for i := range cifkeys {
secrets = append(secrets, ecdh.Compute(cifkeys[i].
From, cifkeys[i].To))
}
log.I.S(secrets)
var cs []byte
if cs, e = CombineCiphers(secrets...); check(e) {
t.Error(e)
t.FailNow()
}
log.I.S(cs)
// this proves we can retreive the third cipher if we know
// the first two
for _, i := range secrets[:2] {
for j := range cs {
cs[j] ^= i[j]
}
}
log.I.S(cs, secrets[2])
testWords := []byte("this is a test")
testBytes1 := make([]byte, len(testWords))
copy(testBytes1, testWords)
testBytes2 := make([]byte, len(testWords))
copy(testBytes2, testWords)
log.I.S(testWords, testBytes1, testBytes2)
// regenerate the combined cipher
if cs, e = CombineCiphers(secrets...); check(e) {
t.Error(e)
t.FailNow()
}
var nonces []nonce2.IV
for i := 0; i < 3; i++ {
nonces = append(nonces, nonce2.New())
}
log.I.S(nonces)
n := nonce2.New()
tmp := make(nonce2.IV, nonce2.IVLen)
copy(tmp, nonces[0])
nt := touint64(tmp)
for i := range nonces {
if i != 0 {
u64 := touint64(nonces[i])
xor64(nt, u64)
}
}
n = tobytes(nt)
log.I.S(n)
var cb cipher.Block
if cb, e = aes.NewCipher(cs); !check(e) {
}
Encipher(cb, n, testBytes1)
log.I.S(testBytes1)
for i := range cifkeys {
var blk cipher.Block
if blk, e = GetBlock(cifkeys[i].From,
cifkeys[i].To); check(e) {
t.Error(e)
t.FailNow()
}
Encipher(blk, nonces[i], testBytes2)
}
log.I.S(testBytes2)
}

103
pkg/ifc/interfaces.go Normal file
View File

@@ -0,0 +1,103 @@
package ifc
import (
"reflect"
"unsafe"
"github.com/Indra-Labs/indra"
log2 "github.com/cybriq/proc/pkg/log"
)
var (
log = log2.GetLogger(indra.PathBase)
check = log.E.Chk
)
type Transport interface {
Send(b Message)
Receive() <-chan Message
}
type Message []byte
func ToMessage(b []byte) (msg Message) { return b }
func (m Message) ToBytes() []byte { return m }
type U64Slice []uint64
// ToU64Slice converts the message with zero allocations if the slice capacity
// was already 8 plus the modulus of the length and 8, otherwise this function
// will trigger a stack allocation, or heap, if the bytes are very long. This is
// intended to be used with short byte slices like cipher nonces and hashes, so
// it usually won't trigger allocations off stack and very often won't trigger
// a copy on stack, saving a lot of time in a short, oft repeated operations.
func (m Message) ToU64Slice() (u U64Slice) {
mLen := uint64(len(m))
uLen := int(mLen / 8)
mMod := mLen % 8
if mMod != 0 {
uLen++
}
// Either extend if there is capacity or this will trigger a copy
for i := uint64(0); i < 8-mMod+8; i++ {
// We could use make with mMod+8 length to extend and ... but
// this does the same thing in practice.
m = append(m, 0)
}
u = make([]uint64, 0, 0)
// With the slice now long enough to be safely converted to []uint64
// plus an extra uint64 to store the original length we can coerce the
// type using unsafe.
//
// First we convert our empty []uint64 header
header := (*reflect.SliceHeader)(unsafe.Pointer(&u))
// then we point its memory location to the extended byte slice data
header.Data = (*reflect.SliceHeader)(unsafe.Pointer(&m)).Data
// lastly, change the element length
header.Len = uLen
// vh := reflect.ValueOf(header)
// vh.SetLen(len(m) / 8)
// This safely pins the total capacity to the length, regardless of what
// it was before. Simply setting the value could cause the GC confusion.
// vh.SetCap(header.Len)
header.Cap = uLen
// store the original byte length
u = append(u, mLen)
return
}
// XOR the U64Slice with the provided slice. Panics if slices are different
// length. The receiver value is mutated in this operation.
func (u U64Slice) XOR(v U64Slice) {
// This should only trigger if the programmer is not XORing same size.
if u[len(u)-1] != v[len(v)-1] {
panic("programmer error, trying to XOR slices of different size")
}
for i := range u[:len(u)-1] {
u[i] ^= v[i]
}
}
func (u U64Slice) ToMessage() (m Message) {
// log.I.S(u)
// length is encoded into the last element
mLen := int(u[len(u)-1])
m = make(Message, 0, 0)
// With the slice now long enough to be safely converted to []uint64
// plus an extra uint64 to store the original length we can coerce the
// type using unsafe.
//
// First we convert our empty []uint64 header
header := (*reflect.SliceHeader)(unsafe.Pointer(&m))
// then we point its memory location to the extended byte slice data
header.Data = (*reflect.SliceHeader)(unsafe.Pointer(&u)).Data
// lastly, change the element length
header.Len = mLen
// vh := reflect.ValueOf(header)
// vh.SetLen(len(m) / 8)
// This safely pins the total capacity to the length, regardless of what
// it was before. Simply setting the value could cause the GC confusion.
// vh.SetCap(header.Len)
header.Cap = mLen
return m
}

View File

@@ -0,0 +1,50 @@
package ifc
import (
"testing"
"github.com/Indra-Labs/indra/pkg/testutils"
)
func TestMessage_ToU64Slice(t *testing.T) {
var e error
var msg1 Message
if msg1, _, e = testutils.GenerateTestMessage(33); check(e) {
t.Error(e)
t.FailNow()
}
log.I.S(msg1)
uMsg1 := msg1.ToU64Slice()
umsg1 := uMsg1.ToMessage()
log.I.S(umsg1)
}
func TestU64Slice_XOR(t *testing.T) {
var e error
var msg1 Message
if msg1, _, e = testutils.GenerateTestMessage(33); check(e) {
t.Error(e)
t.FailNow()
}
log.I.S(msg1)
uMsg1 := msg1.ToU64Slice()
var msg2 Message
if msg2, _, e = testutils.GenerateTestMessage(33); check(e) {
t.Error(e)
t.FailNow()
}
// log.I.S(msg2)
uMsg2 := msg2.ToU64Slice()
var msg3 Message
if msg3, _, e = testutils.GenerateTestMessage(33); check(e) {
t.Error(e)
t.FailNow()
}
// log.I.S(msg3)
uMsg3 := msg3.ToU64Slice()
uMsg1.XOR(uMsg2)
uMsg1.XOR(uMsg3)
uMsg1.XOR(uMsg2)
uMsg1.XOR(uMsg3)
log.I.S(uMsg1.ToMessage())
}

View File

@@ -1,11 +0,0 @@
package ifc
type Transport interface {
Send(b Message)
Receive() <-chan Message
}
type Message []byte
func ToMessage(b []byte) (msg Message) { return b }
func (msg Message) ToBytes() []byte { return msg }

View File

@@ -13,7 +13,9 @@ import (
// Compute computes an elliptic curve diffie hellman shared secret that can be
// decrypted by the holder of the private key matching the public key provided.
func Compute(prv *prv.Key, pub *pub.Key) sha256.Hash {
return secp256k1.GenerateSharedSecret(
(*secp256k1.PrivateKey)(prv), (*secp256k1.PublicKey)(pub),
return sha256.Single(
secp256k1.GenerateSharedSecret(
(*secp256k1.PrivateKey)(prv), (*secp256k1.PublicKey)(pub),
),
)
}

View File

@@ -13,11 +13,11 @@ var (
// GitRef is the gitref, as in refs/heads/branchname.
GitRef = "refs/heads/main"
// ParentGitCommit is the commit hash of the parent HEAD.
ParentGitCommit = "0afe0959a4ae4a82d16f47e58886272acca75623"
ParentGitCommit = "dbe8d115254dc3f5f9857685b56935fe4eb2f51f"
// BuildTime stores the time when the current binary was built.
BuildTime = "2022-12-09T11:56:56+01:00"
BuildTime = "2022-12-09T18:22:47+01:00"
// SemVer lists the (latest) git tag on the build.
SemVer = "v0.0.163"
SemVer = "v0.0.164"
// PathBase is the path base returned from runtime caller.
PathBase = "/home/loki/src/github.com/Indra-Labs/indra/"
// Major is the major number from the tag.
@@ -25,7 +25,7 @@ var (
// Minor is the minor number from the tag.
Minor = 0
// Patch is the patch version number from the tag.
Patch = 163
Patch = 164
)
// Version returns a pretty printed version information string.