Skip to content

Commit

Permalink
initial work and tests for KMaaS v1
Browse files Browse the repository at this point in the history
  • Loading branch information
rishibalakrishnan committed Sep 6, 2024
1 parent 5011c0e commit fc416de
Show file tree
Hide file tree
Showing 11 changed files with 673 additions and 0 deletions.
154 changes: 154 additions & 0 deletions contracts/Account.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import {SignatureRSV, EthereumUtils} from "@oasisprotocol/sapphire-contracts/contracts/EthereumUtils.sol";
import {Sapphire} from "@oasisprotocol/sapphire-contracts/contracts/Sapphire.sol";
import {EIP155Signer} from "@oasisprotocol/sapphire-contracts/contracts/EIP155Signer.sol";
import {AccountBase, AccountFactoryBase} from "./AccountBase.sol";


// A contract to create per-identity account.
contract AccountFactory is AccountFactoryBase {
event AccountCreated(address contractAddress);

function clone (address starterOwner)
public virtual override
returns (AccountBase acct) {
AccountBase acc = new Account(starterOwner);
emit AccountCreated(address(acc));
return acc;
}
}

// A base class for a per-identity account.
// It can be extended to include additional data, for example a symmetric
// key for encrypting and decryption off-chain data.
contract Account is AccountBase {
address private controller;
bytes32 private privateKey;
uint64 nonce;

constructor(address starterOwner) {
controller = starterOwner;
// Generate the private / public keypair
bytes memory pubKey;
bytes memory privKey;
(pubKey, privKey) = Sapphire.generateSigningKeyPair(Sapphire.SigningAlg.Secp256k1PrehashedKeccak256, Sapphire.randomBytes(32, ""));
publicKey = EthereumUtils.k256PubkeyToEthereumAddress(pubKey);
privateKey = bytes32(privKey);
nonce = 0;
}

// Update the controller of this account. Useful when a different
// type of credential is to be used and a validator contract is needed.
function updateController(address _controller) external override authorized {
// I'm not sure if this is the correct way to do access control
require(hasPermission(msg.sender), "Message sender doesn't have permission to access this account");
controller = _controller;
}

// Functions to check and update permissions.
function hasPermission(address grantee) public view override
returns (bool) {
// Check that there's an entry for the grantee and that the expiration is greater than the current timestamp
return (grantee == controller || (permission[grantee] != 0 && permission[grantee] >= block.timestamp));
}

modifier authorized {
require(hasPermission(msg.sender) || msg.sender == address(this),
"Message sender doesn't have permission to access this account");
_;
}

function grantPermission(address grantee, uint256 expiry) public virtual override authorized {
permission[grantee] = expiry;
}

function revokePermission(address grantee) public virtual override authorized {
permission[grantee] = 0;
}

// The remaining functions use the key pair for normal contract operations.
function signEIP155(EIP155Signer.EthTx calldata txToSign)
public view override authorized
returns (bytes memory) {
return EIP155Signer.sign(publicKey, privateKey, txToSign);
}

// Sign a digest.
function sign(bytes32 digest)
public view override authorized
returns (SignatureRSV memory) {
return EthereumUtils.sign(publicKey, privateKey, digest);
}

// Taken from https://github.com/oasisprotocol/sapphire-paratime/blob/main/examples/onchain-signer/contracts/Gasless.sol#L23
function makeProxyTx(
address in_contract,
bytes memory in_data
) external view authorized
returns (bytes memory output) {
bytes memory data = abi.encode(in_contract, in_data);

return
EIP155Signer.sign(
publicKey,
privateKey,
EIP155Signer.EthTx({
nonce: nonce,
gasPrice: 100_000_000_000,
gasLimit: 250000,
to: address(this),
value: 0,
data: abi.encodeCall(this.proxy, data),
chainId: block.chainid
})
);
}

function proxy(bytes memory data) external authorized payable {
(address addr, bytes memory subcallData) = abi.decode(
data,
(address, bytes)
);
(bool success, bytes memory outData) = addr.call{value: msg.value}(
subcallData
);
if (!success) {
// Add inner-transaction meaningful data in case of error.
assembly {
revert(add(outData, 32), mload(outData))
}
}

nonce += 1;
}


// Call another contract.
function call(address in_contract, bytes memory in_data)
public payable override authorized
returns (bool success, bytes memory out_data) {

(success, out_data) = in_contract.call{value: msg.value, gas: gasleft()}(in_data);
if (!success) {
assembly {
revert(add(out_data, 32), mload(out_data))
}
}
nonce++;
}

// Call another contract.
function staticcall(address in_contract, bytes memory in_data)
public override view authorized
returns (bool success, bytes memory out_data) {

(success, out_data) = in_contract.staticcall{gas: gasleft()}(in_data);
if (!success) {
assembly {
revert(add(out_data, 32), mload(out_data))
}
}
}
}
64 changes: 64 additions & 0 deletions contracts/AccountBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import {SignatureRSV, EthereumUtils} from "@oasisprotocol/sapphire-contracts/contracts/EthereumUtils.sol";
import {EIP155Signer} from "@oasisprotocol/sapphire-contracts/contracts/EIP155Signer.sol";

