Skip to content

Commit 266293b

Browse files
authored
Add Bip32 smart contract and adjust the key manager to utilizes it (#37)
* Add sha512 bashscript to mock the precompile in AndromedaForge.sol * Add the crypto interface and the necessary data structures for the BIP32. * Continue the BIP32 protocol implementation in solidity. Add unit tests * Remove the sha512 call from the split function * Fix the nil byte issue. Add more unit tests * Add child's extended public key derivation from a parent extended public key * Add a failing test of hardened key derivation from a parent public key * Add new key manager that utilizes BIP32 key derivation with corresponding unit tests * Modify scripts to utilize the new key manager with the BIP32 implementation * Refactor code as follows: 1- use IHash instead of ICrypto and forging the BIP32 2- add new way to address indexing in the NewKeyManager utilizing the BIP32
1 parent a0ccc2d commit 266293b

14 files changed

+369
-44
lines changed

Makefile

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
.PHONY: build-debug
2+
build-debug:
3+
forge build --revert-strings debug
4+
15
.PHONY: build
26
build:
3-
forge build --revert-strings debug
7+
forge build
48

59
.PHONY: test
610
test:

ffi/sha512.sh

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
3+
# Check if argument is provided
4+
if [ -z "$1" ]
5+
then
6+
echo "No argument supplied. Please provide a string to hash."
7+
exit 1
8+
fi
9+
10+
# Compute and print the SHA512 hash
11+
echo -n "$1" | sha512sum | awk '{ print $1 }'

package-lock.json

+19-19
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/onboard_kettle.ts

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ async function main() {
1414

1515
/* Assumes andromeda is configured, might not be */
1616
const Andromeda = await attach_artifact(LocalConfig.ANDROMEDA_ARTIFACT, wallet, LocalConfig.ADDR_OVERRIDES[LocalConfig.ANDROMEDA_ARTIFACT]);
17+
1718
const [KM, _] = await deploy_artifact(LocalConfig.KEY_MANAGER_SN_ARTIFACT, wallet, Andromeda.target);
1819

1920
let resp = await kettle_advance(new_kettle_socket);

src/Andromeda.sol

+9
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ contract Andromeda is IAndromeda, DcapDemo {
1414
address public constant VOLATILEGET_ADDR = 0x0000000000000000000000000000000000040702;
1515
address public constant RANDOM_ADDR = 0x0000000000000000000000000000000000040703;
1616
address public constant SEALINGKEY_ADDR = 0x0000000000000000000000000000000000040704;
17+
address public constant SHA512_ADDR = 0x0000000000000000000000000000000000050700;
1718

1819
function volatileSet(bytes32 key, bytes32 value) external override {
1920
bytes memory cdata = abi.encodePacked([key, value]);
@@ -62,4 +63,12 @@ contract Andromeda is IAndromeda, DcapDemo {
6263
require(sealingBytes.length == 32);
6364
return bytes32(keccak256(abi.encode(bytes32(sealingBytes), key)));
6465
}
66+
67+
function sha512(bytes memory data) external view override returns (bytes memory) {
68+
require(data.length > 0, "sha512: data length must be greater than 0");
69+
(bool success, bytes memory output) = SHA512_ADDR.staticcall(data);
70+
require(success);
71+
require(output.length == 64);
72+
return output;
73+
}
6574
}

src/AndromedaForge.sol

+9
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,13 @@ contract AndromedaForge is IAndromeda {
7979
}
8080
return string(abi.encodePacked("0x", converted));
8181
}
82+
83+
function sha512(bytes memory data) external view override returns (bytes memory) {
84+
require(data.length > 0, "sha512: data length must be greater than 0");
85+
string[] memory inputs = new string[](3);
86+
inputs[0] = "sh";
87+
inputs[1] = "ffi/sha512.sh";
88+
inputs[2] = string(data);
89+
return vm.ffi(inputs);
90+
}
8291
}

src/AndromedaRemote.sol

+9
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,15 @@ contract AndromedaRemote is IAndromeda, DcapDemo {
163163
//return string(abi.encodePacked("0x", converted));
164164
}
165165

166+
function sha512(bytes memory data) external view override returns (bytes memory) {
167+
require(data.length > 0, "sha512: data length must be greater than 0");
168+
string[] memory inputs = new string[](3);
169+
inputs[0] = "sh";
170+
inputs[1] = "ffi/sha512.sh";
171+
inputs[2] = string(data);
172+
return vm.ffi(inputs);
173+
}
174+
166175
///////////////////////////////////////////////////////
167176
// Forge functions for tcbInfo and qeIdentity from file
168177
///////////////////////////////////////////////////////

src/BIP32.sol

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.8;
3+
4+
import {IHash} from "src/hash/IHash.sol";
5+
import {PKE} from "../src/crypto/encryption.sol";
6+
import {Utils} from "src/utils/Utils.sol";
7+
8+
contract BIP32 {
9+
// Derivation domain separator for BIP39 keys array (Suave seed)
10+
bytes public constant BIP32_DERIVATION_DOMAIN = hex"416E64726F6D6564612073656564";
11+
// Hardened key indexes start from 0x80000000 (2³¹) and above
12+
uint32 public constant HARDENED_START_INDEX = 0x80000000;
13+
14+
struct ExtendedKeyAttributes {
15+
// Index of the key in the parent's children
16+
uint32 childNumber;
17+
bytes32 chainCode;
18+
}
19+
struct ExtendedPrivateKey {
20+
bytes32 key;
21+
ExtendedKeyAttributes attributes;
22+
}
23+
struct ExtendedPublicKey {
24+
bytes key;
25+
ExtendedKeyAttributes attributes;
26+
}
27+
28+
IHash private hasher;
29+
30+
constructor(IHash _hasher) {
31+
hasher = _hasher;
32+
}
33+
34+
function split(bytes memory data) public pure returns(bytes32 key, bytes32 chain_code) {
35+
require(data.length == 64, "BIP32: data length must be 64 bytes");
36+
assembly {
37+
// this starts with 32 index because the first 32 bytes are the length of the bytes array
38+
key := mload(add(data, 32)) // Load first 32 bytes
39+
chain_code := mload(add(data, 64)) // Load second 32 bytes
40+
}
41+
}
42+
43+
// Based on the context, it generates either the extended private key or extended public key
44+
function newFromSeed(bytes memory seed) public view returns (ExtendedPrivateKey memory) {
45+
// if seed length is not 16 32 or 64 bytes, throw
46+
require(seed.length == 16 || seed.length == 32 || seed.length == 64, "BIP32: seed length must be 16, 32 or 64 bytes");
47+
bytes memory output = hasher.sha512(abi.encodePacked(Utils.bytesToHexString(abi.encodePacked(BIP32_DERIVATION_DOMAIN, seed))));
48+
(bytes32 secret_key, bytes32 chain_code) = split(output);
49+
return ExtendedPrivateKey(secret_key, ExtendedKeyAttributes(0, chain_code));
50+
}
51+
52+
// derive extended child key pair from a parent seed
53+
function deriveChildKeyPairFromSeed(bytes memory seed, uint32 index) external view returns (ExtendedPrivateKey memory xPriv, ExtendedPublicKey memory xPub) {
54+
ExtendedPrivateKey memory parent = newFromSeed(seed);
55+
return deriveChildKeyPair(parent, index);
56+
}
57+
58+
// derive extended child key pair from a parent extended private key
59+
function deriveChildKeyPair(ExtendedPrivateKey memory parent, uint32 index) public view returns (ExtendedPrivateKey memory xPriv, ExtendedPublicKey memory xPub) {
60+
// hardened key derivation if index > 0x80000000
61+
if (index >= 0x80000000) {
62+
bytes memory data = abi.encodePacked(hex"00", parent.key, index);
63+
bytes memory output = hasher.sha512(abi.encodePacked(Utils.bytesToHexString(abi.encodePacked(parent.attributes.chainCode, data))));
64+
(bytes32 secret_key, bytes32 chain_code) = split(output);
65+
ExtendedKeyAttributes memory extKeyAttr = ExtendedKeyAttributes(index, chain_code);
66+
xPriv = ExtendedPrivateKey(secret_key, extKeyAttr);
67+
xPub = ExtendedPublicKey(PKE.derivePubKey(secret_key), extKeyAttr);
68+
} else {
69+
bytes memory data = abi.encodePacked(PKE.derivePubKey(parent.key), index);
70+
bytes memory output = hasher.sha512(abi.encodePacked(Utils.bytesToHexString(abi.encodePacked(parent.attributes.chainCode, data))));
71+
(bytes32 secret_key, bytes32 chain_code) = split(output);
72+
ExtendedKeyAttributes memory extKeyAttr = ExtendedKeyAttributes(index, chain_code);
73+
xPriv = ExtendedPrivateKey(secret_key, extKeyAttr);
74+
xPub = ExtendedPublicKey(PKE.derivePubKey(secret_key), extKeyAttr);
75+
}
76+
}
77+
78+
// derive child extended key pairs from a given string path
79+
function deriveChildKeyPairFromPath(bytes memory seed, string memory path) external view returns (ExtendedPrivateKey memory xPriv, ExtendedPublicKey memory xPub) {
80+
bytes memory pathBytes = bytes(path);
81+
// require that the path is not empty and the first character of the path is "m" or "M"
82+
require(pathBytes.length > 0 || pathBytes[0] == bytes1('m') || pathBytes[0] == bytes1('M'), "BIP32: invalid path");
83+
84+
ExtendedPrivateKey memory data = newFromSeed(seed);
85+
// if the path is just "m" or "M", return the extended private key and extended public key
86+
if(pathBytes.length <= 2) {
87+
return (data, ExtendedPublicKey(PKE.derivePubKey(data.key), data.attributes));
88+
}
89+
90+
uint32 index = 0;
91+
// iterate through the path and derive the child key pair at each level and check the occurrence of ' to define hardened derivation or not
92+
for (uint i = 2; i < pathBytes.length; i++) {
93+
if (pathBytes[i] == bytes1('\'')) {
94+
//check if index + 2147483648 does not exceed uint32 max value
95+
require(index <= 2147483647, "BIP32: invalid path");
96+
index += 2147483648;
97+
} else if (pathBytes[i] == bytes1('/')) {
98+
(xPriv, xPub) = deriveChildKeyPair(data, index);
99+
data = xPriv;
100+
index = 0;
101+
} else {
102+
// check if the character is not a number and throw instead of converting it to a number
103+
require(uint8(pathBytes[i]) >= 48 && uint8(pathBytes[i]) <= 57, "BIP32: invalid path");
104+
index = index * 10 + uint32(uint8(pathBytes[i]) - 48);
105+
// TODO further formating checks are probably needed to avoid invalid formats, such as m/000123 or m/' or m/1'' or m// etc...
106+
}
107+
}
108+
(xPriv, xPub) = deriveChildKeyPair(data, index);
109+
}
110+
111+
// derive child public key from a parent public key
112+
function derivePubKeyFromParentPubKey(ExtendedPublicKey memory parent, uint32 index) external view returns (ExtendedPublicKey memory xPub) {
113+
require(index < 0x80000000, "BIP32: can't derive hardened public keys from a parent public key");
114+
bytes memory data = abi.encodePacked(parent.key, index);
115+
bytes memory output = hasher.sha512(abi.encodePacked(Utils.bytesToHexString(abi.encodePacked(parent.attributes.chainCode, data))));
116+
(bytes32 secret_key, bytes32 chain_code) = split(output);
117+
ExtendedKeyAttributes memory extKeyAttr = ExtendedKeyAttributes(index, chain_code);
118+
xPub = ExtendedPublicKey(PKE.derivePubKey(secret_key), extKeyAttr);
119+
}
120+
121+
// derive public key from a given private key
122+
function derivePubKey(ExtendedPrivateKey memory xPriv) external view returns (ExtendedPublicKey memory xPub) {
123+
return ExtendedPublicKey(PKE.derivePubKey(xPriv.key), xPriv.attributes);
124+
}
125+
}

src/IAndromeda.sol

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
pragma solidity ^0.8.8;
33

44
import "solidity-stringutils/strings.sol";
5+
import {IHash} from "src/hash/IHash.sol";
56

6-
interface IAndromeda {
7+
interface IAndromeda is IHash {
78
function attestSgx(bytes32 appData) external returns (bytes memory);
89
function verifySgx(address caller, bytes32 appData, bytes memory att) external view returns (bool);
910
function volatileSet(bytes32 tag, bytes32 value) external;

0 commit comments

Comments
 (0)