Masking and the Use of XOR for Encryption and Decryption

Murray Distributed Technologies
6 min readFeb 23, 2022

The following article covers nChain WP 692 which is published as an “extension to WP#042”. Using the techniques outlined in the previous article, we will show how XOR can be used to implement a “secure and efficient encryption scheme”. Code examples are provided utilizing the libsv/go-bk library. The techniques are also discussed in this video.

The “exclusive OR” operation, XOR, is an operation between two binary bits that returns 1 if and only if one of the input bits is equal to 1, otherwise, the operation returns 0. The operation is denoted by the symbol ⨁. The operation is summed up by the following truth table:

Thus, given a secret key S and message M of equal byte lengths, we can see the result M’ given the XOR operation between S and M:

Because the XOR operation is reversible, we can calculate M given M’ and S:

Thus we can see how S acts as an encryption/decryption key. The process of utilizing the secret key S in this manner is known as a One-Time Pad. By utilizing the public nature of the blockchain, the method outlined in the WP removes the primary risk of transmitting cyphertext over public networks — the malleability of binary strings in the case of attack of an intercepting party.

Alice and Bob generate a shared secret as described in WP042, but the initial derived key is instead used as the One-Time Pad instead of as use as a private key. The One-Time Pad combined with message M enables Alice and Bob to validate their shared secret given message M where M’ is published on the blockchain:

The use of XOR as a cipher can be utilized in various ways in Bitcoin transactions, but we will focus on one specific use case inside Bitcoin Script where the ciphertext is XOR’ed inside the locking script of a transaction:

OP_DUP OP_HASH256 ⟨ℎ(𝑆1 )⟩ OP_EQUALVERIFY ⟨ℎ(𝑃𝐵)⨁𝑆1⟩ OP_XOR OP_SWAP OP_DUP OP_HASH256 OP_ROT OP_EQUALVERIFY OP_CHECKSIG

And the input script is

<SigP_B> <P_B> <S_1>

The locking script first ensures that the correct shared secret is used by checking it against a hash puzzle. The script then uses the secret to decrypt <h(P_B)> and then checks the signature as in a standard transaction.

This type of transaction has a use case where Alice wants to create a transaction redeemable by Bob or a third party, Charlie. Some additional redemption condition for Charlie can be included in the identical way as it was for Bob alongside OP_OR, and the identities of Bob and Charlie are completely hidden until the transaction is redeemed. When it is redeemed, only the identity of the one who redeems the transaction is exposed. Thus we have created a mask where multiple parties can compete to satisfy a condition, but only the winner’s identity is exposed.

The main benefit of using XOR as an encryption/decryption tool is that it allows for secure and rapid decryption even on low processing power devices. This is beneficial for smart cards and IoT devices, for example.

You can play with these types of transactions using this golang repo as a starting point. You can see a transaction onchain of the script type above here.

Key Derivation Schemes

While Bitcoin has standardized key derivation around the BIP32 standard, there are many downsides to the approach. A future article will talk about these downsides, but the important thing to note is that BIP32 is not the only way to have hierarchically derived keys . There are new key derivation schemes that are being created for BSV, and one such scheme involves utilizing a hash chain and XOR. Note in this example that this scheme is not being used necessarily for coin storage, but instead used for decryption of messages. However, it points to a wide variety of possibilities in defining new key derivation schemes.

Utilizing our handy Secret Value Distribution technique, two parties, Alice and Bob, will calculate a shared secret S_1 where it is the x coordinate of the shared secret generated:

Remember, since S_1 is a 256 bit number, it can be utilized as a private key from which further keys can be derived. We will derive n keys such that:

And keys 2 and 3:

And so forth until:

This set of keys is generated using only the initial shared secret and only a single initial elliptic curve point calculation. These keys can then be used to encode the n messages, starting with S_n:

As we can see, the initial shared secret is never used on its own to encrypt a message. Thus, a single compromised message will not destroy the security of all the encrypted messages. The use of a hash chain in the key calculations prevents reversal of the calculation. This property means also that keys can safely be reused.

We can imagine, for instance, a use case whereby a large block of data is broken up into n chunks and streamed to a customer. We can structure the communication of the data such that n keys are securely created between the server and client to allow continuous decryption of content as it is streamed. The server can revoke access as needed by simply stopping the streaming of the content. This would enable applications like Streamanity to enable pay as you go content.

Future articles will expand on these techniques for the purpose of creating a “Blockchain Message Authentication Code” (BMAC), as well as further key derivation schemes that can be used.

See the below gist for two example functions for deriving these keys from a shared secret that has been imported as a bec.PrivateKey:

import (
"github.com/libsv/go-bk/bec"
"github.com/libsv/go-bk/crypto"
)
func DeriveNKeysFromMaster(n int, masterSecretKey *bec.PrivateKey) ([]*bec.PrivateKey) {
matrix := make([]*bec.PrivateKey, n)
secretKey := masterSecretKey
for i := 0; i < n; i++ {
secretKey, _ := DeriveNextKey(masterSecretKey, secretKey)
matrix = append(matrix, secretKey)
}
return matrix
}
func DeriveNextKey(masterKey, secretKey *bec.PrivateKey) (*bec.PrivateKey) {
curve := bec.S256()
secretKeyBytes := secretKey.Serialise()
masterKeyBytes := masterKey.Serialise()
secretHash := crypto.Sha256(secretKeyBytes)
newKeyBytes := make([]byte, len(masterKeyBytes))
for i := range secretKeyBytes {
newKeyBytes[i] = masterKeyBytes[i] ^ secretHash[i]
}
key, _ := bec.PrivKeyFromBytes(curve, newKeyBytes)
return key
}

--

--

Murray Distributed Technologies

Building the future of online reviews powered by blockchain technology at britevue.com