Lets Make a Breedable - Part 1

Posted on

Communication and encryption.

Communication between pets and pets, pets and eggs, pets and foodbowls, update cubes etc is one of the most important parts of a breedable pet. It also must happen in public chat, which means, anyone can intercept and forge chat to mimic your pets.

Chat between pets usually happens with lists of commands, for example a food consuming session might look like this:

  • Pet (broadcast): I’m looking for a food bowl.. (HUNGRY)
  • Food Bowl (to pet): I’m a foodbowl, and I have 24 serves of food (FOOD!24)
  • Pet (to food bowl): I’m eating 3 serves! (EAT!3)

Then the food bowl decreases it’s serving count by 3 and the pet reduces it’s hunger by 3.

It could then be possible for an opportunistic person to eavesdrop and when they hear the word “HUNGRY” respond with FOOD!9999 and bypass the need for food bowls. This is why encryption is important.

In Second Life, the easiest encryption to use is XTEA, but there are some gotchas.

The XTEA code I use was not written by me, but is available in the public domain, which means you can use it as you like.1

Now, the thing with encryption is if your strings are predictable, for example, sending HUNGRY might look like JDOJIJSid9w3 then an opportunistic person could instead of typing HUNGRY in chat, they could type JDOJIJSid9w3 and mimic the HUNGRY command, then get the foodbowl to respond to them FOOD!24, which might appear as f98wej9foiIFOE then they simply respond with that encrypted text and you would be no better off than if you hadn’t encrypted your chat at all.

So, how to get around it? You need unique strings. For example, instead of HUNGRY, you could say HUNGRY!(random number) then the food bowl must return the exact random number which will be validated by the pet, it means the encrypted text would only be valid once.

Now that we’ve learned how to communicate, we need to implement it. The XTEA code is kind of large, and you will need it in every script you plan to encrypt text in, so what I do, is have one communications script in the pet, and each script that needs to communicate will send it’s data via a link_message.

Example Communication Script

First we need some channels, I usually use 3 channels, one for eggs, one for mating and one for everything else. (note you would use different channels for different breedables)

integer EGG_CHANNEL = -894339;
integer MATING_CHANNEL = -437884;
integer AUX_CHANNEL = -3902092;

Next you need a secret encryption key (note: you should change this for each of the breedable pets you create.)

string SUPER_SECRET_KEY = "supersecret";

Next include the XTEA code (at the end of this article).

then the rest of the script

default {
    state_entry() {
        xtea_key = xtea_key_from_string(SUPER_SECRET_KEY);

        llListen(EGG_CHANNEL, "", "", "");
        llListen(MATING_CHANNEL, "", "", "");
        llListen(AUX_CHANNEL, "", "", "");
    }

    listen(integer channel, string sender, key id, string message) {
        if (channel == EGG_CHANNEL) {
            llMessageLinked(LINK_THIS, 100, xtea_decrypt_string(message), id);
        } else if (channel == MATING_CHANNEL) {
            llMessageLinked(LINK_THIS, 200, xtea_decrypt_string(message), id);
        } else if (channel == AUX_CHANNEL) {
            llMessageLinked(LINK_THIS, 300, xtea_decrypt_string(message), id);
        }
    }

    link_message(integer sender, integer number, string message, key id) {
        if (number == 101) {
            llSay(EGG_CHANNEL, xtea_encrypt_string(message));
        } else if (number == 102) {
            llShout(EGG_CHANNEL, xtea_encrypt_string(message));
        } else if (number == 103) {
            llWhisper(EGG_CHANNEL, xtea_encrypt_string(message));
        } else if (number == 104) {
            llRegionSay(EGG_CHANNEL, xtea_encrypt_string(message));
        } else if (number == 105) {
            llRegionSayTo(id, EGG_CHANNEL, xtea_encrypt_string(message));
        } else if (number == 201) {
            llSay(MATING_CHANNEL, xtea_encrypt_string(message));
        } else if (number == 202) {
            llShout(MATING_CHANNEL, xtea_encrypt_string(message));
        } else if (number == 203) {
            llWhisper(MATING_CHANNEL, xtea_encrypt_string(message));
        } else if (number == 204) {
            llRegionSay(MATING_CHANNEL, xtea_encrypt_string(message));
        } else if (number == 205) {
            llRegionSayTo(id, MATING_CHANNEL, xtea_encrypt_string(message));
        } else if (number == 301) {
            llSay(AUX_CHANNEL, xtea_encrypt_string(message));
        } else if (number == 302) {
            llShout(AUX_CHANNEL, xtea_encrypt_string(message));
        } else if (number == 303) {
            llWhisper(AUX_CHANNEL, xtea_encrypt_string(message));
        } else if (number == 304) {
            llRegionSay(AUX_CHANNEL, xtea_encrypt_string(message));
        } else if (number == 305) {
            llRegionSayTo(id, AUX_CHANNEL, xtea_encrypt_string(message));
        }
    }
}

Now, in your other scripts, when you want them to, for example, shout on the egg channel, you would send a link message with the number 102, the string as the text, the key can be blank in this case, and is only needed if you want to message a specific object with llRegionSayTo (which you would do with 105 for the egg channel number).

