-
Notifications
You must be signed in to change notification settings - Fork 134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable custom network signing #1444
Changes from 8 commits
04d6085
66f0699
d724468
9081fb8
44070bd
f4625c1
eef6849
02450c4
33abba1
90bb558
3cb66fb
c35b9a5
105320d
90764e5
2c3a6f2
cd43ef7
1a60fd1
3c799af
d311e92
1685902
ecf67e5
5840e76
5b9bdcf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
+1 −1 | MINA_COMMIT | |
+85,734 −85,733 | compiled/node_bindings/o1js_node.bc.cjs | |
+1 −1 | compiled/node_bindings/o1js_node.bc.map | |
+7 −7 | compiled/web_bindings/o1js_web.bc.js | |
+13 −14 | ocaml/lib/consistency_test.ml |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ import { | |
Signature, | ||
signFieldElement, | ||
verifyFieldElement, | ||
zkAppBodyPrefix, | ||
} from './signature.js'; | ||
import { mocks } from '../../bindings/crypto/constants.js'; | ||
import { NetworkId } from './types.js'; | ||
|
@@ -159,18 +160,6 @@ function accountUpdatesToCallForest<A extends { body: { callDepth: number } }>( | |
return forest; | ||
} | ||
|
||
const zkAppBodyPrefix = (network: string) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. moved this into |
||
switch (network) { | ||
case 'mainnet': | ||
return prefixes.zkappBodyMainnet; | ||
case 'testnet': | ||
return prefixes.zkappBodyTestnet; | ||
|
||
default: | ||
return 'ZkappBody' + network; | ||
} | ||
}; | ||
|
||
function accountUpdateHash(update: AccountUpdate, networkId: NetworkId) { | ||
assertAuthorizationKindValid(update); | ||
let input = AccountUpdate.toInput(update); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -82,42 +82,38 @@ expect(stringify(dummyInput.packed)).toEqual( | |
stringify(dummyInputSnarky.packed) | ||
); | ||
|
||
test(Random.accountUpdate, (accountUpdate) => { | ||
const testnetMinaInstance = Network({ | ||
networkId: 'testnet', | ||
mina: 'http://localhost:8080/graphql', | ||
}); | ||
const mainnetMinaInstance = Network({ | ||
networkId: 'mainnet', | ||
mina: 'http://localhost:8080/graphql', | ||
}); | ||
|
||
fixVerificationKey(accountUpdate); | ||
|
||
// example account update | ||
let accountUpdateJson: Json.AccountUpdate = | ||
AccountUpdate.toJSON(accountUpdate); | ||
|
||
// account update hash | ||
let accountUpdateSnarky = AccountUpdateSnarky.fromJSON(accountUpdateJson); | ||
let inputSnarky = TypesSnarky.AccountUpdate.toInput(accountUpdateSnarky); | ||
let input = AccountUpdate.toInput(accountUpdate); | ||
expect(toJSON(input.fields)).toEqual(toJSON(inputSnarky.fields)); | ||
expect(toJSON(input.packed)).toEqual(toJSON(inputSnarky.packed)); | ||
|
||
let packed = packToFields(input); | ||
let packedSnarky = packToFieldsSnarky(inputSnarky); | ||
expect(toJSON(packed)).toEqual(toJSON(packedSnarky)); | ||
|
||
let hashTestnet = accountUpdateHash(accountUpdate, 'testnet'); | ||
let hashMainnet = accountUpdateHash(accountUpdate, 'mainnet'); | ||
setActiveInstance(testnetMinaInstance); | ||
let hashSnarkyTestnet = accountUpdateSnarky.hash(); | ||
setActiveInstance(mainnetMinaInstance); | ||
let hashSnarkyMainnet = accountUpdateSnarky.hash(); | ||
expect(hashTestnet).toEqual(hashSnarkyTestnet.toBigInt()); | ||
expect(hashMainnet).toEqual(hashSnarkyMainnet.toBigInt()); | ||
}); | ||
test( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cleaned this up a little by utilizing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Trivo25 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we should! And we do expect(
verify(sigFieldElements, networkId === 'mainnet' ? 'testnet' : 'mainnet')
).toEqual(false); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. but I added a check that the hash doesnt match another networks hash! |
||
Random.accountUpdate, | ||
RandomTransaction.networkId, | ||
(accountUpdate, networkId) => { | ||
const minaInstance = Network({ | ||
networkId, | ||
mina: 'http://localhost:8080/graphql', | ||
}); | ||
|
||
fixVerificationKey(accountUpdate); | ||
|
||
// example account update | ||
let accountUpdateJson: Json.AccountUpdate = | ||
AccountUpdate.toJSON(accountUpdate); | ||
|
||
// account update hash | ||
let accountUpdateSnarky = AccountUpdateSnarky.fromJSON(accountUpdateJson); | ||
let inputSnarky = TypesSnarky.AccountUpdate.toInput(accountUpdateSnarky); | ||
let input = AccountUpdate.toInput(accountUpdate); | ||
expect(toJSON(input.fields)).toEqual(toJSON(inputSnarky.fields)); | ||
expect(toJSON(input.packed)).toEqual(toJSON(inputSnarky.packed)); | ||
|
||
let packed = packToFields(input); | ||
let packedSnarky = packToFieldsSnarky(inputSnarky); | ||
expect(toJSON(packed)).toEqual(toJSON(packedSnarky)); | ||
|
||
setActiveInstance(minaInstance); | ||
let hashSnarky = accountUpdateSnarky.hash(); | ||
let hash = accountUpdateHash(accountUpdate, networkId); | ||
expect(hash).toEqual(hashSnarky.toBigInt()); | ||
} | ||
); | ||
|
||
// private key to/from base58 | ||
test(Random.json.privateKey, (feePayerKeyBase58) => { | ||
|
@@ -140,19 +136,25 @@ test(memoGenerator, (memoString) => { | |
}); | ||
|
||
// zkapp transaction - basic properties & commitment | ||
test(RandomTransaction.zkappCommand, (zkappCommand, assert) => { | ||
zkappCommand.accountUpdates.forEach(fixVerificationKey); | ||
|
||
assert(isCallDepthValid(zkappCommand)); | ||
let zkappCommandJson = ZkappCommand.toJSON(zkappCommand); | ||
let ocamlCommitments = Test.hashFromJson.transactionCommitments( | ||
JSON.stringify(zkappCommandJson), | ||
'testnet' | ||
); | ||
let callForest = accountUpdatesToCallForest(zkappCommand.accountUpdates); | ||
let commitment = callForestHash(callForest, 'testnet'); | ||
expect(commitment).toEqual(FieldConst.toBigint(ocamlCommitments.commitment)); | ||
}); | ||
test( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this now tests all network id types |
||
RandomTransaction.zkappCommand, | ||
RandomTransaction.networkId, | ||
(zkappCommand, networkId, assert) => { | ||
zkappCommand.accountUpdates.forEach(fixVerificationKey); | ||
|
||
assert(isCallDepthValid(zkappCommand)); | ||
let zkappCommandJson = ZkappCommand.toJSON(zkappCommand); | ||
let ocamlCommitments = Test.hashFromJson.transactionCommitments( | ||
JSON.stringify(zkappCommandJson), | ||
networkId | ||
); | ||
let callForest = accountUpdatesToCallForest(zkappCommand.accountUpdates); | ||
let commitment = callForestHash(callForest, networkId); | ||
expect(commitment).toEqual( | ||
FieldConst.toBigint(ocamlCommitments.commitment) | ||
); | ||
} | ||
); | ||
|
||
// invalid zkapp transactions | ||
test.negative( | ||
|
@@ -242,7 +244,7 @@ test( | |
let sigOCaml = Test.signature.signFieldElement( | ||
ocamlCommitments.fullCommitment, | ||
Ml.fromPrivateKey(feePayerKeySnarky), | ||
networkId === 'mainnet' ? true : false | ||
networkId | ||
); | ||
|
||
expect(Signature.toBase58(sigFieldElements)).toEqual(sigOCaml); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,6 +39,8 @@ export { | |
signLegacy, | ||
verifyLegacy, | ||
deriveNonce, | ||
signaturePrefix, | ||
zkAppBodyPrefix, | ||
}; | ||
|
||
const networkIdMainnet = 0x01n; | ||
|
@@ -150,10 +152,10 @@ function deriveNonce( | |
): Scalar { | ||
let { x, y } = publicKey; | ||
let d = Field(privateKey); | ||
let id = networkId === 'mainnet' ? networkIdMainnet : networkIdTestnet; | ||
let id = getNetworkIdHashInput(networkId); | ||
let input = HashInput.append(message, { | ||
fields: [x, y, d], | ||
packed: [[id, 8]], | ||
packed: [id], | ||
}); | ||
let packedInput = packToFields(input); | ||
let inputBits = packedInput.map(Field.toBits).flat(); | ||
|
@@ -189,11 +191,7 @@ function hashMessage( | |
): Scalar { | ||
let { x, y } = publicKey; | ||
let input = HashInput.append(message, { fields: [x, y, r] }); | ||
let prefix = | ||
networkId === 'mainnet' | ||
? prefixes.signatureMainnet | ||
: prefixes.signatureTestnet; | ||
return hashWithPrefix(prefix, packToFields(input)); | ||
return hashWithPrefix(signaturePrefix(networkId), packToFields(input)); | ||
} | ||
|
||
/** | ||
|
@@ -280,7 +278,7 @@ function deriveNonceLegacy( | |
): Scalar { | ||
let { x, y } = publicKey; | ||
let scalarBits = Scalar.toBits(privateKey); | ||
let id = networkId === 'mainnet' ? networkIdMainnet : networkIdTestnet; | ||
let id = getNetworkIdHashInput(networkId)[0]; | ||
let idBits = bytesToBits([Number(id)]); | ||
let input = HashInputLegacy.append(message, { | ||
fields: [x, y], | ||
|
@@ -311,9 +309,65 @@ function hashMessageLegacy( | |
): Scalar { | ||
let { x, y } = publicKey; | ||
let input = HashInputLegacy.append(message, { fields: [x, y, r], bits: [] }); | ||
let prefix = | ||
networkId === 'mainnet' | ||
? prefixes.signatureMainnet | ||
: prefixes.signatureTestnet; | ||
let prefix = signaturePrefix(networkId); | ||
return HashLegacy.hashWithPrefix(prefix, packToFieldsLegacy(input)); | ||
} | ||
|
||
const toBytePadded = (b: number) => ('000000000' + b.toString(2)).substr(-8); | ||
|
||
function networkIdOfString(n: string): [bigint, number] { | ||
let l = n.length; | ||
let acc = ''; | ||
for (let i = l - 1; i >= 0; i--) { | ||
let b = n.charCodeAt(i); | ||
let padded = toBytePadded(b); | ||
acc = acc.concat(padded); | ||
} | ||
return [BigInt('0b' + acc), acc.length]; | ||
} | ||
|
||
function getNetworkIdHashInput(networkId: string): [bigint, number] { | ||
switch (networkId) { | ||
case 'mainnet': | ||
return [networkIdMainnet, 8]; | ||
case 'testnet': | ||
return [networkIdTestnet, 8]; | ||
default: | ||
return networkIdOfString(networkId); | ||
} | ||
} | ||
|
||
const createCustomPrefix = (prefix: string) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OCaml uses a |
||
const maxLength = 20; | ||
const paddingChar = '*'; | ||
let length = prefix.length; | ||
|
||
if (length <= maxLength) { | ||
let diff = maxLength - length; | ||
return prefix + paddingChar.repeat(diff); | ||
} else { | ||
return prefix.substring(0, maxLength); | ||
} | ||
}; | ||
|
||
const signaturePrefix = (network: string) => { | ||
switch (network) { | ||
case 'mainnet': | ||
return prefixes.signatureMainnet; | ||
case 'testnet': | ||
return prefixes.signatureTestnet; | ||
default: | ||
return createCustomPrefix(network + 'Signature'); | ||
} | ||
}; | ||
|
||
const zkAppBodyPrefix = (network: string) => { | ||
switch (network) { | ||
case 'mainnet': | ||
return prefixes.zkappBodyMainnet; | ||
case 'testnet': | ||
return prefixes.zkappBodyTestnet; | ||
default: | ||
return createCustomPrefix(network + 'ZkappBody'); | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is no longer needed since the type now is
'testnet' | 'mainnet'
which already is lower case