Skip to content

Commit

Permalink
add keygeneration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ftheirs committed Oct 24, 2023
1 parent c12bdab commit 53c1c7b
Show file tree
Hide file tree
Showing 3 changed files with 276 additions and 2 deletions.
11 changes: 9 additions & 2 deletions tests_zemu/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
],
"scripts": {
"clean": "ts-node tests/pullImageKillOld.ts",
"test": "yarn clean && jest --maxConcurrency 2"
"test": "yarn clean && jest -t 'Standard' --maxConcurrency 2",
"keygeneration": "yarn clean && jest -t 'KeyGeneration'"
},
"dependencies": {
"@zondax/ledger-algorand": "../js",
Expand All @@ -39,6 +40,12 @@
"prettier": "^3.0.3",
"ts-jest": "^29.0.5",
"ts-node": "^10.9.1",
"typescript": "^5.2.2"
"typescript": "^5.2.2",
"bip32": "^4.0.0",
"bip32-ed25519": "https://github.com/Zondax/bip32-ed25519",
"bip39": "^3.0.4",
"blakejs": "^1.2.1",
"bs58": "^5.0.0",
"hash.js": "^1.1.7"
}
}
150 changes: 150 additions & 0 deletions tests_zemu/tests/key_derivation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/** ******************************************************************************
* (c) 2018 - 2023 Zondax AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************* */
import Zemu, {DEFAULT_START_OPTIONS } from '@zondax/zemu'
// @ts-ignore
import AlgorandApp from '@zondax/ledger-algorand'
import { APP_SEED, models } from './common'

import { hdKeyDerivation } from './key_derivation';

const bip39 = require("bip39");
const bip32ed25519 = require("bip32-ed25519");

const ALGO_PATH = 0x8000011B //283'

const defaultOptions = {
...DEFAULT_START_OPTIONS,
logging: true,
custom: `-s "${APP_SEED}"`,
X11: false,
}

jest.setTimeout(300000)
const inputs = [
{
mnemonic: 'lecture leopard sting margin search ticket million fitness index vehicle staff spot lunch disagree grief grab spider dismiss negative retreat voice orange degree hundred',
accountId: 0,
pubkey: 'ae1fe3a08857f17974d097d92a561e963743f6478075c856afdf0518db871c2d'
},
{
mnemonic: 'exhibit tongue gym equip toilet afford tragic eager convince version resource fragile snake hope slim ask quick whisper upper invite umbrella priority stairs include',
accountId: 5,
pubkey: '7994821d2cd724328a4daae5cd98ee35a1c53a4cb5338fa5a001facdfd7979a1'
},
{
mnemonic: 'beauty roof crazy man grid explain order list unlock volcano hip sauce fashion shaft hold honey process wool cluster thumb waste light subway word',
accountId: 100,
pubkey: '99a12ea8e7295a2b31c8abb10bf3cb34b7803a522f5a418b1571ae41da0e12ae'
},
{
mnemonic: 'jelly liquid talk orient sketch elbow arm era abandon review belt pony mobile fetch behave fix spend drastic game laugh connect lizard risk forward',
accountId: 10,
pubkey: 'df06626e8f4c78ef46b776fc2498cf44228a72b60622122ea0dac26d523e4170'
},
{
mnemonic: 'flower animal finger brown cube coffee over other cat taxi tide equip market soon outer worry slam donor jungle method large faint mansion sail',
accountId: 50,
pubkey: '8d111c46fae603940607db815ecb0bd18611edfb59160601acd4bdb129d9b89e'
},
{
mnemonic: 'oil actual else mixture crash sunset tide absurd pistol fatigue era craft purity body hunt boy airport network fantasy enact tube dumb window tumble',
accountId: 3,
pubkey: '0472e4d37052849d8c2bf0e9a9d46b23f6318ae9a1c1edcbe844c7273ee08f8b'
},
{
mnemonic: 'debris coral salt palace bring culture regret glove apology donkey explain upset tourist travel envelope bring multiply duty black divide furnace gesture they tourist',
accountId: 15,
pubkey: '17b7403d163df7bf56d8da481e1e68756385663d5be853c4619fb408ca322628'
},
{
mnemonic: 'leisure aisle rule follow tape pet dinosaur genre interest kingdom crime label joy purpose open vicious sight assault mixture also muscle head category waste',
accountId: 1234,
pubkey: 'a493828b194018372f6d93d0ef369e189843c2880156ce963fb5bb79c351fea2'
},
{
mnemonic: 'boss ride hybrid praise cake warfare pass insane clump mutual habit stumble clown badge energy glide phrase people eternal brisk critic scissors virus design',
accountId: 0,
pubkey: '9b6fbd2b837c75343c2bf66123318c9357a626238ae11f94d2dde4de38ce3547'
},
{
mnemonic: 'require beauty sausage tone remain boil mutual chat sun sheriff run account sheriff code hamster canvas crop essay position achieve legal sound volume engage',
accountId: 1,
pubkey: '3e37e731cf82c0a71ef8579b081676970036a98975c8830a316ed6ea2f5e1c72'
},
]

