Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
25 changes: 25 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,29 @@ 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 {
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