k[31] |= 64
}
-const crypto_derive_a: StaticArray<u8> = new StaticArray<u8>(PRIVATEKEY_BYTES)
-const crypto_derive_az: StaticArray<u8> = new StaticArray<u8>(SECRETKEY_BYTES)
+const crypto_derive_h: StaticArray<u8> = new StaticArray<u8>(SECRETKEY_BYTES)
+const crypto_derive_s: StaticArray<u8> = new StaticArray<u8>(PRIVATEKEY_BYTES)
/**
* Hash seed to 32-byte scalar `a`, clamp it, then point-multiply it by the
- * Ed25519 base point.
- * @param pk 32-byte output buffer for the compressed public key
- * @param seed 32-byte input buffer for the securely-random secret seed value
+ * Ed25519 base point. The Nano specification uses BLAKE2b as the hash function
+ * instead of SHA-512 specified by RFC 8032.
+ *
+ * https://www.rfc-editor.org/info/rfc8032/#section-5.1.5
+ *
+ * @param A 32-byte output buffer for the compressed public key
+ * @param key 32-byte private key "seed" of cryptographically secure random data
*/
-function crypto_derive (pk: StaticArray<u8>, seed: StaticArray<u8>): void {
- const a = crypto_derive_a
- const az = crypto_derive_az
- blake2b.init().update(seed, 32).digest(az)
- memory.copy(changetype<usize>(a), changetype<usize>(az), 32)
- az.fill(0)
- clamp(a)
- ge_scalarmult_base_tobytes(pk, a)
- a.fill(0)
+function crypto_derive (A: StaticArray<u8>, key: StaticArray<u8>): void {
+ const h = crypto_derive_h
+ const s = crypto_derive_s
+ // Hash private key to 64-byte buffer `h`
+ blake2b.init().update(key, 32).digest(h)
+ // Keep only the lower 32 bytes as secret scalar `s` for deriving public key
+ memory.copy(changetype<usize>(s), changetype<usize>(h), 32)
+ h.fill(0)
+ // Clear lowest three bits and highest bit, then set second highest bit
+ clamp(s)
+ // Perform fixed-base scalar multiplication `[s]B`, output to public key `A`
+ ge_scalarmult_base_tobytes(A, s)
+ s.fill(0)
}
-const crypto_sign_a = new StaticArray<u8>(32)
-const crypto_sign_z = new StaticArray<u8>(32)
-const crypto_sign_az = new StaticArray<u8>(64)
-const crypto_sign_nonce = new StaticArray<u8>(64)
-const crypto_sign_hram = new StaticArray<u8>(64)
-const crypto_sign_t = new StaticArray<u8>(PRIVATEKEY_BYTES)
-function crypto_sign (s: StaticArray<u8>, m: usize, mlen: i32, sk: StaticArray<u8>): void {
- const a = crypto_sign_a
- const z = crypto_sign_z
- const az = crypto_sign_az
- const nonce = crypto_sign_nonce
- const hram = crypto_sign_hram
- const t = changetype<usize>(crypto_sign_t)
-
- // Hash secret key to private scalar `a` and prefix for nonce derivation `z`
- memory.copy(t, changetype<usize>(sk), PRIVATEKEY_BYTES)
- blake2b.init().update(t, PRIVATEKEY_BYTES).digest(az)
- memory.copy(changetype<usize>(a), changetype<usize>(az), 32)
- memory.copy(changetype<usize>(a), changetype<usize>(az) + 32, 32)
- clamp(a)
-
- // Derive nonce from prefix `z` and message `m`
- blake2b.init().update(z, 32).update(changetype<StaticArray<u8>>(m), mlen).digest(nonce)
- sc_reduce(nonce)
-
- // Compute R = rB, output to bytes s
- ge_scalarmult_base_tobytes(s, nonce)
-
- // Concatenate public key `A` and message `M` from parameter arguments:
- // `A = sk[32,63], M = m`
- // Compute challenge hash using `s = (R || A || M)`
- blake2b.init().update(s, 32).update(sk + 32, 32).update(m, mlen).digest(hram)
- sc_reduce(hram)
-
- // Compute `S = (r + h*a) mod L` and construct final signature `s = (R || S)`
- sc_muladd(changetype<StaticArray<u8>>(changetype<usize>(s) + 32), az, hram, nonce)
-
- // Clean up sensitive data
- a.fill(0)
- z.fill(0)
- az.fill(0)
- hram.fill(0)
- nonce.fill(0)
- memory.fill(t, 0, PRIVATEKEY_BYTES)
+const crypto_sign_A = new StaticArray<u8>(32)
+const crypto_sign_h = new StaticArray<u8>(64)
+const crypto_sign_s = new StaticArray<u8>(32)
+const crypto_sign_prefix = new StaticArray<u8>(32)
+const crypto_sign_r = new StaticArray<u8>(64)
+const crypto_sign_R = new StaticArray<u8>(64)
+const crypto_sign_k = new StaticArray<u8>(64)
+const crypto_sign_S = new StaticArray<u8>(64)
+/**
+ * Sign a message with a secret key. The Nano specification uses BLAKE2b as the
+ * hash function instead of SHA-512 specified by RFC 8032.
+ *
+ * In this implementation, the secret key is a 64-byte concatenation of the
+ * private key and its public key in order to (1) ensure the user has a full
+ * correct keypair for data integrity, and (2) improve performance by avoiding
+ * expensive point multiplication and instead taking a public key that can be
+ * computed once and cached. This behavior deviates from RFC 8032 which
+ * indicates the public key should be recomputed.
+ *
+ * https://www.rfc-editor.org/info/rfc8032/#section-5.1.6
+ *
+ * @param RS 64-byte output buffer for detached signature
+ * @param M variable-length message to be signed
+ * @param mlen bytelength of `m`
+ * @param sk 64-byte input buffer of private+public key concatenation
+ */
+function crypto_sign (RS: StaticArray<u8>, M: StaticArray<u8>, mlen: i32, key: StaticArray<u8>, A: StaticArray<u8>): void {
+ const h = crypto_sign_h
+ const s = crypto_sign_s
+ const prefix = crypto_sign_prefix
+ const r = crypto_sign_r
+ const R = crypto_sign_R
+ const k = crypto_sign_k
+ const S = crypto_sign_S
+
+ // Hash private key to `h`
+ blake2b.init().update(key, PRIVATEKEY_BYTES).digest(h)
+ key.fill(0)
+
+ // Split `h` into clamped secret scalar `s` and nonce prefix
+ memory.copy(changetype<usize>(s), changetype<usize>(h), 32)
+ clamp(s)
+ memory.copy(changetype<usize>(prefix), changetype<usize>(h) + 32, 32)
+ h.fill(0)
+
+ // Hash prefix and message `M` to nonce `r`
+ blake2b.init().update(prefix, 32).update(M, mlen).digest(r)
+ prefix.fill(0)
+
+ // Reduce `r` modulo `L`, the group order of the base point `B`
+ sc_reduce(r)
+
+ // Perform fixed-base scalar multiplication `[r]B`, output to point `R`
+ ge_scalarmult_base_tobytes(R, r)
+
+ // Compute challenge hash `blake2b(R || A || M)`, output to `k`
+ blake2b.init().update(R, 32).update(A, 32).update(M, mlen).digest(k)
+ A.fill(0)
+
+ // Reduce `k` modulo `L` for efficiency
+ sc_reduce(k)
+
+ // Compute `S = (k * s + r) mod L`
+ sc_muladd(S, k, s, r)
+ k.fill(0)
+ s.fill(0)
+ r.fill(0)
+
+ // Construct final signature `(R || S)`
+ memory.copy(changetype<usize>(RS), changetype<usize>(R), 32)
+ R.fill(0)
+ memory.copy(changetype<usize>(RS) + 32, changetype<usize>(S), 32)
+ S.fill(0)
}
const crypto_verify_h = new StaticArray<u8>(64)
* Verify signature `s` was made by signing message `m` using public key `pk`.
* @returns -1 if signature fails to verify, else return 0 if signature is good
*/
-function crypto_verify (s: StaticArray<u8>, m: usize, mlen: i32, pk: StaticArray<u8>): i32 {
+function crypto_verify (s: StaticArray<u8>, m: usize, mlen: i32, pub: StaticArray<u8>): i32 {
+ const M = changetype<StaticArray<u8>>(m)
const h = crypto_verify_h
const check = crypto_verify_check
const expected_r = crypto_verify_expected_r
const S = crypto_verify_S
// fail if public key `k` is non-canonical (`p = 2²⁵⁵-19 <= k`)
- if (!ge_is_canonical(pk)) return -1
+ if (!ge_is_canonical(pub)) return -1
// fail if private scalar `S` is non-canonical (`L <= S`)
memory.copy(changetype<usize>(S), changetype<usize>(s) + 32, 32)
if (!sc_is_canonical(S)) return -1
- if (ge_frombytes_negate_vartime(A, pk) != 0) return -1
+ if (ge_frombytes_negate_vartime(A, pub) != 0) return -1
if (ge_has_small_order(A) != 0) return -1
if (ge_frombytes(expected_r, s) != 0) return -1
// data to hash is nonce point R, public key A, and message M
// from parameter arguments: R = s[0,32], A = pk, M = m
// R, S, A, and M are all 32-byte values in this implementation
- blake2b.init().update(s, 32).update(pk, 32).update(m, mlen).digest(h)
+ blake2b.init().update(s, 32).update(pub, 32).update(M, mlen).digest(h)
sc_reduce(h)
ge_double_scalarmult_vartime_to_p3(sb_ah, h, A, S)
* static output buffer.
*/
export function derive (): void {
- const pk = changetype<StaticArray<u8>>(OUTPUT_BUFFER)
- const seed = changetype<StaticArray<u8>>(INPUT_BUFFER)
+ const pub = changetype<StaticArray<u8>>(OUTPUT_BUFFER)
+ const prv = changetype<StaticArray<u8>>(INPUT_BUFFER)
memory.fill(OUTPUT_BUFFER, 0, OUTPUT_BUFFER_BYTES)
- crypto_derive(pk, seed)
+ crypto_derive(pub, prv)
memory.fill(INPUT_BUFFER, 0, INPUT_BUFFER_BYTES)
}
const sign_s = new StaticArray<u8>(SIGNATURE_BYTES)
+const sign_prv = new StaticArray<u8>(PRIVATEKEY_BYTES)
+const sign_pub = new StaticArray<u8>(PUBLICKEY_BYTES)
/**
* Retrieve up to 32 KiB of data from the message buffer and a 64-byte secret
* key from the input buffer, and then sign the message using a secret key. The
export function sign (mlen: i32): void {
if (mlen < 0 || mlen > 32768) throw new Error()
const s = sign_s
- const m = MESSAGE_BUFFER
- const sk = changetype<StaticArray<u8>>(INPUT_BUFFER)
+ const prv = sign_prv
+ const pub = sign_pub
+
+ memory.copy(changetype<usize>(prv), INPUT_BUFFER, PRIVATEKEY_BYTES)
+ memory.copy(changetype<usize>(pub), INPUT_BUFFER + PRIVATEKEY_BYTES, PUBLICKEY_BYTES)
+ memory.fill(INPUT_BUFFER, 0, INPUT_BUFFER_BYTES)
+
+ const m = changetype<StaticArray<u8>>(MESSAGE_BUFFER)
memory.fill(OUTPUT_BUFFER, 0, OUTPUT_BUFFER_BYTES)
- crypto_sign(s, m, mlen, sk)
+ crypto_sign(s, m, mlen, prv, pub)
memory.copy(OUTPUT_BUFFER, changetype<usize>(s), SIGNATURE_BYTES)
- memory.fill(INPUT_BUFFER, 0, INPUT_BUFFER_BYTES)
}
const verify_s = new StaticArray<u8>(SIGNATURE_BYTES)