Last active
September 26, 2024 16:13
-
-
Save a2468834/17dc14ed6e64a30b3df5e44c5da04314 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Address, beginCell } from "@ton/ton"; | |
import { mnemonicToPrivateKey, sign, sha256, signVerify } from "@ton/crypto"; | |
import assert from "assert"; | |
// Type | |
type LessThan<N extends number, A extends any[] = []> = N extends A['length'] ? A[number] : LessThan<N, [...A, A['length']]>; | |
type RangeOf<F extends number, T extends number> = Exclude<T | LessThan<T>, LessThan<F>> | |
type AtomicType = `int${RangeOf<0, 257>}` | `uint${RangeOf<0, 256>}` | "slice" | "cell"; | |
type TypeDefinition = Record<string, Array<{ name: string, type: string | AtomicType }>>; | |
// Auxiliary function | |
const linearization = (primaryType: string, typeDefinition: TypeDefinition, found: string[] = []) => { | |
if (found.includes(primaryType)) { | |
return found; | |
} else if (typeDefinition[primaryType] === undefined) { | |
return found; | |
} | |
found.push(primaryType); | |
for (const property of typeDefinition[primaryType]) { | |
for (const child of linearization(property.type, typeDefinition, found)) { | |
if (!found.includes(child)) { | |
found.push(child); | |
} | |
} | |
} | |
return found; | |
}; | |
const encodeType = (primaryType: string, typeDefinition: TypeDefinition) => { | |
const c3LinearResult = linearization(primaryType, typeDefinition); | |
return c3LinearResult.map( | |
eachType => | |
`${eachType}(${typeDefinition[eachType] | |
.map(({ name, type }) => `${type} ${name}`) | |
.join(',')})` | |
).join(''); | |
}; | |
const stringToUint32 = async (str: string) => { | |
const hashString = (await sha256(str)).toString('hex', 0, 4); | |
return BigInt(`0x${hashString}`); | |
}; | |
const bufferToBigInt = (buf: Buffer) => { | |
return BigInt(`0x${buf.toString('hex')}`); | |
}; | |
const getWorkchainId = (addressLike: string) => { | |
return Address.parse(addressLike).workChain; | |
}; | |
const getAccountId = (addressLike: string) => { | |
return '0x' + Address.parse(addressLike).hash.toString('hex'); | |
}; | |
/////////////////////////////// Example test data ////////////////////////////// | |
const signer = mnemonicToPrivateKey([...Array(24)].map(each => "food")); | |
const verifier = "0QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACkT" | |
const testVector = { | |
typeDefinition: { | |
TEP181Domain: [ | |
{ name: 'name', type: 'uint32' }, | |
{ name: 'version', type: 'uint32' }, | |
{ name: 'workchainId', type: 'int8' }, | |
{ name: 'verifierContract', type: 'uint256' } | |
], | |
Permit: [ | |
{ name: 'owner', type: 'Address' }, | |
{ name: 'spender', type: 'Address' }, | |
{ name: 'value', type: 'uint32' }, | |
{ name: 'nonce', type: 'uint32' }, | |
{ name: 'deadline', type: 'uint32' }, | |
], | |
Address: [ | |
{ name: 'workchainId', type: 'int8' }, | |
{ name: 'accountId', type: 'uint256' }, | |
] | |
}, | |
primaryType: 'Permit', | |
typeData: { | |
tep181Domain: { | |
name: stringToUint32('Permit Ticket'), | |
version: stringToUint32('1'), | |
workchainId: getWorkchainId(verifier), | |
verifierContract: getAccountId(verifier) | |
}, | |
message: { | |
owner: { | |
workchainId: BigInt(0), | |
accountId: BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff') | |
}, | |
spender: { | |
workchainId: BigInt(0), | |
accountId: BigInt('0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f') | |
}, | |
value: BigInt(123), | |
nonce: BigInt(456), | |
deadline: BigInt(0xffffffff) | |
} | |
} | |
}; | |
const CONSTANT = { | |
PERMIT_TYPEHASH: sha256(encodeType('Permit', testVector.typeDefinition)), | |
ADDRESS_TYPEHASH: sha256(encodeType('Address', testVector.typeDefinition)), | |
TEP181DOMAIN_TYPEHASH: sha256(encodeType('TEP181Domain', testVector.typeDefinition)), | |
PREFIX: Buffer.concat([Buffer.from("ff", "hex"), Buffer.from("ff", "hex")]), | |
EXPECTED_SIG: "feT5H+PPaB7IlIiqGF0UDcE5kFgKix7sygGLwHMeq1fPzO3m01SI/me/45kZ3W3LRIiNpJobMmi/pKj6TuCEBQ==" | |
}; | |
//////////////////////////////////////////////////////////////////////////////// | |
const main = async () => { | |
const owner_hash = beginCell() | |
.storeUint(bufferToBigInt(await CONSTANT.ADDRESS_TYPEHASH), 256) | |
.storeInt(testVector.typeData.message.owner.workchainId, 8) | |
.storeUint(BigInt(testVector.typeData.message.owner.accountId), 256) | |
.endCell().hash(); | |
const spender_hash = beginCell() | |
.storeUint(bufferToBigInt(await CONSTANT.ADDRESS_TYPEHASH), 256) | |
.storeInt(testVector.typeData.message.spender.workchainId, 8) | |
.storeUint(BigInt(testVector.typeData.message.spender.accountId), 256) | |
.endCell().hash(); | |
const struct_hash = beginCell() | |
.storeUint(bufferToBigInt(await CONSTANT.PERMIT_TYPEHASH), 256) | |
.storeUint(bufferToBigInt(owner_hash), 256) | |
.storeUint(bufferToBigInt(spender_hash), 256) | |
.storeUint(testVector.typeData.message.value, 32) | |
.storeUint(testVector.typeData.message.nonce, 32) | |
.storeUint(testVector.typeData.message.deadline, 32) | |
.endCell().hash(); | |
const domain_separator_hash = beginCell() | |
.storeUint(bufferToBigInt(await CONSTANT.TEP181DOMAIN_TYPEHASH), 256) | |
.storeUint(await testVector.typeData.tep181Domain.name, 32) | |
.storeUint(await testVector.typeData.tep181Domain.version, 32) | |
.storeInt(testVector.typeData.tep181Domain.workchainId, 8) | |
.storeUint(BigInt(testVector.typeData.tep181Domain.verifierContract), 256) | |
.endCell().hash(); | |
const hashedData = beginCell() | |
.storeUint(bufferToBigInt(CONSTANT.PREFIX), 16) | |
.storeUint(bufferToBigInt(domain_separator_hash), 256) | |
.storeUint(bufferToBigInt(struct_hash), 256) | |
.endCell().hash(); | |
const signature = sign( | |
hashedData, | |
(await signer).secretKey | |
); | |
assert(signature.toString("base64") === CONSTANT.EXPECTED_SIG); | |
assert(signVerify(hashedData, signature, (await signer).publicKey) === true); | |
}; | |
main().then(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment