Skip to content

Commit

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

WP-0000
  • Loading branch information
alebusse committed Feb 7, 2024
1 parent 52ca701 commit 9ae8c7e
Show file tree
Hide file tree
Showing 15 changed files with 149 additions and 23 deletions.
2 changes: 1 addition & 1 deletion modules/abstract-eth/src/abstractEthLikeNewCoins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2011,7 +2011,7 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
const walletPassphrase = buildParams.walletPassphrase;

const userKeychain = await this.keychains().get({ id: wallet.keyIds()[0] });
const userPrv = wallet.getUserPrv({ keychain: userKeychain, walletPassphrase });
const userPrv = await wallet.getUserPrv({ keychain: userKeychain, walletPassphrase });
const userPrvBuffer = bip32.fromBase58(userPrv).privateKey;
if (!userPrvBuffer) {
throw new Error('invalid userPrv');
Expand Down
8 changes: 4 additions & 4 deletions modules/bitgo/test/v2/unit/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ describe('V2 Wallet:', function () {
prv,
coldDerivationSeed: '123',
};
wallet.getUserPrv(userPrvOptions).should.eql(derivedPrv);
await wallet.getUserPrv(userPrvOptions).should.eql(derivedPrv);
});

it('should use the user keychain derivedFromParentWithSeed as the cold derivation seed if none is provided', async () => {
Expand All @@ -337,7 +337,7 @@ describe('V2 Wallet:', function () {
type: 'independent',
},
};
wallet.getUserPrv(userPrvOptions).should.eql(derivedPrv);
await wallet.getUserPrv(userPrvOptions).should.eql(derivedPrv);
});

it('should prefer the explicit cold derivation seed to the user keychain derivedFromParentWithSeed', async () => {
Expand All @@ -351,7 +351,7 @@ describe('V2 Wallet:', function () {
type: 'independent',
},
};
wallet.getUserPrv(userPrvOptions).should.eql(derivedPrv);
await wallet.getUserPrv(userPrvOptions).should.eql(derivedPrv);
});

it('should return the prv provided for TSS SMC', async () => {
Expand Down Expand Up @@ -379,7 +379,7 @@ describe('V2 Wallet:', function () {
prv,
keychain,
};
wallet.getUserPrv(userPrvOptions).should.eql(prv);
await wallet.getUserPrv(userPrvOptions).should.eql(prv);
});
});

