# Python [Learning] - pypuf
> [name=D]
## Arbiter PUFs and Compositions
- Arbier PUF
- XOR Arbiter PUF
- Feed-Forward (XOR) Arbiter PUF
- Lightweight Secure PUF
- Permutation PUF
- Interpose PUF
### Arbiter PUF
- `n` : challenge length
- `seed` : 用來讓結果可重現的,假如是同一個 seed 的話,就會產生一模一樣的 PUF
- `eval()` : to evaluate challenges, provide a list of challenges
- `noisiness` : the noisee level, `0.0` ~ `1.0`
- `N` : N 組挑戰
```python=
from pypuf.simulation import ArbiterPUF
from pypuf.io import random_inputs
puf = ArbiterPUF(n=64, seed=1, noisiness=.2)
puf.eval(random_inputs(n=64, N=3, seed=2))
```
### XOR Arbiter PUF
- `k` : the amount of Arbiter PUFs
```python=
from pypuf.simulation import XORArbiterPUF
from pypuf.io import random_inputs
puf = XORArbiterPUF(n=64, k=8, seed=1, noisiness=.05)
puf.eval(random_inputs(n=64, N=3, seed=2))
# array([-1, 1, -1], dtype=int8)
```
### Feed-Forward (XOR) Arbiter PUF
- `ff` : list of forward connections
```python=
from pypuf.simulation import XORFeedForwardArbiterPUF
from pypuf.io import random_inputs
puf = XORFeedForwardArbiterPUF(n=128, k=4, ff=[(32, 68)], seed=1)
puf.eval(random_inputs(n=128, N=6, seed=2))
```
### Lightweight Secure PUF
每條 XOR Arbiter PUF 鏈不再使用相同的 challenge,而是對一個主 challenge 做轉換,產生出不同的子 challenge 後,再放進 Arbiter PUFs 且互相 XOR 輸出的 response
```python=
from pypuf.simulation import LightweightSecurePUF
from pypuf.io import random_inputs
puf = LightweightSecurePUF(n=64, k=8, seed=1, noisiness=.05)
puf.eval(random_inputs(n=64, N=3, seed=2))
```
### Permutation PUF
和 Lightweight Secure PUF 的實作類似,差別是不是用自己所定義的 transformation 函數作為子挑戰的產生,而是變成用排列(Permutation)的方式產生,這樣可以避免攻擊者逆推出 transformation,其中
1. 任兩組排列中,不能有相同的位元從相同位置移動到相同位置(也就是不能出現「兩組排列都把第 i 位移到第 j 位」這種情況)
2. 每一組排列都不能有固定點(fix point),也就是沒有任何一個位元保留在原本的位置
```python=
from pypuf.simulation import PermutationPUF
from pypuf.io import random_inputs
puf = PermutationPUF(n=64, k=8, seed=1, noisiness=.05)
puf.eval(random_inputs(n=64, N=3, seed=2))
```
### Interpose PUF
會分為上下兩層(upper, lower layer),會先將 challenge 輸入到 upper layer 進行運算,然後將該回應插入(Interpose) 到 challenge 的中間位置,形成新的 challenge,再將新 challenge 輸入到 lower layer,最後經過運算後得到最終的 response
```python=
from pypuf.simulation import InterposePUF
from pypuf.io import random_inputs
puf = InterposePUF(n=64, k_up=8, k_down=8, seed=1, noisiness=.05)
puf.eval(random_inputs(n=64, N=3, seed=2))
```
## Additive Delay Model
- LTFArray
- NoisyLTFArray (Gaussian noise)
### LTFArray
- weight_array : ndarray of floats with shape `(k,n)`,此陣列中每個 LTF 的權重
- transform : 輸入轉換方法
- combiner : 用來合併每個 LTF 輸出的組合函數,預設為 XOR
- bias : ndarray of floats with shape `(k, )`,每個 LTF 的偏差值,預設為 0
Parameters:
- 要進行評估的 challenges
- `ndarray` of shape(N, `challenge_length`)
- N 維陣列(要評估 challenge 的數目)
- (1, `challenge_length`) 只評估一個 challenge
- (10, `challenge_length`) 評估十個 challenge
- (N, 0) 為空陣列,來指定希望得到多少筆回應
Returns :
- `ndarry` of shape(N, `response_length`)
```python=
from pypuf.simulation import LTFArray
from numpy import array, ones
my_puf = LTFArray(ones(shape=(1, 4)), transform='id')
my_puf.eval(array([[1, 1, -1, 1]]))
# array([1])
my_puf.eval(array([[1, -1, -1, -1]]))
# array([-1])
```
## Base Class for Simulations
- `r` : 同一組 challenge 模擬 r 次,所以會有 r 個 responses
```python=
from pypuf.simulation import XORArbiterPUF
from pypuf.io import random_inputs
puf = XORArbiterPUF(n=64, k=4, noisiness=.02, seed=1)
responses = puf.r_eval(3, random_inputs(N=6, n=64, seed=4))
responses[0, :, :] # 第一組 challenge 模擬 3 次後,分別得到的 3 個 responses
# array([[-1., 1., 1.]])
responses[1, :, :]
.
.
.
responses[5, :, :] # 總共有 6 組 challenge
```
## Data
### Generating Uniform Random Challenges
`pypuf` 使用 `{-1,1}` 來表示 challenges 和 responses,如果要將其轉換至傳統的 `{0,1}` 的話,可以使用 `x = (1 - x) // 2` 做 encode,用 `x = 1 - 2 * x` 做 decode
```python=
import numpy as np
import pypuf.io
challenges = pypuf.io.ramdom_inputs(n=64, N=10, seed=1)
np.unique(challenges)
# array([-1, 1], dtype=int8)
challenges01 = (1 - challenges) // 2
np.unique(challenges01)
# array([0, 1], dtype=int8)
challenges11 = 1 - 2 * challenges01
np.unique(challenges11)
(challenges11 == challenges).all()
# True
```
### Storing Challenge-Response Data
`pypuf` 會儲存 challenge-response 資料,他們分別為 2 個 numpy arrays
```python=
import numpy as np
import pypuf.io
challenges = np.array([[-1, -1, -1, -1], [-1, -1, -1, 1]])
responses = np.array([1, 1])
crp = pypuf.io.ChallengeResponseSet(challenges, responses)
```
生成 challenges 和 responses
```python=
import pypuf.simulation
import pypuf.io
puf = pypuf.simulation.ArbiterPUF(n=64, seed=1)
crp = pypuf.io.ChallengeResponseSet.from_simulation(puf, N=1000, seed=2)
crp.challenges
# array([ [64 個 1 或 -1], [64 個 1 或 -1], [64 個 1 或 -1] ])
crp.responses
# array([[[-1.]], [[ 1.]], [[-1.]]])
```
將 `pypuf` CRP data 可以儲存成 `.npz`
```python+=
crp.save('crps.npz')
crp_loaded = pypuf.io.ChallengeResponseSet.load('crps.npz')
crp == crp_loaded
# True
```
取 challenges-responses pair
```python+=
# 取 第一個 pair
crp[0]
# (array([-1,...), array([[1.]]))
crp[:10]
# <10 CRPs with challenge length 64 and response length 1, each response measured 1 time(s)>
```
## Distance / Similarity
```python=
from pypuf.simulation import XORArbiterPUF
from pypuf.io import ChallengeResponseSet
from pypuf.metrics import accuracy
puf = XORArbiterPUF(n=128, k=4, noisiness=.1, seed=1)
test_set = ChallengeResponseSet.from_simulation(puf, N=1000, seed=2)
accuracy(puf, test_set)
# array([0.823])
puf = XORArbiterPUF(n=64, k=4, noisiness=.3, seed=2)
test_set = ChallengeResponseSet.from_simulation(puf, N=1000, seed=2, r=5)
accuracy(puf, test_set)
# array([0.706])
```