Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sdks/flashtestations-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ main();

## How Do I Acquire a Particular op-rbuilder's Workload ID?

The Flashtestations protocol exists to let you cryptographically verify that a particular version of op-rbuilder is in fact building the latest block's on Unichain. To cryptographically identify these op-rbuilder versions across all of the various components (the TEE, the smart contracts, and SDK) we use a 32-byte workload ID, which is a [hash of the important components of the TEE attestation](https://github.com/flashbots/flashtestations/blob/7cc7f68492fe672a823dd2dead649793aac1f216/src/BlockBuilderPolicy.sol#L224). But this workload ID tells us nothing about what op-rbuilder source code the builder operators used to build the final Linux OS image that runs on the TEE. We need a trustless (i.e. locally verifiable) method for calculating the workload ID, given a version of op-rbuilder.
The Flashtestations protocol exists to let you cryptographically verify that a particular version of op-rbuilder is in fact building the latest block's on Unichain. To cryptographically identify these op-rbuilder versions across all of the various components (the TEE, the smart contracts, and SDK) we use a 32-byte workload ID, which is a [hash of the important components of the TEE attestation](https://github.com/flashbots/flashtestations/blob/38594f37b5f6d1b1f5f6ad4203a4770c10f72a22/src/BlockBuilderPolicy.sol#L208). But this workload ID tells us nothing about what op-rbuilder source code the builder operators used to build the final Linux OS image that runs on the TEE. We need a trustless (i.e. locally verifiable) method for calculating the workload ID, given a version of op-rbuilder.

With a small caveat we'll explain shortly, that process is what the [flashbots-images](https://github.com/flashbots/flashbots-images/commits/main/) repo is for. Using this repo and a simple bash command, we build a Linux OS image containing a specific version of op-rbuilder (identified by its git commit hash), and then calculate the workload ID directly from this Linux OS image. This completes the full chain of trustless verification; given a particular commit hash of flashbots-images (which has hardcoded into it a particular version of op-rbuilder), we can locally build and compute the workload ID, and then pass that to the SDK's `verifyFlashtestationInBlock` function to verify "is Unichain building blocks with the latest version of op-rbuilder?".

Expand Down
82 changes: 2 additions & 80 deletions sdks/flashtestations-sdk/src/crypto/workload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,6 @@ import {
validateSingularWorkloadMeasurementRegisters,
} from '../types/validation';



/**
* Hardcoded TDX measurement register values that aren't tied to OS image hash
*/
export const TDX_CONSTANTS = {
/** Expected xFAM value (8 bytes) */
/** Copied from https://github.com/flashbots/flashtestations/blob/7cc7f68492fe672a823dd2dead649793aac1f216/src/BlockBuilderPolicy.sol#L231 */
expectedXfamBits: xorBytes(hexToBytes('0x0000000000000001'), hexToBytes('0x0000000000000002')),
/** TD attributes mask to ignore certain bits (8 bytes) */
/** Copied from https://github.com/flashbots/flashtestations/blob/7cc7f68492fe672a823dd2dead649793aac1f216/src/BlockBuilderPolicy.sol#L234 */
ignoredTdAttributesBitmask: orBytes(orBytes(hexToBytes('0x0000000010000000'), hexToBytes('0x0000000040000000')), hexToBytes('0x0000000080000000')),
};

/**
* Converts a hex string to a Uint8Array
* This is a helper function to convert a hex string to a Uint8Array
Expand All @@ -36,63 +22,6 @@ function hexToBytes(hex: `0x${string}`): Uint8Array {
return Uint8Array.from(unprefixedHex.match(/.{1,2}/g)!.map(byte => parseInt(byte, 16)));
}

/**
* Performs bitwise XOR operation on two Uint8Arrays
* This is a helper function to perform a bitwise XOR operation on two Uint8Arrays
* @example:
* - Uint8Array([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde]) XOR Uint8Array([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde]) -> Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
* @param a - The first Uint8Array
* @param b - The second Uint8Array
* @returns The result of the XOR operation
* @throws If the lengths of the Uint8Arrays are mismatched
*/
function xorBytes(a: Uint8Array, b: Uint8Array): Uint8Array {
if (a.length !== b.length) throw new Error("Mismatched lengths");
return Uint8Array.from(a.map((x, i) => x ^ b[i]));
}

/**
* Performs bitwise OR operation on two Uint8Arrays
* This is a helper function to perform a bitwise OR operation on two Uint8Arrays
* @example:
* - Uint8Array([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde]) OR Uint8Array([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x00]) -> Uint8Array([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde])
* @param a - The first Uint8Array
* @param b - The second Uint8Array
* @returns The result of the OR operation
* @throws If the lengths of the Uint8Arrays are mismatched
*/
function orBytes(a: Uint8Array, b: Uint8Array): Uint8Array {
if (a.length !== b.length) throw new Error("Mismatched lengths");
return Uint8Array.from(a.map((x, i) => x | b[i]));
}

/**
* Performs bitwise AND operation on two Uint8Arrays
* This is a helper function to perform a bitwise AND operation on two Uint8Arrays
* @example:
* - Uint8Array([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde]) AND Uint8Array([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x00]) -> Uint8Array([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x00])
* @param a - The first Uint8Array
* @param b - The second Uint8Array
* @returns The result of the AND operation
* @throws If the lengths of the Uint8Arrays are mismatched
*/
function andBytes(a: Uint8Array, b: Uint8Array): Uint8Array {
if (a.length !== b.length) throw new Error("Mismatched lengths");
return Uint8Array.from(a.map((x, i) => x & b[i]));
}

/**
* Performs bitwise NOT operation on a Uint8Array
* This is a helper function to perform a bitwise NOT operation on a Uint8Array
* @example:
* - Uint8Array([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde]) -> Uint8Array([0xed, 0xcb, 0xa9, 0x87, 0x75, 0x43, 0x21])
* @param bytes - The Uint8Array to perform the NOT operation on
* @returns The result of the NOT operation
*/
function notBytes(bytes: Uint8Array): Uint8Array {
return Uint8Array.from(bytes.map(x => ~x));
}

/**
* Concatenates multiple Uint8Arrays
* @example:
Expand All @@ -115,7 +44,7 @@ function concatBytes(...arrays: Uint8Array[]): Uint8Array {
* Computes workload ID from TEE measurement registers
* Formula: keccak256(mrTd + rtMr0 + rtMr1 + rtMr2 + rtMr3 + mrConfigId + (xFAM ^ expectedXfamBits) + (tdAttributes & ~ignoredTdAttributesBitmask))
* This is copied from the Solidity implementation of the workload ID computation at:
* https://github.com/flashbots/flashtestations/blob/7cc7f68492fe672a823dd2dead649793aac1f216/src/BlockBuilderPolicy.sol#L236
* https://github.com/flashbots/flashtestations/blob/38594f37b5f6d1b1f5f6ad4203a4770c10f72a22/src/BlockBuilderPolicy.sol#L208
*
* @param registers - Singular workload measurement registers
* @returns The computed workload ID as a hex string
Expand All @@ -138,16 +67,9 @@ export function computeWorkloadId(registers: SingularWorkloadMeasurementRegister
const xFAM = hexToBytes(registers.xFAM);
const tdAttributes = hexToBytes(registers.tdAttributes);

// Apply bitwise operations with hardcoded values
const xfamPreprocessed = xorBytes(xFAM, TDX_CONSTANTS.expectedXfamBits);
const tdAttributesPreprocessed = andBytes(
tdAttributes,
notBytes(TDX_CONSTANTS.ignoredTdAttributesBitmask)
);

// Concatenate all components and hash
return keccak256(
concatBytes(mrTd, rtMr0, rtMr1, rtMr2, rtMr3, mrConfigId, xfamPreprocessed, tdAttributesPreprocessed)
concatBytes(mrTd, rtMr0, rtMr1, rtMr2, rtMr3, mrConfigId, xFAM, tdAttributes)
);
}

Expand Down
6 changes: 3 additions & 3 deletions sdks/flashtestations-sdk/test/crypto/workload.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ describe('Workload ID computation', () => {
describe('integration tests', () => {
it('should handle complete workload ID computation flow', () => {
// this example workloadId is the same as the one from an actual TDX report in the solidity test code
// here: https://github.com/flashbots/flashtestations/blob/7cc7f68492fe672a823dd2dead649793aac1f216/test/BlockBuilderPolicy.t.sol#L302
// here: https://github.com/flashbots/flashtestations/blob/38594f37b5f6d1b1f5f6ad4203a4770c10f72a22/test/BlockBuilderPolicy.t.sol#L300
const expectedWorkloadId =
'0xf724e7d117f5655cf33beefdfc7d31e930278fcb65cf6d1de632595e97ca82b2';
'0x952569f637f3f7e36cd8f5a7578ae4d03a1cb05ddaf33b35d3054464bb1c862e';
// Create registers with some custom values
const registers: SingularWorkloadMeasurementRegisters = {
tdAttributes: '0x0000001000000000',
Expand Down Expand Up @@ -113,7 +113,7 @@ describe('Workload ID computation', () => {

it('should handle workload ID computation with a different set of registers', () => {
const expectedWorkloadId =
'0xc85f03aebad8acae79c876cbad92cd1da26c0555a383146132b3dbf5709e8662';
'0xc1978eb1e3db791ebcdf41be6577209cb1a555f9fff06b65abe4d3baf92811a3';
// Create registers with some custom values
const registers: SingularWorkloadMeasurementRegisters = {
tdAttributes: '0x0000001000000000',
Expand Down
Loading