Your scripts would also accept link messages with number 100 for egg channel, 200 for mating channel and 300 for aux channel.

Although, this might seem like not much, it’s a start and an important building block for the next article where we will message our food bowl.

XTEA Source Code

integer XTEA_DELTA      = 0x9E3779B9; // (sqrt(5) - 1) * 2^31
integer xtea_num_rounds = 6;
list    xtea_key        = [0, 0, 0, 0];

integer hex2int(string hex) {
    if(llGetSubString(hex,0,1) == "0x")
        return (integer)hex;
    if(llGetSubString(hex,0,0) == "x")
        return (integer)("0"+hex);
    return(integer)("0x"+hex);
}


// Convers any string to a 32 char MD5 string and then to a list of
// 4 * 32 bit integers = 128 bit Key. MD5 ensures always a specific
// 128 bit key is generated for any string passed.
list xtea_key_from_string( string str )
{
    str = llMD5String(str,0); // Use Nonce = 0
    return [    hex2int(llGetSubString(  str,  0,  7)),
        hex2int(llGetSubString(  str,  8,  15)),
        hex2int(llGetSubString(  str,  16,  23)),
        hex2int(llGetSubString(  str,  24,  31))];
}

// Encipher two integers and return the result as a 12-byte string
// containing two base64-encoded integers.
string xtea_encipher( integer v0, integer v1 )
{
    integer num_rounds = xtea_num_rounds;
    integer sum = 0;
    do {
        // LSL does not have unsigned integers, so when shifting right we
        // have to mask out sign-extension bits.
        v0  += (((v1 << 4) ^ ((v1 >> 5) & 0x07FFFFFF)) + v1) ^ (sum + llList2Integer(xtea_key, sum & 3));
        sum +=  XTEA_DELTA;
        v1  += (((v0 << 4) ^ ((v0 >> 5) & 0x07FFFFFF)) + v0) ^ (sum + llList2Integer(xtea_key, (sum >> 11) & 3));

    } while( num_rounds = ~-num_rounds );
    //return only first 6 chars to remove "=="'s and compact encrypted text.
    return llGetSubString(llIntegerToBase64(v0),0,5) +
        llGetSubString(llIntegerToBase64(v1),0,5);
}

// Decipher two base64-encoded integers and return the FIRST 30 BITS of
// each as one 10-byte base64-encoded string.
string xtea_decipher( integer v0, integer v1 )
{
    integer num_rounds = xtea_num_rounds;
    integer sum = XTEA_DELTA*xtea_num_rounds;
    do {
        // LSL does not have unsigned integers, so when shifting right we
        // have to mask out sign-extension bits.
        v1  -= (((v0 << 4) ^ ((v0 >> 5) & 0x07FFFFFF)) + v0) ^ (sum + llList2Integer(xtea_key, (sum>>11) & 3));
        sum -= XTEA_DELTA;
        v0  -= (((v1 << 4) ^ ((v1 >> 5) & 0x07FFFFFF)) + v1) ^ (sum + llList2Integer(xtea_key, sum  & 3));
    } while ( num_rounds = ~-num_rounds );

    return llGetSubString(llIntegerToBase64(v0), 0, 4) +
        llGetSubString(llIntegerToBase64(v1), 0, 4);
}

// Encrypt a full string using XTEA.
string xtea_encrypt_string( string str )
{
    // encode string
    str = llStringToBase64(str);
    // remove trailing =s so we can do our own 0 padding
    integer i = llSubStringIndex( str, "=" );
    if ( i != -1 )
        str = llDeleteSubString( str, i, -1 );

    // we don't want to process padding, so get length before adding it
    integer len = llStringLength(str);

    // zero pad
    str += "AAAAAAAAAA=";

    string result;
    i = 0;

    do {
        // encipher 30 (5*6) bits at a time.
        result += xtea_encipher(llBase64ToInteger(llGetSubString(str,   i, i + 4) + "A="), llBase64ToInteger(llGetSubString(str, i+5, i + 9) + "A="));
        i+=10;
    } while ( i < len );

    return result;
}

// Decrypt a full string using XTEA
string xtea_decrypt_string( string str ) {
    integer len = llStringLength(str);
    integer i=0;
    string result;
    //llOwnerSay(str);
    do {
        integer v0;
        integer v1;

        v0 = llBase64ToInteger(llGetSubString(str,   i, i + 5) + "==");
        i+= 6;
        v1 = llBase64ToInteger(llGetSubString(str,   i, i + 5) + "==");
        i+= 6;

        result += xtea_decipher(v0, v1);
    } while ( i < len );

    // Replace multiple trailing zeroes with a single one

    i = llStringLength(result) - 1;
    while ( llGetSubString(result, i - 1, i) == "AA" ){
        result = llDeleteSubString(result, i, i);
        i--;
    }
    i = llStringLength(result) - 1;
    //    while (llGetSubString(result, i, i + 1) == "A" ) {
    //        i--;
    //    }
    result = llGetSubString(result, 0, i+1);
    i = llStringLength(result);
    integer mod = i%4; //Depending on encoded length diffrent appends are needed
    if(mod == 1) result += "A==";
    else if(mod == 2 ) result += "==";
    else if(mod == 3) result += "=";

    return llBase64ToString(result);
}