Skip to content

Commit d8bbb2b

Browse files
authored
feat: Blake2s implementation (#821)
* chore: upgrade juno module to use the blake2s implementation * fix: compiler error due to juno module upgrade * feat: add Blake2s hash functions - Implemented Blake2s and Blake2sArray functions for hashing felt.Felt pointers. * refactor: update compiledClassHash functions logic + new method for blake hash - Introduced CompiledClassHashV2 for using the Blake2s hash function to generate Compiled Clash Hashes - Make a compiledClassHash function that accepts a hash function as a parameter, and use it for the CompiledClassHash and CompiledClassHashV2 functions. * refactor: simplify bytecode segment hashing logic + accept hashFunc param - Adds a new hashFunc param to make the function work with any hash function specified. - Adjusted return values in hashCasmClassByteCode to return the computed hash instead of a function. * test: enhance ClassHashes tests to include CompiledClassHashV2 * feat: add support for Blake2s hash in BuildDeclareTxn - Introduced the UseBlake2sHash option in TxnOptions to allow selection of the Blake2s hash function for compiled class hash calculation. - Updated BuildDeclareTxn to conditionally use Blake2s or the default hash function based on the new option. - Added unit tests to validate the behavior of BuildDeclareTxn with both hash options. * feat: implement support for dynamic hash selection in transaction methods - Added the shouldUseBlake2sHash function to determine if the Blake2s hash function should be used for compiled class hashes based on the Starknet version. - Updated BuildAndSendDeclareTxn and other transaction methods to utilize the new hash selection logic. - Introduced unit tests for BuildAndSendDeclareTxn to validate behavior with different Starknet versions and their corresponding compiled class hashes. * feat: enhance BuildAndSendDeclareTxn with TxnOptions for Blake2s hash selection - Updated TxnOptions to accept a new UseBlake2sHash field, allowing users to specify whether to use the Blake2s hash function for compiled class hash calculation. - Modified the logic to determine the hash function based on the provided options or default to fetching the Starknet version. - Enhanced unit tests to cover various scenarios for hash selection based on Starknet versions and TxnOptions. * test: fix TestBuildAndSendMethodsWithQueryBit test * chore: use new Juno felt constructors * chore: upgrade Juno version to v0.15.11 * refactor: update hash function calls to use curve package - Modified hash function implementations in curve.go to return pointers to the computed hashes. - Updated references in hash.go to utilize the new curve functions for Poseidon and Pedersen hashes, ensuring consistency across the codebase. * chore: update changelog
1 parent 262d3a9 commit d8bbb2b

File tree

13 files changed

+517
-103
lines changed

13 files changed

+517
-103
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
### Security
1616
-->
1717

18+
### Added
19+
- curve pkg:
20+
- new `Blake2s` and `Blake2sArray` functions.
21+
- hash pkg:
22+
- new `CompiledClassHashV2` function to compute the compiled class hash using the Blake2s hash function.
23+
- utils pkg:
24+
- new `UseBlake2sHash` param in the `TxnOptions` struct to be used by the `BuildDeclareTxn` function.
25+
- account pkg:
26+
- new `UseBlake2sHash` param in the `TxnOptions` struct to be used by the `BuildAndSendDeclareTxn` method.
27+
28+
### Changed
29+
- In the `account.BuildAndSendDeclareTxn` method, if the new `opts.UseBlake2sHash` param is nil,
30+
Starknet.go will automatically fetch the current Starknet version and decide
31+
whether to use the Blake2s hash function.
32+
1833
## [0.17.0](https://github.com/NethermindEth/starknet.go/releases/tag/v0.17.0) - 2025-11-06
1934
### Added
2035
- New `paymaster` pkg for interacting with paymaster services via the SNIP-29 API.

account/transaction.go

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"time"
77

8+
"github.com/Masterminds/semver/v3"
89
"github.com/NethermindEth/juno/core/felt"
910
"github.com/NethermindEth/starknet.go/client/rpcerr"
1011
"github.com/NethermindEth/starknet.go/contracts"
@@ -59,8 +60,9 @@ func (account *Account) BuildAndSendInvokeTxn(
5960
callData,
6061
makeResourceBoundsMapWithZeroValues(),
6162
&utils.TxnOptions{
62-
Tip: tip,
63-
UseQueryBit: opts.UseQueryBit,
63+
Tip: tip,
64+
UseQueryBit: opts.UseQueryBit,
65+
UseBlake2sHash: false,
6466
},
6567
)
6668

@@ -138,6 +140,16 @@ func (account *Account) BuildAndSendDeclareTxn(
138140
return response, err
139141
}
140142

143+
var useBlake2sHash bool
144+
if opts.UseBlake2sHash == nil {
145+
useBlake2sHash, err = shouldUseBlake2sHash(ctx, account.Provider)
146+
if err != nil {
147+
return response, fmt.Errorf("failed to check whether to use Blake2s hash: %w", err)
148+
}
149+
} else {
150+
useBlake2sHash = *opts.UseBlake2sHash
151+
}
152+
141153
// building and signing the txn, as it needs a signature to estimate the fee
142154
broadcastDeclareTxnV3, err := utils.BuildDeclareTxn(
143155
account.Address,
@@ -146,8 +158,9 @@ func (account *Account) BuildAndSendDeclareTxn(
146158
nonce,
147159
makeResourceBoundsMapWithZeroValues(),
148160
&utils.TxnOptions{
149-
Tip: tip,
150-
UseQueryBit: opts.UseQueryBit,
161+
Tip: tip,
162+
UseQueryBit: opts.UseQueryBit,
163+
UseBlake2sHash: useBlake2sHash,
151164
},
152165
)
153166
if err != nil {
@@ -239,8 +252,9 @@ func (account *Account) BuildAndEstimateDeployAccountTxn(
239252
classHash,
240253
makeResourceBoundsMapWithZeroValues(),
241254
&utils.TxnOptions{
242-
Tip: tip,
243-
UseQueryBit: opts.UseQueryBit,
255+
Tip: tip,
256+
UseQueryBit: opts.UseQueryBit,
257+
UseBlake2sHash: false,
244258
},
245259
)
246260

@@ -465,3 +479,35 @@ func (account *Account) WaitForTransactionReceipt(
465479
}
466480
}
467481
}
482+
483+
// shouldUseBlake2sHash determines whether to use the Blake2s hash function for the
484+
// compiled class hash.
485+
// Starknet v0.14.1 upgrade will deprecate the Poseidon hash function for the compiled class hash.
486+
//
487+
// Parameters:
488+
// - ctx: The context
489+
// - provider: The provider
490+
//
491+
// Returns:
492+
// - bool: whether to use the Blake2s hash function for the compiled class hash
493+
// - error: an error if any
494+
func shouldUseBlake2sHash(ctx context.Context, provider rpc.RPCProvider) (bool, error) {
495+
block, err := provider.BlockWithTxHashes(ctx, rpc.WithBlockTag(rpc.BlockTagLatest))
496+
if err != nil {
497+
return false, fmt.Errorf("failed to get block with tx hashes: %w", err)
498+
}
499+
500+
blockTxHashes, ok := block.(*rpc.BlockTxHashes)
501+
if !ok {
502+
return false, fmt.Errorf("block is not a BlockTxHashes: %T", block)
503+
}
504+
505+
upgradeVersion := semver.MustParse("0.14.1")
506+
507+
currentVersion, err := semver.NewVersion(blockTxHashes.StarknetVersion)
508+
if err != nil {
509+
return false, fmt.Errorf("failed to parse block's starknet version: %w", err)
510+
}
511+
512+
return currentVersion.Compare(upgradeVersion) >= 0, nil
513+
}

account/transaction_test.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,155 @@ func TestBuildAndSendDeclareTxn(t *testing.T) {
130130
assert.NotEqual(t, "0x0", txn.Transaction.(rpc.DeclareTxnV3).Tip)
131131
}
132132

133+
func TestBuildAndSendDeclareTxnMock(t *testing.T) {
134+
tests.RunTestOn(t, tests.MockEnv)
135+
136+
// Class
137+
class := *internalUtils.TestUnmarshalJSONFileToType[contracts.ContractClass](
138+
t,
139+
"./testData/contracts_v2_HelloStarknet.sierra.json",
140+
"",
141+
)
142+
// Casm Class
143+
casmClass := *internalUtils.TestUnmarshalJSONFileToType[contracts.CasmClass](
144+
t,
145+
"./testData/contracts_v2_HelloStarknet.casm.json",
146+
"",
147+
)
148+
149+
t.Run("compiled class hash", func(t *testing.T) {
150+
testcases := []struct {
151+
name string
152+
txnOptions *account.TxnOptions
153+
starknetVersion string
154+
expectedCompileClassHash string
155+
}{
156+
{
157+
name: "before 0.14.1",
158+
txnOptions: nil,
159+
starknetVersion: "0.14.0",
160+
expectedCompileClassHash: "0x6ff9f7df06da94198ee535f41b214dce0b8bafbdb45e6c6b09d4b3b693b1f17",
161+
},
162+
{
163+
name: "before 0.14.1 + UseBlake2sHash true",
164+
txnOptions: &account.TxnOptions{
165+
UseBlake2sHash: &[]bool{true}[0],
166+
},
167+
starknetVersion: "0.14.0",
168+
expectedCompileClassHash: "0x23c2091df2547f77185ba592b06ee2e897b0c2a70f968521a6a24fc5bfc1b1e",
169+
},
170+
{
171+
name: "before 0.14.1 + UseBlake2sHash false",
172+
txnOptions: &account.TxnOptions{
173+
UseBlake2sHash: &[]bool{false}[0],
174+
},
175+
starknetVersion: "0.14.0",
176+
expectedCompileClassHash: "0x6ff9f7df06da94198ee535f41b214dce0b8bafbdb45e6c6b09d4b3b693b1f17",
177+
},
178+
{
179+
name: "after 0.14.1",
180+
txnOptions: nil,
181+
starknetVersion: "0.14.1",
182+
expectedCompileClassHash: "0x23c2091df2547f77185ba592b06ee2e897b0c2a70f968521a6a24fc5bfc1b1e",
183+
},
184+
{
185+
name: "after 0.14.1 + UseBlake2sHash true",
186+
txnOptions: &account.TxnOptions{
187+
UseBlake2sHash: &[]bool{true}[0],
188+
},
189+
starknetVersion: "0.14.1",
190+
expectedCompileClassHash: "0x23c2091df2547f77185ba592b06ee2e897b0c2a70f968521a6a24fc5bfc1b1e",
191+
},
192+
{
193+
name: "after 0.14.1 + UseBlake2sHash false",
194+
txnOptions: &account.TxnOptions{
195+
UseBlake2sHash: &[]bool{false}[0],
196+
},
197+
starknetVersion: "0.14.1",
198+
expectedCompileClassHash: "0x6ff9f7df06da94198ee535f41b214dce0b8bafbdb45e6c6b09d4b3b693b1f17",
199+
},
200+
}
201+
202+
for _, test := range testcases {
203+
t.Run(test.name, func(t *testing.T) {
204+
ctrl := gomock.NewController(t)
205+
mockRPCProvider := mocks.NewMockRPCProvider(ctrl)
206+
207+
ks, pub, _ := account.GetRandomKeys()
208+
// called when instantiating the account
209+
mockRPCProvider.EXPECT().ChainID(gomock.Any()).Return("SN_SEPOLIA", nil).Times(1)
210+
acnt, err := account.NewAccount(
211+
mockRPCProvider,
212+
internalUtils.DeadBeef,
213+
pub.String(),
214+
ks,
215+
account.CairoV2,
216+
)
217+
require.NoError(t, err)
218+
219+
// called in the BuildAndSendDeclareTxn method
220+
mockRPCProvider.EXPECT().
221+
Nonce(gomock.Any(), gomock.Any(), gomock.Any()).
222+
Return(new(felt.Felt).SetUint64(1), nil).
223+
Times(1)
224+
mockRPCProvider.EXPECT().
225+
BlockWithTxs(t.Context(), rpc.WithBlockTag(rpc.BlockTagLatest)).
226+
Return(&rpc.Block{}, nil).Times(1)
227+
mockRPCProvider.EXPECT().
228+
EstimateFee(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
229+
Return([]rpc.FeeEstimation{
230+
{
231+
FeeEstimationCommon: rpc.FeeEstimationCommon{
232+
L1GasPrice: new(felt.Felt).SetUint64(10),
233+
L1GasConsumed: new(felt.Felt).SetUint64(100),
234+
L1DataGasPrice: new(felt.Felt).SetUint64(5),
235+
L1DataGasConsumed: new(felt.Felt).SetUint64(50),
236+
L2GasPrice: new(felt.Felt).SetUint64(3),
237+
L2GasConsumed: new(felt.Felt).SetUint64(200),
238+
},
239+
},
240+
}, nil).
241+
Times(1)
242+
243+
var compiledClassHash *felt.Felt
244+
245+
if test.txnOptions == nil {
246+
// if txnOptions is nil, the code should call the BlockWithTxHashes method to get the
247+
// Starknet version and decide whether to use the Blake2s hash function
248+
mockRPCProvider.EXPECT().
249+
BlockWithTxHashes(gomock.Any(), rpc.WithBlockTag(rpc.BlockTagLatest)).
250+
Return(&rpc.BlockTxHashes{
251+
BlockHeader: rpc.BlockHeader{StarknetVersion: test.starknetVersion},
252+
}, nil).Times(1)
253+
}
254+
255+
mockRPCProvider.EXPECT().
256+
AddDeclareTransaction(gomock.Any(), gomock.Any()).
257+
DoAndReturn(
258+
func(_, txn any) (rpc.AddDeclareTransactionResponse, error) {
259+
declareTxn, ok := txn.(*rpc.BroadcastDeclareTxnV3)
260+
require.True(t, ok)
261+
262+
compiledClassHash = declareTxn.CompiledClassHash
263+
264+
return rpc.AddDeclareTransactionResponse{}, nil
265+
},
266+
).Times(1)
267+
268+
_, err = acnt.BuildAndSendDeclareTxn(
269+
t.Context(),
270+
&casmClass,
271+
&class,
272+
test.txnOptions,
273+
)
274+
require.NoError(t, err)
275+
276+
assert.Equal(t, test.expectedCompileClassHash, compiledClassHash.String())
277+
})
278+
}
279+
})
280+
}
281+
133282
// BuildAndEstimateDeployAccountTxn is a test function that tests the BuildAndSendDeployAccount method.
134283
//
135284
// This function tests the BuildAndSendDeployAccount method by setting up test data and invoking the method with different test sets.
@@ -411,6 +560,11 @@ func TestBuildAndSendMethodsWithQueryBit(t *testing.T) {
411560
})
412561

413562
t.Run("BuildAndSendDeclareTxn", func(t *testing.T) {
563+
mockRPCProvider.EXPECT().
564+
BlockWithTxHashes(gomock.Any(), rpc.WithBlockTag(rpc.BlockTagLatest)).
565+
Return(&rpc.BlockTxHashes{
566+
BlockHeader: rpc.BlockHeader{StarknetVersion: "0.14.1"},
567+
}, nil).Times(1)
414568
mockRPCProvider.EXPECT().AddDeclareTransaction(gomock.Any(), gomock.Any()).DoAndReturn(
415569
func(_, txn any) (rpc.AddDeclareTransactionResponse, error) {
416570
bcTxn, ok := txn.(*rpc.BroadcastDeclareTxnV3)

account/txn_options.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ type TxnOptions struct {
4545
UseLatest bool
4646
// The simulation flag to be used when estimating fees. Default: none.
4747
SimulationFlag rpc.SimulationFlag
48+
49+
// ONLY FOR THE `BuildAndSendDeclareTxn` METHOD: A pointer to a boolean flag
50+
// indicating whether to use the Blake2s hash function to calculate the compiled
51+
// class hash.
52+
// If omitted (nil), Starknet.go will automatically fetch the current Starknet
53+
// version and decide whether to use the Blake2s hash function.
54+
UseBlake2sHash *bool
55+
// TODO: remove this field after the Starknet v0.14.1 upgrade
4856
}
4957

5058
// BlockID returns the block ID for fee estimation based on the UseLatest flag.

curve/curve.go

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"math/big"
88

99
junoCrypto "github.com/NethermindEth/juno/core/crypto"
10+
"github.com/NethermindEth/juno/core/crypto/blake2s"
1011
"github.com/NethermindEth/juno/core/felt"
1112
internalUtils "github.com/NethermindEth/starknet.go/internal/utils"
1213
starkcurve "github.com/consensys/gnark-crypto/ecc/stark-curve"
@@ -243,7 +244,9 @@ func ComputeHashOnElements(elems []*big.Int) (hash *big.Int) {
243244
// Returns:
244245
// - *felt.Felt: a pointer to a felt.Felt storing the resulting hash.
245246
func Pedersen(a, b *felt.Felt) *felt.Felt {
246-
return junoCrypto.Pedersen(a, b)
247+
hash := junoCrypto.Pedersen(a, b)
248+
249+
return &hash
247250
}
248251

249252
// Poseidon is a function that implements the Poseidon hash.
@@ -255,7 +258,24 @@ func Pedersen(a, b *felt.Felt) *felt.Felt {
255258
// Returns:
256259
// - *felt.Felt: a pointer to a felt.Felt storing the resulting hash.
257260
func Poseidon(a, b *felt.Felt) *felt.Felt {
258-
return junoCrypto.Poseidon(a, b)
261+
hash := junoCrypto.Poseidon(a, b)
262+
263+
return &hash
264+
}
265+
266+
// Blake2s is a function that implements the Blake2s hash.
267+
//
268+
// Parameters:
269+
// - a: a pointers to felt.Felt to be hashed.
270+
// - b: a pointers to felt.Felt to be hashed.
271+
//
272+
// Returns:
273+
// - *felt.Felt: a pointer to a felt.Felt storing the resulting hash.
274+
func Blake2s(a, b *felt.Felt) *felt.Felt {
275+
hash := blake2s.Blake2s(a, b)
276+
hashFelt := felt.Felt(hash)
277+
278+
return &hashFelt
259279
}
260280

261281
// PedersenArray is a function that takes a variadic number of felt.Felt
@@ -268,7 +288,9 @@ func Poseidon(a, b *felt.Felt) *felt.Felt {
268288
// Returns:
269289
// - *felt.Felt: pointer to a felt.Felt
270290
func PedersenArray(felts ...*felt.Felt) *felt.Felt {
271-
return junoCrypto.PedersenArray(felts...)
291+
hash := junoCrypto.PedersenArray(felts...)
292+
293+
return &hash
272294
}
273295

274296
// PoseidonArray is a function that takes a variadic number of felt.Felt
@@ -281,7 +303,25 @@ func PedersenArray(felts ...*felt.Felt) *felt.Felt {
281303
// Returns:
282304
// - *felt.Felt: pointer to a felt.Felt
283305
func PoseidonArray(felts ...*felt.Felt) *felt.Felt {
284-
return junoCrypto.PoseidonArray(felts...)
306+
hash := junoCrypto.PoseidonArray(felts...)
307+
308+
return &hash
309+
}
310+
311+
// Blake2sArray is a function that takes a variadic number of felt.Felt
312+
// pointers as parameters and calls the Blake2sArray function from the
313+
// junoCrypto package with the provided parameters.
314+
//
315+
// Parameters:
316+
// - felts: A variadic number of pointers to felt.Felt
317+
//
318+
// Returns:
319+
// - *felt.Felt: pointer to a felt.Felt
320+
func Blake2sArray(felts ...*felt.Felt) *felt.Felt {
321+
hash := blake2s.Blake2sArray(felts...)
322+
hashFelt := felt.Felt(hash)
323+
324+
return &hashFelt
285325
}
286326

287327
// StarknetKeccak computes the Starknet Keccak hash of the given byte slice.
@@ -293,7 +333,9 @@ func PoseidonArray(felts ...*felt.Felt) *felt.Felt {
293333
// - *felt.Felt: pointer to a felt.Felt
294334
// - error: An error if any
295335
func StarknetKeccak(b []byte) *felt.Felt {
296-
return junoCrypto.StarknetKeccak(b)
336+
hash := junoCrypto.StarknetKeccak(b)
337+
338+
return &hash
297339
}
298340

299341
// fmtPrivKey formats a private key to a 32 bytes array by padding it

0 commit comments

Comments
 (0)