Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sdk/src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ export {
// Builder
buildExecutionRequestFromExternallySignedData,
computeExternalSigningInputs,
computeMintedNonce,
} from "./external-signing.js";
export type {
FieldLike,
Expand Down
28 changes: 28 additions & 0 deletions sdk/src/external-signing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ExecutionRequest,
Field,
Group,
Poseidon2,
} from "./wasm.js";
import { logAndThrow } from "./utils/utils.js";
import {
Expand All @@ -19,6 +20,7 @@ import type {
ExternalSigningInput,
ExternalSigningOptions,
ExecutionRequestParams,
FieldLike,
InputStrategy,
OutputFormat,
RequestSignInput,
Expand Down Expand Up @@ -198,6 +200,32 @@ export async function computeExternalSigningInputs(
}
}

// ---------------------------------------------------------------------------
// computeMintedNonce
// ---------------------------------------------------------------------------

/**
* Computes the nonce of a static output record minted at `outputIndex` by a request whose transition
* view key is `tvk`, exactly as the `cast` instruction does on-chain:
* `nonce = HashToScalar([tvk, output_index]) * G`.
*
* This is useful when building multi-request flows where a record minted by one request is consumed
* by a later request: the consumer must reference the minted record's nonce, which is derived from
* the producing request's transition view key and the record's output index.
*
* @param {FieldLike} tvk - The transition view key of the request that mints the record.
* @param {number} outputIndex - The output index at which the record is minted.
* @returns {Group} The minted record nonce.
*/
export function computeMintedNonce(tvk: FieldLike, outputIndex: number): Group {
if (!Number.isSafeInteger(outputIndex) || outputIndex < 0) {
throw new Error(`computeMintedNonce: outputIndex must be a non-negative safe integer. Received: ${outputIndex}`);
}
const index = Field.fromString(`${outputIndex}field`);
const randomizer = new Poseidon2().hashToScalar([toField(tvk), index]);
return Group.gScalarMultiply(randomizer);
}
Comment thread
Copilot marked this conversation as resolved.

// ---------------------------------------------------------------------------
// Bytes conversion helpers
// ---------------------------------------------------------------------------
Expand Down
6 changes: 3 additions & 3 deletions sdk/tests/algorithm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,19 @@ describe('Hash Function Export Tests', () => {
expect(Poseidon2Hasher.hash(deepCopyFieldArray(fieldArray)).toString()).equals(algebraicData.expectedPoseidon2Hash);
expect(Poseidon2Hasher.hashToScalar(deepCopyFieldArray(fieldArray)).toString()).equals(algebraicData.expectedPoseidon2HashToScalar);
expect(Poseidon2Hasher.hashToGroup(deepCopyFieldArray(fieldArray)).toString()).equals(algebraicData.expectedPoseidon2HashToGroup);
expect(Poseidon2Hasher.hashMany(deepCopyFieldArray(fieldArray), 2).map(field => field.toString())).deep.equals(algebraicData.expectedPoseidon2HashMany);
expect(Poseidon2Hasher.hashMany(deepCopyFieldArray(fieldArray), 2).map((field: Field) => field.toString())).deep.equals(algebraicData.expectedPoseidon2HashMany);

// Run all possible operations for Poseidon4.
expect(Poseidon4Hasher.hash(deepCopyFieldArray(fieldArray)).toString()).equals(algebraicData.expectedPoseidon4Hash);
expect(Poseidon4Hasher.hashToScalar(deepCopyFieldArray(fieldArray)).toString()).equals(algebraicData.expectedPoseidon4HashToScalar);
expect(Poseidon4Hasher.hashToGroup(deepCopyFieldArray(fieldArray)).toString()).equals(algebraicData.expectedPoseidon4HashToGroup);
expect(Poseidon4Hasher.hashMany(deepCopyFieldArray(fieldArray), 2).map(field => field.toString())).deep.equals(algebraicData.expectedPoseidon4HashMany);
expect(Poseidon4Hasher.hashMany(deepCopyFieldArray(fieldArray), 2).map((field: Field) => field.toString())).deep.equals(algebraicData.expectedPoseidon4HashMany);

// Run all possible operations for Poseidon8.
expect(Poseidon8Hasher.hash(deepCopyFieldArray(fieldArray)).toString()).equals(algebraicData.expectedPoseidon8Hash);
expect(Poseidon8Hasher.hashToScalar(deepCopyFieldArray(fieldArray)).toString()).equals(algebraicData.expectedPoseidon8HashToScalar);
expect(Poseidon8Hasher.hashToGroup(deepCopyFieldArray(fieldArray)).toString()).equals(algebraicData.expectedPoseidon8HashToGroup);
expect(Poseidon8Hasher.hashMany(deepCopyFieldArray(fieldArray), 2).map(field => field.toString())).deep.equals(algebraicData.expectedPoseidon8HashMany);
expect(Poseidon8Hasher.hashMany(deepCopyFieldArray(fieldArray), 2).map((field: Field) => field.toString())).deep.equals(algebraicData.expectedPoseidon8HashMany);
});
});
});
123 changes: 123 additions & 0 deletions sdk/tests/data/test-programs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,126 @@ function run_alpha:
call multi_fn_test.aleo/alpha r0 into r1;
output r1 as u32.private;
`;

// Batches multiple credits.aleo records into a single private transfer.
// Mirrors snarkVM's construct_authorization_examples program source.
// https://testnet.explorer.provable.com/program/ldgbatcher_p28.aleo
export const LDGBATCHER_P28_PROGRAM = `import credits.aleo;