describe('KeyGeneration', function () {
test.concurrent.each(models)('check fixed mnemonic', async function (m) {
const sim = new Zemu(m.path)

// Derivation path: All elements are hardened
// m/44'/283'/accountId'/0'/0'

for (const input of inputs) {
const jsOutput = hdKeyDerivation(input.mnemonic, "", ALGO_PATH, (0x80000000 + input.accountId), 0, 0);

try {
defaultOptions.custom = `-s "${input.mnemonic}"`,
await sim.start({ ...defaultOptions, model: m.name })
const app = new AlgorandApp(sim.getTransport())

const resp = await app.getAddressAndPubKey(input.accountId)
expect(resp.return_code).toEqual(0x9000)
expect(resp.publicKey).toEqual(jsOutput?.pk.toString('hex'))
} finally {
await sim.close()
}
}
})

test.concurrent.each(models)('check random mnemonic', async function (m) {
const sim = new Zemu(m.path)

for (let i = 0; i < 5; i++) {
// Generate random 24-word mnemonic
const mnemonic = bip39.generateMnemonic(256)
console.log(`testing mnemonic: ${mnemonic}`)

try {
defaultOptions.custom = `-s "${mnemonic}"`,
await sim.start({ ...defaultOptions, model: m.name })
const app = new AlgorandApp(sim.getTransport())

for (let i = 0; i < 5; i++) {
const randomAccountId = Math.floor(Math.random() * 101);
const jsOutput = hdKeyDerivation(mnemonic, "", ALGO_PATH, (0x80000000 + randomAccountId), 0, 0);

const resp = await app.getAddressAndPubKey(randomAccountId)

expect(resp.return_code).toEqual(0x9000)
expect(resp.error_message).toEqual('No errors')

expect(resp.publicKey).toEqual(jsOutput?.pk.toString('hex'))
}
} finally {
await sim.close()
}
}

})

})


describe('KeyGeneration', function () {


})
117 changes: 117 additions & 0 deletions tests_zemu/tests/key_derivation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/** ******************************************************************************
* (c) 2019 - 2022 ZondaX AG
* (c) 2016-2017 Ledger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************* */

const bip39 = require("bip39");
const hash = require("hash.js");
const bip32ed25519 = require("bip32-ed25519");
const bs58 = require("bs58");
const blake = require("blakejs");

const HDPATH_0_DEFAULT = 0x8000002c;

function sha512(data: any) {
const digest = hash.sha512().update(data).digest();
return Buffer.from(digest);
}

function hmac256(key: any, data: any) {
const digest = hash.hmac(hash.sha256, key).update(data).digest();
return Buffer.from(digest);
}

function hmac512(key: any, data: any) {
const digest = hash.hmac(hash.sha512, key).update(data).digest();
return Buffer.from(digest);
}

function ss58hash(data: any) {
const hash = blake.blake2bInit(64, null);
blake.blake2bUpdate(hash, Buffer.from("SS58PRE"));
blake.blake2bUpdate(hash, data);
return blake.blake2bFinal(hash);
}

function ss58_encode(prefix: number, pubkey: any) {
if (pubkey.byteLength !== 32) {
return null;
}

const data = Buffer.alloc(35);
data[0] = prefix;
pubkey.copy(data, 1);
const hash = ss58hash(data.slice(0, 33));
data[33] = hash[0];
data[34] = hash[1];

return bs58.encode(data);
}

function root_node_slip10(master_seed: any) {
const data = Buffer.alloc(1 + 64);
data[0] = 0x01;
master_seed.copy(data, 1);
const c = hmac256("ed25519 seed", data);
let I = hmac512("ed25519 seed", data.slice(1));
let kL = I.slice(0, 32);
let kR = I.slice(32);
while ((kL[31] & 32) !== 0) {
I.copy(data, 1);
I = hmac512("ed25519 seed", data.slice(1));
kL = I.slice(0, 32);
kR = I.slice(32);
}
kL[0] &= 248;
kL[31] &= 127;
kL[31] |= 64;

return Buffer.concat([kL, kR, c]);
}

export function hdKeyDerivation(
mnemonic: string,
password: string,
slip0044: number,
accountIndex: number,
changeIndex: number,
addressIndex: number,
) {
if (!bip39.validateMnemonic(mnemonic)) {
console.log("Invalid mnemonic");
return null;
}
const seed = bip39.mnemonicToSeedSync(mnemonic, password);
let node = root_node_slip10(seed);
node = bip32ed25519.derivePrivate(node, HDPATH_0_DEFAULT);
node = bip32ed25519.derivePrivate(node, slip0044);
node = bip32ed25519.derivePrivate(node, accountIndex);
node = bip32ed25519.derivePrivate(node, changeIndex);
node = bip32ed25519.derivePrivate(node, addressIndex);

const kL = node.slice(0, 32);
const sk = sha512(kL).slice(0, 32);
sk[0] &= 248;
sk[31] &= 127;
sk[31] |= 64;

const pk = bip32ed25519.toPublic(sk);
const address = 'notAvailable'
return {
sk: kL,
pk: pk,
address: address,
};
}

0 comments on commit 53c1c7b

Please sign in to comment.