PHP WebShell

Текущая директория: /opt/BitGoJS/modules/sdk-core/src/account-lib/mpc/tss/eddsa

Просмотр файла: eddsa.ts

/**
 * Module provides functions for MPC using threshold signature scheme (TSS). It contains
 * functions for key generation and message signing with EdDSA.
 *
 *
 * ======================
 * EdDSA Key Generation
 * ======================
 * 1. Each signer generates their own key share, which involves a private u-share and a public y-share.
 * 2. Signers distribute their y-share to other signers.
 * 3. After exchanging y-shares the next phase is to combine key shares. Each signer combines their u-share
 *    with the y-shares received from other signers in order to generate a p-share for themselves. We
 *    also save j-shares for other signers.
 * 4. At this point the players do not distribute any shares and the first phase of the
 *    signing protocol is complete.
 *
 * ======================
 * EdDSA Signing
 * ======================
 * 1. The parties from key generation decide they want to sign something. They begin the signing protocol
 *    by generating shares of an ephemeral key.
 *
 *    a) Each signer uses his p-share and the j-shares stored for other players to generate his signing share.
 *    b) This results in each signer having a private x-share and public r-shares.
 *
 * 2. Signers distribute their r-shares to other signers.
 * 3. After exchanging r-shares, each signer signs their share of the ephemeral key using their private
 *    x-share with the r-shares from other signers.
 * 4. This results in each signer having a public g-share which they send to the other signers.
 * 5. After the signers broadcast their g-shares, the final signature can be re-constructed independently.
 */
import { randomBytes, createHash } from 'crypto';
import { Ed25519Curve } from '../../curves';
import Shamir from '../../shamir';
import { bigIntFromBufferLE, bigIntToBufferLE, bigIntFromBufferBE, bigIntToBufferBE, clamp } from '../../util';
import {
  KeyShare,
  UShare,
  YShare,
  KeyCombine,
  PShare,
  SubkeyShare,
  JShare,
  SignShare,
  Signature,
  XShare,
  RShare,
  GShare,
} from './types';
import assert from 'assert';
import { HDTree } from '@bitgo/sdk-lib-mpc';

// 2^256
const base = BigInt('0x010000000000000000000000000000000000000000000000000000000000000000');

export default class Eddsa {
  static curve: Ed25519Curve = new Ed25519Curve();
  static shamir: Shamir = new Shamir(Eddsa.curve);
  static initialized = false;

  static async initialize(hdTree?: HDTree): Promise<Eddsa> {
    if (!Eddsa.initialized) {
      await Ed25519Curve.initialize();
      Eddsa.initialized = true;
    }

    return new Eddsa(hdTree);
  }

  hdTree?: HDTree;

  constructor(hdTree?: HDTree) {
    this.hdTree = hdTree;
  }

  keyShare(index: number, threshold: number, numShares: number, seed?: Buffer): KeyShare {
    if (!(index > 0 && index <= numShares)) {
      throw new Error('Invalid KeyShare config');
    }
    if (seed && seed.length !== 64) {
      throw new Error('Seed must have length 64');
    }
    const seedchain = seed ?? randomBytes(64);
    const actualSeed = seedchain.slice(0, 32);
    const chaincode = seedchain.slice(32);
    const h = createHash('sha512').update(actualSeed).digest();
    const u = clamp(bigIntFromBufferLE(h.slice(0, 32)));
    const y = Eddsa.curve.basePointMult(u);
    const { shares: split_u, v } = Eddsa.shamir.split(u, threshold, numShares);

    const P_i: UShare = {
      i: index,
      t: threshold,
      n: numShares,
      y: bigIntToBufferLE(y, 32).toString('hex'),
      seed: actualSeed.toString('hex'),
      chaincode: chaincode.toString('hex'),
    };
    const shares: KeyShare = {
      uShare: P_i,
      yShares: {},
    };

    for (const ind in split_u) {
      const i = parseInt(ind, 10);
      if (i === index) {
        continue;
      }
      shares.yShares[i] = {
        i,
        j: P_i.i,
        y: bigIntToBufferLE(y, 32).toString('hex'),
        v: bigIntToBufferLE(v[0], 32).toString('hex'),
        u: bigIntToBufferLE(split_u[ind], 32).toString('hex'),
        chaincode: chaincode.toString('hex'),
      };
    }
    return shares;
  }