program ldgbatcher_p28.aleo;

function transfer_private_2:
input r0 as credits.aleo/credits.record;
input r1 as credits.aleo/credits.record;
input r2 as address.private;
input r3 as u64.private;
call credits.aleo/join r0 r1 into r4;
call credits.aleo/transfer_private r4 r2 r3 into r5 r6;
output r5 as credits.aleo/credits.record;
output r6 as credits.aleo/credits.record;

function transfer_private_3:
input r0 as credits.aleo/credits.record;
input r1 as credits.aleo/credits.record;
input r2 as credits.aleo/credits.record;
input r3 as address.private;
input r4 as u64.private;
call credits.aleo/join r0 r1 into r5;
call credits.aleo/join r2 r5 into r6;
call credits.aleo/transfer_private r6 r3 r4 into r7 r8;
output r7 as credits.aleo/credits.record;
output r8 as credits.aleo/credits.record;

function transfer_private_4:
input r0 as credits.aleo/credits.record;
input r1 as credits.aleo/credits.record;
input r2 as credits.aleo/credits.record;
input r3 as credits.aleo/credits.record;
input r4 as address.private;
input r5 as u64.private;
call credits.aleo/join r0 r1 into r6;
call credits.aleo/join r2 r6 into r7;
call credits.aleo/join r3 r7 into r8;
call credits.aleo/transfer_private r8 r4 r5 into r9 r10;
output r9 as credits.aleo/credits.record;
output r10 as credits.aleo/credits.record;

function transfer_private_5:
input r0 as credits.aleo/credits.record;
input r1 as credits.aleo/credits.record;
input r2 as credits.aleo/credits.record;
input r3 as credits.aleo/credits.record;
input r4 as credits.aleo/credits.record;
input r5 as address.private;
input r6 as u64.private;
call credits.aleo/join r0 r1 into r7;
call credits.aleo/join r2 r7 into r8;
call credits.aleo/join r3 r8 into r9;
call credits.aleo/join r4 r9 into r10;
call credits.aleo/transfer_private r10 r5 r6 into r11 r12;
output r11 as credits.aleo/credits.record;
output r12 as credits.aleo/credits.record;

function transfer_private_6:
input r0 as credits.aleo/credits.record;
input r1 as credits.aleo/credits.record;
input r2 as credits.aleo/credits.record;
input r3 as credits.aleo/credits.record;
input r4 as credits.aleo/credits.record;
input r5 as credits.aleo/credits.record;
input r6 as address.private;
input r7 as u64.private;
call credits.aleo/join r0 r1 into r8;
call credits.aleo/join r2 r8 into r9;
call credits.aleo/join r3 r9 into r10;
call credits.aleo/join r4 r10 into r11;
call credits.aleo/join r5 r11 into r12;
call credits.aleo/transfer_private r12 r6 r7 into r13 r14;
output r13 as credits.aleo/credits.record;
output r14 as credits.aleo/credits.record;

function transfer_private_7:
input r0 as credits.aleo/credits.record;
input r1 as credits.aleo/credits.record;
input r2 as credits.aleo/credits.record;
input r3 as credits.aleo/credits.record;
input r4 as credits.aleo/credits.record;
input r5 as credits.aleo/credits.record;
input r6 as credits.aleo/credits.record;
input r7 as address.private;
input r8 as u64.private;
call credits.aleo/join r0 r1 into r9;
call credits.aleo/join r2 r9 into r10;
call credits.aleo/join r3 r10 into r11;
call credits.aleo/join r4 r11 into r12;
call credits.aleo/join r5 r12 into r13;
call credits.aleo/join r6 r13 into r14;
call credits.aleo/transfer_private r14 r7 r8 into r15 r16;
output r15 as credits.aleo/credits.record;
output r16 as credits.aleo/credits.record;

function transfer_private_8:
input r0 as credits.aleo/credits.record;
input r1 as credits.aleo/credits.record;
input r2 as credits.aleo/credits.record;
input r3 as credits.aleo/credits.record;
input r4 as credits.aleo/credits.record;
input r5 as credits.aleo/credits.record;
input r6 as credits.aleo/credits.record;
input r7 as credits.aleo/credits.record;
input r8 as address.private;
input r9 as u64.private;
call credits.aleo/join r0 r1 into r10;
call credits.aleo/join r2 r10 into r11;
call credits.aleo/join r3 r11 into r12;
call credits.aleo/join r4 r12 into r13;
call credits.aleo/join r5 r13 into r14;
call credits.aleo/join r6 r14 into r15;
call credits.aleo/join r7 r15 into r16;
call credits.aleo/transfer_private r16 r8 r9 into r17 r18;
output r17 as credits.aleo/credits.record;
output r18 as credits.aleo/credits.record;

constructor:
assert.eq edition 0u16;
`;
Loading
Loading