Expand Down
2 changes: 1 addition & 1 deletion modules/express/src/clientRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ export async function handleV2Sign(req: express.Request) {
let privKey = decryptPrivKey(bitgo, encryptedPrivKey, walletPw);
const coin = bitgo.coin(req.params.coin);
if (req.body.derivationSeed) {
privKey = coin.deriveKeyWithSeed({ key: privKey, seed: req.body.derivationSeed }).key;
privKey = (await coin.deriveKeyWithSeed({ key: privKey, seed: req.body.derivationSeed })).key;
}
try {
return await coin.signTransaction({ ...req.body, prv: privKey });
Expand Down
2 changes: 1 addition & 1 deletion modules/sdk-coin-algo/src/algo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ export class Algo extends BaseCoin {
}

/** @inheritDoc */
deriveKeyWithSeed({ key, seed }: { key: string; seed: string }): { derivationPath: string; key: string } {
async deriveKeyWithSeed({ key, seed }: { key: string; seed: string }): Promise<any> {
const derivationPathInput = utxolib.crypto.hash256(Buffer.from(seed, 'utf8')).toString('hex');
const derivationPathParts = [
999999,
Expand Down
4 changes: 2 additions & 2 deletions modules/sdk-coin-algo/test/unit/algo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -621,8 +621,8 @@ describe('ALGO:', function () {
basecoin.isValidPrv(kp.prv).should.equal(true);
});

it('should deterministically derive keypair with seed', () => {
const derivedKeypair = basecoin.deriveKeyWithSeed({
it('should deterministically derive keypair with seed', async () => {
const derivedKeypair = await basecoin.deriveKeyWithSeed({
key: 'UBI2KNGT742KGIPHMZDJHHSADIT56HRDPVOOCCRYIETD4BAJLCBMQNSCNE',
seed: 'cold derivation seed',
});
Expand Down
6 changes: 3 additions & 3 deletions modules/sdk-coin-btc/src/inscriptionBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class InscriptionBuilder implements IInscriptionBuilder {
const user = await this.wallet.baseCoin.keychains().get({ id: this.wallet.keyIds()[KeyIndices.USER] });
assert(user.pub);

const derived = this.coin.deriveKeyWithSeed({ key: user.pub, seed: inscriptionData.toString() });
const derived = await this.coin.deriveKeyWithSeed({ key: user.pub, seed: inscriptionData.toString() });
const compressedPublicKey = xpubToCompressedPub(derived.key);
const xOnlyPublicKey = utxolib.bitgo.outputScripts.toXOnlyPublicKey(Buffer.from(compressedPublicKey, 'hex'));

Expand Down Expand Up @@ -220,7 +220,7 @@ export class InscriptionBuilder implements IInscriptionBuilder {
},
})) as HalfSignedUtxoTransaction;

const derived = this.coin.deriveKeyWithSeed({ key: xprv, seed: inscriptionData.toString() });
const derived = await this.coin.deriveKeyWithSeed({ key: xprv, seed: inscriptionData.toString() });
const prv = xprvToRawPrv(derived.key);

const fullySignedRevealTransaction = await inscriptions.signRevealTransaction(
Expand Down Expand Up @@ -250,7 +250,7 @@ export class InscriptionBuilder implements IInscriptionBuilder {
txPrebuild: PrebuildTransactionResult
): Promise<SubmitTransactionResponse> {
const userKeychain = await this.wallet.baseCoin.keychains().get({ id: this.wallet.keyIds()[KeyIndices.USER] });
const prv = this.wallet.getUserPrv({ keychain: userKeychain, walletPassphrase });
const prv = await this.wallet.getUserPrv({ keychain: userKeychain, walletPassphrase });

const halfSigned = (await this.wallet.signTransaction({ prv, txPrebuild })) as HalfSignedUtxoTransaction;
return this.wallet.submitTransaction({ halfSigned });
Expand Down
1 change: 1 addition & 0 deletions modules/sdk-coin-hbar/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"dependencies": {
"@bitgo/sdk-coin-algo": "^1.25.0",
"@bitgo/sdk-core": "^25.1.0",
"@bitgo/sdk-lib-mpc": "^8.32.0",
"@bitgo/statics": "^46.1.0",
"@hashgraph/proto": "2.12.0",
"@hashgraph/sdk": "2.29.0",
Expand Down
86 changes: 86 additions & 0 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,
Eddsa,
} from '@bitgo/sdk-core';
import { BigNumber } from 'bignumber.js';
import * as stellar from 'stellar-sdk';
Expand All @@ -35,6 +36,17 @@ import {
Hbar as HbarUnit,
} from '@hashgraph/sdk';
import { PUBLIC_KEY_PREFIX } from './lib/keyPair';
import {
Ed25519Bip32HdTree,
bigIntFromBufferBE,
bigIntFromBufferLE,
bigIntToBufferBE,
bigIntToBufferLE,
clamp,
getDerivationPath,
} from '@bitgo/sdk-lib-mpc';
import { createHash } from 'crypto';
import nacl from 'tweetnacl';

export interface HbarSignTransactionOptions extends SignTransactionOptions {
txPrebuild: TransactionPrebuild;
Expand Down Expand Up @@ -592,4 +604,78 @@ export class Hbar extends BaseCoin {

return { txId: transactionResponse.transactionId.toString(), status: transactionReceipt.status.toString() };
}

/** @inheritDoc */
async deriveKeyWithSeed({ key, seed }: { key: string; seed: string }): Promise<{ key: any; derivationPath: string }> {
const hdTree = await Ed25519Bip32HdTree.initialize();

const derivationPath = getDerivationPath(seed);

if (key.startsWith('rpub')) {
// generic code that could be moved out of this function
const [pub, cc] = key.replace('rpub', '').split(':');

const pk = bigIntFromBufferLE(Buffer.from(pub, 'hex'));
const chaincode = bigIntFromBufferBE(Buffer.from(cc, 'hex'));

const pubKeychain = { pk, chaincode };
const derivedKeychain = hdTree.publicDerive(pubKeychain, derivationPath);
const derivedPub = bigIntToBufferLE(derivedKeychain.pk).toString('hex');

// coinspecific code
const derivedHbarPub = new HbarKeyPair({ pub: derivedPub }).getKeys().pub;

return { key: derivedHbarPub, derivationPath };
} else if (key.startsWith('rprv')) {
await Eddsa.initialize();
// generic code that could be moved out of this function
const [prv, cc, prfx] = key.replace('rprv', '').split(':');
const chaincode = bigIntFromBufferBE(Buffer.from(cc, 'hex'));
const sk = bigIntFromBufferLE(Buffer.from(prv, 'hex'));
const prefix = bigIntFromBufferBE(Buffer.from(prfx, 'hex'));

// coinspecific code to get the public key
const rootKeyPair = new HbarKeyPair({ prv: prv });
const pub = rootKeyPair.getKeys(true).pub;

// generic code that could be moved out of this function
const pk = bigIntFromBufferLE(Buffer.from(pub, 'hex'));
const derivedKeychain = hdTree.privateDerive({ sk, pk, chaincode, prefix }, derivationPath);
const derivedPrv = bigIntToBufferLE(derivedKeychain.sk).toString('hex');
// const derivedPub = bigIntToBufferLE(derivedKeychain.pk).toString('hex');

// coinspecific code
const derivedHbarPrv = new HbarKeyPair({ prv: derivedPrv }).getKeys().prv;

return { key: derivedHbarPrv, derivationPath };
} else {
throw new Error('Invalid key format');
}
}

createRootKeys(): { prv: string; pub: string } {
const seed = Buffer.from(nacl.randomBytes(64));
const hash = createHash('sha512').update(seed.slice(0, 32)).digest();

const chaincode = bigIntFromBufferBE(seed.slice(32, 64));
const prefix = bigIntFromBufferBE(hash.slice(32, 64));

const sk = clamp(bigIntFromBufferLE(hash.slice(0, 32)));

const pk = new HbarKeyPair({ prv: bigIntToBufferLE(sk).toString('hex') }).getKeys(true).pub;

const pub = 'rpub' + pk + ':' + bigIntToBufferBE(chaincode).toString('hex');
const prv =
'rprv' +
bigIntToBufferLE(sk).toString('hex') +
':' +
bigIntToBufferBE(chaincode).toString('hex') +
':' +
bigIntToBufferBE(prefix).toString('hex');

return {
prv,
pub,
};
}
}
29 changes: 29 additions & 0 deletions modules/sdk-coin-hbar/test/unit/hbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -939,4 +939,33 @@ describe('Hedera Hashgraph:', function () {
);
});
});

describe('Key Derivation', function () {
it('seedDerivationPath should be correct', async function () {
const expectedDerivationPath = 'm/999999/25725073/5121434';
const expectedPub = '302a300506032b657003210044a0c69e1b13ce87410402c96c62bfdccf315ea150d1537b6a43d47acbef60fc';
const expectedPrv =
'302e020100300506032b657004220420245072d243a8e4232a33ecf9578ecb7ed926926edcb7993318f48f45a7cf7e02';

// const rootKeyPair = basecoin.createRootKeys()

const rootKeyPair = {
prv: 'rprvd011c015c8ca989b36a5ee7b73661dc854c50a2cb8e91db73f36d76b97cf7e42:9138336fdba9096b6343b9c8b444baf084528725d58a78531607b8534613e41d:8623583b56ebbe8bbd80e6599682e2b1242cabe97cbe013575db04039c028687',
pub: 'rpubb765e558aff0fdab8ed42e1745a0fb2ebc2d13512701fad02b147b5c01503eba:9138336fdba9096b6343b9c8b444baf084528725d58a78531607b8534613e41d',
};
const seed = 'cold derivation seed';

const derivedPub = await basecoin.deriveKeyWithSeed({ key: rootKeyPair.pub, seed });
assert.ok(derivedPub.derivationPath === expectedDerivationPath);
assert.ok(derivedPub.key === expectedPub);

const derivedPrv = await basecoin.deriveKeyWithSeed({ key: rootKeyPair.prv, seed });
assert.ok(derivedPrv.derivationPath === expectedDerivationPath);
derivedPrv.key.should.equal(expectedPrv);

const derivedKeychain = new KeyPair({ prv: derivedPrv.key }).getKeys();
derivedKeychain.pub.should.equal(expectedPub);
derivedKeychain.prv!.should.equal(expectedPrv);
});
});
});
2 changes: 1 addition & 1 deletion modules/sdk-coin-xlm/src/xlm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1121,7 +1121,7 @@ export class Xlm extends BaseCoin {
* @param key seed for the master key. Note: Not the public key or encoded private key. This is the raw seed.
* @param entropySeed random seed which is hashed to generate the derivation path
*/
deriveKeyWithSeed({ key, seed }: { key: string; seed: string }): { derivationPath: string; key: string } {
async deriveKeyWithSeed({ key, seed }: { key: string; seed: string }): Promise<any> {
const derivationPathInput = utxolib.crypto.hash256(Buffer.from(seed, 'utf8')).toString('hex');
const derivationPathParts = [
999999,
Expand Down
8 changes: 7 additions & 1 deletion modules/sdk-core/src/bitgo/baseCoin/baseCoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,13 @@ export abstract class BaseCoin implements IBaseCoin {
* @param seed
* @returns {{key: string, derivationPath: string}}
*/
deriveKeyWithSeed({ key, seed }: { key: string; seed: string }): { key: string; derivationPath: string } {
async deriveKeyWithSeed({
key,
seed,
}: {
key: string;
seed: string;
}): Promise<{ key: string; derivationPath: string }> {
function sha256(input) {
return crypto.createHash('sha256').update(input).digest();
}
Expand Down
2 changes: 1 addition & 1 deletion modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ export interface IBaseCoin {
): Promise<SignedTransaction>;
newWalletObject(walletParams: any): IWallet;
feeEstimate(params: FeeEstimateOptions): Promise<any>;
deriveKeyWithSeed(params: DeriveKeyWithSeedOptions): { key: string; derivationPath: string };
deriveKeyWithSeed(params: DeriveKeyWithSeedOptions): Promise<{ key: string; derivationPath: string }>;
keyIdsForSigning(): number[];
preCreateBitGo(params: PrecreateBitGoOptions): void;
initiateRecovery(params: InitiateRecoveryOptions): never;
Expand Down
2 changes: 1 addition & 1 deletion modules/sdk-core/src/bitgo/wallet/iWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,7 @@ export interface IWallet {
removeUser(params?: RemoveUserOptions): Promise<any>;
prebuildTransaction(params?: PrebuildTransactionOptions): Promise<PrebuildTransactionResult>;
signTransaction(params?: WalletSignTransactionOptions): Promise<SignedTransaction>;
getUserPrv(params?: GetUserPrvOptions): string;
getUserPrv(params?: GetUserPrvOptions): Promise<string>;
prebuildAndSignTransaction(params?: PrebuildAndSignTransactionOptions): Promise<SignedTransaction>;
accelerateTransaction(params?: AccelerateTransactionOptions): Promise<any>;
submitTransaction(params?: SubmitTransactionOptions): Promise<any>;
Expand Down
16 changes: 10 additions & 6 deletions modules/sdk-core/src/bitgo/wallet/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1705,7 +1705,11 @@ export class Wallet implements IWallet {
});

if (this.multisigType() === 'tss') {
return this.signTransactionTss({ ...presign, prv: this.getUserPrv(presign as GetUserPrvOptions), apiVersion });
return this.signTransactionTss({
...presign,
prv: await this.getUserPrv(presign as GetUserPrvOptions),
apiVersion,
});
}

let { pubs } = params;
Expand Down Expand Up @@ -1737,7 +1741,7 @@ export class Wallet implements IWallet {
}
return this.baseCoin.signTransaction({
...signTransactionParams,
prv: this.getUserPrv(presign as GetUserPrvOptions),
prv: await this.getUserPrv(presign as GetUserPrvOptions),
});
}

Expand Down Expand Up @@ -1768,7 +1772,7 @@ export class Wallet implements IWallet {
...params,
walletData: this._wallet,
tssUtils: this.tssUtils,
prv: this.getUserPrv(userPrvOptions),
prv: await this.getUserPrv(userPrvOptions),
keychain: keychains[0],
backupKeychain: keychains.length > 1 ? keychains[1] : null,
bitgoKeychain: keychains.length > 2 ? keychains[2] : null,
Expand Down Expand Up @@ -1804,7 +1808,7 @@ export class Wallet implements IWallet {
...params,
walletData: this._wallet,
tssUtils: this.tssUtils,
prv: this.getUserPrv(userPrvOptions),
prv: await this.getUserPrv(userPrvOptions),
keychain: keychains[0],
backupKeychain: keychains.length > 1 ? keychains[1] : null,
bitgoKeychain: keychains.length > 2 ? keychains[2] : null,
Expand All @@ -1818,7 +1822,7 @@ export class Wallet implements IWallet {
* @param [params.keychain / params.key] (object) or params.prv (string)
* @param params.walletPassphrase (string)
*/
getUserPrv(params: GetUserPrvOptions = {}): string {
async getUserPrv(params: GetUserPrvOptions = {}): Promise<string> {
const userKeychain = params.keychain || params.key;
let userPrv = params.prv;
if (userPrv && typeof userPrv !== 'string') {
Expand All @@ -1839,7 +1843,7 @@ export class Wallet implements IWallet {

if (userPrv && params.coldDerivationSeed) {
// the derivation only makes sense when a key already exists
const derivation = this.baseCoin.deriveKeyWithSeed({ key: userPrv, seed: params.coldDerivationSeed });
const derivation = await this.baseCoin.deriveKeyWithSeed({ key: userPrv, seed: params.coldDerivationSeed });
userPrv = derivation.key;
} else if (!userPrv) {
if (!userKeychain || typeof userKeychain !== 'object') {
Expand Down
2 changes: 1 addition & 1 deletion modules/sdk-core/src/bitgo/wallet/wallets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ export class Wallets implements IWallets {
userKeychainParams = userKeychain;
if (params.coldDerivationSeed) {
// the derivation only makes sense when a key already exists
const derivation = this.baseCoin.deriveKeyWithSeed({
const derivation = await this.baseCoin.deriveKeyWithSeed({
key: params.userKey,
seed: params.coldDerivationSeed,
});
Expand Down

0 comments on commit 9ae8c7e

Please sign in to comment.