  keyCombine(uShare: UShare, yShares: YShare[]): KeyCombine {
    const h = createHash('sha512').update(Buffer.from(uShare.seed, 'hex')).digest();
    const u = clamp(bigIntFromBufferLE(h.slice(0, 32)));
    const yValues = [uShare, ...yShares].map((share) => bigIntFromBufferLE(Buffer.from(share.y, 'hex')));
    const y = yValues.reduce((partial, share) => Eddsa.curve.pointAdd(partial, share));
    const chaincodes = [uShare, ...yShares].map(({ chaincode }) => bigIntFromBufferBE(Buffer.from(chaincode, 'hex')));
    const chaincode = chaincodes.reduce((acc, chaincode) => (acc + chaincode) % base);

    // Verify shares.
    for (const share of yShares) {
      if ('v' in share) {
        try {
          Eddsa.shamir.verify(
            bigIntFromBufferLE(Buffer.from(share.u, 'hex')),
            [bigIntFromBufferLE(Buffer.from(share.y, 'hex')), bigIntFromBufferLE(Buffer.from(share.v!, 'hex'))],
            uShare.i
          );
        } catch (err) {
          throw new Error(`Could not verify share from participant ${share.j}. Verification error: ${err}`);
        }
      }
    }

    const P_i: PShare = {
      i: uShare.i,
      t: uShare.t,
      n: uShare.n,
      y: bigIntToBufferLE(y, 32).toString('hex'),
      u: bigIntToBufferLE(u, 32).toString('hex'),
      prefix: h.slice(32).toString('hex'),
      chaincode: bigIntToBufferBE(chaincode, 32).toString('hex'),
    };
    const players: KeyCombine = {
      pShare: P_i,
      jShares: {},
    };

    for (let ind = 0; ind < yShares.length; ind++) {
      const P_j = yShares[ind];
      players.jShares[P_j.j] = {
        i: P_j.j,
        j: P_i.i,
      };
    }
    return players;
  }

  /**
   * Derives a child common keychain from common keychain
   *
   * @param commonKeychain - common keychain as a hex string
   * @param path - bip32 path
   * @return {string} derived common keychain as a hex string
   */
  deriveUnhardened(commonKeychain: string, path: string): string {
    if (this.hdTree === undefined) {
      throw new Error("Can't derive key without HDTree implementation");
    }

    const keychain = Buffer.from(commonKeychain, 'hex');

    const derivedPublicKeychain = this.hdTree.publicDerive(
      {
        pk: bigIntFromBufferLE(keychain.slice(0, 32)),
        chaincode: bigIntFromBufferBE(keychain.slice(32)),
      },
      path
    );

    const derivedPk = bigIntToBufferLE(derivedPublicKeychain.pk, 32).toString('hex');
    const derivedChaincode = bigIntToBufferBE(derivedPublicKeychain.chaincode, 32).toString('hex');

    return derivedPk + derivedChaincode;
  }

  keyDerive(uShare: UShare, yShares: YShare[], path: string): SubkeyShare {
    if (this.hdTree === undefined) {
      throw new Error("Can't derive key without HDTree implementation");
    }
    const h = createHash('sha512').update(Buffer.from(uShare.seed, 'hex')).digest();
    const yValues = [uShare, ...yShares].map((share) => bigIntFromBufferLE(Buffer.from(share.y, 'hex')));
    const y = yValues.reduce((partial, share) => Eddsa.curve.pointAdd(partial, share));
    const u = clamp(bigIntFromBufferLE(h.slice(0, 32)));
    const prefix = bigIntFromBufferBE(h.slice(32));
    let contribChaincode = bigIntFromBufferBE(Buffer.from(uShare.chaincode, 'hex'));
    const chaincodes = [
      contribChaincode,
      ...yShares.map(({ chaincode }) => bigIntFromBufferBE(Buffer.from(chaincode, 'hex'))),
    ];
    const chaincode = chaincodes.reduce((acc, chaincode) => (acc + chaincode) % base);

    // Derive subkey.
    const subkey = this.hdTree.privateDerive({ pk: y, sk: u, prefix, chaincode }, path);

    // Calculate new public key contribution.
    const contribY = Eddsa.curve.basePointMult(subkey.sk);

    // Calculate new chaincode contribution.
    const chaincodeDelta = (base + subkey.chaincode - chaincode) % base;
    contribChaincode = (contribChaincode + chaincodeDelta) % base;

    // Calculate new u values.
    const { shares: split_u, v } = Eddsa.shamir.split(subkey.sk, uShare.t, uShare.n);

    const P_i: PShare = {
      i: uShare.i,
      t: uShare.t,
      n: uShare.n,
      y: bigIntToBufferLE(subkey.pk, 32).toString('hex'),
      u: bigIntToBufferLE(subkey.sk, 32).toString('hex'),
      prefix: bigIntToBufferBE(subkey.prefix!, 32).toString('hex'),
      chaincode: bigIntToBufferBE(subkey.chaincode, 32).toString('hex'),
    };

    const shares: SubkeyShare = {
      pShare: P_i,
      yShares: {},
    };

    for (let ind = 0; ind < yShares.length; ind++) {
      const P_j = yShares[ind];
      shares.yShares[P_j.j] = {
        i: P_j.j,
        j: P_i.i,
        y: bigIntToBufferLE(contribY, 32).toString('hex'),
        v: bigIntToBufferLE(v[0], 32).toString('hex'),
        u: bigIntToBufferLE(split_u[P_j.j], 32).toString('hex'),
        chaincode: bigIntToBufferBE(contribChaincode, 32).toString('hex'),
      };
    }

    return shares;
  }