// A contract to create per-identity account.
abstract contract AccountFactoryBase {
function clone (address starterOwner)
public virtual
returns (AccountBase acct);
}

// A base class for a per-identity account.
// It can be extended to include additional data, for example a symmetric
// key for encrypting and decryption off-chain data.
abstract contract AccountBase {
// Address of the controller of this account. It can either be
// an EOA (externally owned account), or a validator contract
// (defined below).
address private controller;

// Update the controller of this account. Useful when a different
// type of credential is to be used and a validator contract is needed.
function updateController(address _controller) external virtual;

// A key pair for the account.
address public publicKey;
bytes32 private privateKey;

// Grant permission for another address to act as owner for this account
// until expiry.
mapping (address => uint256) permission;

// Functions to check and update permissions.
function hasPermission(address grantee) public virtual view
returns (bool);

function grantPermission(address grantee, uint256 expiry)
public virtual;
function revokePermission(address grantee) public virtual;

// The remaining functions use the key pair for normal contract operations.

// Sign a transaction.
function signEIP155(EIP155Signer.EthTx calldata txToSign)
public view virtual
returns (bytes memory);

// Sign a digest.
function sign(bytes32 digest)
public virtual view
returns (SignatureRSV memory);

// Call another contract.
function call(address in_contract, bytes memory in_data)
public payable virtual
returns (bool success, bytes memory out_data);

// Call another contract.
function staticcall(address in_contract, bytes memory in_data)
public virtual view
returns (bool success, bytes memory out_data);
}
69 changes: 69 additions & 0 deletions contracts/AccountWithSymKey.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "./Account.sol";
import "./AccountBase.sol";
import {Sapphire} from "@oasisprotocol/sapphire-contracts/contracts/Sapphire.sol";

// A contract to create per-identity account.
contract AccountWithSymKeyFactory is AccountFactory {
function clone (address starterOwner)
public override
returns (AccountBase acct) {
AccountBase acc = new AccountWithSymKey(starterOwner);
emit AccountCreated(address(acc));
return acc;
}
}

contract AccountWithSymKey is Account {
type Key is bytes32;

constructor(address starterOwner) Account(starterOwner) {}

// Named symmetric keys. name -> key
mapping (string => Key) keys;

// Generate a named symmetric key.
function generateSymKey(string calldata name, bool overwrite)
public authorized {
require(overwrite || Key.unwrap(keys[name]) == bytes32(0), "Key already exists and overwrite is false");

bytes memory domainSep = bytes(name);
(Sapphire.Curve25519PublicKey pubKey, Sapphire.Curve25519SecretKey privKey) = Sapphire.generateCurve25519KeyPair(domainSep);
Key symKey = Key.wrap(Sapphire.deriveSymmetricKey(pubKey, privKey));
keys[name] = symKey;
}

// Retrieve a named symmetric key.
function getSymKey(string calldata name)
public view authorized
returns (Key key) {
key = keys[name];
}

// Delete a named symmetric key.
function deleteSymKey(string calldata name)
external virtual authorized {
keys[name] = Key.wrap(bytes32(0));
}

// Encrypt in_data with the named symmetric key.
function encryptSymKey(string calldata name, bytes memory in_data)
public virtual view authorized
returns (bytes memory out_data) {
require(Key.unwrap(keys[name]) != bytes32(0), "Requested key doesn't exist");
bytes32 nonce = bytes32(Sapphire.randomBytes(32, ""));
bytes memory ciphertext = Sapphire.encrypt(Key.unwrap(keys[name]), nonce, in_data, "");
out_data = abi.encode(nonce, ciphertext);
}

function decryptSymKey(string calldata name, bytes memory in_data)
public view authorized
returns (bytes memory out_data) {
(bytes32 nonce, bytes memory ciphertext) = abi.decode(in_data, (bytes32, bytes));
out_data = Sapphire.decrypt(Key.unwrap(keys[name]), nonce, ciphertext, "");
}

// Some key rotation stuff here?
}
12 changes: 12 additions & 0 deletions contracts/Sender.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

contract Sender {
event CheckSender(address sender);

function emitSender() public returns (address){
emit CheckSender(msg.sender);
return msg.sender;
}

}
14 changes: 14 additions & 0 deletions contracts/Test.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

contract Test {
uint256 counter;

constructor() {
counter = 0;
}

function incrementCounter() public {
counter++;
}
}
Loading

0 comments on commit fc416de

Please sign in to comment.