-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d40d548
commit 5928b77
Showing
6 changed files
with
183 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
import { | ||
IPublishBatchArgs, | ||
IPublishBatchData, | ||
PubKey, | ||
MACI__factory as MACIFactory, | ||
Poll__factory as PollFactory, | ||
} from "maci-cli/sdk"; | ||
import { | ||
IG1ContractParams, | ||
IMessageContractParams, | ||
Keypair, | ||
PCommand, | ||
PrivKey, | ||
} from "maci-domainobjs"; | ||
import { genRandomSalt } from "maci-crypto"; | ||
import { publicClient } from "./permissionless"; | ||
import { SmartAccountClient } from "permissionless"; | ||
import { EntryPoint } from "permissionless/types"; | ||
import { KernelEcdsaSmartAccount } from "permissionless/accounts"; | ||
import { Address, HttpTransport, Chain } from "viem"; | ||
// import { banner, validateSalt } from "node_modules/maci-cli/build/ts/utils"; // TODO: (merge-ok) this import breaks the frontend | ||
|
||
const MESSAGE_TREE_ARITY = 5; | ||
|
||
type ISmartAccountPublishBatchArgs = IPublishBatchArgs & { | ||
smartAccount: KernelEcdsaSmartAccount<EntryPoint, HttpTransport, Chain>; | ||
smartAccountClient: SmartAccountClient<EntryPoint, HttpTransport, Chain>; | ||
}; | ||
|
||
|
||
/** | ||
* @notice copied from maci-cli/sdk to add sponsorship | ||
* Batch publish new messages to a MACI Poll contract | ||
* @param {IPublishBatchArgs} args - The arguments for the publish command | ||
* @returns {IPublishBatchData} The ephemeral private key used to encrypt the message, transaction hash | ||
*/ | ||
export const publishBatch = async ({ | ||
messages, | ||
pollId, | ||
maciAddress, | ||
publicKey, | ||
privateKey, | ||
signer, | ||
quiet = true, | ||
smartAccount, | ||
smartAccountClient, | ||
}: ISmartAccountPublishBatchArgs): Promise<IPublishBatchData> => { | ||
// TODO: (merge-ok) should this be added back? | ||
// banner(quiet); | ||
|
||
if (!PubKey.isValidSerializedPubKey(publicKey)) { | ||
throw new Error("invalid MACI public key"); | ||
} | ||
|
||
if (!PrivKey.isValidSerializedPrivKey(privateKey)) { | ||
throw new Error("invalid MACI private key"); | ||
} | ||
|
||
if (pollId < 0n) { | ||
throw new Error(`invalid poll id ${pollId}`); | ||
} | ||
|
||
const userMaciPubKey = PubKey.deserialize(publicKey); | ||
const userMaciPrivKey = PrivKey.deserialize(privateKey); | ||
const maciContract = MACIFactory.connect(maciAddress, signer); | ||
const pollContracts = await maciContract.getPoll(pollId); | ||
|
||
const pollContract = PollFactory.connect(pollContracts.poll, signer); | ||
|
||
const [treeDepths, coordinatorPubKeyResult] = await Promise.all([ | ||
pollContract.treeDepths(), | ||
pollContract.coordinatorPubKey(), | ||
]); | ||
const maxVoteOptions = Number( | ||
BigInt(MESSAGE_TREE_ARITY) ** treeDepths.voteOptionTreeDepth | ||
); | ||
|
||
// validate the vote options index against the max leaf index on-chain | ||
messages.forEach(({ stateIndex, voteOptionIndex, salt, nonce }) => { | ||
if (voteOptionIndex < 0 || maxVoteOptions < voteOptionIndex) { | ||
throw new Error("invalid vote option index"); | ||
} | ||
|
||
// check < 1 cause index zero is a blank state leaf | ||
if (stateIndex < 1) { | ||
throw new Error("invalid state index"); | ||
} | ||
|
||
if (nonce < 0) { | ||
throw new Error("invalid nonce"); | ||
} | ||
|
||
// TODO: (merge-ok) add validation back here | ||
// if (salt && !validateSalt(salt)) { | ||
// throw new Error("invalid salt"); | ||
// } | ||
}); | ||
|
||
const coordinatorPubKey = new PubKey([ | ||
BigInt(coordinatorPubKeyResult.x.toString()), | ||
BigInt(coordinatorPubKeyResult.y.toString()), | ||
]); | ||
|
||
const encryptionKeypair = new Keypair(); | ||
const sharedKey = Keypair.genEcdhSharedKey( | ||
encryptionKeypair.privKey, | ||
coordinatorPubKey | ||
); | ||
|
||
const payload: [IMessageContractParams, IG1ContractParams][] = messages.map( | ||
({ salt, stateIndex, voteOptionIndex, newVoteWeight, nonce }) => { | ||
const userSalt = salt ? BigInt(salt) : genRandomSalt(); | ||
|
||
// create the command object | ||
const command = new PCommand( | ||
stateIndex, | ||
userMaciPubKey, | ||
voteOptionIndex, | ||
newVoteWeight, | ||
nonce, | ||
BigInt(pollId), | ||
userSalt | ||
); | ||
|
||
// sign the command with the user private key | ||
const signature = command.sign(userMaciPrivKey); | ||
|
||
const message = command.encrypt(signature, sharedKey); | ||
|
||
return [ | ||
message.asContractParam(), | ||
encryptionKeypair.pubKey.asContractParam(), | ||
]; | ||
} | ||
); | ||
|
||
const preparedMessages = payload.map(([message]) => message); | ||
const preparedKeys = payload.map(([, key]) => key); | ||
|
||
// TODO: (merge-ok) make this type casting/handling nicer | ||
const reversedMessages = preparedMessages.reverse().map((item) => ({ | ||
data: item.data.map((val) => BigInt(val)) as [ | ||
bigint, | ||
bigint, | ||
bigint, | ||
bigint, | ||
bigint, | ||
bigint, | ||
bigint, | ||
bigint, | ||
bigint, | ||
bigint, | ||
], | ||
})); | ||
const reversedKeys = preparedKeys.reverse() as readonly { | ||
x: bigint; | ||
y: bigint; | ||
}[]; | ||
|
||
const { request } = await publicClient.simulateContract({ | ||
account: smartAccount, | ||
address: pollContracts.poll as Address, | ||
abi: PollFactory.abi, | ||
functionName: "publishMessageBatch", | ||
args: [reversedMessages, reversedKeys], | ||
}); | ||
const txHash = await smartAccountClient.writeContract(request); | ||
|
||
return { | ||
hash: txHash, | ||
encryptedMessages: preparedMessages, | ||
privateKey: encryptionKeypair.privKey.serialize(), | ||
}; | ||
}; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.