  signShare(message: Buffer, pShare: PShare, jShares: JShare[], seed?: Buffer): SignShare {
    if (seed && seed.length !== 64) {
      throw new Error('Seed must have length 64');
    }
    const indices = [pShare, ...jShares].map(({ i }) => i);
    const { shares: split_u, v } = Eddsa.shamir.split(
      bigIntFromBufferLE(Buffer.from(pShare.u, 'hex')),
      pShare.t,
      pShare.n
    );

    // Generate nonce contribution.
    const prefix = Buffer.from(pShare.prefix, 'hex');
    const randomBuffer = seed ?? randomBytes(64);

    const digest = createHash('sha512')
      .update(Buffer.concat([prefix, message, randomBuffer]))
      .digest();

    const r = Eddsa.curve.scalarReduce(bigIntFromBufferLE(digest));
    const R = Eddsa.curve.basePointMult(r);
    const { shares: split_r } = Eddsa.shamir.split(r, indices.length, indices.length, indices);

    const P_i: XShare = {
      i: pShare.i,
      y: pShare.y,
      u: bigIntToBufferLE(split_u[pShare.i], 32).toString('hex'),
      r: bigIntToBufferLE(split_r[pShare.i], 32).toString('hex'),
      R: bigIntToBufferLE(R, 32).toString('hex'),
    };

    const resultShares: SignShare = {
      xShare: P_i,
      rShares: {},
    };

    for (let ind = 0; ind < jShares.length; ind++) {
      const S_j = jShares[ind];
      resultShares.rShares[S_j.i] = {
        i: S_j.i,
        j: pShare.i,
        u: bigIntToBufferLE(split_u[S_j.i], 32).toString('hex'),
        v: bigIntToBufferLE(v[0], 32).toString('hex'),
        r: bigIntToBufferLE(split_r[S_j.i], 32).toString('hex'),
        R: bigIntToBufferLE(R, 32).toString('hex'),
        commitment: bigIntToBufferLE(Eddsa.curve.basePointMult(split_r[S_j.i]), 32).toString('hex'),
      };
    }
    return resultShares;
  }

  sign(message: Buffer, playerShare: XShare, rShares: RShare[], yShares: YShare[] = []): GShare {
    for (const rShare of rShares) {
      this.validateCommitment(rShare);
    }

    const S_i = playerShare;

    const uValues = [playerShare, ...rShares, ...yShares].map(({ u }) => bigIntFromBufferLE(Buffer.from(u, 'hex')));
    const x = uValues.reduce((acc, u) => Eddsa.curve.scalarAdd(acc, u));

    const RValues = [playerShare, ...rShares].map(({ R }) => bigIntFromBufferLE(Buffer.from(R, 'hex')));
    const R = RValues.reduce((partial, share) => Eddsa.curve.pointAdd(partial, share));

    const rValues = [playerShare, ...rShares].map(({ r }) => bigIntFromBufferLE(Buffer.from(r, 'hex')));
    const r = rValues.reduce((partial, share) => Eddsa.curve.scalarAdd(partial, share));

    const combinedBuffer = Buffer.concat([bigIntToBufferLE(R, 32), Buffer.from(S_i.y, 'hex'), message]);
    const digest = createHash('sha512').update(combinedBuffer).digest();
    const k = Eddsa.curve.scalarReduce(bigIntFromBufferLE(digest));

    const gamma = Eddsa.curve.scalarAdd(r, Eddsa.curve.scalarMult(k, x));
    const result = {
      i: playerShare.i,
      y: playerShare.y,
      gamma: bigIntToBufferLE(gamma, 32).toString('hex'),
      R: bigIntToBufferLE(R, 32).toString('hex'),
    };
    return result;
  }

  signCombine(shares: GShare[]): Signature {
    const y = shares[0].y;
    const R = shares[0].R;

    const resultShares = {};
    for (const ind in shares) {
      const S_i = shares[ind];
      resultShares[S_i.i] = bigIntFromBufferLE(Buffer.from(S_i.gamma, 'hex'));
    }
    const sigma: bigint = Eddsa.shamir.combine(resultShares);
    const result = {
      y,
      R,
      sigma: bigIntToBufferLE(sigma, 32).toString('hex'),
    };
    return result;
  }

  verify(message: Buffer, signature: Signature): boolean {
    const publicKey = bigIntFromBufferLE(Buffer.from(signature.y, 'hex'));
    const signedMessage = Buffer.concat([Buffer.from(signature.R, 'hex'), Buffer.from(signature.sigma, 'hex')]);
    return Eddsa.curve.verify(message, signedMessage, publicKey);
  }

  private validateCommitment(RShare: RShare): void {
    assert(RShare.commitment, 'Commitment is missing');
    const c = Eddsa.curve.basePointMult(bigIntFromBufferLE(Buffer.from(RShare.r, 'hex')));
    const otherPlayerCommitment = bigIntFromBufferLE(Buffer.from(RShare.commitment, 'hex'));
    if (c !== otherPlayerCommitment) {
      throw new Error('Could not verify other player share');
    }
  }
}

Выполнить команду


Для локальной разработки. Не используйте в интернете!