Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add frame voting support to SDK #354

Open
2 tasks done
p4u opened this issue Feb 13, 2024 · 1 comment
Open
2 tasks done

Add frame voting support to SDK #354

p4u opened this issue Feb 13, 2024 · 1 comment
Assignees

Comments

@p4u
Copy link
Member

p4u commented Feb 13, 2024

New protobuf models

There is a new protobuf Proof model:

message Proof {
	oneof payload {
                ......
		ProofFarcasterFrame farcasterFrame = 9;
	}
}
// ProofFarcasterFrame is a proof created on the Farcaster network
message ProofFarcasterFrame {
	bytes signedFrameMessageBody = 1;
	ProofArbo censusProof = 2;
	bytes publicKey = 3;
}

signedFrameMessageBody

This is a byte buffer that comes from the farcaster frame Specification

When a user interacts with a Frame backend, the backend receives the following JSON object:

{
  "untrustedData": {
    "fid": 2,
    "url": "https://farcaster.vote/poll/0x7464837316182738372612638471aabcde",
    "messageHash": "0xd2b1ddc6c88e865a33cb1a565e0058d757042974",
    "timestamp": 1706243218,
    "network": 1,
    "buttonIndex": 2,
    "inputText": "hello world", // "" if requested and no input, undefined if input not requested
    "castId": {
      "fid": 226,
      "hash": "0xa48dd46161d8e57725f5e26e34ec19c13ff7f3b9"
    }
  },
  "trustedData": {
    "messageBytes": "d2b1ddc6c88e865a33cb1a565e0058d757042974..."
  }
}

reference https://docs.farcaster.xyz/reference/frames/spec#data-structures

The messageBytes is signed by the user publicKey. So in the Vochain we can extract it and check the publicKey is on the census.

censusProof

This is a standard OFF_CHAIN_TREE_WEIGHTED arbo based census proof (the same we use for standard voting). This proof is used to verify the pubKey extracted from the farcaster message is actually on the census.

publicKey

This is the publicKey extracted from the farcaster message, to facilitate the Vochain vote verification

The Arbo census

This is a standard census, but since the publicKeys of Farcaster are not Ethereum addresses but ed25519 public keys, we introduce a way to create farcaster addresses, this is: keccack256(publicKey)[:20].

So the census3 or other census based services need to implement these kind of addresses. Usually a pure farcaster census would be then:

->  key: `keccack256(publicKey1)[:20]` | weight: 1
->  key: `keccack256(publicKey2)[:20]` | weight: 1
- > key: `keccack256(publicKey3)[:20]` | weight: 1
....

Restrictions

  • When creating a farcaster election, there must be no more than 1 question.
  • The choices can be either 1, 2, 3 or 4 (0 is not used)
  • A farcaster election cannot be anonymous nor encrypted

Expected implementation on the SDK

The SDK could just facilitate create farcaster elections and help build and send farcaster votes. Meaning that we might skip the farcaster frame specific details, such as the Message Frame JSON or the Farcaster Protobuf model.

There are many libraries around that allow deserialize the farcaster message and extract the public key, we can just ask the user of the SDK to introduce such data.

@p4u
Copy link
Member Author

p4u commented Jun 4, 2024

The way we create census keys has changed to include the Farcaster ID (FID).

Here is the Go reference implementation

func NewFarcasterVoterID(publicKey []byte, fid uint64) []byte {
	fidBytes := make([]byte, 8)
	binary.LittleEndian.PutUint64(fidBytes, fid)
	hashedPubKey := keccack(append(publicKey, fidBytes...))
        return hashedPubKey[:20]
}

And here the proposed implementation made by chatGPT

import { keccak256 } from 'js-sha3';

/**
 * Generate a Farcaster Voter ID based on the public key and fid.
 * 
 * @param {Uint8Array} publicKey - The public key as a byte array.
 * @param {number} fid - The fid as a number.
 * @returns {Uint8Array} - The first 20 bytes of the keccak256 hash of the combined input.
 */
function newFarcasterVoterID(publicKey: Uint8Array, fid: number): Uint8Array {
    // Create an 8-byte array to hold the little-endian representation of fid
    const fidBytes = new Uint8Array(8);

    // Convert fid to a little-endian byte array
    for (let i = 0; i < 8; i++) {
        fidBytes[i] = fid & 0xff; // Get the least significant byte of fid
        fid = fid >> 8; // Shift fid right by 8 bits to process the next byte
    }

    // Create a new array to hold the combined publicKey and fidBytes
    const combined = new Uint8Array(publicKey.length + fidBytes.length);

    // Copy publicKey into the combined array
    combined.set(publicKey);

    // Copy fidBytes into the combined array, starting after the publicKey
    combined.set(fidBytes, publicKey.length);

    // Hash the combined array using keccak256 and convert the result to an ArrayBuffer
    const hashedPubKey = keccak256.arrayBuffer(combined);

    // Return the first 20 bytes of the hashed public key
    return new Uint8Array(hashedPubKey).slice(0, 20);
}

Here some test data

var frameVote1 = farcasterFrame{
	signedMessage: "0a8b01080d109fc20e18b4a7f52e200182017b0a5b68747470733a2f2f63656c6f6e692e766f63646f6e692e6e65742f3633663537626539386638303666393539323134623435383165623837393165366338306561663732313032643465393366373631303030303030303030303310011a1a089fc20e1214000000000000000000000000000000000000000112142d9bd29806c7e54cf5f80f98d9adf710a2ebc58518012240379f4f9897901b24544fba46fcb51183f79d79a5041c47f554c5a4e407c020fdbf43ef27490b944e05372850b1dc78dd97c728a88bdbc14f0174ed589c795a0928013220ec327cd438995a59ce78fddd29631e9b2e41eafc3a6946dd26b4da749f47140d",
	pubkey:        "ec327cd438995a59ce78fddd29631e9b2e41eafc3a6946dd26b4da749f47140d",
	buttonIndex:   1,
	fid:           237855,
}

var frameVote2 = farcasterFrame{
	buttonIndex:   3,
	signedMessage: "0a8901080d10e04e18d1aaf52e200182017a0a5b68747470733a2f2f63656c6f6e692e766f63646f6e692e6e65742f3633663537626539386638303666393539323134623435383165623837393165366338306561663732313032643465393366373631303030303030303030303310031a1908e04e12140000000000000000000000000000000000000001121496f560a1f5c90fa24278277321d7be35d18cf0711801224029863962ecff4b7db6dd8736fc1c238ed6ed5a147d3a36e6eac32e06f10d2dcc1df1618d5da6ce21286e0233656ef985a5b1cced2bee5f2cbb4fd1bfc168aa0128013220d6424e655287aa61df38205da19ddab23b0ff9683c6800e0dbc3e8b65d3eb2e3",
	pubkey:        "d6424e655287aa61df38205da19ddab23b0ff9683c6800e0dbc3e8b65d3eb2e3",
	fid:           10080,
}

@p4u p4u closed this as completed Jun 4, 2024
@p4u p4u reopened this Jun 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants