signBip340 method

List<int> signBip340({
  1. required List<int> digest,
  2. List<int>? tapTweakHash,
  3. List<int>? aux,
})

Signs a given digest using the BIP-340 (Schnorr) signature scheme.

This method follows the BIP-340 specification for creating Schnorr signatures. It ensures that the provided digest has the correct length and applies private key tweaking if a tapTweakHash is provided.

  • digest: The 32-byte message digest to be signed. It must be exactly BitcoinSignerUtils.baselen bytes in length.
  • tapTweakHash: (Optional) A tweak applied to the private key for Taproot-related signatures.
  • aux: (Optional) Auxiliary random data used to add entropy to the signature for security against side-channel attacks.

Implementation

List<int> signBip340(
    {required List<int> digest, List<int>? tapTweakHash, List<int>? aux}) {
  if (digest.length != BitcoinSignerUtils.baselen) {
    throw CryptoSignException(
        "The digest must be a ${BitcoinSignerUtils.baselen}-byte array.");
  }
  if (aux != null && aux.length != 32) {
    throw CryptoSignException("The aux must be a 32-byte array.");
  }

  List<int> byteKey = <int>[];
  if (tapTweakHash != null) {
    byteKey = BitcoinSignerUtils.calculatePrivateTweek(
        _signingKey.privateKey.toBytes(), tapTweakHash);
  } else {
    byteKey = _signingKey.privateKey.toBytes();
  }
  aux ??= QuickCrypto.sha256Hash(<int>[...digest, ...byteKey]);
  final d0 = BigintUtils.fromBytes(byteKey);

  if (!(BigInt.one <= d0 && d0 <= BitcoinSignerUtils.order - BigInt.one)) {
    throw const CryptoSignException(
        "The secret key must be an integer in the range 1..n-1.");
  }
  final P = BitcoinSignerUtils.generator * d0;
  BigInt d = d0;
  if (P.y.isOdd) {
    d = BitcoinSignerUtils.order - d;
  }

  final t = BytesUtils.xor(
      BigintUtils.toBytes(d, length: BitcoinSignerUtils.baselen),
      P2TRUtils.taggedHash("BIP0340/aux", aux));

  final kHash = P2TRUtils.taggedHash(
    "BIP0340/nonce",
    <int>[
      ...t,
      ...BigintUtils.toBytes(P.x, length: BitcoinSignerUtils.baselen),
      ...digest
    ],
  );
  final k0 = BigintUtils.fromBytes(kHash) % BitcoinSignerUtils.order;

  if (k0 == BigInt.zero) {
    throw const CryptoSignException(
        'Failure. This happens only with negligible probability.');
  }
  final R = (BitcoinSignerUtils.generator * k0);
  BigInt k = k0;
  if (R.y.isOdd) {
    k = BitcoinSignerUtils.order - k;
  }

  final eHash = P2TRUtils.taggedHash(
    "BIP0340/challenge",
    List<int>.from([...R.toXonly(), ...P.toXonly(), ...digest]),
  );

  final e = BigintUtils.fromBytes(eHash) % BitcoinSignerUtils.order;

  final eKey = (k + e * d) % BitcoinSignerUtils.order;
  final signature = [
    ...R.toXonly(),
    ...BigintUtils.toBytes(eKey, length: BitcoinSignerUtils.baselen)
  ];
  if (verifierKey.verifyBip340Signature(
      digest: digest, signature: signature, tapTweakHash: tapTweakHash)) {
    return signature;
  }
  throw const CryptoSignException(
      'The created signature does not pass verification.');
}