mirror of
https://github.com/arx-research/libhalo.git
synced 2026-01-08 20:58:02 -05:00
Docs: Add scripts to interact with BJJ RNDSIG (#304)
This commit is contained in:
committed by
GitHub
parent
f1cdf83a8a
commit
b1aa42cdee
139
docs/bjj/verify_bjj_rndsig.js
Normal file
139
docs/bjj/verify_bjj_rndsig.js
Normal file
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* Example script to verify a dynamic URL based off the BJJ curve
|
||||
* Copyright by Arx Research, Inc., a Delaware corporation
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
const hash = require("hash.js");
|
||||
const {PresetCurve} = require("elliptic/lib/elliptic/curves");
|
||||
const {Buffer} = require("buffer");
|
||||
const {sha256} = require("js-sha256");
|
||||
const EC = require('elliptic').ec;
|
||||
|
||||
const BJJ_ORDER = 0x060c89ce5c263405370a08b6d0302b0bab3eedb83920ee0a677297dc392126f1n;
|
||||
|
||||
const ec = new EC({
|
||||
"curve": new PresetCurve({
|
||||
type: 'short',
|
||||
prime: null,
|
||||
p: '30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001',
|
||||
a: '10216f7ba065e00de81ac1e7808072c9b8114d6d7de87adb16a0a72f1a91f6a0',
|
||||
b: '23d885f647fed5743cad3d1ee4aba9c043b4ac0fc2766658a410efdeb21f706e',
|
||||
n: '060c89ce5c263405370a08b6d0302b0bab3eedb83920ee0a677297dc392126f1',
|
||||
hash: hash.sha256,
|
||||
gRed: false,
|
||||
g: [
|
||||
'1fde0a3cac7cb46b36c79f4c0a7a732e38c2c7ee9ac41f44392a07b748a0869f',
|
||||
'203a710160811d5c07ebaeb8fe1d9ce201c66b970d66f18d0d2b264c195309aa',
|
||||
],
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
function parseSig(res, curveOrder) {
|
||||
if (res[0] !== 0x30 || res[2] !== 0x02) {
|
||||
throw new Error("Unable to parse signature, unexpected header (1).");
|
||||
}
|
||||
|
||||
let rLen = res[3];
|
||||
|
||||
if (res[rLen + 4] !== 0x02) {
|
||||
throw new Error("Unable to parse signature, unexpected header (2).");
|
||||
}
|
||||
|
||||
let sLen = res[rLen + 5];
|
||||
|
||||
if (res.length < rLen + 4 + 2 + sLen) {
|
||||
throw new Error("Unable to parse signature, unexpected length.");
|
||||
}
|
||||
|
||||
let r = res.slice(4, rLen + 4);
|
||||
let s = res.slice(rLen + 4 + 2, rLen + 4 + 2 + sLen);
|
||||
let rn = BigInt('0x' + r.toString('hex'));
|
||||
let sn = BigInt('0x' + s.toString('hex'));
|
||||
|
||||
rn %= curveOrder;
|
||||
sn %= curveOrder;
|
||||
|
||||
if (sn > curveOrder / 2n) {
|
||||
// malleable signature, not compliant with Ethereum's EIP-2
|
||||
// we need to flip s value in the signature
|
||||
sn = -sn + curveOrder;
|
||||
}
|
||||
|
||||
return {
|
||||
r: rn.toString(16).padStart(64, '0'),
|
||||
s: sn.toString(16).padStart(64, '0')
|
||||
};
|
||||
}
|
||||
|
||||
function sigToDer(sig) {
|
||||
let r = BigInt('0x' + sig.r);
|
||||
let s = BigInt('0x' + sig.s);
|
||||
|
||||
let padR = r.toString(16).length % 2 ? '0' : '';
|
||||
let padS = s.toString(16).length % 2 ? '0' : '';
|
||||
|
||||
let encR = Buffer.from(padR + r.toString(16), 'hex');
|
||||
let encS = Buffer.from(padS + s.toString(16), 'hex');
|
||||
|
||||
if (encR[0] & 0x80) {
|
||||
// add zero to avoid interpreting this as negative integer
|
||||
encR = Buffer.concat([Buffer.from([0x00]), encR]);
|
||||
}
|
||||
|
||||
if (encS[0] & 0x80) {
|
||||
// add zero to avoid interpreting this as negative integer
|
||||
encS = Buffer.concat([Buffer.from([0x00]), encS]);
|
||||
}
|
||||
|
||||
encR = Buffer.concat([Buffer.from([0x02, encR.length]), encR]);
|
||||
encS = Buffer.concat([Buffer.from([0x02, encS.length]), encS]);
|
||||
|
||||
return Buffer.concat([
|
||||
Buffer.from([0x30, encR.length + encS.length]),
|
||||
encR,
|
||||
encS
|
||||
]);
|
||||
}
|
||||
|
||||
// input: Buffer; DER-encoded signature from HaLo tag
|
||||
// output: Buffer; fixed DER-encoded signature
|
||||
function fixBJJSig(sig) {
|
||||
return sigToDer(parseSig(sig, BJJ_ORDER));
|
||||
}
|
||||
|
||||
function verifyBJJDynamicURL(pkn, rnd, rndsig) {
|
||||
let pk62 = Buffer.from(pkn, "hex").slice(2).toString("hex");
|
||||
|
||||
let pubKey = ec.keyFromPublic(pk62, "hex");
|
||||
let rndBuf = Buffer.from(rnd, "hex");
|
||||
let rndsigBuf = Buffer.from(rndsig, "hex");
|
||||
let counter = rndBuf.readUInt32BE(0);
|
||||
|
||||
let digest = sha256(Buffer.concat([
|
||||
Buffer.from([0x19]),
|
||||
Buffer.from("Attest counter pk62:\n"),
|
||||
rndBuf
|
||||
]));
|
||||
|
||||
let verifyRes = pubKey.verify(digest, fixBJJSig(rndsigBuf).toString("hex"));
|
||||
|
||||
if (!verifyRes) {
|
||||
throw new Error("Failed to verify signature.");
|
||||
}
|
||||
|
||||
return {
|
||||
"counter": counter,
|
||||
"pk62": pk62
|
||||
}
|
||||
}
|
||||
|
||||
// --- EXAMPLE USAGE ---
|
||||
let res = verifyBJJDynamicURL(
|
||||
"62000416FFE77D0F9044B209663A96F1FDF8C41A4C75212F520B1013C64F3AD0193FD7118EB2A50D78520DE66B1D3F89048B7189FCAC8B1B3B5C21834A1E87916D8303",
|
||||
"000005490DEE4E738210E393E3A9E9D292976CAA1915663F82A75415CA765A97",
|
||||
"304402200B49B3FE4B80947D99272192812BE9E4F60232C1BCBBFA26DEDCFA80DCD4B24F0220026AF01F3D324F5006C70B8974AAE12E7CFB73CFE8EFEDE53DD019CE3D93ED8E0000"
|
||||
);
|
||||
|
||||
console.log('PASS', res);
|
||||
93
docs/bjj/verify_bjj_rndsig.py
Normal file
93
docs/bjj/verify_bjj_rndsig.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""
|
||||
* An example script demonstrating how to verify BJJ dynamic URL signature on slot 0x62
|
||||
* HALO ETH - Core layer
|
||||
* Copyright (C) 2022-2024 Arx Research Inc.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import binascii
|
||||
import struct
|
||||
import sys
|
||||
from hashlib import sha256
|
||||
from typing import Tuple
|
||||
|
||||
# ecdsa==0.18.0
|
||||
from ecdsa import VerifyingKey, ellipticcurve, BadSignatureError
|
||||
from ecdsa.util import sigdecode_der, sigencode_der
|
||||
from ecdsa.curves import Curve
|
||||
|
||||
|
||||
def verify_rndsig(pkn: bytes, rnd: bytes, rndsig: bytes) -> Tuple[bytes, int]:
|
||||
# BJJ Curve Parameters
|
||||
_a = 0x10216f7ba065e00de81ac1e7808072c9b8114d6d7de87adb16a0a72f1a91f6a0
|
||||
_b = 0x23d885f647fed5743cad3d1ee4aba9c043b4ac0fc2766658a410efdeb21f706e
|
||||
_p = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001
|
||||
_Gx = 0x1fde0a3cac7cb46b36c79f4c0a7a732e38c2c7ee9ac41f44392a07b748a0869f
|
||||
_Gy = 0x203a710160811d5c07ebaeb8fe1d9ce201c66b970d66f18d0d2b264c195309aa
|
||||
_r = 0x060c89ce5c263405370a08b6d0302b0bab3eedb83920ee0a677297dc392126f1
|
||||
|
||||
curve_babyjubjub = ellipticcurve.CurveFp(_p, _a, _b, 1)
|
||||
generator_babyjubjub = ellipticcurve.PointJacobi(
|
||||
curve_babyjubjub, _Gx, _Gy, 1, _r, generator=True
|
||||
)
|
||||
|
||||
BabyJubJub = Curve(
|
||||
"BabyJubJub",
|
||||
curve_babyjubjub,
|
||||
generator_babyjubjub,
|
||||
(2, 999, 1, 2, 3, 4),
|
||||
None,
|
||||
)
|
||||
|
||||
# remove unnecessary null bytes at the end of the signature
|
||||
rndsig_len = 2 + rndsig[1]
|
||||
rndsig_stripped = rndsig[0:rndsig_len]
|
||||
|
||||
# correct the signature in order to dodge a bug in JCOP 4
|
||||
(r, s) = sigdecode_der(rndsig_stripped, order=_r)
|
||||
r = r % _r
|
||||
rndsig_stripped = sigencode_der(r, s, order=_r)
|
||||
|
||||
# extract public key from the pkN=... field
|
||||
public_key = pkn[2:]
|
||||
|
||||
# perform the actual verification
|
||||
rnd_prefixed = b'\x19Attest counter pk62:\n' + rnd
|
||||
|
||||
vk = VerifyingKey.from_string(public_key, curve=BabyJubJub)
|
||||
vk.verify(rndsig_stripped, rnd_prefixed, sigdecode=sigdecode_der, allow_truncate=True, hashfunc=sha256)
|
||||
|
||||
# parse the counter value
|
||||
counter_value = struct.unpack(">I", rnd[0:4])[0]
|
||||
|
||||
return public_key, counter_value
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
"""
|
||||
Example usage:
|
||||
|
||||
$ pip3 install ecdsa==0.18.0
|
||||
|
||||
$ python3 verify_bjj_fixsig.py --pkn 62000416FFE77D0F9044B209663A96F1FDF8C41A4C75212F520B1013C64F3AD0193FD7118EB2A50D78520DE66B1D3F89048B7189FCAC8B1B3B5C21834A1E87916D8303 --rnd 000005443E81D2B4E37955C50CA0E4C580B8EEEE2319B090D5D516D74C357FB7 --rndsig 304402201F595063ABCC72A8EAA60621A30B9AD2DF77F5D6BEF704435B3DD508CE27A0CA02200426D2E5E9590E46130A034AE47A8A6C12BB3FC770C68A3C1AC49BC302DF0CF70000
|
||||
$ python3 verify_bjj_fixsig.py --pkn 62000416FFE77D0F9044B209663A96F1FDF8C41A4C75212F520B1013C64F3AD0193FD7118EB2A50D78520DE66B1D3F89048B7189FCAC8B1B3B5C21834A1E87916D8303 --rnd 00000545E2940108C99C7B92B4D6D411835863C943007F14564A38326FEC4415 --rndsig 3044022008A6A5F0AEC049E8A99A52B675E12843D53495E676B50E08942C8D679C3F79BE0220023D5AEE66BDBF2F577CA1C46914F104B33D6E8D512D660BF0B82A2A99C278420000
|
||||
$ python3 verify_bjj_fixsig.py --pkn 62000416FFE77D0F9044B209663A96F1FDF8C41A4C75212F520B1013C64F3AD0193FD7118EB2A50D78520DE66B1D3F89048B7189FCAC8B1B3B5C21834A1E87916D8303 --rnd 000005490DEE4E738210E393E3A9E9D292976CAA1915663F82A75415CA765A97 --rndsig 304402200B49B3FE4B80947D99272192812BE9E4F60232C1BCBBFA26DEDCFA80DCD4B24F0220026AF01F3D324F5006C70B8974AAE12E7CFB73CFE8EFEDE53DD019CE3D93ED8E0000
|
||||
"""
|
||||
|
||||
parser = argparse.ArgumentParser(description='HaLo BJJ Dynamic URL Example')
|
||||
parser.add_argument('--pkn', help='Value of the \'pkN\' query string parameter.', required=True)
|
||||
parser.add_argument('--rnd', help='Value of the \'rnd\' query string parameter.', required=True)
|
||||
parser.add_argument('--rndsig', help='Value of the \'rndsig\' query string parameter.', required=True)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
public_key, counter_value = verify_rndsig(
|
||||
pkn=binascii.unhexlify(args.pkn),
|
||||
rnd=binascii.unhexlify(args.rnd),
|
||||
rndsig=binascii.unhexlify(args.rndsig))
|
||||
except BadSignatureError:
|
||||
print('FAIL')
|
||||
sys.exit(1)
|
||||
|
||||
print(f'PASS counter={counter_value}; public_key={public_key.hex()}')
|
||||
Reference in New Issue
Block a user