# Aleo Signing with EdBLS12-377

The `EdBls12377` class provides threshold signing for the [Aleo](https://aleo.org) network using the
`frost-ed-bls12-377` MPC protocol. As with every Sodot scheme, the private key is never reconstructed:
it is split into `t-of-n` shares and signing happens collaboratively between a threshold of parties.

Aleo signing differs from the other schemes in two important ways:

* A key produces an Aleo **`computeKey`** and **`address`** (rather than a single raw public key), and the
  same key can extract an Aleo **view key** for decrypting records.
* Instead of signing an opaque message hash, you usually sign the **fields of an Aleo execution request**.
  Arbitrary byte messages are also supported through [`signBytes`](#signing-arbitrary-bytes).

:::warning
Derivation is **not** available for `ed-bls12-377` keys: each key corresponds to a single Aleo address.
:::

## Key Generation

Generate a threshold `computeKey`/`address` pair. As with the other schemes, the room must be created on
your server using your `API_KEY`, and every party exchanges its `keygenId` over an authenticated channel
before joining the keygen room.

```typescript
import { EdBls12377 } from '@sodot/sodot-web-sdk';

const N = 3;
const T = 2;
const edBls12377 = new EdBls12377();
const API_KEY = 'MY_API_KEY';

// Your server side creates a room for 3 parties using its API_KEY
const keygenRoomUuid = await edBls12377.createRoom(N, API_KEY);
// All parties call initKeygen to get an EdBls12377InitKeygenResult, that contains a keygenId
const keygenInitResult = await edBls12377.initKeygen();
// All parties receive the keygenIds from all other parties over an authenticated channel
const keygenIds = [keygenId1, keygenId2];
// All parties join the keygen room
const keygenResult = await edBls12377.keygen(keygenRoomUuid, N, T, keygenInitResult, keygenIds);
// Discard the keygenInitResult object at this point
// keygenResult now holds { computeKey, address, secretShare }
```

:::tip[API reference]
Full details can be found [here](/web/api-ref/classes/EdBls12377).
:::

## Signing an Aleo Request

`signRequest` signs the fields of an Aleo execution request with a threshold subset of parties. The payload
is an [`EdBls12377RequestSignPayload`](/web/api-ref/interfaces/EdBls12377RequestSignPayload):

* `function_id`: the identifier of the function being executed.
* `is_root`: whether this is the root request of the execution.
* `program_checksum`: the program checksum, or `null`.
* `inputs`: one [`EdBls12377RequestInput`](/web/api-ref/type-aliases/EdBls12377RequestInput) per request input.

Each input is one of two shapes. Non-record inputs use `{ type, index, fields }`, where `type` is one of
`'constant'`, `'public'`, `'private'`, or `'external_record'`. Record inputs use `{ type: 'record', h, tag }`.
All request fields are passed as `Uint8Array`; the SDK hex-encodes them before calling the native bindings.

```typescript
import { EdBls12377 } from '@sodot/sodot-web-sdk';

const T = 2;
const edBls12377 = new EdBls12377();
const API_KEY = 'MY_API_KEY';
const bytes32 = (value: number) => Uint8Array.from([value, ...new Uint8Array(31)]);

// An EdBls12377KeygenResult was previously generated using keygen
const keygenResult = await edBls12377.keygen(...);

// A signing room is created on your server for T parties
const signingRoomUuid = await edBls12377.createRoom(T, API_KEY);

const payload = {
  function_id: bytes32(0x29),
  is_root: false,
  program_checksum: bytes32(0x0b),
  inputs: [{ type: 'constant', index: bytes32(0x00), fields: [bytes32(0x01)] }],
};

// T parties join the signing room
const signedRequest = await edBls12377.signRequest(signingRoomUuid, keygenResult, payload);
// signedRequest holds byte-encoded { signer, signature, tvk, gammas }
```

The result is an [`EdBls12377SignedRequest`](/web/api-ref/interfaces/EdBls12377SignedRequest) containing the
byte-encoded `signer`, `signature`, `tvk`, and `gammas`. See [the end-to-end example](#end-to-end-submitting-an-aleo-transaction)
below for how these fields are fed into the Provable SDK to build and broadcast a transaction.

## Signing Arbitrary Bytes

`signBytes` produces an Aleo Schnorr signature over a raw byte message, matching snarkVM's
`Signature::sign_bytes` / `Signature::verify_bytes`. The signature is returned as a bech32 `sign1...` string
(parseable by snarkVM's `Signature::from_str`) and verifies against the threshold key's `address`.

```typescript
// Reusing edBls12377, signingRoomUuid, and keygenResult from the previous example
const message = new TextEncoder().encode('hello aleo');
const signature = await edBls12377.signBytes(signingRoomUuid, keygenResult, message);
// signature is a bech32-encoded Aleo Signature string, e.g. "sign1..."
```

:::note
The message is bit-packed into 252-bit field-element chunks before signing; messages up to ~128 KiB are
supported. Signing fails before any MPC traffic if the message is too large, or if its packed form
structurally matches an Aleo request preimage (a cross-protocol confusion guard).
:::

## View Keys and Key Export

Beyond exporting the full private key, `ed-bls12-377` keys can also export an Aleo **view key**, which is
used to decrypt records owned by the address. Both can be exported either to a single designated party
through the relay server, or reconstructed locally from a threshold of shares (offline recovery).

```typescript
// Online: export to a single designated party. The recipient first publishes its export ID.
// Your server creates a room for T parties per export operation
const viewKeyExportRoomUuid = await edBls12377.createRoom(T, API_KEY);
const fullKeyExportRoomUuid = await edBls12377.createRoom(T, API_KEY);
const exportTo = await edBls12377.exportID(keygenResult);
const viewKey = await edBls12377.exportViewKey(viewKeyExportRoomUuid, keygenResult, exportTo);
const fullKey = await edBls12377.exportFullPrivateKey(fullKeyExportRoomUuid, keygenResult, exportTo);
// Every party except the recipient receives `undefined`.

// Offline: reconstruct locally from a threshold-sized array of key shares (no relay server).
const viewKeyOffline = await edBls12377.offlineExportViewKey([keygenResult1, keygenResult2]);
const fullKeyOffline = await edBls12377.offlineExportFullPrivateKey([keygenResult1, keygenResult2]);
```

## Refresh, Resharing, and Import

`ed-bls12-377` keys support the same lifecycle operations as the other schemes:
[`refresh`](/web/api-ref/classes/EdBls12377#refresh) to rotate the secret shares without changing the
`computeKey`/`address`, [resharing](/web/api-ref/classes/EdBls12377#resharenewparty) to move the key to a new
quorum, and [importing](/web/api-ref/classes/EdBls12377#importprivatekeyimporter) an externally generated key.

:::warning
Resharing and key import are **advanced** features. Please consult with the Sodot team before using them,
and see the [API reference](/web/api-ref/classes/EdBls12377) for the full safety requirements.
:::

## End-to-End: Submitting an Aleo Transaction

This example shows the full flow of building, signing, and broadcasting an Aleo transaction using
the threshold key together with the [Provable SDK](https://github.com/ProvableHQ/sdk). The helper
`toRequestSignPayload` maps the Provable SDK's signing inputs into the `signRequest` payload shape.

```typescript
import { EdBls12377 } from '@sodot/sodot-web-sdk';
import {
  Address,
  ProgramManager,
  ViewKey,
  buildExecutionRequestFromExternallySignedData,
  computeExternalSigningInputs,
} from '@provablehq/sdk/testnet.js';

const TEST_URL = 'us1-test.sodot.dev';
const API_KEY = 'MY_API_KEY';
const PROVER_URI = 'https://accelerate.provable.com';
const programName = 'credits.aleo';
const functionName = 'transfer_public_to_private';
const inputs = ['aleo1recipient...', '1u64'];
const inputTypes = ['address.private', 'u64.public'];
const seedHex = '...'; // 32-byte Aleo seed hex

function toRequestSignPayload(signingInputs) {
  return {
    function_id: signingInputs.functionId,
    is_root: signingInputs.isRoot,
    program_checksum: signingInputs.checksum ?? null,
    inputs: signingInputs.requestInputs.map((requestInput) => {
      if (requestInput.signingInputType === 'record') {
        if (!requestInput.h || !requestInput.tag) {
          throw new Error('Record input missing h/tag. Provide viewKey when computing record signing inputs.');
        }
        return { type: 'record', h: requestInput.h, tag: requestInput.tag };
      }
      return {
        type: requestInput.signingInputType,
        index: requestInput.index,
        fields: requestInput.data,
      };
    }),
  };
}

const edBls12377 = new EdBls12377(TEST_URL);

// Import an existing Aleo seed into a 1-of-1 threshold key (any t-of-n setup also works).
const importRoomUuid = await edBls12377.createRoom(1, API_KEY);
const importerInit = await edBls12377.initKeygen();
const imported = await edBls12377.importPrivateKeyImporter(importRoomUuid, 1, seedHex, importerInit, [], false);

// Derive the address from the exported view key.
const viewKey = await edBls12377.offlineExportViewKey([imported]);
const signer = Address.from_view_key(ViewKey.from_string(viewKey)).to_string();

// Compute the request signing inputs. `viewKey` is optional when there are no record inputs;
// providing it also derives `signer` and `skTag`.
const signingInputs = await computeExternalSigningInputs({
  programName,
  functionName,
  inputs,
  inputTypes,
  isRoot: true,
  checksum: null,
  viewKey,
  outputFormat: 'bytes',
});
if (!signingInputs.skTag) {
  throw new Error('Expected skTag when viewKey is provided.');
}

// Threshold-sign the request.
const signingRoomUuid = await edBls12377.createRoom(1, API_KEY);
const signedRequest = await edBls12377.signRequest(signingRoomUuid, imported, toRequestSignPayload(signingInputs));

// Reconstruct the execution request from the externally signed data.
const executionRequest = buildExecutionRequestFromExternallySignedData({
  programId: programName,
  functionName,
  inputs,
  inputTypes,
  signature: signedRequest.signature,
  tvk: signedRequest.tvk,
  signer: signedRequest.signer,
  skTag: signingInputs.skTag,
});

const programManager = new ProgramManager();
programManager.networkClient.setProverUri(PROVER_URI);

const provingRequest = await programManager.provingRequest({
  programName,
  functionName,
  inputs,
  priorityFee: 0,
  privateFee: false,
  broadcast: true,
  executionRequest,
});

const provingResult = await programManager.networkClient.submitProvingRequest({
  provingRequest,
  dpsPrivacy: true,
});

console.log('Signer:', signer);
console.log('Transaction ID:', provingResult.transaction.id);
```

`buildExecutionRequestFromExternallySignedData` supports three reconstruction strategies depending on your
privacy model: `{ viewKey, gammas }`, `{ recordViewKeys, gammas }`, or `{ inputIds }`. In all cases the
payload sent to `signRequest` still uses `{ type: 'record', h, tag }` for record inputs. See the
[`EdBls12377` API reference](/web/api-ref/classes/EdBls12377) for the full method documentation.
