Skip to content

Commit

Permalink
feat(root): eddsa root keys
Browse files Browse the repository at this point in the history
test eddsa root keys

WP-0000

TICKET: WP-0000
  • Loading branch information
alebusse committed Feb 13, 2024
1 parent ddc7557 commit b70866a
Show file tree
Hide file tree
Showing 17 changed files with 170 additions and 145 deletions.
2 changes: 1 addition & 1 deletion modules/bitgo/test/v2/unit/internal/tssUtils/ecdsa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -994,7 +994,7 @@ describe('TSS Ecdsa Utils:', async function () {
});

// Seems to be flaky on CI, failed here: https://github.com/BitGo/BitGoJS/actions/runs/5902489990/job/16010623888?pr=3822
it.skip('createOfflineMuDeltaShare should succeed', async function () {
xit('createOfflineMuDeltaShare should succeed', async function () {
const mockPassword = 'password';
const alphaLength = 1536;
const deltaLength = 64;
Expand Down
1 change: 0 additions & 1 deletion modules/sdk-coin-algo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
"dependencies": {
"@bitgo/sdk-core": "^25.1.0",
"@bitgo/statics": "^46.1.0",
"@bitgo/utxo-lib": "^9.34.0",
"@hashgraph/cryptography": "1.1.2",
"@stablelib/hex": "^1.0.0",
"algosdk": "1.14.0",
Expand Down
46 changes: 19 additions & 27 deletions modules/sdk-coin-algo/src/algo.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/**
* @prettier
*/
import * as utxolib from '@bitgo/utxo-lib';
import * as _ from 'lodash';
import { SeedValidator } from './seedValidator';
import { coins, CoinFamily } from '@bitgo/statics';
Expand All @@ -10,7 +9,6 @@ import {
AddressCoinSpecific,
BaseCoin,
BitGoBase,
Ed25519KeyDeriver,
InvalidAddressError,
InvalidKey,
KeyIndices,
Expand All @@ -26,6 +24,7 @@ import {
UnexpectedAddressError,
VerifyAddressOptions,
VerifyTransactionOptions,
NotSupported,
} from '@bitgo/sdk-core';
import stellar from 'stellar-sdk';

Expand Down Expand Up @@ -169,12 +168,12 @@ export class Algo extends BaseCoin {
return true;
}

/**
* Generate ed25519 key pair
*
* @param seed
* @returns {Object} object with generated pub, prv
*/
/** inheritdoc */
deriveKeyWithSeed(): { derivationPath: string; key: string } {
throw new NotSupported('method deriveKeyWithSeed not supported for eddsa curve');
}

/** inheritdoc */
generateKeyPair(seed?: Buffer): KeyPair {
const keyPair = seed ? new AlgoLib.KeyPair({ seed }) : new AlgoLib.KeyPair();
const keys = keyPair.getKeys();
Expand All @@ -188,14 +187,24 @@ export class Algo extends BaseCoin {
};
}

/** inheritdoc */
generateRootKeyPair(seed?: Buffer): KeyPair {
const keyPair = seed ? new AlgoLib.KeyPair({ seed }) : new AlgoLib.KeyPair();
const keys = keyPair.getKeys();
if (!keys.prv) {
throw new Error('Missing prv in key generation.');
}
return { prv: keys.prv + keys.pub, pub: keys.pub };
}

/**
* Return boolean indicating whether input is valid public key for the coin.
*
* @param {String} pub the pub to be checked
* @returns {Boolean} is it valid?
*/
isValidPub(pub: string): boolean {
return AlgoLib.algoUtils.isValidAddress(pub);
return AlgoLib.algoUtils.isValidAddress(pub) || AlgoLib.algoUtils.isValidPublicKey(pub);
}

/**
Expand All @@ -207,7 +216,7 @@ export class Algo extends BaseCoin {
* @returns {Boolean} is it valid?
*/
isValidPrv(prv: string): boolean {
return AlgoLib.algoUtils.isValidSeed(prv);
return AlgoLib.algoUtils.isValidSeed(prv) || AlgoLib.algoUtils.isValidPrivateKey(prv);
}

/**
Expand Down Expand Up @@ -504,23 +513,6 @@ export class Algo extends BaseCoin {
return true;
}

/** @inheritDoc */
deriveKeyWithSeed({ key, seed }: { key: string; seed: string }): { derivationPath: string; key: string } {
const derivationPathInput = utxolib.crypto.hash256(Buffer.from(seed, 'utf8')).toString('hex');
const derivationPathParts = [
999999,
parseInt(derivationPathInput.slice(0, 7), 16),
parseInt(derivationPathInput.slice(7, 14), 16),
];
const derivationPath = 'm/' + derivationPathParts.map((part) => `${part}'`).join('/');
const derivedKey = Ed25519KeyDeriver.derivePath(derivationPath, key).key;
const keypair = new AlgoLib.KeyPair({ seed: derivedKey });
return {
key: keypair.getAddress(),
derivationPath,
};
}

decodeTx(txn: Buffer): unknown {
return AlgoLib.algoUtils.decodeAlgoTxn(txn);
}
Expand Down
25 changes: 21 additions & 4 deletions modules/sdk-coin-algo/src/lib/transactionBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import {
BaseTransactionBuilder,
BuildTransactionError,
InvalidTransactionError,
isValidEd25519SecretKey,
isValidEd25519Seed,
TransactionType,
} from '@bitgo/sdk-core';
import { algoUtils } from '.';

const MIN_FEE = 1000; // in microalgos

Expand Down Expand Up @@ -325,9 +327,18 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {

/** @inheritdoc */
protected signImplementation({ key }: BaseKey): Transaction {
const buffKey = Utils.decodeSeed(key);
const keypair = new KeyPair({ prv: Buffer.from(buffKey.seed).toString('hex') });
this._keyPairs.push(keypair);
try {
const buffKey = Utils.decodeSeed(key);
const keypair = new KeyPair({ prv: Buffer.from(buffKey.seed).toString('hex') });
this._keyPairs.push(keypair);
} catch (e) {
if (algoUtils.isValidPrivateKey(key)) {
const keypair = new KeyPair({ prv: key });
this._keyPairs.push(keypair);
} else {
throw e;
}
}

return this._transaction;
}
Expand Down Expand Up @@ -371,14 +382,20 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
let isValidPrivateKeyFromBytes;
const isValidPrivateKeyFromHex = isValidEd25519Seed(key);
const isValidPrivateKeyFromBase64 = isValidEd25519Seed(Buffer.from(key, 'base64').toString('hex'));
const isValidRootPrvKey = isValidEd25519SecretKey(key);
try {
const decodedSeed = Utils.decodeSeed(key);
isValidPrivateKeyFromBytes = isValidEd25519Seed(Buffer.from(decodedSeed.seed).toString('hex'));
} catch (err) {
isValidPrivateKeyFromBytes = false;
}

if (!isValidPrivateKeyFromBytes && !isValidPrivateKeyFromHex && !isValidPrivateKeyFromBase64) {
if (
!isValidPrivateKeyFromBytes &&
!isValidPrivateKeyFromHex &&
!isValidPrivateKeyFromBase64 &&
!isValidRootPrvKey
) {
throw new BuildTransactionError(`Key validation failed`);
}
}
Expand Down
65 changes: 54 additions & 11 deletions modules/sdk-coin-algo/test/unit/algo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,20 @@ describe('ALGO:', function () {
signed.txHex.should.equal(AlgoResources.rawTx.transfer.signed);
});

it('should sign transaction with root key', async function () {
const keypair = basecoin.generateRootKeyPair(AlgoResources.accounts.account1.secretKey);

const signed = await basecoin.signTransaction({
txPrebuild: {
txHex: AlgoResources.rawTx.transfer.unsigned,
keys: [keypair.pub],
addressVersion: 1,
},
prv: keypair.prv,
});
signed.txHex.should.equal(AlgoResources.rawTx.transfer.signed);
});

it('should sign half signed transaction', async function () {
const signed = await basecoin.signTransaction({
txPrebuild: {
Expand All @@ -553,6 +567,22 @@ describe('ALGO:', function () {
signed.txHex.should.equal(AlgoResources.rawTx.transfer.multisig);
});

it('should sign half signed transaction with root key', async function () {
const keypair = basecoin.generateRootKeyPair(AlgoResources.accounts.account3.secretKey);

const signed = await basecoin.signTransaction({
txPrebuild: {
halfSigned: {
txHex: AlgoResources.rawTx.transfer.halfSigned,
},
keys: [AlgoResources.accounts.account1.pubKey.toString('hex'), keypair.pub],
addressVersion: 1,
},
prv: keypair.prv,
});
signed.txHex.should.equal(AlgoResources.rawTx.transfer.multisig);
});

it('should verify sign params if the key array contains addresses', function () {
const keys = [
AlgoResources.accounts.account1.address,
Expand Down Expand Up @@ -620,19 +650,24 @@ describe('ALGO:', function () {
basecoin.isValidPub(kp.pub).should.equal(true);
basecoin.isValidPrv(kp.prv).should.equal(true);
});
});

it('should deterministically derive keypair with seed', () => {
const derivedKeypair = basecoin.deriveKeyWithSeed({
key: 'UBI2KNGT742KGIPHMZDJHHSADIT56HRDPVOOCCRYIETD4BAJLCBMQNSCNE',
seed: 'cold derivation seed',
});
console.log(JSON.stringify(derivedKeypair));
describe('Generate wallet Root key pair: ', () => {
it('should generate key pair', () => {
const kp = basecoin.generateRootKeyPair();
basecoin.isValidPub(kp.pub).should.equal(true);
basecoin.isValidPrv(kp.prv).should.equal(true);
});

basecoin.isValidPub(derivedKeypair.key).should.be.true();
derivedKeypair.should.deepEqual({
key: 'NAYUBR4HQKJBBTNNKXQIY7GMHUCHVAUH5DQ4ZVHVL67CUQLGHRWDKQAHYU',
derivationPath: "m/999999'/25725073'/5121434'",
});
it('should generate key pair from seed', () => {
const seed = Buffer.from('9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60', 'hex');
const kp = basecoin.generateRootKeyPair(seed);
basecoin.isValidPub(kp.pub).should.equal(true);
kp.pub.should.equal('d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a');
basecoin.isValidPrv(kp.prv).should.equal(true);
kp.prv.should.equal(
'9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a'
);
});
});

Expand Down Expand Up @@ -702,4 +737,12 @@ describe('ALGO:', function () {
explain.operations[0].coin.should.equals('talgo:USDC-10458941');
});
});

describe('deriveKeyWithSeed', function () {
it('should derive key with seed', function () {
(() => {
basecoin.deriveKeyWithSeed('test');
}).should.throw('method deriveKeyWithSeed not supported for eddsa curve');
});
});
});
17 changes: 0 additions & 17 deletions modules/sdk-coin-dot/test/unit/keypair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,6 @@ describe('Dot KeyPair', () => {
const keyPair = new KeyPair({ pub: bs58Account.publicKey });
should.equal(keyPair.getKeys().pub, publicKeyHexString);
});

it('should be able to derive keypair with hardened derivation', () => {
// using ed25519 (polkadot.js uses sr25519)
const keyPair = new KeyPair({
prv: account1.secretKey,
});
const derivationIndex = 0;
const derived = keyPair.deriveHardened(`m/0'/0'/0'/${derivationIndex}'`);
const derivedKeyPair = new KeyPair({
prv: derived.prv || '',
});
should.exists(derivedKeyPair.getAddress(DotAddressFormat.substrate));
should.exists(derivedKeyPair.getKeys().prv);
should.exists(derivedKeyPair.getKeys().pub);
should.equal(derivedKeyPair.getKeys().prv?.length, 64);
should.equal(derivedKeyPair.getKeys().pub?.length, 64);
});
});

describe('KeyPair validation', () => {
Expand Down
28 changes: 22 additions & 6 deletions modules/sdk-coin-hbar/src/hbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
TokenEnablementConfig,
BaseBroadcastTransactionOptions,
BaseBroadcastTransactionResult,
NotSupported,
} from '@bitgo/sdk-core';
import { BigNumber } from 'bignumber.js';
import * as stellar from 'stellar-sdk';
Expand Down Expand Up @@ -174,12 +175,12 @@ export class Hbar extends BaseCoin {
}
}

/**
* Generate Hedera Hashgraph key pair
*
* @param seed
* @returns {Object} object with generated pub, prv
*/
/** inheritdoc */
deriveKeyWithSeed(): { derivationPath: string; key: string } {
throw new NotSupported('method deriveKeyWithSeed not supported for eddsa curve');
}

/** inheritdoc */
generateKeyPair(seed?: Buffer): KeyPair {
const keyPair = seed ? new HbarKeyPair({ seed }) : new HbarKeyPair();
const keys = keyPair.getKeys();
Expand All @@ -194,6 +195,21 @@ export class Hbar extends BaseCoin {
};
}

/** inheritdoc */
async generateKeyPairAsync(seed?: Buffer): Promise<KeyPair> {
const keyPair = seed ? new HbarKeyPair({ seed }) : new HbarKeyPair();
const keys = keyPair.getKeys(true);

if (!keys.prv) {
throw new Error('Keypair generation failed to generate a prv');
}

return {
pub: keys.pub,
prv: keys.prv,
};
}

async parseTransaction(params: ParseTransactionOptions): Promise<ParsedTransaction> {
return {};
}
Expand Down
32 changes: 1 addition & 31 deletions modules/sdk-coin-sol/test/unit/keyPair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import assert from 'assert';
import { KeyPair } from '../../src';
import should from 'should';
import * as testData from '../resources/sol';
import { isValidPublicKey, isValidAddress, isValidPrivateKey } from '../../src/lib/utils';
import { isValidPublicKey, isValidAddress } from '../../src/lib/utils';

describe('Sol KeyPair', function () {
const defaultSeed = { seed: testData.accountWithSeed.seed };
Expand Down Expand Up @@ -158,34 +158,4 @@ describe('Sol KeyPair', function () {
keyPair.verifySignature(message, signature).should.equal(false);
});
});

describe('deriveHardened', () => {
it('should derive child key pairs', () => {
const rootKeyPair = new KeyPair();
for (let i = 0; i < 50; i++) {
const path = `m/0'/0'/0'/${i}'`;
const derived = new KeyPair(rootKeyPair.deriveHardened(path));

isValidPublicKey(derived.getKeys().pub).should.be.true();
isValidAddress(derived.getAddress()).should.be.true();

const derivedPrv = derived.getKeys().prv;
should.exist(derivedPrv);
isValidPrivateKey(derivedPrv as string | Uint8Array).should.be.true();

const rederived = new KeyPair(rootKeyPair.deriveHardened(path));
rederived.getKeys().should.deepEqual(derived.getKeys());
}
});

it('should not be able to derive without private key', () => {
const rootKeyPair = new KeyPair({ pub: testData.accountWithSeed.publicKey });
assert.throws(() => rootKeyPair.deriveHardened("m/0'/0'/0'/0'"), /need private key to derive hardened keypair/);
});

it('should throw error for non-hardened path', () => {
const rootKeyPair = new KeyPair();
assert.throws(() => rootKeyPair.deriveHardened('m/0/0/0/0'), /Invalid derivation path/);
});
});
});
1 change: 0 additions & 1 deletion modules/sdk-coin-xlm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
"dependencies": {
"@bitgo/sdk-core": "^25.1.0",
"@bitgo/statics": "^46.1.0",
"@bitgo/utxo-lib": "^9.34.0",
"bignumber.js": "^9.1.1",
"lodash": "^4.17.14",
"stellar-sdk": "^10.0.1",
Expand Down
Loading

0 comments on commit b70866a

Please sign in to comment.