How does the signing algorithm work?

I'm trying to implement block signing for my application but cannot find a description of the signing algorithm. More specifically: I'm looking for an algorithm, that takes a block hash and the private key and calculates the signature.

Right now, I have taken this code from an IOTA library and modified it to use blake2b. Interestingly this gives me a signature, where the first 32 bytes are matching with the signature I get from NANO Block Hasher/Signer, but the last 32 bytes do not match:

block hash     : 9F10CA3C698020D5FADE57DAF27A9EE818108C94A66A889BB6959B341540D6AD
private key    : 0000000000000000000000000000000000000000000000000000000000000000
my signature   : B6972289F47296FEC6EED3D862E54678157239D55F0880F5C31510D68D9313CAAD997BFC46F98CB2ED6F9A3E3EC3207A26B6D5E638F9024D0EF5405715D36504
right signature: B6972289F47296FEC6EED3D862E54678157239D55F0880F5C31510D68D9313CA7339FACACE0B25749D4C2435C9C649429673EDF00BB30989AAC916296CE1F60E

Looking at the code from NANO Block Hasher/Signer, I get confused, because I don't understand what algorithm is implemented there. This is the code of the signing function there:

// Note: difference from C - smlen returned, not passed as argument.
function crypto_sign(sm, m, n, sk) {
  var d = new Uint8Array(64), h = new Uint8Array(64), r = new Uint8Array(64);
  var i, j, x = new Float64Array(64);
  var p = [gf(), gf(), gf(), gf()];
  
  var pk = derivePublicFromSecret(sk);

  var context = blake2bInit(64, null);
  blake2bUpdate(context, sk);
  d = blake2bFinal(context);
  d[0] &= 248;
  d[31] &= 127;
  d[31] |= 64;

  var smlen = n + 64;
  for (i = 0; i < n; i++) sm[64 + i] = m[i];
  for (i = 0; i < 32; i++) sm[32 + i] = d[32 + i];

  context = blake2bInit(64, null);
  blake2bUpdate(context, sm.subarray(32));
  r = blake2bFinal(context);
  
  reduce(r);
  scalarbase(p, r);
  pack(sm, p);

  for (i = 32; i < 64; i++) sm[i] = pk[i-32];
  
  context = blake2bInit(64, null);
  blake2bUpdate(context, sm);
  h = blake2bFinal(context);
  
  reduce(h);

  for (i = 0; i < 64; i++) x[i] = 0;
  for (i = 0; i < 32; i++) x[i] = r[i];
  for (i = 0; i < 32; i++) {
    for (j = 0; j < 32; j++) {
      x[i+j] += h[i] * d[j];
    }
  }

  modL(sm.subarray(32), x);
  return smlen;
}

Can someone explain to me which algorithm is used to generate the last 32 bytes of the signature?

If no one knows this, my alternative question is: What is x and which role does it play in modL(sm.subarray(32), x)? I don't understand why a function called modL would take more than one argument.

PS: I have also looked at the Sign function from github.com/Inkeliz/blakEd25519, but I can't get it to give me the expected signature either.

After a lot of trial and error, I think I finally found my mistake: The code in the mentioned library expected, that privateKey was 64 bytes long and that the last 32 bytes were the public key. This is apparently the private key format that the standard library uses.