Skip to content

Commit 338a06c

Browse files
authored
feat(library-solidity): update fromExternal to facilitate smart accou… (#1609)
feat(library-solidity): update fromExternal to facilitate smart account integration chore(library-solidity): fix typo in comments chore(library-solidity): reformat comments test(library-solidity): add tests for multisig with helper contract chore(library-solidity): refactor fromExternal chore(library-solidity): updated config chore(common): bump npmjs minor chore(common): bump npmjs minor for host chore(library-solidity): fix forge test test(library-solidity): simplify input approval flow
1 parent 3c2a64d commit 338a06c

File tree

14 files changed

+455
-25
lines changed

14 files changed

+455
-25
lines changed

host-contracts/lib/FHE.sol

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ library FHE {
2727
/// @notice Returned if the returned KMS signatures are not valid.
2828
error InvalidKMSSignatures();
2929

30+
/// @notice Returned if the sender is not allowed to use the handle.
31+
error SenderNotAllowedToUseHandle(bytes32 handle, address sender);
32+
3033
/// @notice This event is emitted when public decryption has been successfully verified.
3134
event PublicDecryptionVerified(bytes32[] handlesList, bytes abiEncodedCleartexts);
3235

@@ -8441,9 +8444,18 @@ library FHE {
84418444

84428445
/**
84438446
* @dev Convert an inputHandle with corresponding inputProof to an encrypted ebool integer.
8447+
* @dev If inputProof is empty, the externalEbool inputHandle can be used as a regular ebool handle if it
8448+
* has already been verified and allowed to the sender.
8449+
* This could facilitate integrating smart contract accounts with fhevm.
84448450
*/
84458451
function fromExternal(externalEbool inputHandle, bytes memory inputProof) internal returns (ebool) {
8446-
return ebool.wrap(Impl.verify(externalEbool.unwrap(inputHandle), inputProof, FheType.Bool));
8452+
if (inputProof.length != 0) {
8453+
return ebool.wrap(Impl.verify(externalEbool.unwrap(inputHandle), inputProof, FheType.Bool));
8454+
} else {
8455+
bytes32 inputBytes32 = externalEbool.unwrap(inputHandle);
8456+
if (!Impl.isAllowed(inputBytes32, msg.sender)) revert SenderNotAllowedToUseHandle(inputBytes32, msg.sender);
8457+
return ebool.wrap(inputBytes32);
8458+
}
84478459
}
84488460

84498461
/**
@@ -8455,9 +8467,18 @@ library FHE {
84558467

84568468
/**
84578469
* @dev Convert an inputHandle with corresponding inputProof to an encrypted euint8 integer.
8470+
* @dev If inputProof is empty, the externalEuint8 inputHandle can be used as a regular euint8 handle if it
8471+
* has already been verified and allowed to the sender.
8472+
* This could facilitate integrating smart contract accounts with fhevm.
84588473
*/
84598474
function fromExternal(externalEuint8 inputHandle, bytes memory inputProof) internal returns (euint8) {
8460-
return euint8.wrap(Impl.verify(externalEuint8.unwrap(inputHandle), inputProof, FheType.Uint8));
8475+
if (inputProof.length != 0) {
8476+
return euint8.wrap(Impl.verify(externalEuint8.unwrap(inputHandle), inputProof, FheType.Uint8));
8477+
} else {
8478+
bytes32 inputBytes32 = externalEuint8.unwrap(inputHandle);
8479+
if (!Impl.isAllowed(inputBytes32, msg.sender)) revert SenderNotAllowedToUseHandle(inputBytes32, msg.sender);
8480+
return euint8.wrap(inputBytes32);
8481+
}
84618482
}
84628483

84638484
/**
@@ -8469,9 +8490,18 @@ library FHE {
84698490

84708491
/**
84718492
* @dev Convert an inputHandle with corresponding inputProof to an encrypted euint16 integer.
8493+
* @dev If inputProof is empty, the externalEuint16 inputHandle can be used as a regular euint16 handle if it
8494+
* has already been verified and allowed to the sender.
8495+
* This could facilitate integrating smart contract accounts with fhevm.
84728496
*/
84738497
function fromExternal(externalEuint16 inputHandle, bytes memory inputProof) internal returns (euint16) {
8474-
return euint16.wrap(Impl.verify(externalEuint16.unwrap(inputHandle), inputProof, FheType.Uint16));
8498+
if (inputProof.length != 0) {
8499+
return euint16.wrap(Impl.verify(externalEuint16.unwrap(inputHandle), inputProof, FheType.Uint16));
8500+
} else {
8501+
bytes32 inputBytes32 = externalEuint16.unwrap(inputHandle);
8502+
if (!Impl.isAllowed(inputBytes32, msg.sender)) revert SenderNotAllowedToUseHandle(inputBytes32, msg.sender);
8503+
return euint16.wrap(inputBytes32);
8504+
}
84758505
}
84768506

84778507
/**
@@ -8483,9 +8513,18 @@ library FHE {
84838513

84848514
/**
84858515
* @dev Convert an inputHandle with corresponding inputProof to an encrypted euint32 integer.
8516+
* @dev If inputProof is empty, the externalEuint32 inputHandle can be used as a regular euint32 handle if it
8517+
* has already been verified and allowed to the sender.
8518+
* This could facilitate integrating smart contract accounts with fhevm.
84868519
*/
84878520
function fromExternal(externalEuint32 inputHandle, bytes memory inputProof) internal returns (euint32) {
8488-
return euint32.wrap(Impl.verify(externalEuint32.unwrap(inputHandle), inputProof, FheType.Uint32));
8521+
if (inputProof.length != 0) {
8522+
return euint32.wrap(Impl.verify(externalEuint32.unwrap(inputHandle), inputProof, FheType.Uint32));
8523+
} else {
8524+
bytes32 inputBytes32 = externalEuint32.unwrap(inputHandle);
8525+
if (!Impl.isAllowed(inputBytes32, msg.sender)) revert SenderNotAllowedToUseHandle(inputBytes32, msg.sender);
8526+
return euint32.wrap(inputBytes32);
8527+
}
84898528
}
84908529

84918530
/**
@@ -8497,9 +8536,18 @@ library FHE {
84978536

84988537
/**
84998538
* @dev Convert an inputHandle with corresponding inputProof to an encrypted euint64 integer.
8539+
* @dev If inputProof is empty, the externalEuint64 inputHandle can be used as a regular euint64 handle if it
8540+
* has already been verified and allowed to the sender.
8541+
* This could facilitate integrating smart contract accounts with fhevm.
85008542
*/
85018543
function fromExternal(externalEuint64 inputHandle, bytes memory inputProof) internal returns (euint64) {
8502-
return euint64.wrap(Impl.verify(externalEuint64.unwrap(inputHandle), inputProof, FheType.Uint64));
8544+
if (inputProof.length != 0) {
8545+
return euint64.wrap(Impl.verify(externalEuint64.unwrap(inputHandle), inputProof, FheType.Uint64));
8546+
} else {
8547+
bytes32 inputBytes32 = externalEuint64.unwrap(inputHandle);
8548+
if (!Impl.isAllowed(inputBytes32, msg.sender)) revert SenderNotAllowedToUseHandle(inputBytes32, msg.sender);
8549+
return euint64.wrap(inputBytes32);
8550+
}
85038551
}
85048552

85058553
/**
@@ -8511,9 +8559,18 @@ library FHE {
85118559

85128560
/**
85138561
* @dev Convert an inputHandle with corresponding inputProof to an encrypted euint128 integer.
8562+
* @dev If inputProof is empty, the externalEuint128 inputHandle can be used as a regular euint128 handle if it
8563+
* has already been verified and allowed to the sender.
8564+
* This could facilitate integrating smart contract accounts with fhevm.
85148565
*/
85158566
function fromExternal(externalEuint128 inputHandle, bytes memory inputProof) internal returns (euint128) {
8516-
return euint128.wrap(Impl.verify(externalEuint128.unwrap(inputHandle), inputProof, FheType.Uint128));
8567+
if (inputProof.length != 0) {
8568+
return euint128.wrap(Impl.verify(externalEuint128.unwrap(inputHandle), inputProof, FheType.Uint128));
8569+
} else {
8570+
bytes32 inputBytes32 = externalEuint128.unwrap(inputHandle);
8571+
if (!Impl.isAllowed(inputBytes32, msg.sender)) revert SenderNotAllowedToUseHandle(inputBytes32, msg.sender);
8572+
return euint128.wrap(inputBytes32);
8573+
}
85178574
}
85188575

85198576
/**
@@ -8525,9 +8582,18 @@ library FHE {
85258582

85268583
/**
85278584
* @dev Convert an inputHandle with corresponding inputProof to an encrypted eaddress integer.
8585+
* @dev If inputProof is empty, the externalEaddress inputHandle can be used as a regular eaddress handle if it
8586+
* has already been verified and allowed to the sender.
8587+
* This could facilitate integrating smart contract accounts with fhevm.
85288588
*/
85298589
function fromExternal(externalEaddress inputHandle, bytes memory inputProof) internal returns (eaddress) {
8530-
return eaddress.wrap(Impl.verify(externalEaddress.unwrap(inputHandle), inputProof, FheType.Uint160));
8590+
if (inputProof.length != 0) {
8591+
return eaddress.wrap(Impl.verify(externalEaddress.unwrap(inputHandle), inputProof, FheType.Uint160));
8592+
} else {
8593+
bytes32 inputBytes32 = externalEaddress.unwrap(inputHandle);
8594+
if (!Impl.isAllowed(inputBytes32, msg.sender)) revert SenderNotAllowedToUseHandle(inputBytes32, msg.sender);
8595+
return eaddress.wrap(inputBytes32);
8596+
}
85318597
}
85328598

85338599
/**
@@ -8539,9 +8605,18 @@ library FHE {
85398605

85408606
/**
85418607
* @dev Convert an inputHandle with corresponding inputProof to an encrypted euint256 integer.
8608+
* @dev If inputProof is empty, the externalEuint256 inputHandle can be used as a regular euint256 handle if it
8609+
* has already been verified and allowed to the sender.
8610+
* This could facilitate integrating smart contract accounts with fhevm.
85428611
*/
85438612
function fromExternal(externalEuint256 inputHandle, bytes memory inputProof) internal returns (euint256) {
8544-
return euint256.wrap(Impl.verify(externalEuint256.unwrap(inputHandle), inputProof, FheType.Uint256));
8613+
if (inputProof.length != 0) {
8614+
return euint256.wrap(Impl.verify(externalEuint256.unwrap(inputHandle), inputProof, FheType.Uint256));
8615+
} else {
8616+
bytes32 inputBytes32 = externalEuint256.unwrap(inputHandle);
8617+
if (!Impl.isAllowed(inputBytes32, msg.sender)) revert SenderNotAllowedToUseHandle(inputBytes32, msg.sender);
8618+
return euint256.wrap(inputBytes32);
8619+
}
85458620
}
85468621

85478622
/**

host-contracts/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@fhevm/host-contracts",
3-
"version": "0.9.0-2",
3+
"version": "0.10.0",
44
"description": "fhevm backend contracts",
55
"repository": {
66
"type": "git",

library-solidity/codegen/src/templateFHEDotSol.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -537,9 +537,18 @@ function handleSolidityTFHEConvertPlaintextAndEinputToRespectiveType(fheType: Ad
537537
let result = `
538538
/**
539539
* @dev Convert an inputHandle with corresponding inputProof to an encrypted e${fheType.type.toLowerCase()} integer.
540+
* @dev If inputProof is empty, the externalE${fheType.type.toLowerCase()} inputHandle can be used as a regular e${fheType.type.toLowerCase()} handle if it
541+
* has already been verified and allowed to the sender.
542+
* This could facilitate integrating smart contract accounts with fhevm.
540543
*/
541544
function fromExternal(externalE${fheType.type.toLowerCase()} inputHandle, bytes memory inputProof) internal returns (e${fheType.type.toLowerCase()}) {
542-
return e${fheType.type.toLowerCase()}.wrap(Impl.verify(externalE${fheType.type.toLowerCase()}.unwrap(inputHandle), inputProof, FheType.${fheType.isAlias ? fheType.aliasType : fheType.type}));
545+
if (inputProof.length!=0) {
546+
return e${fheType.type.toLowerCase()}.wrap(Impl.verify(externalE${fheType.type.toLowerCase()}.unwrap(inputHandle), inputProof, FheType.${fheType.isAlias ? fheType.aliasType : fheType.type}));
547+
} else {
548+
bytes32 inputBytes32 = externalE${fheType.type.toLowerCase()}.unwrap(inputHandle);
549+
if (!Impl.isAllowed(inputBytes32, msg.sender)) revert SenderNotAllowedToUseHandle(inputBytes32, msg.sender);
550+
return e${fheType.type.toLowerCase()}.wrap(inputBytes32);
551+
}
543552
}
544553
545554
`;

library-solidity/codegen/src/templates/FHE.sol-template

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ library FHE {
3333
/// @notice Returned if the returned KMS signatures are not valid.
3434
error InvalidKMSSignatures();
3535

36+
/// @notice Returned if the sender is not allowed to use the handle.
37+
error SenderNotAllowedToUseHandle(bytes32 handle, address sender);
38+
3639
/// @notice This event is emitted when public decryption has been successfully verified.
3740
event PublicDecryptionVerified(bytes32[] handlesList, bytes abiEncodedCleartexts);
3841

library-solidity/config/ZamaConfig.sol

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@ library ZamaConfig {
4848
// The addresses below are placeholders and should be replaced with actual addresses
4949
// once deployed on the Ethereum mainnet.
5050
return
51-
CoprocessorConfig({ACLAddress: address(0), CoprocessorAddress: address(0), KMSVerifierAddress: address(0)});
51+
CoprocessorConfig({
52+
ACLAddress: 0xcA2E8f1F656CD25C01F05d0b243Ab1ecd4a8ffb6,
53+
CoprocessorAddress: 0xD82385dADa1ae3E969447f20A3164F6213100e75,
54+
KMSVerifierAddress: 0x77627828a55156b04Ac0DC0eb30467f1a552BB03
55+
});
5256
}
5357

5458
/// @dev chainid == 11155111
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// SPDX-License-Identifier: BSD-3-Clause-Clear
2+
pragma solidity ^0.8.24;
3+
4+
import "../../lib/FHE.sol";
5+
import {CoprocessorSetup} from "../CoprocessorSetup.sol";
6+
7+
contract EncryptedSetter {
8+
euint64 public encryptedResult;
9+
10+
constructor() {
11+
FHE.setCoprocessor(CoprocessorSetup.defaultConfig());
12+
}
13+
14+
function setEncryptedValue(externalEuint64 inputHandle, bytes memory inputProof) external {
15+
euint64 encryptedInput = FHE.fromExternal(inputHandle, inputProof);
16+
encryptedResult = FHE.add(encryptedInput, 42); // simulate some computation
17+
FHE.allowThis(encryptedResult);
18+
FHE.allow(encryptedResult, msg.sender);
19+
}
20+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// SPDX-License-Identifier: BSD-3-Clause-Clear
2+
pragma solidity ^0.8.24;
3+
4+
import "../../lib/FHE.sol";
5+
import {CoprocessorSetup} from "../CoprocessorSetup.sol";
6+
7+
interface IMultiSig {
8+
function getOwners() external view returns (address[] memory);
9+
}
10+
11+
contract MultiSigHelper {
12+
IMultiSig public immutable multiSig;
13+
14+
constructor(address _multiSig) {
15+
FHE.setCoprocessor(CoprocessorSetup.defaultConfig());
16+
multiSig = IMultiSig(_multiSig);
17+
}
18+
19+
function allowForMultiSig(externalEuint64 inputHandle, bytes memory inputProof) external {
20+
euint64 handle = FHE.fromExternal(inputHandle, inputProof);
21+
FHE.allow(handle, address(multiSig));
22+
address[] memory owners = getMultiSigOwners();
23+
uint256 numOwners = owners.length;
24+
for (uint256 i; i < numOwners; i++) {
25+
FHE.allow(handle, owners[i]);
26+
}
27+
}
28+
29+
function getMultiSigOwners() internal view returns (address[] memory) {
30+
return multiSig.getOwners();
31+
}
32+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// SPDX-License-Identifier: BSD-3-Clause-Clear
2+
pragma solidity ^0.8.24;
3+
4+
/// @notice Simple MultiSig contract, where all owners must approve a tx before executing it
5+
contract SimpleMultiSig {
6+
struct Transaction {
7+
address target;
8+
bytes data;
9+
}
10+
11+
event ProposedTx(uint256 indexed txId, address target, bytes data);
12+
13+
uint256 public txCounter;
14+
address[] internal owners;
15+
mapping(address => bool) public isOwner;
16+
mapping(uint256 => Transaction) public transactions;
17+
mapping(uint256 => mapping(address => bool)) public isApprovedByOwner;
18+
mapping(uint256 => bool) public executed;
19+
20+
constructor(address[] memory _owners) {
21+
uint256 length = _owners.length;
22+
require(length > 1, "Multisig should have several owners");
23+
for (uint256 i; i < length; i++) {
24+
require(!isOwner[_owners[i]], "Owner has already been added");
25+
owners.push(_owners[i]);
26+
isOwner[_owners[i]] = true;
27+
}
28+
}
29+
30+
function proposeTx(address target, bytes calldata data) external {
31+
require(isOwner[msg.sender], "Sender is not an owner");
32+
txCounter++;
33+
uint256 txId = txCounter;
34+
transactions[txCounter] = Transaction({target: target, data: data});
35+
emit ProposedTx(txId, target, data);
36+
approveTx(txId); // proposer automatically approves
37+
}
38+
39+
function approveTx(uint256 txId) public {
40+
require(isOwner[msg.sender], "Sender is not an owner");
41+
require(txId != 0 && txId <= txCounter, "Invalid txId");
42+
require(!isApprovedByOwner[txId][msg.sender], "txId has already been approved by sender");
43+
isApprovedByOwner[txId][msg.sender] = true;
44+
}
45+
46+
function executeTx(uint256 txId) external {
47+
require(txId != 0 && txId <= txCounter, "Invalid txId");
48+
require(!executed[txId], "tx has already been executed");
49+
for (uint i = 0; i < owners.length; i++) {
50+
require(isApprovedByOwner[txId][owners[i]], "txId has not been approved by all owners");
51+
}
52+
Transaction memory transaction = transactions[txId];
53+
54+
(bool success, ) = (transaction.target).call(transaction.data);
55+
require(success, "tx reverted");
56+
executed[txId] = true;
57+
}
58+
59+
function getOwners() external view returns (address[] memory) {
60+
return owners;
61+
}
62+
}

0 commit comments

Comments
 (0)